diff --git a/BossMod/ActionQueue/Casters/BLM.cs b/BossMod/ActionQueue/Casters/BLM.cs index 06a0dc5a8b..5c867f54b1 100644 --- a/BossMod/ActionQueue/Casters/BLM.cs +++ b/BossMod/ActionQueue/Casters/BLM.cs @@ -84,8 +84,6 @@ public enum TraitID : uint public enum SID : uint { None = 0, - LucidDreaming = 1204, // applied by Lucid Dreaming to self - Swiftcast = 167, // applied by Swiftcast to self Manaward = 168, // applied by Manaward to self Thunder = 161, // applied by Thunder to target ThunderII = 162, // applied by Thunder II to target @@ -101,6 +99,9 @@ public enum SID : uint //Shared Addle = ClassShared.SID.Addle, // applied by Addle to target + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self + Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } public sealed class Definitions : IDisposable diff --git a/BossMod/ActionQueue/Casters/PCT.cs b/BossMod/ActionQueue/Casters/PCT.cs index 861915e814..f4249aa0e3 100644 --- a/BossMod/ActionQueue/Casters/PCT.cs +++ b/BossMod/ActionQueue/Casters/PCT.cs @@ -90,7 +90,7 @@ public enum SID : uint Aetherhues = 3675, // applied by Fire in Red, Fire II in Red, Blizzard II in Cyan, Blizzard in Cyan to self AetherhuesII = 3676, // applied by Aero in Green, Aero II in Green, Stone in Yellow to self TemperaCoat = 3686, // applied by Tempera Coat to self - Swiftcast = 167, // applied by Swiftcast to self + TemperaGrassa = 3687, // applied by Tempera Grassa to self Smudge = 3684, // applied by Smudge to self HammerTime = 3680, // applied by Striking Muse to self SubtractivePalette = 3674, // applied by Subtractive Palette to self @@ -103,6 +103,9 @@ public enum SID : uint //Shared Addle = ClassShared.SID.Addle, // applied by Addle to target + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self + Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } public sealed class Definitions : IDisposable diff --git a/BossMod/ActionQueue/Casters/RDM.cs b/BossMod/ActionQueue/Casters/RDM.cs index 29544b2843..ea47fbee68 100644 --- a/BossMod/ActionQueue/Casters/RDM.cs +++ b/BossMod/ActionQueue/Casters/RDM.cs @@ -87,8 +87,6 @@ public enum TraitID : uint public enum SID : uint { None = 0, - LucidDreaming = 1204, // applied by Lucid Dreaming to self - Swiftcast = 167, // applied by Swiftcast to self VerfireReady = 1234, VerstoneReady = 1235, Acceleration = 1238, @@ -101,6 +99,9 @@ public enum SID : uint //Shared Addle = ClassShared.SID.Addle, // applied by Addle to target + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self + Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } public sealed class Definitions : IDisposable diff --git a/BossMod/ActionQueue/Casters/SMN.cs b/BossMod/ActionQueue/Casters/SMN.cs index f08e4ccc9d..a58d4afc4e 100644 --- a/BossMod/ActionQueue/Casters/SMN.cs +++ b/BossMod/ActionQueue/Casters/SMN.cs @@ -126,7 +126,6 @@ public enum SID : uint None = 0, Sleep = 3, // applied by Sleep to target FurtherRuin = 2701, // applied by Energy Drain, Energy Siphon to self - LucidDreaming = 1204, // applied by Lucid Dreaming to self SearingLight = 2703, // applied by Searing Light to self/target TitansFavor = 2853, // applied by Topaz Rite, Topaz Catastrophe to self Rekindle = 2704, // applied by Rekindle to self @@ -140,8 +139,9 @@ public enum SID : uint //Shared Addle = ClassShared.SID.Addle, // applied by Addle to target + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self - } public sealed class Definitions : IDisposable diff --git a/BossMod/ActionQueue/ClassShared.cs b/BossMod/ActionQueue/ClassShared.cs index 69b437b37a..afde3bc74d 100644 --- a/BossMod/ActionQueue/ClassShared.cs +++ b/BossMod/ActionQueue/ClassShared.cs @@ -90,8 +90,12 @@ public enum SID : uint // PhysRanged Peloton = 1199, // applied by Peloton to self/party - // Caster/Healer + // Caster Addle = 1203, // applied by Addle to target + + // Magical + LucidDreaming = 1204, // applied by Lucid Dreaming to self + Surecast = 160, // applied by Surecast to self Swiftcast = 167, // applied by Swiftcast to self Raise = 148, // applied by Raise to target diff --git a/BossMod/ActionQueue/Healers/AST.cs b/BossMod/ActionQueue/Healers/AST.cs index bed6164e22..9927757595 100644 --- a/BossMod/ActionQueue/Healers/AST.cs +++ b/BossMod/ActionQueue/Healers/AST.cs @@ -95,21 +95,46 @@ public enum TraitID : uint public enum SID : uint { None = 0, - LucidDreaming = 1204, // applied by Lucid Dreaming to self - Surecast = 160, // applied by Surecast to self Combust = 838, - AspectedHelios = 836, - AspectedBenefic = 835, - HeliosConjunction = 3894, - Lightspeed = 841, CombustII = 843, CombustIII = 1881, + AspectedBenefic = 835, + AspectedHelios = 836, + HeliosConjunction = 3894, + Horoscope = 1890, + HoroscopeHelios = 1891, + NeutralSect = 1892, + NeutralSectShield = 1921, Divination = 1878, + GiantDominance = 1248, + ClarifyingDraw = 2713, + Macrocosmos = 2718, + Lightspeed = 841, + SelfSynastry = 845, + TargetSynastry = 846, + Divining = 3893, + + //Cards TheBalance = 3887, TheSpear = 3889, - Divining = 3893, + LordOfCrownsDrawn = 2054, + LadyOfCrownsDrawn = 2055, + BalanceDrawn = 913, + BoleDrawn = 914, + ArrowDrawn = 915, + SpearDrawn = 916, + EwerDrawn = 917, + SpireDrawn = 918, + BalanceBuff = 3887, + BoleBuff = 3890, + ArrowBuff = 3888, + SpearBuff = 3889, + EwerBuff = 3891, + SpireBuff = 3892, //Shared + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } diff --git a/BossMod/ActionQueue/Healers/SCH.cs b/BossMod/ActionQueue/Healers/SCH.cs index 37caea13d8..f97935a051 100644 --- a/BossMod/ActionQueue/Healers/SCH.cs +++ b/BossMod/ActionQueue/Healers/SCH.cs @@ -96,15 +96,16 @@ public enum SID : uint Bio2 = 189, // applied by Bio2 to target, dot Biolysis = 1895, Galvanize = 297, // applied by Adloquium to target, shield - LucidDreaming = 1204, // applied by Lucid Dreaming to self Sleep = 3, // applied by Repose to target BanefulImpaction = 3883, // applied by Baneful Impaction to target ImpactImminent = 3882, // applied by Chain Stratagem to self ChainStratagem = 1221, // applied by Chain Stratagem to target - FeyUnion = 1222, // applied by Aetherpact to target + FeyUnion = 1223, // applied by Aetherpact to target Seraphism = 3884, // applied by Seraphism to self //Shared + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } diff --git a/BossMod/ActionQueue/Healers/SGE.cs b/BossMod/ActionQueue/Healers/SGE.cs index 2576c275fd..b64c2cc813 100644 --- a/BossMod/ActionQueue/Healers/SGE.cs +++ b/BossMod/ActionQueue/Healers/SGE.cs @@ -86,11 +86,9 @@ public enum SID : uint { None = 0, Sprint = 50, // applied by Sprint to self - Surecast = 160, // applied by Surecast to self Eukrasia = 2606, Kardion = 2605, // applied by Kardia to self Kardia = 2604, // applied by Kardia to self - LucidDreaming = 1204, // applied by Lucid Dreaming to self Haima = 2612, // applied by Haima to self Haimatinon = 2642, // applied by Haima to self Panhaima = 2613, // applied by Panhaima to self @@ -114,6 +112,8 @@ public enum SID : uint EukrasianDyskrasia = 3897, // applied by Eukrasian Dyskrasia to target //Shared + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } diff --git a/BossMod/ActionQueue/Healers/WHM.cs b/BossMod/ActionQueue/Healers/WHM.cs index 3dac156c94..9d654a9afa 100644 --- a/BossMod/ActionQueue/Healers/WHM.cs +++ b/BossMod/ActionQueue/Healers/WHM.cs @@ -91,11 +91,9 @@ public enum SID : uint Medica2 = 150, // applied by Medica2 to targets, hot Freecure = 155, // applied by Cure1 to self, next cure2 is free ThinAir = 1217, // applied by Thin Air to self, next gcd costs no mp - LucidDreaming = 1204, // applied by Lucid Dreaming to self, mp regen DivineBenison = 1218, // applied by Divine Benison to target, shield Confession = 1219, // applied by Plenary Indulgence to self, heal buff Temperance = 1872, // applied by Temperance to self, heal and mitigate buff - Surecast = 160, // applied by Surecast to self, knockback immune PresenceOfMind = 157, // applied by Presence of Mind to self, damage buff Regen = 158, // applied by Regen to target, hp regen Asylum = 1911, // applied by Asylum to target, hp regen @@ -109,6 +107,8 @@ public enum SID : uint DivineCaress = 3903, // applied by Divine Caress to self/target //Shared + Surecast = ClassShared.SID.Surecast, // applied by Surecast to self + LucidDreaming = ClassShared.SID.LucidDreaming, // applied by Lucid Dreaming to self Swiftcast = ClassShared.SID.Swiftcast, // applied by Swiftcast to self } diff --git a/BossMod/ActionQueue/Ranged/MCH.cs b/BossMod/ActionQueue/Ranged/MCH.cs index 1b27ad7d8c..b7b9bb0671 100644 --- a/BossMod/ActionQueue/Ranged/MCH.cs +++ b/BossMod/ActionQueue/Ranged/MCH.cs @@ -91,7 +91,7 @@ public enum SID : uint Flamethrower = 1205, // applied by Flamethrower to self ExcavatorReady = 3865, // applied by Chain Saw to self FullMetalMachinist = 3866, // applied by Hypercharge to self - + Tactician = 1951, // applied by Tactician to self //Shared Peloton = ClassShared.SID.Peloton, // applied by Peloton to self/party } diff --git a/BossMod/Autorotation/PlanPresetConverter.cs b/BossMod/Autorotation/PlanPresetConverter.cs index 121ba710ed..ee44acb96a 100644 --- a/BossMod/Autorotation/PlanPresetConverter.cs +++ b/BossMod/Autorotation/PlanPresetConverter.cs @@ -2,101 +2,48 @@ namespace BossMod.Autorotation; -// TODO: waiting for .net9 to complete implementation, since it adds proper API for renaming keys, it's a pita to maintain order otherwise public static class PlanPresetConverter { - // note: we always apply renames _after_ changes - this allows converting old modules to new ones without affecting plans/presets that already use new ones - private record class TrackChanges(Dictionary OptionRenames); - private record class ModuleChanges(Dictionary TrackChanges, Dictionary TrackRenames); - private record class ModuleConverter(Dictionary ModuleChanges, Dictionary ModuleRenames); - public static readonly VersionedJSONSchema PlanSchema = BuildSchema(true); public static readonly VersionedJSONSchema PresetSchema = BuildSchema(false); private static VersionedJSONSchema BuildSchema(bool plan) { var res = new VersionedJSONSchema(); - //AddModuleConverter(res, plan, BuildModuleConverterV1()); // v1: StandardWAR -> VeynVAR rename + res.Converters.Add((j, _, _) => // v1: StandardWAR -> VeynVAR rename + { + Dictionary moduleRenames = new() { ["BossMod.Autorotation.StandardWAR"] = "BossMod.Autorotation.VeynWAR" }; + foreach (var m in EnumerateEntriesModules(j, plan)) + RenameKeys(m, moduleRenames); + return j; + }); return res; } - //private static void AddModuleConverter(VersionedJSONSchema schema, bool plan, ModuleConverter cvt) - //{ - // schema.Converters.Add((j, _, _) => - // { - // if (plan) - // { - // var modules = j!["Modules"]!.AsObject(); - // foreach (var (moduleName, moduleData) in modules) - // { - // if (cvt.ModuleChanges.TryGetValue(moduleName, out var moduleChanges)) - // { - // var tracks = moduleData!.AsObject(); - // foreach (var (trackName, trackData) in tracks) - // { - // if (moduleChanges.TrackChanges.TryGetValue(trackName, out var trackChanges)) - // { - // foreach (var entry in trackData!.AsArray()) - // { - // var optionName = entry!["Option"]!.GetValue(); - // if (trackChanges.OptionRenames.TryGetValue(optionName, out var optionNewName)) - // entry["Option"] = optionNewName; - // } - // } - // } - // ApplyRenames(tracks, moduleChanges.TrackRenames); - // } - // } - // ApplyRenames(modules, cvt.ModuleRenames); - // } - // else - // { - // foreach (var preset in j.AsArray()) - // { - // var modules = preset!["Modules"]!.AsObject(); - // foreach (var (moduleName, moduleData) in modules) - // { - // if (cvt.ModuleChanges.TryGetValue(moduleName, out var moduleChanges)) - // { - // foreach (var entry in moduleData!.AsArray()) - // { - // var trackName = entry!["Track"]!.GetValue(); - // if (moduleChanges.TrackChanges.TryGetValue(trackName, out var trackChanges)) - // { - // var optionName = entry!["Option"]!.GetValue(); - // if (trackChanges.OptionRenames.TryGetValue(optionName, out var optionNewName)) - // entry["Option"] = optionNewName; - // } - - // if (moduleChanges.TrackRenames.TryGetValue(trackName, out var trackNewName)) - // entry["Track"] = trackNewName; - // } - // } - // } - // ApplyRenames(modules, cvt.ModuleRenames); - // } - // } - // return j; - // }); - //} - - //private static void ApplyRenames(JsonObject j, Dictionary renames) - //{ - // if (renames.Count == 0) - // return; - - // for (int i = 0; i < j.Count; ++i) - // { - // // TODO: implement... - // } - //} + // returns always 1 element for plans, or multiple (1 per preset) for preset database + private static IEnumerable EnumerateEntriesModules(JsonNode root, bool plan) + { + if (plan) + { + yield return root!["Modules"]!.AsObject(); + } + else + { + foreach (var preset in root.AsArray()) + { + yield return preset!["Modules"]!.AsObject(); + } + } + } - //private static ModuleConverter BuildModuleConverterV1() - //{ - // Dictionary moduleRenames = new() - // { - // ["BossMod.Autorotation.StandardWAR"] = "BossMod.Autorotation.VeynWAR", - // }; - // return new([], moduleRenames); - //} + private static void RenameKeys(JsonObject j, Dictionary renames) + { + // TODO: net9 - use indexed access to simplify & speed up implementation + for (int i = 0, cnt = j.Count; i < cnt; ++i) + { + var (k, v) = j.First(); + j.Remove(k); + j.Add(renames.TryGetValue(k, out var renamed) ? renamed : k, v); + } + } } diff --git a/BossMod/Autorotation/Standard/veyn/StandardWAR.cs b/BossMod/Autorotation/Standard/veyn/VeynWAR.cs similarity index 99% rename from BossMod/Autorotation/Standard/veyn/StandardWAR.cs rename to BossMod/Autorotation/Standard/veyn/VeynWAR.cs index d5090a30b4..b06f2f9f55 100644 --- a/BossMod/Autorotation/Standard/veyn/StandardWAR.cs +++ b/BossMod/Autorotation/Standard/veyn/VeynWAR.cs @@ -2,7 +2,7 @@ namespace BossMod.Autorotation; -public sealed class StandardWAR(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class VeynWAR(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { public enum Track { AOE, Burst, Potion, PrimalRend, Tomahawk, InnerRelease, Infuriate, Upheaval, Wrath, Onslaught, Bozja } public enum AOEStrategy { SingleTarget, ForceAOE, Auto, AutoFinishCombo } diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index bb405ceaec..a247e2f96c 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -7,8 +7,6 @@ public enum StarOption { None, Use, End } public enum HoroscopeOption { None, Use, End } public enum MacrocosmosOption { None, Use, End } public enum HeliosOption { None, Use, UseEx } - public float GetStatusDetail(Actor target, AST.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, AST.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect public Actor? TargetChoice(StrategyValues.OptionRef strategy) => ResolveTargetOverride(strategy.Value); public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(AST.AID.AstralStasis); @@ -19,7 +17,7 @@ public static RotationModuleDefinition Definition() DefineShared(res, IDLimitBreak3); DefineSimpleConfig(res, Track.Helios, "Helios", "", 140, AST.AID.Helios); - DefineSimpleConfig(res, Track.Lightspeed, "Lightspeed", "L.Speed", 140, AST.AID.Lightspeed, 15); //Self oGCD, 60s CD (120s total), 2 charges, 15s effect duration + DefineSimpleConfig(res, Track.Lightspeed, "Lightspeed", "L.speed", 140, AST.AID.Lightspeed, 15); //Self oGCD, 60s CD (120s total), 2 charges, 15s effect duration DefineSimpleConfig(res, Track.BeneficII, "BeneficII", "Bene2", 100, AST.AID.BeneficII); //ST GCD heal DefineSimpleConfig(res, Track.EssentialDignity, "EssentialDignity", "E.Dig", 140, AST.AID.EssentialDignity); //ST oGCD heal, 40s CD (120s Total), 3 charges DefineSimpleConfig(res, Track.AspectedBenefic, "AspectedBenefic", "A.Benefic", 100, AST.AID.AspectedBenefic, 15); //ST GCD regen, 15s effect duration @@ -88,7 +86,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, QueueOGCD(starAction, TargetChoice(star) ?? primaryTarget ?? Player); //Aspected Helios full execution - var heliosUp = HasEffect(Player, AST.SID.AspectedHelios, 15) || HasEffect(Player, AST.SID.HeliosConjunction, 15); + var heliosUp = StatusDetails(Player, AST.SID.AspectedHelios, Player.InstanceID).Left > 0.1f || StatusDetails(Player, AST.SID.HeliosConjunction, Player.InstanceID).Left > 0.1f; var helios = strategy.Option(Track.AspectedHelios); var heliosAction = helios.As() switch { diff --git a/BossMod/Autorotation/Utility/ClassBLMUtility.cs b/BossMod/Autorotation/Utility/ClassBLMUtility.cs index 3e6bb9c02b..8ed1a91be3 100644 --- a/BossMod/Autorotation/Utility/ClassBLMUtility.cs +++ b/BossMod/Autorotation/Utility/ClassBLMUtility.cs @@ -28,15 +28,17 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Manaward), BLM.AID.Manaward, Player); var dash = strategy.Option(Track.AetherialManipulation); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting var dashStrategy = strategy.Option(Track.AetherialManipulation).As(); - if (ShouldUseDash(dashStrategy, dashTarget)) + var dashTarget = ResolveTargetOverride(dash.Value); //Smart-Targeting: Target needs to be set in autorotation or CDPlanner to prevent unexpected behavior + var distance = Player.DistanceToHitbox(dashTarget); + var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(BLM.AID.AetherialManipulation)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.Force => distance <= 25 && cd < 0.6f, + _ => false, + }; + if (shouldDash) Hints.ActionsToExecute.Push(ActionID.MakeSpell(BLM.AID.AetherialManipulation), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.Force => true, - _ => false, - }; } diff --git a/BossMod/Autorotation/Utility/ClassBRDUtility.cs b/BossMod/Autorotation/Utility/ClassBRDUtility.cs index 7bfbdcedd7..7624292de6 100644 --- a/BossMod/Autorotation/Utility/ClassBRDUtility.cs +++ b/BossMod/Autorotation/Utility/ClassBRDUtility.cs @@ -6,7 +6,7 @@ public enum Track { WardensPaean = SharedTrack.Count, Troubadour, NaturesMinne } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(BRD.AID.SagittariusArrow); - public enum TroubOption { None, Use87, Use88 } + public enum TroubOption { None, Use87, Use87IfNotActive, Use88, Use88IfNotActive } public static RotationModuleDefinition Definition() { @@ -17,8 +17,10 @@ public static RotationModuleDefinition Definition() res.Define(Track.Troubadour).As("Troubadour", "Troub", 500) .AddOption(TroubOption.None, "None", "Do not use automatically") - .AddOption(TroubOption.Use87, "Use", "Use Troubadour", 120, 15, ActionTargets.Self, 62, 87) - .AddOption(TroubOption.Use88, "Use88", "Use Troubadour", 90, 15, ActionTargets.Self, 88) + .AddOption(TroubOption.Use87, "Use", "Use Troubadour (120s CD), regardless if equivalent ranged buff is already active", 120, 15, ActionTargets.Self, 62, 87) + .AddOption(TroubOption.Use87IfNotActive, "UseIfNotActive", "Use Troubadour (120s CD), unless equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 62, 87) + .AddOption(TroubOption.Use88, "Use88", "Use Troubadour (90s CD), regardless if equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 88) + .AddOption(TroubOption.Use88IfNotActive, "Use88IfNotActive", "Use Troubadour (90s CD), unless equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 88) .AddAssociatedActions(BRD.AID.Troubadour); DefineSimpleConfig(res, Track.NaturesMinne, "NaturesMinne", "Minne", 400, BRD.AID.NaturesMinne, 15); @@ -29,11 +31,17 @@ public static RotationModuleDefinition Definition() public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); - ExecuteSimple(strategy.Option(Track.WardensPaean), BRD.AID.WardensPaean, Player); + ExecuteSimple(strategy.Option(Track.WardensPaean), BRD.AID.WardensPaean, ResolveTargetOverride(strategy.Option(Track.WardensPaean).Value) ?? primaryTarget ?? Player); ExecuteSimple(strategy.Option(Track.NaturesMinne), BRD.AID.NaturesMinne, Player); var troub = strategy.Option(Track.Troubadour); + var hasDefensive = Player.FindStatus(BRD.SID.Troubadour) != null || Player.FindStatus(MCH.SID.Tactician) != null || Player.FindStatus(DNC.SID.ShieldSamba) != null; if (troub.As() != TroubOption.None) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(BRD.AID.Troubadour), Player, troub.Priority(), troub.Value.ExpireIn); + { + if (troub.As() is TroubOption.Use87 or TroubOption.Use88) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(BRD.AID.Troubadour), Player, troub.Priority(), troub.Value.ExpireIn); + if (troub.As() is TroubOption.Use87IfNotActive or TroubOption.Use88IfNotActive && !hasDefensive) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(BRD.AID.Troubadour), Player, troub.Priority(), troub.Value.ExpireIn); + } } } diff --git a/BossMod/Autorotation/Utility/ClassDNCUtility.cs b/BossMod/Autorotation/Utility/ClassDNCUtility.cs index 979a3293ae..678ab2ca3b 100644 --- a/BossMod/Autorotation/Utility/ClassDNCUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDNCUtility.cs @@ -4,7 +4,7 @@ public sealed class ClassDNCUtility(RotationModuleManager manager, Actor player) { public enum Track { CuringWaltz = SharedTrack.Count, ShieldSamba, Improvisation } - public enum SambaOption { None, Use87, Use88 } + public enum SambaOption { None, Use87, Use87IfNotActive, Use88, Use88IfNotActive } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DNC.AID.CrimsonLotus); @@ -17,8 +17,10 @@ public static RotationModuleDefinition Definition() res.Define(Track.ShieldSamba).As("Shield Samba", "S.Samba", 500) .AddOption(SambaOption.None, "None", "Do not use automatically") - .AddOption(SambaOption.Use87, "Use", "Use Shield Samba", 120, 15, ActionTargets.Self, 56, 87) - .AddOption(SambaOption.Use88, "Use88", "Use Shield Samba", 90, 15, ActionTargets.Self, 88) + .AddOption(SambaOption.Use87, "Use", "Use Shield Samba (120s CD), regardless if equivalent ranged buff is already active", 120, 15, ActionTargets.Self, 56, 87) + .AddOption(SambaOption.Use87IfNotActive, "UseIfNotActive", "Use Shield Samba (120s CD), unless equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 56, 87) + .AddOption(SambaOption.Use88, "Use88", "Use Shield Samba (90s CD), regardless if equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 88) + .AddOption(SambaOption.Use88IfNotActive, "Use88IfNotActive", "Use Shield Samba (90s CD), unless equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 88) .AddAssociatedActions(DNC.AID.ShieldSamba); DefineSimpleConfig(res, Track.Improvisation, "Improvisation", "Improv", 300, DNC.AID.Improvisation, 15); @@ -33,7 +35,13 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Improvisation), DNC.AID.Improvisation, Player); var samba = strategy.Option(Track.ShieldSamba); + var hasDefensive = Player.FindStatus(BRD.SID.Troubadour) != null || Player.FindStatus(MCH.SID.Tactician) != null || Player.FindStatus(DNC.SID.ShieldSamba) != null; if (samba.As() != SambaOption.None) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.ShieldSamba), Player, samba.Priority(), samba.Value.ExpireIn); + { + if (samba.As() is SambaOption.Use87 or SambaOption.Use88) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.ShieldSamba), Player, samba.Priority(), samba.Value.ExpireIn); + if (samba.As() is SambaOption.Use87IfNotActive or SambaOption.Use88IfNotActive && !hasDefensive) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(DNC.AID.ShieldSamba), Player, samba.Priority(), samba.Value.ExpireIn); + } } } diff --git a/BossMod/Autorotation/Utility/ClassDRGUtility.cs b/BossMod/Autorotation/Utility/ClassDRGUtility.cs index 6727390402..e4e699f9c1 100644 --- a/BossMod/Autorotation/Utility/ClassDRGUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDRGUtility.cs @@ -14,8 +14,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.WingedGlide).As("Winged Glide", "Dash", 20) .AddOption(DashStrategy.None, "Automatic", "No use.") - .AddOption(DashStrategy.GapClose, "GapClose", "Use Winged Glide as gapcloser if outside melee range", 60, 0, ActionTargets.Hostile, 45) - .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use Winged Glide as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 84) + .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); return res; @@ -26,16 +26,18 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteShared(strategy, IDLimitBreak3, primaryTarget); var dash = strategy.Option(Track.WingedGlide); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; var dashStrategy = strategy.Option(Track.WingedGlide).As(); - if (ShouldUseDash(dashStrategy, primaryTarget)) + var dashTarget = ResolveTargetOverride(dash.Value); //Smart-Targeting: Target needs to be set in autorotation or CDPlanner to prevent unexpected behavior + var distance = Player.DistanceToHitbox(dashTarget); + var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRG.AID.WingedGlide)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3 and <= 20 && cd <= 60.5f, + DashStrategy.GapCloseHold1 => distance is > 3 and <= 20 && cd < 0.6f, + _ => true, + }; + if (shouldDash) Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRG.AID.WingedGlide), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.GapClose => Player.DistanceToHitbox(primaryTarget) is > 3 and <= 20, - DashStrategy.GapCloseHold1 => Player.DistanceToHitbox(primaryTarget) is > 3 and <= 20 && World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRG.AID.WingedGlide)!.MainCooldownGroup].Remaining <= 59.9f, - _ => false, - }; } diff --git a/BossMod/Autorotation/Utility/ClassDRKUtility.cs b/BossMod/Autorotation/Utility/ClassDRKUtility.cs index d33c5140d5..31bbbb11dd 100644 --- a/BossMod/Autorotation/Utility/ClassDRKUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDRKUtility.cs @@ -5,11 +5,8 @@ public sealed class ClassDRKUtility(RotationModuleManager manager, Actor player) public enum Track { DarkMind = SharedTrack.Count, ShadowWall, LivingDead, TheBlackestNight, Oblation, DarkMissionary, Shadowstride } public enum WallOption { None, ShadowWall, ShadowedVigil } //ShadowWall strategy public enum TBNStrategy { None, Force } //TheBlackestNight strategy - public enum OblationStrategy { None, Force } //Oblation strategy - public enum DashStrategy { None, GapClose } //GapCloser strategy - public bool InMeleeRange(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Checks if we're inside melee range - public float GetStatusDetail(Actor target, DRK.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, DRK.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect + public enum OblationStrategy { None, Force, ForceHold1 } //Oblation strategy + public enum DashStrategy { None, GapClose, GapCloseHold1 } //GapCloser strategy public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(DRK.AID.DarkForce); public static readonly ActionID IDStanceApply = ActionID.MakeSpell(DRK.AID.Grit); @@ -38,6 +35,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.Oblation).As("Oblation", "", 550) //60s (120s total), 10s duration, 2 charges .AddOption(OblationStrategy.None, "None", "Do not use automatically") .AddOption(OblationStrategy.Force, "Use", "Use Oblation", 60, 10, ActionTargets.Self | ActionTargets.Party, 82) + .AddOption(OblationStrategy.ForceHold1, "UseHold1", "Use Oblation; Holds 1 charge for manual usage", 60, 10, ActionTargets.Self | ActionTargets.Party, 82) .AddAssociatedActions(DRK.AID.Oblation); DefineSimpleConfig(res, Track.DarkMissionary, "DarkMissionary", "Mission", 220, DRK.AID.DarkMissionary, 15); //90s CD, 15s duration @@ -45,6 +43,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.Shadowstride).As("Shadowstride", "Dash", 20) .AddOption(DashStrategy.None, "None", "No use") .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 30, 0, ActionTargets.Hostile, 56) + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 84) .AddAssociatedActions(DRK.AID.Shadowstride); return res; @@ -61,19 +60,22 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, var canTBN = ActionUnlocked(ActionID.MakeSpell(DRK.AID.TheBlackestNight)) && Player.HPMP.CurMP >= 3000; var tbn = strategy.Option(Track.TheBlackestNight); var tbnTarget = ResolveTargetOverride(tbn.Value) ?? CoTank() ?? primaryTarget ?? Player; //Smart-Targets Co-Tank if set to Automatic, if no Co-Tank then targets self - if (tbn.As() == TBNStrategy.Force && - canTBN && - !HasEffect(tbnTarget, DRK.SID.TheBlackestNight, 7)) + if (canTBN && tbn.As() == TBNStrategy.Force) Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRK.AID.TheBlackestNight), tbnTarget, tbn.Priority(), tbn.Value.ExpireIn); //Oblation execution var canObl = ActionUnlocked(ActionID.MakeSpell(DRK.AID.Oblation)); - var obl = strategy.Option(Track.Oblation); - var oblTarget = ResolveTargetOverride(obl.Value) ?? primaryTarget ?? Player; //Smart-Targets Co-Tank if set to Automatic, if no Co-Tank then targets self - if (obl.As() == OblationStrategy.Force && - canObl && - !HasEffect(oblTarget, DRK.SID.Oblation, 9)) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRK.AID.Oblation), oblTarget, obl.Priority(), obl.Value.ExpireIn); + var oblation = strategy.Option(Track.Oblation); + var oblationStrat = oblation.As(); + var oblationTarget = ResolveTargetOverride(oblation.Value) ?? primaryTarget ?? Player; //Smart-Targets Co-Tank if set to Automatic, if no Co-Tank then targets self + var oblationStatus = StatusDetails(oblationTarget, DRK.SID.Oblation, Player.InstanceID).Left > 0.1f; + var oblationCD = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRK.AID.Oblation)!.MainCooldownGroup].Remaining; + if (canObl && oblationStrat != OblationStrategy.None && !oblationStatus) + { + if ((oblationStrat == OblationStrategy.Force) || + (oblationStrat == OblationStrategy.ForceHold1 && oblationCD < 0.6f)) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRK.AID.Oblation), oblationTarget, oblation.Priority(), oblation.Value.ExpireIn); + } //Shadow Wall / Vigil execution var wall = strategy.Option(Track.ShadowWall); @@ -88,15 +90,18 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, //Shadowstride execution var dash = strategy.Option(Track.Shadowstride); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; var dashStrategy = strategy.Option(Track.Shadowstride).As(); - if (ShouldUseDash(dashStrategy, primaryTarget)) + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting + var distance = Player.DistanceToHitbox(dashTarget); + var dashCD = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRK.AID.Shadowstride)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3f and <= 20f && dashCD <= 30.5f, + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && dashCD < 0.6f, + _ => false, + }; + if (shouldDash) Hints.ActionsToExecute.Push(ActionID.MakeSpell(DRK.AID.Shadowstride), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.GapClose => !InMeleeRange(primaryTarget), - _ => false, - }; } diff --git a/BossMod/Autorotation/Utility/ClassGNBUtility.cs b/BossMod/Autorotation/Utility/ClassGNBUtility.cs index 0bacc018f1..b5fc68c9f7 100644 --- a/BossMod/Autorotation/Utility/ClassGNBUtility.cs +++ b/BossMod/Autorotation/Utility/ClassGNBUtility.cs @@ -4,11 +4,8 @@ public sealed class ClassGNBUtility(RotationModuleManager manager, Actor player) { public enum Track { Camouflage = SharedTrack.Count, Nebula, Aurora, Superbolide, HeartOfLight, HeartOfCorundum, Trajectory } //Our defensives and utilities public enum HoCOption { None, HeartOfStone, HeartOfCorundum } //Checks for proper HoC - public enum AuroraStrategy { None, Force } //Aurora - public enum DashStrategy { None, GapClose } //Gapcloser purposes - public bool InMeleeRange(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Checks if we're inside melee range - public float GetStatusDetail(Actor target, GNB.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, GNB.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect + public enum AuroraStrategy { None, Force, ForceHold1 } //Aurora + public enum DashStrategy { None, GapClose, GapCloseHold1 } //Gapcloser purposes public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(GNB.AID.GunmetalSoul); public static readonly ActionID IDStanceApply = ActionID.MakeSpell(GNB.AID.RoyalGuard); @@ -25,6 +22,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.Aurora).As("Aurora", "", 150) //60s (120s total), 18s duration, 2 charges .AddOption(AuroraStrategy.None, "None", "Do not use automatically") .AddOption(AuroraStrategy.Force, "Use", "Use Aurora", 60, 18, ActionTargets.Self | ActionTargets.Party, 45) + .AddOption(AuroraStrategy.ForceHold1, "UseHold1", "Use Aurora; Holds 1 charge for manual usage", 60, 18, ActionTargets.Self | ActionTargets.Party, 82) .AddAssociatedActions(GNB.AID.Aurora); DefineSimpleConfig(res, Track.Superbolide, "Superbolide", "Bolide", 600, GNB.AID.Superbolide, 10); //360s CD, 10s duration @@ -39,6 +37,7 @@ public static RotationModuleDefinition Definition() res.Define(Track.Trajectory).As("Trajectory", "Dash", 20) .AddOption(DashStrategy.None, "None", "No use") .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 30, 0, ActionTargets.Hostile, 56) + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 84) .AddAssociatedActions(GNB.AID.Trajectory); return res; @@ -53,41 +52,40 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.HeartOfLight), GNB.AID.HeartOfLight, Player); //Aurora execution - var aur = strategy.Option(Track.Aurora); - var aurTarget = ResolveTargetOverride(aur.Value) ?? primaryTarget ?? Player; //Smart-Targeting - var aurStatus = HasEffect(aurTarget, GNB.SID.Aurora, 17); //Checks if status is present - var aurora = aur.As() switch + var aurora = strategy.Option(Track.Aurora); + var auroraTarget = ResolveTargetOverride(aurora.Value) ?? primaryTarget ?? Player; //Smart-Targeting + var auroraStrat = aurora.As(); + var auroraCD = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRK.AID.Oblation)!.MainCooldownGroup].Remaining; + var hasAurora = StatusDetails(auroraTarget, GNB.SID.Aurora, Player.InstanceID).Left > 0.1f; //Checks if status is present + if (auroraStrat != AuroraStrategy.None && !hasAurora) { - AuroraStrategy.Force => GNB.AID.Aurora, - _ => default - }; - if (aurora != default && !aurStatus) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(GNB.AID.Aurora), aurTarget, aur.Priority(), aur.Value.ExpireIn); + if ((auroraStrat == AuroraStrategy.Force) || + (auroraStrat == AuroraStrategy.ForceHold1) && auroraCD <= 0.6f) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(GNB.AID.Aurora), auroraTarget, aurora.Priority(), aurora.Value.ExpireIn); + } //Heart of Stone / Corundum execution var hoc = strategy.Option(Track.HeartOfCorundum); var hocTarget = ResolveTargetOverride(hoc.Value) ?? CoTank() ?? primaryTarget ?? Player; //Smart-Targets Co-Tank if set to Automatic, if no Co-Tank then targets self - var hocStrat = hoc.As() switch - { - HoCOption.HeartOfStone => GNB.AID.HeartOfStone, - HoCOption.HeartOfCorundum => GNB.AID.HeartOfCorundum, - _ => default - }; + var BestHOC = ActionUnlocked(ActionID.MakeSpell(GNB.AID.HeartOfCorundum)) ? GNB.AID.HeartOfCorundum : GNB.AID.HeartOfStone; + var hocStrat = hoc.As(); if (hocStrat != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(ActionUnlocked(ActionID.MakeSpell(GNB.AID.HeartOfCorundum)) ? GNB.AID.HeartOfCorundum : GNB.AID.HeartOfStone), hocTarget, hoc.Priority(), hoc.Value.ExpireIn); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(BestHOC), hocTarget, hoc.Priority(), hoc.Value.ExpireIn); //Trajectory execution var dash = strategy.Option(Track.Trajectory); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; var dashStrategy = strategy.Option(Track.Trajectory).As(); - if (ShouldUseDash(dashStrategy, primaryTarget)) + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting + var distance = Player.DistanceToHitbox(dashTarget); + var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(GNB.AID.Trajectory)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3f and <= 20f && cd <= 30.5f, + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd < 0.6f, + _ => false, + }; + if (shouldDash) Hints.ActionsToExecute.Push(ActionID.MakeSpell(GNB.AID.Trajectory), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.GapClose => !InMeleeRange(primaryTarget), - _ => false, - }; - } diff --git a/BossMod/Autorotation/Utility/ClassMCHUtility.cs b/BossMod/Autorotation/Utility/ClassMCHUtility.cs index 3280cdb282..60c3d8dbad 100644 --- a/BossMod/Autorotation/Utility/ClassMCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassMCHUtility.cs @@ -9,21 +9,21 @@ public enum Track { Tactician = SharedTrack.Count, Dismantle } // Add Machinist LB3 public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(MCH.AID.SatelliteBeam); - public enum TactOption { None, Use87, Use88 } + public enum TactOption { None, Use87, Use87IfNotActive, Use88, Use88IfNotActive } public static RotationModuleDefinition Definition() { var res = new RotationModuleDefinition("Utility: MCH", "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", "Aimsucks", RotationModuleQuality.Excellent, BitMask.Build((int)Class.MCH), 100); DefineShared(res, IDLimitBreak3); - // Add track for Tactician: 15s long, 15% player damage received reduction res.Define(Track.Tactician).As("Tactician", "Tact", 400) .AddOption(TactOption.None, "None", "Do not use automatically") - .AddOption(TactOption.Use87, "Use", "Use Tactician", 120, 15, ActionTargets.Self, 56, 87) - .AddOption(TactOption.Use88, "Use88", "Use Tactician", 90, 15, ActionTargets.Self, 88) + .AddOption(TactOption.Use87, "Use", "Use Tactician (120s CD), regardless if equivalent ranged buff is already active", 120, 15, ActionTargets.Self, 56, 87) + .AddOption(TactOption.Use87IfNotActive, "UseIfNotActive", "Use Tactician (120s CD), unless equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 56, 87) + .AddOption(TactOption.Use88, "Use88", "Use Tactician (90s CD), regardless if equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 88) + .AddOption(TactOption.Use88IfNotActive, "Use88IfNotActive", "Use Tactician (90s CD), unless equivalent ranged buff is already active", 90, 15, ActionTargets.Self, 88) .AddAssociatedActions(MCH.AID.Tactician); - // Add track for Dismantle: 10s long, 10% target damage reduction DefineSimpleConfig(res, Track.Dismantle, "Dismantle", "Dism", 500, MCH.AID.Dismantle, 10); return res; @@ -32,10 +32,16 @@ public static RotationModuleDefinition Definition() public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); - ExecuteSimple(strategy.Option(Track.Dismantle), MCH.AID.Dismantle, primaryTarget); + ExecuteSimple(strategy.Option(Track.Dismantle), MCH.AID.Dismantle, ResolveTargetOverride(strategy.Option(Track.Dismantle).Value) ?? primaryTarget); var tact = strategy.Option(Track.Tactician); + var hasDefensive = Player.FindStatus(BRD.SID.Troubadour) != null || Player.FindStatus(MCH.SID.Tactician) != null || Player.FindStatus(DNC.SID.ShieldSamba) != null; if (tact.As() != TactOption.None) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(MCH.AID.Tactician), Player, tact.Priority(), tact.Value.ExpireIn); + { + if (tact.As() is TactOption.Use87 or TactOption.Use88) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(MCH.AID.Tactician), Player, tact.Priority(), tact.Value.ExpireIn); + if (tact.As() is TactOption.Use87IfNotActive or TactOption.Use88IfNotActive && !hasDefensive) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(MCH.AID.Tactician), Player, tact.Priority(), tact.Value.ExpireIn); + } } } diff --git a/BossMod/Autorotation/Utility/ClassMNKUtility.cs b/BossMod/Autorotation/Utility/ClassMNKUtility.cs index 60a0bc041c..5f31df827f 100644 --- a/BossMod/Autorotation/Utility/ClassMNKUtility.cs +++ b/BossMod/Autorotation/Utility/ClassMNKUtility.cs @@ -3,12 +3,10 @@ public sealed class ClassMNKUtility(RotationModuleManager manager, Actor player) : RoleMeleeUtility(manager, player) { public enum Track { Mantra = SharedTrack.Count, RiddleOfEarth, Thunderclap } - public enum DashStrategy { None, Force, GapClose } + public enum DashStrategy { None, GapClose, GapCloseHold1, GapCloseHold2 } public float CDleft => World.Client.Cooldowns[ActionDefinitions.GCDGroup].Remaining; public bool InMeleeRange(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Checks if we're inside melee range - public const float DashMinCD = 0.8f; //Triple-weaving dash is not a good idea, since it might delay gcd for longer than normal anim lock - public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(MNK.AID.FinalHeaven); public static RotationModuleDefinition Definition() @@ -21,8 +19,9 @@ public static RotationModuleDefinition Definition() res.Define(Track.Thunderclap).As("Thunderclap", "Dash", 20) .AddOption(DashStrategy.None, "None", "No use") - .AddOption(DashStrategy.Force, "Force", "Use ASAP", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 35) - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 35) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range; uses all charges if needed", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 35) + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; holds 1 charge for manual usage", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 35) + .AddOption(DashStrategy.GapCloseHold2, "GapCloseHold2", "Use as gapcloser if outside melee range; holds 2 charges for manual usage", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 84) .AddAssociatedActions(MNK.AID.Thunderclap); return res; @@ -35,17 +34,19 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.RiddleOfEarth), MNK.AID.RiddleOfEarth, Player); var dash = strategy.Option(Track.Thunderclap); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting var dashStrategy = strategy.Option(Track.Thunderclap).As(); - if (ShouldUseDash(dashStrategy, dashTarget)) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(MNK.AID.Thunderclap), dashTarget, dash.Priority()); + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting + var distance = Player.DistanceToHitbox(dashTarget); + var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(MNK.AID.Thunderclap)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3f and <= 20f, + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd <= 30.6f, + DashStrategy.GapCloseHold2 => distance is > 3f and <= 20f && cd <= 0.6f, + _ => false, + }; + if (shouldDash) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(MNK.AID.Thunderclap), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.Force => CDleft >= DashMinCD, - DashStrategy.GapClose => !InMeleeRange(primaryTarget), - _ => false, - }; } diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 75e916a25a..e84c98241b 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 Shukuchi as gapcloser if outside melee range of any target; will dash to target selected via CDPlanner. If none selected, will dash to current target or not at all if no target", 60, 0, ActionTargets.All, 40) - .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use Shukuchi as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.All, 76) + .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, 74) .AddAssociatedActions(NIN.AID.Shukuchi); return res; @@ -29,19 +29,20 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.ShadeShift), NIN.AID.ShadeShift, Player); var dash = strategy.Option(Track.Shukuchi); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; var dashStrategy = strategy.Option(Track.Shukuchi).As(); - if (ShouldUseDash(dashStrategy, primaryTarget)) - QueueOGCD(NIN.AID.Shukuchi, dashTarget); - + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting + var distance = Player.DistanceToHitbox(dashTarget); + var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(NIN.AID.Shukuchi)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3f and <= 20f, + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd <= 60.5f, + _ => false, + }; + if (shouldDash) + QueueOGCD(NIN.AID.Shukuchi, dashTarget, 3000); } - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.GapClose => Player.DistanceToHitbox(primaryTarget) is > 3 and <= 20, - DashStrategy.GapCloseHold1 => Player.DistanceToHitbox(primaryTarget) is > 3 and <= 20 && World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRG.AID.WingedGlide)!.MainCooldownGroup].Remaining <= 59.9f, - _ => false, - }; #region Core Execution Helpers diff --git a/BossMod/Autorotation/Utility/ClassPCTUtility.cs b/BossMod/Autorotation/Utility/ClassPCTUtility.cs index 0a12b307b8..4010312c79 100644 --- a/BossMod/Autorotation/Utility/ClassPCTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPCTUtility.cs @@ -3,6 +3,7 @@ public sealed class ClassPCTUtility(RotationModuleManager manager, Actor player) : RoleCasterUtility(manager, player) { public enum Track { TemperaCoat = SharedTrack.Count } + public enum TemperaCoatOption { None, CoatOnly, CoatGrassaASAP, CoatGrassaWhenever } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(PCT.AID.ChromaticFantasy); @@ -11,7 +12,12 @@ public static RotationModuleDefinition Definition() var res = new RotationModuleDefinition("Utility: PCT", "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.Good, BitMask.Build((int)Class.PCT), 100); DefineShared(res, IDLimitBreak3); - DefineSimpleConfig(res, Track.TemperaCoat, "Tempora Coat", "T.Coat", 600, PCT.AID.TemperaCoat, 10); + res.Define(Track.TemperaCoat).As("Tempera Coat", "T.Coat", 600) + .AddOption(TemperaCoatOption.None, "None", "Do not use automatically") + .AddOption(TemperaCoatOption.CoatOnly, "Tempera Coat Only", "Use Tempera Coat only; ignores Tempera Grassa (if available)", 60, 10, ActionTargets.Self, 10) + .AddOption(TemperaCoatOption.CoatGrassaASAP, "Tempera Coat + Grassa ASAP", "Use Tempera Coat + Tempera Grassa ASAP, regardless of casting & weaving", 90, 10, ActionTargets.Self, 88) + .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); return res; } @@ -19,6 +25,34 @@ public static RotationModuleDefinition Definition() public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); - ExecuteSimple(strategy.Option(Track.TemperaCoat), PCT.AID.TemperaCoat, Player); + + var canCoat = ActionUnlocked(ActionID.MakeSpell(PCT.AID.TemperaCoat)) && World.Client.Cooldowns[ActionDefinitions.Instance.Spell(PCT.AID.TemperaCoat)!.MainCooldownGroup].Remaining < 0.6f; + var hasCoat = StatusDetails(Player, PCT.SID.TemperaCoat, Player.InstanceID).Left > 0.1f; + var canGrassa = ActionUnlocked(ActionID.MakeSpell(PCT.AID.TemperaGrassa)) && hasCoat; + var hasGrassa = StatusDetails(Player, PCT.SID.TemperaGrassa, Player.InstanceID).Left > 0.1f; + var tempera = strategy.Option(Track.TemperaCoat); + var temperaStrat = tempera.As(); + if (temperaStrat != TemperaCoatOption.None) + { + if (temperaStrat == TemperaCoatOption.CoatOnly) + { + if (canCoat && (!hasCoat || !hasGrassa)) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaCoat), Player, tempera.Priority(), tempera.Value.ExpireIn); + } + if (temperaStrat == TemperaCoatOption.CoatGrassaASAP) + { + if (canCoat && !hasCoat) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaCoat), Player, tempera.Priority() + 2000, tempera.Value.ExpireIn); + if (canGrassa && !hasGrassa) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaGrassa), Player, tempera.Priority() + 2000, tempera.Value.ExpireIn); + } + if (temperaStrat == TemperaCoatOption.CoatGrassaWhenever) + { + if (canCoat && !hasCoat) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaCoat), Player, tempera.Priority(), tempera.Value.ExpireIn); + if (canGrassa && !hasGrassa) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(PCT.AID.TemperaGrassa), Player, tempera.Priority(), tempera.Value.ExpireIn); + } + } } } diff --git a/BossMod/Autorotation/Utility/ClassPLDUtility.cs b/BossMod/Autorotation/Utility/ClassPLDUtility.cs index 265cbc826f..8afa1ad0a6 100644 --- a/BossMod/Autorotation/Utility/ClassPLDUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPLDUtility.cs @@ -34,7 +34,6 @@ public static RotationModuleDefinition Definition() 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 DefineSimpleConfig(res, Track.HallowedGround, "HallowedGround", "Inv", 400, PLD.AID.HallowedGround, 10); //420s CD, 10s duration - //DefineSimpleConfig(res, Track.Clemency, "Clemency", "Clem", 420, PLD.AID.Clemency); (TODO: we don't really care about this, do we? maybe later) return res; } @@ -42,12 +41,11 @@ public static RotationModuleDefinition Definition() public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { ExecuteShared(strategy, IDLimitBreak3, IDStanceApply, IDStanceRemove, (uint)PLD.SID.IronWill, primaryTarget); - ExecuteSimple(strategy.Option(Track.Cover), PLD.AID.Cover, ResolveTargetOverride(strategy.Option(Track.Cover).Value) ?? Player); //Cover execution + ExecuteSimple(strategy.Option(Track.Cover), PLD.AID.Cover, ResolveTargetOverride(strategy.Option(Track.Cover).Value) ?? 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 - //DefineSimpleConfig(res, Track.Clemency, "Clemency", "Clem", 420, PLD.AID.Clemency); (TODO: we don't really care about this, do we? maybe later) var shel = strategy.Option(Track.Sheltron); var shelAction = shel.As() switch diff --git a/BossMod/Autorotation/Utility/ClassSCHUtility.cs b/BossMod/Autorotation/Utility/ClassSCHUtility.cs index 31f1fd892c..7fa63d3ff3 100644 --- a/BossMod/Autorotation/Utility/ClassSCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSCHUtility.cs @@ -9,10 +9,6 @@ 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 float GetStatusDetail(Actor target, SGE.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, SGE.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect - public float GetStatusDetail(Actor target, SCH.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, SCH.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect public Actor? TargetChoice(StrategyValues.OptionRef strategy) => ResolveTargetOverride(strategy.Value); public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(SCH.AID.AngelFeathers); @@ -96,7 +92,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Expedient), SCH.AID.Expedient, Player); ExecuteSimple(strategy.Option(Track.Seraphism), SCH.AID.Seraphism, Player); - var alreadyUp = HasEffect(Player, SGE.SID.EukrasianPrognosis, 30) || HasEffect(Player, SCH.SID.Galvanize, 30); + 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); var succAction = succ.As() switch { @@ -104,7 +100,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, SuccorOption.Concitation => SCH.AID.Concitation, _ => default }; - if (succAction != default && !alreadyUp) + if (succAction != default && !shieldUp) QueueOGCD(succAction, Player); var soil = strategy.Option(Track.SacredSoil); @@ -120,15 +116,18 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, if (deploy.As() != DeployOption.None) QueueOGCD(SCH.AID.DeploymentTactics, Player); + var pact = strategy.Option(Track.Aetherpact); - var pactAction = pact.As() switch + var pactStrat = pact.As(); + var pactTarget = TargetChoice(strategy.Option(Track.Aetherpact)) ?? primaryTarget ?? Player; + var juicing = pactTarget.FindStatus(SCH.SID.FeyUnion) != null; + if (pactStrat != AetherpactOption.None) { - AetherpactOption.Use => SCH.AID.Aetherpact, - AetherpactOption.End => SCH.AID.DissolveUnion, - _ => default - }; - if (pactAction != default) - QueueOGCD(pactAction, Player); + if (pactStrat == AetherpactOption.Use && !juicing) + QueueOGCD(SCH.AID.Aetherpact, pactTarget); + if (pactStrat == AetherpactOption.End && juicing) + QueueOGCD(SCH.AID.DissolveUnion, pactTarget); + } var recit = strategy.Option(Track.Recitation); if (recit.As() != RecitationOption.None) diff --git a/BossMod/Autorotation/Utility/ClassSGEUtility.cs b/BossMod/Autorotation/Utility/ClassSGEUtility.cs index 298252edae..a65dd8198c 100644 --- a/BossMod/Autorotation/Utility/ClassSGEUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSGEUtility.cs @@ -2,18 +2,13 @@ public sealed class ClassSGEUtility(RotationModuleManager manager, Actor player) : RoleHealerUtility(manager, player) { - public enum Track { Kardia = SharedTrack.Count, Physis, Eukrasia, Diagnosis, Prognosis, Druochole, Kerachole, Ixochole, Zoe, Pepsis, Taurochole, Haima, Rhizomata, Holos, Panhaima, Krasis, Philosophia, Icarus } + public enum Track { Kardia = SharedTrack.Count, Physis, Eukrasia, Diagnosis, Prognosis, Druochole, Kerachole, Ixochole, Zoe, Pepsis, Taurochole, Haima, Rhizomata, Holos, Panhaima, Krasis, Pneuma, Philosophia, Icarus } public enum KardiaOption { None, Kardia, Soteria } - public enum DiagOption { None, Use, UseED } - public enum ProgOption { None, Use, UseEP, UseEPEx } + public enum DiagnosisOption { None, Use, UseED } + public enum PrognosisOption { None, Use, UseEP, UseEPEx } public enum PhysisOption { None, Use, UseEx } public enum ZoeOption { None, Use, UseEx } - public enum DashStrategy { None, Force, GapClose } //GapCloser strategy - public bool InMeleeRange(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Checks if we're inside melee range - public float GetStatusDetail(Actor target, SGE.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, SGE.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect - public float GetStatusDetail(Actor target, SCH.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, SCH.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect + public enum DashStrategy { None, GapClose } public Actor? TargetChoice(StrategyValues.OptionRef strategy) => ResolveTargetOverride(strategy.Value); public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(SGE.AID.TechneMakre); @@ -37,17 +32,17 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Eukrasia, "Eukrasia", "", 110, SGE.AID.Eukrasia); //Eukrasia (spell only) - res.Define(Track.Diagnosis).As("Diagnosis", "Diag", 200) //Diagnosis & EukrasianDiagnosis - .AddOption(DiagOption.None, "None", "Do not use automatically") - .AddOption(DiagOption.Use, "Use", "Use Diagnosis", 0, 0, ActionTargets.Self | ActionTargets.Party, 2) - .AddOption(DiagOption.UseED, "UseEx", "Use Eukrasian Diagnosis", 0, 30, ActionTargets.Self | ActionTargets.Party, 30) + res.Define(Track.Diagnosis).As("Diagnosis", "Diag", 200) //Diagnosis & EukrasianDiagnosis + .AddOption(DiagnosisOption.None, "None", "Do not use automatically") + .AddOption(DiagnosisOption.Use, "Use", "Use normal Diagnosis", 0, 0, ActionTargets.Self | ActionTargets.Party, 2) + .AddOption(DiagnosisOption.UseED, "UseED", "Use Eukrasian Diagnosis", 0, 30, ActionTargets.Self | ActionTargets.Party, 30) .AddAssociatedActions(SGE.AID.Diagnosis, SGE.AID.EukrasianDiagnosis); - res.Define(Track.Prognosis).As("Prognosis", "Prog", 200) //Prognosis & EukrasianPrognosis - .AddOption(ProgOption.None, "None", "Do not use automatically") - .AddOption(ProgOption.Use, "Use", "Use Prognosis", 0, 0, ActionTargets.Self, 2) - .AddOption(ProgOption.UseEP, "UseEx", "Use Eukrasian Prognosis", 0, 30, ActionTargets.Self, 30, 95) - .AddOption(ProgOption.UseEPEx, "UseEx", "Use Eukrasian Prognosis II", 0, 30, ActionTargets.Self, 96) + res.Define(Track.Prognosis).As("Prognosis", "Prog", 200) //Prognosis & EukrasianPrognosis + .AddOption(PrognosisOption.None, "None", "Do not use automatically") + .AddOption(PrognosisOption.Use, "Use", "Use normal Prognosis", 0, 0, ActionTargets.Self, 2) + .AddOption(PrognosisOption.UseEP, "UseEP", "Use Eukrasian Prognosis", 0, 30, ActionTargets.Self, 30, 95) + .AddOption(PrognosisOption.UseEPEx, "UseEPEx", "Use Eukrasian Prognosis II", 0, 30, ActionTargets.Self, 96) .AddAssociatedActions(SGE.AID.Prognosis, SGE.AID.EukrasianPrognosis, SGE.AID.EukrasianPrognosisII); DefineSimpleConfig(res, Track.Druochole, "Druochole", "Druo", 150, SGE.AID.Druochole); //Druochole @@ -67,12 +62,12 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Holos, "Holos", "", 240, SGE.AID.Holos, 20); //Holos DefineSimpleConfig(res, Track.Panhaima, "Panhaima", "", 250, SGE.AID.Panhaima, 15); //Panhaima DefineSimpleConfig(res, Track.Krasis, "Krasis", "", 210, SGE.AID.Krasis, 10); //Krasis + DefineSimpleConfig(res, Track.Pneuma, "Pneuma", "", 220, SGE.AID.Pneuma); //Pneuma DefineSimpleConfig(res, Track.Philosophia, "Philosophia", "Philo", 260, SGE.AID.Philosophia, 20); //Philosophia res.Define(Track.Icarus).As("Icarus", "", 20) .AddOption(DashStrategy.None, "None", "No use") - .AddOption(DashStrategy.Force, "Force", "Use ASAP", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 45) - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 45) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 45, 0, ActionTargets.Party | ActionTargets.Hostile, 45) .AddAssociatedActions(SGE.AID.Icarus); return res; @@ -82,81 +77,73 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, { ExecuteShared(strategy, IDLimitBreak3, primaryTarget); ExecuteSimple(strategy.Option(Track.Eukrasia), SGE.AID.Eukrasia, Player); - ExecuteSimple(strategy.Option(Track.Druochole), SGE.AID.Druochole, TargetChoice(strategy.Option(Track.Druochole)) ?? Player); + ExecuteSimple(strategy.Option(Track.Druochole), SGE.AID.Druochole, TargetChoice(strategy.Option(Track.Druochole)) ?? primaryTarget ?? Player); ExecuteSimple(strategy.Option(Track.Kerachole), SGE.AID.Kerachole, Player); ExecuteSimple(strategy.Option(Track.Ixochole), SGE.AID.Ixochole, Player); ExecuteSimple(strategy.Option(Track.Pepsis), SGE.AID.Pepsis, Player); - ExecuteSimple(strategy.Option(Track.Taurochole), SGE.AID.Taurochole, TargetChoice(strategy.Option(Track.Taurochole)) ?? Player); - ExecuteSimple(strategy.Option(Track.Haima), SGE.AID.Haima, TargetChoice(strategy.Option(Track.Haima)) ?? Player); + ExecuteSimple(strategy.Option(Track.Taurochole), SGE.AID.Taurochole, TargetChoice(strategy.Option(Track.Taurochole)) ?? primaryTarget ?? Player); + ExecuteSimple(strategy.Option(Track.Haima), SGE.AID.Haima, TargetChoice(strategy.Option(Track.Haima)) ?? primaryTarget ?? Player); ExecuteSimple(strategy.Option(Track.Rhizomata), SGE.AID.Rhizomata, Player); ExecuteSimple(strategy.Option(Track.Holos), SGE.AID.Holos, Player); ExecuteSimple(strategy.Option(Track.Panhaima), SGE.AID.Panhaima, Player); ExecuteSimple(strategy.Option(Track.Krasis), SGE.AID.Krasis, Player); ExecuteSimple(strategy.Option(Track.Philosophia), SGE.AID.Philosophia, Player); + if (World.Client.Cooldowns[ActionDefinitions.Instance.Spell(SGE.AID.Pneuma)!.MainCooldownGroup].Remaining < 0.6f) + { + ExecuteSimple(strategy.Option(Track.Pneuma), SGE.AID.Pneuma, TargetChoice(strategy.Option(Track.Pneuma)) ?? primaryTarget); + } + //Kardia full execution var kardia = strategy.Option(Track.Kardia); - var kardiaAction = kardia.As() switch + var kardiaStrat = kardia.As(); + var kardiaTarget = TargetChoice(kardia) ?? primaryTarget ?? Player; + var hasKardia = kardiaTarget.FindStatus(SGE.SID.Kardia) != null; + if (kardiaStrat != KardiaOption.None) { - KardiaOption.Kardia => SGE.AID.Kardia, - KardiaOption.Soteria => SGE.AID.Soteria, - _ => default - }; - if (kardiaAction != default) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(kardiaAction), TargetChoice(kardia) ?? Player, kardia.Priority(), kardia.Value.ExpireIn); + if (kardiaStrat == KardiaOption.Kardia && !hasKardia) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Kardia), kardiaTarget, kardia.Priority(), kardia.Value.ExpireIn); + if (kardiaStrat == KardiaOption.Soteria) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Soteria), Player, kardia.Priority(), kardia.Value.ExpireIn); + } //Diagnosis full execution - var hasEukrasia = HasEffect(Player, SGE.SID.Eukrasia, 1000); //Eukrasia - var diag = strategy.Option(Track.Diagnosis); - var diagAction = diag.As() switch + var hasEukrasia = Player.FindStatus(SGE.SID.Eukrasia) != null; + var ed = strategy.Option(Track.Diagnosis); + var edStrat = ed.As(); + var edTarget = TargetChoice(ed) ?? primaryTarget ?? Player; + if (edStrat != DiagnosisOption.None) { - DiagOption.Use => SGE.AID.Diagnosis, - DiagOption.UseED => SGE.AID.EukrasianDiagnosis, - _ => default - }; - - if (diagAction != default) - { - if (!hasEukrasia) + if (edStrat == DiagnosisOption.Use) { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(diagAction), primaryTarget, diag.Priority(), diag.Value.ExpireIn, castTime: ActionDefinitions.Instance.Spell(diagAction)!.CastTime); // TODO[cast-time]: this probably needs explicit cast-time argument (adjusted by swiftcast etc) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Diagnosis), primaryTarget, ed.Priority(), ed.Value.ExpireIn, castTime: ActionDefinitions.Instance.Spell(SGE.AID.Diagnosis)!.CastTime); } - - if (hasEukrasia) + if (edStrat == DiagnosisOption.UseED) { - if (diag.As() == DiagOption.UseED) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.EukrasianDiagnosis), TargetChoice(diag) ?? Player, diag.Priority(), diag.Value.ExpireIn); - } + if (!hasEukrasia) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Eukrasia), Player, ed.Priority(), ed.Value.ExpireIn); + if (hasEukrasia) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.EukrasianDiagnosis), edTarget, ed.Priority(), ed.Value.ExpireIn); } } //Prognosis full execution - var alreadyUp = HasEffect(Player, SGE.SID.EukrasianPrognosis, 30) || HasEffect(Player, SCH.SID.Galvanize, 30); - var prog = strategy.Option(Track.Prognosis); - var progAction = prog.As() switch - { - ProgOption.Use => SGE.AID.Prognosis, - ProgOption.UseEP => SGE.AID.Eukrasia, - ProgOption.UseEPEx => SGE.AID.Eukrasia, - _ => default - }; + var shieldUp = StatusDetails(Player, SCH.SID.Galvanize, Player.InstanceID).Left > 0.1f || StatusDetails(Player, SGE.SID.EukrasianPrognosis, Player.InstanceID).Left > 0.1f; + var ep = strategy.Option(Track.Prognosis); + var epStrat = ep.As(); - if (progAction != default) + if (epStrat != PrognosisOption.None) { - if (!hasEukrasia)// Push the primary action based on the selected option - Hints.ActionsToExecute.Push(ActionID.MakeSpell(progAction), Player, prog.Priority(), prog.Value.ExpireIn, castTime: ActionDefinitions.Instance.Spell(progAction)!.CastTime); // TODO[cast-time]: this probably needs explicit cast-time argument (adjusted by swiftcast etc) - - if (hasEukrasia && !alreadyUp) + if (epStrat == PrognosisOption.Use) { - if (prog.As() == ProgOption.UseEP) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.EukrasianPrognosis), Player, prog.Priority(), prog.Value.ExpireIn); - } - if (prog.As() == ProgOption.UseEPEx) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.EukrasianPrognosisII), Player, prog.Priority(), prog.Value.ExpireIn); - } + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Prognosis), Player, ep.Priority(), ep.Value.ExpireIn, castTime: ActionDefinitions.Instance.Spell(SGE.AID.Prognosis)!.CastTime); + } + if (epStrat is PrognosisOption.UseEP or PrognosisOption.UseEPEx) + { + if (!hasEukrasia) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Eukrasia), Player, ep.Priority(), ep.Value.ExpireIn); + if (hasEukrasia && !shieldUp) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.EukrasianPrognosis), Player, ep.Priority(), ep.Value.ExpireIn); } } @@ -172,16 +159,16 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, //Icarus execution var dash = strategy.Option(Track.Icarus); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting var dashStrategy = strategy.Option(Track.Icarus).As(); - if (ShouldUseDash(dashStrategy, dashTarget)) + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting + var distance = Player.DistanceToHitbox(dashTarget); + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3f and <= 20f, + _ => false, + }; + if (shouldDash) Hints.ActionsToExecute.Push(ActionID.MakeSpell(SGE.AID.Icarus), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.Force => true, - DashStrategy.GapClose => !InMeleeRange(primaryTarget), - _ => false, - }; } diff --git a/BossMod/Autorotation/Utility/ClassSMNUtility.cs b/BossMod/Autorotation/Utility/ClassSMNUtility.cs index 36268688ce..2d2daa7a33 100644 --- a/BossMod/Autorotation/Utility/ClassSMNUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSMNUtility.cs @@ -4,9 +4,6 @@ public sealed class ClassSMNUtility(RotationModuleManager manager, Actor player) { public enum Track { RadiantAegis = SharedTrack.Count } public enum AegisStrategy { None, Use } - public float GetStatusDetail(Actor target, SMN.SID sid) => StatusDetails(target, sid, Player.InstanceID).Left; //Checks if Status effect is on target - public bool HasEffect(Actor target, SMN.SID sid, float duration) => GetStatusDetail(target, sid) < duration; //Checks if anyone has a status effect - public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(SMN.AID.Teraflare); @@ -19,6 +16,8 @@ public static RotationModuleDefinition Definition() .AddOption(AegisStrategy.None, "None", "No use") .AddOption(AegisStrategy.Use, "Use", "Use Radiant Aegis", 60, 30, ActionTargets.Self, 2); + //TODO: Rekindle here or inside own module? + return res; } @@ -27,9 +26,8 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteShared(strategy, IDLimitBreak3, primaryTarget); var radi = strategy.Option(Track.RadiantAegis); - var radiTarget = ResolveTargetOverride(radi.Value) ?? Player; - var hasAegis = HasEffect(radiTarget, SMN.SID.RadiantAegis, 30); + var hasAegis = StatusDetails(Player, SMN.SID.RadiantAegis, Player.InstanceID, 30).Left > 0.1f; if (radi.As() != AegisStrategy.None && !hasAegis) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(SMN.AID.RadiantAegis), radiTarget, radi.Priority(), radi.Value.ExpireIn); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SMN.AID.RadiantAegis), Player, radi.Priority(), radi.Value.ExpireIn); } } diff --git a/BossMod/Autorotation/Utility/ClassVPRUtility.cs b/BossMod/Autorotation/Utility/ClassVPRUtility.cs index 3ddcbf6865..852514d977 100644 --- a/BossMod/Autorotation/Utility/ClassVPRUtility.cs +++ b/BossMod/Autorotation/Utility/ClassVPRUtility.cs @@ -3,11 +3,8 @@ public sealed class ClassVPRUtility(RotationModuleManager manager, Actor player) : RoleMeleeUtility(manager, player) { public enum Track { Slither = SharedTrack.Count } - public enum DashStrategy { None, Force, GapClose } //GapCloser strategy - public float CDleft => World.Client.Cooldowns[ActionDefinitions.GCDGroup].Remaining; - public bool InMeleeRange(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Checks if we're inside melee range + public enum DashStrategy { None, GapClose, GapCloseHold1, GapCloseHold2 } - public const float DashMinCD = 0.8f; //Triple-weaving dash is not a good idea, since it might delay gcd for longer than normal anim lock public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(VPR.AID.WorldSwallower); public static RotationModuleDefinition Definition() @@ -17,8 +14,9 @@ public static RotationModuleDefinition Definition() res.Define(Track.Slither).As("Slither", "", 20) .AddOption(DashStrategy.None, "None", "No use") - .AddOption(DashStrategy.Force, "Force", "Use ASAP", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 40) - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 40) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range; uses all charges if needed", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 35) + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; holds 1 charge for manual usage", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 35) + .AddOption(DashStrategy.GapCloseHold2, "GapCloseHold2", "Use as gapcloser if outside melee range; holds 2 charges for manual usage", 30, 0, ActionTargets.Party | ActionTargets.Hostile, 84) .AddAssociatedActions(VPR.AID.Slither); return res; @@ -29,17 +27,19 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteShared(strategy, IDLimitBreak3, primaryTarget); var dash = strategy.Option(Track.Slither); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting var dashStrategy = strategy.Option(Track.Slither).As(); - if (ShouldUseDash(dashStrategy, dashTarget)) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(VPR.AID.Slither), dashTarget, dash.Priority()); + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting + var distance = Player.DistanceToHitbox(dashTarget); + var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(VPR.AID.Slither)!.MainCooldownGroup].Remaining; + var shouldDash = dashStrategy switch + { + DashStrategy.None => false, + DashStrategy.GapClose => distance is > 3f and <= 20f, + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd <= 30.6f, + DashStrategy.GapCloseHold2 => distance is > 3f and <= 20f && cd <= 0.6f, + _ => false, + }; + if (shouldDash) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(VPR.AID.Slither), dashTarget, dash.Priority(), dash.Value.ExpireIn); } - - private bool ShouldUseDash(DashStrategy strategy, Actor? primaryTarget) => strategy switch - { - DashStrategy.None => false, - DashStrategy.Force => CDleft >= DashMinCD, - DashStrategy.GapClose => !InMeleeRange(primaryTarget), - _ => false, - }; } diff --git a/BossMod/Autorotation/Utility/RoleCasterUtility.cs b/BossMod/Autorotation/Utility/RoleCasterUtility.cs index 5a2d53df7e..28650d23bf 100644 --- a/BossMod/Autorotation/Utility/RoleCasterUtility.cs +++ b/BossMod/Autorotation/Utility/RoleCasterUtility.cs @@ -21,15 +21,15 @@ protected static void DefineShared(RotationModuleDefinition def, ActionID lb3) .AddAssociatedActions(ClassShared.AID.Addle); DefineSimpleConfig(def, SharedTrack.Sleep, "Sleep", "", -10, ClassShared.AID.Sleep); - DefineSimpleConfig(def, SharedTrack.LucidDreaming, "LucidDreaming", "Lucid", 30, ClassShared.AID.LucidDreaming, 21); + DefineSimpleConfig(def, SharedTrack.LucidDreaming, "LucidDreaming", "Lucid D.", 30, ClassShared.AID.LucidDreaming, 21); - def.Define(SharedTrack.Swiftcast).As("Swiftcast", "Swift", 20) + def.Define(SharedTrack.Swiftcast).As("Swiftcast", "Swiftcast", 20) .AddOption(SwiftcastOption.None, "None", "Do not use automatically") .AddOption(SwiftcastOption.Use, "Use", "Use Swiftcast (10s)", 60, 10, ActionTargets.Self, 22, 93) .AddOption(SwiftcastOption.UseEx, "UseEx", "Use Swiftcast (15s)", 40, 10, ActionTargets.Self, 94) .AddAssociatedActions(ClassShared.AID.Swiftcast); - DefineSimpleConfig(def, SharedTrack.Surecast, "Surecast", "Anti-KB", 10, ClassShared.AID.Surecast, 6); // note: secondary effect 15s + DefineSimpleConfig(def, SharedTrack.Surecast, "Surecast", "", 10, ClassShared.AID.Surecast, 6); // note: secondary effect 15s } protected void ExecuteShared(StrategyValues strategy, ActionID lb3, Actor? primaryTarget) diff --git a/BossMod/Autorotation/Utility/RoleMeleeUtility.cs b/BossMod/Autorotation/Utility/RoleMeleeUtility.cs index c3088d4768..4daddd03b4 100644 --- a/BossMod/Autorotation/Utility/RoleMeleeUtility.cs +++ b/BossMod/Autorotation/Utility/RoleMeleeUtility.cs @@ -46,7 +46,7 @@ protected void ExecuteShared(StrategyValues strategy, ActionID lb3, Actor? prima var feint = strategy.Option(SharedTrack.Feint); if (feint.As() != FeintOption.None) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Feint), primaryTarget, feint.Priority(), feint.Value.ExpireIn); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Feint), ResolveTargetOverride(feint.Value) ?? primaryTarget, feint.Priority(), feint.Value.ExpireIn); } } diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index e5eb37b617..f299d119f6 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -1,7 +1,4 @@ -using AID = BossMod.GNB.AID; -using SID = BossMod.GNB.SID; - -namespace BossMod.Autorotation.Utility; +namespace BossMod.Autorotation.Utility; //Contribution by Akechi //Discord @akechdz or 'Akechi' on Puni.sh for maintenance @@ -71,7 +68,7 @@ public static RotationModuleDefinition Definition() .AddOption(ElixirStrategy.Far, "Far") .AddOption(ElixirStrategy.Force, "Force") .AddOption(ElixirStrategy.Hold, "Hold") - .AddAssociatedActions(AID.Elixir); + .AddAssociatedActions(ClassShared.AID.Elixir); res.Define(Track.Recuperate).As("Recuperate", uiPriority: 150) .AddOption(RecuperateStrategy.Automatic, "Automatic") .AddOption(RecuperateStrategy.Seventy, "Seventy") @@ -79,7 +76,7 @@ public static RotationModuleDefinition Definition() .AddOption(RecuperateStrategy.Thirty, "Thirty") .AddOption(RecuperateStrategy.Force, "Force") .AddOption(RecuperateStrategy.Hold, "Hold") - .AddAssociatedActions(AID.Recuperate); + .AddAssociatedActions(ClassShared.AID.Recuperate); res.Define(Track.Guard).As("Guard", uiPriority: 150) .AddOption(GuardStrategy.Automatic, "Automatic") .AddOption(GuardStrategy.Seventy, "Seventy") @@ -87,17 +84,17 @@ public static RotationModuleDefinition Definition() .AddOption(GuardStrategy.Thirty, "Thirty") .AddOption(GuardStrategy.Force, "Force") .AddOption(GuardStrategy.Hold, "Hold") - .AddAssociatedActions(AID.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") - .AddAssociatedActions(AID.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") - .AddAssociatedActions(AID.Sprint); + .AddAssociatedActions(ClassShared.AID.Sprint); return res; } @@ -126,30 +123,25 @@ public enum OGCDPriority private bool canGuard; private bool canPurify; private bool canSprint; - public float GCDLength; - public AID NextGCD; - private GCDPriority NextGCDPrio; #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(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; + private bool IsOffCooldown(ClassShared.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; #endregion public float DebuffsLeft(Actor? target) { return target == null ? 0f : Utils.MaxAll( - StatusDetails(target, SID.Silence, Player.InstanceID, 5).Left, - StatusDetails(target, SID.Stun, Player.InstanceID, 5).Left, - StatusDetails(target, SID.Bind, Player.InstanceID, 5).Left, - StatusDetails(target, SID.Heavy, Player.InstanceID, 5).Left, - StatusDetails(target, SID.Sleep, Player.InstanceID, 5).Left, - StatusDetails(target, SID.HalfAsleep, Player.InstanceID, 5).Left + StatusDetails(target, ClassShared.SID.Silence, Player.InstanceID, 5).Left, + StatusDetails(target, ClassShared.SID.StunPvP, Player.InstanceID, 5).Left, + StatusDetails(target, ClassShared.SID.Bind, Player.InstanceID, 5).Left, + StatusDetails(target, ClassShared.SID.Heavy, Player.InstanceID, 5).Left, + StatusDetails(target, ClassShared.SID.Sleep, Player.InstanceID, 5).Left, + StatusDetails(target, ClassShared.SID.HalfAsleep, Player.InstanceID, 5).Left ); } public bool HasAnyDebuff(Actor? target) => DebuffsLeft(target) > 0; @@ -157,44 +149,49 @@ public float DebuffsLeft(Actor? target) public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { #region Variables - hasSprint = HasEffect(SID.SprintPvP); + hasSprint = Player.FindStatus(ClassShared.SID.SprintPvP) != null; #region Minimal Requirements - canElixir = IsOffCooldown(AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Hold; + 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(AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Hold; - canPurify = IsOffCooldown(AID.Purify) && strategy.Option(Track.Purify).As() != DefensiveStrategy.Delay; + canGuard = IsOffCooldown(ClassShared.AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Hold; + 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)) - QueueGCD(AID.Elixir, Player, elixirStrat == ElixirStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.Elixir); + 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(); if (ShouldUseRecuperate(recuperateStrat)) - QueueOGCD(AID.Recuperate, Player, recuperateStrat == RecuperateStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Recuperate); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Recuperate), Player, strategy.Option(Track.Recuperate).Priority(), strategy.Option(Track.Recuperate).Value.ExpireIn); var guardStrat = strategy.Option(Track.Guard).As(); - if (ShouldUseGuard(guardStrat, primaryTarget)) - QueueOGCD(AID.Guard, Player, guardStrat == GuardStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Guard); + if (ShouldUseGuard(guardStrat)) + 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)) - QueueOGCD(AID.Purify, Player, purifyStrat == DefensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Purify); + 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(); - if (ShouldUseSprint(sprintStrat, primaryTarget)) - QueueOGCD(AID.SprintPvP, Player, sprintStrat == DefensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Sprint); + if (ShouldUseSprint(sprintStrat)) + 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 - private void QueueGCD(AID aid, Actor? target, GCDPriority prio) + /* + 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) { if (prio != GCDPriority.None) { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); // TODO[cast-time]: verify all callers + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); if (prio > NextGCDPrio) { NextGCD = aid; @@ -202,13 +199,14 @@ private void QueueGCD(AID aid, Actor? target, GCDPriority prio) } } } - private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) + 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 @@ -238,7 +236,7 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio _ => false, }; - public bool ShouldUseGuard(GuardStrategy strategy, Actor? target) => strategy switch + public bool ShouldUseGuard(GuardStrategy strategy) => strategy switch { GuardStrategy.Automatic => canGuard && @@ -261,7 +259,7 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio _ => false, }; - public bool ShouldUseSprint(DefensiveStrategy strategy, Actor? target) => strategy switch + public bool ShouldUseSprint(DefensiveStrategy strategy) => strategy switch { DefensiveStrategy.Automatic => !Player.InCombat && diff --git a/BossMod/Autorotation/Utility/RoleRangedUtility.cs b/BossMod/Autorotation/Utility/RoleRangedUtility.cs index 38d1ea8888..9ce8a0fc81 100644 --- a/BossMod/Autorotation/Utility/RoleRangedUtility.cs +++ b/BossMod/Autorotation/Utility/RoleRangedUtility.cs @@ -23,10 +23,10 @@ protected static void DefineShared(RotationModuleDefinition def, ActionID lb3) protected void ExecuteShared(StrategyValues strategy, ActionID lb3, Actor? primaryTarget) { ExecuteSimple(strategy.Option(SharedTrack.Sprint), ClassShared.AID.Sprint, Player); - ExecuteSimple(strategy.Option(SharedTrack.LegGraze), ClassShared.AID.LegGraze, primaryTarget); + ExecuteSimple(strategy.Option(SharedTrack.LegGraze), ClassShared.AID.LegGraze, ResolveTargetOverride(strategy.Option(SharedTrack.LegGraze).Value) ?? primaryTarget); ExecuteSimple(strategy.Option(SharedTrack.SecondWind), ClassShared.AID.SecondWind, Player); - ExecuteSimple(strategy.Option(SharedTrack.FootGraze), ClassShared.AID.FootGraze, primaryTarget); - ExecuteSimple(strategy.Option(SharedTrack.HeadGraze), ClassShared.AID.HeadGraze, primaryTarget); + ExecuteSimple(strategy.Option(SharedTrack.FootGraze), ClassShared.AID.FootGraze, ResolveTargetOverride(strategy.Option(SharedTrack.FootGraze).Value) ?? primaryTarget); + ExecuteSimple(strategy.Option(SharedTrack.HeadGraze), ClassShared.AID.HeadGraze, ResolveTargetOverride(strategy.Option(SharedTrack.HeadGraze).Value) ?? primaryTarget); ExecuteSimple(strategy.Option(SharedTrack.ArmsLength), ClassShared.AID.ArmsLength, Player); var lb = strategy.Option(SharedTrack.LB); diff --git a/BossMod/Autorotation/Utility/RoleTankUtility.cs b/BossMod/Autorotation/Utility/RoleTankUtility.cs index 88204d0fe2..090859ede0 100644 --- a/BossMod/Autorotation/Utility/RoleTankUtility.cs +++ b/BossMod/Autorotation/Utility/RoleTankUtility.cs @@ -41,9 +41,9 @@ protected void ExecuteShared(StrategyValues strategy, ActionID lb3, ActionID sta { ExecuteSimple(strategy.Option(SharedTrack.Sprint), ClassShared.AID.Sprint, Player); ExecuteSimple(strategy.Option(SharedTrack.Rampart), ClassShared.AID.Rampart, Player); - ExecuteSimple(strategy.Option(SharedTrack.LowBlow), ClassShared.AID.LowBlow, primaryTarget); - ExecuteSimple(strategy.Option(SharedTrack.Provoke), ClassShared.AID.Provoke, primaryTarget); - ExecuteSimple(strategy.Option(SharedTrack.Interject), ClassShared.AID.Interject, primaryTarget); + ExecuteSimple(strategy.Option(SharedTrack.LowBlow), ClassShared.AID.LowBlow, ResolveTargetOverride(strategy.Option(SharedTrack.LowBlow).Value) ?? primaryTarget); + ExecuteSimple(strategy.Option(SharedTrack.Provoke), ClassShared.AID.Provoke, ResolveTargetOverride(strategy.Option(SharedTrack.Provoke).Value) ?? primaryTarget); + ExecuteSimple(strategy.Option(SharedTrack.Interject), ClassShared.AID.Interject, ResolveTargetOverride(strategy.Option(SharedTrack.Interject).Value) ?? primaryTarget); ExecuteSimple(strategy.Option(SharedTrack.Shirk), ClassShared.AID.Shirk, CoTank()); ExecuteSimple(strategy.Option(SharedTrack.ArmsLength), ClassShared.AID.ArmsLength, Player);