From 82e69c9949d07378324b2d33016c535e0aa0b717 Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Thu, 26 Dec 2024 05:14:52 +0100 Subject: [PATCH] Paglth'an modules --- BossMod/Components/BaitAway.cs | 7 +- BossMod/Components/CastCounter.cs | 8 +- BossMod/Components/CastHint.cs | 10 +- BossMod/Components/Chains.cs | 4 +- BossMod/Components/ChasingAOEs.cs | 16 +- BossMod/Components/Cleave.cs | 8 +- BossMod/Components/ConcentricAOEs.cs | 4 +- BossMod/Components/Exaflare.cs | 4 +- BossMod/Components/ForcedMarch.cs | 18 +- BossMod/Components/GenericAOEs.cs | 54 ++++- BossMod/Components/Knockback.cs | 22 +- BossMod/Components/LineOfSightAOE.cs | 47 ++-- BossMod/Components/PersistentVoidzone.cs | 14 +- BossMod/Components/Protean.cs | 2 +- BossMod/Components/RotatingAOE.cs | 2 +- BossMod/Components/SharedTankbuster.cs | 4 +- BossMod/Components/StackSpread.cs | 58 ++--- BossMod/Components/StayInBounds.cs | 11 - BossMod/Components/StayMove.cs | 2 +- BossMod/Components/Tethers.cs | 22 +- BossMod/Components/ThinIce.cs | 2 +- BossMod/Components/Towers.cs | 10 +- BossMod/Components/UnavoidableDamage.cs | 10 +- BossMod/Components/WildCharge.cs | 4 +- BossMod/Data/ActionEffect.cs | 1 + .../D03SkydeepCenote/D031FeatherRay.cs | 22 +- .../Dungeon/D03SkydeepCenote/D033Maulskull.cs | 46 ++-- .../D091LindblumZaghnal.cs | 13 +- .../D092OverseerKanilokka.cs | 11 +- .../D093Lunipyati.cs | 47 ++-- .../Raid/M02NHoneyBLovely/Sweethearts.cs | 7 +- .../Savage/M01SBlackCat/OneTwoPaw.cs | 51 +++-- .../Savage/M01SBlackCat/PredaceousPounce.cs | 15 +- .../Savage/M01SBlackCat/QuadrupleCrossing.cs | 16 +- .../Trial/T02ZoraalJa/DoubleEdgedSwords.cs | 15 +- .../Trial/T03QueenEternal/Besiegement.cs | 2 +- .../Trial/T03QueenEternal/LegitimateForce.cs | 33 ++- .../Trial/T03QueenEternal/RoyalBanishment.cs | 4 +- .../Trial/T03QueenEternal/RuthlessRegalia.cs | 2 +- .../T03QueenEternal/WaltzOfTheRegalia.cs | 10 +- .../Dungeon/D03Vanaspati/D032Wrecker.cs | 9 +- .../Dungeon/D03Vanaspati/D033Svarbhanu.cs | 32 +-- .../D04KtisisHyperboreia/D043Hermes.cs | 6 +- .../Modules/Endwalker/Ultimate/TOP/P5Omega.cs | 3 +- .../PalaceOfTheDead/DD160Todesritter.cs | 5 +- .../PalaceOfTheDead/DD60TheBlackRider.cs | 5 +- .../Dungeon/D02SohmAl/D021Raskovnik.cs | 6 +- .../Dungeon/D02SohmAl/D022Myath.cs | 6 +- .../Dungeon/D02SohmAl/D023Tioman.cs | 10 +- .../Heavensward/Dungeon/D03Aery/D031Rangda.cs | 13 +- .../Dungeon/D03Aery/D033Nidhogg.cs | 5 +- .../Dungeon/D04TheVault/D041SerAdelphel.cs | 5 +- .../Dungeon/D15Xelphatol/D152DotoliCiloc.cs | 11 +- .../D29KeeperOfTheLake/D292MagitekGunship.cs | 12 +- .../D29KeeperOfTheLake/D293Midgardsormr.cs | 9 +- .../Dungeon/D01Holminster/D013Philia.cs | 9 +- .../Dungeon/D02DohnMheg/D021AencThon.cs | 20 +- .../D03QitanaRavel/D030RonkanDreamer.cs | 25 ++- .../Dungeon/D03QitanaRavel/D031Lozatl.cs | 21 +- .../Dungeon/D03QitanaRavel/D033Eros.cs | 12 +- .../D04MalikahsWell/D042AmphibiousTalos.cs | 2 +- .../Dungeon/D04MalikahsWell/D043Storge.cs | 31 +-- .../Dungeon/D05MtGulg/D053ForgivenWhimsy.cs | 14 +- .../D05MtGulg/D055ForgivenObscenity.cs | 39 ++-- .../Dungeon/D06Amaurot/D062Bellwether.cs | 7 +- .../Dungeon/D06Amaurot/D063Therion.cs | 10 +- .../Dungeon/D07Twinning/D071AlphaZaghnal.cs | 22 +- .../D08AkadaemiaAnyder/D082MorbolMarquis.cs | 6 - .../D08AkadaemiaAnyder/D083Quetzalcoatl.cs | 28 +-- .../Dungeon/D09GrandCosmos/D092LeannanSith.cs | 63 ++++-- .../Dungeon/D09GrandCosmos/D093Lugus.cs | 108 +++------ .../D113SpectralBerserker.cs | 18 +- .../Dungeon/D12MatoyasRelict/D121Mudman.cs | 20 +- .../Dungeon/D12MatoyasRelict/D122Nixie.cs | 68 +++--- .../D12MatoyasRelict/D123MotherPorxie.cs | 7 +- .../Dungeon/D13Paglthan/D131Amhuluk.cs | 200 +++++++++++++++++ .../D13Paglthan/D132MagitekFortress.cs | 205 ++++++++++++++++++ .../Dungeon/D13Paglthan/D133LunarBahamut.cs | 144 ++++++++++++ .../DelubrumReginaeSavage/DRS3Dahu/DRS3.cs | 5 +- .../Duel/Duel2Lyon/Duel2LyonGenericAttacks.cs | 11 +- .../D04DomaCastle/D041MagitekRearguard.cs | 7 +- BossMod/Pathfinding/NavigationDecision.cs | 6 +- BossMod/Replay/Analysis/AbilityInfo.cs | 2 + BossMod/Replay/Visualization/OpList.cs | 3 +- .../Visualization/ReplayDetailsWindow.cs | 2 + BossMod/Util/Polygon.cs | 168 +++++++------- 86 files changed, 1340 insertions(+), 718 deletions(-) delete mode 100644 BossMod/Components/StayInBounds.cs create mode 100644 BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs create mode 100644 BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs create mode 100644 BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D133LunarBahamut.cs diff --git a/BossMod/Components/BaitAway.cs b/BossMod/Components/BaitAway.cs index 807aeecb90..33cb1dc6c2 100644 --- a/BossMod/Components/BaitAway.cs +++ b/BossMod/Components/BaitAway.cs @@ -7,7 +7,7 @@ public class GenericBaitAway(BossModule module, ActionID aid = default, bool alw { public record struct Bait(Actor Source, Actor Target, AOEShape Shape, DateTime Activation = default) { - public Angle? CustomRotation { get; init; } + public Angle? CustomRotation; public readonly Angle Rotation => CustomRotation ?? (Source != Target ? Angle.FromDirection(Target.Position - Source.Position) : Source.Rotation); @@ -18,8 +18,8 @@ public Bait(Actor source, Actor target, AOEShape shape, DateTime activation, Ang } } - public bool AlwaysDrawOtherBaits = alwaysDrawOtherBaits; // if false, other baits are drawn only if they are clipping a player - public bool CenterAtTarget = centerAtTarget; // if true, aoe source is at target + public readonly bool AlwaysDrawOtherBaits = alwaysDrawOtherBaits; // if false, other baits are drawn only if they are clipping a player + public readonly bool CenterAtTarget = 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 IgnoreOtherBaits; // if true, don't show hints/aoes for baits by others @@ -81,7 +81,6 @@ private void AddTargetSpecificHints(Actor actor, Bait bait, AIHints hints) case AOEShapeCone cone: hints.AddForbiddenZone(ShapeDistance.Cone(bait.Source.Position, 100, bait.Source.AngleTo(a), cone.HalfAngle), bait.Activation); break; - case AOEShapeRect rect: hints.AddForbiddenZone(ShapeDistance.Cone(bait.Source.Position, 100, bait.Source.AngleTo(a), Angle.Asin(rect.HalfWidth / (a.Position - bait.Source.Position).Length())), bait.Activation); break; diff --git a/BossMod/Components/CastCounter.cs b/BossMod/Components/CastCounter.cs index f350b9b9dc..ab96976ef4 100644 --- a/BossMod/Components/CastCounter.cs +++ b/BossMod/Components/CastCounter.cs @@ -3,8 +3,8 @@ // generic component that counts specified casts public class CastCounter(BossModule module, ActionID aid) : BossComponent(module) { - public ActionID WatchedAction { get; private set; } = aid; - public int NumCasts { get; protected set; } + public readonly ActionID WatchedAction = aid; + public int NumCasts; public override void OnEventCast(Actor caster, ActorCastEvent spell) { @@ -15,8 +15,8 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) public class CastCounterMulti(BossModule module, ActionID[] aids) : BossComponent(module) { - public ActionID[] WatchedActions = aids; - public int NumCasts { get; protected set; } + public readonly ActionID[] WatchedActions = aids; + public int NumCasts; public override void OnEventCast(Actor caster, ActorCastEvent spell) { diff --git a/BossMod/Components/CastHint.cs b/BossMod/Components/CastHint.cs index 119d2123e3..046f9d0d8f 100644 --- a/BossMod/Components/CastHint.cs +++ b/BossMod/Components/CastHint.cs @@ -4,7 +4,7 @@ namespace BossMod.Components; public class CastHint(BossModule module, ActionID aid, string hint, bool showCastTimeLeft = false) : CastCounter(module, aid) { public string Hint = hint; - public bool ShowCastTimeLeft = showCastTimeLeft; // if true, show cast time left until next instance + public readonly bool ShowCastTimeLeft = showCastTimeLeft; // if true, show cast time left until next instance public readonly List Casters = []; public bool Active => Casters.Count > 0; @@ -30,10 +30,10 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) public class CastInterruptHint : CastHint { - public bool CanBeInterrupted { get; init; } - public bool CanBeStunned { get; init; } - public bool ShowNameInHint { get; init; } // important if there are several targets - public string HintExtra { get; init; } + public readonly bool CanBeInterrupted; + public readonly bool CanBeStunned; + public readonly bool ShowNameInHint; // important if there are several targets + public readonly string HintExtra; public CastInterruptHint(BossModule module, ActionID aid, bool canBeInterrupted = true, bool canBeStunned = false, string hintExtra = "", bool showNameInHint = false) : base(module, aid, "") { diff --git a/BossMod/Components/Chains.cs b/BossMod/Components/Chains.cs index 7164428544..1704c3e1a2 100644 --- a/BossMod/Components/Chains.cs +++ b/BossMod/Components/Chains.cs @@ -3,8 +3,8 @@ namespace BossMod.Components; // component for breakable chains - Note that chainLength for AI considers the minimum distance needed for a chain-pair to be broken (assuming perfectly stacked at cast) public class Chains(BossModule module, uint tetherID, ActionID aid = default, float chainLength = 0, bool spreadChains = true) : CastCounter(module, aid) { - public uint TID { get; init; } = tetherID; - public bool TethersAssigned { get; private set; } + public readonly uint TID = tetherID; + public bool TethersAssigned; private readonly Actor?[] _partner = new Actor?[PartyState.MaxAllies]; public override void AddHints(int slot, Actor actor, TextHints hints) diff --git a/BossMod/Components/ChasingAOEs.cs b/BossMod/Components/ChasingAOEs.cs index f49c9c7849..4a7fc9c669 100644 --- a/BossMod/Components/ChasingAOEs.cs +++ b/BossMod/Components/ChasingAOEs.cs @@ -83,16 +83,16 @@ private void AddForbiddenZones(Actor actor, AIHints hints, bool isTarget) // standard chasing aoe; first cast is long - assume it is baited on the nearest allowed target; successive casts are instant public class StandardChasingAOEs(BossModule module, AOEShape shape, ActionID actionFirst, ActionID actionRest, float moveDistance, float secondsBetweenActivations, int maxCasts, bool resetExcludedTargets = false, uint icon = default, float activationDelay = 5.1f) : GenericChasingAOEs(module, moveDistance) { - public AOEShape Shape = shape; - public ActionID ActionFirst = actionFirst; - public ActionID ActionRest = actionRest; - public float MoveDistance = moveDistance; - public float SecondsBetweenActivations = secondsBetweenActivations; + public readonly AOEShape Shape = shape; + public readonly ActionID ActionFirst = actionFirst; + public readonly ActionID ActionRest = actionRest; + public readonly float MoveDistance = moveDistance; + public readonly float SecondsBetweenActivations = secondsBetweenActivations; public int MaxCasts = maxCasts; public BitMask ExcludedTargets; // any targets in this mask aren't considered to be possible targets - public uint Icon = icon; - public float ActivationDelay = activationDelay; - public bool ResetExcludedTargets = resetExcludedTargets; + public readonly uint Icon = icon; + public readonly float ActivationDelay = activationDelay; + public readonly bool ResetExcludedTargets = resetExcludedTargets; public readonly List Actors = []; // to keep track of the icon before mechanic starts for handling custom forbidden zones public DateTime Activation; diff --git a/BossMod/Components/Cleave.cs b/BossMod/Components/Cleave.cs index f0484a84cd..e43cce45b0 100644 --- a/BossMod/Components/Cleave.cs +++ b/BossMod/Components/Cleave.cs @@ -4,10 +4,10 @@ // enemy OID == 0 means 'primary actor' public class Cleave(BossModule module, ActionID aid, AOEShape shape, uint enemyOID = 0, bool activeForUntargetable = false, bool originAtTarget = false, bool activeWhileCasting = true) : CastCounter(module, aid) { - public AOEShape Shape { get; init; } = shape; - public bool ActiveForUntargetable { get; init; } = activeForUntargetable; - public bool ActiveWhileCasting { get; init; } = activeWhileCasting; - public bool OriginAtTarget { get; init; } = originAtTarget; + public readonly AOEShape Shape = shape; + public readonly bool ActiveForUntargetable = activeForUntargetable; + public readonly bool ActiveWhileCasting = activeWhileCasting; + public readonly bool OriginAtTarget = originAtTarget; public DateTime NextExpected; private readonly List _enemies = module.Enemies(enemyOID != 0 ? enemyOID : module.PrimaryActor.OID); diff --git a/BossMod/Components/ConcentricAOEs.cs b/BossMod/Components/ConcentricAOEs.cs index b0253fda17..28d065b8fd 100644 --- a/BossMod/Components/ConcentricAOEs.cs +++ b/BossMod/Components/ConcentricAOEs.cs @@ -11,8 +11,8 @@ public struct Sequence public int NumCastsDone; } - public AOEShape[] Shapes = shapes; - public List Sequences = []; + public readonly AOEShape[] Shapes = shapes; + public readonly List Sequences = []; public override IEnumerable ActiveAOEs(int slot, Actor actor) => Sequences.Where(s => s.NumCastsDone < Shapes.Length).Select(s => new AOEInstance(Shapes[s.NumCastsDone], s.Origin, s.Rotation, s.NextActivation)); diff --git a/BossMod/Components/Exaflare.cs b/BossMod/Components/Exaflare.cs index 53f12300b8..2ed77bc9b9 100644 --- a/BossMod/Components/Exaflare.cs +++ b/BossMod/Components/Exaflare.cs @@ -14,10 +14,10 @@ public class Line public int MaxShownExplosions; } - public AOEShape Shape { get; init; } = shape; + public readonly AOEShape Shape = shape; public uint ImminentColor = Colors.Danger; public uint FutureColor = Colors.AOE; - protected List Lines = []; + protected readonly List Lines = []; public bool Active => Lines.Count > 0; diff --git a/BossMod/Components/ForcedMarch.cs b/BossMod/Components/ForcedMarch.cs index eb70ca239b..663d9bec83 100644 --- a/BossMod/Components/ForcedMarch.cs +++ b/BossMod/Components/ForcedMarch.cs @@ -13,10 +13,10 @@ public class PlayerState public bool Active(BossModule module) => ForcedEnd > module.WorldState.CurrentTime || PendingMoves.Count > 0; } - public int NumActiveForcedMarches { get; private set; } - public Dictionary State = []; // key = instance ID + public int NumActiveForcedMarches; + public readonly Dictionary State = []; // key = instance ID public float MovementSpeed = 6; // default movement speed, can be overridden if necessary - public float ActivationLimit = activationLimit; // do not show pending moves that activate later than this limit + public readonly float ActivationLimit = activationLimit; // do not show pending moves that activate later than this limit // called to determine whether we need to show hint public virtual bool DestinationUnsafe(int slot, Actor actor, WPos pos) => !Module.InBounds(pos); @@ -123,12 +123,12 @@ public override void OnStatusLose(Actor actor, ActorStatus status) // action driven forced march public class ActionDrivenForcedMarch(BossModule module, ActionID aid, float duration, Angle rotation, float actioneffectdelay, uint statusForced = 1257, uint statusForcedNPCs = 3629, float activationLimit = float.MaxValue) : GenericForcedMarch(module, activationLimit) { - public float Duration = duration; - public float Actioneffectdelay = actioneffectdelay; - public Angle Rotation = rotation; - public uint StatusForced = statusForced; - public uint StatusForcedNPCs = statusForcedNPCs; - public ActionID Aid = aid; + public readonly float Duration = duration; + public readonly float Actioneffectdelay = actioneffectdelay; + public readonly Angle Rotation = rotation; + public readonly uint StatusForced = statusForced; + public readonly uint StatusForcedNPCs = statusForcedNPCs; + public readonly ActionID Aid = aid; public override void OnStatusGain(Actor actor, ActorStatus status) { diff --git a/BossMod/Components/GenericAOEs.cs b/BossMod/Components/GenericAOEs.cs index ffad6fe11c..699dba2ef5 100644 --- a/BossMod/Components/GenericAOEs.cs +++ b/BossMod/Components/GenericAOEs.cs @@ -35,7 +35,7 @@ public override void DrawArenaBackground(int pcSlot, Actor pc) // self-targeted aoe that happens at the end of the cast public class SelfTargetedAOEs(BossModule module, ActionID aid, AOEShape shape, int maxCasts = int.MaxValue, uint color = 0) : GenericAOEs(module, aid) { - public AOEShape Shape { get; init; } = shape; + public readonly AOEShape Shape = shape; public int MaxCasts = maxCasts; // used for staggered aoes, when showing all active would be pointless public uint Color = color; public bool Risky = true; // can be customized if needed @@ -43,7 +43,21 @@ public class SelfTargetedAOEs(BossModule module, ActionID aid, AOEShape shape, i public IEnumerable ActiveCasters => Casters.Take(MaxCasts); - public override IEnumerable ActiveAOEs(int slot, Actor actor) => ActiveCasters.Select(c => new AOEInstance(Shape, c.Position, c.CastInfo!.Rotation, Module.CastFinishAt(c.CastInfo))); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = Casters.Count; + if (count == 0) + return []; + var min = count > MaxCasts ? MaxCasts : count; + var aoes = new List(min); + for (var i = 0; i < min; ++i) + { + var caster = Casters[i]; + AOEInstance aoeInstance = new(Shape, caster.Position, caster.CastInfo!.Rotation, Module.CastFinishAt(caster.CastInfo)); + aoes.Add(aoeInstance); + } + return aoes; + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { @@ -61,7 +75,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) // self-targeted aoe that uses current caster's rotation instead of rotation from cast-info - used by legacy modules written before i've reversed real cast rotation public class SelfTargetedLegacyRotationAOEs(BossModule module, ActionID aid, AOEShape shape, int maxCasts = int.MaxValue) : GenericAOEs(module, aid) { - public AOEShape Shape { get; init; } = shape; + public readonly AOEShape Shape = shape; public int MaxCasts = maxCasts; // used for staggered aoes, when showing all active would be pointless public readonly List Casters = []; @@ -86,16 +100,23 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) public class LocationTargetedAOEs(BossModule module, ActionID aid, AOEShape shape, int maxCasts = int.MaxValue, bool targetIsLocation = false) : GenericAOEs(module, aid) { public LocationTargetedAOEs(BossModule module, ActionID aid, float radius, int maxCasts = int.MaxValue, bool targetIsLocation = false) : this(module, aid, new AOEShapeCircle(radius), maxCasts, targetIsLocation) { } - public AOEShape Shape { get; init; } = shape; - public int MaxCasts = maxCasts; // used for staggered aoes, when showing all active would be pointless + public readonly AOEShape Shape = shape; + public readonly int MaxCasts = maxCasts; // used for staggered aoes, when showing all active would be pointless public uint Color; // can be customized if needed public bool Risky = true; // can be customized if needed - public bool TargetIsLocation { get; init; } = targetIsLocation; // can be customized if needed + public readonly bool TargetIsLocation = targetIsLocation; // can be customized if needed - public List Casters { get; } = []; + public readonly List Casters = []; public IEnumerable ActiveCasters => Casters.Take(MaxCasts); - public override IEnumerable ActiveAOEs(int slot, Actor actor) => Casters.Take(MaxCasts); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = Casters.Count; + if (count == 0) + yield break; + for (var i = 0; i < (count > MaxCasts ? MaxCasts : count); ++i) + yield return Casters[i]; + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { @@ -116,10 +137,23 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) // 'charge at location' aoes that happen at the end of the cast public class ChargeAOEs(BossModule module, ActionID aid, float halfWidth) : GenericAOEs(module, aid) { - public float HalfWidth { get; init; } = halfWidth; + public readonly float HalfWidth = halfWidth; public readonly List<(Actor caster, AOEShape shape, Angle direction)> Casters = []; - public override IEnumerable ActiveAOEs(int slot, Actor actor) => Casters.Select(csr => new AOEInstance(csr.shape, csr.caster.Position, csr.direction, Module.CastFinishAt(csr.caster.CastInfo))); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = Casters.Count; + if (count == 0) + return []; + var aoes = new List(count); + for (var i = 0; i < count; ++i) + { + var csr = Casters[i]; + AOEInstance aoeInstance = new(csr.shape, csr.caster.Position, csr.direction, Module.CastFinishAt(csr.caster.CastInfo)); + aoes.Add(aoeInstance); + } + return aoes; + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { diff --git a/BossMod/Components/Knockback.cs b/BossMod/Components/Knockback.cs index 4b56d89eee..da3b62e2bd 100644 --- a/BossMod/Components/Knockback.cs +++ b/BossMod/Components/Knockback.cs @@ -45,15 +45,15 @@ protected struct PlayerImmuneState public readonly bool ImmuneAt(DateTime time) => RoleBuffExpire > time || JobBuffExpire > time || DutyBuffExpire > time; } - public List SafeWalls = safeWalls ?? []; - public bool IgnoreImmunes = ignoreImmunes; - public bool StopAtWall = stopAtWall; // use if wall is solid rather than deadly - public bool StopAfterWall = stopAfterWall; // use if the wall is a polygon where you need to check for intersections - public int MaxCasts = maxCasts; // use to limit number of drawn knockbacks + public readonly List SafeWalls = safeWalls ?? []; + public readonly bool IgnoreImmunes = ignoreImmunes; + public readonly bool StopAtWall = stopAtWall; // use if wall is solid rather than deadly + public readonly bool StopAfterWall = stopAfterWall; // use if the wall is a polygon where you need to check for intersections + public readonly int MaxCasts = maxCasts; // use to limit number of drawn knockbacks private const float approxHitBoxRadius = 0.499f; // calculated because due to floating point errors this does not result in 0.001 private const float maxIntersectionError = 0.5f - approxHitBoxRadius; // calculated because due to floating point errors this does not result in 0.001 - protected PlayerImmuneState[] PlayerImmunes = new PlayerImmuneState[PartyState.MaxAllies]; + protected readonly PlayerImmuneState[] PlayerImmunes = new PlayerImmuneState[PartyState.MaxAllies]; public bool IsImmune(int slot, DateTime time) => !IgnoreImmunes && PlayerImmunes[slot].ImmuneAt(time); @@ -225,11 +225,11 @@ public override void OnStatusLose(Actor actor, ActorStatus status) public class KnockbackFromCastTarget(BossModule module, ActionID aid, float distance, bool ignoreImmunes = false, int maxCasts = int.MaxValue, AOEShape? shape = null, Kind kind = Kind.AwayFromOrigin, float minDistance = 0, bool minDistanceBetweenHitboxes = false, bool stopAtWall = false, bool stopAfterWall = false, List? safeWalls = null) : Knockback(module, aid, ignoreImmunes, maxCasts, stopAtWall, stopAfterWall, safeWalls) { - public float Distance = distance; - public AOEShape? Shape = shape; - public Kind KnockbackKind = kind; - public float MinDistance = minDistance; - public bool MinDistanceBetweenHitboxes = minDistanceBetweenHitboxes; + public readonly float Distance = distance; + public readonly AOEShape? Shape = shape; + public readonly Kind KnockbackKind = kind; + public readonly float MinDistance = minDistance; + public readonly bool MinDistanceBetweenHitboxes = minDistanceBetweenHitboxes; public readonly List Casters = []; public override IEnumerable Sources(int slot, Actor actor) diff --git a/BossMod/Components/LineOfSightAOE.cs b/BossMod/Components/LineOfSightAOE.cs index 35e02fd100..bd1b6709b5 100644 --- a/BossMod/Components/LineOfSightAOE.cs +++ b/BossMod/Components/LineOfSightAOE.cs @@ -6,19 +6,19 @@ public abstract class GenericLineOfSightAOE(BossModule module, ActionID aid, float maxRange, bool blockersImpassable = false, bool rect = false, bool safeInsideHitbox = true) : GenericAOEs(module, aid, "Hide behind obstacle!") { public DateTime NextExplosion; - public bool BlockersImpassable = blockersImpassable; - public bool SafeInsideHitbox = safeInsideHitbox; - public float MaxRange { get; private set; } = maxRange; - public bool Rect { get; private set; } = rect; // if the AOE is a rectangle instead of a circle + public readonly bool BlockersImpassable = blockersImpassable; + public readonly bool SafeInsideHitbox = safeInsideHitbox; + public readonly float MaxRange = maxRange; + public readonly bool Rect = rect; // if the AOE is a rectangle instead of a circle public BitMask IgnoredPlayers; - public WPos? Origin { get; private set; } // inactive if null - public List<(WPos Center, float Radius)> Blockers { get; private set; } = []; - public List<(float Distance, Angle Dir, Angle HalfWidth)> Visibility { get; private set; } = []; - public List Safezones = []; - public List UnionShapes = []; - public List DifferenceShapes = []; + public WPos? Origin; // inactive if null + public readonly List<(WPos Center, float Radius)> Blockers = []; + public readonly List<(float Distance, Angle Dir, Angle HalfWidth)> Visibility = []; + public readonly List Safezones = []; + public readonly List UnionShapes = []; + public readonly List DifferenceShapes = []; - public override IEnumerable ActiveAOEs(int slot, Actor actor) => !IgnoredPlayers[slot] ? Safezones.Take(1) : []; + public override IEnumerable ActiveAOEs(int slot, Actor actor) => Safezones.Count != 0 && !IgnoredPlayers[slot] ? [Safezones[0]] : []; public void Modify(WPos? origin, IEnumerable<(WPos Center, float Radius)> blockers, DateTime nextExplosion = default) { @@ -57,37 +57,38 @@ public void AddSafezone(DateTime activation, Angle rotation = default) { if (!Rect) { - foreach (var v in Visibility) + for (var i = 0; i < Visibility.Count; ++i) + { + var v = Visibility[i]; UnionShapes.Add(new DonutSegmentHA(Origin.Value, v.Distance + 0.2f, MaxRange, v.Dir, v.HalfWidth)); + } } else if (Rect) { - foreach (var b in Blockers) + for (var i = 0; i < Blockers.Count; ++i) { + var b = Blockers[i]; var dir = rotation.ToDirection(); UnionShapes.Add(new RectangleSE(b.Center + 0.2f * dir, b.Center + MaxRange * dir, b.Radius)); } } if (BlockersImpassable || !SafeInsideHitbox) - foreach (var b in Blockers) + for (var i = 0; i < Blockers.Count; ++i) + { + var b = Blockers[i]; DifferenceShapes.Add(new Circle(b.Center, !SafeInsideHitbox ? b.Radius : b.Radius + 0.5f)); - Safezones.Add(new(new AOEShapeCustom(CopyShapes(UnionShapes), CopyShapes(DifferenceShapes), InvertForbiddenZone: true), Arena.Center, default, activation, Colors.SafeFromAOE)); + } + Safezones.Add(new(new AOEShapeCustom([.. UnionShapes], [.. DifferenceShapes], InvertForbiddenZone: true), Arena.Center, default, activation, Colors.SafeFromAOE)); UnionShapes.Clear(); + DifferenceShapes.Clear(); } } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (Safezones.Count > 0 && spell.Action == WatchedAction) + if (Safezones.Count != 0 && spell.Action == WatchedAction) Safezones.RemoveAt(0); } - - private static List CopyShapes(List shapes) - { - var copy = new List(); - copy.AddRange(shapes); - return copy; - } } // simple line-of-sight aoe that happens at the end of the cast diff --git a/BossMod/Components/PersistentVoidzone.cs b/BossMod/Components/PersistentVoidzone.cs index 7faf03040a..4f58afc4fc 100644 --- a/BossMod/Components/PersistentVoidzone.cs +++ b/BossMod/Components/PersistentVoidzone.cs @@ -6,8 +6,8 @@ namespace BossMod.Components; // TODO: typically sources are either eventobj's with eventstate != 7 or normal actors that are non dead; other conditions are much rarer public class PersistentVoidzone(BossModule module, float radius, Func> sources, float moveHintLength = 0) : GenericAOEs(module, default, "GTFO from voidzone!") { - public AOEShape Shape { get; init; } = moveHintLength == 0 ? new AOEShapeCircle(radius) : new AOEShapeCapsule(radius, moveHintLength); - public Func> Sources { get; init; } = sources; + public readonly AOEShape Shape = moveHintLength == 0 ? new AOEShapeCircle(radius) : new AOEShapeCapsule(radius, moveHintLength); + public readonly Func> Sources = sources; public override IEnumerable ActiveAOEs(int slot, Actor actor) { @@ -31,9 +31,9 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme // TODO: this has problems if voidzone never actually spawns after castevent, eg because of phase changes public class PersistentVoidzoneAtCastTarget(BossModule module, float radius, ActionID aid, Func> sources, float castEventToSpawn) : GenericAOEs(module, aid, "GTFO from voidzone!") { - public AOEShapeCircle Shape { get; init; } = new(radius); - public Func> Sources { get; init; } = sources; - public float CastEventToSpawn { get; init; } = castEventToSpawn; + public readonly AOEShapeCircle Shape = new(radius); + public readonly Func> Sources = sources; + public readonly float CastEventToSpawn = castEventToSpawn; private readonly List<(WPos pos, DateTime time)> _predictedByEvent = []; private readonly List<(Actor caster, DateTime time)> _predictedByCast = []; @@ -81,8 +81,8 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) // TODO: might want to have per-player invertability public class PersistentInvertibleVoidzone(BossModule module, float radius, Func> sources, ActionID aid = default) : CastCounter(module, aid) { - public AOEShapeCircle Shape { get; init; } = new(radius); - public Func> Sources { get; init; } = sources; + public readonly AOEShapeCircle Shape = new(radius); + public readonly Func> Sources = sources; public DateTime InvertResolveAt; public bool Inverted => InvertResolveAt != default; diff --git a/BossMod/Components/Protean.cs b/BossMod/Components/Protean.cs index 60094a8460..b225f7bb78 100644 --- a/BossMod/Components/Protean.cs +++ b/BossMod/Components/Protean.cs @@ -4,7 +4,7 @@ // TODO: combine with BaitAway public abstract class GenericProtean(BossModule module, ActionID aid, AOEShape shape) : CastCounter(module, aid) { - public AOEShape Shape { get; init; } = shape; + public readonly AOEShape Shape = shape; public abstract IEnumerable<(Actor source, Actor target)> ActiveAOEs(); diff --git a/BossMod/Components/RotatingAOE.cs b/BossMod/Components/RotatingAOE.cs index 606bcb966a..0b711b6685 100644 --- a/BossMod/Components/RotatingAOE.cs +++ b/BossMod/Components/RotatingAOE.cs @@ -15,7 +15,7 @@ public record struct Sequence int MaxShownAOEs = 2 ); - public List Sequences = []; + public readonly List Sequences = []; public virtual uint ImminentColor { get; set; } = Colors.Danger; public uint FutureColor = Colors.AOE; diff --git a/BossMod/Components/SharedTankbuster.cs b/BossMod/Components/SharedTankbuster.cs index 43f7ad869f..988637bfeb 100644 --- a/BossMod/Components/SharedTankbuster.cs +++ b/BossMod/Components/SharedTankbuster.cs @@ -4,8 +4,8 @@ // TODO: revise and improve (track invuln, ai hints, num stacked tanks?) public class GenericSharedTankbuster(BossModule module, ActionID aid, AOEShape shape, bool originAtTarget = false) : CastCounter(module, aid) { - public AOEShape Shape { get; init; } = shape; - public bool OriginAtTarget { get; init; } = originAtTarget; + public readonly AOEShape Shape = shape; + public readonly bool OriginAtTarget = originAtTarget; protected Actor? Source; protected Actor? Target; protected DateTime Activation; diff --git a/BossMod/Components/StackSpread.cs b/BossMod/Components/StackSpread.cs index e326fd8bba..814734984d 100644 --- a/BossMod/Components/StackSpread.cs +++ b/BossMod/Components/StackSpread.cs @@ -27,11 +27,11 @@ public record struct Spread( DateTime Activation = default ); - public bool AlwaysShowSpreads = alwaysShowSpreads; // if false, we only shown own spread radius for spread targets - this reduces visual clutter - public bool RaidwideOnResolve = raidwideOnResolve; // if true, assume even if mechanic is correctly resolved everyone will still take damage - public bool IncludeDeadTargets = includeDeadTargets; // if false, stacks & spreads with dead targets are ignored + public readonly bool AlwaysShowSpreads = alwaysShowSpreads; // if false, we only shown own spread radius for spread targets - this reduces visual clutter + public readonly bool RaidwideOnResolve = raidwideOnResolve; // if true, assume even if mechanic is correctly resolved everyone will still take damage + public readonly bool IncludeDeadTargets = includeDeadTargets; // if false, stacks & spreads with dead targets are ignored public int ExtraAISpreadThreshold = 1; - public List Stacks = []; + public readonly List Stacks = []; public List Spreads = []; public const string StackHint = "Stack!"; @@ -214,8 +214,8 @@ public abstract class UniformStackSpread(BossModule module, float stackRadius, f public class CastStackSpread(BossModule module, ActionID stackAID, ActionID spreadAID, float stackRadius, float spreadRadius, int minStackSize = 2, int maxStackSize = int.MaxValue, bool alwaysShowSpreads = false) : UniformStackSpread(module, stackRadius, spreadRadius, minStackSize, maxStackSize, alwaysShowSpreads) { - public ActionID StackAction { get; init; } = stackAID; - public ActionID SpreadAction { get; init; } = spreadAID; + public readonly ActionID StackAction = stackAID; + public readonly ActionID SpreadAction = spreadAID; public int NumFinishedStacks { get; protected set; } public int NumFinishedSpreads { get; protected set; } @@ -256,14 +256,14 @@ public class StackWithCastTargets(BossModule module, ActionID aid, float radius, public class IconStackSpread(BossModule module, uint stackIcon, uint spreadIcon, ActionID stackAID, ActionID spreadAID, float stackRadius, float spreadRadius, float activationDelay, int minStackSize = 2, int maxStackSize = int.MaxValue, bool alwaysShowSpreads = false, int maxCasts = 1) : UniformStackSpread(module, stackRadius, spreadRadius, minStackSize, maxStackSize, alwaysShowSpreads) { - public uint StackIcon { get; init; } = stackIcon; - public uint SpreadIcon { get; init; } = spreadIcon; - public ActionID StackAction { get; init; } = stackAID; - public ActionID SpreadAction { get; init; } = spreadAID; - public float ActivationDelay { get; init; } = activationDelay; - public int NumFinishedStacks { get; protected set; } - public int NumFinishedSpreads { get; protected set; } - public int MaxCasts { get; init; } = maxCasts; // for stacks where the final AID hits multiple times + public readonly uint StackIcon = stackIcon; + public readonly uint SpreadIcon = spreadIcon; + public readonly ActionID StackAction = stackAID; + public readonly ActionID SpreadAction = spreadAID; + public readonly float ActivationDelay = activationDelay; + public int NumFinishedStacks; + public int NumFinishedSpreads; + public readonly int MaxCasts = maxCasts; // for stacks where the final AID hits multiple times private int castCounter; public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) @@ -313,18 +313,18 @@ public LineStack(BossModule module, uint iconid, ActionID aidResolve, float acti // TODO: add forbidden slots logic? // TODO: add logic for min and max stack size - public ActionID? AidMarker = aidMarker; - public ActionID AidResolve = aidResolve; - public float ActionDelay = activationDelay; - public float Range = range; - public float HalfWidth = halfWidth; - public int MaxStackSize = maxStackSize; - public int MinStackSize = minStackSize; - public int MaxCasts = maxCasts; // for stacks where the final AID hits multiple times - public bool MarkerIsFinalTarget = markerIsFinalTarget; // rarely the marked player is not the target of the line stack - public HashSet ForbiddenActors = []; + public readonly ActionID? AidMarker = aidMarker; + public readonly ActionID AidResolve = aidResolve; + public readonly float ActionDelay = activationDelay; + public readonly float Range = range; + public readonly float HalfWidth = halfWidth; + public readonly int MaxStackSize = maxStackSize; + public readonly int MinStackSize = minStackSize; + public readonly int MaxCasts = maxCasts; // for stacks where the final AID hits multiple times + public readonly bool MarkerIsFinalTarget = markerIsFinalTarget; // rarely the marked player is not the target of the line stack + public readonly HashSet ForbiddenActors = []; private int castCounter; - public uint? Iconid = iconid; + public readonly uint? Iconid = iconid; public const string HintStack = "Stack!"; public const string HintAvoidOther = "GTFO from other line stacks!"; public const string HintAvoid = "GTFO from line stacks!"; @@ -480,10 +480,10 @@ public class DonutStack(BossModule module, ActionID aid, uint icon, float innerR { // this is a donut targeted on each player, it is best solved by stacking // regular stack component won't work because this is self targeted - public AOEShapeDonut Donut { get; init; } = new(innerRadius, outerRadius); - public float ActivationDelay { get; init; } = activationDelay; - public uint Icon { get; init; } = icon; - public ActionID Aid { get; init; } = aid; + public readonly AOEShapeDonut Donut = new(innerRadius, outerRadius); + public readonly float ActivationDelay = activationDelay; + public readonly uint Icon = icon; + public readonly ActionID Aid = aid; public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) { diff --git a/BossMod/Components/StayInBounds.cs b/BossMod/Components/StayInBounds.cs deleted file mode 100644 index 5cfa410358..0000000000 --- a/BossMod/Components/StayInBounds.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace BossMod.Components; - -// component that forces the AI to stay in bounds or return inside (for example after a failed knockback) -public class StayInBounds(BossModule module) : BossComponent(module) -{ - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - if (!Arena.InBounds(actor.Position)) - hints.AddForbiddenZone(ShapeDistance.InvertedCircle(Arena.Center, 3)); - } -} diff --git a/BossMod/Components/StayMove.cs b/BossMod/Components/StayMove.cs index 88acc729dc..51b22e30ad 100644 --- a/BossMod/Components/StayMove.cs +++ b/BossMod/Components/StayMove.cs @@ -8,7 +8,7 @@ public enum Requirement { None, Stay, Move } public record struct PlayerState(Requirement Requirement, DateTime Activation, int Priority = 0); public readonly PlayerState[] PlayerStates = new PlayerState[PartyState.MaxAllies]; - public float MaxTimeToShowHint = maxTimeToShowHint; + public readonly float MaxTimeToShowHint = maxTimeToShowHint; public override void AddHints(int slot, Actor actor, TextHints hints) { diff --git a/BossMod/Components/Tethers.cs b/BossMod/Components/Tethers.cs index 354ad4f876..533040657d 100644 --- a/BossMod/Components/Tethers.cs +++ b/BossMod/Components/Tethers.cs @@ -3,8 +3,8 @@ // generic component for tankbuster at tethered targets; tanks are supposed to intercept tethers and gtfo from the raid public class TankbusterTether(BossModule module, ActionID aid, uint tetherID, float radius) : CastCounter(module, aid) { - public uint TID { get; init; } = tetherID; - public float Radius { get; init; } = radius; + public readonly uint TID = tetherID; + public readonly float Radius = radius; private readonly List<(Actor Player, Actor Enemy)> _tethers = []; private BitMask _tetheredPlayers; private BitMask _inAnyAOE; // players hit by aoe, excluding selves @@ -126,9 +126,9 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether) // generic component for tethers that need to be intercepted eg. to prevent a boss from gaining buffs public class InterceptTether(BossModule module, ActionID aid, uint tetherIDBad = 84, uint tetherIDGood = 17, uint[]? excludedAllies = null) : CastCounter(module, aid) { - public uint TIDGood = tetherIDGood; - public uint TIDBad = tetherIDBad; - public uint[]? ExcludedAllies = excludedAllies; + public readonly uint TIDGood = tetherIDGood; + public readonly uint TIDBad = tetherIDBad; + public readonly uint[]? ExcludedAllies = excludedAllies; private readonly List<(Actor Player, Actor Enemy)> _tethers = []; private BitMask _tetheredPlayers; private const string hint = "Grab the tether!"; @@ -198,15 +198,15 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether) // at the end of the mechanic various things are possible, eg. single target dmg, knockback/pull, AOE etc. public class StretchTetherDuo(BossModule module, float minimumDistance, float activationDelay, uint tetherIDBad = 57, uint tetherIDGood = 1, AOEShape? shape = null, ActionID aid = default, uint enemyOID = default, bool knockbackImmunity = false) : GenericBaitAway(module, aid) { - public AOEShape? Shape = shape; - public uint TIDGood = tetherIDGood; - public uint TIDBad = tetherIDBad; - public float MinimumDistance = minimumDistance; - public bool KnockbackImmunity { get; init; } = knockbackImmunity; + public readonly AOEShape? Shape = shape; + public readonly uint TIDGood = tetherIDGood; + public readonly uint TIDBad = tetherIDBad; + public readonly float MinimumDistance = minimumDistance; + public readonly bool KnockbackImmunity = knockbackImmunity; public readonly List _enemies = module.Enemies(enemyOID); public readonly List<(Actor, uint)> TetherOnActor = []; public readonly List<(Actor, DateTime)> ActivationDelayOnActor = []; - public float ActivationDelay = activationDelay; + public readonly float ActivationDelay = activationDelay; public const string HintGood = "Tether is stretched!"; public const string HintBad = "Stretch tether further!"; public const string HintKnockbackImmmunityGood = "Immune against tether mechanic!"; diff --git a/BossMod/Components/ThinIce.cs b/BossMod/Components/ThinIce.cs index f51c2c4b4f..f06cde19b4 100644 --- a/BossMod/Components/ThinIce.cs +++ b/BossMod/Components/ThinIce.cs @@ -52,7 +52,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme ShapeDistance.InvertedDonut(pos, ddistance, ddistance + 0.5f), ShapeDistance.InvertedRect(pos, offset, 0.5f, 0.5f, 0.5f) }; - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), WorldState.FutureTime(1.1f)); + hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), DateTime.MaxValue); } } } diff --git a/BossMod/Components/Towers.cs b/BossMod/Components/Towers.cs index bb74ad9255..19bb38b2da 100644 --- a/BossMod/Components/Towers.cs +++ b/BossMod/Components/Towers.cs @@ -19,8 +19,8 @@ public struct Tower(WPos position, float radius, int minSoakers = 1, int maxSoak public readonly bool TooManyInside(BossModule module) => NumInside(module) is var count && count > MaxSoakers; } - public List Towers = []; - public bool PrioritizeInsufficient = prioritizeInsufficient; // give priority to towers with more than 0 but less than min soakers + public readonly List Towers = []; + public readonly bool PrioritizeInsufficient = prioritizeInsufficient; // give priority to towers with more than 0 but less than min soakers // default tower styling public static void DrawTower(MiniArena arena, WPos pos, float radius, bool safe) @@ -102,9 +102,9 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme public class CastTowers(BossModule module, ActionID aid, float radius, int minSoakers = 1, int maxSoakers = 1) : GenericTowers(module, aid) { - public float Radius = radius; - public int MinSoakers = minSoakers; - public int MaxSoakers = maxSoakers; + public readonly float Radius = radius; + public readonly int MinSoakers = minSoakers; + public readonly int MaxSoakers = maxSoakers; public override void OnCastStarted(Actor caster, ActorCastInfo spell) { diff --git a/BossMod/Components/UnavoidableDamage.cs b/BossMod/Components/UnavoidableDamage.cs index f3f7efe5ab..2e37d97291 100644 --- a/BossMod/Components/UnavoidableDamage.cs +++ b/BossMod/Components/UnavoidableDamage.cs @@ -13,8 +13,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme // generic unavoidable raidwide, initiated by a custom condition and applied by an instant cast after a delay public class RaidwideInstant(BossModule module, ActionID aid, float delay, string hint = "Raidwide") : CastCounter(module, aid) { - public float Delay = delay; - public string Hint = hint; + public readonly float Delay = delay; + public readonly string Hint = hint; public DateTime Activation; // default if inactive, otherwise expected cast time public override void AddGlobalHints(GlobalHints hints) @@ -82,13 +82,13 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme // generic unavoidable single-target damage, initiated by a custom condition and applied by an instant cast after a delay public class SingleTargetInstant(BossModule module, ActionID aid, float delay, string hint = "Tankbuster") : CastCounter(module, aid) { - public float Delay = delay; // delay from visual cast end to cast event - public string Hint = hint; + public readonly float Delay = delay; // delay from visual cast end to cast event + public readonly string Hint = hint; public readonly List<(int slot, DateTime activation)> Targets = []; public override void AddGlobalHints(GlobalHints hints) { - if (Targets.Count > 0 && Hint.Length > 0) + if (Targets.Count != 0 && Hint.Length != 0) hints.Add(Hint); } diff --git a/BossMod/Components/WildCharge.cs b/BossMod/Components/WildCharge.cs index 7518d32d23..e60f8f520f 100644 --- a/BossMod/Components/WildCharge.cs +++ b/BossMod/Components/WildCharge.cs @@ -13,8 +13,8 @@ public enum PlayerRole Avoid, // player has to avoid aoe } - public float HalfWidth = halfWidth; - public float FixedLength = fixedLength; // if == 0, length is up to target + public readonly float HalfWidth = halfWidth; + public readonly float FixedLength = fixedLength; // if == 0, length is up to target public Actor? Source; // if null, mechanic is not active public DateTime Activation; public PlayerRole[] PlayerRoles = new PlayerRole[PartyState.MaxAllies]; diff --git a/BossMod/Data/ActionEffect.cs b/BossMod/Data/ActionEffect.cs index b20490dd69..ba3132a15e 100644 --- a/BossMod/Data/ActionEffect.cs +++ b/BossMod/Data/ActionEffect.cs @@ -83,6 +83,7 @@ public enum KnockbackDirection SourceForward = 3, // direction = src.direction SourceRight = 4, // direction = src.direction - pi/2 SourceLeft = 5, // direction = src.direction + pi/2 + AwayFromSource2 = 6, // direction = target-source } public enum ActionResourceType diff --git a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs index e4bc691b37..969fa552ec 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D031FeatherRay.cs @@ -62,11 +62,15 @@ class AiryBubble(BossModule module) : Components.GenericAOEs(module) private const float Radius = 1.1f; private const int Length = 3; private static readonly AOEShapeCapsule capsule = new(Radius, Length); - private readonly List _aoes = []; + private readonly List bubbles = module.Enemies(OID.AiryBubble); + private readonly List _aoes = new(36); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - for (var i = 0; i < _aoes.Count; ++i) + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) { var o = _aoes[i]; yield return new(capsule, o.Position, o.Rotation); @@ -75,7 +79,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) public override void OnActorPlayActionTimelineEvent(Actor actor, ushort id) { - if (Module.Enemies(OID.AiryBubble).Where(x => x.HitboxRadius == 1.1f).Contains(actor)) + if (bubbles.Any(x => x.HitboxRadius == 1.1f && x == actor)) if (id == 0x1E46) _aoes.Add(actor); else if (id == 0x1E3C) @@ -84,10 +88,11 @@ public override void OnActorPlayActionTimelineEvent(Actor actor, ushort id) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (_aoes.Count == 0) + var count = _aoes.Count; + if (count == 0) return; - var forbidden = new List>(); - for (var i = 0; i < _aoes.Count; ++i) + var forbidden = new List>(count + 1); + for (var i = 0; i < count; ++i) { var o = _aoes[i]; forbidden.Add(ShapeDistance.Capsule(o.Position, o.Rotation, Length, Radius)); @@ -100,7 +105,8 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme class Burst(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(6); - private readonly List _aoes = []; + private readonly List bubbles = module.Enemies(OID.AiryBubble); + private readonly List _aoes = new(18); public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; @@ -120,7 +126,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) private void AddAOEs(float offset, DateTime activation) { - foreach (var orb in Module.Enemies(OID.AiryBubble).Where(x => x.HitboxRadius != 1.1f)) + foreach (var orb in bubbles.Where(x => x.HitboxRadius != 1.1f)) _aoes.Add(new(circle, orb.Position + new WDir(offset, 0), default, activation)); } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs index b8b6564c3a..78842776e8 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs @@ -75,10 +75,17 @@ class Stonecarver(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_aoes.Count > 0) - yield return _aoes[0] with { Color = Colors.Danger }; - if (_aoes.Count > 1) - yield return _aoes[1] with { Risky = false }; + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var aoe = _aoes[i]; + if (i == 0) + yield return count != 1 ? aoe with { Color = Colors.Danger } : aoe; + else if (i == 1) + yield return aoe with { Risky = false }; + } } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -92,29 +99,33 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (_aoes.Count > 0 && aids.Contains((AID)spell.Action.ID)) + if (_aoes.Count != 0 && aids.Contains((AID)spell.Action.ID)) _aoes.RemoveAt(0); } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { base.AddAIHints(slot, actor, assignment, hints); - if (_aoes.Count > 0) + if (_aoes.Count != 0) hints.AddForbiddenZone(ShapeDistance.InvertedRect(Arena.Center + offset, Arena.Center - offset, 2), _aoes[0].Activation); } } class Shatter(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; - - private static readonly AOEShapeRect rectCenter = new(40, 10); - private static readonly AOEShapeRect rectSides = new(42, 11, 4); + private readonly List _aoes = new(2); + private static readonly AOEShapeRect rectCenter = new(40, 10), rectSides = new(42, 11, 4); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - foreach (var c in _aoes) - yield return new(c.Shape, c.Origin, c.Rotation, c.Activation, Risky: c.Activation.AddSeconds(-6) <= WorldState.CurrentTime); + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var a = _aoes[i]; + yield return new(a.Shape, a.Origin, a.Rotation, a.Activation, Risky: a.Activation.AddSeconds(-6) <= WorldState.CurrentTime); + } } public override void OnCastFinished(Actor caster, ActorCastInfo spell) @@ -144,30 +155,33 @@ abstract class Impact(BossModule module, AID aid, int distance) : Components.Kno class Impact1(BossModule module) : Impact(module, AID.Impact1, 18) { + private static readonly Angle halfAngle = 30.Degrees(); + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { var source = Sources(slot, actor).FirstOrDefault(); if (source != default) - hints.AddForbiddenZone(ShapeDistance.InvertedDonutSector(source.Origin, 10, 12, default, 30.Degrees()), source.Activation); + hints.AddForbiddenZone(ShapeDistance.InvertedDonutSector(source.Origin, 10, 12, default, halfAngle), source.Activation); } } class Impact2(BossModule module) : Impact(module, AID.Impact2, 18) { + private static readonly Angle halfAngle = 20.Degrees(); + public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation) && z.Risky) ?? false) || !Arena.InBounds(pos); public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { var source = Sources(slot, actor).FirstOrDefault(); if (source != default) - hints.AddForbiddenZone(ShapeDistance.InvertedDonutSector(source.Origin, 10, 12, default, 20.Degrees()), source.Activation); + hints.AddForbiddenZone(ShapeDistance.InvertedDonutSector(source.Origin, 10, 12, default, halfAngle), source.Activation); } } class Impact3(BossModule module) : Impact(module, AID.Impact3, 20) { - private static readonly Angle halfAngle = 10.Degrees(); - private static readonly Angle direction = 135.Degrees(); + private static readonly Angle halfAngle = 10.Degrees(), direction = 135.Degrees(); public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { diff --git a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D091LindblumZaghnal.cs b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D091LindblumZaghnal.cs index 04d02ccaf8..12548b8b89 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D091LindblumZaghnal.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D091LindblumZaghnal.cs @@ -38,18 +38,17 @@ public enum AID : uint abstract class LineVoltage(BossModule module, AID narrow, float delay, AID? wide1 = null, AID? wide2 = null) : Components.GenericAOEs(module) { private static readonly AOEShapeRect rectNarrow = new(50, 2.5f), rectWide = new(50, 5); - public readonly List AOEs = []; + public readonly List AOEs = new(18); public override IEnumerable ActiveAOEs(int slot, Actor actor) { var count = AOEs.Count; - if (count > 0) + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) { - for (var i = 0; i < count; ++i) - { - var aoe = AOEs[i]; - yield return (aoe.Activation - AOEs[0].Activation).TotalSeconds <= delay ? aoe with { Color = Colors.Danger } : aoe with { Risky = false }; - } + var aoe = AOEs[i]; + yield return (aoe.Activation - AOEs[0].Activation).TotalSeconds <= delay ? aoe with { Color = Colors.Danger } : aoe with { Risky = false }; } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs index a20da36a7a..2d15fed2ca 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs @@ -123,9 +123,16 @@ class LostHope(BossModule module) : Components.TemporaryMisdirection(module, Act class DarkII(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCone cone = new(35, 15.Degrees()); - private readonly List _aoes = []; + private readonly List _aoes = new(12); - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes.Take(6); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < (count > 6 ? 6 : count); ++i) + yield return _aoes[i]; + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { diff --git a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs index 324cfd34c7..41aab6bdfe 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs @@ -2,7 +2,6 @@ public enum OID : uint { - Boss = 0x464C, // R5.98 Boulder1 = 0x1EBCC0, // R0.5 Boulder2 = 0x1EBCC1, // R0.5 @@ -35,7 +34,7 @@ public enum AID : uint RockBlast = 40611, // Helper->self, 1.0s cast, range 5 circle TuraliStoneIV = 40616, // Helper->players, 5.0s cast, range 6 circle, stack SonicHowl = 40618, // Boss->self, 5.0s cast, range 60 circle, raidwide - Slabber = 40619, // Boss->player, 5.0s cast, single-target, tankbuster + Slabber = 40619 // Boss->player, 5.0s cast, single-target, tankbuster } class ArenaChanges(BossModule module) : Components.GenericAOEs(module) @@ -101,7 +100,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) class BoulderDance(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(4); private static readonly AOEShapeCircle circle = new(7); public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; @@ -139,16 +138,24 @@ class Slabber(BossModule module) : Components.SingleTargetCast(module, ActionID. class LeapingEarth(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(16); private static readonly AOEShapeCircle circle = new(5); - private readonly List angles = []; + private readonly List angles = new(4); private static readonly WPos[] spiralSmallPoints = [D093Lunipyati.ArenaCenter, new(31.8f, -715.5f), new(35, -721.7f), new(41, -722.5f)]; private static readonly WPos[] spiralBigPoints = [D093Lunipyati.ArenaCenter, new(28.7f, -708.2f), new(29.4f, -714), new(35.4f, -715.8f), new(40, -711), new(38.7f, -705), new(34, -701.5f), new(28, -701.4f), new(24, -704.399f), new(22, -709.7f), new(23.1f, -715.099f), new(26.5f, -719.499f), new(32, -721.699f), new(38, -721.5f), new(43, -717.999f), new(45.7f, -712.699f), new(45.9f, -706.699f), new(42.9f, -701.2f), new(38.5f, -697), new(32.5f, -695.199f)]; private int maxCasts; - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes.Take(maxCasts); + + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < (count > maxCasts ? maxCasts : count); ++i) + yield return _aoes[i]; + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { @@ -161,7 +168,8 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) for (var i = 0; i < 4; ++i) total += angles[i]; if ((int)total is 4 or -1) - GenerateAOEsForOppositePairPattern(); + for (var i = 0; i < 4; ++i) + AddAOEs(WPos.GenerateRotatedVertices(D093Lunipyati.ArenaCenter, spiralSmallPoints, angles[i] * Angle.RadToDeg)); else if ((int)(2 * total) == 3) GenerateAOEsForMixedPattern(-45, -135); else @@ -179,10 +187,10 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) } } - private void GenerateAOEsForOppositePairPattern() + public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - for (var i = 0; i < 4; ++i) - AddAOEs(WPos.GenerateRotatedVertices(D093Lunipyati.ArenaCenter, spiralSmallPoints, angles[i] * Angle.RadToDeg)); + if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.LeapingEarth) + _aoes.RemoveAt(0); } private void GenerateAOEsForMixedPattern(int intercardinalOffset, int cardinalOffset) @@ -203,17 +211,11 @@ private void AddAOEs(WPos[] points) for (var i = 0; i < 4; ++i) _aoes.Add(new(circle, points[i], default, WorldState.FutureTime(6.5f + 0.2f * i))); } - - public override void OnCastFinished(Actor caster, ActorCastInfo spell) - { - if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.LeapingEarth) - _aoes.RemoveAt(0); - } } class RockBlast(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(15); private static readonly AOEShapeCircle circle = new(5); private static readonly WPos[] clockPositions = [new(34, -697), new(48, -710), new(21, -710), new(34, -724)]; @@ -225,7 +227,9 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) if (_aoes.Count == 0 && (AID)spell.Action.ID == AID.RockBlast) { var isClockwise = DetermineClockwise(caster, spell.Rotation); - AddAOEs(caster, spell, isClockwise); + var dir = (isClockwise ? 1 : -1) * 22.5f; + for (var i = 0; i < 15; ++i) + _aoes.Add(new(circle, WPos.RotateAroundOrigin(dir * i, D093Lunipyati.ArenaCenter, caster.Position), default, Module.CastFinishAt(spell, 0.6f * i))); } } @@ -244,13 +248,6 @@ private static bool DetermineClockwise(Actor caster, Angle rotation) } return false; } - - private void AddAOEs(Actor caster, ActorCastInfo spell, bool isClockwise) - { - var dir = (isClockwise ? 1 : -1) * 22.5f; - for (var i = 0; i < 15; ++i) - _aoes.Add(new(circle, WPos.RotateAroundOrigin(dir * i, D093Lunipyati.ArenaCenter, caster.Position), default, Module.CastFinishAt(spell, 0.6f * i))); - } } class D093LunipyatiStates : StateMachineBuilder diff --git a/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs b/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs index f0cb5ee9ca..cc65eb3da8 100644 --- a/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs +++ b/BossMod/Modules/Dawntrail/Raid/M02NHoneyBLovely/Sweethearts.cs @@ -4,12 +4,15 @@ abstract class Sweethearts(BossModule module, uint oid, uint aid) : Components.G { private const int Radius = 1, Length = 3; private static readonly AOEShapeCapsule capsule = new(Radius, Length); - private readonly HashSet _hearts = []; + private readonly List _hearts = []; public override IEnumerable ActiveAOEs(int slot, Actor actor) { - foreach (var h in _hearts) + for (var i = 0; i < _hearts.Count; ++i) + { + var h = _hearts[i]; yield return new(capsule, h.Position, h.Rotation); + } } public override void OnActorPlayActionTimelineEvent(Actor actor, ushort id) diff --git a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs index a4b9d068fb..d23ad5298d 100644 --- a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs +++ b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs @@ -3,18 +3,25 @@ class OneTwoPawBoss(BossModule module) : Components.GenericAOEs(module) { private readonly PredaceousPounce? _pounce = module.FindComponent(); - private readonly List _aoes = []; + private readonly List _aoes = new(2); private static readonly HashSet casts = [AID.OneTwoPawBossAOERFirst, AID.OneTwoPawBossAOELSecond, AID.OneTwoPawBossAOELFirst, AID.OneTwoPawBossAOERSecond]; private static readonly AOEShapeCone _shape = new(100, 90.Degrees()); public override IEnumerable ActiveAOEs(int slot, Actor actor) { + var count = _aoes.Count; + if (count == 0) + yield break; var pounce = _pounce != null && _pounce.ActiveAOEs(slot, actor).Any(); - if (_aoes.Count > 0) - yield return _aoes[0] with { Color = pounce ? Colors.AOE : Colors.Danger }; - if (_aoes.Count > 1 && !pounce) - yield return _aoes[1] with { Risky = false }; + for (var i = 0; i < count; ++i) + { + var aoe = _aoes[i]; + if (i == 0) + yield return count > 1 && !pounce ? aoe with { Color = Colors.Danger } : aoe; + else if (i == 1 && !pounce) + yield return aoe with { Risky = false }; + } } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -40,11 +47,18 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) class OneTwoPawShade(BossModule module) : Components.GenericAOEs(module) { private Angle _firstDirection; - private readonly List _aoes = []; + private readonly List _aoes = new(4); private static readonly AOEShapeCone _shape = new(100, 90.Degrees()); - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes.Skip(NumCasts).Take(2); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < (count > 2 ? 2 : count); ++i) + yield return _aoes[i]; + } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { @@ -60,7 +74,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnTethered(Actor source, ActorTetherInfo tether) { - if (tether.ID == (uint)TetherID.Soulshade && _aoes.Count < 4) + if (_aoes.Count < 4 && tether.ID == (uint)TetherID.Soulshade) { _aoes.Add(new(_shape, source.Position, source.Rotation + _firstDirection, WorldState.FutureTime(20.3f))); _aoes.Add(new(_shape, source.Position, source.Rotation - _firstDirection, WorldState.FutureTime(23.3f))); @@ -71,13 +85,17 @@ public override void OnTethered(Actor source, ActorTetherInfo tether) public override void OnEventCast(Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID is AID.OneTwoPawShadeAOERFirst or AID.OneTwoPawShadeAOELSecond or AID.OneTwoPawShadeAOELFirst or AID.OneTwoPawShadeAOERSecond) + { ++NumCasts; + if (_aoes.Count != 0) + _aoes.RemoveAt(0); + } } } class LeapingOneTwoPaw(BossModule module) : Components.GenericAOEs(module) { - public readonly List AOEs = []; + public readonly List AOEs = new(2); private Angle _leapDirection; private Angle _firstDirection; private Actor? _clone; @@ -89,10 +107,17 @@ class LeapingOneTwoPaw(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (AOEs.Count > 0) - yield return AOEs[0] with { Color = Colors.Danger }; - if (AOEs.Count > 1) - yield return AOEs[1] with { Risky = false }; + var count = AOEs.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var aoe = AOEs[i]; + if (i == 0) + yield return count != 1 ? aoe with { Color = Colors.Danger } : aoe; + else if (i == 1) + yield return aoe with { Risky = false }; + } } public override void DrawArenaForeground(int pcSlot, Actor pc) diff --git a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/PredaceousPounce.cs b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/PredaceousPounce.cs index 7ebc30a9f3..563fc8cb52 100644 --- a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/PredaceousPounce.cs +++ b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/PredaceousPounce.cs @@ -2,7 +2,7 @@ class PredaceousPounce(BossModule module) : Components.GenericAOEs(module) { - public readonly List AOEs = []; + public readonly List AOEs = new(12); private bool sorted; private static readonly AOEShapeCircle circle = new(11); private static readonly HashSet chargeTelegraphs = [AID.PredaceousPounceTelegraphCharge1, AID.PredaceousPounceTelegraphCharge2, @@ -16,13 +16,12 @@ class PredaceousPounce(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { var count = AOEs.Count; - if (count > 0) + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) { - var aoeCount = Math.Clamp(count, 0, 2); - for (var i = aoeCount; i < count; ++i) - yield return AOEs[i]; - for (var i = 0; i < aoeCount; ++i) - yield return AOEs[i] with { Color = Colors.Danger }; + var aoe = AOEs[i]; + yield return i < 2 ? count > 2 ? aoe with { Color = Colors.Danger } : aoe : aoe; } } @@ -54,7 +53,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) if (castEnd.Contains((AID)spell.Action.ID)) { ++NumCasts; - if (AOEs.Count > 0) + if (AOEs.Count != 0) AOEs.RemoveAt(0); } } diff --git a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/QuadrupleCrossing.cs b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/QuadrupleCrossing.cs index 567d260dd8..6873420d38 100644 --- a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/QuadrupleCrossing.cs +++ b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/QuadrupleCrossing.cs @@ -96,19 +96,19 @@ public override void OnTethered(Actor source, ActorTetherInfo tether) class QuadrupleCrossingAOE(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(8); private bool ready; private static readonly AOEShapeCone _shape = new(100, 22.5f.Degrees()); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (ready) + if (!ready) + yield break; + var count = _aoes.Count; + for (var i = 0; i < count; ++i) { - var aoeCount = Math.Clamp(_aoes.Count, 0, 4); - for (var i = aoeCount; i < _aoes.Count; ++i) - yield return _aoes[i]; - for (var i = 0; i < aoeCount; ++i) - yield return _aoes[i] with { Color = Colors.Danger }; + var aoe = _aoes[i]; + yield return i < 4 ? count > 4 ? aoe with { Color = Colors.Danger } : aoe : aoe with { Risky = false }; } } @@ -125,7 +125,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) case AID.LeapingQuadrupleCrossingBossAOE: case AID.LeapingQuadrupleCrossingShadeAOE: ++NumCasts; - if (_aoes.Count > 0) + if (_aoes.Count != 0) _aoes.RemoveAt(0); break; } diff --git a/BossMod/Modules/Dawntrail/Trial/T02ZoraalJa/DoubleEdgedSwords.cs b/BossMod/Modules/Dawntrail/Trial/T02ZoraalJa/DoubleEdgedSwords.cs index 7657a347cd..754bbd73b4 100644 --- a/BossMod/Modules/Dawntrail/Trial/T02ZoraalJa/DoubleEdgedSwords.cs +++ b/BossMod/Modules/Dawntrail/Trial/T02ZoraalJa/DoubleEdgedSwords.cs @@ -7,10 +7,17 @@ class DoubleEdgedSwords(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_aoes.Count > 0) - yield return _aoes[0] with { Color = Colors.Danger }; - if (_aoes.Count > 1) - yield return _aoes[1] with { Risky = false }; + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var aoe = _aoes[i]; + if (i == 0) + yield return count != 1 ? aoe with { Color = Colors.Danger } : aoe; + else if (i == 1) + yield return aoe with { Risky = false }; + } } public override void OnCastStarted(Actor caster, ActorCastInfo spell) diff --git a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/Besiegement.cs b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/Besiegement.cs index 4e55d58ba9..1d8a36fd4a 100644 --- a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/Besiegement.cs +++ b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/Besiegement.cs @@ -3,7 +3,7 @@ namespace BossMod.Dawntrail.Trial.T03QueenEternal; class Besiegement(BossModule module) : Components.GenericAOEs(module) { private const int L = 60; - public readonly List AOEs = []; + public readonly List AOEs = new(4); private static readonly AOEShapeRect[] rects = [new(L, 2), new(L, 4), new(L, 5), new(L, 6), new(L, 9)]; private static readonly HashSet casts = [AID.Besiegement1, AID.Besiegement2, AID.Besiegement3, AID.Besiegement4, AID.Besiegement5]; diff --git a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/LegitimateForce.cs b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/LegitimateForce.cs index 0fbf062846..6be6e24b4b 100644 --- a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/LegitimateForce.cs +++ b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/LegitimateForce.cs @@ -2,7 +2,7 @@ namespace BossMod.Dawntrail.Trial.T03QueenEternal; class LegitimateForce(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(2); private static readonly AOEShapeRect rect = new(20, 40); private static readonly HashSet castEnds = [AID.LegitimateForceLL, AID.LegitimateForceLR, AID.LegitimateForceRR, AID.LegitimateForceRL, AID.LegitimateForceR, AID.LegitimateForceL]; @@ -14,18 +14,17 @@ class LegitimateForce(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_aoe.AOEs.Count == 0) + var count = _aoes.Count; + if (count == 0 || _aoe.AOEs.Count != 0) + yield break; + var compare = count > 1 && _aoes[0].Rotation != _aoes[1].Rotation; + for (var i = 0; i < count; ++i) { - var count = _aoes.Count; - var compare = count > 1 && _aoes[0].Rotation != _aoes[1].Rotation; - for (var i = 0; i < count; ++i) - { - var aoe = _aoes[i]; - if (i == 0) - yield return compare ? aoe with { Color = Colors.Danger } : aoe; - else if (i == 1 && compare) - yield return aoe with { Risky = false }; - } + var aoe = _aoes[i]; + if (i == 0) + yield return compare ? aoe with { Color = Colors.Danger } : aoe; + else if (i == 1 && compare) + yield return aoe with { Risky = false }; } } @@ -46,12 +45,12 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) AddAOEs(caster, spell, 90, -90); break; } - } - private void AddAOEs(Actor caster, ActorCastInfo spell, float first, float second) - { - _aoes.Add(new(rect, caster.Position, spell.Rotation + first.Degrees(), Module.CastFinishAt(spell))); - _aoes.Add(new(rect, caster.Position, spell.Rotation + second.Degrees(), Module.CastFinishAt(spell, 3.1f))); + void AddAOEs(Actor caster, ActorCastInfo spell, float first, float second) + { + _aoes.Add(new(rect, caster.Position, spell.Rotation + first.Degrees(), Module.CastFinishAt(spell))); + _aoes.Add(new(rect, caster.Position, spell.Rotation + second.Degrees(), Module.CastFinishAt(spell, 3.1f))); + } } public override void OnEventCast(Actor caster, ActorCastEvent spell) diff --git a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RoyalBanishment.cs b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RoyalBanishment.cs index 44e14dad65..82e59ff982 100644 --- a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RoyalBanishment.cs +++ b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RoyalBanishment.cs @@ -2,7 +2,7 @@ namespace BossMod.Dawntrail.Trial.T03QueenEternal; class RoyalBanishment(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(7); private static readonly AOEShapeCone cone = new(100, 15.Degrees()); public override IEnumerable ActiveAOEs(int slot, Actor actor) @@ -25,7 +25,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (_aoes.Count > 0 && (AID)spell.Action.ID == AID.RoyalBanishment) + if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.RoyalBanishment) _aoes.RemoveAt(0); } } diff --git a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RuthlessRegalia.cs b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RuthlessRegalia.cs index 88068186c2..f030bbac7b 100644 --- a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RuthlessRegalia.cs +++ b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/RuthlessRegalia.cs @@ -4,7 +4,7 @@ class RuthlessRegalia(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeRect rect = new(100, 6); private (Actor, DateTime)? _source; - private readonly List _tethered = []; + private readonly List _tethered = new(2); public override IEnumerable ActiveAOEs(int slot, Actor actor) { diff --git a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/WaltzOfTheRegalia.cs b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/WaltzOfTheRegalia.cs index 0fb5415c4b..efd0515683 100644 --- a/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/WaltzOfTheRegalia.cs +++ b/BossMod/Modules/Dawntrail/Trial/T03QueenEternal/WaltzOfTheRegalia.cs @@ -8,12 +8,18 @@ namespace BossMod.Dawntrail.Trial.T03QueenEternal; class WaltzOfTheRegaliaBait(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(MathF.Sqrt(212) * 0.5f); - private readonly List<(Actor, DateTime)> _targets = []; + private readonly List<(Actor, DateTime)> _targets = new(3); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - foreach (var t in _targets) + var count = _targets.Count; + if (count == 0) + yield break; + for (var i = 0; i < _targets.Count; ++i) + { + var t = _targets[i]; yield return new(circle, t.Item1.Position, default, t.Item2); + } } public override void OnActorPlayActionTimelineEvent(Actor actor, ushort id) diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs index 4ec38762a5..fdc862269e 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs @@ -76,6 +76,8 @@ class AetherSprayWater(BossModule module) : Components.RaidwideCast(module, Acti class AetherSprayFire(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.AetherSprayFire), "Go into a bubble! (Raidwide)"); class AetherSprayWaterKB(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.AetherSprayWater), 13) { + private static readonly Angle a60 = 60.Degrees(), a10 = 10.Degrees(); + public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || !Module.InBounds(pos); public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -83,15 +85,14 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var source = Sources(slot, actor).FirstOrDefault(); if (Module.FindComponent()!.ActiveAOEs(slot, actor).Any() && source != default) { - var forbidden = new List> + var forbidden = new List>(7) { ShapeDistance.InvertedCircle(Arena.Center, 7) }; for (var i = 0; i < 6; ++i) if (Module.Enemies(OID.QueerBubble).Where(x => x.Position.AlmostEqual(WPos.RotateAroundOrigin(i * 60, Arena.Center, x.Position), 1) && Module.FindComponent()!._aoes.Contains(x)) != null) - forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, i * 60.Degrees(), 10.Degrees())); - if (forbidden.Count > 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); + forbidden.Add(ShapeDistance.Cone(Arena.Center, 20, i * a60, a10)); + hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); } } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs index 66031b79fd..e96fde509a 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs @@ -117,13 +117,20 @@ class CosmicKissCircle(BossModule module) : Components.LocationTargetedAOEs(modu class CosmicKissRect(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(9); private static readonly AOEShapeRect rect = new(50, 5); private static readonly Angle rotation = -90.Degrees(); private const int X = 320; - private static readonly List coords = [new(X, -142), new(X, -152), new(X, -162), new(X, -172)]; + private static readonly WPos[] coords = [new(X, -142), new(X, -152), new(X, -162), new(X, -172)]; - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes.Take(3); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < (count > 3 ? 3 : count); ++i) + yield return _aoes[i]; + } public override void OnEventEnvControl(byte index, uint state) { @@ -138,7 +145,7 @@ public override void OnEventEnvControl(byte index, uint state) _ => new List() }; - if (aoeSets.Count > 0) + if (aoeSets.Count != 0) { AddAOEs(aoeSets[0], 4.3f); AddAOEs(aoeSets[1], 9.5f); @@ -147,15 +154,15 @@ public override void OnEventEnvControl(byte index, uint state) } } - private void AddAOEs(IEnumerable indices, float delay) + private void AddAOEs(int[] indices, float delay) { - foreach (var index in indices) - _aoes.Add(new(rect, coords[index], rotation, WorldState.FutureTime(delay))); + for (var i = 0; i < 3; ++i) + _aoes.Add(new(rect, coords[indices[i]], rotation, WorldState.FutureTime(delay))); } public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.CosmicKissRect) + if (_aoes.Count != 0 && (AID)spell.Action.ID == AID.CosmicKissRect) _aoes.RemoveAt(0); } } @@ -164,20 +171,17 @@ class CosmicKissRaidwide(BossModule module) : Components.RaidwideCast(module, Ac class CosmicKissKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.CosmicKiss), 13) { - private static readonly Angle a90 = 90.Degrees(); - private static readonly Angle a45 = 45.Degrees(); - private static readonly Angle a0 = 0.Degrees(); - private static readonly Angle a180 = 180.Degrees(); + private static readonly Angle a90 = 90.Degrees(), a45 = 45.Degrees(), a0 = 0.Degrees(), a180 = 180.Degrees(); public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || !Module.InBounds(pos); public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var forbidden = new List>(); var component = Module.FindComponent()?.ActiveAOEs(slot, actor)?.ToList(); var source = Sources(slot, actor).FirstOrDefault(); if (component != null && component.Count != 0 && source != default) { + var forbidden = new List>(2); if (component!.Any(x => x.Origin.Z == -152) && component!.Any(x => x.Origin.Z == -162)) { forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a0, a45)); @@ -192,7 +196,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a180, a90)); else if (component!.Any(x => x.Origin.Z == -162) && component!.Any(x => x.Origin.Z == -172)) forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a0, a90)); - if (forbidden.Count > 0) + if (forbidden.Count != 0) hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), source.Activation); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D04KtisisHyperboreia/D043Hermes.cs b/BossMod/Modules/Endwalker/Dungeon/D04KtisisHyperboreia/D043Hermes.cs index 606192ffd3..162206296c 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D04KtisisHyperboreia/D043Hermes.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D04KtisisHyperboreia/D043Hermes.cs @@ -32,9 +32,9 @@ public enum AID : uint TrueAeroII2 = 25897, // Helper->player, 5.0s cast, range 6 circle TrueAeroII3 = 25898, // Helper->location, 3.5s cast, range 6 circle - TrueAeroIV1 = 25889, // 348B->self, 4.0s cast, range 50 width 10 rect - TrueAeroIVLOS = 27836, // 348B->self, 4.0s cast, range 50 width 10 rect - TrueAeroIV3 = 27837, // 348B->self, 10.0s cast, range 50 width 10 rect + TrueAeroIV1 = 25889, // Karukeion->self, 4.0s cast, range 50 width 10 rect + TrueAeroIVLOS = 27836, // Karukeion->self, 4.0s cast, range 50 width 10 rect + TrueAeroIV3 = 27837, // Karukeion->self, 10.0s cast, range 50 width 10 rect TrueBravery = 25907, // Boss->self, 5.0s cast, single-target diff --git a/BossMod/Modules/Endwalker/Ultimate/TOP/P5Omega.cs b/BossMod/Modules/Endwalker/Ultimate/TOP/P5Omega.cs index 27dabde5e0..69af85d2fe 100644 --- a/BossMod/Modules/Endwalker/Ultimate/TOP/P5Omega.cs +++ b/BossMod/Modules/Endwalker/Ultimate/TOP/P5Omega.cs @@ -201,9 +201,8 @@ class P5OmegaBlaster : Components.BaitAwayTethers { private readonly P5OmegaNearDistantWorld? _ndw; - public P5OmegaBlaster(BossModule module) : base(module, new AOEShapeCircle(15), (uint)TetherID.Blaster, ActionID.MakeSpell(AID.OmegaBlasterAOE)) + public P5OmegaBlaster(BossModule module) : base(module, new AOEShapeCircle(15), (uint)TetherID.Blaster, ActionID.MakeSpell(AID.OmegaBlasterAOE), centerAtTarget: true) { - CenterAtTarget = true; ForbiddenPlayers = new(0xFF); _ndw = module.FindComponent(); } diff --git a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD160Todesritter.cs b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD160Todesritter.cs index 0fd9cd9a04..9c2d7d0e98 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD160Todesritter.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD160Todesritter.cs @@ -21,7 +21,7 @@ public enum AID : uint class HallOfSorrow(BossModule module) : Components.PersistentVoidzone(module, 9, m => m.Enemies(OID.Voidzone).Where(z => z.EventState != 7)); class Infatuation(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Infatuation), new AOEShapeCircle(7)); class Valfodr(BossModule module) : Components.BaitAwayChargeCast(module, ActionID.MakeSpell(AID.Valfodr), 3); -class ValfodrKB(BossModule module) : Components.Knockback(module) // note actual knockback is delayed by upto 1.2s in replay +class ValfodrKB(BossModule module) : Components.Knockback(module, stopAtWall: true) // note actual knockback is delayed by upto 1.2s in replay { private DateTime _activation; @@ -34,10 +34,7 @@ public override IEnumerable Sources(int slot, Actor actor) public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.Valfodr) - { _activation = Module.CastFinishAt(spell); - StopAtWall = true; - } } public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false); diff --git a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD60TheBlackRider.cs b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD60TheBlackRider.cs index 6781e22b3f..edb86ddd4a 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD60TheBlackRider.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/DD60TheBlackRider.cs @@ -84,7 +84,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) class HallOfSorrow(BossModule module) : Components.PersistentVoidzone(module, 9, m => m.Enemies(OID.Voidzone).Where(z => z.EventState != 7)); class Valfodr(BossModule module) : Components.BaitAwayChargeCast(module, ActionID.MakeSpell(AID.Valfodr), 3); -class ValfodrKB(BossModule module) : Components.Knockback(module) +class ValfodrKB(BossModule module) : Components.Knockback(module, stopAtWall: true) { private DateTime _activation; @@ -97,10 +97,7 @@ public override IEnumerable Sources(int slot, Actor actor) public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.Valfodr) - { _activation = Module.CastFinishAt(spell); - StopAtWall = true; - } } public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) => (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false) || (Module.FindComponent()?.ActiveAOEs(slot, actor).Any(z => z.Shape.Check(pos, z.Origin, z.Rotation)) ?? false); diff --git a/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D021Raskovnik.cs b/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D021Raskovnik.cs index 4d740526a0..8661bd9d3e 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D021Raskovnik.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D021Raskovnik.cs @@ -81,12 +81,12 @@ protected override void DrawEnemies(int pcSlot, Actor pc) protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.DravanianHornet => 2, - OID.Boss => 1, + OID.DravanianHornet => 1, _ => 0 }; } diff --git a/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D022Myath.cs b/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D022Myath.cs index f8de3f1f28..32acf2d9b5 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D022Myath.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D022Myath.cs @@ -94,12 +94,12 @@ protected override void DrawEnemies(int pcSlot, Actor pc) protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.ChymeOfTheMountain => 2, - OID.Boss => 1, + OID.ChymeOfTheMountain => 1, _ => 0 }; } diff --git a/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D023Tioman.cs b/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D023Tioman.cs index 341d9ee708..1ac6d896fa 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D023Tioman.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D02SohmAl/D023Tioman.cs @@ -22,13 +22,13 @@ public enum AID : uint HeavensfallVisual = 3815, // Boss->self, no cast, single-target Heavensfall1 = 3817, // Helper->player, no cast, range 5 circle Heavensfall2 = 3818, // Helper->location, 3.0s cast, range 5 circle - DarkStar = 3812, // Boss->self, 5.0s cast, range 50+R circle + DarkStar = 3812 // Boss->self, 5.0s cast, range 50+R circle } public enum IconID : uint { Comet = 10, // player - Meteor = 7, // player + Meteor = 7 // player } class HeavensfallBait(BossModule module) : Components.BaitAwayIcon(module, new AOEShapeCircle(5), (uint)IconID.Comet, ActionID.MakeSpell(AID.Heavensfall1), 3.1f, true) @@ -132,12 +132,12 @@ protected override void DrawEnemies(int pcSlot, Actor pc) protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.RightWingOfInjury or OID.LeftWingOfTragedy => 2, - OID.Boss => 1, + OID.RightWingOfInjury or OID.LeftWingOfTragedy => 1, _ => 0 }; } diff --git a/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs b/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs index 45b8004ebb..9f1f05e960 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D03Aery/D031Rangda.cs @@ -24,7 +24,7 @@ public enum AID : uint public enum TetherID : uint { - Lightning = 6, // Boss->player/BlackenedStatue + Lightning = 6 // Boss->player/BlackenedStatue } class ElectricPredation(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.ElectricPredation), new AOEShapeCone(12.9f, 60.Degrees())); @@ -45,12 +45,15 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) { ++NumCasts; if (NumCasts == 3 || NumCasts == CurrentBaits.Count) // hits upto 3 random players + { CurrentBaits.Clear(); + NumCasts = 0; + } } } } -class IonosphericCharge(BossModule module) : Components.BaitAwayTethers(module, new AOEShapeCircle(0), (uint)TetherID.Lightning, activationDelay: 10.1f) +class IonosphericCharge(BossModule module) : Components.BaitAwayTethers(module, new AOEShapeCircle(default), (uint)TetherID.Lightning, activationDelay: 10.1f) { public override void AddHints(int slot, Actor actor, TextHints hints) { @@ -124,12 +127,12 @@ protected override void DrawEnemies(int pcSlot, Actor pc) protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.Leyak => 2, - OID.Boss => 1, + OID.Leyak => 1, _ => 0 }; } diff --git a/BossMod/Modules/Heavensward/Dungeon/D03Aery/D033Nidhogg.cs b/BossMod/Modules/Heavensward/Dungeon/D03Aery/D033Nidhogg.cs index fa86d9beb6..f6c5909b0b 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D03Aery/D033Nidhogg.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D03Aery/D033Nidhogg.cs @@ -141,12 +141,13 @@ protected override void DrawEnemies(int pcSlot, Actor pc) protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { OID.TheSablePrice => 2, - OID.Boss or OID.Ahleh or OID.Liegedrake => 1, + OID.Ahleh or OID.Liegedrake => 1, _ => 0 }; } diff --git a/BossMod/Modules/Heavensward/Dungeon/D04TheVault/D041SerAdelphel.cs b/BossMod/Modules/Heavensward/Dungeon/D04TheVault/D041SerAdelphel.cs index 7639c4c95d..8b3c72577e 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D04TheVault/D041SerAdelphel.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D04TheVault/D041SerAdelphel.cs @@ -76,10 +76,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) } } -class Execution : Components.BaitAwayIcon -{ - public Execution(BossModule module) : base(module, new AOEShapeCircle(5), (uint)IconID.Spreadmarker, ActionID.MakeSpell(AID.Execution), 4.8f) { CenterAtTarget = true; } -} +class Execution(BossModule module) : Components.BaitAwayIcon(module, new AOEShapeCircle(5), (uint)IconID.Spreadmarker, ActionID.MakeSpell(AID.Execution), 4.8f, true); class ShiningBlade(BossModule module) : Components.GenericAOEs(module) { diff --git a/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs b/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs index 410ae24d33..826d5315a6 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D15Xelphatol/D152DotoliCiloc.cs @@ -116,7 +116,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) class OnHighHint(BossModule module) : Components.GenericAOEs(module) { - private readonly List cones = []; + private readonly List cones = new(4); private AOEInstance? _aoe; private const string RiskHint = "Use safewalls for knockback!"; private static readonly Angle angle = 11.25f.Degrees(); @@ -146,7 +146,7 @@ private void GenerateHints() public override void OnActorCreated(Actor actor) { - if (cones.Count > 0 && (OID)actor.OID == OID.Whirlwind) // sometimes the creation of whirlwinds is delayed + if (cones.Count != 0 && (OID)actor.OID == OID.Whirlwind) // sometimes the creation of whirlwinds is delayed { cones.Clear(); GenerateHints(); @@ -164,10 +164,11 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) public override void AddHints(int slot, Actor actor, TextHints hints) { - var activeAOEs = ActiveAOEs(slot, actor).ToList(); - if (activeAOEs.Any(c => !c.Check(actor.Position))) + if (_aoe == null) + return; + if (ActiveAOEs(slot, actor).Any(c => !c.Check(actor.Position))) hints.Add(RiskHint); - else if (activeAOEs.Any(c => c.Check(actor.Position))) + else hints.Add(RiskHint, false); } } diff --git a/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D292MagitekGunship.cs b/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D292MagitekGunship.cs index af18c763a6..146e73c761 100644 --- a/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D292MagitekGunship.cs +++ b/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D292MagitekGunship.cs @@ -88,20 +88,22 @@ public class D292MagitekGunship(WorldState ws, Actor primary) : BossModule(ws, p new(-2.84f, -165.86f), new(1.22f, -167.76f), new(15.79f, -167.88f)]; private static readonly ArenaBoundsComplex arena = new([new PolygonCustom(vertices)]); + private static readonly uint[] trash = [(uint)OID.SixthCohortEques, (uint)OID.SixthCohortLaquearius, (uint)OID.SixthCohortSecutor, (uint)OID.SixthCohortSignifer, (uint)OID.SixthCohortVanguard]; + protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.SixthCohortEques).Concat([PrimaryActor]).Concat(Enemies(OID.SixthCohortLaquearius)).Concat(Enemies(OID.SixthCohortSecutor)) - .Concat(Enemies(OID.SixthCohortSignifer)).Concat(Enemies(OID.SixthCohortVanguard))); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(trash)); } protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.SixthCohortLaquearius or OID.SixthCohortEques or OID.SixthCohortVanguard or OID.SixthCohortSignifer or OID.SixthCohortSecutor => 2, - OID.Boss => 1, + OID.SixthCohortLaquearius or OID.SixthCohortEques or OID.SixthCohortVanguard or OID.SixthCohortSignifer or OID.SixthCohortSecutor => 1, _ => 0 }; } diff --git a/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D293Midgardsormr.cs b/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D293Midgardsormr.cs index eb68cc2838..2ee38e0b43 100644 --- a/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D293Midgardsormr.cs +++ b/BossMod/Modules/RealmReborn/Dungeon/D29KeeperOfTheLake/D293Midgardsormr.cs @@ -49,7 +49,7 @@ class Disgust(BossModule module) : Components.RaidwideCastDelay(module, ActionID class MirageAdmonishment(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = []; + private readonly List _aoes = new(2); private static readonly AOEShapeRect rect = new(40, 6); public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; @@ -104,8 +104,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID is AID.AkhMornFirst or AID.AkhMornRest) { - ++numCasts; - if (numCasts == 4) + if (++numCasts == 4) { Stacks.Clear(); numCasts = 0; @@ -137,9 +136,11 @@ public D293MidgardsormrStates(BossModule module) : base(module) public class D293Midgardsormr(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) { private static readonly ArenaBoundsComplex arena = new([new Circle(new(-40.8f, -78.2f), 18.8f)], [new Rectangle(new(-40.787f, -59.416f), 20, 1.25f)]); + private static readonly uint[] dragons = [(uint)OID.MirageDragon3, (uint)OID.MirageDragon4]; protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.MirageDragon3).Concat(Enemies(OID.MirageDragon4)).Concat([PrimaryActor])); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(dragons)); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs index c15d6efa1d..b2a9aa25ab 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D01Holminster/D013Philia.cs @@ -91,13 +91,16 @@ public override void AddGlobalHints(GlobalHints hints) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { if (chained && actor != chaintarget) - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) + { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { OID.IronChain => 1, OID.Boss => -1, _ => 0 }; + } var ironchain = Module.Enemies(OID.IronChain).FirstOrDefault(); if (ironchain != null && !ironchain.IsDead) hints.AddForbiddenZone(ShapeDistance.InvertedCircle(ironchain.Position, ironchain.HitboxRadius + 3)); @@ -127,7 +130,7 @@ class Aethersup(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { if (_aoe != default) - yield return _aoe with { Risky = Module.Enemies(OID.IronChain).All(x => x.IsDead) }; + yield return _aoe with { Risky = Module.Enemies(OID.IronChain).Any(x => x.IsDead) }; } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -191,7 +194,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.CatONineTails && Sequences.Count > 0) + if ((AID)spell.Action.ID == AID.CatONineTails) AdvanceSequence(0, WorldState.CurrentTime); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs b/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs index 614eb6a44f..3fd228a140 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs @@ -37,10 +37,10 @@ class Geyser(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(6); - private static readonly Dictionary>> GeyserPositions = new() + private static readonly Dictionary> GeyserPositions = new() { { - OID.GeyserHelper1, new Dictionary> + OID.GeyserHelper1, new Dictionary { { 0.Degrees(), [new(0, 14.16f), new(-9, 45.16f)] }, { 180.Degrees(), [new(9, 15.16f), new(0, 46.16f)] }, @@ -49,7 +49,7 @@ class Geyser(BossModule module) : Components.GenericAOEs(module) } }, { - OID.GeyserHelper2, new Dictionary> + OID.GeyserHelper2, new Dictionary { { 0.Degrees(), [new(0, 35.16f), new(-9, 15.16f), new(7, 23.16f)] }, { 90.Degrees(), [new(-15, 39.16f), new(-7, 23.16f), new(5, 30.16f)] }, @@ -63,8 +63,14 @@ class Geyser(BossModule module) : Components.GenericAOEs(module) public override IEnumerable ActiveAOEs(int slot, Actor actor) { - foreach (var g in _geysers) + var count = _geysers.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var g = _geysers[i]; yield return new(g.Shape, g.Origin, default, g.Activation, g.Activation == _geysers[0].Activation ? Colors.Danger : Colors.AOE, g.Activation == _geysers[0].Activation); + } } public override void OnActorEAnim(Actor actor, uint state) @@ -77,8 +83,8 @@ public override void OnActorEAnim(Actor actor, uint state) foreach (var (rotation, positions) in positionsByRotation) if (actor.Rotation.AlmostEqual(rotation, Angle.DegToRad)) { - foreach (var pos in positions) - _geysers.Add(new(circle, pos, default, activation)); + for (var i = 0; i < positions.Length; ++i) + _geysers.Add(new(circle, positions[i], default, activation)); break; } } @@ -87,7 +93,7 @@ public override void OnActorEAnim(Actor actor, uint state) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.Geyser && _geysers.Count > 0) + if (_geysers.Count != 0 && (AID)spell.Action.ID == AID.Geyser) _geysers.RemoveAt(0); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D030RonkanDreamer.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D030RonkanDreamer.cs index d80e09d67d..3b44f7a984 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D030RonkanDreamer.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D030RonkanDreamer.cs @@ -12,15 +12,17 @@ public enum OID : uint public enum AID : uint { - WrathOfTheRonka = 17223, // 2A40->self, 6.0s cast, single-target - WrathOfTheRonkaLong = 15918, // 28E8->self, no cast, range 35 width 8 rect - WrathOfTheRonkaShort = 15916, // 28E8->self, no cast, range 12 width 8 rect - WrathOfTheRonkaMedium = 15917, // 28E8->self, no cast, range 22 width 8 rect - RonkanFire = 17433, // 2A40->player, 1.0s cast, single-target - RonkanAbyss = 17387, // 2A40->location, 3.0s cast, range 6 circle - AutoAttack = 872, // 28DD/28DC->player, no cast, single-target - AutoAttack2 = 17949, // 28E3->player, no cast, single-target - BurningBeam = 15923 // 28E3->self, 3.0s cast, range 15 width 4 rect + AutoAttack1 = 872, // RonkanVessel/RonkanIdol->player, no cast, single-target + AutoAttack2 = 17949, // RonkanThorn->player, no cast, single-target + + WrathOfTheRonka = 17223, // Boss->self, 6.0s cast, single-target + WrathOfTheRonkaLong = 15918, // Helper->self, no cast, range 35 width 8 rect + WrathOfTheRonkaShort = 15916, // Helper->self, no cast, range 12 width 8 rect + WrathOfTheRonkaMedium = 15917, // Helper->self, no cast, range 22 width 8 rect + RonkanFire = 17433, // Boss->player, 1.0s cast, single-target + RonkanAbyss = 17387, // Boss->location, 3.0s cast, range 6 circle + + BurningBeam = 15923 // RonkanThorn->self, 3.0s cast, range 15 width 4 rect } public enum TetherID : uint @@ -34,9 +36,8 @@ class RonkanAbyss(BossModule module) : Components.LocationTargetedAOEs(module, A class WrathOfTheRonka(BossModule module) : Components.GenericAOEs(module) { private readonly List _aoes = []; - private static readonly AOEShapeRect rectShort = new(12, 4); - private static readonly AOEShapeRect rectMedium = new(22, 4); - private static readonly AOEShapeRect rectLong = new(35, 4); + private static readonly AOEShapeRect rectShort = new(12, 4), rectMedium = new(22, 4), rectLong = new(35, 4); + private static readonly (WPos Position, AOEShapeRect Shape)[] aoeMap = [(new(-17, 627), rectMedium), (new(17, 642), rectMedium), (new(-17, 436), rectMedium), (new(17, 421), rectMedium), diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs index 10fe5d722a..5b3a517c3d 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D031Lozatl.cs @@ -8,16 +8,17 @@ public enum OID : uint public enum AID : uint { - AutoAttack = 872, // 27AF->player, no cast, single-target - Stonefist = 15497, // 27AF->player, 4.0s cast, single-target - SunToss = 15498, // 27AF->location, 3.0s cast, range 5 circle - LozatlsScorn = 15499, // 27AF->self, 3.0s cast, range 40 circle - RonkanLightRight = 15500, // 233C->self, no cast, range 60 width 20 rect - RonkanLightLeft = 15725, // 233C->self, no cast, range 60 width 20 rect - HeatUp = 15502, // 27AF->self, 3.0s cast, single-target - HeatUp2 = 15501, // 27AF->self, 3.0s cast, single-target - LozatlsFuryA = 15504, // 27AF->self, 4.0s cast, range 60 width 20 rect - LozatlsFuryB = 15503 // 27AF->self, 4.0s cast, range 60 width 20 rect + AutoAttack = 872, // Boss->player, no cast, single-target + + Stonefist = 15497, // Boss->player, 4.0s cast, single-target + SunToss = 15498, // Boss->location, 3.0s cast, range 5 circle + LozatlsScorn = 15499, // Boss->self, 3.0s cast, range 40 circle + RonkanLightRight = 15500, // Helper->self, no cast, range 60 width 20 rect + RonkanLightLeft = 15725, // Helper->self, no cast, range 60 width 20 rect + HeatUp = 15502, // Boss->self, 3.0s cast, single-target + HeatUp2 = 15501, // Boss->self, 3.0s cast, single-target + LozatlsFuryA = 15504, // Boss->self, 4.0s cast, range 60 width 20 rect + LozatlsFuryB = 15503 // Boss->self, 4.0s cast, range 60 width 20 rect } class LozatlsFuryA(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.LozatlsFuryA), new AOEShapeRect(60, 20, 0, 90.Degrees())); // TODO: verify; there should not be an offset in reality here..., also double halfwidth is strange diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs index 066e6b4840..bd0933f908 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D03QitanaRavel/D033Eros.cs @@ -78,14 +78,14 @@ class Inhale(BossModule module) : Components.KnockbackFromCastTarget(module, Act public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var forbidden = new List>(); - var component = _aoe.ActiveAOEs(slot, actor).ToList(); var source = Sources(slot, actor).FirstOrDefault(); if (source != default) { + var component = _aoe.ActiveAOEs(slot, actor); + var forbidden = new List>(component.Count()); foreach (var c in component) forbidden.Add(ShapeDistance.Rect(c.Origin, Module.PrimaryActor.Rotation, 40, 0, 6)); - if (forbidden.Count > 0) + if (forbidden.Count != 0) hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); } } @@ -99,14 +99,14 @@ class HeavingBreath(BossModule module) : Components.KnockbackFromCastTarget(modu public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var forbidden = new List>(); - var component = _aoe.ActiveAOEs(slot, actor).ToList(); var source = Sources(slot, actor).FirstOrDefault(); if (source != default) { + var component = _aoe.ActiveAOEs(slot, actor); + var forbidden = new List>(component.Count()); foreach (var c in component) forbidden.Add(ShapeDistance.Rect(c.Origin, new Angle(), 40, 40, 6)); - if (forbidden.Count > 0) + if (forbidden.Count != 0) hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), source.Activation); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D042AmphibiousTalos.cs b/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D042AmphibiousTalos.cs index 6d9ec29238..ad25b51d71 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D042AmphibiousTalos.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D042AmphibiousTalos.cs @@ -61,7 +61,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if (Sequences.Count > 0 && (AID)spell.Action.ID is AID.SwiftSpillFirst or AID.SwiftSpillRest) + if ((AID)spell.Action.ID is AID.SwiftSpillFirst or AID.SwiftSpillRest) AdvanceSequence(0, WorldState.CurrentTime); } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D043Storge.cs b/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D043Storge.cs index 709673649e..0f3c75b1a9 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D043Storge.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D04MalikahsWell/D043Storge.cs @@ -4,16 +4,18 @@ public enum OID : uint { Boss = 0x267B, // R=5.0 RhapsodicNail = 0x267C, // R=1.5 + Helper2 = 0x2BD6, // R2.0 Helper = 0x233C } public enum AID : uint { AutoAttack = 870, // Boss->player, no cast, single-target + IntestinalCrank = 15601, // Boss->self, 4.0s cast, range 60 circle DeformationVisual1 = 15528, // Boss->self, no cast, single-target DeformationVisual2 = 16808, // Boss->self, no cast, single-target - BreakingWheelWait = 17914, // 2BD6->self, 7.5s cast, single-target, before long Breaking Wheel + BreakingWheelWait = 17914, // Helper2->self, 7.5s cast, single-target, before long Breaking Wheel BreakingWheel1 = 15605, // Boss->self, 5.0s cast, range 5-60 donut BreakingWheel2 = 15610, // RhapsodicNail->self, 9.0s cast, range 5-60 donut BreakingWheel3 = 15887, // Boss->self, 29.0s cast, range 5-60 donut @@ -21,7 +23,7 @@ public enum AID : uint CrystalNail = 15607, // RhapsodicNail->self, 2.5s cast, range 5 circle Censure1 = 15927, // Boss->self, 3.0s cast, range 60 circle, activates nails for Breaking Wheel Censure2 = 15608, // Boss->self, 3.0s cast, range 60 circle, activates nails for Heretics Fork - HereticForkWait = 17913, // 2BD6->self, 6.5s cast, single-target, before long Heretics Fork + HereticForkWait = 17913, // Helper2->self, 6.5s cast, single-target, before long Heretics Fork HereticsFork1 = 15602, // Boss->self, 5.0s cast, range 60 width 10 cross HereticsFork2 = 15609, // RhapsodicNail->self, 8.0s cast, range 60 width 10 cross HereticsFork3 = 15886 // Boss->self, 23.0s cast, range 60 width 10 cross @@ -36,14 +38,16 @@ class HereticsForkBreakingWheelStreak(BossModule module) : Components.GenericAOE { private static readonly AOEShapeDonut donut = new(5, 60); private static readonly AOEShapeCross cross = new(60, 5); - private readonly List _spell1 = [], _spell2 = []; + private readonly List _aoes = new(4); + private AOEInstance? _aoe; public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_spell1.Count > 0) - yield return _spell1[0]; - if (_spell1.Count == 0 && _spell2.Count > 0) - yield return _spell2[0]; + var count = _aoes.Count; + if (count != 0) + yield return _aoes[0]; + if (count == 0 && _aoe != null) + yield return _aoe.Value; } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -51,16 +55,16 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) switch ((AID)spell.Action.ID) { case AID.HereticsFork2: - _spell1.Add(new(cross, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoes.Add(new(cross, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); break; case AID.BreakingWheel2: - _spell1.Add(new(donut, caster.Position, default, Module.CastFinishAt(spell))); + _aoes.Add(new(donut, caster.Position, default, Module.CastFinishAt(spell))); break; case AID.HereticsFork3: - _spell2.Add(new(cross, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoe = new(cross, caster.Position, spell.Rotation, Module.CastFinishAt(spell)); break; case AID.BreakingWheel3: - _spell2.Add(new(donut, caster.Position, default, Module.CastFinishAt(spell))); + _aoe = new(donut, caster.Position, default, Module.CastFinishAt(spell)); break; } } @@ -71,11 +75,12 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) { case AID.HereticsFork2: case AID.BreakingWheel2: - _spell1.RemoveAt(0); + if (_aoes.Count != 0) + _aoes.RemoveAt(0); break; case AID.HereticsFork3: case AID.BreakingWheel3: - _spell2.RemoveAt(0); + _aoe = null; break; } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs index 1298faed95..f8d944a836 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D053ForgivenWhimsy.cs @@ -63,17 +63,9 @@ public override void OnActorCreated(Actor actor) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if (Towers.Count > 0 && (AID)spell.Action.ID is AID.Judged or AID.FoundWanting) + if (Towers.Count != 0 && (AID)spell.Action.ID is AID.Judged or AID.FoundWanting) Towers.RemoveAt(0); } - - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - if (Towers.Count > 0) - base.AddAIHints(slot, actor, assignment, hints); - if (Towers.Count > 1) - hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Sprint), actor, ActionQueue.Priority.High); - } } class Exegesis(BossModule module) : Components.GenericAOEs(module) @@ -91,8 +83,8 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) switch ((AID)spell.Action.ID) { case AID.ExegesisA: //diagonal - foreach (var p in diagonalPositions) - _aoes.Add(new(rect, p, default, _activation)); + for (var i = 0; i < diagonalPositions.Length; ++i) + _aoes.Add(new(rect, diagonalPositions[i], default, _activation)); break; case AID.ExegesisB: //east+west _aoes.Add(new(rect, new(-250, -50), default, _activation)); diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs index 20090e4a76..5fd93a90e3 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D05MtGulg/D055ForgivenObscenity.cs @@ -62,15 +62,18 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (_orbs.Count == 0) + var count = _orbs.Count; + if (count == 0) return; - var forbidden = new List>(); - foreach (var o in _orbs) + var forbidden = new List>(count); + for (var i = 0; i < count; ++i) { + var o = _orbs[i]; var position = o.Position; - var inRect = Module.Enemies(OID.Rings).FirstOrDefault(x => x.Position.InRect(position, 20 * o.Rotation.ToDirection(), Radius)); + var dir = o.Rotation.ToDirection(); + var inRect = Module.Enemies(OID.Rings).FirstOrDefault(x => x.Position.InRect(position, 20 * dir, Radius)); if (inRect != null) - forbidden.Add(ShapeDistance.Capsule(o.Position, o.Rotation.ToDirection(), (position - inRect.Position).Length(), Radius)); + forbidden.Add(ShapeDistance.Capsule(o.Position, dir, (position - inRect.Position).Length(), Radius)); else forbidden.Add(ShapeDistance.Circle(o.Position, Radius)); } @@ -109,33 +112,36 @@ private bool AreCastersInPositions(WPos[] positions) public override IEnumerable ActiveAOEs(int slot, Actor actor) { + var count = _casters.Count; + if (count == 0) + yield break; if (AreCastersInPositions(positionsSet1) || AreCastersInPositions(positionsSet2)) { - if (_casters.Count > 2) + if (count > 2) { if (NumCasts == 0) yield return new(rect, _casters[0].Position, default, _activation.AddSeconds(7.1f), Colors.Danger); if (NumCasts is 0 or 1) yield return new(rect, _casters[1].Position, default, _activation.AddSeconds(7.6f), Colors.Danger); } - if (_casters.Count > 4 && NumCasts is 0 or 1) + if (count > 4 && NumCasts is 0 or 1) { yield return new(rect, _casters[2].Position, default, _activation.AddSeconds(8.1f), Risky: false); yield return new(rect, _casters[3].Position, default, _activation.AddSeconds(8.6f), Risky: false); } - if (_casters.Count > 4) + if (count > 4) { if (NumCasts == 2) yield return new(rect, _casters[2].Position, default, _activation.AddSeconds(8.1f), Colors.Danger); if (NumCasts is 2 or 3) yield return new(rect, _casters[3].Position, default, _activation.AddSeconds(8.6f), Colors.Danger); } - if (_casters.Count == 6 && NumCasts is 2 or 3) + if (count == 6 && NumCasts is 2 or 3) { yield return new(rect, _casters[4].Position, default, _activation.AddSeconds(9.1f), Risky: false); yield return new(rect, _casters[5].Position, default, _activation.AddSeconds(11.1f), Risky: false); } - if (_casters.Count == 6) + if (count == 6) { if (NumCasts == 4) yield return new(rect, _casters[4].Position, default, _activation.AddSeconds(9.1f), Colors.Danger); @@ -145,31 +151,31 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) } else if (AreCastersInPositions(positionsSet3) || AreCastersInPositions(positionsSet4)) { - if (_casters.Count > 2) + if (count > 2) { if (NumCasts == 0) yield return new(rect, _casters[0].Position, default, _activation.AddSeconds(7.1f), Colors.Danger); if (NumCasts is 0 or 1) yield return new(rect, _casters[1].Position, default, _activation.AddSeconds(7.1f), Colors.Danger); } - if (_casters.Count > 4 && NumCasts is 0 or 1) + if (count > 4 && NumCasts is 0 or 1) { yield return new(rect, _casters[2].Position, default, _activation.AddSeconds(8.1f), Risky: false); yield return new(rect, _casters[3].Position, default, _activation.AddSeconds(8.1f), Risky: false); } - if (_casters.Count > 4) + if (count > 4) { if (NumCasts == 2) yield return new(rect, _casters[2].Position, default, _activation.AddSeconds(8.1f), Colors.Danger); if (NumCasts is 2 or 3) yield return new(rect, _casters[3].Position, default, _activation.AddSeconds(8.1f), Colors.Danger); } - if (_casters.Count == 6 && NumCasts is 2 or 3) + if (count == 6 && NumCasts is 2 or 3) { yield return new(rect, _casters[4].Position, default, _activation.AddSeconds(11.1f), Risky: false); yield return new(rect, _casters[5].Position, default, _activation.AddSeconds(11.1f), Risky: false); } - if (_casters.Count == 6) + if (count == 6) { if (NumCasts == 4) yield return new(rect, _casters[4].Position, default, _activation.AddSeconds(11.1f), Colors.Danger); @@ -189,8 +195,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) { if ((AID)spell.Action.ID == AID.Ringsmith) _activation = WorldState.CurrentTime; - - if ((AID)spell.Action.ID == AID.VenaAmoris) + else if ((AID)spell.Action.ID == AID.VenaAmoris) { if (++NumCasts == 6) { diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D062Bellwether.cs b/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D062Bellwether.cs index d999ff3d1b..53c1f3f445 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D062Bellwether.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D062Bellwether.cs @@ -53,10 +53,11 @@ public D062BellwetherStates(BossModule module) : base(module) public class D062Bellwether(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) { private static readonly ArenaBoundsComplex arena = new([new Circle(new(60, -361), 19.5f)], [new Rectangle(new(60, -341), 20, 1)]); - + private static readonly uint[] trash = [(uint)OID.TerminusRoiler, (uint)OID.TerminusShriver, (uint)OID.TerminusFlesher, (uint)OID.TerminusDetonator, + (uint)OID.TerminusBeholder, (uint)OID.TerminusCrier, (uint)OID.TerminusSprinter]; protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.TerminusRoiler).Concat([PrimaryActor]).Concat(Enemies(OID.TerminusShriver)).Concat(Enemies(OID.TerminusFlesher)) - .Concat(Enemies(OID.TerminusDetonator)).Concat(Enemies(OID.TerminusBeholder)).Concat(Enemies(OID.TerminusCrier)).Concat(Enemies(OID.TerminusSprinter))); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(trash)); } } \ No newline at end of file diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D063Therion.cs b/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D063Therion.cs index 036420407e..6c798ee59d 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D063Therion.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D06Amaurot/D063Therion.cs @@ -48,15 +48,21 @@ class Border(BossModule module) : Components.GenericAOEs(module, warningText: "P new(positions[3], SquareHalfWidth), new(positions[4], SquareHalfWidth), new(positions[5], SquareHalfWidth), new(positions[6], SquareHalfWidth), new(positions[7], SquareHalfWidth), new(positions[8], RectangleHalfWidth), new(positions[9], RectangleHalfWidth)]; - private static readonly Rectangle[] rect = [new(new WPos(0, -45), 10, 30)]; + private static readonly Rectangle[] rect = [new(new(0, -45), 10, 30)]; public readonly List unionRefresh = [.. rect.Concat(shapes.Take(8))]; private readonly List difference = []; public static readonly ArenaBoundsComplex DefaultArena = new([.. rect, .. shapes.Take(8)], Offset: PathfindingOffset); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - foreach (var p in BreakingPlatforms) + var count = BreakingPlatforms.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var p = BreakingPlatforms[i]; yield return new(_square, p.Origin, Color: Colors.FutureVulnerable, Risky: Module.FindComponent()!.NumCasts == 0); + } } public override void OnActorEAnim(Actor actor, uint state) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs b/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs index 0a4302c342..60cdb84ab6 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D07Twinning/D071AlphaZaghnal.cs @@ -50,12 +50,14 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (IsSpreadTarget(actor)) { var spread = Spreads.FirstOrDefault(); - var forbidden = new List>(); - var adjustedRadius = spread.Radius + 1; - for (var i = 0; i < Module.Enemies(OID.IronCage).Count; ++i) - forbidden.Add(ShapeDistance.Circle(Module.Enemies(OID.IronCage)[i].Position, adjustedRadius)); - if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), spread.Activation); + var cages = Module.Enemies(OID.IronCage); + var count = cages.Count; + if (count == 0) + return; + var forbidden = new List>(count); + for (var i = 0; i < count; ++i) + forbidden.Add(ShapeDistance.Circle(Module.Enemies(OID.IronCage)[i].Position, 11)); + hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), spread.Activation); } } @@ -104,15 +106,17 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var bait = ActiveBaitsOn(actor).FirstOrDefault(); if (bait != default) { - var forbidden = new List>(); var cages = Module.Enemies(OID.IronCage); + var count = cages.Count; + if (count == 0) + return; + var forbidden = new List>(count); for (var i = 0; i < cages.Count; ++i) { var a = cages[i]; forbidden.Add(ShapeDistance.Cone(bait.Source.Position, 100, bait.Source.AngleTo(a), Angle.Asin(3.5f / (a.Position - bait.Source.Position).Length()))); } - if (forbidden.Count != 0) - hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), bait.Activation); + hints.AddForbiddenZone(p => forbidden.Min(f => f(p)), bait.Activation); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D082MorbolMarquis.cs b/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D082MorbolMarquis.cs index e6b3a55867..cf76735313 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D082MorbolMarquis.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D082MorbolMarquis.cs @@ -108,12 +108,6 @@ public override void OnActorEAnim(Actor actor, uint state) }; } } - - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - if (!Arena.InBounds(actor.Position)) - hints.AddForbiddenZone(ShapeDistance.InvertedDonut(Arena.Center, 11, 14)); - } } class D082MorbolMarquisStates : StateMachineBuilder diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs b/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs index bbdf0ad51a..de3c890678 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D08AkadaemiaAnyder/D083Quetzalcoatl.cs @@ -2,7 +2,6 @@ namespace BossMod.Shadowbringers.Dungeon.D08AkadaemiaAnyder.D083Quetzalcoatl; public enum OID : uint { - Boss = 0x28DA, // R5.4 CollectableOrb = 0x28DB, // R0.7 ExpandingOrb = 0x1EAB51, // R0.5 @@ -40,7 +39,7 @@ class ThunderstormSpread(BossModule module) : Components.SpreadFromCastTargets(m class OrbCollecting(BossModule module) : BossComponent(module) { - private readonly HashSet _orbs = []; + private readonly List _orbs = []; private readonly ShockingPlumage _aoe = module.FindComponent()!; public override void OnActorCreated(Actor actor) @@ -57,21 +56,24 @@ public override void AddGlobalHints(GlobalHints hints) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var orbs = new List>(); - if (_orbs.Count != 0) - foreach (var o in _orbs) - orbs.Add(ShapeDistance.InvertedCircle(o.Position, 0.7f)); - if (orbs.Count > 0) - { - var activation = _aoe.ActiveAOEs(slot, actor).FirstOrDefault().Activation.AddSeconds(1.1f); - hints.AddForbiddenZone(p => orbs.Max(f => f(p)), activation == default ? WorldState.FutureTime(2) : activation); - } + + var count = _orbs.Count; + if (count == 0) + return; + var orbs = new List>(count); + for (var i = 0; i < count; ++i) + orbs.Add(ShapeDistance.InvertedCircle(_orbs[i].Position, 0.7f)); + var activation = _aoe.ActiveAOEs(slot, actor).FirstOrDefault().Activation.AddSeconds(1.1f); + hints.AddForbiddenZone(p => orbs.Max(f => f(p)), activation == default ? WorldState.FutureTime(2) : activation); } public override void DrawArenaForeground(int pcSlot, Actor pc) { - foreach (var orb in _orbs) - Arena.AddCircle(orb.Position, 0.7f, Colors.Safe); + var count = _orbs.Count; + if (count == 0) + return; + for (var i = 0; i < count; ++i) + Arena.AddCircle(_orbs[i].Position, 0.7f, Colors.Safe); } public override void OnEventCast(Actor caster, ActorCastEvent spell) diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs index b5bde1f76f..c430982c6a 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D092LeannanSith.cs @@ -54,16 +54,27 @@ class GreenTiles(BossModule module) : Components.GenericAOEs(module) new(-15, -55), new(5, -55), new(-5, -45), new(15, -45) ]; private Square[] tiles = []; - - public static bool IsTransporting(Actor actor) => actor.FindStatus(SID.Transporting) != null; + public BitMask transporting; public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (ShouldActivateAOEs()) - yield return new(new AOEShapeCustom(tiles), Arena.Center, Color: Colors.FutureVulnerable, Risky: IsTransporting(actor)); + if (ShouldActivateAOEs) + yield return new(new AOEShapeCustom(tiles), Arena.Center, Color: Colors.FutureVulnerable, Risky: transporting[slot] != default); } - private bool ShouldActivateAOEs() => NumCasts == 1 ? tiles.Length > 0 : tiles.Length > 8; + private bool ShouldActivateAOEs => NumCasts == 1 ? tiles.Length > 0 : tiles.Length > 8; + + public override void OnStatusGain(Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Transporting) + transporting.Set(Raid.FindSlot(actor.InstanceID)); + } + + public override void OnStatusLose(Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.Transporting) + transporting[Raid.FindSlot(actor.InstanceID)] = default; + } public override void OnEventEnvControl(byte index, uint state) { @@ -94,7 +105,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) var knockbackDirection = new Angle(MathF.Round(spell.Rotation.Deg / 90) * 90) * Angle.DegToRad; var offset = 10 * knockbackDirection.ToDirection(); var tileList = tiles.ToList(); - var newTiles = new List(); + var newTiles = new List(10); for (var i = 0; i < tileList.Count; ++i) tileList[i] = new(tileList[i].Center - offset, tileList[i].HalfSize); foreach (var t in tileList.Where(x => (caster.Position - x.Center).LengthSq() > 625)) @@ -106,7 +117,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (!ShouldActivateAOEs() || AI.AIManager.Instance?.Beh == null) + if (!ShouldActivateAOEs || AI.AIManager.Instance?.Beh == null) return; base.AddAIHints(slot, actor, assignment, hints); @@ -114,7 +125,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var clippedSeeds = GetClippedSeeds(shape); var closestSeed = clippedSeeds.Closest(actor.Position); - if (!IsTransporting(actor) && closestSeed != null) + if (transporting[slot] != default && closestSeed != null) HandleNonTransportingActor(actor, hints, clippedSeeds, closestSeed); else if (!shape.Shape.Check(actor.Position, shape.Origin, default)) HandleTransportingActor(actor, hints); @@ -123,13 +134,11 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } private IEnumerable GetClippedSeeds(AOEInstance shape) - => Module.Enemies(OID.LeannanSeed1).Concat(Module.Enemies(OID.LeannanSeed2)) - .Concat(Module.Enemies(OID.LeannanSeed3)).Concat(Module.Enemies(OID.LeannanSeed4)) - .Where(x => x.IsTargetable).InShape(shape.Shape, shape.Origin, default); + => Module.Enemies(D092LeananSith.Seeds).Where(x => x.IsTargetable).InShape(shape.Shape, shape.Origin, default); private void HandleNonTransportingActor(Actor actor, AIHints hints, IEnumerable clippedSeeds, Actor closestSeed) { - var forbidden = new List>(); + var forbidden = new List>(4); foreach (var seed in clippedSeeds) forbidden.Add(ShapeDistance.InvertedCircle(seed.Position, 3)); var distance = (actor.Position - closestSeed.Position).LengthSq(); @@ -152,13 +161,20 @@ private void HandleTransportingActor(Actor actor, AIHints hints) public override void AddHints(int slot, Actor actor, TextHints hints) { - var aoes = ActiveAOEs(slot, actor).FirstOrDefault(); - if (IsTransporting(actor) && ActiveAOEs(slot, actor).Any(c => c.Check(actor.Position))) - hints.Add("Drop seed outside of vulnerable area!"); - else if (IsTransporting(actor) && ActiveAOEs(slot, actor).Any(c => !c.Check(actor.Position))) - hints.Add("Drop your seed!"); - else if (!IsTransporting(actor) && aoes.Shape != null && GetClippedSeeds(aoes).Any()) - hints.Add("Pick up seeds in vulnerable squares!"); + var trans = transporting[slot] != default; + if (trans) + { + if (ActiveAOEs(slot, actor).Any(c => c.Check(actor.Position))) + hints.Add("Drop seed outside of vulnerable area!"); + else + hints.Add("Drop your seed!"); + } + else if (!trans) + { + var aoes = ActiveAOEs(slot, actor).FirstOrDefault(); + if (aoes.Shape != null && GetClippedSeeds(aoes).Any()) + hints.Add("Pick up seeds in vulnerable squares!"); + } } } @@ -181,22 +197,23 @@ public D092LeananSithStates(BossModule module) : base(module) public class D092LeananSith(WorldState ws, Actor primary) : BossModule(ws, primary, ArenaCenter, new ArenaBoundsSquare(19.5f)) { public static readonly WPos ArenaCenter = new(0, -60); + public static readonly uint[] Seeds = [(uint)OID.LeannanSeed1, (uint)OID.LeannanSeed2, (uint)OID.LeannanSeed3, (uint)OID.LeannanSeed4]; protected override void DrawEnemies(int pcSlot, Actor pc) { Arena.Actor(PrimaryActor); - Arena.Actors(Enemies(OID.LeannanSeed1).Concat(Enemies(OID.LeannanSeed2)).Concat(Enemies(OID.LeannanSeed3)).Concat(Enemies(OID.LeannanSeed4)), Colors.Object); + Arena.Actors(Enemies(Seeds), Colors.Object); Arena.Actors(Enemies(OID.LoversRing)); } protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var e in hints.PotentialTargets) + for (var i = 0; i < hints.PotentialTargets.Count; ++i) { + var e = hints.PotentialTargets[i]; e.Priority = (OID)e.Actor.OID switch { - OID.LoversRing => 2, - OID.Boss => 1, + OID.LoversRing => 1, _ => 0 }; } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs index 24118e5e02..f43097ade3 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D09GrandCosmos/D093Lugus.cs @@ -60,38 +60,27 @@ public enum SID : uint class MortalFlame(BossModule module) : BossComponent(module) { - public static bool IsBurning(Actor actor) => actor.FindStatus(SID.MortalFlame) != null; - private static readonly HashSet furniture = [OID.FurnitureHelper1, OID.FurnitureHelper2, OID.FurnitureHelper4, OID.FurnitureHelper5, OID.FurnitureHelper6]; // without chandeliers since they get enabled later, but exist invisible to the player - private HashSet _furniture = []; - private bool updated; + public BitMask burning; + private IEnumerable Furniture => Module.Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable) ? Module.Enemies(D093Lugus.FurnitureB) : Module.Enemies(D093Lugus.FurnitureA); - public override void OnActorDestroyed(Actor actor) + public override void OnStatusGain(Actor actor, ActorStatus status) { - _furniture.Remove(actor); + if ((SID)status.ID == SID.MortalFlame) + burning.Set(Raid.FindSlot(actor.InstanceID)); } - public override void OnActorCreated(Actor actor) + public override void OnStatusLose(Actor actor, ActorStatus status) { - if (furniture.Contains((OID)actor.OID)) - _furniture.Add(actor); - } - - public override void Update() - { - if (!updated && Module.Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable)) - { - var list = _furniture.Concat(Module.Enemies(OID.FurnitureHelper3)).ToHashSet(); - _furniture = list; - updated = true; - } + if ((SID)status.ID == SID.MortalFlame) + burning[Raid.FindSlot(actor.InstanceID)] = default; } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (!IsBurning(actor)) + if (!burning[slot]) return; - var forbidden = new List>(); - foreach (var h in _furniture) + var forbidden = new List>(Furniture.Count()); + foreach (var h in Furniture) forbidden.Add(ShapeDistance.InvertedCircle(h.Position, h.HitboxRadius - 0.5f)); if (forbidden.Count > 0) hints.AddForbiddenZone(p => forbidden.Max(f => f(p))); @@ -99,14 +88,14 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme public override void AddHints(int slot, Actor actor, TextHints hints) { - if (IsBurning(actor)) + if (burning[slot]) hints.Add("Pass flames debuff to furniture!"); } public override void DrawArenaForeground(int pcSlot, Actor pc) { - if (IsBurning(pc)) - foreach (var a in _furniture) + if (burning[pcSlot]) + foreach (var a in Furniture) Arena.AddCircle(a.Position, a.HitboxRadius, Colors.Safe); } } @@ -115,30 +104,7 @@ class BlackFlame(BossModule module) : Components.GenericBaitAway(module) { private static readonly AOEShapeCircle circle = new(6); private static readonly AOEShapeCross cross = new(10, 2); - private static readonly HashSet furniture = [OID.FurnitureHelper1, OID.FurnitureHelper2, OID.FurnitureHelper4, OID.FurnitureHelper5, OID.FurnitureHelper6]; // without chandeliers since they get enabled later, but exist invisible to the player - private HashSet _furniture = []; - private bool updated; - - public override void OnActorDestroyed(Actor actor) - { - _furniture.Remove(actor); - } - - public override void OnActorCreated(Actor actor) - { - if (furniture.Contains((OID)actor.OID)) - _furniture.Add(actor); - } - - public override void Update() - { - if (!updated && Module.Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable)) - { - var list = _furniture.Concat(Module.Enemies(OID.FurnitureHelper3)).ToHashSet(); - _furniture = list; - updated = true; - } - } + private IEnumerable Furniture => Module.Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable) ? Module.Enemies(D093Lugus.FurnitureB) : Module.Enemies(D093Lugus.FurnitureA); public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) { @@ -162,7 +128,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (CurrentBaits.Any(x => x.Target == actor)) { var b = ActiveBaitsOn(actor).FirstOrDefault(x => x.Shape == cross); - foreach (var p in _furniture) + foreach (var p in Furniture) { // AOE and hitboxes seem to be forbidden to intersect hints.AddForbiddenZone(ShapeDistance.Cross(p.Position, b.Rotation, cross.Length + p.HitboxRadius, cross.HalfWidth + p.HitboxRadius), b.Activation); @@ -176,7 +142,7 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) base.DrawArenaForeground(pcSlot, pc); if (!ActiveBaits.Any(x => x.Target == pc)) return; - foreach (var a in _furniture) + foreach (var a in Furniture) Arena.AddCircle(a.Position, a.HitboxRadius, Colors.Danger); } @@ -214,7 +180,7 @@ class FiresDomain(BossModule module) : Components.GenericBaitAway(module) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if (CurrentBaits.Count > 0 && (AID)spell.Action.ID is AID.FiresDomain1 or AID.FiresDomain2) + if (CurrentBaits.Count != 0 && (AID)spell.Action.ID is AID.FiresDomain1 or AID.FiresDomain2) CurrentBaits.RemoveAt(0); } @@ -237,30 +203,7 @@ public override void Update() class FiresIreBait(BossModule module) : Components.GenericBaitAway(module) { private static readonly AOEShapeCone cone = new(20, 45.Degrees()); - private static readonly HashSet furniture = [OID.FurnitureHelper1, OID.FurnitureHelper2, OID.FurnitureHelper4, OID.FurnitureHelper5, OID.FurnitureHelper6]; // without chandeliers since they get enabled later, but exist invisible to the player - private HashSet _furniture = []; - private bool updated; - - public override void OnActorDestroyed(Actor actor) - { - _furniture.Remove(actor); - } - - public override void OnActorCreated(Actor actor) - { - if (furniture.Contains((OID)actor.OID)) - _furniture.Add(actor); - } - - public override void Update() - { - if (!updated && Module.Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable)) - { - var list = _furniture.Concat(Module.Enemies(OID.FurnitureHelper3)).ToHashSet(); - _furniture = list; - updated = true; - } - } + private IEnumerable Furniture => Module.Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable) ? Module.Enemies(D093Lugus.FurnitureB) : Module.Enemies(D093Lugus.FurnitureA); public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) { @@ -270,7 +213,7 @@ public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if (CurrentBaits.Count > 0 && (AID)spell.Action.ID is AID.FiresDomain1 or AID.FiresDomain2) + if (CurrentBaits.Count != 0 && (AID)spell.Action.ID is AID.FiresDomain1 or AID.FiresDomain2) CurrentBaits.RemoveAt(0); } @@ -282,7 +225,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme if (CurrentBaits.Any(x => x.Target == actor)) { var b = ActiveBaitsOn(actor).FirstOrDefault(); - var actors = _furniture.Concat(Raid.WithoutSlot().Exclude([actor]).ToHashSet()); + var actors = Furniture.Concat(Raid.WithoutSlot().Exclude([actor])); foreach (var a in actors) hints.AddForbiddenZone(ShapeDistance.Circle(a.Position, 10), b.Activation); } @@ -292,7 +235,7 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) { if (!ActiveBaits.Any(x => x.Target == pc)) return; - foreach (var a in _furniture) + foreach (var a in Furniture) Arena.AddCircle(a.Position, a.HitboxRadius, Colors.Danger); foreach (var b in ActiveBaitsOn(pc)) b.Shape.Outline(Arena, b.Target.Position - (b.Target.HitboxRadius + Module.PrimaryActor.HitboxRadius) * Module.PrimaryActor.DirectionTo(b.Target), b.Rotation); @@ -334,11 +277,12 @@ public D093LugusStates(BossModule module) : base(module) [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 692, NameID = 9046)] public class D093Lugus(WorldState ws, Actor primary) : BossModule(ws, primary, new(0, -340), new ArenaBoundsSquare(24.5f)) { + public static readonly uint[] FurnitureA = [(uint)OID.FurnitureHelper1, (uint)OID.FurnitureHelper2, (uint)OID.FurnitureHelper4, (uint)OID.FurnitureHelper5, (uint)OID.FurnitureHelper6]; + public static readonly uint[] FurnitureB = [.. FurnitureA, (uint)OID.FurnitureHelper3]; + protected override void DrawEnemies(int pcSlot, Actor pc) { Arena.Actor(PrimaryActor); - Arena.Actors(Enemies(OID.FurnitureHelper1).Concat(Enemies(OID.FurnitureHelper2)).Concat(Enemies(OID.FurnitureHelper4)).Concat(Enemies(OID.FurnitureHelper5)).Concat(Enemies(OID.FurnitureHelper6)), Colors.Object, true); - if (Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable)) - Arena.Actors(Enemies(OID.FurnitureHelper3), Colors.Object, true); + Arena.Actors(Enemies(OID.CrystalChandelier).Any(x => x.IsTargetable) ? Enemies(FurnitureB) : Enemies(FurnitureA), Colors.Object, true); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs index 478c618e45..bad339d7e7 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D11HeroesGauntlet/D113SpectralBerserker.cs @@ -108,7 +108,7 @@ class WildAnguish2(BossModule module) : Components.GenericTowers(module) public override void Update() { - if (Towers.Count > 0 && _sp.Spreads.Count == 0) + if (Towers.Count != 0 && _sp.Spreads.Count == 0) Towers.Clear(); } @@ -132,16 +132,14 @@ public override void AddHints(int slot, Actor actor, TextHints hints) { } class WildRageKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.WildRageKnockback), 15) { - private static readonly Angle a10 = 10.Degrees(); - private static readonly Angle a45 = 45.Degrees(); + private static readonly Angle a10 = 10.Degrees(), a45 = 45.Degrees(); public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { var source = Sources(slot, actor).FirstOrDefault(); - if (source != default) { - var forbidden = new List>(); + var forbidden = new List>(2); var dir = source.Origin.X == 738 ? 1 : -1; forbidden.Add(ShapeDistance.InvertedDonutSector(source.Origin, 8, 9, a45 * dir, a10)); forbidden.Add(ShapeDistance.InvertedDonutSector(source.Origin, 8, 9, 3 * a45 * dir, a10)); @@ -156,18 +154,16 @@ class BeastlyFury(BossModule module) : Components.RaidwideCast(module, ActionID. class CratersWildRampage(BossModule module) : Components.GenericAOEs(module) { - private static readonly WPos pos1 = new(738, 482); - private static readonly WPos pos2 = new(762, 482); - private static readonly Circle circle1 = new(pos1, 7); - private static readonly Circle circle2 = new(pos2, 7); - public readonly List Circles = []; + private static readonly WPos pos1 = new(738, 482), pos2 = new(762, 482); + private static readonly Circle circle1 = new(pos1, 7), circle2 = new(pos2, 7); + public readonly List Circles = new(2); private bool invert; private DateTime activation; private const string Hint = "Go inside crater!"; public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (Circles.Count > 0) + if (Circles.Count != 0) yield return new(new AOEShapeCustom(Circles) with { InvertForbiddenZone = invert }, Arena.Center, default, activation, invert ? Colors.SafeFromAOE : Colors.AOE); } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs index 508576ada0..216578aad3 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D121Mudman.cs @@ -61,9 +61,9 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) AddSequence(caster.Position, Module.CastFinishAt(spell), spell.Rotation); } - public override void OnEventCast(Actor caster, ActorCastEvent spell) + public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (Sequences.Count > 0) + if (Sequences.Count != 0) { var order = (AID)spell.Action.ID switch { @@ -79,9 +79,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) class RockyRoll(BossModule module) : Components.GenericBaitAway(module) { - private static readonly AOEShapeRect rect1 = new(60, 2); - private static readonly AOEShapeRect rect2 = new(60, 3); - private static readonly AOEShapeRect rect3 = new(60, 4); + private static readonly AOEShapeRect rect1 = new(60, 2), rect2 = new(60, 3), rect3 = new(60, 4); private readonly List activeHoles = []; private static readonly Dictionary holePositions = new() @@ -118,8 +116,8 @@ public override void OnUntethered(Actor source, ActorTetherInfo tether) public override void DrawArenaForeground(int pcSlot, Actor pc) { base.DrawArenaForeground(pcSlot, pc); - foreach (var h in activeHoles) - Arena.AddCircle(h, 5, Colors.Safe, 5); + for (var i = 0; i < activeHoles.Count; ++i) + Arena.AddCircle(activeHoles[i], 5, Colors.Safe, 5); } public override void Update() @@ -155,11 +153,11 @@ public override void AddHints(int slot, Actor actor, TextHints hints) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { base.AddAIHints(slot, actor, assignment, hints); - var forbidden = new List>(); + var forbidden = new List>(4); foreach (var b in ActiveBaitsOn(actor)) - foreach (var h in activeHoles) - forbidden.Add(ShapeDistance.InvertedRect(b.Source.Position, h, 1)); - if (forbidden.Count > 0) + for (var i = 0; i < activeHoles.Count; ++i) + forbidden.Add(ShapeDistance.InvertedRect(b.Source.Position, activeHoles[i], 1)); + if (forbidden.Count != 0) hints.AddForbiddenZone(p => forbidden.Max(f => f(p))); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D122Nixie.cs b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D122Nixie.cs index afaacc509e..98cea0d22a 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D122Nixie.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D122Nixie.cs @@ -44,18 +44,17 @@ public enum TetherID : uint class Gurgle(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeRect rect = new(60, 5); - private readonly List _aoes = []; - private static readonly Angle[] angles = [89.999f.Degrees(), -90.004f.Degrees()]; - private static readonly Dictionary aoePositions = new() + private readonly List _aoes = new(3); + private static readonly Dictionary aoePositions = new() { - { 0x13, (new(-20, -165), angles[0]) }, - { 0x14, (new(-20, -155), angles[0]) }, - { 0x15, (new(-20, -145), angles[0]) }, - { 0x16, (new(-20, -135), angles[0]) }, - { 0x17, (new(20, -165), angles[1]) }, - { 0x18, (new(20, -155), angles[1]) }, - { 0x19, (new(20, -145), angles[1]) }, - { 0x1A, (new(20, -135), angles[1]) } + { 0x13, new(-20, -165) }, + { 0x14, new(-20, -155) }, + { 0x15, new(-20, -145) }, + { 0x16, new(-20, -135) }, + { 0x17, new(20, -165) }, + { 0x18, new(20, -155) }, + { 0x19, new(20, -145) }, + { 0x1A, new(20, -135) } }; public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; @@ -63,9 +62,7 @@ class Gurgle(BossModule module) : Components.GenericAOEs(module) public override void OnEventEnvControl(byte index, uint state) { if (state == 0x00020001 && aoePositions.TryGetValue(index, out var value)) - { - _aoes.Add(new(rect, value.Item1, value.Item2, WorldState.FutureTime(9))); - } + _aoes.Add(new(rect, value, index < 0x17 ? Angle.AnglesCardinals[3] : Angle.AnglesCardinals[0], WorldState.FutureTime(9))); } public override void OnEventCast(Actor caster, ActorCastEvent spell) @@ -93,7 +90,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) public override void AddGlobalHints(GlobalHints hints) { - if (CurrentBaits.Count > 0) + if (CurrentBaits.Count != 0) hints.Add("4x Tankbuster cleave"); } } @@ -101,18 +98,20 @@ public override void AddGlobalHints(GlobalHints hints) class GeysersCloudPlatform(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(6); - private readonly List _aoes = []; + private readonly List _aoes = new(5); private bool active; private const string RiskHint = "Go to correct geyser!"; private const string StayHint = "Wait for erruption!"; public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (Arena.Bounds == D122Nixie.DefaultArena) + var count = _aoes.Count; + if (count != 0 && Arena.Bounds == D122Nixie.DefaultArena) { var closestGeysir = _aoes.MinBy(a => (a.Origin - D122Nixie.CloudCenter).LengthSq()); - foreach (var a in _aoes) + for (var i = 0; i < count; ++i) { + var a = _aoes[i]; var safeGeysir = active && a == closestGeysir; yield return a with { Shape = safeGeysir ? circle with { InvertForbiddenZone = true } : circle, Color = safeGeysir ? Colors.SafeFromAOE : Colors.AOE }; } @@ -145,25 +144,29 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) public override void DrawArenaBackground(int pcSlot, Actor pc) { base.DrawArenaBackground(pcSlot, pc); + if (D122Nixie.Cloud.Contains(pc.Position - D122Nixie.CloudCenter)) - { - Arena.Bounds = D122Nixie.Cloud; - Arena.Center = D122Nixie.CloudCenter; - } + SetArena(D122Nixie.Cloud, D122Nixie.CloudCenter); else - { - Arena.Bounds = D122Nixie.DefaultArena; - Arena.Center = D122Nixie.ArenaCenter; - } + SetArena(D122Nixie.DefaultArena, D122Nixie.ArenaCenter); + } + + private void SetArena(ArenaBounds bounds, WPos center) + { + Arena.Bounds = bounds; + Arena.Center = center; } public override void AddHints(int slot, Actor actor, TextHints hints) { - var activeAOEs = ActiveAOEs(slot, actor).ToList(); - if (activeAOEs.Any(c => c.Color == Colors.SafeFromAOE && !c.Check(actor.Position))) - hints.Add(RiskHint); - else if (activeAOEs.Any(c => c.Color == Colors.SafeFromAOE && c.Check(actor.Position))) - hints.Add(StayHint, false); + var activeSafespot = ActiveAOEs(slot, actor).Where(c => c.Color == Colors.SafeFromAOE).ToList(); + if (activeSafespot.Count != 0) + { + if (!activeSafespot.Any(c => c.Check(actor.Position))) + hints.Add(RiskHint); + else + hints.Add(StayHint, false); + } else base.AddHints(slot, actor, hints); } @@ -193,6 +196,7 @@ public class D122Nixie(WorldState ws, Actor primary) : BossModule(ws, primary, A protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.UnfinishedNixie).Concat([PrimaryActor])); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(OID.UnfinishedNixie)); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D123MotherPorxie.cs b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D123MotherPorxie.cs index 5380e1e55f..e775336cf1 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D123MotherPorxie.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D12MatoyasRelict/D123MotherPorxie.cs @@ -40,13 +40,13 @@ public enum AID : uint NeerDoneWell = 20045, // Helper->self, 8.0s cast, range 5-40 donut (on limit break fail) OpenFlameVisual = 22818, // Boss->self, 6.0s cast, single-target - OpenFlame = 22819, // Helper->player, no cast, range 5 circle, spread + OpenFlame = 22819 // Helper->player, no cast, range 5 circle, spread } public enum IconID : uint { Tankbuster = 198, // player - Spreadmarker = 169, // player + Spreadmarker = 169 // player } class TenderLoin(BossModule module) : Components.RaidwideCastDelay(module, ActionID.MakeSpell(AID.TenderLoinVisual), ActionID.MakeSpell(AID.TenderLoin), 0.8f); @@ -191,6 +191,7 @@ public D123MotherPorxieStates(BossModule module) : base(module) { protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.AeolianCaveSprite).Concat([PrimaryActor])); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(OID.AeolianCaveSprite)); } } diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs new file mode 100644 index 0000000000..c4e68e43cb --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D131Amhuluk.cs @@ -0,0 +1,200 @@ +namespace BossMod.Shadowbringers.Dungeon.D13Paglthan.D131Amhuluk; + +public enum OID : uint +{ + Boss = 0x3169, // R7.008, x1 + LightningRod = 0x31B6, // R1.0 + BallOfLevin = 0x31A2, // R1.3 + SuperchargedLevin = 0x31A3, // R2.3 + Helper = 0x233C +} + +public enum AID : uint +{ + AutoAttack = 870, // Boss->player, no cast, single-target + Teleport = 23633, // Boss->location, no cast, single-target + + CriticalRip = 23630, // Boss->player, 5.0s cast, single-target + LightningBoltVisual = 23627, // Boss->self, 10.0s cast, single-target + LightningBolt = 23628, // Helper->location, no cast, range 10 circle + ElectricBurst = 23629, // Boss->self, 4.5s cast, range 50 width 40 rect, raidwide (rect also goes to the back of boss) + Thundercall = 23632, // Boss->self, 4.0s cast, single-target + ShockSmall = 23634, // BallOfLevin->self, no cast, range 5 circle + ShockLarge = 23635, // SuperchargedLevin->self, no cast, range 10 circle + WideBlaster = 24773, // Boss->self, 4.0s cast, range 26 120-degree cone + SpikeFlail = 23631 // Boss->self, 1.0s cast, range 25 60-degree cone +} + +public enum SID : uint +{ + LightningRod = 2574 // none->player/LightningRod, extra=0x114 +} + +class ElectricBurst(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.ElectricBurst)); +class CriticalRip(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.CriticalRip)); + +class LightningBolt(BossModule module) : Components.GenericBaitAway(module, ActionID.MakeSpell(AID.LightningBolt), centerAtTarget: true) +{ + private static readonly AOEShapeCircle circle = new(10); + private IEnumerable Rods => Module.Enemies(OID.LightningRod).Where(x => x.FindStatus(SID.LightningRod) == null); + private DateTime activation; + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + if (!ActiveBaits.Any(x => x.Target == actor)) + return; + hints.Add("Pass the lightning to a rod!"); + } + + public override void OnStatusGain(Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.LightningRod) + { + if (activation == default) + activation = WorldState.FutureTime(10.8f); + CurrentBaits.Add(new(actor, actor, circle, activation)); + } + } + + public override void OnStatusLose(Actor actor, ActorStatus status) + { + if ((SID)status.ID == SID.LightningRod) + CurrentBaits.RemoveAll(x => x.Target == actor); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.LightningBolt) + { + CurrentBaits.Clear(); + activation = default; + } + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + base.AddAIHints(slot, actor, assignment, hints); + if (!ActiveBaits.Any(x => x.Target == actor)) + return; + var forbidden = new List>(); + foreach (var a in Rods) + forbidden.Add(ShapeDistance.InvertedCircle(a.Position, 4)); + if (forbidden.Count != 0) + hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), activation); + } + + public override void DrawArenaForeground(int pcSlot, Actor pc) + { + if (!ActiveBaits.Any(x => x.Target == pc)) + return; + base.DrawArenaForeground(pcSlot, pc); + foreach (var a in Rods) + Arena.AddCircle(a.Position, 4, Colors.Safe); + } +} + +class Shock(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeCircle circleSmall = new(5), circleBig = new(10); + private readonly List _aoes = []; + private bool first = true; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + + public override void OnActorCreated(Actor actor) + { + var shape = (OID)actor.OID switch + { + OID.BallOfLevin => circleSmall, + OID.SuperchargedLevin => circleBig, + _ => null + }; + if (shape != null) + _aoes.Add(new(shape, actor.Position, default, WorldState.FutureTime(3.7f))); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.ShockSmall or AID.ShockLarge) + { + if (++NumCasts == (first ? 72 : 30)) + { + _aoes.Clear(); + NumCasts = 0; + first = false; + } + } + } +} + +class WideBlasterSpikeFlail(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeCone coneWide = new(26, 60.Degrees()), coneNarrow = new(25, 30.Degrees()); + private readonly List _aoes = []; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = _aoes.Count; + for (var i = 0; i < count; ++i) + { + var aoe = _aoes[i]; + if (i == 0) + yield return count == 2 ? aoe with { Color = Colors.Danger } : aoe; + else if (i == 1) + yield return aoe; + } + } + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + void AddAOE(AOEShape shape, Angle offset = default, float delay = 0) + => _aoes.Add(new(shape, caster.Position, spell.Rotation + offset, Module.CastFinishAt(spell, delay))); + if ((AID)spell.Action.ID == AID.WideBlaster) + { + AddAOE(coneWide); + AddAOE(coneNarrow, 180.Degrees(), 2.6f); + } + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if (_aoes.Count != 0 && (AID)spell.Action.ID is AID.WideBlaster or AID.SpikeFlail) + _aoes.RemoveAt(0); + } +} + +class D131AmhulukStates : StateMachineBuilder +{ + public D131AmhulukStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 777, NameID = 10075)] +public class D131Amhuluk(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) +{ + private static readonly WPos ArenaCenter = new(-520, 145), LightningRod = new(-538.478f, 137.346f); + + private static List GenerateLightningRods() + { + const float radius = 1.45f; + const int edges = 16; + var polygons = new List(8) + { + new Polygon(LightningRod, radius, edges) + }; + for (var i = 1; i < 8; ++i) + { + polygons.Add(new Polygon(WPos.RotateAroundOrigin(i * 45, ArenaCenter, LightningRod), radius, edges)); + } + return polygons; + } + private static readonly ArenaBoundsComplex arena = new([new Polygon(ArenaCenter, 19.5f, 48)], [.. GenerateLightningRods(), new Rectangle(new(-540, ArenaCenter.Z), 1.25f, 20), + new Rectangle(new(-500, ArenaCenter.Z), 1.26f, 20)]); +} diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs new file mode 100644 index 0000000000..f8699cad92 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D132MagitekFortress.cs @@ -0,0 +1,205 @@ +namespace BossMod.Shadowbringers.Dungeon.D13Paglthan.D132MagitekFortress; + +public enum OID : uint +{ + Boss = 0x32FE, // R1.0 + MagitekCore = 0x31AC, // R2.3 + TemperedImperial = 0x31AD, // R0.5 + TelotekPredator = 0x31AF, // R2.1 + MagitekMissile = 0x31B2, // R1.0 + TelotekSkyArmor = 0x31B0, // R2.0 + MarkIITelotekColossus = 0x31AE, // R3.0 + Helper = 0x233C +} + +public enum AID : uint +{ + AutoAttack = 870, // TemperedImperial/TelotekPredator/TelotekSkyArmor/MarkIITelotekColossus->player, no cast, single-target + + MagitekClaw = 23706, // TelotekPredator->player, 4.0s cast, single-target, mini tank buster, can be ignored + StableCannon = 23700, // Helper->self, no cast, range 60 width 10 rect + TwoTonzeMagitekMissile = 23701, // Helper->location, 5.0s cast, range 12 circle + GroundToGroundBallistic = 23703, // Helper->location, 5.0s cast, range 40 circle, knockback 10, away from source + MissileActivation = 10758, // MagitekMissile->self, no cast, single-target + ExplosiveForce = 23704, // MagitekMissile->player, no cast, single-target + DefensiveReaction = 23710, // MagitekCore->self, 5.0s cast, range 60 circle + Aethershot = 23708, // TelotekSkyArmor->location, 4.0s cast, range 6 circle + Exhaust = 23705 // MarkIITelotekColossus->self, 4.0s cast, range 40 width 7 rect +} + +class TwoTonzeMagitekMissile(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.TwoTonzeMagitekMissile), 12); +class Aethershot(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Aethershot), 6); +class DefensiveReaction(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.DefensiveReaction)); +class Exhaust(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Exhaust), new AOEShapeRect(40, 3.5f)); +class GroundToGroundBallistic(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.GroundToGroundBallistic), 10) +{ + private static readonly Func distance = p => Math.Max(ShapeDistance.InvertedCone(D132MagitekFortress.DefaultCenter, 20, default, 18.Degrees())(p), + ShapeDistance.InvertedCone(D132MagitekFortress.DefaultCenter, 20, 180.Degrees(), 18.Degrees())(p)); + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + var source = Sources(slot, actor).FirstOrDefault(); + if (source != default) + hints.AddForbiddenZone(distance, source.Activation); + } +} + +class StableCannon(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeRect rect = new(60, 5); + private readonly List _aoes = new(2); + + private static readonly Dictionary aoePositions = new() + { + { 0x08, new(-185, 28.3f) }, + { 0x09, new(-175, 28.3f) }, + { 0x0A, new(-165, 28.3f) } + }; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + + public override void OnEventEnvControl(byte index, uint state) + { + if (state == 0x00200010 && aoePositions.TryGetValue(index, out var value)) + _aoes.Add(new(rect, value, Angle.AnglesCardinals[1], WorldState.FutureTime(12.1f))); + else if (index == 0x0D && state == 0x00020001) + _aoes.Clear(); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.StableCannon) + _aoes.Clear(); + } +} + +class MagitekMissile(BossModule module) : Components.GenericAOEs(module) +{ + private const int Radius = 1, Length = 10; + private static readonly AOEShapeCapsule capsule = new(Radius, Length); + private readonly List _missiles = new(15); + + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var count = _missiles.Count; + if (count == 0) + yield break; + for (var i = 0; i < _missiles.Count; ++i) + { + var m = _missiles[i]; + yield return new(capsule, m.Position, m.Rotation); + } + } + + public override void OnActorCreated(Actor actor) + { + if ((OID)actor.OID == OID.MagitekMissile) + _missiles.Add(actor); + } + + public override void OnActorDestroyed(Actor actor) + { + if ((OID)actor.OID == OID.MagitekMissile) + _missiles.Remove(actor); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.ExplosiveForce) + _missiles.Remove(caster); + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + if (_missiles.Count == 0) + return; + var forbidden = new List>(15); + for (var i = 0; i < _missiles.Count; ++i) + { + var m = _missiles[i]; + forbidden.Add(ShapeDistance.Capsule(m.Position, m.Rotation, Length, Radius)); // merging all forbidden zones into one to make pathfinding less demanding + } + hints.AddForbiddenZone(p => forbidden.Min(f => f(p))); + } +} + +class CorePlatform(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeCircle circle = new(2, true); + private AOEInstance? _aoe; + private const string Hint = "Walk into the glowing circle!"; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + if (_aoe != null && Arena.Bounds == D132MagitekFortress.DefaultBounds) + yield return _aoe.Value; + } + + public override void OnEventEnvControl(byte index, uint state) + { + if (index == 0x0D) + { + if (state == 0x00020001) + _aoe = new(circle, new(-175, 30), default, DateTime.MaxValue, Colors.SafeFromAOE); + else if (state == 0x00080004) + _aoe = null; + } + } + + public override void DrawArenaBackground(int pcSlot, Actor pc) + { + base.DrawArenaBackground(pcSlot, pc); + + if (D132MagitekFortress.CoreBounds.Contains(pc.Position - D132MagitekFortress.CoreCenter)) + SetArena(D132MagitekFortress.CoreBounds, D132MagitekFortress.CoreCenter); + else + SetArena(D132MagitekFortress.DefaultBounds, D132MagitekFortress.DefaultCenter); + } + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + if (_aoe != null && Arena.Bounds == D132MagitekFortress.DefaultBounds) + { + if (!ActiveAOEs(slot, actor).Where(c => c.Color == Colors.SafeFromAOE).Any(c => c.Check(actor.Position))) + hints.Add(Hint); + } + } + + private void SetArena(ArenaBounds bounds, WPos center) + { + Arena.Bounds = bounds; + Arena.Center = center; + } +} + +class D132MagitekFortressStates : StateMachineBuilder +{ + public D132MagitekFortressStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 777, NameID = 10067)] +public class D132MagitekFortress(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultCenter, DefaultBounds) +{ + public static readonly WPos DefaultCenter = new(-175, 43), CoreCenter = new(-175, 8.5f); + public static readonly ArenaBoundsSquare DefaultBounds = new(14.5f), CoreBounds = new(7); + private static readonly uint[] trash = [(uint)OID.TelotekPredator, (uint)OID.TemperedImperial, (uint)OID.TelotekSkyArmor, (uint)OID.MarkIITelotekColossus, (uint)OID.MagitekCore]; + + protected override void DrawEnemies(int pcSlot, Actor pc) + { + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(trash)); + } + + protected override bool CheckPull() => Enemies(OID.TelotekPredator).Any(x => x.InCombat); +} diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D133LunarBahamut.cs b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D133LunarBahamut.cs new file mode 100644 index 0000000000..02f172ba87 --- /dev/null +++ b/BossMod/Modules/Shadowbringers/Dungeon/D13Paglthan/D133LunarBahamut.cs @@ -0,0 +1,144 @@ +namespace BossMod.Shadowbringers.Dungeon.D13Paglthan.D133LunarBahamut; + +public enum OID : uint +{ + Boss = 0x316A, // R8.4 + LunarNail = 0x316B, // R1.0 + Helper = 0x233C +} + +public enum AID : uint +{ + AutoAttack = 23620, // Boss->player, no cast, single-target + + TwistedScream = 23367, // Boss->self, 3.0s cast, range 40 circle, tiny raidwide, spawns lunar nails + Upburst = 24667, // LunarNail->self, 3.0s cast, range 2 circle + BigBurst = 23368, // LunarNail->self, 4.0s cast, range 9 circle + PerigeanBreath = 23385, // Boss->self, 5.0s cast, range 30 90-degree cone + AkhMornFirst = 23381, // Boss->players, 5.0s cast, range 4 circle, 4 hits + AkhMornRest = 23382, // Boss->players, no cast, range 4 circle + MegaflareVisual = 23372, // Boss->self, 3.0s cast, single-target + MegaflareSpread = 23373, // Helper->players, 5.0s cast, range 5 circle + MegaflareAOE = 23374, // Helper->location, 3.0s cast, range 6 circle + MegaflareDive = 23378, // Boss->self, 4.0s cast, range 41 width 12 rect + KanRhaiVisual1 = 23375, // Boss->self, 4.0s cast, single-target + KanRhaiVisual2 = 23377, // Helper->self, no cast, single-target + KanRhai = 23376, // Helper->self, no cast, range 30 width 6 rect, baitaway cross 15 * 6 + LunarFlareVisual = 23369, // Boss->self, 3.0s cast, single-target + LunarFlareBig = 23370, // Helper->location, 10.0s cast, range 11 circle + LunarFlareSmall = 23371, // Helper->location, 10.0s cast, range 6 circle + Gigaflare = 23383, // Boss->self, 7.0s cast, range 40 circle + Flatten = 23384 // Boss->player, 5.0s cast, single-target +} + +public enum IconID : uint +{ + KanRhai = 260 // player->self +} + +class Upburst(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Upburst), new AOEShapeCircle(2)); +class BigBurst(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.BigBurst), new AOEShapeCircle(9)); +class PerigeanBreath(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.PerigeanBreath), new AOEShapeCone(30, 45.Degrees())); +class MegaflareAOE(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.MegaflareAOE), 6); +class MegaflareSpread(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.MegaflareSpread), 5); +class MegaflareDive(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MegaflareDive), new AOEShapeRect(41, 6)); +class LunarFlareBig(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.LunarFlareBig), 11); +class LunarFlareSmall(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.LunarFlareSmall), 6); +class Gigaflare(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.Gigaflare)); +class Flatten(BossModule module) : Components.SingleTargetDelayableCast(module, ActionID.MakeSpell(AID.Flatten)); + +class AkhMorn(BossModule module) : Components.UniformStackSpread(module, 6, 0, 4, 4) +{ + private int numCasts; + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.AkhMornFirst) + AddStack(WorldState.Actors.Find(spell.TargetID)!, Module.CastFinishAt(spell)); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID is AID.AkhMornFirst or AID.AkhMornRest) + { + if (++numCasts == 4) + { + Stacks.Clear(); + numCasts = 0; + } + } + } +} + +class KanRhaiBait(BossModule module) : Components.GenericBaitAway(module) +{ + public static readonly AOEShapeCross Cross = new(15, 3); + + public override void OnEventIcon(Actor actor, uint iconID, ulong targetID) + { + if (iconID == (uint)IconID.KanRhai) + CurrentBaits.Add(new(actor, actor, Cross, WorldState.FutureTime(5.6f), default)); + } + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.KanRhaiVisual2) + CurrentBaits.Clear(); + } + + public override void AddHints(int slot, Actor actor, TextHints hints) + { + if (CurrentBaits.Any(x => x.Target == actor)) + hints.Add("Bait away and move!"); + } +} + +class KanRhaiAOE(BossModule module) : Components.GenericAOEs(module) +{ + private readonly List _aoes = new(2); + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.KanRhai) + { + ++NumCasts; + var count = _aoes.Count; + if (count != 0 && NumCasts == count * 20) + { + _aoes.Clear(); + NumCasts = 0; + } + } + else if ((AID)spell.Action.ID == AID.KanRhaiVisual2) + _aoes.Add(new(KanRhaiBait.Cross, caster.Position, default)); + } +} + +class D133LunarBahamutStates : StateMachineBuilder +{ + public D133LunarBahamutStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 777, NameID = 10077)] +public class D133LunarBahamut(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) +{ + private static readonly ArenaBoundsComplex arena = new([new Polygon(new(796.65f, -97.55f), 19.5f * CosPI.Pi40th, 40)], [new Rectangle(new(776f, -97.5f), 1.6f, 20)]); +} diff --git a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS3Dahu/DRS3.cs b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS3Dahu/DRS3.cs index b8e1599121..20dfbf4a9e 100644 --- a/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS3Dahu/DRS3.cs +++ b/BossMod/Modules/Shadowbringers/Foray/DelubrumReginaeSavage/DRS3Dahu/DRS3.cs @@ -6,10 +6,7 @@ class HotCharge(BossModule module) : Components.ChargeAOEs(module, ActionID.Make class HeadDown(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.HeadDown), 2); class HuntersClaw(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HuntersClaw), new AOEShapeCircle(8)); -class Burn : Components.BaitAwayIcon -{ - public Burn(BossModule module) : base(module, new AOEShapeCircle(30), (uint)IconID.Burn, ActionID.MakeSpell(AID.Burn), 8.2f) { CenterAtTarget = true; } -} +class Burn(BossModule module) : Components.BaitAwayIcon(module, new AOEShapeCircle(30), (uint)IconID.Burn, ActionID.MakeSpell(AID.Burn), 8.2f, true); [ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 761, NameID = 9751, PlanLevel = 80)] public class DRS3(WorldState ws, Actor primary) : BossModule(ws, primary, new(82, 138), new ArenaBoundsCircle(29.5f)) diff --git a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel2Lyon/Duel2LyonGenericAttacks.cs b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel2Lyon/Duel2LyonGenericAttacks.cs index c983b662e7..48df62603c 100644 --- a/BossMod/Modules/Shadowbringers/Foray/Duel/Duel2Lyon/Duel2LyonGenericAttacks.cs +++ b/BossMod/Modules/Shadowbringers/Foray/Duel/Duel2Lyon/Duel2LyonGenericAttacks.cs @@ -214,16 +214,11 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) class SpitefulFlameRect(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SpitefulFlame2), new AOEShapeRect(80, 2)); -class DynasticFlame : Components.BaitAwayTethers +class DynasticFlame(BossModule module) : Components.BaitAwayTethers(module, new AOEShapeCircle(10), (uint)TetherID.fireorbs, centerAtTarget: true) { private ulong target; private int orbcount; - public DynasticFlame(BossModule module) : base(module, new AOEShapeCircle(10), (uint)TetherID.fireorbs) - { - CenterAtTarget = true; - } - public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.DynasticFlame1) @@ -233,13 +228,13 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { base.AddAIHints(slot, actor, assignment, hints); - if (target == actor.InstanceID && CurrentBaits.Count > 0) + if (target == actor.InstanceID && CurrentBaits.Count != 0) hints.AddForbiddenZone(ShapeDistance.Circle(Module.Center, 18)); } public override void AddHints(int slot, Actor actor, TextHints hints) { - if (target == actor.InstanceID && CurrentBaits.Count > 0) + if (target == actor.InstanceID && CurrentBaits.Count != 0) hints.Add("Go to the edge and run until 4 orbs are spawned"); } diff --git a/BossMod/Modules/Stormblood/Dungeon/D04DomaCastle/D041MagitekRearguard.cs b/BossMod/Modules/Stormblood/Dungeon/D04DomaCastle/D041MagitekRearguard.cs index 023c741938..b3a351ca33 100644 --- a/BossMod/Modules/Stormblood/Dungeon/D04DomaCastle/D041MagitekRearguard.cs +++ b/BossMod/Modules/Stormblood/Dungeon/D04DomaCastle/D041MagitekRearguard.cs @@ -20,8 +20,11 @@ public enum AID : uint class MagitekRay(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MagitekRay), new AOEShapeRect(45.9f, 1)); class CermetPile(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.CermetPile), new AOEShapeRect(43.5f, 3), activeWhileCasting: false); -class GarleanFire(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.GarleanFire), new AOEShapeCircle(6)); -class SelfDetonate(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.SelfDetonate), new AOEShapeCircle(6)); + +abstract class Circle6(BossModule module, AID aid) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCircle(6)); +class GarleanFire(BossModule module) : Circle6(module, AID.GarleanFire); +class SelfDetonate(BossModule module) : Circle6(module, AID.SelfDetonate); + class RearguardMine(BossModule module) : Components.PersistentVoidzone(module, 0.9f, m => m.Enemies(OID.RearguardMine).Where(x => !x.IsDead), 10); class D041MagitekRearguardStates : StateMachineBuilder diff --git a/BossMod/Pathfinding/NavigationDecision.cs b/BossMod/Pathfinding/NavigationDecision.cs index 27d0727bf8..15c2c97928 100644 --- a/BossMod/Pathfinding/NavigationDecision.cs +++ b/BossMod/Pathfinding/NavigationDecision.cs @@ -30,6 +30,7 @@ public enum Decision UptimeToPositional, UptimeBlocked, BackIntoBounds, + Casting, Optimal } @@ -149,7 +150,10 @@ public static NavigationDecision Build(Context ctx, WorldState ws, AIHints hints return FindPathFromImminent(ctx.ThetaStar, ctx.Map, player.Position, playerSpeed); } } - + if (player.CastInfo != null) + { + return new() { Destination = null, LeewaySeconds = 0, TimeToGoal = 0, Map = ctx.Map, DecisionType = Decision.Casting }; + } // we're safe, see if we can improve our position if (targetPos != null) { diff --git a/BossMod/Replay/Analysis/AbilityInfo.cs b/BossMod/Replay/Analysis/AbilityInfo.cs index f210e902e0..3ba01bb7a2 100644 --- a/BossMod/Replay/Analysis/AbilityInfo.cs +++ b/BossMod/Replay/Analysis/AbilityInfo.cs @@ -230,6 +230,7 @@ public KnockbackAnalysis(List infos) KnockbackDirection.SourceForward => Knockback.Kind.DirForward, KnockbackDirection.SourceRight => Knockback.Kind.DirRight, KnockbackDirection.SourceLeft => Knockback.Kind.DirLeft, + KnockbackDirection.AwayFromSource2 => Knockback.Kind.AwayFromOrigin, _ => Knockback.Kind.None } : Knockback.Kind.None; AddPoint(i, target, (kbData?.Distance ?? 0) + eff.Param0, kind); @@ -237,6 +238,7 @@ public KnockbackAnalysis(List infos) break; case ActionEffectType.Attract1: case ActionEffectType.Attract2: + var attrData = Service.LuminaRow(eff.Value); AddPoint(i, target, attrData?.MaxDistance ?? 0, Knockback.Kind.TowardsOrigin); hasKnockbacks = true; diff --git a/BossMod/Replay/Visualization/OpList.cs b/BossMod/Replay/Visualization/OpList.cs index 6ccf9691b0..75185fb6a4 100644 --- a/BossMod/Replay/Visualization/OpList.cs +++ b/BossMod/Replay/Visualization/OpList.cs @@ -14,7 +14,8 @@ class OpList(Replay replay, Replay.Encounter? enc, BossModuleRegistry.Info? modu 0x2630, 0x2611, 0x2610, 0x2617, 0x2608, 0x2613, 0x2618, 0x2609, 0x261A, 0x262F, 0x2609, 0x2614, 0x2664, 0x2668, 0x2619, 0x2631, 0x2632, 0x260A, 0x2616, 0x2667, 0x2E7F, 0x2F33, 0x2F32, 0x2F38, 0x2E80, 0x2E82, 0x2E81, 0x2F36, 0x2E7D, 0x2F35, 0x2EB0, 0x2F31, 0x2F37, 0x2E7C, 0x2E7B, 0x2EAE, 0x2F3A, 0x2F30, 0x2E7E, 0x2EAF, 0x428B, 0x44B8, 0x43D2, 0x43D1, 0x41FD, 0x42A4, 0x41C5, 0x30B7, 0x4021, 0x4019, 0x401C, 0x401B, 0x401F, 0x40FB, 0x4105, 0x401D, 0x4102, 0x4629, 0x4628, 0x4631, - 0x4630, 0x46D6, 0xF5B, 0xF5C]; + 0x4630, 0x46D6, 0xF5B, 0xF5C, 0x2E20, 0x2E21, 0x318A, 0x2E1E, 0x3346, 0x3353, 0x31D4, 0x3345, 0x3355, 0x3326, 0x3344, 0x31B1, 0x3343, 0x1EB165, 0x1EB166, + 0x1EB167, 0x1EB168]; private readonly HashSet _filteredActions = []; private readonly HashSet _filteredStatuses = []; private readonly HashSet _filteredDirectorUpdateTypes = []; diff --git a/BossMod/Replay/Visualization/ReplayDetailsWindow.cs b/BossMod/Replay/Visualization/ReplayDetailsWindow.cs index 7de8ff12fb..e178f02f50 100644 --- a/BossMod/Replay/Visualization/ReplayDetailsWindow.cs +++ b/BossMod/Replay/Visualization/ReplayDetailsWindow.cs @@ -424,6 +424,8 @@ private void DrawAllActorsTable() ImGui.TableHeadersRow(); foreach (var actor in _player.WorldState.Actors) { + if (OpList.BoringOIDs.Contains(actor.OID) && !_player.WorldState.Party.WithoutSlot().Contains(actor)) // TODO: reconsider? + continue; ImGui.PushID((int)actor.InstanceID); ImGui.TableNextRow(); DrawCommonColumns(actor); diff --git a/BossMod/Util/Polygon.cs b/BossMod/Util/Polygon.cs index c5c6bfb2a3..8d0ccf7d36 100644 --- a/BossMod/Util/Polygon.cs +++ b/BossMod/Util/Polygon.cs @@ -118,87 +118,87 @@ public bool Contains(WDir p) var original = Interlocked.CompareExchange(ref _edgeBuckets, newEdgeBuckets, null); edgeBuckets = original ?? newEdgeBuckets; - } - if (!InSimplePolygon(p, edgeBuckets.ExteriorEdgeBuckets)) - return false; + static ContourEdgeBuckets BuildEdgeBucketsForContour(ReadOnlySpan contour) + { + float minY = float.MaxValue, maxY = float.MinValue; + var count = contour.Length; - for (var i = 0; i < edgeBuckets.HoleEdgeBuckets.Length; ++i) - { - if (InSimplePolygon(p, edgeBuckets.HoleEdgeBuckets[i])) - return false; - } - return true; - } + for (var i = 0; i < count; ++i) + { + var y = contour[i].Z; + if (y < minY) + minY = y; + if (y > maxY) + maxY = y; + } - private static bool InSimplePolygon(WDir p, ContourEdgeBuckets buckets) - { - float x = p.X, y = p.Z; - var bucketIndex = (int)((y - buckets.MinY) * buckets.InvBucketHeight); - if ((uint)bucketIndex >= BucketCount) - return false; - var edges = buckets.EdgeBuckets[bucketIndex]; - var inside = false; - for (var i = 0; i < edges.Length; ++i) - { - var edge = edges[i]; - if ((edge.y0 > y) != (edge.y1 > y) && x < edge.x0 + edge.slopeX * (y - edge.y0)) - { - inside = !inside; - } - } - return inside; - } + var invBucketHeight = BucketCount / (maxY - minY + Epsilon); - private static ContourEdgeBuckets BuildEdgeBucketsForContour(ReadOnlySpan contour) - { - float minY = float.MaxValue, maxY = float.MinValue; - var count = contour.Length; + var edgeBucketsArray = new List[BucketCount]; + for (var b = 0; b < BucketCount; ++b) + { + edgeBucketsArray[b] = []; + } - for (var i = 0; i < count; ++i) - { - var y = contour[i].Z; - if (y < minY) - minY = y; - if (y > maxY) - maxY = y; - } + var prev = contour[^1]; + for (var i = 0; i < count; ++i) + { + var curr = contour[i]; + var edge = new Edges(prev.X, prev.Z, curr.X, curr.Z); - var invBucketHeight = BucketCount / (maxY - minY + Epsilon); + var bucketStart = (int)((Math.Min(edge.y0, edge.y1) - minY) * invBucketHeight); + var bucketEnd = (int)((Math.Max(edge.y0, edge.y1) - minY) * invBucketHeight); - var edgeBucketsArray = new List[BucketCount]; - for (var b = 0; b < BucketCount; ++b) - { - edgeBucketsArray[b] = []; - } + bucketStart = Math.Clamp(bucketStart, 0, BucketCount - 1); + bucketEnd = Math.Clamp(bucketEnd, 0, BucketCount - 1); - var prev = contour[^1]; - for (var i = 0; i < count; ++i) - { - var curr = contour[i]; - var edge = new Edges(prev.X, prev.Z, curr.X, curr.Z); + for (var b = bucketStart; b <= bucketEnd; ++b) + { + edgeBucketsArray[b].Add(edge); + } - var bucketStart = (int)((Math.Min(edge.y0, edge.y1) - minY) * invBucketHeight); - var bucketEnd = (int)((Math.Max(edge.y0, edge.y1) - minY) * invBucketHeight); + prev = curr; + } - bucketStart = Math.Clamp(bucketStart, 0, BucketCount - 1); - bucketEnd = Math.Clamp(bucketEnd, 0, BucketCount - 1); + var edgeBuckets = new Edges[BucketCount][]; + for (var b = 0; b < BucketCount; ++b) + { + edgeBuckets[b] = [.. edgeBucketsArray[b]]; + } - for (var b = bucketStart; b <= bucketEnd; ++b) - { - edgeBucketsArray[b].Add(edge); + return new(edgeBuckets, minY, invBucketHeight); } - - prev = curr; } - var edgeBuckets = new Edges[BucketCount][]; - for (var b = 0; b < BucketCount; ++b) + if (!InSimplePolygon(p, edgeBuckets.ExteriorEdgeBuckets)) + return false; + + for (var i = 0; i < edgeBuckets.HoleEdgeBuckets.Length; ++i) { - edgeBuckets[b] = [.. edgeBucketsArray[b]]; + if (InSimplePolygon(p, edgeBuckets.HoleEdgeBuckets[i])) + return false; } + return true; - return new(edgeBuckets, minY, invBucketHeight); + static bool InSimplePolygon(WDir p, ContourEdgeBuckets buckets) + { + float x = p.X, y = p.Z; + var bucketIndex = (int)((y - buckets.MinY) * buckets.InvBucketHeight); + if ((uint)bucketIndex >= BucketCount) + return false; + var edges = buckets.EdgeBuckets[bucketIndex]; + var inside = false; + for (var i = 0; i < edges.Length; ++i) + { + var edge = edges[i]; + if ((edge.y0 > y) != (edge.y1 > y) && x < edge.x0 + edge.slopeX * (y - edge.y0)) + { + inside = !inside; + } + } + return inside; + } } private readonly struct Edges @@ -564,6 +564,26 @@ public PolygonWithHolesDistanceFunction(WPos origin, RelSimplifiedComplexPolygon } } _spatialIndex = new(_edges); + + static Edge[] GetEdges(ReadOnlySpan vertices, WPos origin) + { + var count = vertices.Length; + + if (count == 0) + return []; + + var edges = new Edge[count]; + + var prev = vertices[count - 1]; + for (var i = 0; i < count; ++i) + { + var curr = vertices[i]; + edges[i] = new(origin.X + prev.X, origin.Z + prev.Z, curr.X - prev.X, curr.Z - prev.Z); + prev = curr; + } + + return edges; + } } public readonly float Distance(WPos p) @@ -586,24 +606,4 @@ public readonly float Distance(WPos p) var minDistance = MathF.Sqrt(minDistanceSq); return isInside ? -minDistance : minDistance; } - - private static Edge[] GetEdges(ReadOnlySpan vertices, WPos origin) - { - var count = vertices.Length; - - if (count == 0) - return []; - - var edges = new Edge[count]; - - var prev = vertices[count - 1]; - for (var i = 0; i < count; ++i) - { - var curr = vertices[i]; - edges[i] = new(origin.X + prev.X, origin.Z + prev.Z, curr.X - prev.X, curr.Z - prev.Z); - prev = curr; - } - - return edges; - } }