From ec1ab2a5e721f9bf0a848715e7ee71bdfae8f6ca Mon Sep 17 00:00:00 2001 From: Ovahlord <18347559+Ovahlord@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:12:10 +0200 Subject: [PATCH] Core/Creatures: move level and faction initialization into SummonInfo API --- .../game/Entities/Creature/Creature.cpp | 38 ++++++-- .../Creature/SummonInfo/SummonInfo.cpp | 94 +++++++++++++------ .../Entities/Creature/SummonInfo/SummonInfo.h | 30 +++--- .../Creature/SummonInfo/SummonInfoArgs.h | 7 +- .../Entities/Creature/TemporarySummon.cpp | 12 +-- src/server/game/Entities/Object/Object.cpp | 45 ++++----- src/server/game/Entities/Pet/Pet.cpp | 8 +- src/server/game/Maps/Map.h | 2 +- src/server/game/Spells/SpellEffects.cpp | 3 +- 9 files changed, 143 insertions(+), 96 deletions(-) diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 8bdf010a8e..5ddb472e87 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -596,7 +596,10 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/, if (!GetCreatureAddon()) SetSheath(SHEATH_STATE_MELEE); - SetFaction(cInfo->faction); + if (SummonInfo const* summonInfo = GetSummonInfo()) + SetFaction(summonInfo->GetFactionId().value_or(cInfo->faction)); + else + SetFaction(cInfo->faction); uint32 npcflags = 0, unitFlags = 0, unitFlags2 = 0; ObjectMgr::ChooseCreatureFlags(cInfo, &npcflags, &unitFlags, &unitFlags2, _staticFlags, data); @@ -1414,12 +1417,22 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask) void Creature::SelectLevel() { - CreatureTemplate const* cInfo = GetCreatureTemplate(); + uint8 level = [&]() -> uint8 + { + // Some summons override their default level selection + if (SummonInfo const* summonInfo = GetSummonInfo()) + if (summonInfo->GetCreatureLevel().has_value()) + return *summonInfo->GetCreatureLevel(); + + CreatureTemplate const* cInfo = GetCreatureTemplate(); + + // level + uint8 minlevel = std::min(cInfo->maxlevel, cInfo->minlevel); + uint8 maxlevel = std::max(cInfo->maxlevel, cInfo->minlevel); + + return minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel); + }(); - // level - uint8 minlevel = std::min(cInfo->maxlevel, cInfo->minlevel); - uint8 maxlevel = std::max(cInfo->maxlevel, cInfo->minlevel); - uint8 level = minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel); SetLevel(level); } @@ -1430,10 +1443,17 @@ void Creature::UpdateLevelDependantStats() CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(getLevel(), cInfo->unit_class); // health - float healthmod = _GetHealthMod(rank); + uint32 health = [&]() -> uint32 + { + // Some summons have their maximum health overridden by their summon spell effect values (e.g. player Shaman totems) + if (SummonInfo const* summonInfo = GetSummonInfo()) + if (summonInfo->GetMaxHealth().has_value()) + return *_summonInfo->GetMaxHealth(); - uint32 basehp = stats->GenerateHealth(cInfo); - uint32 health = uint32(basehp * healthmod); + float healthmod = _GetHealthMod(rank); + uint32 basehp = stats->GenerateHealth(cInfo); + return uint32(basehp * healthmod); + }(); SetCreateHealth(health); SetMaxHealth(health); diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp index 1fde55a9ea..c58fd6be6a 100644 --- a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp @@ -19,20 +19,21 @@ #include "Creature.h" #include "DBCStores.h" +#include "DBCEnums.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) + _summonedCreature(ASSERT_NOTNULL(summonedCreature)), _summonerGUID(args.Summoner ? args.Summoner->GetGUID() : ObjectGuid::Empty), + _remainingDuration(args.Duration), _maxHealth(args.MaxHealth), _creatureLevel(args.CreatureLevel), + _flags(SummonPropertiesFlags::None), _control(SummonPropertiesControl::None), _summonSlot(SummonPropertiesSlot::None) { if (args.SummonPropertiesId.has_value()) - InitializeSummonProperties(*args.SummonPropertiesId); + InitializeSummonProperties(*args.SummonPropertiesId, Object::ToUnit(args.Summoner)); } -void SummonInfo::InitializeSummonProperties(uint32 summonPropertiesId) +void SummonInfo::InitializeSummonProperties(uint32 summonPropertiesId, Unit const* summoner) { SummonPropertiesEntry const* summonProperties = sSummonPropertiesStore.LookupEntry(summonPropertiesId); if (!summonProperties) @@ -44,12 +45,27 @@ void SummonInfo::InitializeSummonProperties(uint32 summonPropertiesId) if (summonProperties->Faction) _factionId = summonProperties->Faction; - _despawnOnSummonerLogout = summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnOnSummonerLogout); - _despawnOnSummonerDeath = summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnOnSummonerDeath); - _despawnWhenExpired = summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DespawnWhenExpired); + _flags = summonProperties->GetFlags(); + _summonSlot = static_cast(summonProperties->Slot); + _control = static_cast(summonProperties->Control); - //if (_despawnOnSummonerDeath && summonProperties->GetFlags().HasFlag(SummonPropertiesFlags::DontDespawnOnSummonerDeath)) - // _despawnOnSummonerDeath = false; + if (summoner) + { + if (_flags.HasFlag(SummonPropertiesFlags::UseSummonerFaction)) + _factionId = summoner->GetFaction(); + + if (_control != SummonPropertiesControl::None) + { + // Controlled summons inherit the level of their summoner unless explicitly stated otherwise. + // Level can be overridden either by SummonPropertiesFlags::UseCreatureLevel or by a spell effect value + if (!_flags.HasFlag(SummonPropertiesFlags::UseCreatureLevel) && !_creatureLevel.has_value()) + _creatureLevel = summoner->getLevel(); + + // Controlled summons inherit their summoner's faction if not overridden by DBC data + if (!_factionId.has_value()) + _factionId = summoner->GetFaction(); + } + } } Creature* SummonInfo::GetSummonedCreature() const @@ -59,18 +75,12 @@ Creature* SummonInfo::GetSummonedCreature() const Unit* SummonInfo::GetUnitSummoner() const { - if (!_summonerGUID.has_value()) - return nullptr; - - return ObjectAccessor::GetUnit(*_summonedCreature, *_summonerGUID); + return ObjectAccessor::GetUnit(*_summonedCreature, _summonerGUID); } GameObject* SummonInfo::GetGameObjectSummoner() const { - if (!_summonerGUID.has_value()) - return nullptr; - - return ObjectAccessor::GetGameObject(*_summonedCreature, *_summonerGUID); + return ObjectAccessor::GetGameObject(*_summonedCreature, _summonerGUID); } Optional SummonInfo::GetRemainingDuration() const @@ -83,42 +93,68 @@ Optional SummonInfo::GetMaxHealth() const return _maxHealth; } -Optional SummonInfo::GetSummonSpellId() const -{ - return _summonSpellId; -} - Optional SummonInfo::GetFactionId() const { return _factionId; } +Optional SummonInfo::GetCreatureLevel() const +{ + return _creatureLevel; +} + bool SummonInfo::DespawnsOnSummonerLogout() const { - return _despawnOnSummonerLogout; + return _flags.HasFlag(SummonPropertiesFlags::DespawnOnSummonerLogout); } void SummonInfo::SetDespawnOnSummonerLogout(bool set) { - _despawnOnSummonerLogout = set; + if (set) + _flags |= SummonPropertiesFlags::DespawnOnSummonerLogout; + else + _flags.RemoveFlag(SummonPropertiesFlags::DespawnOnSummonerLogout); } bool SummonInfo::DespawnsOnSummonerDeath() const { - return _despawnOnSummonerDeath; + return _flags.HasFlag(SummonPropertiesFlags::DespawnOnSummonerDeath); } void SummonInfo::SetDespawnOnSummonerDeath(bool set) { - _despawnOnSummonerDeath = set; + if (set) + _flags |= SummonPropertiesFlags::DespawnOnSummonerDeath; + else + _flags.RemoveFlag(SummonPropertiesFlags::DespawnOnSummonerDeath); } bool SummonInfo::DespawnsWhenExpired() const { - return _despawnWhenExpired; + return _flags.HasFlag(SummonPropertiesFlags::DespawnWhenExpired); } void SummonInfo::SetDespawnWhenExpired(bool set) { - _despawnWhenExpired = set; + if (set) + _flags |= SummonPropertiesFlags::DespawnWhenExpired; + else + _flags.RemoveFlag(SummonPropertiesFlags::DespawnWhenExpired); +} + +bool SummonInfo::UsesSummonerFaction() const +{ + return _flags.HasFlag(SummonPropertiesFlags::UseSummonerFaction); +} + +void SummonInfo::SetUseSummonerFaction(bool set) +{ + if (set) + _flags |= SummonPropertiesFlags::UseSummonerFaction; + else + _flags.RemoveFlag(SummonPropertiesFlags::UseSummonerFaction); +} +bool SummonInfo::IsControlledBySummoner() const +{ + return _control > SummonPropertiesControl::None; } diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h index be0a1ac75f..7ed9c98280 100644 --- a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h @@ -18,8 +18,10 @@ #ifndef _SummonInfo_h__ #define _SummonInfo_h__ +#include "DBCEnums.h" #include "Define.h" #include "Duration.h" +#include "EnumFlag.h" #include "ObjectGuid.h" #include "Optional.h" @@ -40,10 +42,10 @@ public: explicit SummonInfo(Creature* summonedCreature, SummonInfoArgs const& args); // Initializes additional settings based on the provided SummonProperties ID. - void InitializeSummonProperties(uint32 summonPropertiesId); + void InitializeSummonProperties(uint32 summonPropertiesId, Unit const* summoner); // 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. + // Returns the Unit summoner, or nullptr if no summoner has been provided or if the summoner is not an 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; @@ -52,10 +54,10 @@ public: 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 the level of the creature that will override the default level calculation. Nullopt when the creature uses its default values. + Optional GetCreatureLevel() 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; @@ -69,17 +71,23 @@ public: bool DespawnsWhenExpired() const; // Marks the summon to despawn after its duration has expired. If disabled, the summon will just die. void SetDespawnWhenExpired(bool set); + // Returns true when the summon will inherit its summoner's faction. + bool UsesSummonerFaction() const; + // Marks the summon to inherit its summoner's faction. + void SetUseSummonerFaction(bool set); + // Returns true when the summon is either a Guardian, Pet, Vehicle or Possessed summon + bool IsControlledBySummoner() const; private: Creature* _summonedCreature; - Optional _summonerGUID; + ObjectGuid _summonerGUID; Optional _remainingDuration; // NYI - Optional _maxHealth; // NYI - Optional _summonSpellId; // NYI - Optional _factionId; // NYI - bool _despawnOnSummonerLogout; // NYI - bool _despawnOnSummonerDeath; // NYI - bool _despawnWhenExpired; // NYI + Optional _maxHealth; // Implemented in Creature::UpdateLevelDependantStats + Optional _factionId; // Implemented in Creature::UpdateEntry + Optional _creatureLevel; // Implemented in Creature::SelectLevel + EnumFlag _flags; + SummonPropertiesControl _control; + SummonPropertiesSlot _summonSlot; }; #endif // _SummonInfo_h__ diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h b/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h index 46fd2767d2..40f274094f 100644 --- a/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfoArgs.h @@ -21,16 +21,17 @@ #include "Define.h" #include "Duration.h" #include "Optional.h" -#include "ObjectGuid.h" + +class WorldObject; // 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; + WorldObject const* Summoner = nullptr; Optional MaxHealth; Optional Duration; Optional SummonPropertiesId; - Optional SummonSpellId; + Optional CreatureLevel; }; #endif // _SummonInfoArgs_h__ diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 9a5233a9b1..03d37a987a 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -172,8 +172,6 @@ void TempSummon::InitStats(uint32 duration) if (owner && IsTrigger() && m_spells[0]) { - SetFaction(owner->GetFaction()); - SetLevel(owner->getLevel()); if (owner->GetTypeId() == TYPEID_PLAYER) m_ControlledByPlayer = true; } @@ -197,9 +195,6 @@ void TempSummon::InitStats(uint32 duration) if (m_Properties->Control != SUMMON_CATEGORY_WILD) { - if (!m_Properties->Faction) - SetFaction(owner->GetFaction()); - // Creator guid is always set for allied summons SetCreatorGUID(owner->GetGUID()); @@ -218,10 +213,6 @@ void TempSummon::InitStats(uint32 duration) if (owner->IsTotem()) owner->m_Controlled.insert(this); } - - // If property has a faction defined, use it. - if (m_Properties->Faction) - SetFaction(m_Properties->Faction); } void TempSummon::InitSummon() @@ -393,7 +384,7 @@ void Guardian::InitStats(uint32 duration) { Minion::InitStats(duration); - InitStatsForLevel(GetOwner()->getLevel()); + InitStatsForLevel(getLevel()); if (GetOwner()->GetTypeId() == TYPEID_PLAYER && HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) m_charmInfo->InitCharmCreateSpells(); @@ -423,7 +414,6 @@ Puppet::Puppet(SummonPropertiesEntry const* properties, Unit* owner) void Puppet::InitStats(uint32 duration) { Minion::InitStats(duration); - SetLevel(GetOwner()->getLevel()); SetReactState(REACT_PASSIVE); } diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index b72fc90d28..64160f582a 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2076,6 +2076,22 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonCreatur break; } + // Store special settings which have been extracted from spell effects/scripts + SummonInfoArgs args; + if (summonArgs.Summoner) + args.Summoner = summonArgs.Summoner; + if (summonArgs.SummonProperties) + args.SummonPropertiesId = summonArgs.SummonProperties->ID; + if (summonArgs.SummonDuration) + args.Duration = Milliseconds(summonArgs.SummonDuration); + if (summonArgs.CreatureLevel > 0) + args.CreatureLevel = summonArgs.CreatureLevel; + if (summonArgs.SummonHealth) + args.MaxHealth = summonArgs.SummonHealth; + + // Initialize the SummonInfo API which marks the creature as Summon + summon->InitializeSummonInfo(args); + // Create creature entity if (!summon->Create(GenerateLowGuid(), this, entry, pos, nullptr, summonArgs.VehicleRecID, true)) { @@ -2087,21 +2103,7 @@ 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); + summon->SetUInt32Value(UNIT_CREATED_BY_SPELL, summonArgs.SummonSpellId); TransportBase* transport = summonArgs.Summoner ? summonArgs.Summoner->GetTransport() : nullptr; if (transport) @@ -2115,23 +2117,10 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonCreatur transport->AddPassenger(summon); } - // Initialize tempsummon fields - summon->SetUInt32Value(UNIT_CREATED_BY_SPELL, summonArgs.SummonSpellId); summon->SetHomePosition(pos); summon->InitStats(summonArgs.SummonDuration); summon->SetPrivateObjectOwner(summonArgs.PrivateObjectOwner); - // Handle health argument - if (summonArgs.SummonHealth) - { - summon->SetMaxHealth(summonArgs.SummonHealth); - summon->SetHealth(summonArgs.SummonHealth); - } - - // Handle creature level argument - if (summonArgs.CreatureLevel) - summon->SetLevel(summonArgs.CreatureLevel); - if (!AddToMap(summon->ToCreature())) { // Returning false will cause the object to be deleted - remove from transport diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 41bde48c22..d6a6906a55 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -41,11 +41,15 @@ constexpr float PET_XP_FACTOR = 0.05f; Pet::Pet(Player* owner, PetType type) : Guardian(nullptr, owner, true), m_usedTalentCount(0), m_removed(false), m_petType(type), m_duration(0), m_auraRaidUpdateMask(0), m_loading(false), - m_declinedname(nullptr) + m_declinedname(nullptr), m_petSlot(0) { ASSERT(GetOwner()); - InitializeSummonInfo({ .SummonerGUID = GetOwner()->GetGUID()}); + InitializeSummonInfo( + { + .Summoner = GetOwner(), + .CreatureLevel = GetOwner()->getLevel() + }); m_unitTypeMask |= UNIT_MASK_PET; if (type == HUNTER_PET) diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 0edaf0cd33..7e0b73e7a8 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -144,7 +144,7 @@ inline bool CompareRespawnInfo::operator()(RespawnInfo const* a, RespawnInfo con struct TC_GAME_API SummonCreatureExtraArgs { public: - SummonCreatureExtraArgs() { } + SummonCreatureExtraArgs() = default; SummonCreatureExtraArgs& SetSummonDuration(uint32 duration) { SummonDuration = duration; return *this; } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index d95854faef..8e45b8bdca 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2010,7 +2010,7 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) SummonCreatureExtraArgs extraArgs; extraArgs.SummonProperties = properties; extraArgs.SummonDuration = duration; - extraArgs.Summoner = caster->IsUnit() ? caster->ToUnit() : nullptr; + extraArgs.Summoner = Object::ToUnit(caster); extraArgs.SummonSpellId = m_spellInfo->Id; extraArgs.PrivateObjectOwner = privateObjectOwner; @@ -2077,7 +2077,6 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) caster->CastSpell(summon, VEHICLE_SPELL_RIDE_HARDCODED, CastSpellExtraArgs(true).AddSpellBP0(extraArgs.SeatNumber)); else if (extraArgs.RideSpell) caster->CastSpell(summon, extraArgs.RideSpell, true); - } } }