diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 72a0ac417..bf7a0b18e 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -1323,10 +1323,21 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) { - if (IsCreature(*itr)) - (*itr)->ToCreature()->DespawnOrUnsummon(e.action.forceDespawn.delay + 1); - else if (IsGameObject(*itr)) - (*itr)->ToGameObject()->Delete(); + if (Creature* creature = (*itr)->ToCreature()) + { + creature->DespawnOrUnsummon(e.action.forceDespawn.delay + 1); + } + else if (GameObject* go = (*itr)->ToGameObject()) + { + Milliseconds despawnDelay(e.action.forceDespawn.delay); + + // Wait at least one world update tick before despawn, so it doesn't break linked actions. + if (despawnDelay <= 0ms) + { + despawnDelay = 1ms; + } + go->DespawnOrUnsummon(despawnDelay); + } } delete targets; diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index c330c4876..65da670a5 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -53,6 +53,8 @@ GameObject::GameObject() : WorldObject(false), MovableMapObject(), m_valuesCount = GAMEOBJECT_END; m_respawnTime = 0; m_respawnDelayTime = 300; + m_despawnDelay = 0; + m_despawnRespawnTime = 0s; m_lootState = GO_NOT_READY; m_spawnedByDefault = true; m_allowModifyDestructibleBuilding = true; @@ -399,6 +401,19 @@ void GameObject::Update(uint32 diff) else if (!AIM_Initialize()) LOG_ERROR("entities.gameobject", "Could not initialize GameObjectAI"); + if (m_despawnDelay) + { + if (m_despawnDelay > diff) + { + m_despawnDelay -= diff; + } + else + { + m_despawnDelay = 0; + DespawnOrUnsummon(0ms, m_despawnRespawnTime); + } + } + switch (m_lootState) { case GO_NOT_READY: @@ -811,6 +826,47 @@ void GameObject::AddUniqueUse(Player* player) m_unique_users.insert(player->GetGUID()); } +void GameObject::DespawnOrUnsummon(Milliseconds delay, Seconds forceRespawnTime) +{ + if (delay > 0ms) + { + if (!m_despawnDelay || m_despawnDelay > delay.count()) + { + m_despawnDelay = delay.count(); + m_despawnRespawnTime = forceRespawnTime; + } + } + else + { + if (m_goData) + { + int32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_goData->spawntimesecs; + SetRespawnTime(respawnDelay); + } + + // Respawn is handled by the gameobject itself. + // If we delete it from world, it simply never respawns... + // Uncomment this and remove the following lines if dynamic spawn is implemented. + // Delete(); + { + SetLootState(GO_JUST_DEACTIVATED); + SendObjectDeSpawnAnim(GetGUID()); + SetGoState(GO_STATE_READY); + + if (GameObjectTemplateAddon const* addon = GetTemplateAddon()) + { + SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags); + } + + uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool(m_spawnId) : 0; + if (poolid) + { + sPoolMgr->UpdatePool(poolid, m_spawnId); + } + } + } +} + void GameObject::Delete() { SetLootState(GO_NOT_READY); @@ -1075,10 +1131,13 @@ Unit* GameObject::GetOwner() const return ObjectAccessor::GetUnit(*this, GetOwnerGUID()); } -void GameObject::SaveRespawnTime() +void GameObject::SaveRespawnTime(uint32 forceDelay) { - if (m_goData && m_goData->dbData && m_respawnTime > time(nullptr) && m_spawnedByDefault) - GetMap()->SaveGORespawnTime(m_spawnId, m_respawnTime); + if (m_goData && m_goData->dbData && (forceDelay || m_respawnTime > time(nullptr)) && m_spawnedByDefault) + { + time_t respawnTime = forceDelay ? time(nullptr) + forceDelay : m_respawnTime; + GetMap()->SaveGORespawnTime(m_spawnId, respawnTime); + } } bool GameObject::IsNeverVisible() const @@ -1132,6 +1191,16 @@ bool GameObject::IsInvisibleDueToDespawn() const return false; } +void GameObject::SetRespawnTime(int32 respawn) +{ + m_respawnTime = respawn > 0 ? time(nullptr) + respawn : 0; + m_respawnDelayTime = respawn > 0 ? respawn : 0; + if (respawn && !m_spawnedByDefault) + { + UpdateObjectVisibility(true); + } +} + void GameObject::Respawn() { if (m_spawnedByDefault && m_respawnTime > 0) diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 527185ce5..c2e303347 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -810,11 +810,7 @@ public: return now; } - void SetRespawnTime(int32 respawn) - { - m_respawnTime = respawn > 0 ? time(nullptr) + respawn : 0; - m_respawnDelayTime = respawn > 0 ? respawn : 0; - } + void SetRespawnTime(int32 respawn); void Respawn(); [[nodiscard]] bool isSpawned() const { @@ -826,6 +822,7 @@ public: void SetSpawnedByDefault(bool b) { m_spawnedByDefault = b; } [[nodiscard]] uint32 GetRespawnDelay() const { return m_respawnDelayTime; } void Refresh(); + void DespawnOrUnsummon(Milliseconds delay = 0ms, Seconds forcedRespawnTime = 0s); void Delete(); void GetFishLoot(Loot* loot, Player* loot_owner); void GetFishLootJunk(Loot* loot, Player* loot_owner); @@ -872,7 +869,8 @@ public: [[nodiscard]] uint32 GetUseCount() const { return m_usetimes; } [[nodiscard]] uint32 GetUniqueUseCount() const { return m_unique_users.size(); } - void SaveRespawnTime() override; + void SaveRespawnTime() override { SaveRespawnTime(0); } + void SaveRespawnTime(uint32 forceDelay); Loot loot; @@ -985,6 +983,8 @@ protected: uint32 m_spellId; time_t m_respawnTime; // (secs) time of next respawn (or despawn if GO have owner()), uint32 m_respawnDelayTime; // (secs) if 0 then current GO state no dependent from timer + uint32 m_despawnDelay; + Seconds m_despawnRespawnTime; // override respawn time after delayed despawn LootState m_lootState; bool m_spawnedByDefault; uint32 m_cooldownTime; // used as internal reaction delay time store (not state change reaction).