From 5848eedb24c6aaca6fdf4b2210fa500d948d0b92 Mon Sep 17 00:00:00 2001 From: Ovahlord Date: Sat, 9 Aug 2025 15:01:55 +0200 Subject: [PATCH] Core/Creature: implement initial SummonInfo API for handling summons in the future --- src/common/Utilities/advstd.h | 82 ++++++++++++ .../game/Entities/Creature/Creature.cpp | 27 ++++ src/server/game/Entities/Creature/Creature.h | 13 ++ .../Creature/SummonInfo/SummonInfo.cpp | 124 ++++++++++++++++++ .../Entities/Creature/SummonInfo/SummonInfo.h | 85 ++++++++++++ .../Creature/SummonInfo/SummonInfoArgs.h | 36 +++++ src/server/game/Entities/Object/Object.cpp | 17 +++ src/server/game/Entities/Pet/Pet.cpp | 13 ++ src/server/game/Entities/Unit/Unit.cpp | 21 +++ src/server/game/Entities/Unit/Unit.h | 10 +- 10 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp create mode 100644 src/server/game/Entities/Creature/SummonInfo/SummonInfo.h create mode 100644 src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h diff --git a/src/common/Utilities/advstd.h b/src/common/Utilities/advstd.h index ff2717c975..810346ddee 100644 --- a/src/common/Utilities/advstd.h +++ b/src/common/Utilities/advstd.h @@ -18,9 +18,91 @@ #ifndef TRINITY_ADVSTD_H #define TRINITY_ADVSTD_H +#include + +#ifdef __cpp_lib_bit_cast +#include +#else +#include // for memcpy +#endif +#include + // this namespace holds implementations of upcoming stdlib features that our c++ version doesn't have yet namespace advstd { +// libc++ is missing these two +[[nodiscard]] constexpr bool is_eq(std::partial_ordering cmp) noexcept { return cmp == 0; } +[[nodiscard]] constexpr bool is_neq(std::partial_ordering cmp) noexcept { return cmp != 0; } + +#ifdef __cpp_lib_bit_cast +using std::bit_cast; +#else +// libstdc++ v10 is missing this +template , + std::is_trivially_copyable, + std::is_trivially_copyable>, int> = 0> +[[nodiscard]] constexpr To bit_cast(From const& from) noexcept +{ + To to; + std::memcpy(&to, &from, sizeof(To)); + return to; +} +#endif +} + +// std::forward_like +#ifdef __cpp_lib_forward_like +#include +namespace advstd +{ +using std::forward_like; +} +#else +namespace advstd +{ +template +[[nodiscard]] constexpr decltype(auto) forward_like(U&& value) noexcept +{ + using ValueU = std::remove_reference_t; + using ValueConstU = std::conditional_t>, ValueU const, ValueU>; + return static_cast, ValueConstU&&, ValueConstU&>>(value); +} +} +#endif + +// std::ranges::contains +#include +#ifndef __cpp_lib_ranges_contains +#include // for std::ranges::equal_to, std::identity +#include // for std::input_iterator, std::sentinel_for, std::projected +#endif + +namespace advstd::ranges +{ +#ifndef __cpp_lib_ranges_contains +struct Contains +{ + template S, class T, class Proj = std::identity> + requires std::indirect_binary_predicate, T const*> + [[nodiscard]] inline constexpr bool operator()(I first, S last, T const& value, Proj proj = {}) const + { + return std::ranges::find(std::move(first), last, value, proj) != last; + } + + template + requires std::indirect_binary_predicate, Proj>, T const*> + [[nodiscard]] inline constexpr bool operator()(R&& r, T const& value, Proj proj = {}) const + { + auto first = std::ranges::begin(r); + auto last = std::ranges::end(r); + return std::ranges::find(std::move(first), last, value, proj) != last; + } +} inline constexpr contains; +#else +using std::ranges::contains; +#endif } #endif diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index bac27977e0..8bdf010a8e 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -51,6 +51,7 @@ #include "ScriptedGossip.h" #include "SpellAuraEffects.h" #include "SpellMgr.h" +#include "SummonInfo.h" #include "TemporarySummon.h" #include "Transport.h" #include "Util.h" @@ -316,6 +317,11 @@ void Creature::AddToWorld() if (IsVehicle()) GetVehicleKit()->Install(); + // If the creature has been summoned, register it for the summoner + if (IsSummon()) + if (Unit* summoner = GetSummonInfo()->GetUnitSummoner()) + summoner->RegisterSummon(GetSummonInfo()); + if (GetZoneScript()) GetZoneScript()->OnCreatureCreate(this); } @@ -325,6 +331,11 @@ void Creature::RemoveFromWorld() { if (IsInWorld()) { + // If the creature about to despawn, unregister it for the summoner + if (IsSummon()) + if (Unit* summoner = GetSummonInfo()->GetUnitSummoner()) + summoner->UnregisterSummon(GetSummonInfo()); + if (GetZoneScript()) GetZoneScript()->OnCreatureRemove(this); @@ -2761,6 +2772,22 @@ void Creature::InitializeMovementSpeeds() SetSpeed(MOVE_WALK, _creatureMovementInfo.WalkSpeed); } +void Creature::InitializeSummonInfo(SummonInfoArgs const& args) +{ + ASSERT(_summonInfo == nullptr, "SummonInfo has already been initialized and must not be allocated again!"); + _summonInfo = std::make_unique(this, args); +} + +SummonInfo* Creature::GetSummonInfo() const +{ + return _summonInfo.get(); +} + +bool Creature::IsSummon() const +{ + return _summonInfo != nullptr; +} + void Creature::AllLootRemovedFromCorpse() { if (loot.loot_type != LOOT_SKINNING && !IsPet() && GetCreatureTemplate()->SkinLootId && hasLootRecipient()) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 7db13a4529..527bbcb3e1 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -34,8 +34,11 @@ class Group; class Quest; class Player; class SpellInfo; +class SummonInfo; class WorldSession; +struct SummonInfoArgs; + enum MovementGeneratorType : uint8; struct VendorItemCount @@ -420,6 +423,13 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma void ResetNoNpcDamageBelowPctHealthValue() { _noNpcDamageBelowPctHealth = 0.f; } float GetNoNpcDamageBelowPctHealthValue() const { return _noNpcDamageBelowPctHealth; } + // Initializes the summon API for this creature which marks it as summon and will be treated accordingly + void InitializeSummonInfo(SummonInfoArgs const& args); + // Returns a pointer to the SummonInfo API, nullptr if the creature is not a summon + SummonInfo* GetSummonInfo() const; + + bool IsSummon() const override; + protected: bool CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, CreatureData const* data = nullptr, uint32 vehId = 0); bool InitEntry(uint32 entry, CreatureData const* data = nullptr); @@ -510,6 +520,9 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma Optional _defaultMountDisplayIdOverride; float _noNpcDamageBelowPctHealth; + + // The Summon API which controls the behavior of summons + std::unique_ptr _summonInfo; }; class TC_GAME_API AssistDelayEvent : public BasicEvent diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp new file mode 100644 index 0000000000..1fde55a9ea --- /dev/null +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp @@ -0,0 +1,124 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "SummonInfo.h" + +#include "Creature.h" +#include "DBCStores.h" +#include "Log.h" +#include "ObjectAccessor.h" +#include "SummonInfoArgs.h" + +SummonInfo::SummonInfo(Creature* summonedCreature, SummonInfoArgs const& args) : + _summonedCreature(ASSERT_NOTNULL(summonedCreature)), _summonerGUID(args.SummonerGUID), _remainingDuration(args.Duration), + _maxHealth(args.MaxHealth), _summonSpellId(args.SummonSpellId),_despawnOnSummonerLogout(false), + _despawnOnSummonerDeath(false), _despawnWhenExpired(false) +{ + if (args.SummonPropertiesId.has_value()) + InitializeSummonProperties(*args.SummonPropertiesId); +} + +void SummonInfo::InitializeSummonProperties(uint32 summonPropertiesId) +{ + SummonPropertiesEntry const* summonProperties = sSummonPropertiesStore.LookupEntry(summonPropertiesId); + if (!summonProperties) + { + TC_LOG_ERROR("entities.unit", "Creature %s has been summoned with a non-existing SummonProperties.dbc entry (RecId: %u).", _summonedCreature->GetGUID().ToString().c_str(), summonPropertiesId); + return; + } + + if (summonProperties->Faction) + _factionId = summonProperties->Faction; + + _despawnOnSummonerLogout = summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnOnSummonerLogout); + _despawnOnSummonerDeath = summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnOnSummonerDeath); + _despawnWhenExpired = summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnWhenExpired); + + //if (_despawnOnSummonerDeath && summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DontDespawnOnSummonerDeath)) + // _despawnOnSummonerDeath = false; +} + +Creature* SummonInfo::GetSummonedCreature() const +{ + return _summonedCreature; +} + +Unit* SummonInfo::GetUnitSummoner() const +{ + if (!_summonerGUID.has_value()) + return nullptr; + + return ObjectAccessor::GetUnit(*_summonedCreature, *_summonerGUID); +} + +GameObject* SummonInfo::GetGameObjectSummoner() const +{ + if (!_summonerGUID.has_value()) + return nullptr; + + return ObjectAccessor::GetGameObject(*_summonedCreature, *_summonerGUID); +} + +Optional SummonInfo::GetRemainingDuration() const +{ + return _remainingDuration; +} + +Optional SummonInfo::GetMaxHealth() const +{ + return _maxHealth; +} + +Optional SummonInfo::GetSummonSpellId() const +{ + return _summonSpellId; +} + +Optional SummonInfo::GetFactionId() const +{ + return _factionId; +} + +bool SummonInfo::DespawnsOnSummonerLogout() const +{ + return _despawnOnSummonerLogout; +} + +void SummonInfo::SetDespawnOnSummonerLogout(bool set) +{ + _despawnOnSummonerLogout = set; +} + +bool SummonInfo::DespawnsOnSummonerDeath() const +{ + return _despawnOnSummonerDeath; +} + +void SummonInfo::SetDespawnOnSummonerDeath(bool set) +{ + _despawnOnSummonerDeath = set; +} + +bool SummonInfo::DespawnsWhenExpired() const +{ + return _despawnWhenExpired; +} + +void SummonInfo::SetDespawnWhenExpired(bool set) +{ + _despawnWhenExpired = set; +} diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h new file mode 100644 index 0000000000..be0a1ac75f --- /dev/null +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h @@ -0,0 +1,85 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _SummonInfo_h__ +#define _SummonInfo_h__ + +#include "Define.h" +#include "Duration.h" +#include "ObjectGuid.h" +#include "Optional.h" + +class Creature; +class GameObject; +class Unit; + +struct SummonInfoArgs; + +class TC_GAME_API SummonInfo +{ +public: + SummonInfo(SummonInfo const& right) = delete; + SummonInfo(SummonInfo&& right) = delete; + SummonInfo& operator=(SummonInfo const& right) = delete; + SummonInfo& operator=(SummonInfo&& right) = delete; + + explicit SummonInfo(Creature* summonedCreature, SummonInfoArgs const& args); + + // Initializes additional settings based on the provided SummonProperties ID. + void InitializeSummonProperties(uint32 summonPropertiesId); + // Returns the creature that is tied to this SummonInfo instance. + Creature* GetSummonedCreature() const; + // Returns the Unit summoner, or nullptr if no summoner has been provided or if the summoner is not a Unit. + Unit* GetUnitSummoner() const; + // Returns the GameObject summoner, or nullptr if no summoner has been provided or if the summoner is not a GameObject. + GameObject* GetGameObjectSummoner() const; + + // Returns the remaining time until the summon expires. Nullopt when no duration was set which implies that the summon is permanent. + Optional GetRemainingDuration() const; + // Returns the health amount that will override the default max health calculation. Nullopt when no amount is provided. + Optional GetMaxHealth() const; + // Returns the ID of the spell that has been used to summon the creature. Nullopt when the creature has not been summoned by a spell (scripted summons/ other APIs). + Optional GetSummonSpellId() const; + // Returns the FactionTemplate ID of the summon that is overriding the default ID of the creature. Nullopt when the faction has not been overriden. + Optional GetFactionId() const; + + // Returns true when the summon will despawn when the summoner logs out. This also includes despawning and teleporting between map instances. + bool DespawnsOnSummonerLogout() const; + // Marks the summon to despawn when the summoner logs out. This also includes despawning and teleporting between map instaces. + void SetDespawnOnSummonerLogout(bool set); + // Returns true when the summon will despawn when its summoner has died. + bool DespawnsOnSummonerDeath() const; + // Marks the summon to despawn when the summoner has died. + void SetDespawnOnSummonerDeath(bool set); + // Returns true when the summon will despawn after its duration has expired. If not set, the summon will just die. + bool DespawnsWhenExpired() const; + // Marks the summon to despawn after its duration has expired. If disabled, the summon will just die. + void SetDespawnWhenExpired(bool set); + +private: + Creature* _summonedCreature; + Optional _summonerGUID; + Optional _remainingDuration; // NYI + Optional _maxHealth; // NYI + Optional _summonSpellId; // NYI + Optional _factionId; // NYI + bool _despawnOnSummonerLogout; // NYI + bool _despawnOnSummonerDeath; // NYI + bool _despawnWhenExpired; // NYI +}; + +#endif // _SummonInfo_h__ diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h b/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h new file mode 100644 index 0000000000..46fd2767d2 --- /dev/null +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h @@ -0,0 +1,36 @@ +/* +* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef _SummonInfoArgs_h__ +#define _SummonInfoArgs_h__ + +#include "Define.h" +#include "Duration.h" +#include "Optional.h" +#include "ObjectGuid.h" + +// Controls the behavior of a summoned creature and must be set with extreme care. If you want a blank summon that just exists as a permanent spawn, leave all fields untouched. +struct SummonInfoArgs +{ + Optional SummonerGUID; + Optional MaxHealth; + Optional Duration; + Optional SummonPropertiesId; + Optional SummonSpellId; +}; + +#endif // _SummonInfoArgs_h__ diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 3dd381ac6f..b72fc90d28 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -46,6 +46,7 @@ #include "SpellDefines.h" #include "SpellMgr.h" #include "StringConvert.h" +#include "SummonInfoArgs.h" #include "TemporarySummon.h" #include "Totem.h" #include "Transport.h" @@ -2086,6 +2087,22 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonCreatur if (summonArgs.Summoner) PhasingHandler::InheritPhaseShift(summon, summonArgs.Summoner); + // Store special settings which have been extracted from spell effects/scripts + SummonInfoArgs args; + if (summonArgs.Summoner) + args.SummonerGUID = summonArgs.Summoner->GetGUID(); + if (summonArgs.SummonProperties) + args.SummonPropertiesId = summonArgs.SummonProperties->ID; + if (summonArgs.SummonDuration) + args.Duration = Milliseconds(summonArgs.SummonDuration); + if (summonArgs.SummonSpellId) + args.SummonSpellId = summonArgs.SummonSpellId; + if (summonArgs.SummonHealth) + args.MaxHealth = summonArgs.SummonHealth; + + // Initialize the SummonInfo API which marks the creature as Summon + summon->InitializeSummonInfo(args); + TransportBase* transport = summonArgs.Summoner ? summonArgs.Summoner->GetTransport() : nullptr; if (transport) { diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 3374d252e3..41bde48c22 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -28,6 +28,8 @@ #include "SpellHistory.h" #include "SpellAuras.h" #include "SpellAuraEffects.h" +#include "SummonInfo.h" +#include "SummonInfoArgs.h" #include "Unit.h" #include "Util.h" #include "Group.h" @@ -43,6 +45,8 @@ Pet::Pet(Player* owner, PetType type) : { ASSERT(GetOwner()); + InitializeSummonInfo({ .SummonerGUID = GetOwner()->GetGUID()}); + m_unitTypeMask |= UNIT_MASK_PET; if (type == HUNTER_PET) m_unitTypeMask |= UNIT_MASK_HUNTER_PET; @@ -69,6 +73,11 @@ void Pet::AddToWorld() ///- Register the pet for guid lookup GetMap()->GetObjectsStore().Insert(GetGUID(), this); Unit::AddToWorld(); + + // SummonInfo should always be initialized for pets. Make sure that we will not go any further if this is no longer the case. + if (Unit* summoner = ASSERT_NOTNULL(GetSummonInfo())->GetUnitSummoner()) + summoner->RegisterSummon(GetSummonInfo()); + AIM_Initialize(); } @@ -89,6 +98,10 @@ void Pet::RemoveFromWorld() ///- Remove the pet from the accessor if (IsInWorld()) { + // SummonInfo should always be initialized for pets. Make sure that we will not go any further if this is no longer the case. + if (Unit* summoner = ASSERT_NOTNULL(GetSummonInfo())->GetUnitSummoner()) + summoner->UnregisterSummon(GetSummonInfo()); + ///- Don't call the function for Creature, normal mobs + totems go in a different storage Unit::RemoveFromWorld(); GetMap()->GetObjectsStore().Remove(GetGUID()); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 22634ee9f4..569508864f 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -79,6 +79,7 @@ #include "SpellInfo.h" #include "SpellMgr.h" #include "SpellPackets.h" +#include "SummonInfo.h" #include "TemporarySummon.h" #include "Totem.h" #include "Transport.h" @@ -8818,6 +8819,23 @@ uint32 Unit::GetCreatureTypeMask() const return (creatureType >= 1) ? (1 << (creatureType - 1)) : 0; } +void Unit::RegisterSummon(SummonInfo* summon) +{ + if (!summon) + return; + + if (!advstd::ranges::contains(_activeSummons, summon)) + _activeSummons.push_back(summon); +} + +void Unit::UnregisterSummon(SummonInfo* summon) +{ + if (!summon) + return; + + std::erase(_activeSummons, summon); +} + void Unit::SetShapeshiftForm(ShapeshiftForm form) { SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM, form); @@ -9607,6 +9625,9 @@ void Unit::RemoveFromWorld() } } + // Clear all active summon references. If we are about to switch map instances, we want to make sure that we leave all summons behind so we won't do threadunsafe operations + _activeSummons.clear(); + WorldObject::RemoveFromWorld(); m_duringRemoveFromWorld = false; } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index b0aa46ee44..e3efadc912 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -87,6 +87,7 @@ class Spell; class SpellCastTargets; class SpellHistory; class SpellInfo; +class SummonInfo; class Totem; class Transport; class TransportBase; @@ -756,7 +757,7 @@ class TC_GAME_API Unit : public WorldObject uint32 HasUnitTypeMask(uint32 mask) const { return mask & m_unitTypeMask; } void AddUnitTypeMask(uint32 mask) { m_unitTypeMask |= mask; } - bool IsSummon() const { return (m_unitTypeMask & UNIT_MASK_SUMMON) != 0; } + virtual bool IsSummon() const { return false; } bool IsMinion() const { return (m_unitTypeMask & UNIT_MASK_MINION) != 0; } bool IsGuardian() const { return (m_unitTypeMask & UNIT_MASK_GUARDIAN) != 0; } bool IsPet() const { return (m_unitTypeMask & UNIT_MASK_PET) != 0; } @@ -1323,6 +1324,11 @@ class TC_GAME_API Unit : public WorldObject std::array m_SummonSlot; std::array m_ObjectSlot; + // Registers the SummonInfo API of a summoned creature to allow safely accessing it + void RegisterSummon(SummonInfo* summon); + // Unregisters the SummonInfo API of a summoned creature so it can no longer be accessed + void UnregisterSummon(SummonInfo* summon); + ShapeshiftForm GetShapeshiftForm() const { return ShapeshiftForm(GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM)); } void SetShapeshiftForm(ShapeshiftForm form); @@ -1819,6 +1825,8 @@ class TC_GAME_API Unit : public WorldObject std::unordered_map m_pendingMovementChanges; /* Player Movement fields END*/ + + std::vector _activeSummons; }; namespace Trinity