From 3832210925c4b3e08f0500a818a3ba08b795044e Mon Sep 17 00:00:00 2001 From: ace Date: Mon, 3 Feb 2025 18:24:07 -0800 Subject: [PATCH 01/56] Angle-relative dashes --- .../Autorotation/Utility/ClassDNCUtility.cs | 28 ++++++++- .../Autorotation/Utility/ClassDRGUtility.cs | 30 ++++++++-- .../Autorotation/Utility/ClassPCTUtility.cs | 28 ++++++++- .../Autorotation/Utility/ClassRPRUtility.cs | 58 ++++++++++++++++++- 4 files changed, 135 insertions(+), 9 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassDNCUtility.cs b/BossMod/Autorotation/Utility/ClassDNCUtility.cs index f86c89668d..9d1048f521 100644 --- a/BossMod/Autorotation/Utility/ClassDNCUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDNCUtility.cs @@ -1,9 +1,12 @@ -namespace BossMod.Autorotation; +using static BossMod.Autorotation.ClassPCTUtility; + +namespace BossMod.Autorotation; public sealed class ClassDNCUtility(RotationModuleManager manager, Actor player) : RoleRangedUtility(manager, player) { - public enum Track { CuringWaltz = SharedTrack.Count, ShieldSamba, Improvisation } + public enum Track { CuringWaltz = SharedTrack.Count, ShieldSamba, Improvisation, EnAvant } public enum SambaOption { None, Use87, Use87IfNotActive, Use88, Use88IfNotActive } + public enum EnAvantStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DNC.AID.CrimsonLotus); @@ -24,6 +27,14 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Improvisation, "Improvisation", "Improv", 300, DNC.AID.Improvisation, 15); + res.Define(Track.EnAvant).As("En Avant", "EnAvant", 30) + .AddOption(EnAvantStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 35) + .AddOption(EnAvantStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 30, 0, ActionTargets.Self, 50) + .AddOption(EnAvantStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 30, 0, ActionTargets.Self, 50) + .AddOption(EnAvantStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 30, 0, ActionTargets.Self, 50) + .AddOption(EnAvantStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 30, 0, ActionTargets.Self, 50) + .AddAssociatedActions(DNC.AID.EnAvant); + return res; } @@ -44,5 +55,18 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, }; if (wantSamba) Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.ShieldSamba), Player, samba.Priority(), samba.Value.ExpireIn); + + var ea = strategy.Option(Track.EnAvant); + if (ea.As() != EnAvantStrategy.None) + { + var angle = ea.As() switch + { + EnAvantStrategy.CharacterBackward => Player.Rotation + 180.Degrees(), + EnAvantStrategy.CameraForward => World.Client.CameraAzimuth + 180.Degrees(), + EnAvantStrategy.CameraBackward => World.Client.CameraAzimuth, + _ => Player.Rotation + }; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.EnAvant), Player, ea.Priority(), ea.Value.ExpireIn, facingAngle: angle); + } } } diff --git a/BossMod/Autorotation/Utility/ClassDRGUtility.cs b/BossMod/Autorotation/Utility/ClassDRGUtility.cs index e4e699f9c1..63e558dfbd 100644 --- a/BossMod/Autorotation/Utility/ClassDRGUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDRGUtility.cs @@ -2,8 +2,9 @@ public sealed class ClassDRGUtility(RotationModuleManager manager, Actor player) : RoleMeleeUtility(manager, player) { - public enum Track { WingedGlide = SharedTrack.Count } + public enum Track { WingedGlide = SharedTrack.Count, ElusiveJump } public enum DashStrategy { None, GapClose, GapCloseHold1 } + public enum ElusiveStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DRG.AID.DragonsongDive); @@ -12,12 +13,20 @@ public static RotationModuleDefinition Definition() var res = new RotationModuleDefinition("Utility: DRG", "Cooldown Planner support for Utility Actions.\nNOTE: This is NOT a rotation preset! All Utility modules are STRICTLY for cooldown-planning usage.", "Utility for planner", "Akechi", RotationModuleQuality.Excellent, BitMask.Build((int)Class.DRG), 100); DefineShared(res, IDLimitBreak3); - res.Define(Track.WingedGlide).As("Winged Glide", "Dash", 20) - .AddOption(DashStrategy.None, "Automatic", "No use.") + res.Define(Track.WingedGlide).As("Winged Glide", "W.Glide", 20) + .AddOption(DashStrategy.None, "None", "No use.") .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Hostile, 45) .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 84) .AddAssociatedActions(DRG.AID.WingedGlide); + res.Define(Track.ElusiveJump).As("Elusive Jump", "E.Jump", 30) + .AddOption(ElusiveStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 35) + .AddOption(ElusiveStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 30, 15, ActionTargets.Self, 35) + .AddOption(ElusiveStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 30, 15, ActionTargets.Self, 35) + .AddOption(ElusiveStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 30, 15, ActionTargets.Self, 35) + .AddOption(ElusiveStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 30, 15, ActionTargets.Self, 35) + .AddAssociatedActions(DRG.AID.ElusiveJump); + return res; } @@ -25,9 +34,22 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); + var ej = strategy.Option(Track.ElusiveJump); + if (ej.As() != ElusiveStrategy.None) + { + var angle = ej.As() switch + { + ElusiveStrategy.CharacterForward => Player.Rotation + 180.Degrees(), + ElusiveStrategy.CameraBackward => World.Client.CameraAzimuth + 180.Degrees(), + ElusiveStrategy.CameraForward => World.Client.CameraAzimuth, + _ => Player.Rotation + }; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRG.AID.ElusiveJump), Player, ej.Priority(), ej.Value.ExpireIn, facingAngle: angle); + } + var dash = strategy.Option(Track.WingedGlide); var dashStrategy = strategy.Option(Track.WingedGlide).As(); - var dashTarget = ResolveTargetOverride(dash.Value); //Smart-Targeting: Target needs to be set in autorotation or CDPlanner to prevent unexpected behavior + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting var distance = Player.DistanceToHitbox(dashTarget); var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRG.AID.WingedGlide)!.MainCooldownGroup].Remaining; var shouldDash = dashStrategy switch diff --git a/BossMod/Autorotation/Utility/ClassPCTUtility.cs b/BossMod/Autorotation/Utility/ClassPCTUtility.cs index 1fe5d225d2..0c26ebd0e7 100644 --- a/BossMod/Autorotation/Utility/ClassPCTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPCTUtility.cs @@ -1,9 +1,13 @@ -namespace BossMod.Autorotation; +using static BossMod.Autorotation.ClassDNCUtility; +using static BossMod.Autorotation.ClassRPRUtility; + +namespace BossMod.Autorotation; public sealed class ClassPCTUtility(RotationModuleManager manager, Actor player) : RoleCasterUtility(manager, player) { - public enum Track { TemperaCoat = SharedTrack.Count } + public enum Track { TemperaCoat = SharedTrack.Count, Smudge } public enum TemperaCoatOption { None, CoatOnly, CoatGrassaASAP, CoatGrassaWhenever } + public enum SmudgeStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(PCT.AID.ChromaticFantasy); @@ -19,6 +23,14 @@ public static RotationModuleDefinition Definition() .AddOption(TemperaCoatOption.CoatGrassaWhenever, "Tempera Coat + Grassa when available", "Use Tempera Coat + Tempera Grassa when weaving or not casting", 90, 10, ActionTargets.Self, 88) .AddAssociatedActions(PCT.AID.TemperaCoat, PCT.AID.TemperaGrassa); + res.Define(Track.Smudge).As("Smudge", uiPriority: 30) + .AddOption(SmudgeStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 35) + .AddOption(SmudgeStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 5, ActionTargets.Self, 20) + .AddOption(SmudgeStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 5, ActionTargets.Self, 20) + .AddOption(SmudgeStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 5, ActionTargets.Self, 20) + .AddOption(SmudgeStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 20, 5, ActionTargets.Self, 20) + .AddAssociatedActions(PCT.AID.Smudge); + return res; } @@ -54,5 +66,17 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaGrassa), Player, tempera.Priority(), tempera.Value.ExpireIn); } } + var smuh = strategy.Option(Track.Smudge); + if (smuh.As() != SmudgeStrategy.None) + { + var angle = smuh.As() switch + { + SmudgeStrategy.CharacterBackward => Player.Rotation + 180.Degrees(), + SmudgeStrategy.CameraForward => World.Client.CameraAzimuth, + SmudgeStrategy.CameraBackward => World.Client.CameraAzimuth + 180.Degrees(), + _ => Player.Rotation + }; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.Smudge), Player, smuh.Priority(), smuh.Value.ExpireIn, facingAngle: angle); + } } } diff --git a/BossMod/Autorotation/Utility/ClassRPRUtility.cs b/BossMod/Autorotation/Utility/ClassRPRUtility.cs index cd407e4153..de6c826d4e 100644 --- a/BossMod/Autorotation/Utility/ClassRPRUtility.cs +++ b/BossMod/Autorotation/Utility/ClassRPRUtility.cs @@ -2,7 +2,10 @@ public sealed class ClassRPRUtility(RotationModuleManager manager, Actor player) : RoleMeleeUtility(manager, player) { - public enum Track { ArcaneCrest = SharedTrack.Count } + public enum Track { ArcaneCrest = SharedTrack.Count, Ingress, Egress, Regress } + public enum IngressStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } + public enum EgressStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } + public enum RegressStrategy { None, Use } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(RPR.AID.TheEnd); @@ -13,6 +16,27 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.ArcaneCrest, "Crest", "", 600, RPR.AID.ArcaneCrest, 5); + res.Define(Track.Ingress).As("Hell's Ingress", "Ingress", 30) + .AddOption(IngressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 20) + .AddOption(IngressStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 10, ActionTargets.Self, 20) + .AddOption(IngressStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 10, ActionTargets.Self, 20) + .AddOption(IngressStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) + .AddOption(IngressStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) + .AddAssociatedActions(RPR.AID.HellsIngress); + + res.Define(Track.Egress).As("Hell's Egress", "Egress", 30) + .AddOption(EgressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 20) + .AddOption(EgressStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 10, ActionTargets.Self, 20) + .AddOption(EgressStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 10, ActionTargets.Self, 20) + .AddOption(EgressStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) + .AddOption(EgressStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) + .AddAssociatedActions(RPR.AID.HellsEgress); + + res.Define(Track.Regress).As("Regress", "Regress", 30) + .AddOption(RegressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 20) + .AddOption(RegressStrategy.Use, "Use", "Use Regress", 20, 10, ActionTargets.Self, 20) + .AddAssociatedActions(RPR.AID.Regress); + return res; } @@ -20,5 +44,37 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); ExecuteSimple(strategy.Option(Track.ArcaneCrest), RPR.AID.ArcaneCrest, Player); + + var reg = strategy.Option(Track.Regress); + var regStrat = strategy.Option(Track.Regress).As(); + var zone = World.Actors.FirstOrDefault(x => x.OID == 0x4C3 && x.OwnerID == Player.InstanceID); + if (regStrat != RegressStrategy.None) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.Regress), Player, reg.Priority(), reg.Value.ExpireIn, targetPos: zone!.PosRot.XYZ()); + + var ing = strategy.Option(Track.Ingress); + if (ing.As() != IngressStrategy.None && Player.FindStatus(RPR.SID.Threshold) == null) + { + var angle = ing.As() switch + { + IngressStrategy.CharacterBackward => Player.Rotation + 180.Degrees(), + IngressStrategy.CameraForward => World.Client.CameraAzimuth + 180.Degrees(), + IngressStrategy.CameraBackward => World.Client.CameraAzimuth, + _ => Player.Rotation + }; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.HellsIngress), Player, ing.Priority(), ing.Value.ExpireIn, facingAngle: angle); + } + + var egg = strategy.Option(Track.Egress); + if (egg.As() != EgressStrategy.None && Player.FindStatus(RPR.SID.Threshold) == null) + { + var angle = egg.As() switch + { + EgressStrategy.CharacterForward => Player.Rotation + 180.Degrees(), + EgressStrategy.CameraBackward => World.Client.CameraAzimuth + 180.Degrees(), + EgressStrategy.CameraForward => World.Client.CameraAzimuth, + _ => Player.Rotation + }; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.HellsEgress), Player, egg.Priority(), egg.Value.ExpireIn, facingAngle: angle); + } } } From c6170ad4cf51dfd26a2949767b403b830795397c Mon Sep 17 00:00:00 2001 From: ace Date: Mon, 3 Feb 2025 18:28:03 -0800 Subject: [PATCH 02/56] Regress minLevel --- BossMod/Autorotation/Utility/ClassRPRUtility.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassRPRUtility.cs b/BossMod/Autorotation/Utility/ClassRPRUtility.cs index de6c826d4e..000d4f9300 100644 --- a/BossMod/Autorotation/Utility/ClassRPRUtility.cs +++ b/BossMod/Autorotation/Utility/ClassRPRUtility.cs @@ -33,8 +33,8 @@ public static RotationModuleDefinition Definition() .AddAssociatedActions(RPR.AID.HellsEgress); res.Define(Track.Regress).As("Regress", "Regress", 30) - .AddOption(RegressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 20) - .AddOption(RegressStrategy.Use, "Use", "Use Regress", 20, 10, ActionTargets.Self, 20) + .AddOption(RegressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 74) + .AddOption(RegressStrategy.Use, "Use", "Use Regress", 0, 0, ActionTargets.Self, 74) .AddAssociatedActions(RPR.AID.Regress); return res; From 4dc4e50186fed15b10cfbf1ad03fb5dc022afa43 Mon Sep 17 00:00:00 2001 From: ace Date: Mon, 3 Feb 2025 18:29:21 -0800 Subject: [PATCH 03/56] nothing to see here --- BossMod/Autorotation/Utility/ClassDNCUtility.cs | 4 +--- BossMod/Autorotation/Utility/ClassPCTUtility.cs | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassDNCUtility.cs b/BossMod/Autorotation/Utility/ClassDNCUtility.cs index 9d1048f521..bf5a1506b3 100644 --- a/BossMod/Autorotation/Utility/ClassDNCUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDNCUtility.cs @@ -1,6 +1,4 @@ -using static BossMod.Autorotation.ClassPCTUtility; - -namespace BossMod.Autorotation; +namespace BossMod.Autorotation; public sealed class ClassDNCUtility(RotationModuleManager manager, Actor player) : RoleRangedUtility(manager, player) { diff --git a/BossMod/Autorotation/Utility/ClassPCTUtility.cs b/BossMod/Autorotation/Utility/ClassPCTUtility.cs index 0c26ebd0e7..e0efe56fac 100644 --- a/BossMod/Autorotation/Utility/ClassPCTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPCTUtility.cs @@ -1,7 +1,4 @@ -using static BossMod.Autorotation.ClassDNCUtility; -using static BossMod.Autorotation.ClassRPRUtility; - -namespace BossMod.Autorotation; +namespace BossMod.Autorotation; public sealed class ClassPCTUtility(RotationModuleManager manager, Actor player) : RoleCasterUtility(manager, player) { From 073dc76a0e883580356ad68b8894dcc434c077e2 Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 4 Feb 2025 06:18:30 -0800 Subject: [PATCH 04/56] oh --- BossMod/Autorotation/Utility/ClassDNCUtility.cs | 2 +- BossMod/Autorotation/Utility/ClassPCTUtility.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassDNCUtility.cs b/BossMod/Autorotation/Utility/ClassDNCUtility.cs index bf5a1506b3..19f2fc140f 100644 --- a/BossMod/Autorotation/Utility/ClassDNCUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDNCUtility.cs @@ -26,7 +26,7 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Improvisation, "Improvisation", "Improv", 300, DNC.AID.Improvisation, 15); res.Define(Track.EnAvant).As("En Avant", "EnAvant", 30) - .AddOption(EnAvantStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 35) + .AddOption(EnAvantStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 50) .AddOption(EnAvantStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 30, 0, ActionTargets.Self, 50) .AddOption(EnAvantStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 30, 0, ActionTargets.Self, 50) .AddOption(EnAvantStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 30, 0, ActionTargets.Self, 50) diff --git a/BossMod/Autorotation/Utility/ClassPCTUtility.cs b/BossMod/Autorotation/Utility/ClassPCTUtility.cs index e0efe56fac..5271c3ab16 100644 --- a/BossMod/Autorotation/Utility/ClassPCTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPCTUtility.cs @@ -21,7 +21,7 @@ public static RotationModuleDefinition Definition() .AddAssociatedActions(PCT.AID.TemperaCoat, PCT.AID.TemperaGrassa); res.Define(Track.Smudge).As("Smudge", uiPriority: 30) - .AddOption(SmudgeStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 35) + .AddOption(SmudgeStrategy.None, "None", "Do not use automatically", 0, 0, ActionTargets.Self, 20) .AddOption(SmudgeStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 5, ActionTargets.Self, 20) .AddOption(SmudgeStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 5, ActionTargets.Self, 20) .AddOption(SmudgeStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 5, ActionTargets.Self, 20) From cac919d3ca9484e9774e354c60110379c0c8759f Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 4 Feb 2025 09:17:24 -0800 Subject: [PATCH 05/56] Regress breaks ar without this condition lmfao --- BossMod/Autorotation/Utility/ClassRPRUtility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BossMod/Autorotation/Utility/ClassRPRUtility.cs b/BossMod/Autorotation/Utility/ClassRPRUtility.cs index 000d4f9300..b9a171ffff 100644 --- a/BossMod/Autorotation/Utility/ClassRPRUtility.cs +++ b/BossMod/Autorotation/Utility/ClassRPRUtility.cs @@ -48,7 +48,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, var reg = strategy.Option(Track.Regress); var regStrat = strategy.Option(Track.Regress).As(); var zone = World.Actors.FirstOrDefault(x => x.OID == 0x4C3 && x.OwnerID == Player.InstanceID); - if (regStrat != RegressStrategy.None) + if (regStrat != RegressStrategy.None && Player.FindStatus(RPR.SID.Threshold) != null) Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.Regress), Player, reg.Priority(), reg.Value.ExpireIn, targetPos: zone!.PosRot.XYZ()); var ing = strategy.Option(Track.Ingress); From 35662f885b0427e104558ec34e604bd3abceaf97 Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 4 Feb 2025 13:43:51 -0800 Subject: [PATCH 06/56] WAR fixes, AST cleanup --- .../Standard/akechi/Tank/AkechiWAR.cs | 13 ++- .../Autorotation/Utility/ClassASTUtility.cs | 83 ++++--------------- 2 files changed, 25 insertions(+), 71 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs index 3f4a81eb4a..92f233a444 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs @@ -142,10 +142,10 @@ private GCDPriority FellCleave() public enum GCDPriority { None = 0, - Standard = 100, Gauge = 300, PrimalRuination = 400, DelayFC = 390, + Standard = 400, FlexibleFC = 470, FlexibleIR = 490, PrimalRend = 500, @@ -217,6 +217,8 @@ public enum OGCDPriority #endregion public bool IsRiskingGauge() { + if (InnerRelease.Stacks > 0) + return true; if (BeastGauge >= 90 && //if 90 ComboLastMove is AID.Maim) //next is Storm's Path, which overcaps. We need spender here return true; @@ -324,6 +326,9 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) // #endregion ShouldUseAOE = ShouldUseAOECircle(5).OnThreeOrMore; + + BurstWindowLeft = (InnerRelease.CD >= 40) ? 1.0f : 0.0f; + BurstWindowIn = (InnerRelease.CD == 0) ? 1.0f : 0.0f; (BestSplashTargets, NumSplashTargets) = GetBestTarget(primaryTarget, 20, IsSplashTarget); BestSplashTarget = Unlocked(AID.PrimalRend) && NumSplashTargets >= 2 ? BestSplashTargets : primaryTarget; (TwoMinuteLeft, TwoMinuteIn) = EstimateRaidBuffTimings(primaryTarget?.Actor); @@ -337,20 +342,20 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) // QueueGCD(BestRotation(), //queue the next single-target combo action only if combo is finished TargetChoice(strategy.Option(SharedTrack.AOE)) //Get target choice ?? primaryTarget?.Actor, //if none, pick primary target - GCDPriority.Standard); //with priority for 123/10 combo actions + IsRiskingGauge() ? GCDPriority.Standard - 500 : GCDPriority.Standard); //with priority for 123/10 combo actions } if (strategy.ForceST()) //if Force Single Target option is picked { QueueGCD(ST(), TargetChoice(strategy.Option(SharedTrack.AOE)) //Get target choice ?? primaryTarget?.Actor, //if none, pick primary target - GCDPriority.Standard); //with priority for 123/10 combo actions + IsRiskingGauge() ? GCDPriority.Standard - 500 : GCDPriority.Standard); //with priority for 123/10 combo actions } if (strategy.ForceAOE()) //if Force AOE option is picked { QueueGCD(AOE(), Player, - GCDPriority.Standard); //with priority for 123/10 combo actions + IsRiskingGauge() ? GCDPriority.Standard - 500 : GCDPriority.Standard); //with priority for 123/10 combo actions } #endregion diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index b34badf2c7..cad87ca361 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -33,8 +33,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.EarthlyStar).As("EarthlyStar", "E.Star", 200) //AoE GCD heal, 60s CD, 10s + 10s effect duration .AddOption(StarOption.None, "None", "Do not use automatically") - .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Hostile, 62) - .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Hostile, 62) + .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 62) + .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Self, 62) .AddAssociatedActions(AST.AID.EarthlyStar, AST.AID.StellarDetonation); DefineSimpleConfig(res, Track.CelestialIntersection, "CelestialIntersection", "C.Inter", 100, AST.AID.CelestialIntersection, 30); //ST oGCD heal/shield, 30s CD (60s Total), 2 charges @@ -75,7 +75,10 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Exaltation), AST.AID.Exaltation, Player); ExecuteSimple(strategy.Option(Track.SunSign), AST.AID.SunSign, Player); + //TODO: These should work with all Area targeting options? Able to target Waymarks, Center, AbsolutePoint, etc. + //Figure out how to make compatible with WPos var star = strategy.Option(Track.EarthlyStar); + var starTarget = ResolveTargetOverride(star.Value) ?? primaryTarget ?? Player; var starAction = star.As() switch { StarOption.Use => AST.AID.EarthlyStar, @@ -83,7 +86,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (starAction != default) - QueueOGCD(starAction, ResolveTargetOverride(star.Value) ?? primaryTarget ?? Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), starTarget, star.Priority(), star.Value.ExpireIn, targetPos: starTarget!.PosRot.XYZ()); //Aspected Helios full execution var heliosUp = StatusDetails(Player, AST.SID.AspectedHelios, Player.InstanceID).Left > 0.1f || StatusDetails(Player, AST.SID.HeliosConjunction, Player.InstanceID).Left > 0.1f; @@ -95,18 +98,24 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (heliosAction != default && !heliosUp) - QueueGCD(heliosAction, Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(heliosAction), Player, helios.Priority(), helios.Value.ExpireIn); //Horoscope full execution var horo = strategy.Option(Track.Horoscope); + var horoStrat = horo.As() switch + { + HoroscopeOption.Use => Player.FindStatus(AST.SID.Horoscope) == null, + HoroscopeOption.End => Player.FindStatus(AST.SID.Horoscope) != null, + _ => default + }; var horoAction = horo.As() switch { HoroscopeOption.Use => AST.AID.Horoscope, HoroscopeOption.End => AST.AID.HoroscopeEnd, _ => default }; - if (horoAction != default) - QueueOGCD(horoAction, Player); + if (horoStrat != default && horoAction != default) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(horoAction), Player, horo.Priority(), horo.Value.ExpireIn); var cosmos = strategy.Option(Track.Macrocosmos); var cosmosAction = cosmos.As() switch @@ -116,66 +125,6 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (cosmosAction != default) - QueueOGCD(cosmosAction, primaryTarget); - } - - #region Core Execution Helpers - - public AST.AID NextGCD; //Next global cooldown action to be used - public void QueueGCD

(AST.AID aid, Actor? target, P priority, float delay = 0) where P : Enum - => QueueGCD(aid, target, (int)(object)priority, delay); - - public void QueueGCD(AST.AID aid, Actor? target, int priority = 8, float delay = 0) - { - var NextGCDPrio = 0; - - if (priority == 0) - return; - - if (QueueAction(aid, target, ActionQueue.Priority.High, delay) && priority > NextGCDPrio) - { - NextGCD = aid; - } - } - - public void QueueOGCD

(AST.AID aid, Actor? target, P priority, float delay = 0) where P : Enum - => QueueOGCD(aid, target, (int)(object)priority, delay); - - public void QueueOGCD(AST.AID aid, Actor? target, int priority = 4, float delay = 0) - { - if (priority == 0) - return; - - QueueAction(aid, target, ActionQueue.Priority.Medium + priority, delay); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(cosmosAction), primaryTarget, cosmos.Priority(), cosmos.Value.ExpireIn); } - - public bool QueueAction(AST.AID aid, Actor? target, float priority, float delay) - { - if ((uint)(object)aid == 0) - return false; - - var def = ActionDefinitions.Instance.Spell(aid); - if (def == null) - return false; - - if (def.Range != 0 && target == null) - { - return false; - } - - Vector3 targetPos = default; - - if (def.AllowedTargets.HasFlag(ActionTargets.Area)) - { - if (def.Range == 0) - targetPos = Player.PosRot.XYZ(); - else if (target != null) - targetPos = target.PosRot.XYZ(); - } - - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, priority, delay: delay, castTime: def.CastTime, targetPos: targetPos); // TODO[cast-time]: this probably needs explicit cast-time argument (adjusted by swiftcast etc) - return true; - } - #endregion - } From cbc864a0fdb0433b152d63a6ee841cc00dd7d3f2 Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 4 Feb 2025 13:55:50 -0800 Subject: [PATCH 07/56] NIN cleanup --- BossMod/Autorotation/Utility/ClassNINUtility.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 997d705905..16a35bcc43 100644 --- a/BossMod/Autorotation/Utility/ClassNINUtility.cs +++ b/BossMod/Autorotation/Utility/ClassNINUtility.cs @@ -16,7 +16,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.Shukuchi).As("Shukuchi", "Dash", 20) .AddOption(DashStrategy.None, "Automatic", "No use.") - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Hostile, 45) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 45) .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 74) .AddAssociatedActions(NIN.AID.Shukuchi); @@ -29,6 +29,8 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.ShadeShift), NIN.AID.ShadeShift, Player); // TODO: revise, this doesn't look correct (shukuchi is area targeted, so it should use that; probably should expose options to use regardless of melee distance...) + //These should work with all Area targeting options? Able to target Waymarks, Center, AbsolutePoint, etc. + //Figure out how to make compatible with WPos var dash = strategy.Option(Track.Shukuchi); var dashStrategy = strategy.Option(Track.Shukuchi).As(); var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting @@ -38,10 +40,10 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, { DashStrategy.None => false, DashStrategy.GapClose => distance is > 3f and <= 20f, - DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd <= 60.5f, // TODO: this condition doesn't look correct... + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd < 0.6f, _ => false, }; if (shouldDash && dashTarget != null) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, 0, 0, dashTarget.PosRot.XYZ()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, targetPos: dashTarget.PosRot.XYZ()); } } From 200e9a0bec69e71e9fd43d93489bedcbf8cf219e Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 4 Feb 2025 14:10:06 -0800 Subject: [PATCH 08/56] SCH cleanup, remove summons and replace with Seraph only Summons only worked correctly with Seraph, so I just made it its own option. Will return to it later. --- .../Autorotation/Utility/ClassASTUtility.cs | 10 ++- .../Autorotation/Utility/ClassNINUtility.cs | 4 +- .../Autorotation/Utility/ClassSCHUtility.cs | 81 +++---------------- 3 files changed, 22 insertions(+), 73 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index cad87ca361..01eda812e5 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -33,7 +33,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.EarthlyStar).As("EarthlyStar", "E.Star", 200) //AoE GCD heal, 60s CD, 10s + 10s effect duration .AddOption(StarOption.None, "None", "Do not use automatically") - .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 62) + .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 62) // TODO: should use ActionTargets.Area, but the Point options do not work yet .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Self, 62) .AddAssociatedActions(AST.AID.EarthlyStar, AST.AID.StellarDetonation); @@ -118,13 +118,19 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(horoAction), Player, horo.Priority(), horo.Value.ExpireIn); var cosmos = strategy.Option(Track.Macrocosmos); + var cosmosStrat = cosmos.As() switch + { + MacrocosmosOption.Use => Player.FindStatus(AST.SID.Macrocosmos) == null, + MacrocosmosOption.End => Player.FindStatus(AST.SID.Macrocosmos) != null, + _ => default + }; var cosmosAction = cosmos.As() switch { MacrocosmosOption.Use => AST.AID.Macrocosmos, MacrocosmosOption.End => AST.AID.MicrocosmosEnd, _ => default }; - if (cosmosAction != default) + if (cosmosStrat != default) Hints.ActionsToExecute.Push(ActionID.MakeSpell(cosmosAction), primaryTarget, cosmos.Priority(), cosmos.Value.ExpireIn); } } diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 16a35bcc43..395e0b740f 100644 --- a/BossMod/Autorotation/Utility/ClassNINUtility.cs +++ b/BossMod/Autorotation/Utility/ClassNINUtility.cs @@ -16,8 +16,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.Shukuchi).As("Shukuchi", "Dash", 20) .AddOption(DashStrategy.None, "Automatic", "No use.") - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 45) - .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 74) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 45) // TODO: should use ActionTargets.Area, but the Point options do not work yet + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 74) .AddAssociatedActions(NIN.AID.Shukuchi); return res; diff --git a/BossMod/Autorotation/Utility/ClassSCHUtility.cs b/BossMod/Autorotation/Utility/ClassSCHUtility.cs index 2d7bbe67cb..af7e3c08ea 100644 --- a/BossMod/Autorotation/Utility/ClassSCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSCHUtility.cs @@ -2,13 +2,12 @@ public sealed class ClassSCHUtility(RotationModuleManager manager, Actor player) : RoleHealerUtility(manager, player) { - public enum Track { WhisperingDawn = SharedTrack.Count, Adloquium, Succor, FeyIllumination, Lustrate, SacredSoil, Indomitability, DeploymentTactics, EmergencyTactics, Dissipation, Excogitation, Aetherpact, Recitation, FeyBlessing, Consolation, Protraction, Expedient, Seraphism, Summons } + public enum Track { WhisperingDawn = SharedTrack.Count, Adloquium, Succor, FeyIllumination, Lustrate, SacredSoil, Indomitability, DeploymentTactics, EmergencyTactics, Dissipation, Excogitation, Aetherpact, Recitation, FeyBlessing, Consolation, Protraction, Expedient, Seraphism, Seraph } public enum SuccorOption { None, Succor, Concitation } public enum SacredSoilOption { None, Use, UseEx } public enum DeployOption { None, Use, UseEx } public enum AetherpactOption { None, Use, End } public enum RecitationOption { None, Use, UseEx } - public enum PetOption { None, Eos, Seraph } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(SCH.AID.AngelFeathers); @@ -31,8 +30,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.SacredSoil).As("Sacred Soil", "S.Soil", 200) .AddOption(SacredSoilOption.None, "None", "Do not use automatically") - .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.All, 50, 77) - .AddOption(SacredSoilOption.UseEx, "UseEx", "Use Enhanced Sacred Soil", 30, 15, ActionTargets.All, 78) + .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 50, 77) // TODO: should use ActionTargets.Area, but the Point options do not work yet + .AddOption(SacredSoilOption.UseEx, "UseEx", "Use Enhanced Sacred Soil", 30, 15, ActionTargets.Area, 78) .AddAssociatedActions(SCH.AID.SacredSoil); DefineSimpleConfig(res, Track.Indomitability, "Indomitability", "Indom.", 90, SCH.AID.Indomitability); @@ -64,13 +63,7 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Protraction, "Protraction", "Prot.", 110, SCH.AID.Protraction, 10); DefineSimpleConfig(res, Track.Expedient, "Expedient", "Exped.", 200, SCH.AID.Expedient, 20); DefineSimpleConfig(res, Track.Seraphism, "Seraphism", "Seraphism", 300, SCH.AID.Seraphism, 20); - - // Pet Summons - res.Define(Track.Summons).As("Pet", "", 180) - .AddOption(PetOption.None, "None", "Do not use automatically") - .AddOption(PetOption.Eos, "Eos", "Summon Eos", 2, 0, ActionTargets.Self, 4) - .AddOption(PetOption.Seraph, "Seraph", "Summon Seraph", 120, 22, ActionTargets.Self, 80) - .AddAssociatedActions(SCH.AID.SummonEos, SCH.AID.SummonSeraph); + DefineSimpleConfig(res, Track.Seraph, "Seraph", "Seraph", 300, SCH.AID.SummonSeraph, 20); return res; } @@ -91,6 +84,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Protraction), SCH.AID.Protraction, Player); ExecuteSimple(strategy.Option(Track.Expedient), SCH.AID.Expedient, Player); ExecuteSimple(strategy.Option(Track.Seraphism), SCH.AID.Seraphism, Player); + ExecuteSimple(strategy.Option(Track.Seraph), SCH.AID.SummonSeraph, Player); var shieldUp = StatusDetails(Player, SCH.SID.Galvanize, Player.InstanceID).Left > 0.1f || StatusDetails(Player, SGE.SID.EukrasianPrognosis, Player.InstanceID).Left > 0.1f; var succ = strategy.Option(Track.Succor); @@ -101,21 +95,21 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (succAction != default && !shieldUp) - QueueOGCD(succAction, Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(succAction), Player, succ.Priority(), succ.Value.ExpireIn, castTime: 2); // TODO[cast-time]: adjustment (swiftcast etc) var soil = strategy.Option(Track.SacredSoil); + var soilTarget = ResolveTargetOverride(soil.Value) ?? primaryTarget ?? Player; var soilAction = soil.As() switch { SacredSoilOption.Use or SacredSoilOption.UseEx => SCH.AID.SacredSoil, _ => default }; if (soilAction != default) - QueueOGCD(soilAction, ResolveTargetOverride(soil.Value) ?? primaryTarget ?? Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(soilAction), soilTarget, soil.Priority(), soil.Value.ExpireIn, targetPos: soilTarget.PosRot.XYZ()); var deploy = strategy.Option(Track.DeploymentTactics); if (deploy.As() != DeployOption.None) - QueueOGCD(SCH.AID.DeploymentTactics, Player); - + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.DeploymentTactics), Player, deploy.Priority(), deploy.Value.ExpireIn); var pact = strategy.Option(Track.Aetherpact); var pactStrat = pact.As(); @@ -124,64 +118,13 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, if (pactStrat != AetherpactOption.None) { if (pactStrat == AetherpactOption.Use && !juicing) - QueueOGCD(SCH.AID.Aetherpact, pactTarget); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.Aetherpact), pactTarget, pact.Priority(), pact.Value.ExpireIn); if (pactStrat == AetherpactOption.End && juicing) - QueueOGCD(SCH.AID.DissolveUnion, pactTarget); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.DissolveUnion), pactTarget, pact.Priority(), pact.Value.ExpireIn); } var recit = strategy.Option(Track.Recitation); if (recit.As() != RecitationOption.None) - QueueOGCD(SCH.AID.Recitation, Player); - - var pet = strategy.Option(Track.Summons); - var petSummons = pet.As() switch - { - PetOption.Eos => SCH.AID.SummonEos, - PetOption.Seraph => SCH.AID.SummonSeraph, - _ => default - }; - if (petSummons != default) - QueueOGCD(petSummons, Player); - } - - #region Core Execution Helpers - public void QueueOGCD

(SCH.AID aid, Actor? target, P priority, float delay = 0) where P : Enum - => QueueOGCD(aid, target, (int)(object)priority, delay); - - public void QueueOGCD(SCH.AID aid, Actor? target, int priority = 4, float delay = 0) - { - if (priority == 0) - return; - - QueueAction(aid, target, ActionQueue.Priority.Medium + priority, delay); - } - - public bool QueueAction(SCH.AID aid, Actor? target, float priority, float delay) - { - if ((uint)(object)aid == 0) - return false; - - var def = ActionDefinitions.Instance.Spell(aid); - if (def == null) - return false; - - if (def.Range != 0 && target == null) - { - return false; - } - - Vector3 targetPos = default; - - if (def.AllowedTargets.HasFlag(ActionTargets.Area)) - { - if (def.Range == 0) - targetPos = Player.PosRot.XYZ(); - else if (target != null) - targetPos = target.PosRot.XYZ(); - } - - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, priority, delay: delay, castTime: def.CastTime, targetPos: targetPos); // TODO[cast-time]: this probably needs explicit cast-time argument (adjusted by swiftcast etc) - return true; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.Recitation), Player, recit.Priority(), recit.Value.ExpireIn); } - #endregion } From 181eb12b3be2539148ecc6bbca4c812c5799db4b Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 4 Feb 2025 14:15:24 -0800 Subject: [PATCH 09/56] hm --- BossMod/Autorotation/Utility/ClassASTUtility.cs | 4 ++-- BossMod/Autorotation/Utility/ClassNINUtility.cs | 2 +- BossMod/Autorotation/Utility/ClassSCHUtility.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index 01eda812e5..4361469343 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -33,7 +33,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.EarthlyStar).As("EarthlyStar", "E.Star", 200) //AoE GCD heal, 60s CD, 10s + 10s effect duration .AddOption(StarOption.None, "None", "Do not use automatically") - .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 62) // TODO: should use ActionTargets.Area, but the Point options do not work yet + .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 62) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Self, 62) .AddAssociatedActions(AST.AID.EarthlyStar, AST.AID.StellarDetonation); @@ -86,7 +86,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (starAction != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), starTarget, star.Priority(), star.Value.ExpireIn, targetPos: starTarget!.PosRot.XYZ()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), starTarget, star.Priority(), star.Value.ExpireIn, targetPos: starTarget.PosRot.XYZ()); //Aspected Helios full execution var heliosUp = StatusDetails(Player, AST.SID.AspectedHelios, Player.InstanceID).Left > 0.1f || StatusDetails(Player, AST.SID.HeliosConjunction, Player.InstanceID).Left > 0.1f; diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 395e0b740f..667137e532 100644 --- a/BossMod/Autorotation/Utility/ClassNINUtility.cs +++ b/BossMod/Autorotation/Utility/ClassNINUtility.cs @@ -16,7 +16,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.Shukuchi).As("Shukuchi", "Dash", 20) .AddOption(DashStrategy.None, "Automatic", "No use.") - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 45) // TODO: should use ActionTargets.Area, but the Point options do not work yet + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 45) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 74) .AddAssociatedActions(NIN.AID.Shukuchi); diff --git a/BossMod/Autorotation/Utility/ClassSCHUtility.cs b/BossMod/Autorotation/Utility/ClassSCHUtility.cs index af7e3c08ea..c396023e37 100644 --- a/BossMod/Autorotation/Utility/ClassSCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSCHUtility.cs @@ -30,7 +30,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.SacredSoil).As("Sacred Soil", "S.Soil", 200) .AddOption(SacredSoilOption.None, "None", "Do not use automatically") - .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 50, 77) // TODO: should use ActionTargets.Area, but the Point options do not work yet + .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 50, 77) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet .AddOption(SacredSoilOption.UseEx, "UseEx", "Use Enhanced Sacred Soil", 30, 15, ActionTargets.Area, 78) .AddAssociatedActions(SCH.AID.SacredSoil); From 2c316fb394e3b1da025f7652ccc3658d292629f9 Mon Sep 17 00:00:00 2001 From: Akechi Date: Wed, 5 Feb 2025 11:14:14 -0800 Subject: [PATCH 10/56] we're gaming --- BossMod/Autorotation/Utility/ClassASTUtility.cs | 7 ++----- BossMod/Autorotation/Utility/ClassNINUtility.cs | 13 +++++-------- BossMod/Autorotation/Utility/ClassSCHUtility.cs | 5 ++--- BossMod/Data/Actor.cs | 1 + 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index 4361469343..5d5ae61db5 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -33,7 +33,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.EarthlyStar).As("EarthlyStar", "E.Star", 200) //AoE GCD heal, 60s CD, 10s + 10s effect duration .AddOption(StarOption.None, "None", "Do not use automatically") - .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 62) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet + .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Area, 62) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Self, 62) .AddAssociatedActions(AST.AID.EarthlyStar, AST.AID.StellarDetonation); @@ -75,10 +75,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Exaltation), AST.AID.Exaltation, Player); ExecuteSimple(strategy.Option(Track.SunSign), AST.AID.SunSign, Player); - //TODO: These should work with all Area targeting options? Able to target Waymarks, Center, AbsolutePoint, etc. - //Figure out how to make compatible with WPos var star = strategy.Option(Track.EarthlyStar); - var starTarget = ResolveTargetOverride(star.Value) ?? primaryTarget ?? Player; var starAction = star.As() switch { StarOption.Use => AST.AID.EarthlyStar, @@ -86,7 +83,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (starAction != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), starTarget, star.Priority(), star.Value.ExpireIn, targetPos: starTarget.PosRot.XYZ()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), null, star.Priority(), star.Value.ExpireIn, targetPos: ResolveTargetLocation(star.Value).ToVec3()); //Aspected Helios full execution var heliosUp = StatusDetails(Player, AST.SID.AspectedHelios, Player.InstanceID).Left > 0.1f || StatusDetails(Player, AST.SID.HeliosConjunction, Player.InstanceID).Left > 0.1f; diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 667137e532..9a020fffb4 100644 --- a/BossMod/Autorotation/Utility/ClassNINUtility.cs +++ b/BossMod/Autorotation/Utility/ClassNINUtility.cs @@ -16,8 +16,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.Shukuchi).As("Shukuchi", "Dash", 20) .AddOption(DashStrategy.None, "Automatic", "No use.") - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 45) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet - .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Party | ActionTargets.Hostile, 74) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Area, 45) + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Area, 74) .AddAssociatedActions(NIN.AID.Shukuchi); return res; @@ -29,12 +29,9 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.ShadeShift), NIN.AID.ShadeShift, Player); // TODO: revise, this doesn't look correct (shukuchi is area targeted, so it should use that; probably should expose options to use regardless of melee distance...) - //These should work with all Area targeting options? Able to target Waymarks, Center, AbsolutePoint, etc. - //Figure out how to make compatible with WPos var dash = strategy.Option(Track.Shukuchi); var dashStrategy = strategy.Option(Track.Shukuchi).As(); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting - var distance = Player.DistanceToHitbox(dashTarget); + var distance = Player.DistanceToPoint(ResolveTargetLocation(dash.Value)); var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(NIN.AID.Shukuchi)!.MainCooldownGroup].Remaining; var shouldDash = dashStrategy switch { @@ -43,7 +40,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd < 0.6f, _ => false, }; - if (shouldDash && dashTarget != null) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, targetPos: dashTarget.PosRot.XYZ()); + if (shouldDash) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, targetPos: ResolveTargetLocation(dash.Value).ToVec3()); } } diff --git a/BossMod/Autorotation/Utility/ClassSCHUtility.cs b/BossMod/Autorotation/Utility/ClassSCHUtility.cs index c396023e37..030a9639c2 100644 --- a/BossMod/Autorotation/Utility/ClassSCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSCHUtility.cs @@ -30,7 +30,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.SacredSoil).As("Sacred Soil", "S.Soil", 200) .AddOption(SacredSoilOption.None, "None", "Do not use automatically") - .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.Party | ActionTargets.Self | ActionTargets.Hostile, 50, 77) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet + .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.Area, 50, 77) .AddOption(SacredSoilOption.UseEx, "UseEx", "Use Enhanced Sacred Soil", 30, 15, ActionTargets.Area, 78) .AddAssociatedActions(SCH.AID.SacredSoil); @@ -98,14 +98,13 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(succAction), Player, succ.Priority(), succ.Value.ExpireIn, castTime: 2); // TODO[cast-time]: adjustment (swiftcast etc) var soil = strategy.Option(Track.SacredSoil); - var soilTarget = ResolveTargetOverride(soil.Value) ?? primaryTarget ?? Player; var soilAction = soil.As() switch { SacredSoilOption.Use or SacredSoilOption.UseEx => SCH.AID.SacredSoil, _ => default }; if (soilAction != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(soilAction), soilTarget, soil.Priority(), soil.Value.ExpireIn, targetPos: soilTarget.PosRot.XYZ()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(soilAction), null, soil.Priority(), soil.Value.ExpireIn, targetPos: ResolveTargetLocation(soil.Value).ToVec3()); var deploy = strategy.Option(Track.DeploymentTactics); if (deploy.As() != DeployOption.None) diff --git a/BossMod/Data/Actor.cs b/BossMod/Data/Actor.cs index aa4e7f06fa..2e63cf7ddf 100644 --- a/BossMod/Data/Actor.cs +++ b/BossMod/Data/Actor.cs @@ -168,6 +168,7 @@ public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string nam public Angle AngleTo(Actor other) => Angle.FromDirection(other.Position - Position); public float DistanceToHitbox(Actor? other) => other == null ? float.MaxValue : (other.Position - Position).Length() - other.HitboxRadius - HitboxRadius; + public float DistanceToPoint(WPos pos) => (pos - Position).Length(); public override string ToString() => $"{OID:X} '{Name}' <{InstanceID:X}>"; } From 147d50baab95e1e78d04b5ad878449c2123a784a Mon Sep 17 00:00:00 2001 From: Akechi Date: Wed, 5 Feb 2025 11:17:07 -0800 Subject: [PATCH 11/56] . --- BossMod/Autorotation/Utility/ClassASTUtility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index 5d5ae61db5..715a741471 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -33,7 +33,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.EarthlyStar).As("EarthlyStar", "E.Star", 200) //AoE GCD heal, 60s CD, 10s + 10s effect duration .AddOption(StarOption.None, "None", "Do not use automatically") - .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Area, 62) // TODO: should use ActionTargets.Area, but the Point options do not work outside of AI yet + .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Area, 62) .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Self, 62) .AddAssociatedActions(AST.AID.EarthlyStar, AST.AID.StellarDetonation); From 3fc7b5c1f2c73064b2c7f445ef8981a02fb744d1 Mon Sep 17 00:00:00 2001 From: Akechi Date: Wed, 5 Feb 2025 12:14:38 -0800 Subject: [PATCH 12/56] `Player.Pos.Y` --- BossMod/Autorotation/Utility/ClassASTUtility.cs | 2 +- BossMod/Autorotation/Utility/ClassNINUtility.cs | 2 +- BossMod/Autorotation/Utility/ClassSCHUtility.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index 715a741471..c8e7b4ff1e 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -83,7 +83,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (starAction != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), null, star.Priority(), star.Value.ExpireIn, targetPos: ResolveTargetLocation(star.Value).ToVec3()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), null, star.Priority(), star.Value.ExpireIn, targetPos: ResolveTargetLocation(star.Value).ToVec3(Player.PosRot.Y)); //Aspected Helios full execution var heliosUp = StatusDetails(Player, AST.SID.AspectedHelios, Player.InstanceID).Left > 0.1f || StatusDetails(Player, AST.SID.HeliosConjunction, Player.InstanceID).Left > 0.1f; diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 9a020fffb4..4b893a82e0 100644 --- a/BossMod/Autorotation/Utility/ClassNINUtility.cs +++ b/BossMod/Autorotation/Utility/ClassNINUtility.cs @@ -41,6 +41,6 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => false, }; if (shouldDash) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, targetPos: ResolveTargetLocation(dash.Value).ToVec3()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, targetPos: ResolveTargetLocation(dash.Value).ToVec3(Player.PosRot.Y)); } } diff --git a/BossMod/Autorotation/Utility/ClassSCHUtility.cs b/BossMod/Autorotation/Utility/ClassSCHUtility.cs index 030a9639c2..e850fcc360 100644 --- a/BossMod/Autorotation/Utility/ClassSCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSCHUtility.cs @@ -104,7 +104,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (soilAction != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(soilAction), null, soil.Priority(), soil.Value.ExpireIn, targetPos: ResolveTargetLocation(soil.Value).ToVec3()); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(soilAction), null, soil.Priority(), soil.Value.ExpireIn, targetPos: ResolveTargetLocation(soil.Value).ToVec3(Player.PosRot.Y)); var deploy = strategy.Option(Track.DeploymentTactics); if (deploy.As() != DeployOption.None) From 37877d20ac565d4acae1157fa6381042270a97dc Mon Sep 17 00:00:00 2001 From: Akechi Date: Fri, 7 Feb 2025 05:44:49 -0800 Subject: [PATCH 13/56] Passage of Arms (I forgot about this) --- .../Autorotation/Utility/ClassPLDUtility.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassPLDUtility.cs b/BossMod/Autorotation/Utility/ClassPLDUtility.cs index 624bcc528b..e2ec8205e3 100644 --- a/BossMod/Autorotation/Utility/ClassPLDUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPLDUtility.cs @@ -1,10 +1,13 @@ -namespace BossMod.Autorotation; +using static BossMod.Autorotation.ClassPCTUtility; + +namespace BossMod.Autorotation; public sealed class ClassPLDUtility(RotationModuleManager manager, Actor player) : RoleTankUtility(manager, player) { public enum Track { Sheltron = SharedTrack.Count, Sentinel, Cover, Bulwark, DivineVeil, PassageOfArms, HallowedGround } //What we're tracking public enum ShelOption { None, Sheltron, HolySheltron, Intervention } //Sheltron Options public enum SentOption { None, Sentinel, Guardian } //Sentinel enhancement + public enum ArmsDirection { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(PLD.AID.LastBastion); //LB public static readonly ActionID IDStanceApply = ActionID.MakeSpell(PLD.AID.IronWill); //StanceOn @@ -32,7 +35,15 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Cover, "Cover", "", 320, PLD.AID.Cover, 12); //120s CD, 12s duration, -50 OathGauge cost DefineSimpleConfig(res, Track.Bulwark, "Bulwark", "Bul", 450, PLD.AID.Bulwark, 10); //90s CD, 15s duration DefineSimpleConfig(res, Track.DivineVeil, "DivineVeil", "Veil", 220, PLD.AID.DivineVeil, 30); //90s CD, 30s duration - DefineSimpleConfig(res, Track.PassageOfArms, "PassageOfArms", "Arms", 470, PLD.AID.PassageOfArms, 3); //120s CD, 18s max duration + + res.Define(Track.PassageOfArms).As("PassageOfArms", "PoA", 400) //PassageOfArms definition for CD plans + .AddOption(ArmsDirection.None, "None", "Do not use automatically") + .AddOption(ArmsDirection.CharacterForward, "CharacterForward", "Faces the Forward direction relative to the Character", 120, 18, ActionTargets.Self, 70) + .AddOption(ArmsDirection.CharacterBackward, "CharacterBackward", "Faces the Backward direction relative to the Character", 120, 18, ActionTargets.Self, 70) + .AddOption(ArmsDirection.CameraForward, "CameraForward", "Faces the Forward direction relative to the Camera", 120, 18, ActionTargets.Self, 70) + .AddOption(ArmsDirection.CameraBackward, "CameraBackward", "Faces the Backward direction relative to the Camera", 120, 18, ActionTargets.Self, 70) + .AddAssociatedActions(PLD.AID.PassageOfArms); + DefineSimpleConfig(res, Track.HallowedGround, "HallowedGround", "Inv", 400, PLD.AID.HallowedGround, 10); //420s CD, 10s duration return res; @@ -44,7 +55,6 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Cover), PLD.AID.Cover, primaryTarget ?? Player); //Cover execution ExecuteSimple(strategy.Option(Track.Bulwark), PLD.AID.Bulwark, Player); //Bulwark execution ExecuteSimple(strategy.Option(Track.DivineVeil), PLD.AID.DivineVeil, Player); //DivineVeil execution - ExecuteSimple(strategy.Option(Track.PassageOfArms), PLD.AID.PassageOfArms, Player); //PassageOfArms execution ExecuteSimple(strategy.Option(Track.HallowedGround), PLD.AID.HallowedGround, Player); //HallowedGround execution var shel = strategy.Option(Track.Sheltron); @@ -67,5 +77,18 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, }; if (sentAction != default) Hints.ActionsToExecute.Push(ActionID.MakeSpell(sentAction), Player, sent.Priority(), sent.Value.ExpireIn); //Sentinel execution + + var poa = strategy.Option(Track.PassageOfArms); + if (poa.As() != ArmsDirection.None) + { + var angle = poa.As() switch + { + ArmsDirection.CharacterBackward => Player.Rotation + 180.Degrees(), + ArmsDirection.CameraForward => World.Client.CameraAzimuth + 180.Degrees(), + ArmsDirection.CameraBackward => World.Client.CameraAzimuth, + _ => Player.Rotation + }; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PLD.AID.PassageOfArms), Player, poa.Priority(), poa.Value.ExpireIn, facingAngle: angle); + } } } From 0b2aae2f14a1a139299df7a8e75f606c11973e1a Mon Sep 17 00:00:00 2001 From: ace Date: Sun, 9 Feb 2025 14:42:03 -0800 Subject: [PATCH 14/56] small improvements --- .../Autorotation/Standard/akechi/AkechiTools.cs | 15 ++++++++++----- .../Autorotation/Standard/akechi/DPS/AkechiBLM.cs | 6 +++++- .../Standard/akechi/Tank/AkechiDRK.cs | 10 ++++++---- .../Standard/akechi/Tank/AkechiGNB.cs | 3 ++- .../Standard/akechi/Tank/AkechiPLD.cs | 3 ++- .../Standard/akechi/Tank/AkechiWAR.cs | 3 ++- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 326426cb7c..2ea4d6e9c7 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -1152,23 +1152,28 @@ public sealed override void Execute(StrategyValues strategy, ref Actor? primaryT static class ModuleExtensions { #region Shared Definitions - ///

Defines our shared AOE (rotation) and Hold strategies. + /// Defines our shared AOE (rotation) strategies. /// The definitions of our base module's strategies. /// - Options for shared custom strategies to be used via AutoRotation or Cooldown Planner - public static RotationModuleDefinition DefineShared(this RotationModuleDefinition res) + public static RotationModuleDefinition.ConfigRef DefineAOE(this RotationModuleDefinition res) { - res.Define(SharedTrack.AOE).As("AOE", uiPriority: 300) + return res.Define(SharedTrack.AOE).As("AOE", uiPriority: 300) .AddOption(AOEStrategy.Automatic, "Auto", "Automatically execute optimal rotation based on targets", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceST, "ForceST", "Force-execute Single Target", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceAOE, "ForceAOE", "Force-execute AOE rotation", supportedTargets: ActionTargets.Hostile); + } - res.Define(SharedTrack.Hold).As("Hold", uiPriority: 290) + /// Defines our shared Hold strategies. + /// The definitions of our base module's strategies. + /// - Options for shared custom strategies to be used via AutoRotation or Cooldown Planner + public static RotationModuleDefinition.ConfigRef DefineHold(this RotationModuleDefinition res) + { + return res.Define(SharedTrack.Hold).As("Hold", uiPriority: 290) .AddOption(HoldStrategy.DontHold, "DontHold", "Allow use of all cooldowns, buffs, or gauge abilities") .AddOption(HoldStrategy.HoldCooldowns, "Hold", "Forbid use of all cooldowns only") .AddOption(HoldStrategy.HoldGauge, "HoldGauge", "Forbid use of all gauge abilities only") .AddOption(HoldStrategy.HoldBuffs, "HoldBuffs", "Forbid use of all raidbuffs or buff-related abilities only") .AddOption(HoldStrategy.HoldEverything, "HoldEverything", "Forbid use of all cooldowns, buffs, and gauge abilities"); - return res; } /// A quick and easy helper for shortcutting how we define our GCD abilities. diff --git a/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs b/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs index a1a0adac83..0373f55bf2 100644 --- a/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs +++ b/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs @@ -32,7 +32,11 @@ public static RotationModuleDefinition Definition() BitMask.Build(Class.THM, Class.BLM), //Job 100); //Level supported - res.DefineShared(); + res.DefineAOE().AddAssociatedActions( + AID.Fire1, AID.Fire2, AID.Fire3, AID.Fire4, AID.HighFire2, + AID.Blizzard1, AID.Blizzard2, AID.Blizzard3, AID.Freeze, AID.Blizzard4, AID.HighBlizzard2, + AID.Flare, AID.Despair, AID.FlareStar); + res.DefineHold(); res.Define(Track.Movement).As("Movement", uiPriority: 195) .AddOption(MovementStrategy.Allow, "Allow", "Allow the use of all appropriate abilities for movement") .AddOption(MovementStrategy.AllowNoScathe, "AllowNoScathe", "Allow the use of all appropriate abilities for movement except for Scathe") diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs index 4eb5d9f139..bdea2bc8f6 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs @@ -29,14 +29,16 @@ public static RotationModuleDefinition Definition() BitMask.Build((int)Class.DRK), //Job 100); //Level supported - res.DefineShared(); + res.DefineAOE().AddAssociatedActions(AID.HardSlash, AID.SyphonStrike, AID.Souleater, AID.Unleash, AID.StalwartSoul); ; + res.DefineHold(); res.Define(Track.Blood).As("Blood", "Blood", uiPriority: 200) .AddOption(BloodStrategy.Automatic, "Automatic", "Automatically use Blood-related abilities optimally") .AddOption(BloodStrategy.OnlyBloodspiller, "Only Bloodspiller", "Uses Bloodspiller optimally as Blood spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 62) .AddOption(BloodStrategy.OnlyQuietus, "Only Quietus", "Uses Quietus optimally as Blood spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 64) .AddOption(BloodStrategy.ForceBloodspiller, "Force Bloodspiller", "Force use Bloodspiller ASAP", 0, 0, ActionTargets.Hostile, 62) .AddOption(BloodStrategy.ForceQuietus, "Force Quietus", "Force use Quietus ASAP", 0, 0, ActionTargets.Hostile, 64) - .AddOption(BloodStrategy.Conserve, "Conserve", "Conserves all Blood-related abilities as much as possible"); + .AddOption(BloodStrategy.Conserve, "Conserve", "Conserves all Blood-related abilities as much as possible") + .AddAssociatedActions(AID.Bloodspiller, AID.Quietus); res.Define(Track.MP).As("MP", "MP", uiPriority: 190) .AddOption(MPStrategy.Optimal, "Optimal", "Use MP actions optimally; 2 for 1 minute, 4 (or 5 if Dark Arts is active) for 2 minutes") .AddOption(MPStrategy.Auto3k, "Auto 3k", "Automatically decide best MP action to use; Uses when at 3000+ MP", 0, 0, ActionTargets.Self, 30) @@ -60,7 +62,7 @@ public static RotationModuleDefinition Definition() .AddOption(CarveStrategy.ForceCarve, "Force Carve and Spit", "Force use Carve and Spit ASAP", 60, 0, ActionTargets.Hostile, 60) .AddOption(CarveStrategy.ForceDrain, "Force Abyssal Drain", "Force use Abyssal Drain ASAP", 60, 0, ActionTargets.Hostile, 56) .AddOption(CarveStrategy.Delay, "Delay", "Delay the use of Carve and Spit for strategic reasons", 0, 0, ActionTargets.None, 56) - .AddAssociatedActions(AID.CarveAndSpit); + .AddAssociatedActions(AID.CarveAndSpit, AID.AbyssalDrain); res.Define(Track.DeliriumCombo).As("Delirium Combo", "Scarlet", uiPriority: 180) .AddOption(DeliriumComboStrategy.Automatic, "Auto", "Automatically decide when to use Delirium Combo", 0, 0, ActionTargets.Hostile, 96) .AddOption(DeliriumComboStrategy.ScarletDelirum, "Scarlet Delirium", "Force use Scarlet Delirium ASAP", 0, 0, ActionTargets.Hostile, 96) @@ -81,7 +83,7 @@ public static RotationModuleDefinition Definition() .AddOption(UnmendStrategy.Allow, "Allow", "Allow use of Unmend when out of melee range", supportedTargets: ActionTargets.Hostile) .AddOption(UnmendStrategy.Forbid, "Forbid", "Prohibit use of Unmend") .AddAssociatedActions(AID.Unmend); - res.DefineOGCD(Track.Delirium, AID.Delirium, "Delirium", "Deli.", uiPriority: 170, 60, 15, ActionTargets.Self, 35); + res.DefineOGCD(Track.Delirium, AID.Delirium, "Delirium", "Deli.", uiPriority: 170, 60, 15, ActionTargets.Self, 35).AddAssociatedActions(AID.BloodWeapon, AID.Delirium); res.DefineOGCD(Track.SaltedEarth, AID.SaltedEarth, "Salted Earth", "S.Earth", uiPriority: 140, 90, 15, ActionTargets.Self, 52); res.DefineOGCD(Track.SaltAndDarkness, AID.SaltAndDarkness, "Salt & Darkness", "Salt & D.", uiPriority: 135, 20, 0, ActionTargets.Self, 86); res.DefineOGCD(Track.LivingShadow, AID.LivingShadow, "Living Shadow", "L.Shadow", uiPriority: 175, 120, 20, ActionTargets.Self, 80); diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs index e167e3b2d4..526f5d5551 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs @@ -40,7 +40,8 @@ public static RotationModuleDefinition Definition() .AddOption(AOEStrategy.ForceSTwithoutO, "Force ST without Overcap", "Force ST rotation without overcap protection", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceAOEwithO, "Force AOE with Overcap", "Force AOE rotation with overcap protection") .AddOption(AOEStrategy.ForceAOEwithoutO, "Force AOE without Overcap", "Force AOE rotation without overcap protection") - .AddOption(AOEStrategy.GenerateDowntime, "Generate Downtime", "Generate cartridges before downtime"); + .AddOption(AOEStrategy.GenerateDowntime, "Generate Downtime", "Generate cartridges before downtime") + .AddAssociatedActions(AID.KeenEdge, AID.BrutalShell, AID.SolidBarrel, AID.DemonSlice, AID.DemonSlaughter); res.Define(Track.Cooldowns).As("Hold", uiPriority: 190) .AddOption(CooldownStrategy.Allow, "Allow", "Allows the use of all cooldowns & buffs; will use them optimally") .AddOption(CooldownStrategy.Forbid, "Hold", "Forbids the use of all cooldowns & buffs; will not use any actions with a cooldown timer"); diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs index 85b9c6458e..1ce563afbf 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs @@ -35,7 +35,8 @@ public static RotationModuleDefinition Definition() .AddOption(AOEStrategy.AutoFinishCombo, "Auto (Finish Combo)", "Auto-selects best rotation dependant on targets; Finishes combo first", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.AutoBreakCombo, "Auto (Break Combo)", "Auto-selects best rotation dependant on targets; Breaks combo if needed", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceST, "Use AOE", "Force single-target rotation", supportedTargets: ActionTargets.Hostile) - .AddOption(AOEStrategy.ForceAOE, "Force AOE", "Force AOE rotation"); + .AddOption(AOEStrategy.ForceAOE, "Force AOE", "Force AOE rotation") + .AddAssociatedActions(AID.FastBlade, AID.RiotBlade, AID.RageOfHalone, AID.RoyalAuthority, AID.Prominence, AID.TotalEclipse); res.Define(Track.Cooldowns).As("Hold", uiPriority: 190) .AddOption(CooldownStrategy.Allow, "Allow", "Allows the use of all cooldowns & buffs; will use them optimally") .AddOption(CooldownStrategy.Forbid, "Hold", "Forbids the use of all cooldowns & buffs; will not use any actions with a cooldown timer"); diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs index eb4dd4f8d3..6c4431b199 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs @@ -31,7 +31,8 @@ public static RotationModuleDefinition Definition() BitMask.Build(Class.MRD, Class.WAR), //Job 100); //Level supported - res.DefineShared(); + res.DefineAOE().AddAssociatedActions(AID.HeavySwing, AID.Maim, AID.StormEye, AID.StormPath, AID.Overpower, AID.MythrilTempest); + res.DefineHold(); res.Define(Track.Gauge).As("Gauge", "Gauge", uiPriority: 200) .AddOption(GaugeStrategy.Automatic, "Automatic", "Automatically use Gauge-related abilities optimally", minLevel: 35) .AddOption(GaugeStrategy.OnlyST, "Only ST", "Uses Inner Beast / Fell Cleave / Inner Chaos optimally as Beast Gauge spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 35) From e08c1ba14c40b9741ac5aeeb59d9952346e88a83 Mon Sep 17 00:00:00 2001 From: ace Date: Sun, 9 Feb 2025 08:02:42 -0800 Subject: [PATCH 15/56] `Tank` goal zones fix --- BossMod/Autorotation/Standard/akechi/AkechiTools.cs | 2 +- .../Autorotation/Standard/akechi/Tank/AkechiDRK.cs | 4 ++-- .../Autorotation/Standard/akechi/Tank/AkechiGNB.cs | 12 ++++++------ .../Autorotation/Standard/akechi/Tank/AkechiPLD.cs | 6 +++--- .../Autorotation/Standard/akechi/Tank/AkechiWAR.cs | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 2ea4d6e9c7..8427c7882f 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -839,7 +839,7 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// /// The user's picked strategy's option Track, retrieved from module's enums and definitions. (e.g. strategy.Option(Track.NoMercy)) /// - protected Actor? TargetChoice(StrategyValues.OptionRef track) => ResolveTargetOverride(track.Value); //Resolves the target choice based on the strategy + protected Actor? TargetChoice(StrategyValues.OptionRef track) => ResolveTargetOverride(track.Value); /// Targeting function for indicating when or not AOE Circle abilities should be used based on targets nearby. /// The range of the AOE Circle ability, or radius from center of Player; this should be adjusted accordingly to user's module specific to job's abilities. diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs index bdea2bc8f6..e24f4536b4 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs @@ -387,12 +387,12 @@ bloodStrat is BloodStrategy.ForceQuietus ? GCDPriority.ForcedGCD #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE var goal = strategy.Option(SharedTrack.AOE).As() switch //Set goal based on AOE strategy { AOEStrategy.ForceST => goalST, //if forced single target AOEStrategy.ForceAOE => goalAOE, //if forced AOE - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs index 526f5d5551..af03a1538f 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs @@ -459,14 +459,14 @@ reignStrat is ReignStrategy.ForceLion #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE - var goal = AOEStrategy switch //Set goal based on AOE strategy + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE + var goal = strategy.Option(Track.AOE).As() switch //Set goal based on AOE strategy { + AOEStrategy.ForceSTwithO => goalST, //if forced single target AOEStrategy.ForceSTwithoutO => goalST, //if forced single target - AOEStrategy.ForceSTwithO => goalST, //if forced 123 combo - AOEStrategy.ForceAOEwithoutO => goalAOE, //if forced buffs combo - AOEStrategy.ForceAOEwithO => goalAOE, //if forced AOE action - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + AOEStrategy.ForceAOEwithO => goalAOE, //if forced single target + AOEStrategy.ForceAOEwithoutO => goalAOE, //if forced single target + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs index 1ce563afbf..d3e671a9b7 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs @@ -386,12 +386,12 @@ gbStrat is GCDStrategy.Force #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE var goal = strategy.Option(Track.AOE).As() switch //Set goal based on AOE strategy { AOEStrategy.ForceST => goalST, //if forced single target - AOEStrategy.ForceAOE => goalAOE, //if forced AOE - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + AOEStrategy.ForceAOE => goalAOE, //if forced single target + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs index 6c4431b199..0c7189b4fc 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs @@ -490,12 +490,12 @@ bgStrat is GaugeStrategy.ForceAOE #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE var goal = strategy.Option(SharedTrack.AOE).As() switch //Set goal based on AOE strategy { AOEStrategy.ForceST => goalST, //if forced single target AOEStrategy.ForceAOE => goalAOE, //if forced AOE - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones From 3b35c527d8d485e5b4ccdcbb7982743f8bd3d242 Mon Sep 17 00:00:00 2001 From: ace Date: Sun, 9 Feb 2025 14:52:47 -0800 Subject: [PATCH 16/56] forgot these --- BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs | 2 +- BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs index e24f4536b4..77df668606 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs @@ -29,7 +29,7 @@ public static RotationModuleDefinition Definition() BitMask.Build((int)Class.DRK), //Job 100); //Level supported - res.DefineAOE().AddAssociatedActions(AID.HardSlash, AID.SyphonStrike, AID.Souleater, AID.Unleash, AID.StalwartSoul); ; + res.DefineAOE().AddAssociatedActions(AID.HardSlash, AID.SyphonStrike, AID.Souleater, AID.Unleash, AID.StalwartSoul); res.DefineHold(); res.Define(Track.Blood).As("Blood", "Blood", uiPriority: 200) .AddOption(BloodStrategy.Automatic, "Automatic", "Automatically use Blood-related abilities optimally") diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs index 0c7189b4fc..1f2ab1dbcf 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs @@ -239,7 +239,9 @@ public bool IsRiskingGauge() if (ComboLastMove is AID.HeavySwing) return true; } - + if (NascentChaos.IsActive && //if NC is active + InnerRelease.CD > 5) //and IR is not imminent + return true; return false; } #endregion From b0896b16292833c18ed510537105a46f6d653fe3 Mon Sep 17 00:00:00 2001 From: ace Date: Sun, 9 Feb 2025 15:34:32 -0800 Subject: [PATCH 17/56] even more GNB options & optis --- .../Standard/akechi/Tank/AkechiGNB.cs | 128 +++++++++++------- 1 file changed, 77 insertions(+), 51 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs index af03a1538f..608eac2593 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs @@ -12,14 +12,15 @@ public sealed class AkechiGNB(RotationModuleManager manager, Actor player) : Ake public enum Track { AOE, Cooldowns, Cartridges, Potion, LightningShot, NoMercy, SonicBreak, GnashingFang, Reign, Bloodfest, DoubleDown, Zone, BowShock } public enum AOEStrategy { AutoFinishCombo, AutoBreakCombo, ForceSTwithO, ForceSTwithoutO, ForceAOEwithO, ForceAOEwithoutO, GenerateDowntime } public enum CooldownStrategy { Allow, Forbid } - public enum CartridgeStrategy { Automatic, OnlyBS, OnlyFC, ForceBS, ForceFC, Conserve } + public enum CartridgeStrategy { Automatic, OnlyBS, OnlyFC, ForceBS, ForceBS1, ForceBS2, ForceBS3, ForceFC, ForceFC1, ForceFC2, ForceFC3, Conserve } public enum PotionStrategy { Manual, AlignWithRaidBuffs, Immediate } public enum LightningShotStrategy { OpenerFar, OpenerForce, Force, Allow, Forbid } public enum NoMercyStrategy { Automatic, Force, ForceW, ForceQW, Force1, Force1W, Force1QW, Force2, Force2W, Force2QW, Force3, Force3W, Force3QW, Delay } public enum SonicBreakStrategy { Automatic, Force, Early, Late, Delay } - public enum GnashingStrategy { Automatic, ForceGnash, ForceClaw, ForceTalon, Delay } + public enum GnashingStrategy { Automatic, ForceGnash, ForceGnash1, ForceGnash2, ForceGnash3, ForceClaw, ForceTalon, Delay } public enum ReignStrategy { Automatic, ForceReign, ForceNoble, ForceLion, Delay } public enum BloodfestStrategy { Automatic, Force, ForceW, Force0, Force0W, Delay } + public enum DoubleDownStrategy { Automatic, Force, Force1, Force2, Force3, Delay } #endregion #region Module Definitions @@ -49,9 +50,15 @@ public static RotationModuleDefinition Definition() .AddOption(CartridgeStrategy.Automatic, "Automatic", "Automatically decide when to use cartridges; uses them optimally") .AddOption(CartridgeStrategy.OnlyBS, "Only Burst Strike", "Uses Burst Strike optimally as cartridge spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 30) .AddOption(CartridgeStrategy.OnlyFC, "Only Fated Circle", "Uses Fated Circle optimally as cartridge spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 72) - .AddOption(CartridgeStrategy.ForceBS, "Force Burst Strike", "Force use of Burst Strike; consumes 1 cartridge", 0, 0, ActionTargets.Hostile, 30) - .AddOption(CartridgeStrategy.ForceFC, "Force Fated Circle", "Force use of Fated Circle; consumes 1 cartridge", 0, 0, ActionTargets.Hostile, 72) - .AddOption(CartridgeStrategy.Conserve, "Conserve", "Prohibit use of all cartridge-related abilities; will not use any of these actions listed above") + .AddOption(CartridgeStrategy.ForceBS, "Force Burst Strike", "Force use of Burst Strike regardless of cartridge count", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceBS1, "Force Burst Strike (1 cart)", "Force use of Burst Strike when only 1 cartridge is available", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceBS2, "Force Burst Strike (2 cart)", "Force use of Burst Strike when only 2 cartridges are available", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceBS3, "Force Burst Strike (3 cart)", "Force use of Burst Strike when only 3 cartridges are available", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceFC, "Force Fated Circle", "Force use of Fated Circle when any cartridges are available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.ForceFC1, "Force Fated Circle (1 cart)", "Force use of Fated Circle when only 1 cartridge is available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.ForceFC2, "Force Fated Circle (2 cart)", "Force use of Fated Circle when only 2 cartridges are available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.ForceFC3, "Force Fated Circle (3 cart)", "Force use of Fated Circle when only 3 cartridges are available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.Conserve, "Conserve", "Forbid use of Burst Strike & Fated Circle", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.BurstStrike, AID.FatedCircle); res.Define(Track.Potion).As("Potion", uiPriority: 20) .AddOption(PotionStrategy.Manual, "Manual", "Do not use automatically") @@ -70,15 +77,15 @@ public static RotationModuleDefinition Definition() .AddOption(NoMercyStrategy.Force, "Force", "Force use of No Mercy, regardless of weaving", 60, 20, ActionTargets.Self, 2) .AddOption(NoMercyStrategy.ForceW, "Force (Weave)", "Force use of No Mercy in next possible weave slot", 60, 20, ActionTargets.Self, 2) .AddOption(NoMercyStrategy.ForceQW, "Force (Q.Weave)", "Force use of No Mercy in next possible last second weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force1, "Force (1 cart)", "Force use of No Mercy when 1 cartridge is available, regardless of weaving", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force1W, "Force (1 cart, Weave)", "Force use of No Mercy when 1 cartridge is available & in next weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force1QW, "Force (1 cart, Q.Weave)", "Force use of No Mercy when 1 cartridge is available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2, "Force (2 carts)", "Force use of No Mercy when 2 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2W, "Force (2 carts, Weave)", "Force use of No Mercy when 2 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2QW, "Force (2 carts, Q.Weave)", "Force use of No Mercy when 2 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force3, "Force (3 carts)", "Force use of No Mercy when 3 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force3W, "Force (3 carts, Weave)", "Force use of No Mercy when 3 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force3QW, "Force (3 carts, Q.Weave)", "Force use of No Mercy when 3 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force1, "Force (1 cart)", "Force use of No Mercy when only 1 cartridge is available, regardless of weaving", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force1W, "Force (1 cart, Weave)", "Force use of No Mercy when only 1 cartridge is available & in next weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force1QW, "Force (1 cart, Q.Weave)", "Force use of No Mercy when only 1 cartridge is available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2, "Force (2 carts)", "Force use of No Mercy when only 2 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2W, "Force (2 carts, Weave)", "Force use of No Mercy when only 2 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2QW, "Force (2 carts, Q.Weave)", "Force use of No Mercy when only 2 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force3, "Force (3 carts)", "Force use of No Mercy when only 3 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force3W, "Force (3 carts, Weave)", "Force use of No Mercy when only 3 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force3QW, "Force (3 carts, Q.Weave)", "Force use of No Mercy when only 3 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) .AddOption(NoMercyStrategy.Delay, "Delay", "Delay use of No Mercy", 0, 0, ActionTargets.None, 2) .AddAssociatedActions(AID.NoMercy); res.Define(Track.SonicBreak).As("Sonic Break", "S.Break", uiPriority: 150) @@ -90,9 +97,12 @@ public static RotationModuleDefinition Definition() .AddAssociatedActions(AID.SonicBreak); res.Define(Track.GnashingFang).As("Gnashing Fang", "G.Fang", uiPriority: 160) .AddOption(GnashingStrategy.Automatic, "Auto", "Normal use of Gnashing Fang") - .AddOption(GnashingStrategy.ForceGnash, "Force", "Force use of Gnashing Fang (Step 1)", 30, 0, ActionTargets.Hostile, 60) - .AddOption(GnashingStrategy.ForceClaw, "Force", "Force use of Savage Claw (Step 2)", 0, 0, ActionTargets.Hostile, 60) - .AddOption(GnashingStrategy.ForceTalon, "Force", "Force use of Wicked Talon (Step 3)", 0, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash, "Force", "Force use of Gnashing Fang", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash1, "Force (1 cart)", "Force use of Gnashing Fang when only 1 cartridge is available", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash2, "Force (2 carts)", "Force use of Gnashing Fang when only 2 cartridges are available", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash3, "Force (3 carts)", "Force use of Gnashing Fang when only 3 cartridges are available", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceClaw, "Force Savage Claw", "Force use of Savage Claw", 0, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceTalon, "Force Talon", "Force use of Wicked Talon", 0, 0, ActionTargets.Hostile, 60) .AddOption(GnashingStrategy.Delay, "Delay", "Delay use of Gnashing Fang", 0, 0, ActionTargets.None, 60) .AddAssociatedActions(AID.GnashingFang, AID.SavageClaw, AID.WickedTalon); res.Define(Track.Reign).As("Reign of Beasts", "Reign", uiPriority: 160) @@ -110,7 +120,15 @@ public static RotationModuleDefinition Definition() .AddOption(BloodfestStrategy.Force0W, "Force (0 cart, Weave)", "Force use of Bloodfest only if empty on cartridges & in next possible weave slot", 120, 0, ActionTargets.Hostile, 80) .AddOption(BloodfestStrategy.Delay, "Delay", "Delay use of Bloodfest", 0, 0, ActionTargets.None, 80) .AddAssociatedActions(AID.Bloodfest); - res.DefineGCD(Track.DoubleDown, AID.DoubleDown, "DoubleDown", "D.Down", uiPriority: 160, 60, 0, ActionTargets.Hostile, 90); + res.Define(Track.DoubleDown).As("DoubleDown", "D.Down", uiPriority: 160) + .AddOption(DoubleDownStrategy.Automatic, "Automatic", "Normal use of Double Down") + .AddOption(DoubleDownStrategy.Force, "Force Double Down", "Force use of Double Down regardless of cartridge count", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Force1, "Force Double Down (1 cart)", "Force use of Double Down when only 1 cartridge is available", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Force2, "Force Double Down (2 cart)", "Force use of Double Down when only 2 cartridges are available", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Force3, "Force Double Down (3 cart)", "Force use of Double Down when only 3 cartridges are available", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Delay, "Delay", "Delay use of Double Down", 0, 0, ActionTargets.None, 90) + .AddAssociatedActions(AID.DoubleDown); + res.DefineOGCD(Track.Zone, AID.DangerZone, "Zone", "Zone", uiPriority: 150, 30, 0, ActionTargets.Hostile, 18).AddAssociatedActions(AID.BlastingZone, AID.DangerZone); res.DefineOGCD(Track.BowShock, AID.BowShock, "BowShock", "B.Shock", uiPriority: 150, 60, 15, ActionTargets.Self, 62); @@ -123,24 +141,25 @@ public enum GCDPriority { None = 0, Standard = 100, - ForcedCombo = 499, - Gauge = 500, - Reign = 525, - comboNeed = 550, - GF23 = 575, + Gauge = 400, + ForcedCombo = 425, + Reign = 450, + comboNeed = 500, + GF23 = 550, SonicBreak = 600, - DoubleDown = 675, + DoubleDown = 650, GF1 = 700, + Only1Ammo = 750, ForcedGCD = 900, } public enum OGCDPriority { None = 0, - Continuation = 500, - Zone = 550, - BowShock = 600, - Bloodfest = 700, - NoMercy = 875, + Continuation = 400, + Zone = 450, + BowShock = 500, + Bloodfest = 600, + NoMercy = 650, Potion = 900, ForcedOGCD = 1100, //Enough to put it past CDPlanner's "Automatic" priority, which is really only Medium priority } @@ -239,7 +258,7 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) var bf = strategy.Option(Track.Bloodfest); //Bloodfest track var bfStrat = bf.As(); //Bloodfest strategy var dd = strategy.Option(Track.DoubleDown); //Double Down track - var ddStrat = dd.As(); //Double Down strategy + var ddStrat = dd.As(); //Double Down strategy var gf = strategy.Option(Track.GnashingFang); //Gnashing Fang track var gfStrat = gf.As(); //Gnashing Fang strategy var reign = strategy.Option(Track.Reign); //Reign of Beasts track @@ -395,12 +414,13 @@ reignStrat is ReignStrategy.ForceReign if (ShouldUseDoubleDown(ddStrat, primaryTarget?.Actor)) QueueGCD(AID.DoubleDown, primaryTarget?.Actor, - ddStrat is GCDStrategy.Force || Ammo == 1 - ? GCDPriority.ForcedGCD : GCDPriority.DoubleDown); + ddStrat is DoubleDownStrategy.Force or DoubleDownStrategy.Force1 or DoubleDownStrategy.Force2 or DoubleDownStrategy.Force3 + ? GCDPriority.ForcedGCD + : Ammo == 1 ? GCDPriority.Only1Ammo : GCDPriority.DoubleDown); if (ShouldUseGnashingFang(gfStrat, primaryTarget?.Actor)) QueueGCD(AID.GnashingFang, TargetChoice(gf) ?? primaryTarget?.Actor, - gfStrat is GnashingStrategy.ForceGnash + gfStrat is GnashingStrategy.ForceGnash or GnashingStrategy.ForceGnash1 or GnashingStrategy.ForceGnash2 or GnashingStrategy.ForceGnash3 ? GCDPriority.ForcedGCD : GCDPriority.GF1); if (ShouldUseCartridges(cartStrat, primaryTarget?.Actor)) { @@ -409,11 +429,11 @@ gfStrat is GnashingStrategy.ForceGnash TargetChoice(carts) ?? primaryTarget?.Actor, nmCD < 1 && Ammo == 3 ? GCDPriority.ForcedGCD : GCDPriority.Gauge); - if (cartStrat is CartridgeStrategy.OnlyBS or CartridgeStrategy.ForceBS) + if (cartStrat is CartridgeStrategy.OnlyBS or CartridgeStrategy.ForceBS or CartridgeStrategy.ForceBS1 or CartridgeStrategy.ForceBS2 or CartridgeStrategy.ForceBS3) QueueGCD(AID.BurstStrike, TargetChoice(carts) ?? primaryTarget?.Actor, GCDPriority.Gauge); - if (cartStrat is CartridgeStrategy.ForceFC or CartridgeStrategy.OnlyFC) + if (cartStrat is CartridgeStrategy.ForceFC or CartridgeStrategy.OnlyFC or CartridgeStrategy.ForceFC1 or CartridgeStrategy.ForceFC2 or CartridgeStrategy.ForceFC3) QueueGCD(BestFatedCircle, Unlocked(AID.FatedCircle) ? Player : primaryTarget?.Actor, GCDPriority.Gauge); @@ -571,40 +591,46 @@ reignStrat is ReignStrategy.ForceLion }; private bool ShouldUseCartridges(CartridgeStrategy strategy, Actor? target) => strategy switch { - CartridgeStrategy.Automatic => ShouldUseFC ? ShouldUseFatedCircle(CartridgeStrategy.Automatic, target) : ShouldUseBurstStrike(CartridgeStrategy.Automatic, target), - CartridgeStrategy.OnlyBS => ShouldUseBurstStrike(CartridgeStrategy.Automatic, target), - CartridgeStrategy.OnlyFC => ShouldUseFatedCircle(CartridgeStrategy.Automatic, target), + CartridgeStrategy.Automatic => ShouldSpendCarts(CartridgeStrategy.Automatic, target), + CartridgeStrategy.OnlyBS => ShouldSpendCarts(CartridgeStrategy.Automatic, target), + CartridgeStrategy.OnlyFC => ShouldSpendCarts(CartridgeStrategy.Automatic, target), CartridgeStrategy.ForceBS => canBS, + CartridgeStrategy.ForceBS1 => canBS && Ammo == 1, + CartridgeStrategy.ForceBS2 => canBS && Ammo == 2, + CartridgeStrategy.ForceBS3 => canBS && Ammo == 3, CartridgeStrategy.ForceFC => canFC, + CartridgeStrategy.ForceFC1 => canFC && Ammo == 1, + CartridgeStrategy.ForceFC2 => canFC && Ammo == 2, + CartridgeStrategy.ForceFC3 => canFC && Ammo == 3, CartridgeStrategy.Conserve => false, _ => false }; - private bool ShouldUseDoubleDown(GCDStrategy strategy, Actor? target) => strategy switch + private bool ShouldUseDoubleDown(DoubleDownStrategy strategy, Actor? target) => strategy switch { - GCDStrategy.Automatic => Player.InCombat && target != null && In5y(target) && canDD && hasNM, - GCDStrategy.Force => canDD, - GCDStrategy.Delay => false, + DoubleDownStrategy.Automatic => Player.InCombat && target != null && In5y(target) && canDD && hasNM, + DoubleDownStrategy.Force => canDD, + DoubleDownStrategy.Force1 => canDD && Ammo == 1, + DoubleDownStrategy.Force2 => canDD && Ammo == 2, + DoubleDownStrategy.Force3 => canDD && Ammo == 3, + DoubleDownStrategy.Delay => false, _ => false }; private bool ShouldUseGnashingFang(GnashingStrategy strategy, Actor? target) => strategy switch { GnashingStrategy.Automatic => Player.InCombat && target != null && In3y(target) && canGF && (nmLeft > 0 || hasNM || nmCD is < 35 and > 17), GnashingStrategy.ForceGnash => canGF, + GnashingStrategy.ForceGnash1 => canGF && Ammo == 1, + GnashingStrategy.ForceGnash2 => canGF && Ammo == 2, + GnashingStrategy.ForceGnash3 => canGF && Ammo == 3, GnashingStrategy.ForceClaw => Player.InCombat && GunComboStep == 1, GnashingStrategy.ForceTalon => Player.InCombat && GunComboStep == 2, GnashingStrategy.Delay => false, _ => false }; - private bool ShouldUseBurstStrike(CartridgeStrategy strategy, Actor? target) => strategy switch - { - CartridgeStrategy.Automatic => Player.InCombat && target != null && In3y(target) && canBS && - (hasNM || (!(bfCD is <= 90 and >= 30) && nmCD < 1 && Ammo == 3)) || - Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice, - _ => false - }; - private bool ShouldUseFatedCircle(CartridgeStrategy strategy, Actor? target) => strategy switch + private bool ShouldSpendCarts(CartridgeStrategy strategy, Actor? target) => strategy switch { - CartridgeStrategy.Automatic => Player.InCombat && target != null && In5y(target) && canFC && + CartridgeStrategy.Automatic => Player.InCombat && target != null && + (ShouldUseFC ? (In5y(target) && canFC) : (In3y(target) && canBS)) && (hasNM || (!(bfCD is <= 90 and >= 30) && nmCD < 1 && Ammo == 3)) || Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice, _ => false From 509b1b6c9bec2fb41bb6a4328a83be33fb57e9b6 Mon Sep 17 00:00:00 2001 From: ace Date: Mon, 10 Feb 2025 06:23:28 -0800 Subject: [PATCH 18/56] small tools cleanup, prep for `AkechiGNBPvP` fixes --- .../Standard/akechi/AkechiTools.cs | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 8427c7882f..143f55ee45 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -753,37 +753,94 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) #region Targeting #region Position Checks + + #region Core /// /// Checks precise positioning between player target and any other targets. /// protected delegate bool PositionCheck(Actor playerTarget, Actor targetToTest); + /// /// Calculates the priority of a target based on the total number of targets and the primary target itself. /// It is generic, so it can return different types based on the implementation. /// protected delegate P PriorityFunc

(int totalTargets, Actor primaryTarget); + #endregion + + #region Splash ///

/// Position checker for determining the best target for an ability that deals Splash damage. /// protected PositionCheck IsSplashTarget => (primary, other) => Hints.TargetInAOECircle(other, primary.Position, 5); + #endregion + + #region Cones + // some of these use-cases really are only for BLU modules, since their job's ability ranges are all over the place (i.e. 4y, 16y specifically) + + /// + /// Position checker for determining the best target for an ability that deals damage in a Cone within Four (4) yalms. + /// + protected PositionCheck Is4yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 4, Player.DirectionTo(primary), 45.Degrees()); + + /// + /// Position checker for determining the best target for an ability that deals damage in a Cone within Six (6) yalms. + /// + protected PositionCheck Is6yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 6, Player.DirectionTo(primary), 45.Degrees()); + + /// + /// Position checker for determining the best target for an ability that deals damage in a Cone within Eight (8) yalms. + /// + protected PositionCheck Is8yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 8, Player.DirectionTo(primary), 45.Degrees()); + + /// + /// Position checker for determining the best target for an ability that deals damage in a Cone within Ten (10) yalms. + /// + protected PositionCheck Is10yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 10, Player.DirectionTo(primary), 45.Degrees()); + + /// + /// Position checker for determining the best target for an ability that deals damage in a Cone within Twelve (12) yalms. + /// + protected PositionCheck Is12yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 12, Player.DirectionTo(primary), 45.Degrees()); + + /// + /// Position checker for determining the best target for an ability that deals damage in a Cone within Fifteen (15) yalms. + /// + protected PositionCheck Is15yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 15, Player.DirectionTo(primary), 45.Degrees()); + /// - /// Position checker for determining the best target for an ability that deals damage in a Cone . + /// Position checker for determining the best target for an ability that deals damage in a Cone within Sixteen (16) yalms. /// - protected PositionCheck IsConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 8, Player.DirectionTo(primary), 45.Degrees()); + protected PositionCheck Is16yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 16, Player.DirectionTo(primary), 45.Degrees()); + + #endregion + + #region Lines (aka AOE Rectangles) /// /// Position checker for determining the best target for an ability that deals damage in a Line within Ten (10) yalms. /// protected PositionCheck Is10yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 10, 2); + /// /// Position checker for determining the best target for an ability that deals damage in a Line within Fifteen (15) yalms. /// protected PositionCheck Is15yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 15, 2); + + /// + /// Position checker for determining the best target for an ability that deals damage in a Line within Twenty (20) yalms. + /// + protected PositionCheck Is20yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 20, 2); + + /// /// Position checker for determining the best target for an ability that deals damage in a Line within Twenty-five (25) yalms /// protected PositionCheck Is25yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 25, 2); + + #endregion + #endregion + #region Range Checks /// /// Checks if target is within Zero (0) yalms in distance, or if Player is inside hitbox. /// @@ -832,6 +889,7 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// The user's specified Target being checked. /// protected bool In25y(Actor? target) => Player.DistanceToHitbox(target) <= 24.99f; + #endregion /// /// A simpler smart-targeting helper for picking a specific target over your current target. @@ -878,6 +936,22 @@ protected void GetPrimaryTarget(StrategyValues strategy, ref Enemy? primaryTarge } } + /// + /// This function attempts to pick ANY suitable primary target automatically, even if a target is not already picked. + /// + /// The user's current specified Target. + /// + protected void GetPvPTarget(ref Enemy? primaryTarget, float range) + { + if (Player.DistanceToHitbox(primaryTarget?.Actor) > range) + { + var newTarget = Hints.PriorityTargets.FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); + if (newTarget != null) + primaryTarget = newTarget; + } + } + + /// /// This function attempts to pick the best target automatically. /// From cc10f69c79cf6e0f6057271f9c80d4abc4dc8eaa Mon Sep 17 00:00:00 2001 From: ace Date: Mon, 10 Feb 2025 06:26:05 -0800 Subject: [PATCH 19/56] kinda fix `AkechiGNBPvP` (LB & `RoughDivide`) --- BossMod/ActionQueue/Tanks/GNB.cs | 1 + .../Standard/akechi/PvP/AkechiGNBPvP.cs | 180 ++++++++---------- 2 files changed, 85 insertions(+), 96 deletions(-) diff --git a/BossMod/ActionQueue/Tanks/GNB.cs b/BossMod/ActionQueue/Tanks/GNB.cs index 7e8bf67f30..370e59b964 100644 --- a/BossMod/ActionQueue/Tanks/GNB.cs +++ b/BossMod/ActionQueue/Tanks/GNB.cs @@ -152,6 +152,7 @@ public enum SID : uint HeartOfCorundumPvP = 4295, // applied by Heart of Corundum to self CatharsisOfCorundumPvP = 4296, // applied by Heart of Corundum to self RelentlessRushPvP = 3052, + RelentlessShrapnelPvP = 3053, //Shared Elixir = ClassShared.AID.Elixir, diff --git a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs index 9ac911dba1..eda284b350 100644 --- a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs +++ b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs @@ -1,19 +1,21 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Gauge; -using AID = BossMod.GNB.AID; -using SID = BossMod.GNB.SID; +using static BossMod.AIHints; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using BossMod.GNB; namespace BossMod.Autorotation.akechi; //Contribution by Akechi //Discord @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class AkechiGNBPvP(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class AkechiGNBPvP(RotationModuleManager manager, Actor player) : AkechiTools(manager, player) + { #region Enums: Abilities / Strategies public enum Track { Burst, Combo, - LimitBreak, + RelentlessRush, + TerminalTrigger, GnashingFang, FatedCircle, RoughDivide, @@ -35,7 +37,14 @@ public enum ComboStrategy Hold } - public enum LimitBreakStrategy + public enum RushStrategy + { + Automatic, + Force, + Hold + } + + public enum TriggerStrategy { Automatic, Force, @@ -65,7 +74,6 @@ public static RotationModuleDefinition Definition() { var res = new RotationModuleDefinition("Akechi GNB (PvP)", "PvP Rotation Module", "PvP", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100, 30); - #region Custom strategies res.Define(Track.Burst).As("Burst", uiPriority: 190) .AddOption(BurstStrategy.Automatic, "Automatic", "Use everything optimally") .AddOption(BurstStrategy.Force, "Force", "Force everything") @@ -76,13 +84,16 @@ public static RotationModuleDefinition Definition() .AddOption(ComboStrategy.Force, "Force", "Force combo") .AddOption(ComboStrategy.Hold, "Hold", "Hold combo"); - res.Define(Track.LimitBreak).As("Limit Break", uiPriority: 190) - .AddOption(LimitBreakStrategy.Automatic, "Automatic", "Use Limit Break optimally") - .AddOption(LimitBreakStrategy.Force, "Force", "Force Limit Break") - .AddOption(LimitBreakStrategy.Hold, "Hold", "Hold Limit Break"); - #endregion + res.Define(Track.RelentlessRush).As("Relentless Rush", uiPriority: 190) + .AddOption(RushStrategy.Automatic, "Automatic", "Use Relentless Rush optimally") + .AddOption(RushStrategy.Force, "Force", "Force Relentless Rush") + .AddOption(RushStrategy.Hold, "Hold", "Hold Relentless Rush"); + + res.Define(Track.TerminalTrigger).As("Terminal Trigger", uiPriority: 190) + .AddOption(TriggerStrategy.Automatic, "Automatic", "Use Terminal Trigger optimally") + .AddOption(TriggerStrategy.Force, "Force", "Force Terminal Trigger") + .AddOption(TriggerStrategy.Hold, "Hold", "Hold Terminal Trigger"); - #region Offensive Strategies res.Define(Track.GnashingFang).As("Gnashing Fang", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) @@ -112,7 +123,6 @@ public static RotationModuleDefinition Definition() .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.HeartOfCorundumPvP); - #endregion return res; } @@ -160,40 +170,28 @@ public enum OGCDPriority private bool canRip; private bool canTear; private bool canGouge; - public bool LBready; public float GFcomboStep; public float comboStep; public bool inCombo; public bool inGF; - public float GCDLength; - public AID NextGCD; - private GCDPriority NextGCDPrio; #endregion - #region Module Helpers - private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; - private AID ComboLastMove => (AID)World.Client.ComboState.Action; - private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.9; - private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; - public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; - public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; - public AID LimitBreak => HasEffect(SID.RelentlessRushPvP) ? AID.TerminalTriggerPvP : AID.RelentlessRushPvP; - #endregion - - public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) + public override void Execution(StrategyValues strategy, Enemy? primaryTarget) { + GetPvPTarget(ref primaryTarget, 3); + #region Variables var gauge = World.Client.GetGauge(); var GunStep = gauge.AmmoComboStep; - rdCD = CD(AID.RoughDividePvP); - nmLeft = SelfStatusLeft(SID.NoMercyPvP, 7); + rdCD = TotalCD(AID.RoughDividePvP); + nmLeft = StatusRemaining(Player, SID.NoMercyPvP, 7); hasNM = nmLeft > 0; - hasBlast = HasEffect(SID.ReadyToBlastPvP); - hasRaze = HasEffect(SID.ReadyToRazePvP); - hasRip = HasEffect(SID.ReadyToRipPvP) || GunStep == 1; - hasTear = HasEffect(SID.ReadyToTearPvP) || GunStep == 2; - hasGouge = HasEffect(SID.ReadyToGougePvP); + hasBlast = PlayerHasEffect(SID.ReadyToBlastPvP); + hasRaze = PlayerHasEffect(SID.ReadyToRazePvP); + hasRip = PlayerHasEffect(SID.ReadyToRipPvP) || GunStep == 1; + hasTear = PlayerHasEffect(SID.ReadyToTearPvP) || GunStep == 2; + hasGouge = PlayerHasEffect(SID.ReadyToGougePvP); LBready = World.Party.LimitBreakLevel >= 1; GFcomboStep = ComboLastMove switch { @@ -212,19 +210,16 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, }; inCombo = comboStep > 0; inGF = GFcomboStep > 0; - GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); - NextGCD = AID.None; - NextGCDPrio = GCDPriority.None; #region Minimal Requirements canGF = IsOffCooldown(AID.GnashingFangPvP); canFC = IsOffCooldown(AID.GnashingFangPvP); canZone = IsOffCooldown(AID.BlastingZonePvP); - canHyper = hasBlast && In5y(primaryTarget); - canBrand = hasRaze && In5y(primaryTarget); - canRip = hasRip && In5y(primaryTarget); - canTear = hasTear && In5y(primaryTarget); - canGouge = hasGouge && In5y(primaryTarget); + canHyper = hasBlast && In5y(primaryTarget?.Actor); + canBrand = hasRaze && In5y(primaryTarget?.Actor); + canRip = hasRip && In5y(primaryTarget?.Actor); + canTear = hasTear && In5y(primaryTarget?.Actor); + canGouge = hasGouge && In5y(primaryTarget?.Actor); #endregion #endregion @@ -232,80 +227,64 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, var burstStrategy = burst.As(); var hold = burstStrategy == BurstStrategy.Hold; - if (strategy.Option(Track.Combo).As() == ComboStrategy.Force) - QueueGCD(NextCombo(), primaryTarget, GCDPriority.ForcedGCD); + if (strategy.Option(Track.Combo).As() is ComboStrategy.Force) + QueueGCD(NextCombo(), primaryTarget?.Actor, GCDPriority.ForcedGCD); #region Rotation Execution - if (!hold && !inGF) - QueueGCD(NextCombo(), primaryTarget, GCDPriority.Combo); + if (!inGF) + QueueGCD(NextCombo(), primaryTarget?.Actor, GCDPriority.Combo); #region OGCDs var rdStrat = strategy.Option(Track.RoughDivide).As(); if (!hold && - ShouldUseRoughDivide(rdStrat, primaryTarget)) - QueueOGCD(AID.RoughDividePvP, primaryTarget, rdStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.RoughDivide); + ShouldUseRoughDivide(rdStrat, primaryTarget?.Actor)) + QueueOGCD(AID.RoughDividePvP, primaryTarget?.Actor, rdStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.RoughDivide); var zoneStrat = strategy.Option(Track.Zone).As(); if (!hold && - ShouldUseZone(zoneStrat, primaryTarget)) - QueueOGCD(AID.BlastingZonePvP, primaryTarget, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); + ShouldUseZone(zoneStrat, primaryTarget?.Actor)) + QueueOGCD(AID.BlastingZonePvP, primaryTarget?.Actor, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); if (canRip || GunStep == 1) - QueueOGCD(AID.JugularRipPvP, primaryTarget, OGCDPriority.Continuation); - if (canTear || GunStep == 1) - QueueOGCD(AID.AbdomenTearPvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.JugularRipPvP, primaryTarget?.Actor, OGCDPriority.Continuation); + if (canTear || GunStep == 2) + QueueOGCD(AID.AbdomenTearPvP, primaryTarget?.Actor, OGCDPriority.Continuation); if (canGouge) - QueueOGCD(AID.EyeGougePvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.EyeGougePvP, primaryTarget?.Actor, OGCDPriority.Continuation); if (canHyper) - QueueOGCD(AID.HypervelocityPvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.HypervelocityPvP, primaryTarget?.Actor, OGCDPriority.Continuation); if (canBrand) - QueueOGCD(AID.FatedBrandPvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.FatedBrandPvP, primaryTarget?.Actor, OGCDPriority.Continuation); + + if (TargetHPP(Player) < 50) + QueueOGCD(AID.HeartOfCorundumPvP, primaryTarget?.Actor, OGCDPriority.Corundum); #endregion #region GCDs var gfStrat = strategy.Option(Track.GnashingFang).As(); if (!hold && - ShouldUseGnashingFang(gfStrat, primaryTarget)) - QueueGCD(AID.GnashingFangPvP, primaryTarget, GCDPriority.GnashingFang); - if (GunStep == 1 && In5y(primaryTarget)) - QueueGCD(AID.SavageClawPvP, primaryTarget, GCDPriority.GnashingFang); - if (GunStep == 2 && In5y(primaryTarget)) - QueueGCD(AID.WickedTalonPvP, primaryTarget, GCDPriority.GnashingFang); + ShouldUseGnashingFang(gfStrat, primaryTarget?.Actor)) + QueueGCD(AID.GnashingFangPvP, primaryTarget?.Actor, GCDPriority.GnashingFang); + if (GunStep == 1 && In5y(primaryTarget?.Actor)) + QueueGCD(AID.SavageClawPvP, primaryTarget?.Actor, GCDPriority.GnashingFang); + if (GunStep == 2 && In5y(primaryTarget?.Actor)) + QueueGCD(AID.WickedTalonPvP, primaryTarget?.Actor, GCDPriority.GnashingFang); var fcStrat = strategy.Option(Track.FatedCircle).As(); - if (ShouldUseFatedCircle(fcStrat, primaryTarget)) - QueueGCD(AID.FatedCirclePvP, primaryTarget, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); + if (ShouldUseFatedCircle(fcStrat, primaryTarget?.Actor)) + QueueGCD(AID.FatedCirclePvP, primaryTarget?.Actor, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); #endregion #endregion - var lbStrat = strategy.Option(Track.LimitBreak).As(); - if (ShouldUseLimitBreak(lbStrat, primaryTarget)) - QueueOGCD(LimitBreak, primaryTarget, lbStrat == LimitBreakStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.LB); + var rrStrat = strategy.Option(Track.RelentlessRush).As(); + if (ShouldUseRR(rrStrat, primaryTarget?.Actor)) + QueueOGCD(AID.RelentlessRushPvP, Player, rrStrat == RushStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.LB); + var ttStrat = strategy.Option(Track.TerminalTrigger).As(); + if (ShouldUseTT(ttStrat, primaryTarget?.Actor) && Hints.NumPriorityTargetsInAOECircle(Player.Position, 5) > 0) + QueueGCD(AID.TerminalTriggerPvP, Player, ttStrat == TriggerStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.ForcedGCD); } - #region Core Execution Helpers - private void QueueGCD(AID aid, Actor? target, GCDPriority prio) - { - if (prio != GCDPriority.None) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); - if (prio > NextGCDPrio) - { - NextGCD = aid; - NextGCDPrio = prio; - } - } - } - private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) - { - if (prio != OGCDPriority.None) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); - } - } - #endregion - #region Single-Target Helpers private AID NextCombo() => ComboLastMove switch { @@ -321,7 +300,7 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio { OffensiveStrategy.Automatic => target != null && - !hasNM || rdCD >= 7 || IsOffCooldown(AID.RoughDividePvP), + !hasNM && rdCD >= 7 || !OnCooldown(AID.RoughDividePvP), OffensiveStrategy.Force => true, OffensiveStrategy.Delay => false, _ => false @@ -366,15 +345,24 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio _ => false }; - private bool ShouldUseLimitBreak(LimitBreakStrategy strategy, Actor? target) => strategy switch + private bool ShouldUseRR(RushStrategy strategy, Actor? target) => strategy switch { - LimitBreakStrategy.Automatic => + RushStrategy.Automatic => target != null && In5y(target) && hasNM && LBready, - LimitBreakStrategy.Force => true, - LimitBreakStrategy.Hold => false, + RushStrategy.Force => LBready, + RushStrategy.Hold => false, + _ => false + }; + + private bool ShouldUseTT(TriggerStrategy strategy, Actor? target) => strategy switch + { + TriggerStrategy.Automatic + => StacksRemaining(target, SID.RelentlessShrapnelPvP) > 0 && PlayerHasEffect(SID.RelentlessRushPvP), + TriggerStrategy.Force => PlayerHasEffect(SID.RelentlessRushPvP), + TriggerStrategy.Hold => false, _ => false }; #endregion From 996109458683483e47655f1a55f79d383ce13dc6 Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 11 Feb 2025 09:57:34 -0800 Subject: [PATCH 20/56] Fix up this shit, this module was omega broken kek --- .../Autorotation/Utility/RolePvPUtility.cs | 234 ++++++------------ 1 file changed, 75 insertions(+), 159 deletions(-) diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index f299d119f6..6f2d6da782 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -4,52 +4,11 @@ public sealed class RolePvPUtility(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { - #region Enums: Abilities / Strategies - public enum Track - { - Elixir, - Recuperate, - Guard, - Purify, - Sprint, - } - public enum ElixirStrategy - { - Automatic, - Close, - Mid, - Far, - Force, - Hold - } - - public enum RecuperateStrategy - { - Automatic, - Seventy, - Fifty, - Thirty, - Force, - Hold - } - - public enum GuardStrategy - { - Automatic, - Seventy, - Fifty, - Thirty, - Force, - Hold - } - - public enum DefensiveStrategy - { - Automatic, - Force, - Delay - } - #endregion + public enum Track { Elixir, Recuperate, Guard, Purify, Sprint } + public enum ElixirStrategy { Automatic, Close, Far, Force, Delay } + public enum RecuperateStrategy { Automatic, Seventy, Fifty, Thirty, Force, Delay } + public enum GuardStrategy { Automatic, Seventy, Fifty, Thirty, Force, Delay } + public enum DefensiveStrategy { Automatic, Force, Delay } public static RotationModuleDefinition Definition() { @@ -59,41 +18,40 @@ public static RotationModuleDefinition Definition() Class.WHM, Class.SCH, Class.AST, Class.SGE, Class.MNK, Class.DRG, Class.NIN, Class.SAM, Class.RPR, Class.VPR, Class.BRD, Class.MCH, Class.DNC, - Class.BLM, Class.SMN, Class.RDM, Class.PCT), 100, 30); + Class.BLM, Class.SMN, Class.RDM, Class.PCT), 1, 30); res.Define(Track.Elixir).As("Elixir", uiPriority: 150) - .AddOption(ElixirStrategy.Automatic, "Automatic") - .AddOption(ElixirStrategy.Close, "Close") - .AddOption(ElixirStrategy.Mid, "Mid") - .AddOption(ElixirStrategy.Far, "Far") - .AddOption(ElixirStrategy.Force, "Force") - .AddOption(ElixirStrategy.Hold, "Hold") + .AddOption(ElixirStrategy.Automatic, "Automatic", "Automatically use Elixir when no targets are nearby within 30 yalms") + .AddOption(ElixirStrategy.Close, "Close", "Automatically use Elixir when no targets are nearby within 15 yalms") + .AddOption(ElixirStrategy.Far, "Far", "Automatically use Elixir when no targets are nearby within 45 yalms") + .AddOption(ElixirStrategy.Force, "Force", "Force use Elixir") + .AddOption(ElixirStrategy.Delay, "Delay", "Forbids use of Elixir") .AddAssociatedActions(ClassShared.AID.Elixir); res.Define(Track.Recuperate).As("Recuperate", uiPriority: 150) - .AddOption(RecuperateStrategy.Automatic, "Automatic") - .AddOption(RecuperateStrategy.Seventy, "Seventy") - .AddOption(RecuperateStrategy.Fifty, "Fifty") - .AddOption(RecuperateStrategy.Thirty, "Thirty") - .AddOption(RecuperateStrategy.Force, "Force") - .AddOption(RecuperateStrategy.Hold, "Hold") + .AddOption(RecuperateStrategy.Automatic, "Automatic", "Automatically use Recuperate when HP% is under 40%") + .AddOption(RecuperateStrategy.Seventy, "Seventy", "Automatically use Recuperate when HP% is under 70%") + .AddOption(RecuperateStrategy.Fifty, "Fifty", "Automatically use Recuperate when HP% is under 50%") + .AddOption(RecuperateStrategy.Thirty, "Thirty", "Automatically use Recuperate when HP% is under 30%") + .AddOption(RecuperateStrategy.Force, "Force", "Force use Recuperate") + .AddOption(RecuperateStrategy.Delay, "Delay", "Forbids use of Recuperate") .AddAssociatedActions(ClassShared.AID.Recuperate); res.Define(Track.Guard).As("Guard", uiPriority: 150) - .AddOption(GuardStrategy.Automatic, "Automatic") - .AddOption(GuardStrategy.Seventy, "Seventy") - .AddOption(GuardStrategy.Fifty, "Fifty") - .AddOption(GuardStrategy.Thirty, "Thirty") - .AddOption(GuardStrategy.Force, "Force") - .AddOption(GuardStrategy.Hold, "Hold") + .AddOption(GuardStrategy.Automatic, "Automatic", "Automatically use Guard when HP% is under 35%") + .AddOption(GuardStrategy.Seventy, "Seventy", "Automatically use Guard when HP% is under 70%") + .AddOption(GuardStrategy.Fifty, "Fifty", "Automatically use Guard when HP% is under 50%") + .AddOption(GuardStrategy.Thirty, "Thirty", "Automatically use Guard when HP% is under 30%") + .AddOption(GuardStrategy.Force, "Force", "Force use Guard") + .AddOption(GuardStrategy.Delay, "Delay", "Forbids use of Guard") .AddAssociatedActions(ClassShared.AID.Guard); res.Define(Track.Purify).As("Purify", uiPriority: 150) - .AddOption(DefensiveStrategy.Automatic, "Automatic") - .AddOption(DefensiveStrategy.Force, "Force") - .AddOption(DefensiveStrategy.Delay, "Delay") + .AddOption(DefensiveStrategy.Automatic, "Automatic", "Automatically use Purify when under any debuff that can be cleansed") + .AddOption(DefensiveStrategy.Force, "Force", "Force use Purify") + .AddOption(DefensiveStrategy.Delay, "Delay", "Forbids use of Purify") .AddAssociatedActions(ClassShared.AID.Purify); res.Define(Track.Sprint).As("Sprint", uiPriority: 150) - .AddOption(DefensiveStrategy.Automatic, "Automatic") - .AddOption(DefensiveStrategy.Force, "Force") - .AddOption(DefensiveStrategy.Delay, "Delay") + .AddOption(DefensiveStrategy.Automatic, "Automatic", "Automatically uses Sprint when no target is nearby within 15 yalms") + .AddOption(DefensiveStrategy.Force, "Force", "Force use Sprint") + .AddOption(DefensiveStrategy.Delay, "Delay", "Forbids use of Sprint") .AddAssociatedActions(ClassShared.AID.Sprint); return res; } @@ -116,22 +74,20 @@ public enum OGCDPriority } #endregion - #region Placeholders for Variables - private bool hasSprint; - private bool canElixir; - private bool canRecuperate; - private bool canGuard; - private bool canPurify; - private bool canSprint; - public float GCDLength; - #endregion - #region Module Helpers - private bool In10y(Actor? target) => Player.DistanceToHitbox(target) <= 9.9; - private bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.9; - private bool In30y(Actor? target) => Player.DistanceToHitbox(target) <= 29.9; - private bool IsOffCooldown(ClassShared.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; - #endregion + public static float TargetHPP(Actor? target = null) + { + if (target is null || target.IsDead) + return 0f; + + if (target is Actor actor) + { + var HPP = (float)actor.HPMP.CurHP / actor.HPMP.MaxHP * 1f; + return Math.Clamp(HPP, 0f, 1f); + } + + return 0f; + } public float DebuffsLeft(Actor? target) { return target == null ? 0f @@ -145,23 +101,27 @@ public float DebuffsLeft(Actor? target) ); } public bool HasAnyDebuff(Actor? target) => DebuffsLeft(target) > 0; + private bool IsOffCooldown(ClassShared.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; + #endregion + + private bool hasSprint; + private bool canElixir; + private bool canRecuperate; + private bool canGuard; + private bool canPurify; + private bool canSprint; public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { - #region Variables hasSprint = Player.FindStatus(ClassShared.SID.SprintPvP) != null; - - #region Minimal Requirements - canElixir = IsOffCooldown(ClassShared.AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Hold; - canRecuperate = Player.HPMP.CurMP >= 2500 && strategy.Option(Track.Recuperate).As() != RecuperateStrategy.Hold; - canGuard = IsOffCooldown(ClassShared.AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Hold; + canElixir = IsOffCooldown(ClassShared.AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Delay; + canRecuperate = Player.HPMP.CurMP >= 2500 && strategy.Option(Track.Recuperate).As() != RecuperateStrategy.Delay; + canGuard = IsOffCooldown(ClassShared.AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Delay; canPurify = IsOffCooldown(ClassShared.AID.Purify) && strategy.Option(Track.Purify).As() != DefensiveStrategy.Delay; canSprint = !hasSprint && strategy.Option(Track.Sprint).As() != DefensiveStrategy.Delay; - #endregion - #endregion var elixirStrat = strategy.Option(Track.Elixir).As(); - if (ShouldUseElixir(elixirStrat, primaryTarget)) + if (ShouldUseElixir(elixirStrat)) Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Elixir), Player, strategy.Option(Track.Elixir).Priority(), strategy.Option(Track.Elixir).Value.ExpireIn); var recuperateStrat = strategy.Option(Track.Recuperate).As(); @@ -173,7 +133,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Guard), Player, strategy.Option(Track.Guard).Priority(), strategy.Option(Track.Guard).Value.ExpireIn); var purifyStrat = strategy.Option(Track.Purify).As(); - if (ShouldUsePurify(purifyStrat, primaryTarget)) + if (ShouldUsePurify(purifyStrat)) Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Purify), Player, strategy.Option(Track.Purify).Priority(), strategy.Option(Track.Purify).Value.ExpireIn); var sprintStrat = strategy.Option(Track.Sprint).As(); @@ -181,90 +141,46 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Sprint), Player, strategy.Option(Track.Sprint).Priority(), strategy.Option(Track.Sprint).Value.ExpireIn); } - //TODO: fix this later - #region Core Execution Helpers - /* - public ClassShared.AID NextGCD; //Next global cooldown action to be used - public GCDPriority NextGCDPrio; //Priority of the next GCD for cooldown decision making - - private void QueueGCD(ClassShared.AID aid, Actor? target, GCDPriority prio) + public bool ShouldUseElixir(ElixirStrategy strategy) => strategy switch { - if (prio != GCDPriority.None) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); - if (prio > NextGCDPrio) - { - NextGCD = aid; - NextGCDPrio = prio; - } - } - } - private void QueueOGCD(ClassShared.AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) - { - if (prio != OGCDPriority.None) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); - } - } - */ - #endregion - - public bool ShouldUseElixir(ElixirStrategy strategy, Actor? target) => strategy switch - { - ElixirStrategy.Automatic => - canElixir && - Player.HPMP.CurHP <= 2500 && - (In20y(target) || target != null), - ElixirStrategy.Close => Player.HPMP.CurHP <= 4000 && In10y(target), - ElixirStrategy.Mid => Player.HPMP.CurHP <= 4000 && In20y(target), - ElixirStrategy.Far => Player.HPMP.CurHP <= 4000 && In30y(target), + ElixirStrategy.Automatic => canElixir && TargetHPP(Player) <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 30) == 0, + ElixirStrategy.Close => TargetHPP(Player) <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 15) == 0, + ElixirStrategy.Far => TargetHPP(Player) <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 45) == 0, ElixirStrategy.Force => canElixir, - ElixirStrategy.Hold => false, + ElixirStrategy.Delay => false, _ => false, }; - public bool ShouldUseRecuperate(RecuperateStrategy strategy) => strategy switch { - RecuperateStrategy.Automatic => - canRecuperate && - Player.HPMP.CurHP <= 4000, - RecuperateStrategy.Seventy => canRecuperate && Player.HPMP.CurHP <= 7000, - RecuperateStrategy.Fifty => canRecuperate && Player.HPMP.CurHP <= 5000, - RecuperateStrategy.Thirty => canRecuperate && Player.HPMP.CurHP <= 3000, + RecuperateStrategy.Automatic => canRecuperate && TargetHPP(Player) <= 40, + RecuperateStrategy.Seventy => canRecuperate && TargetHPP(Player) <= 70, + RecuperateStrategy.Fifty => canRecuperate && TargetHPP(Player) <= 50, + RecuperateStrategy.Thirty => canRecuperate && TargetHPP(Player) <= 30, RecuperateStrategy.Force => canRecuperate, - RecuperateStrategy.Hold => false, + RecuperateStrategy.Delay => false, _ => false, }; - public bool ShouldUseGuard(GuardStrategy strategy) => strategy switch { - GuardStrategy.Automatic => - canGuard && - Player.HPMP.CurHP <= 3500, - GuardStrategy.Seventy => canGuard && Player.HPMP.CurHP <= 7000, - GuardStrategy.Fifty => canGuard && Player.HPMP.CurHP <= 5000, - GuardStrategy.Thirty => canGuard && Player.HPMP.CurHP <= 3000, + GuardStrategy.Automatic => canGuard && TargetHPP(Player) <= 35, + GuardStrategy.Seventy => canGuard && TargetHPP(Player) <= 70, + GuardStrategy.Fifty => canGuard && TargetHPP(Player) <= 50, + GuardStrategy.Thirty => canGuard && TargetHPP(Player) <= 30, GuardStrategy.Force => canGuard, - GuardStrategy.Hold => false, + GuardStrategy.Delay => false, _ => false, }; - - public bool ShouldUsePurify(DefensiveStrategy strategy, Actor? target) => strategy switch + public bool ShouldUsePurify(DefensiveStrategy strategy) => strategy switch { - DefensiveStrategy.Automatic => - canPurify && - HasAnyDebuff(target), + DefensiveStrategy.Automatic => canPurify && HasAnyDebuff(Player), DefensiveStrategy.Force => canPurify, DefensiveStrategy.Delay => false, _ => false, }; - public bool ShouldUseSprint(DefensiveStrategy strategy) => strategy switch { - DefensiveStrategy.Automatic => - !Player.InCombat && - canSprint, - DefensiveStrategy.Force => true, + DefensiveStrategy.Automatic => Hints.NumPriorityTargetsInAOECircle(Player.Position, 15) == 0 && canSprint, + DefensiveStrategy.Force => canSprint, DefensiveStrategy.Delay => false, _ => false, }; From 92a58c1e7084b3612d3d41db42a9aeb362a45bd3 Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 11 Feb 2025 09:59:17 -0800 Subject: [PATCH 21/56] xdd --- BossMod/Autorotation/Utility/RolePvPUtility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index 6f2d6da782..083e7e5182 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -18,7 +18,7 @@ public static RotationModuleDefinition Definition() Class.WHM, Class.SCH, Class.AST, Class.SGE, Class.MNK, Class.DRG, Class.NIN, Class.SAM, Class.RPR, Class.VPR, Class.BRD, Class.MCH, Class.DNC, - Class.BLM, Class.SMN, Class.RDM, Class.PCT), 1, 30); + Class.BLM, Class.SMN, Class.RDM, Class.PCT), 100, 30); res.Define(Track.Elixir).As("Elixir", uiPriority: 150) .AddOption(ElixirStrategy.Automatic, "Automatic", "Automatically use Elixir when no targets are nearby within 30 yalms") From 07c30fb491d77e354ce23254f450a6691e78b2b4 Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 11 Feb 2025 10:45:26 -0800 Subject: [PATCH 22/56] Okay, last fixes to PvP stuff --- .../Standard/akechi/AkechiTools.cs | 34 +++-- .../Standard/akechi/PvP/AkechiGNBPvP.cs | 126 +++++++----------- .../Autorotation/Utility/RolePvPUtility.cs | 36 ++--- 3 files changed, 80 insertions(+), 116 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 143f55ee45..394a356fb3 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -460,6 +460,17 @@ protected bool QueueAction(AID aid, Actor? target, float priority, float delay, /// Any specified player, ally, or target protected bool TargetHasShield(Actor actor) => actor.HPMP.Shield > 0.1f; + /// + /// A quick and easy helper for retrieving the Current HP Percentage of the Player specifically. + /// Example:
+ /// - PlayerHPP() > 69 + /// Explanation:
+ /// - "PlayerHPP" represents the current HP Percentage value of the specified actor.
+ /// - "> 69" is the example conditional expression specified by the user.
+ ///
+ /// - A value representing the current HP Percentage (%) of the Player. + protected float PlayerHPP() => (float)Player.HPMP.CurHP / Player.HPMP.MaxHP * 100; + /// /// A quick and easy helper for retrieving the Current HP Percentage of any specified actor, whether it is the player or any other target user desires. /// Example:
@@ -471,7 +482,7 @@ protected bool QueueAction(AID aid, Actor? target, float priority, float delay, ///
/// Any specified player, ally, or target /// - A value representing the current HP Percentage (%) of user's specified actor. - public static float TargetHPP(Actor? target = null) + protected static float TargetHPP(Actor? target = null) { if (target is null || target.IsDead) return 0f; @@ -811,7 +822,6 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// Position checker for determining the best target for an ability that deals damage in a Cone within Sixteen (16) yalms. ///
protected PositionCheck Is16yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 16, Player.DirectionTo(primary), 45.Degrees()); - #endregion #region Lines (aka AOE Rectangles) @@ -830,12 +840,10 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// protected PositionCheck Is20yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 20, 2); - /// /// Position checker for determining the best target for an ability that deals damage in a Line within Twenty-five (25) yalms /// protected PositionCheck Is25yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 25, 2); - #endregion #endregion @@ -918,6 +926,7 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// /// This function attempts to pick a suitable primary target automatically, even if a target is not already picked. + /// NOTE: This function is solely used for auto-targeting enemies without having a target selected, mainly for AI usage. Please use appropriately. /// /// The user's picked Strategy /// The user's current specified Target. @@ -937,21 +946,25 @@ protected void GetPrimaryTarget(StrategyValues strategy, ref Enemy? primaryTarge } /// - /// This function attempts to pick ANY suitable primary target automatically, even if a target is not already picked. + /// This function attempts to pick the most suitable primary target automatically, prioritizing the target with the lowest HP percentage within range. + /// NOTE: This function is solely used for finding a PvP target without having to click on other targets. Please use appropriately. /// /// The user's current specified Target. - /// + /// The max range to search for a new target. protected void GetPvPTarget(ref Enemy? primaryTarget, float range) { - if (Player.DistanceToHitbox(primaryTarget?.Actor) > range) + if (primaryTarget == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) { - var newTarget = Hints.PriorityTargets.FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); + var newTarget = Hints.PriorityTargets + .Where(x => x != null && x.Actor != null && Player.DistanceToHitbox(x.Actor) <= range) + .OrderBy(x => (float)x.Actor.HPMP.CurHP / x.Actor.HPMP.MaxHP) + .FirstOrDefault(); + if (newTarget != null) primaryTarget = newTarget; } } - /// /// This function attempts to pick the best target automatically. /// @@ -1048,9 +1061,6 @@ P targetPrio(Actor potentialTarget) /// Player's "actual" target; guaranteed to be an enemy. /// protected Enemy? PlayerTarget { get; private set; } - - //TODO: implement this soon - protected Actor? AnyTarget { get; private set; } #endregion #region Positionals diff --git a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs index eda284b350..1d8afb4136 100644 --- a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs +++ b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs @@ -7,7 +7,6 @@ namespace BossMod.Autorotation.akechi; //Discord @akechdz or 'Akechi' on Puni.sh for maintenance public sealed class AkechiGNBPvP(RotationModuleManager manager, Actor player) : AkechiTools(manager, player) - { #region Enums: Abilities / Strategies public enum Track @@ -153,7 +152,7 @@ public enum OGCDPriority } #endregion - #region Placeholders for Variables + #region Module Variables private float nmLeft; private float rdCD; private bool hasNM; @@ -177,10 +176,8 @@ public enum OGCDPriority public bool inGF; #endregion - public override void Execution(StrategyValues strategy, Enemy? primaryTarget) + public override void Execution(StrategyValues strategy, Enemy? PlayerTarget) { - GetPvPTarget(ref primaryTarget, 3); - #region Variables var gauge = World.Client.GetGauge(); var GunStep = gauge.AmmoComboStep; @@ -210,82 +207,81 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) }; inCombo = comboStep > 0; inGF = GFcomboStep > 0; - - #region Minimal Requirements + var burst = strategy.Option(Track.Burst); + var burstStrategy = burst.As(); + var hold = burstStrategy == BurstStrategy.Hold; canGF = IsOffCooldown(AID.GnashingFangPvP); canFC = IsOffCooldown(AID.GnashingFangPvP); canZone = IsOffCooldown(AID.BlastingZonePvP); - canHyper = hasBlast && In5y(primaryTarget?.Actor); - canBrand = hasRaze && In5y(primaryTarget?.Actor); - canRip = hasRip && In5y(primaryTarget?.Actor); - canTear = hasTear && In5y(primaryTarget?.Actor); - canGouge = hasGouge && In5y(primaryTarget?.Actor); - #endregion + canHyper = hasBlast && In5y(PlayerTarget?.Actor); + canBrand = hasRaze && In5y(PlayerTarget?.Actor); + canRip = hasRip && In5y(PlayerTarget?.Actor); + canTear = hasTear && In5y(PlayerTarget?.Actor); + canGouge = hasGouge && In5y(PlayerTarget?.Actor); #endregion - var burst = strategy.Option(Track.Burst); - var burstStrategy = burst.As(); - var hold = burstStrategy == BurstStrategy.Hold; - - if (strategy.Option(Track.Combo).As() is ComboStrategy.Force) - QueueGCD(NextCombo(), primaryTarget?.Actor, GCDPriority.ForcedGCD); - #region Rotation Execution + GetPvPTarget(ref PlayerTarget, 3); + if (!inGF) - QueueGCD(NextCombo(), primaryTarget?.Actor, GCDPriority.Combo); + QueueGCD(NextCombo(), PlayerTarget?.Actor, GCDPriority.Combo); + if (strategy.Option(Track.Combo).As() is ComboStrategy.Force) + QueueGCD(NextCombo(), PlayerTarget?.Actor, GCDPriority.ForcedGCD); #region OGCDs var rdStrat = strategy.Option(Track.RoughDivide).As(); if (!hold && - ShouldUseRoughDivide(rdStrat, primaryTarget?.Actor)) - QueueOGCD(AID.RoughDividePvP, primaryTarget?.Actor, rdStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.RoughDivide); + ShouldUseRoughDivide(rdStrat, PlayerTarget?.Actor)) + QueueOGCD(AID.RoughDividePvP, PlayerTarget?.Actor, rdStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.RoughDivide); var zoneStrat = strategy.Option(Track.Zone).As(); if (!hold && - ShouldUseZone(zoneStrat, primaryTarget?.Actor)) - QueueOGCD(AID.BlastingZonePvP, primaryTarget?.Actor, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); + ShouldUseZone(zoneStrat, PlayerTarget?.Actor)) + QueueOGCD(AID.BlastingZonePvP, PlayerTarget?.Actor, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); if (canRip || GunStep == 1) - QueueOGCD(AID.JugularRipPvP, primaryTarget?.Actor, OGCDPriority.Continuation); + QueueOGCD(AID.JugularRipPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canTear || GunStep == 2) - QueueOGCD(AID.AbdomenTearPvP, primaryTarget?.Actor, OGCDPriority.Continuation); + QueueOGCD(AID.AbdomenTearPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canGouge) - QueueOGCD(AID.EyeGougePvP, primaryTarget?.Actor, OGCDPriority.Continuation); + QueueOGCD(AID.EyeGougePvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canHyper) - QueueOGCD(AID.HypervelocityPvP, primaryTarget?.Actor, OGCDPriority.Continuation); + QueueOGCD(AID.HypervelocityPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canBrand) - QueueOGCD(AID.FatedBrandPvP, primaryTarget?.Actor, OGCDPriority.Continuation); + QueueOGCD(AID.FatedBrandPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); - if (TargetHPP(Player) < 50) - QueueOGCD(AID.HeartOfCorundumPvP, primaryTarget?.Actor, OGCDPriority.Corundum); + if (TargetHPP(Player) < 55) + QueueOGCD(AID.HeartOfCorundumPvP, Player, OGCDPriority.Corundum); #endregion #region GCDs var gfStrat = strategy.Option(Track.GnashingFang).As(); if (!hold && - ShouldUseGnashingFang(gfStrat, primaryTarget?.Actor)) - QueueGCD(AID.GnashingFangPvP, primaryTarget?.Actor, GCDPriority.GnashingFang); - if (GunStep == 1 && In5y(primaryTarget?.Actor)) - QueueGCD(AID.SavageClawPvP, primaryTarget?.Actor, GCDPriority.GnashingFang); - if (GunStep == 2 && In5y(primaryTarget?.Actor)) - QueueGCD(AID.WickedTalonPvP, primaryTarget?.Actor, GCDPriority.GnashingFang); + ShouldUseGnashingFang(gfStrat, PlayerTarget?.Actor)) + QueueGCD(AID.GnashingFangPvP, PlayerTarget?.Actor, GCDPriority.GnashingFang); + if (GunStep == 1 && In5y(PlayerTarget?.Actor)) + QueueGCD(AID.SavageClawPvP, PlayerTarget?.Actor, GCDPriority.GnashingFang); + if (GunStep == 2 && In5y(PlayerTarget?.Actor)) + QueueGCD(AID.WickedTalonPvP, PlayerTarget?.Actor, GCDPriority.GnashingFang); var fcStrat = strategy.Option(Track.FatedCircle).As(); - if (ShouldUseFatedCircle(fcStrat, primaryTarget?.Actor)) - QueueGCD(AID.FatedCirclePvP, primaryTarget?.Actor, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); + if (ShouldUseFatedCircle(fcStrat, PlayerTarget?.Actor)) + QueueGCD(AID.FatedCirclePvP, PlayerTarget?.Actor, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); #endregion #endregion + #region Limit Break var rrStrat = strategy.Option(Track.RelentlessRush).As(); - if (ShouldUseRR(rrStrat, primaryTarget?.Actor)) + if (ShouldUseRR(rrStrat, PlayerTarget?.Actor)) QueueOGCD(AID.RelentlessRushPvP, Player, rrStrat == RushStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.LB); var ttStrat = strategy.Option(Track.TerminalTrigger).As(); - if (ShouldUseTT(ttStrat, primaryTarget?.Actor) && Hints.NumPriorityTargetsInAOECircle(Player.Position, 5) > 0) + if (ShouldUseTT(ttStrat, PlayerTarget?.Actor) && Hints.NumPriorityTargetsInAOECircle(Player.Position, 5) > 0) QueueGCD(AID.TerminalTriggerPvP, Player, ttStrat == TriggerStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.ForcedGCD); + #endregion } - #region Single-Target Helpers + #region Cooldown Helpers private AID NextCombo() => ComboLastMove switch { AID.SolidBarrelPvP => AID.BurstStrikePvP, @@ -293,74 +289,44 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) AID.KeenEdgePvP => AID.BrutalShellPvP, _ => AID.KeenEdgePvP, }; - #endregion - - #region Cooldown Helpers private bool ShouldUseRoughDivide(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - target != null && - !hasNM && rdCD >= 7 || !OnCooldown(AID.RoughDividePvP), - OffensiveStrategy.Force => true, + OffensiveStrategy.Automatic => target != null && (!hasNM && rdCD <= 14 || !OnCooldown(AID.RoughDividePvP)), + OffensiveStrategy.Force => rdCD <= 14.5f, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseZone(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - Player.InCombat && - target != null && - canZone && - hasNM && - In5y(target), + OffensiveStrategy.Automatic => target != null && canZone && hasNM && In5y(target), OffensiveStrategy.Force => canZone, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseGnashingFang(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - Player.InCombat && - target != null && - In5y(target) && - hasNM && - canGF, + OffensiveStrategy.Automatic => target != null && In5y(target) && hasNM && canGF, OffensiveStrategy.Force => canGF, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseFatedCircle(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - Player.InCombat && - target != null && - In5y(target) && - hasNM && - canFC, + OffensiveStrategy.Automatic => target != null && In5y(target) && hasNM && canFC, OffensiveStrategy.Force => canFC, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseRR(RushStrategy strategy, Actor? target) => strategy switch { - RushStrategy.Automatic => - target != null && - In5y(target) && - hasNM && - LBready, + RushStrategy.Automatic => target != null && In5y(target) && hasNM && LBready, RushStrategy.Force => LBready, RushStrategy.Hold => false, _ => false }; - private bool ShouldUseTT(TriggerStrategy strategy, Actor? target) => strategy switch { - TriggerStrategy.Automatic - => StacksRemaining(target, SID.RelentlessShrapnelPvP) > 0 && PlayerHasEffect(SID.RelentlessRushPvP), + TriggerStrategy.Automatic => StacksRemaining(target, SID.RelentlessShrapnelPvP) > 0 && PlayerHasEffect(SID.RelentlessRushPvP), TriggerStrategy.Force => PlayerHasEffect(SID.RelentlessRushPvP), TriggerStrategy.Hold => false, _ => false diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index 083e7e5182..3f67c40baa 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -75,19 +75,7 @@ public enum OGCDPriority #endregion #region Module Helpers - public static float TargetHPP(Actor? target = null) - { - if (target is null || target.IsDead) - return 0f; - - if (target is Actor actor) - { - var HPP = (float)actor.HPMP.CurHP / actor.HPMP.MaxHP * 1f; - return Math.Clamp(HPP, 0f, 1f); - } - - return 0f; - } + public float PlayerHPP() => (float)Player.HPMP.CurHP / Player.HPMP.MaxHP * 100; public float DebuffsLeft(Actor? target) { return target == null ? 0f @@ -143,29 +131,29 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, public bool ShouldUseElixir(ElixirStrategy strategy) => strategy switch { - ElixirStrategy.Automatic => canElixir && TargetHPP(Player) <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 30) == 0, - ElixirStrategy.Close => TargetHPP(Player) <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 15) == 0, - ElixirStrategy.Far => TargetHPP(Player) <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 45) == 0, + ElixirStrategy.Automatic => canElixir && PlayerHPP() <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 30) == 0, + ElixirStrategy.Close => canElixir && PlayerHPP() <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 15) == 0, + ElixirStrategy.Far => canElixir && PlayerHPP() <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 45) == 0, ElixirStrategy.Force => canElixir, ElixirStrategy.Delay => false, _ => false, }; public bool ShouldUseRecuperate(RecuperateStrategy strategy) => strategy switch { - RecuperateStrategy.Automatic => canRecuperate && TargetHPP(Player) <= 40, - RecuperateStrategy.Seventy => canRecuperate && TargetHPP(Player) <= 70, - RecuperateStrategy.Fifty => canRecuperate && TargetHPP(Player) <= 50, - RecuperateStrategy.Thirty => canRecuperate && TargetHPP(Player) <= 30, + RecuperateStrategy.Automatic => canRecuperate && PlayerHPP() <= 40, + RecuperateStrategy.Seventy => canRecuperate && PlayerHPP() <= 70, + RecuperateStrategy.Fifty => canRecuperate && PlayerHPP() <= 50, + RecuperateStrategy.Thirty => canRecuperate && PlayerHPP() <= 30, RecuperateStrategy.Force => canRecuperate, RecuperateStrategy.Delay => false, _ => false, }; public bool ShouldUseGuard(GuardStrategy strategy) => strategy switch { - GuardStrategy.Automatic => canGuard && TargetHPP(Player) <= 35, - GuardStrategy.Seventy => canGuard && TargetHPP(Player) <= 70, - GuardStrategy.Fifty => canGuard && TargetHPP(Player) <= 50, - GuardStrategy.Thirty => canGuard && TargetHPP(Player) <= 30, + GuardStrategy.Automatic => canGuard && PlayerHPP() <= 35, + GuardStrategy.Seventy => canGuard && PlayerHPP() <= 70, + GuardStrategy.Fifty => canGuard && PlayerHPP() <= 50, + GuardStrategy.Thirty => canGuard && PlayerHPP() <= 30, GuardStrategy.Force => canGuard, GuardStrategy.Delay => false, _ => false, From 56124f86a42b4f9802ad05a51de9b80c5edb46db Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 11 Feb 2025 10:49:35 -0800 Subject: [PATCH 23/56] fix error --- BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs index 1d8afb4136..b076c1852d 100644 --- a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs +++ b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs @@ -176,7 +176,7 @@ public enum OGCDPriority public bool inGF; #endregion - public override void Execution(StrategyValues strategy, Enemy? PlayerTarget) + public override void Execution(StrategyValues strategy, Enemy? primaryTarget) { #region Variables var gauge = World.Client.GetGauge(); @@ -221,7 +221,7 @@ public override void Execution(StrategyValues strategy, Enemy? PlayerTarget) #endregion #region Rotation Execution - GetPvPTarget(ref PlayerTarget, 3); + GetPvPTarget(ref primaryTarget, 3); if (!inGF) QueueGCD(NextCombo(), PlayerTarget?.Actor, GCDPriority.Combo); From caa2b4199c08331243cff54dd5012d53a9777f00 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:00:48 -0500 Subject: [PATCH 24/56] fix autopull UI categorization --- BossMod/Autorotation/MiscAI/AutoPull.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BossMod/Autorotation/MiscAI/AutoPull.cs b/BossMod/Autorotation/MiscAI/AutoPull.cs index db0ebf8c24..e93f88ad8b 100644 --- a/BossMod/Autorotation/MiscAI/AutoPull.cs +++ b/BossMod/Autorotation/MiscAI/AutoPull.cs @@ -8,7 +8,7 @@ public enum Track { QuestBattle, DeepDungeon, EpicEcho, Hunt } public static RotationModuleDefinition Definition() { - var def = new RotationModuleDefinition("Misc AI: Auto-pull", "Automatically attack passive mobs in certain circumstances", "Misc", "xan", RotationModuleQuality.Basic, new(~0ul), 1000, Order: RotationModuleOrder.HighLevel, CanUseWhileRoleplaying: true); + var def = new RotationModuleDefinition("Auto-pull", "Automatically attack passive mobs in certain circumstances", "AI", "xan", RotationModuleQuality.Basic, new(~0ul), 1000, Order: RotationModuleOrder.HighLevel, CanUseWhileRoleplaying: true); def.AbilityTrack(Track.QuestBattle, "Automatically attack solo duty bosses"); def.AbilityTrack(Track.DeepDungeon, "Automatically attack deep dungeon bosses when solo"); From 5a1fd8d408ace89ca47741582d7e39bdd5ce241c Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:01:03 -0500 Subject: [PATCH 25/56] initial commit for hydatos wip stuff --- BossMod/Modules/Stormblood/Foray/Hydatos.cs | 101 ++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 BossMod/Modules/Stormblood/Foray/Hydatos.cs diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs new file mode 100644 index 0000000000..53c9f62685 --- /dev/null +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -0,0 +1,101 @@ +using ImGuiNET; + +namespace BossMod.Stormblood.Foray.Hydatos; + +[ConfigDisplay(Name = "Eureka", Parent = typeof(StormbloodConfig))] +public class EurekaConfig : ConfigNode +{ + [PropertyDisplay("Max range to look for new mobs to pull")] + [PropertySlider(20, 100, Speed = 0.1f)] + public float MaxPullDistance = 30f; + + [PropertyDisplay("Max number of mobs to pull at once (0 for no limit)")] + [PropertySlider(0, 30)] + public int MaxPullCount = 10; +} + +[ConfigDisplay(Name = "Hydatos", Parent = typeof(EurekaConfig))] +public class HydatosConfig : ConfigNode +{ + public enum Farm : uint + { + [PropertyDisplay("")] + None, + [PropertyDisplay("Khalamari (Xzomit)")] + Khalamari = 0x26AB, + [PropertyDisplay("Stegodon (Hydatos Primelephas)")] + Stego = 0x26AF, + [PropertyDisplay("Molech (Val Nullchu)")] + Molech = 0x26B2, + [PropertyDisplay("Piasa (Vivid Gastornis)")] + Piasa = 0x26B3, + [PropertyDisplay("Frostmane (Northern Tiger)")] + Frostmane = 0x26B8, + [PropertyDisplay("Daphne (Dark Void Monk)")] + Daphne = 0x26B9, + [PropertyDisplay("Leuke (Tigerhawk)")] + Leuke = 0x26C0, + [PropertyDisplay("Barong (Laboratory Lion)")] + Barong = 0x26C2, + [PropertyDisplay("Ceto (Hydatos Delphyne)")] + Ceto = 0x26C5, + [PropertyDisplay("PW (Crystal Claw)")] + PW = 0x26CA + } + + public Farm CurrentFarmTarget = Farm.None; +} + +[ZoneModuleInfo(BossModuleInfo.Maturity.WIP, 639)] +public class Hydatos(WorldState ws) : ZoneModule(ws) +{ + private readonly EurekaConfig _eurekaConfig = Service.Config.Get(); + private readonly HydatosConfig _hydatosConfig = Service.Config.Get(); + + public override void CalculateAIHints(int playerSlot, Actor player, AIHints hints) + { + hints.ForbiddenZones.RemoveAll(z => World.Actors.Find(z.Source) is Actor src && ShouldIgnore(src, player)); + + var shouldSetTarget = !player.InCombat && player.TargetID == 0; + + var farmOID = (uint)_hydatosConfig.CurrentFarmTarget; + var farmMax = _eurekaConfig.MaxPullCount; + var farmRange = _eurekaConfig.MaxPullDistance; + + if (farmOID > 0 && (farmMax == 0 || hints.PotentialTargets.Count(e => e.Priority >= 0) < farmMax)) + foreach (var e in hints.PotentialTargets) + if (e.Actor.OID == farmOID && e.Priority == AIHints.Enemy.PriorityUndesirable && (e.Actor.Position - player.Position).LengthSq() <= farmRange * farmRange) + { + e.Priority = 1; + + if (shouldSetTarget && (hints.ForcedTarget == null || (hints.ForcedTarget.Position - player.Position).LengthSq() > (e.Actor.Position - player.Position).LengthSq())) + hints.ForcedTarget = e.Actor; + } + } + + private bool ShouldIgnore(Actor caster, Actor player) + { + return caster.CastInfo != null + && caster.CastInfo.Action.ID switch + { + 15415 or 15416 => true, + 15449 or 15295 => caster.CastInfo.TargetID == player.InstanceID, + _ => false, + }; + } + + public override bool WantDrawExtra() => true; + + public override void DrawExtra() + { + if (UICombo.Enum("Prep mob", ref _hydatosConfig.CurrentFarmTarget)) + _hydatosConfig.Modified.Fire(); + + ImGui.SetNextItemWidth(200); + if (ImGui.DragFloat("Max distance to look for new mobs", ref _eurekaConfig.MaxPullDistance, 1, 20, 80)) + _eurekaConfig.Modified.Fire(); + ImGui.SetNextItemWidth(200); + if (ImGui.DragInt("Max mobs to pull (set to 0 for no limit)", ref _eurekaConfig.MaxPullCount, 1, 0, 30)) + _eurekaConfig.Modified.Fire(); + } +} From 50cbb721ff2ec3cb60dfeeabb952344ac15675bd Mon Sep 17 00:00:00 2001 From: ace Date: Tue, 11 Feb 2025 11:07:48 -0800 Subject: [PATCH 26/56] fix auto-targeting oops --- BossMod/Autorotation/Standard/akechi/AkechiTools.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 394a356fb3..3e22e60fec 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -953,7 +953,9 @@ protected void GetPrimaryTarget(StrategyValues strategy, ref Enemy? primaryTarge /// The max range to search for a new target. protected void GetPvPTarget(ref Enemy? primaryTarget, float range) { - if (primaryTarget == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) + primaryTarget ??= PlayerTarget; + + if (Player.DistanceToHitbox(PlayerTarget?.Actor) > range) { var newTarget = Hints.PriorityTargets .Where(x => x != null && x.Actor != null && Player.DistanceToHitbox(x.Actor) <= range) @@ -961,7 +963,7 @@ protected void GetPvPTarget(ref Enemy? primaryTarget, float range) .FirstOrDefault(); if (newTarget != null) - primaryTarget = newTarget; + PlayerTarget = newTarget; } } From fcb693465b0c341d299e8710cf1c238022f164d1 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:13:08 -0500 Subject: [PATCH 27/56] opt-in cleansability --- .../Autorotation/Standard/xan/AI/Healer.cs | 19 ++++++++-- .../Autorotation/Standard/xan/Healers/WHM.cs | 2 +- BossMod/BossModule/AIHints.cs | 4 +++ .../DeepDungeon/PalaceOfTheDead/D70Yaquaru.cs | 36 ++++++++++++++++++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/AI/Healer.cs b/BossMod/Autorotation/Standard/xan/AI/Healer.cs index a688bea0da..183bf162eb 100644 --- a/BossMod/Autorotation/Standard/xan/AI/Healer.cs +++ b/BossMod/Autorotation/Standard/xan/AI/Healer.cs @@ -9,6 +9,12 @@ public class HealerAI(RotationModuleManager manager, Actor player) : AIBase(mana private readonly TrackPartyHealth Health = new(manager.WorldState); public enum Track { Raise, RaiseTarget, Heal, Esuna, StayNearParty } + public enum EsunaStrategy + { + None, + Hinted, + All + } public enum RaiseStrategy { None, @@ -48,7 +54,12 @@ public static RotationModuleDefinition Definition() .AddOption(RaiseTarget.Everyone, "Any dead player"); def.AbilityTrack(Track.Heal, "Heal"); - def.AbilityTrack(Track.Esuna, "Esuna"); + + def.Define(Track.Esuna).As("Esuna") + .AddOption(EsunaStrategy.None, "Don't cleanse") + .AddOption(EsunaStrategy.Hinted, "Cleanse targets suggested by active module") + .AddOption(EsunaStrategy.All, "Cleanse all party members that have a removable debuff"); + def.AbilityTrack(Track.StayNearParty, "Stay near party"); return def; @@ -97,11 +108,13 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, AutoRaise(strategy); - if (strategy.Enabled(Track.Esuna)) + var esuna = strategy.Option(Track.Esuna).As(); + + if (esuna != EsunaStrategy.None) { foreach (var st in Health.PartyMemberStates) { - if (st.EsunableStatusRemaining > GCD + 2f) + if (st.EsunableStatusRemaining > GCD + 1.14f && (esuna == EsunaStrategy.All || Hints.ShouldCleanse[st.Slot])) { UseGCD(BossMod.WHM.AID.Esuna, World.Party[st.Slot]); break; diff --git a/BossMod/Autorotation/Standard/xan/Healers/WHM.cs b/BossMod/Autorotation/Standard/xan/Healers/WHM.cs index ef145907e6..7852c18da5 100644 --- a/BossMod/Autorotation/Standard/xan/Healers/WHM.cs +++ b/BossMod/Autorotation/Standard/xan/Healers/WHM.cs @@ -89,7 +89,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) PushGCD(AID.Holy1, Player); // TODO make a track for this - if (Lily == 3 || !CanFitGCD(NextLily, 2) && Lily == 2) + if (Unlocked(AID.AfflatusMisery) && (Lily == 3 || !CanFitGCD(NextLily, 2) && Lily == 2)) PushGCD(AID.AfflatusSolace, Player); if (SacredSight > 0) diff --git a/BossMod/BossModule/AIHints.cs b/BossMod/BossModule/AIHints.cs index 180828d3a2..7de69b692c 100644 --- a/BossMod/BossModule/AIHints.cs +++ b/BossMod/BossModule/AIHints.cs @@ -86,6 +86,9 @@ public enum SpecialMode // AI will attempt to shield & mitigate public List<(BitMask players, DateTime activation)> PredictedDamage = []; + // list of party members with cleansable debuffs that are dangerous enough to sacrifice a GCD to cleanse them, i.e. doom, throttle, some types of vuln debuff, etc + public BitMask ShouldCleanse; + // maximal time we can spend casting before we need to move // this is used by the action queue to skip casts that we won't be able to finish and execute lower-priority fallback actions instead public float MaxCastTime = float.MaxValue; @@ -119,6 +122,7 @@ public void Clear() ImminentSpecialMode = default; MisdirectionThreshold = 15.Degrees(); PredictedDamage.Clear(); + ShouldCleanse.Reset(); MaxCastTime = float.MaxValue; ForceCancelCast = false; ActionsToExecute.Clear(); diff --git a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D70Yaquaru.cs b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D70Yaquaru.cs index 4526ffad49..ec1d448326 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D70Yaquaru.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D70Yaquaru.cs @@ -16,6 +16,11 @@ public enum AID : uint FangsEnd = 7092, // Boss->player, no cast, single-target } +public enum SID : uint +{ + Heavy = 14 +} + class DouseCast(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.Douse), new AOEShapeCircle(8)); class DousePuddle(BossModule module) : BossComponent(module) { @@ -55,6 +60,34 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme } class Electrogenesis(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Electrogenesis), 8); +class FangsEnd(BossModule module) : BossComponent(module) +{ + private BitMask _heavy; + + public override void Update() + { + for (var i = 0; i < 4; i++) + { + var player = Raid[i]; + if (player == null) + continue; + + if (player.FindStatus(SID.Heavy) is ActorStatus st && (st.ExpireAt - WorldState.CurrentTime).TotalSeconds > 8) + _heavy.Set(i); + } + } + + public override void OnStatusLose(Actor actor, ActorStatus status) + { + if (status.ID == (uint)SID.Heavy) + _heavy.Clear(Raid.FindSlot(actor.InstanceID)); + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + hints.ShouldCleanse |= _heavy; + } +} class D70TaquaruStates : StateMachineBuilder { @@ -63,7 +96,8 @@ public D70TaquaruStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter() + .ActivateOnEnter(); } } From 876c259b02ce66f95e5ced4ad2aa18fb1ad5009e Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:02:22 -0500 Subject: [PATCH 28/56] add "mount => do nothing" strategy to auto target preset --- BossMod/Autorotation/MiscAI/AutoFarm.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/MiscAI/AutoFarm.cs b/BossMod/Autorotation/MiscAI/AutoFarm.cs index f89432f7c6..427722f8c9 100644 --- a/BossMod/Autorotation/MiscAI/AutoFarm.cs +++ b/BossMod/Autorotation/MiscAI/AutoFarm.cs @@ -2,9 +2,10 @@ public sealed class AutoFarm(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { - public enum Track { General, Fate, Specific } + public enum Track { General, Fate, Specific, Mount } public enum GeneralStrategy { FightBack, AllowPull, Aggressive, Passive } public enum PriorityStrategy { None, Prioritize } + public enum MountedStrategy { None, DisableFightBack, DisableAll } public static RotationModuleDefinition Definition() { @@ -24,6 +25,11 @@ public static RotationModuleDefinition Definition() .AddOption(PriorityStrategy.None, "None", "Do not do anything special") .AddOption(PriorityStrategy.Prioritize, "Prioritize", "Prioritize specific mobs by targeting criterion"); + res.Define(Track.Mount).As("Mount") + .AddOption(MountedStrategy.None, "None", "Do not do anything special") + .AddOption(MountedStrategy.DisableFightBack, "NoFightBack", "Do not engage previously uninteresting mobs if they aggro on player") + .AddOption(MountedStrategy.DisableAll, "NoAll", "Do not engage anything while mounted"); + return res; } @@ -34,6 +40,11 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, if (generalStrategy == GeneralStrategy.Passive) return; + var mountStrategy = strategy.Option(Track.Mount).As(); + var mounted = Player.MountId > 0; + if (mounted && mountStrategy == MountedStrategy.DisableAll) + return; + var allowPulling = generalStrategy switch { GeneralStrategy.AllowPull => !Player.InCombat, @@ -85,7 +96,9 @@ void prioritize(AIHints.Enemy e, int prio) } // if we did not select an enemy to pull, see if we can target something higher-priority than what we have now - if (switchTarget == null && Player.InCombat) + // if mounted, check if the "fight back" strategy is undesired + var mountNoFightBack = mounted && mountStrategy == MountedStrategy.DisableFightBack; + if (switchTarget == null && Player.InCombat && !mountNoFightBack) { var curTargetPrio = Hints.FindEnemy(primaryTarget)?.Priority ?? int.MinValue; switchTarget = ResolveTargetOverride(generalOpt.Value) ?? (curTargetPrio < Hints.HighestPotentialTargetPriority ? Hints.PriorityTargets.MinBy(e => (e.Actor.Position - Player.Position).LengthSq())?.Actor : null); From d7b54fc0aa855302f3528f7158a10aede41235e7 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:02:45 -0500 Subject: [PATCH 29/56] only navigate when positional is imminent --- BossMod/Autorotation/Standard/xan/Basexan.cs | 6 ++++-- BossMod/Autorotation/Standard/xan/Melee/DRG.cs | 2 +- BossMod/Autorotation/Standard/xan/Melee/MNK.cs | 11 +++-------- BossMod/Autorotation/Standard/xan/Melee/NIN.cs | 2 +- BossMod/Autorotation/Standard/xan/Melee/RPR.cs | 2 +- BossMod/Autorotation/Standard/xan/Melee/SAM.cs | 2 +- BossMod/Autorotation/Standard/xan/Melee/VPR.cs | 2 +- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/Basexan.cs b/BossMod/Autorotation/Standard/xan/Basexan.cs index 6f9b833dfe..ec52c75e9b 100644 --- a/BossMod/Autorotation/Standard/xan/Basexan.cs +++ b/BossMod/Autorotation/Standard/xan/Basexan.cs @@ -288,8 +288,10 @@ protected void GoalZoneSingle(float range) Hints.GoalZones.Add(Hints.GoalSingleTarget(PlayerTarget.Actor, range)); } - protected void GoalZoneCombined(StrategyValues strategy, float range, Func fAoe, AID firstUnlockedAoeAction, int minAoe, Positional positional = Positional.Any, float? maximumActionRange = null) + protected void GoalZoneCombined(StrategyValues strategy, float range, Func fAoe, AID firstUnlockedAoeAction, int minAoe, (Positional pos, bool imminent)? positional = null, float? maximumActionRange = null) { + var posActual = positional?.imminent == true ? positional.Value.pos : Positional.Any; + if (!strategy.AOEOk() || !Unlocked(firstUnlockedAoeAction)) minAoe = 50; @@ -300,7 +302,7 @@ protected void GoalZoneCombined(StrategyValues strategy, float range, Func 2) { diff --git a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs index 8e2927474e..98a3af5191 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs @@ -6,7 +6,7 @@ namespace BossMod.Autorotation.xan; public sealed class MNK(RotationModuleManager manager, Actor player) : Attackxan(manager, player) { - public enum Track { BH = SharedTrack.Buffs, RoF, FiresReply, RoW, WindsReply, PB, Nadi, Blitz, SSS, FormShift, Meditation, TC, Potion, Engage, TN, Positional } + public enum Track { BH = SharedTrack.Buffs, RoF, FiresReply, RoW, WindsReply, PB, Nadi, Blitz, SSS, FormShift, Meditation, TC, Potion, Engage, TN } public enum PotionStrategy { Manual, @@ -118,7 +118,6 @@ public static RotationModuleDefinition Definition() .AddOption(WRStrategy.PreDowntime, "Ensure usage at least 2 GCDs before next downtime", minLevel: 96) .AddAssociatedActions(AID.WindsReply); - // PB-related settings def.Define(Track.PB).As("PB", uiPriority: 89) .AddOption(PBStrategy.Automatic, "Automatically use after Opo before or during Riddle of Fire", minLevel: 50) @@ -168,10 +167,6 @@ public static RotationModuleDefinition Definition() .AddOption(EngageStrategy.FacepullDemo, "Precast Demolish from melee range"); def.DefineSimple(Track.TN, "TrueNorth", minLevel: 50, uiPriority: 48).AddAssociatedActions(AID.TrueNorth); - def.Define(Track.Positional).As("Pos (AI)", uiPriority: 45) - .AddOption(PositionalStrategy.Automatic, "Tell AI mode to navigate to hit positionals") - .AddOption(PositionalStrategy.Ignore, "Tell AI mode to ignore positionals") - .AddAssociatedActions(AID.Demolish, AID.SnapPunch, AID.PouncingCoeurl); return def; } @@ -410,11 +405,11 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) Prep(strategy); - var pos = strategy.Option(Track.Positional).As() == PositionalStrategy.Automatic ? NextPositional : (Positional.Any, false); + var pos = NextPositional; UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.ArmOfTheDestroyer, AOEBreakpoint, positional: pos.Item1, maximumActionRange: 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.ArmOfTheDestroyer, AOEBreakpoint, positional: pos, maximumActionRange: 20); if (Player.InCombat) OGCD(strategy, primaryTarget); diff --git a/BossMod/Autorotation/Standard/xan/Melee/NIN.cs b/BossMod/Autorotation/Standard/xan/Melee/NIN.cs index 41f8586d0c..bf744ab74f 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/NIN.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/NIN.cs @@ -133,7 +133,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.DeathBlossom, minAoe: 3, positional: pos.Item1, maximumActionRange: 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.DeathBlossom, minAoe: 3, positional: pos, maximumActionRange: 20); if (TenChiJin.Left > GCD) { diff --git a/BossMod/Autorotation/Standard/xan/Melee/RPR.cs b/BossMod/Autorotation/Standard/xan/Melee/RPR.cs index c6209c0183..d5ab9d1931 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/RPR.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/RPR.cs @@ -136,7 +136,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SpinningScythe, 3, pos.Item1, maximumActionRange: 25); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SpinningScythe, 3, pos, maximumActionRange: 25); if (SoulReaver > GCD || Executioner > GCD) { diff --git a/BossMod/Autorotation/Standard/xan/Melee/SAM.cs b/BossMod/Autorotation/Standard/xan/Melee/SAM.cs index d23d860b46..e2b8c7d297 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/SAM.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/SAM.cs @@ -171,7 +171,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(NumStickers == 2 ? 8 : 5), AID.Fuga, 3, pos.Item1, 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(NumStickers == 2 ? 8 : 5), AID.Fuga, 3, pos, 20); EmergencyMeikyo(strategy, primaryTarget); UseKaeshi(primaryTarget); diff --git a/BossMod/Autorotation/Standard/xan/Melee/VPR.cs b/BossMod/Autorotation/Standard/xan/Melee/VPR.cs index eef5107de1..7dc6f8cdad 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/VPR.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/VPR.cs @@ -127,7 +127,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) _ => Anguine > 0 ? 50 : 3 }; - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SteelMaw, aoeBreakpoint, pos.Item1, 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SteelMaw, aoeBreakpoint, pos, 20); if (CombatTimer < 0.5f && Player.DistanceToHitbox(primaryTarget) > 3) PushGCD(AID.Slither, primaryTarget); From 3d507b8fc2a9dad20b4b60803f43cff0754ee2e6 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:03:10 -0500 Subject: [PATCH 30/56] fix dup name issue --- BossMod/Autorotation/UIPresetEditor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BossMod/Autorotation/UIPresetEditor.cs b/BossMod/Autorotation/UIPresetEditor.cs index abe193466b..ff5e021284 100644 --- a/BossMod/Autorotation/UIPresetEditor.cs +++ b/BossMod/Autorotation/UIPresetEditor.cs @@ -344,6 +344,9 @@ private bool DrawModifier(ref Preset.Modifier mod, Preset.Modifier flag, string private bool CheckNameConflict() { + if (_db.DefaultPresets.Any(p => p.Name == Preset.Name)) + return true; + for (int i = 0; i < _db.UserPresets.Count; ++i) if (i != _sourcePresetIndex && _db.UserPresets[i].Name == Preset.Name) return true; From ebae1f5c4697c556b82090b59804679ecbf254f0 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 01:03:27 -0500 Subject: [PATCH 31/56] tanking goalzone --- BossMod/BossModule/AIHints.cs | 17 +++++++++ .../Modules/Global/DeepDungeon/AutoClear.cs | 1 - .../PalaceOfTheDead/D90TheGodmother.cs | 36 +++++++++++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/BossMod/BossModule/AIHints.cs b/BossMod/BossModule/AIHints.cs index 7de69b692c..1b9e723a0a 100644 --- a/BossMod/BossModule/AIHints.cs +++ b/BossMod/BossModule/AIHints.cs @@ -316,4 +316,21 @@ public Func GoalProximity(WPos destination, float maxDistance, floa return maxWeight * weight; }; } + + public Func PullTargetToLocation(Actor target, WPos destination, float destRadius = 2) + { + var enemy = FindEnemy(target); + if (enemy == null) + return _ => 0; + + var adjRange = enemy.TankDistance + target.HitboxRadius + 0.5f; + var desiredToTarget = target.Position - destination; + var leewaySq = destRadius * destRadius; + if (desiredToTarget.LengthSq() > leewaySq) + { + var dest = destination - adjRange * desiredToTarget.Normalized(); + return GoalSingleTarget(dest, PathfindMapBounds.MapResolution, 10); + } + return _ => 0; + } } diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index 7b483879bb..446b67aef9 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -458,7 +458,6 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint } } - if (_config.TrapHints && _trapsHidden) { var traps = _trapsCurrentZone.Where(t => t.InCircle(player.Position, 30) && !IgnoreTraps.Any(b => b.AlmostEqual(t, 1))).Select(t => ShapeDistance.Circle(t, 2)).ToList(); diff --git a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D90TheGodmother.cs b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D90TheGodmother.cs index 1f536bdc21..c114feb688 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D90TheGodmother.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/PalaceOfTheDead/D90TheGodmother.cs @@ -19,7 +19,7 @@ public enum AID : uint SelfDestruct = 7106, // LavaBomb->self, 3.0s cast, range 6+R circle } -class BossAdds(BossModule module) : Components.AddsMulti(module, [(uint)OID.GreyBomb, (uint)OID.GiddyBomb]); +class GreyBomb(BossModule module) : Components.Adds(module, (uint)OID.GreyBomb, 5); class Burst(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.Burst), "Kill the Grey Bomb! or take 80% of your Max HP"); // future thing to do: maybe add a tether between bomb/boss to show it needs to show the aoe needs to explode on them. . . class HypothermalCombustion(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.HypothermalCombustion), new AOEShapeCircle(7.2f)) @@ -32,6 +32,37 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme hints.SetPriority(g, AIHints.Enemy.PriorityForbidden); } } +class GiddyBomb(BossModule module) : BossComponent(module) +{ + public static readonly WPos[] BombSpawns = [ + new(-305, -240), + new(-295, -240), + new(-295, -240), + new(-300, -235) + ]; + + private int _index; + + public override void OnEventCast(Actor caster, ActorCastEvent spell) + { + if ((AID)spell.Action.ID == AID.HypothermalCombustion) + _index++; + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + // not tanking + if (Module.PrimaryActor.TargetID != actor.InstanceID) + return; + + // giddy bomb is alive, don't pull anywhere + if (Module.Enemies(OID.GiddyBomb).Any(x => !x.IsDeadOrDestroyed)) + return; + + var nextBombSpot = BombSpawns[_index % BombSpawns.Length]; + hints.GoalZones.Add(hints.PullTargetToLocation(Module.PrimaryActor, nextBombSpot)); + } +} class MassiveBurst(BossModule module) : Components.RaidwideCast(module, ActionID.MakeSpell(AID.MassiveBurst), "Knock the Giddy bomb into the boss and let it explode on the boss. \n or else take 99% damage!"); class Sap(BossModule module) : Components.LocationTargetedAOEs(module, ActionID.MakeSpell(AID.Sap), 8); class ScaldingScolding(BossModule module) : Components.Cleave(module, ActionID.MakeSpell(AID.ScaldingScolding), new AOEShapeCone(11.75f, 45.Degrees())) @@ -83,7 +114,8 @@ class D90TheGodmotherStates : StateMachineBuilder public D90TheGodmotherStates(BossModule module) : base(module) { TrivialPhase() - .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() .ActivateOnEnter() From bb3bda72bd09de100e775e585964b99f736b542a Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:35:31 -0500 Subject: [PATCH 32/56] add foray info and max MP to actorstate --- BossMod/Data/Actor.cs | 5 ++++- BossMod/Data/ActorState.cs | 15 ++++++++++++++- BossMod/Framework/WorldStateGameSync.cs | 5 +++++ BossMod/Replay/ReplayParserLog.cs | 8 +++++--- BossMod/Replay/ReplayRecorder.cs | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/BossMod/Data/Actor.cs b/BossMod/Data/Actor.cs index aa4e7f06fa..c82d118c7a 100644 --- a/BossMod/Data/Actor.cs +++ b/BossMod/Data/Actor.cs @@ -61,7 +61,7 @@ public sealed record class ActorCastEvent(ActionID Action, ulong MainTargetID, f public bool IsSpell(AID aid) where AID : Enum => Action == ActionID.MakeSpell(aid); } -public record struct ActorHPMP(uint CurHP, uint MaxHP, uint Shield, uint CurMP); +public record struct ActorHPMP(uint CurHP, uint MaxHP, uint Shield, uint CurMP, uint MaxMP); // note on tethers - it is N:1 type of relation, actor can be tethered to 0 or 1 actors, but can itself have multiple actors tethering themselves to itself // target is an instance id @@ -71,6 +71,8 @@ public record struct ActorStatus(uint ID, ushort Extra, DateTime ExpireAt, ulong public record struct ActorModelState(byte ModelState, byte AnimState1, byte AnimState2); +public record struct ActorForayInfo(byte Level, byte Element); + public readonly record struct ActorIncomingEffect(uint GlobalSequence, int TargetIndex, ulong SourceInstanceId, ActionID Action, ActionEffects Effects); public record struct PendingEffect(uint GlobalSequence, int TargetIndex, ulong SourceInstanceId, DateTime Expiration); public record struct PendingEffectDelta(PendingEffect Effect, int Value); @@ -99,6 +101,7 @@ public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string nam public bool InCombat; public bool AggroPlayer; // determines whether a given actor shows in the player's UI enemy list public ActorModelState ModelState; + public ActorForayInfo ForayInfo; public byte EventState; // not sure about the field meaning... public ulong OwnerID = ownerID; // uuid of owner, for pets and similar public ulong TargetID; diff --git a/BossMod/Data/ActorState.cs b/BossMod/Data/ActorState.cs index 2688c7c891..8b97dbe75b 100644 --- a/BossMod/Data/ActorState.cs +++ b/BossMod/Data/ActorState.cs @@ -36,6 +36,8 @@ public IEnumerable CompareToInitial() yield return new OpTarget(act.InstanceID, act.TargetID); if (act.MountId != 0) yield return new OpMount(act.InstanceID, act.MountId); + if (act.ForayInfo != default) + yield return new OpForayInfo(act.InstanceID, act.ForayInfo); if (act.Tether.ID != 0) yield return new OpTether(act.InstanceID, act.Tether); if (act.CastInfo != null) @@ -241,7 +243,7 @@ protected override void ExecActor(WorldState ws, Actor actor) actor.HPMP = HPMP; ws.Actors.HPMPChanged.Fire(actor); } - public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("HP "u8).EmitActor(InstanceID).Emit(HPMP.CurHP).Emit(HPMP.MaxHP).Emit(HPMP.Shield).Emit(HPMP.CurMP); + public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("HP "u8).EmitActor(InstanceID).Emit(HPMP.CurHP).Emit(HPMP.MaxHP).Emit(HPMP.Shield).Emit(HPMP.CurMP).Emit(HPMP.MaxMP); } public Event IsTargetableChanged = new(); @@ -343,6 +345,17 @@ protected override void ExecActor(WorldState ws, Actor actor) public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("MNTD"u8).EmitActor(InstanceID).Emit(Value); } + public Event ForayInfoChanged = new(); + public sealed record class OpForayInfo(ulong InstanceID, ActorForayInfo Value) : Operation(InstanceID) + { + protected override void ExecActor(WorldState ws, Actor actor) + { + actor.ForayInfo = Value; + ws.Actors.ForayInfoChanged.Fire(actor); + } + public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("FORA"u8).EmitActor(InstanceID).Emit(Value.Level).Emit(Value.Element); + } + // note: this is currently based on network events rather than per-frame state inspection public Event Tethered = new(); public Event Untethered = new(); // note that actor structure still contains previous tether info when this is invoked; invoked if actor disappears without untethering diff --git a/BossMod/Framework/WorldStateGameSync.cs b/BossMod/Framework/WorldStateGameSync.cs index b012615d74..5d4e49fe73 100644 --- a/BossMod/Framework/WorldStateGameSync.cs +++ b/BossMod/Framework/WorldStateGameSync.cs @@ -259,6 +259,7 @@ private unsafe void UpdateActor(GameObject* obj, int index, Actor? act) hpmp.MaxHP = chr->MaxHealth; hpmp.Shield = (uint)(chr->ShieldValue * 0.01f * hpmp.MaxHP); hpmp.CurMP = chr->Mana; + hpmp.MaxMP = chr->MaxMana; inCombat = chr->InCombat; } var targetable = obj->GetIsTargetable(); @@ -270,6 +271,8 @@ private unsafe void UpdateActor(GameObject* obj, int index, Actor? act) var eventState = obj->EventState; var radius = obj->GetRadius(); var mountId = chr != null ? chr->Mount.MountId : 0u; + var forayInfoPtr = chr != null ? chr->GetForayInfo() : null; + var forayInfo = forayInfoPtr == null ? default : new ActorForayInfo(forayInfoPtr->Level, forayInfoPtr->Element); if (act == null) { @@ -314,6 +317,8 @@ private unsafe void UpdateActor(GameObject* obj, int index, Actor? act) _ws.Execute(new ActorState.OpTarget(act.InstanceID, target)); if (act.MountId != mountId) _ws.Execute(new ActorState.OpMount(act.InstanceID, mountId)); + if (act.ForayInfo != forayInfo) + _ws.Execute(new ActorState.OpForayInfo(act.InstanceID, forayInfo)); DispatchActorEvents(act.InstanceID); diff --git a/BossMod/Replay/ReplayParserLog.cs b/BossMod/Replay/ReplayParserLog.cs index d58de0f42c..7a21129eaa 100644 --- a/BossMod/Replay/ReplayParserLog.cs +++ b/BossMod/Replay/ReplayParserLog.cs @@ -313,6 +313,7 @@ private ReplayParserLog(Input input, ReplayBuilder builder) [new("EVTS"u8)] = ParseActorEventState, [new("TARG"u8)] = ParseActorTarget, [new("MNTD"u8)] = ParseActorMount, + [new("FORA"u8)] = ParseActorForay, [new("TETH"u8)] = () => ParseActorTether(true), [new("TET+"u8)] = () => ParseActorTether(true), // legacy (up to v4) [new("TET-"u8)] = () => ParseActorTether(false), // legacy (up to v4) @@ -508,7 +509,7 @@ private ActorState.OpCreate ParseActorCreate() _version < 12 ? 0 : _input.ReadInt(), new(_input.ReadVec3(), _input.ReadAngle().Rad), _input.ReadFloat(), - new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false)), + new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _version < 24 ? 10000 : _input.ReadUInt(false)), _input.ReadBool(), _input.ReadBool(), _input.ReadActorID(), @@ -564,6 +565,7 @@ private ActorState.OpModelState ParseActorModelState() private ActorState.OpEventState ParseActorEventState() => new(_input.ReadActorID(), _input.ReadByte(false)); private ActorState.OpTarget ParseActorTarget() => new(_input.ReadActorID(), _input.ReadActorID()); private ActorState.OpMount ParseActorMount() => new(_input.ReadActorID(), _input.ReadUInt(false)); + private ActorState.OpForayInfo ParseActorForay() => new(_input.ReadActorID(), new(_input.ReadByte(false), _input.ReadByte(false))); private ActorState.OpTether ParseActorTether(bool tether) => new(_input.ReadActorID(), tether ? new(_input.ReadUInt(false), _input.ReadActorID()) : default); private ActorState.OpCastInfo ParseActorCastInfo(bool start) @@ -762,11 +764,11 @@ private ActorHPMP ReadActorHPMP() if (_version < 10) { var parts = _input.ReadString().Split('/'); - return new(uint.Parse(parts[0]), uint.Parse(parts[1]), parts.Length > 2 ? uint.Parse(parts[2]) : 0, parts.Length > 3 ? uint.Parse(parts[3]) : 0); + return new(uint.Parse(parts[0]), uint.Parse(parts[1]), parts.Length > 2 ? uint.Parse(parts[2]) : 0, parts.Length > 3 ? uint.Parse(parts[3]) : 0, 10000); } else { - return new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false)); + return new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _version < 24 ? 10000 : _input.ReadUInt(false)); } } } diff --git a/BossMod/Replay/ReplayRecorder.cs b/BossMod/Replay/ReplayRecorder.cs index cef4662b2d..43fea1e165 100644 --- a/BossMod/Replay/ReplayRecorder.cs +++ b/BossMod/Replay/ReplayRecorder.cs @@ -176,7 +176,7 @@ public override void EndEntry() { } private readonly Output _logger; private readonly EventSubscription _subscription; - public const int Version = 23; + public const int Version = 24; public ReplayRecorder(WorldState ws, ReplayLogFormat format, bool logInitialState, DirectoryInfo targetDirectory, string logPrefix) { From aeb82f1c3a9104918bb9b9647df2c1966c6825b4 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:34:23 -0500 Subject: [PATCH 33/56] whoops hpmp --- BossMod/Data/ActorState.cs | 1 + BossMod/Replay/ReplayParserLog.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BossMod/Data/ActorState.cs b/BossMod/Data/ActorState.cs index 8b97dbe75b..1a4c7fe8c3 100644 --- a/BossMod/Data/ActorState.cs +++ b/BossMod/Data/ActorState.cs @@ -148,6 +148,7 @@ public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("A .Emit(HPMP.MaxHP) .Emit(HPMP.Shield) .Emit(HPMP.CurMP) + .Emit(HPMP.MaxMP) .Emit(IsTargetable) .Emit(IsAlly) .EmitActor(OwnerID) diff --git a/BossMod/Replay/ReplayParserLog.cs b/BossMod/Replay/ReplayParserLog.cs index 7a21129eaa..fc29af7633 100644 --- a/BossMod/Replay/ReplayParserLog.cs +++ b/BossMod/Replay/ReplayParserLog.cs @@ -509,7 +509,7 @@ private ActorState.OpCreate ParseActorCreate() _version < 12 ? 0 : _input.ReadInt(), new(_input.ReadVec3(), _input.ReadAngle().Rad), _input.ReadFloat(), - new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _version < 24 ? 10000 : _input.ReadUInt(false)), + new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _version >= 24 ? _input.ReadUInt(false) : 10000), _input.ReadBool(), _input.ReadBool(), _input.ReadActorID(), @@ -768,7 +768,7 @@ private ActorHPMP ReadActorHPMP() } else { - return new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _version < 24 ? 10000 : _input.ReadUInt(false)); + return new(_input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _input.ReadUInt(false), _version >= 24 ? _input.ReadUInt(false) : 10000); } } } From fc6f423bc76ea3ef2e4beb7fe606e23a7bd24c65 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:14:39 -0500 Subject: [PATCH 34/56] improve positionals --- BossMod/Autorotation/Standard/xan/Basexan.cs | 9 +++++---- BossMod/Autorotation/Standard/xan/Melee/DRG.cs | 4 ++-- BossMod/Autorotation/Standard/xan/Melee/MNK.cs | 4 ++-- BossMod/Autorotation/Standard/xan/Melee/NIN.cs | 4 ++-- BossMod/Autorotation/Standard/xan/Melee/RPR.cs | 4 ++-- BossMod/Autorotation/Standard/xan/Melee/SAM.cs | 4 ++-- BossMod/Autorotation/Standard/xan/Melee/VPR.cs | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/Basexan.cs b/BossMod/Autorotation/Standard/xan/Basexan.cs index ec52c75e9b..6927910a35 100644 --- a/BossMod/Autorotation/Standard/xan/Basexan.cs +++ b/BossMod/Autorotation/Standard/xan/Basexan.cs @@ -288,9 +288,9 @@ protected void GoalZoneSingle(float range) Hints.GoalZones.Add(Hints.GoalSingleTarget(PlayerTarget.Actor, range)); } - protected void GoalZoneCombined(StrategyValues strategy, float range, Func fAoe, AID firstUnlockedAoeAction, int minAoe, (Positional pos, bool imminent)? positional = null, float? maximumActionRange = null) + protected void GoalZoneCombined(StrategyValues strategy, float range, Func fAoe, AID firstUnlockedAoeAction, int minAoe, float? maximumActionRange = null) { - var posActual = positional?.imminent == true ? positional.Value.pos : Positional.Any; + var (_, positional, imminent, _) = Hints.RecommendedPositional; if (!strategy.AOEOk() || !Unlocked(firstUnlockedAoeAction)) minAoe = 50; @@ -302,7 +302,7 @@ protected void GoalZoneCombined(StrategyValues strategy, float range, Func GCD; var target = enemy?.Actor; if ((target?.Omnidirectional ?? true) || target?.TargetID == Player.InstanceID && target?.CastInfo == null && positional.pos != Positional.Front && target?.NameID != 541) positional = (Positional.Any, false); diff --git a/BossMod/Autorotation/Standard/xan/Melee/DRG.cs b/BossMod/Autorotation/Standard/xan/Melee/DRG.cs index 13f2798062..09214ddf4a 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/DRG.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/DRG.cs @@ -81,7 +81,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) (BestDiveTarget, NumDiveTargets) = SelectTarget(strategy, primaryTarget, 20, IsSplashTarget); var pos = GetPositional(strategy, primaryTarget); - UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); + UpdatePositionals(primaryTarget, ref pos); if (primaryTarget == null) return; @@ -94,7 +94,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOERect(primaryTarget.Actor, 10, 2), AID.DoomSpike, minAoe: 3, positional: pos, maximumActionRange: 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOERect(primaryTarget.Actor, 10, 2), AID.DoomSpike, minAoe: 3, maximumActionRange: 20); if (NumAOETargets > 2) { diff --git a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs index 98a3af5191..574c9db081 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs @@ -407,9 +407,9 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) var pos = NextPositional; - UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); + UpdatePositionals(primaryTarget, ref pos); - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.ArmOfTheDestroyer, AOEBreakpoint, positional: pos, maximumActionRange: 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.ArmOfTheDestroyer, AOEBreakpoint, maximumActionRange: 20); if (Player.InCombat) OGCD(strategy, primaryTarget); diff --git a/BossMod/Autorotation/Standard/xan/Melee/NIN.cs b/BossMod/Autorotation/Standard/xan/Melee/NIN.cs index bf744ab74f..26c72c729b 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/NIN.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/NIN.cs @@ -121,7 +121,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) NumAOETargets = NumMeleeAOETargets(strategy); var pos = GetNextPositional(primaryTarget?.Actor); - UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); + UpdatePositionals(primaryTarget, ref pos); OGCD(strategy, primaryTarget); @@ -133,7 +133,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.DeathBlossom, minAoe: 3, positional: pos, maximumActionRange: 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.DeathBlossom, minAoe: 3, maximumActionRange: 20); if (TenChiJin.Left > GCD) { diff --git a/BossMod/Autorotation/Standard/xan/Melee/RPR.cs b/BossMod/Autorotation/Standard/xan/Melee/RPR.cs index d5ab9d1931..08c5283fa3 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/RPR.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/RPR.cs @@ -124,7 +124,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) (BestRangedAOETarget, NumRangedAOETargets) = SelectTarget(strategy, primaryTarget, 25, IsSplashTarget); var pos = GetNextPositional(primaryTarget?.Actor); - UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); + UpdatePositionals(primaryTarget, ref pos); OGCD(strategy, primaryTarget); @@ -136,7 +136,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SpinningScythe, 3, pos, maximumActionRange: 25); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SpinningScythe, 3, maximumActionRange: 25); if (SoulReaver > GCD || Executioner > GCD) { diff --git a/BossMod/Autorotation/Standard/xan/Melee/SAM.cs b/BossMod/Autorotation/Standard/xan/Melee/SAM.cs index e2b8c7d297..abd43812c1 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/SAM.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/SAM.cs @@ -153,7 +153,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) } var pos = GetNextPositional(strategy); - UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); + UpdatePositionals(primaryTarget, ref pos); OGCD(strategy, primaryTarget); @@ -171,7 +171,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) return; } - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(NumStickers == 2 ? 8 : 5), AID.Fuga, 3, pos, 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(NumStickers == 2 ? 8 : 5), AID.Fuga, 3, 20); EmergencyMeikyo(strategy, primaryTarget); UseKaeshi(primaryTarget); diff --git a/BossMod/Autorotation/Standard/xan/Melee/VPR.cs b/BossMod/Autorotation/Standard/xan/Melee/VPR.cs index 7dc6f8cdad..b871333283 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/VPR.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/VPR.cs @@ -113,7 +113,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) NumAOETargets = NumMeleeAOETargets(strategy); var pos = GetPositional(strategy); - UpdatePositionals(primaryTarget, ref pos, TrueNorthLeft > GCD); + UpdatePositionals(primaryTarget, ref pos); OGCD(strategy, primaryTarget); @@ -127,7 +127,7 @@ public override void Exec(StrategyValues strategy, Enemy? primaryTarget) _ => Anguine > 0 ? 50 : 3 }; - GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SteelMaw, aoeBreakpoint, pos, 20); + GoalZoneCombined(strategy, 3, Hints.GoalAOECircle(5), AID.SteelMaw, aoeBreakpoint, 20); if (CombatTimer < 0.5f && Player.DistanceToHitbox(primaryTarget) > 3) PushGCD(AID.Slither, primaryTarget); From 2213f03a8c68fa13fa0767695a02b3190d6a5653 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 13 Feb 2025 11:36:35 -0500 Subject: [PATCH 35/56] max pull logic for dd --- .../Modules/Global/DeepDungeon/AutoClear.cs | 20 ++++++++++--------- BossMod/Modules/Global/DeepDungeon/Config.cs | 5 +++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index 446b67aef9..336c936c5b 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -304,11 +304,12 @@ public override void DrawExtra() ImGui.Text($"Kills: {Kills}"); - var navInCombat = _config.NavigateInCombat; + var maxPull = _config.MaxPull; - if (ImGui.Checkbox("Allow navigation in combat", ref navInCombat)) + ImGui.SetNextItemWidth(200); + if (ImGui.DragInt("Max mobs to pull", ref maxPull, 0.05f, 0, 15)) { - _config.NavigateInCombat = navInCombat; + _config.MaxPull = maxPull; _config.Modified.Fire(); } @@ -392,10 +393,14 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint if (!_config.Enable || Palace.IsBossFloor || BetweenFloors) return; + var canNavigate = _config.MaxPull == 0 ? !player.InCombat : hints.PotentialTargets.Count(t => t.Actor.AggroPlayer && !t.Actor.IsDeadOrDestroyed) < _config.MaxPull; + foreach (var (w, rot) in Walls) hints.AddForbiddenZone(new AOEShapeRect(w.Depth, 20, w.Depth), w.Position, (rot ? 90f : 0f).Degrees()); - HandleFloorPathfind(player, hints); + if (canNavigate) + HandleFloorPathfind(player, hints); + DrawAOEs(playerSlot, player, hints); CalculateExtraHints(playerSlot, player, hints); @@ -487,7 +492,7 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint hints.ActionsToExecute.Push(new ActionID(ActionType.Pomander, (uint)p2), null, ActionQueue.Priority.VeryHigh); Actor? wantCoffer = null; - if (coffer is Actor t && !IsPlayerTransformed(player) && (_config.AutoMoveTreasure && (!player.InCombat || _config.NavigateInCombat) || player.DistanceToHitbox(t) < 3.5f)) + if (coffer is Actor t && !IsPlayerTransformed(player) && (_config.AutoMoveTreasure && canNavigate || player.DistanceToHitbox(t) < 3.5f)) wantCoffer = t; if (!player.InCombat && _config.AutoPassage && Palace.PassageActive) @@ -515,7 +520,7 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint if (revealedTraps.Count > 0) hints.AddForbiddenZone(ShapeDistance.Union(revealedTraps)); - if (!IsPlayerTransformed(player) && (!player.InCombat || _config.NavigateInCombat) && _config.AutoMoveTreasure && hoardLight is Actor h && Palace.GetPomanderState(PomanderID.Intuition).Active) + if (!IsPlayerTransformed(player) && canNavigate && _config.AutoMoveTreasure && hoardLight is Actor h && Palace.GetPomanderState(PomanderID.Intuition).Active) hints.GoalZones.Add(hints.GoalSingleTarget(h.Position, 2, 10)); var shouldTargetMobs = _config.AutoClear switch @@ -647,9 +652,6 @@ private void DrawAOEs(int playerSlot, Actor player, AIHints hints) private void HandleFloorPathfind(Actor player, AIHints hints) { - if (player.InCombat && !_config.NavigateInCombat) - return; - var playerRoom = Palace.Party[0].Room; if (DesiredRoom == playerRoom || DesiredRoom == 0) diff --git a/BossMod/Modules/Global/DeepDungeon/Config.cs b/BossMod/Modules/Global/DeepDungeon/Config.cs index e126593501..3ff6ea1870 100644 --- a/BossMod/Modules/Global/DeepDungeon/Config.cs +++ b/BossMod/Modules/Global/DeepDungeon/Config.cs @@ -27,8 +27,9 @@ public enum ClearBehavior [PropertyDisplay("Automatic mob targeting behavior")] public ClearBehavior AutoClear = ClearBehavior.Leveling; - [PropertyDisplay("Allow navigation in combat")] - public bool NavigateInCombat = false; + [PropertyDisplay("Max number of mobs to pull before pausing navigation (set to 0 to disable navigation while in combat)")] + [PropertySlider(0, 15)] + public int MaxPull = 0; [PropertyDisplay("Try to use terrain to LOS attacks")] public bool AutoLOS = false; From 50a958bc7110482bae4f3c0f1f4702d9d7f77ba0 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:19:57 -0500 Subject: [PATCH 36/56] include obstacles in boss modules --- BossMod/BossModule/BossModule.cs | 14 ++++++++++++++ BossMod/Modules/Stormblood/Foray/Hydatos.cs | 2 ++ 2 files changed, 16 insertions(+) diff --git a/BossMod/BossModule/BossModule.cs b/BossMod/BossModule/BossModule.cs index b297284ef1..d35d8d636d 100644 --- a/BossMod/BossModule/BossModule.cs +++ b/BossMod/BossModule/BossModule.cs @@ -14,6 +14,7 @@ public abstract class BossModule : IDisposable public readonly MiniArena Arena; public readonly BossModuleRegistry.Info? Info; public readonly StateMachine StateMachine; + public readonly Pathfinding.ObstacleMapManager Obstacles; private readonly EventSubscriptions _subscriptions; @@ -79,6 +80,7 @@ public void DeactivateComponent() where T : BossComponent protected BossModule(WorldState ws, Actor primary, WPos center, ArenaBounds bounds) { + Obstacles = new(ws); WorldState = ws; PrimaryActor = primary; Arena = new(WindowConfig, center, bounds); @@ -122,6 +124,7 @@ protected virtual void Dispose(bool disposing) ClearComponents(_ => true); _subscriptions.Dispose(); + Obstacles.Dispose(); } public void Update() @@ -222,6 +225,17 @@ public void CalculateAIHints(int slot, Actor actor, PartyRolesConfig.Assignment { hints.PathfindMapCenter = Center; hints.PathfindMapBounds = Bounds; + + var (entry, bitmap) = Obstacles.Find(new Vector3(Center.X, actor.PosRot.Y, Center.Z)); + if (entry != null && bitmap != null) + { + var originCell = (Center - entry.Origin) / bitmap.PixelSize; + var originX = (int)originCell.X; + var originZ = (int)originCell.Z; + var halfSize = (int)(Bounds.Radius / bitmap.PixelSize); + hints.PathfindMapObstacles = new(bitmap, new(originX - halfSize, originZ - halfSize, originX + halfSize, originZ + halfSize)); + } + foreach (var comp in _components) comp.AddAIHints(slot, actor, assignment, hints); CalculateModuleAIHints(slot, actor, assignment, hints); diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index 53c9f62685..d7c34aa269 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -86,6 +86,8 @@ private bool ShouldIgnore(Actor caster, Actor player) public override bool WantDrawExtra() => true; + public override string WindowName() => "Hydatos###Eureka module"; + public override void DrawExtra() { if (UICombo.Enum("Prep mob", ref _hydatosConfig.CurrentFarmTarget)) From acbb91ed490ce9fceef8e4525f7a5da9b9b4d8fa Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:38:50 -0500 Subject: [PATCH 37/56] improve module viewer categorization for NM --- BossMod/BossModule/BossModuleInfo.cs | 1 + BossMod/Config/ModuleViewer.cs | 5 ++++- BossMod/Modules/Stormblood/Foray/NM/Ceto.cs | 2 +- BossMod/Modules/Stormblood/Foray/NM/Daphne.cs | 2 +- BossMod/Modules/Stormblood/Foray/NM/Ovni.cs | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/BossMod/BossModule/BossModuleInfo.cs b/BossMod/BossModule/BossModuleInfo.cs index 515baae890..e04fe852a3 100644 --- a/BossMod/BossModule/BossModuleInfo.cs +++ b/BossMod/BossModule/BossModuleInfo.cs @@ -64,6 +64,7 @@ public enum GroupType Hunt, // group id is HuntRank BozjaCE, // group id is ContentFinderCondition row, name id is DynamicEvent row BozjaDuel, // group id is ContentFinderCondition row, name id is DynamicEvent row + EurekaNM, // group id is ContentFinderCondition row, name id is Fate row GoldSaucer, // group id is GoldSaucerTextData row } diff --git a/BossMod/Config/ModuleViewer.cs b/BossMod/Config/ModuleViewer.cs index d42cbbeb24..542724ff4c 100644 --- a/BossMod/Config/ModuleViewer.cs +++ b/BossMod/Config/ModuleViewer.cs @@ -3,7 +3,6 @@ using Dalamud.Interface.Utility.Raii; using ImGuiNET; using Lumina.Excel.Sheets; -using Lumina.Text; using Lumina.Text.ReadOnly; using System.Data; using System.Globalization; @@ -287,6 +286,10 @@ private void DrawModules(UITree tree, WorldState ws) groupId |= module.GroupID; var duelName = $"{FixCase(Service.LuminaRow(module.GroupID)!.Value.Name)} Duel"; return (new(duelName, groupId, groupId), new(module, Service.LuminaRow(module.NameID)!.Value.Name.ToString(), module.SortOrder)); + case BossModuleInfo.GroupType.EurekaNM: + groupId |= module.GroupID; + var nmName = FixCase(Service.LuminaRow(module.GroupID)!.Value.Name); + return (new(nmName, groupId, groupId), new(module, Service.LuminaRow(module.NameID)!.Value.Name.ToString(), module.SortOrder)); case BossModuleInfo.GroupType.GoldSaucer: return (new("Gold saucer", groupId, groupId), new(module, $"{Service.LuminaRow(module.GroupID)?.Text}: {BNpcName(module.NameID)}", module.SortOrder)); default: diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs index b9be6eea7d..5127c17594 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs @@ -48,6 +48,6 @@ public CetoStates(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 7955, Contributors = "xan")] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1421, Contributors = "xan")] public class Ceto(WorldState ws, Actor primary) : BossModule(ws, primary, new(747.8959f, -878.8765f), new ArenaBoundsCircle(80, MapResolution: 1)); diff --git a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs index eac8a65344..a28f873843 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs @@ -37,6 +37,6 @@ public DaphneStates(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 7967, Contributors = "xan")] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1417, Contributors = "xan")] public class Daphne(WorldState ws, Actor primary) : BossModule(ws, primary, new(207.8475f, -736.8179f), new ArenaBoundsCircle(80, MapResolution: 1)); diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs index 0b353e10de..965e4a105f 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs @@ -73,6 +73,6 @@ public OvniStates(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8060, Contributors = "xan")] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1424, Contributors = "xan")] public class Ovni(WorldState ws, Actor primary) : BossModule(ws, primary, new(266.1068f, -97.09414f), new ArenaBoundsCircle(80, MapResolution: 1)); From 5a6795ae4babcb33fa4a9c7f7bed602509d7f622 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:41:14 -0500 Subject: [PATCH 38/56] should check if this works at 90 --- BossMod/Autorotation/Standard/xan/Casters/RDM.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BossMod/Autorotation/Standard/xan/Casters/RDM.cs b/BossMod/Autorotation/Standard/xan/Casters/RDM.cs index 6e6afc4dcb..6c3983493a 100644 --- a/BossMod/Autorotation/Standard/xan/Casters/RDM.cs +++ b/BossMod/Autorotation/Standard/xan/Casters/RDM.cs @@ -220,7 +220,8 @@ private void OGCD(StrategyValues strategy, Enemy? primaryTarget) if (strategy.BuffsOk()) { PushOGCD(AID.Embolden, Player); - PushOGCD(AID.Manafication, Player); + if (!InCombo) + PushOGCD(AID.Manafication, Player); } PushOGCD(AID.ContreSixte, BestAOETarget); From 34b2c1482a16a70664f3ec46f521c03e46bc22af Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:41:30 -0500 Subject: [PATCH 39/56] hydatos: add golde --- BossMod/Modules/Stormblood/Foray/Hydatos.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index d7c34aa269..c908925d61 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -33,6 +33,8 @@ public enum Farm : uint Frostmane = 0x26B8, [PropertyDisplay("Daphne (Dark Void Monk)")] Daphne = 0x26B9, + [PropertyDisplay("Goldemar (Hydatos Wraith)")] + Golde = 0x26E6, [PropertyDisplay("Leuke (Tigerhawk)")] Leuke = 0x26C0, [PropertyDisplay("Barong (Laboratory Lion)")] @@ -66,6 +68,10 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint foreach (var e in hints.PotentialTargets) if (e.Actor.OID == farmOID && e.Priority == AIHints.Enemy.PriorityUndesirable && (e.Actor.Position - player.Position).LengthSq() <= farmRange * farmRange) { + // level 60 hydatos wraith does not spawn golde + if (farmOID == 0x26E6 && e.Actor.ForayInfo.Level < 61) + continue; + e.Priority = 1; if (shouldSetTarget && (hints.ForcedTarget == null || (hints.ForcedTarget.Position - player.Position).LengthSq() > (e.Actor.Position - player.Position).LengthSq())) From 7f7ff75f16655e187e28d4817565ede0b8138ffe Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:20:06 -0500 Subject: [PATCH 40/56] improve autofarm logic for party members --- BossMod/Autorotation/MiscAI/AutoFarm.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/BossMod/Autorotation/MiscAI/AutoFarm.cs b/BossMod/Autorotation/MiscAI/AutoFarm.cs index 427722f8c9..6c1aa46152 100644 --- a/BossMod/Autorotation/MiscAI/AutoFarm.cs +++ b/BossMod/Autorotation/MiscAI/AutoFarm.cs @@ -69,15 +69,17 @@ void prioritize(AIHints.Enemy e, int prio) // first deal with pulling new enemies if (allowPulling) { - if (Utils.IsPlayerSyncedToFate(World) && strategy.Option(Track.Fate).As() == PriorityStrategy.Prioritize) + var allowFate = Utils.IsPlayerSyncedToFate(World) && strategy.Option(Track.Fate).As() == PriorityStrategy.Prioritize; + foreach (var e in Hints.PotentialTargets) { - foreach (var e in Hints.PotentialTargets) + if (allowFate && e.Actor.FateID == World.Client.ActiveFate.ID && e.Priority == AIHints.Enemy.PriorityUndesirable) { - if (e.Actor.FateID == World.Client.ActiveFate.ID && e.Priority == AIHints.Enemy.PriorityUndesirable) - { - prioritize(e, 1); - } + prioritize(e, 1); } + + // allow targeting mobs that already have positive prio because e.g. they are attacking a party member + if (e.Priority >= 0) + prioritize(e, e.Priority); } var specific = strategy.Option(Track.Specific); From 4233919892a9d9888e6645022420b20015b7d503 Mon Sep 17 00:00:00 2001 From: ace Date: Thu, 13 Feb 2025 15:48:44 -0800 Subject: [PATCH 41/56] shorten `AkechiTools` by removing redundant XMLs --- .../Standard/akechi/AkechiTools.cs | 767 +++++------------- 1 file changed, 197 insertions(+), 570 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 3e22e60fec..64846fdc24 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -2,286 +2,94 @@ namespace BossMod.Autorotation.akechi; -#region Shared Enums: Strategies +#region Shared Enums /// -/// The SharedTrack enum used for AOE and Hold strategies, typically for modules featuring damage rotations. -///
This enum defines tracks that can be used for all PvE classes and jobs, such as strategies containing executing standard rotations or explicitly holding abilities.
-/// Example Given: -///
- public enum Track { NoMercy = SharedTrack.Count }
-/// Explanation: -///
- Track is the enum for tracking specific abilities on user's specific rotation module.
-///
- NoMercy is the example enum of a specific ability being tracked on user's specific rotation module.
-///
- SharedTrack.Count is the shared track being used for executing our shared strategies listed above, called using .
+/// SharedTrack enum for AOE and Hold strategies, used in damage rotations for all PvE classes and jobs. ///
-/// - All strategies listed under into user's rotation module public enum SharedTrack { - /// - /// The main strategy for tracking single-target and AOE rotations. - /// - /// - All strategies listed under into user's rotation module + /// Tracks single-target and AOE rotations. AOE, - /// - /// The main strategy used for tracking when to hold any buffs, gauge, or cooldown abilties for optimal usage. - /// - /// - All strategies listed under into user's rotation module + /// Tracks holding buffs, gauge, or cooldown abilities for optimal usage. Hold, - /// - /// Represents the total count of strategies available inside this specific track. We generally never actually use this as a strategy since there isn't any logic really linked to this besides the counting. - /// + /// Represents the total count of strategies in this track. Count } /// -/// The Default Strategy enum used for tracking single-target and AOE strategies. -/// Example Given:
-/// - strategy.Option(SharedTrack.AOE).As<>(); -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(SharedTrack.AOE)" is the enum representing all options relating to this custom strategy being tracked in the user's rotation module.
-/// - ".As<>();" is the relative strategy for user's specific ability being tracked in the user's rotation module. +/// AOEStrategy enum for tracking single-target and AOE strategies. ///
-/// - All strategies listed under into user's rotation module public enum AOEStrategy { - /// - /// The default strategy used for automatically executing the rotation.
- /// This can also be called using , or also as `strategy.Automatic()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.AOE).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.AOE).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The most optimal rotation automatically executed. + /// Executes the most optimal rotation automatically. Automatic, - /// - /// The main strategy used for force-executing the single-target rotation.
- /// This can also be called using , or also as `strategy.ForceST()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.AOE).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.AOE).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The single-target rotation force-executed, regardless of any conditions. + /// Forces execution of the single-target rotation. ForceST, - /// - /// The main strategy used for force-executing the AOE rotation.
- /// This can also be called using , or also as `strategy.ForceAOE()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.AOE).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.AOE).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The AOE rotation force-executed, regardless of any conditions. + /// Forces execution of the AOE rotation. ForceAOE } /// -/// The Default Strategy enum used for tracking when to hold any buffs, gauge, or cooldown abilties for optimal usage. -/// Example Given:
-/// - strategy.Option(SharedTrack.Hold).As<>(); -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(SharedTrack.Hold)" is the enum representing all options relating to this custom strategy being tracked in the user's rotation module.
-/// - ".As<>();" is the relative strategy for user's specific ability being tracked in the user's rotation module. +/// HoldStrategy enum for tracking when to hold buffs, gauge, or cooldown abilities. ///
-/// - All strategies listed under into user's rotation module public enum HoldStrategy { - /// - /// The default strategy used for not holding any buffs, gauge, or cooldown abilties.
- /// This can also be called using , or also as `strategy.DontHold()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all buffs, gauge, or cooldown abilties. + /// Uses all buffs, gauge, and cooldown abilities without restriction. DontHold, - /// - /// The main strategy used for only holding any ability that is cooldown-related.
- /// This can also be called using , or also as `strategy.HoldCDs()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all buffs and gauge abilties, but forbidden from using any cooldowns. + /// Holds all cooldown-related abilities only. HoldCooldowns, - /// - /// The main strategy used for only holding any ability that is gauge-related.
- /// This can also be called using , or also as `strategy.HoldGauge()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all buffs and cooldowns, but forbidden from using any gauge abilties. + /// Holds all gauge-related abilities only. HoldGauge, - /// - /// The main strategy used for only holding any ability that is buff-related.
- /// This can also be called using , or also as `strategy.HoldBuffs()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all cooldowns and gauge abilties, but forbidden from using any buffs. + /// Holds all buff-related abilities only. HoldBuffs, - /// - /// The main strategy used for holding any ability that is buff, cooldown, or gauge-related.
- /// This can also be called using , or also as `strategy.HoldAll()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The forbiddance from using all buffs, cooldowns and gauge abilties. + /// Holds all buffs, cooldowns, and gauge abilities. HoldEverything } /// -/// The Default Strategy enum used for allowing or forbidding use of module-specific GCD abilities. -/// Example Given:
-/// - strategy.Option(Track.SonicBreak).As<>() -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(Track.SonicBreak)" is the user's module-specific GCD ability enum being tracked.
-/// - ".As<>()" is the relative Default strategy for user's module-specific GCD ability being tracked in the user's rotation module. +/// GCDStrategy enum for managing module-specific GCD abilities. ///
-/// - All strategies listed under . public enum GCDStrategy { - /// - /// The default strategy used for automatically executing user's module-specific GCD ability. - /// Example:
- /// - strategy.Option(Track.SonicBreak).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.SonicBreak).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Automatic execution of ability based on user's logic. + /// Executes the ability automatically based on user logic. Automatic, - /// - /// The main strategy used for force-executing user's module-specific GCD ability. - /// Example:
- /// - strategy.Option(Track.SonicBreak).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.SonicBreak).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific GCD ability. + /// Forces execution of the ability. Force, - /// - /// The main strategy used for forbidding use of user's module-specific GCD ability. - /// Example:
- /// - strategy.Option(Track.SonicBreak).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.SonicBreak).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forbiddance of execution for user's module-specific GCD ability. + /// Forbids execution of the ability. Delay } /// -/// The Default Strategy enum used for allowing or forbidding use of module-specific GCD abilities. -/// Example Given:
-/// - strategy.Option(Track.NoMercy).As<>() -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(Track.NoMercy)" is the user's module-specific OGCD ability enum being tracked.
-/// - ".As<>()" is the relative Default strategy for user's module-specific OGCD ability being tracked in the user's rotation module. +/// OGCDStrategy enum for managing module-specific OGCD abilities. ///
-/// - All strategies listed under . public enum OGCDStrategy { - /// - /// The default strategy used for automatically executing user's module-specific OGCD ability. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Automatic execution of ability based on user's logic. + /// Executes the ability automatically based on user logic. Automatic, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability. + /// Forces execution of the ability. Force, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability in the very next weave window. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability inside next weave. + /// Forces execution of the ability in the very next weave window. AnyWeave, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability in the first-half of very next weave window. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability inside next early-weave. + /// Forces execution of the ability in the very next EARLY-weave window. EarlyWeave, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability in the second-half of very next weave window. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability inside next late-weave. + /// Forces execution of the ability in the very next LATE-weave window. LateWeave, - /// - /// The main strategy used for forbidding use of user's module-specific OGCD ability. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forbiddance of execution for user's module-specific OGCD ability. + /// Forbids execution of the ability. Delay } #endregion @@ -302,10 +110,10 @@ public abstract class AkechiTools(RotationModuleManager manager, A protected int NextGCDPrio; #region Queuing + #region GCD /// - /// The primary helper we use for calling all our GCD abilities onto any actor. - ///
This also handles Ground Target abilities, such as BLM:LeyLines or NIN:Shukuchi
+ /// The primary helper we use for calling all our GCD abilities onto any actor. /// NOTE: For compatibility between Actor? and Enemy? inside one function, use primaryTarget?.Actor as Enemy? definition. ///
/// The user's specified Action ID being checked. @@ -332,8 +140,7 @@ protected void QueueGCD(AID aid, Actor? target, int priority = 2, float delay = #region OGCD /// - /// The primary helper we use for calling all our OGCD abilities onto any actor.
- /// This also handles Ground Target abilities, such as BLM:LeyLines or NIN:Shukuchi + /// The primary helper we use for calling all our OGCD abilities onto any actor. /// NOTE: For compatibility between Actor? and Enemy? inside one function, use primaryTarget?.Actor as Enemy? definition. ///
/// The user's specified Action ID being checked. @@ -391,240 +198,152 @@ protected bool QueueAction(AID aid, Actor? target, float priority, float delay, #region HP/MP/Shield /// - /// A quick and easy helper for retrieving the current HP value of the player. - /// Example:
- /// - HP >= 6900 - /// Explanation:
- /// - "HP" represents the current HP value of the player.
- /// - ">= 6900" is the example conditional expression specified by the user.
+ /// Retrieves the current HP value of the player. ///
- /// - A value representing the Player's current HP protected uint HP { get; private set; } /// - /// A quick and easy helper for retrieving the current MP value of the player. - /// Example:
- /// - MP != 4200 - /// Explanation:
- /// - "MP" represents the current MP value of the player.
- /// - "!= 4200" is the example conditional expression specified by the user.
+ /// Retrieves the current MP value of the player. ///
- /// - A value representing the Player's current MP protected uint MP { get; private set; } /// - /// A quick and easy helper for retrieving the current Shield value of the player. - /// Example:
- /// - Shield > 0 - /// Explanation:
- /// - "Shield" represents the current Shield value of the player.
- /// - "> 0" is the example conditional expression specified by the user.
+ /// Retrieves the current Shield value of the player. ///
- /// - A value representing the Player's current Shield protected uint Shield { get; private set; } /// - /// A quick and easy helper for retrieving the Current HP of any specified actor, whether it is the player or any other target user desires. - /// Example:
- /// - TargetCurrentHP(primaryTarget) > 0 - /// Explanation:
- /// - "TargetCurrentHP" represents the current HP value of the specified actor.
- /// - "(primaryTarget)" represents the specified actor being checked.
- /// - "> 0" is the example conditional expression specified by the user.
+ /// Retrieves the current HP of a specified actor. ///
- /// Any specified player, ally, or target - /// - A value representing the current HP of user's specified actor + /// The target actor. protected uint TargetCurrentHP(Actor actor) => actor.HPMP.CurHP; /// - /// A quick and easy helper for retrieving the Current Shield of any specified actor, whether it is the player or any other target user desires. - /// Example:
- /// - TargetCurrentShield(primaryTarget) > 0 - /// Explanation:
- /// - "TargetCurrentShield" represents the current Shield value of the specified actor.
- /// - "(primaryTarget)" represents the specified actor being checked.
- /// - "> 0" is the example conditional expression specified by the user.
+ /// Retrieves the current Shield value of a specified actor. ///
- /// Any specified player, ally, or target - /// - A value representing the current Shield of user's specified actor. + /// The target actor. protected uint TargetCurrentShield(Actor actor) => actor.HPMP.Shield; /// - /// A quick and easy helper for checking if specified actor has any current Shield present, whether it is the player or any other target user desires. - /// Example:
- /// - TargetHasShield(primaryTarget) - /// Explanation:
- /// - "TargetHasShield" checks if the specified actor has any current Shield value.
- /// - "(primaryTarget)" represents the specified actor being checked.
+ /// Checks if a specified actor has any active Shield. ///
- /// Any specified player, ally, or target + /// The target actor. protected bool TargetHasShield(Actor actor) => actor.HPMP.Shield > 0.1f; /// - /// A quick and easy helper for retrieving the Current HP Percentage of the Player specifically. - /// Example:
- /// - PlayerHPP() > 69 - /// Explanation:
- /// - "PlayerHPP" represents the current HP Percentage value of the specified actor.
- /// - "> 69" is the example conditional expression specified by the user.
+ /// Retrieves the player's current HP percentage. ///
- /// - A value representing the current HP Percentage (%) of the Player. protected float PlayerHPP() => (float)Player.HPMP.CurHP / Player.HPMP.MaxHP * 100; /// - /// A quick and easy helper for retrieving the Current HP Percentage of any specified actor, whether it is the player or any other target user desires. - /// Example:
- /// - TargetHPP(primaryTarget) > 50 - /// Explanation:
- /// - "TargetHPP" represents the current HP Percentage value of the specified actor.
- /// - "(primaryTarget)" represents the specified actor being checked.
- /// - "> 50" is the example conditional expression specified by the user.
+ /// Retrieves the HP percentage of a specified actor. ///
- /// Any specified player, ally, or target - /// - A value representing the current HP Percentage (%) of user's specified actor. + /// The target actor. protected static float TargetHPP(Actor? target = null) { if (target is null || target.IsDead) return 0f; - if (target is Actor actor) - { - var HPP = (float)actor.HPMP.CurHP / actor.HPMP.MaxHP * 100f; - return Math.Clamp(HPP, 0f, 100f); - } - - return 0f; + var HPP = (float)target.HPMP.CurHP / target.HPMP.MaxHP * 100f; + return Math.Clamp(HPP, 0f, 100f); } #endregion #region Actions /// - /// Checks if specified action is Unlocked based on Level and Job Quest (if required). - /// Example:
- /// - Unlocked(AID.GnashingFang) - /// Explanation:
- /// - "Unlocked" is the function.
- /// - "(AID.GnashingFang)" is the example ability being checked, called using .. + /// Checks if specified action is Unlocked based on Level and Job Quest (if required). ///
/// The user's specified Action ID being checked. - /// - TRUE if the specified action is Unlocked.
- /// - FALSE if the specified action is still locked or Job Quest is unfinished.
- protected bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); //Check if the desired ability is unlocked + protected bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); /// - /// Checks if specified trait is Unlocked based on Level and Job Quest (if required). - /// Example:
- /// - Unlocked(TraitID.EnhancedBloodfest) - /// Explanation:
- /// - "Unlocked" is the function.
- /// - "(TraitID.EnhancedBloodfest)" is the example trait being checked, called using .. + /// Checks if specified trait is Unlocked based on Level and Job Quest (if required). ///
- /// The user's specified Trait ID being checked. - /// - TRUE if the specified action is Unlocked.
- /// - FALSE if the specified action is still locked or Job Quest is unfinished.
+ /// The Trait ID being checked. protected bool Unlocked(TraitID tid) => TraitUnlocked((uint)(object)tid); /// - /// Checks if last combo action is what the user is specifying.
- /// NOTE: This does NOT check all actions, only combo actions. - /// Example:
- /// - ComboLastMove == AID.BrutalShell - /// Explanation:
- /// - "ComboLastMove" is the function.
- /// - "AID.BrutalShell" is the example specified combo action being checked, called using .. + /// Checks the last combo action is what the user is specifying. ///
- /// - TRUE if the last combo action is what the user is specifying.
- /// - FALSE if otherwise or last action was not a combo action.
protected AID ComboLastMove => (AID)(object)World.Client.ComboState.Action; /// - /// Checks the time left remaining inside current combo before expiration. - /// NOTE: This does NOT check all actions, only combo actions. + /// Checks the time left remaining inside current combo before expiration. /// protected float ComboTimer => (float)(object)World.Client.ComboState.Remaining; /// - /// Retrieves actual cast time of a specified action. - /// Example:
- /// - ActualCastTime(AID.Fire3) > 0 - /// Explanation:
- /// - "ActualCastTime" is the function.
- /// - "AID.Fire3" is the example specified action being checked, called using ..
- /// - "> 0" is the example conditional expression specified by the user.
+ /// Retrieves actual cast time of a specified action. ///
/// The user's specified Action ID being checked. - /// - A value representing the current real-time Cast Time of user's specified action protected virtual float ActualCastTime(AID aid) => ActionDefinitions.Instance.Spell(aid)!.CastTime; /// - /// Retrieves effective cast time of a specified action. - /// Example:
- /// - EffectiveCastTime(AID.Fire3) > 0 - /// Explanation:
- /// - "EffectiveCastTime" is the function.
- /// - "AID.Fire3" is the example specified action being checked, called using ..
- /// - "> 0" is the example conditional expression specified by the user.
+ /// Retrieves effective cast time of a specified action. ///
/// The user's specified Action ID being checked. - /// - A value representing the current effective Cast Time of user's specified action protected virtual float EffectiveCastTime(AID aid) => PlayerHasEffect(ClassShared.SID.Swiftcast, 10) ? 0 : ActualCastTime(aid) * SpSGCDLength / 2.5f; - /// Retrieves player's GCD length based on Skill-Speed. - /// NOTE: This function is only recommended for jobs with Skill-Speed. Spell-Speed users are unaffected by this function. - /// - A value representing the player's current GCD Length + /// + /// Retrieves player's GCD length based on Skill-Speed. + /// protected float SkSGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); - /// Retrieves player's current Skill-Speed stat. - /// - A value representing the player's current Skill-Speed + /// + /// Retrieves player's current Skill-Speed stat. + /// protected float SkS => ActionSpeed.Round(World.Client.PlayerStats.SkillSpeed); - /// Retrieves player's GCD length based on Spell-Speed. - /// NOTE: This function is only recommended for jobs with Spell-Speed. Skill-Speed users are unaffected by this function. - /// - A value representing the player's current GCD Length + /// + /// Retrieves player's GCD length based on Spell-Speed. + /// protected float SpSGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SpellSpeed, World.Client.PlayerStats.Haste, Player.Level); - /// Retrieves player's current Spell-Speed stat. - /// - A value representing the player's current Spell-Speed + /// + /// Retrieves player's current Spell-Speed stat. + /// protected float SpS => ActionSpeed.Round(World.Client.PlayerStats.SpellSpeed); - /// Checks if we can fit in a skill-speed based GCD. - /// - /// How many extra GCDs the user can fit in. - /// - TRUE if we can fit in a skill-speed based GCD - FALSE if otherwise + /// + /// Checks if we can fit in a skill-speed based GCD. + /// + /// The duration to check against. + /// How many extra GCDs the user can fit in. protected bool CanFitSkSGCD(float duration, int extraGCDs = 0) => GCD + SkSGCDLength * extraGCDs < duration; - /// Checks if we can fit in a spell-speed based GCD. - /// - /// How many extra GCDs the user can fit in. - /// - TRUE if we can fit in a spell-speed based GCD - FALSE if otherwise + /// + /// Checks if we can fit in a spell-speed based GCD. + /// + /// The duration to check against. + /// How many extra GCDs the user can fit in. protected bool CanFitSpSGCD(float duration, int extraGCDs = 0) => GCD + SpSGCDLength * extraGCDs < duration; - /// Checks if player is available to weave in any abilities. - /// NOTE: This function is only recommended for jobs with Skill-Speed. Spell-Speed users are unaffected by this. - /// The cooldown time of the action specified. - /// The animation lock time of the action specified. - /// How many extra GCDs the user can fit in. - /// How much extra delay the user can add in, in seconds. - /// - TRUE if we can weave in a skill-speed based GCD - FALSE if otherwise + /// + /// Checks if player is available to weave in any abilities. + /// + /// The cooldown time of the action specified. + /// The animation lock time of the action specified. + /// How many extra GCDs the user can fit in. + /// How much extra delay the user can add in, in seconds. protected bool CanWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + SkSGCDLength * extraGCDs + extraFixedDelay; - /// Checks if player is available to weave in any spells. - /// NOTE: This function is only recommended for jobs with Spell-Speed. Skill-Speed users are unaffected by this. - /// The cooldown time of the action specified. - /// The animation lock time of the action specified. - /// How many extra GCDs the user can fit in. - /// How much extra delay the user can add in, in seconds. - /// - TRUE if we can weave in a spell-speed based GCD - FALSE if otherwise + /// + /// Checks if player is available to weave in any spells. + /// + /// The cooldown time of the action specified. + /// The animation lock time of the action specified. + /// How many extra GCDs the user can fit in. + /// How much extra delay the user can add in, in seconds. protected bool CanSpellWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + SpSGCDLength * extraGCDs + extraFixedDelay; - /// Checks if player is available to weave in any abilities. - /// NOTE: This function is only recommended for jobs with Skill-Speed. Spell-Speed users are unaffected by this. - /// The user's specified Action ID being checked. - /// How many extra GCDs the user can fit in. - /// How much extra delay the user can add in, in seconds. - /// - TRUE if we can weave in any abilities - FALSE if otherwise + /// + /// Checks if player is available to weave in any abilities. + /// + /// The Action ID being checked. + /// How many extra GCDs the user can fit in. + /// How much extra delay the user can add in, in seconds. protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) { if (!Unlocked(aid)) @@ -636,127 +355,113 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) : SpS > 100 && CanWeave(ChargeCD(aid), res.InstantAnimLock, extraGCDs, extraFixedDelay); } - /// Checks if user is in pre-pull stage; useful for First GCD openings. - /// - TRUE if user is in pre-pull stage or fully not in combat- FALSE if otherwise + /// + /// Checks if user is in pre-pull stage. + /// protected bool IsFirstGCD() => !Player.InCombat || (World.CurrentTime - Manager.CombatStart).TotalSeconds < 0.1f; - /// Checks if user can Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// + /// Checks if user can Weave in any abilities. + /// protected bool CanWeaveIn => GCD is <= 2.49f and >= 0.01f; - /// Checks if user can Early Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// + /// Checks if user can Early Weave in any abilities. + /// protected bool CanEarlyWeaveIn => GCD is <= 2.49f and >= 1.26f; - /// Checks if user can Late Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// + /// Checks if user can Late Weave in any abilities. + /// protected bool CanLateWeaveIn => GCD is <= 1.25f and >= 0.01f; - /// Checks if user can Quarter Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// + /// Checks if user can Quarter Weave in any abilities. + /// protected bool CanQuarterWeaveIn => GCD is < 0.9f and >= 0.01f; #endregion #region Cooldown - /// Retrieves the total cooldown time left on the specified action. + /// Retrieves the total cooldown time left on the specified action. /// The user's specified Action ID being checked. - /// - A value representing the current cooldown on user's specified Action ID protected float TotalCD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; - /// Returns the charge cooldown time left on the specified action. + /// Returns the charge cooldown time left on the specified action. /// The user's specified Action ID being checked. - /// - A value representing the current charge cooldown on user's specified Action ID protected float ChargeCD(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ReadyIn(World.Client.Cooldowns, World.Client.DutyActions) : float.MaxValue; - /// Checks if action is ready to be used based on if it's Unlocked and its charge cooldown timer. + /// Checks if action is ready to be used based on if it's Unlocked and its charge cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is Unlocked and off cooldown. - FALSE if locked or still on cooldown protected bool ActionReady(AID aid) => Unlocked(aid) && ChargeCD(aid) < 0.6f; - /// Checks if action has any charges remaining. + /// Checks if action has any charges remaining. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action has any charges available - FALSE if locked or still on cooldown protected bool HasCharges(AID aid) => ChargeCD(aid) < 0.6f; - /// Checks if action is on cooldown based on its total cooldown timer. + /// Checks if action is on cooldown based on its total cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is still on cooldown - FALSE if off cooldown protected bool IsOnCooldown(AID aid) => TotalCD(aid) > 0.6f; - /// Checks if action is off cooldown based on its total cooldown timer. + /// Checks if action is off cooldown based on its total cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is off cooldown - FALSE if still on cooldown protected bool IsOffCooldown(AID aid) => !IsOnCooldown(aid); - /// Checks if action is off cooldown based on its charge cooldown timer. + /// Checks if action is off cooldown based on its charge cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is off cooldown - FALSE if still on cooldown protected bool OnCooldown(AID aid) => MaxChargesIn(aid) > 0; - /// Checks if last action used is what the user is specifying and within however long. + /// Checks if last action used is what the user is specifying. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action was just used - FALSE if was not just used protected bool LastActionUsed(AID aid) => Manager.LastCast.Data?.IsSpell(aid) == true; - /// Retrieves time remaining until specified action is at max charges. + /// Retrieves time remaining until specified action is at max charges. /// The user's specified Action ID being checked. protected float MaxChargesIn(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ChargeCapIn(World.Client.Cooldowns, World.Client.DutyActions, Player.Level) : float.MaxValue; #endregion #region Status - /// Retrieves the amount of specified status effect's stacks remaining on any target. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "StacksRemaining(Player, SID.Requiescat, 30) > 0" + /// Retrieves the amount of specified status effect's stacks remaining on any target. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating how many stacks exist protected int StacksRemaining(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Stacks; - /// Retrieves the amount of specified status effect's time left remaining on any target. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "StatusRemaining(Player, SID.Requiescat, 30) > 0f" + /// Retrieves the amount of specified status effect's time left remaining on any target. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating how much time left on existing effect - protected float StatusRemaining(Actor? target, SID sid, float duration) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Left; + protected float StatusRemaining(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Left; - /// Checks if a specific status effect on the player exists. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "PlayerHasEffect(SID.NoMercy, 20)" + /// Checks if a specific status effect on the Player exists. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating if the effect exists protected bool PlayerHasEffect(SID sid, float duration = 1000f) where SID : Enum => StatusRemaining(Player, sid, duration) > 0.1f; - /// Checks if a specific status effect on the player exists. - /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs or even enemies - /// Example Given: "PlayerHasEffectAny(SID.Troubadour)" + /// Checks if a specific status effect on the Player exists. + /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs, or even enemies. /// The user's specified Status ID being checked. - /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating if the effect exists protected bool PlayerHasAnyEffect(SID sid) where SID : Enum => Player.FindStatus(sid) != null; - /// Checks if a specific status effect on any specified target exists. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "TargetHasEffect(primaryTarget, SID.SonicBreak, 30)" + /// Checks if a specific status effect on any specified Target exists. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating if the effect exists protected bool TargetHasEffect(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusRemaining(target, sid, duration) > 0.1f; - /// Checks if a specific status effect on any specified target exists. - /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs or even enemies - /// Example Given: "TargetHasAnyEffect(primaryTarget, SID.MeditativeBrotherhood)" + /// Checks if a specific status effect on any specified Target exists. + /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs, or even enemies. /// The user's specified Target being checked. /// The user's specified Status ID being checked. - /// - A value indicating if the effect exists protected bool TargetHasAnyEffect(Actor? target, SID sid) where SID : Enum => target?.FindStatus(sid) != null; - /// Checks if Player has any stacks of specific status effect. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "PlayerHasStacks(SID.Requiescat)" + /// Checks if Player has any stacks of a specific status effect. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Status ID being checked. - /// - A value indicating if the effect exists protected bool PlayerHasStacks(SID sid) where SID : Enum => StacksRemaining(Player, sid) > 0; #endregion @@ -786,117 +491,58 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) #endregion #region Cones - // some of these use-cases really are only for BLU modules, since their job's ability ranges are all over the place (i.e. 4y, 16y specifically) - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Four (4) yalms. + /// Creates a Position Check for Cone AOE attacks with the given range. /// - protected PositionCheck Is4yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 4, Player.DirectionTo(primary), 45.Degrees()); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Six (6) yalms. - /// - protected PositionCheck Is6yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 6, Player.DirectionTo(primary), 45.Degrees()); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Eight (8) yalms. - /// - protected PositionCheck Is8yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 8, Player.DirectionTo(primary), 45.Degrees()); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Ten (10) yalms. - /// - protected PositionCheck Is10yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 10, Player.DirectionTo(primary), 45.Degrees()); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Twelve (12) yalms. - /// - protected PositionCheck Is12yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 12, Player.DirectionTo(primary), 45.Degrees()); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Fifteen (15) yalms. - /// - protected PositionCheck Is15yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 15, Player.DirectionTo(primary), 45.Degrees()); + private PositionCheck ConeTargetCheck(float range) => (primary, other) => + { + var playerDir = Player.DirectionTo(primary); // Cache calculation + return Hints.TargetInAOECone(other, Player.Position, range, playerDir, 45.Degrees()); + }; - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone within Sixteen (16) yalms. - /// - protected PositionCheck Is16yConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 16, Player.DirectionTo(primary), 45.Degrees()); + protected PositionCheck Is4yConeTarget => ConeTargetCheck(4); + protected PositionCheck Is6yConeTarget => ConeTargetCheck(6); + protected PositionCheck Is8yConeTarget => ConeTargetCheck(8); + protected PositionCheck Is10yConeTarget => ConeTargetCheck(10); + protected PositionCheck Is12yConeTarget => ConeTargetCheck(12); + protected PositionCheck Is15yConeTarget => ConeTargetCheck(15); + protected PositionCheck Is16yConeTarget => ConeTargetCheck(16); #endregion - #region Lines (aka AOE Rectangles) + #region Lines (AOE Rectangles) /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Ten (10) yalms. + /// Creates a Position Check for Line AOE (AOE Rectangle) attacks with the given range. /// - protected PositionCheck Is10yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 10, 2); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Fifteen (15) yalms. - /// - protected PositionCheck Is15yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 15, 2); - - /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Twenty (20) yalms. - /// - protected PositionCheck Is20yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 20, 2); + private PositionCheck LineTargetCheck(float range) => (primary, other) => + { + var playerDir = Player.DirectionTo(primary); // Cache calculation + return Hints.TargetInAOERect(other, Player.Position, playerDir, range, 2); + }; - /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Twenty-five (25) yalms - /// - protected PositionCheck Is25yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 25, 2); + protected PositionCheck Is10yRectTarget => LineTargetCheck(10); + protected PositionCheck Is15yRectTarget => LineTargetCheck(15); + protected PositionCheck Is20yRectTarget => LineTargetCheck(20); + protected PositionCheck Is25yRectTarget => LineTargetCheck(25); #endregion #endregion #region Range Checks /// - /// Checks if target is within Zero (0) yalms in distance, or if Player is inside hitbox. + /// Checks if target is within the specified distance in yalms. /// /// The user's specified Target being checked. + /// The maximum distance threshold. /// - protected bool In0y(Actor? target) => Player.DistanceToHitbox(target) <= 0.00f; - - /// - /// Checks if target is within Three (3) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 2.99f; - - /// - /// Checks if target is within Five (5) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.99f; - - /// - /// Checks if target is within Ten (10) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In10y(Actor? target) => Player.DistanceToHitbox(target) <= 9.99f; - - /// - /// Checks if target is within Fifteen (15) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In15y(Actor? target) => Player.DistanceToHitbox(target) <= 14.99f; - - /// - /// Checks if target is within Twenty (20) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.99f; - - /// - /// Checks if target is within Twenty-five (25) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In25y(Actor? target) => Player.DistanceToHitbox(target) <= 24.99f; + protected bool InRange(Actor? target, float maxDistance) => Player.DistanceToHitbox(target) <= maxDistance - 0.01f; + + protected bool In0y(Actor? target) => InRange(target, 0.00f); + protected bool In3y(Actor? target) => InRange(target, 3.00f); + protected bool In5y(Actor? target) => InRange(target, 5.00f); + protected bool In10y(Actor? target) => InRange(target, 10.00f); + protected bool In15y(Actor? target) => InRange(target, 15.00f); + protected bool In20y(Actor? target) => InRange(target, 20.00f); + protected bool In25y(Actor? target) => InRange(target, 25.00f); #endregion /// @@ -907,21 +553,27 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// protected Actor? TargetChoice(StrategyValues.OptionRef track) => ResolveTargetOverride(track.Value); - /// Targeting function for indicating when or not AOE Circle abilities should be used based on targets nearby. - /// The range of the AOE Circle ability, or radius from center of Player; this should be adjusted accordingly to user's module specific to job's abilities. - /// - A tuple with the following booleans: - /// -- OnTwoOrMore: A boolean indicating if there are two (2) or more targets inside Player's AOE Circle. - /// -- OnThreeOrMore: A boolean indicating if there are three (3) or more targets inside Player's AOE Circle. - /// -- OnFourOrMore: A boolean indicating if there are four (4) or more targets inside Player's AOE Circle. - /// -- OnFiveOrMore: A boolean indicating if there are five (5) or more targets inside Player's AOE Circle. - protected (bool OnTwoOrMore, bool OnThreeOrMore, bool OnFourOrMore, bool OnFiveOrMore) ShouldUseAOECircle(float range) + /// + /// Targeting function for indicating when AOE Circle abilities should be used based on nearby targets. + /// + /// The radius of the AOE Circle ability from the Player. + /// + /// A tuple with boolean values indicating whether the AOE Circle should be used based on the number of targets nearby:
+ /// OnAny: At least 1 target is inside.
+ /// OnTwoOrMore: At least 2 targets are inside.
+ /// OnThreeOrMore: At least 3 targets are inside.
+ /// OnFourOrMore: At least 4 targets are inside.
+ /// OnFiveOrMore: At least 5 targets are inside. + ///
+ protected (bool OnAny, bool OnTwoOrMore, bool OnThreeOrMore, bool OnFourOrMore, bool OnFiveOrMore) + ShouldUseAOECircle(float range) { - var OnTwoOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 1; - var OnThreeOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 2; - var OnFourOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 3; - var OnFiveOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 4; - - return (OnTwoOrMore, OnThreeOrMore, OnFourOrMore, OnFiveOrMore); + var numTargets = Hints.NumPriorityTargetsInAOECircle(Player.Position, range); + return (numTargets > 0, // OnAny + numTargets > 1, // OnTwoOrMore + numTargets > 2, // OnThreeOrMore + numTargets > 3, // OnFourOrMore + numTargets > 4); // OnFiveOrMore } /// @@ -931,7 +583,7 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// The user's picked Strategy /// The user's current specified Target. /// - protected void GetPrimaryTarget(StrategyValues strategy, ref Enemy? primaryTarget, float range) + protected void GetPvETarget(StrategyValues strategy, ref Enemy? primaryTarget, float range) { var AOEStrat = strategy.Option(SharedTrack.AOE).As(); if (AOEStrat is AOEStrategy.Automatic) @@ -970,7 +622,6 @@ protected void GetPvPTarget(ref Enemy? primaryTarget, float range) /// /// This function attempts to pick the best target automatically. /// - /// The user's picked Strategy /// The user's current picked Target. /// /// @@ -980,7 +631,6 @@ protected void GetPvPTarget(ref Enemy? primaryTarget, float range) /// /// This function picks the target based on HP, modified by how many targets are in the AOE. /// - /// /// The user's current picked Target. /// /// @@ -991,7 +641,6 @@ protected void GetPvPTarget(ref Enemy? primaryTarget, float range) /// Main function for picking a target, generalized for any prioritization and simplification logic. /// /// - /// /// The user's current picked Target. /// /// @@ -1015,14 +664,12 @@ P targetPrio(Actor potentialTarget) /// Identify an appropriate target for applying DoT effect. This has no impact if any auto-targeting is disabled. ///
/// - /// /// /// /// /// - protected (Enemy? Target, P Timer) GetDOTTarget

(Enemy? initial, Func getTimer, int maxAllowedTargets) where P : struct, IComparable + protected (Enemy? Best, P Timer) GetDOTTarget

(Enemy? initial, Func getTimer, int maxAllowedTargets) where P : struct, IComparable { - // Check if the initial target is null or if the maxAllowedTargets is 0, in which case no valid target can be selected if (initial == null || maxAllowedTargets <= 0) { return (null, getTimer(null)); @@ -1035,18 +682,14 @@ P targetPrio(Actor potentialTarget) foreach (var dotTarget in Hints.PriorityTargets) { - // Skip targets that forbid DOTs if (dotTarget.ForbidDOTs) continue; - // If we exceed the max number of allowed targets, stop and return the current best if (++numTargets > maxAllowedTargets) return (newTarget, newTimer); - // Get the timer for the current target var thisTimer = getTimer(dotTarget.Actor); - // Update the new target and timer if the current one is better (has a smaller timer value) if (thisTimer.CompareTo(newTimer) < 0) { newTarget = dotTarget; @@ -1058,13 +701,6 @@ P targetPrio(Actor potentialTarget) } #endregion - #region Actors - ///

- /// Player's "actual" target; guaranteed to be an enemy. - /// - protected Enemy? PlayerTarget { get; private set; } - #endregion - #region Positionals /// /// Retrieves the current positional of the target based on target's position and rotation. @@ -1154,7 +790,6 @@ protected void AnyGoalZoneCombined(float range, Func fAoe, AID firs #region Misc /// Estimates the delay caused by animation lock. - /// - A value representing the current Animation Lock Delay protected float AnimationLockDelay { get; private set; } /// Estimates the time remaining until the next Down-time phase. @@ -1168,6 +803,11 @@ protected void AnyGoalZoneCombined(float range, Func fAoe, AID firs /// Checks if player is currently moving. protected bool IsMoving { get; private set; } + + /// + /// Player's "actual" target; guaranteed to be an enemy. + /// + protected Enemy? PlayerTarget { get; private set; } #endregion #region Shared Abilities @@ -1313,44 +953,31 @@ public static RotationModuleDefinition.ConfigRef DefineOGCDA global helper for easily retrieving the user's Rotation strategy. See for more details. + public static AOEStrategy Rotation(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As(); + /// A global helper for automatically executing the best optimal rotation. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool Automatic(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.Automatic; /// A global helper for force-executing the single-target rotation. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool ForceST(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.ForceST; /// A global helper for force-executing the AOE rotation. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool ForceAOE(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() == AOEStrategy.ForceAOE; /// A global helper for forbidding ALL available abilities that are buff, gauge, or cooldown related. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldAll(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldEverything; /// A global helper for forbidding ALL available abilities that are related to raidbuffs. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldBuffs(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldBuffs; /// A global helper for forbidding ALL available abilities that have any sort of cooldown attached to it. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldCDs(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldCooldowns; /// A global helper for forbidding ALL available abilities that are related to the job's gauge. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldGauge(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldGauge; /// A global helper for allowing ALL available abilities that are buff, gauge, or cooldown related. This is the default option for this strategy. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool DontHold(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.DontHold; #endregion } From 02ec692089bfd373e134e21d900cf3d76a652858 Mon Sep 17 00:00:00 2001 From: ace Date: Thu, 13 Feb 2025 20:59:49 -0800 Subject: [PATCH 42/56] last little cleanup --- .../Standard/akechi/AkechiTools.cs | 443 +++++++----------- 1 file changed, 173 insertions(+), 270 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 64846fdc24..99d18d2017 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -83,10 +83,10 @@ public enum OGCDStrategy /// Forces execution of the ability in the very next weave window. AnyWeave, - /// Forces execution of the ability in the very next EARLY-weave window. + /// Forces execution of the ability in the very next EARLY-weave window. EarlyWeave, - /// Forces execution of the ability in the very next LATE-weave window. + /// Forces execution of the ability in the very next LATE-weave window. LateWeave, /// Forbids execution of the ability. @@ -197,130 +197,88 @@ protected bool QueueAction(AID aid, Actor? target, float priority, float delay, #endregion #region HP/MP/Shield - /// - /// Retrieves the current HP value of the player. - /// + /// Retrieves the current HP value of the player. protected uint HP { get; private set; } - /// - /// Retrieves the current MP value of the player. - /// + /// Retrieves the current MP value of the player. protected uint MP { get; private set; } - /// - /// Retrieves the current Shield value of the player. - /// + /// Retrieves the current Shield value of the player. protected uint Shield { get; private set; } - /// - /// Retrieves the current HP of a specified actor. - /// - /// The target actor. + /// Retrieves the current HP value of a specified actor. + /// The specified target actor. protected uint TargetCurrentHP(Actor actor) => actor.HPMP.CurHP; - /// - /// Retrieves the current Shield value of a specified actor. - /// + /// Retrieves the current Shield value of a specified actor. /// The target actor. protected uint TargetCurrentShield(Actor actor) => actor.HPMP.Shield; - /// - /// Checks if a specified actor has any active Shield. - /// + /// Checks if a specified actor has any current active Shield. /// The target actor. protected bool TargetHasShield(Actor actor) => actor.HPMP.Shield > 0.1f; - /// - /// Retrieves the player's current HP percentage. - /// + /// Retrieves the player's current HP percentage. protected float PlayerHPP() => (float)Player.HPMP.CurHP / Player.HPMP.MaxHP * 100; - /// - /// Retrieves the HP percentage of a specified actor. - /// - /// The target actor. - protected static float TargetHPP(Actor? target = null) + /// Retrieves the current HP percentage of a specified actor. + /// The target actor. + protected static float TargetHPP(Actor? actor = null) { - if (target is null || target.IsDead) + if (actor is null || actor.IsDead) return 0f; - var HPP = (float)target.HPMP.CurHP / target.HPMP.MaxHP * 100f; + var HPP = (float)actor.HPMP.CurHP / actor.HPMP.MaxHP * 100f; return Math.Clamp(HPP, 0f, 100f); } #endregion #region Actions - /// - /// Checks if specified action is Unlocked based on Level and Job Quest (if required). - /// + /// Checks if specified action is Unlocked based on Level and Job Quest (if required). /// The user's specified Action ID being checked. protected bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); - /// - /// Checks if specified trait is Unlocked based on Level and Job Quest (if required). - /// + /// Checks if specified trait is Unlocked based on Level and Job Quest (if required). /// The Trait ID being checked. protected bool Unlocked(TraitID tid) => TraitUnlocked((uint)(object)tid); - /// - /// Checks the last combo action is what the user is specifying. - /// + /// Checks the last combo action is what the user is specifying. protected AID ComboLastMove => (AID)(object)World.Client.ComboState.Action; - /// - /// Checks the time left remaining inside current combo before expiration. - /// + /// Checks the time left remaining inside current combo before expiration. protected float ComboTimer => (float)(object)World.Client.ComboState.Remaining; - /// - /// Retrieves actual cast time of a specified action. - /// + /// Retrieves actual cast time of a specified action. /// The user's specified Action ID being checked. protected virtual float ActualCastTime(AID aid) => ActionDefinitions.Instance.Spell(aid)!.CastTime; - /// - /// Retrieves effective cast time of a specified action. - /// + /// Retrieves effective cast time of a specified action. /// The user's specified Action ID being checked. protected virtual float EffectiveCastTime(AID aid) => PlayerHasEffect(ClassShared.SID.Swiftcast, 10) ? 0 : ActualCastTime(aid) * SpSGCDLength / 2.5f; - /// - /// Retrieves player's GCD length based on Skill-Speed. - /// + /// Retrieves player's GCD length based on Skill-Speed. protected float SkSGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); - /// - /// Retrieves player's current Skill-Speed stat. - /// + /// Retrieves player's current Skill-Speed stat. protected float SkS => ActionSpeed.Round(World.Client.PlayerStats.SkillSpeed); - /// - /// Retrieves player's GCD length based on Spell-Speed. - /// + /// Retrieves player's GCD length based on Spell-Speed. protected float SpSGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SpellSpeed, World.Client.PlayerStats.Haste, Player.Level); - /// - /// Retrieves player's current Spell-Speed stat. - /// + /// Retrieves player's current Spell-Speed stat. protected float SpS => ActionSpeed.Round(World.Client.PlayerStats.SpellSpeed); - /// - /// Checks if we can fit in a skill-speed based GCD. - /// + /// Checks if we can fit in a skill-speed based GCD. /// The duration to check against. /// How many extra GCDs the user can fit in. protected bool CanFitSkSGCD(float duration, int extraGCDs = 0) => GCD + SkSGCDLength * extraGCDs < duration; - /// - /// Checks if we can fit in a spell-speed based GCD. - /// + /// Checks if we can fit in a spell-speed based GCD. /// The duration to check against. /// How many extra GCDs the user can fit in. protected bool CanFitSpSGCD(float duration, int extraGCDs = 0) => GCD + SpSGCDLength * extraGCDs < duration; - /// - /// Checks if player is available to weave in any abilities. - /// + /// Checks if player is available to weave in any abilities. /// The cooldown time of the action specified. /// The animation lock time of the action specified. /// How many extra GCDs the user can fit in. @@ -328,9 +286,7 @@ protected static float TargetHPP(Actor? target = null) protected bool CanWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + SkSGCDLength * extraGCDs + extraFixedDelay; - /// - /// Checks if player is available to weave in any spells. - /// + /// Checks if player is available to weave in any spells. /// The cooldown time of the action specified. /// The animation lock time of the action specified. /// How many extra GCDs the user can fit in. @@ -338,9 +294,7 @@ protected bool CanWeave(float cooldown, float actionLock, int extraGCDs = 0, flo protected bool CanSpellWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + SpSGCDLength * extraGCDs + extraFixedDelay; - /// - /// Checks if player is available to weave in any abilities. - /// + /// Checks if player is available to weave in any abilities. /// The Action ID being checked. /// How many extra GCDs the user can fit in. /// How much extra delay the user can add in, in seconds. @@ -355,111 +309,101 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) : SpS > 100 && CanWeave(ChargeCD(aid), res.InstantAnimLock, extraGCDs, extraFixedDelay); } - /// - /// Checks if user is in pre-pull stage. - /// + /// Checks if user is in pre-pull stage. protected bool IsFirstGCD() => !Player.InCombat || (World.CurrentTime - Manager.CombatStart).TotalSeconds < 0.1f; - /// - /// Checks if user can Weave in any abilities. - /// + /// Checks if user can Weave in any abilities. protected bool CanWeaveIn => GCD is <= 2.49f and >= 0.01f; - /// - /// Checks if user can Early Weave in any abilities. - /// + /// Checks if user can Early Weave in any abilities. protected bool CanEarlyWeaveIn => GCD is <= 2.49f and >= 1.26f; - /// - /// Checks if user can Late Weave in any abilities. - /// + /// Checks if user can Late Weave in any abilities. protected bool CanLateWeaveIn => GCD is <= 1.25f and >= 0.01f; - /// - /// Checks if user can Quarter Weave in any abilities. - /// + /// Checks if user can Quarter Weave in any abilities. protected bool CanQuarterWeaveIn => GCD is < 0.9f and >= 0.01f; #endregion #region Cooldown - /// Retrieves the total cooldown time left on the specified action. + /// Retrieves the total cooldown time left on the specified action. /// The user's specified Action ID being checked. protected float TotalCD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; - /// Returns the charge cooldown time left on the specified action. + /// Returns the charge cooldown time left on the specified action. /// The user's specified Action ID being checked. protected float ChargeCD(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ReadyIn(World.Client.Cooldowns, World.Client.DutyActions) : float.MaxValue; - /// Checks if action is ready to be used based on if it's Unlocked and its charge cooldown timer. + /// Checks if action is ready to be used based on if it's Unlocked and its charge cooldown timer. /// The user's specified Action ID being checked. protected bool ActionReady(AID aid) => Unlocked(aid) && ChargeCD(aid) < 0.6f; - /// Checks if action has any charges remaining. + /// Checks if action has any charges remaining. /// The user's specified Action ID being checked. protected bool HasCharges(AID aid) => ChargeCD(aid) < 0.6f; - /// Checks if action is on cooldown based on its total cooldown timer. + /// Checks if action is on cooldown based on its total cooldown timer. /// The user's specified Action ID being checked. protected bool IsOnCooldown(AID aid) => TotalCD(aid) > 0.6f; - /// Checks if action is off cooldown based on its total cooldown timer. + /// Checks if action is off cooldown based on its total cooldown timer. /// The user's specified Action ID being checked. protected bool IsOffCooldown(AID aid) => !IsOnCooldown(aid); - /// Checks if action is off cooldown based on its charge cooldown timer. + /// Checks if action is off cooldown based on its charge cooldown timer. /// The user's specified Action ID being checked. protected bool OnCooldown(AID aid) => MaxChargesIn(aid) > 0; - /// Checks if last action used is what the user is specifying. + /// Checks if last action used is what the user is specifying. /// The user's specified Action ID being checked. protected bool LastActionUsed(AID aid) => Manager.LastCast.Data?.IsSpell(aid) == true; - /// Retrieves time remaining until specified action is at max charges. + /// Retrieves time remaining until specified action is at max charges. /// The user's specified Action ID being checked. protected float MaxChargesIn(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ChargeCapIn(World.Client.Cooldowns, World.Client.DutyActions, Player.Level) : float.MaxValue; #endregion #region Status - /// Retrieves the amount of specified status effect's stacks remaining on any target. + /// Retrieves the amount of specified status effect's stacks remaining on any target. /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. protected int StacksRemaining(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Stacks; - /// Retrieves the amount of specified status effect's time left remaining on any target. + /// Retrieves the amount of specified status effect's time left remaining on any target. /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. protected float StatusRemaining(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Left; - /// Checks if a specific status effect on the Player exists. + /// Checks if a specific status effect on the Player exists. /// NOTE: The effect MUST be owned by the Player. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. protected bool PlayerHasEffect(SID sid, float duration = 1000f) where SID : Enum => StatusRemaining(Player, sid, duration) > 0.1f; - /// Checks if a specific status effect on the Player exists. + /// Checks if a specific status effect on the Player exists. /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs, or even enemies. /// The user's specified Status ID being checked. protected bool PlayerHasAnyEffect(SID sid) where SID : Enum => Player.FindStatus(sid) != null; - /// Checks if a specific status effect on any specified Target exists. + /// Checks if a specific status effect on any specified Target exists. /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. protected bool TargetHasEffect(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusRemaining(target, sid, duration) > 0.1f; - /// Checks if a specific status effect on any specified Target exists. + /// Checks if a specific status effect on any specified Target exists. /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs, or even enemies. /// The user's specified Target being checked. /// The user's specified Status ID being checked. protected bool TargetHasAnyEffect(Actor? target, SID sid) where SID : Enum => target?.FindStatus(sid) != null; - /// Checks if Player has any stacks of a specific status effect. + /// Checks if Player has any stacks of a specific status effect. /// NOTE: The effect MUST be owned by the Player. /// The user's specified Status ID being checked. protected bool PlayerHasStacks(SID sid) where SID : Enum => StacksRemaining(Player, sid) > 0; @@ -471,182 +415,178 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) #region Position Checks #region Core - /// - /// Checks precise positioning between player target and any other targets. - /// + /// Checks precise positioning between player target and any other targets. protected delegate bool PositionCheck(Actor playerTarget, Actor targetToTest); - /// - /// Calculates the priority of a target based on the total number of targets and the primary target itself. - /// It is generic, so it can return different types based on the implementation. - /// + /// Calculates the priority of a target based on the total number of targets and the primary target itself.
It is generic, so it can return different types based on the implementation.
protected delegate P PriorityFunc

(int totalTargets, Actor primaryTarget); #endregion #region Splash - ///

- /// Position checker for determining the best target for an ability that deals Splash damage. - /// + /// Position checker for determining the best target for an ability that deals Splash damage. protected PositionCheck IsSplashTarget => (primary, other) => Hints.TargetInAOECircle(other, primary.Position, 5); #endregion #region Cones - /// - /// Creates a Position Check for Cone AOE attacks with the given range. - /// + //some use-cases for these are mainly for BLU modules, since the ranges for their abilities are all over the place. (e.g. 4y & 16y specifically) + + /// Creates a Position Check for Cone AOE attacks with the given range. private PositionCheck ConeTargetCheck(float range) => (primary, other) => { - var playerDir = Player.DirectionTo(primary); // Cache calculation + var playerDir = Player.DirectionTo(primary); return Hints.TargetInAOECone(other, Player.Position, range, playerDir, 45.Degrees()); }; + /// Checks if the target is within a 4-yalm Cone range. protected PositionCheck Is4yConeTarget => ConeTargetCheck(4); + + /// Checks if the target is within a 6-yalm Cone range. protected PositionCheck Is6yConeTarget => ConeTargetCheck(6); + + /// Checks if the target is within an 8-yalm Cone range. protected PositionCheck Is8yConeTarget => ConeTargetCheck(8); + + /// Checks if the target is within a 10-yalm Cone range. protected PositionCheck Is10yConeTarget => ConeTargetCheck(10); + + /// Checks if the target is within a 12-yalm Cone range. protected PositionCheck Is12yConeTarget => ConeTargetCheck(12); + + /// Checks if the target is within a 15-yalm Cone range. protected PositionCheck Is15yConeTarget => ConeTargetCheck(15); + + /// Checks if the target is within a 16-yalm Cone range. protected PositionCheck Is16yConeTarget => ConeTargetCheck(16); #endregion #region Lines (AOE Rectangles) - /// - /// Creates a Position Check for Line AOE (AOE Rectangle) attacks with the given range. - /// + /// Creates a Position Check for Line AOE (AOE Rectangle) attacks with the given range. private PositionCheck LineTargetCheck(float range) => (primary, other) => { var playerDir = Player.DirectionTo(primary); // Cache calculation return Hints.TargetInAOERect(other, Player.Position, playerDir, range, 2); }; + /// Checks if the target is within a 10-yalm AOE Rect range. protected PositionCheck Is10yRectTarget => LineTargetCheck(10); + + /// Checks if the target is within a 15-yalm AOE Rect range. protected PositionCheck Is15yRectTarget => LineTargetCheck(15); + + /// Checks if the target is within a 20-yalm AOE Rect range. protected PositionCheck Is20yRectTarget => LineTargetCheck(20); + + /// Checks if the target is within a 25-yalm AOE Rect range. protected PositionCheck Is25yRectTarget => LineTargetCheck(25); #endregion #endregion #region Range Checks - /// - /// Checks if target is within the specified distance in yalms. - /// + /// Checks if target is within the specified distance in yalms. /// The user's specified Target being checked. /// The maximum distance threshold. - /// protected bool InRange(Actor? target, float maxDistance) => Player.DistanceToHitbox(target) <= maxDistance - 0.01f; - protected bool In0y(Actor? target) => InRange(target, 0.00f); + /// Checks if the target is within 0-yalm range. + protected bool In0y(Actor? target) => InRange(target, 0.01f); + + /// Checks if the target is within 3-yalm range. protected bool In3y(Actor? target) => InRange(target, 3.00f); + + /// Checks if the target is within 5-yalm range. protected bool In5y(Actor? target) => InRange(target, 5.00f); + + /// Checks if the target is within 10-yalm range. protected bool In10y(Actor? target) => InRange(target, 10.00f); + + /// Checks if the target is within 15-yalm range. protected bool In15y(Actor? target) => InRange(target, 15.00f); + + /// Checks if the target is within 20-yalm range. protected bool In20y(Actor? target) => InRange(target, 20.00f); + + /// Checks if the target is within 25-yalm range. protected bool In25y(Actor? target) => InRange(target, 25.00f); #endregion - /// - /// A simpler smart-targeting helper for picking a specific target over your current target. - /// Very useful for intricate planning of ability targeting in specific situations. - /// - /// The user's picked strategy's option Track, retrieved from module's enums and definitions. (e.g. strategy.Option(Track.NoMercy)) + #region Smart-Targeting + /// A simpler smart-targeting helper for picking a specific target over your current target.
Very useful for intricate planning of ability targeting in specific situations.
+ /// The user's selected strategy's option Track, retrieved from module's enums and definitions. (e.g. strategy.Option(Track.NoMercy)) /// protected Actor? TargetChoice(StrategyValues.OptionRef track) => ResolveTargetOverride(track.Value); - /// - /// Targeting function for indicating when AOE Circle abilities should be used based on nearby targets. - /// - /// The radius of the AOE Circle ability from the Player. - /// - /// A tuple with boolean values indicating whether the AOE Circle should be used based on the number of targets nearby:
- /// OnAny: At least 1 target is inside.
- /// OnTwoOrMore: At least 2 targets are inside.
- /// OnThreeOrMore: At least 3 targets are inside.
- /// OnFourOrMore: At least 4 targets are inside.
- /// OnFiveOrMore: At least 5 targets are inside. - ///
- protected (bool OnAny, bool OnTwoOrMore, bool OnThreeOrMore, bool OnFourOrMore, bool OnFiveOrMore) - ShouldUseAOECircle(float range) - { - var numTargets = Hints.NumPriorityTargetsInAOECircle(Player.Position, range); - return (numTargets > 0, // OnAny - numTargets > 1, // OnTwoOrMore - numTargets > 2, // OnThreeOrMore - numTargets > 3, // OnFourOrMore - numTargets > 4); // OnFiveOrMore - } - - /// - /// This function attempts to pick a suitable primary target automatically, even if a target is not already picked. - /// NOTE: This function is solely used for auto-targeting enemies without having a target selected, mainly for AI usage. Please use appropriately. - /// - /// The user's picked Strategy - /// The user's current specified Target. - /// + /// Attempts to select a suitable primary PvE target automatically. + /// NOTE: This function is solely used for auto-targeting enemies without having a target selected or for AI usage. Please use appropriately. + /// The user's selected strategy. + /// The user's current target. + /// The max range to consider a new target. protected void GetPvETarget(StrategyValues strategy, ref Enemy? primaryTarget, float range) { - var AOEStrat = strategy.Option(SharedTrack.AOE).As(); - if (AOEStrat is AOEStrategy.Automatic) + if (primaryTarget?.Actor == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) { - if (Player.DistanceToHitbox(primaryTarget?.Actor) > range) + var AOEStrat = strategy.Option(SharedTrack.AOE).As(); + if (AOEStrat == AOEStrategy.Automatic) { - var newTarget = Hints.PriorityTargets.FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); - if (newTarget != null) - primaryTarget = newTarget; + primaryTarget = Hints.PriorityTargets + .FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); } } } - /// - /// This function attempts to pick the most suitable primary target automatically, prioritizing the target with the lowest HP percentage within range. - /// NOTE: This function is solely used for finding a PvP target without having to click on other targets. Please use appropriately. - /// - /// The user's current specified Target. - /// The max range to search for a new target. + /// Attempts to select the most suitable PvP target automatically, prioritizing the target with the lowest HP percentage within range. + /// NOTE: This function is solely used for finding the best PvP target without having to manually scan & click on other targets. Please use appropriately. + /// The user's current target. + /// The max range to consider a new target. protected void GetPvPTarget(ref Enemy? primaryTarget, float range) { - primaryTarget ??= PlayerTarget; - - if (Player.DistanceToHitbox(PlayerTarget?.Actor) > range) + if (primaryTarget?.Actor == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) { - var newTarget = Hints.PriorityTargets - .Where(x => x != null && x.Actor != null && Player.DistanceToHitbox(x.Actor) <= range) + primaryTarget = Hints.PriorityTargets + .Where(x => Player.DistanceToHitbox(x.Actor) <= range && x.Actor.FindStatus(ClassShared.SID.Guard) == null) .OrderBy(x => (float)x.Actor.HPMP.CurHP / x.Actor.HPMP.MaxHP) .FirstOrDefault(); - - if (newTarget != null) - PlayerTarget = newTarget; } } + #endregion - /// - /// This function attempts to pick the best target automatically. - /// - /// The user's current picked Target. - /// - /// - /// - protected (Enemy? Best, int Targets) GetBestTarget(Enemy? primaryTarget, float range, PositionCheck isInAOE) => GetTarget(primaryTarget, range, isInAOE, (numTargets, _) => numTargets, a => a); - - /// - /// This function picks the target based on HP, modified by how many targets are in the AOE. - /// - /// The user's current picked Target. - /// - /// - /// - protected (Enemy? Best, int Targets) GetTargetByHP(Enemy? primaryTarget, float range, PositionCheck isInAOE) => GetTarget(primaryTarget, range, isInAOE, (numTargets, enemy) => (numTargets, numTargets > 2 ? enemy.HPMP.CurHP : 0), args => args.numTargets); + /// Targeting function for indicating when AOE Circle abilities should be used based on nearby targets. + /// The radius of the AOE Circle ability from the Player. + /// + /// A tuple with boolean values indicating whether the AOE Circle should be used based on the number of targets nearby:
+ /// - OnAny: At least 1 target is inside.
+ /// - OnTwoOrMore: At least 2 targets are inside.
+ /// - OnThreeOrMore: At least 3 targets are inside.
+ /// - OnFourOrMore: At least 4 targets are inside.
+ /// - OnFiveOrMore: At least 5 targets are inside. + ///
+ protected (bool OnAny, bool OnTwoOrMore, bool OnThreeOrMore, bool OnFourOrMore, bool OnFiveOrMore) ShouldUseAOECircle(float range) + { + var numTargets = Hints.NumPriorityTargetsInAOECircle(Player.Position, range); + return (numTargets > 0, numTargets > 1, numTargets > 2, numTargets > 3, numTargets > 4); + } - /// - /// Main function for picking a target, generalized for any prioritization and simplification logic. - /// + /// This function attempts to pick the best target automatically. + /// The user's current selected Target. + /// The range within which to evaluate potential targets. + /// A flag indicating if the target is within the Area of Effect (AOE). + protected (Enemy? Best, int Targets) GetBestTarget(Enemy? primaryTarget, float range, PositionCheck isInAOE) + => GetTarget(primaryTarget, range, isInAOE, (numTargets, _) => numTargets, a => a); + + /// This function picks the target based on HP, modified by how many targets are in the AOE. + /// The user's current selected Target. + /// The range within which to evaluate potential targets. + /// A flag indicating if the target is within the Area of Effect (AOE). + protected (Enemy? Best, int Targets) GetBestHPTarget(Enemy? primaryTarget, float range, PositionCheck isInAOE) + => GetTarget(primaryTarget, range, isInAOE, (numTargets, enemy) => (numTargets, numTargets > 2 ? enemy.HPMP.CurHP : 0), args => args.numTargets); + + /// Main function for picking a target, generalized for any prioritization and simplification logic. /// - /// The user's current picked Target. - /// - /// - /// - /// - /// + /// The user's current selected Target. + /// The range within which to evaluate potential targets. + /// A flag indicating if the target is within the Area of Effect (AOE). + /// A flag indicating whether prioritization of certain targets should occur. + /// A flag indicating whether the simplification of target selection should apply. protected (Enemy? Best, int Priority) GetTarget

(Enemy? primaryTarget, float range, PositionCheck isInAOE, PriorityFunc

prioritize, Func simplify) where P : struct, IComparable { P targetPrio(Actor potentialTarget) @@ -660,14 +600,11 @@ P targetPrio(Actor potentialTarget) return (newnewprio > 0 ? Hints.FindEnemy(newtarget) : null, newnewprio); } - ///

- /// Identify an appropriate target for applying DoT effect. This has no impact if any auto-targeting is disabled. - /// - /// - /// - /// - /// - /// + /// Identify an appropriate target for applying DoT effect. This has no impact if any auto-targeting is disabled. + /// The type representing the timer value, which must be a struct and implement . + /// The initial target to consider for applying the DoT effect. + /// A function that retrieves the timer value associated with a given actor. + /// The maximum number of valid targets to evaluate. protected (Enemy? Best, P Timer) GetDOTTarget

(Enemy? initial, Func getTimer, int maxAllowedTargets) where P : struct, IComparable { if (initial == null || maxAllowedTargets <= 0) @@ -702,38 +639,27 @@ P targetPrio(Actor potentialTarget) #endregion #region Positionals - ///

- /// Retrieves the current positional of the target based on target's position and rotation. - /// + /// Retrieves the current positional of the target based on target's position and rotation. /// The user's specified Target being checked. - /// - protected Positional GetCurrentPositional(Actor target) => (Player.Position - target.Position).Normalized().Dot(target.Rotation.ToDirection()) switch //Check current positional based on target + protected Positional GetCurrentPositional(Actor target) => (Player.Position - target.Position).Normalized().Dot(target.Rotation.ToDirection()) switch { - < -0.7071068f => Positional.Rear, //value for Rear positional - < 0.7071068f => Positional.Flank, //value for Flank positional - _ => Positional.Front //default to Front positional if not on Rear or Flank + < -0.7071068f => Positional.Rear, + < 0.7071068f => Positional.Flank, + _ => Positional.Front }; - /// - /// Checks if player is on specified target's Rear Positional. - /// + /// Checks if player is on specified target's Rear Positional. /// The user's specified Target being checked. - /// protected bool IsOnRear(Actor target) => GetCurrentPositional(target) == Positional.Rear; - /// - /// Checks if player is on specified target's Flank Positional. - /// + /// Checks if player is on specified target's Flank Positional. /// The user's specified Target being checked. - /// protected bool IsOnFlank(Actor target) => GetCurrentPositional(target) == Positional.Flank; #endregion #region AI - /// - /// Establishes a goal-zone for a single target within a specified range.
- /// Primarily utilized by caster-DPS jobs that lack a dedicated maximize-AOE function. - ///
+ /// Establishes a goal-zone for a single target within a specified range.
+ /// Primarily utilized by caster-DPS jobs that lack a dedicated maximize-AOE function.
/// The range within which the goal zone is applied. protected void GoalZoneSingle(float range) { @@ -741,9 +667,7 @@ protected void GoalZoneSingle(float range) Hints.GoalZones.Add(Hints.GoalSingleTarget(PlayerTarget.Actor, range)); } - /// - /// Defines a goal-zone using a combined strategy, factoring in AOE considerations. - /// + /// Defines a goal-zone using a combined strategy, factoring in AOE considerations. /// The strategy values that influence the goal zone logic. /// The base range for the goal zone. /// A function determining the area of effect. @@ -804,41 +728,27 @@ protected void AnyGoalZoneCombined(float range, Func fAoe, AID firs /// Checks if player is currently moving. protected bool IsMoving { get; private set; } - /// - /// Player's "actual" target; guaranteed to be an enemy. - /// + /// Player's actual target; guaranteed to be an enemy. protected Enemy? PlayerTarget { get; private set; } #endregion #region Shared Abilities - /// - /// Checks if player is able to execute the melee-DPS shared ability: True North - /// + /// Checks if player is able to execute the melee-DPS shared ability: True North protected bool CanTrueNorth { get; private set; } - /// - /// Checks if player is under the effect of the melee-DPS shared ability: True North - /// + /// Checks if player is under the effect of the melee-DPS shared ability: True North protected bool HasTrueNorth { get; private set; } - /// - /// Checks if player is able to execute the caster-DPS shared ability: Swiftcast - /// + /// Checks if player is able to execute the caster-DPS shared ability: Swiftcast protected bool CanSwiftcast { get; private set; } - /// - /// Checks if player is under the effect of the caster-DPS shared ability: Swiftcast - /// + /// Checks if player is under the effect of the caster-DPS shared ability: Swiftcast protected bool HasSwiftcast { get; private set; } - /// - /// Checks if player is able to execute the ranged-DPS shared ability: Peloton - /// + /// Checks if player is able to execute the ranged-DPS shared ability: Peloton protected bool CanPeloton { get; private set; } - /// - /// Checks if player is under the effect of the ranged-DPS shared ability: Peloton - /// + /// Checks if player is under the effect of the ranged-DPS shared ability: Peloton protected bool HasPeloton { get; private set; } #endregion @@ -866,12 +776,9 @@ public sealed override void Execute(StrategyValues strategy, ref Actor? primaryT Execution(strategy, PlayerTarget); } - /// - /// The core function responsible for orchestrating the execution of all abilities and strategies.
- ///
+ /// The core function responsible for orchestrating the execution of all abilities and strategies.
/// The user's specified Strategy. /// The user's specified Enemy. - /// - Primary execution of user's rotation module. public abstract void Execution(StrategyValues strategy, Enemy? primaryTarget); } @@ -880,7 +787,6 @@ static class ModuleExtensions #region Shared Definitions /// Defines our shared AOE (rotation) strategies. /// The definitions of our base module's strategies. - /// - Options for shared custom strategies to be used via AutoRotation or Cooldown Planner public static RotationModuleDefinition.ConfigRef DefineAOE(this RotationModuleDefinition res) { return res.Define(SharedTrack.AOE).As("AOE", uiPriority: 300) @@ -891,7 +797,6 @@ public static RotationModuleDefinition.ConfigRef DefineAOE(this Rot /// Defines our shared Hold strategies. /// The definitions of our base module's strategies. - /// - Options for shared custom strategies to be used via AutoRotation or Cooldown Planner public static RotationModuleDefinition.ConfigRef DefineHold(this RotationModuleDefinition res) { return res.Define(SharedTrack.Hold).As("Hold", uiPriority: 290) @@ -912,7 +817,6 @@ public static RotationModuleDefinition.ConfigRef DefineHold(this R /// The Targets Supported for the ability that the user is specifying. /// The Minimum Level required for the ability that the user is specifying. /// The Maximum Level required for the ability that the user is specifying. - /// - Basic GCD options for any specified ability to be used via AutoRotation or Cooldown Planner public static RotationModuleDefinition.ConfigRef DefineGCD(this RotationModuleDefinition res, Index track, AID aid, string internalName, string displayName = "", int uiPriority = 100, float cooldown = 0, float effectDuration = 0, ActionTargets supportedTargets = ActionTargets.None, int minLevel = 1, int maxLevel = 100) where Index : Enum where AID : Enum @@ -935,7 +839,6 @@ public static RotationModuleDefinition.ConfigRef DefineGCDThe Targets Supported for the ability that the user is specifying. /// The Minimum Level required for the ability that the user is specifying. /// The Maximum Level required for the ability that the user is specifying. - /// - Basic OGCD options for any specified ability to be used via AutoRotation or Cooldown Planner public static RotationModuleDefinition.ConfigRef DefineOGCD(this RotationModuleDefinition res, Index track, AID aid, string internalName, string displayName = "", int uiPriority = 100, float cooldown = 0, float effectDuration = 0, ActionTargets supportedTargets = ActionTargets.None, int minLevel = 1, int maxLevel = 100) where Index : Enum where AID : Enum From 8fd7107767f7057670b5c4e493f4509ba094eefe Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Sat, 15 Feb 2025 17:25:35 -0500 Subject: [PATCH 43/56] fix aoe target range bug --- BossMod/Autorotation/RotationModule.cs | 7 ++++++- BossMod/Autorotation/Standard/xan/Basexan.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/BossMod/Autorotation/RotationModule.cs b/BossMod/Autorotation/RotationModule.cs index bf884f5b48..9e40d69fda 100644 --- a/BossMod/Autorotation/RotationModule.cs +++ b/BossMod/Autorotation/RotationModule.cs @@ -170,9 +170,14 @@ public int FindDutyActionSlot(ActionID action, ActionID other) protected (Actor? Target, P Priority) FindBetterTargetBy

(Actor? initial, float maxDistanceFromPlayer, Func prioFunc, Func? filterFunc = null) where P : struct, IComparable { + bool inRange(Actor tar) => tar.Position.InCircle(Player.Position, maxDistanceFromPlayer + tar.HitboxRadius + 0.5f); + + if (initial != null && !inRange(initial)) + initial = null; + var bestTarget = initial; var bestPrio = initial != null ? prioFunc(initial) : default; - foreach (var enemy in Hints.PriorityTargets.Where(x => x.Actor != initial && x.Actor.Position.InCircle(Player.Position, maxDistanceFromPlayer + x.Actor.HitboxRadius) && (filterFunc?.Invoke(x) ?? true))) + foreach (var enemy in Hints.PriorityTargets.Where(x => x.Actor != initial && inRange(x.Actor) && (filterFunc?.Invoke(x) ?? true))) { var newPrio = prioFunc(enemy.Actor); if (newPrio.CompareTo(bestPrio) > 0) diff --git a/BossMod/Autorotation/Standard/xan/Basexan.cs b/BossMod/Autorotation/Standard/xan/Basexan.cs index 6927910a35..ec2f9bfe7a 100644 --- a/BossMod/Autorotation/Standard/xan/Basexan.cs +++ b/BossMod/Autorotation/Standard/xan/Basexan.cs @@ -215,7 +215,7 @@ P targetPrio(Actor potentialTarget) targeting = Targeting.Manual; if (targeting == Targeting.AutoTryPri) - targeting = primaryTarget == null ? Targeting.Auto : Targeting.AutoPrimary; + targeting = Player.DistanceToHitbox(primaryTarget) <= range ? Targeting.AutoPrimary : Targeting.Auto; var (newtarget, newprio) = targeting switch { From ccbd83aac53f757e646442a60b9ad682aca20d84 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:36:20 -0500 Subject: [PATCH 44/56] delete bad prio stuff --- BossMod/Autorotation/MiscAI/AutoFarm.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/BossMod/Autorotation/MiscAI/AutoFarm.cs b/BossMod/Autorotation/MiscAI/AutoFarm.cs index 6c1aa46152..95c6b8fa16 100644 --- a/BossMod/Autorotation/MiscAI/AutoFarm.cs +++ b/BossMod/Autorotation/MiscAI/AutoFarm.cs @@ -76,10 +76,6 @@ void prioritize(AIHints.Enemy e, int prio) { prioritize(e, 1); } - - // allow targeting mobs that already have positive prio because e.g. they are attacking a party member - if (e.Priority >= 0) - prioritize(e, e.Priority); } var specific = strategy.Option(Track.Specific); From d7c0231170fae3a90f7ddb6a998db19dfb8e697d Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:38:33 -0500 Subject: [PATCH 45/56] set adds pointless in ceto --- BossMod/Modules/Stormblood/Foray/NM/Ceto.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs index 5127c17594..43500c2981 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs @@ -29,7 +29,14 @@ class CircleOfFlames(BossModule module) : Components.LocationTargetedAOEs(module class TailSlap(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.TailSlap), new AOEShapeCone(12, 60.Degrees())); class Petrattraction(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.Petrattraction), 50, kind: Kind.TowardsOrigin); class CircleBlade(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CircleBlade), new AOEShapeCircle(7)); -class Adds(BossModule module) : Components.Adds(module, (uint)OID.FaithlessGuard); +class Adds(BossModule module) : Components.Adds(module, (uint)OID.FaithlessGuard) +{ + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + foreach (var act in ActiveActors) + hints.SetPriority(act, AIHints.Enemy.PriorityPointless); + } +} class CetoStates : StateMachineBuilder { From 3d31d6ec5adb24a8c060ab21bd99623ed841bb1c Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:32:08 -0500 Subject: [PATCH 46/56] add global fate event --- BossMod/Components/Adds.cs | 10 ++++ BossMod/Data/WorldState.cs | 7 +++ BossMod/Framework/WorldStateGameSync.cs | 15 +++++ BossMod/Modules/Stormblood/Foray/Hydatos.cs | 4 +- BossMod/Modules/Stormblood/Foray/NM/Ceto.cs | 11 +--- BossMod/Modules/Stormblood/Foray/NM/Daphne.cs | 2 +- BossMod/Modules/Stormblood/Foray/NM/Molech.cs | 55 ++++++++++++++----- BossMod/Modules/Stormblood/Foray/NM/Ovni.cs | 2 +- BossMod/Replay/ReplayParserLog.cs | 3 + 9 files changed, 81 insertions(+), 28 deletions(-) diff --git a/BossMod/Components/Adds.cs b/BossMod/Components/Adds.cs index d7692bc459..a0f22f4900 100644 --- a/BossMod/Components/Adds.cs +++ b/BossMod/Components/Adds.cs @@ -18,6 +18,16 @@ public override void DrawArenaForeground(int pcSlot, Actor pc) } } +// component for adds that shouldn't be targeted at all, but should still be drawn +public class AddsPointless(BossModule module, uint oid) : Adds(module, oid) +{ + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + foreach (var act in ActiveActors) + hints.SetPriority(act, AIHints.Enemy.PriorityPointless); + } +} + // generic component used for drawing multiple adds with multiple oids, when it's not useful to distinguish between them public class AddsMulti(BossModule module, uint[] oids, int priority = 0) : BossComponent(module) { diff --git a/BossMod/Data/WorldState.cs b/BossMod/Data/WorldState.cs index c527daed9d..72454d8789 100644 --- a/BossMod/Data/WorldState.cs +++ b/BossMod/Data/WorldState.cs @@ -160,4 +160,11 @@ public override void Write(ReplayRecorder.Output output) output.Emit(arg); } } + + public Event FateInfo = new(); + public sealed record class OpFateInfo(uint FateId, DateTime StartTime) : Operation + { + protected override void Exec(WorldState ws) => ws.FateInfo.Fire(this); + public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("FATE"u8).Emit(FateId).Emit(StartTime.Ticks); + } } diff --git a/BossMod/Framework/WorldStateGameSync.cs b/BossMod/Framework/WorldStateGameSync.cs index 5d4e49fe73..cc95a710a1 100644 --- a/BossMod/Framework/WorldStateGameSync.cs +++ b/BossMod/Framework/WorldStateGameSync.cs @@ -68,6 +68,9 @@ sealed class WorldStateGameSync : IDisposable private unsafe delegate void* ProcessSystemLogMessageDelegate(uint entityId, uint logMessageId, int* args, byte argCount); private readonly Hook _processSystemLogMessageHook; + private unsafe delegate void* ProcessPacketFateInfoDelegate(ulong fateId, long startTimestamp, ulong durationSecs); + private readonly Hook _processPacketFateInfoHook; + public unsafe WorldStateGameSync(WorldState ws, ActionManagerEx amex) { _ws = ws; @@ -124,6 +127,10 @@ public unsafe WorldStateGameSync(WorldState ws, ActionManagerEx amex) _processPacketOpenTreasureHook = Service.Hook.HookFromSignature("40 53 48 83 EC 20 48 8B DA 48 8D 0D ?? ?? ?? ?? 8B 52 10 E8 ?? ?? ?? ?? 48 85 C0 74 1B", ProcessPacketOpenTreasureDetour); _processPacketOpenTreasureHook.Enable(); Service.Log($"[WSG] ProcessPacketOpenTreasure address = 0x{_processPacketOpenTreasureHook.Address:X}"); + + _processPacketFateInfoHook = Service.Hook.HookFromSignature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 0F B7 4B 10 48 8D 53 12 41 B8 ?? ?? ?? ??", ProcessPacketFateInfoDetour); + _processPacketFateInfoHook.Enable(); + Service.Log($"[WSG] ProcessPacketFateInfo address = 0x{_processPacketFateInfoHook.Address:X}"); } public void Dispose() @@ -137,6 +144,7 @@ public void Dispose() _processPacketRSVDataHook.Dispose(); _processSystemLogMessageHook.Dispose(); _processPacketOpenTreasureHook.Dispose(); + _processPacketFateInfoHook.Dispose(); _subscriptions.Dispose(); _netConfig.Dispose(); _interceptor.Dispose(); @@ -910,4 +918,11 @@ private unsafe void ProcessPacketOpenTreasureDetour(uint actorID, byte* packet) _globalOps.Add(new WorldState.OpSystemLogMessage(messageId, new Span(args, argCount).ToArray())); return res; } + + private unsafe void* ProcessPacketFateInfoDetour(ulong fateId, long startTimestamp, ulong durationSecs) + { + var res = _processPacketFateInfoHook.Original(fateId, startTimestamp, durationSecs); + _globalOps.Add(new WorldState.OpFateInfo((uint)fateId, DateTimeOffset.FromUnixTimeSeconds(startTimestamp).UtcDateTime)); + return res; + } } diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index c908925d61..40d538bfc0 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -58,7 +58,7 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint { hints.ForbiddenZones.RemoveAll(z => World.Actors.Find(z.Source) is Actor src && ShouldIgnore(src, player)); - var shouldSetTarget = !player.InCombat && player.TargetID == 0; + var shouldSetTarget = true; // !player.InCombat && player.TargetID == 0; var farmOID = (uint)_hydatosConfig.CurrentFarmTarget; var farmMax = _eurekaConfig.MaxPullCount; @@ -72,7 +72,7 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint if (farmOID == 0x26E6 && e.Actor.ForayInfo.Level < 61) continue; - e.Priority = 1; + e.Priority = 0; if (shouldSetTarget && (hints.ForcedTarget == null || (hints.ForcedTarget.Position - player.Position).LengthSq() > (e.Actor.Position - player.Position).LengthSq())) hints.ForcedTarget = e.Actor; diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs index 43500c2981..41fff809d6 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ceto.cs @@ -29,14 +29,7 @@ class CircleOfFlames(BossModule module) : Components.LocationTargetedAOEs(module class TailSlap(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.TailSlap), new AOEShapeCone(12, 60.Degrees())); class Petrattraction(BossModule module) : Components.KnockbackFromCastTarget(module, ActionID.MakeSpell(AID.Petrattraction), 50, kind: Kind.TowardsOrigin); class CircleBlade(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.CircleBlade), new AOEShapeCircle(7)); -class Adds(BossModule module) : Components.Adds(module, (uint)OID.FaithlessGuard) -{ - public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) - { - foreach (var act in ActiveActors) - hints.SetPriority(act, AIHints.Enemy.PriorityPointless); - } -} +class Adds(BossModule module) : Components.AddsPointless(module, (uint)OID.FaithlessGuard); class CetoStates : StateMachineBuilder { @@ -55,6 +48,6 @@ public CetoStates(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1421, Contributors = "xan")] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1421, Contributors = "xan", SortOrder = 9)] public class Ceto(WorldState ws, Actor primary) : BossModule(ws, primary, new(747.8959f, -878.8765f), new ArenaBoundsCircle(80, MapResolution: 1)); diff --git a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs index a28f873843..cc5f11f874 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Daphne.cs @@ -37,6 +37,6 @@ public DaphneStates(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1417, Contributors = "xan")] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1417, Contributors = "xan", SortOrder = 5)] public class Daphne(WorldState ws, Actor primary) : BossModule(ws, primary, new(207.8475f, -736.8179f), new ArenaBoundsCircle(80, MapResolution: 1)); diff --git a/BossMod/Modules/Stormblood/Foray/NM/Molech.cs b/BossMod/Modules/Stormblood/Foray/NM/Molech.cs index dd0293b6d7..131dfbae98 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Molech.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Molech.cs @@ -1,19 +1,44 @@ -//namespace BossMod.Stormblood.Foray.NM.Molech; +namespace BossMod.Stormblood.Foray.NM.Molech; -//public enum OID : uint -//{ -// Boss = 0x275D, -// Helper = 0x233C, -//} +public enum OID : uint +{ + Boss = 0x275D, // R6.000, x1 + Adulator = 0x275E, // R2.800, x3 +} -//class MolechStates : StateMachineBuilder -//{ -// public MolechStates(BossModule module) : base(module) -// { -// TrivialPhase(); -// } -//} +public enum AID : uint +{ + W11TonzeSwipeAdds = 14978, // Adulator->player, no cast, single-target + W11TonzeSwipe = 14972, // Boss->self, 3.0s cast, range 9 ?-degree cone + W111TonzeSwing = 14973, // Boss->self, 4.0s cast, range 13 circle + OrderToStandFast = 14976, // Boss->self, 3.0s cast, range 100 circle + W111TonzeSwingAdds = 14979, // Adulator->self, 3.0s cast, range 13 circle + W111TonzeSwingBig = 14974, // Boss->self, 4.0s cast, range 20 circle + OrderToAssault = 14975, // Boss->self, 3.0s cast, range 100 circle + ZoomIn = 14980, // Adulator->location, 3.0s cast, width 8 rect charge +} -//[ModuleInfo(BossModuleInfo.Maturity.WIP, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 639, NameID = 8070)] -//public class Molech(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsCircle(20)); +class Adds(BossModule module) : Components.AddsPointless(module, (uint)OID.Adulator); +class W11TonzeSwipe(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W11TonzeSwipe), new AOEShapeCone(9, 75.Degrees())); +class W111TonzeSwing(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwing), new AOEShapeCircle(13)); +class W111TonzeSwingAdds(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwingAdds), new AOEShapeCircle(13)); +class W111TonzeSwingBig(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwingBig), new AOEShapeCircle(20)); +class ZoomIn(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.ZoomIn), 4); + +class MolechStates : StateMachineBuilder +{ + public MolechStates(BossModule module) : base(module) + { + TrivialPhase() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter() + .ActivateOnEnter(); + } +} + +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1414, Contributors = "xan", SortOrder = 3)] +public class Molech(WorldState ws, Actor primary) : BossModule(ws, primary, new(-676.8632f, -441.8009f), new ArenaBoundsCircle(80, MapResolution: 1)); diff --git a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs index 965e4a105f..b657b3175f 100644 --- a/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs +++ b/BossMod/Modules/Stormblood/Foray/NM/Ovni.cs @@ -73,6 +73,6 @@ public OvniStates(BossModule module) : base(module) } } -[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1424, Contributors = "xan")] +[ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1424, Contributors = "xan", SortOrder = 11)] public class Ovni(WorldState ws, Actor primary) : BossModule(ws, primary, new(266.1068f, -97.09414f), new ArenaBoundsCircle(80, MapResolution: 1)); diff --git a/BossMod/Replay/ReplayParserLog.cs b/BossMod/Replay/ReplayParserLog.cs index fc29af7633..cf6ee45294 100644 --- a/BossMod/Replay/ReplayParserLog.cs +++ b/BossMod/Replay/ReplayParserLog.cs @@ -292,6 +292,7 @@ private ReplayParserLog(Input input, ReplayBuilder builder) [new("DIRU"u8)] = ParseDirectorUpdate, [new("ENVC"u8)] = ParseEnvControl, [new("SLOG"u8)] = ParseSystemLog, + [new("FATE"u8)] = ParseFateInfo, [new("WAY+"u8)] = () => ParseWaymarkChange(true), [new("WAY-"u8)] = () => ParseWaymarkChange(false), [new("ACT+"u8)] = ParseActorCreate, @@ -461,6 +462,8 @@ private WorldState.OpSystemLogMessage ParseSystemLog() return new(id, args); } + private WorldState.OpFateInfo ParseFateInfo() => new(_input.ReadUInt(false), new(_input.ReadLong())); + private WaymarkState.OpWaymarkChange ParseWaymarkChange(bool set) => new(_version < 10 ? Enum.Parse(_input.ReadString()) : (Waymark)_input.ReadByte(false), set ? _input.ReadVec3() : null); From 06da3c0184fbce9ea8d89a8d12182d23c47a2c14 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Tue, 18 Feb 2025 23:45:23 -0500 Subject: [PATCH 47/56] move fate spawns to clientstate --- BossMod/Data/ClientState.cs | 7 ++++ BossMod/Data/WorldState.cs | 7 ---- BossMod/Framework/WorldStateGameSync.cs | 2 +- BossMod/Modules/Stormblood/Foray/Hydatos.cs | 41 ++++++++++++++++++++- BossMod/Replay/ReplayParserLog.cs | 4 +- 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/BossMod/Data/ClientState.cs b/BossMod/Data/ClientState.cs index b33d14ba6d..32806eddfd 100644 --- a/BossMod/Data/ClientState.cs +++ b/BossMod/Data/ClientState.cs @@ -385,4 +385,11 @@ public override void Write(ReplayRecorder.Output output) output.Emit(val); } } + + public Event FateInfo = new(); + public sealed record class OpFateInfo(uint FateId, DateTime StartTime) : WorldState.Operation + { + protected override void Exec(WorldState ws) => ws.Client.FateInfo.Fire(this); + public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("FATE"u8).Emit(FateId).Emit(StartTime.Ticks); + } } diff --git a/BossMod/Data/WorldState.cs b/BossMod/Data/WorldState.cs index 72454d8789..c527daed9d 100644 --- a/BossMod/Data/WorldState.cs +++ b/BossMod/Data/WorldState.cs @@ -160,11 +160,4 @@ public override void Write(ReplayRecorder.Output output) output.Emit(arg); } } - - public Event FateInfo = new(); - public sealed record class OpFateInfo(uint FateId, DateTime StartTime) : Operation - { - protected override void Exec(WorldState ws) => ws.FateInfo.Fire(this); - public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("FATE"u8).Emit(FateId).Emit(StartTime.Ticks); - } } diff --git a/BossMod/Framework/WorldStateGameSync.cs b/BossMod/Framework/WorldStateGameSync.cs index cc95a710a1..cffa7435b7 100644 --- a/BossMod/Framework/WorldStateGameSync.cs +++ b/BossMod/Framework/WorldStateGameSync.cs @@ -922,7 +922,7 @@ private unsafe void ProcessPacketOpenTreasureDetour(uint actorID, byte* packet) private unsafe void* ProcessPacketFateInfoDetour(ulong fateId, long startTimestamp, ulong durationSecs) { var res = _processPacketFateInfoHook.Original(fateId, startTimestamp, durationSecs); - _globalOps.Add(new WorldState.OpFateInfo((uint)fateId, DateTimeOffset.FromUnixTimeSeconds(startTimestamp).UtcDateTime)); + _globalOps.Add(new ClientState.OpFateInfo((uint)fateId, DateTimeOffset.FromUnixTimeSeconds(startTimestamp).UtcDateTime)); return res; } } diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index 40d538bfc0..ffc87f3f25 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -49,11 +49,50 @@ public enum Farm : uint } [ZoneModuleInfo(BossModuleInfo.Maturity.WIP, 639)] -public class Hydatos(WorldState ws) : ZoneModule(ws) +public class Hydatos : ZoneModule { private readonly EurekaConfig _eurekaConfig = Service.Config.Get(); private readonly HydatosConfig _hydatosConfig = Service.Config.Get(); + private static readonly Dictionary FateIDs = new() + { + [1412] = HydatosConfig.Farm.Khalamari, + [1413] = HydatosConfig.Farm.Stego, + [1414] = HydatosConfig.Farm.Molech, + [1415] = HydatosConfig.Farm.Piasa, + [1416] = HydatosConfig.Farm.Frostmane, + [1417] = HydatosConfig.Farm.Daphne, + [1418] = HydatosConfig.Farm.Golde, + [1419] = HydatosConfig.Farm.Leuke, + [1420] = HydatosConfig.Farm.Barong, + [1421] = HydatosConfig.Farm.Ceto, + [1423] = HydatosConfig.Farm.PW, + }; + + private readonly EventSubscriptions _subscriptions; + + public Hydatos(WorldState ws) : base(ws) + { + _subscriptions = new( + ws.Client.FateInfo.Subscribe(OnFateSpawned) + ); + } + + protected override void Dispose(bool disposing) + { + _subscriptions.Dispose(); + base.Dispose(disposing); + } + + private void OnFateSpawned(ClientState.OpFateInfo fate) + { + if (FateIDs.TryGetValue(fate.FateId, out var farm) && farm == _hydatosConfig.CurrentFarmTarget) + { + _hydatosConfig.CurrentFarmTarget = HydatosConfig.Farm.None; + _hydatosConfig.Modified.Fire(); + } + } + public override void CalculateAIHints(int playerSlot, Actor player, AIHints hints) { hints.ForbiddenZones.RemoveAll(z => World.Actors.Find(z.Source) is Actor src && ShouldIgnore(src, player)); diff --git a/BossMod/Replay/ReplayParserLog.cs b/BossMod/Replay/ReplayParserLog.cs index cf6ee45294..ae1042514d 100644 --- a/BossMod/Replay/ReplayParserLog.cs +++ b/BossMod/Replay/ReplayParserLog.cs @@ -292,7 +292,6 @@ private ReplayParserLog(Input input, ReplayBuilder builder) [new("DIRU"u8)] = ParseDirectorUpdate, [new("ENVC"u8)] = ParseEnvControl, [new("SLOG"u8)] = ParseSystemLog, - [new("FATE"u8)] = ParseFateInfo, [new("WAY+"u8)] = () => ParseWaymarkChange(true), [new("WAY-"u8)] = () => ParseWaymarkChange(false), [new("ACT+"u8)] = ParseActorCreate, @@ -355,6 +354,7 @@ private ReplayParserLog(Input input, ReplayBuilder builder) [new("CLFT"u8)] = ParseClientFocusTarget, [new("CLFD"u8)] = ParseClientForcedMovementDirection, [new("CLKV"u8)] = ParseClientContentKVData, + [new("FATE"u8)] = ParseClientFateInfo, [new("DDPG"u8)] = ParseDeepDungeonProgress, [new("DDMP"u8)] = ParseDeepDungeonMap, [new("DDPT"u8)] = ParseDeepDungeonParty, @@ -462,7 +462,7 @@ private WorldState.OpSystemLogMessage ParseSystemLog() return new(id, args); } - private WorldState.OpFateInfo ParseFateInfo() => new(_input.ReadUInt(false), new(_input.ReadLong())); + private ClientState.OpFateInfo ParseClientFateInfo() => new(_input.ReadUInt(false), new(_input.ReadLong())); private WaymarkState.OpWaymarkChange ParseWaymarkChange(bool set) => new(_version < 10 ? Enum.Parse(_input.ReadString()) : (Waymark)_input.ReadByte(false), set ? _input.ReadVec3() : null); From 1e6a46f723c530149a1d3539d8a6f8ecb2abdd48 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 19 Feb 2025 15:54:35 -0500 Subject: [PATCH 48/56] improve targeting behavior --- BossMod/Modules/Stormblood/Foray/Hydatos.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index ffc87f3f25..c6af26fc08 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -97,8 +97,6 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint { hints.ForbiddenZones.RemoveAll(z => World.Actors.Find(z.Source) is Actor src && ShouldIgnore(src, player)); - var shouldSetTarget = true; // !player.InCombat && player.TargetID == 0; - var farmOID = (uint)_hydatosConfig.CurrentFarmTarget; var farmMax = _eurekaConfig.MaxPullCount; var farmRange = _eurekaConfig.MaxPullDistance; @@ -113,6 +111,8 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint e.Priority = 0; + var shouldSetTarget = !e.Actor.InCombat; + if (shouldSetTarget && (hints.ForcedTarget == null || (hints.ForcedTarget.Position - player.Position).LengthSq() > (e.Actor.Position - player.Position).LengthSq())) hints.ForcedTarget = e.Actor; } From 00206e4b09241bb507bc31660a781657a762a49b Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:35:02 -0500 Subject: [PATCH 49/56] support prio override for pot --- BossMod/Autorotation/Standard/xan/Melee/MNK.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs index 574c9db081..0fbd2d95e8 100644 --- a/BossMod/Autorotation/Standard/xan/Melee/MNK.cs +++ b/BossMod/Autorotation/Standard/xan/Melee/MNK.cs @@ -158,7 +158,8 @@ public static RotationModuleDefinition Definition() def.Define(Track.Potion).As("Pot", uiPriority: 59) .AddOption(PotionStrategy.Manual, "Do not automatically use") .AddOption(PotionStrategy.PreBuffs, "Use ~4 GCDs before raid buff window") - .AddOption(PotionStrategy.Now, "Use ASAP"); + .AddOption(PotionStrategy.Now, "Use ASAP") + .AddAssociatedAction(ActionDefinitions.IDPotionStr); def.Define(Track.Engage).As("Engage", uiPriority: 49) .AddOption(EngageStrategy.TC, "Thunderclap to target") @@ -543,14 +544,16 @@ private void QueuePB(StrategyValues strategy, Enemy? primaryTarget) private void OGCD(StrategyValues strategy, Enemy? primaryTarget) { - switch (strategy.Option(Track.Potion).As()) + var potionTrack = strategy.Option(Track.Potion); + var potionPrio = potionTrack.Priority(ActionQueue.Priority.Low + 100 + (float)OGCDPriority.Potion); + switch (potionTrack.As()) { case PotionStrategy.Now: - Potion(); + Potion(potionPrio); break; case PotionStrategy.PreBuffs: if (HaveTarget && CanWeave(AID.Brotherhood, 4)) - Potion(); + Potion(potionPrio); break; } @@ -713,7 +716,7 @@ private void WindsReply(StrategyValues strategy) private float DesiredFireWindow => GCDLength * 10; private float EarliestRoF(float estimatedDelay) => MathF.Max(estimatedDelay + 0.8f, 20.6f - DesiredFireWindow); - private void Potion() => Hints.ActionsToExecute.Push(ActionDefinitions.IDPotionStr, Player, ActionQueue.Priority.Low + 100 + (float)OGCDPriority.Potion); + private void Potion(float priority) => Hints.ActionsToExecute.Push(ActionDefinitions.IDPotionStr, Player, priority); private (bool Use, bool LateWeave) ShouldRoF(StrategyValues strategy, int extraGCDs = 0) { From 9ce7a07ef680d3ddc3713bea8c23fb4b37508e35 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:35:17 -0500 Subject: [PATCH 50/56] make hydatos dropdown work more gooder --- BossMod/Modules/Stormblood/Foray/Hydatos.cs | 113 ++++++++++++-------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index c6af26fc08..a405de56fe 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -10,42 +10,76 @@ public class EurekaConfig : ConfigNode public float MaxPullDistance = 30f; [PropertyDisplay("Max number of mobs to pull at once (0 for no limit)")] - [PropertySlider(0, 30)] + [PropertySlider(0, 30, Speed = 0.1f)] public int MaxPullCount = 10; } [ConfigDisplay(Name = "Hydatos", Parent = typeof(EurekaConfig))] public class HydatosConfig : ConfigNode { - public enum Farm : uint + public NotoriousMonster CurrentFarmTarget = NotoriousMonster.None; +} + +public enum NotoriousMonster : uint +{ + None, + [PropertyDisplay("Khalamari (Xzomit)")] + Khalamari, + [PropertyDisplay("Stegodon (Hydatos Primelephas)")] + Stego, + [PropertyDisplay("Molech (Val Nullchu)")] + Molech, + [PropertyDisplay("Piasa (Vivid Gastornis)")] + Piasa, + [PropertyDisplay("Frostmane (Northern Tiger)")] + Frostmane, + [PropertyDisplay("Daphne (Dark Void Monk)")] + Daphne, + [PropertyDisplay("Goldemar (Hydatos Wraith)")] + Golde, + [PropertyDisplay("Leuke (Tigerhawk)")] + Leuke, + [PropertyDisplay("Barong (Laboratory Lion)")] + Barong, + [PropertyDisplay("Ceto (Hydatos Delphyne)")] + Ceto, + [PropertyDisplay("PW (Crystal Claw)")] + PW +} + +static class NMExtensions +{ + public static uint GetMobID(this NotoriousMonster opt) => opt switch { - [PropertyDisplay("")] - None, - [PropertyDisplay("Khalamari (Xzomit)")] - Khalamari = 0x26AB, - [PropertyDisplay("Stegodon (Hydatos Primelephas)")] - Stego = 0x26AF, - [PropertyDisplay("Molech (Val Nullchu)")] - Molech = 0x26B2, - [PropertyDisplay("Piasa (Vivid Gastornis)")] - Piasa = 0x26B3, - [PropertyDisplay("Frostmane (Northern Tiger)")] - Frostmane = 0x26B8, - [PropertyDisplay("Daphne (Dark Void Monk)")] - Daphne = 0x26B9, - [PropertyDisplay("Goldemar (Hydatos Wraith)")] - Golde = 0x26E6, - [PropertyDisplay("Leuke (Tigerhawk)")] - Leuke = 0x26C0, - [PropertyDisplay("Barong (Laboratory Lion)")] - Barong = 0x26C2, - [PropertyDisplay("Ceto (Hydatos Delphyne)")] - Ceto = 0x26C5, - [PropertyDisplay("PW (Crystal Claw)")] - PW = 0x26CA - } + NotoriousMonster.Khalamari => 0x26AB, + NotoriousMonster.Stego => 0x26AF, + NotoriousMonster.Molech => 0x26B2, + NotoriousMonster.Piasa => 0x26B3, + NotoriousMonster.Frostmane => 0x26B8, + NotoriousMonster.Daphne => 0x26B9, + NotoriousMonster.Golde => 0x26E6, + NotoriousMonster.Leuke => 0x26C0, + NotoriousMonster.Barong => 0x26C2, + NotoriousMonster.Ceto => 0x26C5, + NotoriousMonster.PW => 0x26CA, + _ => 0 + }; - public Farm CurrentFarmTarget = Farm.None; + public static uint GetFateID(this NotoriousMonster opt) => opt switch + { + NotoriousMonster.Khalamari => 1412, + NotoriousMonster.Stego => 1413, + NotoriousMonster.Molech => 1414, + NotoriousMonster.Piasa => 1415, + NotoriousMonster.Frostmane => 1416, + NotoriousMonster.Daphne => 1417, + NotoriousMonster.Golde => 1418, + NotoriousMonster.Leuke => 1419, + NotoriousMonster.Barong => 1420, + NotoriousMonster.Ceto => 1421, + NotoriousMonster.PW => 1423, + _ => 0 + }; } [ZoneModuleInfo(BossModuleInfo.Maturity.WIP, 639)] @@ -54,21 +88,6 @@ public class Hydatos : ZoneModule private readonly EurekaConfig _eurekaConfig = Service.Config.Get(); private readonly HydatosConfig _hydatosConfig = Service.Config.Get(); - private static readonly Dictionary FateIDs = new() - { - [1412] = HydatosConfig.Farm.Khalamari, - [1413] = HydatosConfig.Farm.Stego, - [1414] = HydatosConfig.Farm.Molech, - [1415] = HydatosConfig.Farm.Piasa, - [1416] = HydatosConfig.Farm.Frostmane, - [1417] = HydatosConfig.Farm.Daphne, - [1418] = HydatosConfig.Farm.Golde, - [1419] = HydatosConfig.Farm.Leuke, - [1420] = HydatosConfig.Farm.Barong, - [1421] = HydatosConfig.Farm.Ceto, - [1423] = HydatosConfig.Farm.PW, - }; - private readonly EventSubscriptions _subscriptions; public Hydatos(WorldState ws) : base(ws) @@ -86,9 +105,9 @@ protected override void Dispose(bool disposing) private void OnFateSpawned(ClientState.OpFateInfo fate) { - if (FateIDs.TryGetValue(fate.FateId, out var farm) && farm == _hydatosConfig.CurrentFarmTarget) + if (_hydatosConfig.CurrentFarmTarget.GetFateID() == fate.FateId) { - _hydatosConfig.CurrentFarmTarget = HydatosConfig.Farm.None; + _hydatosConfig.CurrentFarmTarget = NotoriousMonster.None; _hydatosConfig.Modified.Fire(); } } @@ -97,7 +116,7 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint { hints.ForbiddenZones.RemoveAll(z => World.Actors.Find(z.Source) is Actor src && ShouldIgnore(src, player)); - var farmOID = (uint)_hydatosConfig.CurrentFarmTarget; + var farmOID = _hydatosConfig.CurrentFarmTarget.GetMobID(); var farmMax = _eurekaConfig.MaxPullCount; var farmRange = _eurekaConfig.MaxPullDistance; @@ -135,7 +154,7 @@ private bool ShouldIgnore(Actor caster, Actor player) public override void DrawExtra() { - if (UICombo.Enum("Prep mob", ref _hydatosConfig.CurrentFarmTarget)) + if (UICombo.Enum("Prep", ref _hydatosConfig.CurrentFarmTarget)) _hydatosConfig.Modified.Fire(); ImGui.SetNextItemWidth(200); From 74d52b881ae799d48452a117537b6a9118096b20 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:35:26 -0500 Subject: [PATCH 51/56] use tank mits when attacked --- BossMod/Autorotation/Standard/xan/AI/Tank.cs | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/BossMod/Autorotation/Standard/xan/AI/Tank.cs b/BossMod/Autorotation/Standard/xan/AI/Tank.cs index 5d26a0037f..e9ac6e5262 100644 --- a/BossMod/Autorotation/Standard/xan/AI/Tank.cs +++ b/BossMod/Autorotation/Standard/xan/AI/Tank.cs @@ -202,21 +202,21 @@ private void AutoProtect() private void AutoMit() { - if (EnemiesAutoingMe.Any()) - { - if (Player.PredictedHPRatio < 0.8) - { - var delay = 0f; - if (JobActions.ShortMit.ID == ActionID.MakeSpell(WAR.AID.RawIntuition)) - delay = GCD - 0.8f; - Hints.ActionsToExecute.Push(JobActions.ShortMit.ID, Player, ActionQueue.Priority.Minimal, delay: delay); - } + if (Player.PredictedHPRaw == Player.HPMP.CurHP && !Player.InCombat) + return; - if (Player.PredictedHPRatio < 0.6) - // set arbitrary deadline to 1 second in the future - UseOneMit(1); + if (Player.PredictedHPRatio < 0.8) + { + var delay = 0f; + if (JobActions.ShortMit.ID == ActionID.MakeSpell(WAR.AID.RawIntuition)) + delay = GCD - 0.8f; + Hints.ActionsToExecute.Push(JobActions.ShortMit.ID, Player, ActionQueue.Priority.Minimal, delay: delay); } + if (Player.PredictedHPRatio < 0.6) + // set arbitrary deadline to 1 second in the future + UseOneMit(1); + // TODO figure out how consistent this is or if we should use predictively instead if (Player.PredictedHPRaw <= 0) Hints.ActionsToExecute.Push(JobActions.Invuln.ID, Player, ActionQueue.Priority.VeryHigh); From f1e9118363af79476303e5efaab81f6c07ee35ed Mon Sep 17 00:00:00 2001 From: ace Date: Wed, 19 Feb 2025 20:34:19 -0800 Subject: [PATCH 52/56] stuff --- .../Standard/akechi/AkechiTools.cs | 35 +++++++---- .../Standard/akechi/DPS/AkechiBLM.cs | 4 +- .../Standard/akechi/Tank/AkechiDRK.cs | 6 +- .../Standard/akechi/Tank/AkechiGNB.cs | 60 +++++++++---------- .../Standard/akechi/Tank/AkechiWAR.cs | 10 +++- 5 files changed, 64 insertions(+), 51 deletions(-) diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 99d18d2017..21aafa6744 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -19,12 +19,18 @@ public enum SharedTrack } ///

-/// AOEStrategy enum for tracking single-target and AOE strategies. +/// AOEStrategy enum for tracking single-target and AOE strategies. +/// NOTE: For jobs with combos that have no relative combo timer (e.g. BLM), and are essentially the same function. /// public enum AOEStrategy { - /// Executes the most optimal rotation automatically. - Automatic, + /// Executes the most optimal rotation automatically.
+ /// Finishes any combo if currently inside one.
+ AutoFinish, + + /// Executes the most optimal rotation automatically.
+ /// Breaks any combo if currently inside one.
+ AutoBreak, /// Forces execution of the single-target rotation. ForceST, @@ -334,9 +340,9 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) /// The user's specified Action ID being checked. protected float ChargeCD(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ReadyIn(World.Client.Cooldowns, World.Client.DutyActions) : float.MaxValue; - /// Checks if action is ready to be used based on if it's Unlocked and its charge cooldown timer. + /// Checks if action is ready to be used based on if it's Unlocked and its total cooldown timer. /// The user's specified Action ID being checked. - protected bool ActionReady(AID aid) => Unlocked(aid) && ChargeCD(aid) < 0.6f; + protected bool ActionReady(AID aid) => Unlocked(aid) && !IsOnCooldown(aid); /// Checks if action has any charges remaining. /// The user's specified Action ID being checked. @@ -526,16 +532,15 @@ protected void GetPvETarget(StrategyValues strategy, ref Enemy? primaryTarget, f if (primaryTarget?.Actor == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) { var AOEStrat = strategy.Option(SharedTrack.AOE).As(); - if (AOEStrat == AOEStrategy.Automatic) + if (AOEStrat is AOEStrategy.AutoFinish or AOEStrategy.AutoBreak) { - primaryTarget = Hints.PriorityTargets - .FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); + primaryTarget = Hints.PriorityTargets.FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); } } } /// Attempts to select the most suitable PvP target automatically, prioritizing the target with the lowest HP percentage within range. - /// NOTE: This function is solely used for finding the best PvP target without having to manually scan & click on other targets. Please use appropriately. + /// NOTE: This function is solely used for finding the best PvP target without having to manually scan and click on other targets. Please use appropriately.
/// The user's current target. /// The max range to consider a new target. protected void GetPvPTarget(ref Enemy? primaryTarget, float range) @@ -790,9 +795,10 @@ static class ModuleExtensions public static RotationModuleDefinition.ConfigRef DefineAOE(this RotationModuleDefinition res) { return res.Define(SharedTrack.AOE).As("AOE", uiPriority: 300) - .AddOption(AOEStrategy.Automatic, "Auto", "Automatically execute optimal rotation based on targets", supportedTargets: ActionTargets.Hostile) + .AddOption(AOEStrategy.AutoFinish, "Auto (Finish combo)", "Automatically execute optimal rotation based on targets; finishes combo if possible", supportedTargets: ActionTargets.Hostile) + .AddOption(AOEStrategy.AutoBreak, "Auto (Break combo)", "Automatically execute optimal rotation based on targets; breaks combo if necessary", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceST, "ForceST", "Force-execute Single Target", supportedTargets: ActionTargets.Hostile) - .AddOption(AOEStrategy.ForceAOE, "ForceAOE", "Force-execute AOE rotation", supportedTargets: ActionTargets.Hostile); + .AddOption(AOEStrategy.ForceAOE, "ForceAOE", "Force-execute AOE rotation", supportedTargets: ActionTargets.Hostile | ActionTargets.Self); } /// Defines our shared Hold strategies. @@ -859,8 +865,11 @@ public static RotationModuleDefinition.ConfigRef DefineOGCDA global helper for easily retrieving the user's Rotation strategy. See for more details.
public static AOEStrategy Rotation(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As(); - /// A global helper for automatically executing the best optimal rotation. See for more details. - public static bool Automatic(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.Automatic; + /// A global helper for automatically executing the best optimal rotation; finishes combo if possible. See for more details. + public static bool AutoFinish(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.AutoFinish; + + /// A global helper for automatically executing the best optimal rotation; breaks combo if necessary. See for more details. + public static bool AutoBreak(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.AutoBreak; /// A global helper for force-executing the single-target rotation. See for more details. public static bool ForceST(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.ForceST; diff --git a/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs b/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs index 0373f55bf2..ade6cef995 100644 --- a/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs +++ b/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs @@ -400,7 +400,7 @@ movingOption is CastingOption.Forbid && SelfStatusLeft(SID.Firestarter, 30) is < 25 and not 0 || //or can use F3P Unlocked(TraitID.EnhancedAstralFire) && MP is < 1600 and not 0)) //instant cast Despair { - if (strategy.Automatic()) + if (strategy.AutoFinish() || strategy.AutoBreak()) BestRotation(TargetChoice(AOE) ?? BestSplashTarget?.Actor); if (strategy.ForceST()) BestST(TargetChoice(AOE) ?? primaryTarget?.Actor); @@ -413,7 +413,7 @@ movingOption is CastingOption.Forbid && //Thunder if (ShouldUseThunder(BestSplashTarget?.Actor, thunderStrat)) //if Thunder should be used based on strategy { - if (strategy.Automatic()) + if (strategy.AutoFinish() || strategy.AutoBreak()) QueueGCD(BestThunder, TargetChoice(thunder) ?? (ShouldUseAOE ? BestSplashTargets?.Actor : BestDOTTarget?.Actor), thunderLeft <= 3 ? GCDPriority.NeedDOT : diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs index 77df668606..275424b0ef 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs @@ -235,10 +235,14 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) #region Full Rotation Execution #region Standard Rotations - if (strategy.Automatic()) + if (strategy.AutoFinish()) QueueGCD(BestRotation(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, GCDPriority.Standard); + if (strategy.AutoBreak()) + QueueGCD(ShouldUseAOE ? AOE() : ST(), + TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, + GCDPriority.Standard); if (strategy.ForceST()) QueueGCD(ST(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs index 608eac2593..3c22305764 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs @@ -167,7 +167,7 @@ public enum OGCDPriority #region Upgrade Paths private AID BestZone => Unlocked(AID.BlastingZone) ? AID.BlastingZone : AID.DangerZone; - private AID BestCartSpender => ShouldUseFC ? BestFatedCircle : canBS ? AID.BurstStrike : BestRotation(); + private AID BestCartSpender => ShouldUseAOE ? BestFatedCircle : canBS ? AID.BurstStrike : BestRotation(); private AID BestFatedCircle => Unlocked(AID.FatedCircle) ? AID.FatedCircle : AID.BurstStrike; private AID BestContinuation => hasRaze ? AID.FatedBrand : hasBlast ? AID.Hypervelocity : hasGouge ? AID.EyeGouge : hasTear ? AID.AbdomenTear : hasRip ? AID.JugularRip : AID.Continuation; #endregion @@ -200,7 +200,6 @@ public enum OGCDPriority private bool canContinue; //Checks if Continuation is completely available private bool canReign; //Checks if Reign of Beasts & its combo chain are completely available private bool ShouldUseAOE; //Checks if AOE rotation should be used - private bool ShouldUseFC; //Checks if Fated Circle should be used private int NumSplashTargets; private Enemy? BestSplashTargets; private Enemy? BestSplashTarget; @@ -225,21 +224,20 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) hasTear = PlayerHasEffect(SID.ReadyToTear, 10f) && !LastActionUsed(AID.AbdomenTear); //Checks for Ready To Tear buff hasGouge = PlayerHasEffect(SID.ReadyToGouge, 10f) && !LastActionUsed(AID.EyeGouge); //Checks for Ready To Gouge buff inOdd = bfCD is <= 90 and >= 30; //Checks if we are in an odd-minute window - ShouldUseAOE = Unlocked(TraitID.MeleeMastery) ? ShouldUseAOECircle(5).OnThreeOrMore : ShouldUseAOECircle(5).OnTwoOrMore; - ShouldUseFC = ShouldUseAOECircle(5).OnTwoOrMore; //Determine if we should use Fated Circle + ShouldUseAOE = ShouldUseAOECircle(5).OnTwoOrMore; (BestSplashTargets, NumSplashTargets) = GetBestTarget(primaryTarget, 3, IsSplashTarget); BestSplashTarget = Unlocked(AID.ReignOfBeasts) && NumSplashTargets > 1 ? BestSplashTargets : primaryTarget; #region Minimal Requirements - canNM = TotalCD(AID.NoMercy) < 1; //No Mercy conditions + canNM = ActionReady(AID.NoMercy); //No Mercy conditions canBS = Unlocked(AID.BurstStrike) && Ammo > 0; //Burst Strike conditions; -1 Ammo ST - canGF = Unlocked(AID.GnashingFang) && ActionReady(AID.GnashingFang) && Ammo > 0; //Gnashing Fang conditions; -1 Ammo ST + canGF = ActionReady(AID.GnashingFang) && Ammo > 0; //Gnashing Fang conditions; -1 Ammo ST canFC = Unlocked(AID.FatedCircle) && Ammo > 0; //Fated Circle conditions; -1 Ammo AOE - canDD = Unlocked(AID.DoubleDown) && ActionReady(AID.DoubleDown) && Ammo > 0; //Double Down conditions; -1 Ammo AOE - canBF = Unlocked(AID.Bloodfest) && ActionReady(AID.Bloodfest); //Bloodfest conditions; +all Ammo (must have target) - canZone = Unlocked(AID.DangerZone) && ActionReady(AID.DangerZone); //Zone conditions - canBreak = hasBreak && Unlocked(AID.SonicBreak); //Sonic Break conditions - canBow = Unlocked(AID.BowShock) && ActionReady(AID.BowShock); //Bow Shock conditions + canDD = ActionReady(AID.DoubleDown) && Ammo > 0; //Double Down conditions; -1 Ammo AOE + canBF = ActionReady(AID.Bloodfest); //Bloodfest conditions; +all Ammo (must have target) + canZone = ActionReady(AID.DangerZone); //Zone conditions + canBreak = Unlocked(AID.SonicBreak) && hasBreak; //Sonic Break conditions + canBow = ActionReady(AID.BowShock); //Bow Shock conditions canContinue = Unlocked(AID.Continuation); //Continuation conditions canReign = Unlocked(AID.ReignOfBeasts) && hasReign; //Reign of Beasts conditions #endregion @@ -302,34 +300,34 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) //TODO: refactor this if (AOEStrategy == AOEStrategy.GenerateDowntime) //if Generate Downtime option is selected { - if (DowntimeIn == GCD * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 6 && Ammo == 0) //if 6 GCDs until downtime & has 0 cartridges + if (DowntimeIn == SkSGCDLength * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 6 && Ammo == 0) //if 6 GCDs until downtime & has 0 cartridges QueueGCD(AID.DemonSlice, //queue Demon Slice Player, //on Self (no target needed) GCDPriority.ForcedCombo); //with priority for forced GCDs - if (DowntimeIn == GCD * 3 && Ammo == 2 || //if 3 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 5 && Ammo == 1 || //if 5 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 8 && Ammo == 0 || //if 8 GCDs until downtime & has 0 cartridges - DowntimeIn == GCD * 9 && Ammo == 0) //if 9 GCDs until downtime & has 0 cartridges + if (DowntimeIn == SkSGCDLength * 3 && Ammo == 2 || //if 3 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 5 && Ammo == 1 || //if 5 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 8 && Ammo == 0 || //if 8 GCDs until downtime & has 0 cartridges + DowntimeIn == SkSGCDLength * 9 && Ammo == 0) //if 9 GCDs until downtime & has 0 cartridges QueueGCD(AID.KeenEdge, //queue Keen Edge primaryTarget?.Actor, //on the primary target GCDPriority.ForcedCombo); //with priority for forced GCDs if (ComboLastMove == AID.DemonSlice && //if last move was Demon Slice (DowntimeIn == GCD && Ammo == 2 || //if 1 GCD until downtime & has 2 cartridges - DowntimeIn == GCD * 3 && Ammo == 1 || //if 3 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 5 && Ammo == 0)) //if 5 GCDs until downtime & has 0 cartridges + DowntimeIn == SkSGCDLength * 3 && Ammo == 1 || //if 3 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 5 && Ammo == 0)) //if 5 GCDs until downtime & has 0 cartridges QueueGCD(AID.DemonSlaughter, //queue Demon Slaughter Player, //on Self (no target needed) GCDPriority.ForcedCombo); //with priority for forced GCDs if (ComboLastMove == AID.KeenEdge && //if last move was Keen Edge - (DowntimeIn == GCD * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 7 && Ammo == 2 || //if 7 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 8 && Ammo == 2)) //if 8 GCDs until downtime & has 2 cartridges + (DowntimeIn == SkSGCDLength * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 7 && Ammo == 2 || //if 7 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 8 && Ammo == 2)) //if 8 GCDs until downtime & has 2 cartridges QueueGCD(AID.BrutalShell, //queue Brutal Shell primaryTarget?.Actor, //on the primary target GCDPriority.ForcedCombo); //with priority for forced GCDs @@ -337,8 +335,8 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) if (ComboLastMove == AID.BrutalShell) //if last move was Brutal Shell { if (DowntimeIn == GCD && (Ammo == 2 || Ammo == 3) || //if 1 GCD until downtime & has 2 or 3 cartridges - DowntimeIn == GCD * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 7 && Ammo == 0) //if 7 GCDs until downtime & has 0 cartridges + DowntimeIn == SkSGCDLength * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 7 && Ammo == 0) //if 7 GCDs until downtime & has 0 cartridges QueueGCD(AID.SolidBarrel, //queue Solid Barrel primaryTarget?.Actor, //on the primary target GCDPriority.ForcedCombo); //with priority for forced GCDs @@ -540,10 +538,8 @@ reignStrat is ReignStrategy.ForceLion private bool ShouldUseNoMercy(NoMercyStrategy strategy, Actor? target) => strategy switch { NoMercyStrategy.Automatic => Player.InCombat && target != null && canNM && - ((Unlocked(AID.DoubleDown) && (inOdd && Ammo >= 2 || !inOdd && Ammo < 3)) || - (!Unlocked(AID.DoubleDown) && CanQuarterWeaveIn && - ((Unlocked(AID.Bloodfest) && Ammo >= 1) || (!Unlocked(AID.Bloodfest) && canGF) || - !Unlocked(AID.GnashingFang)))), + ((Unlocked(AID.DoubleDown) && (inOdd && Ammo >= 2 || !inOdd && Ammo < 3)) || //90+ + (!Unlocked(AID.DoubleDown) && CanQuarterWeaveIn && Ammo >= 1)), //2-89 NoMercyStrategy.Force => canNM, NoMercyStrategy.ForceW => canNM && CanWeaveIn, NoMercyStrategy.ForceQW => canNM && CanQuarterWeaveIn, @@ -630,9 +626,9 @@ reignStrat is ReignStrategy.ForceLion private bool ShouldSpendCarts(CartridgeStrategy strategy, Actor? target) => strategy switch { CartridgeStrategy.Automatic => Player.InCombat && target != null && - (ShouldUseFC ? (In5y(target) && canFC) : (In3y(target) && canBS)) && + ((ShouldUseAOE ? (In5y(target) && canFC) : (In3y(target) && canBS)) && (hasNM || (!(bfCD is <= 90 and >= 30) && nmCD < 1 && Ammo == 3)) || - Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice, + (Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice)), _ => false }; private bool ShouldUseSonicBreak(SonicBreakStrategy strategy, Actor? target) => strategy switch diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs index 1f2ab1dbcf..4fe6f3c4ac 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs @@ -150,12 +150,12 @@ public enum GCDPriority { None = 0, Gauge = 300, - PrimalRuination = 400, DelayFC = 390, Standard = 400, FlexibleFC = 470, FlexibleIR = 490, PrimalRend = 500, + PrimalRuination = 500, BuffedFC = 550, BuffedIR = 570, NeedTempest = 650, @@ -350,10 +350,14 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) #region Full Rotation Execution #region Standard Rotations - if (strategy.Automatic()) + if (strategy.AutoFinish()) QueueGCD(BestRotation(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, - IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.ForcedCombo); + IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.Standard); + if (strategy.AutoBreak()) + QueueGCD(ShouldUseAOE ? AOE() : ST(), + TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, + IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.Standard); if (strategy.ForceST()) QueueGCD(ST(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, From b577ebfb1c91d4f4a9e07bccb225072c4c991d06 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:07:09 -0500 Subject: [PATCH 53/56] add shared data consumer code for navmesh interop --- BossMod/Framework/MovementOverride.cs | 16 ++++++++++++++-- BossMod/Framework/Plugin.cs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/BossMod/Framework/MovementOverride.cs b/BossMod/Framework/MovementOverride.cs index a76ed0016b..063f476249 100644 --- a/BossMod/Framework/MovementOverride.cs +++ b/BossMod/Framework/MovementOverride.cs @@ -1,4 +1,5 @@ using Dalamud.Game.Config; +using Dalamud.Plugin; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.System.Input; @@ -28,10 +29,12 @@ public sealed unsafe class MovementOverride : IDisposable public WDir UserMove { get; private set; } // unfiltered movement direction, as read from input public WDir ActualMove { get; private set; } // actual movement direction, as of last input read + private readonly IDalamudPluginInterface _dalamud; private readonly ActionTweaksConfig _tweaksConfig = Service.Config.Get(); private bool _movementBlocked; private bool? _forcedControlState; private bool _legacyMode; + private bool[]? _navmeshPathIsRunning; public bool IsMoving() => ActualMove != default; public bool IsMoveRequested() => UserMove != default; @@ -67,8 +70,10 @@ public bool MovementBlocked private delegate byte MoveControlIsInputActiveDelegate(void* self, byte inputSourceFlags); private readonly HookAddress _mcIsInputActiveHook; - public MovementOverride() + public MovementOverride(IDalamudPluginInterface dalamud) { + _dalamud = dalamud; + var rmiWalkIsInputEnabled1Addr = Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 38 43 3C"); var rmiWalkIsInputEnabled2Addr = Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 84 C0 75 03 88 47 3F"); Service.Log($"RMIWalkIsInputEnabled1 address: 0x{rmiWalkIsInputEnabled1Addr:X}"); @@ -86,6 +91,7 @@ public MovementOverride() public void Dispose() { + _dalamud.RelinquishData("vnav.PathIsRunning"); Service.GameConfig.UiControlChanged -= OnConfigChanged; _movementBlocked = false; _mcIsInputActiveHook.Dispose(); @@ -93,13 +99,19 @@ public void Dispose() _rmiFlyHook.Dispose(); } + private bool NavmeshActive() + { + _navmeshPathIsRunning ??= _dalamud.GetData("vnav.PathIsRunning"); + return _navmeshPathIsRunning != null && _navmeshPathIsRunning[0]; + } + private void RMIWalkDetour(void* self, float* sumLeft, float* sumForward, float* sumTurnLeft, byte* haveBackwardOrStrafe, byte* a6, byte bAdditiveUnk) { _forcedControlState = null; _rmiWalkHook.Original(self, sumLeft, sumForward, sumTurnLeft, haveBackwardOrStrafe, a6, bAdditiveUnk); // TODO: we really need to introduce some extra checks that PlayerMoveController::readInput does - sometimes it skips reading input, and returning something non-zero breaks stuff... - var movementAllowed = bAdditiveUnk == 0 && _rmiWalkIsInputEnabled1(self) && _rmiWalkIsInputEnabled2(self); + var movementAllowed = bAdditiveUnk == 0 && _rmiWalkIsInputEnabled1(self) && _rmiWalkIsInputEnabled2(self) && !NavmeshActive(); var misdirectionMode = PlayerHasMisdirection(); if (!movementAllowed && misdirectionMode) { diff --git a/BossMod/Framework/Plugin.cs b/BossMod/Framework/Plugin.cs index 161eb08f99..5d1bc6312b 100644 --- a/BossMod/Framework/Plugin.cs +++ b/BossMod/Framework/Plugin.cs @@ -79,7 +79,7 @@ public unsafe Plugin(IDalamudPluginInterface dalamud, ICommandManager commandMan _bossmod = new(_ws); _zonemod = new(_ws); _hintsBuilder = new(_ws, _bossmod, _zonemod); - _movementOverride = new(); + _movementOverride = new(dalamud); _amex = new(_ws, _hints, _movementOverride); _wsSync = new(_ws, _amex); _rotation = new(_rotationDB, _bossmod, _hints); From ea4a2679c083ee674abaafa8862bca7dd8b6ed88 Mon Sep 17 00:00:00 2001 From: xanunderscore <149614526+xanunderscore@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:18:32 -0500 Subject: [PATCH 54/56] ok then --- BossMod/Framework/MovementOverride.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BossMod/Framework/MovementOverride.cs b/BossMod/Framework/MovementOverride.cs index 063f476249..fed3bd57e9 100644 --- a/BossMod/Framework/MovementOverride.cs +++ b/BossMod/Framework/MovementOverride.cs @@ -101,7 +101,9 @@ public void Dispose() private bool NavmeshActive() { - _navmeshPathIsRunning ??= _dalamud.GetData("vnav.PathIsRunning"); + if (_navmeshPathIsRunning == null && _dalamud.TryGetData("vnav.PathIsRunning", out var data)) + _navmeshPathIsRunning = data; + return _navmeshPathIsRunning != null && _navmeshPathIsRunning[0]; } From 5e154b1c704b77fcf5d36c447fb772f4d657eeb3 Mon Sep 17 00:00:00 2001 From: ace Date: Thu, 20 Feb 2025 14:35:39 -0800 Subject: [PATCH 55/56] Requested Changes --- .../Autorotation/Utility/ClassDNCUtility.cs | 24 +------- .../Autorotation/Utility/ClassDRGUtility.cs | 24 +------- .../Autorotation/Utility/ClassPCTUtility.cs | 23 +------- .../Autorotation/Utility/ClassPLDUtility.cs | 29 +--------- .../Autorotation/Utility/ClassRPRUtility.cs | 58 +------------------ 5 files changed, 6 insertions(+), 152 deletions(-) diff --git a/BossMod/Autorotation/Utility/ClassDNCUtility.cs b/BossMod/Autorotation/Utility/ClassDNCUtility.cs index 19f2fc140f..f86c89668d 100644 --- a/BossMod/Autorotation/Utility/ClassDNCUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDNCUtility.cs @@ -2,9 +2,8 @@ public sealed class ClassDNCUtility(RotationModuleManager manager, Actor player) : RoleRangedUtility(manager, player) { - public enum Track { CuringWaltz = SharedTrack.Count, ShieldSamba, Improvisation, EnAvant } + public enum Track { CuringWaltz = SharedTrack.Count, ShieldSamba, Improvisation } public enum SambaOption { None, Use87, Use87IfNotActive, Use88, Use88IfNotActive } - public enum EnAvantStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DNC.AID.CrimsonLotus); @@ -25,14 +24,6 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Improvisation, "Improvisation", "Improv", 300, DNC.AID.Improvisation, 15); - res.Define(Track.EnAvant).As("En Avant", "EnAvant", 30) - .AddOption(EnAvantStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 50) - .AddOption(EnAvantStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 30, 0, ActionTargets.Self, 50) - .AddOption(EnAvantStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 30, 0, ActionTargets.Self, 50) - .AddOption(EnAvantStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 30, 0, ActionTargets.Self, 50) - .AddOption(EnAvantStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 30, 0, ActionTargets.Self, 50) - .AddAssociatedActions(DNC.AID.EnAvant); - return res; } @@ -53,18 +44,5 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, }; if (wantSamba) Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.ShieldSamba), Player, samba.Priority(), samba.Value.ExpireIn); - - var ea = strategy.Option(Track.EnAvant); - if (ea.As() != EnAvantStrategy.None) - { - var angle = ea.As() switch - { - EnAvantStrategy.CharacterBackward => Player.Rotation + 180.Degrees(), - EnAvantStrategy.CameraForward => World.Client.CameraAzimuth + 180.Degrees(), - EnAvantStrategy.CameraBackward => World.Client.CameraAzimuth, - _ => Player.Rotation - }; - Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.EnAvant), Player, ea.Priority(), ea.Value.ExpireIn, facingAngle: angle); - } } } diff --git a/BossMod/Autorotation/Utility/ClassDRGUtility.cs b/BossMod/Autorotation/Utility/ClassDRGUtility.cs index 63e558dfbd..bc6afba67f 100644 --- a/BossMod/Autorotation/Utility/ClassDRGUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDRGUtility.cs @@ -2,9 +2,8 @@ public sealed class ClassDRGUtility(RotationModuleManager manager, Actor player) : RoleMeleeUtility(manager, player) { - public enum Track { WingedGlide = SharedTrack.Count, ElusiveJump } + public enum Track { WingedGlide = SharedTrack.Count } public enum DashStrategy { None, GapClose, GapCloseHold1 } - public enum ElusiveStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DRG.AID.DragonsongDive); @@ -19,14 +18,6 @@ public static RotationModuleDefinition Definition() .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 84) .AddAssociatedActions(DRG.AID.WingedGlide); - res.Define(Track.ElusiveJump).As("Elusive Jump", "E.Jump", 30) - .AddOption(ElusiveStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 35) - .AddOption(ElusiveStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 30, 15, ActionTargets.Self, 35) - .AddOption(ElusiveStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 30, 15, ActionTargets.Self, 35) - .AddOption(ElusiveStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 30, 15, ActionTargets.Self, 35) - .AddOption(ElusiveStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 30, 15, ActionTargets.Self, 35) - .AddAssociatedActions(DRG.AID.ElusiveJump); - return res; } @@ -34,19 +25,6 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); - var ej = strategy.Option(Track.ElusiveJump); - if (ej.As() != ElusiveStrategy.None) - { - var angle = ej.As() switch - { - ElusiveStrategy.CharacterForward => Player.Rotation + 180.Degrees(), - ElusiveStrategy.CameraBackward => World.Client.CameraAzimuth + 180.Degrees(), - ElusiveStrategy.CameraForward => World.Client.CameraAzimuth, - _ => Player.Rotation - }; - Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRG.AID.ElusiveJump), Player, ej.Priority(), ej.Value.ExpireIn, facingAngle: angle); - } - var dash = strategy.Option(Track.WingedGlide); var dashStrategy = strategy.Option(Track.WingedGlide).As(); var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting diff --git a/BossMod/Autorotation/Utility/ClassPCTUtility.cs b/BossMod/Autorotation/Utility/ClassPCTUtility.cs index 5271c3ab16..1fe5d225d2 100644 --- a/BossMod/Autorotation/Utility/ClassPCTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPCTUtility.cs @@ -2,9 +2,8 @@ public sealed class ClassPCTUtility(RotationModuleManager manager, Actor player) : RoleCasterUtility(manager, player) { - public enum Track { TemperaCoat = SharedTrack.Count, Smudge } + public enum Track { TemperaCoat = SharedTrack.Count } public enum TemperaCoatOption { None, CoatOnly, CoatGrassaASAP, CoatGrassaWhenever } - public enum SmudgeStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(PCT.AID.ChromaticFantasy); @@ -20,14 +19,6 @@ public static RotationModuleDefinition Definition() .AddOption(TemperaCoatOption.CoatGrassaWhenever, "Tempera Coat + Grassa when available", "Use Tempera Coat + Tempera Grassa when weaving or not casting", 90, 10, ActionTargets.Self, 88) .AddAssociatedActions(PCT.AID.TemperaCoat, PCT.AID.TemperaGrassa); - res.Define(Track.Smudge).As("Smudge", uiPriority: 30) - .AddOption(SmudgeStrategy.None, "None", "Do not use automatically", 0, 0, ActionTargets.Self, 20) - .AddOption(SmudgeStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 5, ActionTargets.Self, 20) - .AddOption(SmudgeStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 5, ActionTargets.Self, 20) - .AddOption(SmudgeStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 5, ActionTargets.Self, 20) - .AddOption(SmudgeStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 20, 5, ActionTargets.Self, 20) - .AddAssociatedActions(PCT.AID.Smudge); - return res; } @@ -63,17 +54,5 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaGrassa), Player, tempera.Priority(), tempera.Value.ExpireIn); } } - var smuh = strategy.Option(Track.Smudge); - if (smuh.As() != SmudgeStrategy.None) - { - var angle = smuh.As() switch - { - SmudgeStrategy.CharacterBackward => Player.Rotation + 180.Degrees(), - SmudgeStrategy.CameraForward => World.Client.CameraAzimuth, - SmudgeStrategy.CameraBackward => World.Client.CameraAzimuth + 180.Degrees(), - _ => Player.Rotation - }; - Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.Smudge), Player, smuh.Priority(), smuh.Value.ExpireIn, facingAngle: angle); - } } } diff --git a/BossMod/Autorotation/Utility/ClassPLDUtility.cs b/BossMod/Autorotation/Utility/ClassPLDUtility.cs index e2ec8205e3..5a472c3dab 100644 --- a/BossMod/Autorotation/Utility/ClassPLDUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPLDUtility.cs @@ -1,13 +1,10 @@ -using static BossMod.Autorotation.ClassPCTUtility; - -namespace BossMod.Autorotation; +namespace BossMod.Autorotation; public sealed class ClassPLDUtility(RotationModuleManager manager, Actor player) : RoleTankUtility(manager, player) { - public enum Track { Sheltron = SharedTrack.Count, Sentinel, Cover, Bulwark, DivineVeil, PassageOfArms, HallowedGround } //What we're tracking + public enum Track { Sheltron = SharedTrack.Count, Sentinel, Cover, Bulwark, DivineVeil, HallowedGround } //What we're tracking public enum ShelOption { None, Sheltron, HolySheltron, Intervention } //Sheltron Options public enum SentOption { None, Sentinel, Guardian } //Sentinel enhancement - public enum ArmsDirection { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(PLD.AID.LastBastion); //LB public static readonly ActionID IDStanceApply = ActionID.MakeSpell(PLD.AID.IronWill); //StanceOn @@ -35,15 +32,6 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Cover, "Cover", "", 320, PLD.AID.Cover, 12); //120s CD, 12s duration, -50 OathGauge cost DefineSimpleConfig(res, Track.Bulwark, "Bulwark", "Bul", 450, PLD.AID.Bulwark, 10); //90s CD, 15s duration DefineSimpleConfig(res, Track.DivineVeil, "DivineVeil", "Veil", 220, PLD.AID.DivineVeil, 30); //90s CD, 30s duration - - res.Define(Track.PassageOfArms).As("PassageOfArms", "PoA", 400) //PassageOfArms definition for CD plans - .AddOption(ArmsDirection.None, "None", "Do not use automatically") - .AddOption(ArmsDirection.CharacterForward, "CharacterForward", "Faces the Forward direction relative to the Character", 120, 18, ActionTargets.Self, 70) - .AddOption(ArmsDirection.CharacterBackward, "CharacterBackward", "Faces the Backward direction relative to the Character", 120, 18, ActionTargets.Self, 70) - .AddOption(ArmsDirection.CameraForward, "CameraForward", "Faces the Forward direction relative to the Camera", 120, 18, ActionTargets.Self, 70) - .AddOption(ArmsDirection.CameraBackward, "CameraBackward", "Faces the Backward direction relative to the Camera", 120, 18, ActionTargets.Self, 70) - .AddAssociatedActions(PLD.AID.PassageOfArms); - DefineSimpleConfig(res, Track.HallowedGround, "HallowedGround", "Inv", 400, PLD.AID.HallowedGround, 10); //420s CD, 10s duration return res; @@ -77,18 +65,5 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, }; if (sentAction != default) Hints.ActionsToExecute.Push(ActionID.MakeSpell(sentAction), Player, sent.Priority(), sent.Value.ExpireIn); //Sentinel execution - - var poa = strategy.Option(Track.PassageOfArms); - if (poa.As() != ArmsDirection.None) - { - var angle = poa.As() switch - { - ArmsDirection.CharacterBackward => Player.Rotation + 180.Degrees(), - ArmsDirection.CameraForward => World.Client.CameraAzimuth + 180.Degrees(), - ArmsDirection.CameraBackward => World.Client.CameraAzimuth, - _ => Player.Rotation - }; - Hints.ActionsToExecute.Push(ActionID.MakeSpell(PLD.AID.PassageOfArms), Player, poa.Priority(), poa.Value.ExpireIn, facingAngle: angle); - } } } diff --git a/BossMod/Autorotation/Utility/ClassRPRUtility.cs b/BossMod/Autorotation/Utility/ClassRPRUtility.cs index b9a171ffff..cd407e4153 100644 --- a/BossMod/Autorotation/Utility/ClassRPRUtility.cs +++ b/BossMod/Autorotation/Utility/ClassRPRUtility.cs @@ -2,10 +2,7 @@ public sealed class ClassRPRUtility(RotationModuleManager manager, Actor player) : RoleMeleeUtility(manager, player) { - public enum Track { ArcaneCrest = SharedTrack.Count, Ingress, Egress, Regress } - public enum IngressStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } - public enum EgressStrategy { None, CharacterForward, CharacterBackward, CameraForward, CameraBackward } - public enum RegressStrategy { None, Use } + public enum Track { ArcaneCrest = SharedTrack.Count } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(RPR.AID.TheEnd); @@ -16,27 +13,6 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.ArcaneCrest, "Crest", "", 600, RPR.AID.ArcaneCrest, 5); - res.Define(Track.Ingress).As("Hell's Ingress", "Ingress", 30) - .AddOption(IngressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 20) - .AddOption(IngressStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 10, ActionTargets.Self, 20) - .AddOption(IngressStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 10, ActionTargets.Self, 20) - .AddOption(IngressStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) - .AddOption(IngressStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) - .AddAssociatedActions(RPR.AID.HellsIngress); - - res.Define(Track.Egress).As("Hell's Egress", "Egress", 30) - .AddOption(EgressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 20) - .AddOption(EgressStrategy.CharacterForward, "CharacterForward", "Dashes in the Forward direction relative to the Character", 20, 10, ActionTargets.Self, 20) - .AddOption(EgressStrategy.CharacterBackward, "CharacterBackward", "Dashes in the Backward direction relative to the Character", 20, 10, ActionTargets.Self, 20) - .AddOption(EgressStrategy.CameraForward, "CameraForward", "Dashes in the Forward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) - .AddOption(EgressStrategy.CameraBackward, "CameraBackward", "Dashes in the Backward direction relative to the Camera", 20, 10, ActionTargets.Self, 20) - .AddAssociatedActions(RPR.AID.HellsEgress); - - res.Define(Track.Regress).As("Regress", "Regress", 30) - .AddOption(RegressStrategy.None, "None", "No use.", 0, 0, ActionTargets.Self, 74) - .AddOption(RegressStrategy.Use, "Use", "Use Regress", 0, 0, ActionTargets.Self, 74) - .AddAssociatedActions(RPR.AID.Regress); - return res; } @@ -44,37 +20,5 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); ExecuteSimple(strategy.Option(Track.ArcaneCrest), RPR.AID.ArcaneCrest, Player); - - var reg = strategy.Option(Track.Regress); - var regStrat = strategy.Option(Track.Regress).As(); - var zone = World.Actors.FirstOrDefault(x => x.OID == 0x4C3 && x.OwnerID == Player.InstanceID); - if (regStrat != RegressStrategy.None && Player.FindStatus(RPR.SID.Threshold) != null) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.Regress), Player, reg.Priority(), reg.Value.ExpireIn, targetPos: zone!.PosRot.XYZ()); - - var ing = strategy.Option(Track.Ingress); - if (ing.As() != IngressStrategy.None && Player.FindStatus(RPR.SID.Threshold) == null) - { - var angle = ing.As() switch - { - IngressStrategy.CharacterBackward => Player.Rotation + 180.Degrees(), - IngressStrategy.CameraForward => World.Client.CameraAzimuth + 180.Degrees(), - IngressStrategy.CameraBackward => World.Client.CameraAzimuth, - _ => Player.Rotation - }; - Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.HellsIngress), Player, ing.Priority(), ing.Value.ExpireIn, facingAngle: angle); - } - - var egg = strategy.Option(Track.Egress); - if (egg.As() != EgressStrategy.None && Player.FindStatus(RPR.SID.Threshold) == null) - { - var angle = egg.As() switch - { - EgressStrategy.CharacterForward => Player.Rotation + 180.Degrees(), - EgressStrategy.CameraBackward => World.Client.CameraAzimuth + 180.Degrees(), - EgressStrategy.CameraForward => World.Client.CameraAzimuth, - _ => Player.Rotation - }; - Hints.ActionsToExecute.Push(ActionID.MakeSpell(RPR.AID.HellsEgress), Player, egg.Priority(), egg.Value.ExpireIn, facingAngle: angle); - } } } From 8d4346b1fe79bb67c553b9e7a10167ecb2f18b4c Mon Sep 17 00:00:00 2001 From: CarnifexOptimus <156172553+CarnifexOptimus@users.noreply.github.com> Date: Sat, 22 Feb 2025 20:22:25 +0100 Subject: [PATCH 56/56] merge fixes --- BossMod/BossModule/AIHints.cs | 6 +-- BossMod/BossModule/ArenaBounds.cs | 20 +++++----- BossMod/BossModule/BossModule.cs | 29 ++++++++++----- BossMod/BossModule/SimpleBossModule.cs | 2 +- BossMod/Components/Adds.cs | 23 ++++++++++-- BossMod/Data/ActorState.cs | 4 +- BossMod/Data/ClientState.cs | 7 ++-- .../Modules/Global/DeepDungeon/AutoClear.cs | 4 +- .../Heavensward/DeepDungeon/DD70Yaquaru.cs | 37 ++++++++++++++++++- .../DeepDungeon/DD90TheGodmother.cs | 19 +++++----- BossMod/Modules/Stormblood/Foray/Hydatos.cs | 10 +++-- .../Modules/Stormblood/Foray/Hydatos/Ceto.cs | 2 +- .../Stormblood/Foray/Hydatos/Daphne.cs | 2 +- .../Stormblood/Foray/Hydatos/Molech.cs | 16 ++++---- .../Modules/Stormblood/Foray/Hydatos/Ovni.cs | 17 +++++++-- 15 files changed, 135 insertions(+), 63 deletions(-) diff --git a/BossMod/BossModule/AIHints.cs b/BossMod/BossModule/AIHints.cs index 4063af1b80..485789e74e 100644 --- a/BossMod/BossModule/AIHints.cs +++ b/BossMod/BossModule/AIHints.cs @@ -416,8 +416,6 @@ public Func GoalProximity(WPos destination, float maxDistance, floa }; } - public WPos ClampToBounds(WPos position) => PathfindMapCenter + PathfindMapBounds.ClampToBounds(position - PathfindMapCenter); - public Func PullTargetToLocation(Actor target, WPos destination, float destRadius = 2) { var enemy = FindEnemy(target); @@ -430,8 +428,8 @@ public Func PullTargetToLocation(Actor target, WPos destination, fl if (desiredToTarget.LengthSq() > leewaySq) { var dest = destination - adjRange * desiredToTarget.Normalized(); - return GoalSingleTarget(dest, PathfindMapBounds.MapResolution, 10); + return GoalSingleTarget(dest, PathfindMapBounds.MapResolution, 10f); } - return _ => 0; + return _ => 0f; } } diff --git a/BossMod/BossModule/ArenaBounds.cs b/BossMod/BossModule/ArenaBounds.cs index a89458fd4d..8a70c1c544 100644 --- a/BossMod/BossModule/ArenaBounds.cs +++ b/BossMod/BossModule/ArenaBounds.cs @@ -2,7 +2,7 @@ // radius is the largest horizontal/vertical dimension: radius for circle, max of width/height for rect // note: this class to represent *relative* arena bounds (relative to arena center) - the reason being that in some cases effective center moves every frame, and bounds caches a lot (clip poly & base map for pathfinding) // note: if arena bounds are changed, new instance is recreated; max approx error can change without recreating the instance -public abstract record class ArenaBounds(float Radius, float MapResolution, float ScaleFactor = 1) +public abstract record class ArenaBounds(float Radius, float MapResolution, float ScaleFactor = 1, bool AllowObstacleMap = false) { // fields below are used for clipping & drawing borders public readonly PolygonClipper Clipper = new(); @@ -145,7 +145,7 @@ public void AddToInstanceCache(object key, object value) } } -public sealed record class ArenaBoundsCircle(float Radius, float MapResolution = 0.5f) : ArenaBounds(Radius, MapResolution) +public sealed record class ArenaBoundsCircle(float Radius, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBounds(Radius, MapResolution, AllowObstacleMap: AllowObstacleMap) { private Pathfinding.Map? _cachedMap; @@ -175,7 +175,7 @@ private Pathfinding.Map BuildMap() } // if rotation is 0, half-width is along X and half-height is along Z -public record class ArenaBoundsRect(float HalfWidth, float HalfHeight, Angle Rotation = default, float MapResolution = 0.5f) : ArenaBounds(Math.Max(HalfWidth, HalfHeight), MapResolution, Rotation != default ? CalculateScaleFactor(Rotation) : 1) +public record class ArenaBoundsRect(float HalfWidth, float HalfHeight, Angle Rotation = default, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBounds(Math.Max(HalfWidth, HalfHeight), MapResolution, Rotation != default ? CalculateScaleFactor(Rotation) : 1, AllowObstacleMap) { private Pathfinding.Map? _cachedMap; public readonly WDir Orientation = Rotation.ToDirection(); @@ -215,7 +215,7 @@ public override WDir ClampToBounds(WDir offset) } } -public sealed record class ArenaBoundsSquare(float Radius, Angle Rotation = default, float MapResolution = 0.5f) : ArenaBoundsRect(Radius, Radius, Rotation, MapResolution) { } +public sealed record class ArenaBoundsSquare(float Radius, Angle Rotation = default, float MapResolution = 0.5f, bool AllowObstacleMap = false) : ArenaBoundsRect(Radius, Radius, Rotation, MapResolution, AllowObstacleMap) { } // custom complex polygon bounds public record class ArenaBoundsCustom : ArenaBounds @@ -225,8 +225,8 @@ public record class ArenaBoundsCustom : ArenaBounds private readonly (WDir, WDir)[] edges; public float HalfWidth, HalfHeight; - public ArenaBoundsCustom(float Radius, RelSimplifiedComplexPolygon Poly, float MapResolution = 0.5f, float ScaleFactor = 1) - : base(Radius, MapResolution, ScaleFactor) + public ArenaBoundsCustom(float Radius, RelSimplifiedComplexPolygon Poly, float MapResolution = 0.5f, float ScaleFactor = 1, bool AllowObstacleMap = false) + : base(Radius, MapResolution, ScaleFactor, AllowObstacleMap) { poly = Poly; @@ -383,21 +383,21 @@ public sealed record class ArenaBoundsComplex : ArenaBoundsCustom public readonly WPos Center; public bool IsCircle; // can be used by gaze component for gazes outside of the arena - public ArenaBoundsComplex(Shape[] UnionShapes, Shape[]? DifferenceShapes = null, Shape[]? AdditionalShapes = null, float MapResolution = 0.5f, float ScaleFactor = 1) - : base(BuildBounds(UnionShapes, DifferenceShapes, AdditionalShapes, MapResolution, ScaleFactor, out var center, out var halfWidth, out var halfHeight)) + public ArenaBoundsComplex(Shape[] UnionShapes, Shape[]? DifferenceShapes = null, Shape[]? AdditionalShapes = null, float MapResolution = 0.5f, float ScaleFactor = 1, bool AllowObstacleMap = false) + : base(BuildBounds(UnionShapes, DifferenceShapes, AdditionalShapes, MapResolution, ScaleFactor, AllowObstacleMap, out var center, out var halfWidth, out var halfHeight)) { Center = center; HalfWidth = halfWidth; HalfHeight = halfHeight; } - private static ArenaBoundsCustom BuildBounds(Shape[] unionShapes, Shape[]? differenceShapes, Shape[]? additionalShapes, float mapResolution, float scalefactor, out WPos center, out float halfWidth, out float halfHeight) + private static ArenaBoundsCustom BuildBounds(Shape[] unionShapes, Shape[]? differenceShapes, Shape[]? additionalShapes, float mapResolution, float scalefactor, bool allowObstacleMap, out WPos center, out float halfWidth, out float halfHeight) { var properties = CalculatePolygonProperties(unionShapes, differenceShapes ?? [], additionalShapes ?? []); center = properties.Center; halfWidth = properties.HalfWidth; halfHeight = properties.HalfHeight; - return new(scalefactor == 1 ? properties.Radius : properties.Radius / scalefactor, properties.Poly, mapResolution, scalefactor); + return new(scalefactor == 1 ? properties.Radius : properties.Radius / scalefactor, properties.Poly, mapResolution, scalefactor, allowObstacleMap); } private static (WPos Center, float HalfWidth, float HalfHeight, float Radius, RelSimplifiedComplexPolygon Poly) CalculatePolygonProperties(Shape[] unionShapes, Shape[] differenceShapes, Shape[] additionalShapes) diff --git a/BossMod/BossModule/BossModule.cs b/BossMod/BossModule/BossModule.cs index 3711da8fd6..fbe1ffef56 100644 --- a/BossMod/BossModule/BossModule.cs +++ b/BossMod/BossModule/BossModule.cs @@ -297,16 +297,18 @@ public void CalculateAIHints(int slot, ref Actor actor, ref PartyRolesConfig.Ass hints.PathfindMapCenter = Center; hints.PathfindMapBounds = Bounds; - var (entry, bitmap) = Obstacles.Find(new Vector3(Center.X, actor.PosRot.Y, Center.Z)); - if (entry != null && bitmap != null) + if (Arena.Bounds.AllowObstacleMap) { - var originCell = (Center - entry.Origin) / bitmap.PixelSize; - var originX = (int)originCell.X; - var originZ = (int)originCell.Z; - var halfSize = (int)(Bounds.Radius / bitmap.PixelSize); - hints.PathfindMapObstacles = new(bitmap, new(originX - halfSize, originZ - halfSize, originX + halfSize, originZ + halfSize)); + var (entry, bitmap) = Obstacles.Find(new Vector3(Center.X, actor.PosRot.Y, Center.Z)); + if (entry != null && bitmap != null) + { + var originCell = (Center - entry.Origin) / bitmap.PixelSize; + var originX = (int)originCell.X; + var originZ = (int)originCell.Z; + var halfSize = (int)(Bounds.Radius / bitmap.PixelSize); + hints.PathfindMapObstacles = new(bitmap, new(originX - halfSize, originZ - halfSize, originX + halfSize, originZ + halfSize)); + } } - var count = Components.Count; for (var i = 0; i < count; ++i) Components[i].AddAIHints(slot, actor, assignment, hints); @@ -390,9 +392,15 @@ private void DrawWaymark(Vector3? pos, string text, uint color) private void DrawPartyMembers(int pcSlot, ref Actor pc) { - foreach (var (slot, player) in Raid.WithSlot().Exclude(pcSlot)) + var raid = Raid.WithSlot(); + var count = raid.Length; + for (var i = 0; i < count; ++i) { - var (prio, color) = CalculateHighestPriority(pcSlot, ref pc, slot, player); + if (i == pcSlot) + continue; + + var player = raid[i].Item2; + var (prio, color) = CalculateHighestPriority(pcSlot, ref pc, i, player); var isFocus = WorldState.Client.FocusTargetId == player.InstanceID; if (prio == BossComponent.PlayerPriority.Irrelevant && !WindowConfig.ShowIrrelevantPlayers && !(isFocus && WindowConfig.ShowFocusTargetPlayer)) @@ -429,6 +437,7 @@ private void DrawPartyMembers(int pcSlot, ref Actor pc) } } } + Arena.Actor(player, color); } } diff --git a/BossMod/BossModule/SimpleBossModule.cs b/BossMod/BossModule/SimpleBossModule.cs index 22b643f460..a805ba7b48 100644 --- a/BossMod/BossModule/SimpleBossModule.cs +++ b/BossMod/BossModule/SimpleBossModule.cs @@ -2,7 +2,7 @@ // base class for simple boss modules (hunts, fates, dungeons, etc.) // these always center map around PC -public abstract class SimpleBossModule(WorldState ws, Actor primary) : BossModule(ws, primary, primary.Position, new ArenaBoundsCircle(30f)) +public abstract class SimpleBossModule(WorldState ws, Actor primary) : BossModule(ws, primary, primary.Position, new ArenaBoundsCircle(30, AllowObstacleMap: true)) { private WPos _prevFramePathfindCenter; diff --git a/BossMod/Components/Adds.cs b/BossMod/Components/Adds.cs index a1abba4c91..feaa16b5d1 100644 --- a/BossMod/Components/Adds.cs +++ b/BossMod/Components/Adds.cs @@ -4,7 +4,23 @@ public class Adds(BossModule module, uint oid, int priority = 0) : BossComponent(module) { public readonly List Actors = module.Enemies(oid); - public IEnumerable ActiveActors => Actors.Where(a => a.IsTargetable && !a.IsDead); + public IEnumerable ActiveActors + { + get + { + var count = Actors.Count; + var activeActors = new List(count); + for (var i = 0; i < count; ++i) + { + var actor = Actors[i]; + if (actor.IsTargetable && !actor.IsDead) + { + activeActors.Add(actor); + } + } + return activeActors; + } + } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { @@ -23,8 +39,9 @@ public class AddsPointless(BossModule module, uint oid) : Adds(module, oid) { public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - foreach (var act in ActiveActors) - hints.SetPriority(act, AIHints.Enemy.PriorityPointless); + var count = Actors.Count; + for (var i = 0; i < count; ++i) + hints.SetPriority(Actors[i], AIHints.Enemy.PriorityPointless); } } diff --git a/BossMod/Data/ActorState.cs b/BossMod/Data/ActorState.cs index 50d14f08e2..c3462fcf68 100644 --- a/BossMod/Data/ActorState.cs +++ b/BossMod/Data/ActorState.cs @@ -43,7 +43,7 @@ public List CompareToInitial() if (act.MountId != 0) ops.Add(new OpMount(instanceID, act.MountId)); if (act.ForayInfo != default) - yield return new OpForayInfo(act.InstanceID, act.ForayInfo); + ops.Add(new OpForayInfo(act.InstanceID, act.ForayInfo)); if (act.Tether.ID != 0) ops.Add(new OpTether(instanceID, act.Tether)); if (act.CastInfo != null) @@ -371,7 +371,7 @@ protected override void ExecActor(ref WorldState ws, ref Actor actor) public Event ForayInfoChanged = new(); public sealed record class OpForayInfo(ulong InstanceID, ActorForayInfo Value) : Operation(InstanceID) { - protected override void ExecActor(WorldState ws, Actor actor) + protected override void ExecActor(ref WorldState ws, ref Actor actor) { actor.ForayInfo = Value; ws.Actors.ForayInfoChanged.Fire(actor); diff --git a/BossMod/Data/ClientState.cs b/BossMod/Data/ClientState.cs index 63fd484e09..ec923268e0 100644 --- a/BossMod/Data/ClientState.cs +++ b/BossMod/Data/ClientState.cs @@ -451,15 +451,16 @@ protected override void Exec(ref WorldState ws) public override void Write(ReplayRecorder.Output output) { output.EmitFourCC("CLKV"u8); - foreach (var val in Value) - output.Emit(val); + var len = Value.Length; + for (var i = 0; i < len; ++i) + output.Emit(Value[i]); } } public Event FateInfo = new(); public sealed record class OpFateInfo(uint FateId, DateTime StartTime) : WorldState.Operation { - protected override void Exec(WorldState ws) => ws.Client.FateInfo.Fire(this); + protected override void Exec(ref WorldState ws) => ws.Client.FateInfo.Fire(this); public override void Write(ReplayRecorder.Output output) => output.EmitFourCC("FATE"u8).Emit(FateId).Emit(StartTime.Ticks); } } diff --git a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs index 4f120616f6..89fb193b78 100644 --- a/BossMod/Modules/Global/DeepDungeon/AutoClear.cs +++ b/BossMod/Modules/Global/DeepDungeon/AutoClear.cs @@ -389,14 +389,14 @@ public override void CalculateAIHints(int playerSlot, Actor player, AIHints hint if (!Config.Enable || Palace.IsBossFloor || BetweenFloors) return; - var canNavigate = _config.MaxPull == 0 ? !player.InCombat : hints.PotentialTargets.Count(t => t.Actor.AggroPlayer && !t.Actor.IsDeadOrDestroyed) < _config.MaxPull; + var canNavigate = Config.MaxPull == 0 ? !player.InCombat : hints.PotentialTargets.Count(t => t.Actor.AggroPlayer && !t.Actor.IsDeadOrDestroyed) < Config.MaxPull; var countWalls = Walls.Count; for (var i = 0; i < countWalls; ++i) { var wall = Walls[i]; var w = wall.Wall; - hints.AddForbiddenZone(ShapeDistance.Rect(w.Position, (wall.Rotated ? 90f : 0f).Degrees(), w.Depth, w.Depth, 20f)); + hints.AddForbiddenZone(ShapeDistance.Rect(w.Position, (wall.Rotated ? 90f : default).Degrees(), w.Depth, w.Depth, 20f)); } if (canNavigate) diff --git a/BossMod/Modules/Heavensward/DeepDungeon/DD70Yaquaru.cs b/BossMod/Modules/Heavensward/DeepDungeon/DD70Yaquaru.cs index 896607d1f4..ead7617fe2 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/DD70Yaquaru.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/DD70Yaquaru.cs @@ -16,6 +16,11 @@ public enum AID : uint FangsEnd = 7092 // Boss->player, no cast, single-target } +public enum SID : uint +{ + Heavy = 14 +} + class Douse(BossModule module) : Components.PersistentVoidzoneAtCastTarget(module, 8f, ActionID.MakeSpell(AID.Douse), GetVoidzones, 0.8f) { public static Actor[] GetVoidzones(BossModule module) @@ -89,6 +94,35 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme class Electrogenesis(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.Electrogenesis), 8f); +class FangsEnd(BossModule module) : BossComponent(module) +{ + private BitMask _heavy; + + public override void Update() + { + for (var i = 0; i < 4; ++i) + { + var player = Raid[i]; + if (player == null) + continue; + + if (player.FindStatus((uint)SID.Heavy) is ActorStatus st && (st.ExpireAt - WorldState.CurrentTime).TotalSeconds > 8d) + _heavy.Set(i); + } + } + + public override void OnStatusLose(Actor actor, ActorStatus status) + { + if (status.ID == (uint)SID.Heavy) + _heavy.Clear(Raid.FindSlot(actor.InstanceID)); + } + + public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) + { + hints.ShouldCleanse |= _heavy; + } +} + class DD70YaquaruStates : StateMachineBuilder { public DD70YaquaruStates(BossModule module) : base(module) @@ -96,7 +130,8 @@ public DD70YaquaruStates(BossModule module) : base(module) TrivialPhase() .ActivateOnEnter() .ActivateOnEnter() - .ActivateOnEnter(); + .ActivateOnEnter() + .ActivateOnEnter(); } } diff --git a/BossMod/Modules/Heavensward/DeepDungeon/DD90TheGodmother.cs b/BossMod/Modules/Heavensward/DeepDungeon/DD90TheGodmother.cs index 1671a1a523..e07c229783 100644 --- a/BossMod/Modules/Heavensward/DeepDungeon/DD90TheGodmother.cs +++ b/BossMod/Modules/Heavensward/DeepDungeon/DD90TheGodmother.cs @@ -33,21 +33,17 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme hints.SetPriority(g, AIHints.Enemy.PriorityForbidden); } } + class GiddyBomb(BossModule module) : BossComponent(module) { - public static readonly WPos[] BombSpawns = [ - new(-305, -240), - new(-295, -240), - new(-295, -240), - new(-300, -235) - ]; + public static readonly WPos[] BombSpawns = [new(-305f, -240f), new(-295f, -240f), new(-295f, -240f), new(-300f, -235f)]; private int _index; public override void OnEventCast(Actor caster, ActorCastEvent spell) { - if ((AID)spell.Action.ID == AID.HypothermalCombustion) - _index++; + if (spell.Action.ID == (uint)AID.HypothermalCombustion) + ++_index; } public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) @@ -57,8 +53,11 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme return; // giddy bomb is alive, don't pull anywhere - if (Module.Enemies(OID.GiddyBomb).Any(x => !x.IsDeadOrDestroyed)) - return; + var giddybombs = Module.Enemies((uint)OID.GiddyBomb); + var count = giddybombs.Count; + for (var i = 0; i < count; ++i) + if (!giddybombs[i].IsDeadOrDestroyed) + return; var nextBombSpot = BombSpawns[_index % BombSpawns.Length]; hints.GoalZones.Add(hints.PullTargetToLocation(Module.PrimaryActor, nextBombSpot)); diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos.cs b/BossMod/Modules/Stormblood/Foray/Hydatos.cs index a405de56fe..67c0bc83fa 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos.cs @@ -12,6 +12,10 @@ public class EurekaConfig : ConfigNode [PropertyDisplay("Max number of mobs to pull at once (0 for no limit)")] [PropertySlider(0, 30, Speed = 0.1f)] public int MaxPullCount = 10; + + [PropertyDisplay("Show auto farm window")] + public bool ShowAutoFarmWindow = false; + } [ConfigDisplay(Name = "Hydatos", Parent = typeof(EurekaConfig))] @@ -85,8 +89,8 @@ static class NMExtensions [ZoneModuleInfo(BossModuleInfo.Maturity.WIP, 639)] public class Hydatos : ZoneModule { - private readonly EurekaConfig _eurekaConfig = Service.Config.Get(); - private readonly HydatosConfig _hydatosConfig = Service.Config.Get(); + private static readonly EurekaConfig _eurekaConfig = Service.Config.Get(); + private static readonly HydatosConfig _hydatosConfig = Service.Config.Get(); private readonly EventSubscriptions _subscriptions; @@ -148,7 +152,7 @@ private bool ShouldIgnore(Actor caster, Actor player) }; } - public override bool WantDrawExtra() => true; + public override bool WantDrawExtra() => _eurekaConfig.ShowAutoFarmWindow; public override string WindowName() => "Hydatos###Eureka module"; diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos/Ceto.cs b/BossMod/Modules/Stormblood/Foray/Hydatos/Ceto.cs index a43b9cd8ed..c8ba2ff276 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos/Ceto.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos/Ceto.cs @@ -49,5 +49,5 @@ public CetoStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1421, Contributors = "xan", SortOrder = 9)] -public class Ceto(WorldState ws, Actor primary) : BossModule(ws, primary, new(747.8959f, -878.8765f), new ArenaBoundsCircle(80f, MapResolution: 1)); +public class Ceto(WorldState ws, Actor primary) : BossModule(ws, primary, new(747.8959f, -878.8765f), new ArenaBoundsCircle(80f, 1f, true)); diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos/Daphne.cs b/BossMod/Modules/Stormblood/Foray/Hydatos/Daphne.cs index deef05b083..a00865e192 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos/Daphne.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos/Daphne.cs @@ -38,5 +38,5 @@ public DaphneStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1417, Contributors = "xan", SortOrder = 5)] -public class Daphne(WorldState ws, Actor primary) : BossModule(ws, primary, new(207.8475f, -736.8179f), new ArenaBoundsCircle(80f, MapResolution: 1)); +public class Daphne(WorldState ws, Actor primary) : BossModule(ws, primary, new(207.8475f, -736.8179f), new ArenaBoundsCircle(80f, 1f, true)); diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos/Molech.cs b/BossMod/Modules/Stormblood/Foray/Hydatos/Molech.cs index 43fe4dd21c..a901a6ecdf 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos/Molech.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos/Molech.cs @@ -3,7 +3,7 @@ namespace BossMod.Stormblood.Foray.Hydatos.Molech; public enum OID : uint { Boss = 0x275D, // R6.000, x1 - Adulator = 0x275E, // R2.800, x3 + Adulator = 0x275E // R2.800, x3 } public enum AID : uint @@ -15,15 +15,15 @@ public enum AID : uint W111TonzeSwingAdds = 14979, // Adulator->self, 3.0s cast, range 13 circle W111TonzeSwingBig = 14974, // Boss->self, 4.0s cast, range 20 circle OrderToAssault = 14975, // Boss->self, 3.0s cast, range 100 circle - ZoomIn = 14980, // Adulator->location, 3.0s cast, width 8 rect charge + ZoomIn = 14980 // Adulator->location, 3.0s cast, width 8 rect charge } class Adds(BossModule module) : Components.AddsPointless(module, (uint)OID.Adulator); -class W11TonzeSwipe(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W11TonzeSwipe), new AOEShapeCone(9, 75.Degrees())); -class W111TonzeSwing(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwing), new AOEShapeCircle(13)); -class W111TonzeSwingAdds(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwingAdds), new AOEShapeCircle(13)); -class W111TonzeSwingBig(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwingBig), new AOEShapeCircle(20)); -class ZoomIn(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.ZoomIn), 4); +class W11TonzeSwipe(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.W11TonzeSwipe), new AOEShapeCone(9f, 75f.Degrees())); +class W111TonzeSwing(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwing), 13f); +class W111TonzeSwingAdds(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwingAdds), 13f); +class W111TonzeSwingBig(BossModule module) : Components.SimpleAOEs(module, ActionID.MakeSpell(AID.W111TonzeSwingBig), 20f); +class ZoomIn(BossModule module) : Components.ChargeAOEs(module, ActionID.MakeSpell(AID.ZoomIn), 4f); class MolechStates : StateMachineBuilder { @@ -40,5 +40,5 @@ public MolechStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1414, Contributors = "xan", SortOrder = 3)] -public class Molech(WorldState ws, Actor primary) : BossModule(ws, primary, new(-676.8632f, -441.8009f), new ArenaBoundsCircle(80, MapResolution: 1)); +public class Molech(WorldState ws, Actor primary) : BossModule(ws, primary, new(-676.8632f, -441.8009f), new ArenaBoundsCircle(80f, 1f, true)); diff --git a/BossMod/Modules/Stormblood/Foray/Hydatos/Ovni.cs b/BossMod/Modules/Stormblood/Foray/Hydatos/Ovni.cs index c3d5faace0..c2adfe1fd9 100644 --- a/BossMod/Modules/Stormblood/Foray/Hydatos/Ovni.cs +++ b/BossMod/Modules/Stormblood/Foray/Hydatos/Ovni.cs @@ -50,9 +50,18 @@ public override void OnEventCast(Actor caster, ActorCastEvent spell) public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignment assignment, AIHints hints) { - if (Spreads.Any(s => s.Target == actor)) - // just gtfo from boss as far as possible - hints.GoalZones.Add(p => (p - Module.PrimaryActor.Position).LengthSq() > 1600f ? 100f : 0f); + var found = false; + var count = Spreads.Count; + for (var i = 0; i < count; ++i) + { + if (Spreads[i].Target == actor) + { + found = true; + break; + } + } + if (found) // just gtfo from boss as far as possible + hints.GoalZones.Add(p => (p - Module.PrimaryActor.Position).LengthSq() > 1600f ? 100f : default); else base.AddAIHints(slot, actor, assignment, hints); } @@ -74,5 +83,5 @@ public OvniStates(BossModule module) : base(module) } [ModuleInfo(BossModuleInfo.Maturity.Contributed, GroupType = BossModuleInfo.GroupType.EurekaNM, GroupID = 639, NameID = 1424, Contributors = "xan", SortOrder = 11)] -public class Ovni(WorldState ws, Actor primary) : BossModule(ws, primary, new(266.1068f, -97.09414f), new ArenaBoundsCircle(80f, MapResolution: 1)); +public class Ovni(WorldState ws, Actor primary) : BossModule(ws, primary, new(266.1068f, -97.09414f), new ArenaBoundsCircle(80f, 1f, true));