From 27f04307f93cd8a7979031fc4fa9a4e2478afecf Mon Sep 17 00:00:00 2001 From: sourpuh <150582765+sourpuh@users.noreply.github.com> Date: Thu, 4 Apr 2024 23:39:08 -0700 Subject: [PATCH] Add Menenius Duel Module --- BossMod/BossModule/ArenaColor.cs | 1 + BossMod/Components/Exaflare.cs | 16 +- .../Dungeon/D01Holminster/D013Philia.cs | 4 +- .../Duel/Duel5Menenius/BlueHiddenMines.cs | 24 +++ .../Foray/Duel/Duel5Menenius/Duel5Menenius.cs | 91 +++++++++++ .../Duel/Duel5Menenius/Duel5MeneniusEnums.cs | 42 +++++ .../Duel/Duel5Menenius/Duel5MeneniusStates.cs | 147 ++++++++++++++++++ .../Foray/Duel/Duel5Menenius/GigaTempest.cs | 103 ++++++++++++ .../Foray/Duel/Duel5Menenius/GunberdShot.cs | 72 +++++++++ .../Duel/Duel5Menenius/RedHiddenMines.cs | 36 +++++ .../Foray/Duel/Duel5Menenius/Ruination.cs | 66 ++++++++ 11 files changed, 593 insertions(+), 9 deletions(-) create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/BlueHiddenMines.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5Menenius.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusEnums.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusStates.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GigaTempest.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GunberdShot.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/RedHiddenMines.cs create mode 100644 BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Ruination.cs diff --git a/BossMod/BossModule/ArenaColor.cs b/BossMod/BossModule/ArenaColor.cs index d13ea24eb0..b7629dee40 100644 --- a/BossMod/BossModule/ArenaColor.cs +++ b/BossMod/BossModule/ArenaColor.cs @@ -9,6 +9,7 @@ public static class ArenaColor public const uint SafeFromAOE = 0x80008000; public const uint Danger = 0xff00ffff; public const uint Safe = 0xff00ff00; + public const uint Trap = 0x80000080; public const uint PC = 0xff00ff00; public const uint Enemy = 0xff0000ff; public const uint Object = 0xff0080ff; diff --git a/BossMod/Components/Exaflare.cs b/BossMod/Components/Exaflare.cs index 2bbd186a15..cec6406f4f 100644 --- a/BossMod/Components/Exaflare.cs +++ b/BossMod/Components/Exaflare.cs @@ -11,6 +11,8 @@ public class Line public float TimeToMove; public int ExplosionsLeft; public int MaxShownExplosions; + // non-circular Shapes may be rotated based on the orientation of the caster. + public Angle Rotation; } public AOEShape Shape { get; private init; } @@ -28,15 +30,15 @@ public class Line public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - foreach (var (c, t) in FutureAOEs(module.WorldState.CurrentTime)) - yield return new(Shape, c, activation: t, color: FutureColor); - foreach (var (c, t) in ImminentAOEs()) - yield return new(Shape, c, activation: t, color: ImminentColor); + foreach (var (c, t, a) in FutureAOEs(module.WorldState.CurrentTime)) + yield return new(Shape, c, rotation: a, activation: t, color: FutureColor); + foreach (var (c, t, a) in ImminentAOEs()) + yield return new(Shape, c, rotation: a, activation: t, color: ImminentColor); } - protected IEnumerable<(WPos, DateTime)> ImminentAOEs() => Lines.Where(l => l.ExplosionsLeft > 0).Select(l => (l.Next, l.NextExplosion)); + protected IEnumerable<(WPos, DateTime, Angle)> ImminentAOEs() => Lines.Where(l => l.ExplosionsLeft > 0).Select(l => (l.Next, l.NextExplosion, l.Rotation)); - protected IEnumerable<(WPos, DateTime)> FutureAOEs(DateTime currentTime) + protected IEnumerable<(WPos, DateTime, Angle)> FutureAOEs(DateTime currentTime) { foreach (var l in Lines) { @@ -47,7 +49,7 @@ public override IEnumerable ActiveAOEs(BossModule module, int slot, { pos += l.Advance; time = time.AddSeconds(l.TimeToMove); - yield return (pos, time); + yield return (pos, time, l.Rotation); } } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs index ce341ba87c..53fac3978d 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs @@ -298,9 +298,9 @@ public FierceBeating() : base(4) { } public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - foreach (var (c, t) in FutureAOEs(module.WorldState.CurrentTime)) + foreach (var (c, t, a) in FutureAOEs(module.WorldState.CurrentTime)) yield return new(Shape, c, activation: t, color: FutureColor); - foreach (var (c, t) in ImminentAOEs()) + foreach (var (c, t, a) in ImminentAOEs()) yield return new(Shape, c, activation: t, color: ImminentColor); if (Lines.Count > 0 && linesstartedcount1 < 8) yield return new(circle, CalculateCirclePosition(linesstartedcount1, module.Bounds.Center, _casters[0]), activation: _activation.AddSeconds(linesstartedcount1 * 3.7f)); diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/BlueHiddenMines.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/BlueHiddenMines.cs new file mode 100644 index 0000000000..621ced2495 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/BlueHiddenMines.cs @@ -0,0 +1,24 @@ +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +internal class BlueHiddenMines : Components.GenericTowers +{ + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ActivateBlueMine) + { + Towers.Add(new(caster.Position, 3.6f)); + } + else if ((AID)spell.Action.ID is AID.DetonateBlueMine) + { + Towers.RemoveAll(t => t.Position.AlmostEqual(caster.Position, 1)); + } + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (Towers.Count > 0) + { + hints.Add("Soak the mine!"); + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5Menenius.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5Menenius.cs new file mode 100644 index 0000000000..d03a9fc740 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5Menenius.cs @@ -0,0 +1,91 @@ +using BossMod.Components; + +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +internal class SpiralScourge : SingleTargetCast +{ + public SpiralScourge() : + base(ActionID.MakeSpell(AID.SpiralScourge), "Use Manawall, Excellence, or Invuln.") + { } +} + +internal class CallousCrossfire : SingleTargetCast +{ + public CallousCrossfire() : + base(ActionID.MakeSpell(AID.CallousCrossfire), "Use Light Curtain / Reflect.") + { } +} + +class ReactiveMunition : StayMove +{ + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID is SID.AccelerationBomb) + { + if (module.Raid.FindSlot(actor.InstanceID) is var slot && slot >= 0 && slot < Requirements.Length) + Requirements[slot] = Requirement.Stay; + } + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID is SID.AccelerationBomb) + { + if (module.Raid.FindSlot(actor.InstanceID) is var slot && slot >= 0 && slot < Requirements.Length) + Requirements[slot] = Requirement.None; + } + } +} + +class SenseWeakness : StayMove +{ + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SenseWeakness) + { + if (module.Raid.FindSlot(caster.TargetID) is var slot && slot >= 0 && slot < Requirements.Length) + Requirements[slot] = Requirement.Move; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.SenseWeakness) + { + if (module.Raid.FindSlot(caster.TargetID) is var slot && slot >= 0 && slot < Requirements.Length) + Requirements[slot] = Requirement.None; + } + } +} + +class MagitekImpetus : StatusDrivenForcedMarch +{ + public MagitekImpetus() : base( + 3, + (uint)SID.ForwardMarch, + (uint)SID.AboutFace, + (uint)SID.LeftFace, + (uint)SID.RightFace) + { + ActivationLimit = 1; + } +} + +class ProactiveMunition : StandardChasingAOEs +{ + public ProactiveMunition() : base( + new AOEShapeCircle(6), + ActionID.MakeSpell(AID.ProactiveMunitionTrackingStart), + ActionID.MakeSpell(AID.ProactiveMunitionTrackingMove), + 6, + 1, + 5) + { } +} + + +[ModuleInfo(NameID = 0x25DF)] +public class Duel5Menenius : BossModule +{ + public Duel5Menenius(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-810, 520 /*y=260.3*/), 20)) { } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusEnums.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusEnums.cs new file mode 100644 index 0000000000..4061c5eaff --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusEnums.cs @@ -0,0 +1,42 @@ +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +public enum AID : uint +{ + AutoAttack = 6497, + TeraTempest = 0x5D60, + CallousCrossfire = 23901, + MagitekMinefield = 23887, + ActivateBlueMine = 23888, + DetonateBlueMine = 23890, + ActivateRedMine = 23889, + DetonateRedMine = 0x5D41, + IndiscriminateDetonation = 23892, + GigaTempest = 23875, + GigaTempestLargeStart = 0x5D44, + GigaTempestLargeMove = 0x5D46, + GigaTempestSmallStart = 0x5D45, + GigaTempestSmallMove = 0x5D47, + Ruination = 23880, + RuinationExaStart = 23881, + RuinationExaMove = 23882, + SpiralScourge = 23900, + WindslicerShot = 23883, + GunberdWindslicer = 23885, + DarkShot = 23884, + GunberdDark = 23886, + ProactiveMunition = 0x5D58, + ProactiveMunitionTrackingStart = 0x5D59, + ProactiveMunitionTrackingMove = 0x5D5A, + ReactiveMunition = 23894, + SenseWeakness = 23893, + MagitekImpetus = 23899, +}; + +public enum SID : uint +{ + ForwardMarch = 0x50D, + AboutFace = 0x50E, + LeftFace = 0x50F, + RightFace = 0x510, + AccelerationBomb = 0x430, +}; diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusStates.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusStates.cs new file mode 100644 index 0000000000..64aa99b828 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Duel5MeneniusStates.cs @@ -0,0 +1,147 @@ +using static BossMod.Shadowbringers.Foray.Duel.Duel5Menenius.GigaTempest; + +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +class Duel5MeneniusStates : StateMachineBuilder +{ + uint i = 1; + private uint nextId => i++ * 0x10000; + + public Duel5MeneniusStates(BossModule module) : base(module) + { + DeathPhase(0, SinglePhase) + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + + private void SinglePhase(uint id) + { + CallousCrossfire(id, 13); + + MagitekMinefield(id + nextId, 12); + GigaTempest(id + nextId, 11.25f); + ReadyShot(id + nextId, 15.5f); + Gunberd(id + nextId, 2); + MagitekImpetus(id + nextId, 2.6f); + MagitekMinefield(id + nextId, 6.2f); + ReadyShot(id + nextId, 10.25f); + Gunberd(id + nextId, 2); + MagitekMinefield(id + nextId, 5.6f); + Ruination(id + nextId, 6.5f); + SpiralScourge(id + nextId, 18.25f); + + ProactiveMunition(id + nextId, 7.25f); + MagitekMinefield(id + nextId, 7.25f); + ReactiveMunition(id + nextId, 7.25f); + SenseWeakness(id + nextId, 13); + ReadyShot(id + nextId, 12.25f); + GigaTempest(id + nextId, 2); + MagitekImpetus(id + nextId, 3.3f); + Gunberd(id + nextId, 10); + ReactiveMunition(id + nextId, 13.6f); + Ruination(id + nextId, 4f); + SenseWeakness(id + nextId, 8.25f); + IndiscriminateDetonation(id + nextId, 3.25f); + + ReadyShot(id + nextId, 11.25f); + ReactiveMunition(id + nextId, 2); + MagitekMinefield(id + nextId, 2); + MagitekImpetus(id + nextId, 10.3f); + MagitekMinefield(id + nextId, 8.25f); + Gunberd(id + nextId, 9.25f); + MagitekMinefield(id + nextId, 8.6f); + ReadyShot(id + nextId, 12.5f); + Ruination(id + nextId, 2.25f); + MagitekMinefield(id + nextId, 10.3f); + Gunberd(id + nextId, 9.3f); + ReadyShot(id + nextId, 13.8f); + GigaTempest(id + nextId, 2.2f); + MagitekImpetus(id + nextId, 3.5f); + Gunberd(id + nextId, 10.3f); + ReactiveMunition(id + nextId, 13.7f); + Ruination(id + nextId, 4.5f); + SenseWeakness(id + nextId, 8.25f); + IndiscriminateDetonation(id + nextId, 3.2f); + + TeraTempest(id + nextId, 12.9f); + } + + private void CallousCrossfire(uint id, float delay) + { + Cast(id, AID.CallousCrossfire, delay, 4, "Turret Crossfire") + .ActivateOnEnter() + .DeactivateOnExit(); + } + + private void MagitekMinefield(uint id, float delay) + { + Cast(id, AID.MagitekMinefield, delay, 3, "Place Mine"); + } + + private void IndiscriminateDetonation(uint id, float delay) + { + Cast(id, AID.IndiscriminateDetonation, delay, 4, "Detonate Mines"); + } + + private void GigaTempest(uint id, float delay) + { + Cast(id, AID.GigaTempest, delay, 5, "Gigatempest"); + } + + private void MagitekImpetus(uint id, float delay) + { + Cast(id, AID.MagitekImpetus, delay, 3, "Place Forced March"); + } + + private void ReadyShot(uint id, float delay) + { + CastMulti(id, new[] { AID.DarkShot, AID.WindslicerShot }, delay, 4, "Load Dark/Windslicer Shot"); + } + + private void Gunberd(uint id, float delay) + { + CastMulti(id, new[] { AID.GunberdDark, AID.GunberdWindslicer }, delay, 4, "Shoot Dark/Windslicer Shot"); + } + + private void Ruination(uint id, float delay) + { + Cast(id, AID.Ruination, delay, 4, "Ruination") + .ActivateOnEnter() + .DeactivateOnExit(); + } + + private void SpiralScourge(uint id, float delay) + { + Cast(id, AID.SpiralScourge, delay, 6, "Tankbuster") + .ActivateOnEnter() + .DeactivateOnExit(); + } + private void ProactiveMunition(uint id, float delay) + { + Cast(id, AID.ProactiveMunition, delay, 5, "Chasing AOE"); + } + + private void ReactiveMunition(uint id, float delay) + { + Cast(id, AID.ReactiveMunition, delay, 3, "Place Acceleration Bomb"); + } + + private void SenseWeakness(uint id, float delay) + { + Cast(id, AID.SenseWeakness, delay, 4.5f, "Move") + .ActivateOnEnter() + .DeactivateOnExit(); + } + + private void TeraTempest(uint id, float delay) + { + Cast(id + nextId, AID.TeraTempest, delay, 25, "Enrage"); + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GigaTempest.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GigaTempest.cs new file mode 100644 index 0000000000..e31e136cee --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GigaTempest.cs @@ -0,0 +1,103 @@ +using BossMod.Components; + +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +internal abstract class GigaTempest : Exaflare +{ + private static AOEShapeRect _aoeShapeSmall = new(10, 6.5f, 0); + private static AOEShapeRect _aoeShapeLarge = new(35, 6.5f, 0); + + internal class SmallGigaTempest : GigaTempest + { + public SmallGigaTempest() : base(_aoeShapeSmall) { } + + public override bool IsStart(AID aid) + { + return aid is AID.GigaTempestSmallStart; + } + public override bool IsMove(AID aid) + { + return aid is AID.GigaTempestSmallMove; + } + + } + + internal class LargeGigaTempest : GigaTempest + { + public LargeGigaTempest() : base(_aoeShapeLarge) { } + + public override bool IsStart(AID aid) + { + return aid is AID.GigaTempestLargeStart; + } + public override bool IsMove(AID aid) + { + return aid is AID.GigaTempestLargeMove; + } + } + + public GigaTempest(AOEShapeRect shape) : base(shape) { } + + public abstract bool IsStart(AID aid); + public abstract bool IsMove(AID aid); + + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if (IsStart((AID)spell.Action.ID)) + { + WDir? advance = GetExaDirection(caster); + if (advance == null) return; + Lines.Add(new() + { + Next = caster.Position, + Advance = advance.Value, + NextExplosion = caster.CastInfo!.NPCFinishAt, + TimeToMove = 0.9f, + ExplosionsLeft = 5, + MaxShownExplosions = 5, + Rotation = caster.Rotation, + }); + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (Lines.Count > 0 && IsStart((AID)spell.Action.ID) || IsMove((AID)spell.Action.ID)) + { + int index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); + if (index < 0) return; + AdvanceLine(module, Lines[index], caster.Position); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } + } + + // The Gigatempest caster's heading is only used for rotating the AOE shape. + // The exaflare direction must be derived from the caster's location. + private WDir? GetExaDirection(Actor caster) + { + Angle? forwardAngle = null; + if (caster.Position.Z == 536) + { + forwardAngle = 180.Degrees(); + } + if (caster.Position.Z == 504) + { + forwardAngle = 0.Degrees(); + } + if (caster.Position.X == -826) + { + forwardAngle = 90.Degrees(); + } + if (caster.Position.X == -794) + { + forwardAngle = 270.Degrees(); + } + + if (forwardAngle == null) return null; + + const float _advanceDistance = 8; + return _advanceDistance * forwardAngle.Value.ToDirection(); + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GunberdShot.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GunberdShot.cs new file mode 100644 index 0000000000..4b9a4ad86e --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/GunberdShot.cs @@ -0,0 +1,72 @@ +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +class GunberdShot : BossComponent +{ + private Actor? _gunberdCaster; + + public bool darkShotLoaded { get; private set; } + public bool windslicerLoaded { get; private set; } + + public bool Gunberding { get; private set; } + + + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (Gunberding) + { + if (darkShotLoaded) + hints.Add("Maintain Distance"); + if (windslicerLoaded) + hints.Add("Knockback"); + } + else + { + if (darkShotLoaded) + hints.Add("Dark Loaded"); + if (windslicerLoaded) + hints.Add("Windslicer Loaded"); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + switch ((AID)spell.Action.ID) + { + case AID.DarkShot: + darkShotLoaded = true; + break; + case AID.WindslicerShot: + windslicerLoaded = true; + break; + case AID.GunberdDark: + case AID.GunberdWindslicer: + Gunberding = true; + _gunberdCaster = caster; + break; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + switch ((AID)spell.Action.ID) + { + case AID.GunberdDark: + darkShotLoaded = false; + Gunberding = false; + break; + case AID.GunberdWindslicer: + windslicerLoaded = false; + Gunberding = false; + break; + } + } + + public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + if (Gunberding && windslicerLoaded) + { + var adjPos = Components.Knockback.AwayFromSource(pc.Position, _gunberdCaster, 10); + Components.Knockback.DrawKnockback(pc, adjPos, arena); + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/RedHiddenMines.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/RedHiddenMines.cs new file mode 100644 index 0000000000..73c8bbc124 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/RedHiddenMines.cs @@ -0,0 +1,36 @@ +using BossMod.Components; + +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +internal class RedHiddenMines : GenericAOEs +{ + private List _mines = new(); + private static AOEShapeCircle _shapeTrigger = new(3.6f); + private static AOEShapeCircle _shapeExplosion = new(8f); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _mines; + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ActivateRedMine) + { + _mines.Add(new(_shapeTrigger, caster.Position, color: ArenaColor.Trap)); + } + if ((AID)spell.Action.ID is AID.DetonateRedMine) + { + _mines.RemoveAll(t => t.Origin.AlmostEqual(caster.Position, 1)); + } + } + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.IndiscriminateDetonation) + { + List _detonatingMines = new(); + for (int i = 0; i < _mines.Count; i++) + { + _detonatingMines.Add(new(_shapeExplosion, _mines[i].Origin, color: ArenaColor.AOE)); + } + _mines = _detonatingMines; + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Ruination.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Ruination.cs new file mode 100644 index 0000000000..f8457fd912 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel5Menenius/Ruination.cs @@ -0,0 +1,66 @@ +using BossMod.Components; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.Foray.Duel.Duel5Menenius; + +class RuinationCross : GenericAOEs +{ + private static AOEShapeRect _aoeShape = new(20, 4, 20); + private List _aoes = new(); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes; + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.Ruination) + { + _aoes.Add(new(_aoeShape, caster.Position)); + _aoes.Add(new(_aoeShape, caster.Position, rotation: 90.Degrees())); + } + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.Ruination) + { + _aoes.Clear(); + } + } +} + +class RuinationExaflare : Exaflare +{ + public RuinationExaflare() : base(4) { } + + class LineWithActor : Line + { + public Actor Caster; + + public LineWithActor(Actor caster) + { + Next = caster.Position; + Advance = 4 * caster.Rotation.ToDirection(); + NextExplosion = caster.CastInfo!.NPCFinishAt; + TimeToMove = 1.1f; + ExplosionsLeft = 6; + MaxShownExplosions = 7; + Caster = caster; + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.RuinationExaStart) + Lines.Add(new LineWithActor(caster)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (Lines.Count > 0 && (AID)spell.Action.ID is AID.RuinationExaStart or AID.RuinationExaMove) + { + int index = Lines.FindIndex(item => ((LineWithActor)item).Caster == caster); + if (index < 0) return; + AdvanceLine(module, Lines[index], caster.Position); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } + } +}