diff options
author | Shauren <shauren.trinity@gmail.com> | 2025-04-27 17:09:17 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2025-04-27 17:09:17 +0200 |
commit | 6a20d3181d1ef939bca61e7fe2ce265a974678f4 (patch) | |
tree | b306ed5b8af2761d44911abf76693824c547db1a | |
parent | 93f3b605418b95461e5bf117739099ce9052468f (diff) |
Core/Chat: Support new 11.1.5 chat link color format and more chat link types
-rw-r--r-- | sql/updates/hotfixes/master/2025_04_27_00_hotfixes.sql | 61 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/HotfixDatabase.cpp | 16 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/HotfixDatabase.h | 10 | ||||
-rw-r--r-- | src/server/game/Chat/HyperlinkTags.cpp | 18 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.cpp | 307 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.h | 39 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2LoadInfo.h | 39 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2Stores.cpp | 6 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2Stores.h | 3 | ||||
-rw-r--r-- | src/server/game/DataStores/DB2Structure.h | 24 | ||||
-rw-r--r-- | src/server/game/Miscellaneous/SharedDefines.h | 3 | ||||
-rw-r--r-- | tests/game/Hyperlinks.cpp | 10 |
12 files changed, 431 insertions, 105 deletions
diff --git a/sql/updates/hotfixes/master/2025_04_27_00_hotfixes.sql b/sql/updates/hotfixes/master/2025_04_27_00_hotfixes.sql new file mode 100644 index 00000000000..525d91f485c --- /dev/null +++ b/sql/updates/hotfixes/master/2025_04_27_00_hotfixes.sql @@ -0,0 +1,61 @@ +-- +-- Table structure for table `crafting_quality` +-- +DROP TABLE IF EXISTS `crafting_quality`; +CREATE TABLE `crafting_quality` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `QualityTier` int NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `modified_crafting_item` +-- +DROP TABLE IF EXISTS `modified_crafting_item`; +CREATE TABLE `modified_crafting_item` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `ModifiedCraftingReagentItemID` int NOT NULL DEFAULT '0', + `CraftingQualityID` int NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `perks_activity` +-- +DROP TABLE IF EXISTS `perks_activity`; +CREATE TABLE `perks_activity` ( + `ActivityName` text, + `Description` text, + `ID` int unsigned NOT NULL DEFAULT '0', + `CriteriaTreeID` int NOT NULL DEFAULT '0', + `ThresholdContributionAmount` int NOT NULL DEFAULT '0', + `Supersedes` int NOT NULL DEFAULT '0', + `Priority` int NOT NULL DEFAULT '0', + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `perks_activity_locale` +-- +DROP TABLE IF EXISTS `perks_activity_locale`; +CREATE TABLE `perks_activity_locale` ( + `ID` int unsigned NOT NULL DEFAULT '0', + `locale` varchar(4) NOT NULL, + `ActivityName_lang` text, + `Description_lang` text, + `VerifiedBuild` int NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`,`locale`,`VerifiedBuild`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci PARTITION BY LIST COLUMNS(locale) + (PARTITION deDE VALUES IN ('deDE') ENGINE = InnoDB, + PARTITION esES VALUES IN ('esES') ENGINE = InnoDB, + PARTITION esMX VALUES IN ('esMX') ENGINE = InnoDB, + PARTITION frFR VALUES IN ('frFR') ENGINE = InnoDB, + PARTITION itIT VALUES IN ('itIT') ENGINE = InnoDB, + PARTITION koKR VALUES IN ('koKR') ENGINE = InnoDB, + PARTITION ptBR VALUES IN ('ptBR') ENGINE = InnoDB, + PARTITION ruRU VALUES IN ('ruRU') ENGINE = InnoDB, + PARTITION zhCN VALUES IN ('zhCN') ENGINE = InnoDB, + PARTITION zhTW VALUES IN ('zhTW') ENGINE = InnoDB); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.cpp b/src/server/database/Database/Implementation/HotfixDatabase.cpp index 94f1ced8408..24fb9700207 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.cpp +++ b/src/server/database/Database/Implementation/HotfixDatabase.cpp @@ -488,6 +488,10 @@ void HotfixDatabaseConnection::DoPrepareStatements() " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_CORRUPTION_EFFECTS, "SELECT MAX(ID) + 1 FROM corruption_effects", CONNECTION_SYNCH); + // CraftingQuality.db2 + PrepareStatement(HOTFIX_SEL_CRAFTING_QUALITY, "SELECT ID, QualityTier FROM crafting_quality WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_CRAFTING_QUALITY, "SELECT MAX(ID) + 1 FROM crafting_quality", CONNECTION_SYNCH); + // CreatureDisplayInfo.db2 PrepareStatement(HOTFIX_SEL_CREATURE_DISPLAY_INFO, "SELECT ID, ModelID, SoundID, SizeClass, CreatureModelScale, CreatureModelAlpha, BloodID, " "ExtendedDisplayInfoID, NPCSoundID, ParticleColorID, PortraitCreatureDisplayInfoID, PortraitTextureFileDataID, ObjectEffectPackageID, " @@ -1186,6 +1190,11 @@ void HotfixDatabaseConnection::DoPrepareStatements() PrepareStatement(HOTFIX_SEL_MAW_POWER, "SELECT ID, SpellID, MawPowerRarityID FROM maw_power WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_MAW_POWER, "SELECT MAX(ID) + 1 FROM maw_power", CONNECTION_SYNCH); + // ModifiedCraftingItem.db2 + PrepareStatement(HOTFIX_SEL_MODIFIED_CRAFTING_ITEM, "SELECT ID, ModifiedCraftingReagentItemID, CraftingQualityID FROM modified_crafting_item" + " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_MODIFIED_CRAFTING_ITEM, "SELECT MAX(ID) + 1 FROM modified_crafting_item", CONNECTION_SYNCH); + // ModifierTree.db2 PrepareStatement(HOTFIX_SEL_MODIFIER_TREE, "SELECT ID, Parent, Operator, Amount, Type, Asset, SecondaryAsset, TertiaryAsset FROM modifier_tree" " WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); @@ -1272,6 +1281,13 @@ void HotfixDatabaseConnection::DoPrepareStatements() PrepareStatement(HOTFIX_SEL_PATH_PROPERTY, "SELECT ID, PathID, PropertyIndex, Value FROM path_property WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_PATH_PROPERTY, "SELECT MAX(ID) + 1 FROM path_property", CONNECTION_SYNCH); + // PerksActivity.db2 + PrepareStatement(HOTFIX_SEL_PERKS_ACTIVITY, "SELECT ActivityName, Description, ID, CriteriaTreeID, ThresholdContributionAmount, Supersedes, " + "Priority FROM perks_activity WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); + PREPARE_MAX_ID_STMT(HOTFIX_SEL_PERKS_ACTIVITY, "SELECT MAX(ID) + 1 FROM perks_activity", CONNECTION_SYNCH); + PREPARE_LOCALE_STMT(HOTFIX_SEL_PERKS_ACTIVITY, "SELECT ID, ActivityName_lang, Description_lang FROM perks_activity_locale" + " WHERE (`VerifiedBuild` > 0) = ? AND locale = ?", CONNECTION_SYNCH); + // Phase.db2 PrepareStatement(HOTFIX_SEL_PHASE, "SELECT ID, Flags FROM phase WHERE (`VerifiedBuild` > 0) = ?", CONNECTION_SYNCH); PREPARE_MAX_ID_STMT(HOTFIX_SEL_PHASE, "SELECT MAX(ID) + 1 FROM phase", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/HotfixDatabase.h b/src/server/database/Database/Implementation/HotfixDatabase.h index f713884f6cb..989e8512a41 100644 --- a/src/server/database/Database/Implementation/HotfixDatabase.h +++ b/src/server/database/Database/Implementation/HotfixDatabase.h @@ -287,6 +287,9 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_CORRUPTION_EFFECTS, HOTFIX_SEL_CORRUPTION_EFFECTS_MAX_ID, + HOTFIX_SEL_CRAFTING_QUALITY, + HOTFIX_SEL_CRAFTING_QUALITY_MAX_ID, + HOTFIX_SEL_CREATURE_DISPLAY_INFO, HOTFIX_SEL_CREATURE_DISPLAY_INFO_MAX_ID, @@ -681,6 +684,9 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_MAW_POWER, HOTFIX_SEL_MAW_POWER_MAX_ID, + HOTFIX_SEL_MODIFIED_CRAFTING_ITEM, + HOTFIX_SEL_MODIFIED_CRAFTING_ITEM_MAX_ID, + HOTFIX_SEL_MODIFIER_TREE, HOTFIX_SEL_MODIFIER_TREE_MAX_ID, @@ -736,6 +742,10 @@ enum HotfixDatabaseStatements : uint32 HOTFIX_SEL_PATH_PROPERTY, HOTFIX_SEL_PATH_PROPERTY_MAX_ID, + HOTFIX_SEL_PERKS_ACTIVITY, + HOTFIX_SEL_PERKS_ACTIVITY_MAX_ID, + HOTFIX_SEL_PERKS_ACTIVITY_LOCALE, + HOTFIX_SEL_PHASE, HOTFIX_SEL_PHASE_MAX_ID, diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp index 64ce77b3f2d..6d7c41fa854 100644 --- a/src/server/game/Chat/HyperlinkTags.cpp +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -163,6 +163,15 @@ bool Trinity::Hyperlinks::LinkTags::conduit::StoreTo(SoulbindConduitRankEntry co return !!(val = sDB2Manager.GetSoulbindConduitRank(soulbindConduitId, rank)); } +bool Trinity::Hyperlinks::LinkTags::curio::StoreTo(SpellInfo const*& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 spellId; + if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) + return false; + return !!(val = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)); +} + bool Trinity::Hyperlinks::LinkTags::currency::StoreTo(CurrencyLinkData& val, std::string_view text) { HyperlinkDataTokenizer t(text); @@ -426,6 +435,15 @@ bool Trinity::Hyperlinks::LinkTags::mount::StoreTo(MountLinkData& val, std::stri return t.TryConsumeTo(val.Customizations) && t.IsEmpty(); } +bool Trinity::Hyperlinks::LinkTags::perksactivity::StoreTo(PerksActivityEntry const*& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 perksActivityId; + if (!t.TryConsumeTo(perksActivityId)) + return false; + return !!(val = sPerksActivityStore.LookupEntry(perksActivityId)) && t.IsEmpty(); +} + bool Trinity::Hyperlinks::LinkTags::pvptal::StoreTo(PvpTalentEntry const*& val, std::string_view text) { HyperlinkDataTokenizer t(text); diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp index 2d991fd05bf..9a77dfc23b9 100644 --- a/src/server/game/Chat/Hyperlinks.cpp +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -30,33 +30,50 @@ using namespace Trinity::Hyperlinks; -inline uint8 toHex(char c) { return (c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00; } +bool HyperlinkColor::operator==(ItemQualities q) const +{ + return data.starts_with("IQ") && q < MAX_ITEM_QUALITY && Trinity::StringTo<uint32>(data.substr(2)) == uint32(q); +} + // Validates a single hyperlink HyperlinkInfo Trinity::Hyperlinks::ParseSingleHyperlink(std::string_view str) { - uint32 color = 0; + using namespace std::string_view_literals; + + std::string_view color; std::string_view tag; std::string_view data; std::string_view text; //color tag - if (str.substr(0, 2) != "|c") + if (!str.starts_with("|c"sv)) return {}; str.remove_prefix(2); if (str.length() < 8) return {}; - for (uint8 i = 0; i < 8; ++i) + if (str[0] == 'n') { - if (uint8 hex = toHex(str[i])) - color = (color << 4) | (hex & 0xf); + // numeric color id + str.remove_prefix(1); + + if (size_t endOfColor = str.find(":"sv); endOfColor != std::string_view::npos) + { + color = str.substr(0, endOfColor); + str.remove_prefix(endOfColor + 1); + } else return {}; } - str.remove_prefix(8); + else + { + // hex color + color = str.substr(0, 8); + str.remove_prefix(8); + } - if (str.substr(0, 2) != "|H") + if (!str.starts_with("|H"sv)) return {}; str.remove_prefix(2); @@ -77,24 +94,41 @@ HyperlinkInfo Trinity::Hyperlinks::ParseSingleHyperlink(std::string_view str) } // ok, next should be link data end tag... - if (str.substr(0, 1) != "h") + if (!str.starts_with('h')) return {}; str.remove_prefix(1); - // skip to final | - if (size_t end = str.find('|'); end != std::string_view::npos) + + // extract text, must be between [] + if (str[0] != '[') + return {}; + + size_t openBrackets = 0; + for (size_t nameItr = 0; nameItr < str.length(); ++nameItr) { - // check end tag - if (str.substr(end, 4) != "|h|r") - return {}; - // check text brackets - if ((str[0] != '[') || (str[end - 1] != ']')) - return {}; - text = str.substr(1, end - 2); - // tail - str = str.substr(end + 4); + switch (str[nameItr]) + { + case '[': + ++openBrackets; + break; + case ']': + --openBrackets; + break; + default: + break; + } + + if (!openBrackets) + { + text = str.substr(1, nameItr - 1); + str.remove_prefix(nameItr + 1); + break; + } } - else + + // check end tag + if (!str.starts_with("|h|r"sv)) return {}; + str.remove_prefix(4); // ok, valid hyperlink, return info return { str, color, tag, data, text }; @@ -198,7 +232,14 @@ struct LinkValidator<LinkTags::azessence> static bool IsColorValid(AzeriteEssenceLinkData const& data, HyperlinkColor c) { - return c == ItemQualityColors[data.Rank + 1]; + ItemQualities quality = ItemQualities(data.Rank + 1); + if (c == ItemQualityColors[quality]) + return true; + + if (c == ItemQualities(quality)) + return true; + + return false; } }; @@ -250,6 +291,23 @@ struct LinkValidator<LinkTags::conduit> }; template <> +struct LinkValidator<LinkTags::curio> +{ + static bool IsTextValid(SpellInfo const* info, std::string_view text) + { + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); + } + + static bool IsColorValid(SpellInfo const*, HyperlinkColor c) + { + for (uint32 i = 0; i < MAX_ITEM_QUALITY; ++i) + if (c == ItemQualities(i)) + return true; + return false; + } +}; + +template <> struct LinkValidator<LinkTags::currency> { static bool IsTextValid(CurrencyLinkData const& data, std::string_view text) @@ -263,7 +321,14 @@ struct LinkValidator<LinkTags::currency> static bool IsColorValid(CurrencyLinkData const& data, HyperlinkColor c) { - return c == ItemQualityColors[(data.Container ? data.Container->ContainerQuality : data.Currency->Quality)]; + ItemQualities quality = ItemQualities(data.Container ? data.Container->ContainerQuality : data.Currency->Quality); + if (c == ItemQualityColors[quality]) + return true; + + if (c == quality) + return true; + + return false; } }; @@ -317,7 +382,13 @@ struct LinkValidator<LinkTags::garrfollower> static bool IsColorValid(GarrisonFollowerLinkData const& data, HyperlinkColor c) { - return c == ItemQualityColors[data.Quality]; + if (c == ItemQualityColors[data.Quality]) + return true; + + if (c == ItemQualities(data.Quality)) + return true; + + return false; } }; @@ -375,43 +446,90 @@ struct LinkValidator<LinkTags::instancelock> template <> struct LinkValidator<LinkTags::item> { + static constexpr std::array<std::string_view, 6> CRAFTING_QUALITY_ICON = + { + "", + " |A:Professions-ChatIcon-Quality-Tier1:17:15::1|a", + " |A:Professions-ChatIcon-Quality-Tier2:17:23::1|a", + " |A:Professions-ChatIcon-Quality-Tier3:17:18::1|a", + " |A:Professions-ChatIcon-Quality-Tier4:17:17::1|a", + " |A:Professions-ChatIcon-Quality-Tier5:17:17::1|a", + }; + static bool IsTextValid(ItemLinkData const& data, std::string_view text) { LocalizedString const* suffixStrings = nullptr; if (!data.Item->HasFlag(ITEM_FLAG3_HIDE_NAME_SUFFIX) && data.Suffix) suffixStrings = &data.Suffix->Description; - return IsTextValid(data.Item, suffixStrings, text); + Optional<int32> craftingQualityId; + auto craftingQualityIdItr = std::ranges::find(data.Modifiers, ITEM_MODIFIER_CRAFTING_QUALITY_ID, &ItemLinkData::Modifier::Type); + if (craftingQualityIdItr != data.Modifiers.end()) + craftingQualityId = craftingQualityIdItr->Value; + + return IsTextValid(data.Item, suffixStrings, craftingQualityId, text); } - static bool IsTextValid(ItemTemplate const* itemTemplate, LocalizedString const* suffixStrings, std::string_view text) + static bool IsTextValid(ItemTemplate const* itemTemplate, LocalizedString const* suffixStrings, Optional<int32> craftingQualityId, std::string_view text) { + // default icon + if (!craftingQualityId) + if (ModifiedCraftingItemEntry const* modifiedCraftingItemEntry = sModifiedCraftingItemStore.LookupEntry(itemTemplate->GetId())) + craftingQualityId = modifiedCraftingItemEntry->CraftingQualityID; + + std::string_view craftingQualityIcon = CRAFTING_QUALITY_ICON[0]; + if (craftingQualityId) + if (CraftingQualityEntry const* craftingQualityEntry = sCraftingQualityStore.LookupEntry(*craftingQualityId)) + if (craftingQualityEntry->QualityTier < std::ranges::ssize(CRAFTING_QUALITY_ICON)) + craftingQualityIcon = CRAFTING_QUALITY_ICON[craftingQualityEntry->QualityTier]; + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - { - std::string_view name = itemTemplate->GetName(i); - if (name.empty()) - continue; - if (suffixStrings) - { - std::string_view suffix = (*suffixStrings)[i]; - if ( - (!suffix.empty()) && - (text.length() == (name.length() + 1 + suffix.length())) && - (text.substr(0, name.length()) == name) && - (text[name.length()] == ' ') && - (text.substr(name.length() + 1) == suffix) - ) - return true; - } - else if (text == name) + if (IsTextValid(text, itemTemplate->GetName(i), suffixStrings ? Optional<std::string_view>((*suffixStrings)[i]) : std::nullopt, craftingQualityIcon)) return true; - } + return false; } + static bool IsTextValid(std::string_view toValidate, std::string_view name, Optional<std::string_view> suffix, std::string_view craftingQualityIcon) + { + if (name.empty()) + return false; + + if (!toValidate.starts_with(name)) + return false; + + toValidate.remove_prefix(name.length()); + if (suffix) + { + if (toValidate.length() < suffix->length() + 1) + return false; + + if (toValidate[0] != ' ') + return false; + + toValidate.remove_prefix(1); + if (!toValidate.starts_with(*suffix)) + return false; + + toValidate.remove_prefix(suffix->length()); + } + + if (!toValidate.starts_with(craftingQualityIcon)) + return false; + + toValidate.remove_prefix(craftingQualityIcon.length()); + return toValidate.empty(); + } + static bool IsColorValid(ItemLinkData const& data, HyperlinkColor c) { - return c == ItemQualityColors[data.Quality]; + if (c == ItemQualityColors[data.Quality]) + return true; + + if (c == ItemQualities(data.Quality)) + return true; + + return false; } }; @@ -456,45 +574,14 @@ struct LinkValidator<LinkTags::keystone> static bool IsColorValid(KeystoneLinkData const&, HyperlinkColor c) { - return c == ItemQualityColors[ITEM_QUALITY_EPIC]; - } -}; - -template <> -struct LinkValidator<LinkTags::quest> -{ - static bool IsTextValid(QuestLinkData const& data, std::string_view text) - { - if (text.empty()) - return false; - - if (text == data.Quest->GetLogTitle()) + if (c == ItemQualityColors[ITEM_QUALITY_EPIC]) return true; - QuestTemplateLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId()); - if (!locale) - return false; - - for (uint8 i = 0; i < TOTAL_LOCALES; ++i) - { - if (i == DEFAULT_LOCALE) - continue; - - std::string_view name = ObjectMgr::GetLocaleString(locale->LogTitle, LocaleConstant(i)); - if (!name.empty() && (text == name)) - return true; - } + if (c == ITEM_QUALITY_EPIC) + return true; return false; } - - static bool IsColorValid(QuestLinkData const&, HyperlinkColor c) - { - for (uint8 i = 0; i < MAX_QUEST_DIFFICULTY; ++i) - if (c == QuestDifficultyColors[i]) - return true; - return false; - } }; template <> @@ -542,6 +629,23 @@ struct LinkValidator<LinkTags::outfit> }; template <> +struct LinkValidator<LinkTags::perksactivity> +{ + static bool IsTextValid(PerksActivityEntry const* perksActivity, std::string_view text) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (perksActivity->ActivityName[i] == text) + return true; + return false; + } + + static bool IsColorValid(PerksActivityEntry const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_NEUTRAL; + } +}; + +template <> struct LinkValidator<LinkTags::pvptal> { static bool IsTextValid(PvpTalentEntry const* pvpTalent, std::string_view text) @@ -558,6 +662,43 @@ struct LinkValidator<LinkTags::pvptal> }; template <> +struct LinkValidator<LinkTags::quest> +{ + static bool IsTextValid(QuestLinkData const& data, std::string_view text) + { + if (text.empty()) + return false; + + if (text == data.Quest->GetLogTitle()) + return true; + + QuestTemplateLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId()); + if (!locale) + return false; + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + if (i == DEFAULT_LOCALE) + continue; + + std::string_view name = ObjectMgr::GetLocaleString(locale->LogTitle, LocaleConstant(i)); + if (!name.empty() && (text == name)) + return true; + } + + return false; + } + + static bool IsColorValid(QuestLinkData const&, HyperlinkColor c) + { + for (uint8 i = 0; i < MAX_QUEST_DIFFICULTY; ++i) + if (c == QuestDifficultyColors[i]) + return true; + return false; + } +}; + +template <> struct LinkValidator<LinkTags::talent> { static bool IsTextValid(TalentEntry const* talent, std::string_view text) @@ -593,7 +734,7 @@ struct LinkValidator<LinkTags::transmogappearance> static bool IsTextValid(ItemModifiedAppearanceEntry const* enchantment, std::string_view text) { if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(enchantment->ItemID)) - return LinkValidator<LinkTags::item>::IsTextValid(itemTemplate, nullptr, text); + return LinkValidator<LinkTags::item>::IsTextValid(itemTemplate, nullptr, {}, text); return false; } @@ -701,6 +842,7 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info) TryValidateAs(conduit); TryValidateAs(creature); TryValidateAs(creature_entry); + TryValidateAs(curio); TryValidateAs(currency); TryValidateAs(dungeonScore); TryValidateAs(enchant); @@ -718,6 +860,7 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info) TryValidateAs(mawpower); TryValidateAs(mount); TryValidateAs(outfit); + TryValidateAs(perksactivity); TryValidateAs(player); TryValidateAs(pvptal); TryValidateAs(quest); @@ -739,7 +882,7 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info) // Validates all hyperlinks and control sequences contained in str bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str) { - // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r + // Step 1: Disallow all control sequences except ||, |H, |h, |c, |A, |a and |r { std::string_view::size_type pos = 0; while ((pos = str.find('|', pos)) != std::string::npos) @@ -748,7 +891,7 @@ bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str) if (pos == str.length()) return false; char next = str[pos]; - if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|') + if (next == 'H' || next == 'h' || next == 'c' || next == 'A' || next == 'a' || next == 'r' || next == '|') ++pos; else return false; diff --git a/src/server/game/Chat/Hyperlinks.h b/src/server/game/Chat/Hyperlinks.h index 8adb96187f9..01412544e09 100644 --- a/src/server/game/Chat/Hyperlinks.h +++ b/src/server/game/Chat/Hyperlinks.h @@ -38,11 +38,13 @@ struct GarrMissionEntry; struct GlyphPropertiesEntry; struct ItemModifiedAppearanceEntry; struct ItemNameDescriptionEntry; +enum ItemQualities : uint8; struct ItemTemplate; struct LocalizedString; struct MapEntry; struct MapChallengeModeEntry; struct MawPowerEntry; +struct PerksActivityEntry; struct PvpTalentEntry; class Quest; struct SkillLineEntry; @@ -375,6 +377,13 @@ namespace Trinity::Hyperlinks static bool StoreTo(SoulbindConduitRankEntry const*& val, std::string_view text); }; + struct TC_GAME_API curio + { + using value_type = SpellInfo const*; + static constexpr std::string_view tag() { return "curio"; } + static bool StoreTo(SpellInfo const*& val, std::string_view text); + }; + struct TC_GAME_API currency { using value_type = CurrencyLinkData const&; @@ -459,6 +468,13 @@ namespace Trinity::Hyperlinks static bool StoreTo(MountLinkData& val, std::string_view text); }; + struct TC_GAME_API perksactivity + { + using value_type = PerksActivityEntry const*; + static constexpr std::string_view tag() { return "perksactivity"; } + static bool StoreTo(PerksActivityEntry const*& val, std::string_view text); + }; + struct TC_GAME_API pvptal { using value_type = PvpTalentEntry const*; @@ -532,29 +548,24 @@ namespace Trinity::Hyperlinks struct HyperlinkColor { - HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {} - uint8 const r, g, b, a; + HyperlinkColor() = default; + HyperlinkColor(std::string_view c) : data(c) {} + std::string_view data; bool operator==(uint32 c) const { - if ((c & 0xff) ^ b) - return false; - if (((c >>= 8) & 0xff) ^ g) - return false; - if (((c >>= 8) & 0xff) ^ r) - return false; - if ((c >>= 8) ^ a) - return false; - return true; + return Trinity::StringTo<uint32>(data, 16) == c; } + + bool operator==(ItemQualities q) const; }; struct HyperlinkInfo { - HyperlinkInfo() : ok(false), color(0) {} - HyperlinkInfo(std::string_view t, uint32 c, std::string_view ta, std::string_view d, std::string_view te) : + HyperlinkInfo() : ok(false) {} + HyperlinkInfo(std::string_view t, std::string_view c, std::string_view ta, std::string_view d, std::string_view te) : ok(true), tail(t), color(c), tag(ta), data(d), text(te) {} - explicit operator bool() { return ok; } + explicit operator bool() const { return ok; } bool const ok; std::string_view const tail; HyperlinkColor const color; diff --git a/src/server/game/DataStores/DB2LoadInfo.h b/src/server/game/DataStores/DB2LoadInfo.h index a9d7548b077..e903b611777 100644 --- a/src/server/game/DataStores/DB2LoadInfo.h +++ b/src/server/game/DataStores/DB2LoadInfo.h @@ -1387,6 +1387,17 @@ struct CorruptionEffectsLoadInfo static constexpr DB2LoadInfo Instance{ Fields, 5, &CorruptionEffectsMeta::Instance, HOTFIX_SEL_CORRUPTION_EFFECTS }; }; +struct CraftingQualityLoadInfo +{ + static constexpr DB2FieldMeta Fields[2] = + { + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "QualityTier" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 2, &CraftingQualityMeta::Instance, HOTFIX_SEL_CRAFTING_QUALITY }; +}; + struct CreatureDisplayInfoLoadInfo { static constexpr DB2FieldMeta Fields[31] = @@ -3859,6 +3870,18 @@ struct MawPowerLoadInfo static constexpr DB2LoadInfo Instance{ Fields, 3, &MawPowerMeta::Instance, HOTFIX_SEL_MAW_POWER }; }; +struct ModifiedCraftingItemLoadInfo +{ + static constexpr DB2FieldMeta Fields[3] = + { + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "ModifiedCraftingReagentItemID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "CraftingQualityID" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 3, &ModifiedCraftingItemMeta::Instance, HOTFIX_SEL_MODIFIED_CRAFTING_ITEM }; +}; + struct ModifierTreeLoadInfo { static constexpr DB2FieldMeta Fields[8] = @@ -4128,6 +4151,22 @@ struct PathPropertyLoadInfo static constexpr DB2LoadInfo Instance{ Fields, 4, &PathPropertyMeta::Instance, HOTFIX_SEL_PATH_PROPERTY }; }; +struct PerksActivityLoadInfo +{ + static constexpr DB2FieldMeta Fields[7] = + { + { .IsSigned = false, .Type = FT_STRING, .Name = "ActivityName" }, + { .IsSigned = false, .Type = FT_STRING, .Name = "Description" }, + { .IsSigned = false, .Type = FT_INT, .Name = "ID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "CriteriaTreeID" }, + { .IsSigned = true, .Type = FT_INT, .Name = "ThresholdContributionAmount" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Supersedes" }, + { .IsSigned = true, .Type = FT_INT, .Name = "Priority" }, + }; + + static constexpr DB2LoadInfo Instance{ Fields, 7, &PerksActivityMeta::Instance, HOTFIX_SEL_PERKS_ACTIVITY }; +}; + struct PhaseLoadInfo { static constexpr DB2FieldMeta Fields[2] = diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp index 441123fe052..7195afe57c7 100644 --- a/src/server/game/DataStores/DB2Stores.cpp +++ b/src/server/game/DataStores/DB2Stores.cpp @@ -115,6 +115,7 @@ DB2Storage<ContentTuningXExpectedEntry> sContentTuningXExpectedStore("Co DB2Storage<ContentTuningXLabelEntry> sContentTuningXLabelStore("ContentTuningXLabel.db2", &ContentTuningXLabelLoadInfo::Instance); DB2Storage<ConversationLineEntry> sConversationLineStore("ConversationLine.db2", &ConversationLineLoadInfo::Instance); DB2Storage<CorruptionEffectsEntry> sCorruptionEffectsStore("CorruptionEffects.db2", &CorruptionEffectsLoadInfo::Instance); +DB2Storage<CraftingQualityEntry> sCraftingQualityStore("CraftingQuality.db2", &CraftingQualityLoadInfo::Instance); DB2Storage<CreatureDisplayInfoEntry> sCreatureDisplayInfoStore("CreatureDisplayInfo.db2", &CreatureDisplayInfoLoadInfo::Instance); DB2Storage<CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore("CreatureDisplayInfoExtra.db2", &CreatureDisplayInfoExtraLoadInfo::Instance); DB2Storage<CreatureFamilyEntry> sCreatureFamilyStore("CreatureFamily.db2", &CreatureFamilyLoadInfo::Instance); @@ -234,6 +235,7 @@ DB2Storage<MapChallengeModeEntry> sMapChallengeModeStore("MapChall DB2Storage<MapDifficultyEntry> sMapDifficultyStore("MapDifficulty.db2", &MapDifficultyLoadInfo::Instance); DB2Storage<MapDifficultyXConditionEntry> sMapDifficultyXConditionStore("MapDifficultyXCondition.db2", &MapDifficultyXConditionLoadInfo::Instance); DB2Storage<MawPowerEntry> sMawPowerStore("MawPower.db2", &MawPowerLoadInfo::Instance); +DB2Storage<ModifiedCraftingItemEntry> sModifiedCraftingItemStore("ModifiedCraftingItem.db2", &ModifiedCraftingItemLoadInfo::Instance); DB2Storage<ModifierTreeEntry> sModifierTreeStore("ModifierTree.db2", &ModifierTreeLoadInfo::Instance); DB2Storage<MountCapabilityEntry> sMountCapabilityStore("MountCapability.db2", &MountCapabilityLoadInfo::Instance); DB2Storage<MountEntry> sMountStore("Mount.db2", &MountLoadInfo::Instance); @@ -252,6 +254,7 @@ DB2Storage<ParagonReputationEntry> sParagonReputationStore("Paragon DB2Storage<PathEntry> sPathStore("Path.db2", &PathLoadInfo::Instance); DB2Storage<PathNodeEntry> sPathNodeStore("PathNode.db2", &PathNodeLoadInfo::Instance); DB2Storage<PathPropertyEntry> sPathPropertyStore("PathProperty.db2", &PathPropertyLoadInfo::Instance); +DB2Storage<PerksActivityEntry> sPerksActivityStore("PerksActivity.db2", &PerksActivityLoadInfo::Instance); DB2Storage<PhaseEntry> sPhaseStore("Phase.db2", &PhaseLoadInfo::Instance); DB2Storage<PhaseXPhaseGroupEntry> sPhaseXPhaseGroupStore("PhaseXPhaseGroup.db2", &PhaseXPhaseGroupLoadInfo::Instance); DB2Storage<PlayerConditionEntry> sPlayerConditionStore("PlayerCondition.db2", &PlayerConditionLoadInfo::Instance); @@ -734,6 +737,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sContentTuningXLabelStore); LOAD_DB2(sConversationLineStore); LOAD_DB2(sCorruptionEffectsStore); + LOAD_DB2(sCraftingQualityStore); LOAD_DB2(sCreatureDisplayInfoStore); LOAD_DB2(sCreatureDisplayInfoExtraStore); LOAD_DB2(sCreatureFamilyStore); @@ -853,6 +857,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sMapDifficultyStore); LOAD_DB2(sMapDifficultyXConditionStore); LOAD_DB2(sMawPowerStore); + LOAD_DB2(sModifiedCraftingItemStore); LOAD_DB2(sModifierTreeStore); LOAD_DB2(sMountCapabilityStore); LOAD_DB2(sMountStore); @@ -871,6 +876,7 @@ uint32 DB2Manager::LoadStores(std::string const& dataPath, LocaleConstant defaul LOAD_DB2(sPathStore); LOAD_DB2(sPathNodeStore); LOAD_DB2(sPathPropertyStore); + LOAD_DB2(sPerksActivityStore); LOAD_DB2(sPhaseStore); LOAD_DB2(sPhaseXPhaseGroupStore); LOAD_DB2(sPlayerConditionStore); diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h index 17bdbc93c54..80c077822e1 100644 --- a/src/server/game/DataStores/DB2Stores.h +++ b/src/server/game/DataStores/DB2Stores.h @@ -85,6 +85,7 @@ TC_GAME_API extern DB2Storage<ConditionalChrModelEntry> sConditional TC_GAME_API extern DB2Storage<ContentTuningEntry> sContentTuningStore; TC_GAME_API extern DB2Storage<ConversationLineEntry> sConversationLineStore; TC_GAME_API extern DB2Storage<CorruptionEffectsEntry> sCorruptionEffectsStore; +TC_GAME_API extern DB2Storage<CraftingQualityEntry> sCraftingQualityStore; TC_GAME_API extern DB2Storage<CreatureDisplayInfoEntry> sCreatureDisplayInfoStore; TC_GAME_API extern DB2Storage<CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore; TC_GAME_API extern DB2Storage<CreatureFamilyEntry> sCreatureFamilyStore; @@ -186,6 +187,7 @@ TC_GAME_API extern DB2Storage<MapEntry> sMapStore; TC_GAME_API extern DB2Storage<MapChallengeModeEntry> sMapChallengeModeStore; TC_GAME_API extern DB2Storage<MapDifficultyEntry> sMapDifficultyStore; TC_GAME_API extern DB2Storage<MawPowerEntry> sMawPowerStore; +TC_GAME_API extern DB2Storage<ModifiedCraftingItemEntry> sModifiedCraftingItemStore; TC_GAME_API extern DB2Storage<ModifierTreeEntry> sModifierTreeStore; TC_GAME_API extern DB2Storage<MountCapabilityEntry> sMountCapabilityStore; TC_GAME_API extern DB2Storage<MountEntry> sMountStore; @@ -194,6 +196,7 @@ TC_GAME_API extern DB2Storage<MovieEntry> sMovieStore; TC_GAME_API extern DB2Storage<MythicPlusSeasonEntry> sMythicPlusSeasonStore; TC_GAME_API extern DB2Storage<OverrideSpellDataEntry> sOverrideSpellDataStore; TC_GAME_API extern DB2Storage<ParagonReputationEntry> sParagonReputationStore; +TC_GAME_API extern DB2Storage<PerksActivityEntry> sPerksActivityStore; TC_GAME_API extern DB2Storage<PhaseEntry> sPhaseStore; TC_GAME_API extern DB2Storage<PlayerConditionEntry> sPlayerConditionStore; TC_GAME_API extern DB2Storage<PowerDisplayEntry> sPowerDisplayStore; diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h index eff70fb4b9d..46e8346ca9f 100644 --- a/src/server/game/DataStores/DB2Structure.h +++ b/src/server/game/DataStores/DB2Structure.h @@ -1009,6 +1009,12 @@ struct CorruptionEffectsEntry int32 Flags; }; +struct CraftingQualityEntry +{ + uint32 ID; + int32 QualityTier; +}; + //struct CreatureDifficultyEntry //{ // uint32 ID; @@ -2933,6 +2939,13 @@ struct MawPowerEntry int32 MawPowerRarityID; }; +struct ModifiedCraftingItemEntry +{ + uint32 ID; + int32 ModifiedCraftingReagentItemID; + int32 CraftingQualityID; +}; + struct ModifierTreeEntry { uint32 ID; @@ -3109,6 +3122,17 @@ struct PathPropertyEntry PathPropertyIndex GetPropertyIndex() const { return static_cast<PathPropertyIndex>(PropertyIndex); } }; +struct PerksActivityEntry +{ + LocalizedString ActivityName; + LocalizedString Description; + uint32 ID; + int32 CriteriaTreeID; + int32 ThresholdContributionAmount; + int32 Supersedes; + int32 Priority; +}; + struct PhaseEntry { uint32 ID; diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index affd195bea3..639ffbd665b 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -373,7 +373,7 @@ inline SpellSchools GetFirstSchoolInMask(SpellSchoolMask mask) return SPELL_SCHOOL_NORMAL; } -enum ItemQualities +enum ItemQualities : uint8 { ITEM_QUALITY_POOR = 0, // GREY ITEM_QUALITY_NORMAL = 1, // WHITE @@ -6252,6 +6252,7 @@ enum ChatLinkColors : uint32 CHAT_LINK_COLOR_INSTANCE_LOCK = 0xffff8000, CHAT_LINK_COLOR_JOURNAL = 0xff66bbff, CHAT_LINK_COLOR_TRANSMOG = 0xffff80ff, + CHAT_LINK_COLOR_NEUTRAL = 0xffff00ff, }; enum class ChatMessageResult : uint32 diff --git a/tests/game/Hyperlinks.cpp b/tests/game/Hyperlinks.cpp index a4f01a37992..9fd8bff0009 100644 --- a/tests/game/Hyperlinks.cpp +++ b/tests/game/Hyperlinks.cpp @@ -31,10 +31,7 @@ TEST_CASE("Basic link structure", "[Hyperlinks]") HyperlinkInfo info = ParseSingleHyperlink("|cabcdef01|HTag|h[text]|h|r"); REQUIRE(info.ok); REQUIRE(info.color == 0xabcdef01); - REQUIRE(info.color.a == 0xab); - REQUIRE(info.color.r == 0xcd); - REQUIRE(info.color.g == 0xef); - REQUIRE(info.color.b == 0x01); + REQUIRE(info.color.data == "abcdef01"sv); REQUIRE(info.tag == "Tag"); REQUIRE(info.data == ""); REQUIRE(info.text == "text"); @@ -45,10 +42,7 @@ TEST_CASE("Basic link structure", "[Hyperlinks]") HyperlinkInfo info = ParseSingleHyperlink("|c12345678|Htag:data1:data2:data3:data4:data5|h[Text]|h|rtail"); REQUIRE(info.ok); REQUIRE(info.color == 0x12345678); - REQUIRE(info.color.a == 0x12); - REQUIRE(info.color.r == 0x34); - REQUIRE(info.color.g == 0x56); - REQUIRE(info.color.b == 0x78); + REQUIRE(info.color.data == "12345678"sv); REQUIRE(info.tag == "tag"); REQUIRE(info.data == "data1:data2:data3:data4:data5"); REQUIRE(info.text == "Text"); |