mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-10-10 07:06:38 +08:00
fix(Scripts/Ulduar): stop Freya's Tidal Wave when its cast is kicked (#27636)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
--
|
||||
-- Ancient Water Spirit: Tidal Wave damage and knockback now follow the cast, not a timer
|
||||
|
||||
DELETE FROM `spell_script_names` WHERE `spell_id` IN (62653, 62935) AND `ScriptName` = 'spell_freya_tidal_wave';
|
||||
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
|
||||
(62653, 'spell_freya_tidal_wave'),
|
||||
(62935, 'spell_freya_tidal_wave');
|
||||
+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, one lands under the player rather than all on the elder, and a wave 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 | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestAC_27539_*`, `TestUlduar_BrightleafSunBeams*`, `TestUlduar_FreyaWardLasherOutlivesSummonDuration`, `TestUlduar_ThorimEvadeDespawnDoesNotYield`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #27539 #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, one lands under the player rather than all on the elder, and a wave 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; an Ancient Water Spirit whose Tidal Wave is kicked casts no damage-and-knockback follow-up | P2 | covered (`TestAC_27095_*`, `TestAC_27602_*`, `TestAC_27590_*`, `TestAC_27539_*`, `TestAC_27455_*`, `TestUlduar_BrightleafSunBeams*`, `TestUlduar_FreyaWardLasherOutlivesSummonDuration`, `TestUlduar_ThorimEvadeDespawnDoesNotYield`); Kologarn Charge `blocked-harness` (bridge Z after Charge) | #26266 #27095 #27455 #27539 #27590 #27602 |
|
||||
| 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 |
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
package ulduar_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -468,6 +470,20 @@ type sunBeamSnap struct {
|
||||
x, y, z float32
|
||||
}
|
||||
|
||||
// unitFlags reads UNIT_FIELD_FLAGS off a unit in the object cache.
|
||||
func unitFlags(bot *e2eharness.ScenarioBot, guid uint64) uint32 {
|
||||
obj := bot.World.GetObject(guid)
|
||||
if obj == nil {
|
||||
return 0
|
||||
}
|
||||
return obj.Value(client.UnitFieldFlags)
|
||||
}
|
||||
|
||||
// elderNotSelectable reports the flag boss_freya_elder_brightleaf::JustEngagedWith gates on.
|
||||
func elderNotSelectable(bot *e2eharness.ScenarioBot, guid uint64) bool {
|
||||
return unitFlags(bot, guid)&client.UnitFlagNotSelectable != 0
|
||||
}
|
||||
|
||||
// sunBeamsInCache reports beams by presence, not liveness: the bug is the object still existing.
|
||||
func sunBeamsInCache(bot *e2eharness.ScenarioBot, entry uint32, maxDist float32) []sunBeamSnap {
|
||||
var out []sunBeamSnap
|
||||
@@ -508,6 +524,35 @@ func waitLivingBrightleaf(t *testing.T, bot *e2eharness.ScenarioBot) uint64 {
|
||||
if bot.UnitHasAura(elder, spellPurpleBanish) || bot.UnitHasAura(elder, spellBrightleafEssence) {
|
||||
e2eharness.Preconditionf(t, "Elder Brightleaf is banished into Freya's hard mode in this instance, so he schedules no sun beams")
|
||||
}
|
||||
|
||||
// Freya's hard-mode activation full-heals every living elder, flags it
|
||||
// UNIT_FLAG_NOT_SELECTABLE, turns it REACT_PASSIVE and puts it in combat with the zone.
|
||||
// Either leftover suppresses the beams: boss_freya_elder_brightleaf::JustEngagedWith returns
|
||||
// on that flag without scheduling anything, and an elder still in combat never gets a second
|
||||
// JustEngagedWith when this bot pulls it. The banish auras applied alongside are dropped when
|
||||
// Freya resets, so the aura checks above see a healthy elder either way. Killing it and
|
||||
// respawning runs the Reset that clears both.
|
||||
if elderNotSelectable(bot, elder) || bot.UnitInCombat(elder) {
|
||||
t.Logf("Elder Brightleaf is left over from a Freya pull in this instance (flags=0x%X inCombat=%v); respawning him",
|
||||
unitFlags(bot, elder), bot.UnitInCombat(elder))
|
||||
bot.DamageKill(t, []uint64{elder}, 10_000_000, 15*time.Second)
|
||||
if err := bot.World.SetTarget(elder); err != nil {
|
||||
e2eharness.Preconditionf(t, "select Elder Brightleaf to respawn: %v", err)
|
||||
}
|
||||
bot.GM(t, ".respawn")
|
||||
respawned := time.Now().Add(30 * time.Second)
|
||||
for {
|
||||
elder = bot.WaitUnit(t, npcElderBrightleaf, 10*time.Second)
|
||||
if hp, maxHP := bot.UnitHP(elder); maxHP > 0 && hp > 0 && !elderNotSelectable(bot, elder) && !bot.UnitInCombat(elder) {
|
||||
break
|
||||
}
|
||||
if !time.Now().Before(respawned) {
|
||||
e2eharness.Preconditionf(t, "Elder Brightleaf still unpullable after respawn (flags=0x%X inCombat=%v), so he schedules no sun beams",
|
||||
unitFlags(bot, elder), bot.UnitInCombat(elder))
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return elder
|
||||
}
|
||||
|
||||
@@ -525,8 +570,10 @@ func waitBrightleafWave(t *testing.T, bot *e2eharness.ScenarioBot, elder uint64)
|
||||
}
|
||||
if !time.Now().Before(deadline) {
|
||||
hp, maxHP := bot.UnitHP(elder)
|
||||
t.Logf("no wave: elder hp=%d/%d banished=%v essence=%v drained=%v 33050@500y=%d 33170@500y=%d",
|
||||
t.Logf("no wave: elder hp=%d/%d flags=0x%X inCombat=%v banished=%v essence=%v drained=%v 33050@500y=%d 33170@500y=%d",
|
||||
hp, maxHP,
|
||||
unitFlags(bot, elder),
|
||||
bot.UnitInCombat(elder),
|
||||
bot.UnitHasAura(elder, spellPurpleBanish),
|
||||
bot.UnitHasAura(elder, spellBrightleafEssence),
|
||||
bot.UnitHasAura(elder, spellDrainedOfPower),
|
||||
@@ -800,6 +847,270 @@ 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/27455
|
||||
// An Ancient Water Spirit's Tidal Wave (62653, 25-man rank 62935) is a 2s cast that surges the
|
||||
// spirit forward and knocks everything in its path off its feet with a second spell, 62654
|
||||
// (25-man 62936). Kicking the cast used to stop the surge and nothing else: the script queued
|
||||
// that second spell on a standalone 3s EventMap timer started at cast time, so the damage and
|
||||
// knockback still went out on an empty cast bar.
|
||||
//
|
||||
// The oracle is whether the spirit casts 62654 at all, read off SMSG_SPELL_GO. That is the
|
||||
// mechanism the bug is about, and it holds regardless of who the cone ends up covering. The
|
||||
// knockback the player actually receives (SMSG_MOVE_KNOCK_BACK, which AC sends to the knocked
|
||||
// player's own session out of Unit::KnockbackFrom) is logged next to it but is not the oracle:
|
||||
// it depends on where the bot is standing when the wave goes off, and the harness never acks a
|
||||
// knockback, which makes repeat knockbacks in one session unreliable.
|
||||
//
|
||||
// An uninterrupted wave is measured too. Without it a quiet window after a Kick proves nothing:
|
||||
// it would pass just as happily against a spirit that never casts anything.
|
||||
//
|
||||
// A lone spawned spirit rather than Freya's Allies of Nature waves, because the trio is one of
|
||||
// three wave kinds picked at random roughly every 60s and a real pull spends minutes rolling
|
||||
// for the subject. The wave is scheduled from JustEngagedWith and needs nothing from Freya.
|
||||
func TestAC_27455_TidalWaveInterruptStopsKnockback(t *testing.T) {
|
||||
meta.Begin(t, meta.TestMeta{
|
||||
Tags: []string{"med", "instances", "issue"},
|
||||
Runtime: "med",
|
||||
Issue: 27455,
|
||||
Category: "instances/northrend/ulduar",
|
||||
})
|
||||
|
||||
const (
|
||||
npcWaterSpirit = uint32(33202)
|
||||
// spelldifficulty_dbc swaps the 10-man ids for the 25-man ones on a 25-man instance.
|
||||
spellTidalWave10 = uint32(62653)
|
||||
spellTidalWave25 = uint32(62935)
|
||||
spellTidalDmg10 = uint32(62654)
|
||||
spellTidalDmg25 = uint32(62936)
|
||||
spellKick = uint32(1766)
|
||||
|
||||
// game_tele BossRazorscale, not Freya's platform: the spirit only needs the Ulduar
|
||||
// instance script (RegisterUlduarCreatureAI), and this is the emptiest ground in the
|
||||
// raid, with nothing spawned inside 100y. Fighting it in the Conservatory pulls Freya
|
||||
// herself, and her Allies of Nature then land Conservator's Grip (62532) on the bot,
|
||||
// which is pacify-silence and blocks Kick outright.
|
||||
padX, padY, padZ = float32(589.2), float32(-145.0), float32(391.5)
|
||||
|
||||
botHealth = 10000000
|
||||
|
||||
// JustEngagedWith schedules the first wave at 12s and repeats on that interval.
|
||||
waveWindow = 40 * time.Second
|
||||
// 2s cast, then the surge. Everything the cast does has happened inside this.
|
||||
resolveWindow = 5 * time.Second
|
||||
sampleEvery = 20 * time.Millisecond
|
||||
// Kick is a melee ability and can be dodged or parried, which leaves the cast running.
|
||||
// Take the next wave when that happens rather than calling the run unjudgeable.
|
||||
kickAttempts = 6
|
||||
)
|
||||
|
||||
bot := e2eharness.NewSolo(t, e2eharness.ScenarioOpts{
|
||||
Prefix: "TidWav", Race: e2eharness.RaceHuman,
|
||||
Class: e2eharness.ClassRogue, Level: 80, LearnAllClass: true,
|
||||
})
|
||||
|
||||
// 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 pad tele map=%d", m)
|
||||
}
|
||||
|
||||
spirit := bot.Spawn(t, npcWaterSpirit, 30*time.Second)
|
||||
t.Logf("Ancient Water Spirit guid=0x%X", spirit)
|
||||
|
||||
// Only now drop GM: Spawn turns it on for `.npc add` and leaves it on, and the spirit picks
|
||||
// its target through SelectTargetFromPlayerList, which skips game masters. Note the bare
|
||||
// CombatReady helpers would also turn `.cheat god` on, leaving the bot invulnerable.
|
||||
e2eharness.CombatReady(t, bot.World, e2eharness.CombatReadyOpts{})
|
||||
if err := bot.World.SetTarget(bot.World.CharGUID()); err != nil {
|
||||
e2eharness.Preconditionf(t, "select self: %v", err)
|
||||
}
|
||||
bot.GM(t, fmt.Sprintf(".modify hp %d", botHealth))
|
||||
// A fresh level-80 character still carries its starting weapon skills, and an unarmed bot
|
||||
// misses a level-81 elite with Kick most of the time.
|
||||
bot.GM(t, ".maxskill")
|
||||
|
||||
bot.Engage(t, spirit, 15*time.Second)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
castStart time.Time
|
||||
castGo time.Time
|
||||
dmgGo time.Time
|
||||
knockAt time.Time
|
||||
)
|
||||
isWave := func(id uint32) bool { return id == spellTidalWave10 || id == spellTidalWave25 }
|
||||
isDamage := func(id uint32) bool { return id == spellTidalDmg10 || id == spellTidalDmg25 }
|
||||
cancel := bot.World.AddPacketHook(func(opcode uint16, data []byte) {
|
||||
switch opcode {
|
||||
case client.SmsgSpellStart:
|
||||
if id, ok := castSpellID(data); ok && isWave(id) {
|
||||
mu.Lock()
|
||||
castStart = time.Now()
|
||||
mu.Unlock()
|
||||
}
|
||||
case client.SmsgSpellGo:
|
||||
id, ok := castSpellID(data)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
if isWave(id) {
|
||||
castGo = time.Now()
|
||||
} else if isDamage(id) {
|
||||
dmgGo = time.Now()
|
||||
}
|
||||
mu.Unlock()
|
||||
case client.SmsgMoveKnockBack:
|
||||
mu.Lock()
|
||||
knockAt = time.Now()
|
||||
mu.Unlock()
|
||||
}
|
||||
})
|
||||
defer cancel()
|
||||
|
||||
reset := func() {
|
||||
mu.Lock()
|
||||
castStart, castGo, dmgGo, knockAt = time.Time{}, time.Time{}, time.Time{}, time.Time{}
|
||||
mu.Unlock()
|
||||
}
|
||||
snapshot := func() (start, done, dmg, knock time.Time) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return castStart, castGo, dmgGo, knockAt
|
||||
}
|
||||
|
||||
// The creature's combat flag reaches the object cache a beat after Engage, so a spirit that
|
||||
// evades is only called out once it has been seen fighting.
|
||||
sawCombat := false
|
||||
status := func() string {
|
||||
hp, _ := bot.UnitHP(spirit)
|
||||
self, _ := bot.UnitHP(bot.World.CharGUID())
|
||||
return fmt.Sprintf("inCombat=%v target=0x%X spiritHP=%d botHP=%d",
|
||||
bot.UnitInCombat(spirit), bot.UnitTarget(spirit), hp, self)
|
||||
}
|
||||
waitCast := func(what string) {
|
||||
deadline := time.Now().Add(waveWindow)
|
||||
for time.Now().Before(deadline) {
|
||||
if start, _, _, _ := snapshot(); !start.IsZero() {
|
||||
return
|
||||
}
|
||||
if bot.UnitInCombat(spirit) {
|
||||
sawCombat = true
|
||||
} else if sawCombat {
|
||||
e2eharness.Preconditionf(t, "spirit left combat while waiting for the %s wave (%s)", what, status())
|
||||
}
|
||||
time.Sleep(sampleEvery)
|
||||
}
|
||||
e2eharness.Preconditionf(t, "no Tidal Wave cast within %s (%s wave, %s)", waveWindow, what, status())
|
||||
}
|
||||
|
||||
// Target and facing are settled up front so the only thing between seeing a cast start and
|
||||
// the Kick going out is one CMSG_CAST_SPELL.
|
||||
if err := bot.World.SetTarget(spirit); err != nil {
|
||||
e2eharness.Preconditionf(t, "select spirit: %v", err)
|
||||
}
|
||||
bot.Face(t, spirit)
|
||||
|
||||
var kickedDmg, kickedKnock, kickedStart time.Time
|
||||
interrupted := false
|
||||
var lastFail string
|
||||
for attempt := 1; attempt <= kickAttempts && !interrupted; attempt++ {
|
||||
reset()
|
||||
waitCast("interrupted")
|
||||
kicked := false
|
||||
for deadline := time.Now().Add(1200 * time.Millisecond); time.Now().Before(deadline); {
|
||||
// Re-faced every attempt: a wave that went off leaves the spirit 40y past the
|
||||
// bot, and Kick on a target behind it is SPELL_FAILED_UNIT_NOT_INFRONT.
|
||||
bot.Face(t, spirit)
|
||||
res, err := bot.TryCast(t, spellKick, spirit, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
lastFail = err.Error()
|
||||
} else if res.Success {
|
||||
kicked = true
|
||||
break
|
||||
} else {
|
||||
lastFail = e2eharness.SpellFailReasonName(res.FailReason)
|
||||
}
|
||||
}
|
||||
if !kicked {
|
||||
t.Logf("attempt %d: Kick never went out (%s, casterFlags=0x%X, auras=%v)",
|
||||
attempt, lastFail, casterFlags(bot), bot.World.SelfAuras())
|
||||
continue
|
||||
}
|
||||
time.Sleep(resolveWindow)
|
||||
start, done, dmg, knock := snapshot()
|
||||
if !done.IsZero() {
|
||||
// Kick went out but was dodged or parried; the cast ran to completion.
|
||||
t.Logf("attempt %d: Kick did not land, wave completed %s after cast start",
|
||||
attempt, done.Sub(start).Round(time.Millisecond))
|
||||
continue
|
||||
}
|
||||
interrupted = true
|
||||
kickedStart, kickedDmg, kickedKnock = start, dmg, knock
|
||||
}
|
||||
if !interrupted {
|
||||
e2eharness.Preconditionf(t, "no Tidal Wave was interrupted in %d attempts (last Kick result: %s, %s)",
|
||||
kickAttempts, lastFail, status())
|
||||
}
|
||||
t.Logf("kicked wave: damage cast=%v knockback=%v", !kickedDmg.IsZero(), !kickedKnock.IsZero())
|
||||
|
||||
// Control: let the next one through, so the quiet window above means something.
|
||||
reset()
|
||||
waitCast("uninterrupted")
|
||||
time.Sleep(resolveWindow)
|
||||
plainStart, plainGo, plainDmg, plainKnock := snapshot()
|
||||
if plainGo.IsZero() {
|
||||
e2eharness.Preconditionf(t, "Tidal Wave started but never completed without an interrupt")
|
||||
}
|
||||
if plainDmg.IsZero() {
|
||||
e2eharness.Preconditionf(t,
|
||||
"an uninterrupted Tidal Wave never cast its damage spell; the interrupt oracle would be vacuous")
|
||||
}
|
||||
t.Logf("uninterrupted wave: damage cast %s after cast start, knockback=%v",
|
||||
plainDmg.Sub(plainStart).Round(time.Millisecond), !plainKnock.IsZero())
|
||||
|
||||
if !kickedDmg.IsZero() {
|
||||
e2eharness.ConfirmedBugf(t, 27455,
|
||||
"interrupted Tidal Wave still cast its damage and knockback %s after the cast started",
|
||||
kickedDmg.Sub(kickedStart).Round(time.Millisecond))
|
||||
}
|
||||
t.Logf("PASS AC#27455 kicked Tidal Wave cast no damage spell within %s", resolveWindow)
|
||||
}
|
||||
|
||||
// casterFlags reads UNIT_FIELD_FLAGS off the bot, so a refused cast names the state that
|
||||
// refused it.
|
||||
func casterFlags(bot *e2eharness.ScenarioBot) uint32 {
|
||||
obj := bot.World.GetObject(bot.World.CharGUID())
|
||||
if obj == nil {
|
||||
return 0
|
||||
}
|
||||
return obj.Value(client.UnitFieldFlags)
|
||||
}
|
||||
|
||||
// castSpellID pulls the spell id out of an SMSG_SPELL_START / SMSG_SPELL_GO header, which both
|
||||
// open with the cast-item and caster packed GUIDs and a cast counter ahead of it. The harness
|
||||
// parses SPELL_GO for its own waiters but exposes neither the header nor a packed-GUID reader.
|
||||
func castSpellID(data []byte) (uint32, bool) {
|
||||
off := 0
|
||||
for i := 0; i < 2; i++ {
|
||||
if off >= len(data) {
|
||||
return 0, false
|
||||
}
|
||||
mask := data[off]
|
||||
off++
|
||||
for bit := 0; bit < 8; bit++ {
|
||||
if mask&(1<<uint(bit)) != 0 {
|
||||
off++
|
||||
}
|
||||
}
|
||||
}
|
||||
off++ // cast count
|
||||
if off+4 > len(data) {
|
||||
return 0, false
|
||||
}
|
||||
return binary.LittleEndian.Uint32(data[off : off+4]), true
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -152,7 +152,6 @@ enum FreyaEvents
|
||||
EVENT_ANCIENT_CONSERVATOR_NATURE_FURY = 40,
|
||||
EVENT_ANCIENT_CONSERVATOR_GRIP = 41,
|
||||
EVENT_WATER_SPIRIT_CHARGE = 45,
|
||||
EVENT_WATER_SPIRIT_DAMAGE = 46,
|
||||
EVENT_STORM_LASHER_LIGHTNING_LASH = 50,
|
||||
EVENT_STORM_LASHER_STORMBOLT = 51,
|
||||
EVENT_DETONATING_LASHER_FLAME_LASH = 55,
|
||||
@@ -1226,13 +1225,8 @@ struct boss_freya_summons : public ScriptedAI
|
||||
me->CastSpell(me, SPELL_CONSERVATOR_GRIP, true);
|
||||
break;
|
||||
case EVENT_WATER_SPIRIT_CHARGE:
|
||||
me->CastSpell(me, SPELL_TIDAL_WAVE_AURA, true);
|
||||
me->CastSpell(me->GetVictim(), SPELL_TIDAL_WAVE, false);
|
||||
events.Repeat(12s);
|
||||
events.ScheduleEvent(EVENT_WATER_SPIRIT_DAMAGE, 3s);
|
||||
break;
|
||||
case EVENT_WATER_SPIRIT_DAMAGE:
|
||||
me->CastSpell(me, SPELL_TIDAL_WAVE_DAMAGE, false);
|
||||
break;
|
||||
case EVENT_STORM_LASHER_LIGHTNING_LASH:
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0))
|
||||
@@ -1386,6 +1380,27 @@ class spell_freya_attuned_to_nature_dose_reduction : public SpellScript
|
||||
}
|
||||
};
|
||||
|
||||
// 62653, 62935 - Tidal Wave
|
||||
class spell_freya_tidal_wave : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_freya_tidal_wave);
|
||||
|
||||
void HandleSurge(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
// The cone is caster-referenced: taken before the charge moves the spirit, it spans
|
||||
// the same 40 yds the surge is about to cross.
|
||||
Unit* caster = GetCaster();
|
||||
caster->CastSpell(caster, SPELL_TIDAL_WAVE_AURA, true);
|
||||
// Untriggered: no SpellVisual, so a triggered cast would lose its SMSG_SPELL_GO.
|
||||
caster->CastSpell(caster, SPELL_TIDAL_WAVE_DAMAGE, false);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectLaunch += SpellEffectFn(spell_freya_tidal_wave::HandleSurge, EFFECT_1, SPELL_EFFECT_CHARGE_DEST);
|
||||
}
|
||||
};
|
||||
|
||||
// 62207 - Unstable Sun Beam
|
||||
class spell_freya_brightleaf_unstable_sun_beam : public SpellScript
|
||||
{
|
||||
@@ -1432,6 +1447,7 @@ void AddSC_boss_freya()
|
||||
RegisterUlduarCreatureAI(boss_freya_nature_bomb);
|
||||
|
||||
RegisterSpellScript(spell_freya_attuned_to_nature_dose_reduction);
|
||||
RegisterSpellScript(spell_freya_tidal_wave);
|
||||
RegisterSpellScript(spell_freya_brightleaf_unstable_sun_beam);
|
||||
RegisterSpellScript(spell_freya_unstable_sun_beam);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user