aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/updates/hotfixes/master/2025_04_27_00_hotfixes.sql61
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.cpp16
-rw-r--r--src/server/database/Database/Implementation/HotfixDatabase.h10
-rw-r--r--src/server/game/Chat/HyperlinkTags.cpp18
-rw-r--r--src/server/game/Chat/Hyperlinks.cpp307
-rw-r--r--src/server/game/Chat/Hyperlinks.h39
-rw-r--r--src/server/game/DataStores/DB2LoadInfo.h39
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp6
-rw-r--r--src/server/game/DataStores/DB2Stores.h3
-rw-r--r--src/server/game/DataStores/DB2Structure.h24
-rw-r--r--src/server/game/Miscellaneous/SharedDefines.h3
-rw-r--r--tests/game/Hyperlinks.cpp10
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");