Core/Chat: Support new 11.1.5 chat link color format and more chat link types

This commit is contained in:
Shauren
2025-04-27 17:09:17 +02:00
parent 93f3b60541
commit 6a20d3181d
12 changed files with 433 additions and 107 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,25 +94,42 @@ 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)
{
// 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);
}
else
// extract text, must be between []
if (str[0] != '[')
return {};
size_t openBrackets = 0;
for (size_t nameItr = 0; nameItr < str.length(); ++nameItr)
{
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;
}
}
// 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;
}
};
@@ -249,6 +290,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>
{
@@ -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,43 +574,12 @@ 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;
if (c == ITEM_QUALITY_EPIC)
return true;
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;
}
};
@@ -541,6 +628,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>
{
@@ -557,6 +661,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>
{
@@ -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;

View File

@@ -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;

View File

@@ -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] =

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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");