From 25375f198321f88e1ed72280f5db47d156eb29f8 Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:32:15 +0100 Subject: [PATCH] some cleanup --- BossMod/BossModule/StateMachine.cs | 8 +- .../D02WorqorZormor/D021RyoqorTerteh.cs | 37 ++++-- .../D05Origenics/D050OrigenicsAerostat.cs | 17 ++- .../Dungeon/D05Origenics/D052Deceiver.cs | 107 +++++++++++++----- .../Dungeon/D05Origenics/D053Ambrose.cs | 72 ++++++++---- .../Savage/M01SBlackCat/M01SBlackCatEnums.cs | 1 + .../Savage/M01SBlackCat/OneTwoPaw.cs | 4 +- .../Trial/T01Valigarmanda/T01Valigarmanda.cs | 6 +- .../Endwalker/Alliance/A30Trash/A30Trash1.cs | 30 ++++- .../Dungeon/D11Antitower/D113Calcabrina.cs | 20 +++- BossMod/Pathfinding/Map.cs | 38 ++++--- BossMod/Pathfinding/MapVisualizer.cs | 6 +- BossMod/Pathfinding/ThetaStar.cs | 31 +++-- BossMod/Util/ShapeDistance.cs | 14 ++- 14 files changed, 278 insertions(+), 113 deletions(-) diff --git a/BossMod/BossModule/StateMachine.cs b/BossMod/BossModule/StateMachine.cs index f91397bd8a..221a08e3bc 100644 --- a/BossMod/BossModule/StateMachine.cs +++ b/BossMod/BossModule/StateMachine.cs @@ -57,7 +57,7 @@ public class Phase(State initialState, string name, float expectedDuration = -1) public PhaseHint Hint = PhaseHint.None; // special flags for phase } - public List Phases { get; private init; } = phases; + public readonly List Phases = phases; private DateTime _curTime; private DateTime _activation; @@ -68,14 +68,14 @@ public class Phase(State initialState, string name, float expectedDuration = -1) public float TimeSinceTransition => (float)(_curTime - _lastTransition).TotalSeconds; public float TimeSinceTransitionClamped => Math.Min(TimeSinceTransition, ActiveState?.Duration ?? 0); - public int ActivePhaseIndex { get; private set; } = -1; + public int ActivePhaseIndex = -1; public Phase? ActivePhase => Phases.ElementAtOrDefault(ActivePhaseIndex); - public State? ActiveState { get; private set; } + public State? ActiveState; public void Start(DateTime now) { _activation = _curTime = now; - if (Phases.Count > 0) + if (Phases.Count != 0) TransitionToPhase(0); } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs index 617655d195..8eea38bd9b 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D02WorqorZormor/D021RyoqorTerteh.cs @@ -62,12 +62,22 @@ class IceScreamFrozenSwirl(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeRect rect = new(20, 10); private static readonly AOEShapeCircle circle = new(15); - private readonly List _aoesCircle = []; - private readonly List _aoesRect = []; - private readonly HashSet circleAOE = []; - private readonly HashSet rectAOE = []; + private readonly List _aoesCircle = new(4), _aoesRect = new(4); + private readonly List circleAOE = new(4), rectAOE = new(4); - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoesCircle.Take(2).Concat(_aoesRect.Take(2)); + public override IEnumerable ActiveAOEs(int slot, Actor actor) + { + var countCircle = _aoesCircle.Count; + var countRect = _aoesRect.Count; + if (countCircle == 0 && countRect == 0) + return []; + var result = new List(4); + for (var i = 0; i < 2 && i < countCircle; ++i) + result.Add(_aoesCircle[i]); + for (var i = 0; i < 2 && i < countRect; ++i) + result.Add(_aoesRect[i]); + return result; + } public override void OnActorCreated(Actor actor) { @@ -89,10 +99,10 @@ public override void OnTethered(Actor source, ActorTetherInfo tether) circleAOE.Remove(source); if (_aoesCircle.Count == 2) { - foreach (var e in circleAOE) - _aoesCircle.Add(new(circle, e.Position, default, activation1)); + for (var i = 0; i < circleAOE.Count; ++i) + _aoesCircle.Add(new(circle, circleAOE[i].Position, default, activation1)); circleAOE.Clear(); - _aoesCircle.SortBy(x => x.Activation); + _aoesCircle.Sort((x, y) => x.Activation.CompareTo(y.Activation)); } } else if (rectAOE.Contains(source)) @@ -101,10 +111,13 @@ public override void OnTethered(Actor source, ActorTetherInfo tether) rectAOE.Remove(source); if (_aoesRect.Count == 2) { - foreach (var e in rectAOE) + for (var i = 0; i < rectAOE.Count; ++i) + { + var e = rectAOE[i]; _aoesRect.Add(new(rect, e.Position, e.Rotation, activation1)); + } rectAOE.Clear(); - _aoesRect.SortBy(x => x.Activation); + _aoesRect.Sort((x, y) => x.Activation.CompareTo(y.Activation)); } } } @@ -112,9 +125,9 @@ public override void OnTethered(Actor source, ActorTetherInfo tether) public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (_aoesRect.Count > 0 && (AID)spell.Action.ID == AID.IceScream) + if (_aoesRect.Count != 0 && (AID)spell.Action.ID == AID.IceScream) _aoesRect.RemoveAt(0); - else if (_aoesCircle.Count > 0 && (AID)spell.Action.ID == AID.FrozenSwirl) + else if (_aoesCircle.Count != 0 && (AID)spell.Action.ID == AID.FrozenSwirl) _aoesCircle.RemoveAt(0); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D050OrigenicsAerostat.cs b/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D050OrigenicsAerostat.cs index 1b6cf1e226..a99d73faf9 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D050OrigenicsAerostat.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D050OrigenicsAerostat.cs @@ -30,8 +30,17 @@ public D050OrigenicsAerostatStates(D050OrigenicsAerostat module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Aerostat2).Concat([module.PrimaryActor]).Concat(module.Enemies(OID.OrigenicsSentryS9)) - .Concat(module.Enemies(OID.OrigenicsSentryS92)).Concat(module.Enemies(OID.OrigenicsSentryG10)).All(e => e.IsDeadOrDestroyed); + .Raw.Update = () => + { + var enemies = module.Enemies(D050OrigenicsAerostat.Trash); + for (var i = 0; i < enemies.Count; ++i) + { + var e = enemies[i]; + if (!e.IsDeadOrDestroyed) + return false; + } + return true; + }; } } @@ -40,10 +49,10 @@ public class D050OrigenicsAerostat(WorldState ws, Actor primary) : BossModule(ws { private static readonly ArenaBoundsComplex arena = new([new Polygon(new(-116, -80), 14.5f, 6, 30.Degrees()), new Rectangle(new(-88, -80), 20, 5.5f), new Polygon(new(-60, -80), 14.5f, 6, 30.Degrees()), new Rectangle(new(-144, -80), 20, 5.5f)]); + public static readonly uint[] Trash = [(uint)OID.Boss, (uint)OID.Aerostat2, (uint)OID.OrigenicsSentryS9, (uint)OID.OrigenicsSentryS92, (uint)OID.OrigenicsSentryG10]; protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actor(PrimaryActor); - Arena.Actors(Enemies(OID.Aerostat2).Concat(Enemies(OID.OrigenicsSentryS9)).Concat(Enemies(OID.OrigenicsSentryS92)).Concat(Enemies(OID.OrigenicsSentryG10))); + Arena.Actors(Enemies(Trash)); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D052Deceiver.cs b/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D052Deceiver.cs index 2570499440..c9c5912348 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D052Deceiver.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D052Deceiver.cs @@ -11,7 +11,7 @@ public enum OID : uint public enum AID : uint { - AutoAttack = 870, // Boss->player, no cast, single-target + AutoAttack1 = 870, // Boss->player, no cast, single-target AutoAttack2 = 873, // OrigenicsSentryG92->player, no cast, single-target Teleport = 36362, // Boss->location, no cast, single-target @@ -111,7 +111,7 @@ class Electray(BossModule module) : Components.SpreadFromCastTargets(module, Act class Surge(BossModule module) : Components.Knockback(module) { - private readonly List _sources = []; + public readonly List SourcesList = new(2); private const float XWest = -187.5f, XEast = -156.5f; private const int ZRow1 = -122, ZRow2 = -132, ZRow3 = -142, ZRow4 = -152, ZRow5 = -162; private static readonly WDir offset = new(4, 0); @@ -124,16 +124,18 @@ class Surge(BossModule module) : Components.Knockback(module) private static readonly SafeWall[] walls2B1C = [new(new(XWest, ZRow4), new(XWest, ZRow5)), new(new(XWest, ZRow2), new(XWest, ZRow3)), new(new(XEast, ZRow3), new(XEast, ZRow4)), new(new(XEast, ZRow1), new(XEast, ZRow2))]; private static readonly AOEShapeCone _shape = new(60, 90.Degrees()); + private Func? distance; - public override IEnumerable Sources(int slot, Actor actor) => _sources; + public override IEnumerable Sources(int slot, Actor actor) => SourcesList; public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.Surge) { - var activation = Module.CastFinishAt(spell, 0.8f); - _sources.Add(new(caster.Position, 30, activation, _shape, spell.Rotation + Angle.AnglesCardinals[3], Kind.DirForward, default, GetActiveSafeWalls())); - _sources.Add(new(caster.Position, 30, activation, _shape, spell.Rotation + Angle.AnglesCardinals[0], Kind.DirForward, default, GetActiveSafeWalls())); + var activation = Module.CastFinishAt(spell); + var safewalls = GetActiveSafeWalls(); + SourcesList.Add(new(caster.Position, 30, activation, _shape, spell.Rotation + Angle.AnglesCardinals[3], Kind.DirForward, default, safewalls)); + SourcesList.Add(new(caster.Position, 30, activation, _shape, spell.Rotation + Angle.AnglesCardinals[0], Kind.DirForward, default, safewalls)); } } @@ -160,50 +162,96 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) { if ((AID)spell.Action.ID == AID.Surge) { - _sources.Clear(); + SourcesList.Clear(); + distance = null; ++NumCasts; } } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var source = Sources(slot, actor).FirstOrDefault(); - if (source != default) + if (SourcesList.Count != 0) { - var forbidden = new List>(); - var safewalls = GetActiveSafeWalls(); - for (var i = 0; i < safewalls.Length; ++i) - forbidden.Add(ShapeDistance.InvertedRect(new(Arena.Center.X, safewalls[i].Vertex1.Z - 5), safewalls[i].Vertex1.X == XWest ? -offset : offset, 10, default, 20)); - hints.AddForbiddenZone(p => forbidden.Max(f => f(p)), source.Activation); + if (distance == null) + { + var safewalls = GetActiveSafeWalls(); + var forbidden = new List>(4); + + var centerX = Arena.Center.X; + for (var i = 0; i < 4; ++i) + { + var safeWall = safewalls[i]; + forbidden.Add(ShapeDistance.InvertedRect(new(centerX, safeWall.Vertex1.Z - 5), safeWall.Vertex1.X == XWest ? -offset : offset, 10, default, 20)); + } + distance = p => + { + var maxDistance = float.MinValue; + for (var i = 0; i < 4; ++i) + { + var distance = forbidden[i](p); + if (distance > maxDistance) + { + maxDistance = distance; + } + } + return maxDistance; + }; + } + hints.AddForbiddenZone(distance, SourcesList[0].Activation); } } } class SurgeHint(BossModule module) : Components.GenericAOEs(module) { - private const string Risk2Hint = "Walk into safespot for knockback!"; - private const string StayHint = "Wait inside safespot for knockback!"; + private const string Hint = "Wait inside safespot for knockback!"; private static readonly AOEShapeRect rect = new(15.5f, 5); + private readonly List _hints = new(4); + private readonly Surge _kb = module.FindComponent()!; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => _hints; - public override IEnumerable ActiveAOEs(int slot, Actor actor) + public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - var component = Module.FindComponent()!.Sources(slot, actor).Any(); - var activeSafeWalls = Module.FindComponent()!.GetActiveSafeWalls(); - if (component) - for (var i = 0; i < activeSafeWalls.Length; ++i) - yield return new(rect, new(Arena.Center.X, activeSafeWalls[i].Vertex1.Z - 5), activeSafeWalls[i].Vertex1.X == -187.5f ? Angle.AnglesCardinals[0] : Angle.AnglesCardinals[3], default, Colors.SafeFromAOE, false); + if ((AID)spell.Action.ID == AID.Surge) + { + var activeSafeWalls = _kb.GetActiveSafeWalls(); + var centerX = Arena.Center.X; + for (var i = 0; i < 4; ++i) + { + var safewall = activeSafeWalls[i].Vertex1; + _hints.Add(new(rect, new(centerX, safewall.Z - 5), safewall.X == -187.5f ? Angle.AnglesCardinals[0] : Angle.AnglesCardinals[3], default, Colors.SafeFromAOE, false)); + } + } + } + + public override void OnCastFinished(Actor caster, ActorCastInfo spell) + { + if ((AID)spell.Action.ID == AID.Surge) + _hints.Clear(); } public override void AddHints(int slot, Actor actor, TextHints hints) { - base.AddHints(slot, actor, hints); - var activeSafespot = ActiveAOEs(slot, actor).Where(c => c.Shape == rect).ToList(); - if (activeSafespot.Count != 0) + AOEInstance[] activeSafespot = [.. ActiveAOEs(slot, actor)]; + var len = activeSafespot.Length; + if (len != 0) { - if (!activeSafespot.Any(c => c.Check(actor.Position))) - hints.Add(Risk2Hint); + var isPositionSafe = false; + for (var i = 0; i < len; ++i) + { + if (activeSafespot[i].Check(actor.Position)) + { + isPositionSafe = true; + break; + } + } + if (!isPositionSafe) + { + hints.Add(Hint); + } else - hints.Add(StayHint, false); + hints.Add(Hint, false); } } } @@ -231,6 +279,7 @@ public class D052Deceiver(WorldState ws, Actor primary) : BossModule(ws, primary protected override void DrawEnemies(int pcSlot, Actor pc) { Arena.Actor(PrimaryActor); - Arena.Actors(Enemies(OID.OrigenicsSentryG92).Concat(Enemies(OID.OrigenicsSentryG91))); + Arena.Actors(Enemies(OID.OrigenicsSentryG92)); + Arena.Actors(Enemies(OID.OrigenicsSentryG91)); } } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D053Ambrose.cs b/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D053Ambrose.cs index d606e297fb..0625c2addd 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D053Ambrose.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D05Origenics/D053Ambrose.cs @@ -39,7 +39,7 @@ public enum AID : uint ElectrolanceAssimilationVisual = 36430, // Boss->self, 0.5s cast, single-target ElectrolanceAssimilation = 36431, // Helper->self, 1.0s cast, range 33 width 10 rect - WhorlOfTheMind = 36438, // Helper->player, 5.0s cast, range 5 circle + WhorlOfTheMind = 36438 // Helper->player, 5.0s cast, range 5 circle } class PsychicWaveArenaChange(BossModule module) : Components.GenericAOEs(module) @@ -48,17 +48,18 @@ class PsychicWaveArenaChange(BossModule module) : Components.GenericAOEs(module) private AOEInstance? _aoe; public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if ((AID)spell.Action.ID == AID.PsychicWave && Module.Arena.Bounds == D053Ambrose.StartingBounds) - _aoe = new(rect, Module.Center, default, Module.CastFinishAt(spell, 0.7f)); + if ((AID)spell.Action.ID == AID.PsychicWave && Arena.Bounds == D053Ambrose.StartingBounds) + _aoe = new(rect, Arena.Center, default, Module.CastFinishAt(spell, 0.7f)); } public override void OnEventEnvControl(byte index, uint state) { if (state == 0x00020001 && index == 0x28) { - Module.Arena.Bounds = D053Ambrose.DefaultBounds; + Arena.Bounds = D053Ambrose.DefaultBounds; _aoe = null; } } @@ -72,12 +73,13 @@ class ExtrasensoryExpulsion(BossModule module) : Components.Knockback(module, ma private const float QuarterWidth = 7.5f; private const float QuarterHeight = 9.75f; private const float HalfHeight = 19.5f; - public readonly List<(WPos, Angle)> Data = []; + public readonly List<(WPos, Angle)> Data = new(2); public DateTime Activation; - private readonly List _sources = []; + private readonly List _sources = new(4); private static readonly AOEShapeRect rectNS = new(HalfHeight, QuarterWidth); private static readonly AOEShapeRect rectEW = new(15, QuarterHeight); private static readonly Angle[] angles = [-0.003f.Degrees(), -180.Degrees(), -89.982f.Degrees(), 89.977f.Degrees()]; + private Func? distance; public override IEnumerable Sources(int slot, Actor actor) => _sources; @@ -123,20 +125,43 @@ private void AddSourceAndData(WDir direction, AOEShapeRect shape, Angle angle) public override void Update() { - if (Data.Count > 0 && WorldState.CurrentTime > Activation) + if (Data.Count != 0 && WorldState.CurrentTime > Activation) { _sources.Clear(); Data.Clear(); ++NumCasts; + distance = null; } } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (Sources(slot, actor).Any() || Activation > WorldState.CurrentTime) // 0.8s delay to wait for action effect + if (_sources.Count != 0) { - var forbiddenZones = Data.Select(w => ShapeDistance.InvertedRect(w.Item1, w.Item2, HalfHeight - 0.5f, 0, QuarterWidth)).ToList(); - hints.AddForbiddenZone(p => forbiddenZones.Max(f => f(p)), Activation.AddSeconds(-0.8f)); + if (distance == null) + { + var forbidden = new List>(2); + + for (var i = 0; i < 2; ++i) + { + var w = Data[i]; + forbidden.Add(ShapeDistance.InvertedRect(w.Item1, w.Item2, HalfHeight - 0.5f, 0, QuarterWidth)); + } + distance = p => + { + var maxDistance = float.MinValue; + for (var i = 0; i < 2; ++i) + { + var distance = forbidden[i](p); + if (distance > maxDistance) + { + maxDistance = distance; + } + } + return maxDistance; + }; + } + hints.AddForbiddenZone(distance, _sources[0].Activation); } } } @@ -150,6 +175,7 @@ class OverwhelmingCharge(BossModule module) : Components.GenericAOEs(module) private static readonly AOEShapeCone cone = new(26, 90.Degrees()); private static readonly AOEShapeRect rect = new(19, 7.5f); private AOEInstance _aoe; + private static readonly Angle a180 = 180.Degrees(); public override IEnumerable ActiveAOEs(int slot, Actor actor) { @@ -160,7 +186,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) yield return _aoe with { Risky = !componentActive }; if (componentActive) { - var safezone = component.Data.FirstOrDefault(x => _aoe.Rotation.AlmostEqual(x.Item2 + 180.Degrees(), Angle.DegToRad)); + var safezone = component.Data.FirstOrDefault(x => _aoe.Rotation.AlmostEqual(x.Item2 + a180, Angle.DegToRad)); yield return new(rect, safezone.Item1, safezone.Item2, component.Activation, Colors.SafeFromAOE, false); } } @@ -186,7 +212,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme var component = Module.FindComponent()!.Sources(slot, actor).Any() || Module.FindComponent()!.Activation > WorldState.CurrentTime; var aoe = ActiveAOEs(slot, actor).FirstOrDefault(); if (component && ActiveAOEs(slot, actor).Any()) - hints.AddForbiddenZone(aoe.Shape, aoe.Origin, aoe.Rotation + 180.Degrees(), aoe.Activation); + hints.AddForbiddenZone(aoe.Shape, aoe.Origin, aoe.Rotation + a180, aoe.Activation); else base.AddAIHints(slot, actor, assignment, hints); } @@ -211,14 +237,18 @@ class WhorlOfTheMind(BossModule module) : Components.SpreadFromCastTargets(modul class Rush(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeRect rect = new(33, 5); - private readonly List _aoes = []; + private readonly List _aoes = new(7); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_aoes.Count > 0) - yield return _aoes[0] with { Color = Colors.Danger }; - for (var i = 1; i < _aoes.Count; ++i) - yield return _aoes[i]; + var count = _aoes.Count; + if (count == 0) + yield break; + for (var i = 0; i < count; ++i) + { + var aoe = _aoes[i]; + yield return i == 0 ? count > 1 ? aoe with { Color = Colors.Danger } : aoe : aoe; + } } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -227,16 +257,17 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) { var activation = Module.CastFinishAt(spell, 6.8f); var dir = spell.LocXZ - caster.Position; + if (_aoes.Count < 7) _aoes.Add(new(new AOEShapeRect(dir.Length(), 5), caster.Position, Angle.FromDirection(dir), activation)); - else if (_aoes.Count == 7) + else _aoes.Add(new(rect, new(190, 19.5f), -180.Degrees(), activation)); } } public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if (_aoes.Count > 0 && (AID)spell.Action.ID is AID.Rush or AID.ElectrolanceAssimilation) + if (_aoes.Count != 0 && (AID)spell.Action.ID is AID.Rush or AID.ElectrolanceAssimilation) _aoes.RemoveAt(0); } } @@ -268,6 +299,7 @@ public class D053Ambrose(WorldState ws, Actor primary) : BossModule(ws, primary, protected override void DrawEnemies(int pcSlot, Actor pc) { Arena.Actor(PrimaryActor); - Arena.Actors(Enemies(OID.Superfluity).Concat(Enemies(OID.OrigenicsEyeborg))); + Arena.Actors(Enemies(OID.Superfluity)); + Arena.Actors(Enemies(OID.OrigenicsEyeborg)); } } diff --git a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/M01SBlackCatEnums.cs b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/M01SBlackCatEnums.cs index 8d640ad35d..a346111c6d 100644 --- a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/M01SBlackCatEnums.cs +++ b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/M01SBlackCatEnums.cs @@ -14,6 +14,7 @@ public enum AID : uint { AutoAttack = 39152, // Boss->player, no cast, single-target Teleport = 37640, // Boss->location, no cast, single-target + BiscuitMaker = 38037, // Boss->player, 5.0s cast, single-target, tankbuster BiscuitMakerSecond = 38038, // Boss->player, no cast, single-target, tankbuster second hit BloodyScratch = 38036, // Boss->self, 5.0s cast, range 100 circle, raidwide diff --git a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs index d23ad5298d..652bcef47f 100644 --- a/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs +++ b/BossMod/Modules/Dawntrail/Savage/M01SBlackCat/OneTwoPaw.cs @@ -13,7 +13,7 @@ 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(); + var pounce = _pounce != null && _pounce.AOEs.Count != 0; for (var i = 0; i < count; ++i) { var aoe = _aoes[i]; @@ -38,7 +38,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) if (casts.Contains((AID)spell.Action.ID)) { ++NumCasts; - if (_aoes.Count > 0) + if (_aoes.Count != 0) _aoes.RemoveAt(0); } } diff --git a/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/T01Valigarmanda.cs b/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/T01Valigarmanda.cs index a256d89561..e8ade0ff35 100644 --- a/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/T01Valigarmanda.cs +++ b/BossMod/Modules/Dawntrail/Trial/T01Valigarmanda/T01Valigarmanda.cs @@ -53,7 +53,7 @@ class Eruption(BossModule module) : Components.LocationTargetedAOEs(module, Acti { public override void AddGlobalHints(GlobalHints hints) { - if (CurrentBaits.Count > 0) + if (CurrentBaits.Count != 0) hints.Add("Tankbuster cleave"); } } @@ -95,8 +95,10 @@ public T01ValigarmandaStates(BossModule module) : base(module) [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 832, NameID = 12854)] public class T01Valigarmanda(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsRect(20, 15)) { + private static readonly uint[] objects = [(uint)OID.IceBoulder, (uint)OID.FlameKissedBeacon, (uint)OID.GlacialBeacon, (uint)OID.ThunderousBeacon]; protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.IceBoulder).Concat(Enemies(OID.FlameKissedBeacon)).Concat(Enemies(OID.GlacialBeacon)).Concat(Enemies(OID.ThunderousBeacon)).Concat([PrimaryActor])); + Arena.Actor(PrimaryActor); + Arena.Actors(Enemies(objects), Colors.Object); } } diff --git a/BossMod/Modules/Endwalker/Alliance/A30Trash/A30Trash1.cs b/BossMod/Modules/Endwalker/Alliance/A30Trash/A30Trash1.cs index 02b443b334..96f82e34ca 100644 --- a/BossMod/Modules/Endwalker/Alliance/A30Trash/A30Trash1.cs +++ b/BossMod/Modules/Endwalker/Alliance/A30Trash/A30Trash1.cs @@ -11,6 +11,7 @@ public enum OID : uint public enum AID : uint { AutoAttack = 870, // Serpent/Triton->player, no cast, single-target + WaterIII = 35438, // Serpent->location, 4.0s cast, range 8 circle PelagicCleaver1 = 35439, // Triton->self, 5.0s cast, range 40 60-degree cone PelagicCleaver2 = 35852, // Triton->self, 5.0s cast, range 40 60-degree cone @@ -41,7 +42,17 @@ public A30Trash1States(A30Trash1 module) : base(module) // as soon as the last serpent dies, other adds are spawned; serpents are destroyed a bit later TrivialPhase() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Serpent).All(e => e.IsDeadOrDestroyed); + .Raw.Update = () => + { + var enemies = module.Enemies(OID.Serpent); + for (var i = 0; i < enemies.Count; ++i) + { + var e = enemies[i]; + if (!e.IsDeadOrDestroyed) + return false; + } + return true; + }; TrivialPhase(1) .ActivateOnEnter() .ActivateOnEnter() @@ -49,15 +60,28 @@ public A30Trash1States(A30Trash1 module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Serpent).Count == 0 && module.Enemies(OID.Triton).Concat(module.Enemies(OID.DivineSprite)).Concat(module.Enemies(OID.WaterSprite)).All(e => e.IsDeadOrDestroyed); + .Raw.Update = () => + { + var enemies = module.Enemies(A30Trash1.Trash); + for (var i = 0; i < enemies.Count; ++i) + { + var e = enemies[i]; + if (!e.IsDeadOrDestroyed) + return false; + } + return module.Enemies(OID.Serpent).Count == 0; + }; } } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "Malediktus, LTS", PrimaryActorOID = (uint)OID.Serpent, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 962, NameID = 12478, SortOrder = 1)] public class A30Trash1(WorldState ws, Actor primary) : BossModule(ws, primary, new(-800, -800), new ArenaBoundsCircle(20)) { + public static readonly uint[] Trash = [(uint)OID.Triton, (uint)OID.DivineSprite, (uint)OID.WaterSprite]; + protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.Serpent).Concat(Enemies(OID.Triton)).Concat(Enemies(OID.DivineSprite)).Concat(Enemies(OID.WaterSprite))); + Arena.Actors(Enemies(OID.Serpent)); + Arena.Actors(Enemies(Trash)); } } diff --git a/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D113Calcabrina.cs b/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D113Calcabrina.cs index 63f55d908a..272875de71 100644 --- a/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D113Calcabrina.cs +++ b/BossMod/Modules/Heavensward/Dungeon/D11Antitower/D113Calcabrina.cs @@ -124,19 +124,31 @@ public D113CalcabrinaStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(OID.Calcabrina).Concat(module.Enemies(OID.Boss)).Concat(module.Enemies(OID.Brina)).All(e => e.IsDeadOrDestroyed); + .Raw.Update = () => + { + var enemies = module.Enemies(D113Calcabrina.NpcDolls); + for (var i = 0; i < enemies.Count; ++i) + { + var e = enemies[i]; + if (!e.IsDeadOrDestroyed) + return false; + } + return true; + }; } } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 141, NameID = 4813)] public class D113Calcabrina(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) { - private static readonly ArenaBoundsComplex arena = new([new Polygon(new(232, -182), 19.5f * CosPI.Pi36th, 36)], [new Rectangle(new(252, -182), 20, 1.15f, 90.Degrees())]); + private static readonly ArenaBoundsComplex arena = new([new Polygon(new(232, -182), 19.5f * CosPI.Pi36th, 36)], [new Rectangle(new(252, -182), 1.15f, 20)]); + private static readonly uint[] playerDolls = [(uint)OID.CalcaPlayer1, (uint)OID.CalcaPlayer2, (uint)OID.BrinaPlayer1, (uint)OID.BrinaPlayer2]; + public static readonly uint[] NpcDolls = [(uint)OID.Boss, (uint)OID.Brina, (uint)OID.Calcabrina]; protected override void DrawEnemies(int pcSlot, Actor pc) { - Arena.Actors(Enemies(OID.Brina).Concat(Enemies(OID.Boss)).Concat(Enemies(OID.Calcabrina))); - Arena.Actors(Enemies(OID.CalcaPlayer1).Concat(Enemies(OID.CalcaPlayer2)).Concat(Enemies(OID.BrinaPlayer1)).Concat(Enemies(OID.BrinaPlayer2)), Colors.Vulnerable); + Arena.Actors(Enemies(NpcDolls)); + Arena.Actors(Enemies(playerDolls), Colors.Vulnerable); } protected override void CalculateModuleAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) diff --git a/BossMod/Pathfinding/Map.cs b/BossMod/Pathfinding/Map.cs index 7c06628e25..1ae5df37a1 100644 --- a/BossMod/Pathfinding/Map.cs +++ b/BossMod/Pathfinding/Map.cs @@ -19,19 +19,19 @@ public struct Pixel public int Priority; // >0 if goal } - public float Resolution { get; private set; } // pixel size, in world units - public int Width { get; private set; } // always even - public int Height { get; private set; } // always even + public float Resolution; // pixel size, in world units + public int Width; // always even + public int Height; // always even public Pixel[] Pixels = []; private const float Epsilon = 1e-5f; private const float HalfPixel = 0.5f; - public WPos Center { get; private set; } // position of map center in world units - public Angle Rotation { get; private set; } // rotation relative to world space (=> ToDirection() is equal to direction of local 'height' axis in world space) - private WDir LocalZDivRes { get; set; } + public WPos Center; // position of map center in world units + public Angle Rotation; // rotation relative to world space (=> ToDirection() is equal to direction of local 'height' axis in world space) + private WDir LocalZDivRes; - public float MaxG { get; private set; } // maximal 'maxG' value of all blocked pixels - public int MaxPriority { get; private set; } // maximal 'priority' value of all goal pixels + public float MaxG; // maximal 'maxG' value of all blocked pixels + public int MaxPriority; // maximal 'priority' value of all goal pixels //public float Speed = 6; // used for converting activation time into max g-value: num world units that player can move per second @@ -212,48 +212,55 @@ public int AddGoal(Func shape, float threshold, int minPriority, in return maxAdjustedPriority; } - public IEnumerable<(int x, int y, int priority)> Goals() + public List<(int x, int y, int priority)> Goals() { + var result = new List<(int x, int y, int priority)>(Width * Height); var index = 0; for (var y = 0; y < Height; ++y) { for (var x = 0; x < Width; ++x) { if (Pixels[index].MaxG == float.MaxValue) - yield return (x, y, Pixels[index].Priority); + result.Add((x, y, Pixels[index].Priority)); ++index; } } + return result; } - public IEnumerable<(int x, int y, WPos center)> EnumeratePixels() + public List<(int x, int y, WPos center)> EnumeratePixels() { + var result = new List<(int x, int y, WPos center)>(Width * Height); var rsq = Resolution * Resolution; // since we then multiply by _localZDivRes, end result is same as * res * rotation.ToDir() var dx = LocalZDivRes.OrthoL() * rsq; var dy = LocalZDivRes * rsq; var cy = Center + (-Width * HalfPixel + HalfPixel) * dx + (-Height * HalfPixel + HalfPixel) * dy; - for (var y = 0; y < Height; y++) + + for (var y = 0; y < Height; ++y) { var cx = cy; for (var x = 0; x < Width; ++x) { - yield return (x, y, cx); + result.Add((x, y, cx)); cx += dx; } cy += dy; } + return result; } // enumerate pixels along line starting from (x1, y1) to (x2, y2); first is not returned, last is returned - public IEnumerable<(int x, int y)> EnumeratePixelsInLine(int x1, int y1, int x2, int y2) + public List<(int x, int y)> EnumeratePixelsInLine(int x1, int y1, int x2, int y2) { + var estimatedLength = Math.Max(Math.Abs(x2 - x1), Math.Abs(y2 - y1)) + 1; + var result = new List<(int x, int y)>(estimatedLength); int dx = Math.Abs(x2 - x1), sx = x1 < x2 ? 1 : -1; int dy = -Math.Abs(y2 - y1), sy = y1 < y2 ? 1 : -1; int err = dx + dy, e2; while (true) { - yield return (x1, y1); + result.Add((x1, y1)); if (x1 == x2 && y1 == y2) break; e2 = 2 * err; @@ -268,5 +275,6 @@ public int AddGoal(Func shape, float threshold, int minPriority, in y1 += sy; } } + return result; } } diff --git a/BossMod/Pathfinding/MapVisualizer.cs b/BossMod/Pathfinding/MapVisualizer.cs index 36a0d830bf..5573b909f4 100644 --- a/BossMod/Pathfinding/MapVisualizer.cs +++ b/BossMod/Pathfinding/MapVisualizer.cs @@ -8,9 +8,9 @@ public class MapVisualizer public int GoalPriority; public WPos StartPos; public float ScreenPixelSize = 10; - public List<(WPos center, float ir, float or, Angle dir, Angle halfWidth)> Sectors = []; - public List<(WPos origin, float lenF, float lenB, float halfWidth, Angle dir)> Rects = []; - public List<(WPos origin, WPos dest)> Lines = []; + public readonly List<(WPos center, float ir, float or, Angle dir, Angle halfWidth)> Sectors = []; + public readonly List<(WPos origin, float lenF, float lenB, float halfWidth, Angle dir)> Rects = []; + public readonly List<(WPos origin, WPos dest)> Lines = []; private ThetaStar _pathfind; diff --git a/BossMod/Pathfinding/ThetaStar.cs b/BossMod/Pathfinding/ThetaStar.cs index 5f78e9fa54..cf265b7062 100644 --- a/BossMod/Pathfinding/ThetaStar.cs +++ b/BossMod/Pathfinding/ThetaStar.cs @@ -13,7 +13,7 @@ public struct Node } private Map _map = new(); - private readonly List<(int x, int y)> _goals = []; + private (int x, int y)[] _goals = []; private Node[] _nodes = []; private float[] _distances = []; private readonly List _openList = []; @@ -29,13 +29,13 @@ public struct Node private float _mapHalfResolution; // gMultiplier is typically inverse speed, which turns g-values into time - public void Start(Map map, IEnumerable<(int x, int y)> goals, (int x, int y) start, float gMultiplier) + public void Start(Map map, List<(int x, int y)> goals, (int x, int y) start, float gMultiplier) { lock (_lock) { _map = map; - _goals.Clear(); - _goals.AddRange(goals); + _goals = [.. goals]; + var numPixels = map.Width * map.Height; if (_nodes == null || _nodes.Length < numPixels) _nodes = new Node[numPixels]; @@ -71,14 +71,27 @@ public void Start(Map map, IEnumerable<(int x, int y)> goals, (int x, int y) sta } } - public void Start(Map map, int goalPriority, WPos startPos, float gMultiplier) => Start(map, map.Goals().Where(g => g.priority >= goalPriority).Select(g => (g.x, g.y)), map.WorldToGrid(startPos), gMultiplier); + public void Start(Map map, int goalPriority, WPos startPos, float gMultiplier) + { + var goals = map.Goals(); + var count = goals.Count; + var filteredGoals = new List<(int x, int y)>(count); + for (var i = 0; i < count; ++i) + { + var g = goals[i]; + if (g.priority >= goalPriority) + filteredGoals.Add((g.x, g.y)); + } + + Start(map, filteredGoals, map.WorldToGrid(startPos), gMultiplier); + } // returns whether search is to be terminated; on success, first node of the open list would contain found goal public bool ExecuteStep() { lock (_lock) { - if (_goals.Count == 0 || _openList.Count == 0 || _nodes[_openList[0]].HScore <= 0) + if (_goals.Length == 0 || _openList.Count == 0 || _nodes[_openList[0]].HScore <= 0) return false; var nextNodeIndex = PopMinOpen(); @@ -252,12 +265,12 @@ private void DijkstraDistance() { var numPixels = _map.Width * _map.Height; Array.Fill(_distances, float.MaxValue, 0, numPixels); - - var openList = new List(); + var count = _goals.Length; + var openList = new List(count); var inOpenHeapIndex = new int[numPixels]; Array.Fill(inOpenHeapIndex, 0, 0, numPixels); - for (var i = 0; i < _goals.Count; ++i) + for (var i = 0; i < count; ++i) { var goal = _goals[i]; var goalIndex = CellIndex(goal.x, goal.y); diff --git a/BossMod/Util/ShapeDistance.cs b/BossMod/Util/ShapeDistance.cs index 7ae15bad95..f92a791f4d 100644 --- a/BossMod/Util/ShapeDistance.cs +++ b/BossMod/Util/ShapeDistance.cs @@ -5,6 +5,8 @@ public static class ShapeDistance { + private static readonly Angle a90 = 90.Degrees(); + public static Func HalfPlane(WPos point, WDir normal) { var normalX = normal.X; @@ -156,8 +158,8 @@ public static Func DonutSector(WPos origin, float innerRadius, floa return Cone(origin, outerRadius, centerDir, halfAngle); float coneFactor = halfAngle.Rad > Angle.HalfPi ? -1 : 1; - var nl = coneFactor * (centerDir + halfAngle + 90.Degrees()).ToDirection(); - var nr = coneFactor * (centerDir - halfAngle - 90.Degrees()).ToDirection(); + var nl = coneFactor * (centerDir + halfAngle + a90).ToDirection(); + var nr = coneFactor * (centerDir - halfAngle - a90).ToDirection(); var innerSq = innerRadius * innerRadius; var outerSq = outerRadius * outerRadius; var originX = origin.X; @@ -172,7 +174,7 @@ public static Func DonutSector(WPos origin, float innerRadius, floa var pXoriginX = p.X - originX; var pZoriginZ = p.Z - originZ; var distSqOrigin = pXoriginX * pXoriginX + pZoriginZ * pZoriginZ; - var distOuter = outerSq - outerRadius; + var distOuter = distSqOrigin - outerSq; var distInner = innerSq - distSqOrigin; var distLeft = pXoriginX * nlX + pZoriginZ * nlZ; var distRight = pXoriginX * nrX + pZoriginZ * nrZ; @@ -196,8 +198,8 @@ public static Func InvertedDonutSector(WPos origin, float innerRadi return Cone(origin, outerRadius, centerDir, halfAngle); float coneFactor = halfAngle.Rad > Angle.HalfPi ? -1 : 1; - var nl = coneFactor * (centerDir + halfAngle + 90.Degrees()).ToDirection(); - var nr = coneFactor * (centerDir - halfAngle - 90.Degrees()).ToDirection(); + var nl = coneFactor * (centerDir + halfAngle + a90).ToDirection(); + var nr = coneFactor * (centerDir - halfAngle - a90).ToDirection(); var innerSq = innerRadius * innerRadius; var outerSq = outerRadius * outerRadius; var originX = origin.X; @@ -212,7 +214,7 @@ public static Func InvertedDonutSector(WPos origin, float innerRadi var pXoriginX = p.X - originX; var pZoriginZ = p.Z - originZ; var distSqOrigin = pXoriginX * pXoriginX + pZoriginZ * pZoriginZ; - var distOuter = outerSq - outerRadius; + var distOuter = distSqOrigin - outerSq; var distInner = innerSq - distSqOrigin; var distLeft = pXoriginX * nlX + pZoriginZ * nlZ; var distRight = pXoriginX * nrX + pZoriginZ * nrZ;