diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
index 62467c5175..9a9a407948 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
@@ -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.
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 8f84bd3351..2182bd8d02 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -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();
}
diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp
index 14ce15f25d..6cc5048c40 100644
--- a/src/server/game/Instances/InstanceSaveMgr.cpp
+++ b/src/server/game/Instances/InstanceSaveMgr.cpp
@@ -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;
diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp
index cf4d56aa0f..16b336ca91 100644
--- a/src/server/game/Instances/InstanceScript.cpp
+++ b/src/server/game/Instances/InstanceScript.cpp
@@ -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);
diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h
index 8b9c1e6779..f97999d7cb 100644
--- a/src/server/game/Instances/InstanceScript.h
+++ b/src/server/game/Instances/InstanceScript.h
@@ -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 _bossSpawns;
ObjectInfoMap _gameObjectInfo;
ObjectInfoMap _summonInfo;
ObjectGuidMap _objectGuids;
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index 86b86a2729..ba4c57ed8e 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -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(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(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;
diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h
index 6eb3d4de6d..c74bab8363 100644
--- a/src/server/game/Maps/Map.h
+++ b/src/server/game/Maps/Map.h
@@ -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 const& GetCreatureRespawnTimes() const { return _creatureRespawnTimes; }
@@ -649,6 +651,7 @@ private:
void _RemoveObjectFromUpdateList(WorldObject* obj);
std::unordered_map _creatureRespawnTimes;
+ std::unordered_set _forcedCreatureRespawns;
std::unordered_map _goRespawnTimes;
// Time-ordered index for ProcessRespawns() — avoids O(n) full scan.
diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp
index 2598fbf75b..5cfc67f586 100644
--- a/src/server/scripts/Commands/cs_misc.cpp
+++ b/src/server/scripts/Commands/cs_misc.cpp
@@ -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;
}