fix(Core/Maps): keep bosses of DONE encounters down on the dynamic respawn path (#27788)

Co-authored-by: Gultask <[email protected]>
This commit is contained in:
Andrew
2026-09-25 22:34:58 -03:00
committed by GitHub
co-authored by Gultask
parent 7c87b145a1
commit eeab5f408d
8 changed files with 53 additions and 12 deletions
@@ -618,7 +618,11 @@ BossAI::BossAI(Creature* creature, uint32 bossId) : ScriptedAI(creature),
{
callForHelpRange = 0.0f;
if (instance)
{
SetBoundary(instance->GetBossBoundary(bossId));
if (creature->GetSpawnId())
instance->RegisterBossSpawn(creature->GetSpawnId(), bossId);
}
// Prevents updating the scheduler's timer while the creature is casting.
// Clear it in the script if you need it to update while the creature is casting.
@@ -2154,8 +2154,13 @@ void Creature::Respawn(bool force)
if (m_spawnId)
{
// Set respawn time to now so ProcessRespawns() picks it up
time_t now = GameTime::GetGameTime().count();
GetMap()->SaveCreatureRespawnTime(m_spawnId, now);
if (force)
GetMap()->ForceCreatureRespawn(m_spawnId);
else
{
time_t now = GameTime::GetGameTime().count();
GetMap()->SaveCreatureRespawnTime(m_spawnId, now);
}
}
AddObjectToRemoveList();
}
@@ -360,10 +360,12 @@ void InstanceSaveMgr::LoadResetTimes()
// calculate the next reset time
t = (t / DAY) * DAY;
t += ((today - t) / period + 1) * period + diff;
SetResetTimeFor(mapid, difficulty, t);
CharacterDatabase.DirectExecute("UPDATE instance_reset SET resettime = '{}' WHERE mapid = '{}' AND difficulty = '{}'", (uint32)t, mapid, difficulty);
}
SetExtendedResetTimeFor(mapid, difficulty, t);
// An extended lock runs one period past the reset, as _ResetOrWarnAll sets it after a runtime reset
SetExtendedResetTimeFor(mapid, difficulty, t + period);
// schedule the global reset/warning
uint8 type;
@@ -87,6 +87,12 @@ void InstanceScript::OnCreatureRemove(Creature* creature)
RemoveMinion(creature);
}
bool InstanceScript::IsBossSpawnDone(ObjectGuid::LowType spawnId) const
{
auto itr = _bossSpawns.find(spawnId);
return itr != _bossSpawns.end() && GetBossState(itr->second) == DONE;
}
void InstanceScript::OnGameObjectCreate(GameObject* go)
{
AddObject(go);
@@ -250,6 +250,9 @@ public:
virtual bool SetBossState(uint32 id, EncounterState state);
EncounterState GetBossState(uint32 id) const { return id < bosses.size() ? bosses[id].state : TO_BE_DECIDED; }
// Spawns a BossAI bound to an encounter; the respawn system keeps them down while it is DONE
void RegisterBossSpawn(ObjectGuid::LowType spawnId, uint32 bossId) { _bossSpawns[spawnId] = bossId; }
bool IsBossSpawnDone(ObjectGuid::LowType spawnId) const;
static std::string GetBossStateName(uint8 state);
CreatureBoundary const* GetBossBoundary(uint32 id) const { return id < bosses.size() ? &bosses[id].boundary : nullptr; }
BossInfo const* GetBossInfo(uint32 id) const { return &bosses[id]; }
@@ -351,6 +354,7 @@ private:
DoorInfoMap doors;
MinionInfoMap minions;
ObjectInfoMap _creatureInfo;
std::unordered_map<ObjectGuid::LowType, uint32 /*bossId*/> _bossSpawns;
ObjectInfoMap _gameObjectInfo;
ObjectInfoMap _summonInfo;
ObjectGuidMap _objectGuids;
+24 -3
View File
@@ -2519,6 +2519,13 @@ void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType spawnId)
CharacterDatabase.Execute(stmt);
}
void Map::ForceCreatureRespawn(ObjectGuid::LowType spawnId)
{
time_t now = GameTime::GetGameTime().count();
SaveCreatureRespawnTime(spawnId, now);
_forcedCreatureRespawns.insert(spawnId);
}
void Map::SaveGORespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTime)
{
if (!respawnTime)
@@ -2810,6 +2817,9 @@ void Map::ProcessRespawns()
void Map::ProcessCreatureRespawn(ObjectGuid::LowType spawnId)
{
// Consumed by every attempt, or a mark set on a continent would never go away
bool const forced = _forcedCreatureRespawns.erase(spawnId) != 0;
// Pool members are handled entirely by the pool system on this map's pool data
if (uint32 poolId = sPoolMgr->IsPartOfAPool<Creature>(spawnId))
{
@@ -2860,14 +2870,25 @@ void Map::ProcessCreatureRespawn(ObjectGuid::LowType spawnId)
}
}
// A spawn whose BossAI bound it to a DONE encounter stays down, as BossAI::CanRespawn keeps
// compat-mode spawns down. The expired row is kept so a reload still loads the boss dead, and
// the check repeats like an inactive group's. A forced Respawn() passes, as force does in compat mode.
if (InstanceMap* instanceMap = ToInstanceMap())
if (InstanceScript* script = instanceMap->GetInstanceScript())
if (!forced && script->IsBossSpawnDone(spawnId))
{
_respawnQueue.insert({GameTime::GetGameTime().count() + 5, SPAWN_TYPE_CREATURE, spawnId});
return;
}
// Check linked_respawn: don't spawn if the master creature is still dead.
// This mirrors the check in Creature::Respawn() for compat-mode creatures:
// hard-reset creatures bypass it (they despawn on evade and must always
// come back), and a creature linked to itself never auto-respawns.
// hard-reset creatures and forced respawns bypass it (the former despawn on
// evade and must always come back), and a creature linked to itself never auto-respawns.
ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Unit>(data->id, spawnId);
time_t linkedRespawntime = GetLinkedRespawnTime(dbtableHighGuid);
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(data->id);
if (linkedRespawntime && !(cInfo && cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_HARD_RESET)))
if (linkedRespawntime && !forced && !(cInfo && cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_HARD_RESET)))
{
time_t now = GameTime::GetGameTime().count();
time_t newRespawnTime;
+3
View File
@@ -433,6 +433,8 @@ public:
void SaveCreatureRespawnTime(ObjectGuid::LowType dbGuid, time_t& respawnTime);
void RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid);
// Queues the spawn for the next respawn pass and lets it through the DONE-boss hold, as force bypasses CanRespawn in compat mode
void ForceCreatureRespawn(ObjectGuid::LowType dbGuid);
void SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t& respawnTime);
void RemoveGORespawnTime(ObjectGuid::LowType dbGuid);
[[nodiscard]] std::unordered_map<ObjectGuid::LowType, time_t> const& GetCreatureRespawnTimes() const { return _creatureRespawnTimes; }
@@ -649,6 +651,7 @@ private:
void _RemoveObjectFromUpdateList(WorldObject* obj);
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes;
std::unordered_set<ObjectGuid::LowType> _forcedCreatureRespawns;
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes;
// Time-ordered index for ProcessRespawns() — avoids O(n) full scan.
+2 -6
View File
@@ -2563,10 +2563,7 @@ public:
}
// Also trigger via respawn time queue for fully-removed spawns
if (map->GetCreatureRespawnTime(spawnId) > 0)
{
time_t now = GameTime::GetGameTime().count();
map->SaveCreatureRespawnTime(spawnId, now);
}
map->ForceCreatureRespawn(spawnId);
handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_QUEUED, spawnId, creData->id);
return true;
}
@@ -2658,7 +2655,6 @@ public:
return false;
}
time_t now = GameTime::GetGameTime().count();
uint32 count = 0;
// Phase 1: respawn dead corpses that are still tracked in the spawn-id store.
@@ -2691,7 +2687,7 @@ public:
}
for (ObjectGuid::LowType spawnId : toRespawn)
{
map->SaveCreatureRespawnTime(spawnId, now);
map->ForceCreatureRespawn(spawnId);
++count;
}