aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Chat/Hyperlinks.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2025-04-27 17:09:17 +0200
committerShauren <shauren.trinity@gmail.com>2025-04-27 17:09:17 +0200
commit6a20d3181d1ef939bca61e7fe2ce265a974678f4 (patch)
treeb306ed5b8af2761d44911abf76693824c547db1a /src/server/game/Chat/Hyperlinks.cpp
parent93f3b605418b95461e5bf117739099ce9052468f (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.cpp307
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;