fix(Scripts/TheBlackMorass): rework time rift event logic (#16535)

* Fix visual crystals

This fixes the crystals not despawning on Medivh's death and being
resummoned after every reset

* Fix summoning circle visual after cleanup

Fix respawn of summoning circle visual NPC
Move respawn to same moment as Medivh respawn

* Fix race condition during script cleanup after event failure

This fixes the bug that rifts still spawn after shield is at 0%,
enabling players to exploit the instance and defeat bosses at their own
pace.

In general, removing rifts schedules spawning of new rifts

After failing the event (shield at 0%) this introduces a race between
rift despawn and (scheduler) cleanup

Fix it by introducing a check if the event has been started and should
still progress.

* Fix softlock of event during break after boss

Make sure that the delay condition is removed if event fails

Meeting certain conditions it was possible to softlock the event by
extending the after-boss-break indefinitely

* Cleanup failed instance without player interaction

This respawns Medivh and re-enables the event 5 minutes after a failure

Retrying the event shouldn't require all players to leave the instance
and to reenter

* Misc cleanups

Remove unused DataTypes

Rename variable to something more self-explanatory

Remove unnecessary checks and make scheduling of rifts more
straightforward

Fix typos
This commit is contained in:
Johaine
2023-06-18 16:47:27 +02:00
committed by GitHub
parent d2555e8d5b
commit 5060407598
3 changed files with 75 additions and 57 deletions
@@ -56,7 +56,8 @@ public:
_currentRift = 0; _currentRift = 0;
_shieldPercent = 100; _shieldPercent = 100;
_encounterNPCs.clear(); _encounterNPCs.clear();
_canSpawnPortal = true; // Delay after bosses _noBossSpawnDelay = true; // Delay after bosses
_eventStatus = EVENT_PREPARE;
} }
void CleanupInstance() void CleanupInstance()
@@ -72,10 +73,24 @@ public:
_availableRiftPositions.push_back(pos); _availableRiftPositions.push_back(pos);
} }
// prevent getting stuck if event fails during boss break
_noBossSpawnDelay = true;
instance->LoadGrid(-2023.0f, 7121.0f); instance->LoadGrid(-2023.0f, 7121.0f);
if (Creature* medivh = GetCreature(DATA_MEDIVH)) if (Creature* medivh = GetCreature(DATA_MEDIVH))
{ {
medivh->DespawnOrUnsummon(0ms, 3s); medivh->Respawn();
}
for (ObjectGuid const& guid : _encounterNPCs)
{
if (guid.GetEntry() == NPC_DP_BEAM_STALKER)
{
if (Creature* creature = instance->GetCreature(guid))
{
creature->Respawn();
}
break;
}
} }
} }
@@ -121,8 +136,8 @@ public:
case NPC_RIFT_LORD: case NPC_RIFT_LORD:
case NPC_RIFT_LORD_2: case NPC_RIFT_LORD_2:
case NPC_TIME_RIFT: case NPC_TIME_RIFT:
case NPC_INFINITE_ASSASIN: case NPC_INFINITE_ASSASSIN:
case NPC_INFINITE_ASSASIN_2: case NPC_INFINITE_ASSASSIN_2:
case NPC_INFINITE_WHELP: case NPC_INFINITE_WHELP:
case NPC_INFINITE_CHRONOMANCER: case NPC_INFINITE_CHRONOMANCER:
case NPC_INFINITE_CHRONOMANCER_2: case NPC_INFINITE_CHRONOMANCER_2:
@@ -143,14 +158,14 @@ public:
case DATA_CHRONO_LORD_DEJA: case DATA_CHRONO_LORD_DEJA:
case DATA_TEMPORUS: case DATA_TEMPORUS:
{ {
_canSpawnPortal = false; _noBossSpawnDelay = false;
_scheduler.Schedule(2min + 30s, [this](TaskContext) _scheduler.Schedule(2min + 30s, [this](TaskContext)
{ {
_canSpawnPortal = true; _noBossSpawnDelay = true;
ScheduleNextPortal(0s, Position(0.0f, 0.0f, 0.0f, 0.0f));
}); });
ScheduleNextPortal(2min + 30s, Position(0.0f, 0.0f, 0.0f, 0.0f));
break; break;
} }
default: default:
@@ -163,26 +178,27 @@ public:
void OnPlayerEnter(Player* player) override void OnPlayerEnter(Player* player) override
{ {
if (instance->GetPlayersCountExceptGMs() <= 1 && GetBossState(DATA_AEONUS) != DONE) if (instance->GetPlayersCountExceptGMs() <= 1 && GetBossState(DATA_AEONUS) != DONE && _eventStatus != EVENT_IN_PROGRESS)
{ {
CleanupInstance(); CleanupInstance();
} }
player->SendUpdateWorldState(WORLD_STATE_BM, _currentRift > 0 ? 1 : 0); player->SendUpdateWorldState(WORLD_STATE_BM, _eventStatus);
player->SendUpdateWorldState(WORLD_STATE_BM_SHIELD, _shieldPercent); player->SendUpdateWorldState(WORLD_STATE_BM_SHIELD, _shieldPercent);
player->SendUpdateWorldState(WORLD_STATE_BM_RIFT, _currentRift); player->SendUpdateWorldState(WORLD_STATE_BM_RIFT, _currentRift);
} }
void ScheduleNextPortal(Milliseconds time, Position lastPosition) void ScheduleNextPortal(Milliseconds time, Position lastPosition)
{ {
// only one rift can be scheduled at any time
_scheduler.CancelGroup(CONTEXT_GROUP_RIFTS); _scheduler.CancelGroup(CONTEXT_GROUP_RIFTS);
_scheduler.Schedule(time, [this, lastPosition](TaskContext context) _scheduler.Schedule(time, [this, lastPosition](TaskContext context)
{ {
if (GetCreature(DATA_MEDIVH)) if (GetCreature(DATA_MEDIVH))
{ {
// Spawning prevented - there's a 150s delay after a boss dies. // Spawning prevented: after-boss-delay or event failed/not started or last portal spawned
if (!_canSpawnPortal) if (!_noBossSpawnDelay || _eventStatus == EVENT_PREPARE || _currentRift >= 18)
{ {
return; return;
} }
@@ -208,19 +224,10 @@ public:
instance->SummonCreature(NPC_TIME_RIFT, spawnPos); instance->SummonCreature(NPC_TIME_RIFT, spawnPos);
// Here we check if we have available rift spots. // queue next portal if group doesn't kill keepers fast enough
if (_currentRift < 18)
{
if (!_availableRiftPositions.empty())
{
context.Repeat((_currentRift >= 13 ? 2min : 90s)); context.Repeat((_currentRift >= 13 ? 2min : 90s));
} }
else // if no rift positions are available, the next rift will be scheduled in OnCreatureRemove
{
context.Repeat(4s);
}
}
}
} }
context.SetGroup(CONTEXT_GROUP_RIFTS); context.SetGroup(CONTEXT_GROUP_RIFTS);
@@ -241,8 +248,8 @@ public:
case NPC_RIFT_KEEPER_MAGE: case NPC_RIFT_KEEPER_MAGE:
case NPC_RIFT_LORD: case NPC_RIFT_LORD:
case NPC_RIFT_LORD_2: case NPC_RIFT_LORD_2:
case NPC_INFINITE_ASSASIN: case NPC_INFINITE_ASSASSIN:
case NPC_INFINITE_ASSASIN_2: case NPC_INFINITE_ASSASSIN_2:
case NPC_INFINITE_WHELP: case NPC_INFINITE_WHELP:
case NPC_INFINITE_CHRONOMANCER: case NPC_INFINITE_CHRONOMANCER:
case NPC_INFINITE_CHRONOMANCER_2: case NPC_INFINITE_CHRONOMANCER_2:
@@ -251,6 +258,7 @@ public:
case NPC_INFINITE_VANQUISHER: case NPC_INFINITE_VANQUISHER:
case NPC_INFINITE_VANQUISHER_2: case NPC_INFINITE_VANQUISHER_2:
case NPC_DP_BEAM_STALKER: case NPC_DP_BEAM_STALKER:
case NPC_DP_EMITTER_STALKER:
_encounterNPCs.insert(creature->GetGUID()); _encounterNPCs.insert(creature->GetGUID());
break; break;
} }
@@ -263,7 +271,7 @@ public:
switch (creature->GetEntry()) switch (creature->GetEntry())
{ {
case NPC_TIME_RIFT: case NPC_TIME_RIFT:
if (_currentRift < 18) if (_currentRift < 18 && _noBossSpawnDelay && _eventStatus == EVENT_IN_PROGRESS)
{ {
if (_availableRiftPositions.size() < 3) if (_availableRiftPositions.size() < 3)
{ {
@@ -286,8 +294,8 @@ public:
case NPC_RIFT_KEEPER_MAGE: case NPC_RIFT_KEEPER_MAGE:
case NPC_RIFT_LORD: case NPC_RIFT_LORD:
case NPC_RIFT_LORD_2: case NPC_RIFT_LORD_2:
case NPC_INFINITE_ASSASIN: case NPC_INFINITE_ASSASSIN:
case NPC_INFINITE_ASSASIN_2: case NPC_INFINITE_ASSASSIN_2:
case NPC_INFINITE_WHELP: case NPC_INFINITE_WHELP:
case NPC_INFINITE_CHRONOMANCER: case NPC_INFINITE_CHRONOMANCER:
case NPC_INFINITE_CHRONOMANCER_2: case NPC_INFINITE_CHRONOMANCER_2:
@@ -295,6 +303,7 @@ public:
case NPC_INFINITE_EXECUTIONER_2: case NPC_INFINITE_EXECUTIONER_2:
case NPC_INFINITE_VANQUISHER: case NPC_INFINITE_VANQUISHER:
case NPC_INFINITE_VANQUISHER_2: case NPC_INFINITE_VANQUISHER_2:
case NPC_DP_EMITTER_STALKER:
_encounterNPCs.erase(creature->GetGUID()); _encounterNPCs.erase(creature->GetGUID());
break; break;
} }
@@ -308,27 +317,14 @@ public:
{ {
case DATA_MEDIVH: case DATA_MEDIVH:
{ {
DoUpdateWorldState(WORLD_STATE_BM, 1); _eventStatus = EVENT_IN_PROGRESS;
DoUpdateWorldState(WORLD_STATE_BM, _eventStatus);
DoUpdateWorldState(WORLD_STATE_BM_SHIELD, _shieldPercent); DoUpdateWorldState(WORLD_STATE_BM_SHIELD, _shieldPercent);
DoUpdateWorldState(WORLD_STATE_BM_RIFT, _currentRift); DoUpdateWorldState(WORLD_STATE_BM_RIFT, _currentRift);
ScheduleNextPortal(3s, Position(0.0f, 0.0f, 0.0f, 0.0f)); ScheduleNextPortal(3s, Position(0.0f, 0.0f, 0.0f, 0.0f));
for (ObjectGuid const& guid : _encounterNPCs)
{
if (guid.GetEntry() == NPC_DP_BEAM_STALKER)
{
if (Creature* creature = instance->GetCreature(guid))
{
if (!creature->IsAlive())
{
creature->Respawn(true);
}
}
break;
}
}
break; break;
} }
case DATA_DAMAGE_SHIELD: case DATA_DAMAGE_SHIELD:
@@ -348,6 +344,8 @@ public:
if (!_shieldPercent) if (!_shieldPercent)
{ {
_eventStatus = EVENT_PREPARE;
if (Creature* medivh = GetCreature(DATA_MEDIVH)) if (Creature* medivh = GetCreature(DATA_MEDIVH))
{ {
if (medivh->IsAlive() && medivh->IsAIEnabled) if (medivh->IsAlive() && medivh->IsAIEnabled)
@@ -404,6 +402,12 @@ public:
GuidSet encounterNPCSCopy = _encounterNPCs; GuidSet encounterNPCSCopy = _encounterNPCs;
for (ObjectGuid const& guid : encounterNPCSCopy) for (ObjectGuid const& guid : encounterNPCSCopy)
{ {
// Don't despawn permanent visual effect NPC twice or it won't respawn correctly
if (guid.GetEntry() == NPC_DP_BEAM_STALKER)
{
continue;
}
if (Creature* creature = instance->GetCreature(guid)) if (Creature* creature = instance->GetCreature(guid))
{ {
creature->CastSpell(creature, SPELL_TELEPORT_VISUAL, true); creature->CastSpell(creature, SPELL_TELEPORT_VISUAL, true);
@@ -412,6 +416,16 @@ public:
} }
_scheduler.CancelAll(); _scheduler.CancelAll();
// Step 4 - Schedule instance cleanup without player interaction
_scheduler.Schedule(300s, [this](TaskContext)
{
CleanupInstance();
DoUpdateWorldState(WORLD_STATE_BM, _eventStatus);
DoUpdateWorldState(WORLD_STATE_BM_SHIELD, _shieldPercent);
DoUpdateWorldState(WORLD_STATE_BM_RIFT, _currentRift);
});
}); });
}); });
}); });
@@ -447,7 +461,8 @@ public:
GuidSet _encounterNPCs; GuidSet _encounterNPCs;
uint8 _currentRift; uint8 _currentRift;
int8 _shieldPercent; int8 _shieldPercent;
bool _canSpawnPortal; bool _noBossSpawnDelay;
EventStatus _eventStatus;
TaskScheduler _scheduler; TaskScheduler _scheduler;
}; };
}; };
@@ -43,9 +43,9 @@ enum medivhMisc
EVENT_OUTRO_8 = 17 EVENT_OUTRO_8 = 17
}; };
static std::vector<uint32> firstWave = { NPC_INFINITE_ASSASIN, NPC_INFINITE_WHELP, NPC_INFINITE_CHRONOMANCER }; static std::vector<uint32> firstWave = { NPC_INFINITE_ASSASSIN, NPC_INFINITE_WHELP, NPC_INFINITE_CHRONOMANCER };
static std::vector<uint32> secondWave = { NPC_INFINITE_EXECUTIONER, NPC_INFINITE_CHRONOMANCER, NPC_INFINITE_WHELP, NPC_INFINITE_ASSASIN }; static std::vector<uint32> secondWave = { NPC_INFINITE_EXECUTIONER, NPC_INFINITE_CHRONOMANCER, NPC_INFINITE_WHELP, NPC_INFINITE_ASSASSIN };
static std::vector<uint32> thirdWave = { NPC_INFINITE_EXECUTIONER, NPC_INFINITE_VANQUISHER, NPC_INFINITE_CHRONOMANCER, NPC_INFINITE_ASSASIN }; static std::vector<uint32> thirdWave = { NPC_INFINITE_EXECUTIONER, NPC_INFINITE_VANQUISHER, NPC_INFINITE_CHRONOMANCER, NPC_INFINITE_ASSASSIN };
class NpcRunToHome : public BasicEvent class NpcRunToHome : public BasicEvent
{ {
@@ -323,8 +323,8 @@ struct npc_time_rift : public NullCreatureAI
{ {
switch (entry) switch (entry)
{ {
case NPC_INFINITE_ASSASIN: case NPC_INFINITE_ASSASSIN:
entry = NPC_INFINITE_ASSASIN_2; entry = NPC_INFINITE_ASSASSIN_2;
break; break;
case NPC_INFINITE_CHRONOMANCER: case NPC_INFINITE_CHRONOMANCER:
entry = NPC_INFINITE_CHRONOMANCER_2; entry = NPC_INFINITE_CHRONOMANCER_2;
@@ -36,13 +36,10 @@ enum DataTypes
MAX_ENCOUNTER = 3, MAX_ENCOUNTER = 3,
DATA_MEDIVH = 10, DATA_MEDIVH = 10,
DATA_RIFT_KILLED = 11,
DATA_DAMAGE_SHIELD = 12, DATA_DAMAGE_SHIELD = 12,
DATA_SHIELD_PERCENT = 13, DATA_SHIELD_PERCENT = 13,
DATA_RIFT_NUMBER = 14, DATA_RIFT_NUMBER = 14
DATA_SUMMONED_NPC = 20,
DATA_DELETED_NPC = 21
}; };
enum WorldStateIds enum WorldStateIds
@@ -52,6 +49,12 @@ enum WorldStateIds
WORLD_STATE_BM_RIFT = 2784 WORLD_STATE_BM_RIFT = 2784
}; };
enum EventStatus
{
EVENT_PREPARE = 0,
EVENT_IN_PROGRESS = 1
};
enum QuestIds enum QuestIds
{ {
QUEST_OPENING_PORTAL = 10297, QUEST_OPENING_PORTAL = 10297,
@@ -75,13 +78,13 @@ enum CreatureIds
NPC_INFINITE_TIMEREAVER = 21698, NPC_INFINITE_TIMEREAVER = 21698,
NPC_AEONUS = 17881, NPC_AEONUS = 17881,
NPC_INFINITE_ASSASIN = 17835, NPC_INFINITE_ASSASSIN = 17835,
NPC_INFINITE_WHELP = 21818, NPC_INFINITE_WHELP = 21818,
NPC_INFINITE_CHRONOMANCER = 17892, NPC_INFINITE_CHRONOMANCER = 17892,
NPC_INFINITE_EXECUTIONER = 18994, NPC_INFINITE_EXECUTIONER = 18994,
NPC_INFINITE_VANQUISHER = 18995, NPC_INFINITE_VANQUISHER = 18995,
NPC_INFINITE_ASSASIN_2 = 21137, NPC_INFINITE_ASSASSIN_2 = 21137,
NPC_INFINITE_CHRONOMANCER_2 = 21136, NPC_INFINITE_CHRONOMANCER_2 = 21136,
NPC_INFINITE_EXECUTIONER_2 = 21138, NPC_INFINITE_EXECUTIONER_2 = 21138,
NPC_INFINITE_VANQUISHER_2 = 21139, NPC_INFINITE_VANQUISHER_2 = 21139,