diff --git a/BossMod/Components/UnavoidableDamage.cs b/BossMod/Components/UnavoidableDamage.cs index 91c2807ad4..93e94e9cab 100644 --- a/BossMod/Components/UnavoidableDamage.cs +++ b/BossMod/Components/UnavoidableDamage.cs @@ -1,4 +1,6 @@ -namespace BossMod.Components +using System; + +namespace BossMod.Components { // generic unavoidable raidwide cast public class RaidwideCast : CastHint @@ -12,6 +14,48 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR } } + // generic unavoidable raidwide cast after NPC yell, typically used at the end of boss "limit break" phases + public class RaidwideAfterNPCYell : CastHint + { + public uint NPCYellID; + public float Delay; //delay from NPCyell for raidwide to cast event + private bool casting; + private DateTime _activation; + + public RaidwideAfterNPCYell(ActionID aid, uint nPCYellid, float delay, string hint = "Raidwide") : base(aid, hint) + { + NPCYellID = nPCYellid; + Delay = delay; + } + + public override void OnActorNpcYell(BossModule module, Actor actor, ushort id) + { + if (id == NPCYellID) + { + casting = true; + _activation = module.WorldState.CurrentTime; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (spell.Action == WatchedAction) + casting = false; + } + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + if (casting) + hints.PredictedDamage.Add((module.Raid.WithSlot().Mask(), _activation.AddSeconds(Delay))); + } + + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (casting && Hint.Length > 0) + hints.Add(Hint); + } + } + // generic unavoidable single-target damage cast (typically tankbuster, but not necessary) public class SingleTargetCast : CastHint { diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D130AlbusGriffin.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D130AlbusGriffin.cs new file mode 100644 index 0000000000..2ddec3d7fe --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D130AlbusGriffin.cs @@ -0,0 +1,51 @@ +// CONTRIB: made by malediktus, not checked +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13LapisManalis.D130AlbusGriffin +{ + public enum OID : uint + { + Boss = 0x3E9F, //R=4.6 + } + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + WindsOfWinter = 32785, // Boss->self, 5,0s cast, range 40 circle + Freefall = 32786, // Boss->location, 3,5s cast, range 8 circle + GoldenTalons = 32787, // Boss->self, 4,5s cast, range 8 90-degree cone + }; + + class WindsOfWinter : Components.RaidwideCast + { + public WindsOfWinter() : base(ActionID.MakeSpell(AID.WindsOfWinter), "Stun Albus Griffin, Raidwide") { } + } + + class GoldenTalons : Components.SelfTargetedAOEs + { + public GoldenTalons() : base(ActionID.MakeSpell(AID.GoldenTalons), new AOEShapeCone(8, 45.Degrees())) { } + } + + class Freefall : Components.LocationTargetedAOEs + { + public Freefall() : base(ActionID.MakeSpell(AID.Freefall), 8) { } + } + + class D130AlbusGriffinStates : StateMachineBuilder + { + public D130AlbusGriffinStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead); + } + } + + [ModuleInfo(CFCID = 896, NameID = 12245)] + public class D130AlbusGriffin : BossModule + { + public D130AlbusGriffin(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsRect(new(47, -570.5f), 8.5f, 11.5f)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D130CaladriusMaturus.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D130CaladriusMaturus.cs new file mode 100644 index 0000000000..758ca47650 --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D130CaladriusMaturus.cs @@ -0,0 +1,46 @@ +// CONTRIB: made by malediktus, not checked +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13LapisManalis.D130CaladriusMaturus +{ + public enum OID : uint + { + Boss = 0x3D56, //R=3.96 + Caladrius = 0x3CE2, //R=1.8 + } + + public enum AID : uint + { + AutoAttack = 872, // Caladrius/Boss->player, no cast, single-target + TransonicBlast = 32535, // Caladrius->self, 4,0s cast, range 9 90-degree cone + }; + + class TransonicBlast : Components.SelfTargetedAOEs + { + public TransonicBlast() : base(ActionID.MakeSpell(AID.TransonicBlast), new AOEShapeCone(9, 45.Degrees())) { } + } + + class D130CaladriusMaturusStates : StateMachineBuilder + { + public D130CaladriusMaturusStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.Caladrius).All(e => e.IsDead); + } + } + + [ModuleInfo(CFCID = 896, NameID = 12078)] + public class D130CaladriusMaturus : BossModule + { + public D130CaladriusMaturus(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsRect(new(47, -570.5f), 8.5f, 11.5f)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy); + foreach (var s in Enemies(OID.Caladrius)) + Arena.Actor(s, ArenaColor.Enemy); + } + + } +} diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D131Albion.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D131Albion.cs new file mode 100644 index 0000000000..ed3ba2051e --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D131Albion.cs @@ -0,0 +1,367 @@ +// CONTRIB: made by malediktus, not checked +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13LapisManalis.D131Albion +{ + public enum OID : uint + { + Boss = 0x3CFE, //R=4.6 + WildBeasts = 0x3D03, //R=0.5 + Helper = 0x233C, + WildBeasts1 = 0x3CFF, // R1,320 + WildBeasts2 = 0x3D00, // R1,700 + WildBeasts3 = 0x3D02, // R4,000 + WildBeasts4 = 0x3D01, // R2,850 + IcyCrystal = 0x3D04, // R2,000 + } + + public enum AID : uint + { + AutoAttack = 872, // Boss->player, no cast, single-target + Teleport = 32812, // Boss->location, no cast, single-target, boss teleports mid + CallOfTheMountain = 31356, // Boss->self, 3,0s cast, single-target, boss calls wild beasts + WildlifeCrossing = 31357, // WildBeasts->self, no cast, range 7 width 10 rect + AlbionsEmbrace = 31365, // Boss->player, 5,0s cast, single-target + RightSlam = 32813, // Boss->self, 5,0s cast, range 80 width 20 rect + LeftSlam = 32814, // Boss->self, 5,0s cast, range 80 width 20 rect + KnockOnIce = 31358, // Boss->self, 4,0s cast, single-target + KnockOnIce2 = 31359, // Helper->self, 6,0s cast, range 5 circle + Icebreaker = 31361, // Boss->3D04, 5,0s cast, range 17 circle + IcyThroes = 31362, // Boss->self, no cast, single-target + IcyThroes2 = 32783, // Helper->self, 5,0s cast, range 6 circle + IcyThroes3 = 31363, // Helper->player, 5,0s cast, range 6 circle + IcyThroes4 = 32697, // Helper->self, 5,0s cast, range 6 circle + RoarOfAlbion = 31364, // Boss->self, 7,0s cast, range 60 circle + }; + + public enum IconID : uint + { + Tankbuster = 218, // player + Target = 210, // IceCrystal + Spreadmarker = 139, // player + }; + + class WildlifeCrossing : Components.GenericAOEs + { + private static readonly AOEShapeRect rect = new(20, 5, 20); + private int stampede1counter; + private int stampede2counter; + private bool active1; + private bool active2; + private Angle _rotation1; + private Angle _rotation2; + private DateTime _reset1; + private DateTime _reset2; + private List beasts1 = new(); + private List beasts2 = new(); + private WPos stampede1 = default; + private WPos stampede2 = default; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (active1 && beasts1.Count > 0) + yield return new(new AOEShapeRect((beasts1.First().Position - beasts1.Last().Position).Length() + 30, 5), new (beasts1.Last().Position.X, stampede1.Z), _rotation1); + if (active2 && beasts2.Count > 0) + yield return new(new AOEShapeRect((beasts2.First().Position - beasts2.Last().Position).Length() + 30, 5), new (beasts2.Last().Position.X, stampede2.Z), _rotation2); + if (active1 && beasts1.Count == 0) + yield return new(rect, stampede1, 90.Degrees()); + if (active2 && beasts2.Count == 0) + yield return new(rect, stampede2, 90.Degrees()); + } + + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + var newstampede = stampede1 == default; + if (state == 0x00020001) + { + if (index == 0x21) + if (newstampede) + { + active1 = true; + _rotation1 = 90.Degrees(); + stampede1 = new(4, -759); + } + else + { + active2 = true; + _rotation2 = 90.Degrees(); + stampede2 = new(4, -759); + } + if (index == 0x25) + if (newstampede) + { + active1 = true; + _rotation1 = -90.Degrees(); + stampede1 = new(44, -759); + } + else + { + active2 = true; + _rotation2 = -90.Degrees(); + stampede2 = new(44, -759); + } + if (index == 0x22) + if (newstampede) + { + active1 = true; + _rotation1 = 90.Degrees(); + stampede1 = new(4, -749); + } + else + { + active2 = true; + _rotation2 = 90.Degrees(); + stampede2 = new(4, -749); + } + if (index == 0x26) + if (newstampede) + { + active1 = true; + _rotation1 = -90.Degrees(); + stampede1 = new(44, -749); + } + else + { + active2 = true; + _rotation2 = -90.Degrees(); + stampede2 = new(44, -749); + } + if (index == 0x23) + if (newstampede) + { + active1 = true; + _rotation1 = 90.Degrees(); + stampede1 = new(4, -739); + } + else + { + active2 = true; + _rotation2 = 90.Degrees(); + stampede2 = new(4, -739); + } + if (index == 0x27) + if (newstampede) + { + active1 = true; + _rotation1 = -90.Degrees(); + stampede1 = new(44, -739); + } + else + { + active2 = true; + _rotation2 = -90.Degrees(); + stampede2 = new(44, -739); + } + if (index == 0x24) + if (newstampede) + { + active1 = true; + _rotation1 = 90.Degrees(); + stampede1 = new(4, -729); + } + else + { + active2 = true; + _rotation2 = 90.Degrees(); + stampede2 = new(4, -729); + } + if (index == 0x28) + if (newstampede) + { + active1 = true; + _rotation1 = -90.Degrees(); + stampede1 = new(44, -729); + } + else + { + active2 = true; + _rotation2 = -90.Degrees(); + stampede2 = new(44, -729); + } + } + } + + public override void Update(BossModule module) + { + foreach (var b in module.Enemies(OID.WildBeasts4)) + if (b.Position.InRect(new(24, stampede1.Z), _rotation1, 33, 33, 5) && !beasts1.Contains(b)) + beasts1.Add(b); + foreach (var b in module.Enemies(OID.WildBeasts3)) + if (b.Position.InRect(new(24, stampede1.Z), _rotation1, 33, 33, 5) && !beasts1.Contains(b)) + beasts1.Add(b); + foreach (var b in module.Enemies(OID.WildBeasts2)) + if (b.Position.InRect(new(24, stampede1.Z), _rotation1, 33, 33, 5) && !beasts1.Contains(b)) + beasts1.Add(b); + foreach (var b in module.Enemies(OID.WildBeasts1)) + if (b.Position.InRect(new(24, stampede1.Z), _rotation1, 33, 33, 5) && !beasts1.Contains(b)) + beasts1.Add(b); + + foreach (var b in module.Enemies(OID.WildBeasts4)) + if (b.Position.InRect(new(24, stampede2.Z), _rotation2, 33, 33, 5) && !beasts2.Contains(b)) + beasts2.Add(b); + foreach (var b in module.Enemies(OID.WildBeasts3)) + if (b.Position.InRect(new(24, stampede2.Z), _rotation2, 33, 33, 5) && !beasts2.Contains(b)) + beasts2.Add(b); + foreach (var b in module.Enemies(OID.WildBeasts2)) + if (b.Position.InRect(new(24, stampede2.Z), _rotation2, 33, 33, 5) && !beasts2.Contains(b)) + beasts2.Add(b); + foreach (var b in module.Enemies(OID.WildBeasts1)) + if (b.Position.InRect(new(24, stampede2.Z), _rotation2, 33, 33, 5) && !beasts2.Contains(b)) + beasts2.Add(b); + + if (_reset1 != default && module.WorldState.CurrentTime > _reset1) + { + active1 = false; + stampede1counter = 0; + stampede1 = default; + beasts1.Clear(); + _reset1 = default; + } + if (_reset2 != default && module.WorldState.CurrentTime > _reset2) + { + active2 = false; + stampede2counter = 0; + stampede2 = default; + beasts2.Clear(); + _reset2 = default; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.WildlifeCrossing) + { + if (MathF.Abs(caster.Position.Z - stampede1.Z) < 1) + ++stampede1counter; + if (MathF.Abs(caster.Position.Z - stampede2.Z) < 1) + ++stampede2counter; + if (stampede1counter == 30) //sometimes stampedes only have 30 instead of 31 hits for some reason, so i take the lower value and add a 0,5s reset timer via update + _reset1 = module.WorldState.CurrentTime.AddSeconds(0.5f); + if (stampede2counter == 30) + _reset2 = module.WorldState.CurrentTime.AddSeconds(0.5f); + } + } + } + + class IcyThroes : Components.GenericBaitAway + { + private readonly List _targets = new(); + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Spreadmarker) + { + CurrentBaits.Add(new(module.PrimaryActor, actor, new AOEShapeCircle(6))); + _targets.Add(actor); + CenterAtTarget = true; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.IcyThroes3) + { + CurrentBaits.Clear(); + _targets.Clear(); + } + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (_targets.Contains(actor)) + hints.Add("Bait away!"); + } + } + + class Icebreaker : Components.GenericAOEs + { + private List _casters = new(); + private static readonly AOEShapeCircle circle = new(17); + private DateTime _activation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_casters.Count > 0) + foreach (var c in _casters) + yield return new(circle, c.Position, activation: _activation); + } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Target) + { + _casters.Add(actor); + _activation = module.WorldState.CurrentTime.AddSeconds(6); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Icebreaker) + _activation = spell.NPCFinishAt; + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Icebreaker) + _casters.Clear(); + } + } + + class IcyThroes2 : Components.SelfTargetedAOEs + { + public IcyThroes2() : base(ActionID.MakeSpell(AID.IcyThroes4), new AOEShapeCircle(6)) { } + } + + + class KnockOnIce : Components.SelfTargetedAOEs + { + public KnockOnIce() : base(ActionID.MakeSpell(AID.KnockOnIce2), new AOEShapeCircle(5)) { } + } + + class RightSlam : Components.SelfTargetedAOEs + { + public RightSlam() : base(ActionID.MakeSpell(AID.RightSlam), new AOEShapeRect(20, 80, directionOffset: -90.Degrees())) { } //full width = half width in this case + angle is detected incorrectly, length and width are also switched + } + + class LeftSlam : Components.SelfTargetedAOEs + { + public LeftSlam() : base(ActionID.MakeSpell(AID.LeftSlam), new AOEShapeRect(20, 80, directionOffset: 90.Degrees())) { } //full width = half width in this case + angle is detected incorrectly, length and width are also switched + } + + class AlbionsEmbrace : Components.SingleTargetCast + { + public AlbionsEmbrace() : base(ActionID.MakeSpell(AID.AlbionsEmbrace)) { } + } + + class RoarOfAlbion : Components.CastLineOfSightAOE + { + public RoarOfAlbion() : base(ActionID.MakeSpell(AID.RoarOfAlbion), 60, false) { } + public override IEnumerable BlockerActors(BossModule module) => module.Enemies(OID.IcyCrystal); + } + + class D131AlbionStates : StateMachineBuilder + { + public D131AlbionStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 896, NameID = 11992)] + public class D131Albion : BossModule + { + public D131Albion(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(24, -744), 19.5f)) { } + } +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D132GalateaMagna.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D132GalateaMagna.cs new file mode 100644 index 0000000000..51aa53a070 --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D132GalateaMagna.cs @@ -0,0 +1,274 @@ +// CONTRIB: made by malediktus, not checked +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13LapisManalis.D132GalateaMagna +{ + public enum OID : uint + { + Boss = 0x3971, //R=5.0 + Helper = 0x233C, + } + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + Teleport = 32625, // Boss->location, no cast, single-target, boss teleports to spot marked by icons 1,2,3,4 or to mid + WaningCycle0 = 32623, // Boss->self, no cast, single-target (between in->out) + WaningCycle1 = 32622, // Boss->self, 4,0s cast, range 10-40 donut + WaningCycle2 = 32624, // Helper->self, 6,0s cast, range 10 circle + WaxingCycle0 = 31378, // Boss->self, no cast, single-target (between out->in) + WaxingCycle1 = 31377, // Boss->self, 4,0s cast, range 10 circle + WaxingCycle2 = 31379, // Helper->self, 6,7s cast, range 10-40 donut + SoulScythe = 31386, // Boss->location, 6,0s cast, range 18 circle + SoulNebula = 31390, // Boss->self, 5,0s cast, range 40 circle, raidwide + ScarecrowChase = 31387, // Boss->self, 8,0s cast, single-target + ScarecrowChase2 = 31389, // Boss->self, no cast, single-target + ScarecrowChase3 = 32703, // Helper->self, 1,8s cast, range 40 width 10 cross + Tenebrism = 31382, // Boss->self, 4,0s cast, range 40 circle, small raidwide, spawns 4 towers, applies glass-eyed on tower resolve + Burst = 31383, // Helper->self, no cast, range 5 circle, tower success + BigBurst = 31384, // Helper->self, no cast, range 60 circle, tower fail + StonyGaze = 31385, // Helper->self, no cast, gaze + }; + + public enum IconID : uint + { + Icon1 = 336, // 3D06 + Icon2 = 337, // 3D06 + Icon3 = 338, // 3D06 + Icon4 = 339, // 3D06 + PlayerGaze = 73, // player + }; + + public enum SID : uint + { + SustainedDamage = 2935, // Helper->player, extra=0x0 + ScarecrowChase = 2056, // none->Boss, extra=0x22B + Doom = 3364, // Helper->player, extra=0x0 + GlassyEyed = 3511, // Boss->player, extra=0x0, takes possession of the player after status ends and does a petrifying attack in all direction + }; + + class ScarecrowChase : Components.GenericAOEs + { + private List<(Actor actor, uint icon)> _casters = new(); + private List _casterssorted = new(); + private static readonly AOEShapeCross cross = new(40, 5); + private DateTime _activation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + var activation = 3 * (_casters.Count - _casterssorted.Count); + if (_casterssorted.Count == 1) + yield return new(cross, _casterssorted[0].Position, 45.Degrees(), _activation.AddSeconds(activation), ArenaColor.Danger); + if (_casterssorted.Count > 1) + { + yield return new(cross, _casterssorted[0].Position, 45.Degrees(), _activation.AddSeconds(activation), ArenaColor.Danger); + yield return new(cross, _casterssorted[1].Position, 45.Degrees(), _activation.AddSeconds(3 + activation)); + } + } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Icon1) + { + _casters.Add((actor, iconID)); + _activation = module.WorldState.CurrentTime.AddSeconds(9.9f); + } + if (iconID == (uint)IconID.Icon2) + _casters.Add((actor, iconID)); + if (iconID == (uint)IconID.Icon3) + _casters.Add((actor, iconID)); + if (iconID == (uint)IconID.Icon4) + _casters.Add((actor, iconID)); + var _order = _casters.OrderBy(x => x.Item2); // icons can appear in random order in raw ops, so need to be sorted + _casterssorted = _order.Select(x => x.Item1).ToList(); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if (_casters.Count > 0 && _casterssorted.Count > 0 && (AID)spell.Action.ID == AID.ScarecrowChase3) + { + _casterssorted.RemoveAt(0); + if (_casterssorted.Count == 0) + _casters.Clear(); + } + } + } + + class OutInAOE : Components.ConcentricAOEs + { + private static readonly AOEShape[] _shapes = { new AOEShapeCircle(10), new AOEShapeDonut(10, 40) }; + + public OutInAOE() : base(_shapes) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.WaxingCycle1) + AddSequence(module.Bounds.Center, spell.NPCFinishAt); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (Sequences.Count > 0) + { + var order = (AID)spell.Action.ID switch + { + AID.WaxingCycle1 => 0, + AID.WaxingCycle2 => 1, + _ => -1 + }; + AdvanceSequence(order, caster.Position, module.WorldState.CurrentTime.AddSeconds(2.7f)); + } + } + } + + class InOutAOE : Components.ConcentricAOEs + { + private static readonly AOEShape[] _shapes = [new AOEShapeDonut(10, 40), new AOEShapeCircle(10)]; + + public InOutAOE() : base(_shapes) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.WaningCycle1) + AddSequence(module.Bounds.Center, spell.NPCFinishAt); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (Sequences.Count > 0) + { + var order = (AID)spell.Action.ID switch + { + AID.WaningCycle1 => 0, + AID.WaningCycle2 => 1, + _ => -1 + }; + AdvanceSequence(order, caster.Position, module.WorldState.CurrentTime.AddSeconds(2)); + } + } + } + + class GlassyEyed : Components.GenericGaze + { + private DateTime _activation; + private List _affected = new(); + + public override IEnumerable ActiveEyes(BossModule module, int slot, Actor actor) + { + foreach (var a in _affected) + if (_affected.Count > 0 && module.WorldState.CurrentTime > _activation.AddSeconds(-10)) + yield return new(a.Position, _activation); + } + + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.GlassyEyed) + { + _activation = status.ExpireAt; + _affected.Add(actor); + } + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.GlassyEyed) + _affected.Remove(actor); + } + } + + public class TenebrismTowers : Components.GenericTowers + { + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (state == 0x00010008 && index == 0x07) + Towers.Add(new(new(350, -404), 5, 1, 1)); + if (state == 0x00010008 && index == 0x08) + Towers.Add(new(new(360, -394), 5, 1, 1)); + if (state == 0x00010008 && index == 0x09) + Towers.Add(new(new(350, -384), 5, 1, 1)); + if (state == 0x00010008 && index == 0x0A) + Towers.Add(new(new(340, -394), 5, 1, 1)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.Burst or AID.BigBurst) + Towers.RemoveAll(t => t.Position.AlmostEqual(caster.Position, 1)); + } + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + if (Towers.Count > 0) + hints.AddForbiddenZone(ShapeDistance.InvertedCircle(Towers[0].Position, 6)); + } + } + + class Doom : BossComponent + { + private List _doomed = new(); + public bool Doomed { get; private set; } + + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Doom) + _doomed.Add(actor); + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Doom) + _doomed.Remove(actor); + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (_doomed.Contains(actor) && !(actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add("You were doomed! Get cleansed fast."); + if (_doomed.Contains(actor) && (actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add("Cleanse yourself! (Doom)."); + foreach (var c in _doomed) + if (!_doomed.Contains(actor) && (actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add($"Cleanse {c.Name} (Doom)"); + } + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + base.AddAIHints(module, slot, actor, assignment, hints); + foreach (var c in _doomed) + { + if (_doomed.Count > 0 && actor.Role == Role.Healer) + hints.PlannedActions.Add((ActionID.MakeSpell(WHM.AID.Esuna), c, 1, false)); + if (_doomed.Count > 0 && actor.Class == Class.BRD) + hints.PlannedActions.Add((ActionID.MakeSpell(BRD.AID.WardensPaean), c, 1, false)); + } + } + } + + class SoulScythe : Components.LocationTargetedAOEs + { + public SoulScythe() : base(ActionID.MakeSpell(AID.SoulScythe), 18) { } + } + + class D132GalateaMagnaStates : StateMachineBuilder + { + public D132GalateaMagnaStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 896, NameID = 10308)] + public class D132GalateaMagna : BossModule + { + public D132GalateaMagna(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(350, -394), 19.5f)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D133Cagnazzo.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D133Cagnazzo.cs new file mode 100644 index 0000000000..7fe064dda4 --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D133Cagnazzo.cs @@ -0,0 +1,252 @@ +// CONTRIB: made by malediktus, not checked +using System; +using System.Collections.Generic; + +namespace BossMod.Endwalker.Dungeon.D13LapisManalis.D133Cagnazzo +{ + public enum OID : uint + { + Boss = 0x3AE2, //R=8.0 + FearsomeFlotsam = 0x3AE3, //R=2.4 + Helper = 0x233C, + } + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + StygianDeluge = 31139, // Boss->self, 5,0s cast, range 80 circle + Antediluvian = 31119, // Boss->self, 5,0s cast, single-target + Antediluvian2 = 31120, // Helper->self, 6,5s cast, range 15 circle + BodySlam = 31121, // Boss->location, 6,5s cast, single-target + BodySlam2 = 31122, // Helper->self, 7,5s cast, range 60 circle, knockback 10, away from source + BodySlam3 = 31123, // Helper->self, 7,5s cast, range 8 circle + Teleport = 31131, // Boss->location, no cast, single-target, boss teleports + HydrobombTelegraph = 32695, // Helper->location, 2,0s cast, range 4 circle + HydraulicRamTelegraph = 32693, // Helper->location, 2,0s cast, width 8 rect charge + HydraulicRam = 32692, // Boss->self, 6,0s cast, single-target + HydraulicRam2 = 32694, // Boss->location, no cast, width 8 rect charge + Hydrobomb = 32696, // Helper->location, no cast, range 4 circle + StartHydrofall = 31126, // Boss->self, no cast, single-target + Hydrofall = 31375, // Boss->self, 5,0s cast, single-target + Hydrofall2 = 31376, // Helper->players, 5,5s cast, range 6 circle + CursedTide = 31130, // Boss->self, 5,0s cast, single-target + StartLimitbreakPhase = 31132, // Boss->self, no cast, single-target + NeapTide = 31134, // Helper->player, no cast, range 6 circle + Hydrovent = 31136, // Helper->location, 5,0s cast, range 6 circle + SpringTide = 31135, // Helper->players, no cast, range 6 circle + Tsunami = 31137, // Helper->self, no cast, range 80 width 60 rect + Voidcleaver = 31110, // Boss->self, 4,0s cast, single-target + Voidcleaver2 = 31111, // Helper->self, no cast, range 100 circle + VoidMiasma = 32691, // Helper->self, 3,0s cast, range 50 30-degree cone + Lifescleaver = 31112, // Boss->self, 4,0s cast, single-target + Lifescleaver2 = 31113, // Helper->self, 5,0s cast, range 50 30-degree cone + VoidTorrent = 31118, // Boss->self/player, 5,0s cast, range 60 width 8 rect + }; + + public enum IconID : uint + { + Stackmarker = 161, // player + Spreadmarker = 139, // player + Tankbuster = 230, // player + }; + + public enum TetherID : uint + { + LimitBreakCharger = 3, // FearsomeFlotsam->Boss + BaitAway = 1, // 3E97->player + }; + + public enum NPCYell : uint + { + LimitBreakStart = 15175, + }; + + class VoidTorrent : Components.BaitAwayCast + { + public VoidTorrent() : base(ActionID.MakeSpell(AID.VoidTorrent), new AOEShapeRect(60, 4)) { } + } + + class VoidTorrentHint : Components.SingleTargetCast + { + public VoidTorrentHint() : base(ActionID.MakeSpell(AID.VoidTorrent), "Tankbuster cleave") { } + } + + class Voidcleaver : Components.RaidwideCast + { + public Voidcleaver() : base(ActionID.MakeSpell(AID.Voidcleaver)) { } + } + + class VoidMiasmaBait : Components.BaitAwayTethers + { + public VoidMiasmaBait() : base(new AOEShapeCone(50, 15.Degrees()), (uint)TetherID.BaitAway) { } + } + + class VoidMiasma : Components.SelfTargetedAOEs + { + public VoidMiasma() : base(ActionID.MakeSpell(AID.VoidMiasma), new AOEShapeCone(50, 15.Degrees())) { } + } + + class Lifescleaver : Components.SelfTargetedAOEs + { + public Lifescleaver() : base(ActionID.MakeSpell(AID.Lifescleaver2), new AOEShapeCone(50, 15.Degrees())) { } + } + + class Tsunami : Components.RaidwideAfterNPCYell + { + public Tsunami() : base(ActionID.MakeSpell(AID.Tsunami), (uint)NPCYell.LimitBreakStart, 4.5f) { } + } + + class StygianDeluge : Components.RaidwideCast + { + public StygianDeluge() : base(ActionID.MakeSpell(AID.StygianDeluge)) { } + } + + class Antediluvian : Components.SelfTargetedAOEs + { + public Antediluvian() : base(ActionID.MakeSpell(AID.Antediluvian2), new AOEShapeCircle(15)) { } + } + + class BodySlam : Components.SelfTargetedAOEs + { + public BodySlam() : base(ActionID.MakeSpell(AID.BodySlam3), new AOEShapeCircle(8)) { } + } + + class BodySlamKB : Components.KnockbackFromCastTarget + { + public BodySlamKB() : base(ActionID.MakeSpell(AID.BodySlam2), 10) { } + } + + class HydraulicRam : Components.GenericAOEs + { + private List<(WPos source, AOEShape shape, Angle direction)> _casters = new(); + private DateTime _activation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_casters.Count > 0) + yield return new(_casters[0].shape, _casters[0].source, _casters[0].direction, _activation, ArenaColor.Danger); + for (int i = 1; i < _casters.Count; ++i) + yield return new(_casters[i].shape, _casters[i].source, _casters[i].direction, _activation); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.HydraulicRamTelegraph) + { + var dir = spell.LocXZ - caster.Position; + _casters.Add((caster.Position, new AOEShapeRect(dir.Length(), 4), Angle.FromDirection(dir))); + } + if ((AID)spell.Action.ID == AID.HydraulicRam) + _activation = spell.NPCFinishAt.AddSeconds(1.5f); //since these are charges of different length with 0s cast time, the activation times are different for each and there are different patterns, so we just pretend that they all start after the telegraphs end + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (_casters.Count > 0 && (AID)spell.Action.ID == AID.HydraulicRam2) + _casters.RemoveAt(0); + } + } + + class Hydrobomb : Components.GenericAOEs + { + private List _casters = new(); + private static readonly AOEShapeCircle circle = new(4); + private DateTime _activation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_casters.Count > 1) + for (int i = 0; i < 2; ++i) + yield return new(circle, _casters[i], activation: _activation.AddSeconds(6 - _casters.Count / 2), color: ArenaColor.Danger); + for (int i = 2; i < _casters.Count; ++i) + yield return new(circle, _casters[i], activation: _activation.AddSeconds(MathF.Ceiling(i / 2) + 6 - _casters.Count / 2)); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.HydrobombTelegraph) + _casters.Add(spell.LocXZ); + if ((AID)spell.Action.ID == AID.HydraulicRam) + _activation = spell.NPCFinishAt.AddSeconds(2.2f); + + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (_casters.Count > 0 && (AID)spell.Action.ID == AID.Hydrobomb) + _casters.RemoveAt(0); + } + } + + class Hydrovent : Components.LocationTargetedAOEs + { + public Hydrovent() : base(ActionID.MakeSpell(AID.Hydrovent), 6) { } + } + + class NeapTide : Components.UniformStackSpread + { + public NeapTide() : base(0, 6, alwaysShowSpreads: true) { } + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Spreadmarker) + AddSpread(actor); + } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.NeapTide) + Spreads.Clear(); + } + } + + class Stackmarkers : Components.UniformStackSpread //Hydrofall and Springtide, both use the same icon + { + public Stackmarkers() : base(6, 0) { } + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Stackmarker) + AddStack(actor); + } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.SpringTide or AID.Hydrofall2) + Stacks.Clear(); + } + } + + + class D133CagnazzoStates : StateMachineBuilder + { + public D133CagnazzoStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 896, NameID = 11995)] + public class D133Cagnazzo : BossModule + { + public D133Cagnazzo(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-250, 130), 20)) { } + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy); + foreach (var s in Enemies(OID.FearsomeFlotsam)) + Arena.Actor(s, ArenaColor.Enemy); + } + } +} diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs index db4d1adebf..7c2e756f5d 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs @@ -8,6 +8,7 @@ public enum OID : uint { Boss = 0x27CC, //R=20.00 Helper = 0x2E8, //R=0.5 + Helper2 = 0x233C, Brightsphere = 0x27CD, //R=1.0 Towers = 0x1EAACF, //R=0.5 } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs index f6c9cf186b..ea62021970 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs @@ -8,6 +8,7 @@ public enum OID : uint { Boss = 0x28F3, //R=7.5 Helper = 0x2E8, //R=0.5 + Helper2 = 0x233C, Brightsphere = 0x2947, //R=1.0 } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs index d15d0bee0f..6e44d86545 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs @@ -9,6 +9,7 @@ public enum OID : uint Boss = 0x27CE, //R=5.0 BossClones = 0x27CF, //R=5.0 Helper = 0x2E8, //R=0.5 + Helper2 = 0x233C, Orbs = 0x27D0, //R=1.0 Rings = 0x1EAB62, }