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

View File

@@ -56,7 +56,8 @@ public:
_currentRift = 0;
_shieldPercent = 100;
_encounterNPCs.clear();
_canSpawnPortal = true; // Delay after bosses
_noBossSpawnDelay = true; // Delay after bosses
_eventStatus = EVENT_PREPARE;
}
void CleanupInstance()
@@ -72,10 +73,24 @@ public:
_availableRiftPositions.push_back(pos);
}
// prevent getting stuck if event fails during boss break
_noBossSpawnDelay = true;
instance->LoadGrid(-2023.0f, 7121.0f);
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_2:
case NPC_TIME_RIFT:
case NPC_INFINITE_ASSASIN:
case NPC_INFINITE_ASSASIN_2:
case NPC_INFINITE_ASSASSIN:
case NPC_INFINITE_ASSASSIN_2:
case NPC_INFINITE_WHELP:
case NPC_INFINITE_CHRONOMANCER:
case NPC_INFINITE_CHRONOMANCER_2:
@@ -143,14 +158,14 @@ public:
case DATA_CHRONO_LORD_DEJA:
case DATA_TEMPORUS:
{
_canSpawnPortal = false;
_noBossSpawnDelay = false;
_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;
}
default:
@@ -163,26 +178,27 @@ public:
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();
}
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_RIFT, _currentRift);
}
void ScheduleNextPortal(Milliseconds time, Position lastPosition)
{
// only one rift can be scheduled at any time
_scheduler.CancelGroup(CONTEXT_GROUP_RIFTS);
_scheduler.Schedule(time, [this, lastPosition](TaskContext context)
{
if (GetCreature(DATA_MEDIVH))
{
// Spawning prevented - there's a 150s delay after a boss dies.
if (!_canSpawnPortal)
// Spawning prevented: after-boss-delay or event failed/not started or last portal spawned
if (!_noBossSpawnDelay || _eventStatus == EVENT_PREPARE || _currentRift >= 18)
{
return;
}
@@ -208,19 +224,10 @@ public:
instance->SummonCreature(NPC_TIME_RIFT, spawnPos);
// Here we check if we have available rift spots.
if (_currentRift < 18)
{
if (!_availableRiftPositions.empty())
{
context.Repeat((_currentRift >= 13 ? 2min : 90s));
}
else
{
context.Repeat(4s);
}
}
// queue next portal if group doesn't kill keepers fast enough
context.Repeat((_currentRift >= 13 ? 2min : 90s));
}
// if no rift positions are available, the next rift will be scheduled in OnCreatureRemove
}
context.SetGroup(CONTEXT_GROUP_RIFTS);
@@ -241,8 +248,8 @@ public:
case NPC_RIFT_KEEPER_MAGE:
case NPC_RIFT_LORD:
case NPC_RIFT_LORD_2:
case NPC_INFINITE_ASSASIN:
case NPC_INFINITE_ASSASIN_2:
case NPC_INFINITE_ASSASSIN:
case NPC_INFINITE_ASSASSIN_2:
case NPC_INFINITE_WHELP:
case NPC_INFINITE_CHRONOMANCER:
case NPC_INFINITE_CHRONOMANCER_2:
@@ -251,6 +258,7 @@ public:
case NPC_INFINITE_VANQUISHER:
case NPC_INFINITE_VANQUISHER_2:
case NPC_DP_BEAM_STALKER:
case NPC_DP_EMITTER_STALKER:
_encounterNPCs.insert(creature->GetGUID());
break;
}
@@ -263,7 +271,7 @@ public:
switch (creature->GetEntry())
{
case NPC_TIME_RIFT:
if (_currentRift < 18)
if (_currentRift < 18 && _noBossSpawnDelay && _eventStatus == EVENT_IN_PROGRESS)
{
if (_availableRiftPositions.size() < 3)
{
@@ -286,8 +294,8 @@ public:
case NPC_RIFT_KEEPER_MAGE:
case NPC_RIFT_LORD:
case NPC_RIFT_LORD_2:
case NPC_INFINITE_ASSASIN:
case NPC_INFINITE_ASSASIN_2:
case NPC_INFINITE_ASSASSIN:
case NPC_INFINITE_ASSASSIN_2:
case NPC_INFINITE_WHELP:
case NPC_INFINITE_CHRONOMANCER:
case NPC_INFINITE_CHRONOMANCER_2:
@@ -295,6 +303,7 @@ public:
case NPC_INFINITE_EXECUTIONER_2:
case NPC_INFINITE_VANQUISHER:
case NPC_INFINITE_VANQUISHER_2:
case NPC_DP_EMITTER_STALKER:
_encounterNPCs.erase(creature->GetGUID());
break;
}
@@ -308,27 +317,14 @@ public:
{
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_RIFT, _currentRift);
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;
}
case DATA_DAMAGE_SHIELD:
@@ -348,6 +344,8 @@ public:
if (!_shieldPercent)
{
_eventStatus = EVENT_PREPARE;
if (Creature* medivh = GetCreature(DATA_MEDIVH))
{
if (medivh->IsAlive() && medivh->IsAIEnabled)
@@ -404,6 +402,12 @@ public:
GuidSet encounterNPCSCopy = _encounterNPCs;
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))
{
creature->CastSpell(creature, SPELL_TELEPORT_VISUAL, true);
@@ -412,6 +416,16 @@ public:
}
_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;
uint8 _currentRift;
int8 _shieldPercent;
bool _canSpawnPortal;
bool _noBossSpawnDelay;
EventStatus _eventStatus;
TaskScheduler _scheduler;
};
};

