mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-10-10 07:06:38 +08:00
fix(Scripts/Ulduar): stop Yogg-Saron's Psychosis and Malady of the Mind from picking low Sanity targets (#27628)
Co-authored-by: horn <[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 | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestUlduar_BrightleafSunBeams*`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #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 | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestUlduar_BrightleafSunBeams*`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -525,3 +525,164 @@ func sunBeamGUIDs(beams []sunBeamSnap) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Issue: https://github.com/azerothcore/azerothcore-wotlk/issues/27590
|
||||
// PR: https://github.com/azerothcore/azerothcore-wotlk/pull/27628
|
||||
// Psychosis (63795) and Malady of the Mind (63830) must skip players sitting at 40 Sanity
|
||||
// or less. Both pick a random enemy through TARGET_UNIT_SRC_AREA_ENEMY over a 50000 yd
|
||||
// radius and AzerothCore never filtered that list, so the players closest to going insane
|
||||
// kept being the ones picked.
|
||||
//
|
||||
// Sara is faction 35 and can never own an enemy list, so she cannot be the fixture caster.
|
||||
// A Laughing Skull is the stand-in: faction 14, NullCreatureAI, no threat list, and the
|
||||
// Lunatic Gaze test above already proves its spells select the bot as an enemy. Its own
|
||||
// gaze is stripped first, otherwise the 2 sanity a second it drains drowns the oracle.
|
||||
func TestAC_27590_PsychosisSkipsLowSanity(t *testing.T) {
|
||||
meta.Begin(t, meta.TestMeta{
|
||||
Tags: []string{"med", "instances", "issue"},
|
||||
Runtime: "med",
|
||||
Issue: 27590,
|
||||
Category: "instances/northrend/ulduar",
|
||||
})
|
||||
|
||||
const (
|
||||
npcLaughingSkull = uint32(33990)
|
||||
spellSanity = uint32(63050)
|
||||
spellSanityWell = uint32(64169)
|
||||
spellLunaticGaze = uint32(64167)
|
||||
spellPsychosis = uint32(63795)
|
||||
spellMalady = uint32(63830)
|
||||
|
||||
// Icecrown illusion chamber floor, the pad the Lunatic Gaze test spawns on.
|
||||
padX, padY, padZ = float32(1930.0), float32(-120.0), float32(240.07)
|
||||
|
||||
// Threshold from the 2009-07-02 hotfix: at or below it neither spell may pick the player.
|
||||
sanityFloor = 40
|
||||
|
||||
// Where the walk down starts. A Sanity Well tops up in steps of 20 from the single
|
||||
// stack `.aura` creates, so 1 -> 21 -> 41 -> 61 lands here and Psychosis then steps
|
||||
// 61 -> 52 -> 43 -> 34, straddling the threshold without overshooting it far.
|
||||
sanityStart = 60
|
||||
|
||||
// 63795 is a 2.9s cast and target selection only runs once it completes. A cast that
|
||||
// finds nobody expires quietly, so the no-drain half has to burn the whole window.
|
||||
castWindow = 8 * time.Second
|
||||
|
||||
// Well ticks every 2s; this only has to outlast the three steps up to sanityStart.
|
||||
rampWindow = 20 * time.Second
|
||||
|
||||
// Enough steps to walk sanityStart down past the threshold at 9 a hit, with room to spare.
|
||||
maxPsychosisCasts = 20
|
||||
)
|
||||
|
||||
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
|
||||
Prefix: "YoggPs", Race: e2eharness.RaceHuman, Level: 80,
|
||||
})
|
||||
|
||||
// Stay GM through the raid enter (.go xyz onto 603 is ignored after .gm off).
|
||||
bot.Teleport(t, padX, padY, padZ, e2eharness.MapUlduar)
|
||||
if _, _, _, m := bot.Pos(); m != e2eharness.MapUlduar {
|
||||
e2eharness.Preconditionf(t, "not in Ulduar after brain room tele map=%d", m)
|
||||
}
|
||||
skull := bot.Spawn(t, npcLaughingSkull, 30*time.Second)
|
||||
|
||||
// GM casts and aura edits both act on the current selection, so every one of them says
|
||||
// out loud which unit it means.
|
||||
selectUnit := func(label string, guid uint64) {
|
||||
t.Helper()
|
||||
if err := bot.World.SetTarget(guid); err != nil {
|
||||
e2eharness.Preconditionf(t, "select %s 0x%X: %v", label, guid, err)
|
||||
}
|
||||
}
|
||||
selectSkull := func() { t.Helper(); selectUnit("skull", skull) }
|
||||
selectSelf := func() { t.Helper(); selectUnit("self", bot.World.CharGUID()) }
|
||||
|
||||
selectSkull()
|
||||
bot.GM(t, fmt.Sprintf(".unaura %d", spellLunaticGaze))
|
||||
|
||||
// Drop GM so the spells can select the bot; god mode absorbs the damage half of Psychosis.
|
||||
bot.CombatReady(t)
|
||||
bot.CheatGod(t)
|
||||
|
||||
// Sanity carries AURA_INTERRUPT_FLAG_CHANGE_MAP, so apply it after the tele. `.aura`
|
||||
// creates it at one stack, which is already under the threshold, so a Sanity Well
|
||||
// buff tops it up the same way Freya's wells do in the fight.
|
||||
bot.ApplyAura(t, spellSanity)
|
||||
bot.ApplyAura(t, spellSanityWell)
|
||||
rampDeadline := time.Now().Add(rampWindow)
|
||||
for bot.AuraStacks(spellSanity) < sanityStart && time.Now().Before(rampDeadline) {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
// CMSG_CANCEL_AURA does not take the well off, so strip it server side.
|
||||
selectSelf()
|
||||
bot.GM(t, fmt.Sprintf(".unaura %d", spellSanityWell))
|
||||
|
||||
start := bot.AuraStacks(spellSanity)
|
||||
if start <= sanityFloor {
|
||||
e2eharness.Preconditionf(t, "Sanity ramped to %d stacks, need more than %d to drive the threshold", start, sanityFloor)
|
||||
}
|
||||
|
||||
// Every later delta is attributed to the spell under test, so nothing else may be moving
|
||||
// sanity now: this catches both a skull that still gazes and a well that never came off.
|
||||
time.Sleep(3 * time.Second)
|
||||
if quiet := bot.AuraStacks(spellSanity); quiet != start {
|
||||
e2eharness.Preconditionf(t, "sanity still moving on its own before the first cast (%d -> %d), skull 0x%X", start, quiet, skull)
|
||||
}
|
||||
|
||||
// `.cast self` makes the *selected* unit cast on itself. Psychosis and Malady take their
|
||||
// real targets from the area around the caster, which is the same selection path Sara
|
||||
// drives in phase 2. Returns the stacks the bot lost, 0 if the cast never reached it.
|
||||
castFromSkull := func(spellID uint32) int {
|
||||
t.Helper()
|
||||
before := bot.AuraStacks(spellSanity)
|
||||
selectSkull()
|
||||
bot.GM(t, fmt.Sprintf(".cast self %d", spellID))
|
||||
deadline := time.Now().Add(castWindow)
|
||||
for time.Now().Before(deadline) {
|
||||
if now := bot.AuraStacks(spellSanity); now != before {
|
||||
return before - now
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Fixture check: at full sanity the spell must reach the bot at all. Without this a
|
||||
// mis-aimed `.cast self` (it falls back to the caster's own player when nothing is
|
||||
// selected) would read as a clean pass on both of the assertions below.
|
||||
if lost := castFromSkull(spellMalady); lost <= 0 {
|
||||
e2eharness.Preconditionf(t, "fixture dead: Malady of the Mind drained nothing at %d sanity", bot.AuraStacks(spellSanity))
|
||||
}
|
||||
|
||||
lastValid := 0
|
||||
for i := 0; i < maxPsychosisCasts; i++ {
|
||||
before := bot.AuraStacks(spellSanity)
|
||||
if before <= sanityFloor {
|
||||
break
|
||||
}
|
||||
lost := castFromSkull(spellPsychosis)
|
||||
if lost <= 0 {
|
||||
e2eharness.ConfirmedBugf(t, 27590,
|
||||
"Psychosis drained nothing at %d sanity, which is above the %d threshold", before, sanityFloor)
|
||||
}
|
||||
lastValid = before
|
||||
t.Logf("Psychosis at %d sanity drained %d -> %d", before, lost, bot.AuraStacks(spellSanity))
|
||||
}
|
||||
|
||||
atFloor := bot.AuraStacks(spellSanity)
|
||||
if atFloor > sanityFloor {
|
||||
e2eharness.Preconditionf(t, "never reached the threshold, stuck at %d sanity after %d casts", atFloor, maxPsychosisCasts)
|
||||
}
|
||||
|
||||
if lost := castFromSkull(spellPsychosis); lost > 0 {
|
||||
e2eharness.ConfirmedBugf(t, 27590,
|
||||
"Psychosis drained %d sanity from a player at %d, at or below the %d threshold", lost, atFloor, sanityFloor)
|
||||
}
|
||||
atFloor = bot.AuraStacks(spellSanity)
|
||||
if lost := castFromSkull(spellMalady); lost > 0 {
|
||||
e2eharness.ConfirmedBugf(t, 27590,
|
||||
"Malady of the Mind drained %d sanity from a player at %d, at or below the %d threshold", lost, atFloor, sanityFloor)
|
||||
}
|
||||
|
||||
t.Logf("PASS low sanity targeting: both spells landed down to %d sanity and neither reached the player at %d", lastValid, atFloor)
|
||||
}
|
||||
|
||||
@@ -2873,9 +2873,27 @@ class spell_yogg_saron_sanity_reduce : public SpellScript
|
||||
}
|
||||
}
|
||||
|
||||
// Psychosis and Malady of the Mind skip anyone at 40 Sanity or less, so that their random
|
||||
// targeting evens out across the raid instead of finishing off the lowest players.
|
||||
void FilterTargets(std::list<WorldObject*>& targets)
|
||||
{
|
||||
targets.remove_if([](WorldObject* target)
|
||||
{
|
||||
Unit* unit = target->ToUnit();
|
||||
if (!unit)
|
||||
return true;
|
||||
|
||||
Aura* sanity = unit->GetAura(SPELL_SANITY);
|
||||
return !sanity || sanity->GetStackAmount() <= 40;
|
||||
});
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_yogg_saron_sanity_reduce::HandleScriptEffect, EFFECT_FIRST_FOUND, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
|
||||
if (m_scriptSpellId == SPELL_SARA_PSYCHOSIS_10 || m_scriptSpellId == SPELL_SARA_PSYCHOSIS_25 || m_scriptSpellId == SPELL_MALADY_OF_THE_MIND)
|
||||
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_yogg_saron_sanity_reduce::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user