diff options
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp | 14 | ||||
-rw-r--r-- | src/server/game/Chat/ChatCommands/ChatCommandArgs.h | 7 | ||||
-rw-r--r-- | src/server/game/Chat/HyperlinkTags.cpp | 250 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.cpp | 435 | ||||
-rw-r--r-- | src/server/game/Chat/Hyperlinks.h | 231 | ||||
-rw-r--r-- | src/server/game/Miscellaneous/SharedDefines.h | 5 |
6 files changed, 903 insertions, 39 deletions
diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp index 20893d9df90..340a13bd15d 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp @@ -36,6 +36,20 @@ char const* Trinity::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume( return args; } +struct CurrencyTypesVisitor +{ + using value_type = CurrencyTypesEntry const*; + value_type operator()(Hyperlink<currency> currency) const { return currency->Currency; } + value_type operator()(uint32 currencyId) const { return sCurrencyTypesStore.LookupEntry(currencyId); } +}; +char const* Trinity::ChatCommands::ArgInfo<CurrencyTypesEntry const*>::TryConsume(CurrencyTypesEntry const*& data, char const* args) +{ + Variant <Hyperlink<currency>, uint32> val; + if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args))) + data = boost::apply_visitor(CurrencyTypesVisitor(), val); + return args; +} + struct GameTeleVisitor { using value_type = GameTele const*; diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h index 110103ebb83..db5deddb1dc 100644 --- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h @@ -116,6 +116,13 @@ struct TC_GAME_API ArgInfo<AchievementEntry const*> static char const* TryConsume(AchievementEntry const*&, char const*); }; +// CurrencyTypesEntry* from numeric id or link +template <> +struct TC_GAME_API ArgInfo<CurrencyTypesEntry const*> +{ + static char const* TryConsume(CurrencyTypesEntry const*&, char const*); +}; + // GameTele* from string name or link template <> struct TC_GAME_API ArgInfo<GameTele const*> diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp index 194ad874228..dafe9cc8c73 100644 --- a/src/server/game/Chat/HyperlinkTags.cpp +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -74,13 +74,130 @@ bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& va t.TryConsumeTo(val.Criteria[1]) && t.TryConsumeTo(val.Criteria[2]) && t.TryConsumeTo(val.Criteria[3]) && t.IsEmpty(); } +bool Trinity::Hyperlinks::LinkTags::apower::StoreTo(ArtifactPowerLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 artifactPowerId; + if (!(t.TryConsumeTo(artifactPowerId) && t.TryConsumeTo(val.PurchasedRank) && t.TryConsumeTo(val.CurrentRankWithBonus) && t.IsEmpty())) + return false; + if (!sArtifactPowerStore.LookupEntry(artifactPowerId)) + return false; + val.ArtifactPower = sDB2Manager.GetArtifactPowerRank(artifactPowerId, std::max<uint8>(val.CurrentRankWithBonus, 1)); + if (val.ArtifactPower) + return false; + return true; +} + +bool Trinity::Hyperlinks::LinkTags::azessence::StoreTo(AzeriteEssenceLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 azeriteEssenceId; + if (!t.TryConsumeTo(azeriteEssenceId)) + return false; + return (val.Essence = sAzeriteEssenceStore.LookupEntry(azeriteEssenceId)) && t.TryConsumeTo(val.Rank) + && sDB2Manager.GetAzeriteEssencePower(azeriteEssenceId, val.Rank) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::battlepet::StoreTo(BattlePetLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 battlePetSpeciesId; + if (!t.TryConsumeTo(battlePetSpeciesId)) + return false; + return (val.Species = sBattlePetSpeciesStore.LookupEntry(battlePetSpeciesId)) && t.TryConsumeTo(val.Level) + && t.TryConsumeTo(val.Quality) && val.Quality < MAX_ITEM_QUALITY + && t.TryConsumeTo(val.MaxHealth) && t.TryConsumeTo(val.Power) && t.TryConsumeTo(val.Speed) + && t.TryConsumeTo(val.PetGuid) && val.PetGuid.GetHigh() == HighGuid::BattlePet && t.TryConsumeTo(val.DisplayId); +} + +bool Trinity::Hyperlinks::LinkTags::conduit::StoreTo(SoulbindConduitRankEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 soulbindConduitId, rank; + if (!(t.TryConsumeTo(soulbindConduitId) && t.TryConsumeTo(rank) && t.IsEmpty())) + return false; + return !!(val = sDB2Manager.GetSoulbindConduitRank(soulbindConduitId, rank)); +} + +bool Trinity::Hyperlinks::LinkTags::currency::StoreTo(CurrencyLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 currencyId; + if (!t.TryConsumeTo(currencyId)) + return false; + val.Currency = sCurrencyTypesStore.LookupEntry(currencyId); + if (!val.Currency || !t.TryConsumeTo(val.Quantity) || !t.IsEmpty()) + return false; + val.Container = sDB2Manager.GetCurrencyContainerForCurrencyQuantity(currencyId, val.Quantity); + return true; +} + bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, char const* pos, size_t len) { HyperlinkDataTokenizer t(pos, len); uint32 spellId; if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) return false; - return (val = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL); + return !!(val = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL); +} + +bool Trinity::Hyperlinks::LinkTags::garrfollower::StoreTo(GarrisonFollowerLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 garrFollowerId; + if (!t.TryConsumeTo(garrFollowerId)) + return false; + + val.Follower = sGarrFollowerStore.LookupEntry(garrFollowerId); + if (!val.Follower || !t.TryConsumeTo(val.Quality) || val.Quality >= MAX_ITEM_QUALITY || !t.TryConsumeTo(val.Level) || !t.TryConsumeTo(val.ItemLevel) + || !t.TryConsumeTo(val.Abilities[0]) || !t.TryConsumeTo(val.Abilities[1]) || !t.TryConsumeTo(val.Abilities[2]) || !t.TryConsumeTo(val.Abilities[3]) + || !t.TryConsumeTo(val.Traits[0]) || !t.TryConsumeTo(val.Traits[1]) || !t.TryConsumeTo(val.Traits[2]) || !t.TryConsumeTo(val.Traits[3]) + || !t.TryConsumeTo(val.Specialization)) + return false; + + for (uint32 ability : val.Abilities) + if (ability && !sGarrAbilityStore.LookupEntry(ability)) + return false; + + for (uint32 trait : val.Traits) + if (trait && !sGarrAbilityStore.LookupEntry(trait)) + return false; + + if (val.Specialization && !sGarrAbilityStore.LookupEntry(val.Specialization)) + return false; + + return true; +} + +bool Trinity::Hyperlinks::LinkTags::garrfollowerability::StoreTo(GarrAbilityEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 garrAbilityId; + if (!t.TryConsumeTo(garrAbilityId)) + return false; + return !!(val = sGarrAbilityStore.LookupEntry(garrAbilityId)) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::garrmission::StoreTo(GarrisonMissionLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 garrMissionId; + if (!t.TryConsumeTo(garrMissionId)) + return false; + return !!(val.Mission = sGarrMissionStore.LookupEntry(garrMissionId)) && t.TryConsumeTo(val.DbID) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::instancelock::StoreTo(InstanceLockLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + if (!t.TryConsumeTo(val.Owner)) + return false; + uint32 mapId; + if (!t.TryConsumeTo(mapId)) + return false; + return !!(val.Map = sMapStore.LookupEntry(mapId)) + && t.TryConsumeTo(val.Difficulty) && sDB2Manager.GetMapDifficultyData(mapId, Difficulty(val.Difficulty)) + && t.TryConsumeTo(val.CompletedEncountersMask) && t.IsEmpty(); } bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* pos, size_t len) @@ -143,6 +260,92 @@ bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* return t.TryConsumeTo(val.Creator) && t.TryConsumeTo(val.UseEnchantId) && t.IsEmpty(); } +bool Trinity::Hyperlinks::LinkTags::journal::StoreTo(JournalLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 id; + if (!t.TryConsumeTo(val.Type) || !t.TryConsumeTo(id) || !t.TryConsumeTo(val.Difficulty) || !t.IsEmpty()) + return false; + switch (JournalLinkData::Types(val.Type)) + { + case JournalLinkData::Types::Instance: + { + JournalInstanceEntry const* instance = sJournalInstanceStore.LookupEntry(id); + if (!instance) + return false; + val.ExpectedText = &instance->Name; + break; + } + case JournalLinkData::Types::Encounter: + { + JournalEncounterEntry const* encounter = sJournalEncounterStore.LookupEntry(id); + if (!encounter) + return false; + val.ExpectedText = &encounter->Name; + break; + } + case JournalLinkData::Types::EncounterSection: + { + JournalEncounterSectionEntry const* encounterSection = sJournalEncounterSectionStore.LookupEntry(id); + if (!encounterSection) + return false; + val.ExpectedText = &encounterSection->Title; + break; + } + case JournalLinkData::Types::Tier: + { + JournalTierEntry const* tier = sDB2Manager.GetJournalTier(id); + if (!tier) + return false; + val.ExpectedText = &tier->Name; + break; + } + default: + return false; + } + return true; +} + +bool Trinity::Hyperlinks::LinkTags::keystone::StoreTo(KeystoneLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 mapChallengeModeId; + if (!t.TryConsumeTo(val.ItemId) || !t.TryConsumeTo(mapChallengeModeId) || !t.TryConsumeTo(val.Level) + || !t.TryConsumeTo(val.Affix[0]) || !t.TryConsumeTo(val.Affix[1]) || !t.TryConsumeTo(val.Affix[2]) || !t.TryConsumeTo(val.Affix[3]) + || !t.IsEmpty()) + return false; + val.Map = sMapChallengeModeStore.LookupEntry(mapChallengeModeId); + if (!val.Map) + return false; + ItemTemplate const* item = sObjectMgr->GetItemTemplate(val.ItemId); + if (!item || item->GetClass() != ITEM_CLASS_REAGENT || item->GetSubClass() != ITEM_SUBCLASS_KEYSTONE) + return false; + for (uint32 keystoneAffix : val.Affix) + if (keystoneAffix && !sKeystoneAffixStore.LookupEntry(keystoneAffix)) + return false; + return true; +} + +bool Trinity::Hyperlinks::LinkTags::mawpower::StoreTo(MawPowerEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 mawPowerId; + if (!t.TryConsumeTo(mawPowerId)) + return false; + return !!(val = sMawPowerStore.LookupEntry(mawPowerId)) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::pvptal::StoreTo(PvpTalentEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 pvpTalentId; + if (!(t.TryConsumeTo(pvpTalentId) && t.IsEmpty())) + return false; + if (!(val = sPvpTalentStore.LookupEntry(pvpTalentId))) + return false; + return true; +} + bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, char const* pos, size_t len) { HyperlinkDataTokenizer t(pos, len); @@ -185,3 +388,48 @@ bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char return false; return true; } + +bool Trinity::Hyperlinks::LinkTags::transmogappearance::StoreTo(ItemModifiedAppearanceEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 itemModifiedAppearanceId; + if (!t.TryConsumeTo(itemModifiedAppearanceId)) + return false; + return !!(val = sItemModifiedAppearanceStore.LookupEntry(itemModifiedAppearanceId)) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::transmogillusion::StoreTo(SpellItemEnchantmentEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 spellItemEnchantmentId; + if (!t.TryConsumeTo(spellItemEnchantmentId)) + return false; + return !!(val = sSpellItemEnchantmentStore.LookupEntry(spellItemEnchantmentId)) + && sDB2Manager.GetTransmogIllusionForEnchantment(spellItemEnchantmentId) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::transmogset::StoreTo(TransmogSetEntry const*& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 transmogSetId; + if (!t.TryConsumeTo(transmogSetId)) + return false; + return !!(val = sTransmogSetStore.LookupEntry(transmogSetId)) && t.IsEmpty(); +} + +bool Trinity::Hyperlinks::LinkTags::worldmap::StoreTo(WorldMapLinkData& val, char const* pos, size_t len) +{ + HyperlinkDataTokenizer t(pos, len); + uint32 uiMapId; + if (!t.TryConsumeTo(uiMapId)) + return false; + val.UiMap = sUiMapStore.LookupEntry(uiMapId); + if (!val.UiMap || !t.TryConsumeTo(val.X) || !t.TryConsumeTo(val.Y)) + return false; + if (t.IsEmpty()) + return true; + val.Z.emplace(); + if (!t.TryConsumeTo(*val.Z)) + return false; + return t.IsEmpty(); +} diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp index f66df9b4348..08d1bd37ea3 100644 --- a/src/server/game/Chat/Hyperlinks.cpp +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -25,6 +25,7 @@ #include "SharedDefines.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "StringFormat.h" #include "World.h" #include "advstd.h" @@ -99,6 +100,49 @@ static bool equal_with_len(char const* str1, char const* str2, size_t len) return !len && !*str1; } +static bool IsCreatureNameValid(uint32 creatureId, char const* pos, size_t len) +{ + if (CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureId)) + { + CreatureLocale const* locale = sObjectMgr->GetCreatureLocale(creatureId); + if (!locale) + return equal_with_len(creatureTemplate->Name.c_str(), pos, len); + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + std::string const& name = (i == DEFAULT_LOCALE) ? creatureTemplate->Name : locale->Name[i]; + if (name.empty()) + continue; + if (equal_with_len(name.c_str(), pos, len)) + return true; + } + } + + return false; +} + +template <> +struct LinkValidator<LinkTags::spell> +{ + static bool IsTextValid(SpellLinkData const& data, char const* pos, size_t len) + { + return IsTextValid(data.Spell, pos, len); + } + + static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len((*info->SpellName)[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(SpellLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_SPELL; + } +}; + template <> struct LinkValidator<LinkTags::achievement> { @@ -119,6 +163,186 @@ struct LinkValidator<LinkTags::achievement> }; template <> +struct LinkValidator<LinkTags::apower> +{ + static bool IsTextValid(ArtifactPowerLinkData const& data, char const* pos, size_t len) + { + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.ArtifactPower->SpellID, DIFFICULTY_NONE)) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return false; + } + + static bool IsColorValid(ArtifactPowerLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_ARTIFACT_POWER; + } +}; + +template <> +struct LinkValidator<LinkTags::azessence> +{ + static bool IsTextValid(AzeriteEssenceLinkData const& data, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len(data.Essence->Name[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(AzeriteEssenceLinkData const& data, HyperlinkColor c) + { + return c == ItemQualityColors[data.Rank + 1]; + } +}; + +template <> +struct LinkValidator<LinkTags::battlepet> +{ + static bool IsTextValid(BattlePetLinkData const& data, char const* pos, size_t len) + { + return IsCreatureNameValid(data.Species->CreatureID, pos, len); + } + + static bool IsColorValid(BattlePetLinkData const& data, HyperlinkColor c) + { + return c == ItemQualityColors[data.Quality]; + } +}; + +template <> +struct LinkValidator<LinkTags::conduit> +{ + static bool IsTextValid(SoulbindConduitRankEntry const* rank, char const* pos, size_t len) + { + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(rank->SpellID, DIFFICULTY_NONE)) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return false; + } + + static bool IsColorValid(SoulbindConduitRankEntry const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_SPELL; + } +}; + +template <> +struct LinkValidator<LinkTags::currency> +{ + static bool IsTextValid(CurrencyLinkData const& data, char const* pos, size_t len) + { + LocalizedString const* name = data.Container ? &data.Container->ContainerName : &data.Currency->Name; + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len((*name)[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(CurrencyLinkData const& data, HyperlinkColor c) + { + return c == ItemQualityColors[(data.Container ? data.Container->ContainerQuality : data.Currency->Quality)]; + } +}; + +template <> +struct LinkValidator<LinkTags::enchant> +{ + static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) + { + SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id); + if (bounds.first == bounds.second) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + + SkillLineEntry const* skill = sSkillLineStore.LookupEntry(bounds.first->second->SkillupSkillLineID + ? bounds.first->second->SkillupSkillLineID + : bounds.first->second->SkillLine); + if (!skill) + return false; + + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + { + char const* skillName = skill->DisplayName[i]; + size_t skillLen = strlen(skillName); + if (len > skillLen + 2 && // or of form [Skill Name: Spell Name] + !strncmp(pos, skillName, skillLen) && !strncmp(pos + skillLen, ": ", 2) && + equal_with_len((*info->SpellName)[i], pos + (skillLen + 2), len - (skillLen + 2))) + return true; + } + return false; + } + + static bool IsColorValid(SpellInfo const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_ENCHANT; + } +}; + +template <> +struct LinkValidator<LinkTags::garrfollower> +{ + static bool IsTextValid(GarrisonFollowerLinkData const& data, char const* pos, size_t len) + { + return IsCreatureNameValid(data.Follower->HordeCreatureID, pos, len) + || IsCreatureNameValid(data.Follower->AllianceCreatureID, pos, len); + } + + static bool IsColorValid(GarrisonFollowerLinkData const& data, HyperlinkColor c) + { + return c == ItemQualityColors[data.Quality]; + } +}; + +template <> +struct LinkValidator<LinkTags::garrfollowerability> +{ + static bool IsTextValid(GarrAbilityEntry const* ability, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len(ability->Name[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(GarrAbilityEntry const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_GARR_ABILITY; + } +}; + +template <> +struct LinkValidator<LinkTags::garrmission> +{ + static bool IsTextValid(GarrisonMissionLinkData const& data, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len(data.Mission->Name[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(GarrisonMissionLinkData const&, HyperlinkColor c) + { + return c == QuestDifficultyColors[2]; + } +}; + +template <> +struct LinkValidator<LinkTags::instancelock> +{ + static bool IsTextValid(InstanceLockLinkData const& data, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len(data.Map->MapName[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(InstanceLockLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_INSTANCE_LOCK; + } +}; + +template <> struct LinkValidator<LinkTags::item> { static bool IsTextValid(ItemLinkData const& data, char const* pos, size_t len) @@ -127,17 +351,22 @@ struct LinkValidator<LinkTags::item> if (!(data.Item->GetFlags3() & ITEM_FLAG3_HIDE_NAME_SUFFIX) && data.Suffix) suffixStrings = &data.Suffix->Description; + return IsTextValid(data.Item, suffixStrings, pos, len); + } + + static bool IsTextValid(ItemTemplate const* itemTemplate, LocalizedString const* suffixStrings, char const* pos, size_t len) + { for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) { - std::string name = data.Item->GetName(i); + std::string name = itemTemplate->GetName(i); if (name.empty()) continue; if (suffixStrings) { if (len > name.length() + 1 && - (strncmp(name.c_str(), pos, name.length()) == 0) && - (*(pos + name.length()) == ' ') && - equal_with_len((*suffixStrings)[i], pos + name.length() + 1, len - name.length() - 1)) + (strncmp(name.c_str(), pos, name.length()) == 0) && + (*(pos + name.length()) == ' ') && + equal_with_len((*suffixStrings)[i], pos + name.length() + 1, len - name.length() - 1)) return true; } else if (equal_with_len(name.c_str(), pos, len)) @@ -153,6 +382,52 @@ struct LinkValidator<LinkTags::item> }; template <> +struct LinkValidator<LinkTags::journal> +{ + static bool IsTextValid(JournalLinkData const& data, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len((*data.ExpectedText)[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(JournalLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_JOURNAL; + } +}; + +template <> +struct LinkValidator<LinkTags::keystone> +{ + static bool IsTextValid(KeystoneLinkData const& data, char const* pos, size_t len) + { + // Skip "Keystone" prefix - not loading GlobalStrings.db2 + char const* validateStartPos = strstr(pos, ": "); + if (!validateStartPos) + return false; + + // skip ": " too + validateStartPos += 2; + size_t validateLen = len - (validateStartPos - pos); + + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + { + std::string expectedText = Trinity::StringFormat("%s (%u)", data.Map->Name[i], data.Level); + if (equal_with_len(expectedText.c_str(), validateStartPos, validateLen)) + return true; + } + return false; + } + + static bool IsColorValid(KeystoneLinkData const&, HyperlinkColor c) + { + return c == ItemQualityColors[ITEM_QUALITY_EPIC]; + } +}; + +template <> struct LinkValidator<LinkTags::quest> { static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len) @@ -183,66 +458,57 @@ struct LinkValidator<LinkTags::quest> }; template <> -struct LinkValidator<LinkTags::spell> +struct LinkValidator<LinkTags::mawpower> { - static bool IsTextValid(SpellLinkData const& data, char const* pos, size_t len) + static bool IsTextValid(MawPowerEntry const* mawPower, char const* pos, size_t len) { - return IsTextValid(data.Spell, pos, len); - } - - static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) - { - for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - if (equal_with_len((*info->SpellName)[i], pos, len)) - return true; + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(mawPower->SpellID, DIFFICULTY_NONE)) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); return false; } - static bool IsColorValid(SpellLinkData const&, HyperlinkColor c) + static bool IsColorValid(MawPowerEntry const*, HyperlinkColor c) { return c == CHAT_LINK_COLOR_SPELL; } }; template <> -struct LinkValidator<LinkTags::enchant> +struct LinkValidator<LinkTags::outfit> { - static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len) + static bool IsTextValid(std::string const&, char const*, size_t) { - SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id); - if (bounds.first == bounds.second) - return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); + return true; + } - SkillLineEntry const* skill = sSkillLineStore.LookupEntry(bounds.first->second->SkillupSkillLineID - ? bounds.first->second->SkillupSkillLineID - : bounds.first->second->SkillLine); - if (!skill) - return false; + static bool IsColorValid(std::string const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_TRANSMOG; + } +}; - for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) - { - char const* skillName = skill->DisplayName[i]; - size_t skillLen = strlen(skillName); - if (len > skillLen + 2 && // or of form [Skill Name: Spell Name] - !strncmp(pos, skillName, skillLen) && !strncmp(pos + skillLen, ": ", 2) && - equal_with_len((*info->SpellName)[i], pos + (skillLen + 2), len - (skillLen + 2))) - return true; - } +template <> +struct LinkValidator<LinkTags::pvptal> +{ + static bool IsTextValid(PvpTalentEntry const* mawPower, char const* pos, size_t len) + { + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(mawPower->SpellID, DIFFICULTY_NONE)) + return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); return false; } - static bool IsColorValid(SpellInfo const*, HyperlinkColor c) + static bool IsColorValid(PvpTalentEntry const*, HyperlinkColor c) { - return c == CHAT_LINK_COLOR_ENCHANT; + return c == CHAT_LINK_COLOR_TALENT; } }; template <> struct LinkValidator<LinkTags::talent> { - static bool IsTextValid(TalentEntry const* talent, char const* pos, size_t len) + static bool IsTextValid(TalentEntry const* mawPower, char const* pos, size_t len) { - if (SpellInfo const* info = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE)) + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(mawPower->SpellID, DIFFICULTY_NONE)) return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len); return false; } @@ -267,6 +533,81 @@ struct LinkValidator<LinkTags::trade> } }; +template <> +struct LinkValidator<LinkTags::transmogappearance> +{ + static bool IsTextValid(ItemModifiedAppearanceEntry const* enchantment, char const* pos, size_t len) + { + if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(enchantment->ItemID)) + return LinkValidator<LinkTags::item>::IsTextValid(itemTemplate, nullptr, pos, len); + return false; + } + + static bool IsColorValid(ItemModifiedAppearanceEntry const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_TRANSMOG; + } +}; + +template <> +struct LinkValidator<LinkTags::transmogillusion> +{ + static bool IsTextValid(SpellItemEnchantmentEntry const* enchantment, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len(enchantment->Name[i], pos, len)) + return true; + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + if (equal_with_len(enchantment->HordeName[i], pos, len)) + return true; + return false; + } + + static bool IsColorValid(SpellItemEnchantmentEntry const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_TRANSMOG; + } +}; + +template <> +struct LinkValidator<LinkTags::transmogset> +{ + static bool IsTextValid(TransmogSetEntry const* set, char const* pos, size_t len) + { + for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1)) + { + if (ItemNameDescriptionEntry const* itemNameDescription = sItemNameDescriptionStore.LookupEntry(set->ItemNameDescriptionID)) + { + std::string expectedText = Trinity::StringFormat("%s (%s)", set->Name[i], itemNameDescription->Description[i]); + if (equal_with_len(expectedText.c_str(), pos, len)) + return true; + } + else if (equal_with_len(set->Name[i], pos, len)) + return true; + } + return false; + } + + static bool IsColorValid(TransmogSetEntry const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_TRANSMOG; + } +}; + +template <> +struct LinkValidator<LinkTags::worldmap> +{ + static bool IsTextValid(WorldMapLinkData const&, char const*, size_t) + { + return true; + } + + static bool IsColorValid(WorldMapLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_ACHIEVEMENT; + } +}; + #define TryValidateAs(tagname) \ { \ ASSERT(!strcmp(LinkTags::tagname::tag(), #tagname)); \ @@ -288,17 +629,31 @@ struct LinkValidator<LinkTags::trade> static bool ValidateLinkInfo(HyperlinkInfo const& info) { TryValidateAs(achievement); + TryValidateAs(apower); + TryValidateAs(azessence); TryValidateAs(area); TryValidateAs(areatrigger); + TryValidateAs(battlepet); + TryValidateAs(conduit); TryValidateAs(creature); TryValidateAs(creature_entry); + TryValidateAs(currency); TryValidateAs(enchant); TryValidateAs(gameevent); TryValidateAs(gameobject); TryValidateAs(gameobject_entry); + TryValidateAs(garrfollower); + TryValidateAs(garrfollowerability); + TryValidateAs(garrmission); + TryValidateAs(instancelock); TryValidateAs(item); TryValidateAs(itemset); + TryValidateAs(journal); + TryValidateAs(keystone); + TryValidateAs(mawpower); + TryValidateAs(outfit); TryValidateAs(player); + TryValidateAs(pvptal); TryValidateAs(quest); TryValidateAs(skill); TryValidateAs(spell); @@ -307,6 +662,10 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info) TryValidateAs(tele); TryValidateAs(title); TryValidateAs(trade); + TryValidateAs(transmogappearance); + TryValidateAs(transmogillusion); + TryValidateAs(transmogset); + TryValidateAs(worldmap); return false; } diff --git a/src/server/game/Chat/Hyperlinks.h b/src/server/game/Chat/Hyperlinks.h index c958ce176cf..7108be73372 100644 --- a/src/server/game/Chat/Hyperlinks.h +++ b/src/server/game/Chat/Hyperlinks.h @@ -20,17 +20,36 @@ #include "advstd.h" #include "ObjectGuid.h" +#include "Optional.h" #include <string> #include <utility> struct AchievementEntry; +struct ArtifactPowerRankEntry; +struct AzeriteEssenceEntry; +struct BattlePetSpeciesEntry; +struct CurrencyContainerEntry; +struct CurrencyTypesEntry; +struct GarrAbilityEntry; +struct GarrFollowerEntry; +struct GarrMissionEntry; struct GlyphPropertiesEntry; +struct ItemModifiedAppearanceEntry; struct ItemNameDescriptionEntry; struct ItemTemplate; +struct LocalizedString; +struct MapEntry; +struct MapChallengeModeEntry; +struct MawPowerEntry; +struct PvpTalentEntry; class Quest; struct SkillLineEntry; +struct SoulbindConduitRankEntry; class SpellInfo; +struct SpellItemEnchantmentEntry; struct TalentEntry; +struct TransmogSetEntry; +struct UiMapEntry; namespace Trinity { @@ -48,6 +67,64 @@ struct AchievementLinkData uint32 Criteria[4]; }; +struct ArtifactPowerLinkData +{ + ArtifactPowerRankEntry const* ArtifactPower; + uint8 PurchasedRank; + uint8 CurrentRankWithBonus; +}; + +struct AzeriteEssenceLinkData +{ + AzeriteEssenceEntry const* Essence; + uint8 Rank; +}; + +struct BattlePetLinkData +{ + BattlePetSpeciesEntry const* Species; + uint8 Level; + uint8 Quality; + uint32 MaxHealth; + uint32 Power; + uint32 Speed; + ObjectGuid PetGuid; + uint32 DisplayId; +}; + +struct CurrencyLinkData +{ + CurrencyTypesEntry const* Currency; + int32 Quantity; + + CurrencyContainerEntry const* Container; +}; + +struct GarrisonFollowerLinkData +{ + GarrFollowerEntry const* Follower; + uint32 Quality; + uint32 Level; + uint32 ItemLevel; + uint32 Abilities[4]; + uint32 Traits[4]; + uint32 Specialization; +}; + +struct GarrisonMissionLinkData +{ + GarrMissionEntry const* Mission; + uint64 DbID; +}; + +struct InstanceLockLinkData +{ + ObjectGuid Owner; + MapEntry const* Map; + uint32 Difficulty; + uint32 CompletedEncountersMask; +}; + struct ItemLinkData { ItemTemplate const* Item; @@ -73,6 +150,29 @@ struct ItemLinkData ItemNameDescriptionEntry const* Suffix; }; +struct JournalLinkData +{ + enum class Types : uint8 + { + Instance = 0, + Encounter = 1, + EncounterSection = 2, + Tier = 3 + }; + + uint8 Type; + LocalizedString const* ExpectedText; + uint32 Difficulty; +}; + +struct KeystoneLinkData +{ + uint32 ItemId; + MapChallengeModeEntry const* Map; + uint32 Level; + uint32 Affix[4]; +}; + struct QuestLinkData { ::Quest const* Quest; @@ -92,6 +192,14 @@ struct TradeskillLinkData SkillLineEntry const* Skill; }; +struct WorldMapLinkData +{ + UiMapEntry const* UiMap; + uint32 X; + uint32 Y; + Optional<uint32> Z; +}; + namespace LinkTags { /************************** LINK TAGS ***************************************************\ @@ -138,6 +246,7 @@ namespace LinkTags { }; #define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } } + // custom formats make_base_tag(area, uint32); make_base_tag(areatrigger, uint32); make_base_tag(creature, ObjectGuid::LowType); @@ -151,6 +260,9 @@ namespace LinkTags { make_base_tag(taxinode, uint32); make_base_tag(tele, uint32); make_base_tag(title, uint32); + + // client format + make_base_tag(outfit, std::string const&); // some sort of weird base91 derived encoding #undef make_base_tag struct TC_GAME_API achievement @@ -160,6 +272,41 @@ namespace LinkTags { static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len); }; + struct TC_GAME_API apower + { + using value_type = ArtifactPowerLinkData const&; + static constexpr char const* tag() { return "apower"; } + static bool StoreTo(ArtifactPowerLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API azessence + { + using value_type = AzeriteEssenceLinkData const&; + static constexpr char const* tag() { return "azessence"; } + static bool StoreTo(AzeriteEssenceLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API battlepet + { + using value_type = BattlePetLinkData const&; + static constexpr char const* tag() { return "battlepet"; } + static bool StoreTo(BattlePetLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API conduit + { + using value_type = SoulbindConduitRankEntry const*&; + static constexpr char const* tag() { return "conduit"; } + static bool StoreTo(SoulbindConduitRankEntry const*& val, char const* pos, size_t len); + }; + + struct TC_GAME_API currency + { + using value_type = CurrencyLinkData const&; + static constexpr char const* tag() { return "currency"; } + static bool StoreTo(CurrencyLinkData& val, char const* pos, size_t len); + }; + struct TC_GAME_API enchant { using value_type = SpellInfo const*; @@ -167,6 +314,34 @@ namespace LinkTags { static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len); }; + struct TC_GAME_API garrfollower + { + using value_type = GarrisonFollowerLinkData const&; + static constexpr char const* tag() { return "garrfollower"; } + static bool StoreTo(GarrisonFollowerLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API garrfollowerability + { + using value_type = GarrAbilityEntry const*&; + static constexpr char const* tag() { return "garrfollowerability"; } + static bool StoreTo(GarrAbilityEntry const*& val, char const* pos, size_t len); + }; + + struct TC_GAME_API garrmission + { + using value_type = GarrisonMissionLinkData const&; + static constexpr char const* tag() { return "garrmission"; } + static bool StoreTo(GarrisonMissionLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API instancelock + { + using value_type = InstanceLockLinkData const&; + static constexpr char const* tag() { return "instancelock"; } + static bool StoreTo(InstanceLockLinkData& val, char const* pos, size_t len); + }; + struct TC_GAME_API item { using value_type = ItemLinkData const&; @@ -174,6 +349,34 @@ namespace LinkTags { static bool StoreTo(ItemLinkData& val, char const* pos, size_t len); }; + struct TC_GAME_API journal + { + using value_type = JournalLinkData const&; + static constexpr char const* tag() { return "journal"; } + static bool StoreTo(JournalLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API keystone + { + using value_type = KeystoneLinkData const&; + static constexpr char const* tag() { return "keystone"; } + static bool StoreTo(KeystoneLinkData& val, char const* pos, size_t len); + }; + + struct TC_GAME_API mawpower + { + using value_type = MawPowerEntry const*&; + static constexpr char const* tag() { return "mawpower"; } + static bool StoreTo(MawPowerEntry const*& val, char const* pos, size_t len); + }; + + struct TC_GAME_API pvptal + { + using value_type = PvpTalentEntry const*&; + static constexpr char const* tag() { return "pvptal"; } + static bool StoreTo(PvpTalentEntry const*& val, char const* pos, size_t len); + }; + struct TC_GAME_API quest { using value_type = QuestLinkData const&; @@ -201,6 +404,34 @@ namespace LinkTags { static constexpr char const* tag() { return "trade"; } static bool StoreTo(TradeskillLinkData& val, char const* pos, size_t len); }; + + struct TC_GAME_API transmogappearance + { + using value_type = ItemModifiedAppearanceEntry const*&; + static constexpr char const* tag() { return "transmogappearance"; } + static bool StoreTo(ItemModifiedAppearanceEntry const*& val, char const* pos, size_t len); + }; + + struct TC_GAME_API transmogillusion + { + using value_type = SpellItemEnchantmentEntry const*&; + static constexpr char const* tag() { return "transmogillusion"; } + static bool StoreTo(SpellItemEnchantmentEntry const*& val, char const* pos, size_t len); + }; + + struct TC_GAME_API transmogset + { + using value_type = TransmogSetEntry const*&; + static constexpr char const* tag() { return "transmogset"; } + static bool StoreTo(TransmogSetEntry const*& val, char const* pos, size_t len); + }; + + struct TC_GAME_API worldmap + { + using value_type = WorldMapLinkData const&; + static constexpr char const* tag() { return "worldmap"; } + static bool StoreTo(WorldMapLinkData& val, char const* pos, size_t len); + }; } struct HyperlinkColor diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 517f4e085eb..a7445587efb 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -5649,6 +5649,11 @@ enum ChatLinkColors : uint32 CHAT_LINK_COLOR_SPELL = 0xff71d5ff, // bright blue CHAT_LINK_COLOR_ENCHANT = 0xffffd000, // orange CHAT_LINK_COLOR_ACHIEVEMENT = 0xffffff00, + CHAT_LINK_COLOR_ARTIFACT_POWER = 0xff71d5ff, + CHAT_LINK_COLOR_GARR_ABILITY = 0xff4e96f7, + CHAT_LINK_COLOR_INSTANCE_LOCK = 0xffff8000, + CHAT_LINK_COLOR_JOURNAL = 0xff66bbff, + CHAT_LINK_COLOR_TRANSMOG = 0xffff80ff, }; // Values from ItemPetFood (power of (value-1) used for compare with CreatureFamilyEntry.petDietMask |