diff --git a/BossMod/BossModule/BossComponent.cs b/BossMod/BossModule/BossComponent.cs index e68e3b294a..d9280f7493 100644 --- a/BossMod/BossModule/BossComponent.cs +++ b/BossMod/BossModule/BossComponent.cs @@ -58,6 +58,7 @@ public virtual void OnActorEState(BossModule module, Actor actor, ushort state) public virtual void OnActorEAnim(BossModule module, Actor actor, uint state) { } public virtual void OnActorPlayActionTimelineEvent(BossModule module, Actor actor, ushort id) { } public virtual void OnActorNpcYell(BossModule module, Actor actor, ushort id) { } + public virtual void OnActorModelStateChange(BossModule module, Actor actor, byte modelstate, byte animstate1, byte animstate2) { } public virtual void OnEventEnvControl(BossModule module, byte index, uint state) { } } } diff --git a/BossMod/BossModule/BossModule.cs b/BossMod/BossModule/BossModule.cs index e2f478cc6e..0257fbb21a 100644 --- a/BossMod/BossModule/BossModule.cs +++ b/BossMod/BossModule/BossModule.cs @@ -135,6 +135,7 @@ public BossModule(WorldState ws, Actor primary, ArenaBounds bounds) WorldState.Actors.EventObjectAnimation += OnActorEAnim; WorldState.Actors.PlayActionTimelineEvent += OnActorPlayActionTimelineEvent; WorldState.Actors.EventNpcYell += OnActorNpcYell; + WorldState.Actors.ModelStateChanged += OnActorModelStateChange; WorldState.EnvControl += OnEnvControl; foreach (var v in WorldState.Actors) OnActorCreated(null, v); @@ -170,6 +171,7 @@ protected virtual void Dispose(bool disposing) WorldState.Actors.EventObjectAnimation -= OnActorEAnim; WorldState.Actors.PlayActionTimelineEvent -= OnActorPlayActionTimelineEvent; WorldState.Actors.EventNpcYell -= OnActorNpcYell; + WorldState.Actors.ModelStateChanged += OnActorModelStateChange; WorldState.EnvControl -= OnEnvControl; } } @@ -481,6 +483,12 @@ private void OnActorNpcYell(object? sender, (Actor actor, ushort id) arg) comp.OnActorNpcYell(this, arg.actor, arg.id); } + private void OnActorModelStateChange(object? sender, (Actor actor, byte modelstate, byte animstate1, byte animstate2) arg) + { + foreach (var comp in _components) + comp.OnActorModelStateChange(this, arg.actor, arg.modelstate, arg.animstate1, arg.animstate2); + } + private void OnEnvControl(object? sender, WorldState.OpEnvControl op) { foreach (var comp in _components) diff --git a/BossMod/BossModule/BossModuleConfig.cs b/BossMod/BossModule/BossModuleConfig.cs index df56da4d71..04825c7b0b 100644 --- a/BossMod/BossModule/BossModuleConfig.cs +++ b/BossMod/BossModule/BossModuleConfig.cs @@ -28,6 +28,10 @@ public class BossModuleConfig : ConfigNode [PropertyDisplay("Show cardinal direction names")] public bool ShowCardinals = false; + [PropertyDisplay("Cardinal direction font size")] + [PropertySlider(0.1f, 100, Speed = 1)] + public float CadrdinalsFontSize = 17; + [PropertyDisplay("Show waymarks on radar")] public bool ShowWaymarks = false; diff --git a/BossMod/BossModule/MiniArena.cs b/BossMod/BossModule/MiniArena.cs index f163529bdc..0df534a8a9 100644 --- a/BossMod/BossModule/MiniArena.cs +++ b/BossMod/BossModule/MiniArena.cs @@ -211,13 +211,14 @@ public void Border(uint color) public void CardinalNames() { + var fontsize = Service.Config.Get().CadrdinalsFontSize; var offCenter = ScreenHalfSize + ScreenMarginSize / 2; var offS = RotatedCoords(new(0, offCenter)); var offE = RotatedCoords(new(offCenter, 0)); - TextScreen(ScreenCenter - offS, "N", ArenaColor.Border); - TextScreen(ScreenCenter + offS, "S", ArenaColor.Border); - TextScreen(ScreenCenter + offE, "E", ArenaColor.Border); - TextScreen(ScreenCenter - offE, "W", ArenaColor.Border); + TextScreen(ScreenCenter - offS, "N", ArenaColor.Border, fontsize); + TextScreen(ScreenCenter + offS, "S", ArenaColor.Border, fontsize); + TextScreen(ScreenCenter + offE, "E", ArenaColor.Border, fontsize); + TextScreen(ScreenCenter - offE, "W", ArenaColor.Border, fontsize); } // draw actor representation diff --git a/BossMod/Components/BaitAway.cs b/BossMod/Components/BaitAway.cs index d31e7a66cb..5423d17e3c 100644 --- a/BossMod/Components/BaitAway.cs +++ b/BossMod/Components/BaitAway.cs @@ -31,6 +31,7 @@ public Bait(Actor source, Actor target, AOEShape shape, DateTime activation = de public bool CenterAtTarget; // if true, aoe source is at target public bool AllowDeadTargets = true; // if false, baits with dead targets are ignored public bool EnableHints = true; + public bool EndsOnCastEvent; public bool IgnoreOtherBaits = false; // if true, don't show hints/aoes for baits by others public PlayerPriority BaiterPriority = PlayerPriority.Interesting; public BitMask ForbiddenPlayers; // these players should avoid baiting @@ -202,7 +203,13 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { - if (spell.Action == WatchedAction) + if (spell.Action == WatchedAction && !EndsOnCastEvent) + CurrentBaits.RemoveAll(b => b.Source == caster); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (spell.Action == WatchedAction && EndsOnCastEvent) CurrentBaits.RemoveAll(b => b.Source == caster); } } diff --git a/BossMod/Components/CastHint.cs b/BossMod/Components/CastHint.cs index 6be084801b..25d6a32b4b 100644 --- a/BossMod/Components/CastHint.cs +++ b/BossMod/Components/CastHint.cs @@ -7,6 +7,7 @@ namespace BossMod.Components public class CastHint : CastCounter { public string Hint; + public bool EndsOnCastEvent; public bool ShowCastTimeLeft; // if true, show cast time left until next instance private List _casters = new(); public IReadOnlyList Casters => _casters; @@ -32,28 +33,67 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { - if (spell.Action == WatchedAction) + if (spell.Action == WatchedAction && !EndsOnCastEvent) + _casters.Remove(caster); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (spell.Action == WatchedAction && EndsOnCastEvent) _casters.Remove(caster); } } public class CastInterruptHint : CastHint { - public bool CanBeInterrupted { get; init; } - public bool CanBeStunned { get; init; } + public bool CanBeInterrupted; + public bool CanBeStunned; + public bool ShowNameInHint; + public string HintExtra; + private List _casters = new(); + public new IReadOnlyList Casters => _casters; + public new bool Active => _casters.Count > 0; - public CastInterruptHint(ActionID aid, bool canBeInterrupted = true, bool canBeStunned = false, string hint = "") : base(aid, "") + public CastInterruptHint(ActionID aid, bool canBeInterrupted = true, bool canBeStunned = false, string hintExtra = "", bool showNameInHint = false) : base(aid, hintExtra) { CanBeInterrupted = canBeInterrupted; CanBeStunned = canBeStunned; - if (canBeInterrupted || canBeStunned) - { - Hint = !canBeStunned ? "Interrupt" : !canBeInterrupted ? "Stun" : "Interrupt/stun"; - if (hint.Length > 0) - Hint += $" {hint}"; - } + ShowNameInHint = showNameInHint; + HintExtra = hintExtra; + } + + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (!Active) return; + string action = ""; + if (CanBeInterrupted && !CanBeStunned) + action = "Interrupt"; + else if (CanBeInterrupted && CanBeStunned) + action = "Interrupt/Stun"; + else if (!CanBeInterrupted && CanBeStunned) + action = "Stun"; + string hint = $"{action}!"; + if (ShowNameInHint && Casters.Count > 0) + hint = $"{action} {Casters[0].Name}!"; + if (HintExtra.Length > 0) + hint += $" {HintExtra}"; + hints.Add(hint); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + _casters.Add(caster); } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + _casters.Remove(caster); + } + + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { foreach (var c in Casters) diff --git a/BossMod/Components/ChasingAOEs.cs b/BossMod/Components/ChasingAOEs.cs new file mode 100644 index 0000000000..d499543d4b --- /dev/null +++ b/BossMod/Components/ChasingAOEs.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Components +{ + // generic 'chasing AOE' component - these are AOEs that follow the target for a set amount of casts + public class ChasingAOEs : GenericAOEs + { + public class Chaser + { + public Actor Player; + public WPos? Pos; + public int NumCasts; + public DateTime Activation; + + public Chaser(Actor player) + { + Player = player; + } + + public WPos PredictedPosition() + { + if (Pos == null) + return default; + if (NumCasts == 0) + return Pos.Value; + var toPlayer = Player.Position - Pos.Value; + var dist = toPlayer.Length(); + if (dist < MoveDist) + return Player.Position; + return Pos.Value + toPlayer * MoveDist / dist; + } + } + + private List _chasers = new(); + + public bool Active => _chasers.Count > 0; + + public AOEShape Shape; + public static int MaxCasts; + public static float MoveDist; + public bool LocationTargeted; //if true chaser is location targeted instead of self targeted + public Angle Rotation; + public uint Icon; + public ActionID ChasingAOEFirst; + public ActionID ChasingAOERest; + public float TimeBetweenCasts; + + public ChasingAOEs(uint icon, AOEShape shape, ActionID chasingAOEFirst, ActionID chasingAOERest, float movedist, int maxCasts, float timeBetweenCasts, bool locationtargeted = false, Angle rotation = default) : base(chasingAOEFirst, "GTFO from chasing AOE!") + { + Shape = shape; + Icon = icon; + ChasingAOEFirst = chasingAOEFirst; + ChasingAOERest = chasingAOERest; + MoveDist = movedist; + MaxCasts = maxCasts; + TimeBetweenCasts = timeBetweenCasts; + LocationTargeted = locationtargeted; + Rotation = rotation; + } + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _chasers.Select(c => new AOEInstance(Shape, c.PredictedPosition(), Rotation, c.Activation)); + + public override void Update(BossModule module) + { + _chasers.RemoveAll(c => (c.Player.IsDestroyed || c.Player.IsDead) && (c.Pos == null || c.NumCasts > 0)); + } + + public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + foreach (var c in _chasers) + if (c.Pos != null) + { + if (arena.Config.ShowOutlinesAndShadows) + arena.AddLine(c.Pos.Value, c.Player.Position, 0xFF000000, 2); + arena.AddLine(c.Pos.Value, c.Player.Position, ArenaColor.Danger); + } + } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == Icon) + _chasers.Add(new(actor)); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == ChasingAOEFirst && _chasers.Where(c => c.Pos == null).MinBy(c => (c.Player.Position - caster.Position).LengthSq()) is var chaser && chaser != null) + { + if (LocationTargeted) + chaser.Pos = spell.LocXZ; + else + chaser.Pos = caster.Position; + chaser.Activation = spell.NPCFinishAt; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == ChasingAOEFirst) + { + if (LocationTargeted) + Advance(module, spell.LocXZ); + else + Advance(module, caster.Position); + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (spell.Action == ChasingAOERest) + { + if (LocationTargeted) + Advance(module, spell.TargetXZ); + else + Advance(module, caster.Position); + } + } + + private void Advance(BossModule module, WPos pos) + { + ++NumCasts; + var chaser = _chasers.MinBy(c => c.Pos != null ? (c.PredictedPosition() - pos).LengthSq() : float.MaxValue); + if (chaser == null) + return; + + if (++chaser.NumCasts < MaxCasts) + { + chaser.Pos = pos; + chaser.Activation = module.WorldState.CurrentTime.AddSeconds(TimeBetweenCasts); + } + else + { + _chasers.Remove(chaser); + } + } + } +} diff --git a/BossMod/Components/PersistentVoidzone.cs b/BossMod/Components/PersistentVoidzone.cs index d7e77880d4..8360c2f97a 100644 --- a/BossMod/Components/PersistentVoidzone.cs +++ b/BossMod/Components/PersistentVoidzone.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace BossMod.Components { @@ -77,4 +78,72 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent _predictedByEvent.Add((module.WorldState.Actors.Find(spell.MainTargetID)?.Position ?? spell.TargetXZ, module.WorldState.CurrentTime.AddSeconds(CastEventToSpawn))); } } + + // voidzone (circle aoe that stays active for some time) centered at each existing object with specified OID, assumed to be persistent voidzone center + // inverts from dangerous to safe when a specific AID is being casted + public class PersistentInvertibleVoidzone : GenericAOEs + { + public AOEShapeCircle Shape { get; private init; } + public Func> Sources { get; private init; } + private bool inverting; + private DateTime _activation; + private float Radius; + + public PersistentInvertibleVoidzone(float radius, ActionID aid, Func> sources) : base(aid, "GTFO from voidzone!") + { + Shape = new(radius); + Sources = sources; + Radius = radius; + } + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (!inverting) + foreach (var s in Sources(module)) + yield return new(Shape, s.Position); + if (inverting) + foreach (var s in Sources(module)) + yield return new(Shape, s.Position, activation: _activation, color: ArenaColor.SafeFromAOE, risky: false); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + { + inverting = true; + _activation = spell.NPCFinishAt; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + inverting = false; + } + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + var shapes = new List>(); + foreach (var c in ActiveAOEs(module, slot, actor)) + { + if (c.Risky) + hints.AddForbiddenZone(c.Shape, c.Origin, c.Rotation, c.Activation); + if (inverting) + shapes.Add(ShapeDistance.InvertedCircle(c.Origin, Radius)); + } + if (shapes.Count > 0) + hints.AddForbiddenZone(p => shapes.Select(f => f(p)).Max(), _activation); + if (shapes.Count > 0 && !inverting) + shapes.Clear(); + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + base.AddHints(module, slot, actor, hints, movementHints); + if (ActiveAOEs(module, slot, actor).Any(c => c.Check(actor.Position)) && inverting) + hints.Add("Wait in puddle until mechanic resolves!", false); + if (!ActiveAOEs(module, slot, actor).Any(c => c.Check(actor.Position)) && inverting) + hints.Add("Go into puddle until mechanic resolves!"); + } + } } diff --git a/BossMod/Components/UnavoidableDamage.cs b/BossMod/Components/UnavoidableDamage.cs index 2427468eba..255010154d 100644 --- a/BossMod/Components/UnavoidableDamage.cs +++ b/BossMod/Components/UnavoidableDamage.cs @@ -68,9 +68,12 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR { foreach (var c in Casters) { - BitMask targets = new(); - targets.Set(module.Raid.FindSlot(c.CastInfo!.TargetID)); - hints.PredictedDamage.Add((targets, c.CastInfo!.NPCFinishAt)); + if (c.CastInfo != null) //is null after casts finishes if EndsOnCastEvent is used + { + BitMask targets = new(); + targets.Set(module.Raid.FindSlot(c.CastInfo!.TargetID)); + hints.PredictedDamage.Add((targets, c.CastInfo!.NPCFinishAt)); + } } } } diff --git a/BossMod/Data/ActorState.cs b/BossMod/Data/ActorState.cs index c9047ba91c..709e6f9f93 100644 --- a/BossMod/Data/ActorState.cs +++ b/BossMod/Data/ActorState.cs @@ -259,7 +259,7 @@ protected override void ExecActor(WorldState ws, Actor actor) public override void Write(ReplayRecorder.Output output) => WriteTag(output, Value ? "COM+" : "COM-").EmitActor(InstanceID); } - public event EventHandler? ModelStateChanged; + public event EventHandler<(Actor, byte, byte, byte)>? ModelStateChanged; public class OpModelState : Operation { public ActorModelState Value; @@ -267,7 +267,7 @@ public class OpModelState : Operation protected override void ExecActor(WorldState ws, Actor actor) { actor.ModelState = Value; - ws.Actors.ModelStateChanged?.Invoke(ws, actor); + ws.Actors.ModelStateChanged?.Invoke(ws, (actor, Value.ModelState, Value.AnimState1, Value.AnimState2)); } public override void Write(ReplayRecorder.Output output) => WriteTag(output, "MDLS").EmitActor(InstanceID).Emit(Value.ModelState).Emit(Value.AnimState1).Emit(Value.AnimState2); diff --git a/BossMod/Modules/Endwalker/DeepDungeons/EurekaOrthos/Floors21-30/DD30TiamatsClone.cs b/BossMod/Modules/Endwalker/DeepDungeons/EurekaOrthos/Floors21-30/DD30TiamatsClone.cs new file mode 100644 index 0000000000..f368267a80 --- /dev/null +++ b/BossMod/Modules/Endwalker/DeepDungeons/EurekaOrthos/Floors21-30/DD30TiamatsClone.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Drawing; + +namespace BossMod.Endwalker.DeepDungeons.EurekaOrthos.Floors21to30.DD30TiamatsClone +{ + public enum OID : uint + { + Boss = 0x3D9A, // R19.000 + DarkWanderer = 0x3D9B, // R2.000 + Helper = 0x233C, // R0.500 + }; + + public enum AID : uint + { + HeadAttack = 31842, // 233C->player, no cast, single-target + AutoAttack = 32702, // 3D9A->player, no cast, single-target + CreatureOfDarkness = 31841, // 3D9A->self, 3.0s cast, single-target // Summon Heads E<->W heading S + DarkMegaflare1 = 31849, // 3D9A->self, 3.0s cast, single-target + DarkMegaflare2 = 31850, // 233C->location, 3.0s cast, range 6 circle + DarkWyrmtail1 = 31843, // 3D9A->self, 5.0s cast, single-target + DarkWyrmtail2 = 31844, // 233C->self, 6.0s cast, range 40 width 16 rect // Summon Heads Heading E/W from Middle Lane + DarkWyrmwing1 = 31845, // 3D9A->self, 5.0s cast, single-target + DarkWyrmwing2 = 31846, // 233C->self, 6.0s cast, range 40 width 16 rect // Summon Heads Heading E/W from E/W Walls + WheiMornFirst = 31847, // 3D9A->location, 5.0s cast, range 6 circle + WheiMornRest = 31848, // 3D9A->location, no cast, range 6 circle + }; + + public enum IconID : uint + { + ChasingAOE = 197, // player + }; + + class WheiMorn : Components.ChasingAOEs + { + public WheiMorn() : base((uint)IconID.ChasingAOE, new AOEShapeCircle(6), ActionID.MakeSpell(AID.WheiMornFirst), ActionID.MakeSpell(AID.WheiMornRest), 6, 5, 2, true) { } + } + + class DarkMegaflare : Components.LocationTargetedAOEs + { + public DarkMegaflare() : base(ActionID.MakeSpell(AID.DarkMegaflare2), 6) { } + } + + class DarkWyrmwing : Components.SelfTargetedAOEs + { + public DarkWyrmwing() : base(ActionID.MakeSpell(AID.DarkWyrmwing2), new AOEShapeRect(40, 8)) { } + } + + class DarkWyrmtail : Components.SelfTargetedAOEs + { + public DarkWyrmtail() : base(ActionID.MakeSpell(AID.DarkWyrmtail2), new AOEShapeRect(40, 8)) { } + } + + class CreatureOfDarkness : Components.GenericAOEs + { + private readonly List _heads = new(); + private static readonly AOEShapeRect rect = new(2, 2, 2); + private static readonly AOEShapeRect rect2 = new(6, 2); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + foreach (var c in _heads) + { + yield return new(rect, c.Position, c.Rotation, color: ArenaColor.Danger); + yield return new(rect2, c.Position + 2 * c.Rotation.ToDirection(), c.Rotation); + } + } + + public override void OnActorModelStateChange(BossModule module, Actor actor, byte ModelState, byte AnimState1, byte AnimState2) + { + if ((OID)actor.OID == OID.DarkWanderer) + { + if (AnimState1 == 1) + _heads.Add(actor); + if (AnimState1 == 0) + _heads.Remove(actor); + } + } + } + + class DD30TiamatsCloneStates : StateMachineBuilder + { + public DD30TiamatsCloneStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 899, NameID = 12242)] + public class DD30TiamatsClone : BossModule + { + public DD30TiamatsClone(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-300, -300), 20)) { } + } +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D011Minduruva.cs b/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D011Minduruva.cs new file mode 100644 index 0000000000..57bff97b3e --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D011Minduruva.cs @@ -0,0 +1,168 @@ +// CONTRIB: made by dhoggpt, improvements by Malediktus, not checked +using System.Collections.Generic; + +namespace BossMod.Endwalker.Dungeon.D01TowerOfZot.D011Minduruva +{ + public enum OID : uint + { + Boss = 0x33EE, // R=2.04 + Helper = 0x233C, + }; + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + ManusyaBio = 25248, // Boss->player, 4,0s cast, single-target + Teleport = 25241, // Boss->location, no cast, single-target + ManusyaBlizzardIII1 = 25234, // Boss->self, 4,0s cast, single-target + ManusyaBlizzardIII2 = 25238, // Helper->self, 4,0s cast, range 40+R 20-degree cone + ManusyaFireIII1 = 25233, // Boss->self, 4,0s cast, single-target + ManusyaFireIII2 = 25237, // Helper->self, 4,0s cast, range 5-40 donut + ManusyaThunderIII1 = 25235, // Boss->self, 4,0s cast, single-target + ManusyaThunderIII2 = 25239, // Helper->self, 4,0s cast, range 3 circle + ManusyaBioIII1 = 25236, // Boss->self, 4,0s cast, single-target + ManusyaBioIII2 = 25240, // Helper->self, 4,0s cast, range 40+R 180-degree cone + TransmuteFireIII = 25242, // Boss->self, 2,7s cast, single-target + Unknown = 25243, // Helper->Boss, 3,6s cast, single-target + ManusyaFire2 = 25699, // Boss->player, 2,0s cast, single-target + Dhrupad = 25244, // Boss->self, 4,0s cast, single-target, after this each of the non-tank players get hit once by a single-target spell (ManusyaBlizzard, ManusyaFire1, ManusyaThunder) + ManusyaFire1 = 25245, // Boss->player, no cast, single-target + ManusyaBlizzard = 25246, // Boss->player, no cast, single-target + ManusyaThunder = 25247, // Boss->player, no cast, single-target + TransmuteBlizzardIII = 25371, // Boss->self, 2,7s cast, single-target + TransmuteThunderIII = 25372, // Boss->self, 2,7s cast, single-target + }; + + public enum SID : uint + { + Poison = 18, // Boss->player, extra=0x0 + ThunderAlchemy = 2753, // Boss->Boss, extra=0x0 + Burns = 2082, // Boss->player, extra=0x0 + Frostbite = 2083, // Boss->player, extra=0x0 + Electrocution = 2086, // Boss->player, extra=0x0 + IceAlchemy = 2752, // Boss->Boss, extra=0x0 + ToxicAlchemy = 2754, // Boss->Boss, extra=0x0 + FireAlchemy = 2751, // Boss->Boss, extra=0x0 + }; + + + class ManusyaBio : Components.SingleTargetCast + { + public ManusyaBio() : base(ActionID.MakeSpell(AID.ManusyaBio), "Tankbuster + cleansable poison") { } + } + + class Poison : BossComponent + { + private readonly List _poisoned = []; + + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Poison) + _poisoned.Add(actor); + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Poison) + _poisoned.Remove(actor); + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (_poisoned.Contains(actor) && !(actor.Role == Role.Healer || actor.Class == Class.BRD)) //theoretically only the tank can ge poisoned, this is just in here incase of bad tanks + hints.Add("You were poisoned! Get cleansed fast."); + if (_poisoned.Contains(actor) && (actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add("Cleanse yourself! (Poison)."); + foreach (var c in _poisoned) + if (!_poisoned.Contains(actor) && (actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add($"Cleanse {c.Name} (Poison)"); + } + + 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 _poisoned) + { + if (_poisoned.Count > 0 && actor.Role == Role.Healer) + hints.PlannedActions.Add((ActionID.MakeSpell(WHM.AID.Esuna), c, 1, false)); + if (_poisoned.Count > 0 && actor.Class == Class.BRD) + hints.PlannedActions.Add((ActionID.MakeSpell(BRD.AID.WardensPaean), c, 1, false)); + } + } + } + + class Dhrupad : BossComponent + { + private int NumCasts; + private bool active; + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Dhrupad) + active = true; + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ManusyaFire1 or AID.ManusyaBlizzard or AID.ManusyaThunder) + { + ++NumCasts; + if (NumCasts == 3) + { + NumCasts = 0; + active = false; + } + } + } + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (active) + hints.Add("3 single target hits + DoTs"); + } + } + + class ManusyaThunderIII : Components.SelfTargetedAOEs + { + public ManusyaThunderIII() : base(ActionID.MakeSpell(AID.ManusyaThunderIII2), new AOEShapeCircle(3)) { } + } + + class ManusyaBioIII : Components.SelfTargetedAOEs + { + public ManusyaBioIII() : base(ActionID.MakeSpell(AID.ManusyaBioIII2), new AOEShapeCone(40.5f, 90.Degrees())) { } + } + + class ManusyaBlizzardIII : Components.SelfTargetedAOEs + { + public ManusyaBlizzardIII() : base(ActionID.MakeSpell(AID.ManusyaBlizzardIII2), new AOEShapeCone(40.5f, 10.Degrees())) { } + } + + class ManusyaFireIII : Components.SelfTargetedAOEs + { + public ManusyaFireIII() : base(ActionID.MakeSpell(AID.ManusyaFireIII2), new AOEShapeDonut(5, 60)) { } + } + + class D011MinduruvaStates : StateMachineBuilder + { + public D011MinduruvaStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 783, NameID = 10256)] + public class D011Minduruva : BossModule + { + public D011Minduruva(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(68, -124), 19.5f)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy, true); + } + } +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D012Sanduruva.cs b/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D012Sanduruva.cs new file mode 100644 index 0000000000..c748b1b7a3 --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D012Sanduruva.cs @@ -0,0 +1,136 @@ +// CONTRIB: made by dhoggpt, improvements by Malediktus, not checked +using System; +using System.Collections.Generic; + +namespace BossMod.Endwalker.Dungeon.D01TheTowerOifZot.D012Sanduruva +{ + public enum OID : uint + { + Boss = 0x33EF, // R=2.5 + BerserkerSphere = 0x33F0, // R=1.5-2.5 + }; + + public enum AID : uint + { + AutoAttack = 871, // Boss->player, no cast, single-target + Teleport = 25254, // Boss->location, no cast, single-target + ExplosiveForce = 25250, //Boss->self, 3.0s cast, single-target + IsitvaSiddhi = 25257, // Boss->player, 4.0s cast, single-target + ManusyaBerserk = 25249, // Boss->self, 3.0s cast, single-target + ManusyaConfuse = 25253, // Boss->self, 3.0s cast, range 40 circle + ManusyaStop = 25255, // Boss->self, 3.0s cast, range 40 circle + PrakamyaSiddhi = 25251, // Boss->self, 4.0s cast, range 5 circle + PraptiSiddhi = 25256, //Boss->self, 2.0s cast, range 40 width 4 rect + SphereShatter = 25252, // BerserkerSphere->self, 2.0s cast, range 15 circle + }; + + public enum SID : uint + { + ManusyaBerserk = 2651, // Boss->player, extra=0x0 + ManusyaStop = 2653, // none->player, extra=0x0 + TemporalDisplacement = 900, // none->player, extra=0x0 + ManusyaConfuse = 2652, // Boss->player, extra=0x1C6 + WhoIsShe = 2655, // none->Boss, extra=0x0 + WhoIsShe2 = 2654, // none->BerserkerSphere, extra=0x1A8 + }; + + class IsitvaSiddhi : Components.SingleTargetCast + { + public IsitvaSiddhi() : base(ActionID.MakeSpell(AID.IsitvaSiddhi)) { } + } + + class SphereShatter : Components.GenericAOEs + { + private DateTime _activation; + private readonly List _casters = []; + private static readonly AOEShapeCircle circle = new(15); + 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, risky: _activation.AddSeconds(-7) < module.WorldState.CurrentTime); + } + + public override void OnActorCreated(BossModule module, Actor actor) + { + if ((OID)actor.OID == OID.BerserkerSphere) + { + _casters.Add(actor); + if (NumCasts == 0) + _activation = module.WorldState.CurrentTime.AddSeconds(10.8f); + else + _activation = module.WorldState.CurrentTime.AddSeconds(20); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SphereShatter) + { + _casters.Remove(caster); + ++NumCasts; + } + } + } + + class PraptiSiddhi : Components.SelfTargetedAOEs + { + public PraptiSiddhi() : base(ActionID.MakeSpell(AID.PraptiSiddhi), new AOEShapeRect(40, 2)) { } + } + + class PrakamyaSiddhi : Components.SelfTargetedAOEs + { + public PrakamyaSiddhi() : base(ActionID.MakeSpell(AID.PrakamyaSiddhi), new AOEShapeCircle(5)) { } + } + + class ManusyaConfuse : Components.CastHint + { + public ManusyaConfuse() : base(ActionID.MakeSpell(AID.ManusyaConfuse), "Applies Manyusa Confusion") { } + } + + class ManusyaStop : Components.CastHint + { + public ManusyaStop() : base(ActionID.MakeSpell(AID.ManusyaStop), "Applies Manyusa Stop") { } + } + + class D012SanduruvaStates : StateMachineBuilder + { + public D012SanduruvaStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 783, NameID = 10257)] + public class D012Sanduruva : BossModule + { + public D012Sanduruva(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-258, -26), 20)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy, true); + } + + public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + base.CalculateAIHints(slot, actor, assignment, hints); + { + foreach (var e in hints.PotentialTargets) + { + e.Priority = (OID)e.Actor.OID switch + { + OID.Boss => 1, + OID.BerserkerSphere => -1, + _ => 0 + }; + } + } + } + } +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D013MagusSisters.cs b/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D013MagusSisters.cs new file mode 100644 index 0000000000..74311acaff --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D01TowerOfZot/D013MagusSisters.cs @@ -0,0 +1,305 @@ +// CONTRIB: made by dhoggpt, improvements by Malediktus, not checked +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D01TheTowerOfZot.D013MagusSisters +{ + public enum OID : uint + { + Boss = 0x33F1, // R2.2 + Sanduruva = 0x33F2, // R=2.5 + Minduruva = 0x33F3, // R=2.04 + BerserkerSphere = 0x33F0, // R=1.5-2.5 + Helper = 0x233C, + Helper2 = 0x3610, + }; + + public enum AID : uint + { + AutoAttack = 871, // Sanduruva->player, no cast, single-target + Teleport = 25254, // Sanduruva->location, no cast, single-target + DeltaAttack = 25260, // Minduruva->Boss, 5.0s cast, single-target + DeltaAttack1= 25261, // Minduruva->Boss, 5.0s cast, single-target + DeltaAttack2 = 25262, // Minduruva->Boss, 5.0s cast, single-target + DeltaBlizzardIII1 = 25266, // Helper->self, 3.0s cast, range 40+R 20-degree cone + DeltaBlizzardIII2 = 25267, // Helper->self, 3.0s cast, range 44 width 4 rect + DeltaBlizzardIII3 = 25268, // Helper->location, 5.0s cast, range 40 circle + DeltaFireIII1 = 25263, // Helper->self, 4.0s cast, range 5-40 donut + DeltaFireIII2 = 25264, // Helper->self, 3.0s cast, range 44 width 10 rect + DeltaFireIII3 = 25265, // Helper->player, 5.0s cast, range 6 circle, spread + DeltaThunderIII1 = 25269, // Helper->location, 3.0s cast, range 3 circle + DeltaThunderIII2 = 25270, // Helper->location, 3.0s cast, range 5 circle + DeltaThunderIII3 = 25271, // Helper->self, 3.0s cast, range 40 width 10 rect + DeltaThunderIII4 = 25272, // Helper->player, 5.0s cast, range 5 circle, stack + Dhrupad = 25281, // Minduruva->self, 4.0s cast, single-target, after this each of the non-tank players get hit once by a single-target spell (ManusyaBlizzard1, ManusyaFire1, ManusyaThunder1) + IsitvaSiddhi = 25280, // Sanduruva->player, 4.0s cast, single-target, tankbuster + ManusyaBlizzard1 = 25283, // Minduruva->player, no cast, single-target + ManusyaBlizzard2 = 25288, // Minduruva->player, 2.0s cast, single-target + ManusyaFaith = 25258, // Sanduruva->Minduruva, 4.0s cast, single-target + ManusyaFire1 = 25282, // Minduruva->player, no cast, single-target + ManusyaFire2 = 25287, // Minduruva->player, 2.0s cast, single-target + ManusyaGlare = 25274, // Boss->none, no cast, single-target + ManusyaReflect = 25259, // Boss->self, 4.2s cast, range 40 circle + ManusyaThunder1 = 25284, // Minduruva->player, no cast, single-target + ManusyaThunder2 = 25289, // Minduruva->player, 2.0s cast, single-target + PraptiSiddhi = 25275, // Sanduruva->self, 2.0s cast, range 40 width 4 rect + Samsara = 25273, // Boss->self, 3.0s cast, range 40 circle + ManusyaBio = 25290, // Minduruva->player, 4,0s cast, single-target + ManusyaBerserk = 25276, // Sanduruva->self, 3,0s cast, single-target + ExplosiveForce = 25277, // Sanduruva->self, 2,0s cast, single-target + SphereShatter = 25279, // BerserkerSphere->self, 1,5s cast, range 15 circle + PrakamyaSiddhi = 25278, // Sanduruva->self, 4,0s cast, range 5 circle + ManusyaBlizzardIII = 25285, // Minduruva->self, 4,0s cast, single-target + ManusyaBlizzardIII2 = 25286, // Helper->self, 4,0s cast, range 40+R 20-degree cone + }; + + public enum SID : uint + { + Poison = 18, // Boss->player, extra=0x0 + Burns = 2082, // Minduruva->player, extra=0x0 + Frostbite = 2083, // Minduruva->player, extra=0x0 + Electrocution = 2086, // Minduruva->player, extra=0x0 + }; + + class Dhrupad : BossComponent + { + private int NumCasts; + private bool active; + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Dhrupad) + active = true; + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ManusyaFire1 or AID.ManusyaBlizzard1 or AID.ManusyaThunder1) + { + ++NumCasts; + if (NumCasts == 3) + { + NumCasts = 0; + active = false; + } + } + } + + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (active) + hints.Add("3 single target hits + DoTs"); + } + } + + class ManusyaBio : Components.SingleTargetCast + { + public ManusyaBio() : base(ActionID.MakeSpell(AID.ManusyaBio), "Tankbuster + cleansable poison") { } + } + + class Poison : BossComponent + { + private List _poisoned = []; + + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Poison) + _poisoned.Add(actor); + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Poison) + _poisoned.Remove(actor); + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (_poisoned.Contains(actor) && !(actor.Role == Role.Healer || actor.Class == Class.BRD)) //theoretically only the tank can ge poisoned, this is just in here incase of bad tanks + hints.Add("You were poisoned! Get cleansed fast."); + if (_poisoned.Contains(actor) && (actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add("Cleanse yourself! (Poison)."); + foreach (var c in _poisoned) + if (!_poisoned.Contains(actor) && (actor.Role == Role.Healer || actor.Class == Class.BRD)) + hints.Add($"Cleanse {c.Name} (Poison)"); + } + + 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 _poisoned) + { + if (_poisoned.Count > 0 && actor.Role == Role.Healer) + hints.PlannedActions.Add((ActionID.MakeSpell(WHM.AID.Esuna), c, 1, false)); + if (_poisoned.Count > 0 && actor.Class == Class.BRD) + hints.PlannedActions.Add((ActionID.MakeSpell(BRD.AID.WardensPaean), c, 1, false)); + } + } + } + + class IsitvaSiddhi : Components.SingleTargetCast + { + public IsitvaSiddhi() : base(ActionID.MakeSpell(AID.IsitvaSiddhi)) { } + } + + class Samsara : Components.RaidwideCast + { + public Samsara() : base(ActionID.MakeSpell(AID.Samsara)) { } + } + + class DeltaThunderIII1 : Components.LocationTargetedAOEs + { + public DeltaThunderIII1() : base(ActionID.MakeSpell(AID.DeltaThunderIII1), 3) { } + } + + class DeltaThunderIII2 : Components.LocationTargetedAOEs + { + public DeltaThunderIII2() : base(ActionID.MakeSpell(AID.DeltaThunderIII2), 5) { } + } + + class DeltaThunderIII3 : Components.SelfTargetedAOEs + { + public DeltaThunderIII3() : base(ActionID.MakeSpell(AID.DeltaThunderIII3), new AOEShapeRect(40, 5)) { } + } + + class DeltaThunderIII4 : Components.StackWithCastTargets + { + public DeltaThunderIII4() : base(ActionID.MakeSpell(AID.DeltaThunderIII4), 5) { } + } + + class DeltaBlizzardIII1 : Components.SelfTargetedAOEs + { + public DeltaBlizzardIII1() : base(ActionID.MakeSpell(AID.DeltaBlizzardIII1), new AOEShapeCone(40.5f, 10.Degrees())) { } + } + + class DeltaBlizzardIII2 : Components.SelfTargetedAOEs + { + public DeltaBlizzardIII2() : base(ActionID.MakeSpell(AID.DeltaBlizzardIII2), new AOEShapeRect(44, 2)) { } + } + + class DeltaBlizzardIII3 : Components.SelfTargetedAOEs + { + public DeltaBlizzardIII3() : base(ActionID.MakeSpell(AID.DeltaBlizzardIII3), new AOEShapeCircle(15)) { } + } + + class DeltaFireIII1 : Components.SelfTargetedAOEs + { + public DeltaFireIII1() : base(ActionID.MakeSpell(AID.DeltaFireIII1), new AOEShapeDonut(5, 40)) { } + } + + class DeltaFireIII2 : Components.SelfTargetedAOEs + { + public DeltaFireIII2() : base(ActionID.MakeSpell(AID.DeltaFireIII2), new AOEShapeRect(44, 5)) { } + } + + class DeltaFireIII3 : Components.SpreadFromCastTargets + { + public DeltaFireIII3() : base(ActionID.MakeSpell(AID.DeltaFireIII3), 6) { } + } + + class PraptiSiddhi : Components.SelfTargetedAOEs + { + public PraptiSiddhi() : base(ActionID.MakeSpell(AID.PraptiSiddhi), new AOEShapeRect(40, 2)) { } + } + + class SphereShatter : Components.GenericAOEs + { + private DateTime _activation; + private readonly List _casters = []; + private static readonly AOEShapeCircle circle = new(15); + 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 OnActorCreated(BossModule module, Actor actor) + { + if ((OID)actor.OID == OID.BerserkerSphere) + { + _casters.Add(actor); + _activation = module.WorldState.CurrentTime.AddSeconds(7.3f); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SphereShatter) + { + _casters.Remove(caster); + ++NumCasts; + } + } + } + + class PrakamyaSiddhi : Components.SelfTargetedAOEs + { + public PrakamyaSiddhi() : base(ActionID.MakeSpell(AID.PrakamyaSiddhi), new AOEShapeCircle(5)) { } + } + + class ManusyaBlizzardIII : Components.SelfTargetedAOEs + { + public ManusyaBlizzardIII() : base(ActionID.MakeSpell(AID.ManusyaBlizzardIII2), new AOEShapeCone(40.5f, 10.Degrees())) { } + } + + class D013MagusSistersStates : StateMachineBuilder + { + public D013MagusSistersStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.Sanduruva).All(e => e.IsDead) && module.Enemies(OID.Minduruva).All(e => e.IsDead); + } + } + + [ModuleInfo(CFCID = 783, NameID = 10265)] + class D013MagusSisters : BossModule + { + public D013MagusSisters(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-27.5f, -49.5f), 20)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy); + foreach (var s in Enemies(OID.Minduruva)) + Arena.Actor(s, ArenaColor.Enemy); + foreach (var s in Enemies(OID.Sanduruva)) + Arena.Actor(s, ArenaColor.Enemy); + } + + public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + base.CalculateAIHints(slot, actor, assignment, hints); + { + foreach (var e in hints.PotentialTargets) + { + e.Priority = (OID)e.Actor.OID switch + { + OID.Boss => 3, + OID.Minduruva => 2, + OID.Sanduruva => 1, + _ => 0 + }; + } + } + } + } +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D111Albion.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D111Albion.cs index 2f0e604e11..322ff852db 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D111Albion.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D111Albion.cs @@ -46,208 +46,126 @@ public enum IconID : uint 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; + private static readonly Angle _rot90 = 90.Degrees(); + private static readonly Angle _rotM90 = -90.Degrees(); + private (bool active, WPos position, Angle rotation, int count, DateTime reset, List beasts) stampede1; + private (bool active, WPos position, Angle rotation, int count, DateTime reset, List beasts) stampede2; + private readonly (bool, WPos, Angle, int, DateTime, List)[] stampedePositions = + [ + (true, new WPos(4, -759), _rot90, 0, default(DateTime), new List()), + (true, new WPos(44, -759), _rotM90, 0, default(DateTime), new List()), + (true, new WPos(4, -749), _rot90, 0, default(DateTime), new List()), + (true, new WPos(44, -749), _rotM90, 0, default(DateTime), new List()), + (true, new WPos(4, -739), _rot90, 0, default(DateTime), new List()), + (true, new WPos(44, -739), _rotM90, 0, default(DateTime), new List()), + (true, new WPos(4, -729), _rot90, 0, default(DateTime), new List()), + (true, new WPos(44, -729), _rotM90, 0, default(DateTime), new List()), + ]; + private bool Newstampede => stampede1 == 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()); + if (stampede1.active && stampede1.beasts.Count > 0) + yield return new(new AOEShapeRect(CalculateStampedeLength(stampede1.beasts) + 30, 5), new(stampede1.beasts.Last().Position.X, stampede1.position.Z), stampede1.rotation); + if (stampede2.active && stampede2.beasts.Count > 0) + yield return new(new AOEShapeRect(CalculateStampedeLength(stampede2.beasts) + 30, 5), new(stampede2.beasts.Last().Position.X, stampede2.position.Z), stampede2.rotation); + if (stampede1.active && stampede1.beasts.Count == 0) + yield return new(rect, stampede1.position, _rot90); + if (stampede2.active && stampede2.beasts.Count == 0) + yield return new(rect, stampede2.position, _rot90); } + private static float CalculateStampedeLength(List beasts) => (beasts.First().Position - beasts.Last().Position).Length(); + 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); - } + if (Newstampede) + stampede1 = stampedePositions[0]; else - { - active2 = true; - _rotation2 = 90.Degrees(); - stampede2 = new(4, -759); - } + stampede2 = stampedePositions[0]; if (index == 0x25) - if (newstampede) - { - active1 = true; - _rotation1 = -90.Degrees(); - stampede1 = new(44, -759); - } + if (Newstampede) + stampede1 = stampedePositions[1]; else - { - active2 = true; - _rotation2 = -90.Degrees(); - stampede2 = new(44, -759); - } + stampede2 = stampedePositions[1]; if (index == 0x22) - if (newstampede) - { - active1 = true; - _rotation1 = 90.Degrees(); - stampede1 = new(4, -749); - } + if (Newstampede) + stampede1 = stampedePositions[2]; else - { - active2 = true; - _rotation2 = 90.Degrees(); - stampede2 = new(4, -749); - } + stampede2 = stampedePositions[2]; if (index == 0x26) - if (newstampede) - { - active1 = true; - _rotation1 = -90.Degrees(); - stampede1 = new(44, -749); - } + if (Newstampede) + stampede1 = stampedePositions[3]; else - { - active2 = true; - _rotation2 = -90.Degrees(); - stampede2 = new(44, -749); - } + stampede2 = stampedePositions[3]; if (index == 0x23) - if (newstampede) - { - active1 = true; - _rotation1 = 90.Degrees(); - stampede1 = new(4, -739); - } + if (Newstampede) + stampede1 = stampedePositions[4]; else - { - active2 = true; - _rotation2 = 90.Degrees(); - stampede2 = new(4, -739); - } + stampede2 = stampedePositions[4]; if (index == 0x27) - if (newstampede) - { - active1 = true; - _rotation1 = -90.Degrees(); - stampede1 = new(44, -739); - } + if (Newstampede) + stampede1 = stampedePositions[5]; else - { - active2 = true; - _rotation2 = -90.Degrees(); - stampede2 = new(44, -739); - } + stampede2 = stampedePositions[5]; if (index == 0x24) - if (newstampede) - { - active1 = true; - _rotation1 = 90.Degrees(); - stampede1 = new(4, -729); - } + if (Newstampede) + stampede1 = stampedePositions[6]; else - { - active2 = true; - _rotation2 = 90.Degrees(); - stampede2 = new(4, -729); - } + stampede2 = stampedePositions[6]; if (index == 0x28) - if (newstampede) - { - active1 = true; - _rotation1 = -90.Degrees(); - stampede1 = new(44, -729); - } + if (Newstampede) + stampede1 = stampedePositions[7]; else - { - active2 = true; - _rotation2 = -90.Degrees(); - stampede2 = new(44, -729); - } + stampede2 = stampedePositions[7]; } } 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); + var stampede1Position = new WPos(24, stampede1.position.Z); + var stampede2Position = new WPos(24, stampede2.position.Z); - if (_reset1 != default && module.WorldState.CurrentTime > _reset1) + foreach (var oid in new[] { OID.WildBeasts4, OID.WildBeasts3, OID.WildBeasts2, OID.WildBeasts1 }) { - active1 = false; - stampede1counter = 0; - stampede1 = default; - beasts1.Clear(); - _reset1 = default; + var beasts = module.Enemies(oid); + foreach (var b in beasts) + { + if (b.Position.InRect(stampede1Position, stampede1.rotation, 33, 33, 5) && !stampede1.beasts.Contains(b)) + stampede1.beasts.Add(b); + if (b.Position.InRect(stampede2Position, stampede2.rotation, 33, 33, 5) && !stampede2.beasts.Contains(b)) + stampede2.beasts.Add(b); + } } - if (_reset2 != default && module.WorldState.CurrentTime > _reset2) - { - active2 = false; - stampede2counter = 0; + + if (stampede1.reset != default && module.WorldState.CurrentTime > stampede1.reset) + stampede1 = default; + if (stampede2.reset != default && module.WorldState.CurrentTime > stampede2.reset) 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); + if (MathF.Abs(caster.Position.Z - stampede1.position.Z) < 1) + ++stampede1.count; + if (MathF.Abs(caster.Position.Z - stampede2.position.Z) < 1) + ++stampede2.count; + if (stampede1.count == 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 + stampede1.reset = module.WorldState.CurrentTime.AddSeconds(0.5f); + if (stampede2.count == 30) + stampede1.reset = module.WorldState.CurrentTime.AddSeconds(0.5f); } } } class IcyThroes : Components.GenericBaitAway { - private readonly List _targets = new(); + private readonly List _targets = []; public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { @@ -277,7 +195,7 @@ public override void AddHints(BossModule module, int slot, Actor actor, TextHint class Icebreaker : Components.GenericAOEs { - private List _casters = new(); + private readonly List _casters = []; private static readonly AOEShapeCircle circle = new(17); private DateTime _activation; @@ -364,4 +282,4 @@ public class D111Albion : BossModule { public D111Albion(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/D112GalateaMagna.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs index 69a3577ad9..fd7f2dcfe3 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D112GalateaMagna.cs @@ -51,8 +51,8 @@ public enum SID : uint class ScarecrowChase : Components.GenericAOEs { - private List<(Actor actor, uint icon)> _casters = new(); - private List _casterssorted = new(); + private readonly List<(Actor actor, uint icon)> _casters = []; + private List _casterssorted = []; private static readonly AOEShapeCross cross = new(40, 5); private DateTime _activation; @@ -70,19 +70,14 @@ public override IEnumerable ActiveAOEs(BossModule module, int slot, public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { - if (iconID == (uint)IconID.Icon1) + var icon = (IconID)iconID; + if (icon >= IconID.Icon1 && icon <= IconID.Icon4) { _casters.Add((actor, iconID)); - _activation = module.WorldState.CurrentTime.AddSeconds(9.9f); + if (_activation == default) + _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(); + _casterssorted = _casters.OrderBy(x => x.icon).Select(x => x.actor).ToList(); } public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) @@ -91,14 +86,17 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn { _casterssorted.RemoveAt(0); if (_casterssorted.Count == 0) + { _casters.Clear(); + _activation = default; + } } } } class OutInAOE : Components.ConcentricAOEs { - private static readonly AOEShape[] _shapes = { new AOEShapeCircle(10), new AOEShapeDonut(10, 40) }; + private static readonly AOEShape[] _shapes = [new AOEShapeCircle(10), new AOEShapeDonut(10, 40)]; public OutInAOE() : base(_shapes) { } @@ -153,7 +151,7 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent class GlassyEyed : Components.GenericGaze { private DateTime _activation; - private List _affected = new(); + private readonly List _affected = []; public override IEnumerable ActiveEyes(BossModule module, int slot, Actor actor) { @@ -180,16 +178,29 @@ public override void OnStatusLose(BossModule module, Actor actor, ActorStatus st public class TenebrismTowers : Components.GenericTowers { + private WPos position; 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)); + + if (state == 0x00010008) + { + switch (index) + { + case 0x07: + position = new(350, -404); + break; + case 0x08: + position = new(360, -394); + break; + case 0x09: + position = new(350, -384); + break; + case 0x0A: + position = new(340, -394); + break; + } + Towers.Add(new(position, 5, 1, 1)); + } } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) @@ -207,7 +218,7 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR class Doom : BossComponent { - private List _doomed = new(); + private readonly List _doomed = []; public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) { diff --git a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D113Cagnazzo.cs b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D113Cagnazzo.cs index 30d113d803..660eb87f4d 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D113Cagnazzo.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D11LapisManalis/D113Cagnazzo.cs @@ -118,7 +118,7 @@ public BodySlamKB() : base(ActionID.MakeSpell(AID.BodySlam2), 10) { } class HydraulicRam : Components.GenericAOEs { - private List<(WPos source, AOEShape shape, Angle direction)> _casters = new(); + private readonly List<(WPos source, AOEShape shape, Angle direction)> _casters = []; private DateTime _activation; public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) @@ -149,7 +149,7 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent class Hydrobomb : Components.GenericAOEs { - private List _casters = new(); + private readonly List _casters = []; private static readonly AOEShapeCircle circle = new(4); private DateTime _activation; diff --git a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D121Lyngbakr.cs b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D121Lyngbakr.cs index c0cb3390b1..4b166b50f5 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D121Lyngbakr.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D121Lyngbakr.cs @@ -1,37 +1,82 @@ -// CONTRIB: made by dhoggpt, not checked +// CONTRIB: made by dhoggpt, improvements by Malediktus, not checked +using System; +using System.Collections.Generic; + namespace BossMod.Endwalker.Dungeon.D12Aetherfont.D121Lyngbakr { public enum OID : uint { - Boss = 0x3EEB, // ??? - Helper = 0x233C, // ??? - Actor1e8f2f = 0x1E8F2F, // R0.500, x1, EventObj type - Actor1e8fb8 = 0x1E8FB8, // R2.000, x2, EventObj type - Actor1eb882 = 0x1EB882, // R0.500, EventObj type, spawn during fight - Actor1eb883 = 0x1EB883, // R0.500, EventObj type, spawn during fight + Boss = 0x3EEB, //R=7.6 + Helper = 0x233C, + SmallCrystal = 0x1EB882, // R=0.5 + BigCrystal = 0x1EB883, // R=0.5 }; public enum AID : uint { - BodySlam = 33335, // Boss->self, 3.0s cast, range 40 circle Lyngbakr moves to the center of the area and unleashes a arena-wide AoE with moderate damage to the entire party and spawn crystals of various sizes that will cast an AoE dependent on the crystals size once Upsweep is cast. - SonicBloop = 33345, // Boss->none, 5.0s cast, single-target Moderate damage tank buster. - ExplosiveFrequency = 33340, // Helper->self, 10.0s cast, range 15 circle i think this is the exploding crystals + AutoAttack = 34517, // Boss->player, no cast, single-target + BodySlam = 33335, // Boss->self, 3.0s cast, range 40 circle + SonicBloop = 33345, // Boss->player, 5.0s cast, single-target, tankbuster + ExplosiveFrequency = 33340, // Helper->self, 10.0s cast, range 15 circle ResonantFrequency = 33339, // Helper->self, 5.0s cast, range 8 circle - Floodstide = 33341, // Boss->self, 3.0s cast, single-target Casts AoE markers on all party members that do light damage - TidalBreath = 33344, // Boss->self, 5.0s cast, range 40 180-degree cone Lyngbakr turns around and casts a Large arena-wide AoE with high damage to anyone not behind them. - Tidalspout = 33343, // Helper->none, 5.0s cast, range 6 circle - Upsweep = 33338, // Boss->self, 5.0s cast, range 40 circle Moderate damage to the entire party. Will cause any crystals formed to cast a wide or small AoE dependent on crystal size. - Waterspout = 33342, // Helper->player, 5.0s cast, range 5 circle THIS IS THE SPREADER + TidalBreath = 33344, // Boss->self, 5.0s cast, range 40 180-degree cone + Tidalspout = 33343, // Helper->player, 5.0s cast, range 6 circle + Upsweep = 33338, // Boss->self, 5.0s cast, range 40 circle + Floodstide = 33341, // Boss->self, 3.0s cast, single-target + Waterspout = 33342, // Helper->player, 5.0s cast, range 5 circle, spread }; - class SonicBloop : Components.StackWithCastTargets + class Frequencies : Components.GenericAOEs { - public SonicBloop() : base(ActionID.MakeSpell(AID.SonicBloop),3,4) { } + private static readonly AOEShapeCircle smallcircle = new(8); + private static readonly AOEShapeCircle bigcircle = new(15); + private DateTime _activation1; + private DateTime _activation2; + private readonly List _bigcrystals = []; + private readonly List _smallcrystals = []; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_smallcrystals.Count > 0) + foreach (var c in _smallcrystals) + yield return new(smallcircle, c.Position, activation: _activation1); + if (_bigcrystals.Count > 0 && _smallcrystals.Count == 0) + foreach (var c in _bigcrystals) + yield return new(bigcircle, c.Position, activation: _activation2); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.ResonantFrequency) + { + _activation1 = spell.NPCFinishAt; + _smallcrystals.Add(caster); + } + if ((AID)spell.Action.ID == AID.ExplosiveFrequency) + { + _activation2 = spell.NPCFinishAt; + _bigcrystals.Add(caster); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.ResonantFrequency or AID.ExplosiveFrequency) + { + _smallcrystals.Remove(caster); + _bigcrystals.Remove(caster); + } + } + } + + class SonicBloop : Components.SingleTargetCast + { + public SonicBloop() : base(ActionID.MakeSpell(AID.SonicBloop)) { } } class Waterspout : Components.SpreadFromCastTargets { - public Waterspout() : base(ActionID.MakeSpell(AID.Waterspout),5) { } + public Waterspout() : base(ActionID.MakeSpell(AID.Waterspout), 5) { } } class TidalBreath : Components.SelfTargetedAOEs @@ -39,9 +84,9 @@ class TidalBreath : Components.SelfTargetedAOEs public TidalBreath() : base(ActionID.MakeSpell(AID.TidalBreath), new AOEShapeCone(40, 90.Degrees())) { } } - class Tidalspout : Components.SelfTargetedAOEs + class Tidalspout : Components.StackWithCastTargets { - public Tidalspout() : base(ActionID.MakeSpell(AID.Tidalspout), new AOEShapeCircle(6)) { } + public Tidalspout() : base(ActionID.MakeSpell(AID.Tidalspout), 6) { } } class Upsweep : Components.RaidwideCast @@ -54,16 +99,6 @@ class BodySlam : Components.RaidwideCast public BodySlam() : base(ActionID.MakeSpell(AID.BodySlam)) { } } - class ExplosiveFrequency : Components.SelfTargetedAOEs - { - public ExplosiveFrequency() : base(ActionID.MakeSpell(AID.ExplosiveFrequency), new AOEShapeCircle(15)) { } - } - - class ResonantFrequency : Components.SelfTargetedAOEs - { - public ResonantFrequency() : base(ActionID.MakeSpell(AID.ResonantFrequency), new AOEShapeCircle(8)) { } - } - class D121LyngbakrStates : StateMachineBuilder { public D121LyngbakrStates(BossModule module) : base(module) @@ -75,8 +110,7 @@ public D121LyngbakrStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter(); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D122Arkas.cs b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D122Arkas.cs index 005aa1ef45..64ba3c743b 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D122Arkas.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D122Arkas.cs @@ -1,80 +1,125 @@ -// CONTRIB: made by dhoggpt, not checked +// CONTRIB: made by dhoggpt, improvements by Malediktus, not checked +using System.Collections.Generic; +using System.Linq; + namespace BossMod.Endwalker.Dungeon.D12Aetherfont.D122Arkas { public enum OID : uint { - Boss = 0x3EEA, // ??? - Helper = 0x233C, // ??? + Boss = 0x3EEA, //R=5.1 + Helper = 0x233C, }; public enum AID : uint { - AutoAttack = 870, // Boss->none, no cast, single-target + AutoAttack = 870, // Boss->player, no cast, single-target BattleCry1 = 33364, // Boss->self, 5.0s cast, range 40 circle BattleCry2 = 34605, // Boss->self, 5.0s cast, range 40 circle ElectricEruption = 33615, // Boss->self, 5.0s cast, range 40 circle Electrify = 33367, // Helper->location, 5.5s cast, range 10 circle LightningClaw1 = 33366, // Boss->location, no cast, range 6 circle - LightningClaw2 = 34712, // Boss->none, 5.0s cast, single-target - LightningLeap1 = 33358, // Boss->location, 4.0s cast, single-target - LightningLeap2 = 33359, // Boss->location, 5.0s cast, single-target - LightningLeap3 = 33360, // Helper->location, 6.0s cast, range 10 circle - LightningLeap4 = 34713, // Helper->location, 5.0s cast, range 10 circle - LightningRampage1 = 34318, // Boss->location, 4.0s cast, single-target - LightningRampage2 = 34319, // Boss->location, 2.0s cast, single-target - LightningRampage3 = 34320, // Boss->location, 2.0s cast, single-target - LightningRampage4 = 34321, // Helper->location, 5.0s cast, range 10 circle - LightningRampage5 = 34714, // Helper->location, 5.0s cast, range 10 circle - RipperClaw = 33368, // Boss->none, 5.0s cast, single-target + LightningClaw2 = 34712, // Boss->player, 5.0s cast, single-target + LightningLeapA = 33358, // Boss->location, 4.0s cast, single-target + LightningLeapB = 33359, // Boss->location, 5.0s cast, single-target + LightningLeap1 = 33360, // Helper->location, 6.0s cast, range 10 circle + LightningLeap2 = 34713, // Helper->location, 5.0s cast, range 10 circle + LightningRampageA = 34318, // Boss->location, 4.0s cast, single-target + LightningRampageB = 34319, // Boss->location, 2.0s cast, single-target + LightningRampageC = 34320, // Boss->location, 2.0s cast, single-target + LightningRampage1 = 34321, // Helper->location, 5.0s cast, range 10 circle + LightningRampage2 = 34714, // Helper->location, 5.0s cast, range 10 circle + RipperClaw = 33368, // Boss->player, 5.0s cast, single-target Shock = 33365, // Helper->location, 3.5s cast, range 6 circle SpinningClaw = 33362, // Boss->self, 3.5s cast, range 10 circle ForkedFissures = 33361, // Helper->location, 1.0s cast, width 4 rect charge SpunLightning = 33363, // Helper->self, 3.5s cast, range 30 width 8 rect }; - class LightningRampage1 : Components.LocationTargetedAOEs + public enum IconID : uint { - public LightningRampage1() : base(ActionID.MakeSpell(AID.LightningRampage1), 5) { } - } + Tankbuster = 218, // player + Stackmarker = 161, // 39D7/3DC2 + }; - class LightningRampage2 : Components.LocationTargetedAOEs + class Voidzone : BossComponent { - public LightningRampage2() : base(ActionID.MakeSpell(AID.LightningRampage2), 5) { } + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (index == 0x00) + { + if (state == 0x00020001) + module.Arena.Bounds = new ArenaBoundsCircle(new(425, -440), 10); + if (state == 0x00080004) + module.Arena.Bounds = new ArenaBoundsCircle(new(425, -440), 14.5f); + } + } } - class LightningRampage3 : Components.LocationTargetedAOEs + class SpunLightning : Components.SelfTargetedAOEs { - public LightningRampage3() : base(ActionID.MakeSpell(AID.LightningRampage3), 5) { } + public SpunLightning() : base(ActionID.MakeSpell(AID.SpunLightning), new AOEShapeRect(30, 4)) { } } - class LightningLeap1 : Components.LocationTargetedAOEs + class LightningClaw : Components.StackWithIcon { - public LightningLeap1() : base(ActionID.MakeSpell(AID.LightningLeap1), 5) { } + public LightningClaw() : base((uint)IconID.Stackmarker, ActionID.MakeSpell(AID.LightningClaw2), 6, 5.2f, 4) { } } - class LightningLeap2 : Components.LocationTargetedAOEs + class ForkedFissures : Components.GenericAOEs { - public LightningLeap2() : base(ActionID.MakeSpell(AID.LightningLeap2), 5) { } - } + private static readonly WPos[] patternIndex04Start = [new(419.293f, -445.661f), new(422.919f, -448.675f), new(419.359f, -445.715f), new(419.333f, -437.25f), new(432.791f, -434.82f), new(423.239f, -442.489f), new(426.377f, -437.596f), new(419.335f, -445.663f), new(417.162f, -442.421f), new(427.274f, -448.618f), new(430.839f, -441.877f), new(419.292f, -445.596f), new(427.482f, -448.548f), new(419.101f, -434.242f), new(424.274f, -433.427f), new(419.326f, -445.681f)]; + private static readonly WPos[] patternIndex04End = [new(420.035f, -454.124f), new(427.42f, -448.692f), new(412.039f, -447.562f), new(417.533f, -427.085f), new(433.860f, -427.97f), new(426.993f, -437.034f), new(433.646f, -433.433f), new(411.276f, -434.165f), new(419.394f, -436.118f), new(430.442f, -453.971f), new(439.872f, -438.59f), new(423.667f, -442.039f), new(431.815f, -441.032f), new(425.437f, -432.547f), new(428.824f, -425.528f), new(424.002f, -448.966f)]; + private static readonly WPos[] patternIndex03Start = [new(419.343f, -434.343f), new(416.325f, -437.829f), new(419.304f, -434.353f), new(427.97f, -434.253f), new(430.244f, -447.772f), new(422.523f, -438.223f), new(427.408f, -441.363f), new(419.274f, -434.245f), new(422.582f, -432.152f), new(416.35f, -442.222f), new(423.09f, -445.755f), new(419.412f, -434.285f), new(419.294f, -434.309f), new(416.47f, -442.448f), new(430.633f, -433.69f), new(431.389f, -439.114f)]; + private static readonly WPos[] patternIndex03End = [new(410.880f, -435.019f), new(416.312f, -442.557f), new(417.441f, -427.085f), new(437.949f, -432.547f), new(436.942f, -448.875f), new(428.031f, -442.039f), new(431.571f, -448.63f), new(430.9f, -426.261f), new(428.916f, -434.379f), new(411.032f, -445.457f), new(426.413f, -454.917f), new(422.934f, -438.59f), new(416.037f, -438.926f), new(423.941f, -446.738f), new(432.364f, -440.177f), new(439.475f, -443.809f)]; + private static readonly WPos[] patternIndex02Start = [new(430.635f, -434.592f), new(430.708f, -434.484f), new(434.518f, -440.005f), new(424.457f, -445.105f), new(430.834f, -434.374f), new(431.156f, -439.05f), new(430.599f, -434.383f), new(434.571f, -440.454f), new(423.033f, -437.371f), new(422.069f, -437.329f), new(419.287f, -441.464f), new(430.513f, -434.548f), new(417.501f, -435.027f), new(431.252f, -446.301f), new(430.458f, -434.36f), new(425.28f, -430.49f)]; + private static readonly WPos[] patternIndex02End = [new(439.139f, -435.325f), new(422.232f, -437.583f), new(431.083f, -446.983f), new(420.279f, -454.215f), new(429.831f, -440.299f), new(424.063f, -445.762f), new(434.379f, -440.269f), new(439.811f, -441.733f), new(411.612f, -433.28f), new(418.936f, -441.794f), new(412.07f, -447.532f), new(424.308f, -430.228f), new(410.025f, -440.269f), new(430.594f, -453.88f), new(429.587f, -425.834f), new(414.298f, -429.557f)]; + private static readonly WPos[] patternIndex01Start = [new(430.357f, -445.557f), new(434.507f, -440.159f), new(430.357f, -445.557f), new(424.561f, -449.554f), new(425.887f, -446.107f), new(423.516f, -434.294f), new(430.346f, -445.616f), new(419.902f, -439.485f), new(430.357f, -445.557f), new(430.404f, -445.54f), new(429.973f, -432.501f), new(427.648f, -437.101f), new(430.357f, -445.557f), new(427.648f, -438.04f), new(424.713f, -449.483f), new(418.756f, -446.251f)]; + private static readonly WPos[] patternIndex01End = [new(439.2f, -444.602f), new(435.416f, -429.313f), new(429.618f, -454.246f), new(423.24f, -454.887f), new(419.303f, -439.078f), new(417.441f, -427.054f), new(424.704f, -444.846f), new(410.757f, -435.294f), new(424.643f, -449.577f), new(427.451f, -437.247f), new(424.796f, -425.132f), new(423.148f, -433.951f), new(434.867f, -439.109f), new(431.693f, -426.627f), new(418.051f, -446.036f), new(411.063f, -445.579f)]; - class LightningClaw2 : Components.LocationTargetedAOEs - { - public LightningClaw2() : base(ActionID.MakeSpell(AID.LightningClaw2), 5) { } - } + private readonly List _patternStart = []; + private readonly List _patternEnd = []; + private readonly List _aoes = []; - class SpunLightning : Components.SelfTargetedAOEs - { - public SpunLightning() : base(ActionID.MakeSpell(AID.SpunLightning), new AOEShapeRect(8, 4, -50)) { } - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(16); - class LightningClaw1 : Components.LocationTargetedAOEs - { - public LightningClaw1() : base(ActionID.MakeSpell(AID.LightningClaw1), 6) { } - } + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (state is 0x00200010 or 0x00020001) + { + if (index == 0x04) + { + _patternStart.AddRange(patternIndex04Start); + _patternEnd.AddRange(patternIndex04End); + } + if (index == 0x03) + { + _patternStart.AddRange(patternIndex03Start); + _patternEnd.AddRange(patternIndex03End); + } + if (index == 0x02) + { + _patternStart.AddRange(patternIndex02Start); + _patternEnd.AddRange(patternIndex02End); + } + if (index == 0x01) + { + _patternStart.AddRange(patternIndex01Start); + _patternEnd.AddRange(patternIndex01End); + } + for (int i = 0; i < _patternEnd.Count; ++i) + _aoes.Add(new (new AOEShapeRect((_patternEnd[i] - _patternStart[i]).Length(), 2), _patternStart[i], Angle.FromDirection(_patternEnd[i] - _patternStart[i]), module.WorldState.CurrentTime.AddSeconds(6))); + } + } - class ForkedFissures : Components.ChargeAOEs - { - public ForkedFissures() : base(ActionID.MakeSpell(AID.ForkedFissures), 2) { } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.ForkedFissures && _aoes.Count > 0 && _patternStart.Count > 0 && _patternEnd.Count > 0) + { + _aoes.RemoveAt(0); + _patternStart.RemoveAt(0); + _patternEnd.RemoveAt(0); + } + } } class ElectricEruption : Components.RaidwideCast @@ -87,24 +132,24 @@ class Electrify : Components.LocationTargetedAOEs public Electrify() : base(ActionID.MakeSpell(AID.Electrify), 10) { } } - class LightningLeap3 : Components.LocationTargetedAOEs + class LightningLeap1 : Components.LocationTargetedAOEs { - public LightningLeap3() : base(ActionID.MakeSpell(AID.LightningLeap3), 10) { } + public LightningLeap1() : base(ActionID.MakeSpell(AID.LightningLeap1), 10) { } } - class LightningLeap4 : Components.LocationTargetedAOEs + class LightningLeap2 : Components.LocationTargetedAOEs { - public LightningLeap4() : base(ActionID.MakeSpell(AID.LightningLeap4), 10) { } + public LightningLeap2() : base(ActionID.MakeSpell(AID.LightningLeap2), 10) { } } - class LightningRampage5 : Components.LocationTargetedAOEs + class LightningRampage1 : Components.LocationTargetedAOEs { - public LightningRampage5() : base(ActionID.MakeSpell(AID.LightningRampage5), 10) { } + public LightningRampage1() : base(ActionID.MakeSpell(AID.LightningRampage1), 10) { } } - class LightningRampage4 : Components.LocationTargetedAOEs + class LightningRampage2 : Components.LocationTargetedAOEs { - public LightningRampage4() : base(ActionID.MakeSpell(AID.LightningRampage4), 10) { } + public LightningRampage2() : base(ActionID.MakeSpell(AID.LightningRampage2), 10) { } } class RipperClaw : Components.SingleTargetCast @@ -129,7 +174,7 @@ public BattleCry1() : base(ActionID.MakeSpell(AID.BattleCry1)) { } class BattleCry2 : Components.RaidwideCast { - public BattleCry2() : base(ActionID.MakeSpell(AID.BattleCry1)) { } + public BattleCry2() : base(ActionID.MakeSpell(AID.BattleCry2)) { } } class D122ArkasStates : StateMachineBuilder @@ -137,21 +182,16 @@ class D122ArkasStates : StateMachineBuilder public D122ArkasStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() @@ -163,6 +203,6 @@ public D122ArkasStates(BossModule module) : base(module) [ModuleInfo(CFCID = 822, NameID = 12337)] public class D122Arkas : BossModule { - public D122Arkas(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(425, -440), 12)) { } + public D122Arkas(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(425, -440), 14.5f)) { } } -} +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs index 6ffb0a9974..1bcd90940c 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D12Aetherfont/D123Octomammoth.cs @@ -1,49 +1,46 @@ using System; using System.Linq; -// CONTRIB: made by dhoggpt, not checked +// CONTRIB: made by dhoggpt, improvements by veyn & Malediktus, not checked namespace BossMod.Endwalker.Dungeon.D12Aetherfont.D123Octomammoth { public enum OID : uint { - Boss = 0x3EAA, // R26.000, x1 - MammothTentacle = 0x3EAB, // R6.000, x8, 523 type - Helper = 0x233C, // ??? + Boss = 0x3EAA, // R=26.0 + MammothTentacle = 0x3EAB, // R=6.0 + Crystals = 0x3EAC, // R=0.5 + Helper = 0x233C, }; public enum AID : uint { - BossSelfUKN = 33350, // Boss->self, no cast, single-target (ignored) + AutoAttack = 33357, // Boss->player, no cast, single-target Breathstroke = 34551, // Boss->self, 16.5s cast, range 35 180-degree cone Clearout = 33348, // MammothTentacle->self, 9.0s cast, range 16 120-degree cone - Octostroke = 33347, // Boss->self, 16.0s cast, single-target (GUESSING SIZE) + Octostroke = 33347, // Boss->self, 16.0s cast, single-target SalineSpit1 = 33352, // Boss->self, 3.0s cast, single-target SalineSpit2 = 33353, // Helper->self, 6.0s cast, range 8 circle - Telekinesis1 = 33349, // Boss->self, 5.0s cast, single-target (ignored. the helper does the telegraph) + Telekinesis1 = 33349, // Boss->self, 5.0s cast, single-target Telekinesis2 = 33351, // Helper->self, 10.0s cast, range 12 circle TidalBreath = 33354, // Boss->self, 10.0s cast, range 35 180-degree cone TidalRoar = 33356, // Boss->self, 5.0s cast, range 60 circle - VividEyes = 33355, // Boss->self, 4.0s cast, range ?-26 donut + VividEyes = 33355, // Boss->self, 4.0s cast, range 20-26 donut WaterDrop = 34436, // Helper->player, 5.0s cast, range 6 circle - Wallop = 33346, // MammothTentacle->self, 3.0s cast, range 22 width 8 rect - idk if i did this one right - }; - - public enum TetherID : uint - { - Telekinesis = 167, // Actor3eac->Boss + WallopVisual = 33350, // Boss->self, no cast, single-target, visual, starts tentacle wallops + Wallop = 33346, // MammothTentacle->self, 3.0s cast, range 22 width 8 rect }; class Border : BossComponent { - private static float _platformOffset = 25; - private static float _platformRadius = 8; - private static Angle[] _platformDirections = [-90.Degrees(), -45.Degrees(), 0.Degrees(), 45.Degrees(), 90.Degrees()]; - private static WDir[] _platformCenters = _platformDirections.Select(d => _platformOffset * d.ToDirection()).ToArray(); + private static readonly float _platformOffset = 25; + private static readonly float _platformRadius = 8; + private static readonly Angle[] _platformDirections = [-90.Degrees(), -45.Degrees(), 0.Degrees(), 45.Degrees(), 90.Degrees()]; + private static readonly WDir[] _platformCenters = _platformDirections.Select(d => _platformOffset * d.ToDirection()).ToArray(); - private static float _bridgeInner = 20; - private static float _bridgeOuter = 26; - private static Angle _offInner = DirToPointAtDistance(_bridgeInner); - private static Angle _offOuter = DirToPointAtDistance(_bridgeOuter); + private static readonly float _bridgeInner = 22; + private static readonly float _bridgeOuter = 26; + private static readonly Angle _offInner = DirToPointAtDistance(_bridgeInner); + private static readonly Angle _offOuter = DirToPointAtDistance(_bridgeOuter); public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { @@ -86,12 +83,7 @@ private void DrawBridgeLine(MiniArena arena, WPos center, Angle from, Angle to, class Wallop : Components.SelfTargetedAOEs { - public Wallop() : base(ActionID.MakeSpell(AID.Wallop), new AOEShapeRect(11, 5.5f, 16)) { } - } - - class SalineSpit1 : Components.SpreadFromCastTargets - { - public SalineSpit1() : base(ActionID.MakeSpell(AID.SalineSpit1), 3) { } + public Wallop() : base(ActionID.MakeSpell(AID.Wallop), new AOEShapeRect(22, 4)) { } } class VividEyes : Components.SelfTargetedAOEs @@ -114,29 +106,24 @@ class Breathstroke : Components.SelfTargetedAOEs public Breathstroke() : base(ActionID.MakeSpell(AID.Breathstroke), new AOEShapeCone(35, 90.Degrees())) { } } - class Octostroke : Components.SelfTargetedAOEs - { - public Octostroke() : base(ActionID.MakeSpell(AID.Octostroke), new AOEShapeCircle(3)) { } - } - class TidalRoar : Components.RaidwideCast { public TidalRoar() : base(ActionID.MakeSpell(AID.TidalRoar)) { } } - class WaterDrop : Components.SelfTargetedAOEs + class WaterDrop : Components.SpreadFromCastTargets { - public WaterDrop() : base(ActionID.MakeSpell(AID.WaterDrop), new AOEShapeCircle(6)) { } + public WaterDrop() : base(ActionID.MakeSpell(AID.WaterDrop), 6) { } } - class SalineSpit2 : Components.SelfTargetedAOEs + class SalineSpit : Components.SelfTargetedAOEs { - public SalineSpit2() : base(ActionID.MakeSpell(AID.SalineSpit2), new AOEShapeCircle(8)) { } + public SalineSpit() : base(ActionID.MakeSpell(AID.SalineSpit2), new AOEShapeCircle(8)) { } } - class Telekinesis2 : Components.SelfTargetedAOEs + class Telekinesis : Components.SelfTargetedAOEs { - public Telekinesis2() : base(ActionID.MakeSpell(AID.Telekinesis2), new AOEShapeCircle(12)) { } + public Telekinesis() : base(ActionID.MakeSpell(AID.Telekinesis2), new AOEShapeCircle(12)) { } } class D123OctomammothStates : StateMachineBuilder @@ -144,24 +131,24 @@ class D123OctomammothStates : StateMachineBuilder public D123OctomammothStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter(); } } [ModuleInfo(CFCID = 822, NameID = 12334)] class D123Octomammoth : BossModule { - public D123Octomammoth(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-370, -368), 36)) { } + public D123Octomammoth(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(-370, -368), 33.3f)) + { + ActivateComponent(); + } } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D131DarkElf.cs b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D131DarkElf.cs new file mode 100644 index 0000000000..4109786c5c --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D131DarkElf.cs @@ -0,0 +1,157 @@ +// CONTRIB: made by Malediktus, not checked +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13TheLunarSubterrane.D121Lyngbakr +{ + public enum OID : uint + { + Boss = 0x3FE2, // R=5.0 + HexingStaff = 0x3FE3, // R=1.2 + Helper = 0x233C, + }; + + public enum AID : uint + { + AutoAttack = 872, // 3FE2->player, no cast, single-target + HexingStaves = 34777, // 3FE2->self, 3,0s cast, single-target + RuinousHex = 34783, // 3FE3->self, 5,0s cast, single-target + RuinousHex2 = 35254, // 3FE3->self, 5,0s cast, range 40 width 8 cross + RuinousHex3 = 34789, // 233C->self, 5,5s cast, range 40 width 8 cross + RuinousConfluence = 35205, // 3FE2->self, 5,0s cast, single-target + ShadowySigil = 34779, // 3FE2->self, 6,0s cast, single-target + ShadowySigil2 = 34780, // 3FE2->self, 6,0s cast, single-target + Explosion = 34787, // 233C->self, 6,5s cast, range 8 width 8 rect + SorcerousShroud = 34778, // 3FE2->self, 5,0s cast, single-target + VoidDarkII = 34781, // 3FE2->self, 2,5s cast, single-target + VoidDarkII2 = 34788, // 233C->player, 5,0s cast, range 6 circle + StaffSmite = 35204, // 3FE2->player, 5,0s cast, single-target + AbyssalOutburst = 34782, // 3FE2->self, 5,0s cast, range 60 circle + }; + + public enum SID : uint + { + Doom = 3364, // none->player, extra=0x0 + }; + + class HexingStaves : Components.GenericAOEs + { + private readonly List _staves = new(); + private static readonly AOEShapeCross cross = new(40, 4); + private DateTime _activation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (module.FindComponent() != null && !module.FindComponent()!.ActiveAOEs(module, slot, actor).Any()) + foreach (var c in _staves) + yield return new(cross, c.Position, c.Rotation, _activation, risky: _activation.AddSeconds(-5) < module.WorldState.CurrentTime); + } + + public override void OnActorModelStateChange(BossModule module, Actor actor, byte ModelState, byte AnimState1, byte AnimState2) + { + if ((OID)actor.OID == OID.HexingStaff) + { + if (AnimState2 == 1) + { + _staves.Add(actor); + if (NumCasts == 0) + _activation = module.WorldState.CurrentTime.AddSeconds(8.1f); + if (NumCasts == 1) + _activation = module.WorldState.CurrentTime.AddSeconds(25.9f); + if (NumCasts > 1) + _activation = module.WorldState.CurrentTime.AddSeconds(32); + } + if (AnimState2 == 0) + _staves.Remove(actor); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.RuinousConfluence) + ++NumCasts; + } + } + + class StaffSmite : Components.SingleTargetCast + { + public StaffSmite() : base(ActionID.MakeSpell(AID.StaffSmite)) { } + } + + class VoidDarkII : Components.SpreadFromCastTargets + { + public VoidDarkII() : base(ActionID.MakeSpell(AID.VoidDarkII2), 6) { } + } + + class Explosion : Components.SelfTargetedAOEs + { + public Explosion() : base(ActionID.MakeSpell(AID.Explosion), new AOEShapeRect(4, 4, 4)) { } + } + + class AbyssalOutburst : Components.RaidwideCast + { + public AbyssalOutburst() : base(ActionID.MakeSpell(AID.AbyssalOutburst)) { } + } + + 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 D131DarkElfStates : StateMachineBuilder + { + public D131DarkElfStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 823, NameID = 12500)] + public class D131DarkElf : BossModule + { + public D131DarkElf(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-401, -231), 15.5f)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntilon.cs b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntilon.cs new file mode 100644 index 0000000000..0652047e14 --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D132DamcyanAntilon.cs @@ -0,0 +1,196 @@ +// CONTRIB: made by Malediktus, not checked +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13TheLunarSubterrane.D132DamcyanAntilon +{ + public enum OID : uint + { + Boss = 0x4022, // R=7.5 + StonePillar = 0x4023, // R=3.0 + StonePillar2 = 0x3FD1, // R=1.5 + QuicksandVoidzone = 0x1EB90E, + Helper = 0x233C, + }; + + public enum AID : uint + { + AutoAttack = 872, // Boss, no cast, single-target + Sandblast = 34813, // Boss->self, 5,0s cast, range 60 circle + Landslip = 34818, // Boss->self, 7,0s cast, single-target + Landslip2 = 34819, // Helper->self, 7,7s cast, range 40 width 10 rect, knockback dir 20 forward + Teleport = 34824, // Boss->location, no cast, single-target + AntilonMarchTelegraph = 35871, // Helper->location, 1,5s cast, width 8 rect charge + AntlionMarch = 34816, // Boss->self, 5,5s cast, single-target + AntlionMarch2 = 34817, // Boss->location, no cast, width 8 rect charge + Towerfall = 34820, // StonePillar->self, 2,0s cast, range 40 width 10 rect + EarthenGeyser = 34821, // Boss->self, 4,0s cast, single-target + EarthenGeyser2 = 34822, // Helper->players, 5,0s cast, range 10 circle + PoundSand = 34443, // Boss->location, 6,0s cast, range 12 circle + }; + + class Sandblast : Components.RaidwideCast + { + public Sandblast() : base(ActionID.MakeSpell(AID.Sandblast)) { } + } + + class Voidzone : BossComponent + { + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (state == 0x00020001 && index == 0x00) + module.Arena.Bounds = new ArenaBoundsRect(new(0, 60), 19.5f, 20); + } + } + + class Landslip : Components.Knockback + { + private List _casters = new(); + private DateTime _activation; + private static readonly AOEShapeRect rect = new(40, 5); + + public override IEnumerable Sources(BossModule module, int slot, Actor actor) + { + foreach (var c in _casters) + yield return new(c.Position, 20, _activation, rect, c.Rotation, Kind.DirForward); + } + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Landslip2) + { + _activation = spell.NPCFinishAt; + _casters.Add(caster); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Landslip2) + _casters.Remove(caster); + } + + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) + { + if (module.FindComponent() != null && module.FindComponent()!.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation))) + return true; + if (!module.Bounds.Contains(pos)) + return true; + else + return false; + } + } + + class EarthenGeyser : Components.StackWithCastTargets + { + public EarthenGeyser() : base(ActionID.MakeSpell(AID.EarthenGeyser2), 10) { } + } + + class QuicksandVoidzone : Components.PersistentVoidzone + { + public QuicksandVoidzone() : base(10, m => m.Enemies(OID.QuicksandVoidzone).Where(z => z.EventState != 7)) { } + } + + class PoundSand : Components.LocationTargetedAOEs + { + public PoundSand() : base(ActionID.MakeSpell(AID.PoundSand), 12) { } + } + + class AntlionMarch : 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.AntilonMarchTelegraph) + { + var dir = spell.LocXZ - caster.Position; + _casters.Add((caster.Position, new AOEShapeRect(dir.Length(), 4), Angle.FromDirection(dir))); + } + if ((AID)spell.Action.ID == AID.AntlionMarch) + _activation = spell.NPCFinishAt.AddSeconds(0.2f); //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.AntlionMarch2) + _casters.RemoveAt(0); + } + } + + class Towerfall : Components.GenericAOEs + { + private List<(WPos source, AOEShape shape, Angle direction, DateTime activation)> _casters = new(); + private static readonly AOEShapeRect rect = new(40, 5); + private static readonly Angle _rot1 = 89.999f.Degrees(); + private static readonly Angle _rot2 = -90.004f.Degrees(); + + 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, _casters[0].activation); + if (_casters.Count > 1) + yield return new(_casters[1].shape, _casters[1].source, _casters[1].direction, _casters[1].activation); + } + + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (state == 0x00020001) + { + if (index == 0x01) + _casters.Add((new WPos(-20, 45), rect, _rot1, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); //timings can vary 1-3 seconds depending on Antilonmarch charges duration, so i took the lowest i could find + if (index == 0x02) + _casters.Add((new WPos(-20, 55), rect, _rot1, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + if (index == 0x03) + _casters.Add((new WPos(-20, 65), rect, _rot1, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + if (index == 0x04) + _casters.Add((new WPos(-20, 75), rect, _rot1, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + if (index == 0x05) + _casters.Add((new WPos(20, 45), rect, _rot2, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + if (index == 0x06) + _casters.Add((new WPos(20, 55), rect, _rot2, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + if (index == 0x07) + _casters.Add((new WPos(20, 65), rect, _rot2, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + if (index == 0x08) + _casters.Add((new WPos(20, 75), rect, _rot2, module.WorldState.CurrentTime.AddSeconds(13 - _casters.Count))); + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.Towerfall) + _casters.Clear(); + } + } + + class D132DamcyanAntilonStates : StateMachineBuilder + { + public D132DamcyanAntilonStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 823, NameID = 12484)] + public class D132DamcyanAntilon : BossModule + { + public D132DamcyanAntilon(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsRect(new(0, 60), 19.5f, 25)) { } + } +} diff --git a/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D133Durante.cs b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D133Durante.cs new file mode 100644 index 0000000000..386e0ec9e2 --- /dev/null +++ b/BossMod/Modules/Endwalker/Dungeon/D13LunarSubterrane/D133Durante.cs @@ -0,0 +1,206 @@ +// CONTRIB: made by Malediktus, not checked +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Endwalker.Dungeon.D13TheLunarSubterrane.D133DamcyanAntilon +{ + public enum OID : uint + { + Boss = 0x4042, // R=6.0 + AethericCharge = 0x4043, // R=1.0 + Helper = 0x233C, + }; + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + ArcaneEdge = 35010, // Boss->player, 5,0s cast, single-target + OldMagic = 35011, // Boss->self, 5,0s cast, range 60 circle + Teleport = 34991, // Boss->location, no cast, single-target + DuplicitousBatteryTelegraph = 36058, // Helper->location, 3,5s cast, range 5 circle + DuplicitousBattery = 36057, // Boss->self, 6,0s cast, single-target + DuplicitousBattery2 = 34994, // Helper->location, no cast, range 5 circle + ForsakenFount = 35003, // Boss->self, 3,0s cast, single-target + Explosion = 35006, // 4043->self, 5,0s cast, range 11 circle + Explosion2 = 35005, // Helper->self, 12,0s cast, range 9 circle + FallenGrace = 35882, // Helper->player, 5,0s cast, range 6 circle + Contrapasso = 35905, // Boss->self, 3,0s cast, range 60 circle + Splinter = 35004, // 4043->self, no cast, single-target + AntipodalAssaultMarker = 14588, // Helper->player, no cast, single-target + AntipodalAssault = 35007, // Boss->self, 5,0s cast, single-target, line stack + AntipodalAssault2 = 35008, // Boss->location, no cast, width 8 rect charge + HardSlash = 35009, // Boss->self, 5,0s cast, range 50 90-degree cone + TwilightPhase = 36055, // Boss->self, 6,0s cast, single-target + TwilightPhaseA = 34997, // Boss->self, no cast, single-target + TwilightPhaseB = 34998, // Boss->self, no cast, single-target + TwilightPhase2 = 36056, // Helper->self, 7,3s cast, range 60 width 20 rect + DarkImpact = 35001, // Boss->location, 7,0s cast, single-target + DarkImpact2 = 35002, // Helper->self, 8,0s cast, range 25 circle + DeathsJourney = 34995, // Boss->self, 6,0s cast, range 8 circle + DeathsJourney2 = 34996, // Helper->self, 6,5s cast, range 30 30-degree cone, this does the damage + DeathsJourney3 = 35872, // Helper->self, 6,5s cast, range 30 30-degree cone, visual + }; + + class Voidzone : BossComponent + { + public override void OnEventEnvControl(BossModule module, byte index, uint state) + { + if (state == 0x00020001 && index == 0x0A) + module.Arena.Bounds = new ArenaBoundsCircle(new(0, -422), 20); + } + } + + class ArcaneEdge : Components.RaidwideCast + { + public ArcaneEdge() : base(ActionID.MakeSpell(AID.ArcaneEdge)) { } + } + + class OldMagic : Components.RaidwideCast + { + public OldMagic() : base(ActionID.MakeSpell(AID.OldMagic)) { } + } + + class Contrapasso : Components.RaidwideCast + { + public Contrapasso() : base(ActionID.MakeSpell(AID.Contrapasso)) { } + } + + class DuplicitousBattery : Components.GenericAOEs + { + private List<(WPos source, DateTime activation)> _casters = new(); + private static readonly AOEShapeCircle circle = new(5); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_casters.Count > 0) + for (int i = 0; i < Math.Clamp(_casters.Count, 1, 16); ++i) + yield return new(circle, _casters[i].source, activation: _casters[i].activation, color: ArenaColor.Danger); + if (_casters.Count > 16) + for (int i = 16; i < Math.Clamp(_casters.Count, 16, 32); ++i) + yield return new(circle, _casters[i].source, activation: _casters[i].activation, risky: false); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.DuplicitousBatteryTelegraph) + _casters.Add((spell.LocXZ, module.WorldState.CurrentTime.AddSeconds(6.5f))); + + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (_casters.Count > 0 && (AID)spell.Action.ID == AID.DuplicitousBattery2) + _casters.RemoveAt(0); + } + } + + class Explosion : Components.SelfTargetedAOEs + { + public Explosion() : base(ActionID.MakeSpell(AID.Explosion), new AOEShapeCircle(11)) { } + } + + class Explosion2 : Components.SelfTargetedAOEs + { + public Explosion2() : base(ActionID.MakeSpell(AID.Explosion2), new AOEShapeCircle(9)) { } + } + class FallenGrace : Components.SpreadFromCastTargets + { + public FallenGrace() : base(ActionID.MakeSpell(AID.FallenGrace), 6) { } + } + + // TODO: create and use generic 'line stack' component + class AntipodalAssault : Components.GenericBaitAway + { + private Actor? target; + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.AntipodalAssaultMarker) + { + target = module.WorldState.Actors.Find(spell.MainTargetID); + CurrentBaits.Add(new(module.PrimaryActor, target!, new AOEShapeRect(50, 4))); // the actual range is not 50, but just a charge of 8 width, but always goes until the edge of the arena, so we can simplify it + } + if ((AID)spell.Action.ID == AID.AntipodalAssault2) + CurrentBaits.Clear(); + } + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + if (CurrentBaits.Count > 0 && actor != target) + hints.AddForbiddenZone(ShapeDistance.InvertedRect(module.PrimaryActor.Position, (target!.Position - module.PrimaryActor.Position).Normalized(), 50, 0, 4)); + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + if (CurrentBaits.Count > 0) + { + if (!actor.Position.InRect(module.PrimaryActor.Position, (target!.Position - module.PrimaryActor.Position).Normalized(), 50, 0, 4)) + hints.Add("Stack!"); + else + hints.Add("Stack!", false); + } + } + + public override void DrawArenaBackground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + { + foreach (var bait in CurrentBaits) + bait.Shape.Draw(arena, BaitOrigin(bait), bait.Rotation, ArenaColor.SafeFromAOE); + } + + public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) { } + } + + class HardSlash : Components.SelfTargetedAOEs + { + public HardSlash() : base(ActionID.MakeSpell(AID.HardSlash), new AOEShapeCone(50, 45.Degrees())) { } + } + + class TwilightPhase : Components.SelfTargetedAOEs + { + public TwilightPhase() : base(ActionID.MakeSpell(AID.TwilightPhase2), new AOEShapeRect(30, 10, 30)) { } + } + + class DarkImpact : Components.SelfTargetedAOEs + { + public DarkImpact() : base(ActionID.MakeSpell(AID.DarkImpact2), new AOEShapeCircle(25)) { } + } + + class DeathsJourney : Components.SelfTargetedAOEs + { + public DeathsJourney() : base(ActionID.MakeSpell(AID.DeathsJourney), new AOEShapeCircle(8)) { } + } + + class DeathsJourney2 : Components.SelfTargetedAOEs + { + public DeathsJourney2() : base(ActionID.MakeSpell(AID.DeathsJourney2), new AOEShapeCone(30, 15.Degrees())) { } + } + + class D133DuranteStates : StateMachineBuilder + { + public D133DuranteStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(CFCID = 823, NameID = 12584)] + class D133Durante : BossModule + { + public D133Durante(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(0, -422), 23)) { } + } +} diff --git a/BossMod/Modules/Endwalker/FATE/Chi.cs b/BossMod/Modules/Endwalker/FATE/Chi.cs index cb7ca28b1a..baa35b423a 100644 --- a/BossMod/Modules/Endwalker/FATE/Chi.cs +++ b/BossMod/Modules/Endwalker/FATE/Chi.cs @@ -52,7 +52,7 @@ public enum AID : uint class Bunkerbuster : Components.GenericAOEs { - private List _casters = new(); + private readonly List _casters = []; private DateTime _activation; private int NumCastsStarted; @@ -91,6 +91,11 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn _casters.Remove(caster); if (NumCasts is 3 or 6 or 9 or 12 or 15) _activation = _activation.AddSeconds(1.9f); + if (_casters.Count == 0 && NumCasts != 0) + { + NumCasts = 0; + NumCastsStarted = 0; + } } } @@ -103,20 +108,11 @@ public override void OnActorCreated(BossModule module, Actor actor) _activation = module.WorldState.CurrentTime.AddSeconds(20); //placeholder value that gets overwritten when cast actually starts } } - - public override void Update(BossModule module) - { - if (_casters.Count == 0 && NumCasts != 0) - { - NumCasts = 0; - NumCastsStarted = 0; - } - } } class BouncingBomb : Components.GenericAOEs { - private List _casters = new(); + private readonly List _casters = []; private DateTime _activation; private int bombcount; @@ -148,13 +144,13 @@ public override IEnumerable ActiveAOEs(BossModule module, int slot, yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation, ArenaColor.Danger); if (_casters.Count >= 7 && NumCasts == 0) for (int i = 2; i < 7; ++i) - yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation.AddSeconds(2.8f)); + yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation.AddSeconds(2.8f), risky : false); if (_casters.Count >= 5 && NumCasts == 2) for (int i = 0; i < 5; ++i) yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation, ArenaColor.Danger); if (_casters.Count >= 13 && NumCasts == 2) for (int i = 5; i < 13; ++i) - yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation.AddSeconds(2.8f)); + yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation.AddSeconds(2.8f), risky : false); if (_casters.Count >= 8 && NumCasts == 7) for (int i = 0; i < 8; ++i) yield return new(rect, _casters[i].Position, _casters[i].Rotation, _activation, ArenaColor.Danger); @@ -187,15 +183,11 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn _casters.Remove(caster); if ((bombcount == 1 && NumCasts is 1 or 4) || (bombcount == 2 && NumCasts is 2 or 7)) _activation = _activation.AddSeconds(2.8f); - } - } - - public override void Update(BossModule module) - { - if (_casters.Count == 0 && bombcount != 0) - { - bombcount = 0; - NumCasts = 0; + if (_casters.Count == 0 && bombcount != 0) + { + bombcount = 0; + NumCasts = 0; + } } } } @@ -204,32 +196,27 @@ class Combos : Components.GenericAOEs { private static readonly AOEShapeCone cone = new(45, 90.Degrees()); private static readonly AOEShapeDonut donut = new(16, 60); - private static readonly AOEShapeRect rect = new(120, 16, 120); - private DateTime _activation1; - private DateTime _activation2; - private AOEShape? _shape1; - private AOEShape? _shape2; - private bool offset; - private Angle _rotation; + private static readonly AOEShapeRect rect = new(60, 16, 60); + private (AOEShape shape1, AOEShape shape2, DateTime activation1, DateTime activation2, bool offset, Angle rotation) combo; public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - if (_shape1 != null && _shape2 != null) + if (combo != default) { if (NumCasts == 0) { - yield return new(_shape1, module.PrimaryActor.Position, _rotation, activation: _activation1, ArenaColor.Danger); - if (!offset) - yield return new(_shape2, module.PrimaryActor.Position, _rotation, activation: _activation2); + yield return new(combo.shape1, module.PrimaryActor.Position, combo.rotation, combo.activation1, ArenaColor.Danger); + if (!combo.offset) + yield return new(combo.shape2, module.PrimaryActor.Position, combo.rotation, combo.activation2, risky: combo.shape1 != combo.shape2); else - yield return new(_shape2, module.PrimaryActor.Position, _rotation + 180.Degrees(), activation: _activation2); + yield return new(combo.shape2, module.PrimaryActor.Position, combo.rotation + 180.Degrees(), combo.activation2, risky: combo.shape1 != combo.shape2); } if (NumCasts == 1) { - if (!offset) - yield return new(_shape2, module.PrimaryActor.Position, _rotation, activation: _activation2, ArenaColor.Danger); + if (!combo.offset) + yield return new(combo.shape2, module.PrimaryActor.Position, combo.rotation, combo.activation2, ArenaColor.Danger); else - yield return new(_shape2, module.PrimaryActor.Position, _rotation + 180.Degrees(), activation: _activation2, ArenaColor.Danger); + yield return new(combo.shape2, module.PrimaryActor.Position, combo.rotation + 180.Degrees(), combo.activation2, ArenaColor.Danger); } } } @@ -239,35 +226,22 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf switch ((AID)spell.Action.ID) { case AID.Carapace_ForeArms2dot0A: - _shape1 = rect; - _shape2 = cone; + combo = (rect, cone, spell.NPCFinishAt, spell.NPCFinishAt.AddSeconds(3.1f), false, spell.Rotation); break; case AID.Carapace_ForeArms2dot0B: - _shape1 = donut; - _shape2 = cone; + combo = (donut, cone, spell.NPCFinishAt, spell.NPCFinishAt.AddSeconds(3.1f), false, spell.Rotation); break; case AID.Carapace_RearGuns2dot0A: - _shape1 = rect; - _shape2 = cone; - offset = true; + combo = (rect, cone, spell.NPCFinishAt, spell.NPCFinishAt.AddSeconds(3.1f), true, spell.Rotation); break; case AID.Carapace_RearGuns2dot0B: - _shape1 = donut; - _shape2 = cone; - offset = true; + combo = (donut, cone, spell.NPCFinishAt, spell.NPCFinishAt.AddSeconds(3.1f), true, spell.Rotation); break; case AID.RearGuns_ForeArms2dot0: case AID.ForeArms_RearGuns2dot0: - _shape1 = _shape2 = cone; - offset = true; + combo = (cone, cone, spell.NPCFinishAt, spell.NPCFinishAt.AddSeconds(3.1f), true, spell.Rotation); break; } - if ((AID)spell.Action.ID is AID.Carapace_ForeArms2dot0A or AID.Carapace_ForeArms2dot0B or AID.Carapace_RearGuns2dot0A or AID.Carapace_RearGuns2dot0B or AID.RearGuns_ForeArms2dot0 or AID.ForeArms_RearGuns2dot0) - { - _activation1 = spell.NPCFinishAt; - _activation2 = spell.NPCFinishAt.AddSeconds(3.1f); - _rotation = spell.Rotation; - } } public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) @@ -281,9 +255,7 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent if ((AID)spell.Action.ID is AID.RearGuns2dot0 or AID.ForeArms2dot0) { NumCasts = 0; - offset = false; - _shape1 = null; - _shape2 = null; + combo = default; } } } @@ -310,12 +282,12 @@ public ThermobaricExplosive() : base(ActionID.MakeSpell(AID.ThermobaricExplosive class AssaultCarapace : Components.SelfTargetedAOEs { - public AssaultCarapace() : base(ActionID.MakeSpell(AID.AssaultCarapace), new AOEShapeRect(120, 16, 120)) { } + public AssaultCarapace() : base(ActionID.MakeSpell(AID.AssaultCarapace), new AOEShapeRect(60, 16, 60)) { } } class AssaultCarapace2 : Components.SelfTargetedAOEs { - public AssaultCarapace2() : base(ActionID.MakeSpell(AID.AssaultCarapace2), new AOEShapeRect(120, 16, 120)) { } + public AssaultCarapace2() : base(ActionID.MakeSpell(AID.AssaultCarapace2), new AOEShapeRect(60, 16, 60)) { } } class AssaultCarapace3 : Components.SelfTargetedAOEs @@ -376,4 +348,4 @@ public class Chi : BossModule { public Chi(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(650, 0), 30)) { } } -} +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/FATE/Daivadipa.cs b/BossMod/Modules/Endwalker/FATE/Daivadipa.cs index c365af7627..b5f1ec6c91 100644 --- a/BossMod/Modules/Endwalker/FATE/Daivadipa.cs +++ b/BossMod/Modules/Endwalker/FATE/Daivadipa.cs @@ -59,9 +59,8 @@ public enum SID : uint class LitPath : Components.GenericAOEs { private static readonly AOEShapeRect rect = new(50, 5); - private DateTime _activation1; - private DateTime _activation2; - private bool active; + private static readonly Angle[] rotations = [0.Degrees(), 90.Degrees(), 180.Degrees(), -90.Degrees()]; + private DateTime _activation; private bool redblue1; private bool redblue2; private bool bluered1; @@ -70,52 +69,41 @@ class LitPath : Components.GenericAOEs public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - if (active) + if (_activation != default) { foreach (var o in module.Enemies(OID.OrbOfImmolationBlue)) { - if (bluered1 && (o.Rotation.AlmostEqual(90.Degrees(), maxError) || o.Rotation.AlmostEqual(0.Degrees(), maxError) || o.Rotation.AlmostEqual(180.Degrees(), maxError) || o.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, o.Position, o.Rotation, activation: _activation1.AddSeconds(6.9f)); - if (redblue2 && !redblue1 && (o.Rotation.AlmostEqual(90.Degrees(), maxError) || o.Rotation.AlmostEqual(0.Degrees(), maxError) || o.Rotation.AlmostEqual(180.Degrees(), maxError) || o.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, o.Position, o.Rotation, activation: _activation2.AddSeconds(8.9f)); + if (bluered1 && o.Rotation.AlmostEqual(rotations.FirstOrDefault(r => o.Rotation.AlmostEqual(r, maxError)), maxError)) + yield return new(rect, o.Position, o.Rotation, _activation.AddSeconds(1.9f)); + if (redblue2 && !redblue1 && o.Rotation.AlmostEqual(rotations.FirstOrDefault(r => o.Rotation.AlmostEqual(r, maxError)), maxError)) + yield return new(rect, o.Position, o.Rotation, _activation.AddSeconds(4)); } foreach (var o in module.Enemies(OID.OrbOfImmolationRed)) { - if (bluered2 && !bluered1 && (o.Rotation.AlmostEqual(90.Degrees(), maxError) || o.Rotation.AlmostEqual(0.Degrees(), maxError) || o.Rotation.AlmostEqual(180.Degrees(), maxError) || o.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, o.Position, o.Rotation, activation: _activation1.AddSeconds(6.9f)); - if (redblue1 && (o.Rotation.AlmostEqual(90.Degrees(), maxError) || o.Rotation.AlmostEqual(0.Degrees(), maxError) || o.Rotation.AlmostEqual(180.Degrees(), maxError) || o.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, o.Position, o.Rotation, activation: _activation2.AddSeconds(8.9f)); + if (bluered2 && !bluered1 && o.Rotation.AlmostEqual(rotations.FirstOrDefault(r => o.Rotation.AlmostEqual(r, maxError)), maxError)) + yield return new(rect, o.Position, o.Rotation, _activation.AddSeconds(4)); + if (redblue1 && o.Rotation.AlmostEqual(rotations.FirstOrDefault(r => o.Rotation.AlmostEqual(r, maxError)), maxError)) + yield return new(rect, o.Position, o.Rotation, _activation.AddSeconds(1.9f)); } } } public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.LoyalFlame) + if (!module.Enemies(OID.OrbOfImmolationRed).All(x => x.IsDead) && !module.Enemies(OID.OrbOfImmolationBlue).All(x => x.IsDead)) { - _activation1 = module.WorldState.CurrentTime; ; - active = true; - bluered1 = true; - bluered2 = true; - } - if ((AID)spell.Action.ID == AID.LoyalFlame2) - { - _activation2 = module.WorldState.CurrentTime; ; - active = true; - redblue1 = true; - redblue2 = true; - } - } - - public override void Update(BossModule module) //Note: this is required because LitPath and Burn use the same color telegraph AID - { - if (module.Enemies(OID.OrbOfImmolationRed).All(x => x.IsDead) && module.Enemies(OID.OrbOfImmolationBlue).All(x => x.IsDead)) - { - active = false; - bluered1 = false; - redblue2 = false; - bluered2 = false; - redblue1 = false; + if ((AID)spell.Action.ID == AID.LoyalFlame) + { + _activation = spell.NPCFinishAt; + bluered1 = true; + bluered2 = true; + } + if ((AID)spell.Action.ID == AID.LoyalFlame2) + { + _activation = spell.NPCFinishAt; + redblue1 = true; + redblue2 = true; + } } } @@ -123,26 +111,22 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn { if ((AID)spell.Action.ID == AID.LitPath1) { - _activation1 = spell.NPCFinishAt; bluered1 = false; redblue2 = false; - ++NumCasts; - if (NumCasts == 5) + if (++NumCasts == 5) { NumCasts = 0; - active = false; + _activation = default; } } if ((AID)spell.Action.ID == AID.LitPath2) { - _activation2 = spell.NPCFinishAt; bluered2 = false; redblue1 = false; - ++NumCasts; - if (NumCasts == 5) + if (++NumCasts == 5) { NumCasts = 0; - active = false; + _activation = default; } } } @@ -151,9 +135,7 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn class Burn : Components.GenericAOEs { private static readonly AOEShapeCircle circle = new(10); - private DateTime _activation1; - private DateTime _activation2; - private bool active; + private DateTime _activation; private bool redblue1; private bool redblue2; private bool bluered1; @@ -161,52 +143,41 @@ class Burn : Components.GenericAOEs public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - if (active) + if (_activation != default) { foreach (var o in module.Enemies(OID.OrbOfConflagrationBlue)) { if (bluered1) - yield return new(circle, o.Position, o.Rotation, activation: _activation1.AddSeconds(6.2f)); + yield return new(circle, o.Position, activation: _activation.AddSeconds(2.1f)); if (redblue2 && !redblue1) - yield return new(circle, o.Position, o.Rotation, activation: _activation2.AddSeconds(10.2f)); + yield return new(circle, o.Position, activation: _activation.AddSeconds(6.1f)); } foreach (var o in module.Enemies(OID.OrbOfConflagrationRed)) { if (bluered2 && !bluered1) - yield return new(circle, o.Position, o.Rotation, activation: _activation1.AddSeconds(6.2f)); + yield return new(circle, o.Position, activation: _activation.AddSeconds(6.1f)); if (redblue1) - yield return new(circle, o.Position, o.Rotation, activation: _activation2.AddSeconds(10.2f)); + yield return new(circle, o.Position, activation: _activation.AddSeconds(2.1f)); } } } public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.LoyalFlame) + if (!module.Enemies(OID.OrbOfConflagrationRed).All(x => x.IsDead) && !module.Enemies(OID.OrbOfConflagrationBlue).All(x => x.IsDead)) { - _activation1 = module.WorldState.CurrentTime; - active = true; - bluered1 = true; - bluered2 = true; - } - if ((AID)spell.Action.ID == AID.LoyalFlame2) - { - _activation2 = module.WorldState.CurrentTime; - active = true; - redblue1 = true; - redblue2 = true; - } - } - - public override void Update(BossModule module) //Note: this is required because LitPath and Burn use the same color telegraph AID - { - if (module.Enemies(OID.OrbOfConflagrationRed).All(x => x.IsDead) && module.Enemies(OID.OrbOfConflagrationBlue).All(x => x.IsDead)) - { - active = false; - bluered1 = false; - redblue2 = false; - bluered2 = false; - redblue1 = false; + if ((AID)spell.Action.ID == AID.LoyalFlame) + { + _activation = spell.NPCFinishAt; + bluered1 = true; + bluered2 = true; + } + if ((AID)spell.Action.ID == AID.LoyalFlame2) + { + _activation = spell.NPCFinishAt; + redblue1 = true; + redblue2 = true; + } } } @@ -214,26 +185,23 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn { if ((AID)spell.Action.ID == AID.Burn) { - _activation1 = spell.NPCFinishAt; bluered1 = false; redblue2 = false; - ++NumCasts; - if (NumCasts == 16) + if (++NumCasts == 16) { NumCasts = 0; - active = false; + _activation = default; } } if ((AID)spell.Action.ID == AID.Burn2) { - _activation2 = spell.NPCFinishAt; bluered2 = false; redblue1 = false; ++NumCasts; - if (NumCasts == 16) + if (++NumCasts == 16) { NumCasts = 0; - active = false; + _activation = default; } } } @@ -355,4 +323,4 @@ public class Daivadipa : BossModule { public Daivadipa(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsSquare(new(-608, 811), 24.5f)) { } } -} +} \ No newline at end of file diff --git a/BossMod/Modules/Endwalker/HuntA/Petalodus.cs b/BossMod/Modules/Endwalker/HuntA/Petalodus.cs index 9a53698811..fe1bf73654 100644 --- a/BossMod/Modules/Endwalker/HuntA/Petalodus.cs +++ b/BossMod/Modules/Endwalker/HuntA/Petalodus.cs @@ -18,7 +18,7 @@ public enum AID : uint class MarineMayhem : Components.CastInterruptHint { - public MarineMayhem() : base(ActionID.MakeSpell(AID.MarineMayhem), hint: "(Raidwide x3)") { } + public MarineMayhem() : base(ActionID.MakeSpell(AID.MarineMayhem), hintExtra: "(Raidwide x3)") { } } class Waterga : Components.SpreadFromCastTargets diff --git a/BossMod/Modules/Endwalker/HuntA/Sugriva.cs b/BossMod/Modules/Endwalker/HuntA/Sugriva.cs index 6f2f712abe..8be4edbf48 100644 --- a/BossMod/Modules/Endwalker/HuntA/Sugriva.cs +++ b/BossMod/Modules/Endwalker/HuntA/Sugriva.cs @@ -61,13 +61,17 @@ class ScytheTail : Components.SelfTargetedAOEs class Butcher : Components.BaitAwayCast { - public Butcher() : base(ActionID.MakeSpell(AID.Butcher), new AOEShapeCone(8, 60.Degrees())) { } + public Butcher() : base(ActionID.MakeSpell(AID.Butcher), new AOEShapeCone(8, 60.Degrees())) + { + EndsOnCastEvent = true; + } + } - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { } - public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) //tankbuster resolves on cast event, which can be delayed by moving out of tankbuster range + class ButcherHint : Components.SingleTargetCast + { + public ButcherHint() : base(ActionID.MakeSpell(AID.Butcher), "Tankbuster cleave") { - if (spell.Action == WatchedAction) - CurrentBaits.RemoveAll(b => b.Source == caster); + EndsOnCastEvent = true; } } @@ -135,6 +139,7 @@ public SugrivaStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter(); diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs index c10da0c689..82dba2453a 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMegakantha.cs @@ -1,5 +1,4 @@ // CONTRIB: made by malediktus, not checked -using System; using System.Collections.Generic; using System.Linq; @@ -67,25 +66,14 @@ public VineWhip() : base(ActionID.MakeSpell(AID.VineWhip)) { } class OdiousAtmosphere : Components.GenericAOEs { - private bool activeBreath; - private Actor? _caster; - private DateTime _activation; - private static readonly AOEShapeCone cone = new(40, 90.Degrees()); + private AOEInstance? _aoe; - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (activeBreath && _caster != null) - yield return new(cone, _caster.Position, _caster.Rotation, _activation); - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.OdiousAtmosphere0) - { - activeBreath = true; - _caster = caster; - _activation = spell.NPCFinishAt; - } + _aoe = new(new AOEShapeCone(40, 90.Degrees()), caster.Position, spell.Rotation, activation: spell.NPCFinishAt); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) @@ -98,7 +86,7 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent case AID.OdiousAtmosphere3: if (++NumCasts == 6) { - activeBreath = false; + _aoe = null; NumCasts = 0; } break; diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs index e4ded8e8fd..2f667b337f 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouMeganereis.cs @@ -43,12 +43,6 @@ public enum AID : uint HeavySmash = 32317, // 3D4E->location, 3,0s cast, range 6 circle }; - public enum IconID : uint - { - Tankbuster = 218, // player - Spread = 135, // player - }; - class Ceras : Components.SingleTargetCast { public Ceras() : base(ActionID.MakeSpell(AID.Ceras)) { } @@ -60,6 +54,7 @@ public WaveOfTurmoil() : base(ActionID.MakeSpell(AID.WaveOfTurmoil), 20) { StopAtWall = true; } + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } @@ -83,25 +78,9 @@ class Hydrocannon2 : Components.SelfTargetedAOEs public Hydrocannon2() : base(ActionID.MakeSpell(AID.Hydrocannon2), new AOEShapeRect(27, 3)) { } } - class FallingWater : Components.UniformStackSpread + class FallingWater : Components.SpreadFromCastTargets { - public FallingWater() : base(0, 8, alwaysShowSpreads: true) { } - - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - if (iconID == (uint)IconID.Spread) - { - AddSpread(actor); - } - } - - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.FallingWater) - { - Spreads.Clear(); - } - } + public FallingWater() : base(ActionID.MakeSpell(AID.FallingWater), 8) { } } class Immersion : Components.RaidwideCast diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs index 31e7564e19..b41a815fd6 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouPithekos.cs @@ -46,9 +46,8 @@ class Thundercall : Components.LocationTargetedAOEs public Thundercall() : base(ActionID.MakeSpell(AID.Thundercall), 3) { } } - class Thundercall2 : Components.UniformStackSpread + class Thundercall2 : Components.GenericBaitAway { - public Thundercall2() : base(0, 18, alwaysShowSpreads: true) { } private bool targeted; private Actor? target; @@ -56,7 +55,7 @@ public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { if (iconID == (uint)IconID.Thundercall) { - AddSpread(actor); + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(18))); targeted = true; target = actor; } @@ -66,7 +65,7 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf { if ((AID)spell.Action.ID == AID.Thundercall) { - Spreads.Clear(); + CurrentBaits.Clear(); targeted = false; } } @@ -80,8 +79,9 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { + base.AddHints(module, slot, actor, hints, movementHints); if (target == actor && targeted) - hints.Add("Bait away!"); + hints.Add("Bait levinorb away!"); } } diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs index 628897b53b..a42adb52a7 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouSphinx.cs @@ -42,12 +42,6 @@ public enum AID : uint HeavySmash = 32317, // 3D4E->location, 3,0s cast, range 6 circle }; - public enum IconID : uint - { - Tankbuster = 218, // player - Spread = 140, // player - }; - class Scratch : Components.SingleTargetCast { public Scratch() : base(ActionID.MakeSpell(AID.Scratch)) { } @@ -78,25 +72,9 @@ class AlpineDraft : Components.SelfTargetedAOEs public AlpineDraft() : base(ActionID.MakeSpell(AID.AlpineDraft), new AOEShapeRect(45, 2.5f)) { } } - class FeatherRain : Components.UniformStackSpread + class FeatherRain : Components.SpreadFromCastTargets { - public FeatherRain() : base(0, 6, alwaysShowSpreads: true) { } - - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - if (iconID == (uint)IconID.Spread) - { - AddSpread(actor); - } - } - - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.FeatherRain) - { - Spreads.Clear(); - } - } + public FeatherRain() : base(ActionID.MakeSpell(AID.FeatherRain), 6) { } } class AeroII : Components.LocationTargetedAOEs diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs index e608667827..c9f82beffd 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/GymnasiouStyphnolobion.cs @@ -1,5 +1,4 @@ // CONTRIB: made by malediktus, not checked -using System.Collections.Generic; using System.Linq; namespace BossMod.Endwalker.TreasureHunt.ShiftingGymnasionAgonon.GymnasiouStyphnolobion @@ -59,12 +58,15 @@ public StoneIII() : base(ActionID.MakeSpell(AID.StoneIII2), 6) { } class EarthShaker : Components.BaitAwayCast { - public EarthShaker() : base(ActionID.MakeSpell(AID.EarthShaker2), new AOEShapeCone(60, 15.Degrees())) { } + public EarthShaker() : base(ActionID.MakeSpell(AID.EarthShaker2), new AOEShapeCone(60, 15.Degrees())) + { + EndsOnCastEvent = true; + } } class EarthQuaker : Components.ConcentricAOEs { - private static AOEShape[] _shapes = { new AOEShapeCircle(10), new AOEShapeDonut(10, 20) }; + private static readonly AOEShape[] _shapes = [new AOEShapeCircle(10), new AOEShapeDonut(10, 20)]; public EarthQuaker() : base(_shapes) { } diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs index 1340646705..f88b251a76 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/LyssaChrysine.cs @@ -1,5 +1,4 @@ // CONTRIB: made by malediktus, not checked -using System; using System.Collections.Generic; using System.Linq; @@ -114,27 +113,19 @@ public HeavySmash() : base(ActionID.MakeSpell(AID.HeavySmash), 6) { } class IcePillarSpawn : Components.GenericAOEs { - private bool activePillar; - private DateTime _activation; - private static readonly AOEShapeCircle circle = new(6); - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (activePillar) - foreach (var p in module.Enemies(OID.IcePillars)) - yield return new(circle, p.Position, default, _activation); - } + private readonly List _aoes = new(); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(4); + public override void OnActorCreated(BossModule module, Actor actor) { if ((OID)actor.OID == OID.IcePillars) - { - activePillar = true; - _activation = module.WorldState.CurrentTime.AddSeconds(3.75f); - } + _aoes.Add(new (new AOEShapeCircle(6), actor.Position, activation: module.WorldState.CurrentTime.AddSeconds(3.75f))); } public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.IcePillar) - activePillar = false; + _aoes.RemoveAt(0); } } diff --git a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs index 770b8e69c9..2f35d930ac 100644 --- a/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs +++ b/BossMod/Modules/Endwalker/TreasureHunt/TheShiftingGymnasionAgonon/Narkissos.cs @@ -39,12 +39,6 @@ public enum SID : uint LeftFace = 1960, // Boss->player, extra=0x0 }; - public enum IconID : uint - { - Tankbuster = 218, // player - Spread = 140, // player - }; - class Brainstorm : Components.StatusDrivenForcedMarch { public Brainstorm() : base(2, (uint)SID.ForwardMarch, (uint)SID.AboutFace, (uint)SID.LeftFace, (uint)SID.RightFace) { } @@ -116,23 +110,9 @@ class PutridBreath : Components.SelfTargetedAOEs public PutridBreath() : base(ActionID.MakeSpell(AID.PutridBreath), new AOEShapeCone(25, 45.Degrees())) { } } - class RockHard : Components.UniformStackSpread + class RockHard : Components.SpreadFromCastTargets { - public RockHard() : base(0, 6, alwaysShowSpreads: true) { } - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - if (iconID == (uint)IconID.Spread) - { - AddSpread(actor); - } - } - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.RockHard) - { - Spreads.Clear(); - } - } + public RockHard() : base(ActionID.MakeSpell(AID.RockHard), 6) { } } class BeguilingGas : Components.CastHint diff --git a/BossMod/Modules/Endwalker/Trials/T02Hydaelyn/WeaponTracker.cs b/BossMod/Modules/Endwalker/Trials/T02Hydaelyn/WeaponTracker.cs index 0084dcf2f2..b9d67d6141 100644 --- a/BossMod/Modules/Endwalker/Trials/T02Hydaelyn/WeaponTracker.cs +++ b/BossMod/Modules/Endwalker/Trials/T02Hydaelyn/WeaponTracker.cs @@ -1,53 +1,33 @@ using System; using System.Collections.Generic; +using System.Linq; namespace BossMod.Endwalker.Trials.T02Hydaelyn { class WeaponTracker : Components.GenericAOEs { - public enum Stance { Sword, Staff, Chakram } - public Stance CurStance { get; private set; } - private DateTime _activation; - private static AOEShapeCross _aoeSword = new(40, 5); - private static AOEShapeCircle _aoeStaff = new(10); - private static AOEShapeDonut _aoeChakram = new(5, 40); + private AOEInstance? _aoe; - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (_activation != default) - { - if (CurStance == Stance.Sword) - yield return new(_aoeSword, module.PrimaryActor.Position, module.PrimaryActor.Rotation, activation: _activation); - if (CurStance == Stance.Staff) - yield return new(_aoeStaff, module.PrimaryActor.Position, module.PrimaryActor.Rotation, activation: _activation); - if (CurStance == Stance.Chakram) - yield return new(_aoeChakram, module.PrimaryActor.Position, module.PrimaryActor.Rotation, activation: _activation); - } - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) { - if ((SID)status.ID == SID.HydaelynsWeapon) - _activation = module.WorldState.CurrentTime.AddSeconds(6); if ((SID)status.ID == SID.HydaelynsWeapon && status.Extra == 0x1B4) - CurStance = Stance.Staff; + _aoe = new(new AOEShapeCircle(10), module.PrimaryActor.Position, activation: module.WorldState.CurrentTime.AddSeconds(6)); if ((SID)status.ID == SID.HydaelynsWeapon && status.Extra == 0x1B5) - CurStance = Stance.Chakram; + _aoe = new(new AOEShapeDonut(5, 40), module.PrimaryActor.Position, activation: module.WorldState.CurrentTime.AddSeconds(6)); } public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) { if ((SID)status.ID == SID.HydaelynsWeapon) - { - CurStance = Stance.Sword; - _activation = module.WorldState.CurrentTime.AddSeconds(6.9f); - } + _aoe = new(new AOEShapeCross(40, 5), module.PrimaryActor.Position, activation: module.WorldState.CurrentTime.AddSeconds(6.9f)); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID is AID.Equinox2 or AID.HighestHoly or AID.Anthelion) - _activation = default; + _aoe = null; } } } diff --git a/BossMod/Modules/Endwalker/Trials/T08Asura/T08Asura.cs b/BossMod/Modules/Endwalker/Trials/T08Asura/T08Asura.cs index 55aef01119..03e5472efe 100644 --- a/BossMod/Modules/Endwalker/Trials/T08Asura/T08Asura.cs +++ b/BossMod/Modules/Endwalker/Trials/T08Asura/T08Asura.cs @@ -62,21 +62,9 @@ class Scattering : Components.SelfTargetedAOEs public Scattering() : base(ActionID.MakeSpell(AID.Scattering), new AOEShapeRect(20, 3)) { } } - class OrderedChaos : Components.UniformStackSpread + class OrderedChaos : Components.SpreadFromCastTargets { - public OrderedChaos() : base(0, 5, alwaysShowSpreads: true) { } - - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - if (iconID == (uint)IconID.Spreadmarker) - AddSpread(actor); - } - - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.OrderedChaos2) - Spreads.Clear(); - } + public OrderedChaos() : base(ActionID.MakeSpell(AID.OrderedChaos), 5) { } } class T08AsuraStates : StateMachineBuilder diff --git a/BossMod/Modules/GoldSaucer/TheSliceIsRight/TheSliceIsRight.cs b/BossMod/Modules/GoldSaucer/TheSliceIsRight/TheSliceIsRight.cs index 44720466d8..283a37c480 100644 --- a/BossMod/Modules/GoldSaucer/TheSliceIsRight/TheSliceIsRight.cs +++ b/BossMod/Modules/GoldSaucer/TheSliceIsRight/TheSliceIsRight.cs @@ -43,13 +43,13 @@ public enum AID : uint class BambooSplits : Components.GenericAOEs { - private List _doublesidedsplit = new(); - private List _singlesplit = new(); - private List _circle = new(); - private List _doublesidedsplitToberemoved = new(); - private List _singlesplitToberemoved = new(); - private List _circleToberemoved = new(); - private List _bamboospawn = new(); + private readonly List _doublesidedsplit = []; + private readonly List _singlesplit = []; + private readonly List _circle = []; + private readonly List _doublesidedsplitToberemoved = []; + private readonly List _singlesplitToberemoved = []; + private readonly List _circleToberemoved = []; + private readonly List _bamboospawn = []; private static readonly AOEShapeRect rectdouble = new(28, 2.5f, 28); private static readonly AOEShapeRect rectsingle = new(28, 2.5f); private static readonly AOEShapeCircle circle = new(11); diff --git a/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act1.cs b/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act1.cs index d16d62e582..dcb5ff7694 100644 --- a/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act1.cs +++ b/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act1.cs @@ -20,7 +20,7 @@ public LastSong() : base(ActionID.MakeSpell(AID.TheLastSong), 60, true) { } //TO class LastSongHint : BossComponent { - public static bool casting; + public bool casting; public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { @@ -57,7 +57,7 @@ public Stage14Act1States(BossModule module) : base(module) .DeactivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && !LastSongHint.casting; + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && !(module.FindComponent()?.casting ?? false); } } diff --git a/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act2.cs b/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act2.cs index 41bc4bae47..ab4127649b 100644 --- a/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act2.cs +++ b/BossMod/Modules/MaskedCarnivale/Stage14BlobsintheWoods/Stage14Act2.cs @@ -21,7 +21,7 @@ public LastSong() : base(ActionID.MakeSpell(AID.TheLastSong), 60, true) { } //TO class LastSongHint : BossComponent { - public static bool casting; + public bool casting; public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { @@ -57,7 +57,7 @@ public Stage14Act2States(BossModule module) : base(module) .DeactivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && !LastSongHint.casting; + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && !(module.FindComponent()?.casting ?? false); } } diff --git a/BossMod/Modules/PVP/HiddenGorge/GoblinMercenary.cs b/BossMod/Modules/PVP/HiddenGorge/GoblinMercenary.cs index e1db38aadc..4b69719700 100644 --- a/BossMod/Modules/PVP/HiddenGorge/GoblinMercenary.cs +++ b/BossMod/Modules/PVP/HiddenGorge/GoblinMercenary.cs @@ -32,79 +32,43 @@ public enum IconID : uint class GobspinSwipe : Components.GenericAOEs { - private DateTime _activation; - private bool castingGobspin; - private bool castingGobswipe; - private static readonly AOEShapeCircle circle = new(8); - private static readonly AOEShapeDonut donut = new(5, 30); + private AOEInstance? _aoe; - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (castingGobspin) - yield return new(circle, module.PrimaryActor.Position, default, _activation); - if (castingGobswipe) - yield return new(donut, module.PrimaryActor.Position, default, _activation); - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.GobspinWhooshdropsTelegraph) - { - castingGobspin = true; - _activation = spell.NPCFinishAt.AddSeconds(4); - } + _aoe = new(new AOEShapeCircle(8), module.PrimaryActor.Position, activation: spell.NPCFinishAt.AddSeconds(4)); if ((AID)spell.Action.ID == AID.GobswipeConklopsTelegraph) - { - castingGobswipe = true; - _activation = spell.NPCFinishAt.AddSeconds(4); - } + _aoe = new(new AOEShapeDonut(5, 30), module.PrimaryActor.Position, activation: spell.NPCFinishAt.AddSeconds(4)); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.GobspinWhooshdrops) - castingGobspin = false; - if ((AID)spell.Action.ID == AID.GobswipeConklops) - castingGobswipe = false; + if ((AID)spell.Action.ID is AID.GobspinWhooshdrops or AID.GobswipeConklops) + _aoe = null; } } class Knockbacks : Components.Knockback { - private DateTime _activation; - private bool castingGobspin; - private bool castingGobswipe; - private static readonly AOEShapeCircle circle = new(8); - private static readonly AOEShapeDonut donut = new(5, 30); + private Source? _knockback; - public override IEnumerable Sources(BossModule module, int slot, Actor actor) - { - if (castingGobspin) - yield return new(module.PrimaryActor.Position, 15, _activation, circle); - if (castingGobswipe) - yield return new(module.PrimaryActor.Position, 15, _activation, donut); - } + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_knockback); public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.GobspinWhooshdropsTelegraph) - { - castingGobspin = true; - _activation = spell.NPCFinishAt.AddSeconds(4); - } + _knockback = new(module.PrimaryActor.Position, 15, spell.NPCFinishAt.AddSeconds(4), new AOEShapeCircle(8)); if ((AID)spell.Action.ID == AID.GobswipeConklopsTelegraph) - { - castingGobswipe = true; - _activation = spell.NPCFinishAt.AddSeconds(4); - } + _knockback = new(module.PrimaryActor.Position, 15, spell.NPCFinishAt.AddSeconds(4), new AOEShapeDonut(5, 30)); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.GobspinWhooshdrops) - castingGobspin = false; - if ((AID)spell.Action.ID == AID.GobswipeConklops) - castingGobswipe = false; + if ((AID)spell.Action.ID is AID.GobspinWhooshdrops or AID.GobswipeConklops) + _knockback = null; } } diff --git a/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs b/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs index 602ee9d31b..440ea77d30 100644 --- a/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs +++ b/BossMod/Modules/RealmReborn/Trial/T04PortaDecumana/T04PortaDecumana2.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace BossMod.RealmReborn.Trial.T04PortaDecumana.Phase2 diff --git a/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHBodyslam.cs b/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHBodyslam.cs index 317e4ffbd8..5ab650994d 100644 --- a/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHBodyslam.cs +++ b/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHBodyslam.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; @@ -6,9 +5,7 @@ namespace BossMod.Modules.RealmReborn.Trial.T09WhorleaterH { class BodySlamKB : Components.Knockback { - private DateTime _activation; - private float Distance; - private Angle Direction; + private Source? _knockback; private float LeviathanZ; public BodySlamKB() @@ -16,32 +13,23 @@ public BodySlamKB() StopAtWall = true; } - public override IEnumerable Sources(BossModule module, int slot, Actor actor) - { - if (Distance > 0) - yield return new(module.Bounds.Center, Distance, _activation, null, Direction, Kind.DirForward); - } + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_knockback); public override void Update(BossModule module) { - base.Update(module); + if (LeviathanZ == default) + LeviathanZ = module.PrimaryActor.Position.Z; + if (module.PrimaryActor.Position.Z != LeviathanZ && module.PrimaryActor.Position.Z != 0) { - if (LeviathanZ == default) - LeviathanZ = module.PrimaryActor.Position.Z; - if (module.PrimaryActor.Position.Z != LeviathanZ && module.PrimaryActor.Position.Z != 0) - { - LeviathanZ = module.PrimaryActor.Position.Z; - Distance = 25; - Direction = module.PrimaryActor.Position.Z <= 0 ? 180.Degrees() : 0.Degrees(); - _activation = module.WorldState.CurrentTime.AddSeconds(4.8f); - } + LeviathanZ = module.PrimaryActor.Position.Z; + _knockback = new(module.Bounds.Center, 25, module.WorldState.CurrentTime.AddSeconds(4.8f), Direction: module.PrimaryActor.Position.Z <= 0 ? 180.Degrees() : 0.Degrees(), Kind: Kind.DirForward); } } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID is AID.BodySlamNorth or AID.BodySlamSouth) - Distance = 0; + _knockback = null; } public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false); @@ -49,36 +37,26 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent class BodySlamAOE : Components.GenericAOEs { - private bool active; + private AOEInstance? _aoe; private float LeviathanZ; - private DateTime _activation; - private static readonly AOEShapeRect rect = new(30, 5); - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (active) - yield return new(rect, module.PrimaryActor.Position, module.PrimaryActor.Rotation, _activation); - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void Update(BossModule module) { - base.Update(module); + if (LeviathanZ == default) + LeviathanZ = module.PrimaryActor.Position.Z; + if (module.PrimaryActor.Position.Z != LeviathanZ && module.PrimaryActor.Position.Z != 0) { - if (LeviathanZ == default) - LeviathanZ = module.PrimaryActor.Position.Z; - if (module.PrimaryActor.Position.Z != LeviathanZ && module.PrimaryActor.Position.Z != 0) - { - LeviathanZ = module.PrimaryActor.Position.Z; - active = true; - _activation = module.WorldState.CurrentTime.AddSeconds(2.6f); - } + LeviathanZ = module.PrimaryActor.Position.Z; + _aoe = new(new AOEShapeRect(30, 5), module.PrimaryActor.Position, module.PrimaryActor.Rotation, module.WorldState.CurrentTime.AddSeconds(2.6f)); } } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID == AID.BodySlamRectAOE) - active = false; + _aoe = null; } } } diff --git a/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHSpinningDive.cs b/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHSpinningDive.cs index 3276af6fce..2432cf8c4b 100644 --- a/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHSpinningDive.cs +++ b/BossMod/Modules/RealmReborn/Trial/T09WhorleaterH/T09WhorleaterHSpinningDive.cs @@ -1,71 +1,50 @@ using System.Linq; using System.Collections.Generic; -using System; namespace BossMod.Modules.RealmReborn.Trial.T09WhorleaterH { class SpinningDive : Components.GenericAOEs //TODO: Find out how to detect spinning dives earlier eg. the water column telegraph { - private DateTime _activation; - private Actor? SpinningDiveHelper; - private bool dived; - private static readonly AOEShapeRect rect = new(46, 8); + private AOEInstance? _aoe; - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - SpinningDiveHelper = module.Enemies(OID.SpinningDiveHelper).FirstOrDefault(); - if (SpinningDiveHelper != null && !dived) - yield return new(rect, SpinningDiveHelper.Position, SpinningDiveHelper.Rotation, _activation); - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnActorCreated(BossModule module, Actor actor) { + var SpinningDiveHelper = module.Enemies(OID.SpinningDiveHelper).FirstOrDefault(); if ((OID)actor.OID == OID.SpinningDiveHelper) - { - dived = false; - _activation = module.WorldState.CurrentTime.AddSeconds(0.6f); - } + _aoe = new(new AOEShapeRect(46, 8), SpinningDiveHelper!.Position, SpinningDiveHelper.Rotation, module.WorldState.CurrentTime.AddSeconds(0.6f)); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID == AID.SpinningDiveSnapshot) - dived = true; + _aoe = null; } } class SpinningDiveKB : Components.Knockback //TODO: Find out how to detect spinning dives earlier eg. the water column telegraph { - private DateTime _activation; - private Actor? SpinningDiveHelper; - private bool dived; - private static readonly AOEShapeRect rect = new(46, 8); + private Source? _knockback; public SpinningDiveKB() { StopAtWall = true; } - public override IEnumerable Sources(BossModule module, int slot, Actor actor) - { - SpinningDiveHelper = module.Enemies(OID.SpinningDiveHelper).FirstOrDefault(); - if (SpinningDiveHelper != null && !dived) - yield return new(SpinningDiveHelper.Position, 10, _activation, rect, SpinningDiveHelper.Rotation); - } + public override IEnumerable Sources(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_knockback); public override void OnActorCreated(BossModule module, Actor actor) { + var SpinningDiveHelper = module.Enemies(OID.SpinningDiveHelper).FirstOrDefault(); if ((OID)actor.OID == OID.SpinningDiveHelper) - { - dived = false; - _activation = module.WorldState.CurrentTime.AddSeconds(1.4f); - } + _knockback = new(SpinningDiveHelper!.Position, 10, module.WorldState.CurrentTime.AddSeconds(1.4f), new AOEShapeRect(46, 8), SpinningDiveHelper!.Rotation); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID == AID.SpinningDiveEffect) - dived = true; + _knockback = null; } public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false); diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs index c71878ce03..e4575eb502 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D011ForgivenDissonance.cs @@ -54,7 +54,10 @@ class WoodenHorse : Components.SelfTargetedAOEs class Pillory : Components.SingleTargetCast { - public Pillory() : base(ActionID.MakeSpell(AID.Pillory)) { } + public Pillory() : base(ActionID.MakeSpell(AID.Pillory)) + { + EndsOnCastEvent = true; + } } class D011ForgivenDissonanceStates : StateMachineBuilder diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs index cfb4102d22..d7efb7a30d 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D012TesleentheForgiven.cs @@ -38,7 +38,10 @@ public enum IconID : uint class TheTickler : Components.SingleTargetCast { - public TheTickler() : base(ActionID.MakeSpell(AID.TheTickler)) { } + public TheTickler() : base(ActionID.MakeSpell(AID.TheTickler)) + { + EndsOnCastEvent = true; + } } class ScoldsBridle : Components.RaidwideCast @@ -48,8 +51,6 @@ public ScoldsBridle() : base(ActionID.MakeSpell(AID.ScoldsBridle)) { } class FeveredFlagellation : Components.GenericBaitAway { - private static readonly AOEShapeRect rect = new AOEShapeRect(0, 2); - public override void Update(BossModule module) { foreach (var b in CurrentBaits) @@ -64,14 +65,9 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { - if (iconID == (uint)IconID.Icon1) - CurrentBaits.Add(new(module.PrimaryActor, actor, rect)); - if (iconID == (uint)IconID.Icon2) - CurrentBaits.Add(new(module.PrimaryActor, actor, rect)); - if (iconID == (uint)IconID.Icon3) - CurrentBaits.Add(new(module.PrimaryActor, actor, rect)); - if (iconID == (uint)IconID.Icon4) - CurrentBaits.Add(new(module.PrimaryActor, actor, rect)); + var icon = (IconID)iconID; + if (icon >= IconID.Icon1 && icon <= IconID.Icon4) + CurrentBaits.Add(new(module.PrimaryActor, actor, new AOEShapeRect(0, 2))); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs index 84175c2c6c..33e5ee9a1e 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs @@ -103,22 +103,6 @@ public override void AddGlobalHints(BossModule module, GlobalHints hints) hints.Add($"Destroy chains on {chaintarget.Name}!"); } - public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - if (chained && actor != chaintarget) - { - foreach (var e in hints.PotentialTargets) - { - e.Priority = (OID)e.Actor.OID switch - { - OID.IronChain => 1, - OID.Boss => -1, - _ => 0 - }; - } - } - } - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { if (iconID == (uint)IconID.ChainTarget) @@ -137,25 +121,14 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn class Aethersup : Components.GenericAOEs { - private bool activeSup; - private Actor? _caster; - private DateTime _activation; - private static readonly AOEShapeCone cone = new(24, 60.Degrees()); + private AOEInstance? _aoe; - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (activeSup && _caster != null) - yield return new(cone, _caster.Position, _caster.Rotation, _activation); - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.Aethersup) - { - activeSup = true; - _caster = caster; - _activation = spell.NPCFinishAt; - } + _aoe = new(new AOEShapeCone(24, 60.Degrees()), module.PrimaryActor.Position, spell.Rotation, spell.NPCFinishAt); } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) @@ -166,7 +139,7 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent case AID.Aethersup2: if (++NumCasts == 4) { - activeSup = false; + _aoe = null; NumCasts = 0; } break; @@ -208,6 +181,7 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { + base.AddHints(module, slot, actor, hints, movementHints); if (target == actor && targeted) hints.Add("Bait away!"); } @@ -228,25 +202,9 @@ class RightKnout : Components.SelfTargetedAOEs public RightKnout() : base(ActionID.MakeSpell(AID.RightKnout), new AOEShapeCone(24, 105.Degrees())) { } } - class Taphephobia : Components.UniformStackSpread + class Taphephobia : Components.SpreadFromCastTargets { - public Taphephobia() : base(0, 6, alwaysShowSpreads: true) { } - - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - if (iconID == (uint)IconID.Spread) - { - AddSpread(actor); - } - } - - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.Taphephobia2) - { - Spreads.Clear(); - } - } + public Taphephobia() : base(ActionID.MakeSpell(AID.Taphephobia2), 6) { } } // TODO: create and use generic 'line stack' component @@ -310,32 +268,33 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn class FierceBeating : Components.Exaflare { - private readonly List _casters = new(); + private readonly List _casters = []; private int linesstartedcounttotal; private int linesstartedcount1; private int linesstartedcount2; private static readonly AOEShapeCircle circle = new(4); private DateTime _activation; - private const float radianconversion = MathF.PI / 180; + private const float RadianConversion = 45 * (MathF.PI / 180); public FierceBeating() : base(4) { } public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - float angleInRadians1 = linesstartedcount1 * 45 * radianconversion; - float angleInRadians2 = linesstartedcount2 * 45 * radianconversion; - float cosTheta1 = MathF.Cos(angleInRadians1); - float sinTheta1 = MathF.Sin(angleInRadians1); - float cosTheta2 = MathF.Cos(angleInRadians2); - float sinTheta2 = MathF.Sin(angleInRadians2); foreach (var (c, t) in FutureAOEs(module.WorldState.CurrentTime)) yield return new(Shape, c, activation: t, color: FutureColor); foreach (var (c, t) in ImminentAOEs()) yield return new(Shape, c, activation: t, color: ImminentColor); if (Lines.Count > 0 && linesstartedcount1 < 8) - yield return new(circle, new(cosTheta1 * (_casters[0].X - module.Bounds.Center.X) - sinTheta1 * (_casters[0].Z - module.Bounds.Center.Z) + module.Bounds.Center.X, sinTheta1 * (_casters[0].X - module.Bounds.Center.X) + cosTheta1 * (_casters[0].Z - module.Bounds.Center.Z) + module.Bounds.Center.Z), activation: _activation.AddSeconds(linesstartedcount1 * 3.7f)); + yield return new(circle, CalculateCirclePosition(linesstartedcount1, module.Bounds.Center, _casters[0]), activation: _activation.AddSeconds(linesstartedcount1 * 3.7f)); if (Lines.Count > 1 && linesstartedcount2 < 8) - yield return new(circle, new(cosTheta2 * (_casters[1].X - module.Bounds.Center.X) - sinTheta2 * (_casters[1].Z - module.Bounds.Center.Z) + module.Bounds.Center.X, sinTheta2 * (_casters[1].X - module.Bounds.Center.X) + cosTheta2 * (_casters[1].Z - module.Bounds.Center.Z) + module.Bounds.Center.Z), activation: _activation.AddSeconds(linesstartedcount2 * 3.7f)); + yield return new(circle, CalculateCirclePosition(linesstartedcount2, module.Bounds.Center, _casters[1]), activation: _activation.AddSeconds(linesstartedcount2 * 3.7f)); + } + + private static WPos CalculateCirclePosition(int count, WPos origin, WPos caster) + { + float x = MathF.Cos(count * RadianConversion) * (caster.X - origin.X) - MathF.Sin(count * RadianConversion) * (caster.Z - origin.Z); + float z = MathF.Sin(count * RadianConversion) * (caster.X - origin.X) + MathF.Cos(count * RadianConversion) * (caster.Z - origin.Z); + return new WPos(origin.X + x, origin.Z + z); } public override void Update(BossModule module) @@ -376,19 +335,22 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent else ++linesstartedcount2; } - if ((AID)spell.Action.ID is AID.FierceBeating4 or AID.FierceBeating6) - { - int index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); - AdvanceLine(module, Lines[index], caster.Position); - if (Lines[index].ExplosionsLeft == 0) - Lines.RemoveAt(index); - } - if ((AID)spell.Action.ID == AID.FierceBeating5) + if (Lines.Count > 0) { - int index = Lines.FindIndex(item => item.Next.AlmostEqual(spell.TargetXZ, 1)); - AdvanceLine(module, Lines[index], spell.TargetXZ); - if (Lines[index].ExplosionsLeft == 0) - Lines.RemoveAt(index); + if ((AID)spell.Action.ID is AID.FierceBeating4 or AID.FierceBeating6) + { + int index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); + AdvanceLine(module, Lines[index], caster.Position); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } + if ((AID)spell.Action.ID == AID.FierceBeating5) + { + int index = Lines.FindIndex(item => item.Next.AlmostEqual(spell.TargetXZ, 1)); + AdvanceLine(module, Lines[index], spell.TargetXZ); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } } } } @@ -418,5 +380,21 @@ public D013PhiliaStates(BossModule module) : base(module) public class D013Philia : BossModule { public D013Philia(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(134, -465), 19.5f)) { } + + public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + if (FindComponent()?.chained ?? true && (FindComponent()?.chaintarget == actor)) + foreach (var e in hints.PotentialTargets) + { + e.Priority = (OID)e.Actor.OID switch + { + OID.IronChain => 1, + OID.Boss => -1, + _ => 0 + }; + } + else + base.CalculateAIHints(slot, actor, assignment, hints); + } } -} +} \ No newline at end of file diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs index 2a2ad1564c..b8fcee32cc 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs @@ -1,5 +1,4 @@ // CONTRIB: made by malediktus, not checked -using System; using System.Collections.Generic; namespace BossMod.Shadowbringers.Dungeon.D03QitanaRavel.D031Lozatl @@ -36,7 +35,10 @@ class LozatlsFuryB : Components.SelfTargetedAOEs class Stonefist : Components.SingleTargetCast { - public Stonefist() : base(ActionID.MakeSpell(AID.Stonefist)) { } + public Stonefist() : base(ActionID.MakeSpell(AID.Stonefist)) + { + EndsOnCastEvent = true; + } } class LozatlsScorn : Components.RaidwideCast @@ -51,38 +53,26 @@ public SunToss() : base(ActionID.MakeSpell(AID.SunToss), 5) { } class RonkanLight : Components.GenericAOEs { - private bool castingRight; - private bool castingLeft; private static readonly AOEShapeRect rect = new(60, 20); //TODO: double halfwidth is strange - private DateTime _activation; + private AOEInstance? _aoe; - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (castingRight) - yield return new(rect, module.Bounds.Center, 90.Degrees(), _activation); - if (castingLeft) - yield return new(rect, module.Bounds.Center, -90.Degrees(), _activation); - } + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnActorEAnim(BossModule module, Actor actor, uint state) { if (state == 0x00040008) { if (actor.Position.AlmostEqual(new(8, 328), 1)) - castingRight = true; + _aoe = new(rect, module.Bounds.Center, 90.Degrees(), module.WorldState.CurrentTime.AddSeconds(8)); if (actor.Position.AlmostEqual(new(-7, 328), 1)) - castingLeft = true; - _activation = module.WorldState.CurrentTime.AddSeconds(8); + _aoe = new(rect, module.Bounds.Center, -90.Degrees(), module.WorldState.CurrentTime.AddSeconds(8)); } } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID is AID.RonkanLightLeft or AID.RonkanLightRight) - { - castingRight = false; - castingLeft = false; - } + _aoe = null; } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D032Batsquatch.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D032Batsquatch.cs index df2539dd7d..77bee52aa3 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D032Batsquatch.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D032Batsquatch.cs @@ -37,7 +37,10 @@ public Subsonics() : base(ActionID.MakeSpell(AID.Subsonics), "Raidwide x11") { } class RipperFang : Components.SingleTargetCast { - public RipperFang() : base(ActionID.MakeSpell(AID.RipperFang)) { } + public RipperFang() : base(ActionID.MakeSpell(AID.RipperFang)) + { + EndsOnCastEvent = true; + } } class FallingBoulder : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs index 25a1ebe213..4accd38381 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs @@ -129,11 +129,6 @@ class ViperPoisonPatterns : Components.PersistentVoidzoneAtCastTarget public ViperPoisonPatterns() : base(6, ActionID.MakeSpell(AID.ViperPoisonPatterns), m => m.Enemies(OID.PoisonVoidzone).Where(z => z.EventState != 7), 0) { } } - class ViperPoisonBaitAway : Components.PersistentVoidzone - { - public ViperPoisonBaitAway() : base(6, m => m.Enemies(OID.PoisonVoidzone).Where(z => z.EventState != 7)) { } - } - class ConfessionOfFaithLeft : Components.SelfTargetedAOEs { public ConfessionOfFaithLeft() : base(ActionID.MakeSpell(AID.ConfessionOfFaithLeft), new AOEShapeCone(60, 46.Degrees(), 20.Degrees())) { } // TODO: verify; there should not be an offset in reality here... @@ -153,28 +148,13 @@ class ConfessionOfFaithCenter : Components.SelfTargetedAOEs public ConfessionOfFaithCenter() : base(ActionID.MakeSpell(AID.ConfessionOfFaithCenter), new AOEShapeCone(60, 40.Degrees())) { } } - class ConfessionOfFaithSpread : Components.UniformStackSpread + class ConfessionOfFaithSpread : Components.SpreadFromCastTargets { - public ConfessionOfFaithSpread() : base(0, 5, alwaysShowSpreads: true) { } - public override void OnEventIcon(BossModule module, Actor actor, uint iconID) - { - if (iconID == (uint)IconID.spread) - { - AddSpread(actor); - } - } - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID == AID.ConfessionOfFaithSpread) - { - Spreads.Clear(); - } - } + public ConfessionOfFaithSpread() : base(ActionID.MakeSpell(AID.ConfessionOfFaithSpread), 5) { } } - class ViperPoisonBait : Components.UniformStackSpread + class ViperPoisonBait : Components.GenericBaitAway { - public ViperPoisonBait() : base(0, 6, alwaysShowSpreads: true) { } private bool targeted; private Actor? target; @@ -182,7 +162,7 @@ public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { if (iconID == (uint)IconID.poisonbait) { - AddSpread(actor); + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(6))); targeted = true; target = actor; } @@ -192,17 +172,16 @@ public override void OnCastFinished(BossModule module, Actor caster, ActorCastIn { if ((AID)spell.Action.ID == AID.ViperPoisonBaitAway) { - Spreads.Clear(); + CurrentBaits.Clear(); targeted = false; } } public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { + base.AddHints(module, slot, actor, hints, movementHints); if (target == actor && targeted) - { - hints.Add("Bait away!"); - } + hints.Add("Bait voidzone away!"); } public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -218,7 +197,7 @@ class Inhale : Components.KnockbackFromCastTarget public Inhale() : base(ActionID.MakeSpell(AID.Inhale), 50, kind: Kind.TowardsOrigin) { } //TODO: consider testing if path is unsafe in addition to destination - public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false); + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } class HeavingBreath : Components.KnockbackFromCastTarget @@ -229,7 +208,7 @@ public HeavingBreath() : base(ActionID.MakeSpell(AID.HeavingBreath), 35, kind: K } //TODO: consider testing if path is unsafe in addition to destination - public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || (module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false); + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } class Glossolalia : Components.RaidwideCast @@ -239,7 +218,10 @@ public Glossolalia() : base(ActionID.MakeSpell(AID.Glossolalia)) { } class Rend : Components.SingleTargetCast { - public Rend() : base(ActionID.MakeSpell(AID.Rend)) { } + public Rend() : base(ActionID.MakeSpell(AID.Rend)) + { + EndsOnCastEvent = true; + } } class D033ErosStates : StateMachineBuilder @@ -249,7 +231,6 @@ public D033ErosStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D050ForgivePrejudice.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D050ForgivenPrejudice.cs similarity index 98% rename from BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D050ForgivePrejudice.cs rename to BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D050ForgivenPrejudice.cs index 2fa5c40c5e..0848286a3c 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D050ForgivePrejudice.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D050ForgivenPrejudice.cs @@ -31,7 +31,7 @@ class SanctifiedAero : Components.SelfTargetedAOEs class PunitiveLight : Components.CastInterruptHint { //Note: this attack is a r20 circle, not drawing it because it is too big and the damage not all that high even if interrupt/stun fails - public PunitiveLight() : base(ActionID.MakeSpell(AID.PunitiveLight), true, true, "(Raidwide)") { } + public PunitiveLight() : base(ActionID.MakeSpell(AID.PunitiveLight), true, true, "(Raidwide)", true) { } } class Sanctification : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D051ForgivenCruelty.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D051ForgivenCruelty.cs index e6f1fbda7e..3a61c2f913 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D051ForgivenCruelty.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D051ForgivenCruelty.cs @@ -26,7 +26,10 @@ public enum AID : uint class Rake : Components.SingleTargetCast { - public Rake() : base(ActionID.MakeSpell(AID.Rake)) { } + public Rake() : base(ActionID.MakeSpell(AID.Rake)) + { + EndsOnCastEvent = true; + } } class CycloneWing : Components.RaidwideCast diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D052ForgivenApathy.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D052ForgivenApathy.cs index 8faa7e0554..be075159fc 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D052ForgivenApathy.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D052ForgivenApathy.cs @@ -1,7 +1,4 @@ // CONTRIB: made by malediktus, not checked -using System.Collections.Generic; -using System.Linq; - namespace BossMod.Shadowbringers.Dungeon.D05MtGulg.D052ForgivenApathy { public enum OID : uint @@ -29,7 +26,7 @@ public enum AID : uint class PunitiveLight : Components.CastInterruptHint { //Note: this attack is a r20 circle, not drawing it because it is too big and the damage not all that high even if interrupt/stun fails - public PunitiveLight() : base(ActionID.MakeSpell(AID.PunitiveLight), true, true, "(Raidwide)") { } + public PunitiveLight() : base(ActionID.MakeSpell(AID.PunitiveLight), true, true, "(Raidwide)", true) { } } class Sanctification : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs index 7c2e756f5d..bdbcebc274 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs @@ -66,40 +66,19 @@ class PerfectContrition : Components.SelfTargetedAOEs public class JudgmentDay : Components.GenericTowers { - private bool tower1; - private bool tower2; - private Actor? Tower1; - public override void OnActorEState(BossModule module, Actor actor, ushort state) { if (state is 0x01C or 0x02C) { - if (!tower1) - { - tower1 = true; - Towers.Add(new(actor.Position, 5, 1, 1)); - Tower1 = actor; - } - if (tower1 && !tower2 && actor != Tower1) - { - tower2 = true; + if (!Towers.Contains(new (actor.Position, 5, 1, 1))) Towers.Add(new(actor.Position, 5, 1, 1)); - } } } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID is AID.Judged or AID.FoundWanting) - { - Towers.RemoveAll(t => t.Position.AlmostEqual(caster.Position, 1)); - if (Towers.Count == 0) //Note: I don't think towers can repeat, this is just a safety precaution - { - tower1 = default; - tower2 = default; - Tower1 = default; - } - } + Towers.RemoveAt(0); } public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -114,15 +93,13 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR class Exegesis : Components.GenericAOEs { private DateTime _activation; - private bool ExegesisA; - private bool ExegesisB; - private bool ExegesisC; - private bool ExegesisD; + public enum Patterns { None, Diagonal, Cross, EastWest, NorthSouth } + public Patterns Pattern { get; private set; } private static readonly AOEShapeRect rect = new(5, 5, 5); public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - if (ExegesisA) //diagonal squares + if (Pattern == Patterns.Diagonal) { yield return new(rect, new(-240, -50), default, _activation); yield return new(rect, new(-250, -40), default, _activation); @@ -130,17 +107,17 @@ public override IEnumerable ActiveAOEs(BossModule module, int slot, yield return new(rect, new(-250, -60), default, _activation); yield return new(rect, new(-230, -60), default, _activation); } - if (ExegesisB) //West+East Square + if (Pattern == Patterns.EastWest) { yield return new(rect, new(-250, -50), default, _activation); yield return new(rect, new(-230, -50), default, _activation); } - if (ExegesisC) //North+South Square + if (Pattern == Patterns.NorthSouth) { yield return new(rect, new(-240, -60), default, _activation); yield return new(rect, new(-240, -40), default, _activation); } - if (ExegesisD) //cross pattern + if (Pattern == Patterns.Cross) { yield return new(rect, new(-230, -50), default, _activation); yield return new(rect, new(-240, -60), default, _activation); @@ -155,44 +132,28 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf switch ((AID)spell.Action.ID) { case AID.ExegesisA: - ExegesisA = true; - _activation = spell.NPCFinishAt; + Pattern = Patterns.Diagonal; + _activation = spell.NPCFinishAt.AddSeconds(0.5f); break; case AID.ExegesisB: - ExegesisB = true; - _activation = spell.NPCFinishAt; + Pattern = Patterns.EastWest; + _activation = spell.NPCFinishAt.AddSeconds(0.5f); break; case AID.ExegesisC: - ExegesisC = true; - _activation = spell.NPCFinishAt; + Pattern = Patterns.NorthSouth; + _activation = spell.NPCFinishAt.AddSeconds(0.5f); break; case AID.ExegesisD: - ExegesisD = true; - _activation = spell.NPCFinishAt; + Pattern = Patterns.Cross; + _activation = spell.NPCFinishAt.AddSeconds(0.5f); break; } } - public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) - { - if ((AID)spell.Action.ID is AID.ExegesisA or AID.ExegesisB or AID.ExegesisC or AID.ExegesisD) - { - ExegesisA = false; - ExegesisB = false; - ExegesisC = false; - ExegesisD = false; - } - } - - public override void DrawArenaForeground(BossModule module, int pcSlot, Actor pc, MiniArena arena) + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { - if (ExegesisA || ExegesisB || ExegesisC || ExegesisD) - { - arena.AddLine(new(-235, -65), new(-235, -35), ArenaColor.Border, 2); - arena.AddLine(new(-245, -65), new(-245, -35), ArenaColor.Border, 2); - arena.AddLine(new(-225, -55), new(-255, -55), ArenaColor.Border, 2); - arena.AddLine(new(-225, -45), new(-255, -45), ArenaColor.Border, 2); - } + if ((AID)spell.Action.ID == AID.Exegesis) + Pattern = Patterns.None; } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs index ea62021970..2360505b9b 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D054ForgivenRevelry.cs @@ -24,31 +24,21 @@ public enum AID : uint class PalmAttacks : Components.GenericAOEs //Palm Attacks have a wrong origin, so i made a custom solution { - private DateTime _activation; - private bool left; - private bool right; - private static readonly AOEShapeRect rect = new(15, 15); - public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) - { - if (left) - yield return new(rect, new(module.PrimaryActor.Position.X, module.Bounds.Center.Z), -90.Degrees(), _activation); - if (right) - yield return new(rect, new(module.PrimaryActor.Position.X, module.Bounds.Center.Z), 90.Degrees(), _activation); + private AOEInstance? _aoe; - } + private static readonly AOEShapeRect rect = new(15, 15); + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { switch ((AID)spell.Action.ID) { case AID.LeftPalm2: - left = true; - _activation = spell.NPCFinishAt; + _aoe = new(rect, new(module.PrimaryActor.Position.X, module.Bounds.Center.Z), -90.Degrees(), spell.NPCFinishAt); break; case AID.RightPalm2: - right = true; - _activation = spell.NPCFinishAt; + _aoe = new(rect, new(module.PrimaryActor.Position.X, module.Bounds.Center.Z), 90.Degrees(), spell.NPCFinishAt); break; } } @@ -56,10 +46,7 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID is AID.LeftPalm2 or AID.RightPalm2) - { - left = false; - right = false; - } + _aoe = null; } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs index 6e44d86545..0569486748 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs @@ -42,7 +42,7 @@ public enum AID : uint class Orbs : Components.GenericAOEs { - private List _orbs = new(); + private readonly List _orbs = []; private static readonly AOEShapeCircle circle = new(3); public Orbs() : base(new(), "GTFO from voidzone!") { } @@ -54,9 +54,7 @@ public override IEnumerable ActiveAOEs(BossModule module, int slot, public override void OnActorCreated(BossModule module, Actor actor) { if ((OID)actor.OID == OID.Orbs) - { _orbs.Add(actor); - } } public override void OnActorEAnim(BossModule module, Actor actor, uint state) { @@ -68,8 +66,9 @@ public override void OnActorEAnim(BossModule module, Actor actor, uint state) class GoldChaser : Components.GenericAOEs { private DateTime _activation; - private List _casters = new(); + private readonly List _casters = []; private static readonly AOEShapeRect rect = new(100, 2.5f, 100); + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { if (_casters.Count > 1 && ((_casters[0].Position.AlmostEqual(new(-227.5f, 253), 1) && _casters[1].Position.AlmostEqual(new(-232.5f, 251.5f), 1)) || (_casters[0].Position.AlmostEqual(new(-252.5f, 253), 1) && _casters[1].Position.AlmostEqual(new(-247.5f, 251.5f), 1)))) @@ -175,14 +174,6 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent NumCasts = 0; } } - if ((AID)spell.Action.ID == AID.AutoAttack) - { - if (NumCasts > 0) //failsafe - { - _casters.Clear(); - NumCasts = 0; - } - } } } @@ -257,17 +248,15 @@ class Voidzone : BossComponent public override void OnActorEAnim(BossModule module, Actor actor, uint state) { if (state == 0x00040008) + { + module.Arena.Bounds = new ArenaBoundsRect(new(-240, 237), 15, 20); active = false; + } if (state == 0x00010002) + { active = true; - } - - public override void Update(BossModule module) - { - if (!active) - module.Arena.Bounds = new ArenaBoundsRect(new(-240, 237), 15, 20); - if (active) - module.Arena.Bounds = new ArenaBoundsCircle(new(-240, 237), 15); + module.Arena.Bounds = new ArenaBoundsCircle(new(-240, 237), 15); + } } public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) diff --git a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE31MetalFoxChaos.cs b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE31MetalFoxChaos.cs index d8ea5c1049..0223c23280 100644 --- a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE31MetalFoxChaos.cs +++ b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE31MetalFoxChaos.cs @@ -1,6 +1,7 @@ // CONTRIB: made by malediktus, not checked using System; using System.Collections.Generic; +using System.Linq; namespace BossMod.Shadowbringers.Foray.CriticalEngagement.CE31MetalFoxChaos { @@ -25,199 +26,73 @@ public enum AID : uint class MagitekBitLasers : Components.GenericAOEs { - private int numcasts; - private bool SatelliteLaser; - private bool DiffractiveLaserAngle0; - private bool DiffractiveLaserAngle90; - private bool DiffractiveLaserAngle180; - private bool DiffractiveLaserAngleM90; - private bool LaserShowerAngle90; - private bool LaserShowerAngleM90; - private bool LaserShowerAngle0; - private bool LaserShowerAngle180; - private DateTime time; - private DateTime _activation1; - private DateTime _activation2; - private DateTime _activation3; + private static readonly Angle[] rotations = [0.Degrees(), 90.Degrees(), 180.Degrees(), -90.Degrees()]; + private readonly List _times = []; + private Angle startrotation; + public enum Types { None, SatelliteLaser, DiffractiveLaser, LaserShower } + public Types Type { get; private set; } private const float maxError = MathF.PI / 180; private static readonly AOEShapeRect rect = new(100, 3); public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { - foreach (var p in module.Enemies(OID.MagitekBit)) - { - if (module.Bounds.Contains(p.Position)) + if (_times.Count > 0) + foreach (var p in module.Enemies(OID.MagitekBit)) { - if (SatelliteLaser && module.WorldState.CurrentTime > time.AddSeconds(2.5f) && (p.Rotation.AlmostEqual(180.Degrees(), maxError) || p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation1); - if (DiffractiveLaserAngle0 && module.WorldState.CurrentTime > time.AddSeconds(2)) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(180.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(0.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(0.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (DiffractiveLaserAngleM90 && module.WorldState.CurrentTime > time.AddSeconds(2)) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(-90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(-90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (DiffractiveLaserAngle90 && module.WorldState.CurrentTime > time.AddSeconds(2)) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(-90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (DiffractiveLaserAngle180 && module.WorldState.CurrentTime > time.AddSeconds(2)) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(0.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(180.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(180.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (LaserShowerAngle90) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(-90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(-90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (LaserShowerAngleM90) + if (Type == Types.SatelliteLaser && module.WorldState.CurrentTime > _times[0]) + yield return new(rect, p.Position, p.Rotation, _times[1]); + if ((Type == Types.DiffractiveLaser && module.WorldState.CurrentTime > _times[0]) || Type == Types.LaserShower) { - if (numcasts < 5 && p.Rotation.AlmostEqual(-90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(0.Degrees(), maxError) || p.Rotation.AlmostEqual(180.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(90.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (LaserShowerAngle0) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(0.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(180.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(180.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); - } - if (LaserShowerAngle180) - { - if (numcasts < 5 && p.Rotation.AlmostEqual(180.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation1, ArenaColor.Danger); - if (numcasts < 5 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2); - if (numcasts >= 5 && numcasts < 9 && (p.Rotation.AlmostEqual(90.Degrees(), maxError) || p.Rotation.AlmostEqual(-90.Degrees(), maxError))) - yield return new(rect, p.Position, p.Rotation, _activation2, ArenaColor.Danger); - if (numcasts >= 5 && numcasts < 9 && p.Rotation.AlmostEqual(0.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3); - if (numcasts >= 9 && p.Rotation.AlmostEqual(0.Degrees(), maxError)) - yield return new(rect, p.Position, p.Rotation, _activation3, ArenaColor.Danger); + if (NumCasts < 5 && p.Rotation.AlmostEqual(startrotation, maxError)) + yield return new(rect, p.Position, p.Rotation, _times[1], ArenaColor.Danger); + if (NumCasts < 5 && (p.Rotation.AlmostEqual(startrotation + 90.Degrees(), maxError) || p.Rotation.AlmostEqual(startrotation - 90.Degrees(), maxError))) + yield return new(rect, p.Position, p.Rotation, _times[2]); + if (NumCasts >= 5 && NumCasts < 9 && (p.Rotation.AlmostEqual(startrotation + 90.Degrees(), maxError) || p.Rotation.AlmostEqual(startrotation - 90.Degrees(), maxError))) + yield return new(rect, p.Position, p.Rotation, _times[2], ArenaColor.Danger); + if (NumCasts >= 5 && NumCasts < 9 && p.Rotation.AlmostEqual(startrotation + 180.Degrees(), maxError)) + yield return new(rect, p.Position, p.Rotation, _times[3]); + if (NumCasts >= 9 && p.Rotation.AlmostEqual(startrotation + 180.Degrees(), maxError)) + yield return new(rect, p.Position, p.Rotation, _times[3], ArenaColor.Danger); } } - } } public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) { + var _time = module.WorldState.CurrentTime; if ((AID)spell.Action.ID == AID.SatelliteLaser) { - SatelliteLaser = true; - time = module.WorldState.CurrentTime; - _activation1 = module.WorldState.CurrentTime.AddSeconds(12.3f); + Type = Types.SatelliteLaser; + _times.Add(_time.AddSeconds(2.5f)); + _times.Add(_time.AddSeconds(12.3f)); } if ((AID)spell.Action.ID == AID.DiffractiveLaser) { - { - if (spell.Rotation.AlmostEqual(0.Degrees(), maxError)) - DiffractiveLaserAngle0 = true; - if (spell.Rotation.AlmostEqual(-90.Degrees(), maxError)) - DiffractiveLaserAngleM90 = true; - if (spell.Rotation.AlmostEqual(90.Degrees(), maxError)) - DiffractiveLaserAngle90 = true; - if (spell.Rotation.AlmostEqual(180.Degrees(), maxError)) - DiffractiveLaserAngle180 = true; - } - time = module.WorldState.CurrentTime; - _activation1 = module.WorldState.CurrentTime.AddSeconds(8.8f); - _activation2 = module.WorldState.CurrentTime.AddSeconds(10.6f); - _activation3 = module.WorldState.CurrentTime.AddSeconds(12.4f); + DateTime[] times = [_time.AddSeconds(2), _time.AddSeconds(8.8f), _time.AddSeconds(10.6f), _time.AddSeconds(12.4f)]; + startrotation = rotations.FirstOrDefault(r => spell.Rotation.AlmostEqual(r, maxError)) + 180.Degrees(); + Type = Types.DiffractiveLaser; + _times.AddRange(times); } if ((AID)spell.Action.ID == AID.LaserShower2) { - { - if (caster.Rotation.AlmostEqual(90.Degrees(), maxError)) - LaserShowerAngle90 = true; - if (caster.Rotation.AlmostEqual(0.Degrees(), maxError)) - LaserShowerAngle0 = true; - if (caster.Rotation.AlmostEqual(180.Degrees(), maxError)) - LaserShowerAngle180 = true; - if (caster.Rotation.AlmostEqual(-90.Degrees(), maxError)) - LaserShowerAngleM90 = true; - } - _activation1 = module.WorldState.CurrentTime.AddSeconds(6.5f); - _activation2 = module.WorldState.CurrentTime.AddSeconds(8.3f); - _activation3 = module.WorldState.CurrentTime.AddSeconds(10.1f); + DateTime[] times = [_time, _time.AddSeconds(6.5f), _time.AddSeconds(8.3f), _time.AddSeconds(10.1f)]; + startrotation = rotations.FirstOrDefault(r => caster.Rotation.AlmostEqual(r, maxError)); + Type = Types.LaserShower; + _times.AddRange(times); } } public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) { - base.OnEventCast(module, caster, spell); if ((AID)spell.Action.ID == AID.RefractedLaser) - ++numcasts; - if (numcasts == 14) { - numcasts = 0; - DiffractiveLaserAngle0 = false; - DiffractiveLaserAngle90 = false; - DiffractiveLaserAngleM90 = false; - DiffractiveLaserAngle180 = false; - SatelliteLaser = false; - LaserShowerAngle90 = false; - LaserShowerAngleM90 = false; - LaserShowerAngle0 = false; - LaserShowerAngle180 = false; + ++NumCasts; + if (NumCasts == 14) + { + NumCasts = 0; + _times.Clear(); + Type = Types.None; + } } } } diff --git a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE42FromBeyondTheGrave.cs b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE42FromBeyondTheGrave.cs index 8375b11ec9..e8b4dbb53f 100644 --- a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE42FromBeyondTheGrave.cs +++ b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE42FromBeyondTheGrave.cs @@ -155,7 +155,7 @@ public Aethertide() : base(ActionID.MakeSpell(AID.AethertideAOE), 8) { } class MarchingBreath : Components.CastInterruptHint //heals all allies by 20% of max health (raidwide) { - public MarchingBreath() : base(ActionID.MakeSpell(AID.MarchingBreath), hint: "(20% HP AOE heal)") { } + public MarchingBreath() : base(ActionID.MakeSpell(AID.MarchingBreath), showNameInHint: true) { } } class TacticalAero : Components.SelfTargetedAOEs @@ -175,7 +175,7 @@ public DarkFlare() : base(ActionID.MakeSpell(AID.DarkFlare), 8) { } class SoulSacrifice : Components.CastInterruptHint //WarWraith sacrifices itself to give boss a damage buff { - public SoulSacrifice() : base(ActionID.MakeSpell(AID.SoulSacrifice), hint: "(Dmg buff on boss)") { } + public SoulSacrifice() : base(ActionID.MakeSpell(AID.SoulSacrifice), showNameInHint: true) { } } class PurifyingLight : Components.LocationTargetedAOEs diff --git a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE44FamiliarFace.cs b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE44FamiliarFace.cs index e3c5f18376..2548f0086a 100644 --- a/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE44FamiliarFace.cs +++ b/BossMod/Modules/Shadowbringers/Foray/CriticalEngagement/CE44FamiliarFace.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace BossMod.Shadowbringers.Foray.CriticalEngagement.CE44FamiliarFace { @@ -50,7 +52,10 @@ public TectonicEruption() : base(ActionID.MakeSpell(AID.TectonicEruption), 6) { class RockCutter : Components.SingleTargetCast { - public RockCutter() : base(ActionID.MakeSpell(AID.RockCutter)) { } + public RockCutter() : base(ActionID.MakeSpell(AID.RockCutter)) + { + EndsOnCastEvent = true; + } } class AncientQuake : Components.RaidwideCast @@ -68,15 +73,52 @@ class ControlTowerAppear : Components.SelfTargetedAOEs public ControlTowerAppear() : base(ActionID.MakeSpell(AID.ControlTowerAppear), new AOEShapeCircle(6)) { } } - class Towerfall : Components.SelfTargetedAOEs + class Towerfall : Components.GenericAOEs { - public Towerfall() : base(ActionID.MakeSpell(AID.Towerfall), new AOEShapeRect(40, 5)) { } + private readonly List<(Actor tower, DateTime activation)> _towers = []; + public enum Types { None, TowerRound, ControlTower } + public Types Type { get; private set; } + private readonly static AOEShapeRect _shape = new(40, 5); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + foreach (var c in _towers) + yield return new(_shape, c.tower.Position, c.tower.Rotation, c.activation); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + switch ((AID)spell.Action.ID) + { + case AID.TowerRound: + Type = Types.TowerRound; + break; + case AID.ControlTower: + Type = Types.ControlTower; + break; + case AID.Towerfall: + Type = Types.None; + break; + } + } + + public override void OnActorCreated(BossModule module, Actor actor) + { + if ((OID)actor.OID == OID.FallingTower) + _towers.Add((actor, Type == Types.TowerRound ? module.WorldState.CurrentTime.AddSeconds(24.4f) : Type == Types.ControlTower ? module.WorldState.CurrentTime.AddSeconds(9.6f) : module.WorldState.CurrentTime.AddSeconds(13))); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Towerfall) + _towers.RemoveAll(c => c.tower.Position.AlmostEqual(caster.Position, 1)); + } } class ExtremeEdge : Components.GenericAOEs { - private List<(Actor caster, float offset)> _casters = new(); - private static AOEShapeRect _shape = new(60, 18); + private readonly List<(Actor caster, float offset)> _casters = []; + private readonly static AOEShapeRect _shape = new(60, 18); public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) { @@ -88,8 +130,8 @@ public override void OnCastStarted(BossModule module, Actor caster, ActorCastInf { var offset = (AID)spell.Action.ID switch { - AID.ExtremeEdgeL => 15, - AID.ExtremeEdgeR => -15, + AID.ExtremeEdgeL => 12, + AID.ExtremeEdgeR => -12, _ => 0 }; if (offset != 0) @@ -134,10 +176,24 @@ public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent } } - // TODO: consider prediction - actor-create happens ~4.7s before cast start - class Hammerfall : Components.SelfTargetedAOEs + class Hammerfall : Components.GenericAOEs { - public Hammerfall() : base(ActionID.MakeSpell(AID.Hammerfall), new AOEShapeCircle(37)) { } + private readonly List _aoes = []; + private readonly static AOEShapeCircle _shape = new(37); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(2); + + public override void OnActorCreated(BossModule module, Actor actor) + { + if ((OID)actor.OID == OID.Hammer) + _aoes.Add(new(_shape, actor.Position, activation: module.WorldState.CurrentTime.AddSeconds(12.6f))); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Hammerfall) + _aoes.RemoveAt(0); + } } class CE44FamiliarFaceStates : StateMachineBuilder diff --git a/BossMod/Modules/Shadowbringers/HuntA/Baal.cs b/BossMod/Modules/Shadowbringers/HuntA/Baal.cs new file mode 100644 index 0000000000..6eb3fcab08 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Baal.cs @@ -0,0 +1,91 @@ +// CONTRIB: made by malediktus, not checked +using System; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntA.Baal +{ + public enum OID : uint + { + Boss = 0x2854, // R=3.2 + }; + + public enum AID : uint + { + AutoAttack = 872, // 2854/2850->player, no cast, single-target + SewerWater = 17956, // 2854->self, 3,0s cast, range 12 180-degree cone + SewerWater2 = 17957, // 2854->self, 3,0s cast, range 12 180-degree cone + SewageWave = 17423, // 2854->self, 5,0s cast, range 30 180-degree cone + SewageWave1 = 17422, // 2854->self, no cast, range 30 180-degree cone + SewageWave2 = 17424, // 2854->self, 5,0s cast, range 30 180-degree cone + SewageWave3 = 17421, // 2854->self, no cast, range 30 180-degree cone + }; + + class SewerWater : Components.SelfTargetedAOEs + { + public SewerWater() : base(ActionID.MakeSpell(AID.SewerWater), new AOEShapeCone(12, 90.Degrees())) { } + } + + class SewerWater2 : Components.SelfTargetedAOEs + { + public SewerWater2() : base(ActionID.MakeSpell(AID.SewerWater2), new AOEShapeCone(12, 90.Degrees())) { } + } + + class SewageWave : Components.GenericAOEs + { + private static readonly AOEShapeCone cone = new(30, 90.Degrees()); + private DateTime _activation; + private Angle _rotation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_activation != default) + { + if (NumCasts == 0) + { + yield return new(cone, module.PrimaryActor.Position, _rotation, _activation, ArenaColor.Danger); + yield return new(cone, module.PrimaryActor.Position, _rotation + 180.Degrees(), _activation.AddSeconds(2.3f), risky: false); + } + if (NumCasts == 1) + yield return new(cone, module.PrimaryActor.Position, _rotation + 180.Degrees(), _activation.AddSeconds(2.3f), ArenaColor.Danger); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.SewageWave or AID.SewageWave2) + { + _rotation = spell.Rotation; + _activation = spell.NPCFinishAt; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.SewageWave or AID.SewageWave2) + ++NumCasts; + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.SewageWave1 or AID.SewageWave3) + { + NumCasts = 0; + _activation = default; + } + } + } + + class BaalStates : StateMachineBuilder + { + public BaalStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 140)] + public class Baal(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/Grassman.cs b/BossMod/Modules/Shadowbringers/HuntA/Grassman.cs new file mode 100644 index 0000000000..3bfad74715 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Grassman.cs @@ -0,0 +1,103 @@ +// CONTRIB: made by malediktus, not checked +using System; + +namespace BossMod.Shadowbringers.HuntA.Grassman +{ + public enum OID : uint + { + Boss = 0x283A, // R=4.0 + }; + + public enum AID : uint + { + AutoAttack = 872, // 283A->player, no cast, single-target + ChestThump = 17859, // 283A->self, 4,0s cast, range 30 circle, one cast on 1st time, 5 hits on subsequent times, dmg buff on boss for each cast + ChestThump2 = 17863, // 283A->self, no cast, range 30 circle + StoolPelt = 17861, // 283A->location, 3,0s cast, range 5 circle + Browbeat = 17860, // 283A->player, 4,0s cast, single-target + Streak = 17862, // 283A->location, 3,0s cast, width 6 rect charge, knockback 10, away from source + }; + + class ChestThump : BossComponent + { + private int NumCasts; + private int NumCasts2; + private bool casting; + private DateTime _activation; + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.ChestThump) + { + casting = true; + _activation = spell.NPCFinishAt; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.ChestThump) + { + ++NumCasts; + if (NumCasts == 1) + casting = false; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ChestThump2) + { + ++NumCasts2; + if (NumCasts2 == 4) + { + casting = false; + NumCasts2 = 0; + } + } + } + + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (casting && NumCasts == 0) + hints.Add($"Raidwide"); + if (casting && NumCasts > 0) + hints.Add($"Raidwide x5"); + } + + public override void AddAIHints(BossModule module, int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + hints.PredictedDamage.Add((module.Raid.WithSlot().Mask(), _activation)); + } + } + + class StoolPelt : Components.LocationTargetedAOEs + { + public StoolPelt() : base(ActionID.MakeSpell(AID.StoolPelt), 5) { } + } + + class Browbeat : Components.SingleTargetCast + { + public Browbeat() : base(ActionID.MakeSpell(AID.Browbeat)) { } + } + + class Streak : Components.ChargeAOEs + { + public Streak() : base(ActionID.MakeSpell(AID.Streak), 3) { } + } + + class GrassmanStates : StateMachineBuilder + { + public GrassmanStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 135)] + public class Grassman(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/Huracan.cs b/BossMod/Modules/Shadowbringers/HuntA/Huracan.cs new file mode 100644 index 0000000000..776c7ada6e --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Huracan.cs @@ -0,0 +1,110 @@ +// CONTRIB: made by malediktus, not checked +using System; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntA.Huracan +{ + public enum OID : uint + { + Boss = 0x28B5, // R=4.9 + }; + + public enum AID : uint + { + AutoAttack = 872, // Boss->player, no cast, single-target + WindsEnd = 17494, // Boss->player, no cast, single-target + WinterRain = 17497, // Boss->location, 4,0s cast, range 6 circle + Windburst = 18042, // Boss->self, no cast, range 80 width 10 rect + SummerHeat = 17499, // Boss->self, 4,0s cast, range 40 circle + DawnsEdge = 17495, // Boss->self, 3,5s cast, range 15 width 10 rect + SpringBreeze = 17496, // Boss->self, 3,5s cast, range 80 width 10 rect + AutumnWreath = 17498, // Boss->self, 4,0s cast, range 10-20 donut + }; + + class SpringBreeze : Components.SelfTargetedAOEs + { + public SpringBreeze() : base(ActionID.MakeSpell(AID.SpringBreeze), new AOEShapeRect(40, 5, 40)) { } + } + + class SummerHeat : Components.RaidwideCast + { + public SummerHeat() : base(ActionID.MakeSpell(AID.SummerHeat)) { } + } + + class Combos : Components.GenericAOEs + { + private static readonly AOEShapeDonut donut = new(10, 20); + private static readonly AOEShapeCircle circle = new(6); + private static readonly AOEShapeRect rect = new(15, 5); + private static readonly AOEShapeRect rect2 = new(40, 5, 40); + private DateTime _activation; + private Angle _rotation; + private AOEShape? _shape; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_activation != default && _shape != null) + { + if (NumCasts == 0) + { + yield return new(_shape, module.PrimaryActor.Position, _rotation, _activation, ArenaColor.Danger); + yield return new(rect2, module.PrimaryActor.Position, module.PrimaryActor.Rotation, _activation.AddSeconds(3.1f), risky: false); + } + if (NumCasts == 1) + yield return new(rect2, module.PrimaryActor.Position, module.PrimaryActor.Rotation, _activation.AddSeconds(3.1f), ArenaColor.Danger); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.AutumnWreath) + { + _rotation = spell.Rotation; + _activation = spell.NPCFinishAt; + _shape = donut; + } + if ((AID)spell.Action.ID is AID.DawnsEdge) + { + _rotation = spell.Rotation; + _activation = spell.NPCFinishAt; + _shape = rect; + } + if ((AID)spell.Action.ID is AID.WinterRain) + { + _rotation = spell.Rotation; + _activation = spell.NPCFinishAt; + _shape = circle; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.AutumnWreath or AID.DawnsEdge or AID.WinterRain) + ++NumCasts; + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.Windburst) + { + NumCasts = 0; + _activation = default; + _shape = null; + } + } + } + + class HuracanStates : StateMachineBuilder + { + public HuracanStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 120)] + public class Huracan(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/LilMurderer.cs b/BossMod/Modules/Shadowbringers/HuntA/LilMurderer.cs new file mode 100644 index 0000000000..2f5696f79b --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/LilMurderer.cs @@ -0,0 +1,84 @@ +// CONTRIB: made by malediktus, not checked +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntA.LilMurderer +{ + public enum OID : uint + { + Boss = 0x28B4, // R=2.1 + }; + + public enum AID : uint + { + AutoAttack = 872, // Boss->player, no cast, single-target + GobthunderIII = 17493, // Boss->player, 6,0s cast, range 20 circle, interruptible, applies Lightning Resistance Down II + GoblinPunch = 17488, // Boss->player, 3,0s cast, single-target + GobthunderII = 17492, // Boss->location, 4,0s cast, range 8 circle, applies Lightning Resistance Down II + Gobhaste = 17491, // Boss->self, 3,0s cast, single-target + GoblinSlash = 17489, // Boss->self, no cast, range 8 circle, sometimes boss uses Gobthunder II on itself, next attack after is this + }; + + + class GoblinSlash : Components.GenericAOEs + { + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.GobthunderII && spell.LocXZ == caster.Position) + _aoe = new(new AOEShapeCircle(8), module.PrimaryActor.Position, activation: spell.NPCFinishAt.AddSeconds(2.6f)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.GoblinSlash) + _aoe = null; + } + } + + + class GobthunderIII : Components.SpreadFromCastTargets + { + public GobthunderIII() : base(ActionID.MakeSpell(AID.GobthunderIII), 20) { } + } + + class GobthunderIIIHint : Components.CastInterruptHint + { + public GobthunderIIIHint() : base(ActionID.MakeSpell(AID.GobthunderIII)) { } + } + + class GoblinPunch : Components.SingleTargetCast + { + public GoblinPunch() : base(ActionID.MakeSpell(AID.GoblinPunch)) { } + } + + class Gobhaste : Components.CastHint + { + public Gobhaste() : base(ActionID.MakeSpell(AID.Gobhaste), "Attack speed buff") { } + } + + class GobthunderII : Components.LocationTargetedAOEs + { + public GobthunderII() : base(ActionID.MakeSpell(AID.GobthunderII), 8) { } + } + + + class LilMurdererStates : StateMachineBuilder + { + public LilMurdererStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 119)] + public class LilMurderer(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/Maliktender.cs b/BossMod/Modules/Shadowbringers/HuntA/Maliktender.cs index b7b7500425..9aa38fbfdf 100644 --- a/BossMod/Modules/Shadowbringers/HuntA/Maliktender.cs +++ b/BossMod/Modules/Shadowbringers/HuntA/Maliktender.cs @@ -3,7 +3,7 @@ namespace BossMod.Shadowbringers.HuntA.Maliktender { public enum OID : uint { - Boss = 0x2874, + Boss = 0x2874, // R=3.06 }; public enum AID : uint @@ -28,7 +28,7 @@ class Sabotendance : Components.SelfTargetedAOEs class TwentyKNeedles : Components.SelfTargetedAOEs { - public TwentyKNeedles() : base(ActionID.MakeSpell(AID.TwentyKNeedles), new AOEShapeRect(20,4)) { } + public TwentyKNeedles() : base(ActionID.MakeSpell(AID.TwentyKNeedles), new AOEShapeRect(20, 4)) { } } class Haste : BossComponent @@ -48,7 +48,7 @@ public override void AddGlobalHints(BossModule module, GlobalHints hints) class NineNineNineKNeedles : Components.SelfTargetedAOEs { - public NineNineNineKNeedles() : base(ActionID.MakeSpell(AID.NineNineNineKNeedles), new AOEShapeRect(20,4)) { } + public NineNineNineKNeedles() : base(ActionID.MakeSpell(AID.NineNineNineKNeedles), new AOEShapeRect(20, 4)) { } } class MaliktenderStates : StateMachineBuilder diff --git a/BossMod/Modules/Shadowbringers/HuntA/Nariphon.cs b/BossMod/Modules/Shadowbringers/HuntA/Nariphon.cs new file mode 100644 index 0000000000..6d0afd0284 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Nariphon.cs @@ -0,0 +1,108 @@ +// CONTRIB: made by malediktus, not checked +namespace BossMod.Shadowbringers.HuntA.Nariphon +{ + public enum OID : uint + { + Boss = 0x2890, // R=6.0 + }; + + public enum AID : uint + { + AutoAttack = 870, // 2890->player, no cast, single-target + VineHammer = 16969, // 2890->player, no cast, single-target, attacks several random players in a row + AllergenInjection = 16972, // 2890->player, 5,0s cast, range 6 circle + RootsOfAtopy = 16971, // 2890->player, 5,0s cast, range 6 circle + OdiousMiasma = 16970, // 2890->self, 3,0s cast, range 12 120-degree cone + }; + + public enum SID : uint + { + PiercingResistanceDownII = 1435, // Boss->player, extra=0x0 + }; + + public enum IconID : uint + { + Baitaway = 140, // player + Stackmarker = 62, // player + }; + + + class OdiousMiasma : Components.SelfTargetedAOEs + { + public OdiousMiasma() : base(ActionID.MakeSpell(AID.OdiousMiasma), new AOEShapeCone(12, 60.Degrees())) { } + } + + class AllergenInjection : Components.GenericBaitAway + { + private bool targeted; + private Actor? target; + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Baitaway) + { + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(6))); + targeted = true; + target = actor; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.AllergenInjection) + { + CurrentBaits.Clear(); + targeted = false; + } + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + base.AddHints(module, slot, actor, hints, movementHints); + if (target == actor && targeted) + hints.Add("Bait away!"); + } + } + + class RootsOfAtopy : Components.GenericStackSpread + { + private BitMask _forbidden; + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.RootsOfAtopy) + Stacks.Add(new(module.WorldState.Actors.Find(spell.TargetID)!, 6, activation: spell.NPCFinishAt, forbiddenPlayers: _forbidden)); + } + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.PiercingResistanceDownII) + _forbidden.Set(module.Raid.FindSlot(actor.InstanceID)); + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.PiercingResistanceDownII) + _forbidden.Clear(module.Raid.FindSlot(actor.InstanceID)); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.RootsOfAtopy) + Stacks.RemoveAt(0); + } + } + + class NariphonStates : StateMachineBuilder + { + public NariphonStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 115)] + public class Nariphon(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/Nuckelavee.cs b/BossMod/Modules/Shadowbringers/HuntA/Nuckelavee.cs new file mode 100644 index 0000000000..d3942a3df6 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Nuckelavee.cs @@ -0,0 +1,86 @@ +// CONTRIB: made by malediktus, not checked +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntA.Nuckelavee +{ + public enum OID : uint + { + Boss = 0x288F, // R=3.6 + }; + + public enum AID : uint + { + AutoAttack = 872, // 288F->player, no cast, single-target + Torpedo = 16964, // 288F->player, 4,0s cast, single-target, tankbuster on cast event + BogBody = 16965, // 288F->player, 5,0s cast, range 5 circle, spread, applies bleed that can be dispelled + Gallop = 16967, // 288F->location, 4,5s cast, rushes to target and casts Spite + Spite = 18037, // 288F->self, no cast, range 8 circle + }; + + class Torpedo : Components.SingleTargetCast + { //Tankbuster resolves on cast event instead of cast finished + private List _casters = new(); + public new IReadOnlyList Casters => _casters; + public new bool Active => _casters.Count > 0; + + public Torpedo() : base(ActionID.MakeSpell(AID.Torpedo)) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if (spell.Action == WatchedAction) + _casters.Add(caster); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (spell.Action == WatchedAction) + _casters.Remove(caster); + } + + public override void AddGlobalHints(BossModule module, GlobalHints hints) + { + if (Active) + hints.Add("Tankbuster"); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { } + } + + class BogBody : Components.SpreadFromCastTargets + { + public BogBody() : base(ActionID.MakeSpell(AID.BogBody), 5) { } + } + + class Spite : Components.GenericAOEs + { + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Gallop) + _aoe = new(new AOEShapeCircle(8), spell.LocXZ, activation: spell.NPCFinishAt); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.Spite) + _aoe = null; + } + } + + class NuckelaveeStates : StateMachineBuilder + { + public NuckelaveeStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 114)] + public class Nuckelavee(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/OPoorestPauldia.cs b/BossMod/Modules/Shadowbringers/HuntA/OPoorestPauldia.cs new file mode 100644 index 0000000000..d1a9e429f6 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/OPoorestPauldia.cs @@ -0,0 +1,52 @@ +// CONTRIB: made by malediktus, not checked +namespace BossMod.Shadowbringers.HuntA.OPoorestPauldia +{ + public enum OID : uint + { + Boss = 0x2820, // R=4.025 + }; + + public enum AID : uint + { + AutoAttack = 870, // 2820->player, no cast, single-target + RustingClaw = 16830, // 2820->self, 3,0s cast, range 8+R 120-degree cone + TailDrive = 16831, // 2820->self, 5,0s cast, range 30+R 90-degree cone + WordsOfWoe = 16832, // 2820->self, 3,0s cast, range 45+R width 6 rect + TheSpin = 16833, // 2820->self, 3,0s cast, range 40 circle + }; + + class RustingClaw : Components.SelfTargetedAOEs + { + public RustingClaw() : base(ActionID.MakeSpell(AID.RustingClaw), new AOEShapeCone(12.025f, 60.Degrees())) { } + } + + class TailDrive : Components.SelfTargetedAOEs + { + public TailDrive() : base(ActionID.MakeSpell(AID.TailDrive), new AOEShapeCone(34.025f, 60.Degrees())) { } + } + + class WordsOfWoe : Components.SelfTargetedAOEs + { + public WordsOfWoe() : base(ActionID.MakeSpell(AID.WordsOfWoe), new AOEShapeRect(49.025f, 3)) { } + } + + class TheSpin : Components.RaidwideCast + { + public TheSpin() : base(ActionID.MakeSpell(AID.TheSpin)) { } + } + + class OPoorestPauldiaStates : StateMachineBuilder + { + public OPoorestPauldiaStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 130)] + public class OPoorestPauldia(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/Rusalka.cs b/BossMod/Modules/Shadowbringers/HuntA/Rusalka.cs new file mode 100644 index 0000000000..f1183967d8 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Rusalka.cs @@ -0,0 +1,71 @@ +// CONTRIB: made by malediktus, not checked +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Shadowbringers.HuntA.Rusalka +{ + public enum OID : uint + { + Boss = 0x2853, // R=3.6 + }; + + public enum AID : uint + { + AutoAttack = 17364, // Boss->player, no cast, single-target + Hydrocannon = 17363, // Boss->location, 3,5s cast, range 8 circle + AetherialSpark = 17368, // Boss->self, 2,5s cast, range 12 width 4 rect + AetherialPull = 17366, // Boss->self, 4,0s cast, range 30 circle, pull 30 between centers + Flood = 17369, // Boss->self, no cast, range 8 circle + }; + + class Hydrocannon : Components.LocationTargetedAOEs + { + public Hydrocannon() : base(ActionID.MakeSpell(AID.Hydrocannon), 8) { } + } + + class AetherialSpark : Components.SelfTargetedAOEs + { + public AetherialSpark() : base(ActionID.MakeSpell(AID.AetherialSpark), new AOEShapeRect(12, 2)) { } + } + + class AetherialPull : Components.KnockbackFromCastTarget + { + public AetherialPull() : base(ActionID.MakeSpell(AID.AetherialPull), 30, shape: new AOEShapeCircle(30), kind: Kind.TowardsOrigin) { } + + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; + } + + class Flood : Components.GenericAOEs + { + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.AetherialPull) + _aoe = new(new AOEShapeCircle(8), module.PrimaryActor.Position, activation: spell.NPCFinishAt.AddSeconds(3.6f)); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.Flood) + _aoe = null; + } + } + + class RusalkaStates : StateMachineBuilder + { + public RusalkaStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 139)] + public class Rusalka(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/Supay.cs b/BossMod/Modules/Shadowbringers/HuntA/Supay.cs new file mode 100644 index 0000000000..2e34b7ed62 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/Supay.cs @@ -0,0 +1,72 @@ +// CONTRIB: made by malediktus, not checked + +namespace BossMod.Shadowbringers.HuntA.Supay +{ + public enum OID : uint + { + Boss = 0x2839, // R=3.6 + }; + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + BlasphemousHowl = 17858, // Boss->players, 3,0s cast, range 8 circle, spread, applies terror + PetroEyes = 17856, // Boss->self, 3,0s cast, range 40 circle, gaze, inflicts petrification + Beakaxe = 17857, // Boss->player, no cast, single-target, instantlyy kills petrified players + }; + + public enum IconID : uint + { + Baitaway = 159, // player + }; + + class BlasphemousHowl : Components.GenericBaitAway + { + private bool targeted; + private Actor? target; + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Baitaway) + { + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(8))); + targeted = true; + target = actor; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.BlasphemousHowl) + { + CurrentBaits.Clear(); + targeted = false; + } + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + base.AddHints(module, slot, actor, hints, movementHints); + if (target == actor && targeted) + hints.Add("Bait away + look away!"); + } + } + + class PetroEyes : Components.CastGaze + { + public PetroEyes() : base(ActionID.MakeSpell(AID.PetroEyes)) { } + } + + class SupayStates : StateMachineBuilder + { + public SupayStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 134)] + public class Supay(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntA/TheMudman.cs b/BossMod/Modules/Shadowbringers/HuntA/TheMudman.cs new file mode 100644 index 0000000000..318b99e5a6 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntA/TheMudman.cs @@ -0,0 +1,90 @@ +// CONTRIB: made by malediktus, not checked +namespace BossMod.Shadowbringers.HuntA.TheMudman +{ + public enum OID : uint + { + Boss = 0x281F, // R=4.2 + }; + + public enum AID : uint + { + AutoAttack = 872, // Boss->player, no cast, single-target + FeculentFlood = 16828, // Boss->self, 3,0s cast, range 40 60-degree cone + RoyalFlush = 16826, // Boss->self, 3,0s cast, range 8 circle + BogBequest = 16827, // Boss->self, 5,0s cast, range 5-20 donut + GravityForce = 16829, // Boss->player, 5,0s cast, range 6 circle, interruptible, applies heavy + }; + + public enum IconID : uint + { + Baitaway = 140, // player + }; + + class BogBequest : Components.SelfTargetedAOEs + { + public BogBequest() : base(ActionID.MakeSpell(AID.BogBequest), new AOEShapeDonut(5, 20)) { } + } + + class FeculentFlood : Components.SelfTargetedAOEs + { + public FeculentFlood() : base(ActionID.MakeSpell(AID.FeculentFlood), new AOEShapeCone(40, 30.Degrees())) { } + } + + class RoyalFlush : Components.SelfTargetedAOEs + { + public RoyalFlush() : base(ActionID.MakeSpell(AID.RoyalFlush), new AOEShapeCircle(8)) { } + } + + class GravityForce : Components.GenericBaitAway + { + private bool targeted; + private Actor? target; + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Baitaway) + { + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(6))); + targeted = true; + target = actor; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.GravityForce) + { + CurrentBaits.Clear(); + targeted = false; + } + } + + public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) + { + base.AddHints(module, slot, actor, hints, movementHints); + if (target == actor && targeted) + hints.Add("Bait away or interrupt!"); + } + } + + class GravityForceHint : Components.CastInterruptHint + { + public GravityForceHint() : base(ActionID.MakeSpell(AID.GravityForce)) { } + } + + class TheMudmanStates : StateMachineBuilder + { + public TheMudmanStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 129)] + public class TheMudman(WorldState ws, Actor primary) : SimpleBossModule(ws, primary) {} +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/Aglaope.cs b/BossMod/Modules/Shadowbringers/HuntS/Aglaope.cs new file mode 100644 index 0000000000..07d1df9b45 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/Aglaope.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntS.Aglaope +{ + public enum OID : uint + { + Boss = 0x281E, // R=2.4 + }; + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + FourfoldSuffering = 16819, // Boss->self, 5,0s cast, range 5-50 donut + SeductiveSonata = 16824, // Boss->self, 3,0s cast, range 40 circle, applies Seduced for 6s (forced march towards boss at 1.7y/s) + DeathlyVerse = 17074, // Boss->self, 5,0s cast, range 6 circle (right after Seductive Sonata, instant kill), 6*1.7 = 10.2 + 6 = 16.2y minimum distance to survive + Tornado = 18040, // Boss->location, 3,0s cast, range 6 circle + AncientAero = 16823, // Boss->self, 3,0s cast, range 40+R width 6 rect + SongOfTorment = 16825, // Boss->self, 5,0s cast, range 50 circle, interruptible raidwide with bleed + AncientAeroIII = 18056, // Boss->self, 3,0s cast, range 30 circle, knockback 10, away from source + }; + + public enum SID : uint + { + Seduced = 991, // Boss->player, extra=0x11 + Bleeding = 642, // Boss->player, extra=0x0 + }; + + class SongOfTorment : Components.CastInterruptHint + { + public SongOfTorment() : base(ActionID.MakeSpell(AID.SongOfTorment), hint: "(Raidwide + Bleed)") { } + } + +//TODO: ideally this AOE should just wait for Effect Results, since they can be delayed by over 2.1s, which would cause unknowning players and AI to run back into the death zone, +//not sure how to do this though considering there can be anywhere from 0-32 targets with different time for effect results each + class SeductiveSonata : Components.GenericAOEs + { + private DateTime _activation; + private DateTime _time; + private bool casting; + private static readonly AOEShapeCircle circle = new(16.2f); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (casting || (_time != default && _time > module.WorldState.CurrentTime)) + yield return new(circle, module.PrimaryActor.Position, activation: _activation); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SeductiveSonata) + { + casting = true; + _activation = spell.NPCFinishAt; + _time = spell.NPCFinishAt.AddSeconds(2.2f); + } + } + + public override void Update(BossModule module) + { + if (_time != default && _time < module.WorldState.CurrentTime) + { + _time = default; + casting = false; + } + } + } + + class DeathlyVerse : Components.SelfTargetedAOEs + { + public DeathlyVerse() : base(ActionID.MakeSpell(AID.DeathlyVerse), new AOEShapeCircle(6)) { } + } + + class Tornado : Components.LocationTargetedAOEs + { + public Tornado() : base(ActionID.MakeSpell(AID.Tornado), 6) { } + } + + class FourfoldSuffering : Components.SelfTargetedAOEs + { + public FourfoldSuffering() : base(ActionID.MakeSpell(AID.FourfoldSuffering), new AOEShapeDonut(5, 50)) { } + } + + class AncientAero : Components.SelfTargetedAOEs + { + public AncientAero() : base(ActionID.MakeSpell(AID.AncientAero), new AOEShapeRect(42.4f, 3)) { } + } + + class AncientAeroIII : Components.RaidwideCast + { + public AncientAeroIII() : base(ActionID.MakeSpell(AID.AncientAeroIII)) { } + } + + class AncientAeroIIIKB : Components.KnockbackFromCastTarget + { + public AncientAeroIIIKB() : base(ActionID.MakeSpell(AID.AncientAeroIII), 10, shape: new AOEShapeCircle(30)) { } + } + + class AglaopeStates : StateMachineBuilder + { + public AglaopeStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 131)] + public class Aglaope : SimpleBossModule + { + public Aglaope(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/ForgivenGossip.cs b/BossMod/Modules/Shadowbringers/HuntS/ForgivenGossip.cs new file mode 100644 index 0000000000..7d6b5870d1 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/ForgivenGossip.cs @@ -0,0 +1,40 @@ +namespace BossMod.Shadowbringers.HuntS.ForgivenGossip +{ + public enum OID : uint + { + Boss = 0x2A03, // R=0.75 + }; + + public enum AID : uint + { + AutoAttack = 18129, // Boss->player, no cast, single-target + Icefall = 17043, // Boss->location, 3,0s cast, range 5 circle, deadly if petrified by gaze + PetrifyingEye = 18041, // Boss->self, 3,0s cast, range 40 circle + }; + + class Icefall : Components.LocationTargetedAOEs + { + public Icefall() : base(ActionID.MakeSpell(AID.Icefall), 5) { } + } + + class PetrifyingEye : Components.CastGaze + { + public PetrifyingEye() : base(ActionID.MakeSpell(AID.PetrifyingEye)) { } + } + + class ForgivenGossipStates : StateMachineBuilder + { + public ForgivenGossipStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 142)] + public class ForgivenGossip : SimpleBossModule + { + public ForgivenGossip(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/ForgivenPedantry.cs b/BossMod/Modules/Shadowbringers/HuntS/ForgivenPedantry.cs new file mode 100644 index 0000000000..df5b4def1c --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/ForgivenPedantry.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntS.ForgivenPedantry +{ + public enum OID : uint + { + Boss = 0x298A, // R=5.5 + }; + + public enum AID : uint + { + AutoAttack_SanctifiedScathe = 17439, // 298A->player, no cast, single-target + LeftCheek = 17446, // 298A->self, 5,0s cast, range 60 180-degree cone + LeftCheek2 = 17447, // 298A->self, no cast, range 60 180-degree cone + RightCheek = 17448, // 298A->self, 5,0s cast, range 60 180-degree cone + RightCheek2 = 17449, // 298A->self, no cast, range 60 180-degree cone + TerrifyingGlance = 17955, // 298A->self, 3,0s cast, range 50 circle, gaze + TheStake = 17443, // 298A->self, 4,0s cast, range 18 circle + SecondCircle = 17441, // 298A->self, 3,0s cast, range 40 width 8 rect + CleansingFire = 17442, // 298A->self, 4,0s cast, range 40 circle + FeveredFlagellation = 17440, // 298A->players, 4,0s cast, range 15 90-degree cone, tankbuster + SanctifiedShock = 17900, // 298A->player, no cast, single-target, stuns target before WitchHunt + WitchHunt = 17444, // 298A->players, 3,0s cast, width 10 rect charge + WitchHunt2 = 17445, // 298A->players, no cast, width 10 rect charge, targets main tank + }; + + class LeftRightCheek : Components.GenericAOEs + { + private static readonly AOEShapeCone cone = new(60, 90.Degrees()); + private DateTime _activation; + private Angle _rotation; + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_activation != default) + { + if (NumCasts == 0) + { + yield return new(cone, module.PrimaryActor.Position, _rotation, _activation, ArenaColor.Danger); + yield return new(cone, module.PrimaryActor.Position, _rotation + 180.Degrees(), _activation.AddSeconds(3.1f), risky: false); + } + if (NumCasts == 1) + yield return new(cone, module.PrimaryActor.Position, _rotation + 180.Degrees(), _activation.AddSeconds(3.1f), ArenaColor.Danger); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.LeftCheek or AID.RightCheek) + { + _rotation = spell.Rotation; + _activation = spell.NPCFinishAt; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.LeftCheek or AID.RightCheek) + ++NumCasts; + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.LeftCheek2 or AID.RightCheek2) + { + NumCasts = 0; + _activation = default; + } + } + } + + class TerrifyingGlance : Components.CastGaze + { + public TerrifyingGlance() : base(ActionID.MakeSpell(AID.TerrifyingGlance)) { } + } + + class TheStake : Components.SelfTargetedAOEs + { + public TheStake() : base(ActionID.MakeSpell(AID.TheStake), new AOEShapeCircle(18)) { } + } + + class SecondCircle : Components.SelfTargetedAOEs + { + public SecondCircle() : base(ActionID.MakeSpell(AID.SecondCircle), new AOEShapeRect(40, 4)) { } + } + + class CleansingFire : Components.CastGaze + { + public CleansingFire() : base(ActionID.MakeSpell(AID.CleansingFire)) { } + } + + class FeveredFlagellation : Components.BaitAwayCast + { + public FeveredFlagellation() : base(ActionID.MakeSpell(AID.FeveredFlagellation), new AOEShapeCone(15, 45.Degrees())) { } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) //tankbuster resolves on cast event, which can be delayed by moving out of tankbuster range + { + if (spell.Action == WatchedAction) + CurrentBaits.RemoveAll(b => b.Source == caster); + } + } + + class FeveredFlagellationHint : Components.SingleTargetCast + { + public FeveredFlagellationHint() : base(ActionID.MakeSpell(AID.FeveredFlagellation), "Cleave tankbuster") { } + } + + class WitchHunt : Components.GenericBaitAway + { + private static readonly AOEShapeRect rect = new AOEShapeRect(0, 5); + private bool witchHunt1done; + + public override void Update(BossModule module) + { + foreach (var b in CurrentBaits) + ((AOEShapeRect)b.Shape).LengthFront = (b.Target.Position - b.Source.Position).Length(); + if (CurrentBaits.Count > 0 && witchHunt1done) //updating WitchHunt2 target incase of sudden tank swap + { + var Target = CurrentBaits[0]; + Target.Target = module.WorldState.Actors.Find(module.PrimaryActor.TargetID)!; + CurrentBaits[0] = Target; + } + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.SanctifiedShock) + CurrentBaits.Add(new(module.PrimaryActor, module.WorldState.Actors.Find(spell.MainTargetID)!, rect)); + if ((AID)spell.Action.ID == AID.WitchHunt2) + { + CurrentBaits.Clear(); + witchHunt1done = false; + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.WitchHunt) + { + CurrentBaits.Clear(); + CurrentBaits.Add(new(module.PrimaryActor, module.WorldState.Actors.Find(module.PrimaryActor.TargetID)!, rect)); + } + } + } + + class ForgivenPedantryStates : StateMachineBuilder + { + public ForgivenPedantryStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 121)] + public class ForgivenPedantry : SimpleBossModule + { + public ForgivenPedantry(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/ForgivenRebellion.cs b/BossMod/Modules/Shadowbringers/HuntS/ForgivenRebellion.cs new file mode 100644 index 0000000000..5ae1b5110a --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/ForgivenRebellion.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BossMod.Shadowbringers.HuntS.ForgivenRebellion +{ + public enum OID : uint + { + Boss = 0x28B6, // R=3.4 + }; + + public enum AID : uint + { + AutoAttack = 872, // Boss->player, no cast, single-target + SanctifiedBlizzard = 17598, // Boss->self, 3,0s cast, range 40 45-degree cone + RoyalDecree = 17597, // Boss->self, 4,0s cast, range 40 circle, raidwide + SanctifiedBlizzardChain = 17628, // Boss->self, 5,0s cast, range 40 45-degree cone, seems to rotate 45° in a random direction, no AID or Icon to tell apart + SanctifiedBlizzardChain2 = 17629, // Boss->self, 0,5s cast, range 40 45-degree cone + SanctifiedBlizzardChain3 = 18080, // Boss->self, 0,5s cast, range 40 45-degree cone + HeavenlyScythe = 17600, // Boss->self, 2,5s cast, range 10 circle + Transference = 17611, // Boss->player, no cast, single-target, gap closer + RotateCW = 18078, // Boss->self, 0,5s cast, single-target + RotateCCW = 18079, // Boss->self, 0,5s cast, single-target + HeavenlyCyclone = 18126, // Boss->self, 5,0s cast, range 28 180-degree cone + HeavenlyCyclone1 = 18127, // Boss->self, 0,5s cast, range 28 180-degree cone + HeavenlyCyclone2 = 18128, // Boss->self, 0,5s cast, range 28 180-degree cone + Mindjack = 17599, // Boss->self, 4,0s cast, range 40 circle, applies forced march buffs + RagingFire = 17601, // Boss->self, 5,0s cast, range 5-40 donut + Interference = 17602, // Boss->self, 4,5s cast, range 28 180-degree cone + }; + + public enum SID : uint + { + AboutFace = 1959, // Boss->player, extra=0x0 + ForwardMarch = 1958, // Boss->player, extra=0x0 + RightFace = 1961, // Boss->player, extra=0x0 + LeftFace = 1960, // Boss->player, extra=0x0 + ForcedMarch = 1257, // Boss->player, extra=0x2/0x1/0x8/0x4 + }; + + public enum IconID : uint + { + RotateCCW = 168, // Boss + RotateCW = 167, // Boss + }; + + class SanctifiedBlizzardChain : Components.GenericRotatingAOE + { + private Angle _increment; + private DateTime _activation; + private Angle _rot1; + private Angle _rot2; + private static readonly AOEShapeCone _shape = new(40, 22.5f.Degrees()); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + // direction seems to be server side until after first rotation + if (_rot1 != default && Sequences.Count == 0 && NumCasts == 0) + { + yield return new(_shape, module.PrimaryActor.Position, _rot1, _activation, ImminentColor); + yield return new(_shape, module.PrimaryActor.Position, _rot1 + 45.Degrees(), _activation, FutureColor); + yield return new(_shape, module.PrimaryActor.Position, _rot1 - 45.Degrees(), _activation, FutureColor); + } + if (_rot1 != default && Sequences.Count == 0 && NumCasts == 1) + { + yield return new(_shape, module.PrimaryActor.Position, _rot1 + 45.Degrees(), _activation, ImminentColor); + yield return new(_shape, module.PrimaryActor.Position, _rot1 - 45.Degrees(), _activation, ImminentColor); + } + foreach (var s in Sequences) + { + int num = Math.Min(s.NumRemainingCasts, s.MaxShownAOEs); + var rot = s.Rotation; + var time = s.NextActivation > module.WorldState.CurrentTime ? s.NextActivation : module.WorldState.CurrentTime; + for (int i = 1; i < num; ++i) + { + rot += s.Increment; + time = time.AddSeconds(s.SecondsBetweenActivations); + yield return new(s.Shape, s.Origin, rot, time, FutureColor); + } + } + foreach (var s in Sequences) + if (s.NumRemainingCasts > 0) + yield return new(s.Shape, s.Origin, s.Rotation, s.NextActivation, ImminentColor); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SanctifiedBlizzardChain) + { + _activation = spell.NPCFinishAt; + _rot1 = spell.Rotation; + } + if ((AID)spell.Action.ID is AID.SanctifiedBlizzardChain2 or AID.SanctifiedBlizzardChain3) + { + if (NumCasts == 1) + _rot2 = spell.Rotation; + if ((_rot1 - _rot2).Normalized().Rad > 0) + _increment = -45.Degrees(); + if ((_rot1 - _rot2).Normalized().Rad < 0) + _increment = 45.Degrees(); + if (Sequences.Count == 0) + Sequences.Add(new(_shape, module.PrimaryActor.Position, _rot2, _increment, _activation, 1.3f, 7)); + } + } + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SanctifiedBlizzardChain) + ++NumCasts; + if ((AID)spell.Action.ID is AID.SanctifiedBlizzardChain2 or AID.SanctifiedBlizzardChain3) + { + if (Sequences.Count > 0) + AdvanceSequence(0, module.WorldState.CurrentTime); + if (NumCasts == 8) + { + NumCasts = 0; + _rot1 = default; + _rot2 = default; + } + } + } + } + + class SanctifiedBlizzardChainHint : Components.RaidwideCast + { + public SanctifiedBlizzardChainHint() : base(ActionID.MakeSpell(AID.SanctifiedBlizzardChain), "Rotation direction undeterminable until start of the 2nd cast") { } + } + + class HeavenlyCyclone : Components.GenericRotatingAOE + { + private Angle _increment; + private Angle _rotation; + private DateTime _activation; + private static readonly AOEShapeCone _shape = new(28, 90.Degrees()); + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + var increment = (IconID)iconID switch + { + IconID.RotateCW => -90.Degrees(), + IconID.RotateCCW => 90.Degrees(), + _ => default + }; + if (increment != default) + { + _increment = increment; + InitIfReady(module, actor); + } + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.RotateCW or AID.RotateCCW) + { + _rotation = spell.Rotation; + _activation = spell.NPCFinishAt.AddSeconds(5.2f); + } + if (_rotation != default) + InitIfReady(module, caster); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (Sequences.Count > 0 && (AID)spell.Action.ID is AID.HeavenlyCyclone or AID.HeavenlyCyclone1 or AID.HeavenlyCyclone2) + AdvanceSequence(0, module.WorldState.CurrentTime); + } + + private void InitIfReady(BossModule module, Actor source) + { + if (_rotation != default && _increment != default) + { + Sequences.Add(new(_shape, source.Position, _rotation, _increment, _activation, 1.7f, 6)); + _rotation = default; + _increment = default; + } + } + } + + class HeavenlyScythe : Components.SelfTargetedAOEs + { + public HeavenlyScythe() : base(ActionID.MakeSpell(AID.HeavenlyScythe), new AOEShapeCircle(10)) { } + } + + class RagingFire : Components.SelfTargetedAOEs + { + public RagingFire() : base(ActionID.MakeSpell(AID.RagingFire), new AOEShapeDonut(5, 40)) { } + } + + class Interference : Components.SelfTargetedAOEs + { + public Interference() : base(ActionID.MakeSpell(AID.Interference), new AOEShapeCone(28, 90.Degrees())) { } + } + + class SanctifiedBlizzard : Components.SelfTargetedAOEs + { + public SanctifiedBlizzard() : base(ActionID.MakeSpell(AID.SanctifiedBlizzard), new AOEShapeCone(40, 22.5f.Degrees())) { } + } + + class RoyalDecree : Components.RaidwideCast + { + public RoyalDecree() : base(ActionID.MakeSpell(AID.RoyalDecree)) { } + } + + class MindJack : Components.StatusDrivenForcedMarch + { + public MindJack() : base(2, (uint)SID.ForwardMarch, (uint)SID.AboutFace, (uint)SID.LeftFace, (uint)SID.RightFace) { } + + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) + { + if (module.FindComponent() != null && module.FindComponent()!.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation))) + return true; + if (module.FindComponent() != null && module.FindComponent()!.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation))) + return true; + else + return false; + } + } + + class ForgivenRebellionStates : StateMachineBuilder + { + public ForgivenRebellionStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 146)] + public class ForgivenRebellion : SimpleBossModule + { + public ForgivenRebellion(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/Gunitt.cs b/BossMod/Modules/Shadowbringers/HuntS/Gunitt.cs new file mode 100644 index 0000000000..851d36f7af --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/Gunitt.cs @@ -0,0 +1,121 @@ +namespace BossMod.Shadowbringers.HuntS.Gunitt +{ + public enum OID : uint + { + Boss = 0x2852, // R=4.0 + }; + + public enum AID : uint + { + AutoAttack = 870, // 2852->player, no cast, single-target + TheDeepSeeks = 17356, // 2852->player, 4,0s cast, single-target + TheDeepReaches = 17357, // 2852->self, 4,0s cast, range 40 width 2 rect + TheDeepBeckons = 17358, // 2852->self, 4,0s cast, range 40 circle + Abordage = 17359, // 2852->players, no cast, width 8 rect charge, seems to target random player before stack marker, no telegraph? + SwivelGun = 17361, // 2852->players, 5,0s cast, range 10 circle, stack marker, applies magic vuln up, 3 times in a row + CoinToss = 17360, // 2852->self, 4,0s cast, range 40 circle, gaze, applies Seduced (forced march to boss) + TheDeepRends = 17351, // 2852->self, 5,5s cast, range 20 60-degree cone + TheDeepRends2 = 17352, // 2852->self, no cast, range 20 60-degree cone, seems to target 5 random players after first The Deep Rends, no telegraph? + }; + + public enum SID : uint + { + MagicVulnerabilityUp = 1138, // Boss->player, extra=0x0 + Seduced = 227, // Boss->player, extra=0x0 + }; + + public enum IconID : uint + { + Stackmarker = 93, // player + }; + + + class TheDeepSeeks : Components.SingleTargetCast + { + public TheDeepSeeks() : base(ActionID.MakeSpell(AID.TheDeepSeeks)) { } + } + + class TheDeepReaches : Components.SelfTargetedAOEs + { + public TheDeepReaches() : base(ActionID.MakeSpell(AID.TheDeepReaches), new AOEShapeRect(40, 1)) { } + } + + class TheDeepBeckons : Components.RaidwideCast + { + public TheDeepBeckons() : base(ActionID.MakeSpell(AID.TheDeepBeckons)) { } + } + + class CoinToss : Components.CastGaze + { + public CoinToss() : base(ActionID.MakeSpell(AID.CoinToss)) { } + } + + class TheDeepRends : Components.SelfTargetedAOEs + { + public TheDeepRends() : base(ActionID.MakeSpell(AID.TheDeepRends), new AOEShapeCone(20, 30.Degrees())) { } + } + + class TheDeepRendsHint : Components.CastHint + { + public TheDeepRendsHint() : base(ActionID.MakeSpell(AID.TheDeepRends), "Targets 5 random players after initial hit") { } + } + + class SwivelGun : Components.GenericStackSpread + { + private BitMask _forbidden; + + public override void Update(BossModule module) + { + if (Stacks.Count > 0) //updating forbiddenplayers because debuffs can be applied after new stack marker appears + { + var Forbidden = Stacks[0]; + Forbidden.ForbiddenPlayers = _forbidden; + Stacks[0] = Forbidden; + } + } + + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) + { + if (iconID == (uint)IconID.Stackmarker) + Stacks.Add(new(actor, 10, activation: module.WorldState.CurrentTime.AddSeconds(5), forbiddenPlayers: _forbidden)); + } + public override void OnStatusGain(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.MagicVulnerabilityUp) + _forbidden.Set(module.Raid.FindSlot(actor.InstanceID)); + } + + public override void OnStatusLose(BossModule module, Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.MagicVulnerabilityUp) + _forbidden.Clear(module.Raid.FindSlot(actor.InstanceID)); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.SwivelGun) + Stacks.RemoveAt(0); + } + } + + class GunittStates : StateMachineBuilder + { + public GunittStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 141)] + public class Gunitt : SimpleBossModule + { + public Gunitt(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/Ixtab.cs b/BossMod/Modules/Shadowbringers/HuntS/Ixtab.cs new file mode 100644 index 0000000000..9e8849c015 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/Ixtab.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace BossMod.Shadowbringers.HuntS.Ixtab +{ + public enum OID : uint + { + Boss = 0x2838, // R=3.24 + }; + + public enum AID : uint + { + AutoAttack = 17850, // Boss->player, no cast, single-target + TartareanAbyss = 17848, // Boss->players, 4,0s cast, range 6 circle + TartareanFlare = 17846, // Boss->location, 4,5s cast, range 18 circle + TartareanBlizzard = 17845, // Boss->self, 3,0s cast, range 40 45-degree cone + TartareanFlame = 17999, // Boss->self, 5,0s cast, range 8-40 donut + TartareanFlame2 = 18074, // Boss->self, no cast, range 8-40 donut + TartareanThunder = 17843, // Boss->location, 5,0s cast, range 20 circle + TartareanThunder2 = 18075, // Boss->location, no cast, range 20 circle + TartareanMeteor = 17844, // Boss->players, 5,0s cast, range 10 circle + ArchaicDualcast = 18077, // Boss->self, 3,0s cast, single-target, either out/in or in/out with Tartarean Flame and Tartarean Thunder + Cryptcall = 17847, // Boss->self/players, 3,0s cast, range 35+R 120-degree cone, sets hp to 1, applies heal to full doom with 25s duration + TartareanQuake = 17849, // Boss->self, 4,0s cast, range 40 circle + TartareanTwister = 18072, // Boss->self, 5,0s cast, range 55 circle, raidwide + windburn DoT, interruptible + }; + + public enum SID : uint + { + Burns = 267, // Boss->player, extra=0x0 + Dualcast = 1798, // Boss->Boss, extra=0x0 + Paralysis = 17, // Boss->player, extra=0x0 + Doom = 1769, // Boss->player, extra=0x0 + }; + + class DualCastTartareanFlameThunder : Components.GenericAOEs + { + private readonly List _aoes = new(); + private static readonly AOEShapeCircle circle = new(20); + private static readonly AOEShapeDonut donut = new(8, 40); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) => _aoes.Take(1); + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + var dualcast = module.PrimaryActor.FindStatus(SID.Dualcast) != null; + if ((AID)spell.Action.ID == AID.TartareanThunder) + if (!dualcast) + _aoes.Add(new(circle, caster.Position, activation: spell.NPCFinishAt)); + else + { + _aoes.Add(new(circle, caster.Position, activation: spell.NPCFinishAt)); + _aoes.Add(new(donut, caster.Position, activation: spell.NPCFinishAt.AddSeconds(5.1f))); + } + if ((AID)spell.Action.ID == AID.TartareanFlame) + if (!dualcast) + _aoes.Add(new(donut, caster.Position, activation: spell.NPCFinishAt)); + else + { + _aoes.Add(new(donut, caster.Position, activation: spell.NPCFinishAt)); + _aoes.Add(new(circle, caster.Position, activation: spell.NPCFinishAt.AddSeconds(5.1f))); + } + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if (_aoes.Count > 0 && (AID)spell.Action.ID is AID.TartareanThunder or AID.TartareanFlame) + _aoes.RemoveAt(0); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (_aoes.Count > 0 && (AID)spell.Action.ID is AID.TartareanThunder2 or AID.TartareanFlame2) + _aoes.RemoveAt(0); + } + } + + class TartareanTwister : Components.CastInterruptHint + { + public TartareanTwister() : base(ActionID.MakeSpell(AID.TartareanTwister)) { } + } + + class TartareanBlizzard : Components.SelfTargetedAOEs + { + public TartareanBlizzard() : base(ActionID.MakeSpell(AID.TartareanBlizzard), new AOEShapeCone(40, 22.5f.Degrees())) { } + } + + class TartareanQuake : Components.RaidwideCast + { + public TartareanQuake() : base(ActionID.MakeSpell(AID.TartareanQuake)) { } + } + + class TartareanAbyss : Components.BaitAwayCast + { + public TartareanAbyss() : base(ActionID.MakeSpell(AID.TartareanAbyss), new AOEShapeCircle(6), true) { } + } + + class TartareanAbyssHint : Components.SingleTargetCast + { + public TartareanAbyssHint() : base(ActionID.MakeSpell(AID.TartareanAbyss), "Tankbuster circle") { } + } + + class TartareanFlare : Components.LocationTargetedAOEs + { + public TartareanFlare() : base(ActionID.MakeSpell(AID.TartareanFlare), 18) { } + } + + class TartareanMeteor : Components.StackWithCastTargets + { + public TartareanMeteor() : base(ActionID.MakeSpell(AID.TartareanMeteor), 10) { } + } + + class ArchaicDualcast : Components.CastHint + { + public ArchaicDualcast() : base(ActionID.MakeSpell(AID.ArchaicDualcast), "Preparing In/Out or Out/In AOE") { } + } + + class Cryptcall : Components.BaitAwayCast + { + public Cryptcall() : base(ActionID.MakeSpell(AID.Cryptcall), new AOEShapeCone(38.24f, 60.Degrees())) { } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { } + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) //bait resolves on cast event instead of cast finish + { + if (spell.Action == WatchedAction) + CurrentBaits.RemoveAll(b => b.Source == caster); + } + } + + class CryptcallHint : Components.CastHint + { + public CryptcallHint() : base(ActionID.MakeSpell(AID.Cryptcall), "Cone reduces health to 1 + applies Doom") { } + } + + 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)) + hints.Add("You were doomed! Get healed to full fast."); + if (_doomed.Contains(actor) && (actor.Role == Role.Healer)) + hints.Add("Heal yourself to full! (Doom)."); + if (_doomed.Count > 0 && (actor.Role == Role.Healer) && !_doomed.Contains(actor)) + hints.Add($"One or more players are affected by doom. Heal them to full."); + } + + 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 IxtabStates : StateMachineBuilder + { + public IxtabStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 136)] + public class Ixtab : SimpleBossModule + { + public Ixtab(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/Tarchia.cs b/BossMod/Modules/Shadowbringers/HuntS/Tarchia.cs new file mode 100644 index 0000000000..be2f81bd62 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/Tarchia.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntS.Tarchia +{ + public enum OID : uint + { + Boss = 0x2873, // R=9.86 + }; + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + WakeUp = 18103, // Boss->self, no cast, single-target, visual for waking up from sleep + WildHorn = 18026, // Boss->self, 3,0s cast, range 17 120-degree cone + BafflementBulb = 18029, // Boss->self, 3,0s cast, range 40 circle, pull 50 between hitboxes, temporary misdirection + ForestFire = 18030, // Boss->self, 5,0s cast, range 40 circle, damage fall off AOE, hard to tell optimal distance because logs are polluted by vuln stacks, guessing about 15 + MightySpin = 18028, // Boss->self, 3,0s cast, range 14 circle + MightySpin2 = 18093, // Boss->self, no cast, range 14 circle, after 1s after boss wakes up and 4s after every Groundstorm + Trounce = 18027, // Boss->self, 4,0s cast, range 40 60-degree cone + MetamorphicBlast = 18031, // Boss->self, 4,0s cast, range 40 circle + Groundstorm = 18023, // Boss->self, 5,0s cast, range 5-40 donut + }; + + class WildHorn : Components.SelfTargetedAOEs + { + public WildHorn() : base(ActionID.MakeSpell(AID.WildHorn), new AOEShapeCone(17, 60.Degrees())) { } + } + + class Trounce : Components.SelfTargetedAOEs + { + public Trounce() : base(ActionID.MakeSpell(AID.Trounce), new AOEShapeCone(40, 30.Degrees())) { } + } + + class Groundstorm : Components.SelfTargetedAOEs + { + public Groundstorm() : base(ActionID.MakeSpell(AID.Groundstorm), new AOEShapeDonut(5, 40)) { } + } + + class MightySpin : Components.SelfTargetedAOEs + { + public MightySpin() : base(ActionID.MakeSpell(AID.MightySpin), new AOEShapeCircle(14)) { } + } + + class ForestFire : Components.SelfTargetedAOEs + { + public ForestFire() : base(ActionID.MakeSpell(AID.ForestFire), new AOEShapeCircle(15)) { } + } + + class BafflementBulb : Components.CastHint + { + public BafflementBulb() : base(ActionID.MakeSpell(AID.BafflementBulb), "Pull + Temporary Misdirection -> Donut -> Out") { } + } + + class MetamorphicBlast : Components.RaidwideCast + { + public MetamorphicBlast() : base(ActionID.MakeSpell(AID.MetamorphicBlast)) { } + } + + class MightySpin2 : Components.GenericAOEs + { + private DateTime _activation; + private static readonly AOEShapeCircle circle = new(14); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_activation != default || NumCasts == 0) + yield return new(circle, module.PrimaryActor.Position, activation: _activation); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Groundstorm) + _activation = spell.NPCFinishAt.AddSeconds(4); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.MightySpin2 or AID.Trounce or AID.AutoAttack or AID.MightySpin or AID.WildHorn or AID.Groundstorm or AID.BafflementBulb or AID.MetamorphicBlast or AID.Groundstorm) //everything but Mightyspin2 is a failsafe incase player joins fight/starts replay record late and Numcasts is 0 because of it + ++NumCasts; + if ((AID)spell.Action.ID == AID.MightySpin2) + _activation = default; + } + } + + class TarchiaStates : StateMachineBuilder + { + public TarchiaStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 126)] + public class Tarchia : SimpleBossModule + { + public Tarchia(WorldState ws, Actor primary) : base(ws, primary) + { + ActivateComponent(); + } + } +} diff --git a/BossMod/Modules/Shadowbringers/HuntS/Tyger.cs b/BossMod/Modules/Shadowbringers/HuntS/Tyger.cs new file mode 100644 index 0000000000..7e7c85fabb --- /dev/null +++ b/BossMod/Modules/Shadowbringers/HuntS/Tyger.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; + +namespace BossMod.Shadowbringers.HuntS.Tyger +{ + public enum OID : uint + { + Boss = 0x288E, // R=5.92 + }; + + public enum AID : uint + { + AutoAttack = 870, // Boss->player, no cast, single-target + TheLionsBreath = 16957, // Boss->self, 4,0s cast, range 30 120-degree cone + TheScorpionsSting = 16961, // Boss->self, no cast, range 18 90-degree cone, 2-4s after a voice attack, timing seems to vary, maybe depends if voice was interrupted and how fast? + TheDragonsBreath = 16959, // Boss->self, 4,0s cast, range 30 120-degree cone + TheRamsBreath = 16958, // Boss->self, 4,0s cast, range 30 120-degree cone + TheRamsEmbrace = 16960, // Boss->location, 3,0s cast, range 9 circle + TheDragonsVoice = 16963, // Boss->self, 4,0s cast, range 8-30 donut, interruptible raidwide donut + TheRamsVoice = 16962, // Boss->self, 4,0s cast, range 9 circle + }; + + class TheLionsBreath : Components.SelfTargetedAOEs + { + public TheLionsBreath() : base(ActionID.MakeSpell(AID.TheLionsBreath), new AOEShapeCone(30, 60.Degrees())) { } + } + + class TheScorpionsSting : Components.GenericAOEs + { + private DateTime _activation; + private static readonly AOEShapeCone cone = new(18, 45.Degrees()); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_activation != default) + yield return new(cone, module.PrimaryActor.Position, module.PrimaryActor.Rotation + 180.Degrees(), _activation); + } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.TheRamsVoice or AID.TheDragonsVoice) + _activation = spell.NPCFinishAt.AddSeconds(2.3f); + } + + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID is AID.TheRamsVoice or AID.TheDragonsVoice) + _activation = module.WorldState.CurrentTime.AddSeconds(2.3f); //timing varies, just used the lowest i could find, probably depends on interrupt status + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.TheScorpionsSting) + _activation = default; + } + } + + class TheDragonsBreath : Components.SelfTargetedAOEs + { + public TheDragonsBreath() : base(ActionID.MakeSpell(AID.TheDragonsBreath), new AOEShapeCone(30, 60.Degrees())) { } + } + + class TheRamsBreath : Components.SelfTargetedAOEs + { + public TheRamsBreath() : base(ActionID.MakeSpell(AID.TheRamsBreath), new AOEShapeCone(30, 60.Degrees())) { } + } + + class TheRamsEmbrace : Components.SelfTargetedAOEs + { + public TheRamsEmbrace() : base(ActionID.MakeSpell(AID.TheRamsEmbrace), new AOEShapeCircle(9)) { } + } + + class TheRamsVoice : Components.SelfTargetedAOEs + { + public TheRamsVoice() : base(ActionID.MakeSpell(AID.TheRamsVoice), new AOEShapeCircle(9)) { } + } + + class TheRamsVoiceHint : Components.CastInterruptHint + { + public TheRamsVoiceHint() : base(ActionID.MakeSpell(AID.TheRamsVoice)) { } + } + + class TheDragonsVoice : Components.SelfTargetedAOEs + { + public TheDragonsVoice() : base(ActionID.MakeSpell(AID.TheDragonsVoice), new AOEShapeDonut(8, 30)) { } + } + + class TheDragonsVoiceHint : Components.CastInterruptHint + { + public TheDragonsVoiceHint() : base(ActionID.MakeSpell(AID.TheDragonsVoice), hint: "(Donut Raidwide)") { } + } + + + class TygerStates : StateMachineBuilder + { + public TygerStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } + } + + [ModuleInfo(NotoriousMonsterID = 116)] + public class Tyger : SimpleBossModule + { + public Tyger(WorldState ws, Actor primary) : base(ws, primary) { } + } +} diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheDungeonsOfLyheGhiah/Goliath.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheDungeonsOfLyheGhiah/Goliath.cs index 8e86b42ae0..b7beb4f979 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheDungeonsOfLyheGhiah/Goliath.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheDungeonsOfLyheGhiah/Goliath.cs @@ -54,7 +54,7 @@ class Compress2 : Components.SelfTargetedAOEs public Compress2() : base(ActionID.MakeSpell(AID.Compress2), new AOEShapeRect(102.1f, 3.5f)) { } } - class Accelerate : Components.StackWithCastTargets //not sure if this is always a stack, in one replay the icon is a spread marker, need to investigate, but since this is a rare boss it is hard to test + class Accelerate : Components.StackWithCastTargets { public Accelerate() : base(ActionID.MakeSpell(AID.Accelerate), 6) { } } diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/GreedyPixie.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/GreedyPixie.cs index 1c4a0ae9f6..bfb4ad3e1d 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/GreedyPixie.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/GreedyPixie.cs @@ -75,7 +75,7 @@ class NatureCall : Components.SelfTargetedAOEs class NatureCall2 : Components.SelfTargetedAOEs { - public NatureCall2() : base(ActionID.MakeSpell(AID.NatureCall), new AOEShapeCone(30, 60.Degrees())) { } + public NatureCall2() : base(ActionID.MakeSpell(AID.NatureCall2), new AOEShapeCone(30, 60.Degrees())) { } } class PluckAndPrune : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretKorrigan.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretKorrigan.cs index 797553a03e..3d041557df 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretKorrigan.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretKorrigan.cs @@ -38,7 +38,10 @@ public Hypnotize() : base(ActionID.MakeSpell(AID.Hypnotize)) { } class Ram : Components.SingleTargetCast { - public Ram() : base(ActionID.MakeSpell(AID.Ram)) { } + public Ram() : base(ActionID.MakeSpell(AID.Ram)) + { + EndsOnCastEvent = true; + } } class SaibaiMandragora : Components.CastHint diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPegasus.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPegasus.cs index 83672a5178..5e5c2c56bb 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPegasus.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPegasus.cs @@ -17,7 +17,7 @@ public enum AID : uint BurningBright = 21667, // Boss->self, 3,0s cast, range 47 width 6 rect Nicker = 21668, // Boss->self, 4,0s cast, range 12 circle CloudCall = 21666, // Boss->self, 3,0s cast, single-target, calls clouds - Gallop = 21665, // Boss->players, no cast, width 10 rect charge + Gallop = 21665, // Boss->players, no cast, width 10 rect charge, seems to target random player 5-6s after CloudCall LightningBolt = 21669, // Thunderhead->self, 3,0s cast, range 8 circle Telega = 9630, // BonusAdds->self, no cast, single-target, bonus adds disappear diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPorxie.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPorxie.cs new file mode 100644 index 0000000000..217a727f2a --- /dev/null +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretPorxie.cs @@ -0,0 +1,134 @@ +// CONTRIB: made by malediktus, not checked +using System.Linq; + +namespace BossMod.Shadowbringers.TreasureHunt.ShiftingOubliettesOfLyheGhiah.SecretPorxie +{ + public enum OID : uint + { + Boss = 0x3014, //R=1.2 + BossHelper = 0x233C, + MagickedBroomHelper = 0x310A, // R=0.5 + MagickedBroom1 = 0x30F3, // R=3.125 + MagickedBroom2 = 0x30F2, // R=3.125 + MagickedBroom3 = 0x30F4, // R=3.125 + MagickedBroom4 = 0x30F1, // R=3.125 + MagickedBroom5 = 0x3015, // R=3.125 + MagickedBroom6 = 0x30F0, // R=3.125 + BonusAdd_TheKeeperOfTheKeys = 0x3034, // R3.230 + }; + + public enum AID : uint + { + AutoAttack = 872, // Boss/BonusAdd_TheKeeperOfTheKeys->player, no cast, single-target + BrewingStorm = 21670, // Boss->self, 2,5s cast, range 5 60-degree cone + HarrowingDream = 21671, // Boss->self, 3,0s cast, range 6 circle, applies sleep + BecloudingDust = 22935, // Boss->self, 3,0s cast, single-target + BecloudingDust2 = 22936, // BossHelper->location, 3,0s cast, range 6 circle + SweepStart = 22937, // 30F4/30F3/30F2/30F1/3015/30F0->self, 4,0s cast, range 6 circle + SweepRest = 21672, // 30F4/30F3/30F2/30F1/3015/30F0->self, no cast, range 6 circle + SweepVisual = 22508, // 30F4/30F3/30F2/30F1/3015/30F0->self, no cast, single-target, visual + SweepVisual2 = 22509, // 30F4/30F3/30F2/30F1/3015/30F0->self, no cast, single-target + + Telega = 9630, // BonusAdds->self, no cast, single-target, bonus adds disappear + Mash = 21767, // 3034->self, 3,0s cast, range 13 width 4 rect + Inhale = 21770, // 3034->self, no cast, range 20 120-degree cone, attract 25 between hitboxes, shortly before Spin + Spin = 21769, // 3034->self, 4,0s cast, range 11 circle + Scoop = 21768, // 3034->self, 4,0s cast, range 15 120-degree cone + }; + + + class BrewingStorm : Components.SelfTargetedAOEs + { + public BrewingStorm() : base(ActionID.MakeSpell(AID.BrewingStorm), new AOEShapeCone(5, 30.Degrees())) { } + } + + class HarrowingDream : Components.SelfTargetedAOEs + { + public HarrowingDream() : base(ActionID.MakeSpell(AID.HarrowingDream), new AOEShapeCircle(6)) { } + } + + class BecloudingDust : Components.LocationTargetedAOEs + { + public BecloudingDust() : base(ActionID.MakeSpell(AID.BecloudingDust2), 6) { } + } + + class Sweep : Components.Exaflare + { + public Sweep() : base(6) { } + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.SweepStart) + Lines.Add(new() { Next = caster.Position, Advance = 12 * spell.Rotation.ToDirection(), NextExplosion = spell.NPCFinishAt.AddSeconds(0.9f), TimeToMove = 4.5f, ExplosionsLeft = 4, MaxShownExplosions = 3 }); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if (Lines.Count > 0 && (AID)spell.Action.ID == AID.SweepRest) + { + int index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); + AdvanceLine(module, Lines[index], caster.Position); + if (Lines[index].ExplosionsLeft == 0) + Lines.RemoveAt(index); + } + } + } + + class Spin : Components.SelfTargetedAOEs + { + public Spin() : base(ActionID.MakeSpell(AID.Spin), new AOEShapeCircle(11)) { } + } + + class Mash : Components.SelfTargetedAOEs + { + public Mash() : base(ActionID.MakeSpell(AID.Mash), new AOEShapeRect(13, 2)) { } + } + + class Scoop : Components.SelfTargetedAOEs + { + public Scoop() : base(ActionID.MakeSpell(AID.Scoop), new AOEShapeCone(15, 60.Degrees())) { } + } + + class PorxieStates : StateMachineBuilder + { + public PorxieStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BonusAdd_TheKeeperOfTheKeys).All(e => e.IsDead); + } + } + + [ModuleInfo(CFCID = 745, NameID = 9795)] + public class Porxie : BossModule + { + public Porxie(WorldState ws, Actor primary) : base(ws, primary, new ArenaBoundsCircle(new(100, 100), 20)) { } + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor, ArenaColor.Enemy); + foreach (var s in Enemies(OID.BonusAdd_TheKeeperOfTheKeys)) + Arena.Actor(s, ArenaColor.Vulnerable); + } + + public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + base.CalculateAIHints(slot, actor, assignment, hints); + foreach (var e in hints.PotentialTargets) + { + e.Priority = (OID)e.Actor.OID switch + { + OID.BonusAdd_TheKeeperOfTheKeys => 2, + OID.Boss => 1, + _ => 0 + }; + } + } + } +} diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretSerpent.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretSerpent.cs index cfbaaa15fb..d53a7526c7 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretSerpent.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretSerpent.cs @@ -8,6 +8,7 @@ public enum OID : uint Boss = 0x3025, //R=5.29 BossAdd = 0x3026, //R=3.45 BossHelper = 0x233C, + WaterVoidzone = 0x1EA7D5, SecretQueen = 0x3021, // R0,840, icon 5, needs to be killed in order from 1 to 5 for maximum rewards SecretGarlic = 0x301F, // R0,840, icon 3, needs to be killed in order from 1 to 5 for maximum rewards SecretTomato = 0x3020, // R0,840, icon 4, needs to be killed in order from 1 to 5 for maximum rewards @@ -33,9 +34,9 @@ public enum AID : uint Telega = 9630, // BonusAdds->self, no cast, single-target, bonus adds disappear }; - class Douse : Components.LocationTargetedAOEs + class Douse : Components.PersistentVoidzoneAtCastTarget { - public Douse() : base(ActionID.MakeSpell(AID.Douse), 8) { } + public Douse() : base(8, ActionID.MakeSpell(AID.Douse), m => m.Enemies(OID.WaterVoidzone).Where(z => z.EventState != 7), 0) { } } class FangsEnd : Components.SingleTargetCast diff --git a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretUndine.cs b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretUndine.cs index 5144b392bd..f7be83eb40 100644 --- a/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretUndine.cs +++ b/BossMod/Modules/Shadowbringers/TreasureHunt/TheShiftingOubliettesOfLyheGhiah/SecretUndine.cs @@ -9,11 +9,13 @@ public enum OID : uint BossAdd = 0x3013, //R=1.12 Bubble = 0x3012, //R=1.3, untargetable BossHelper = 0x233C, + BonusAdd_TheKeeperOfTheKeys = 0x3034, // R3.230 }; public enum AID : uint { AutoAttack = 23186, // Boss/3013->player, no cast, single-target + AutoAttack2 = 872, // BonusAdd_TheKeeperOfTheKeys->player, no cast, single-target Hydrowhirl = 21658, // Boss->self, 3,0s cast, range 8 circle Hypnowave = 21659, // Boss->self, 3,0s cast, range 30 120-degree cone, causes sleep Hydrotaph = 21661, // Boss->self, 4,0s cast, single-target @@ -21,6 +23,12 @@ public enum AID : uint Hydrofan = 21663, // 3012->self, 5,0s cast, range 44 30-degree cone Hydropins = 21660, // Boss->self, 2,5s cast, range 12 width 4 rect AquaGlobe = 21664, // 3013->location, 3,0s cast, range 8 circle + + Telega = 9630, // BonusAdds->self, no cast, single-target, bonus adds disappear + Mash = 21767, // 3034->self, 3,0s cast, range 13 width 4 rect + Inhale = 21770, // 3034->self, no cast, range 20 120-degree cone, attract 25 between hitboxes, shortly before Spin + Spin = 21769, // 3034->self, 4,0s cast, range 11 circle + Scoop = 21768, // 3034->self, 4,0s cast, range 15 120-degree cone }; class Hydrofan : Components.SelfTargetedAOEs @@ -53,6 +61,21 @@ class Hydrotaph : Components.RaidwideCast public Hydrotaph() : base(ActionID.MakeSpell(AID.Hydrotaph2)) { } } + class Spin : Components.SelfTargetedAOEs + { + public Spin() : base(ActionID.MakeSpell(AID.Spin), new AOEShapeCircle(11)) { } + } + + class Mash : Components.SelfTargetedAOEs + { + public Mash() : base(ActionID.MakeSpell(AID.Mash), new AOEShapeRect(13, 2)) { } + } + + class Scoop : Components.SelfTargetedAOEs + { + public Scoop() : base(ActionID.MakeSpell(AID.Scoop), new AOEShapeCone(15, 60.Degrees())) { } + } + class UndineStates : StateMachineBuilder { public UndineStates(BossModule module) : base(module) @@ -64,7 +87,10 @@ public UndineStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BossAdd).All(e => e.IsDead); + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BossAdd).All(e => e.IsDead) && module.Enemies(OID.BonusAdd_TheKeeperOfTheKeys).All(e => e.IsDead); } } @@ -78,6 +104,8 @@ protected override void DrawEnemies(int pcSlot, Actor pc) Arena.Actor(PrimaryActor, ArenaColor.Enemy); foreach (var s in Enemies(OID.BossAdd)) Arena.Actor(s, ArenaColor.Object); + foreach (var s in Enemies(OID.BonusAdd_TheKeeperOfTheKeys)) + Arena.Actor(s, ArenaColor.Vulnerable); } public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -87,6 +115,7 @@ public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.As { e.Priority = (OID)e.Actor.OID switch { + OID.BonusAdd_TheKeeperOfTheKeys => 3, OID.BossAdd => 2, OID.Boss => 1, _ => 0 diff --git a/BossMod/Modules/Stormblood/HuntA/Angada.cs b/BossMod/Modules/Stormblood/HuntA/Angada.cs index 54532ff837..32391ac7be 100644 --- a/BossMod/Modules/Stormblood/HuntA/Angada.cs +++ b/BossMod/Modules/Stormblood/HuntA/Angada.cs @@ -1,4 +1,7 @@ // CONTRIB: made by malediktus, not checked +using System; +using System.Collections.Generic; + namespace BossMod.Stormblood.HuntA.Angada { public enum OID : uint @@ -9,7 +12,7 @@ public enum OID : uint public enum AID : uint { AutoAttack = 872, // 1AC0->player, no cast, single-target - ScytheTail = 8190, // 1AC0->self, 3,0s cast, range 4+R circle + ScytheTail = 8190, // 1AC0->self, 3,0s cast, range 4+R circle, knockback 10, away from source + stun RockThrow = 8193, // 1AC0->location, 3,0s cast, range 6 circle Butcher = 8191, // 1AC0->self, 3,0s cast, range 6+R 120-degree cone Rip = 8192, // 1AC0->self, no cast, range 6+R 120-degree cone, always happens directly after Butcher @@ -25,6 +28,31 @@ class Butcher : Components.SelfTargetedAOEs public Butcher() : base(ActionID.MakeSpell(AID.Butcher), new AOEShapeCone(11.4f, 60.Degrees())) { } } + class Rip : Components.GenericAOEs + { + private DateTime _activation; + private static readonly AOEShapeCone cone = new(11.4f, 60.Degrees()); + + public override IEnumerable ActiveAOEs(BossModule module, int slot, Actor actor) + { + if (_activation != default) + yield return new(cone, module.PrimaryActor.Position, module.PrimaryActor.Rotation, _activation); + } + + + public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Butcher) // boss can move after cast started, so we can't use aoe instance, since that would cause outdated position data to be used + _activation = spell.NPCFinishAt.AddSeconds(1.1f); + } + + public override void OnEventCast(BossModule module, Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.Rip) + _activation = default; + } + } + class RockThrow : Components.LocationTargetedAOEs { public RockThrow() : base(ActionID.MakeSpell(AID.RockThrow), 6) { } @@ -37,6 +65,7 @@ public AngadaStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter(); } } diff --git a/BossMod/Modules/Stormblood/HuntA/Gajasura.cs b/BossMod/Modules/Stormblood/HuntA/Gajasura.cs index e5afc762f9..4c6222e463 100644 --- a/BossMod/Modules/Stormblood/HuntA/Gajasura.cs +++ b/BossMod/Modules/Stormblood/HuntA/Gajasura.cs @@ -16,12 +16,12 @@ public enum AID : uint class Spin : Components.SelfTargetedAOEs { - public Spin() : base(ActionID.MakeSpell(AID.Spin), new AOEShapeCircle(8.23f)) { } + public Spin() : base(ActionID.MakeSpell(AID.Spin), new AOEShapeCone(8.23f, 60.Degrees())) { } } - class Hurl : Components.SelfTargetedAOEs + class Hurl : Components.LocationTargetedAOEs { - public Hurl() : base(ActionID.MakeSpell(AID.Hurl), new AOEShapeCircle(6)) { } + public Hurl() : base(ActionID.MakeSpell(AID.Hurl), 6) { } } class Buffet : Components.SingleTargetCast diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs index 4a19697f8c..a6bf127826 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarAiravata.cs @@ -51,7 +51,10 @@ class BarbarousScream : Components.SelfTargetedAOEs class Huff : Components.SingleTargetCast { - public Huff() : base(ActionID.MakeSpell(AID.Huff)) { } + public Huff() : base(ActionID.MakeSpell(AID.Huff)) + { + EndsOnCastEvent = true; + } } class Buffet : Components.KnockbackFromCastTarget diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs index 38211694b7..312ddcb64b 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarArachne.cs @@ -1,4 +1,5 @@ // CONTRIB: made by malediktus, not checked +using System.Collections.Generic; using System.Linq; namespace BossMod.Stormblood.TreasureHunt.ShiftingAltarsOfUznair.AltarArachne @@ -33,7 +34,10 @@ public enum AID : uint class DarkSpike : Components.SingleTargetCast { - public DarkSpike() : base(ActionID.MakeSpell(AID.DarkSpike)) { } + public DarkSpike() : base(ActionID.MakeSpell(AID.DarkSpike)) + { + EndsOnCastEvent = true; + } } class FrondAffeared : Components.CastGaze diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs index 53cd59df41..cb600b9797 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarChimera.cs @@ -46,9 +46,9 @@ class TheRamsVoice : Components.SelfTargetedAOEs public TheRamsVoice() : base(ActionID.MakeSpell(AID.TheRamsVoice), new AOEShapeCircle(9.92f)) { } } - class TheRamsVoiceHint : Components.CastHint + class TheRamsVoiceHint : Components.CastInterruptHint { - public TheRamsVoiceHint() : base(ActionID.MakeSpell(AID.TheRamsVoice), "Interrupt!") { } + public TheRamsVoiceHint() : base(ActionID.MakeSpell(AID.TheRamsVoice)) { } } class TheLionsBreath : Components.SelfTargetedAOEs @@ -66,31 +66,31 @@ class TheDragonsVoice : Components.SelfTargetedAOEs public TheDragonsVoice() : base(ActionID.MakeSpell(AID.TheDragonsVoice), new AOEShapeDonut(8, 30)) { } } - class TheDragonsVoiceHint : Components.CastHint + class TheDragonsVoiceHint : Components.CastInterruptHint { - public TheDragonsVoiceHint() : base(ActionID.MakeSpell(AID.TheDragonsVoice), "Interrupt!") { } + public TheDragonsVoiceHint() : base(ActionID.MakeSpell(AID.TheDragonsVoice)) { } } - class TheRamsKeeper : Components.UniformStackSpread + class TheRamsKeeper : Components.GenericBaitAway { - public TheRamsKeeper() : base(0, 6, alwaysShowSpreads: true) { } private bool targeted; private Actor? target; + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { if (iconID == (uint)IconID.Baitaway) { - AddSpread(actor); + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(6))); targeted = true; target = actor; } } - public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.TheRamsKeeper) { - Spreads.Clear(); + CurrentBaits.Clear(); targeted = false; } } @@ -104,14 +104,15 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { + base.AddHints(module, slot, actor, hints, movementHints); if (target == actor && targeted) - hints.Add("Bait away!"); + hints.Add("Bait voidzone away!"); } } - class IceVoidzone : Components.PersistentVoidzoneAtCastTarget + class IceVoidzone : Components.PersistentVoidzone { - public IceVoidzone() : base(6, ActionID.MakeSpell(AID.TheRamsKeeper), m => m.Enemies(OID.IceVoidzone).Where(z => z.EventState != 7), 0) { } + public IceVoidzone() : base(6, m => m.Enemies(OID.IceVoidzone).Where(z => z.EventState != 7)) { } } class RaucousScritch : Components.SelfTargetedAOEs @@ -129,7 +130,6 @@ class Spin : Components.Cleave public Spin() : base(ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.BonusAdd_AltarMatanga) { } } - class ChimeraStates : StateMachineBuilder { public ChimeraStates(BossModule module) : base(module) diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs index ed03e5a9bd..7319b7c7c2 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDiresaur.cs @@ -1,4 +1,5 @@ // CONTRIB: made by malediktus, not checked +using System.Collections.Generic; using System.Linq; namespace BossMod.Stormblood.TreasureHunt.ShiftingAltarsOfUznair.AltarDiresaur @@ -40,7 +41,10 @@ public enum IconID : uint class DeadlyHold : Components.SingleTargetCast { - public DeadlyHold() : base(ActionID.MakeSpell(AID.DeadlyHold)) { } + public DeadlyHold() : base(ActionID.MakeSpell(AID.DeadlyHold)) + { + EndsOnCastEvent = true; + } } class HeatBreath : Components.SelfTargetedAOEs @@ -68,30 +72,29 @@ class HardStomp : Components.SelfTargetedAOEs public HardStomp() : base(ActionID.MakeSpell(AID.HardStomp), new AOEShapeCircle(10)) { } } - class Fireball : Components.UniformStackSpread + class Fireball : Components.GenericBaitAway { - public Fireball() : base(0, 6, alwaysShowSpreads: true) { } private bool targeted; - private int numcasts; private Actor? target; + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { if (iconID == (uint)IconID.Baitaway) { - AddSpread(actor); + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(6))); targeted = true; target = actor; } } - public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.Fireball) - ++numcasts; - if (numcasts == 3) + ++NumCasts; + if (NumCasts == 3) { - Spreads.Clear(); - numcasts = 0; + CurrentBaits.Clear(); + NumCasts = 0; targeted = false; } } @@ -106,13 +109,13 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { if (target == actor && targeted) - hints.Add("Bait away (3 times!)"); + hints.Add("Bait voidzone away! (3 times)"); } } - class FireballVoidzone : Components.PersistentVoidzoneAtCastTarget + class FireballVoidzone : Components.PersistentVoidzone { - public FireballVoidzone() : base(6, ActionID.MakeSpell(AID.Fireball), m => m.Enemies(OID.FireVoidzone).Where(z => z.EventState != 7), 1) { } + public FireballVoidzone() : base(6,m => m.Enemies(OID.FireVoidzone).Where(z => z.EventState != 7)) { } } class RaucousScritch : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs index 8c8d0b5b46..50d80d7237 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarDullahan.cs @@ -90,6 +90,7 @@ public StygianReleaseKB() : base(ActionID.MakeSpell(AID.StygianRelease), 20) { StopAtWall = true; } + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs index 1257a0b3b1..e5bfadf595 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarKelpie.cs @@ -7,9 +7,14 @@ public enum OID : uint { Boss = 0x2537, //R=5.4 Hydrosphere = 0x255B, //R=1.2 + BossHelper = 0x233C, BonusAdd_AltarMatanga = 0x2545, // R3.420 BonusAdd_GoldWhisker = 0x2544, // R0.540 - BossHelper = 0x233C, + AltarQueen = 0x254A, // R0,840, icon 5, needs to be killed in order from 1 to 5 for maximum rewards + AltarGarlic = 0x2548, // R0,840, icon 3, needs to be killed in order from 1 to 5 for maximum rewards + AltarTomato = 0x2549, // R0,840, icon 4, needs to be killed in order from 1 to 5 for maximum rewards + AltarOnion = 0x2546, // R0,840, icon 1, needs to be killed in order from 1 to 5 for maximum rewards + AltarEgg = 0x2547, // R0,840, icon 2, needs to be killed in order from 1 to 5 for maximum rewards }; public enum AID : uint @@ -27,6 +32,11 @@ public enum AID : uint Spin = 8599, // BonusAdd_AltarMatanga->self, no cast, range 6+R 120-degree cone RaucousScritch = 8598, // BonusAdd_AltarMatanga->self, 2,5s cast, range 5+R 120-degree cone Hurl = 5352, // BonusAdd_AltarMatanga->location, 3,0s cast, range 6 circle + PluckAndPrune = 6449, // AltarEgg->self, 3,5s cast, range 6+R circle + PungentPirouette = 6450, // AltarGarlic->self, 3,5s cast, range 6+R circle + TearyTwirl = 6448, // AltarOnion->self, 3,5s cast, range 6+R circle + Pollen = 6452, // AltarQueen->self, 3,5s cast, range 6+R circle + HeirloomScream = 6451, // AltarTomato->self, 3,5s cast, range 6+R circle Telega = 9630, // BonusAdds->self, no cast, single-target, bonus adds disappear }; @@ -47,7 +57,10 @@ class BloodyPuddle : Components.SelfTargetedAOEs class Torpedo : Components.SingleTargetCast { - public Torpedo() : base(ActionID.MakeSpell(AID.Torpedo)) { } + public Torpedo() : base(ActionID.MakeSpell(AID.Torpedo)) + { + EndsOnCastEvent = true; + } } class RisingSeas : Components.RaidwideCast @@ -69,6 +82,7 @@ public RisingSeasKB() : base(ActionID.MakeSpell(AID.RisingSeas), 20) { StopAtWall = true; } + public override bool DestinationUnsafe(BossModule module, int slot, Actor actor, WPos pos) => module.FindComponent()?.ActiveAOEs(module, slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false; } @@ -87,6 +101,31 @@ class Spin : Components.Cleave public Spin() : base(ActionID.MakeSpell(AID.Spin), new AOEShapeCone(9.42f, 60.Degrees()), (uint)OID.BonusAdd_AltarMatanga) { } } + class PluckAndPrune : Components.SelfTargetedAOEs + { + public PluckAndPrune() : base(ActionID.MakeSpell(AID.PluckAndPrune), new AOEShapeCircle(6.84f)) { } + } + + class TearyTwirl : Components.SelfTargetedAOEs + { + public TearyTwirl() : base(ActionID.MakeSpell(AID.TearyTwirl), new AOEShapeCircle(6.84f)) { } + } + + class HeirloomScream : Components.SelfTargetedAOEs + { + public HeirloomScream() : base(ActionID.MakeSpell(AID.HeirloomScream), new AOEShapeCircle(6.84f)) { } + } + + class PungentPirouette : Components.SelfTargetedAOEs + { + public PungentPirouette() : base(ActionID.MakeSpell(AID.PungentPirouette), new AOEShapeCircle(6.84f)) { } + } + + class Pollen : Components.SelfTargetedAOEs + { + public Pollen() : base(ActionID.MakeSpell(AID.Pollen), new AOEShapeCircle(6.84f)) { } + } + class KelpieStates : StateMachineBuilder { public KelpieStates(BossModule module) : base(module) @@ -102,7 +141,12 @@ public KelpieStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BonusAdd_GoldWhisker).All(e => e.IsDead) && module.Enemies(OID.BonusAdd_AltarMatanga).All(e => e.IsDead); + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .Raw.Update = () => module.Enemies(OID.Boss).All(e => e.IsDead) && module.Enemies(OID.BonusAdd_GoldWhisker).All(e => e.IsDead) && module.Enemies(OID.BonusAdd_AltarMatanga).All(e => e.IsDead) && module.Enemies(OID.AltarEgg).All(e => e.IsDead) && module.Enemies(OID.AltarQueen).All(e => e.IsDead) && module.Enemies(OID.AltarOnion).All(e => e.IsDead) && module.Enemies(OID.AltarGarlic).All(e => e.IsDead) && module.Enemies(OID.AltarTomato).All(e => e.IsDead); } } @@ -118,6 +162,16 @@ protected override void DrawEnemies(int pcSlot, Actor pc) Arena.Actor(s, ArenaColor.Vulnerable); foreach (var s in Enemies(OID.BonusAdd_AltarMatanga)) Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarEgg)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarTomato)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarQueen)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarGarlic)) + Arena.Actor(s, ArenaColor.Vulnerable); + foreach (var s in Enemies(OID.AltarOnion)) + Arena.Actor(s, ArenaColor.Vulnerable); } public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -127,7 +181,11 @@ public override void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.As { e.Priority = (OID)e.Actor.OID switch { - OID.BonusAdd_GoldWhisker => 3, + OID.AltarOnion => 7, + OID.AltarEgg => 6, + OID.AltarGarlic => 5, + OID.AltarTomato => 4, + OID.AltarQueen or OID.BonusAdd_GoldWhisker => 3, OID.BonusAdd_AltarMatanga => 2, OID.Boss => 1, _ => 0 diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs index c93c57d93a..d5915c2ddb 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarMandragora.cs @@ -1,4 +1,5 @@ // CONTRIB: made by malediktus, not checked +using System.Collections.Generic; using System.Linq; namespace BossMod.Stormblood.TreasureHunt.ShiftingAltarsOfUznair.AltarMandragora @@ -34,7 +35,10 @@ public enum AID : uint class OpticalIntrusion : Components.SingleTargetCast { - public OpticalIntrusion() : base(ActionID.MakeSpell(AID.OpticalIntrusion)) { } + public OpticalIntrusion() : base(ActionID.MakeSpell(AID.OpticalIntrusion)) + { + EndsOnCastEvent = true; + } } class Hypnotize : Components.SelfTargetedAOEs diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs index 15da577252..ec7499b976 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarSkatene.cs @@ -1,4 +1,5 @@ // CONTRIB: made by malediktus, not checked +using System.Collections.Generic; using System.Linq; namespace BossMod.Stormblood.TreasureHunt.ShiftingAltarsOfUznair.AltarSkatene @@ -37,7 +38,10 @@ public VoidCall() : base(ActionID.MakeSpell(AID.VoidCall), "Calls adds") { } class RecklessAbandon : Components.SingleTargetCast { - public RecklessAbandon() : base(ActionID.MakeSpell(AID.RecklessAbandon)) { } + public RecklessAbandon() : base(ActionID.MakeSpell(AID.RecklessAbandon)) + { + EndsOnCastEvent = true; + } } class SkateneStates : StateMachineBuilder diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs index f9c0070e92..e1dbfd50b8 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/AltarTotem.cs @@ -47,30 +47,29 @@ class TheWardensVerdict : Components.SelfTargetedAOEs public TheWardensVerdict() : base(ActionID.MakeSpell(AID.TheWardensVerdict), new AOEShapeRect(45.06f, 2)) { } } - class FlamesOfFury : Components.UniformStackSpread + class FlamesOfFury : Components.GenericBaitAway { - public FlamesOfFury() : base(0, 10, alwaysShowSpreads: true) { } private bool targeted; - private int numcasts; private Actor? target; + public override void OnEventIcon(BossModule module, Actor actor, uint iconID) { if (iconID == (uint)IconID.Baitaway) { - AddSpread(actor); + CurrentBaits.Add(new(actor, actor, new AOEShapeCircle(10))); targeted = true; target = actor; } } - public override void OnCastStarted(BossModule module, Actor caster, ActorCastInfo spell) + public override void OnCastFinished(BossModule module, Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.FlamesOfFury) - ++numcasts; - if (numcasts == 3) + ++NumCasts; + if (NumCasts == 3) { - Spreads.Clear(); - numcasts = 0; + CurrentBaits.Clear(); + NumCasts = 0; targeted = false; } } @@ -85,13 +84,13 @@ public override void AddAIHints(BossModule module, int slot, Actor actor, PartyR public override void AddHints(BossModule module, int slot, Actor actor, TextHints hints, MovementHints? movementHints) { if (target == actor && targeted) - hints.Add("Bait away (3 times!)"); + hints.Add("Bait voidzone away! (3 times)"); } } - class FlamesOfFuryVoidzone : Components.PersistentVoidzoneAtCastTarget + class FlamesOfFuryVoidzone : Components.PersistentVoidzone { - public FlamesOfFuryVoidzone() : base(10, ActionID.MakeSpell(AID.FlamesOfFury), m => m.Enemies(OID.FireVoidzone).Where(z => z.EventState != 7), 0) { } + public FlamesOfFuryVoidzone() : base(10, m => m.Enemies(OID.FireVoidzone).Where(z => z.EventState != 7)) { } } class HatiStates : StateMachineBuilder diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs index 9801a33c51..a5ae5afce1 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheGreatGoldWhisker.cs @@ -1,4 +1,5 @@ // CONTRIB: made by malediktus, not checked +using System.Collections.Generic; using System.Linq; namespace BossMod.Stormblood.TreasureHunt.ShiftingAltarsOfUznair.TheGreatGoldWhisker @@ -21,7 +22,10 @@ public enum AID : uint class TripleTrident : Components.SingleTargetCast { - public TripleTrident() : base(ActionID.MakeSpell(AID.TripleTrident)) { } + public TripleTrident() : base(ActionID.MakeSpell(AID.TripleTrident)) + { + EndsOnCastEvent = true; + } } class FishOutOfWater : Components.CastHint diff --git a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs index 5aad227c1e..6529de5c3f 100644 --- a/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs +++ b/BossMod/Modules/Stormblood/TreasureHunt/TheShiftingAltarsOfUznair/TheOlderOne.cs @@ -1,4 +1,5 @@ // CONTRIB: made by malediktus, not checked +using System.Collections.Generic; using System.Linq; namespace BossMod.Stormblood.TreasureHunt.ShiftingAltarsOfUznair.TheOlderOne @@ -72,7 +73,10 @@ public MysticLevin() : base(ActionID.MakeSpell(AID.MysticLevin)) { } class MysticFlash : Components.SingleTargetCast { - public MysticFlash() : base(ActionID.MakeSpell(AID.MysticFlash)) { } + public MysticFlash() : base(ActionID.MakeSpell(AID.MysticFlash)) + { + EndsOnCastEvent = true; + } } class PluckAndPrune : Components.SelfTargetedAOEs