mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-10-10 07:06:38 +08:00
fix(Scripts/Ulduar): give Algalon a stasis period after Big Bang (#27630)
Co-authored-by: Shauren <[email protected]>
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; Elder Brightleaf's Unstable Sun Beams despawn on their own timer instead of outliving him, and one lands under the player rather than all on the elder; Psychosis and Malady of the Mind stop picking a player once they are at 40 Sanity or less | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestUlduar_BrightleafSunBeams*`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #27590 #27602 |
|
||||
| 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; Elder Brightleaf's Unstable Sun Beams despawn on their own timer instead of outliving him, and one lands under the player rather than all on the elder; 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 | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestAC_27539_*`, `TestUlduar_BrightleafSunBeams*`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #27539 #27590 #27602 |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
_ "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"
|
||||
)
|
||||
@@ -686,3 +687,254 @@ func TestAC_27590_PsychosisSkipsLowSanity(t *testing.T) {
|
||||
|
||||
t.Logf("PASS low sanity targeting: both spells landed down to %d sanity and neither reached the player at %d", lastValid, atFloor)
|
||||
}
|
||||
|
||||
// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/27539
|
||||
// PR: https://github.com/azerothcore/azerothcore-wotlk/pull/27630
|
||||
// Big Bang is an 8s cast the raid survives by hiding inside a black hole. When it ends
|
||||
// Algalon holds still for 3s before he picks the fight back up; AzerothCore resumed melee,
|
||||
// Quantum Strike and the chase on the very next AI tick instead.
|
||||
//
|
||||
// The oracle is the bots' combined health, compared against a running peak because the pool
|
||||
// regenerates while the fight runs: regeneration only raises the peak, so any decrease at all
|
||||
// is Algalon landing something. Big Bang
|
||||
// triggers 64445 on every player for exactly one second at the moment the cast lands, which
|
||||
// is the protocol-visible marker the window is measured from. Reading the total rather than
|
||||
// one bot's health keeps a tank swap mid-window from reading as a quiet boss.
|
||||
//
|
||||
// Two bots, because a solo one is not enough to hold the encounter up. Phase Punch's 5th
|
||||
// stack applies 64417, a SPELL_AURA_PHASE that moves its target out of Algalon's phase for
|
||||
// good (taking 64412 off afterwards does not remove it). With one player that leaves every
|
||||
// threat reference offline via !CanSeeOrDetect, and Algalon sits flagged in combat unable to
|
||||
// touch anyone. A second body is what a real raid's tank swap provides.
|
||||
//
|
||||
// Living Constellations are killed on sight because their Arcane Barrage is the one other
|
||||
// thing that can reach a bot inside the window. Collapsing Stars are left alone: all four
|
||||
// spawn points sit about 20y from where Algalon lands, well clear of a black hole's 6y reach.
|
||||
func TestAC_27539_AlgalonBigBangStasis(t *testing.T) {
|
||||
meta.Begin(t, meta.TestMeta{
|
||||
Tags: []string{"long", "instances", "issue", "multi_bot"},
|
||||
Runtime: "long",
|
||||
Issue: 27539,
|
||||
Category: "instances/northrend/ulduar",
|
||||
})
|
||||
|
||||
const (
|
||||
npcAlgalon = uint32(32871)
|
||||
npcLivingConstellation = uint32(33052)
|
||||
// Enough to drop a constellation in one hit.
|
||||
constellationHit = uint32(500000)
|
||||
|
||||
goPlanetarium10 = uint32(194628)
|
||||
goPlanetarium25 = uint32(194752)
|
||||
itemKey10 = uint32(45797)
|
||||
itemKey25 = uint32(45798)
|
||||
|
||||
spellPhasePunch = uint32(64412)
|
||||
// The 5th Phase Punch stack: SPELL_AURA_PHASE, misc 16, the same phase a black hole
|
||||
// puts a player in.
|
||||
spellPhasePunchShift = uint32(64417)
|
||||
spellRemovePhase = uint32(64445)
|
||||
|
||||
// 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. Standing on his exact coordinates leaves
|
||||
// the facing check no direction and every swing returns SMSG_ATTACKSWING_BAD_FACING.
|
||||
landX, landY, landZ = float32(1632.668), float32(-308.5), float32(417.321)
|
||||
|
||||
botHealth = 10000000
|
||||
|
||||
// Brann's walk plus Algalon's descent before EVENT_INTRO_FINISH drops SetImmuneToPC.
|
||||
introWindow = 4 * time.Minute
|
||||
// 90s fight timer plus the intro delay (26s on a first pull), with slack.
|
||||
bigBangWindow = 3 * time.Minute
|
||||
|
||||
// Big Bang's damage and its 64445 marker come out of the same cast; give the health
|
||||
// update from that cast time to arrive before the quiet window opens.
|
||||
settle = 700 * time.Millisecond
|
||||
// The script holds Algalon for 3s, but the server starts counting when it applies
|
||||
// 64445 and this window starts when the client *sees* it, so the tail of a full 3s
|
||||
// window runs past the moment he is allowed to move again. Stop short of the boundary
|
||||
// by enough to cover that lag plus a world tick. A boss that never held still at all
|
||||
// lands something around 1s, far inside what is still asserted.
|
||||
stasisWindow = 2300 * time.Millisecond
|
||||
// Quantum Strike repeats every 3-4.5s and melee is faster, so a boss that resumed
|
||||
// lands something well inside this.
|
||||
resumeWindow = 10 * time.Second
|
||||
|
||||
sampleEvery = 100 * time.Millisecond
|
||||
// Throttle for the sweep that keeps the bots targetable and the field clear.
|
||||
sweepEvery = 3 * time.Second
|
||||
)
|
||||
|
||||
bots := e2eharness.NewScenario(t, e2eharness.ScenarioOpts{
|
||||
Prefix: "AlgBB",
|
||||
Bots: []e2eharness.BotSpec{
|
||||
{Role: "tank", Race: e2eharness.RaceHuman, Class: e2eharness.ClassWarrior, Level: 80, LearnAllClass: true},
|
||||
{Role: "spare", Race: e2eharness.RaceHuman, Class: e2eharness.ClassWarrior, Level: 80, LearnAllClass: true},
|
||||
},
|
||||
})
|
||||
tank := e2eharness.ByRole(t, bots, "tank")
|
||||
spare := e2eharness.ByRole(t, bots, "spare")
|
||||
|
||||
// Stay GM through the raid enter (.go xyz onto 603 is ignored after .gm off).
|
||||
for _, b := range bots {
|
||||
b.Teleport(t, consoleX, consoleY, consoleZ, e2eharness.MapUlduar)
|
||||
if _, _, _, m := b.Pos(); m != e2eharness.MapUlduar {
|
||||
e2eharness.Preconditionf(t, "%s not in Ulduar after console pad tele map=%d", b.Name, 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.
|
||||
tank.AddItem(t, itemKey10, 1)
|
||||
tank.AddItem(t, itemKey25, 1)
|
||||
|
||||
console := e2eharness.TryNearbyGameObjectByEntry(t, tank.World, goPlanetarium10, 15*time.Second)
|
||||
if console == 0 {
|
||||
console = e2eharness.TryNearbyGameObjectByEntry(t, tank.World, goPlanetarium25, 15*time.Second)
|
||||
}
|
||||
if console == 0 {
|
||||
e2eharness.Preconditionf(t, "no Celestial Planetarium Access console (%d/%d) at the pad", goPlanetarium10, goPlanetarium25)
|
||||
}
|
||||
tank.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.
|
||||
tank.Teleport(t, landX, landY, landZ, e2eharness.MapUlduar)
|
||||
spare.Teleport(t, landX+4, landY-2, landZ, e2eharness.MapUlduar)
|
||||
algalon := tank.WaitUnit(t, npcAlgalon, introWindow)
|
||||
t.Logf("Algalon summoned guid=0x%X", algalon)
|
||||
|
||||
// Pulling him mid-roleplay is not the same fight: JustEngagedWith calls events.Reset(),
|
||||
// which drops the pending EVENT_INTRO_FINISH that clears SetImmuneToPC, and he then never
|
||||
// reaches EVENT_INTRO_TIMER_DONE to turn REACT_AGGRESSIVE. Wait for the flag to clear.
|
||||
immune := func() bool {
|
||||
o := tank.World.GetObject(algalon)
|
||||
return o == nil || o.Value(client.UnitFieldFlags)&client.UnitFlagImmuneToPC != 0
|
||||
}
|
||||
introDeadline := time.Now().Add(introWindow)
|
||||
for immune() && time.Now().Before(introDeadline) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
if immune() {
|
||||
e2eharness.Preconditionf(t, "Algalon 0x%X still UNIT_FLAG_IMMUNE_TO_PC after %s; intro roleplay never finished", algalon, introWindow)
|
||||
}
|
||||
t.Logf("intro roleplay done, Algalon attackable")
|
||||
|
||||
// Drop GM so he fights back, then make both bots able to sit through a Big Bang. Note the
|
||||
// bare CombatReady/CombatReadyFull helpers turn `.cheat god` on, which would leave the
|
||||
// bots invulnerable and every quiet window vacuous: this oracle needs real damage.
|
||||
for _, b := range bots {
|
||||
e2eharness.CombatReady(t, b.World, e2eharness.CombatReadyOpts{})
|
||||
if err := b.World.SetTarget(b.World.CharGUID()); err != nil {
|
||||
e2eharness.Preconditionf(t, "%s select self: %v", b.Name, err)
|
||||
}
|
||||
b.GM(t, fmt.Sprintf(".modify hp %d", botHealth))
|
||||
}
|
||||
|
||||
tank.Engage(t, algalon, 60*time.Second)
|
||||
t.Logf("engaged Algalon, waiting for Big Bang")
|
||||
|
||||
totalHP := func() uint32 {
|
||||
var sum uint32
|
||||
for _, b := range bots {
|
||||
hp, _ := b.UnitHP(b.World.CharGUID())
|
||||
sum += hp
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
sweep := func() bool {
|
||||
// Stripped every pass rather than on a stack threshold: a missed read is
|
||||
// unrecoverable, because once the 5th stack lands 64417 stays on and taking 64412 off
|
||||
// does not remove it. This is the tank swap a real raid does, and Phase Punch only
|
||||
// comes around every 15.5s.
|
||||
for _, b := range bots {
|
||||
if err := b.World.SetTarget(b.World.CharGUID()); err == nil {
|
||||
b.GM(t, fmt.Sprintf(".unaura %d", spellPhasePunch))
|
||||
b.GM(t, fmt.Sprintf(".unaura %d", spellPhasePunchShift))
|
||||
}
|
||||
}
|
||||
if guids := e2eharness.LivingByEntries(tank.World, 300, npcLivingConstellation); len(guids) > 0 {
|
||||
tank.DamageKill(t, guids, constellationHit, 10*time.Second)
|
||||
t.Logf("cleared %d Living Constellation(s)", len(guids))
|
||||
}
|
||||
bossHP, _ := tank.UnitHP(algalon)
|
||||
t.Logf("sweep: total hp=%d boss hp=%d bossTarget=0x%X inCombat=%v",
|
||||
totalHP(), bossHP, tank.UnitTarget(algalon), tank.UnitInCombat(algalon))
|
||||
tank.Attack(t, algalon)
|
||||
return tank.UnitInCombat(algalon)
|
||||
}
|
||||
|
||||
startHP := totalHP()
|
||||
var bigBangAt time.Time
|
||||
lastSweep := time.Time{}
|
||||
waitDeadline := time.Now().Add(bigBangWindow)
|
||||
for time.Now().Before(waitDeadline) {
|
||||
if tank.HasAura(spellRemovePhase) || spare.HasAura(spellRemovePhase) {
|
||||
bigBangAt = time.Now()
|
||||
break
|
||||
}
|
||||
if time.Since(lastSweep) >= sweepEvery {
|
||||
if !sweep() {
|
||||
e2eharness.Preconditionf(t, "Algalon left combat before Big Bang (total hp %d -> %d)", startHP, totalHP())
|
||||
}
|
||||
lastSweep = time.Now()
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
if bigBangAt.IsZero() {
|
||||
e2eharness.Preconditionf(t, "no Big Bang (%d) within %s of the pull", spellRemovePhase, bigBangWindow)
|
||||
}
|
||||
|
||||
// Oracle liveness: if nothing has been hitting the bots up to here, a quiet window proves
|
||||
// nothing at all.
|
||||
preHP := totalHP()
|
||||
if preHP >= startHP {
|
||||
e2eharness.Preconditionf(t, "bots took no damage before Big Bang (total %d -> %d), the quiet window would be vacuous", startHP, preHP)
|
||||
}
|
||||
t.Logf("Big Bang landed, total hp %d -> %d", startHP, preHP)
|
||||
|
||||
// The bots regenerate while this runs, so the total is not monotonic and a plain
|
||||
// "below the value at window start" check would let regeneration mask a hit. Compare
|
||||
// against a running peak instead: regeneration only raises it, and any decrease at all
|
||||
// is Algalon landing something.
|
||||
time.Sleep(settle)
|
||||
quietHP := totalHP()
|
||||
peakHP := quietHP
|
||||
for time.Since(bigBangAt) < stasisWindow {
|
||||
hp := totalHP()
|
||||
if hp < peakHP {
|
||||
e2eharness.ConfirmedBugf(t, 27539,
|
||||
"Algalon acted %s after Big Bang, inside the 3s stasis: total hp %d -> %d",
|
||||
time.Since(bigBangAt).Round(time.Millisecond), peakHP, hp)
|
||||
}
|
||||
if hp > peakHP {
|
||||
peakHP = hp
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
|
||||
// The window must be quiet because he is holding still, not because he evaded or the bots
|
||||
// stopped being targets: he has to come back swinging right after it.
|
||||
resumed := false
|
||||
resumeDeadline := time.Now().Add(resumeWindow)
|
||||
for time.Now().Before(resumeDeadline) {
|
||||
hp := totalHP()
|
||||
if hp < peakHP {
|
||||
resumed = true
|
||||
break
|
||||
}
|
||||
if hp > peakHP {
|
||||
peakHP = hp
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
if !resumed {
|
||||
e2eharness.Preconditionf(t,
|
||||
"Algalon never resumed within %s of the stasis ending (total hp still %d, in combat=%v) — the quiet window proves nothing",
|
||||
resumeWindow, quietHP, tank.UnitInCombat(algalon))
|
||||
}
|
||||
|
||||
t.Logf("PASS Big Bang stasis: no damage for %s after the cast, then Algalon resumed", stasisWindow)
|
||||
}
|
||||
|
||||
@@ -171,6 +171,11 @@ enum EncounterPhases
|
||||
PHASE_MASK_NO_CAST_CHECK = 1 << (PHASE_ROLE_PLAY - 1),
|
||||
};
|
||||
|
||||
enum EventGroups
|
||||
{
|
||||
GROUP_COMBAT = 1,
|
||||
};
|
||||
|
||||
enum Texts
|
||||
{
|
||||
SAY_BRANN_ALGALON_INTRO_1 = 0,
|
||||
@@ -395,6 +400,7 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
me->SetCombatMovement(true);
|
||||
me->SetImmuneToPC(false);
|
||||
me->SetSheath(SHEATH_STATE_UNARMED);
|
||||
me->SetFaction(190);
|
||||
@@ -477,6 +483,7 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
case ACTION_ASCEND:
|
||||
summons.DespawnAll();
|
||||
events.SetPhase(PHASE_BIG_BANG);
|
||||
events.CancelEvent(EVENT_RESUME_UPDATING);
|
||||
events.ScheduleEvent(EVENT_ASCEND_TO_THE_HEAVENS, 1500ms);
|
||||
break;
|
||||
case ACTION_FEEDS_ON_TEARS_FAILED:
|
||||
@@ -522,10 +529,10 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
introDelay = 8500ms;
|
||||
|
||||
events.ScheduleEvent(EVENT_INTRO_TIMER_DONE, introDelay);
|
||||
events.ScheduleEvent(EVENT_QUANTUM_STRIKE, 3500ms + introDelay);
|
||||
events.ScheduleEvent(EVENT_PHASE_PUNCH, 15500ms + introDelay);
|
||||
events.ScheduleEvent(EVENT_QUANTUM_STRIKE, 3500ms + introDelay, GROUP_COMBAT);
|
||||
events.ScheduleEvent(EVENT_PHASE_PUNCH, 15500ms + introDelay, GROUP_COMBAT);
|
||||
events.ScheduleEvent(EVENT_SUMMON_COLLAPSING_STAR, 16500ms + introDelay);
|
||||
events.ScheduleEvent(EVENT_COSMIC_SMASH, 26s + introDelay);
|
||||
events.ScheduleEvent(EVENT_COSMIC_SMASH, 26s + introDelay, GROUP_COMBAT);
|
||||
events.ScheduleEvent(EVENT_ACTIVATE_LIVING_CONSTELLATION, 60s + introDelay);
|
||||
events.ScheduleEvent(EVENT_BIG_BANG, 90s + introDelay);
|
||||
events.ScheduleEvent(EVENT_ASCEND_TO_THE_HEAVENS, 360s + introDelay);
|
||||
@@ -745,13 +752,29 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
summons.DoAction(ACTION_BIG_BANG, pred);
|
||||
|
||||
me->CastSpell((Unit*)nullptr, SPELL_BIG_BANG, false);
|
||||
|
||||
// 8s cast, then Algalon holds still for 3s before resuming
|
||||
events.SetPhase(PHASE_BIG_BANG);
|
||||
events.ScheduleEvent(EVENT_RESUME_UPDATING, 11s);
|
||||
events.DelayEventsToMax(11s, GROUP_COMBAT);
|
||||
events.Repeat(90s + 500ms);
|
||||
|
||||
me->SetCombatMovement(false);
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
break;
|
||||
}
|
||||
case EVENT_RESUME_UPDATING:
|
||||
events.SetPhase(PHASE_NORMAL);
|
||||
me->SetCombatMovement(true);
|
||||
me->ResumeChasingVictim();
|
||||
break;
|
||||
case EVENT_ASCEND_TO_THE_HEAVENS:
|
||||
Talk(SAY_ALGALON_ASCEND);
|
||||
me->CastSpell((Unit*)nullptr, SPELL_ASCEND_TO_THE_HEAVENS, false);
|
||||
events.ScheduleEvent(EVENT_EVADE, 2500ms);
|
||||
events.CancelEvent(EVENT_RESUME_UPDATING);
|
||||
me->SetCombatMovement(true);
|
||||
break;
|
||||
case EVENT_EVADE:
|
||||
events.Reset();
|
||||
@@ -849,7 +872,8 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
break;
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
if (!(events.GetPhaseMask() & PHASE_MASK_NO_UPDATE))
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user