diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs index 667aefd2a9..e51be638c0 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs @@ -1,5 +1,6 @@ namespace BossMod.Dawntrail.Alliance.A14ShadowLord; +class Teleport(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.Teleport)); class TeraSlash(BossModule module) : Components.CastCounter(module, ActionID.MakeSpell(AID.TeraSlash)); class UnbridledRage(BossModule module) : Components.BaitAwayIcon(module, new AOEShapeRect(100, 4), (uint)IconID.UnbridledRage, ActionID.MakeSpell(AID.UnbridledRageAOE), 5.9f); class DarkNova(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.DarkNova), 6); diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordEnums.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordEnums.cs index c090235776..7f660f2470 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordEnums.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordEnums.cs @@ -66,15 +66,21 @@ public enum AID : uint BindingSigilPreview = 41513, // Helper->self, 1.5s cast, range 9 circle, visual SoulBinding = 41514, // Helper->self, 1.0s cast, range 9 circle, apply debuff SoulBindingBurst = 41531, // Helper->self, no cast, vuln on debuffed people - DamningStrikes = 40791, // Boss->self, 8.0s cast, single-target, visual (towers) + DamningStrikes1 = 40791, // Boss->self, 8.0s cast, single-target, visual (towers) + DamningStrikes2 = 41054, // Boss->self, 8.7s cast, single-target, visual (towers, much more rare, ??? difference) DamningStrikesGrab = 41530, // Helper->player, no cast, single-target, grab target - DamningStrikesTeleport1 = 41816, // Boss->location, no cast, single-target, teleport before first hit - DamningStrikesTeleport2 = 41815, // Boss->location, no cast, single-target, teleport before second & third hits - DamningStrikesVisual1 = 42052, // Boss->self, no cast, single-target, visual before first and second hits - DamningStrikesVisual2 = 40793, // Boss->self, no cast, single-target, visual before third hit + DamningStrikesTeleport1 = 40794, // Boss->location, no cast, single-target, teleport before hit + DamningStrikesTeleport2 = 41815, // Boss->location, no cast, single-target, teleport before hit + DamningStrikesTeleport3 = 41816, // Boss->location, no cast, single-target, teleport before hit + DamningStrikesTeleport4 = 42054, // Boss->location, no cast, single-target, teleport before hit + DamningStrikesTeleport5 = 42055, // Boss->location, no cast, single-target, teleport before hit + DamningStrikesVisual1 = 40793, // Boss->self, no cast, single-target, visual before hit + DamningStrikesVisual2 = 42052, // Boss->self, no cast, single-target, visual before hit + DamningStrikesVisual3 = 42053, // Boss->self, no cast, single-target, visual before hit DamningStrikesImpact1 = 40792, // Helper->self, 10.5s cast, range 3 circle, tower 1 DamningStrikesImpact2 = 41110, // Helper->self, 13.0s cast, range 3 circle, tower 2 DamningStrikesImpact3 = 41111, // Helper->self, 15.7s cast, range 3 circle, tower 3 + DamningStrikesShockwave = 41112, // Helper->self, no cast, range 100 circle, raidwide with dot if tower is not soaked DoomArc = 40806, // Boss->self, 15.0s cast, range 100 circle, raidwide with bleed + damage up } diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordStates.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordStates.cs index a66bb6f37d..60654f9560 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordStates.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLordStates.cs @@ -9,37 +9,58 @@ public A14ShadowLordStates(BossModule module) : base(module) private void SinglePhase(uint id) { - GigaSlash(id, 10.2f); - UmbraSmashGigaSlash(id + 0x10000, 6.5f); - FlamesOfHatred(id + 0x20000, 4.1f); - Implosion(id + 0x30000, 3.1f); - CthonicFury1(id + 0x40000, 3.5f); - NightfallTeraSlash(id + 0x50000, 4.8f); - - GigaSlashNightfall(id + 0x100000, 12.2f); - ShadowSpawnGigaSlashNightfallImplosion(id + 0x110000, 5.4f); - UnbridledRage(id + 0x120000, 2.6f); - EchoesOfAgony(id + 0x130000, 1.2f, 7); - BindingSigil(id + 0x140000, 2.6f); - DamningStrikes(id + 0x150000, 3.5f); - CthonicFury2(id + 0x160000, 9.1f); - ShadowSpawnUmbraSmashGigaSlashNightfall(id + 0x170000, 4.5f); - DoomArc(id + 0x180000, 2.9f); - - // TODO: mechanic repeats?.. - GigaSlashNightfall(id + 0x200000, 10.2f); - ShadowSpawnGigaSlashNightfallImplosion(id + 0x210000, 5.4f); - UnbridledRage(id + 0x220000, 2.6f); // TODO: never seen this and below... - EchoesOfAgony(id + 0x230000, 1.2f, 7); - BindingSigil(id + 0x240000, 2.6f); - DamningStrikes(id + 0x250000, 3.5f); - CthonicFury2(id + 0x260000, 9.1f); - ShadowSpawnUmbraSmashGigaSlashNightfall(id + 0x270000, 4.5f); - DoomArc(id + 0x280000, 2.9f); + // note: this is a very weird fight, if you wipe, it uses a slightly different script (no initial giga slash and slightly different cthonic fury 1) + Dictionary buildState)> dispatch = new() + { + [true] = (1, SinglePhaseInitial), + [false] = (2, SinglePhaseAfterWipe), + }; + ConditionFork(id, 10.2f, () => Module.PrimaryActor.CastInfo != null || Module.FindComponent()?.NumCasts > 0, () => (AID)(Module.PrimaryActor.CastInfo?.Action.ID ?? 0) is AID.GigaSlashL or AID.GigaSlashR, dispatch, "First mechanic...") + .ActivateOnEnter() + .DeactivateOnExit(); + } + private void SinglePhaseInitial(uint id) + { + GigaSlash(id, 0); + PhaseInitial(id + 0x100000, 6.5f, true); + PhaseRepeats(id + 0x200000, 12.2f); + PhaseRepeats(id + 0x300000, 10.2f); + PhaseRepeats(id + 0x400000, 10.2f); SimpleState(id + 0xFF0000, 10000, "???"); } + private void SinglePhaseAfterWipe(uint id) + { + PhaseInitial(id + 0x100000, 2.4f, false); + PhaseRepeats(id + 0x200000, 12.2f); + PhaseRepeats(id + 0x300000, 10.2f); + PhaseRepeats(id + 0x400000, 10.2f); + SimpleState(id + 0xFF0000, 10000, "???"); + } + + private void PhaseInitial(uint id, float delay, bool initialPull) + { + UmbraSmashGigaSlash(id, delay); + FlamesOfHatred(id + 0x10000, 4.1f); + Implosion(id + 0x20000, 3.2f); + CthonicFury1(id + 0x30000, 3.6f, initialPull); + NightfallTeraSlash(id + 0x40000, 4.8f); + } + + private void PhaseRepeats(uint id, float delay) + { + GigaSlashNightfall(id, delay); + ShadowSpawnGigaSlashNightfallImplosion(id + 0x10000, 5.4f); + UnbridledRage(id + 0x20000, 2.6f); + EchoesOfAgony(id + 0x30000, 1.2f, 7); + BindingSigil(id + 0x40000, 2.6f); + DamningStrikes(id + 0x50000, 3.5f); + CthonicFury2(id + 0x60000, 9.0f); + ShadowSpawnUmbraSmashGigaSlashNightfall(id + 0x70000, 4.6f); + DoomArc(id + 0x80000, 3); + } + private State GigaSlash(uint id, float delay) { CastMulti(id, [AID.GigaSlashL, AID.GigaSlashR], delay, 11) @@ -72,18 +93,12 @@ private void Implosion(uint id, float delay) .DeactivateOnExit(); } - private void BurningCourtMoat(uint id, float delay, bool withRect) + private void BurningCourtMoat(uint id, float delay) { - Condition(id, delay, () => Module.FindComponent()?.Casters.Count > 0 || Module.FindComponent()?.Casters.Count > 0) - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter(withRect) - .ActivateOnEnter(withRect); - Condition(id + 1, 7, () => Module.FindComponent()?.NumCasts > 0 || Module.FindComponent()?.NumCasts > 0, "Platform in/out") - .DeactivateOnExit() - .DeactivateOnExit() - .DeactivateOnExit(withRect) - .DeactivateOnExit(withRect); + ComponentCondition(id, delay, comp => comp.AOEs.Count > 0) + .ActivateOnEnter(); + ComponentCondition(id + 1, 7, comp => comp.AOEs.Count == 0, "Platform in/out") + .DeactivateOnExit(); } private void EchoesOfAgony(uint id, float delay, int numCasts) @@ -119,14 +134,22 @@ private void DarkNebula(uint id, float delay) .DeactivateOnExit(); } - private void CthonicFury1(uint id, float delay) + private void CthonicFury1(uint id, float delay, bool initialPull) { CthonicFuryStart(id, delay); - BurningCourtMoat(id + 0x1000, 8.2f, false); - BurningCourtMoat(id + 0x2000, 3, false); - DarkNebula(id + 0x3000, 6); - Implosion(id + 0x4000, 4); - BurningCourtMoat(id + 0x5000, 2.2f, true); + if (initialPull) + { + BurningCourtMoat(id + 0x1000, 8.2f); + BurningCourtMoat(id + 0x2000, 3.1f); + DarkNebula(id + 0x3000, 6); + } + else + { + BurningCourtMoat(id + 0x1000, 3.2f); + DarkNebula(id + 0x3000, 3); + } + Implosion(id + 0x4000, 4.1f); + BurningCourtMoat(id + 0x5000, 2.2f); EchoesOfAgony(id + 0x6000, 4, 5); CthonicFuryEnd(id + 0x7000, 1.7f); } @@ -189,7 +212,7 @@ private void BindingSigil(uint id, float delay) { Cast(id, AID.BindingSigil, delay, 12) .ActivateOnEnter(); - ComponentCondition(id + 2, 2.2f, comp => comp.NumCasts > 0, "Puddles 1"); // 8 or 9 + ComponentCondition(id + 2, 2.1f, comp => comp.NumCasts > 0, "Puddles 1"); // 8 or 9 ComponentCondition(id + 3, 2.5f, comp => comp.NumCasts > 9, "Puddles 2"); // 16 or 17 ComponentCondition(id + 4, 2.5f, comp => comp.NumCasts > 17, "Puddles 3") // 25 .DeactivateOnExit(); @@ -197,7 +220,7 @@ private void BindingSigil(uint id, float delay) private void DamningStrikes(uint id, float delay) { - Cast(id, AID.DamningStrikes, delay, 8) + CastMulti(id, [AID.DamningStrikes1, AID.DamningStrikes2], delay, 8) // note: alt cast is longer by 0.7s, whatever... .ActivateOnEnter(); ComponentCondition(id + 2, 2.5f, comp => comp.NumCasts >= 1, "Tower 1"); ComponentCondition(id + 3, 2.5f, comp => comp.NumCasts >= 2, "Tower 2"); @@ -208,7 +231,7 @@ private void DamningStrikes(uint id, float delay) private void DarkNebulaGigaSlashNightfall(uint id, float delay) { Cast(id, AID.DarkNebula, delay, 3); - ComponentCondition(id + 0x10, 1.1f, comp => comp.Casters.Count > 0) + ComponentCondition(id + 0x10, 1.2f, comp => comp.Casters.Count > 0) .ActivateOnEnter(); ComponentCondition(id + 0x20, 13, comp => comp.NumCasts > 0, "Knockback 1"); ComponentCondition(id + 0x21, 3, comp => comp.NumCasts > 1, "Knockback 2"); @@ -228,18 +251,18 @@ private void CthonicFury2(uint id, float delay) { CthonicFuryStart(id, delay); DarkNebulaGigaSlashNightfall(id + 0x1000, 3.2f); - BurningCourtMoat(id + 0x2000, 3, true); + BurningCourtMoat(id + 0x2000, 3); DarkNova(id + 0x3000, 5.4f); - CthonicFuryEnd(id + 0x4000, 2); + CthonicFuryEnd(id + 0x4000, 2.1f); } private void ShadowSpawnUmbraSmashGigaSlashNightfall(uint id, float delay) { Cast(id, AID.ShadowSpawn, delay, 3); - Cast(id + 0x10, AID.UmbraSmashBoss, 4.1f, 4) + Cast(id + 0x10, AID.UmbraSmashBoss, 4.2f, 4) .ActivateOnEnter(); ComponentCondition(id + 0x20, 0.5f, comp => comp.NumCasts > 0, "Exalines"); - GigaSlashNightfall(id + 0x100, 12) + GigaSlashNightfall(id + 0x100, 12.2f) .DeactivateOnExit(); } diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/CthonicFury.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/CthonicFury.cs index c379988c72..5eb4624b15 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/CthonicFury.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/CthonicFury.cs @@ -39,17 +39,46 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) } } -class BurningCourt(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BurningCourt), new AOEShapeCircle(8)); -class BurningMoat(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BurningMoat), new AOEShapeDonut(5, 15)); -class BurningKeep(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BurningKeep), new AOEShapeRect(11.5f, 11.5f, 11.5f)); -class BurningBattlements(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BurningBattlements), new AOEShapeCustom(BuildPolygon())) +class BurningCourtMoatKeepBattlements(BossModule module) : Components.GenericAOEs(module) { - public static RelSimplifiedComplexPolygon BuildPolygon() + public readonly List AOEs = []; + + private static readonly AOEShape _shapeC = new AOEShapeCircle(8); + private static readonly AOEShape _shapeM = new AOEShapeDonut(5, 15); + private static readonly AOEShape _shapeK = new AOEShapeRect(11.5f, 11.5f, 11.5f); + private static readonly AOEShape _shapeB = new AOEShapeCustom(BuildBattlementsPolygon()); + + private static RelSimplifiedComplexPolygon BuildBattlementsPolygon() { RelPolygonWithHoles poly = new([.. CurveApprox.Rect(new(100, 0), new(0, 100))]); poly.AddHole(CurveApprox.Rect(new(11.5f, 0), new(0, 11.5f))); return new([poly]); } + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => AOEs; + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + var shape = ShapeForAction(spell.Action); + if (shape != null) + AOEs.Add(new(shape, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + } + + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + var shape = ShapeForAction(spell.Action); + if (shape != null) + AOEs.RemoveAll(aoe => aoe.Shape == shape && aoe.Origin.AlmostEqual(caster.Position, 1)); + } + + private AOEShape? ShapeForAction(ActionID aid) => (AID)aid.ID switch + { + AID.BurningCourt => _shapeC, + AID.BurningMoat => _shapeM, + AID.BurningKeep => _shapeK, + AID.BurningBattlements => _shapeB, + _ => null + }; } class DarkNebula(BossModule module) : Components.Knockback(module)