diff options
author | Seyden <saiifii@live.de> | 2022-12-27 16:39:05 +0100 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2024-10-05 17:25:07 +0200 |
commit | 78fe841adb81e3fd1b5bc5f14b1d023d3125d9bb (patch) | |
tree | 557625564f1d72c892823ede1e27538df1cc1a59 | |
parent | da7367208ca5967ee48310643243cdd66f36f248 (diff) |
Core/Creatures: Implement StringId for Creatures, a custom identifier to make finding specific creatures in script easier (#28500)
Allows targeting specific spawns without hardcoding guids or find a bunch of different creatures with a single search
Co-authored-by: Shauren <shauren.trinity@gmail.com>
(cherry picked from commit 61c51b76c00d932a9180bc6781a244dc18375ef7)
-rw-r--r-- | sql/updates/world/3.3.5/2024_10_05_00_world_2022_12_27_01_world.sql | 8 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/WorldDatabase.cpp | 2 | ||||
-rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedCreature.h | 11 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/Creature.cpp | 23 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/Creature.h | 5 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/CreatureData.h | 1 | ||||
-rw-r--r-- | src/server/game/Entities/Object/Object.cpp | 21 | ||||
-rw-r--r-- | src/server/game/Entities/Object/Object.h | 5 | ||||
-rw-r--r-- | src/server/game/Globals/ObjectMgr.cpp | 10 | ||||
-rw-r--r-- | src/server/game/Grids/Notifiers/GridNotifiers.h | 47 | ||||
-rw-r--r-- | src/server/game/Maps/SpawnData.h | 1 | ||||
-rw-r--r-- | src/server/game/Miscellaneous/Language.h | 2 | ||||
-rw-r--r-- | src/server/scripts/Commands/cs_npc.cpp | 2 |
13 files changed, 124 insertions, 14 deletions
diff --git a/sql/updates/world/3.3.5/2024_10_05_00_world_2022_12_27_01_world.sql b/sql/updates/world/3.3.5/2024_10_05_00_world_2022_12_27_01_world.sql new file mode 100644 index 00000000000..5e0f6fd45c8 --- /dev/null +++ b/sql/updates/world/3.3.5/2024_10_05_00_world_2022_12_27_01_world.sql @@ -0,0 +1,8 @@ +-- +DELETE FROM `trinity_string` WHERE `entry`=5089; +INSERT INTO `trinity_string` (`entry`,`content_default`) VALUES +(5089,'Template StringID: %.*s\Spawn StringID: %.*s\nScript StringID: %.*s'); + +ALTER TABLE `creature` ADD `StringId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL AFTER ScriptName; + +ALTER TABLE `creature_template` ADD `StringId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL AFTER ScriptName; diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 372f64a273c..b28abd0e7fc 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, help FROM command", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?", CONNECTION_SYNCH); diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 1616d3e5787..48cd8e63888 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -385,6 +385,11 @@ inline Creature* GetClosestCreatureWithEntry(WorldObject* source, uint32 entry, return source->FindNearestCreature(entry, maxSearchRange, alive); } +inline Creature* GetClosestCreatureWithOptions(WorldObject* source, float maxSearchRange, FindCreatureOptions const& options) +{ + return source->FindNearestCreatureWithOptions(maxSearchRange, options); +} + inline GameObject* GetClosestGameObjectWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool spawnedOnly = true) { return source->FindNearestGameObject(entry, maxSearchRange, spawnedOnly); @@ -397,6 +402,12 @@ inline void GetCreatureListWithEntryInGrid(Container& container, WorldObject* so } template <typename Container> +inline void GetCreatureListWithOptionsInGrid(Container& container, WorldObject* source, float maxSearchRange, FindCreatureOptions const& options) +{ + source->GetCreatureListWithOptionsInGrid(container, maxSearchRange, options); +} + +template <typename Container> inline void GetGameObjectListWithEntryInGrid(Container& container, WorldObject* source, uint32 entry, float maxSearchRange) { source->GetGameObjectListWithEntryInGrid(container, entry, maxSearchRange); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 5f730c9c996..ffe0e3ba804 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -635,6 +635,8 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/, //We must update last scriptId or it looks like we reloaded a script, breaking some things such as gossip temporarily LastUsedScriptID = GetScriptId(); + m_stringIds[0] = cInfo->StringId; + return true; } @@ -1685,6 +1687,8 @@ bool Creature::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, // checked at creature_template loading m_defaultMovementType = MovementGeneratorType(data->movementType); + m_stringIds[1] = data->StringId; + if (addToMap && !GetMap()->AddToMap(this)) return false; return true; @@ -2772,6 +2776,25 @@ uint32 Creature::GetScriptId() const return ASSERT_NOTNULL(sObjectMgr->GetCreatureTemplate(GetEntry()))->ScriptID; } +bool Creature::HasStringId(std::string_view id) const +{ + return std::find(m_stringIds.begin(), m_stringIds.end(), id) != m_stringIds.end(); +} + +void Creature::SetScriptStringId(std::string id) +{ + if (!id.empty()) + { + m_scriptStringId.emplace(std::move(id)); + m_stringIds[2] = *m_scriptStringId; + } + else + { + m_scriptStringId.reset(); + m_stringIds[2] = {}; + } +} + VendorItemData const* Creature::GetVendorItems() const { return sObjectMgr->GetNpcVendorItemList(GetEntry()); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 8a65ca53724..789912326ec 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -198,6 +198,9 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma std::string const& GetAIName() const; std::string GetScriptName() const; uint32 GetScriptId() const; + bool HasStringId(std::string_view id) const; + void SetScriptStringId(std::string id); + std::array<std::string_view, 3> const& GetStringIds() const { return m_stringIds; } // override WorldObject function for proper name localization std::string const& GetNameForLocaleIdx(LocaleConstant locale_idx) const override; @@ -421,6 +424,8 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma CreatureTemplate const* m_creatureInfo; // Can differ from sObjectMgr->GetCreatureTemplate(GetEntry()) in difficulty mode > 0 CreatureData const* m_creatureData; + std::array<std::string_view, 3> m_stringIds; + Optional<std::string> m_scriptStringId; uint16 m_LootMode; // Bitmask (default: LOOT_MODE_DEFAULT) that determines what loot will be lootable diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 325bc22ab26..f4e6bdb1968 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -349,6 +349,7 @@ struct TC_GAME_API CreatureTemplate uint32 SpellSchoolImmuneMask; uint32 flags_extra; uint32 ScriptID; + std::string StringId; WorldPacket QueryData[TOTAL_LOCALES]; uint32 GetRandomValidModelId() const; uint32 GetFirstValidModelId() const; diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index fa78ef07700..003e4f53cf1 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2129,8 +2129,9 @@ Creature* WorldObject::FindNearestCreature(uint32 entry, float range, bool alive Creature* WorldObject::FindNearestCreatureWithOptions(float range, FindCreatureOptions const& options) const { Creature* creature = nullptr; - Trinity::NearestCreatureEntryWithOptionsInObjectRangeCheck checker(*this, range, options); - Trinity::CreatureLastSearcher<Trinity::NearestCreatureEntryWithOptionsInObjectRangeCheck> searcher(this, creature, checker); + Trinity::NearestCheckCustomizer checkCustomizer(*this, range); + Trinity::CreatureWithOptionsInObjectRangeCheck checker(*this, checkCustomizer, options); + Trinity::CreatureLastSearcher searcher(this, creature, checker); if (options.IgnorePhases) searcher.i_phaseMask = PHASEMASK_ANYWHERE; @@ -3152,6 +3153,18 @@ void WorldObject::GetCreatureListWithEntryInGrid(Container& creatureContainer, u } template <typename Container> +void WorldObject::GetCreatureListWithOptionsInGrid(Container& creatureContainer, float maxSearchRange, FindCreatureOptions const& options) const +{ + Trinity::NoopCheckCustomizer checkCustomizer; + Trinity::CreatureWithOptionsInObjectRangeCheck check(*this, checkCustomizer, options); + Trinity::CreatureListSearcher searcher(this, creatureContainer, check); + if (options.IgnorePhases) + searcher.i_phaseMask = PHASEMASK_ANYWHERE; + + Cell::VisitGridObjects(this, searcher, maxSearchRange); +} + +template <typename Container> void WorldObject::GetPlayerListInGrid(Container& playerContainer, float maxSearchRange, bool alive /*= true*/) const { Trinity::AnyPlayerInObjectRangeCheck checker(this, maxSearchRange, alive); @@ -3608,6 +3621,10 @@ template TC_GAME_API void WorldObject::GetCreatureListWithEntryInGrid(std::list< template TC_GAME_API void WorldObject::GetCreatureListWithEntryInGrid(std::deque<Creature*>&, uint32, float) const; template TC_GAME_API void WorldObject::GetCreatureListWithEntryInGrid(std::vector<Creature*>&, uint32, float) const; +template TC_GAME_API void WorldObject::GetCreatureListWithOptionsInGrid(std::list<Creature*>&, float, FindCreatureOptions const&) const; +template TC_GAME_API void WorldObject::GetCreatureListWithOptionsInGrid(std::deque<Creature*>&,float, FindCreatureOptions const&) const; +template TC_GAME_API void WorldObject::GetCreatureListWithOptionsInGrid(std::vector<Creature*>&, float, FindCreatureOptions const&) const; + template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::list<Player*>&, float, bool) const; template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::deque<Player*>&, float, bool) const; template TC_GAME_API void WorldObject::GetPlayerListInGrid(std::vector<Player*>&, float, bool) const; diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 0974aab5fa0..d489900eb16 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -301,6 +301,7 @@ struct FindCreatureOptions FindCreatureOptions() = default; FindCreatureOptions& SetCreatureId(uint32 creatureId) { CreatureId = creatureId; return *this; } + FindCreatureOptions& SetStringId(std::string_view stringId) { StringId = stringId; return *this; } FindCreatureOptions& SetIsAlive(bool isAlive) { IsAlive = isAlive; return *this; } FindCreatureOptions& SetIsInCombat(bool isInCombat) { IsInCombat = isInCombat; return *this; } @@ -317,6 +318,7 @@ struct FindCreatureOptions FindCreatureOptions& SetPrivateObjectOwner(ObjectGuid privateObjectOwnerGuid) { PrivateObjectOwnerGuid = privateObjectOwnerGuid; return *this; } Optional<uint32> CreatureId; + Optional<std::string_view> StringId; Optional<bool> IsAlive; Optional<bool> IsInCombat; @@ -531,6 +533,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void GetCreatureListWithEntryInGrid(Container& creatureContainer, uint32 entry, float maxSearchRange = 250.0f) const; template <typename Container> + void GetCreatureListWithOptionsInGrid(Container& creatureContainer, float maxSearchRange, FindCreatureOptions const& options) const; + + template <typename Container> void GetPlayerListInGrid(Container& playerContainer, float maxSearchRange, bool alive = true) const; void DestroyForNearbyPlayers(); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 31a1fa1d2a9..d89fd4c5894 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -495,7 +495,9 @@ void ObjectMgr::LoadCreatureTemplates() // 62 "flags_extra," // 63 - "ScriptName" + "ScriptName," + // 64 + "StringId" " FROM creature_template ct" " LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId"); @@ -615,6 +617,7 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields) creatureTemplate.SpellSchoolImmuneMask = fields[61].GetUInt32(); creatureTemplate.flags_extra = fields[62].GetUInt32(); creatureTemplate.ScriptID = GetScriptId(fields[63].GetString()); + creatureTemplate.StringId = fields[64].GetString(); } void ObjectMgr::LoadCreatureTemplateResistances() @@ -2164,8 +2167,8 @@ void ObjectMgr::LoadCreatures() QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, wander_distance, " // 11 12 13 14 15 16 17 18 19 20 21 "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, poolSpawnId, creature.npcflag, creature.unit_flags, creature.dynamicflags, " - // 22 - "creature.ScriptName " + // 22 23 + "creature.ScriptName, creature.StringId " "FROM creature " "LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid " "LEFT OUTER JOIN pool_members ON pool_members.type = 0 AND creature.guid = pool_members.spawnId"); @@ -2221,6 +2224,7 @@ void ObjectMgr::LoadCreatures() data.unit_flags = fields[20].GetUInt32(); data.dynamicflags = fields[21].GetUInt32(); data.scriptId = GetScriptId(fields[22].GetString()); + data.StringId = fields[23].GetString(); data.spawnGroupData = GetDefaultSpawnGroup(); MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapId); diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 1f676016b3c..e02f49cce8e 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -622,6 +622,35 @@ namespace Trinity // CHECKS && DO classes + // CHECK modifiers + class NoopCheckCustomizer + { + public: + bool Test(WorldObject const* /*o*/) const { return true; } + + void Update(WorldObject const* /*o*/) { } + }; + + class NearestCheckCustomizer + { + public: + explicit NearestCheckCustomizer(WorldObject const& obj, float range) : i_obj(obj), i_range(range) { } + + bool Test(WorldObject const* o) const + { + return i_obj.IsWithinDistInMap(o, i_range); + } + + void Update(WorldObject const* o) + { + i_range = i_obj.GetDistance(o); + } + + private: + WorldObject const& i_obj; + float i_range; + }; + // WorldObject check classes class TC_GAME_API AnyDeadUnitObjectInRangeCheck @@ -1398,13 +1427,14 @@ namespace Trinity NearestCreatureEntryWithLiveStateInObjectRangeCheck(NearestCreatureEntryWithLiveStateInObjectRangeCheck const&) = delete; }; - class NearestCreatureEntryWithOptionsInObjectRangeCheck + template <typename Customizer = NoopCheckCustomizer> + class CreatureWithOptionsInObjectRangeCheck { public: - NearestCreatureEntryWithOptionsInObjectRangeCheck(WorldObject const& obj, float range, FindCreatureOptions const& args) - : i_obj(obj), i_args(args), i_range(range) { } + CreatureWithOptionsInObjectRangeCheck(WorldObject const& obj, Customizer& customizer, FindCreatureOptions const& args) + : i_obj(obj), i_args(args), i_customizer(customizer) { } - bool operator()(Creature* u) + bool operator()(Creature* u) const { if (u->getDeathState() == DEAD) // Despawned return false; @@ -1412,12 +1442,15 @@ namespace Trinity if (u->GetGUID() == i_obj.GetGUID()) return false; - if (!i_obj.IsWithinDistInMap(u, i_range)) + if (!i_customizer.Test(u)) return false; if (i_args.CreatureId && u->GetEntry() != i_args.CreatureId) return false; + if (i_args.StringId && u->HasStringId(*i_args.StringId)) + return false; + if (i_args.IsAlive.has_value() && u->IsAlive() != i_args.IsAlive) return false; @@ -1442,14 +1475,14 @@ namespace Trinity if (i_args.AuraSpellId && !u->HasAura(*i_args.AuraSpellId)) return false; - i_range = i_obj.GetDistance(u); // use found unit range as new range limit for next check + i_customizer.Update(u); return true; } private: WorldObject const& i_obj; FindCreatureOptions const& i_args; - float i_range; + Customizer& i_customizer; }; class AnyPlayerInObjectRangeCheck diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h index 863fa13f4bc..d12b814f91f 100644 --- a/src/server/game/Maps/SpawnData.h +++ b/src/server/game/Maps/SpawnData.h @@ -99,6 +99,7 @@ struct SpawnData : public SpawnMetadata int32 spawntimesecs = 0; uint8 spawnMask = 0; uint32 scriptId = 0; + std::string StringId; protected: SpawnData(SpawnObjectType t) : SpawnMetadata(t) {} diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 138991a5115..5ad1cef8d55 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1153,7 +1153,7 @@ enum TrinityStrings LANG_NPCINFO_NPC_FLAGS = 5086, // master branch ONLY LANG_NPCINFO_PHASE_IDS = 5087, // master branch ONLY LANG_SCENARIO = 5088, // master branch ONLY - LANG_OBJECTINFO_STRINGIDS = 5089, // master branch ONLY + LANG_OBJECTINFO_STRINGIDS = 5089, // Room for more Trinity strings 5090-6603 diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 9e8529f1cb8..eacccbbdd8d 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -507,6 +507,8 @@ public: handler->PSendSysMessage(LANG_NPCINFO_ARMOR, target->GetArmor()); handler->PSendSysMessage(LANG_NPCINFO_POSITION, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()); handler->PSendSysMessage(LANG_OBJECTINFO_AIINFO, target->GetAIName().c_str(), target->GetScriptName().c_str()); + handler->PSendSysMessage(LANG_OBJECTINFO_STRINGIDS, STRING_VIEW_FMT_ARG(target->GetStringIds()[0]), + STRING_VIEW_FMT_ARG(target->GetStringIds()[1]), STRING_VIEW_FMT_ARG(target->GetStringIds()[2])); handler->PSendSysMessage(LANG_NPCINFO_REACTSTATE, DescribeReactState(target->GetReactState())); if (CreatureAI const* ai = target->AI()) handler->PSendSysMessage(LANG_OBJECTINFO_AITYPE, Trinity::GetTypeName(*ai).c_str()); |