View File

@@ -43,9 +43,9 @@ enum medivhMisc
EVENT_OUTRO_8 = 17
};
static std::vector<uint32> firstWave = { NPC_INFINITE_ASSASIN, 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> thirdWave = { NPC_INFINITE_EXECUTIONER, NPC_INFINITE_VANQUISHER, NPC_INFINITE_CHRONOMANCER, NPC_INFINITE_ASSASIN };
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_ASSASSIN };
static std::vector<uint32> thirdWave = { NPC_INFINITE_EXECUTIONER, NPC_INFINITE_VANQUISHER, NPC_INFINITE_CHRONOMANCER, NPC_INFINITE_ASSASSIN };
class NpcRunToHome : public BasicEvent
{
@@ -323,8 +323,8 @@ struct npc_time_rift : public NullCreatureAI
{
switch (entry)
{
case NPC_INFINITE_ASSASIN:
entry = NPC_INFINITE_ASSASIN_2;
case NPC_INFINITE_ASSASSIN:
entry = NPC_INFINITE_ASSASSIN_2;
break;
case NPC_INFINITE_CHRONOMANCER:
entry = NPC_INFINITE_CHRONOMANCER_2;

View File

@@ -36,13 +36,10 @@ enum DataTypes
MAX_ENCOUNTER = 3,
DATA_MEDIVH = 10,
DATA_RIFT_KILLED = 11,
DATA_DAMAGE_SHIELD = 12,
DATA_SHIELD_PERCENT = 13,
DATA_RIFT_NUMBER = 14,
DATA_SUMMONED_NPC = 20,
DATA_DELETED_NPC = 21
DATA_RIFT_NUMBER = 14
};
enum WorldStateIds
@@ -52,6 +49,12 @@ enum WorldStateIds
WORLD_STATE_BM_RIFT = 2784
};
enum EventStatus
{
EVENT_PREPARE = 0,
EVENT_IN_PROGRESS = 1
};
enum QuestIds
{
QUEST_OPENING_PORTAL = 10297,
@@ -75,13 +78,13 @@ enum CreatureIds
NPC_INFINITE_TIMEREAVER = 21698,
NPC_AEONUS = 17881,
NPC_INFINITE_ASSASIN = 17835,
NPC_INFINITE_ASSASSIN = 17835,
NPC_INFINITE_WHELP = 21818,
NPC_INFINITE_CHRONOMANCER = 17892,
NPC_INFINITE_EXECUTIONER = 18994,
NPC_INFINITE_VANQUISHER = 18995,
NPC_INFINITE_ASSASIN_2 = 21137,
NPC_INFINITE_ASSASSIN_2 = 21137,
NPC_INFINITE_CHRONOMANCER_2 = 21136,
NPC_INFINITE_EXECUTIONER_2 = 21138,
NPC_INFINITE_VANQUISHER_2 = 21139,