diff --git a/BossMod/BossModule/AOEShapes.cs b/BossMod/BossModule/AOEShapes.cs index 4498ece0e9..5b0435056a 100644 --- a/BossMod/BossModule/AOEShapes.cs +++ b/BossMod/BossModule/AOEShapes.cs @@ -234,4 +234,61 @@ private IEnumerable ContourPoints(WPos origin, Angle rotation, float offse yield return origin + dx1 + dy2; } } + + public class AOEShapeTriangle : AOEShape + { + public float SideLength; + public Angle DirectionOffset; + + public AOEShapeTriangle(float sideLength, Angle directionOffset = new()) + { + SideLength = sideLength; + DirectionOffset = directionOffset; + } + + public override bool Check(WPos position, WPos origin, Angle rotation) + { + var vertices = CalculateVertices(origin, rotation + DirectionOffset); + return position.InTri(vertices.p1, vertices.p2, vertices.p3); + } + + public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) + { + var vertices = CalculateVertices(origin, rotation + DirectionOffset); + arena.AddTriangleFilled(vertices.p1, vertices.p2, vertices.p3, color); + } + + public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.Danger) + { + var vertices = CalculateVertices(origin, rotation + DirectionOffset); + arena.AddTriangle(vertices.p1, vertices.p2, vertices.p3, color); + } + + public override IEnumerable> Contour(WPos origin, Angle rotation, float offset = 0, float maxError = 1) + { + var vertices = CalculateVertices(origin, rotation + DirectionOffset, offset); + return new List> { new[] { vertices.p1, vertices.p2, vertices.p3 } }; + } + + public override Func Distance(WPos origin, Angle rotation) + { + // Implementing an exact distance calculation for a triangle shape might be complex and is beyond the scope of this basic implementation. + return p => (p - origin).Length(); // Simplified placeholder + } + + private (WPos p1, WPos p2, WPos p3) CalculateVertices(WPos origin, Angle rotation, float offset = 0) + { + // Calculate vertex positions for an equilateral triangle with origin as one vertex + var sideOffset = (SideLength + offset) / 2; + var height = MathF.Sqrt(3) / 2 * (SideLength + offset); + var direction = rotation.ToDirection(); + var ortho = direction.OrthoR(); + + var p1 = origin; // The origin is one of the vertices + var p2 = origin + direction * height - ortho * sideOffset; + var p3 = origin + direction * height + ortho * sideOffset; + + return (p1, p2, p3); + } + } } diff --git a/BossMod/BossModule/ArenaBounds.cs b/BossMod/BossModule/ArenaBounds.cs index 3705d0288f..41d8f21d6e 100644 --- a/BossMod/BossModule/ArenaBounds.cs +++ b/BossMod/BossModule/ArenaBounds.cs @@ -209,4 +209,69 @@ public override WDir ClampToBounds(WDir offset, float scale) return offset; } } + + public class ArenaBoundsTri : ArenaBounds + { + private const float sqrt3 = 1.73205080757f; // Square root of 3 + + public ArenaBoundsTri(WPos center, float sideLength) + : base(center, sideLength * sqrt3 / 3) { } // HalfSize is the radius of the circumscribed circle + + public override IEnumerable BuildClipPoly(float offset = 0) + { + // Calculate the vertices of the equilateral triangle + var height = HalfSize * sqrt3; // Height of the equilateral triangle + var halfSide = HalfSize; + yield return Center + new WDir(-halfSide, height / 3); + yield return Center + new WDir(halfSide, height / 3); + yield return Center + new WDir(0, -2 * height / 3); + } + + public override Pathfinding.Map BuildMap(float resolution = 0.5f) + { + // BuildMap implementation for equilateral triangle + // This is a simplified example and would need to be adapted based on specific pathfinding requirements + throw new NotImplementedException(); + } + + public override bool Contains(WPos p) + { + var a = Center + new WDir(-HalfSize, HalfSize * sqrt3 / 3); + var b = Center + new WDir(HalfSize, HalfSize * sqrt3 / 3); + var c = Center + new WDir(0, -2 * HalfSize * sqrt3 / 3); + + bool b1 = Sign(p, a, b) < 0.0f; + bool b2 = Sign(p, b, c) < 0.0f; + bool b3 = Sign(p, c, a) < 0.0f; + + return ((b1 == b2) && (b2 == b3)); + } + + private float Sign(WPos p1, WPos p2, WPos p3) + { + return (p1.X - p3.X) * (p2.Z - p3.Z) - (p2.X - p3.X) * (p1.Z - p3.Z); + } + + + public override float IntersectRay(WPos origin, WDir dir) + { + // Define triangle vertices + var a = Center + new WDir(-HalfSize, HalfSize * sqrt3 / 3); + var b = Center + new WDir(HalfSize, HalfSize * sqrt3 / 3); + var c = Center + new WDir(0, -2 * HalfSize * sqrt3 / 3); + + // Ray-triangle intersection algorithm goes here + // This is a complex topic and requires a bit of math + // Placeholder for the actual intersection calculation + return float.NaN; // Return NaN to indicate that this method needs proper implementation + } + + + public override WDir ClampToBounds(WDir offset, float scale = 1) + { + // Clamping within a triangle is highly context-dependent + // This method needs a detailed implementation based on specific requirements + return new WDir(0, 0); // Placeholder to indicate that clamping logic is needed + } + } } diff --git a/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobs.cs b/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobs.cs new file mode 100644 index 0000000000..7ef8c17fc6 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobs.cs @@ -0,0 +1,54 @@ +namespace BossMod.Endwalker.Alliance.A30OpeningMobs +{ + class WaterIII : Components.SelfTargetedAOEs + { + public WaterIII() : base(ActionID.MakeSpell(AID.WaterIII), new AOEShapeCircle(8)) { } + } + + class PelagicCleaver1 : Components.SelfTargetedAOEs + { + public PelagicCleaver1() : base(ActionID.MakeSpell(AID.PelagicCleaver1), new AOEShapeCone(40, 30.Degrees())) { } + } + + class PelagicCleaver2 : Components.SelfTargetedAOEs + { + public PelagicCleaver2() : base(ActionID.MakeSpell(AID.PelagicCleaver2), new AOEShapeCone(40, 30.Degrees())) { } + } + + class WaterFlood : Components.SelfTargetedAOEs + { + public WaterFlood() : base(ActionID.MakeSpell(AID.WaterFlood), new AOEShapeCircle(6)) { } + } + + class WaterBurst : Components.SelfTargetedAOEs + { + public WaterBurst() : base(ActionID.MakeSpell(AID.WaterBurst), new AOEShapeCircle(40)) { } + } + + class DivineFlood : Components.SelfTargetedAOEs + { + public DivineFlood() : base(ActionID.MakeSpell(AID.DivineFlood), new AOEShapeCircle(6)) { } + } + + class DivineBurst : Components.SelfTargetedAOEs + { + public DivineBurst() : base(ActionID.MakeSpell(AID.DivineBurst), new AOEShapeCircle(40)) { } + } + + //[ModuleInfo(CFCID = 962, PrimaryActorOID = 0x4010)] + public class A30OpeningMobs : BossModule + { + public A30OpeningMobs(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-800, -800), 20)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy, true); + foreach (var e in Enemies(OID.Triton)) + Arena.Actor(e, ArenaColor.Enemy); + foreach (var e in Enemies(OID.DivineSprite)) + Arena.Actor(e, ArenaColor.Enemy); + foreach (var e in Enemies(OID.WaterSprite)) + Arena.Actor(e, ArenaColor.Enemy); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobsEnums.cs b/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobsEnums.cs new file mode 100644 index 0000000000..e1b2d77a7c --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobsEnums.cs @@ -0,0 +1,26 @@ +namespace BossMod.Endwalker.Alliance.A30OpeningMobs +{ + public enum OID : uint + { + Serpent = 0x4010, // R3.450, x6 + Triton = 0x4011, // R1.950, x2 + DivineSprite = 0x4012, // R1.600, x3 + WaterSprite = 0x4085, // R0.800, x5 + UnknownEnemy = 0x400E, // R0.500, x1 + UnknownActor1 = 0x1E8FB8, // R2.000, x2, EventObj type + UnknownActor2 = 0x1E8F2F, // R0.500, x1, EventObj type + }; + + public enum AID : uint + { + AutoAttack = 870, // Serpent/Triton->player, no cast, single-target + WaterIII = 35438, // Serpent->location, 4.0s cast, range 8 circle + PelagicCleaver1 = 35439, // Triton->self, 5.0s cast, range 40 60-degree cone + PelagicCleaver2 = 35852, // Triton->self, 5.0s cast, range 40 60-degree cone + Water = 35469, // Water Sprite/Divine Sprite->player, no cast, single-target + WaterFlood = 35442, // Water Sprite->self, 3.0s cast, range 6 circle + WaterBurst = 35443, // Water Sprite->self, no cast, range 40 circle + DivineFlood = 35440, // Divine Sprite->self, 3.0s cast, range 6 circle + DivineBurst = 35441, // Divine Sprite->self, no cast, range 40 circle + }; +} diff --git a/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobsStates.cs b/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobsStates.cs new file mode 100644 index 0000000000..8709a3d02c --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A30OpeningMobs/A30OpeningMobsStates.cs @@ -0,0 +1,15 @@ +namespace BossMod.Endwalker.Alliance.A30OpeningMobs +{ + public class A30OpeningMobsStates : StateMachineBuilder + { + public A30OpeningMobsStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs new file mode 100644 index 0000000000..67d3d0a331 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31Thaliak.cs @@ -0,0 +1,16 @@ +namespace BossMod.Endwalker.Alliance.A31Thaliak +{ + [ModuleInfo(CFCID = 962, PrimaryActorOID = 0x404C)] + public class A31Thaliak : BossModule + { + public static ArenaBoundsSquare BoundsSquare = new ArenaBoundsSquare(new(-945.006f, 944.976f), 24f); + public static ArenaBoundsTri BoundsTri = new ArenaBoundsTri(new(-945.006f, 948.500f), 41f); + public A31Thaliak(WorldState ws, Actor primary) : base(ws, primary, BoundsSquare) { } + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy, true); + foreach (var s in Enemies(OID.ThaliakHelper)) + Arena.Actor(s, ArenaColor.Object, true); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakEnums.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakEnums.cs new file mode 100644 index 0000000000..5bb51fb9f3 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakEnums.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A31Thaliak + +{ + + public enum OID : uint +{ + Thaliak = 0x404C, // R9.496, x1 + ThaliakClone = 0x404D, // R9.496, x1 + ThaliakHelper = 0x233C, // R0.500, x44, 523 type + WindWreathedPortal = 0x1EB91D, // R0.500, x1, EventObj type + HieroglyphikaIndicator = 0x40AA, // R0.500, x1 // Rotation Indicator + UnknownActor = 0x400E, // R0.500, x1 +}; + + + public enum AID : uint +{ + AutoAttack = 35036, // Thaliak->player, no cast, single-target + Ability1_1 = 35035, // Thaliak->location, no cast, single-target + + Katarraktes = 35025, // Thaliak->self, 5.0s cast, single-target // Raidwide damage and bleeding damage-over-time. + KatarraktesHelper = 35034, // ThaliakHelper->self, 5.7s cast, range 70 circle + + Hieroglyphika = 35023, // Thaliak->self, 5.0s cast, single-target // Covers all but two tiles of the arena with a green AoE telegraph + HieroglyphikaHelper = 35024, // ThaliakHelper->self, 3.0s cast, range 12 width 12 rect // 2 safe spots + + Thlipsis = 35032, // Thaliak->self, 4.0s cast, single-target // Stack marker on random player + ThlipsisHelper = 35033, // ThaliakHelper->players, 6.0s cast, range 6 circle + + Hydroptosis = 35028, // Thaliak->self, 4.0s cast, single-target // Spread markers on random players. Inflicts Water resistance down making overlap lethal. + HydroptosisHelper = 35029, // ThaliakHelper->player, 5.0s cast, range 6 circle + + Rhyton = 35030, // Thaliak->self, 5.0s cast, single-target // Line AoE tankbusters targeting all 3 tanks, or whoever is top enmity if not all tanks are alive. + RhytonHelper = 35031, // ThaliakHelper->players, no cast, range 70 width 6 rect + + Rheognosis = 35012, // Thaliak->self, 5.0s cast, single-target // Castbar Indicator + RheognosisPetrine = 35013, // Thaliak->self, 5.0s cast, single-target // Castbar Indicator + RheognosisPetrineHelper = 35014, // ThaliakClone->self, no cast, single-target + RheognosisKnockback = 35015, // ThaliakHelper->self, 3.0s cast, range 48 width 48 rect // Summons the same knockback clone as in Rheognosis, but two spheres of water + RheognosisCrashExaflare = 35016, // ThaliakHelper->self, no cast, range 10 width 24 rect // 5 helpers + + Tetraktys = 35017, // Thaliak->self, 6.0s cast, single-target // Transforms the arena into a triangle, with a dangerous AoE surrounding it. + TetraBlueTriangles = 35018, // ThaliakHelper->self, 1.8s cast, ??? // Blue triangles? + TetraGreenTriangles = 35019, // ThaliakHelper->self, 1.8s cast, ??? // Green triangles? + + TetraktuosKosmos = 35020, // Thaliak->self, 4.0s cast, single-target // Summons a triangular tower on a panel + TetraktuosKosmosHelper = 35022, // ThaliakHelper->self, 2.9s cast, ??? // telegraphed line AoEs + TetraktuosKosmosHelper2 = 35021, // ThaliakHelper->self, 2.9s cast, range 30 width 16 rect // This attack repeats, but now two towers will spawn + + LeftBank = 35026, // Thaliak->self, 5.0s cast, range 60 180-degree cone // A half-room cleave + LeftBank2 = 35884, // Thaliak->self, 22.0s cast, range 60 180-degree cone // A half-room cleave + RightBank = 35027, // Thaliak->self, 5.0s cast, range 60 180-degree cone // A half-room cleave + RightBank2 = 35885, // Thaliak->self, 22.0s cast, range 60 180-degree cone // A half-room cleave +}; + + public enum SID : uint +{ + Weakness = 43, // none->player, extra=0x0 + Bleeding = 2088, // ThaliakHelper->player, extra=0x0 + VulnerabilityUp = 1789, // Thaliak/ThaliakHelper->player, extra=0x1 + WaterResistanceDownII = 1025, // ThaliakHelper->player, extra=0x0 + Transcendent = 418, // none->player, extra=0x0 + SustainedDamage = 2935, // ThaliakHelper->player, extra=0x0 + DownForTheCount = 783, // ThaliakHelper->player, extra=0xEC7 + Inscribed = 3732, // none->player, extra=0x0 + Bind = 2518, // none->player, extra=0x0 + BrinkOfDeath = 44, // none->player, extra=0x0 + +}; + + + public enum IconID : uint +{ + Icon_318 = 318, // player + HydroptosisSpread = 139, // player + RhytonBuster = 471, // player + ClockwiseHieroglyphika = 487, // HieroglyphikaIndicator + CounterClockwiseHieroglyphika = 490, // HieroglyphikaIndicator +}; + +public enum TetherID : uint +{ +}; + + +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs new file mode 100644 index 0000000000..c955e418cf --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/A31ThaliakStates.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A31Thaliak +{ + class A31ThaliakStates : StateMachineBuilder + { + public A31ThaliakStates(BossModule module) : base(module) + { + SimplePhase(0, id => { SimpleState(id, 10000, "Enrage"); }, "Single phase") + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + //.ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => Module.PrimaryActor.IsDestroyed; + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakAbilites.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakAbilites.cs new file mode 100644 index 0000000000..b7fddf8771 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakAbilites.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A31Thaliak +{ + class Thlipsis : Components.StackWithCastTargets + { + public Thlipsis() : base(ActionID.MakeSpell(AID.ThlipsisHelper), 6) { } + } + class Hydroptosis : Components.SpreadFromCastTargets + { + public Hydroptosis() : base(ActionID.MakeSpell(AID.HydroptosisHelper), 6) { } + } + + // + class Rhyton : Components.GenericBaitAway + { + private static AOEShapeRect _shape = new(70, 3); + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.RhytonBuster) + CurrentBaits.Add(new(module.PrimaryActor, actor, _shape)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.RhytonHelper) + { + ++NumCasts; + CurrentBaits.Clear(); + } + } + } + class LeftBank : Components.SelfTargetedAOEs + { + public LeftBank() : base(ActionID.MakeSpell(AID.LeftBank), new AOEShapeCone(60, 90.Degrees())) { } + } + class LeftBank2 : Components.SelfTargetedAOEs + { + public LeftBank2() : base(ActionID.MakeSpell(AID.LeftBank2), new AOEShapeCone(60, 90.Degrees())) { } + } + class RightBank : Components.SelfTargetedAOEs + { + public RightBank() : base(ActionID.MakeSpell(AID.RightBank), new AOEShapeCone(60, 90.Degrees())) { } + } + class RightBank2 : Components.SelfTargetedAOEs + { + public RightBank2() : base(ActionID.MakeSpell(AID.RightBank2), new AOEShapeCone(60, 90.Degrees())) { } + } + // + + class RheognosisKnockback : KnockbackFromCastTarget + { + public RheognosisKnockback() : base(ActionID.MakeSpell(AID.RheognosisKnockback), 25f, kind: Kind.DirForward) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakCrashExaflare b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakCrashExaflare new file mode 100644 index 0000000000..62b23972a8 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakCrashExaflare @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Components +{ + public class RheognosisCrashExaflare : Exaflare + { + public RheognosisCrashExaflare() : basebase(new AOEShapeRect(10, 12)) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.RheognosisCrashExaflare) + { + Lines.Add(new() { Next = caster.Position, Advance = 4.8f * spell.Rotation.ToDirection(), NextExplosion = spell.NPCFinishAt, TimeToMove = 0.1f, ExplosionsLeft = 5, MaxShownExplosions = 5 }); + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.RheognosisCrashExaflare) + { + ++NumCasts; + int index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); + if (index == -1) + { + module.ReportError(this, $"Failed to find entry for {caster.InstanceID:X}"); + return; + } + + AdvanceLine(module, Lines[index], caster.Position); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakHieroglyphika.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakHieroglyphika.cs new file mode 100644 index 0000000000..6e09d08fad --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakHieroglyphika.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A31Thaliak +{ + class Hieroglyphika : Components.GenericAOEs + { + private AOEShapeRect _shape = new(6, 6, 6); + private List _aoes = new(); + + public Hieroglyphika() : base(ActionID.MakeSpell(AID.HieroglyphikaHelper)) { } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(15); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.HieroglyphikaHelper) + _aoes.Add(new(_shape, caster.Position, caster.Rotation, spell.NPCFinishAt)); + } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.HieroglyphikaHelper) + { + ++NumCasts; + if (_aoes.Count > 0) + _aoes.RemoveAt(0); + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakTetraktys.cs b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakTetraktys.cs new file mode 100644 index 0000000000..a5092b2b99 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A31Thaliak/ThaliakTetraktys.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A31Thaliak + +{ + class Tetraktys : BossComponent + { + private bool active; + + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (state == 0x00080004 && index <= 4) + active = false; + } + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Tetraktys) + active = true; + } + public override void Update(BossModule module) + { + if (!active) + module.Arena.Bounds = A31Thaliak.BoundsSquare; + else if (active) + module.Arena.Bounds = A31Thaliak.BoundsTri; + } + } + class TetraBlueTriangles : Components.SelfTargetedAOEs + { + public TetraBlueTriangles() : base(ActionID.MakeSpell(AID.TetraBlueTriangles), new AOEShapeTriangle(16)) { } + } + class TetraGreenTriangles : Components.SelfTargetedAOEs + { + public TetraGreenTriangles() : base(ActionID.MakeSpell(AID.TetraGreenTriangles), new AOEShapeTriangle(32)) { } + } + class TetraktuosKosmosHelper : Components.SelfTargetedAOEs + { + public TetraktuosKosmosHelper() : base(ActionID.MakeSpell(AID.TetraktuosKosmosHelper), new AOEShapeRect(30, 8)) { } + } + public class TetraktuosKosmosHelper2 : Components.SelfTargetedAOEs + { + public TetraktuosKosmosHelper2() : base(ActionID.MakeSpell(AID.TetraktuosKosmosHelper2), new AOEShapeRect(30, 8)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32Llymlaen.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32Llymlaen.cs new file mode 100644 index 0000000000..3ea5e7032a --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32Llymlaen.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + // + [ModuleInfo(CFCID = 962, PrimaryActorOID = 0x4024)] + public class A32Llymlaen : BossModule + { + public A32Llymlaen(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsRect(new(-0.015f, -900.023f),20, 30)) + { + } + protected override void DrawArenaForeground(int pcSlot, Actor pc) + { + Arena.PathLineTo(new(-20.015f, -870.023f)); // Top Left + Arena.PathLineTo(new(19.985f, -870.023f)); // Top-Right + Arena.PathLineTo(new(19.985f, -930.023f)); // Bottom-Right + Arena.PathLineTo(new(-20.015f, -930.023f)); // Bottom-Left + Arena.PathLineTo(new(-20.015f, -905.000f)); + Arena.PathLineTo(new(-78.000f, -905.000f)); + Arena.PathLineTo(new(-78.000f, -895.000f)); + Arena.PathLineTo(new(-20.015f, -895.000f)); + Arena.PathStroke(true, ArenaColor.Border); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenEnum.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenEnum.cs new file mode 100644 index 0000000000..f69b469e28 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenEnum.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + + public enum OID : uint +{ + Llymlaen = 0x4024, // R7.000, x1 + LlymlaenHelper = 0x233C, // R0.500, x25, 523 type + Thalaos = 0x4027, // R6.300, x1 + Perykos = 0x4026, // R6.300, x1 + SeaFoam = 0x4029, // R1.500, spawn during fight + Trident = 0x4025, // R3.000, spawn during fight + OschonsAvatar = 0x406E, // R8.000, spawn during fight + Unknown = 0x400E, // R0.500, x1 +}; + public enum AID : uint +{ + AutoAttack = 871, // Llymlaen->player, no cast, single-target + + TempestRaidwide = 34827, // Llymlaen->self, 5.0s cast, range 100 circle // Raidwide + ShockwaveRaidwide = 34836, // LlymlaenHelper->self, 10.2s cast, range 100 circle // Raidwide + LandingRaidwide = 34843, // Trident->self, no cast, range 80 circle // Raidwide + GodsbaneRaidwide = 35948, // LlymlaenHelper->self, 7.0s cast, range 100 circle // Raidwide + + SeafoamSpiralDonut = 34829, // Llymlaen->self, 6.0s cast, range 7-70 donut // DonutAOE + + WindRoseAOE = 34828, // Llymlaen->self, 6.0s cast, range 12 circle // CircleAOE + + DireStraitAltVisual = 34825, // Llymlaen->location, no cast, single-target // Visual + DireStraitAltRectAOE1 = 36045, // LlymlaenHelper->self, 2.5s cast, range 40 width 80 rect // RectAOE + DireStraitAltRectAOE2 = 36046, // LlymlaenHelper->self, 4.5s cast, range 40 width 80 rect // RectAOE + + DireStraitsVisual = 36044, // Llymlaen->self, no cast, single-target // Visual + DireStraitsRectAOE1 = 34831, // LlymlaenHelper->self, 7.5s cast, range 40 width 80 rect // RectAOE + DireStraitsRectAOE2 = 34832, // LlymlaenHelper->self, 9.3s cast, range 40 width 80 rect // RectAOE + + _Ability4_ = 34859, // Llymlaen->self, no cast, single-target + + NavigatorsTridentVisual1 = 34830, // Llymlaen->self, 6.5s cast, single-target + NavigatorsTridentVisual2 = 36048, // Llymlaen->self, no cast, single-target + NavigatorsTridentRectAOE = 34833, // LlymlaenHelper->self, 7.0s cast, range 60 width 60 rect // RectAOE with knockback + + SurgingWaveKnockback = 34834, // Llymlaen->location, 9.0s cast, single-target // Knockback + SurgingWaveAOE = 34835, // LlymlaenHelper->self, 10.0s cast, range 6 circle // CircleAOE + SphereShatter = 34861, // SeaFoam->player, no cast, single-target // Sphere pop + + FrothingSeaRectAOE = 34826, // LlymlaenHelper->self, no cast, range 25 width 100 rect // RectAOE + + // one these are cast after Llymlaen shoves everyone back after parting the water + RightStraitCone = 34898, // Llymlaen->self, 6.0s cast, range 60 180-degree cone // ConeCleaves + LeftStraitCone = 34897, // Llymlaen->self, 6.0s cast, range 60 180-degree cone // ConeCleaves + + DeepDiveStack1 = 34841, // Llymlaen->players, 5.0s cast, range 6 circle // Stack + DeepDiveStack2 = 34868, // Llymlaen->players, 9.0s cast, range 6 circle // Stack + HardWaterStack1 = 34869, // Perykos->players, 5.0s cast, range 6 circle // Stack + HardWaterStack2 = 34870, // Thalaos->players, 7.0s cast, range 6 circle // Stack + + TorrentialTridents = 34842, // Llymlaen->self, 4.0s cast, single-target + LandingAOE = 34844, // Trident->self, 6.0s cast, range 18 circle // CircleAOE + StormySeas = 34845, // Llymlaen->self, no cast, single-target + StormwhorlLocAOE = 34846, // LlymlaenHelper->location, 4.0s cast, range 6 circle // LocationAOE + StormwindsSpread = 34847, // LlymlaenHelper->players, 5.0s cast, range 6 circle // Spread + + DenizensOfTheDeep = 34848, // Llymlaen->self, 4.0s cast, single-target // Summons adds + + SerpentsTideRectAOE1 = 34855, // Perykos->self, no cast, range 80 width 20 rect // RectAOE + SerpentsTideRectAOE2 = 34857, // Thalaos->self, no cast, range 80 width 20 rect // RectAOE + SerpentsTideRectAOE3 = 34854, // Perykos->self, no cast, range 80 width 20 rect // RectAOE + SerpentsTideRectAOE4 = 34856, // Thalaos->self, no cast, range 80 width 20 rect // RectAOE + SerpentsTideRectAOE5 = 34853, // LlymlaenHelper->self, 8.0s cast, range 80 width 20 rect // RectAOE + SerpentsTideRectAOE6 = 34838, // LlymlaenHelper->self, 1.0s cast, range 80 width 10 rect // RectAOE // This one does not fully cover the section of the arena, this is intentional to allow a safespot if youre willing to use antiknock + + MaelstromLocAOE = 34858, // LlymlaenHelper->location, 4.0s cast, range 6 circle // LocationAOE + Godsbane1 = 34852, // LlymlaenHelper->self, 2.0s cast, single-target + Godsbane2 = 34849, // Llymlaen->self, 5.0s cast, single-target + Godsbane3 = 34850, // Perykos->self, 5.0s cast, single-target + Godsbane4 = 34851, // Thalaos->self, 5.0s cast, single-target + DireStraits4 = 36043, // Llymlaen->self, no cast, single-target + _Ability6_ = 36047, // Llymlaen->self, no cast, single-target + ToTheLast1 = 34837, // Llymlaen->self, 5.0s cast, single-target + ToTheLast2 = 34839, // Llymlaen->self, no cast, single-target + ToTheLastRectAOE = 34840, // LlymlaenHelper->self, 6.0s cast, range 80 width 10 rect // RectAOE +}; + + public enum SID : uint +{ + VulnerabilityUp = 1789, // Llymlaen/SeaFoam/LlymlaenHelper/Trident/Perykos/Thalaos->player, extra=0x1/0x2/0x3/0x4/0x5 + SeafoamStatus = 2234, // none->SeaFoam, extra=0x14 + DownForTheCount = 783, // LlymlaenHelper->player, extra=0xEC7 + Liftoff = 3262, // SeaFoam->player, extra=0x0 + Weakness = 43, // none->player, extra=0x0 + Transcendent = 418, // none->player, extra=0x0 + WindResistanceDownII = 2096, // LlymlaenHelper->player, extra=0x0 + Dropsy1 = 3777, // none->player, extra=0x0 + Dropsy2 = 3778, // none->player, extra=0x0 + Dropsy3 = 2087, // LlymlaenHelper->player, extra=0x0 + BrinkOfDeath = 44, // none->player, extra=0x0 + +}; + + public enum IconID : uint +{ + DiveStack = 161, // player + StormwindBait = 139, // player + HardWaterStack = 305, // player +}; + + + +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs new file mode 100644 index 0000000000..b8bb475d27 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/A32LlymlaenStates.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen + +{ + class A32LlymlaenStates : StateMachineBuilder + { + public A32LlymlaenStates(BossModule module) : base(module) + { + SimplePhase(0, id => { SimpleState(id, 10000, "Enrage"); }, "Single phase") + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => Module.PrimaryActor.IsDestroyed; + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenCircleAOE.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenCircleAOE.cs new file mode 100644 index 0000000000..f0a4731c37 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenCircleAOE.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class WindRoseAOE : Components.SelfTargetedAOEs + { + public WindRoseAOE() : base(ActionID.MakeSpell(AID.WindRoseAOE), new AOEShapeCircle(12)) { } + } + class SurgingWaveAOE : Components.SelfTargetedAOEs + { + public SurgingWaveAOE() : base(ActionID.MakeSpell(AID.SurgingWaveAOE), new AOEShapeCircle(6)) { } + } + class LandingAOE : Components.SelfTargetedAOEs + { + public LandingAOE() : base(ActionID.MakeSpell(AID.LandingAOE), new AOEShapeCircle(18)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenConeCleaves.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenConeCleaves.cs new file mode 100644 index 0000000000..e005597756 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenConeCleaves.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class RightStraitCone : Components.SelfTargetedAOEs + { + public RightStraitCone() : base(ActionID.MakeSpell(AID.RightStraitCone), new AOEShapeCone(60, 90.Degrees())) { } + } + class LeftStraitCone : Components.SelfTargetedAOEs + { + public LeftStraitCone() : base(ActionID.MakeSpell(AID.LeftStraitCone), new AOEShapeCone(60, 90.Degrees())) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenDireStraits.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenDireStraits.cs new file mode 100644 index 0000000000..d65312f55b --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenDireStraits.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class DireStraits : Components.GenericAOEs + { + private List _aoes = new(); + + private static AOEShapeRect _shape = new(80, 40); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(1); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.DireStraitAltRectAOE1 or AID.DireStraitsRectAOE1) + _aoes.Add(new(_shape, caster.Position, spell.Rotation, spell.NPCFinishAt.AddSeconds(9.2f))); + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.DireStraitAltRectAOE2 or AID.DireStraitsRectAOE2) + { + _aoes.Clear(); + ++NumCasts; + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenDonutAOE.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenDonutAOE.cs new file mode 100644 index 0000000000..eaf21994b3 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenDonutAOE.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class SeafoamSpiralDonut : Components.SelfTargetedAOEs + { + public SeafoamSpiralDonut() : base(ActionID.MakeSpell(AID.SeafoamSpiralDonut), new AOEShapeDonut(7, 70)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenLocationAOEs.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenLocationAOEs.cs new file mode 100644 index 0000000000..f2c9e1a475 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenLocationAOEs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen + +{ + class StormwhorlLocAOE : Components.LocationTargetedAOEs + { + public StormwhorlLocAOE() : base(ActionID.MakeSpell(AID.StormwhorlLocAOE), 6) { } + } + class MaelstromLocAOE : Components.LocationTargetedAOEs + { + public MaelstromLocAOE() : base(ActionID.MakeSpell(AID.MaelstromLocAOE), 6) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenNavigatorsTrident.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenNavigatorsTrident.cs new file mode 100644 index 0000000000..4c523ea430 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenNavigatorsTrident.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class NavigatorsTridentRectAOE : Components.GenericAOEs + { + private List _aoes = new(); + + private static AOEShapeRect _shape = new(20, 5, 20); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(1); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.NavigatorsTridentRectAOE) + _aoes.Add(new(_shape, caster.Position, spell.Rotation, spell.NPCFinishAt.AddSeconds(7.3f))); + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.NavigatorsTridentVisual1 or AID.NavigatorsTridentVisual2 or AID.NavigatorsTridentRectAOE) + { + _aoes.Clear(); + ++NumCasts; + } + } + } + class NavigatorsTridentKnockback : Components.Knockback + { + private List _sources = new(); + private static AOEShapeCone _shape = new(30, 90.Degrees()); + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => _sources; + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.NavigatorsTridentRectAOE) + { + _sources.Clear(); + // charge always happens through center, so create two sources with origin at center looking orthogonally + _sources.Add(new(module.Bounds.Center, 12, spell.NPCFinishAt, _shape, spell.Rotation + 90.Degrees(), Kind.DirForward)); + _sources.Add(new(module.Bounds.Center, 12, spell.NPCFinishAt, _shape, spell.Rotation - 90.Degrees(), Kind.DirForward)); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.NavigatorsTridentVisual1 or AID.NavigatorsTridentVisual2 or AID.NavigatorsTridentRectAOE) + { + _sources.Clear(); + ++NumCasts; + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenRectCleaves.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenRectCleaves.cs new file mode 100644 index 0000000000..724b61b342 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenRectCleaves.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class FrothingSeaRectAOE : Components.SelfTargetedAOEs + { + public FrothingSeaRectAOE() : base(ActionID.MakeSpell(AID.FrothingSeaRectAOE), new AOEShapeRect(25, 50)) { } + } + + class SerpentsTideRectAOE1 : Components.SelfTargetedAOEs + { + public SerpentsTideRectAOE1() : base(ActionID.MakeSpell(AID.SerpentsTideRectAOE1), new AOEShapeRect(80, 10)) { } + } + class SerpentsTideRectAOE2 : Components.SelfTargetedAOEs + { + public SerpentsTideRectAOE2() : base(ActionID.MakeSpell(AID.SerpentsTideRectAOE2), new AOEShapeRect(80, 10)) { } + } + class SerpentsTideRectAOE3 : Components.SelfTargetedAOEs + { + public SerpentsTideRectAOE3() : base(ActionID.MakeSpell(AID.SerpentsTideRectAOE3), new AOEShapeRect(80, 10)) { } + } + class SerpentsTideRectAOE4 : Components.SelfTargetedAOEs + { + public SerpentsTideRectAOE4() : base(ActionID.MakeSpell(AID.SerpentsTideRectAOE4), new AOEShapeRect(80, 10)) { } + } + class SerpentsTideRectAOE5 : Components.SelfTargetedAOEs + { + public SerpentsTideRectAOE5() : base(ActionID.MakeSpell(AID.SerpentsTideRectAOE5), new AOEShapeRect(80, 10)) { } + } + class SerpentsTideRectAOE6 : Components.SelfTargetedAOEs + { + public SerpentsTideRectAOE6() : base(ActionID.MakeSpell(AID.SerpentsTideRectAOE6), new AOEShapeRect(80, 5)) { } + } + class ToTheLastRectAOE : Components.SelfTargetedAOEs + { + public ToTheLastRectAOE() : base(ActionID.MakeSpell(AID.ToTheLastRectAOE), new AOEShapeRect(80, 5)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenStacksSpreads.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenStacksSpreads.cs new file mode 100644 index 0000000000..818dc33657 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenStacksSpreads.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class DeepDiveStack : Components.UniformStackSpread + { + public DeepDiveStack() : base(6,0) { } + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.DiveStack) + AddStack(actor, module.WorldState.CurrentTime.AddSeconds(9f)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.DeepDiveStack1 or AID.DeepDiveStack2) + Stacks.Clear(); + } + } + class HardWaterStack : Components.UniformStackSpread + { + public HardWaterStack() : base(6,0) { } + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.HardWaterStack) + AddStack(actor, module.WorldState.CurrentTime.AddSeconds(7f)); + } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.HardWaterStack1 or AID.HardWaterStack2) + Stacks.Clear(); + } + } + class StormwindsSpread : Components.SpreadFromCastTargets + { + public StormwindsSpread() : base(ActionID.MakeSpell(AID.StormwindsSpread), 6) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenSurgingWave.cs b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenSurgingWave.cs new file mode 100644 index 0000000000..60bec538a2 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A32Llymlaen/LlymlaenSurgingWave.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A32Llymlaen +{ + class SphereShatter : Components.GenericAOEs + { + private IReadOnlyList _bubbles = ActorEnumeration.EmptyList; + + private static AOEShapeCircle _shape = new(1.5f); // TODO: verify explosion radius + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _bubbles.Where(actor => !actor.IsDead).Select(b => new AOEInstance(_shape, b.Position)); + + public override void Init(BossModule module) + { + _bubbles = module.Enemies(OID.SeaFoam); + } + } + class SurgingWaveKnockback : KnockbackFromCastTarget + { + public SurgingWaveKnockback() : base(ActionID.MakeSpell(AID.SurgingWaveKnockback), 40f, kind: Kind.AwayFromOrigin) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs new file mode 100644 index 0000000000..fc2bd52482 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33Oschon.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A33Oschon + +{ + class TheArrow2 : Components.BaitAwayCast + { + public TheArrow2() : base(ActionID.MakeSpell(AID.TheArrow2), new AOEShapeCircle(6), true) { } + } + + class FlintedFoehnStack : Components.StackWithCastTargets + { + public FlintedFoehnStack() : base(ActionID.MakeSpell(AID.FlintedFoehnStack), 6) { } + } + + + [ModuleInfo(CFCID = 962, PrimaryActorOID = 0x406D)] + public class A33Oschon : BossModule + { + public A33Oschon(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsRect(new(-0.015f, 749.996f), 25, 25)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs new file mode 100644 index 0000000000..95e18d5f26 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonEnum.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A33Oschon + +{ + + public enum OID : uint +{ + Oschon = 0x406D, // R8.000, x1 + OschonBig = 0x406F, // R24.990, spawn during fight + OschonsAvatar = 0x406E, // R8.000, x4 + OschonHelper = 0x233C, // R0.500, x40, 523 type + Unknown = 0x400E, // R0.500, x1 + Actor1e8fb8 = 0x1E8FB8, // R2.000, x1, EventObj type + Actor1e8f2f = 0x1E8F2F, // R0.500, x1, EventObj type + PedestalOfPassage = 0x1EB91A, // R0.500, x1, EventObj type + ExitToTheOmphalos = 0x1EB91E, // R0.500, x1, EventObj type +}; + + public enum AID : uint +{ + AutoAttack = 35906, // Oschon->player, no cast, single-target + AutoAttackBig = 35907, // OschonBig->player, no cast, single-target + + Ability1 = 35224, // Oschon->location, no cast, single-target + Ability2 = 34863, // OschonHelper->self, no cast, single-target + Ability3 = 34864, // OschonHelper->self, no cast, single-target + Ability4 = 34862, // OschonHelper->self, no cast, single-target + Ability5 = 34865, // OschonHelper->self, no cast, single-target + + TrekShot1 = 35214, // Oschon->self, 6.0s cast, single-target + TrekShot2 = 35908, // OschonHelper->self, 9.5s cast, range 65 ?-degree cone // wide frontal cone AoE + TrekShot3 = 35213, // Oschon->self, 6.0s cast, single-target + TrekShot4 = 35215, // OschonHelper->self, 9.5s cast, range 65 ?-degree cone // wide frontal cone AoE + + SwingingDraw1 = 35210, // OschonsAvatar->self, 7.0s cast, single-target + SwingingDraw2 = 35212, // OschonsAvatar->self, 2.0s cast, range 65 120-degree cone // wide frontal cone AoE + SwingingDraw3 = 35211, // OschonsAvatar->self, 7.0s cast, single-target + Reproduce = 35209, // Oschon->self, 3.0s cast, single-target // Summons an OschonsAvatar add; The add will use Swinging Draw + + SuddenDownpour1 = 35225, // Oschon->self, 4.0s cast, single-target + SuddenDownpour2 = 36026, // OschonHelper->self, 5.0s cast, range 60 circle // Raidwide + + Downhill1 = 35231, // Oschon->self, 3.0s cast, single-target + Downhill2 = 35233, // OschonHelper->location, 8.5s cast, range 6 circle // Summons several circle AoE telegraphs. This is used with Climbing Shot. + ClimbingShot = 35217, // Oschon->self, 5.0s cast, range 80 circle // knockback; Soaring Minuet immediately follows + ClimbingShot2 = 35216, // Oschon->self, 5.0s cast, range 80 circle // knockback; Soaring Minuet immediately follows + + SoaringMinuet1 = 36110, // Oschon->self, 5.0s cast, range 65 270-degree cone // 270 degree frontal cleave from the boss. Only has a brief AoE indicator. + SoaringMinuet2 = 35220, // Oschon->self, 5.0s cast, range 65 270-degree cone // 270 degree frontal cleave from the boss. Only has a brief AoE indicator. + + FlintedFoehn1 = 35235, // Oschon->self, 4.5s cast, single-target + FlintedFoehnStack = 35237, // OschonHelper->players, no cast, range 6 circle // Multi-hit stack AoE + + TheArrow1 = 35227, // Oschon->self, 4.0s cast, single-target + TheArrow2 = 35229, // OschonHelper->players, 5.0s cast, range 6 circle // Telegraphed AoE tankbusters on all three tanks. + + LoftyPeaks = 35239, // Oschon->self, 5.0s cast, single-target // Phase Change + MovingMountains = 36067, // OschonHelper->self, no cast, range 60 circle // Raidwide + PeakPeril = 36068, // OschonHelper->self, no cast, range 60 circle // Raidwide + Shockwave = 35240, // OschonHelper->self, 8.4s cast, range 60 circle // Raidwide +}; + + public enum SID : uint +{ + Windburn1 = 3069, // none->player, extra=0x0 + Windburn2 = 3070, // none->player, extra=0x0 + Unknown = 2970, // Oschon->Oschon, extra=0x294 + Weakness = 43, // none->player, extra=0x0 + VulnerabilityUp = 1789, // OschonsAvatar/Oschon/OschonHelper->player, extra=0x1/0x2 + Transcendent = 418, // none->player, extra=0x0 + TheRoadTo80 = 1411, // none->player, extra=0x0 + Invincibility = 1570, // none->player, extra=0x0 + +}; + + public enum IconID : uint +{ + FlintedFoehnStack = 316, // player + Icon_344 = 344, // player +}; + + +} diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs new file mode 100644 index 0000000000..cfbc78c67a --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/A33OschonStates.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A33Oschon + +{ + class A33OschonStates : StateMachineBuilder + { + public A33OschonStates(BossModule module) : base(module) + { + SimplePhase(0, id => { SimpleState(id, 10000, "Enrage"); }, "Single phase") + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + //.ActivateOnEnter() // this is here to test what these do + //.ActivateOnEnter() + //.ActivateOnEnter() + //.ActivateOnEnter() + .Raw.Update = () => Module.PrimaryActor.IsDestroyed || Module.PrimaryActor.HP.Cur <= 1 && !Module.PrimaryActor.IsTargetable; + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonDownhillClimb.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonDownhillClimb.cs new file mode 100644 index 0000000000..70db3a6b63 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonDownhillClimb.cs @@ -0,0 +1,47 @@ +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A33Oschon + +{ + class Downhill2 : Components.LocationTargetedAOEs + { + public Downhill2() : base(ActionID.MakeSpell(AID.Downhill2), 6) { } + } + + class ClimbingShot : Components.KnockbackFromCastTarget + { + public ClimbingShot() : base(ActionID.MakeSpell(AID.ClimbingShot), 20) { } + } + + class ClimbingShot2 : Components.KnockbackFromCastTarget + { + public ClimbingShot2() : base(ActionID.MakeSpell(AID.ClimbingShot2), 20) { } + } + + class SoaringMinuet1 : Components.SelfTargetedAOEs + { + public SoaringMinuet1() : base(ActionID.MakeSpell(AID.SoaringMinuet1), new AOEShapeCone(65, 135.Degrees())) { } + } + class SoaringMinuet2 : Components.SelfTargetedAOEs + { + public SoaringMinuet2() : base(ActionID.MakeSpell(AID.SoaringMinuet2), new AOEShapeCone(65, 135.Degrees())) { } + } + + class Ability1 : Components.LocationTargetedAOEs + { + public Ability1() : base(ActionID.MakeSpell(AID.Ability1), 6) { } + } + class Ability2 : Components.SelfTargetedAOEs + { + public Ability2() : base(ActionID.MakeSpell(AID.Ability2), new AOEShapeCone(65, 135.Degrees())) { } + } + class Ability3 : Components.SelfTargetedAOEs + { + public Ability3() : base(ActionID.MakeSpell(AID.Ability3), new AOEShapeCone(65, 135.Degrees())) { } + } + class Ability4 : Components.SelfTargetedAOEs + { + public Ability4() : base(ActionID.MakeSpell(AID.Ability4), new AOEShapeCone(65, 135.Degrees())) { } + } + +} diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonFlintedFoehn.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonFlintedFoehn.cs new file mode 100644 index 0000000000..482560c472 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonFlintedFoehn.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A33Oschon +{ + class FlintedFoehn : Components.UniformStackSpread + { + public FlintedFoehn() : base(6,0) { } + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.FlintedFoehnStack) + AddStack(actor, module.WorldState.CurrentTime.AddSeconds(10.45f)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.FlintedFoehnStack) + Stacks.Clear(); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonSwingingTrekShots.cs b/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonSwingingTrekShots.cs new file mode 100644 index 0000000000..71926dfe43 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A33Oschon/OschonSwingingTrekShots.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A33Oschon + +{ + class TrekDraws : Components.GenericAOEs + { + private List _aoes = new(); + + private static AOEShapeCone _shape = new(65, 60.Degrees()); + + 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.TrekShot2 or AID.TrekShot4 or AID.SwingingDraw2) + _aoes.Add(new(_shape, caster.Position, spell.Rotation, spell.NPCFinishAt)); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.TrekShot2 or AID.TrekShot4 or AID.SwingingDraw2) + { + _aoes.Clear(); + ++NumCasts; + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonBig.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonBig.cs new file mode 100644 index 0000000000..198ab19fb0 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonBig.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A34OschonBig + +{ + + [ModuleInfo(CFCID = 962, PrimaryActorOID = 0x406F)] + public class A34Oschon : BossModule + { + public A34Oschon(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsRect(new(-0.015f, 749.996f), 20, 20)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy, true); + foreach (var s in Enemies(OID.OschonHelper)) + Arena.Actor(s, ArenaColor.Object, false); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonBigEnums.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonBigEnums.cs new file mode 100644 index 0000000000..6986ef12fb --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonBigEnums.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A34OschonBig + +{ + public enum OID : uint +{ + OschonBig = 0x406F, // R24.990, x1 + OschonSmall = 0x406D, // R8.000, x1 + OschonsAvatar = 0x406E, // R8.000, x4 + OschonHelper = 0x233C, // R0.500, x40, 523 type + PedestalOfPassage = 0x1EB91A, // R0.500, x1, EventObj type + ExitToTheOmphalos = 0x1EB91E, // R0.500, x1, EventObj type + Actor1e8fb8 = 0x1E8FB8, // R2.000, x1, EventObj type + Actor1e8f2f = 0x1E8F2F, // R0.500, x1, EventObj type + Unknown = 0x400E, // R0.500, spawn during fight +}; + public enum AID : uint +{ + _AutoAttack_ = 35907, // OschonBig->player, no cast, single-target + PitonPull1Visual = 35241, // OschonBig->self, 8.0s cast, single-target // NW and ES Visual + PitonPull3Visual2 = 35242, // OschonBig->self, 8.0s cast, single-target // NE and SW Visual + PitonPullAOE = 35243, // OschonHelper->location, 8.5s cast, range 22 circle // Massive AOEs + + WeaponskillAOE = 35248, // OschonHelper->location, 2.0s cast, range 6 circle + + AltitudeVisual = 35247, // OschonBig->location, 6.0s cast, single-target // Visual + AltitudeAOE = 35249, // OschonHelper->location, 7.0s cast, range 6 circle // Multiple AOEs + + //For the life of me couldnt figure out why this would not appear + FlintedFoehnVisual = 35236, // OschonBig->self, 4.5s cast, single-target // Visual + FlintedFoehnStack = 35238, // OschonHelper->players, no cast, range 8 circle // Multihit party stack + + WanderingShotVisual = 36087, // OschonBig->self, 7.0s cast, range 40 width 40 rect + WanderingShot2 = 36086, // OschonBig->self, 7.0s cast, range 40 width 40 rect + + GreatWhirlwindAOE = 35246, // OschonHelper->location, 3.6s cast, range 23 circle // Massive AOE + + TheArrowVisual = 35228, // OschonBig->self, 6.0s cast, single-target + TheArrowTankbuster = 35230, // OschonHelper->player, 7.0s cast, range 10 circle // Tankbuster + + + //Not finished + ArrowTrailVisual = 35250, // OschonBig->self, 3.0s cast, single-target + ArrowTrailAOE = 35252, // OschonHelper->self, no cast, range 10 width 10 rect // Arrows will travel down several columns in the arena, telegraphed by red areas + ArrowTrailRectAOE = 35251, // OschonHelper->self, 2.0s cast, range 40 width 10 rect // telegraph for ArrowTrailAOE + + DownhillVisual = 35232, // OschonBig->self, 3.0s cast, single-target + DownhillSmallAOE = 35909, // OschonHelper->location, 3.0s cast, range 6 circle + DownhillBigAOE = 35234, // OschonHelper->location, 14.0s cast, range 8 circle + + WanderingVolley = 35245, // OschonBig->self, 10.0s cast, range 40 width 40 rect + WanderingVolley2 = 35244, // OschonBig->self, 10.0s cast, range 40 width 40 rect + + +}; + + public enum SID : uint +{ + VulnerabilityUp = 1789, // OschonHelper->player, extra=0x1/0x2/0x3 + Weakness = 43, // none->player, extra=0x0 + Transcendent = 418, // none->player, extra=0x0 + SustainedDamage = 2935, // OschonHelper->player, extra=0x0 + +}; + + public enum IconID : uint +{ + FlintedFoehnMarker = 316, // player + Arrowbuster = 500, // player +}; + +} diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonStates.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonStates.cs new file mode 100644 index 0000000000..c083f053ab --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/A34OschonStates.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A34OschonBig + +{ + class A34OschonStates : StateMachineBuilder + { + public A34OschonStates(BossModule module) : base(module) + { + SimplePhase(0, id => { SimpleState(id, 10000, "Enrage"); }, "Single phase") + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + //.ActivateOnEnter() + //.ActivateOnEnter() // I beleive this is redundant + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => Module.PrimaryActor.IsDestroyed || Module.PrimaryActor.HP.Cur <= 1 && !Module.PrimaryActor.IsTargetable; + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigArrowExaflare.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigArrowExaflare.cs new file mode 100644 index 0000000000..8f79ff0cf6 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigArrowExaflare.cs @@ -0,0 +1,3 @@ +//namespace BossMod.Endwalker.Alliance.A34Oschon +// class ArrowTrail : Components.Exaflare +// i give up lol diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigBigAOEs.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigBigAOEs.cs new file mode 100644 index 0000000000..4c8feff561 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigBigAOEs.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A34OschonBig + +{ + class PitonPullAOE : Components.LocationTargetedAOEs + { + public PitonPullAOE() : base(ActionID.MakeSpell(AID.PitonPullAOE), 22) { } + } + class WeaponskillAOE : Components.LocationTargetedAOEs + { + public WeaponskillAOE() : base(ActionID.MakeSpell(AID.WeaponskillAOE), 6) { } + } + class AltitudeAOE : Components.LocationTargetedAOEs + { + public AltitudeAOE() : base(ActionID.MakeSpell(AID.AltitudeAOE), 6) { } + } + class GreatWhirlwindAOE : Components.LocationTargetedAOEs + { + public GreatWhirlwindAOE() : base(ActionID.MakeSpell(AID.GreatWhirlwindAOE), 23) { } + } + class DownhillSmallAOE : Components.LocationTargetedAOEs + { + public DownhillSmallAOE() : base(ActionID.MakeSpell(AID.DownhillSmallAOE), 6) { } + } + class DownhillBigAOE : Components.LocationTargetedAOEs + { + public DownhillBigAOE() : base(ActionID.MakeSpell(AID.DownhillBigAOE), 8) { } + } + + class ArrowTrailAOE : Components.SelfTargetedAOEs + { + public ArrowTrailAOE() : base(ActionID.MakeSpell(AID.ArrowTrailAOE), new AOEShapeRect(10f, 5f)) { } + } + + class ArrowTrailRectAOE : Components.SelfTargetedAOEs + { + public ArrowTrailRectAOE() : base(ActionID.MakeSpell(AID.ArrowTrailRectAOE), new AOEShapeRect(40f, 5f)) { } + } + + class WanderingVolley : Components.Knockback + { + private List _sources = new(); + private static AOEShapeCone _shape = new(30, 90.Degrees()); + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => _sources; + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.WanderingVolley or AID.WanderingVolley2) + { + _sources.Clear(); + // charge always happens through center, so create two sources with origin at center looking orthogonally + _sources.Add(new(module.Bounds.Center, 12, spell.NPCFinishAt, _shape, spell.Rotation + 90.Degrees(), Kind.DirForward)); + _sources.Add(new(module.Bounds.Center, 12, spell.NPCFinishAt, _shape, spell.Rotation - 90.Degrees(), Kind.DirForward)); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.WanderingVolley or AID.WanderingVolley2) + { + _sources.Clear(); + ++NumCasts; + } + } + } + class WanderingVolleyAOE : Components.GenericAOEs + { + private List _aoes = new(); + + private static AOEShapeRect _shape = new(40, 5, 40); + + 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.WanderingVolley or AID.WanderingVolley2) + _aoes.Add(new(_shape, caster.Position, spell.Rotation, spell.NPCFinishAt)); + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.WanderingVolley or AID.WanderingVolley2) + { + _aoes.Clear(); + ++NumCasts; + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigFlintedFoehn.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigFlintedFoehn.cs new file mode 100644 index 0000000000..f3772ee576 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigFlintedFoehn.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A34OschonBig +{ + + class BigFlintedFoehn : Components.UniformStackSpread + { + public int NumCasts { get; private set; } + + public BigFlintedFoehn() : base(6, 0, 6) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.FlintedFoehnStack && module.WorldState.Actors.Find(spell.TargetID) is var target && target != null) + AddStack(target); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.FlintedFoehnStack) + ++NumCasts; + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigTankbusters.cs b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigTankbusters.cs new file mode 100644 index 0000000000..5d1273fe67 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A34OschonBig/OschonBigTankbusters.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A34OschonBig + +{ + class TheArrowTankbuster : Components.BaitAwayCast + { + public TheArrowTankbuster() : base(ActionID.MakeSpell(AID.TheArrowTankbuster), new AOEShapeCircle(10), true) { } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35Eulogia.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35Eulogia.cs new file mode 100644 index 0000000000..1983d0e092 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35Eulogia.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class SunbeamSelf : Components.BaitAwayCast + { + public SunbeamSelf() : base(ActionID.MakeSpell(AID.SunbeamTankBuster), new AOEShapeCircle(6), true) { } + } + class DestructiveBoltStack : Components.UniformStackSpread + { + public DestructiveBoltStack() : base(6,0) { } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Stackmarker) + AddStack(actor, module.WorldState.CurrentTime.AddSeconds(6.9f)); + } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.DestructiveBoltStack) + Stacks.Clear(); + } + } + + [ModuleInfo(CFCID = 962, PrimaryActorOID = 0x4086)] + public class A35Eulogia : BossModule + { + public A35Eulogia(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(944.976f, -945.006f), 30f)) { } + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy, true); + foreach (var s in Enemies(OID.Helper)) + Arena.Actor(s, ArenaColor.Object, true); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35EulogiaEnums.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35EulogiaEnums.cs new file mode 100644 index 0000000000..c5b88d4b9c --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35EulogiaEnums.cs @@ -0,0 +1,187 @@ +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + + public enum OID : uint + { + Eulogia = 0x4086, // R11.000, x1 + Helper = 0x233C, // R0.500, x14, 523 type + Avatar = 0x4087, // R11.000, spawn during fight + Trident = 0x408E, // R3.000, spawn during fight + FistOfWrath = 0x408C, // R3.600, spawn during fight + FistOfJudgment = 0x408D, // R3.600, spawn during fight + WardensFlame = 0x408B, // R2.400, spawn during fight + HydrostasisQuick = 0x408A, // R1.000, spawn during fight + FistPortalHelper = 0x408F, // R1.000, spawn during fight + _Gen_Actor4028 = 0x4028, // R1.000, spawn during fight + MatronsBreathHelper = 0x4090, // R1.000, spawn during fight, related somehow to MatronsBreath + GoldSafeZone = 0x1EB846, // R0.500, EventObj type, spawn during fight + BlueSafeZone = 0x1EB845, // R0.500, EventObj type, spawn during fight + GoldTower = 0x1EB844, // R0.500, EventObj type, spawn during fight + BlueTower = 0x1EB843, // R0.500, EventObj type, spawn during fight + Exit = 0x1E850B, // R0.500, x1, EventObj type + Graha = 0xFEA51, // R0.500-8.000, EventNpc type, spawn during fight lol + }; + + public enum AID : uint + { + _Weaponskill_Attack1 = 35326, // Eulogia->self, no cast, single-target + _Weaponskill_Attack2 = 35327, // Helper->player, no cast, single-target + DawnOfTime = 35331, // Eulogia->self, 5.0s cast, range 70 circle + Teleport = 35330, // Eulogia->location, no cast, single-target // 99% sure this is the teleport + _Ability_2 = 35336, // Eulogia->self, no cast, single-target + _Ability_3 = 35337, // Eulogia->self, no cast, single-target + _Ability_4 = 36066, // Eulogia->self, no cast, single-target + _Weaponskill_1 = 35360, // Avatar->self, 0.5s cast, single-target + _Weaponskill_2 = 35361, // Avatar->self, 0.5s cast, single-target + _Weaponskill_3 = 35358, // Avatar->self, 0.5s cast, single-target + _Weaponskill_4 = 35359, // Avatar->self, 0.5s cast, single-target + _Weaponskill_5 = 35357, // Avatar->self, 0.5s cast, single-target + _Weaponskill_6 = 35397, // FistOfWrath->self, no cast, single-target + _Weaponskill_7 = 35398, // FistOfJudgment->self, no cast, single-target + + FirstFormRight = 35338, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceFirstRight + FirstFormLeft = 35341, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceFirstLeft + FirstFormAOE = 35344, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceFirstAOE + SecondFormRight = 35339, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceSecondRight + SecondFormLeft = 35342, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceSecondLeft + SecondFormAOE = 35345, // Eulogia->self, 7.0s cast, single-target (inferred, no log) // visual for QuintessenceSecondAOE + ThirdFormRight = 35340, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceThirdRight + ThirdFormLeft = 35343, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceThirdLeft + ThirdFormAOE = 35346, // Eulogia->self, 7.0s cast, single-target // visual for QuintessenceThirdAOE + QuintessenceSetup = 35350, // Eulogia->self, 4.0s cast, single-target // teleport + Quintessence1stSpot = 35351, // Eulogia->location, no cast, single-target, // casting location + Quintessence2ndSpot = 35352, // Eulogia->location, no cast, single-target, // casting location + Quintessence3rdSpot = 35353, // Eulogia->location, no cast, single-target, // casting location + QuintessenceFirstRight = 35354, // Helper->self, 4.8s cast, range 50 180-degree cone + QuintessenceFirstLeft = 35355, // Helper->self, 4.8s cast, range 50 180-degree cone + QuintessenceFirstAOE = 35356, // Helper->self, 4.8s cast, range 8 50 donut + QuintessenceSecondRight = 36069, // Helper->self, 8.3s cast, range 50 180-degree cone + QuintessenceSecondLeft = 36070, // Helper->self, 8.3s cast, range 50 180-degree cone + QuintessenceSecondAOE = 36071, // Helper->self, 8.3s cast, range 8 50 donut (inferred, no log) + QuintessenceThirdRight = 36072, // Helper->self, 11.9s cast, range 50 180-degree cone + QuintessenceThirdLeft = 36073, // Helper->self, 11.9s cast, range 50 180-degree cone + QuintessenceThirdAOE = 36074, // Helper->self, 11.9s cast, range 8 50 donut + + SunbeamSelf = 35328, // Eulogia->self, 5.0s cast, single-target visual (tankbuster) + SunbeamTankBuster = 35329, // Helper->players, 5.0s cast, range 6 circle, tankbusters + + TheWhorl = 35375, // Eulogia->self, 7.0s cast, range 40 circle // raidwide, adds an instant kill AOE around arena + + LovesLight = 35376, // Eulogia->self, 4.0s cast, single-target + FullBright = 35377, // Eulogia->self, 3.0s cast, single-target + FirstBlush1 = 35379, // Helper->self, 10.3s cast, range 80 width 25 rect + FirstBlush2 = 35380, // Helper->self, 12.3s cast, range 80 width 25 rect + FirstBlush3 = 35381, // Helper->self, 14.3s cast, range 80 width 25 rect + FirstBlush4 = 35382, // Helper->self, 16.3s cast, range 80 width 25 rect + + SolarFans = 35387, // Eulogia->self, 3.0s cast, single-target + SolarFansAOE = 35388, // WardensFlame->location, 4.0s cast, width 10 rect charge + RadiantRhythm = 35389, // Eulogia->self, no cast, range 100 circle + TeleportFlame = 35390, // WardensFlame->location, no cast, single-target + RadiantFlight = 35391, // Helper->self, 0.5s cast, range 30 ?-degree cone + RadiantFlourish = 35393, // WardensFlame->self, 3.0s cast, range 25 circle + RadiantFinish = 35392, // Eulogia->self, 3.0s cast, single-target + + TimeAndTide = 35378, // Eulogia->self, 6.0s cast, single-target, single-target, visual (speed up time) + Hydrostasis = 35383, // Eulogia->self, 4.0s cast, single-target, 4.0s cast, single-target, visual (knockbacks) + HydrostasisAOE1 = 35384, // Helper->self, 7.0s cast, range 72 circle + HydrostasisAOE2 = 35385, // Helper->self, 10.0s cast, range 72 circle + HydrostasisAOE3 = 35386, // Helper->self, 13.0s cast, range 72 circle + + DestructiveBolt = 36076, // Eulogia->self, 6.0s cast, single-target, tankbuster visual + DestructiveBoltStack = 36093, // Helper->players, 7.0s cast, range 6 circle, stack marker + + HieroglyphikaVisual = 35395, // Eulogia->self, 5.0s cast, single-target + HieroglyphikaAOE = 35396, // Helper->self, 3.0s cast, range 12 width 12 rect + HandOfTheDestroyerWrath = 35399, // Eulogia->self, 7.5s cast, single-target + HandOfTheDestroyerJudgment = 35400, // Eulogia->self, 7.5s cast, single-target + HandOfTheDestroyerWrathAOE = 35401, // FistOfWrath->self, 8.0s cast, range 90 width 40 rect + HandOfTheDestroyerJudgmentAOE = 35402, // FistOfJudgment->self, 8.0s cast, range 90 width 40 rect + + MatronsBreath = 35403, // Eulogia->self, 3.0s cast, single-target, visual (color towers) + Giltblossoms = 35405, // Helper->self, no cast, range 100 circle, blue tower explosion + Blueblossoms = 35404, // Helper->self, no cast, range 100 circle, gold tower explosion + + TorrentialTridents = 35406, // Eulogia->self, 2.0s cast, single-target + Landing = 35407, // Trident->self, no cast, range 80 circle + LightningBolt = 35408, // Trident->self, 5.0s cast, range 18 circle + ByregotStrikeJump = 35410, // Eulogia->location, 6.0s cast, range 8 circle + ByregotStrikeKnockback = 35411, // Helper->self, 6.7s cast, range 45 circle knockback 18 + ByregotStrikeCone = 35412, // Helper->self, 6.7s cast, range 90 30-degree cone + + ThousandfoldThrustFirst1 = 35415, // Eulogia->self, 5.0s cast, single-target + ThousandfoldThrustFirst2 = 35416, // Eulogia->self, 5.0s cast, single-target + ThousandfoldThrustAOEFirst = 35417, // Helper->self, 6.3s cast, range 60 180-degree cone + ThousandfoldThrustAOERest = 35418, // Helper->self, no cast, range 60 180-degree cone + + AsAboveSoBelow = 35419, // Eulogia->self, 5.0s cast, range 40 circle + AsAboveSoBelowAlt = 35420, // Eulogia->self, 5.0s cast, range 40 circle + + ClimbingShot1 = 36106, // Eulogia->self, 8.0s cast, range 40 circle + ClimbingShot2 = 35431, // Eulogia->self, no cast, range 40 circle + ClimbingShot3 = 35429, // Eulogia->self, no cast, range 40 circle + ClimbingShot4 = 36107, // Eulogia->self, 8.0s cast, range 40 circle + ClimbingShot5 = 35430, // Eulogia->self, no cast, range 40 circle + OnceBurnedFake = 36094, // Helper->self, 9.0s cast, range 6 circle + EverFireFake = 36095, // Helper->self, 9.0s cast, range 6 circle + OnceBurnedFirst = 36096, // Helper->self, 9.0s cast, range 6 circle + EverfireFirst = 36097, // Helper->self, 9.0s cast, range 6 circle + OnceBurnedRest = 36098, // Helper->self, no cast, range 6 circle + EverfireRest = 36099, // Helper->self, no cast, range 6 circle + + + //SoaringMinuet = 35220, // Inferred from splatoon, no log + SoaringMinuet = 35433, // Eulogia->self, 7.0s cast, range 40 ?-degree cone + //SoaringMinuet = 36110, // Inferred from splatoon, no log + + TheBuildersArt = 35362, // Helper->self, no cast, range 80 circle, raidwide + TheDestroyersMight = 35363, // Helper->self, no cast, range 80 circle, raidwide + TheWardensRadiance = 35364, // Helper->self, no cast, range 80 circle, raidwide + TheTradersEquity = 35365, // Helper->self, no cast, range 80 circle, raidwide + TheMatronsPlenty = 35366, // Helper->self, no cast, range 80 circle, raidwide + TheKeepersGravity = 35367, // Helper->self, no cast, range 80 circle, raidwide + TheFurysAmbition = 35368, // Helper->self, no cast, range 80 circle, raidwide + TheLoversDevotion = 35369, // Helper->self, no cast, range 80 circle, raidwide + TheScholarsWisdom = 35370, // Helper->self, no cast, range 80 circle, raidwide + TheSpinnersCunning = 35371, // Helper->self, no cast, range 80 circle, raidwide + TheNavigatorsCommand = 35373, // Helper->self, no cast, range 80 circle, raidwide + TheWanderersWhimsy = 35374, // Helper->self, no cast, range 80 circle, raidwide + EudaimonEorzea1 = 35372, // Eulogia->self, 22.2s cast, single-target + EudaimonEorzea2 = 36091, // Helper->self, 24.9s cast, range 40 circle + }; + + public enum SID : uint + { + VulnerabilityUp = 1789, // Helper/FistOfJudgment/Trident->player, extra=0x1/0x2 + Weakness = 43, // none->player, extra=0x0 + Transcendent = 418, // none->player, extra=0x0 + Glow = 2056, // WardensFlame->WardensFlame/UnknownEnemy2, extra=0x195/0x29E/0x29F/0x2A0/0x2A1/0x2A2/0x2A3/0x2A4/0x2A5/0x2A6/0x2A7/0x2A8/0x2A9 + Inscribed = 3732, // none->player, extra=0x0 + Bleeding = 3077, // none->player, extra=0x0 + Bleeding2 = 3078, // none->player, extra=0x0 + Bind = 2518, // none->player, extra=0x0 + BloomingGold = 3460, // none->player, extra=0x0 + BloomingBlue = 3459, // none->player, extra=0x0 + BrinkOfDeath = 44, // none->player, extra=0x0 + }; + + public enum TetherID : uint + { + HydrostasisQuick = 219, // HydrostasisQuick->Eulogia + }; + + public enum IconID : uint + { + Stackmarker = 317, // player + Sunbeam = 344, // player // Used in GolbezEX for VoidMeteor and Endsinger for Hubris + FistOfWrath = 487, // FistPortalHelper // Might be FistOfJudgment + FistOfJudgment = 490, // FistPortalHelper // Might be FistOfWrath + ThousandfoldThrust1 = 388, // Eulogia + ThousandfoldThrust2 = 389, // Eulogia + Order1 = 398, // MatronsBreathHelper + Order2 = 399, // MatronsBreathHelper + Order3 = 400, // MatronsBreathHelper + Order4 = 401, // MatronsBreathHelper + }; + +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35EulogiaStates.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35EulogiaStates.cs new file mode 100644 index 0000000000..79983091e4 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/A35EulogiaStates.cs @@ -0,0 +1,64 @@ +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class SoaringMinuet : Components.SelfTargetedAOEs + { + public SoaringMinuet() : base(ActionID.MakeSpell(AID.SoaringMinuet), new AOEShapeCone(40, 135.Degrees())) { } + } + class LightningBolt : Components.SelfTargetedAOEs + { + public LightningBolt() : base(ActionID.MakeSpell(AID.LightningBolt), new AOEShapeCircle(18)) { } + } + ///////// + class FirstBlush1 : Components.SelfTargetedAOEs + { + public FirstBlush1() : base(ActionID.MakeSpell(AID.FirstBlush1), new AOEShapeRect(120, 12.5f)) { } + } + class FirstBlush2 : Components.SelfTargetedAOEs + { + public FirstBlush2() : base(ActionID.MakeSpell(AID.FirstBlush2), new AOEShapeRect(120, 12.5f)) { } + } + class FirstBlush3 : Components.SelfTargetedAOEs + { + public FirstBlush3() : base(ActionID.MakeSpell(AID.FirstBlush3), new AOEShapeRect(120, 12.5f)) { } + } + class FirstBlush4 : Components.SelfTargetedAOEs + { + public FirstBlush4() : base(ActionID.MakeSpell(AID.FirstBlush4), new AOEShapeRect(120, 12.5f)) { } + } + ///////// + class ClimbingShotKnockback : Components.KnockbackFromCastTarget + { + public ClimbingShotKnockback() : base(ActionID.MakeSpell(AID.ClimbingShot1), 20) { } + } + + class A35EulogiaStates : StateMachineBuilder + { + public A35EulogiaStates(BossModule module) : base(module) + { + SimplePhase(0, id => { SimpleState(id, 10000, "Enrage"); }, "Single phase") + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => Module.PrimaryActor.IsDestroyed; + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaAboveBelow.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaAboveBelow.cs new file mode 100644 index 0000000000..8c80e98cea --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaAboveBelow.cs @@ -0,0 +1,53 @@ +namespace BossMod.Endwalker.Alliance.A35Eulogia + +{ + class AsAboveSoBelow : Components.Exaflare + { + public AsAboveSoBelow() : base(6) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.EverfireFirst or AID.OnceBurnedFirst) + { + var advance = 6 * spell.Rotation.ToDirection(); + // lines are offset by 6/18/30; outer have 1 explosion only, mid have 4 or 5, inner 5 + var numExplosions = (caster.Position - module.Bounds.Center).LengthSq() > 500 ? 1 : 5; + Lines.Add(new() { Next = caster.Position, Advance = advance, NextExplosion = spell.NPCFinishAt, TimeToMove = 1.5f, ExplosionsLeft = numExplosions, MaxShownExplosions = 5 }); + Lines.Add(new() { Next = caster.Position, Advance = -advance, NextExplosion = spell.NPCFinishAt, TimeToMove = 1.5f, ExplosionsLeft = numExplosions, MaxShownExplosions = 5 }); + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + switch ((AID)spell.Action.ID) + { + case AID.EverfireFirst: + case AID.OnceBurnedFirst: + var dir = caster.Rotation.ToDirection(); + Advance(module, caster.Position, dir); + Advance(module, caster.Position, -dir); + ++NumCasts; + break; + case AID.EverfireRest: + case AID.OnceBurnedRest: + Advance(module, caster.Position, caster.Rotation.ToDirection()); + ++NumCasts; + break; + } + } + + private void Advance(BossModule module, WPos position, WDir dir) + { + int index = Lines.FindIndex(item => item.Next.AlmostEqual(position, 1) && item.Advance.Dot(dir) > 5); + if (index == -1) + { + module.ReportError(this, $"Failed to find entry for {position} / {dir}"); + return; + } + + AdvanceLine(module, Lines[index], position); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaByregotStrike.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaByregotStrike.cs new file mode 100644 index 0000000000..8dddb9d2b6 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaByregotStrike.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class ByregotStrikeJump : Components.LocationTargetedAOEs + { + public ByregotStrikeJump() : base(ActionID.MakeSpell(AID.ByregotStrikeJump), 8) { } + } + + class ByregotStrikeKnockback : Components.KnockbackFromCastTarget + { + public ByregotStrikeKnockback() : base(ActionID.MakeSpell(AID.ByregotStrikeKnockback), 18) { } + } + + class ByregotStrikeCone : Components.GenericAOEs + { + private List _aoes = new(); + + private static AOEShapeCone _shape = new(90, 15.Degrees()); + + 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 == AID.ByregotStrikeKnockback) + for (int i = 0; i < 4; ++i) + _aoes.Add(new(_shape, caster.Position, spell.Rotation + i * 90.Degrees(), spell.NPCFinishAt)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.ByregotStrikeCone) + { + _aoes.Clear(); + ++NumCasts; + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHandOfTheDestroyer.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHandOfTheDestroyer.cs new file mode 100644 index 0000000000..1b8f3e6f9c --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHandOfTheDestroyer.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class HandOfTheDestroyer : Components.GenericAOEs + { + private List _aoes = new(); + + private static AOEShapeRect _shape = new(90, 20); + + 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.HandOfTheDestroyerWrathAOE or AID.HandOfTheDestroyerJudgmentAOE) + _aoes.Add(new(_shape, caster.Position, spell.Rotation, spell.NPCFinishAt)); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.HandOfTheDestroyerWrathAOE or AID.HandOfTheDestroyerJudgmentAOE) + { + _aoes.Clear(); + ++NumCasts; + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHieroglyphika.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHieroglyphika.cs new file mode 100644 index 0000000000..aba68afd8d --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHieroglyphika.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A35Eulogia + +{ + class Hieroglyphika : Components.GenericAOEs + { + private AOEShapeRect _shape = new(6, 6, 6); + private List _aoes = new(); + + public Hieroglyphika() : base(ActionID.MakeSpell(AID.HieroglyphikaAOE)) { } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(15); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.HieroglyphikaAOE) + _aoes.Add(new(_shape, caster.Position, caster.Rotation, spell.NPCFinishAt)); + } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.HieroglyphikaAOE) + { + ++NumCasts; + if (_aoes.Count > 0) + _aoes.RemoveAt(0); + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHydrostasis.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHydrostasis.cs new file mode 100644 index 0000000000..40fb37a00e --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaHydrostasis.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class Hydrostasis : Components.Knockback + { + private List _sources = new(); + + public bool Active => _sources.Count == 3 || NumCasts > 0; + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => Active ? _sources : Enumerable.Empty(); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.HydrostasisAOE1 or AID.HydrostasisAOE2 or AID.HydrostasisAOE3) + AddSource(caster.Position, spell.NPCFinishAt); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.HydrostasisAOE1 or AID.HydrostasisAOE2 or AID.HydrostasisAOE3) + { + ++NumCasts; + if (_sources.Count > 0) + _sources.RemoveAt(0); + } + } + + // public override void OnTethered(BossModule module, Actor source, ActorTetherInfo tether) + // { + // if (tether.ID == (uint)TetherID.HydrostasisQuick) + // AddSource(source.Position, module.WorldState.CurrentTime.AddSeconds(12)); + //} + + private void AddSource(WPos pos, DateTime activation) + { + _sources.Add(new(pos, 28, activation)); + _sources.SortBy(s => s.Activation); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaMatronsBreath.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaMatronsBreath.cs new file mode 100644 index 0000000000..2181e2d224 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaMatronsBreath.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class MatronsBreath : BossComponent + { + public int NumCasts { get; private set; } + private IReadOnlyList _blueSafe = ActorEnumeration.EmptyList; + private IReadOnlyList _goldSafe = ActorEnumeration.EmptyList; + private List _towers = new(); + + private static AOEShapeDonut _shape = new(8, 50); // TODO: verify safe zone radius + + public override void Init(BossModule module) + { + _blueSafe = module.Enemies(OID.BlueSafeZone); + _goldSafe = module.Enemies(OID.GoldSafeZone); + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (_shape.Check(actor.Position, NextSafeZone)) + hints.Add("Go to correct safe zone!"); + } + + public override void DrawArenaBackground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + _shape.Draw(arena, NextSafeZone); + } + + public override void OnActorCreated(BossModule module, Actor actor) + { + if ((OID)actor.OID is OID.BlueTower or OID.GoldTower) + _towers.Add(actor); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.Blueblossoms or AID.Giltblossoms) + { + ++NumCasts; + if (_towers.Count > 0) + _towers.RemoveAt(0); + } + } + + private Actor? NextSafeZone => _towers.Count == 0 ? null : (OID)_towers[0].OID == OID.BlueTower ? _blueSafe.FirstOrDefault() : _goldSafe.FirstOrDefault(); + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaQuintessence.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaQuintessence.cs new file mode 100644 index 0000000000..c091144253 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaQuintessence.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BossMod.Components; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class Quintessence : Components.GenericAOEs + { + private List _aoes = new(); + private Dictionary _formToEffectTiming = new(); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(3); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + // Map initial forms to their visual effects and store the timing + switch ((AID)spell.Action.ID) + { + case AID.FirstFormAOE: + case AID.SecondFormAOE: + case AID.ThirdFormAOE: + case AID.FirstFormRight: + case AID.FirstFormLeft: + case AID.SecondFormRight: + case AID.SecondFormLeft: + case AID.ThirdFormRight: + case AID.ThirdFormLeft: + _formToEffectTiming[(AID)spell.Action.ID] = spell.NPCFinishAt; + break; + } + + AOEShape? shape = DetermineShape((AID)spell.Action.ID); + DateTime effectTiming = _formToEffectTiming.GetValueOrDefault((AID)spell.Action.ID, spell.NPCFinishAt); // Use specific timing if available, else fallback to current spell's timing + + if (shape != null) + { + _aoes.Add(new AOEInstance(shape, caster.Position, spell.Rotation, effectTiming.AddSeconds(26.25f))); + _aoes.SortBy(aoe => aoe.Activation); + } + } + + private AOEShape? DetermineShape(AID actionID) + { + return actionID switch + { + AID.QuintessenceFirstAOE or AID.QuintessenceSecondAOE or AID.QuintessenceThirdAOE => new AOEShapeDonut(10, 60), + AID.QuintessenceFirstRight or AID.QuintessenceFirstLeft or + AID.QuintessenceSecondRight or AID.QuintessenceSecondLeft or + AID.QuintessenceThirdRight or AID.QuintessenceThirdLeft => new AOEShapeCone(50, 90.Degrees()), + _ => null + }; + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.QuintessenceFirstRight or AID.QuintessenceFirstLeft or AID.QuintessenceFirstAOE or AID.QuintessenceSecondRight or AID.QuintessenceSecondLeft or AID.QuintessenceSecondAOE or AID.QuintessenceThirdRight or AID.QuintessenceThirdLeft or AID.QuintessenceThirdAOE) + { + ++NumCasts; + if (_aoes.Count > 0) + _aoes.RemoveAt(0); + } + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.QuintessenceThirdRight or AID.QuintessenceThirdLeft or AID.QuintessenceThirdAOE) + { + ++NumCasts; + _aoes.Clear(); + } + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaSolarFans.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaSolarFans.cs new file mode 100644 index 0000000000..015ae4e7f9 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaSolarFans.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class SolarFans : BossComponent + { + private List<(Actor, AOEShapeRect)> _start = new(); + private bool _rhythmActive; + private List _finish = new(); + + private static float _flightRadiusInner = 20; // TODO: check whether this is correct + private static float _flightRadiusOuter = 40; + private static AOEShapeCircle _aoeFinish = new(25); + + public override void DrawArenaBackground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + foreach (var (fan, shape) in _start) + { + shape.Draw(arena, fan); + } + + if (_rhythmActive) + { + foreach (var flame in module.Enemies(OID.WardensFlame)) + { + var dir = Angle.FromDirection(flame.Position - module.Bounds.Center) + 45.Degrees(); + arena.ZoneCone(module.Bounds.Center, _flightRadiusInner, _flightRadiusOuter, dir, 45.Degrees(), ArenaColor.AOE); + } + } + + foreach (var finish in _finish) + { + _aoeFinish.Draw(arena, finish); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + switch ((AID)spell.Action.ID) + { + case AID.SolarFansAOE: + AOEShapeRect shape = new(0, 5); + shape.SetEndPointFromCastLocation(caster); + _start.Add((caster, shape)); + break; + case AID.RadiantFlourish: + _rhythmActive = false; + _finish.Add(caster); + break; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + switch ((AID)spell.Action.ID) + { + case AID.SolarFansAOE: + _start.RemoveAll(e => e.Item1 == caster); + _rhythmActive = true; + break; + case AID.RadiantFlourish: + _finish.Remove(caster); + break; + } + } + + private bool ActorInRhythmAOE(WPos center, Actor flame, Actor player) + { + return !player.Position.InCircle(center, _flightRadiusInner) && player.Position.InCone(center, Angle.FromDirection(flame.Position - center) + 45.Degrees(), 45.Degrees()); + } + } +} diff --git a/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaThousandfoldThrust.cs b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaThousandfoldThrust.cs new file mode 100644 index 0000000000..4b371e3783 --- /dev/null +++ b/BossMod/Modules/Endwalker/Alliance/A35Eulogia/EulogiaThousandfoldThrust.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace BossMod.Endwalker.Alliance.A35Eulogia +{ + class ThousandfoldThrust : Components.GenericAOEs + { + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_aoe != null) + yield return _aoe.Value; + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.ThousandfoldThrustAOEFirst) + _aoe = new(new AOEShapeCone(60, 90.Degrees()), caster.Position, spell.Rotation, spell.NPCFinishAt); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ThousandfoldThrustAOEFirst or AID.ThousandfoldThrustAOERest) + ++NumCasts; + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.ThousandfoldThrustAOEFirst or AID.ThousandfoldThrustAOERest) + _aoe = null; + } + } +}