fix(Scripts/Ulduar): make Yogg-Saron's Laughing Skulls respect line of sight (#27614)

This commit is contained in:
Andrew
2026-09-11 21:28:07 -03:00
committed by GitHub
parent 3ef3432cb6
commit c0e6272af9
3 changed files with 89 additions and 8 deletions
+1 -1
View File
@@ -163,7 +163,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 | P2 | covered (`TestAC_27095_*`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 |
| 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 | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #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 |
---
@@ -277,3 +277,81 @@ func TestUlduar_MultiBotLoginNearBossPad(t *testing.T) {
}
t.Logf("PASS multi-bot Freya pad login n=%d map=%d", len(bots), m0)
}
// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/27602
// PR: https://github.com/azerothcore/azerothcore-wotlk/pull/27614
// A Laughing Skull's Lunatic Gaze (64168) must not reach a player behind the brain
// room's geometry. 64168 has no ignore-LoS attribute of its own; it used to inherit
// one from the aura that triggers it (64167), so the skulls drained sanity through
// walls. The clear-line half runs first and is the fixture check: without sanity
// loss there, the blocked half proves nothing.
func TestAC_27602_LaughingSkullGazeLoS(t *testing.T) {
meta.Begin(t, meta.TestMeta{
Tags: []string{"med", "instances", "issue"},
Runtime: "med",
Issue: 27602,
Category: "instances/northrend/ulduar",
})
const (
npcLaughingSkull = uint32(33990)
spellSanity = uint32(63050)
// Icecrown illusion chamber floor. The pair sits 18 yd either side of the
// skull: due west is open, due east is behind structure (verified in-world
// with a LoS-respecting player cast at 12, 18 and 24 yd).
skullX, skullY, skullZ = float32(1930.0), float32(-120.0), float32(240.07)
clearX, clearY = float32(1912.0), float32(-120.0)
blockedX, blockedY = float32(1948.0), float32(-120.0)
// 64167 ticks every second for 2 sanity; 8s leaves margin for spawn settle.
gazeWindow = 8 * time.Second
)
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
Prefix: "YoggLo", Race: e2eharness.RaceHuman, Level: 80,
})
// Stay GM through the raid enter (.go xyz onto 603 is ignored after .gm off).
bot.Teleport(t, skullX, skullY, skullZ, e2eharness.MapUlduar)
if _, _, _, m := bot.Pos(); m != e2eharness.MapUlduar {
e2eharness.Preconditionf(t, "not in Ulduar after brain room tele map=%d", m)
}
// The skulls are SummonCreature'd by the Brain AI, not by a spell, so a GM spawn
// is the fixture rather than a stand-in for a summon path. 64167 rides on
// creature_template_addon, so the spawn gazes on its own with no AI to drive.
skull := bot.Spawn(t, npcLaughingSkull, 30*time.Second)
// Drop GM so the gaze can select the bot; god mode absorbs the 1749/s it deals.
bot.CombatReady(t)
bot.CheatGod(t)
// Sanity carries AURA_INTERRUPT_FLAG_CHANGE_MAP, so apply it after the tele.
sanityLost := func(label string, px, py float32) int {
bot.Teleport(t, px, py, skullZ, e2eharness.MapUlduar)
bot.WaitUnitGUID(t, skull, 15*time.Second) // tele clears the object cache
bot.Face(t, skull) // 64168 only takes targets facing the caster
bot.ApplyAura(t, spellSanity)
before := bot.AuraStacks(spellSanity)
if before == 0 {
e2eharness.Preconditionf(t, "%s: Sanity 63050 did not apply", label)
}
time.Sleep(gazeWindow)
after := bot.AuraStacks(spellSanity)
bot.CancelAura(t, spellSanity)
t.Logf("%s pos=(%.1f,%.1f) skull=0x%X sanity %d -> %d", label, px, py, skull, before, after)
return before - after
}
clearLoss := sanityLost("CLEAR", clearX, clearY)
if clearLoss <= 0 {
e2eharness.Preconditionf(t, "fixture dead: skull 0x%X drained no sanity with a clear line", skull)
}
blockedLoss := sanityLost("BLOCKED", blockedX, blockedY)
if blockedLoss > 0 {
e2eharness.ConfirmedBugf(t, 27602,
"Lunatic Gaze drained %d sanity through the brain room geometry (clear line drained %d)",
blockedLoss, clearLoss)
}
t.Logf("PASS Lunatic Gaze LoS: clear drained %d, blocked drained %d", clearLoss, blockedLoss)
}
@@ -2586,14 +2586,17 @@ class spell_yogg_saron_lunatic_gaze : public SpellScript
void FilterTargets(std::list<WorldObject*>& targets)
{
std::list<WorldObject*> tmplist;
for (std::list<WorldObject*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
if ((*itr)->HasInArc(M_PI, GetCaster()))
tmplist.push_back(*itr);
Unit* caster = GetCaster();
// 64168 inherits SPELL_ATTR2_IGNORE_LINE_OF_SIGHT from the aura triggering it, so the illusion room walls have to be checked here
bool ignoreLos = GetSpellInfo()->HasAttribute(SPELL_ATTR2_IGNORE_LINE_OF_SIGHT);
targets.clear();
for (std::list<WorldObject*>::iterator itr = tmplist.begin(); itr != tmplist.end(); ++itr)
targets.push_back(*itr);
targets.remove_if([caster, ignoreLos](WorldObject* target)
{
if (!target->HasInArc(M_PI, caster))
return true;
return !ignoreLos && !caster->IsWithinLOSInMap(target, VMAP::ModelIgnoreFlags::M2);
});
}
void Register() override