fix(Scripts/Stratholme): recover events after a restart/persist in instance state (#27834)

This commit is contained in:
moostigre
2026-10-03 12:31:02 -03:00
committed by GitHub
parent 501e97d750
commit 9ce0f3dcce
@@ -16,6 +16,7 @@
*/
#include "GameObjectScript.h"
#include "GameTime.h"
#include "InstanceMapScript.h"
#include "InstanceScript.h"
#include "Player.h"
@@ -24,6 +25,7 @@
#include <array>
#include <list>
#include <unordered_set>
const Position BlackGuardPos[10] =
{
@@ -57,11 +59,28 @@ Position const MindlessUndeadPos = { 3941.75f, -3393.06f, 119.70f, 0.0f };
Position const BarthilasPos = { 4068.74f, -3535.97f, 122.825f, 2.478367567062377929f };
Position const SlaughterPos = { 4032.20f, -3378.06f, 119.75f, 4.67f };
enum SlaughterPhases
{
SLAUGHTER_ABOMINATIONS,
SLAUGHTER_RAMSTEIN,
SLAUGHTER_MINDLESS_UNDEAD,
SLAUGHTER_BLACK_GUARD,
SLAUGHTER_COMPLETE
};
enum GateTrapIndexes
{
GATE_TRAP_SCARLET,
GATE_TRAP_UNDEAD,
MAX_GATE_TRAPS
};
constexpr uint8 MAX_GATE_TRAP_GATES = 4;
constexpr uint32 GATE_TRAP_COOLDOWN = 30 * MINUTE;
static constexpr uint8 ScarletThreadSpawnCount = 4;
static constexpr uint8 AllScarletThreadLocations = (1 << ScarletThreadSpawnCount) - 1;
// uint32 m_uiGateTrapTimers[2][3] = { {0,0,0}, {0,0,0} };
class instance_stratholme : public InstanceMapScript
{
public:
@@ -81,8 +100,9 @@ public:
_zigguratState1 = 0;
_zigguratState2 = 0;
_zigguratState3 = 0;
_slaughterProgress = 0;
_slaughterProgress = SLAUGHTER_ABOMINATIONS;
_slaughterNPCs = 0;
_abominationSpawnIds.clear();
_postboxesOpened = 0;
_scarletThreadUsedLocations = 0;
@@ -91,6 +111,14 @@ public:
_gateTrapsCooldown[0] = false;
_gateTrapsCooldown[1] = false;
_gateTrapCooldownEnd[0] = 0;
_gateTrapCooldownEnd[1] = 0;
for (bool& needsRecovery : _gateTrapNeedsRecovery)
needsRecovery = true;
for (ObjectGuid& trappedPlayerGUID : _trappedPlayerGUIDs)
trappedPlayerGUID.Clear();
events.Reset();
}
@@ -113,19 +141,19 @@ public:
break;
case NPC_VENOM_BELCHER:
case NPC_BILE_SPEWER:
if (_slaughterProgress == 0)
++_slaughterNPCs;
if (_slaughterProgress == SLAUGHTER_ABOMINATIONS && creature->GetSpawnId())
_abominationSpawnIds.insert(creature->GetSpawnId());
break;
case NPC_RAMSTEIN_THE_GORGER:
if (_slaughterProgress == 1)
if (_slaughterProgress == SLAUGHTER_RAMSTEIN)
++_slaughterNPCs;
break;
case NPC_MINDLESS_UNDEAD:
if (_slaughterProgress == 2)
if (_slaughterProgress == SLAUGHTER_MINDLESS_UNDEAD)
++_slaughterNPCs;
break;
case NPC_BLACK_GUARD:
if (_slaughterProgress == 3)
if (_slaughterProgress == SLAUGHTER_BLACK_GUARD)
++_slaughterNPCs;
break;
case NPC_BARTHILAS:
@@ -138,14 +166,14 @@ public:
void ProcessSlaughterEvent()
{
if (_slaughterProgress == 1)
if (_slaughterProgress == SLAUGHTER_RAMSTEIN)
{
if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
baron->AI()->Talk(SAY_BRAON_SUMMON_RAMSTEIN);
instance->SummonCreature(NPC_RAMSTEIN_THE_GORGER, SlaughterPos);
}
if (_slaughterProgress == 2)
if (_slaughterProgress == SLAUGHTER_MINDLESS_UNDEAD)
{
for (uint32 i = 0; i < 33; ++i)
events.ScheduleEvent(EVENT_SPAWN_MINDLESS, Milliseconds(5000 + i * 210));
@@ -153,11 +181,11 @@ public:
if (GameObject* gate = baron->FindNearestGameObject(GO_SLAUGHTER_GATE_SIDE, 200.0f))
gate->SetGoState(GO_STATE_ACTIVE);
}
if (_slaughterProgress == 3)
if (_slaughterProgress == SLAUGHTER_BLACK_GUARD)
{
events.ScheduleEvent(EVENT_SPAWN_BLACK_GUARD, 20s);
}
if (_slaughterProgress == 4)
if (_slaughterProgress == SLAUGHTER_COMPLETE)
{
if (Creature* baron = instance->GetCreature(_baronRivendareGUID))
baron->AI()->Talk(SAY_BARON_GUARD_DEAD);
@@ -172,15 +200,22 @@ public:
{
case NPC_VENOM_BELCHER:
case NPC_BILE_SPEWER:
{
Creature* creature = unit->ToCreature();
if (_slaughterProgress != SLAUGHTER_ABOMINATIONS || !creature ||
!_abominationSpawnIds.count(creature->GetSpawnId()) ||
HasLivingAbominations(creature->GetSpawnId()))
break;
_abominationSpawnIds.clear();
AdvanceSlaughterEvent();
break;
}
case NPC_RAMSTEIN_THE_GORGER:
case NPC_MINDLESS_UNDEAD:
case NPC_BLACK_GUARD:
if (--_slaughterNPCs == 0)
{
++_slaughterProgress;
ProcessSlaughterEvent();
SaveToDB();
}
if (IsCurrentSlaughterTarget(unit->GetEntry()) && _slaughterNPCs && --_slaughterNPCs == 0)
AdvanceSlaughterEvent();
break;
case NPC_BARON_RIVENDARE:
events.CancelEvent(EVENT_BARON_TIME);
@@ -233,35 +268,31 @@ public:
case GO_ZIGGURAT_DOORS4:
go->AllowSaveToDB(true);
_zigguratDoorsGUID4 = go->GetGUID();
if (_slaughterProgress == 4)
if (_slaughterProgress == SLAUGHTER_COMPLETE)
go->SetGoState(GO_STATE_ACTIVE);
break;
case GO_ZIGGURAT_DOORS5:
go->AllowSaveToDB(true);
_zigguratDoorsGUID5 = go->GetGUID();
if (_slaughterProgress == 4)
if (_slaughterProgress == SLAUGHTER_COMPLETE)
go->SetGoState(GO_STATE_ACTIVE);
break;
case GO_SLAUGHTER_GATE_SIDE:
go->AllowSaveToDB(true);
if (_slaughterProgress >= 2)
if (_slaughterProgress >= SLAUGHTER_MINDLESS_UNDEAD)
go->SetGoState(GO_STATE_ACTIVE);
break;
case GO_PORT_TRAP_GATE_1:
go->AllowSaveToDB(true);
_trapGatesGUIDs[0] = go->GetGUID();
HandleGateTrapCreate(go, 0);
break;
case GO_PORT_TRAP_GATE_2:
go->AllowSaveToDB(true);
_trapGatesGUIDs[1] = go->GetGUID();
HandleGateTrapCreate(go, 1);
break;
case GO_PORT_TRAP_GATE_3:
go->AllowSaveToDB(true);
_trapGatesGUIDs[2] = go->GetGUID();
HandleGateTrapCreate(go, 2);
break;
case GO_PORT_TRAP_GATE_4:
go->AllowSaveToDB(true);
_trapGatesGUIDs[3] = go->GetGUID();
HandleGateTrapCreate(go, 3);
break;
default:
break;
@@ -402,19 +433,41 @@ public:
data >> _postboxesOpened;
data >> _barthilasrunProgress;
uint32 scarletThreadUsedLocations;
if (data >> scarletThreadUsedLocations && scarletThreadUsedLocations <= AllScarletThreadLocations)
_scarletThreadUsedLocations = scarletThreadUsedLocations;
// Older saves contain either the thread mask or the two trap cooldowns.
std::array<time_t, 3> savedFields{};
std::size_t savedFieldCount = 0;
while (savedFieldCount < savedFields.size() && data >> savedFields[savedFieldCount])
++savedFieldCount;
if (savedFieldCount == MAX_GATE_TRAPS)
{
_gateTrapCooldownEnd[GATE_TRAP_SCARLET] = savedFields[0];
_gateTrapCooldownEnd[GATE_TRAP_UNDEAD] = savedFields[1];
}
else
{
if (savedFieldCount && savedFields[0] >= 0 && savedFields[0] <= AllScarletThreadLocations)
_scarletThreadUsedLocations = uint8(savedFields[0]);
if (savedFieldCount == savedFields.size())
{
_gateTrapCooldownEnd[GATE_TRAP_SCARLET] = savedFields[1];
_gateTrapCooldownEnd[GATE_TRAP_UNDEAD] = savedFields[2];
}
}
if (_baronRunTime)
{
events.ScheduleEvent(EVENT_BARON_TIME, 60s);
}
if (_slaughterProgress > 0 && _slaughterProgress < 4)
if (_slaughterProgress > SLAUGHTER_ABOMINATIONS && _slaughterProgress < SLAUGHTER_COMPLETE)
{
events.ScheduleEvent(EVENT_FORCE_SLAUGHTER_EVENT, 5s);
}
RestoreGateTrapCooldown(GATE_TRAP_SCARLET);
RestoreGateTrapCooldown(GATE_TRAP_UNDEAD);
}
void WriteSaveDataMore(std::ostringstream& data) override
@@ -427,7 +480,9 @@ public:
<< _slaughterProgress << ' '
<< _postboxesOpened << ' '
<< _barthilasrunProgress << ' '
<< uint32(_scarletThreadUsedLocations);
<< uint32(_scarletThreadUsedLocations) << ' '
<< _gateTrapCooldownEnd[GATE_TRAP_SCARLET] << ' '
<< _gateTrapCooldownEnd[GATE_TRAP_UNDEAD];
}
uint32 GetData(uint32 type) const override
@@ -458,7 +513,7 @@ public:
{
// if the gate is in cooldown, skip the other checks
if (_gateTrapsCooldown[i])
break;
continue;
// Check that the trap is not on cooldown, if so check if player/pet is in range
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
@@ -468,38 +523,8 @@ public:
// should pet also trigger the trap? could not find any source for it
if (!player->IsGameMaster() && player->IsWithinDist2d(aGateTrap[i].m_positionX, aGateTrap[i].m_positionY, 5.5f))
{
// Check if timer was not already set by another player/pet a few milliseconds before
if (_gateTrapsCooldown[i])
return;
_gateTrapsCooldown[i] = true;
// close the gates
if (_trapGatesGUIDs[2 * i])
DoUseDoorOrButton(_trapGatesGUIDs[2 * i]);
if (_trapGatesGUIDs[2 * i + 1])
DoUseDoorOrButton(_trapGatesGUIDs[2 * i + 1]);
_trappedPlayerGUID = player->GetGUID();
if (i == 0)
{
// set timer to reset the trap
events.ScheduleEvent(EVENT_GATE1_TRAP, 1800s);
// set timer to reopen gates
events.ScheduleEvent(EVENT_GATE1_DELAY, 20s);
// set timer to spawn the plagued critters
events.ScheduleEvent(EVENT_GATE1_CRITTER_DELAY, 2s);
}
else if (i == 1)
{
// set timer to reset the trap
events.ScheduleEvent(EVENT_GATE2_TRAP, 1800s);
// set timer to reopen gates
events.ScheduleEvent(EVENT_GATE2_DELAY, 20s);
// set timer to spawn the plagued critters
events.ScheduleEvent(EVENT_GATE2_CRITTER_DELAY, 2s);
}
StartGateTrap(i, player);
break;
}
}
}
@@ -512,9 +537,13 @@ public:
{
case EVENT_GATE1_TRAP:
_gateTrapsCooldown[GATE1] = false;
_gateTrapCooldownEnd[GATE1] = 0;
SaveToDB();
break;
case EVENT_GATE2_TRAP:
_gateTrapsCooldown[GATE2] = false;
_gateTrapCooldownEnd[GATE2] = 0;
SaveToDB();
break;
case EVENT_GATE1_DELAY:
gate_delay(GATE1);
@@ -624,6 +653,7 @@ public:
uint32 _zigguratState3;
uint32 _slaughterProgress;
uint32 _slaughterNPCs;
std::unordered_set<ObjectGuid::LowType> _abominationSpawnIds;
uint32 _barthilasrunProgress{};
uint32 _postboxesOpened;
EventMap events;
@@ -638,9 +668,110 @@ public:
ObjectGuid _baronRivendareGUID;
ObjectGuid _barthilasGUID;
bool _gateTrapsCooldown[2];
ObjectGuid _trappedPlayerGUID;
ObjectGuid _trapGatesGUIDs[4];
bool _gateTrapsCooldown[MAX_GATE_TRAPS];
bool _gateTrapNeedsRecovery[MAX_GATE_TRAP_GATES];
time_t _gateTrapCooldownEnd[MAX_GATE_TRAPS];
ObjectGuid _trappedPlayerGUIDs[MAX_GATE_TRAPS];
ObjectGuid _trapGatesGUIDs[MAX_GATE_TRAP_GATES];
bool HasLivingAbominations(ObjectGuid::LowType dyingSpawnId) const
{
auto const& creatures = instance->GetCreatureBySpawnIdStore();
for (ObjectGuid::LowType spawnId : _abominationSpawnIds)
{
if (spawnId == dyingSpawnId)
continue;
auto const range = creatures.equal_range(spawnId);
if (range.first == range.second)
{
// An unloaded spawn still blocks the event unless it is waiting to respawn.
if (instance->GetCreatureRespawnTime(spawnId) <= GameTime::GetGameTime().count())
return true;
}
else
for (auto itr = range.first; itr != range.second; ++itr)
if (itr->second->IsAlive())
return true;
}
return false;
}
void AdvanceSlaughterEvent()
{
++_slaughterProgress;
SaveToDB();
ProcessSlaughterEvent();
}
bool IsCurrentSlaughterTarget(uint32 entry) const
{
switch (_slaughterProgress)
{
case SLAUGHTER_RAMSTEIN:
return entry == NPC_RAMSTEIN_THE_GORGER;
case SLAUGHTER_MINDLESS_UNDEAD:
return entry == NPC_MINDLESS_UNDEAD;
case SLAUGHTER_BLACK_GUARD:
return entry == NPC_BLACK_GUARD;
default:
return false;
}
}
void HandleGateTrapCreate(GameObject* gate, uint8 index)
{
gate->AllowSaveToDB(true);
_trapGatesGUIDs[index] = gate->GetGUID();
// EventMap timers do not survive an instance unload. Finish an interrupted
// trap in its safe open state instead of restoring a permanently closed gate.
if (_gateTrapNeedsRecovery[index])
{
_gateTrapNeedsRecovery[index] = false;
gate->SetGoState(GO_STATE_ACTIVE);
}
}
void RestoreGateTrapCooldown(uint8 gate)
{
time_t now = GameTime::GetGameTime().count();
if (_gateTrapCooldownEnd[gate] <= now)
{
_gateTrapCooldownEnd[gate] = 0;
return;
}
_gateTrapsCooldown[gate] = true;
uint32 eventId = gate == GATE_TRAP_SCARLET ? EVENT_GATE1_TRAP : EVENT_GATE2_TRAP;
events.ScheduleEvent(eventId, Seconds(_gateTrapCooldownEnd[gate] - now));
}
void StartGateTrap(uint8 gate, Player* player)
{
_gateTrapsCooldown[gate] = true;
_gateTrapCooldownEnd[gate] = GameTime::GetGameTime().count() + GATE_TRAP_COOLDOWN;
_trappedPlayerGUIDs[gate] = player->GetGUID();
HandleGameObject(_trapGatesGUIDs[2 * gate], false);
HandleGameObject(_trapGatesGUIDs[2 * gate + 1], false);
if (gate == GATE_TRAP_SCARLET)
{
events.ScheduleEvent(EVENT_GATE1_TRAP, Seconds(GATE_TRAP_COOLDOWN));
events.ScheduleEvent(EVENT_GATE1_DELAY, 20s);
events.ScheduleEvent(EVENT_GATE1_CRITTER_DELAY, 2s);
}
else
{
events.ScheduleEvent(EVENT_GATE2_TRAP, Seconds(GATE_TRAP_COOLDOWN));
events.ScheduleEvent(EVENT_GATE2_DELAY, 20s);
events.ScheduleEvent(EVENT_GATE2_CRITTER_DELAY, 2s);
}
SaveToDB();
}
uint8 _scarletThreadUsedLocations;
uint8 _scarletThreadLocation;
@@ -673,21 +804,15 @@ public:
void gate_delay(int gate)
{
if (_trapGatesGUIDs[2 * gate])
{
DoUseDoorOrButton(_trapGatesGUIDs[2 * gate]);
}
if (_trapGatesGUIDs[2 * gate + 1])
{
DoUseDoorOrButton(_trapGatesGUIDs[2 * gate + 1]);
}
HandleGameObject(_trapGatesGUIDs[2 * gate], true);
HandleGameObject(_trapGatesGUIDs[2 * gate + 1], true);
}
void gate_critter_delay(int gate)
{
if (_trappedPlayerGUID)
if (_trappedPlayerGUIDs[gate])
{
if (Player* pPlayer = ObjectAccessor::GetPlayer(instance, _trappedPlayerGUID))
if (Player* pPlayer = ObjectAccessor::GetPlayer(instance, _trappedPlayerGUIDs[gate]))
{
DoSpawnPlaguedCritters(gate, pPlayer);
}