mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-10-10 07:06:38 +08:00
fix(Scripts/Ulduar): let the hard-reset respawn bring Algalon back after a wipe (#27807)
This commit is contained in:
+1
-1
@@ -164,7 +164,7 @@ go test -tags=e2e ./local/... -count=1 -v -timeout 30m -parallel 1
|
||||
| guild/charter_bank | charter buy+turn-in | P2 | covered | — |
|
||||
| instances/bind_reset | party tele; ritual summon | P2 | covered; post-reset summon `blocked-harness` (AcceptSummon after reset) | #10708 |
|
||||
| instances/classic/stratholme | Timmy remains hidden while a relevant Square Scarlet lives, then emerges after the area is clear | P2 | covered (`TestAC_26363_TimmyEmergesAfterSquareCleared`) | #26363 |
|
||||
| instances/ulduar | named tele; Freya wave interval; a Laughing Skull's Lunatic Gaze stops at the brain room's geometry instead of draining sanity through it; a wave of Elder Brightleaf's Unstable Sun Beams stays capped however many players stand in range; Psychosis and Malady of the Mind stop picking a player once they are at 40 Sanity or less; Algalon holds still for 3s once his Big Bang cast lands instead of resuming melee and Quantum Strike on the next tick; a Writhing Lasher stays up past 62947's own 10s summon duration, because the despawn type its AI sets in `IsSummonedBy` is no longer overwritten on the way out of the summon call; a wipe to Thorim's melee with a death-persistent damage shield on the last victim spawns no Cache of Storms while his hard-reset despawn runs, and a lethal hit on the respawned Thorim still yields, runs the outro to his teleport-out without an evade and leaves the saved encounter DONE; an Ancient Water Spirit whose Tidal Wave is kicked casts no damage-and-knockback follow-up; one Elder left alive spawns the one-Elder Freya's Gift, which is the chest whose emblem count the fix corrects; Ignis dying kills every Iron Construct including the dormant ones, and his Kill All Constructs instakill spares the player and every other creature in range | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestAC_27539_*`, `TestAC_27455_*`, `TestUlduar_BrightleafSunBeamsCappedPerWave`, `TestUlduar_FreyaWardLasherOutlivesSummonDuration`, `TestUlduar_ThorimEvadeDespawnDoesNotYield`, `TestUlduar_FreyaGiftMatchesElderCount`, `TestAC_27736_IgnisKillsAllConstructs`); Kologarn Charge `blocked-harness` (bridge Z after Charge); Freya's Gift emblem counts `blocked-harness` (a chest's loot opens only through SPELL_EFFECT_OPEN_LOCK, and CMSG_CAST_SPELL carries no TARGET_FLAG_GAMEOBJECT); Ignis constructs on 25-man `blocked-harness` (no way to set a bot's raid difficulty: CMSG_SET_RAID_DIFFICULTY is unexported and a `characters`.`instance_mode_mask` write is overwritten by the save on Relog's logout) | #26266 #27095 #27455 #27539 #27590 #27602 #27736 |
|
||||
| instances/ulduar | named tele; Freya wave interval; a Laughing Skull's Lunatic Gaze stops at the brain room's geometry instead of draining sanity through it; a wave of Elder Brightleaf's Unstable Sun Beams stays capped however many players stand in range; Psychosis and Malady of the Mind stop picking a player once they are at 40 Sanity or less; Algalon holds still for 3s once his Big Bang cast lands instead of resuming melee and Quantum Strike on the next tick; a Writhing Lasher stays up past 62947's own 10s summon duration, because the despawn type its AI sets in `IsSummonedBy` is no longer overwritten on the way out of the summon call; a wipe to Thorim's melee with a death-persistent damage shield on the last victim spawns no Cache of Storms while his hard-reset despawn runs, and a lethal hit on the respawned Thorim still yields, runs the outro to his teleport-out without an evade and leaves the saved encounter DONE; an Ancient Water Spirit whose Tidal Wave is kicked casts no damage-and-knockback follow-up; one Elder left alive spawns the one-Elder Freya's Gift, which is the chest whose emblem count the fix corrects; Ignis dying kills every Iron Construct including the dormant ones, and his Kill All Constructs instakill spares the player and every other creature in range; after a wipe exactly one Algalon comes back through his hard-reset respawn, arriving from the sky flagged immune before he lands, even when a player re-enters the raid inside the 20s respawn window, and it stays one past the 30s reload fallback | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestAC_27539_*`, `TestAC_27455_*`, `TestUlduar_BrightleafSunBeamsCappedPerWave`, `TestUlduar_FreyaWardLasherOutlivesSummonDuration`, `TestUlduar_ThorimEvadeDespawnDoesNotYield`, `TestUlduar_FreyaGiftMatchesElderCount`, `TestAC_27736_IgnisKillsAllConstructs`, `TestUlduar_AlgalonWipeRespawnsOnce`); Kologarn Charge `blocked-harness` (bridge Z after Charge); Freya's Gift emblem counts `blocked-harness` (a chest's loot opens only through SPELL_EFFECT_OPEN_LOCK, and CMSG_CAST_SPELL carries no TARGET_FLAG_GAMEOBJECT); Ignis constructs on 25-man `blocked-harness` (no way to set a bot's raid difficulty: CMSG_SET_RAID_DIFFICULTY is unexported and a `characters`.`instance_mode_mask` write is overwritten by the save on Relog's logout) | #26266 #27095 #27455 #27539 #27590 #27602 #27736 |
|
||||
| professions/trainers | every Engineering trainer group offers a visitor exactly the twelve TBC master-tier recipes its class can learn — and none of the others, so a broken class filter fails too. Eleven are class-restricted goggles: four classes cover all twelve across the eight groups, two characters per class cover the Horde/Alliance and Aldor/Scryer interaction splits, and the remaining five classes pin the armour-type sharing at Technician Mihila. Why a hidden recipe is hidden is pinned too: either the class cannot wear the goggle at all — the refusal read from SMSG_INVENTORY_CHANGE_FAILURE, which must be EQUIP_ERR_NO_REQUIRED_PROFICIENCY — or the goggle is another class's within the same armour type, which is why armour proficiency would be the wrong filter to gate on | P2 | covered (`TestAC_27146_*`) | #27146 |
|
||||
| world/dalaran | Dalaran's faction guards eject a hostile player from the Silver Enclave and Sunreaver's Sanctuary, and from nowhere else — public mailboxes, the street, the sewers and the neutral inn are left alone. Both directions are asserted: only checking public ground would pass a build where the guards never fire. One case parks a pet inside a quarter with its owner outside, since every other case is a lone player and would not notice the guard reading the owner's position instead of the trespasser's | P2 | covered (`TestAC_4467_*`) | #4467 |
|
||||
| world/gameevents | Call to Arms banners at the Dalaran portals belong to the side they stand on, and the already-correct Warsong set is unchanged. **Wants an exclusive realm**: starting a holiday re-anchors its schedule in the running worldserver until restart; holidays already running are left alone | P2 | covered (`TestAC_24380_*`); Shattrath's 23 positions `gap` | #24380 |
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
//go:build e2e
|
||||
|
||||
package ulduar_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/azerothcore/AzerothGhost/client"
|
||||
"github.com/azerothcore/AzerothGhost/e2e/e2eharness"
|
||||
"github.com/azerothcore/azerothcore-wotlk/e2e/internal/meta"
|
||||
)
|
||||
|
||||
// Issue: https://github.com/chromiecraft/chromiecraft/issues/10228
|
||||
// Earlier upstream report of the same symptom: https://github.com/azerothcore/azerothcore-wotlk/issues/26759
|
||||
//
|
||||
// Algalon is a temp summon with CREATURE_FLAG_EXTRA_HARD_RESET: a wipe despawns him and the map
|
||||
// re-summons him 20s later (Map::ScheduleCreatureRespawn). The instance script used to keep a
|
||||
// resummon of its own and summon him outright from OnPlayerEnter, so a player who ran back into
|
||||
// Ulduar inside that window found a second Algalon standing in the first one's spot.
|
||||
//
|
||||
// Oracle: after a wipe, with the player re-entering the raid inside the respawn window, exactly
|
||||
// one Algalon comes back, and it stays one past the 30s point where the reload fallback fires.
|
||||
// The one that comes back replays the arrival: it summons the Azeroth planet, drops the immunity
|
||||
// flag and ends up resting where the first arrival did.
|
||||
//
|
||||
// One bot is enough: the wipe is the bot's own death after Algalon has turned aggressive, not a
|
||||
// fight that has to be held up.
|
||||
func TestUlduar_AlgalonWipeRespawnsOnce(t *testing.T) {
|
||||
meta.Begin(t, meta.TestMeta{
|
||||
Tags: []string{"long", "instances"},
|
||||
Runtime: "long",
|
||||
Category: "instances/northrend/ulduar",
|
||||
})
|
||||
|
||||
const (
|
||||
npcAlgalon = uint32(32871)
|
||||
// The planet Algalon summons during his arrival roleplay.
|
||||
npcAzeroth = uint32(34246)
|
||||
|
||||
goPlanetarium10 = uint32(194628)
|
||||
goPlanetarium25 = uint32(194752)
|
||||
itemKey10 = uint32(45797)
|
||||
itemKey25 = uint32(45798)
|
||||
|
||||
// Planetarium console, where Brann spawns and the summon roleplay starts.
|
||||
consoleX, consoleY, consoleZ = float32(1646.2), float32(-174.7), float32(427.3)
|
||||
// Off AlgalonLandPos, where he sets down.
|
||||
landX, landY, landZ = float32(1632.668), float32(-308.5), float32(417.321)
|
||||
|
||||
// Brann's walk plus Algalon's descent before EVENT_INTRO_FINISH drops SetImmuneToPC.
|
||||
introWindow = 4 * time.Minute
|
||||
// 26s intro delay on a first pull before EVENT_INTRO_TIMER_DONE clears NOT_SELECTABLE,
|
||||
// the point from which UpdateVictim runs and a wipe can evade him.
|
||||
pullWindow = 90 * time.Second
|
||||
evadeWindow = 30 * time.Second
|
||||
// DespawnOnEvade's respawn delay is 20s; the re-entry has to land inside it.
|
||||
respawnDelay = 20 * time.Second
|
||||
respawnWindow = 45 * time.Second
|
||||
// OnPlayerEnter's reload fallback fires 30s after the re-entry; watch past it.
|
||||
fallbackWindow = 45 * time.Second
|
||||
// EVENT_INTRO_SUMMON fires 7s into the arrival.
|
||||
azerothWindow = 20 * time.Second
|
||||
// The re-arrival descends at 3s and is done at 5s; a create after that is plain.
|
||||
settledAfter = 15 * time.Second
|
||||
sampleEvery = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
|
||||
Prefix: "AlgRsp", Race: e2eharness.RaceHuman,
|
||||
Class: e2eharness.ClassWarrior, Level: 80, LearnAllClass: true,
|
||||
})
|
||||
|
||||
// Stay GM through the raid enter (.go xyz onto 603 is ignored after .gm off).
|
||||
bot.Teleport(t, consoleX, consoleY, consoleZ, e2eharness.MapUlduar)
|
||||
if _, _, _, m := bot.Pos(); m != e2eharness.MapUlduar {
|
||||
e2eharness.Preconditionf(t, "bot not in Ulduar after console pad tele map=%d", m)
|
||||
}
|
||||
|
||||
// The console is a locked goober with one key entry per raid size, and which one spawned
|
||||
// depends on the difficulty this instance came up at. Carry both.
|
||||
bot.AddItem(t, itemKey10, 1)
|
||||
bot.AddItem(t, itemKey25, 1)
|
||||
|
||||
console := e2eharness.TryNearbyGameObjectByEntry(t, bot.World, goPlanetarium10, 15*time.Second)
|
||||
if console == 0 {
|
||||
console = e2eharness.TryNearbyGameObjectByEntry(t, bot.World, goPlanetarium25, 15*time.Second)
|
||||
}
|
||||
if console == 0 {
|
||||
e2eharness.Preconditionf(t, "no Celestial Planetarium Access console (%d/%d) at the pad", goPlanetarium10, goPlanetarium25)
|
||||
}
|
||||
bot.GameObjectUse(t, console)
|
||||
|
||||
// Brann walks the intro from the console end of the room; wait it out on the platform
|
||||
// Algalon lands on. Instance grids are always loaded, so the roleplay runs regardless.
|
||||
bot.Teleport(t, landX, landY, landZ, e2eharness.MapUlduar)
|
||||
first := bot.WaitUnit(t, npcAlgalon, introWindow)
|
||||
t.Logf("Algalon summoned guid=0x%X", first)
|
||||
|
||||
flags := func(guid uint64) (uint32, bool) {
|
||||
o := bot.World.GetObject(guid)
|
||||
if o == nil {
|
||||
return 0, false
|
||||
}
|
||||
return o.Value(client.UnitFieldFlags), true
|
||||
}
|
||||
// Returns once the flag is clear; a creature that leaves the cache is reported as a
|
||||
// precondition, since a stale flag and a despawn would otherwise read the same.
|
||||
waitFlagClear := func(guid uint64, flag uint32, timeout time.Duration, what string) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
f, ok := flags(guid)
|
||||
if !ok {
|
||||
e2eharness.Preconditionf(t, "Algalon 0x%X vanished while waiting for %s", guid, what)
|
||||
}
|
||||
if f&flag == 0 {
|
||||
return true
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Pulling him mid-roleplay is not the same fight: JustEngagedWith calls events.Reset(),
|
||||
// which drops the pending EVENT_INTRO_FINISH that clears SetImmuneToPC. Wait for the flag.
|
||||
if !waitFlagClear(first, client.UnitFlagImmuneToPC, introWindow, "the arrival roleplay to finish") {
|
||||
e2eharness.Preconditionf(t, "Algalon 0x%X still UNIT_FLAG_IMMUNE_TO_PC after %s; intro roleplay never finished", first, introWindow)
|
||||
}
|
||||
firstRest := bot.World.GetObject(first)
|
||||
if firstRest == nil {
|
||||
e2eharness.Preconditionf(t, "Algalon 0x%X left the cache right after his arrival", first)
|
||||
}
|
||||
restX, restY, restZ := firstRest.PosX, firstRest.PosY, firstRest.PosZ
|
||||
t.Logf("intro roleplay done, Algalon attackable, resting at (%.1f, %.1f, %.1f)", restX, restY, restZ)
|
||||
|
||||
// Drop GM so he fights back, with god on: an ungeared bot dies to his first swings and
|
||||
// he would evade before the hand-over below is observed. DieMust drops god for the wipe.
|
||||
e2eharness.CombatReady(t, bot.World, e2eharness.CombatReadyOpts{God: true})
|
||||
bot.Engage(t, first, 60*time.Second)
|
||||
if !waitFlagClear(first, client.UnitFlagNotSelectable, pullWindow, "the engage roleplay to hand over to combat") {
|
||||
e2eharness.Preconditionf(t, "Algalon 0x%X still NOT_SELECTABLE %s after the pull; the engage roleplay never handed over to combat", first, pullWindow)
|
||||
}
|
||||
t.Logf("Algalon in combat and aggressive, wiping")
|
||||
|
||||
// The wipe: with nobody alive within 120y his evade fails the encounter, and the hard-reset
|
||||
// despawn removes this creature outright.
|
||||
bot.DieMust(t, 25*time.Second)
|
||||
evadeDeadline := time.Now().Add(evadeWindow)
|
||||
for bot.World.GetObject(first) != nil && time.Now().Before(evadeDeadline) {
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
if bot.World.GetObject(first) != nil {
|
||||
e2eharness.Preconditionf(t, "Algalon 0x%X still present %s after the wipe: no evade despawn", first, evadeWindow)
|
||||
}
|
||||
evadeAt := time.Now()
|
||||
t.Logf("Algalon 0x%X despawned on the wipe at %s", first, evadeAt.Format(time.StampMilli))
|
||||
|
||||
// Run back inside the respawn window: leave the raid and come back through the map
|
||||
// transfer, which is what fires OnPlayerEnter. GM mode again for the .go onto 603.
|
||||
bot.GM(t, ".revive")
|
||||
bot.WaitAlive(t, 10*time.Second)
|
||||
bot.GM(t, ".gm on")
|
||||
bot.FlushWorld(t)
|
||||
bot.TeleNamed(t, "dalaran")
|
||||
if _, _, _, m := bot.Pos(); m == e2eharness.MapUlduar {
|
||||
e2eharness.Preconditionf(t, "bot still in Ulduar after the tele out")
|
||||
}
|
||||
bot.Teleport(t, landX, landY, landZ, e2eharness.MapUlduar)
|
||||
if _, _, _, m := bot.Pos(); m != e2eharness.MapUlduar {
|
||||
e2eharness.Preconditionf(t, "bot not back in Ulduar map=%d", m)
|
||||
}
|
||||
reenterAt := time.Now()
|
||||
if reenterAt.Sub(evadeAt) >= respawnDelay {
|
||||
e2eharness.Preconditionf(t, "re-entered the raid %s after the evade, outside the %s respawn window the race needs",
|
||||
reenterAt.Sub(evadeAt).Round(time.Millisecond), respawnDelay)
|
||||
}
|
||||
t.Logf("re-entered Ulduar %s after the evade", reenterAt.Sub(evadeAt).Round(time.Millisecond))
|
||||
|
||||
// The respawn's create packet carries his flight spline, and the harness reads no values out
|
||||
// of such a create (entry and health come back 0) and later loses track of the object, so the
|
||||
// living-unit helpers cannot see him until a plain create arrives. Until the relog below, count
|
||||
// by the entry packed in the GUID and only ever fail on a second one: a fallback summon is a
|
||||
// plain create and always shows up.
|
||||
algalons := func() []uint64 {
|
||||
var out []uint64
|
||||
for _, u := range bot.NearbyUnits(300) {
|
||||
if uint32((u.GUID>>24)&0xFFFFFF) == npcAlgalon {
|
||||
out = append(out, u.GUID)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
failOnSecond := func(seen []uint64, since time.Time, what string) {
|
||||
if len(seen) > 1 {
|
||||
e2eharness.Assertf(t, "%d Algalons %s after %s (guids %v): the re-entry summoned one next to the respawn",
|
||||
len(seen), time.Since(since).Round(time.Millisecond), what, seen)
|
||||
}
|
||||
}
|
||||
|
||||
// Exactly one Algalon comes back.
|
||||
var respawn uint64
|
||||
respawnDeadline := evadeAt.Add(respawnWindow)
|
||||
for time.Now().Before(respawnDeadline) {
|
||||
seen := algalons()
|
||||
failOnSecond(seen, evadeAt, "the wipe")
|
||||
if len(seen) == 1 {
|
||||
respawn = seen[0]
|
||||
break
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
if respawn == 0 {
|
||||
e2eharness.Assertf(t, "no Algalon came back within %s of the wipe", respawnWindow)
|
||||
}
|
||||
if respawn == first {
|
||||
e2eharness.Assertf(t, "Algalon 0x%X came back with the guid of the despawned creature", respawn)
|
||||
}
|
||||
backAt := time.Now()
|
||||
t.Logf("Algalon back as 0x%X %s after the wipe", respawn, backAt.Sub(evadeAt).Round(time.Millisecond))
|
||||
|
||||
// The arrival roleplay summons the planet 7s in; a bare summon at his home position never does.
|
||||
azeroth := bot.WaitUnit(t, npcAzeroth, azerothWindow)
|
||||
t.Logf("arrival replayed: Azeroth 0x%X summoned", azeroth)
|
||||
|
||||
// Keep watching for a second one until his last arrival movement (the descent at 10s) is over,
|
||||
// then relog for a plain, readable create of him.
|
||||
for time.Now().Before(backAt.Add(settledAfter)) {
|
||||
failOnSecond(algalons(), reenterAt, "the re-entry")
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
bot.Relog(t)
|
||||
if landed := bot.WaitUnit(t, npcAlgalon, 30*time.Second); landed != respawn {
|
||||
e2eharness.Assertf(t, "Algalon after the relog is 0x%X, not the respawn 0x%X", landed, respawn)
|
||||
}
|
||||
|
||||
// Still exactly one past the fallback the re-entry scheduled.
|
||||
holdDeadline := reenterAt.Add(fallbackWindow)
|
||||
for time.Now().Before(holdDeadline) {
|
||||
living := e2eharness.LivingByEntries(bot.World, 300, npcAlgalon)
|
||||
if seen := algalons(); len(seen) != 1 || len(living) != 1 {
|
||||
e2eharness.Assertf(t, "%d Algalons (%d living) %s after the re-entry (guids %v), want exactly the respawn 0x%X",
|
||||
len(seen), len(living), time.Since(reenterAt).Round(time.Millisecond), seen, respawn)
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
t.Logf("still one Algalon %s after the re-entry", fallbackWindow)
|
||||
|
||||
// The arrival finishes: he becomes attackable and rests where the first arrival did.
|
||||
if !waitFlagClear(respawn, client.UnitFlagImmuneToPC, introWindow, "the replayed arrival to finish") {
|
||||
e2eharness.Assertf(t, "respawned Algalon 0x%X never dropped UNIT_FLAG_IMMUNE_TO_PC within %s", respawn, introWindow)
|
||||
}
|
||||
o := bot.World.GetObject(respawn)
|
||||
if o == nil {
|
||||
e2eharness.Assertf(t, "respawned Algalon 0x%X gone after finishing the arrival", respawn)
|
||||
}
|
||||
if d := e2eharness.Distance3D(o.PosX, o.PosY, o.PosZ, restX, restY, restZ); d > 3 {
|
||||
e2eharness.Assertf(t, "respawned Algalon 0x%X finished the arrival at (%.1f, %.1f, %.1f), %.1fy from where the first arrival ended (%.1f, %.1f, %.1f)",
|
||||
respawn, o.PosX, o.PosY, o.PosZ, d, restX, restY, restZ)
|
||||
}
|
||||
t.Logf("PASS one Algalon (0x%X) back after the wipe, arrival replayed and resting at (%.1f, %.1f, %.1f)", respawn, o.PosX, o.PosY, o.PosZ)
|
||||
}
|
||||
@@ -363,7 +363,7 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
}
|
||||
}
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
void EnterEvadeMode(EvadeReason /*why*/) override
|
||||
{
|
||||
if (_fightWon)
|
||||
return;
|
||||
@@ -373,23 +373,23 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
me->SetInCombatWithZone();
|
||||
return;
|
||||
}
|
||||
else if (events.GetPhaseMask() & PHASE_NORMAL)
|
||||
|
||||
// A wipe ends with Ascend to the Heavens; EVENT_EVADE then fails the encounter
|
||||
DoAction(ACTION_ASCEND);
|
||||
}
|
||||
|
||||
// Algalon has CREATURE_FLAG_EXTRA_HARD_RESET: the base evade despawns him and the map re-summons him
|
||||
// 20s later at his home position, so point that at the sky for the arrival the instance script replays.
|
||||
void FailEncounter(EvadeReason why)
|
||||
{
|
||||
if (_instance)
|
||||
{
|
||||
DoAction(ACTION_ASCEND);
|
||||
return;
|
||||
_instance->SetBossState(BOSS_ALGALON, FAIL);
|
||||
_instance->StorePersistentData(PERSISTENT_DATA_ALGALON_FIRST_PULL, 1);
|
||||
}
|
||||
|
||||
if (_instance)
|
||||
_instance->SetBossState(BOSS_ALGALON, FAIL);
|
||||
|
||||
if (!_EnterEvadeMode(why))
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
|
||||
Reset();
|
||||
|
||||
sScriptMgr->OnUnitEnterEvadeMode(me, why);
|
||||
me->SetHomePosition(AlgalonSummonPos);
|
||||
ScriptedAI::EnterEvadeMode(why);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
@@ -408,16 +408,7 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
_phaseTwo = false;
|
||||
_heraldOfTheTitans = true;
|
||||
if (_instance)
|
||||
{
|
||||
if (_instance->GetBossState(BOSS_ALGALON) == FAIL)
|
||||
{
|
||||
_firstPull = false;
|
||||
_instance->StorePersistentData(PERSISTENT_DATA_ALGALON_FIRST_PULL, 1);
|
||||
_instance->SetData(DATA_RESUMMON_ALGALON, 0);
|
||||
me->DespawnOrUnsummon(1ms);
|
||||
}
|
||||
_instance->SetBossState(BOSS_ALGALON, NOT_STARTED);
|
||||
}
|
||||
}
|
||||
|
||||
void KilledUnit(Unit* victim) override
|
||||
@@ -443,13 +434,25 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
me->SetHomePosition(AlgalonLandPos);
|
||||
events.Reset();
|
||||
events.SetPhase(PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_1, 5s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_CHANNEL, 6s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_SUMMON, 7s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_DESCEND, 10s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_2, 15s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_3, 23s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_FINISH, 36s, 0, PHASE_ROLE_PLAY);
|
||||
if (_firstPull)
|
||||
{
|
||||
events.ScheduleEvent(EVENT_INTRO_1, 5s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_CHANNEL, 6s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_SUMMON, 7s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_DESCEND, 10s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_2, 15s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_3, 23s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_FINISH, 36s, 0, PHASE_ROLE_PLAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The re-arrival after a wipe has no dialogue, so the visuals run back to back
|
||||
events.ScheduleEvent(EVENT_INTRO_1, 1s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_CHANNEL, 1500ms, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_SUMMON, 2s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_DESCEND, 3s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_FINISH, 5s, 0, PHASE_ROLE_PLAY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_DESPAWN_ALGALON:
|
||||
@@ -778,7 +781,7 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
break;
|
||||
case EVENT_EVADE:
|
||||
events.Reset();
|
||||
ScriptedAI::EnterEvadeMode();
|
||||
FailEncounter(EVADE_REASON_OTHER);
|
||||
return;
|
||||
case EVENT_OUTRO_START:
|
||||
if (_instance)
|
||||
|
||||
@@ -256,7 +256,7 @@ public:
|
||||
// Shared
|
||||
EventMap _events;
|
||||
bool _mimironTramUsed;
|
||||
bool _algalonResummonPending;
|
||||
bool _algalonArrivalPending;
|
||||
|
||||
void Initialize() override
|
||||
{
|
||||
@@ -281,7 +281,7 @@ public:
|
||||
// Shared
|
||||
_events.Reset();
|
||||
_mimironTramUsed = false;
|
||||
_algalonResummonPending = false;
|
||||
_algalonArrivalPending = false;
|
||||
}
|
||||
|
||||
void FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) override
|
||||
@@ -515,29 +515,13 @@ public:
|
||||
ObservationRingKeeperEntry[i],
|
||||
ObservationRingKeepersPos[i]);
|
||||
|
||||
uint32 algalonTimer =
|
||||
GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER);
|
||||
if (!GetObjectGuid(BOSS_ALGALON) && !_algalonResummonPending && algalonTimer
|
||||
&& (algalonTimer <= 60
|
||||
|| algalonTimer == TIMER_ALGALON_TO_SUMMON))
|
||||
{
|
||||
TempSummon* algalon = instance->SummonCreature(NPC_ALGALON, AlgalonLandPos);
|
||||
if (!algalon)
|
||||
return;
|
||||
|
||||
if (algalonTimer <= 60)
|
||||
{
|
||||
_events.RescheduleEvent(EVENT_UPDATE_ALGALON_TIMER, 1min);
|
||||
algalon->AI()->DoAction(ACTION_INIT_ALGALON);
|
||||
}
|
||||
else // if (algalonTimer == TIMER_ALGALON_TO_SUMMON)
|
||||
{
|
||||
StorePersistentData(
|
||||
PERSISTENT_DATA_ALGALON_TIMER,
|
||||
TIMER_ALGALON_SUMMONED);
|
||||
algalon->SetImmuneToPC(false);
|
||||
}
|
||||
}
|
||||
// Only covers an instance reload: after a wipe the map re-summons Algalon itself 20s after
|
||||
// the evade, so wait long enough for that respawn to land first.
|
||||
uint32 algalonTimer = GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER);
|
||||
if (!GetCreature(BOSS_ALGALON) && algalonTimer
|
||||
&& (algalonTimer <= 60 || algalonTimer == TIMER_ALGALON_TO_SUMMON)
|
||||
&& !_events.HasTimeUntilEvent(EVENT_RESUMMON_ALGALON))
|
||||
_events.ScheduleEvent(EVENT_RESUMMON_ALGALON, 30s);
|
||||
}
|
||||
|
||||
bool IsEncounterInProgress() const override
|
||||
@@ -677,6 +661,11 @@ public:
|
||||
case NPC_ALGALON:
|
||||
if (!GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER))
|
||||
creature->DespawnOrUnsummon();
|
||||
else if (_algalonArrivalPending)
|
||||
{
|
||||
_algalonArrivalPending = false;
|
||||
creature->AI()->DoAction(ACTION_START_INTRO);
|
||||
}
|
||||
break;
|
||||
// Gone for good once Flame Leviathan is defeated
|
||||
case NPC_STEELFORGED_DEFENDER:
|
||||
@@ -714,6 +703,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void OnCreatureEvade(Creature* creature) override
|
||||
{
|
||||
// The map re-summons Algalon 20s after his hard-reset evade; the new one replays the arrival
|
||||
if (creature->GetEntry() == NPC_ALGALON && GetBossState(BOSS_ALGALON) != DONE)
|
||||
_algalonArrivalPending = true;
|
||||
}
|
||||
|
||||
void OpenIfDone(uint32 encounter, GameObject* go, GOState state)
|
||||
{
|
||||
if (GetBossState(encounter) == DONE)
|
||||
@@ -976,10 +972,6 @@ public:
|
||||
StorePersistentData(PERSISTENT_DATA_ALGALON_TIMER, 60);
|
||||
_events.RescheduleEvent(EVENT_UPDATE_ALGALON_TIMER, 1min);
|
||||
return;
|
||||
case DATA_RESUMMON_ALGALON:
|
||||
_algalonResummonPending = true;
|
||||
_events.RescheduleEvent(EVENT_RESUMMON_ALGALON, 2s);
|
||||
return;
|
||||
case DATA_ALGALON_SUMMON_STATE:
|
||||
case DATA_ALGALON_DEFEATED:
|
||||
DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, 0);
|
||||
@@ -1209,11 +1201,28 @@ public:
|
||||
break;
|
||||
}
|
||||
case EVENT_RESUMMON_ALGALON:
|
||||
_algalonResummonPending = false;
|
||||
if (!GetCreature(BOSS_ALGALON))
|
||||
if (Creature* algalon = instance->SummonCreature(NPC_ALGALON, AlgalonSummonPos))
|
||||
algalon->AI()->DoAction(ACTION_START_INTRO);
|
||||
{
|
||||
uint32 algalonTimer = GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER);
|
||||
if (GetCreature(BOSS_ALGALON) || !algalonTimer
|
||||
|| (algalonTimer > 60 && algalonTimer != TIMER_ALGALON_TO_SUMMON))
|
||||
break;
|
||||
|
||||
TempSummon* algalon = instance->SummonCreature(NPC_ALGALON, AlgalonLandPos);
|
||||
if (!algalon)
|
||||
break;
|
||||
|
||||
if (algalonTimer <= 60)
|
||||
{
|
||||
_events.RescheduleEvent(EVENT_UPDATE_ALGALON_TIMER, 1min);
|
||||
algalon->AI()->DoAction(ACTION_INIT_ALGALON);
|
||||
}
|
||||
else // TIMER_ALGALON_TO_SUMMON
|
||||
{
|
||||
StorePersistentData(PERSISTENT_DATA_ALGALON_TIMER, TIMER_ALGALON_SUMMONED);
|
||||
algalon->SetImmuneToPC(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,6 @@ enum UlduarData
|
||||
DATA_UNIVERSE_GLOBE = 608,
|
||||
DATA_ALGALON_TRAPDOOR = 609,
|
||||
DATA_BRANN_BRONZEBEARD_ALG = 610,
|
||||
DATA_RESUMMON_ALGALON = 611,
|
||||
|
||||
// Achievements
|
||||
DATA_DWARFAGEDDON = 700,
|
||||
|
||||
Reference in New Issue
Block a user