From 1fd888e542cb9d29aa1b3d77acacd5ea0dcd1a9f Mon Sep 17 00:00:00 2001 From: LTS-FFXIV <127939494+LTS-FFXIV@users.noreply.github.com> Date: Sat, 4 Jan 2025 14:40:57 -0600 Subject: [PATCH] Update various classes with new properties and logic Updated `AST_Default` to remove `SmartCard` and add `SimpleLord` and `UseEarthlyStarTime`. Modified card usage logic to prevent overwriting. Updated `LordOfCrownsPvE` logic. Updated `SCH_Default` to add `RuinTime` and `DOTTime` properties. Adjusted `RecitationPvE`, `IndomitabilityPvE`, and `DeploymentTacticsPvE` logic. Updated `RuinIiPvE` and DOT spell logic for movement. Updated `RDM_Default` to add `SuicideByDumb` and `SuicideByDumber` properties. Modified `EngagementPvE` and `CorpsacorpsPvE` logic. Updated `ActionTargetInfo` to add `TheSpear` and `TheBalance` target types. Added `FindTheSpear` and `FindTheBalance` methods. Refactored `FindDancePartner`. Updated `TargetType` enum to include `TheBalance` and `TheSpear`. Updated `DataCenter` to add `MovingRaw` property. Updated `AstrologianRotation` to use new target types for `TheBalancePvE` and `TheSpearPvE`. Updated `CustomRotation` to add `MovingTime` property. Updated `RotationConfigWindow` to display `Moving Time`. Updated `ActionUpdater` to track player movement times and update `MovingRaw`. --- BasicRotations/Healer/AST_Default.cs | 29 ++++-- BasicRotations/Healer/SCH_Default.cs | 31 ++++--- BasicRotations/Magical/RDM_Default.cs | 11 ++- .../Actions/ActionTargetInfo.cs | 89 ++++++++++++++++++- RotationSolver.Basic/DataCenter.cs | 2 + .../Rotations/Basic/AstrologianRotation.cs | 4 +- .../Rotations/CustomRotation_OtherInfo.cs | 8 ++ RotationSolver/UI/RotationConfigWindow.cs | 1 + RotationSolver/Updaters/ActionUpdater.cs | 10 ++- 9 files changed, 157 insertions(+), 28 deletions(-) diff --git a/BasicRotations/Healer/AST_Default.cs b/BasicRotations/Healer/AST_Default.cs index 3771d54b2..628624272 100644 --- a/BasicRotations/Healer/AST_Default.cs +++ b/BasicRotations/Healer/AST_Default.cs @@ -12,9 +12,6 @@ public sealed class AST_Default : AstrologianRotation [RotationConfig(CombatType.PvE, Name = "Use both stacks of Lightspeed while moving")] public bool LightspeedMove { get; set; } = true; - [RotationConfig(CombatType.PvE, Name = "Use experimental card logic to pool for divination buff if possible")] - public bool SmartCard { get; set; } = true; - [RotationConfig(CombatType.PvE, Name = "Use spells with cast times to heal. (Ignored if you are the only healer in party)")] public bool GCDHeal { get; set; } = false; @@ -24,6 +21,9 @@ public sealed class AST_Default : AstrologianRotation [RotationConfig(CombatType.PvE, Name = "Prioritize Microcosmos over all other healing when available")] public bool MicroPrio { get; set; } = false; + [RotationConfig(CombatType.PvE, Name = "Simple Lord of Crowns logic (use under divinaiton)")] + public bool SimpleLord { get; set; } = false; + [Range(4, 20, ConfigUnitType.Seconds)] [RotationConfig(CombatType.PvE, Name = "Use Earthly Star during countdown timer.")] public float UseEarthlyStarTime { get; set; } = 15; @@ -77,6 +77,10 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) { if (SynastryPvE.CanUse(out act)) return true; } + + if (DivinationPvE.CanUse(out _) + && UseBurstMedicine(out act)) return true; + return base.EmergencyAbility(nextGCD, out act); } @@ -167,10 +171,20 @@ protected override bool GeneralAbility(IAction nextGCD, out IAction? act) act = null; if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + // Use LadyOfCrownsPvE if your party is needs to be topped up or you are about to use Astral Draw (to prevent overwriting) + if (PartyMembersAverHP < .8 && LadyOfCrownsPvE.CanUse(out act)) return true; + if (AstralDrawPvE.Cooldown.WillHaveOneCharge(3) && LadyOfCrownsPvE.CanUse(out act)) return true; + + // Use cards if you are about to use Umbral or Astral Draw (to prevent overwriting) + if (AstralDrawPvE.Cooldown.WillHaveOneCharge(3) && InCombat && TheArrowPvE.CanUse(out act)) return true; + if (AstralDrawPvE.Cooldown.WillHaveOneCharge(3) && InCombat && TheBolePvE.CanUse(out act)) return true; + if (UmbralDrawPvE.Cooldown.WillHaveOneCharge(3) && InCombat && TheArrowPvE.CanUse(out act)) return true; + if (UmbralDrawPvE.Cooldown.WillHaveOneCharge(3) && InCombat && TheSpirePvE.CanUse(out act)) return true; + if (AstralDrawPvE.CanUse(out act)) return true; if (UmbralDrawPvE.CanUse(out act)) return true; - if (((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && SmartCard || (!SmartCard)) && InCombat && TheBalancePvE.CanUse(out act)) return true; - if (((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && SmartCard || (!SmartCard)) && InCombat && TheSpearPvE.CanUse(out act)) return true; + if ((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && InCombat && TheBalancePvE.CanUse(out act)) return true; + if ((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || AstralDrawPvE.Cooldown.WillHaveOneCharge(3)) && InCombat && TheSpearPvE.CanUse(out act)) return true; return base.GeneralAbility(nextGCD, out act); } @@ -179,6 +193,9 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act) act = null; if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + // Use LordOfCrownsPvE if you have SimpleLord enabled and Divination is active + if (SimpleLord && InCombat && Player.HasStatus(true, StatusID.Divination) && LordOfCrownsPvE.CanUse(out act)) return true; + if (!Player.HasStatus(true, StatusID.Lightspeed) && InCombat && DivinationPvE.Cooldown.ElapsedAfter(115) @@ -207,7 +224,7 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act) } { - if (((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && SmartCard || (!SmartCard)) && LordOfCrownsPvE.CanUse(out act)) return true; + if (!SimpleLord && (Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && LordOfCrownsPvE.CanUse(out act)) return true; } } return base.AttackAbility(nextGCD, out act); diff --git a/BasicRotations/Healer/SCH_Default.cs b/BasicRotations/Healer/SCH_Default.cs index d72b1116b..030f214f9 100644 --- a/BasicRotations/Healer/SCH_Default.cs +++ b/BasicRotations/Healer/SCH_Default.cs @@ -25,8 +25,16 @@ public sealed class SCH_Default : ScholarRotation [RotationConfig(CombatType.PvE, Name = "Remove Aetherpact to conserve resources if party member is above this percentage")] public float AetherpactRemove { get; set; } = 0.9f; + [Range(0, 5, ConfigUnitType.Seconds)] + [RotationConfig(CombatType.PvE, Name = "How long you need to be moving before Ruin is used")] + public float RuinTime { get; set; } = 0; + [RotationConfig(CombatType.PvE, Name = "Use DOT while moving even if it does not need refresh (disabling is a damage down)")] public bool DOTUpkeep { get; set; } = true; + + [Range(0, 5, ConfigUnitType.Seconds)] + [RotationConfig(CombatType.PvE, Name = "How long you need to be moving before DOT is used (requires above to be enabled)")] + public float DOTTime { get; set; } = 0; #endregion #region Countdown Logic @@ -50,11 +58,6 @@ public sealed class SCH_Default : ScholarRotation #region oGCD Logic protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) { - if (nextGCD.IsTheSameTo(true, SuccorPvE, AdloquiumPvE)) - { - if (RecitationPvE.CanUse(out act)) return true; - } - //Remove Aetherpact foreach (var item in PartyMembers) { @@ -72,6 +75,9 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) [RotationDesc(ActionID.SummonSeraphPvE, ActionID.ConsolationPvE, ActionID.WhisperingDawnPvE, ActionID.SacredSoilPvE, ActionID.IndomitabilityPvE)] protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) { + if (!IndomitabilityPvE.Cooldown.IsCoolingDown && RecitationPvE.CanUse(out act)) return true; + if (IsLastAbility(ActionID.RecitationPvE) && IndomitabilityPvE.CanUse(out act)) return true; + if (AccessionPvE.CanUse(out act)) return true; if (ConcitationPvE.CanUse(out act)) return true; if (WhisperingDawnPvE_16537.Cooldown.ElapsedOneChargeAfterGCD(1) || FeyIlluminationPvE_16538.Cooldown.ElapsedOneChargeAfterGCD(1) || FeyBlessingPvE.Cooldown.ElapsedOneChargeAfterGCD(1)) @@ -82,9 +88,10 @@ protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) if (FeyBlessingPvE.CanUse(out act)) return true; if (WhisperingDawnPvE_16537.CanUse(out act)) return true; - if (SacredSoilPvE.CanUse(out act)) return true; if (IndomitabilityPvE.CanUse(out act)) return true; + if (SacredSoilPvE.CanUse(out act)) return true; + return base.HealAreaAbility(nextGCD, out act); } @@ -105,7 +112,7 @@ protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) [RotationDesc(ActionID.FeyIlluminationPvE, ActionID.ExpedientPvE, ActionID.SummonSeraphPvE, ActionID.ConsolationPvE, ActionID.SacredSoilPvE)] protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) { - if (DeploymentTacticsPvE.CanUse(out act)) return true; + if ((DeploymentTacticsPvE.Target.Target?.WillStatusEnd(0, false, DeploymentTacticsPvE.Setting.TargetStatusNeed ?? []) ?? false) && DeploymentTacticsPvE.CanUse(out act)) return true; if (SeraphismPvE.CanUse(out act)) return true; @@ -219,12 +226,12 @@ protected override bool GeneralGCD(out IAction? act) if (RuinPvE.CanUse(out act)) return true; //Single Instant for when moving. - if (RuinIiPvE.CanUse(out act)) return true; - + if (MovingTime > RuinTime && RuinIiPvE.CanUse(out act)) return true; + //Add dot while moving. - if (BiolysisPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; - if (BioIiPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; - if (BioPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (MovingTime > DOTTime && BiolysisPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (MovingTime > DOTTime && BioIiPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (MovingTime > DOTTime && BioPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; return base.GeneralGCD(out act); } diff --git a/BasicRotations/Magical/RDM_Default.cs b/BasicRotations/Magical/RDM_Default.cs index e86823bf2..9d6963f7f 100644 --- a/BasicRotations/Magical/RDM_Default.cs +++ b/BasicRotations/Magical/RDM_Default.cs @@ -20,6 +20,12 @@ public sealed class RDM_Default : RedMageRotation //Fine, ill do it myself [RotationConfig(CombatType.PvE, Name = "Cast manafication outside of embolden window (use at own risk).")] public bool AnyoneManafication { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Corps-a-corps when standing still (use at own risk).")] + public bool SuicideByDumb { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Displacement after Engagement (use at own risk).")] + public bool SuicideByDumber { get; set; } = false; #endregion #region Countdown Logic @@ -149,8 +155,9 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act) if (ViceOfThornsPvE.CanUse(out act, skipAoeCheck: true)) return true; if (ContreSixtePvE.CanUse(out act, skipAoeCheck: true)) return true; if (FlechePvE.CanUse(out act)) return true; - if (EngagementPvE.CanUse(out act, usedUp: true)) return true; - if (CorpsacorpsPvE.CanUse(out act) && !IsMoving) return true; + if (EngagementPvE.CanUse(out act, usedUp: !SuicideByDumber)) return true; + if (SuicideByDumb && CorpsacorpsPvE.CanUse(out act) && !IsMoving) return true; + if (SuicideByDumber && EngagementPvE.Cooldown.CurrentCharges == 1 && DisplacementPvE.CanUse(out act, usedUp: true)) return true; return base.AttackAbility(nextGCD, out act); } diff --git a/RotationSolver.Basic/Actions/ActionTargetInfo.cs b/RotationSolver.Basic/Actions/ActionTargetInfo.cs index 5ecd43d9e..4e3f58c4d 100644 --- a/RotationSolver.Basic/Actions/ActionTargetInfo.cs +++ b/RotationSolver.Basic/Actions/ActionTargetInfo.cs @@ -771,6 +771,8 @@ private readonly bool CanGetTarget(IGameObject target, IGameObject subTarget) TargetType.Magical => IGameObjects != null ? RandomMagicalTarget(IGameObjects) : null, TargetType.Physical => IGameObjects != null ? RandomPhysicalTarget(IGameObjects) : null, TargetType.DancePartner => FindDancePartner(), + TargetType.TheSpear => FindTheSpear(), + TargetType.TheBalance => FindTheBalance(), _ => FindHostile(), }; @@ -781,14 +783,91 @@ private readonly bool CanGetTarget(IGameObject target, IGameObject subTarget) if (IGameObjects == null) return null; - var PartyMembers = IGameObjects.Where(ObjectHelper.IsParty); + var partyMembers = new List(); + foreach (var obj in IGameObjects) + { + if (ObjectHelper.IsParty(obj)) + { + partyMembers.Add(obj); + } + } foreach (var job in DancePartnerPriority) { - var partner = PartyMembers.FirstOrDefault(member => member.IsJobs(job) && !member.IsDead); - if (partner != null) + foreach (var member in partyMembers) + { + if (member.IsJobs(job) && !member.IsDead) + { + return member; + } + } + } + + return RandomMeleeTarget(IGameObjects) + ?? RandomRangeTarget(IGameObjects) + ?? RandomMagicalTarget(IGameObjects) + ?? RandomPhysicalTarget(IGameObjects) + ?? null; + } + + IBattleChara? FindTheSpear() + { + // The Spear priority based on the info from The Balance Discord for Level 100 Dance Partner + Job[] TheSpearpriority = { Job.PCT, Job.SAM, Job.RPR, Job.VPR, Job.MNK, Job.NIN, Job.DRG, Job.PCT, Job.SAM, Job.BLM, Job.RDM, Job.SMN, Job.MCH, Job.BRD, Job.DNC }; + + if (IGameObjects == null) return null; + + var partyMembers = new List(); + foreach (var obj in IGameObjects) + { + if (ObjectHelper.IsParty(obj)) + { + partyMembers.Add(obj); + } + } + + foreach (var job in TheSpearpriority) + { + foreach (var member in partyMembers) { - return partner; + if (member.IsJobs(job) && !member.IsDead) + { + return member; + } + } + } + + return RandomRangeTarget(IGameObjects) + ?? RandomMeleeTarget(IGameObjects) + ?? RandomMagicalTarget(IGameObjects) + ?? RandomPhysicalTarget(IGameObjects) + ?? null; + } + + IBattleChara? FindTheBalance() + { + // The Balance priority based on the info from The Balance Discord for Level 100 Dance Partner + Job[] TheBalancepriority = { Job.PCT, Job.SAM, Job.BLM, Job.RDM, Job.SMN, Job.MCH, Job.BRD, Job.DNC, Job.RPR, Job.VPR, Job.MNK, Job.NIN, Job.DRG }; + + if (IGameObjects == null) return null; + + var partyMembers = new List(); + foreach (var obj in IGameObjects) + { + if (ObjectHelper.IsParty(obj)) + { + partyMembers.Add(obj); + } + } + + foreach (var job in TheBalancepriority) + { + foreach (var member in partyMembers) + { + if (member.IsJobs(job) && !member.IsDead) + { + return member; + } } } @@ -1136,6 +1215,8 @@ public enum TargetType : byte Magical, Self, DancePartner, + TheBalance, + TheSpear, } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index 95c02bb69..fe790711b 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -203,6 +203,8 @@ public static TargetingType TargetingType internal static float StopMovingRaw { get; set; } + internal static float MovingRaw { get; set; } + public static unsafe ushort FateId { get diff --git a/RotationSolver.Basic/Rotations/Basic/AstrologianRotation.cs b/RotationSolver.Basic/Rotations/Basic/AstrologianRotation.cs index 84b9b7dbc..c88f16703 100644 --- a/RotationSolver.Basic/Rotations/Basic/AstrologianRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/AstrologianRotation.cs @@ -165,7 +165,7 @@ static partial void ModifyTheBalancePvE(ref ActionSetting setting) setting.ActionCheck = () => HasBalance; setting.TargetStatusProvide = [StatusID.TheBalance_3887, StatusID.Weakness, StatusID.BrinkOfDeath]; - setting.TargetType = TargetType.Melee; + setting.TargetType = TargetType.TheBalance; setting.IsFriendly = true; } @@ -190,7 +190,7 @@ static partial void ModifyTheSpearPvE(ref ActionSetting setting) setting.ActionCheck = () => HasSpear; setting.TargetStatusProvide = [StatusID.TheSpear_3889, StatusID.Weakness, StatusID.BrinkOfDeath]; - setting.TargetType = TargetType.Range; + setting.TargetType = TargetType.TheSpear; setting.IsFriendly = true; } diff --git a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs index 6a2d7ce4a..ad44be4d9 100644 --- a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs +++ b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs @@ -555,6 +555,14 @@ public static float LiveComboTime [Description("Stop moving time")] public static float StopMovingTime => IsMoving ? 0 : DataCenter.StopMovingRaw + DataCenter.DefaultGCDRemain; + + /// + /// How long the player has been moving. + ///
WARNING: Do Not make this method the main of your rotation.
+ ///
+ [Description("Moving time")] + public static float MovingTime => IsMoving ? DataCenter.MovingRaw + DataCenter.DefaultGCDRemain : 0; + /// /// Time from GCD. /// diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 4089916a2..f3f96574d 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -2656,6 +2656,7 @@ private static unsafe void DrawStatus() ImGui.Text($"Height: {Player.Character->ModelContainer.CalculateHeight()}"); ImGui.Text($"OnlineStatus: {Player.OnlineStatus}"); ImGui.Text($"Moving: {DataCenter.IsMoving}"); + ImGui.Text($"Moving Time: {DataCenter.MovingRaw}"); ImGui.Text($"Stop Moving: {DataCenter.StopMovingRaw}"); ImGui.Text($"CountDownTime: {Service.CountDownTime}"); ImGui.Text($"Combo Time: {DataCenter.ComboTime}"); diff --git a/RotationSolver/Updaters/ActionUpdater.cs b/RotationSolver/Updaters/ActionUpdater.cs index af44040ec..0aa08cfa8 100644 --- a/RotationSolver/Updaters/ActionUpdater.cs +++ b/RotationSolver/Updaters/ActionUpdater.cs @@ -118,7 +118,9 @@ private unsafe static void UpdateSlots() } } + static DateTime _startMovingTime = DateTime.MinValue; static DateTime _stopMovingTime = DateTime.MinValue; + private unsafe static void UpdateMoving() { var last = DataCenter.IsMoving; @@ -127,14 +129,18 @@ private unsafe static void UpdateMoving() { _stopMovingTime = DateTime.Now; } - else if (DataCenter.IsMoving) + else if (DataCenter.IsMoving && !last) { - _stopMovingTime = DateTime.MinValue; + _startMovingTime = DateTime.Now; } DataCenter.StopMovingRaw = _stopMovingTime == DateTime.MinValue ? 0 : (float)(DateTime.Now - _stopMovingTime).TotalSeconds; + + DataCenter.MovingRaw = _startMovingTime == DateTime.MinValue + ? 0 + : (float)(DateTime.Now - _startMovingTime).TotalSeconds; } static DateTime _startCombatTime = DateTime.MinValue;