From f97b86d65c2d6d0a4a2fed08a1e6b937b28b24a8 Mon Sep 17 00:00:00 2001 From: ccrs Date: Thu, 5 Feb 2026 22:18:52 +0100 Subject: [PATCH 1/3] Core/Entities: first attempt on trying to keep Guardians or Minions in combat after their owner's death --- .../Entities/Creature/TemporarySummon.cpp | 15 ++++---- .../game/Entities/Creature/TemporarySummon.h | 1 + src/server/game/Entities/Unit/Unit.cpp | 34 ++++++++++++++----- src/server/game/Entities/Unit/Unit.h | 2 +- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index bf6ad762..bbd8339d 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -278,7 +278,7 @@ void TempSummon::UnSummon(uint32 msTime) return; } - if (WorldObject * owner = GetSummoner()) + if (WorldObject* owner = GetSummoner()) { if (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled()) owner->ToCreature()->AI()->SummonedCreatureDespawn(this); @@ -354,8 +354,7 @@ std::string TempSummon::GetDebugInfo() const return sstr.str(); } -Minion::Minion(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject) - : TempSummon(properties, owner, isWorldObject), m_owner(owner) +Minion::Minion(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject) : TempSummon(properties, owner, isWorldObject), m_owner(owner) { ASSERT(m_owner); m_unitTypeMask |= UNIT_MASK_MINION; @@ -379,7 +378,9 @@ void Minion::RemoveFromWorld() if (!IsInWorld()) return; - GetOwner()->SetMinion(this, false); + if (GetOwner()) + GetOwner()->SetMinion(this, false); + TempSummon::RemoveFromWorld(); } @@ -419,8 +420,7 @@ std::string Minion::GetDebugInfo() const return sstr.str(); } -Guardian::Guardian(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject) : Minion(properties, owner, isWorldObject) -, m_bonusSpellDamage(0) +Guardian::Guardian(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject) : Minion(properties, owner, isWorldObject), m_bonusSpellDamage(0) { memset(m_statFromOwner, 0, sizeof(float)*MAX_STATS); m_unitTypeMask |= UNIT_MASK_GUARDIAN; @@ -462,8 +462,7 @@ std::string Guardian::GetDebugInfo() const return sstr.str(); } -Puppet::Puppet(SummonPropertiesEntry const* properties, Unit* owner) - : Minion(properties, owner, false) //maybe true? +Puppet::Puppet(SummonPropertiesEntry const* properties, Unit* owner) : Minion(properties, owner, false) //maybe true? { ASSERT(m_owner->GetTypeId() == TYPEID_PLAYER); m_unitTypeMask |= UNIT_MASK_PUPPET; diff --git a/src/server/game/Entities/Creature/TemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon.h index c59c2135..909195e6 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.h +++ b/src/server/game/Entities/Creature/TemporarySummon.h @@ -52,6 +52,7 @@ class TC_GAME_API TempSummon : public Creature ObjectGuid GetSummonerGUID() const { return m_summonerGUID; } TempSummonType GetSummonType() const { return m_type; } uint32 GetTimer() const { return m_timer; } + void SetTimer(uint32 timer) { m_timer = timer; } bool CanFollowOwner() const { return m_canFollowOwner; } void SetCanFollowOwner(bool can) { m_canFollowOwner = can; } bool IsFollowerDespawnActive() const { return _followerDespawnActive; } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5a7a2eba..9de41437 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -6251,28 +6251,44 @@ Unit* Unit::GetFirstControlled() const return unit; } -void Unit::RemoveAllControlled() +void Unit::RemoveAllControlled(bool onDeath/* = false*/) { // possessed pet and vehicle if (GetTypeId() == TYPEID_PLAYER) ToPlayer()->StopCastingCharm(); - while (!m_Controlled.empty()) + bool exception = false; + for (auto itr = m_Controlled.begin(); itr != m_Controlled.end();) { - Unit* target = *m_Controlled.begin(); - m_Controlled.erase(m_Controlled.begin()); + Unit* target = *itr; + itr = m_Controlled.erase(itr); if (target->GetCharmerGUID() == GetGUID()) target->RemoveCharmAuras(); else if (target->GetOwnerGUID() == GetGUID() && target->IsSummon()) - target->ToTempSummon()->UnSummon(); + { + TempSummon* summon = target->ToTempSummon(); + if (onDeath && summon->HasUnitTypeMask(UNIT_MASK_GUARDIAN | UNIT_MASK_MINION) && TempSummon::ShouldFollowOnSpawn(summon->m_Properties)) + { + if (summon->IsInCombat() && summon->CanHaveThreatList() && summon->GetCombatManager().HasPvECombatWithPlayers()) + { + summon->SetTimer(1000); + summon->SetTempSummonType(TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT); + exception = true; + } + else + summon->UnSummon(); + } + else + summon->UnSummon(); + } else TC_LOG_ERROR("entities.unit", "Unit {} is trying to release unit {} which is neither charmed nor owned by it", GetEntry(), target->GetEntry()); } - if (!GetPetGUID().IsEmpty()) + if (!exception && !GetPetGUID().IsEmpty()) TC_LOG_FATAL("entities.unit", "Unit {} is not able to release its pet {}", GetEntry(), GetPetGUID().ToString()); - if (!GetMinionGUID().IsEmpty()) + if (!exception && !GetMinionGUID().IsEmpty()) TC_LOG_FATAL("entities.unit", "Unit {} is not able to release its minion {}", GetEntry(), GetMinionGUID().ToString()); - if (!GetCharmedGUID().IsEmpty()) + if (!exception && !GetCharmedGUID().IsEmpty()) TC_LOG_FATAL("entities.unit", "Unit {} is not able to release its charm {}", GetEntry(), GetCharmedGUID().ToString()); if (!IsPet()) // pets don't use the flag for this RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat @@ -8745,7 +8761,7 @@ void Unit::setDeathState(DeathState s) // vehicles use special type of charm that is not removed by the next function // triggering an assert UnsummonAllTotems(); - RemoveAllControlled(); + RemoveAllControlled(true); RemoveAllAurasOnDeath(); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index d55ed801..fd8d9c48 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1279,7 +1279,7 @@ class TC_GAME_API Unit : public WorldObject ControlList m_Controlled; Unit* GetFirstControlled() const; - void RemoveAllControlled(); + void RemoveAllControlled(bool onDeath = false); bool IsCharmed() const { return !GetCharmerGUID().IsEmpty(); } bool IsCharming() const { return !GetCharmedGUID().IsEmpty(); } From b3ed41864c3332a6e24076de8cee3bdd6e27271e Mon Sep 17 00:00:00 2001 From: ccrs Date: Thu, 5 Feb 2026 22:30:32 +0100 Subject: [PATCH 2/3] Core/Entities: include SUMMON_SLOT_PET in this new special summon handling... --- src/server/game/Entities/Unit/Unit.cpp | 26 ++++++++++++++++++++------ src/server/game/Entities/Unit/Unit.h | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 9de41437..a4c47b2a 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -6267,7 +6267,7 @@ void Unit::RemoveAllControlled(bool onDeath/* = false*/) else if (target->GetOwnerGUID() == GetGUID() && target->IsSummon()) { TempSummon* summon = target->ToTempSummon(); - if (onDeath && summon->HasUnitTypeMask(UNIT_MASK_GUARDIAN | UNIT_MASK_MINION) && TempSummon::ShouldFollowOnSpawn(summon->m_Properties)) + if (onDeath && GetTypeId() == TYPEID_UNIT && summon->HasUnitTypeMask(UNIT_MASK_GUARDIAN | UNIT_MASK_MINION) && TempSummon::ShouldFollowOnSpawn(summon->m_Properties)) { if (summon->IsInCombat() && summon->CanHaveThreatList() && summon->GetCombatManager().HasPvECombatWithPlayers()) { @@ -6406,16 +6406,30 @@ void Unit::RemoveCharmAuras() RemoveAurasByType(SPELL_AURA_AOE_CHARM); } -void Unit::UnsummonAllTotems() +void Unit::UnsummonAllTotems(bool onDeath/* = false*/) { for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i) { if (!m_SummonSlot[i]) continue; - if (Creature* OldTotem = GetMap()->GetCreature(m_SummonSlot[i])) - if (OldTotem->IsSummon()) - OldTotem->ToTempSummon()->UnSummon(); + if (Creature* summonCreature = GetMap()->GetCreature(m_SummonSlot[i])) + if (summonCreature->IsSummon()) + { + TempSummon* summon = summonCreature->ToTempSummon(); + if (onDeath && GetTypeId() == TYPEID_UNIT && i == SUMMON_SLOT_PET && summon->HasUnitTypeMask(UNIT_MASK_GUARDIAN | UNIT_MASK_MINION) && TempSummon::ShouldFollowOnSpawn(summon->m_Properties)) + { + if (summon->IsInCombat() && summon->CanHaveThreatList() && summon->GetCombatManager().HasPvECombatWithPlayers()) + { + summon->SetTimer(1000); + summon->SetTempSummonType(TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT); + } + else + summon->UnSummon(); + } + else + summon->UnSummon(); + } } } @@ -8760,7 +8774,7 @@ void Unit::setDeathState(DeathState s) ExitVehicle(); // Exit vehicle before calling RemoveAllControlled // vehicles use special type of charm that is not removed by the next function // triggering an assert - UnsummonAllTotems(); + UnsummonAllTotems(true); RemoveAllControlled(true); RemoveAllAurasOnDeath(); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index fd8d9c48..1cbe3e85 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1609,7 +1609,7 @@ class TC_GAME_API Unit : public WorldObject void ModifyAuraState(AuraStateType flag, bool apply); uint32 BuildAuraStateUpdateForTarget(Unit const* target) const; bool HasAuraState(AuraStateType flag, SpellInfo const* spellProto = nullptr, Unit const* Caster = nullptr) const; - void UnsummonAllTotems(); + void UnsummonAllTotems(bool onDeath = false); bool IsMagnet() const; Unit* GetMeleeHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo = nullptr); From d69e760f0bb218f6038bde32d64954e4add49677 Mon Sep 17 00:00:00 2001 From: ccrs Date: Thu, 5 Feb 2026 23:05:54 +0100 Subject: [PATCH 3/3] Core/Entities: fix TempSummon timer override --- src/server/game/Entities/Creature/TemporarySummon.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Entities/Creature/TemporarySummon.h b/src/server/game/Entities/Creature/TemporarySummon.h index 909195e6..88626385 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.h +++ b/src/server/game/Entities/Creature/TemporarySummon.h @@ -52,7 +52,7 @@ class TC_GAME_API TempSummon : public Creature ObjectGuid GetSummonerGUID() const { return m_summonerGUID; } TempSummonType GetSummonType() const { return m_type; } uint32 GetTimer() const { return m_timer; } - void SetTimer(uint32 timer) { m_timer = timer; } + void SetTimer(uint32 timer) { m_timer = timer; m_lifetime = timer; } bool CanFollowOwner() const { return m_canFollowOwner; } void SetCanFollowOwner(bool can) { m_canFollowOwner = can; } bool IsFollowerDespawnActive() const { return _followerDespawnActive; }