/* * 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 "TemporarySummon.h" #include "CellImpl.h" #include "CharmInfo.h" #include "CreatureAI.h" #include "DB2Stores.h" #include "GameObject.h" #include "GameObjectAI.h" #include "GridNotifiers.h" #include "Log.h" #include "Map.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Pet.h" #include "Player.h" #include "SmoothPhasing.h" #include "SpellMgr.h" #include #include TempSummon::TempSummon(SummonPropertiesEntry const* properties, WorldObject* owner, bool isWorldObject) : Creature(isWorldObject), m_Properties(properties), m_type(TEMPSUMMON_MANUAL_DESPAWN), m_timer(0ms), m_lifetime(0ms), m_canFollowOwner(true) { if (owner) m_summonerGUID = owner->GetGUID(); m_unitTypeMask |= UNIT_MASK_SUMMON; } TempSummon::~TempSummon() = default; WorldObject* TempSummon::GetSummoner() const { return !m_summonerGUID.IsEmpty() ? ObjectAccessor::GetWorldObject(*this, m_summonerGUID) : nullptr; } Unit* TempSummon::GetSummonerUnit() const { if (WorldObject* summoner = GetSummoner()) return summoner->ToUnit(); return nullptr; } Creature* TempSummon::GetSummonerCreatureBase() const { return !m_summonerGUID.IsEmpty() ? ObjectAccessor::GetCreature(*this, m_summonerGUID) : nullptr; } GameObject* TempSummon::GetSummonerGameObject() const { if (WorldObject* summoner = GetSummoner()) return summoner->ToGameObject(); return nullptr; } void TempSummon::Update(uint32 diff) { Creature::Update(diff); if (m_deathState == DEAD) { UnSummon(); return; } Milliseconds msDiff = Milliseconds(diff); switch (m_type) { case TEMPSUMMON_MANUAL_DESPAWN: case TEMPSUMMON_DEAD_DESPAWN: break; case TEMPSUMMON_TIMED_DESPAWN: { if (m_timer <= msDiff) { UnSummon(); return; } m_timer -= msDiff; break; } case TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT: { if (!IsInCombat()) { if (m_timer <= msDiff) { UnSummon(); return; } m_timer -= msDiff; } else if (m_timer != m_lifetime) m_timer = m_lifetime; break; } case TEMPSUMMON_CORPSE_TIMED_DESPAWN: { if (m_deathState == CORPSE) { if (m_timer <= msDiff) { UnSummon(); return; } m_timer -= msDiff; } break; } case TEMPSUMMON_CORPSE_DESPAWN: { // if m_deathState is DEAD, CORPSE was skipped if (m_deathState == CORPSE) { UnSummon(); return; } break; } case TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN: { if (m_deathState == CORPSE) { UnSummon(); return; } if (!IsInCombat()) { if (m_timer <= msDiff) { UnSummon(); return; } else m_timer -= msDiff; } else if (m_timer != m_lifetime) m_timer = m_lifetime; break; } case TEMPSUMMON_TIMED_OR_DEAD_DESPAWN: { if (!IsInCombat() && IsAlive()) { if (m_timer <= msDiff) { UnSummon(); return; } else m_timer -= msDiff; } else if (m_timer != m_lifetime) m_timer = m_lifetime; break; } default: UnSummon(); TC_LOG_ERROR("entities.unit", "Temporary summoned creature (entry: {}) have unknown type {} of ", GetEntry(), m_type); break; } } void TempSummon::InitStats(WorldObject* summoner, Milliseconds duration) { ASSERT(!IsPet()); m_timer = duration; m_lifetime = duration; if (m_type == TEMPSUMMON_MANUAL_DESPAWN) { if (duration <= 0s) m_type = TEMPSUMMON_DEAD_DESPAWN; else if (m_Properties && m_Properties->GetFlags().HasFlag(SummonPropertiesFlags::UseDemonTimeout)) m_type = TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT; else m_type = TEMPSUMMON_TIMED_DESPAWN; } if (summoner && summoner->IsPlayer()) { if (IsTrigger() && m_spells[0]) m_ControlledByPlayer = true; if (CreatureSummonedData const* summonedData = sObjectMgr->GetCreatureSummonedData(GetEntry())) { m_creatureIdVisibleToSummoner = summonedData->CreatureIDVisibleToSummoner; if (summonedData->CreatureIDVisibleToSummoner) { CreatureTemplate const* creatureTemplateVisibleToSummoner = ASSERT_NOTNULL(sObjectMgr->GetCreatureTemplate(*summonedData->CreatureIDVisibleToSummoner)); m_displayIdVisibleToSummoner = ObjectMgr::ChooseDisplayId(creatureTemplateVisibleToSummoner, nullptr)->CreatureDisplayID; } } } if (!m_Properties) return; if (Unit* unitSummoner = ToUnit(summoner)) { std::ptrdiff_t slot = m_Properties->Slot; if (slot == SUMMON_SLOT_ANY_TOTEM) slot = FindUsableTotemSlot(unitSummoner); if (slot != 0) { if (!unitSummoner->m_SummonSlot[slot].IsEmpty() && unitSummoner->m_SummonSlot[slot] != GetGUID()) { Creature* oldSummon = GetMap()->GetCreature(unitSummoner->m_SummonSlot[slot]); if (oldSummon && oldSummon->IsSummon()) oldSummon->ToTempSummon()->UnSummon(); } unitSummoner->m_SummonSlot[slot] = GetGUID(); } if (!m_Properties->GetFlags().HasFlag(SummonPropertiesFlags::UseCreatureLevel)) { int32 minLevel = m_unitData->ScalingLevelMin + m_unitData->ScalingLevelDelta; int32 maxLevel = m_unitData->ScalingLevelMax + m_unitData->ScalingLevelDelta; uint8 level = std::clamp(unitSummoner->GetLevel(), minLevel, maxLevel); SetLevel(level); } } uint32 faction = m_Properties->Faction; if (summoner && m_Properties->GetFlags().HasFlag(SummonPropertiesFlags::UseSummonerFaction)) // TODO: Determine priority between faction and flag faction = summoner->GetFaction(); if (faction) SetFaction(faction); if (m_Properties->GetFlags().HasFlag(SummonPropertiesFlags::SummonFromBattlePetJournal)) RemoveNpcFlag(UNIT_NPC_FLAG_WILD_BATTLE_PET); } void TempSummon::InitSummon(WorldObject* summoner) { if (summoner) { if (summoner->GetTypeId() == TYPEID_UNIT) { if (summoner->ToCreature()->IsAIEnabled()) summoner->ToCreature()->AI()->JustSummoned(this); } else if (summoner->GetTypeId() == TYPEID_GAMEOBJECT) { if (summoner->ToGameObject()->AI()) summoner->ToGameObject()->AI()->JustSummoned(this); } if (IsAIEnabled()) AI()->IsSummonedBy(summoner); } } void TempSummon::UpdateObjectVisibilityOnCreate() { boost::container::small_vector objectsToUpdate; objectsToUpdate.push_back(this); if (SmoothPhasing const* smoothPhasing = GetSmoothPhasing()) { SmoothPhasingInfo const* infoForSeer = smoothPhasing->GetInfoForSeer(GetDemonCreatorGUID()); if (infoForSeer && infoForSeer->ReplaceObject && smoothPhasing->IsReplacing(*infoForSeer->ReplaceObject)) if (WorldObject* original = ObjectAccessor::GetWorldObject(*this, *infoForSeer->ReplaceObject)) objectsToUpdate.push_back(original); } Trinity::VisibleChangesNotifier notifier({ objectsToUpdate.data(), objectsToUpdate.data() + objectsToUpdate.size() }); Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); } void TempSummon::UpdateObjectVisibilityOnDestroy() { boost::container::small_vector objectsToUpdate; objectsToUpdate.push_back(this); WorldObject* original = nullptr; if (SmoothPhasing const* smoothPhasing = GetSmoothPhasing()) { SmoothPhasingInfo const* infoForSeer = smoothPhasing->GetInfoForSeer(GetDemonCreatorGUID()); if (infoForSeer && infoForSeer->ReplaceObject && smoothPhasing->IsReplacing(*infoForSeer->ReplaceObject)) original = ObjectAccessor::GetWorldObject(*this, *infoForSeer->ReplaceObject); if (original) { objectsToUpdate.push_back(original); // disable replacement without removing - it is still needed for next step (visibility update) if (SmoothPhasing* originalSmoothPhasing = original->GetSmoothPhasing()) originalSmoothPhasing->DisableReplacementForSeer(GetDemonCreatorGUID()); } } Trinity::VisibleChangesNotifier notifier({ objectsToUpdate.data(), objectsToUpdate.data() + objectsToUpdate.size() }); Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); if (original) // original is only != nullptr when it was replaced if (SmoothPhasing* originalSmoothPhasing = original->GetSmoothPhasing()) originalSmoothPhasing->ClearViewerDependentInfo(GetDemonCreatorGUID()); } void TempSummon::SetTempSummonType(TempSummonType type) { m_type = type; } void TempSummon::UnSummon(uint32 msTime) { if (msTime) { m_Events.AddEventAtOffset(new ForcedDespawnDelayEvent(*this, 0s), Milliseconds(msTime)); return; } //ASSERT(!IsPet()); if (IsPet()) { ToPet()->Remove(PET_SAVE_NOT_IN_SLOT); ASSERT(!IsInWorld()); return; } if (WorldObject * owner = GetSummoner()) { if (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsAIEnabled()) owner->ToCreature()->AI()->SummonedCreatureDespawn(this); else if (owner->GetTypeId() == TYPEID_GAMEOBJECT && owner->ToGameObject()->AI()) owner->ToGameObject()->AI()->SummonedCreatureDespawn(this); } AddObjectToRemoveList(); } void TempSummon::RemoveFromWorld() { if (!IsInWorld()) return; if (m_Properties && m_Properties->Slot != 0) if (Unit* owner = GetSummonerUnit()) for (ObjectGuid& summonSlot : owner->m_SummonSlot) if (summonSlot == GetGUID()) summonSlot.Clear(); //if (GetOwnerGUID()) // TC_LOG_ERROR("entities.unit", "Unit {} has owner guid when removed from world", GetEntry()); Creature::RemoveFromWorld(); } std::ptrdiff_t TempSummon::FindUsableTotemSlot(Unit const* summoner) const { auto totemBegin = summoner->m_SummonSlot.begin() + SUMMON_SLOT_TOTEM; auto totemEnd = summoner->m_SummonSlot.begin() + MAX_TOTEM_SLOT; // first try exact guid match auto totemSlot = std::find_if(totemBegin, totemEnd, [&](ObjectGuid const& otherTotemGuid) { return otherTotemGuid == GetGUID(); }); // then a slot that shares totem category with this new summon if (totemSlot == totemEnd) totemSlot = std::find_if(totemBegin, totemEnd, [&](ObjectGuid const& otherTotemGuid) { return IsSharingTotemSlotWith(otherTotemGuid); }); // any empty slot...? if (totemSlot == totemEnd) totemSlot = std::find_if(totemBegin, totemEnd, [](ObjectGuid const& otherTotemGuid) { return otherTotemGuid.IsEmpty(); }); // if no usable slot was found, try used slot by a summon with the same creature id // we must not despawn unrelated summons if (totemSlot == totemEnd) totemSlot = std::find_if(totemBegin, totemEnd, [&](ObjectGuid const& otherTotemGuid) { return GetEntry() == otherTotemGuid.GetEntry(); }); // if no slot was found, this summon gets no slot and will not be stored in m_SummonSlot if (totemSlot == totemEnd) return 0; return totemSlot - summoner->m_SummonSlot.begin(); } bool TempSummon::IsSharingTotemSlotWith(ObjectGuid objectGuid) const { Creature const* otherSummon = GetMap()->GetCreature(objectGuid); if (!otherSummon) return false; SpellInfo const* mySummonSpell = sSpellMgr->GetSpellInfo(m_unitData->CreatedBySpell, DIFFICULTY_NONE); if (!mySummonSpell) return false; SpellInfo const* otherSummonSpell = sSpellMgr->GetSpellInfo(otherSummon->m_unitData->CreatedBySpell, DIFFICULTY_NONE); if (!otherSummonSpell) return false; for (uint16 myTotemCategory : mySummonSpell->TotemCategory) if (myTotemCategory) for (uint16 otherTotemCategory : otherSummonSpell->TotemCategory) if (otherTotemCategory && DB2Manager::IsTotemCategoryCompatibleWith(myTotemCategory, otherTotemCategory, false)) return true; for (int32 myTotemId : mySummonSpell->Totem) if (myTotemId) for (int32 otherTotemId : otherSummonSpell->Totem) if (otherTotemId && myTotemId == otherTotemId) return true; return false; } std::string TempSummon::GetDebugInfo() const { std::stringstream sstr; sstr << Creature::GetDebugInfo() << "\n" << std::boolalpha << "TempSummonType: " << std::to_string(GetSummonType()) << " Summoner: " << GetSummonerGUID().ToString() << "Timer: " << GetTimer().count() << "ms"; return sstr.str(); } Minion::Minion(SummonPropertiesEntry const* properties, Unit* owner, bool isWorldObject) : TempSummon(properties, owner, isWorldObject), m_owner(owner) { ASSERT(m_owner); m_unitTypeMask |= UNIT_MASK_MINION; m_followAngle = PET_FOLLOW_ANGLE; /// @todo: Find correct way InitCharmInfo(); } void Minion::InitStats(WorldObject* summoner, Milliseconds duration) { TempSummon::InitStats(summoner, duration); SetReactState(REACT_PASSIVE); SetCreatorGUID(GetOwner()->GetGUID()); SetFaction(GetOwner()->GetFaction()); // TODO: Is this correct? Overwrite the use of SummonPropertiesFlags::UseSummonerFaction GetOwner()->SetMinion(this, true); } void Minion::RemoveFromWorld() { if (!IsInWorld()) return; GetOwner()->SetMinion(this, false); TempSummon::RemoveFromWorld(); } void Minion::setDeathState(DeathState s) { Creature::setDeathState(s); if (s != JUST_DIED || !IsGuardianPet()) return; Unit* owner = GetOwner(); if (!owner || owner->GetTypeId() != TYPEID_PLAYER || owner->GetMinionGUID() != GetGUID()) return; for (Unit* controlled : owner->m_Controlled) { if (controlled->GetEntry() == GetEntry() && controlled->IsAlive()) { owner->SetMinionGUID(controlled->GetGUID()); owner->SetPetGUID(controlled->GetGUID()); owner->ToPlayer()->CharmSpellInitialize(); break; } } } bool Minion::IsGuardianPet() const { return IsPet() || (m_Properties && m_Properties->Control == SUMMON_CATEGORY_PET); } std::string Minion::GetDebugInfo() const { std::stringstream sstr; sstr << TempSummon::GetDebugInfo() << "\n" << std::boolalpha << "Owner: " << (GetOwner() ? GetOwner()->GetGUID().ToString() : ""); return sstr.str(); } 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; if (properties && (SummonTitle(properties->Title) == SummonTitle::Pet || properties->Control == SUMMON_CATEGORY_PET)) { m_unitTypeMask |= UNIT_MASK_CONTROLABLE_GUARDIAN; InitCharmInfo(); } } void Guardian::InitStats(WorldObject* summoner, Milliseconds duration) { Minion::InitStats(summoner, duration); InitStatsForLevel(GetLevel()); // level is already initialized in TempSummon::InitStats, so use that if (GetOwner()->GetTypeId() == TYPEID_PLAYER && HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) m_charmInfo->InitCharmCreateSpells(); SetReactState(REACT_AGGRESSIVE); } void Guardian::InitSummon(WorldObject* summoner) { TempSummon::InitSummon(summoner); if (GetOwner()->GetTypeId() == TYPEID_PLAYER && GetOwner()->GetMinionGUID() == GetGUID() && !GetOwner()->GetCharmedGUID()) { GetOwner()->ToPlayer()->CharmSpellInitialize(); } } std::string Guardian::GetDebugInfo() const { std::stringstream sstr; sstr << Minion::GetDebugInfo(); return sstr.str(); } Puppet::Puppet(SummonPropertiesEntry const* properties, Unit* owner) : Minion(properties, owner, false) //maybe true? { ASSERT(m_owner->GetTypeId() == TYPEID_PLAYER); m_unitTypeMask |= UNIT_MASK_PUPPET; } void Puppet::InitStats(WorldObject* summoner, Milliseconds duration) { Minion::InitStats(summoner, duration); SetReactState(REACT_PASSIVE); } void Puppet::InitSummon(WorldObject* summoner) { Minion::InitSummon(summoner); if (!SetCharmedBy(GetOwner(), CHARM_TYPE_POSSESS)) ABORT(); } void Puppet::Update(uint32 time) { Minion::Update(time); //check if caster is channelling? if (IsInWorld()) { if (!IsAlive()) { UnSummon(); /// @todo why long distance .die does not remove it } } }