From d32daab96905db192693c105e1bcff337ba6850b Mon Sep 17 00:00:00 2001 From: UltraNix <80540499+UltraNix@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:44:44 +0100 Subject: [PATCH] =?UTF-8?q?fix(Core/GameObject):=20Implement=20restock=20m?= =?UTF-8?q?echanic=20for=20non-consumable=20gameob=E2=80=A6=20(#13950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core/GameObject: Implement restock mechanic for non-consumable gameobjects. Don't allow non-consumable goobers/chests to despawn on use. Source: TrinityCore. Fixes #13909 --- .../game/Entities/GameObject/GameObject.cpp | 52 +++++++++++++++---- .../game/Entities/GameObject/GameObject.h | 3 +- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 81b823b62..e801d7a2f 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -52,6 +52,7 @@ GameObject::GameObject() : WorldObject(false), MovableMapObject(), m_respawnDelayTime = 300; m_despawnDelay = 0; m_despawnRespawnTime = 0s; + m_restockTime = 0s; m_lootState = GO_NOT_READY; m_spawnedByDefault = true; m_allowModifyDestructibleBuilding = true; @@ -585,6 +586,16 @@ void GameObject::Update(uint32 diff) spellCaster->CastSpell(spellCaster, spellId, triggered); return; } + case GAMEOBJECT_TYPE_CHEST: + if (m_restockTime > GameTime::GetGameTime()) + { + return; + } + // If there is no restock timer, or if the restock timer passed, the chest becomes ready to loot + m_restockTime = 0s; + m_lootState = GO_READY; + AddToObjectUpdateIfNeeded(); + break; default: m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY break; @@ -768,6 +779,14 @@ void GameObject::Update(uint32 diff) m_groupLootTimer -= diff; } } + + // Non-consumable chest was partially looted and restock time passed, restock all loot now + if (GetGOInfo()->chest.consumable == 0 && GameTime::GetGameTime() >= m_restockTime) + { + m_restockTime = 0s; + m_lootState = GO_READY; + AddToObjectUpdateIfNeeded(); + } break; case GAMEOBJECT_TYPE_TRAP: { @@ -826,21 +845,29 @@ void GameObject::Update(uint32 diff) loot.clear(); - //! If this is summoned by a spell with ie. SPELL_EFFECT_SUMMON_OBJECT_WILD, with or without owner, we check respawn criteria based on spell - //! The GetOwnerGUID() check is mostly for compatibility with hacky scripts - 99% of the time summoning should be done trough spells. - if (GetSpellId() || GetOwnerGUID()) + // Do not delete chests or goobers that are not consumed on loot, while still allowing them to despawn when they expire if summoned + bool isSummonedAndExpired = (GetOwner() || GetSpellId()) && m_respawnTime == 0; + if ((GetGoType() == GAMEOBJECT_TYPE_CHEST || GetGoType() == GAMEOBJECT_TYPE_GOOBER) && !GetGOInfo()->IsDespawnAtAction() && !isSummonedAndExpired) { - //Don't delete spell spawned chests, which are not consumed on loot - if (m_respawnTime > 0 && GetGoType() == GAMEOBJECT_TYPE_CHEST && !GetGOInfo()->IsDespawnAtAction()) + if (GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.chestRestockTime > 0) { - UpdateObjectVisibility(); - SetLootState(GO_READY); + // Start restock timer when the chest is fully looted + m_restockTime = GameTime::GetGameTime() + Seconds(GetGOInfo()->chest.chestRestockTime); + SetLootState(GO_NOT_READY); + AddToObjectUpdateIfNeeded(); } else { - SetRespawnTime(0); - Delete(); + SetLootState(GO_READY); } + + UpdateObjectVisibility(); + return; + } + else if (GetOwnerGUID() || GetSpellId()) + { + SetRespawnTime(0); + Delete(); return; } @@ -2421,6 +2448,13 @@ void GameObject::SetLootState(LootState state, Unit* unit) AI()->OnStateChanged(state, unit); sScriptMgr->OnGameObjectLootStateChanged(this, state, unit); + + // Start restock timer if the chest is partially looted or not looted at all + if (GetGoType() == GAMEOBJECT_TYPE_CHEST && state == GO_ACTIVATED && GetGOInfo()->chest.chestRestockTime > 0 && m_restockTime == 0s) + { + m_restockTime = GameTime::GetGameTime() + Seconds(GetGOInfo()->chest.chestRestockTime); + } + // pussywizard: lootState has nothing to do with collision, it depends entirely on GOState. Loot state is for timed close/open door and respawning, which then sets GOState /*if (m_model) { diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 458189d1a..0360820ba 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -1071,9 +1071,10 @@ protected: 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 + Seconds m_restockTime; LootState m_lootState; bool m_spawnedByDefault; - uint32 m_cooldownTime; // used as internal reaction delay time store (not state change reaction). + uint32 m_cooldownTime; // used as internal reaction delay time store (not state change reaction). // For traps this: spell casting cooldown, for doors/buttons: reset time. std::unordered_map m_SkillupList;