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 /src/server/game/Chat/Hyperlinks.cpp | |
parent | 93f3b605418b95461e5bf117739099ce9052468f (diff) |
Core/Chat: Support new 11.1.5 chat link color format and more chat link types
Diffstat (limited to 'src/server/game/Chat/Hyperlinks.cpp')
-rw-r--r-- | src/server/game/Chat/Hyperlinks.cpp | 307 |
1 files changed, 225 insertions, 82 deletions
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; |