From 73ca07217f4bbeb067b9256d7e784a6bb6dfa755 Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Fri, 7 Feb 2025 23:31:23 +0100 Subject: [PATCH] some fixes --- BossMod/AI/AIBehaviour.cs | 31 +++- BossMod/Autorotation/MiscAI/NormalMovement.cs | 4 +- BossMod/BossModule/AIHints.cs | 2 +- BossMod/BossModule/BossModuleHintsWindow.cs | 8 +- BossMod/BossModule/BossModuleMainWindow.cs | 14 +- BossMod/BossModule/BossModuleManager.cs | 2 +- BossMod/Framework/MovementOverride.cs | 4 +- .../Alliance/A10Trash/A10Aquarius.cs | 32 +++- .../Dawntrail/Alliance/A10Trash/A10Despot.cs | 75 +++++--- .../Alliance/A10Trash/A10Groundskeeper.cs | 20 ++- .../A10Trash/A10VanguardPathfinder.cs | 20 ++- .../Dawntrail/Alliance/A11Prishe/A11Prishe.cs | 8 +- .../Alliance/A11Prishe/ArenaChanges.cs | 20 +-- .../Alliance/A11Prishe/BanishStorm.cs | 2 +- .../Dawntrail/Alliance/A11Prishe/Explosion.cs | 5 +- .../Alliance/A11Prishe/KnuckleSandwich.cs | 2 +- .../Dawntrail/Alliance/A12Fafnir/A12Fafnir.cs | 14 +- .../Alliance/A12Fafnir/ArenaChanges.cs | 1 + .../A13ArkAngels/A13ArkAngelsStates.cs | 16 +- .../Alliance/A13ArkAngels/ConeDonutCross.cs | 3 +- .../Alliance/A13ArkAngels/Rampage.cs | 3 +- .../Alliance/A14ShadowLord/A14ShadowLord.cs | 8 +- .../Dungeon/D03SkydeepCenote/D033Maulskull.cs | 4 +- .../D092OverseerKanilokka.cs | 4 +- .../D093Lunipyati.cs | 20 ++- .../Dungeon/D03Vanaspati/D031Snatcher.cs | 11 +- .../Dungeon/D03Vanaspati/D032Wrecker.cs | 118 +++++++++--- .../Dungeon/D03Vanaspati/D033Svarbhanu.cs | 169 +++++++++++------- .../Dungeon/D02DohnMheg/D021AencThon.cs | 8 +- BossMod/Pathfinding/Map.cs | 21 ++- BossMod/Pathfinding/NavigationDecision.cs | 4 +- BossMod/Pathfinding/ThetaStar.cs | 62 +++---- 32 files changed, 477 insertions(+), 238 deletions(-) diff --git a/BossMod/AI/AIBehaviour.cs b/BossMod/AI/AIBehaviour.cs index fea7c965e5..ea68434de4 100644 --- a/BossMod/AI/AIBehaviour.cs +++ b/BossMod/AI/AIBehaviour.cs @@ -90,7 +90,6 @@ public async Task Execute(Actor player, Actor master) autorot.Preset = target.Target != null ? AIPreset : null; } UpdateMovement(player, master, target, gazeImminent || pyreticImminent, misdirectionMode ? autorot.Hints.MisdirectionThreshold : default, !forbidTargeting ? autorot.Hints.ActionsToExecute : null); - } finally { @@ -236,10 +235,34 @@ private void UpdateMovement(Actor player, Actor master, Targeting target, bool g } else if (misdirectionAngle != default && _naviDecision.Destination != null) { - var turn = (_naviDecision.Destination.Value - player.Position).OrthoL().Dot((_naviDecision.NextWaypoint ?? _naviDecision.Destination).Value - _naviDecision.Destination.Value); - ctrl.NaviTargetPos = turn == 0f ? _naviDecision.Destination - : player.Position + (_naviDecision.Destination.Value - player.Position).Rotate(turn > 0f ? -misdirectionAngle : misdirectionAngle); ctrl.AllowInterruptingCastByMovement = true; + var dir = _naviDecision.Destination.Value - player.Position; + var distSq = dir.LengthSq(); + var threshold = 30f.Degrees(); + var forceddir = WorldState.Client.ForcedMovementDirection; + var allowMovement = forceddir.AlmostEqual(Angle.FromDirection(dir), threshold.Rad); + if (allowMovement) + allowMovement = CalculateUnobstructedPathLength(forceddir) >= Math.Min(4f, distSq); + ctrl.NaviTargetPos = allowMovement && distSq >= 0.01f ? _naviDecision.Destination.Value : null; + + float CalculateUnobstructedPathLength(Angle dir) + { + var start = _naviCtx.Map.WorldToGrid(player.Position); + if (!_naviCtx.Map.InBounds(start.x, start.y)) + return 0; + + var end = _naviCtx.Map.WorldToGrid(player.Position + 100f * dir.ToDirection()); + var startG = _naviCtx.Map.PixelMaxG[_naviCtx.Map.GridToIndex(start.x, start.y)]; + foreach (var p in _naviCtx.Map.EnumeratePixelsInLine(start.x, start.y, end.x, end.y)) + { + if (!_naviCtx.Map.InBounds(p.x, p.y) || _naviCtx.Map.PixelMaxG[_naviCtx.Map.GridToIndex(p.x, p.y)] < startG) + { + var dest = _naviCtx.Map.GridToWorld(p.x, p.y, 0.5f, 0.5f); + return (dest - player.Position).LengthSq(); + } + } + return float.MaxValue; + } // debug //void drawLine(WPos from, WPos to, uint color) => Camera.Instance!.DrawWorldLine(new(from.X, player.PosRot.Y, from.Z), new(to.X, player.PosRot.Y, to.Z), color); diff --git a/BossMod/Autorotation/MiscAI/NormalMovement.cs b/BossMod/Autorotation/MiscAI/NormalMovement.cs index d05f11b206..e9e189782c 100644 --- a/BossMod/Autorotation/MiscAI/NormalMovement.cs +++ b/BossMod/Autorotation/MiscAI/NormalMovement.cs @@ -142,7 +142,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa // this means that cos(threshold) = speed * dt / 2 / distance // assuming we wanna move at least for a second, speed is standard 6, threshold of 60 degrees would be fine for distances >= 6 // for micro adjusts, if we move for 1 frame (1/60s), threshold of 60 degrees would be fine for distance 0.1, which is our typical threshold - var threshold = 30.Degrees(); + var threshold = 30f.Degrees(); var allowMovement = World.Client.ForcedMovementDirection.AlmostEqual(Angle.FromDirection(dir), threshold.Rad); if (allowMovement && destinationStrategy == DestinationStrategy.Pathfind) { @@ -190,7 +190,7 @@ private float CalculateUnobstructedPathLength(Angle dir) if (!_navCtx.Map.InBounds(start.x, start.y)) return 0; - var end = _navCtx.Map.WorldToGrid(Player.Position + 100 * dir.ToDirection()); + var end = _navCtx.Map.WorldToGrid(Player.Position + 100f * dir.ToDirection()); var startG = _navCtx.Map.PixelMaxG[_navCtx.Map.GridToIndex(start.x, start.y)]; foreach (var p in _navCtx.Map.EnumeratePixelsInLine(start.x, start.y, end.x, end.y)) { diff --git a/BossMod/BossModule/AIHints.cs b/BossMod/BossModule/AIHints.cs index 6257f9c5c6..8ce10d4c69 100644 --- a/BossMod/BossModule/AIHints.cs +++ b/BossMod/BossModule/AIHints.cs @@ -118,7 +118,7 @@ public void Clear() RecommendedPositional = default; ForbiddenDirections.Clear(); ImminentSpecialMode = default; - MisdirectionThreshold = 15.Degrees(); + MisdirectionThreshold = 15f.Degrees(); PredictedDamage.Clear(); MaxCastTime = float.MaxValue; ForceCancelCast = false; diff --git a/BossMod/BossModule/BossModuleHintsWindow.cs b/BossMod/BossModule/BossModuleHintsWindow.cs index 3635803ade..3c84325b7c 100644 --- a/BossMod/BossModule/BossModuleHintsWindow.cs +++ b/BossMod/BossModule/BossModuleHintsWindow.cs @@ -16,13 +16,13 @@ public class BossModuleHintsWindow : UIWindow public override void PreOpenCheck() { - IsOpen = _mgr.Config.HintsInSeparateWindow && (_mgr.ActiveModule != null || ShowZoneModule()); + IsOpen = BossModuleManager.Config.HintsInSeparateWindow && (_mgr.ActiveModule != null || ShowZoneModule()); Flags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; - if (_mgr.Config.Lock) + if (BossModuleManager.Config.Lock) Flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoInputs; - if (_mgr.Config.HintsInSeparateWindowTransparent) + if (BossModuleManager.Config.HintsInSeparateWindowTransparent) Flags |= ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoBackground; - ForceMainWindow = _mgr.Config.HintsInSeparateWindowTransparent; // NoBackground flag without ForceMainWindow works incorrectly for whatever reason + ForceMainWindow = BossModuleManager.Config.HintsInSeparateWindowTransparent; // NoBackground flag without ForceMainWindow works incorrectly for whatever reason } public override void Draw() diff --git a/BossMod/BossModule/BossModuleMainWindow.cs b/BossMod/BossModule/BossModuleMainWindow.cs index 3ccfa4e3ed..30826c3d9d 100644 --- a/BossMod/BossModule/BossModuleMainWindow.cs +++ b/BossMod/BossModule/BossModuleMainWindow.cs @@ -21,17 +21,17 @@ public class BossModuleMainWindow : UIWindow public override void PreOpenCheck() { var showZoneModule = ShowZoneModule(); - IsOpen = _mgr.Config.Enable && (_mgr.LoadedModules.Count > 0 || showZoneModule); + IsOpen = BossModuleManager.Config.Enable && (_mgr.LoadedModules.Count > 0 || showZoneModule); ShowCloseButton = _mgr.ActiveModule != null && !showZoneModule; WindowName = (showZoneModule ? $"Zone module ({_zmm.ActiveModule?.GetType().Name})" : _mgr.ActiveModule != null ? $"Boss module ({_mgr.ActiveModule.GetType().Name})" : "Loaded boss modules") + _windowID; Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; - if (_mgr.Config.TrishaMode) + if (BossModuleManager.Config.TrishaMode) Flags |= ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoBackground; - if (_mgr.Config.Lock) + if (BossModuleManager.Config.Lock) Flags |= ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoInputs; - ForceMainWindow = _mgr.Config.TrishaMode; // NoBackground flag without ForceMainWindow works incorrectly for whatever reason + ForceMainWindow = BossModuleManager.Config.TrishaMode; // NoBackground flag without ForceMainWindow works incorrectly for whatever reason - if (_mgr.Config.ShowWorldArrows && _mgr.ActiveModule != null && _mgr.WorldState.Party[PartyState.PlayerSlot] is var pc && pc != null) + if (BossModuleManager.Config.ShowWorldArrows && _mgr.ActiveModule != null && _mgr.WorldState.Party[PartyState.PlayerSlot] is var pc && pc != null) DrawMovementHints(_mgr.ActiveModule.CalculateMovementHintsForRaidMember(PartyState.PlayerSlot, ref pc), pc.PosRot.Y); } @@ -68,7 +68,7 @@ public override void Draw() { try { - _mgr.ActiveModule.Draw(_mgr.Config.RotateArena ? _mgr.WorldState.Client.CameraAzimuth : _mgr.Config.FlipArena ? 180.Degrees() : default, PartyState.PlayerSlot, !_mgr.Config.HintsInSeparateWindow, true); + _mgr.ActiveModule.Draw(BossModuleManager.Config.RotateArena ? _mgr.WorldState.Client.CameraAzimuth : BossModuleManager.Config.FlipArena ? 180f.Degrees() : default, PartyState.PlayerSlot, !BossModuleManager.Config.HintsInSeparateWindow, true); } catch (Exception ex) { @@ -113,5 +113,5 @@ private void OpenModuleConfig() _ = new BossModuleConfigWindow(_mgr.ActiveModule.Info, _mgr.WorldState); } - private bool ShowZoneModule() => _mgr.Config.ShowGlobalHints && !_mgr.Config.HintsInSeparateWindow && _mgr.ActiveModule?.StateMachine.ActivePhase == null && (_zmm.ActiveModule?.WantToBeDrawn() ?? false); + private bool ShowZoneModule() => BossModuleManager.Config.ShowGlobalHints && !BossModuleManager.Config.HintsInSeparateWindow && _mgr.ActiveModule?.StateMachine.ActivePhase == null && (_zmm.ActiveModule?.WantToBeDrawn() ?? false); } diff --git a/BossMod/BossModule/BossModuleManager.cs b/BossMod/BossModule/BossModuleManager.cs index ed054f639c..c1015816b2 100644 --- a/BossMod/BossModule/BossModuleManager.cs +++ b/BossMod/BossModule/BossModuleManager.cs @@ -5,7 +5,7 @@ public sealed class BossModuleManager : IDisposable { public readonly WorldState WorldState; public readonly RaidCooldowns RaidCooldowns; - public readonly BossModuleConfig Config = Service.Config.Get(); + public static readonly BossModuleConfig Config = Service.Config.Get(); private readonly EventSubscriptions _subsciptions; public List LoadedModules { get; } = []; diff --git a/BossMod/Framework/MovementOverride.cs b/BossMod/Framework/MovementOverride.cs index 176ac4fbb7..c923acb3a1 100644 --- a/BossMod/Framework/MovementOverride.cs +++ b/BossMod/Framework/MovementOverride.cs @@ -133,7 +133,7 @@ private void RMIWalkDetour(void* self, float* sumLeft, float* sumForward, float* if (misdirectionMode) { var thresholdDeg = UserMove != default ? _tweaksConfig.MisdirectionThreshold : MisdirectionThreshold.Deg; - if (thresholdDeg < 180) + if (thresholdDeg < 180f) { // note: if we are already moving, it doesn't matter what we do here, only whether 'is input active' function returns true or false _forcedControlState = ActualMove != default && (Angle.FromDirection(ActualMove) + ForwardMovementDirection() - ForcedMovementDirection->Radians()).Normalized().Abs().Deg <= thresholdDeg; @@ -184,7 +184,7 @@ private byte MCIsInputActiveDetour(void* self, byte inputSourceFlags) return (dirH - ForwardMovementDirection(), dirV); } - private Angle ForwardMovementDirection() => _legacyMode ? Camera.Instance!.CameraAzimuth.Radians() + 180.Degrees() : GameObjectManager.Instance()->Objects.IndexSorted[0].Value->Rotation.Radians(); + private Angle ForwardMovementDirection() => _legacyMode ? Camera.Instance!.CameraAzimuth.Radians() + 180f.Degrees() : GameObjectManager.Instance()->Objects.IndexSorted[0].Value->Rotation.Radians(); private bool PlayerHasMisdirection() { diff --git a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Aquarius.cs b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Aquarius.cs index 6f65271b90..e4a866a06f 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Aquarius.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Aquarius.cs @@ -29,13 +29,13 @@ public enum AID : uint Agaricus = 41661 // DeathCap->self, 3.0s cast, range 5 circle } -class CursedSphere(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.CursedSphere), 3); -class WaterIII(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WaterIII), 7); -class BubbleShower(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.BubbleShower), new AOEShapeCone(6, 30.Degrees())); -class Scoop(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Scoop), new AOEShapeCone(15, 60.Degrees())); -class Agaricus(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Agaricus), 5); -class Beatdown(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Beatdown), new AOEShapeRect(9, 1.5f)); -class SpiderWeb(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.SpiderWeb), 6); +class CursedSphere(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.CursedSphere), 3f); +class WaterIII(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WaterIII), 7f); +class BubbleShower(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.BubbleShower), new AOEShapeCone(6f, 30f.Degrees())); +class Scoop(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Scoop), new AOEShapeCone(15f, 60f.Degrees())); +class Agaricus(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Agaricus), 5f); +class Beatdown(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Beatdown), new AOEShapeRect(9f, 1.5f)); +class SpiderWeb(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.SpiderWeb), 6f); class HundredFists(BossModule module) : Components.CastInterruptHint(module, ActionID.MakeSpell(AID.HundredFists), showNameInHint: true); public class A10AquariusStates : StateMachineBuilder @@ -51,7 +51,21 @@ public A10AquariusStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => Module.Enemies(A10Aquarius.Trash).All(x => x.IsDeadOrDestroyed); + .Raw.Update = () => + { + var allDeadOrDestroyed = true; + var enemies = module.Enemies(A10Aquarius.Trash); + var count = enemies.Count; + for (var i = 0; i < count; ++i) + { + if (!enemies[i].IsDeadOrDestroyed) + { + allDeadOrDestroyed = false; + break; + } + } + return allDeadOrDestroyed; + }; } } @@ -80,7 +94,7 @@ public class A10Aquarius(WorldState ws, Actor primary) : BossModule(ws, primary, new(-410.14f, 755.88f), new(-409.01f, 756.23f), new(-409.3f, 759.38f), new(-409.32f, 760.07f), new(-407.32f, 764.18f), new(-407.06f, 764.83f), new(-405.69f, 770.83f), new(-402.62f, 778.61f), new(-402.54f, 779.34f), new(-404.95f, 792.13f), new(-404.88f, 792.77f), new(-403.92f, 794.71f), new(-404.14f, 810.99f), new(-403.93f, 815.01f), new(-412.73f, 817.93f), - new(-413.45f, 818), new(-427.66f, 817.49f), new(-428.17f, 817.69f), new(-429.68f, 822.64f), new(-429.97f, 823.22f), + new(-413.45f, 818f), new(-427.66f, 817.49f), new(-428.17f, 817.69f), new(-429.68f, 822.64f), new(-429.97f, 823.22f), new(-431.56f, 825.26f), new(-432.02f, 825.68f), new(-432.24f, 825.13f), new(-439.69f, 821.71f), new(-440.34f, 821.33f), new(-440.26f, 820.77f), new(-440.05f, 820.31f), new(-439.57f, 819.75f), new(-438.55f, 818.8f), new(-436.36f, 816.11f), new(-436.1f, 815.58f), new(-437.12f, 802.89f), new(-443.22f, 794.68f), new(-443.57f, 786.54f), new(-441.28f, 772.63f), diff --git a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Despot.cs b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Despot.cs index 9581142afc..ad7eadb3c0 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Despot.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Despot.cs @@ -13,7 +13,7 @@ public enum AID : uint AutoAttack2 = 872, // Boss->player, no cast, single-target WingCutter = 41672, // Flamingo2->self, 2.5s cast, range 6 120-degree cone - ScraplineStorm = 40650, // Boss->self, 5.0s cast, range 30 circle, pull 12.5 between centers, log says distance 10, but it seems to be actually ~12.5? + ScraplineStorm = 40650, // Boss->self, 5.0s cast, range 30 circle, pull 10 between centers Scrapline = 41393, // Boss->self, 1.0s cast, range 10 circle Typhoon = 41902, // Boss->self, 1.5s cast, range 8-40 donut IsleDrop = 41699, // Boss->location, 3.0s cast, range 6 circle @@ -26,28 +26,38 @@ class IsleDrop(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeS class WingCutter(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WingCutter), new AOEShapeCone(6f, 60f.Degrees())); class PanzerfaustHint(BossModule module) : Components.CastInterruptHint(module, ActionID.MakeSpell(AID.Panzerfaust), showNameInHint: true); class Panzerfaust(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.Panzerfaust)); -class ScraplineStorm(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.ScraplineStorm), 12.5f, kind: Kind.TowardsOrigin) +class ScraplineStorm(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.ScraplineStorm), 10f, kind: Kind.TowardsOrigin) { - 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; + private readonly ScraplineTyphoon _aoe = module.FindComponent()!; + + public override bool DestinationUnsafe(int slot, Actor actor, WPos pos) + { + var aoes = _aoe.AOEs; + var aoe = aoes[0]; + if (aoes.Count != 0 && aoe.Shape.Check(pos, aoe.Origin, aoe.Rotation)) + return true; + else + return false; + } 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.Circle(source.Origin, 22.5f), source.Activation); + var source = Casters.Count != 0 ? Casters[0] : null; + if (source != null) + hints.AddForbiddenZone(ShapeDistance.Circle(source.Position, 20f), Module.CastFinishAt(source.CastInfo)); } } class ScraplineTyphoon(BossModule module) : Components.GenericAOEs(module) { - private readonly List _aoes = new(2); + public readonly List AOEs = new(2); private static readonly AOEShapeCircle circle = new(10f); - private static readonly AOEShapeDonut donut = new(8f, 4f); + private static readonly AOEShapeDonut donut = new(8f, 40f); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_aoes.Count != 0) - return [_aoes[0]]; + if (AOEs.Count != 0) + return [AOEs[0]]; else return []; } @@ -56,15 +66,15 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if (spell.Action.ID == (uint)AID.ScraplineStorm) { - _aoes.Add(new(circle, spell.LocXZ, default, Module.CastFinishAt(spell, 2.1f))); - _aoes.Add(new(donut, spell.LocXZ, default, Module.CastFinishAt(spell, 5.6f))); + AOEs.Add(new(circle, spell.LocXZ, default, Module.CastFinishAt(spell, 2.1f))); + AOEs.Add(new(donut, spell.LocXZ, default, Module.CastFinishAt(spell, 5.6f))); } } public override void OnCastFinished(Actor caster, ActorCastInfo spell) { - if (_aoes.Count != 0 && spell.Action.ID is (uint)AID.Scrapline or (uint)AID.Typhoon) - _aoes.RemoveAt(0); + if (AOEs.Count != 0 && spell.Action.ID is (uint)AID.Scrapline or (uint)AID.Typhoon) + AOEs.RemoveAt(0); } } @@ -76,10 +86,24 @@ public A10DespotStates(BossModule module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() - .Raw.Update = () => Module.Enemies(A10Despot.Trash).All(x => x.IsDeadOrDestroyed); + .Raw.Update = () => + { + var allDeadOrDestroyed = true; + var enemies = module.Enemies(A10Despot.Trash); + var count = enemies.Count; + for (var i = 0; i < count; ++i) + { + if (!enemies[i].IsDeadOrDestroyed) + { + allDeadOrDestroyed = false; + break; + } + } + return allDeadOrDestroyed; + }; } } @@ -100,7 +124,7 @@ public class A10Despot(WorldState ws, Actor primary) : BossModule(ws, primary, a new(-483.28f, -598.38f), new(-483.22f, -597.79f), new(-482.95f, -597.2f), new(-482.4f, -596.83f), new(-482.56f, -596.32f), new(-492.75f, -573.42f), new(-492.65f, -572.93f), new(-487.62f, -569.97f), new(-487.26f, -569.61f), new(-487.45f, -566.88f), new(-487.39f, -566.19f), new(-487.2f, -565.48f), new(-486.95f, -564.82f), new(-486.64f, -564.13f), new(-486.01f, -561.99f), - new(-485.78f, -561.53f), new(-485.22f, -560.66f), new(-485, -560), new(-498.54f, -538.05f), new(-499.21f, -537.84f), + new(-485.78f, -561.53f), new(-485.22f, -560.66f), new(-485f, -560f), new(-498.54f, -538.05f), new(-499.21f, -537.84f), new(-507.26f, -536.82f), new(-507.98f, -536.85f), new(-509.25f, -538.49f), new(-509.72f, -538.95f), new(-510.24f, -539.05f), new(-511.53f, -538.97f), new(-512.14f, -538.89f), new(-512.7f, -538.69f), new(-513.01f, -538.17f), new(-513.51f, -537.7f), new(-517.45f, -535.31f), new(-519.94f, -532.16f), new(-520.52f, -531.83f), new(-521.18f, -531.89f), new(-521.83f, -532.08f), @@ -111,12 +135,12 @@ public class A10Despot(WorldState ws, Actor primary) : BossModule(ws, primary, a new(-539.23f, -544.06f), new(-539.07f, -544.64f), new(-539.1f, -545.25f), new(-539.21f, -545.86f), new(-539.63f, -546.23f), new(-542.42f, -547.97f), new(-543.03f, -548.24f), new(-545.06f, -548.75f), new(-546.33f, -548.68f), new(-546.94f, -548.49f), new(-547.44f, -548.05f), new(-550.57f, -546.55f), new(-551.17f, -546.91f), new(-552.21f, -547.72f), new(-554.33f, -565.17f), - new(-554.29f, -565.67f), new(-553.44f, -567.42f), new(-553.24f, -568), new(-553.48f, -568.46f), new(-575, -585.35f), + new(-554.29f, -565.67f), new(-553.44f, -567.42f), new(-553.24f, -568), new(-553.48f, -568.46f), new(-575f, -585.35f), new(-575.48f, -585.85f), new(-576.07f, -585.86f), new(-576.48f, -585.42f), new(-579.7f, -581.35f), new(-580.39f, -581.19f), new(-581.01f, -581.22f), new(-581.59f, -580.82f), new(-582.12f, -580.35f), new(-582.41f, -579.83f), new(-582.31f, -579.14f), new(-581.86f, -578.57f), new(-585.24f, -574.25f), new(-585.66f, -573.94f), new(-592.4f, -579.21f), new(-592.88f, -579.69f), new(-593.98f, -580.42f), new(-594.46f, -580.26f), new(-596.61f, -577.68f), new(-608.31f, -586.89f), new(-608.07f, -587.53f), - new(-591, -609.36f), new(-590.71f, -609.86f), new(-593.18f, -623.19f), new(-589.12f, -632.73f), new(-588.73f, -633.32f), + new(-591f, -609.36f), new(-590.71f, -609.86f), new(-593.18f, -623.19f), new(-589.12f, -632.73f), new(-588.73f, -633.32f), new(-588.21f, -633.36f), new(-587.48f, -633.3f), new(-586.78f, -633.16f), new(-584.14f, -632.28f), new(-583.52f, -632.53f), new(-583.31f, -633.17f), new(-583.26f, -633.79f), new(-583.79f, -634.2f), new(-585.11f, -634.48f), new(-585.79f, -634.7f), new(-586.45f, -635.02f), new(-587.01f, -635.5f), new(-587.48f, -636.06f), new(-587.43f, -636.72f), new(-586.1f, -639.84f), @@ -124,7 +148,18 @@ public class A10Despot(WorldState ws, Actor primary) : BossModule(ws, primary, a private static readonly ArenaBoundsComplex arena = new([new PolygonCustom(vertices)]); public static readonly uint[] Trash = [(uint)OID.Boss, (uint)OID.Flamingo1, (uint)OID.Flamingo2]; - protected override bool CheckPull() => Enemies(Trash).Any(x => x.InCombat); + protected override bool CheckPull() + { + var enemies = Enemies(Trash); + var count = enemies.Count; + for (var i = 0; i < count; ++i) + { + if (enemies[i].InCombat) + return true; + } + return false; + } + protected override void DrawEnemies(int pcSlot, Actor pc) { Arena.Actors(Enemies(Trash)); diff --git a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Groundskeeper.cs b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Groundskeeper.cs index a41c78c1c3..ed4a830687 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Groundskeeper.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10Groundskeeper.cs @@ -16,7 +16,7 @@ public enum AID : uint DoubleRay = 41668 // Sprinkler->player, no cast, single-target } -class IsleDrop(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.IsleDrop), 6); +class IsleDrop(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.IsleDrop), 6f); class MysteriousLight(BossModule module) : Components.CastGaze(module, ActionID.MakeSpell(AID.MysteriousLight)); public class A10GroundskeeperStates : StateMachineBuilder @@ -26,7 +26,21 @@ public A10GroundskeeperStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => Module.Enemies(A10Groundskeeper.Trash).All(x => x.IsDeadOrDestroyed); + .Raw.Update = () => + { + var allDeadOrDestroyed = true; + var enemies = module.Enemies(A10Groundskeeper.Trash); + var count = enemies.Count; + for (var i = 0; i < count; ++i) + { + if (!enemies[i].IsDeadOrDestroyed) + { + allDeadOrDestroyed = false; + break; + } + } + return allDeadOrDestroyed; + }; } } @@ -48,7 +62,7 @@ public class A10Groundskeeper(WorldState ws, Actor primary) : BossModule(ws, pri new(-564.71f, -598.33f), new(-568.95f, -600.82f), new(-569.28f, -601.23f), new(-567.14f, -604.83f), new(-566.46f, -604.8f), new(-565.21f, -604.06f), new(-564.32f, -603.14f), new(-563.89f, -602.73f), new(-563.4f, -602.37f), new(-562.83f, -602.15f), new(-562.26f, -602.15f), new(-561.68f, -602.32f), new(-561.05f, -603.42f), new(-557.61f, -611.37f), new(-557.63f, -611.93f), - new(-558.2f, -612.25f), new(-562.33f, -614), new(-562.88f, -614.49f), new(-561.34f, -618.12f), new(-561.07f, -618.57f), + new(-558.2f, -612.25f), new(-562.33f, -614f), new(-562.88f, -614.49f), new(-561.34f, -618.12f), new(-561.07f, -618.57f), new(-557.2f, -616.93f), new(-556.69f, -616.59f), new(-556.17f, -616.37f), new(-555.61f, -616.44f), new(-555.32f, -616.94f), new(-544.54f, -642.34f)]; private static readonly ArenaBoundsComplex arena = new([new PolygonCustom(vertices)]); diff --git a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10VanguardPathfinder.cs b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10VanguardPathfinder.cs index d0c60a0b81..be496fb422 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10VanguardPathfinder.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A10Trash/A10VanguardPathfinder.cs @@ -18,8 +18,8 @@ public enum AID : uint GoblinRush = 41654 // Boss->players, no cast, single-target } -class BombToss(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.BombToss), 3); -class Seismostomp(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Seismostomp), 5); +class BombToss(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.BombToss), 3f); +class Seismostomp(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Seismostomp), 5f); public class A10VanguardPathfinderStates : StateMachineBuilder { @@ -28,7 +28,21 @@ public A10VanguardPathfinderStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => Module.Enemies(A10VanguardPathfinder.Trash).All(x => x.IsDeadOrDestroyed); + .Raw.Update = () => + { + var allDeadOrDestroyed = true; + var enemies = module.Enemies(A10VanguardPathfinder.Trash); + var count = enemies.Count; + for (var i = 0; i < count; ++i) + { + if (!enemies[i].IsDeadOrDestroyed) + { + allDeadOrDestroyed = false; + break; + } + } + return allDeadOrDestroyed; + }; } } diff --git a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/A11Prishe.cs b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/A11Prishe.cs index 9075623cfd..f7813939c0 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/A11Prishe.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/A11Prishe.cs @@ -1,13 +1,13 @@ namespace BossMod.Dawntrail.Alliance.A11Prishe; -class NullifyingDropkick(BossModule module) : Components.CastSharedTankbuster(module, ActionID.MakeSpell(AID.NullifyingDropkick), 6); -class Holy(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Holy), 6); +class NullifyingDropkick(BossModule module) : Components.CastSharedTankbuster(module, ActionID.MakeSpell(AID.NullifyingDropkick), 6f); +class Holy(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Holy), 6f); class BanishgaIV(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.BanishgaIV)); class Banishga(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.Banishga)); [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1015, NameID = 13351, SortOrder = 2, PlanLevel = 100)] public class A11Prishe(WorldState ws, Actor primary) : BossModule(ws, primary, ArenaCenter, DefaultBounds) { - public static readonly WPos ArenaCenter = new(800, 400); - public static readonly ArenaBoundsSquare DefaultBounds = new(35); + public static readonly WPos ArenaCenter = new(800f, 400f); + public static readonly ArenaBoundsSquare DefaultBounds = new(35f); } diff --git a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/ArenaChanges.cs b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/ArenaChanges.cs index 99562d8788..8828c0cf64 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/ArenaChanges.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/ArenaChanges.cs @@ -4,15 +4,15 @@ class ArenaChanges(BossModule module) : Components.GenericAOEs(module, ActionID. { public bool Active => _aoe != null || Arena.Bounds != A11Prishe.DefaultBounds; private AOEInstance? _aoe; - private static readonly Square[] defaultSquare = [new(A11Prishe.ArenaCenter, 35)]; - public static readonly Square[] MiddleENVC00020001 = [new(new(795, 405), 10), new(new(805, 395), 10)]; - private static readonly Shape[] differenceENVC00020001 = [.. MiddleENVC00020001, new Rectangle(new(810, 430), 15, 5), - new Rectangle(new(830, 420), 5, 15), new Rectangle(new(790, 370), 15, 5), new Rectangle(new(770, 380), 5, 15)]; + private static readonly Square[] defaultSquare = [new(A11Prishe.ArenaCenter, 35f)]; + public static readonly Square[] MiddleENVC00020001 = [new(new(795f, 405f), 10f), new(new(805f, 395f), 10f)]; + private static readonly Shape[] differenceENVC00020001 = [.. MiddleENVC00020001, new Rectangle(new(810f, 430f), 15f, 5f), + new Rectangle(new(830f, 420f), 5f, 15f), new Rectangle(new(790f, 370f), 15f, 5f), new Rectangle(new(770f, 380f), 5f, 15f)]; private static readonly AOEShapeCustom arenaChangeENVC00020001 = new(defaultSquare, differenceENVC00020001); public static readonly ArenaBoundsComplex ArenaENVC00020001 = new(differenceENVC00020001); - public static readonly Square[] MiddleENVC02000100 = [new(new(795, 395), 10), new(new(805, 405), 10)]; - private static readonly Shape[] differenceENVC02000100 = [.. MiddleENVC02000100, new Rectangle(new(820, 370), 15, 5), - new Rectangle(new(830, 390), 5, 15), new Rectangle(new(780, 430), 15, 5), new Rectangle(new(770, 410), 5, 15)]; + public static readonly Square[] MiddleENVC02000100 = [new(new(795f, 395f), 10f), new(new(805f, 405f), 10f)]; + private static readonly Shape[] differenceENVC02000100 = [.. MiddleENVC02000100, new Rectangle(new(820f, 370f), 15f, 5f), + new Rectangle(new(830f, 390f), 5f, 15f), new Rectangle(new(780f, 430f), 15f, 5f), new Rectangle(new(770f, 410f), 5f, 15f)]; private static readonly AOEShapeCustom arenaChangeENVC02000100 = new(defaultSquare, differenceENVC02000100); public static readonly ArenaBoundsComplex ArenaENVC02000100 = new(differenceENVC02000100); @@ -52,7 +52,7 @@ private void SetArena(ArenaBoundsComplex bounds) private void SetAOE(AOEShapeCustom shape) { - _aoe = new(shape, Arena.Center, default, WorldState.FutureTime(5)); + _aoe = new(shape, Arena.Center, default, WorldState.FutureTime(5d)); } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { } // no need to generate a hint here, we generate a special hint in CrystallineThornsHint @@ -90,12 +90,12 @@ public override void OnEventEnvControl(byte index, uint state) private void SetAOE(AOEShapeCustom shape) { - _aoe = new(shape, Arena.Center, default, WorldState.FutureTime(5), Colors.SafeFromAOE); + _aoe = new(shape, Arena.Center, default, WorldState.FutureTime(5d), Colors.SafeFromAOE); } public override void AddHints(int slot, Actor actor, TextHints hints) { - if (ActiveAOEs(slot, actor).Any(c => !c.Check(actor.Position))) + if (_aoe != null && !_aoe.Value.Check(actor.Position)) hints.Add(RiskHint); } } diff --git a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/BanishStorm.cs b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/BanishStorm.cs index 361b20b164..4a92e9bcd8 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/BanishStorm.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/BanishStorm.cs @@ -76,7 +76,7 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) if (spell.Action.ID == (uint)AID.Banish) { ++NumCasts; - var index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1)); + var index = Lines.FindIndex(item => item.Next.AlmostEqual(caster.Position, 1f)); AdvanceLine(Lines[index], caster.Position); if (Lines[index].ExplosionsLeft == 0) Lines.RemoveAt(index); diff --git a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/Explosion.cs b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/Explosion.cs index c5b8a37607..97078047a6 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/Explosion.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/Explosion.cs @@ -12,10 +12,11 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) return []; var firstactivation = _aoes[0].Activation; var aoes = new AOEInstance[count]; + var color = Colors.Danger; for (var i = 0; i < count; ++i) { var aoe = _aoes[i]; - aoes[i] = (aoe.Activation - firstactivation).TotalSeconds < 1d ? aoe with { Color = Colors.Danger } : aoe with { Risky = false }; + aoes[i] = (aoe.Activation - firstactivation).TotalSeconds < 1d ? aoe with { Color = color } : aoe with { Risky = false }; } return aoes; } @@ -23,7 +24,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if (spell.Action.ID == (uint)AID.Explosion) - _aoes.Add(new(circle, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoes.Add(new(circle, spell.LocXZ, default, Module.CastFinishAt(spell))); } public override void OnCastFinished(Actor caster, ActorCastInfo spell) diff --git a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/KnuckleSandwich.cs b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/KnuckleSandwich.cs index 9c4d49b105..401807853d 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A11Prishe/KnuckleSandwich.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A11Prishe/KnuckleSandwich.cs @@ -45,7 +45,7 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell) case (uint)AID.BrittleImpact2: case (uint)AID.BrittleImpact3: ++NumCasts; - if (_aoes.Count > 0) + if (_aoes.Count != 0) _aoes.RemoveAt(0); break; } diff --git a/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/A12Fafnir.cs b/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/A12Fafnir.cs index de9876ae31..6e892c838a 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/A12Fafnir.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/A12Fafnir.cs @@ -21,22 +21,22 @@ class Darter(BossModule module) : Components.Adds(module, (uint)OID.Darter, 1) { public override bool KeepOnPhaseChange => true; } -class Venom(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Venom), new AOEShapeCone(30, 60.Degrees())) +class Venom(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Venom), new AOEShapeCone(30f, 60f.Degrees())) { public override bool KeepOnPhaseChange => true; } -class AbsoluteTerror(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.AbsoluteTerrorAOE), new AOEShapeRect(70, 10)) +class AbsoluteTerror(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.AbsoluteTerrorAOE), new AOEShapeRect(70f, 10f)) { public override bool KeepOnPhaseChange => true; } -class WingedTerror(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WingedTerrorAOE), new AOEShapeRect(70, 12.5f)) +class WingedTerror(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.WingedTerrorAOE), new AOEShapeRect(70f, 12.5f)) { public override bool KeepOnPhaseChange => true; } -class BalefulBreath(BossModule module) : Components.LineStack(module, (uint)IconID.BalefulBreath, ActionID.MakeSpell(AID.BalefulBreathAOERest), 8.2f, 70, 3, PartyState.MaxAllianceSize, PartyState.MaxAllianceSize, 3, false) +class BalefulBreath(BossModule module) : Components.LineStack(module, (uint)IconID.BalefulBreath, ActionID.MakeSpell(AID.BalefulBreathAOERest), 8.2f, 70f, 3f, PartyState.MaxAllianceSize, PartyState.MaxAllianceSize, 3, false) { public override bool KeepOnPhaseChange => true; } @@ -49,7 +49,7 @@ class BalefulBreath(BossModule module) : Components.LineStack(module, (uint)Icon [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 1015, NameID = 13662, SortOrder = 4, PlanLevel = 100)] public class A12Fafnir(WorldState ws, Actor primary) : BossModule(ws, primary, ArenaCenter, new ArenaBoundsCircle(34.5f)) { - public static readonly WPos ArenaCenter = new(-500, 600); - public static readonly ArenaBoundsCircle DefaultBounds = new(30); - public static readonly ArenaBoundsCircle FireArena = new(16); + public static readonly WPos ArenaCenter = new(-500f, 600f); + public static readonly ArenaBoundsCircle DefaultBounds = new(30f); + public static readonly ArenaBoundsCircle FireArena = new(16f); } diff --git a/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/ArenaChanges.cs b/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/ArenaChanges.cs index 0e0cf34000..e65b83ca03 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/ArenaChanges.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A12Fafnir/ArenaChanges.cs @@ -6,6 +6,7 @@ class ArenaChange(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 (spell.Action.ID == (uint)AID.DarkMatterBlast && Arena.Bounds != A12Fafnir.DefaultBounds) diff --git a/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/A13ArkAngelsStates.cs b/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/A13ArkAngelsStates.cs index 438c5d0338..ac8f7577d4 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/A13ArkAngelsStates.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/A13ArkAngelsStates.cs @@ -27,7 +27,21 @@ public A13ArkAngelsStates(A13ArkAngels module) : base(module) .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .Raw.Update = () => module.Enemies(A13ArkAngels.Bosses).All(x => x.IsDeadOrDestroyed); + .Raw.Update = () => + { + var allDeadOrDestroyed = true; + var enemies = module.Enemies(A13ArkAngels.Bosses); + var count = enemies.Count; + for (var i = 0; i < count; ++i) + { + if (!enemies[i].IsDeadOrDestroyed) + { + allDeadOrDestroyed = false; + break; + } + } + return allDeadOrDestroyed; + }; } private void SinglePhase(uint id) diff --git a/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/ConeDonutCross.cs b/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/ConeDonutCross.cs index b47f205262..2853535265 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/ConeDonutCross.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/ConeDonutCross.cs @@ -11,10 +11,11 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) var check = chain != null && chain.Casters.Count != 0; var aoes = new AOEInstance[count]; + var color = Colors.Danger; for (var i = 0; i < count; ++i) { var aoe = Casters[i]; - aoes[i] = check ? aoe with { Color = Colors.Danger } : aoe; + aoes[i] = check ? aoe with { Color = color } : aoe; } return aoes; } diff --git a/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/Rampage.cs b/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/Rampage.cs index 80344c61af..f7c8d8781b 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/Rampage.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A13ArkAngels/Rampage.cs @@ -12,10 +12,11 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) if (count == 0) return []; var aoes = new AOEInstance[count]; + var color = Colors.Danger; for (var i = 0; i < count; ++i) { var aoe = AOEs[i]; - aoes[i] = i == 0 ? count > 1 ? aoe with { Color = Colors.Danger } : aoe : aoe; + aoes[i] = i == 0 ? count > 1 ? aoe with { Color = color } : aoe : aoe; } return aoes; } diff --git a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs index f6bdf9962b..394608b289 100644 --- a/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs +++ b/BossMod/Modules/Dawntrail/Alliance/A14ShadowLord/A14ShadowLord.cs @@ -12,10 +12,10 @@ public class A14ShadowLord(WorldState ws, Actor primary) : BossModule(ws, primar private const int RadiusSmall = 8; private const int HalfWidth = 2; private const int Edges = 64; - public static readonly WPos ArenaCenter = new(150, 800); - public static readonly ArenaBoundsCircle DefaultBounds = new(30); - public static readonly Polygon[] Circles = [new(new(166.251f, 800), RadiusSmall, Edges), new(new(133.788f, 800), RadiusSmall, Edges), - new(new(150, 816.227f), RadiusSmall, Edges), new(new(150, 783.812f), RadiusSmall, Edges)]; // the circle coordinates are not perfectly placed for some reason, got these from analyzing the collision data + public static readonly WPos ArenaCenter = new(150f, 800f); + public static readonly ArenaBoundsCircle DefaultBounds = new(30f); + public static readonly Polygon[] Circles = [new(new(166.251f, 800f), RadiusSmall, Edges), new(new(133.788f, 800f), RadiusSmall, Edges), + new(new(150f, 816.227f), RadiusSmall, Edges), new(new(150f, 783.812f), RadiusSmall, Edges)]; // the circle coordinates are not perfectly placed for some reason, got these from analyzing the collision data private static readonly RectangleSE[] rects = [new(Circles[1].Center, Circles[2].Center, HalfWidth), new(Circles[1].Center, Circles[3].Center, HalfWidth), new(Circles[3].Center, Circles[0].Center, HalfWidth), new(Circles[0].Center, Circles[2].Center, HalfWidth)]; public static readonly Shape[] Combined = [.. Circles, .. rects]; diff --git a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs index d6a8c3cb81..63b20b949e 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D03SkydeepCenote/D033Maulskull.cs @@ -87,7 +87,7 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) else aoes[i] = aoe with { Risky = false }; } - return []; + return aoes; } public override void OnCastStarted(Actor caster, ActorCastInfo spell) @@ -98,7 +98,7 @@ public override void OnCastStarted(Actor caster, ActorCastInfo spell) case (uint)AID.Stonecarver2: case (uint)AID.Stonecarver3: case (uint)AID.Stonecarver4: - _aoes.Add(new(rect, caster.Position, spell.Rotation, Module.CastFinishAt(spell))); + _aoes.Add(new(rect, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell))); if (_aoes.Count == 2) _aoes.SortBy(x => x.Activation); break; diff --git a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs index 38e8516133..b3c1abe67a 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D092OverseerKanilokka.cs @@ -92,10 +92,12 @@ public override IEnumerable ActiveAOEs(int slot, Actor actor) if (count == 0) return []; var aoes = new AOEInstance[count]; + var act0 = _aoes[0].Activation; + var color = Colors.Danger; for (var i = 0; i < count; ++i) { var aoe = _aoes[i]; - aoes[i] = (aoe.Activation - _aoes[0].Activation).TotalSeconds <= 1.3d ? aoe with { Color = Colors.Danger } : aoe with { Risky = false }; + aoes[i] = (aoe.Activation - act0).TotalSeconds <= 1.3d ? aoe with { Color = color } : aoe with { Risky = false }; } return aoes; } diff --git a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs index 3f05e636d4..752926e729 100644 --- a/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs +++ b/BossMod/Modules/Dawntrail/Dungeon/D09YuweyawataFieldStation/D093Lunipyati.cs @@ -79,7 +79,7 @@ class RagingClaw(BossModule module) : Components.GenericAOEs(module) public override void OnCastStarted(Actor caster, ActorCastInfo spell) { if (spell.Action.ID == (uint)AID.RagingClawFirst) - _aoe = new(cone, caster.Position, spell.Rotation, Module.CastFinishAt(spell)); + _aoe = new(cone, spell.LocXZ, spell.Rotation, Module.CastFinishAt(spell)); } public override void OnEventCast(Actor caster, ActorCastEvent spell) @@ -143,9 +143,9 @@ class LeapingEarth(BossModule module) : Components.GenericAOEs(module) 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(40f, -711), new(38.7f, -705), new(34f, -701.5f), new(28f, -701.4f), new(24f, -704.399f), new(22f, -709.7f), new(23.1f, -715.099f), + new(40f, -711f), new(38.7f, -705f), new(34f, -701.5f), new(28f, -701.4f), new(24f, -704.399f), new(22f, -709.7f), new(23.1f, -715.099f), new(26.5f, -719.499f), new(32f, -721.699f), new(38f, -721.5f), new(43f, -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)]; + new(42.9f, -701.2f), new(38.5f, -697f), new(32.5f, -695.199f)]; private int maxCasts; public override IEnumerable ActiveAOEs(int slot, Actor actor) @@ -202,9 +202,19 @@ private void GenerateAOEsForMixedPattern(float intercardinalOffset, float cardin { var angle = angles[i]; var deg = angle * Angle.RadToDeg; - var isIntercardinal = Angle.AnglesIntercardinals.Any(x => x.AlmostEqual(new(angle), Angle.DegToRad)); + var isIntercardinal = false; + + for (var j = 0; j < 4; ++j) + { + var angle2 = Angle.AnglesIntercardinals[j]; + if (angle2.AlmostEqual(new(angle), Angle.DegToRad)) + { + isIntercardinal = true; + break; + } + } + var adjustedAngle = isIntercardinal ? deg + intercardinalOffset : deg + cardinalOffset; - var rotatedPoints = WPos.GenerateRotatedVertices(D093Lunipyati.ArenaCenter, spiralSmallPoints, adjustedAngle); AddAOEs(WPos.GenerateRotatedVertices(D093Lunipyati.ArenaCenter, spiralSmallPoints, adjustedAngle)); } } diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D031Snatcher.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D031Snatcher.cs index 6df1f3e7a7..c94573e516 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D031Snatcher.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D031Snatcher.cs @@ -20,14 +20,14 @@ public enum AID : uint WhatIsRight = 25139 // Boss->self, 8.0s cast, range 20 180-degree cone } -class Cleave(BossModule module, AID aid) : Components.SimpleAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(40, 90.Degrees())); +class Cleave(BossModule module, AID aid) : Components.SimpleAOEs(module, ActionID.MakeSpell(aid), new AOEShapeCone(40f, 90f.Degrees())); class WhatIsLeft(BossModule module) : Cleave(module, AID.WhatIsLeft); class WhatIsRight(BossModule module) : Cleave(module, AID.WhatIsRight); class LostHope(BossModule module) : Components.TemporaryMisdirection(module, ActionID.MakeSpell(AID.LostHope)); -class Vitriol(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Vitriol), 13); +class Vitriol(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Vitriol), 13f); class NoteOfDespair(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.NoteOfDespair)); -class Wallow(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Wallow), 6); +class Wallow(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.Wallow), 6f); class LastGasp(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.LastGasp)); class D031SnatcherStates : StateMachineBuilder @@ -46,7 +46,8 @@ public D031SnatcherStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (LTS, Malediktus)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 789, NameID = 10717)] -public class D031Snatcher(WorldState ws, Actor primary) : BossModule(ws, primary, DefaultBounds.Center, DefaultBounds) +public class D031Snatcher(WorldState ws, Actor primary) : BossModule(ws, primary, arena.Center, arena) { - private static readonly ArenaBoundsComplex DefaultBounds = new([new Circle(new(-375, 85), 19.5f)], [new Rectangle(new(-375, 106.25f), 20, 2.4f), new Rectangle(new(-375, 61), 20, 2, -30.Degrees())]); + private static readonly ArenaBoundsComplex arena = new([new Polygon(new(-375f, 85f), 19.5f * CosPI.Pi36th, 36)], + [new Rectangle(new(-375f, 105f), 20f, 1.2f), new Rectangle(new(-375f, 61f), 20f, 2f, -30f.Degrees())]); } diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs index a3978ba9c7..c0fe1267d5 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D032Wrecker.cs @@ -23,46 +23,80 @@ public enum AID : uint Withdraw = 27847 // 3731->player, 1.0s cast, single-target, pull 30 between centers } +class ArenaChange(BossModule module) : Components.GenericAOEs(module) +{ + private static readonly AOEShapeDonut donut = new(20f, 30f); + private AOEInstance? _aoe; + + public override IEnumerable ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe); + + public override void OnCastStarted(Actor caster, ActorCastInfo spell) + { + if (spell.Action.ID == (uint)AID.MeaninglessDestruction && Arena.Bounds == D032Wrecker.StartingArena) + _aoe = new(donut, Arena.Center, default, Module.CastFinishAt(spell, 0.7f)); + } + + public override void OnEventEnvControl(byte index, uint state) + { + if (state == 0x00020001 && index == 0x06) + { + Arena.Bounds = D032Wrecker.DefaultArena; + Arena.Center = D032Wrecker.DefaultArena.Center; + _aoe = null; + } + } +} class QueerBubble(BossModule module) : Components.GenericAOEs(module) { - public readonly List _aoes = []; + private readonly AetherSprayFire _aoe = module.FindComponent()!; + public readonly List AOEs = []; private static readonly AOEShapeCircle circle = new(2.5f); public override IEnumerable ActiveAOEs(int slot, Actor actor) { - if (_aoes.Count > 0) - foreach (var a in _aoes.Where(x => !x.IsDead)) - yield return new(circle, a.Position, default, default, Module.FindComponent()!.Active ? Colors.SafeFromAOE : Colors.AOE); + var count = AOEs.Count; + if (count == 0) + return []; + var aoes = new AOEInstance[count]; + var color = Colors.SafeFromAOE; + var index = 0; + for (var i = 0; i < count; ++i) + { + var b = AOEs[i]; + if (!b.IsDead) + aoes[index++] = new(circle, b.Position, Color: _aoe.Active ? color : 0); + } + return aoes[..index]; } public override void OnActorCreated(Actor actor) { - if ((OID)actor.OID == OID.QueerBubble) - _aoes.Add(actor); + if (actor.OID == (uint)OID.QueerBubble) + AOEs.Add(actor); } public override void OnActorDestroyed(Actor actor) { - if ((OID)actor.OID == OID.QueerBubble && _aoes.Count > 0) - _aoes.Remove(actor); + if (AOEs.Count != 0 && actor.OID == (uint)OID.QueerBubble) + AOEs.Remove(actor); } public override void OnCastStarted(Actor caster, ActorCastInfo spell) { - if (_aoes.Count > 0 && (AID)spell.Action.ID == AID.Withdraw) - _aoes.Remove(caster); + if (AOEs.Count != 0 && spell.Action.ID == (uint)AID.Withdraw) + AOEs.Remove(caster); } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (Module.FindComponent()!.Active) + if (_aoe.Active) { - var forbiddenInverted = new List>(); - foreach (var a in _aoes) - forbiddenInverted.Add(ShapeDistance.InvertedCircle(a.Position, 2.5f)); - var activation = Module.CastFinishAt(Module.FindComponent()!.Casters[0].CastInfo); - if (forbiddenInverted.Count != 0) - hints.AddForbiddenZone(ShapeDistance.Intersection(forbiddenInverted), activation); + var forbidden = new List>(6); + var count = AOEs.Count; + for (var i = 0; i < count; ++i) + forbidden.Add(ShapeDistance.InvertedCircle(AOEs[i].Position, 2.5f)); + if (forbidden.Count != 0) + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), Module.CastFinishAt(_aoe.Casters[0].CastInfo)); } else base.AddAIHints(slot, actor, assignment, hints); @@ -70,29 +104,48 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } class MeaninglessDestruction(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.MeaninglessDestruction)); -class PoisonHeartStack(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.PoisonHeartStack), 6, 4, 4); +class PoisonHeartStack(BossModule module) : Components.StackWithCastTargets(module, ActionID.MakeSpell(AID.PoisonHeartStack), 6f, 4, 4); class TotalWreck(BossModule module) : Components.SingleTargetCast(module, ActionID.MakeSpell(AID.TotalWreck)); class AetherSprayWater(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.AetherSprayWater)); 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) + +class AetherSprayWaterKB(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.AetherSprayWater), 13f) { - private static readonly Angle a60 = 60.Degrees(), a10 = 10.Degrees(); + private readonly QueerBubble _aoe = module.FindComponent()!; + private static readonly Angle a60 = 60f.Degrees(), a10 = 10f.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 bool DestinationUnsafe(int slot, Actor actor, WPos pos) + { + foreach (var z in _aoe.ActiveAOEs(slot, actor)) + if (z.Shape.Check(pos, z.Origin, z.Rotation)) + return true; + return !Module.InBounds(pos); + } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var source = Sources(slot, actor).FirstOrDefault(); - if (Module.FindComponent()!.ActiveAOEs(slot, actor).Any() && source != default) + var source = Casters.Count != 0 ? Casters[0] : null; + if (_aoe.AOEs.Count != 0 && source != null) { var forbidden = new List>(7) { - ShapeDistance.InvertedCircle(Arena.Center, 7) + ShapeDistance.InvertedCircle(Arena.Center, 7f) }; + var bubbles = Module.Enemies(OID.QueerBubble); 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 * a60, a10)); - hints.AddForbiddenZone(ShapeDistance.Union(forbidden), source.Activation); + { + var bcount = bubbles.Count; + for (var j = 0; j < bcount; ++j) + { + var b = bubbles[j]; + if (b.Position.AlmostEqual(WPos.RotateAroundOrigin(i * 60f, Arena.Center, b.Position), 1f) && _aoe.AOEs.Contains(b)) + { + forbidden.Add(ShapeDistance.Cone(Arena.Center, 20f, i * a60, a10)); + break; + } + } + } + hints.AddForbiddenZone(ShapeDistance.Union(forbidden), Module.CastFinishAt(source.CastInfo)); } } } @@ -102,10 +155,11 @@ class D032WreckerStates : StateMachineBuilder public D032WreckerStates(BossModule module) : base(module) { TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter(); @@ -113,4 +167,10 @@ public D032WreckerStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 789, NameID = 10718)] -public class D032Wrecker(WorldState ws, Actor primary) : BossModule(ws, primary, new(-295, -354), new ArenaBoundsCircle(20)); +public class D032Wrecker(WorldState ws, Actor primary) : BossModule(ws, primary, StartingArena.Center, StartingArena) +{ + private static readonly WPos arenaCenter = new(-295f, -354f); + public static readonly ArenaBoundsComplex StartingArena = new([new Polygon(arenaCenter, 24.5f, 36)], + [new Rectangle(new(-295f, -328f), 20f, 2.5f), new Rectangle(new(-295f, -379f), 20f, 1.32f)]); + public static readonly ArenaBoundsComplex DefaultArena = new([new Polygon(arenaCenter, 20, 36)]); +} diff --git a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs index 86f87a96ad..a1f37690da 100644 --- a/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs +++ b/BossMod/Modules/Endwalker/Dungeon/D03Vanaspati/D033Svarbhanu.cs @@ -28,15 +28,14 @@ public enum AID : uint class ChaoticUndercurrent(BossModule module) : Components.GenericAOEs(module) { - private enum Pattern { None, BBRR, RRBB, BRRB, RBBR } - private Pattern currentPattern; - private readonly List _aoes = []; - private static readonly AOEShapeRect rect = new(40, 5); - private static readonly Angle rotation = 90.Degrees(); - private const int X = 280; - private static readonly List coords = [new(X, -142), new(X, -152), new(X, -162), new(X, -172)]; + public enum Pattern { None, BBRR, RRBB, BRRB, RBBR } + public Pattern currentPattern; + public readonly List AOEs = new(2); + private static readonly AOEShapeRect rect = new(40f, 5f); + private static readonly Angle rotation = 90f.Degrees(); + private static readonly WPos[] coords = [new(280f, -142f), new(280f, -152f), new(280f, -162f), new(280f, -172f)]; - public override IEnumerable ActiveAOEs(int slot, Actor actor) => _aoes; + public override IEnumerable ActiveAOEs(int slot, Actor actor) => AOEs; public override void OnEventEnvControl(byte index, uint state) { @@ -60,68 +59,69 @@ public override void OnEventEnvControl(byte index, uint state) public override void OnEventCast(Actor caster, ActorCastEvent spell) { - var activation = WorldState.FutureTime(7.7f); - switch ((AID)spell.Action.ID) + switch (spell.Action.ID) { - case AID.ChaoticUndercurrentBlueVisual: - AddAOEsForPattern(true, activation); + case (uint)AID.ChaoticUndercurrentBlueVisual: + AddAOEsForPattern(true); break; - case AID.ChaoticUndercurrentRedVisual: - AddAOEsForPattern(false, activation); + case (uint)AID.ChaoticUndercurrentRedVisual: + AddAOEsForPattern(false); break; - case AID.ChaoticUndercurrentRedRect: - case AID.ChaoticUndercurrentBlueRect: - _aoes.Clear(); + case (uint)AID.ChaoticUndercurrentRedRect: + case (uint)AID.ChaoticUndercurrentBlueRect: + AOEs.Clear(); currentPattern = Pattern.None; break; } } - private void AddAOEsForPattern(bool isBlue, DateTime activation) + private void AddAOEsForPattern(bool isBlue) { switch (currentPattern) { case Pattern.RBBR: - AddAOEs(isBlue ? (1, 2) : (0, 3), activation); + AddAOEs(isBlue ? (1, 2) : (0, 3)); break; case Pattern.BBRR: - AddAOEs(isBlue ? (2, 3) : (0, 1), activation); + AddAOEs(isBlue ? (2, 3) : (0, 1)); break; case Pattern.BRRB: - AddAOEs(isBlue ? (0, 3) : (1, 2), activation); + AddAOEs(isBlue ? (0, 3) : (1, 2)); break; case Pattern.RRBB: - AddAOEs(isBlue ? (0, 1) : (2, 3), activation); + AddAOEs(isBlue ? (0, 1) : (2, 3)); break; } - } - - private void AddAOEs((int, int) indices, DateTime activation) - { - _aoes.Add(new(rect, coords[indices.Item1], rotation, activation)); - _aoes.Add(new(rect, coords[indices.Item2], rotation, activation)); + void AddAOEs((int, int) indices) + { + AddAOE(coords[indices.Item1]); + AddAOE(coords[indices.Item2]); + void AddAOE(WPos pos) + { + var activation = WorldState.FutureTime(7.7d); + AOEs.Add(new(rect, pos, rotation, activation)); + } + } } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var source = Module.FindComponent()!.Sources(slot, actor).FirstOrDefault(); - if (source != default) + if (Module.FindComponent()?.Casters.Count != 0) { } // remove forbidden zones while knockback is active to not confuse the AI else base.AddAIHints(slot, actor, assignment, hints); } } -class CosmicKissSpread(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.CosmicKissSpread), 6); -class CosmicKissCircle(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.CosmicKissCircle), 6); +class CosmicKissSpread(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.CosmicKissSpread), 6f); +class CosmicKissCircle(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.CosmicKissCircle), 6f); class CosmicKissRect(BossModule module) : Components.GenericAOEs(module) { 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 WPos[] coords = [new(X, -142), new(X, -152), new(X, -162), new(X, -172)]; + private static readonly AOEShapeRect rect = new(50f, 5f); + private static readonly Angle rotation = -90f.Degrees(); + private static readonly WPos[] coords = [new(320f, -142), new(320f, -152), new(320f, -162), new(320f, -172)]; public override IEnumerable ActiveAOEs(int slot, Actor actor) { @@ -147,57 +147,93 @@ public override void OnEventEnvControl(byte index, uint state) if (aoeSets.Count != 0) { - AddAOEs(aoeSets[0], 4.3f); - AddAOEs(aoeSets[1], 9.5f); - AddAOEs(aoeSets[2], 14.6f); + AddAOEs(aoeSets[0], 4.3d); + AddAOEs(aoeSets[1], 9.5d); + AddAOEs(aoeSets[2], 14.6d); + } + void AddAOEs(int[] indices, double delay) + { + for (var i = 0; i < 3; ++i) + _aoes.Add(new(rect, coords[indices[i]], rotation, WorldState.FutureTime(delay))); } } } - private void AddAOEs(int[] indices, float 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 (_aoes.Count != 0 && (AID)spell.Action.ID == AID.CosmicKissRect) + if (_aoes.Count != 0 && spell.Action.ID == (uint)AID.CosmicKissRect) _aoes.RemoveAt(0); } } class CosmicKissRaidwide(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.CosmicKiss)); -class CosmicKissKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.CosmicKiss), 13) +class CosmicKissKnockback(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.CosmicKiss), 13f) { - private static readonly Angle a90 = 90.Degrees(), a45 = 45.Degrees(), a0 = 0.Degrees(), a180 = 180.Degrees(); + private static readonly Angle a90 = 90f.Degrees(), a45 = 45f.Degrees(), a180 = 180f.Degrees(); + private readonly ChaoticUndercurrent _aoe = module.FindComponent()!; - 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 bool DestinationUnsafe(int slot, Actor actor, WPos pos) + { + var count = _aoe.AOEs.Count; + for (var i = 0; i < count; ++i) + { + var z = _aoe.AOEs[i]; + if (z.Shape.Check(pos, z.Origin, z.Rotation)) + return true; + } + return !Module.InBounds(pos); + } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - var component = Module.FindComponent()?.ActiveAOEs(slot, actor)?.ToList(); - var source = Sources(slot, actor).FirstOrDefault(); - if (component != null && component.Count != 0 && source != default) + var component = _aoe.AOEs; + var source = Casters.Count != 0 ? Casters[0] : null; + if (component.Count != 0 && source != null) { var forbidden = new List>(2); - if (component!.Any(x => x.Origin.Z == -152) && component!.Any(x => x.Origin.Z == -162)) + + var hasMinus142 = false; + var hasMinus152 = false; + var hasMinus162 = false; + var hasMinus172 = false; + + var count = component.Count; + for (var i = 0; i < count; ++i) { - forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a0, a45)); - forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a180, a45)); + var x = component[i].Origin; + switch (x.Z) + { + case -142f: + hasMinus142 = true; + break; + case -152f: + hasMinus152 = true; + break; + case -162f: + hasMinus162 = true; + break; + case -172f: + hasMinus172 = true; + break; + } } - else if (component!.Any(x => x.Origin.Z == -142) && component!.Any(x => x.Origin.Z == -172)) + + if (hasMinus152 && hasMinus162) + { + forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7f, default, a45)); + forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7f, a180, a45)); + } + else if (hasMinus142 && hasMinus172) { - forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, a90, a45)); - forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7, -a90, a45)); + forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7f, a90, a45)); + forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7f, -a90, a45)); } - else if (component!.Any(x => x.Origin.Z == -142) && component!.Any(x => x.Origin.Z == -152)) - 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) - hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), source.Activation); + else if (hasMinus142 && hasMinus152) + forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7f, a180, a90)); + else + forbidden.Add(ShapeDistance.InvertedCone(Arena.Center, 7f, default, a90)); + hints.AddForbiddenZone(ShapeDistance.Intersection(forbidden), Module.CastFinishAt(source.CastInfo)); } } } @@ -222,4 +258,7 @@ public D033SvarbhanuStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Verified, Contributors = "The Combat Reborn Team (Malediktus, LTS)", GroupType = BossModuleInfo.GroupType.CFC, GroupID = 789, NameID = 10719)] -public class D033Svarbhanu(WorldState ws, Actor primary) : BossModule(ws, primary, new(300, -157), new ArenaBoundsSquare(20)); +public class D033Svarbhanu(WorldState ws, Actor primary) : BossModule(ws, primary, arenaCenter, new ArenaBoundsSquare(20)) +{ + private static readonly WPos arenaCenter = new(300f, -157f); +} diff --git a/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs b/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs index 2d6bf6f7a3..38f0a6cf0c 100644 --- a/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs +++ b/BossMod/Modules/Shadowbringers/Dungeon/D02DohnMheg/D021AencThon.cs @@ -36,10 +36,10 @@ class Geyser(BossModule module) : Components.GenericAOEs(module) { private static readonly AOEShapeCircle circle = new(6f); - private static readonly Dictionary> GeyserPositions = new() + private static readonly Dictionary> GeyserPositions = new() { { - OID.GeyserHelper1, new Dictionary + (uint)OID.GeyserHelper1, new Dictionary { { 0.Degrees(), [new(0f, 14.16f), new(-9f, 45.16f)] }, { 180.Degrees(), [new(9f, 15.16f), new(0f, 46.16f)] }, @@ -48,7 +48,7 @@ class Geyser(BossModule module) : Components.GenericAOEs(module) } }, { - OID.GeyserHelper2, new Dictionary + (uint)OID.GeyserHelper2, new Dictionary { { 0.Degrees(), [new(0f, 35.16f), new(-9f, 15.16f), new(7f, 23.16f)] }, { 90.Degrees(), [new(-15f, 39.16f), new(-7f, 23.16f), new(5f, 30.16f)] }, @@ -81,7 +81,7 @@ public override void OnActorEAnim(Actor actor, uint state) { if (state == 0x00100020) { - if (GeyserPositions.TryGetValue((OID)actor.OID, out var positionsByRotation)) + if (GeyserPositions.TryGetValue(actor.OID, out var positionsByRotation)) { var activation = WorldState.FutureTime(5.1f); foreach (var (rotation, positions) in positionsByRotation) diff --git a/BossMod/Pathfinding/Map.cs b/BossMod/Pathfinding/Map.cs index 12a54832d8..89b21edbf1 100644 --- a/BossMod/Pathfinding/Map.cs +++ b/BossMod/Pathfinding/Map.cs @@ -211,17 +211,23 @@ public void BlockPixelsInside2(Func shape, float maxG) } // enumerate pixels along line starting from (x1, y1) to (x2, y2); first is not returned, last is returned - public List<(int x, int y)> EnumeratePixelsInLine(int x1, int y1, int x2, int y2) + public (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; + var shiftx2x1 = (x2 - x1) >> 31; + var shifty2y1 = (y2 - y1) >> 31; + var absDx = (x2 - x1) ^ (shiftx2x1 - shiftx2x1); + var absDy = (y2 - y1) ^ (shifty2y1 - shifty2y1); + var estimatedLength = (absDx ^ ((absDx ^ absDy) & -(absDx < absDy ? 1 : 0))) + 1; + + var result = new (int x, int y)[estimatedLength]; + + int dx = absDx, sx = x1 < x2 ? 1 : -1; + int dy = -absDy, sy = y1 < y2 ? 1 : -1; int err = dx + dy, e2; - while (true) + for (var i = 0; i < estimatedLength; ++i) { - result.Add((x1, y1)); + result[i] = (x1, y1); if (x1 == x2 && y1 == y2) break; e2 = 2 * err; @@ -236,6 +242,7 @@ public void BlockPixelsInside2(Func shape, float maxG) y1 += sy; } } + return result; } } diff --git a/BossMod/Pathfinding/NavigationDecision.cs b/BossMod/Pathfinding/NavigationDecision.cs index f6afc0ea35..1c59bc72c1 100644 --- a/BossMod/Pathfinding/NavigationDecision.cs +++ b/BossMod/Pathfinding/NavigationDecision.cs @@ -370,6 +370,8 @@ private static (WPos? first, WPos? second) GetFirstWaypoints(ThetaStar pf, Map m { ref var startingNode = ref pf.NodeByIndex(cell); var iterations = 0; // iteration counter to prevent rare cases of infinite loops + var maxIterations = map.Width * map.Height; + if (startingNode.GScore == 0f && startingNode.PathMinG == float.MaxValue) return (null, null); // we're already in safe zone @@ -377,7 +379,7 @@ private static (WPos? first, WPos? second) GetFirstWaypoints(ThetaStar pf, Map m do { ref var node = ref pf.NodeByIndex(cell); - if (pf.NodeByIndex(node.ParentIndex).GScore == 0f || iterations++ == 1000) + if (pf.NodeByIndex(node.ParentIndex).GScore == 0f || iterations++ == maxIterations) { //var dest = pf.CellCenter(cell); // if destination coord matches player coord, do not move along that coordinate, this is used for precise positioning diff --git a/BossMod/Pathfinding/ThetaStar.cs b/BossMod/Pathfinding/ThetaStar.cs index db2abbb07d..8d0611f793 100644 --- a/BossMod/Pathfinding/ThetaStar.cs +++ b/BossMod/Pathfinding/ThetaStar.cs @@ -114,14 +114,30 @@ public bool ExecuteStep() if (nextNode.Score == Score.UltimatelySafe && (_fallbackIndex == _startNodeIndex || CompareNodeScores(ref nextNode, ref _nodes[_fallbackIndex]) < 0)) _fallbackIndex = nextNodeIndex; - if (nextNodeY > _map.MinY) + var haveN = nextNodeY > 0; + var haveS = nextNodeY < _map.Height - 1; + var haveE = nextNodeX > 0; + var haveW = nextNodeX < _map.Width - 1; + if (haveN) + { VisitNeighbour(nextNodeIndex, nextNodeX, nextNodeY - 1, nextNodeIndex - _map.Width, _deltaGSide); - if (nextNodeX > _map.MinX) + if (haveE) + VisitNeighbour(nextNodeIndex, nextNodeX - 1, nextNodeY - 1, nextNodeIndex - _map.Width - 1, _deltaGDiag); + if (haveW) + VisitNeighbour(nextNodeIndex, nextNodeX + 1, nextNodeY - 1, nextNodeIndex - _map.Width + 1, _deltaGDiag); + } + if (haveE) VisitNeighbour(nextNodeIndex, nextNodeX - 1, nextNodeY, nextNodeIndex - 1, _deltaGSide); - if (nextNodeX < _map.MaxX) + if (haveW) VisitNeighbour(nextNodeIndex, nextNodeX + 1, nextNodeY, nextNodeIndex + 1, _deltaGSide); - if (nextNodeY < _map.MaxY) + if (haveS) + { VisitNeighbour(nextNodeIndex, nextNodeX, nextNodeY + 1, nextNodeIndex + _map.Width, _deltaGSide); + if (haveE) + VisitNeighbour(nextNodeIndex, nextNodeX - 1, nextNodeY + 1, nextNodeIndex + _map.Width - 1, _deltaGDiag); + if (haveW) + VisitNeighbour(nextNodeIndex, nextNodeX + 1, nextNodeY + 1, nextNodeIndex + _map.Width + 1, _deltaGDiag); + } return true; } @@ -314,10 +330,6 @@ public bool LineOfSight(int x0, int y0, int x1, int y1, float parentGScore, out y += stepY; cumulativeG += _deltaGDiag; } - - // If we exceed maxG at any point, line of sight fails - // if (cumulativeG - Epsilon > maxG) - // return false; } // If we made it out of the loop, line of sight is good @@ -364,35 +376,23 @@ private void VisitNeighbour(int parentIndex, int nodeX, int nodeY, int nodeIndex if (LineOfSight(gx, gy, nodeX, nodeY, _nodes[grandParentIndex].GScore, out var losLeeway, out var losDist, out var losMinG)) { var losScore = CalculateScore(destPixG, losMinG, losLeeway, nodeIndex); - altNode.GScore = _nodes[grandParentIndex].GScore + losDist; - altNode.ParentIndex = grandParentIndex; - altNode.PathLeeway = losLeeway; - altNode.PathMinG = losMinG; - altNode.Score = losScore; + if (losScore > altNode.Score || losScore == altNode.Score && losLeeway >= (losScore >= Score.Safe ? 0 : altNode.PathLeeway)) + { + parentIndex = grandParentIndex; + altNode.GScore = _nodes[parentIndex].GScore + _deltaGSide * losDist; + altNode.ParentIndex = grandParentIndex; + altNode.PathLeeway = losLeeway; + altNode.PathMinG = losMinG; + altNode.Score = losScore; + } } } - bool shouldVisit; - if (destNode.OpenHeapIndex == 0) + var visit = destNode.OpenHeapIndex == 0 || CompareNodeScores(ref altNode, ref destNode) < (destNode.OpenHeapIndex < 0 ? -1 : 0); + if (visit) { - // never visited, definitely add it - shouldVisit = true; - } - else - { - // compare old vs new - var cmp = CompareNodeScores(ref altNode, ref destNode); - // if altNode is significantly better (cmp < 0) we do the update - shouldVisit = cmp < 0; - } - - if (shouldVisit) - { - // if it was on the closed list, count re-open, etc. if (destNode.OpenHeapIndex < 0) ++NumReopens; - - // adopt altNode destNode = altNode; AddToOpen(nodeIndex); }