From d9aabb0534ddc08298805c588b62803e2501d11d Mon Sep 17 00:00:00 2001 From: Kaedys Date: Fri, 12 Jul 2024 22:28:56 -0400 Subject: [PATCH] [BUGFIX] CalcBestAction and CooldownData - Revamped CooldownData, based on prior work by and with significant input from @vitharr137. - Removed a number of not currently useful functions, which can be replicated by remaining functions anyway. - Updated the mechanics of a number of the CooldownData functions. Full change details can be found at: - https://github.com/MKhayle/XIVComboExpanded/issues/305#issuecomment-2226556969 - Renamed `IsOffCooldown` to `IsCooldownUsable`, and clarified its semantics with regards to charge-based actions. - Removed the global functions `HasCharges`, `HasNoCharges`, and `IsOnCooldown`. - All prior usages of these have been replaced with `IsCooldownUsable`. - Added new function `IsRecharging`, which returns whether an ability is below maximum charges, without regard to whether it is usable currently. - Fixed a bug preventing the icon replacement if Ricochet (and its upgrade Checkmate). - As a side effect, fixes the action selection instability in #305, and probably a few other related bugs. --- XIVComboExpanded/Combos/ADV.cs | 8 +- XIVComboExpanded/Combos/AST.cs | 6 +- XIVComboExpanded/Combos/BRD.cs | 12 +- XIVComboExpanded/Combos/DRG.cs | 10 +- XIVComboExpanded/Combos/DRK.cs | 8 +- XIVComboExpanded/Combos/GNB.cs | 4 +- XIVComboExpanded/Combos/MCH.cs | 14 +-- XIVComboExpanded/Combos/PCT.cs | 2 +- XIVComboExpanded/Combos/PLD.cs | 2 +- XIVComboExpanded/Combos/RDM.cs | 8 +- XIVComboExpanded/Combos/RPR.cs | 8 +- XIVComboExpanded/Combos/SAM.cs | 6 +- XIVComboExpanded/Combos/SCH.cs | 8 +- XIVComboExpanded/Combos/SGE.cs | 12 +- XIVComboExpanded/Combos/SMN.cs | 2 +- XIVComboExpanded/Combos/VPR.cs | 4 +- XIVComboExpanded/Combos/WAR.cs | 8 +- XIVComboExpanded/CooldownData.cs | 183 +++++++------------------------ XIVComboExpanded/CustomCombo.cs | 99 +++++------------ 19 files changed, 128 insertions(+), 276 deletions(-) diff --git a/XIVComboExpanded/Combos/ADV.cs b/XIVComboExpanded/Combos/ADV.cs index e13ec5cb..26e0cb7c 100644 --- a/XIVComboExpanded/Combos/ADV.cs +++ b/XIVComboExpanded/Combos/ADV.cs @@ -44,7 +44,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim (actionID == SGE.Egeiro && level >= SGE.Levels.Egeiro) || (actionID == WHM.Raise && level >= WHM.Levels.Raise)) { - if (level >= ADV.Levels.Swiftcast && IsOffCooldown(ADV.Swiftcast)) + if (level >= ADV.Levels.Swiftcast && IsCooldownUsable(ADV.Swiftcast)) return ADV.Swiftcast; } @@ -57,7 +57,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim } else if (!IsEnabled(CustomComboPreset.AdvDisableVerRaiseFeature)) { - if (level >= ADV.Levels.Swiftcast && IsOffCooldown(ADV.Swiftcast)) + if (level >= ADV.Levels.Swiftcast && IsCooldownUsable(ADV.Swiftcast)) return ADV.Swiftcast; } } @@ -116,7 +116,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim return GNB.RoyalGuard; } - if (IsEnabled(CustomComboPreset.AdvStanceBackProvokeFeature) && IsOnCooldown(ADV.Provoke)) + if (IsEnabled(CustomComboPreset.AdvStanceBackProvokeFeature) && !IsCooldownUsable(ADV.Provoke)) { if (job == PLD.JobID && level >= PLD.Levels.IronWill) return PLD.IronWillRemoval; @@ -139,7 +139,7 @@ internal class ShirkStanceFeature : CustomCombo protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level) { - if (actionID == ADV.Shirk && IsOnCooldown(ADV.Shirk)) + if (actionID == ADV.Shirk && !IsCooldownUsable(ADV.Shirk)) { var job = LocalPlayer?.ClassJob.Id; diff --git a/XIVComboExpanded/Combos/AST.cs b/XIVComboExpanded/Combos/AST.cs index 12b68bcf..9a2e3df0 100644 --- a/XIVComboExpanded/Combos/AST.cs +++ b/XIVComboExpanded/Combos/AST.cs @@ -89,14 +89,14 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.AstrologianMaleficArcanaFeature) && gauge.DrawnCrownCard == CardType.LORD && level >= AST.Levels.MinorArcana) return OriginalHook(AST.MinorArcanaDT); - if (IsEnabled(CustomComboPreset.AstrologianDraw1Feature) && IsOriginal(AST.Play1) && (IsOffCooldown(AST.AstralDraw) || IsOffCooldown(AST.UmbralDraw))) + if (IsEnabled(CustomComboPreset.AstrologianDraw1Feature) && IsOriginal(AST.Play1) && (IsCooldownUsable(AST.AstralDraw) || IsCooldownUsable(AST.UmbralDraw))) return gauge.ActiveDraw == DrawType.ASTRAL ? OriginalHook(AST.AstralDraw) : OriginalHook(AST.UmbralDraw); if (IsOriginal(AST.Play1) && IsOriginal(AST.Play2) && IsOriginal(AST.Play3) && (IsOriginal(AST.MinorArcanaDT) || level < AST.Levels.MinorArcana) - && (IsOffCooldown(AST.AstralDraw) || IsOffCooldown(AST.UmbralDraw))) + && (IsCooldownUsable(AST.AstralDraw) || IsCooldownUsable(AST.UmbralDraw))) return gauge.ActiveDraw == DrawType.ASTRAL ? OriginalHook(AST.AstralDraw) : OriginalHook(AST.UmbralDraw); } @@ -121,7 +121,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim && IsOriginal(AST.Play2) && IsOriginal(AST.Play3) && (IsOriginal(AST.MinorArcanaDT) || level < AST.Levels.MinorArcana) - && (IsOffCooldown(AST.AstralDraw) || IsOffCooldown(AST.UmbralDraw))) + && (IsCooldownUsable(AST.AstralDraw) || IsCooldownUsable(AST.UmbralDraw))) return gauge.ActiveDraw == DrawType.ASTRAL ? OriginalHook(AST.AstralDraw) : OriginalHook(AST.UmbralDraw); } diff --git a/XIVComboExpanded/Combos/BRD.cs b/XIVComboExpanded/Combos/BRD.cs index ba920eb2..44184994 100644 --- a/XIVComboExpanded/Combos/BRD.cs +++ b/XIVComboExpanded/Combos/BRD.cs @@ -216,7 +216,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.BardShadowbiteBarrageFeature)) { - if (level >= BRD.Levels.Barrage && IsOffCooldown(BRD.Barrage)) + if (level >= BRD.Levels.Barrage && IsCooldownUsable(BRD.Barrage)) return BRD.Barrage; } @@ -384,13 +384,13 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.BardRadiantStrikesFeature)) { - if (level >= BRD.Levels.RagingStrikes && IsOffCooldown(BRD.RagingStrikes)) + if (level >= BRD.Levels.RagingStrikes && IsCooldownUsable(BRD.RagingStrikes)) return BRD.RagingStrikes; } if (IsEnabled(CustomComboPreset.BardRadiantVoiceFeature)) { - if (level >= BRD.Levels.BattleVoice && IsOffCooldown(BRD.BattleVoice)) + if (level >= BRD.Levels.BattleVoice && IsCooldownUsable(BRD.BattleVoice)) return BRD.BattleVoice; } @@ -447,7 +447,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (gauge.Song == Song.WANDERER && gauge.SongTimer >= remaining) return BRD.WanderersMinuet; - if (IsOffCooldown(BRD.WanderersMinuet)) + if (IsCooldownUsable(BRD.WanderersMinuet)) return BRD.WanderersMinuet; } @@ -456,7 +456,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (gauge.Song == Song.MAGE && gauge.SongTimer >= remaining) return BRD.MagesBallad; - if (IsOffCooldown(BRD.MagesBallad)) + if (IsCooldownUsable(BRD.MagesBallad)) return BRD.MagesBallad; } @@ -465,7 +465,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (gauge.Song == Song.ARMY && gauge.SongTimer >= remaining) return BRD.ArmysPaeon; - if (IsOffCooldown(BRD.ArmysPaeon)) + if (IsCooldownUsable(BRD.ArmysPaeon)) return BRD.ArmysPaeon; } diff --git a/XIVComboExpanded/Combos/DRG.cs b/XIVComboExpanded/Combos/DRG.cs index bde66abe..7ef4911d 100644 --- a/XIVComboExpanded/Combos/DRG.cs +++ b/XIVComboExpanded/Combos/DRG.cs @@ -215,14 +215,14 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.DragoonStardiverNastrondFeature)) { - if (level >= DRG.Levels.Geirskogul && (!gauge.IsLOTDActive || IsOffCooldown(DRG.Nastrond) || IsOnCooldown(DRG.Stardiver))) + if (level >= DRG.Levels.Geirskogul && (!gauge.IsLOTDActive || IsCooldownUsable(DRG.Nastrond) || !IsCooldownUsable(DRG.Stardiver))) // Nastrond return OriginalHook(DRG.Geirskogul); } if (IsEnabled(CustomComboPreset.DragoonStardiverDragonfireDiveFeature)) { - if (level < DRG.Levels.Stardiver || !gauge.IsLOTDActive || IsOnCooldown(DRG.Stardiver) || (IsOffCooldown(DRG.DragonfireDive) && gauge.LOTDTimer > 7.5)) + if (level < DRG.Levels.Stardiver || !gauge.IsLOTDActive || !IsCooldownUsable(DRG.Stardiver) || (IsCooldownUsable(DRG.DragonfireDive) && gauge.LOTDTimer > 7.5)) return DRG.DragonfireDive; } } @@ -249,7 +249,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim ? DRG.Nastrond : DRG.Geirskogul; - if (IsOnCooldown(action)) + if (!IsCooldownUsable(action)) return DRG.WyrmwindThrust; } } @@ -267,10 +267,10 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (actionID == DRG.LanceCharge) { - if (!IsOnCooldown(DRG.LanceCharge)) + if (!!IsCooldownUsable(DRG.LanceCharge)) return DRG.LanceCharge; - if (level >= DRG.Levels.BattleLitany && !IsOnCooldown(DRG.BattleLitany)) + if (level >= DRG.Levels.BattleLitany && !!IsCooldownUsable(DRG.BattleLitany)) return DRG.BattleLitany; } diff --git a/XIVComboExpanded/Combos/DRK.cs b/XIVComboExpanded/Combos/DRK.cs index fe5aa6c6..97e0855d 100644 --- a/XIVComboExpanded/Combos/DRK.cs +++ b/XIVComboExpanded/Combos/DRK.cs @@ -192,7 +192,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (actionID == DRK.CarveAndSpit && level < DRK.Levels.CarveAndSpit) return OriginalHook(DRK.BloodWeapon); - if (level >= DRK.Levels.BloodWeapon && IsOffCooldown(DRK.BloodWeapon)) + if (level >= DRK.Levels.BloodWeapon && IsCooldownUsable(DRK.BloodWeapon)) return OriginalHook(DRK.BloodWeapon); } } @@ -213,7 +213,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.DarkLivingShadowFeature)) { - if (level >= DRK.Levels.LivingShadow && gauge.Blood >= 50 && IsOffCooldown(DRK.LivingShadow)) + if (level >= DRK.Levels.LivingShadow && gauge.Blood >= 50 && IsCooldownUsable(DRK.LivingShadow)) return DRK.LivingShadow; } } @@ -234,13 +234,13 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.DarkLivingShadowbringerFeature)) { - if (level >= DRK.Levels.Shadowbringer && gauge.ShadowTimeRemaining > 0 && HasCharges(DRK.Shadowbringer)) + if (level >= DRK.Levels.Shadowbringer && gauge.ShadowTimeRemaining > 0 && IsCooldownUsable(DRK.Shadowbringer)) return DRK.Shadowbringer; } if (IsEnabled(CustomComboPreset.DarkLivingShadowbringerHpFeature)) { - if (level >= DRK.Levels.Shadowbringer && HasCharges(DRK.Shadowbringer) && IsOnCooldown(DRK.LivingShadow)) + if (level >= DRK.Levels.Shadowbringer && IsCooldownUsable(DRK.Shadowbringer) && !IsCooldownUsable(DRK.LivingShadow)) return DRK.Shadowbringer; } } diff --git a/XIVComboExpanded/Combos/GNB.cs b/XIVComboExpanded/Combos/GNB.cs index 3ddff91d..29794c97 100644 --- a/XIVComboExpanded/Combos/GNB.cs +++ b/XIVComboExpanded/Combos/GNB.cs @@ -166,7 +166,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.GunbreakerDoubleDownFeature)) { - if (level >= GNB.Levels.DoubleDown && gauge.Ammo >= 2 && IsOffCooldown(GNB.DoubleDown)) + if (level >= GNB.Levels.DoubleDown && gauge.Ammo >= 2 && IsCooldownUsable(GNB.DoubleDown)) return GNB.DoubleDown; } @@ -245,7 +245,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (level >= GNB.Levels.NoMercy && HasEffect(GNB.Buffs.NoMercy)) { - if (level >= GNB.Levels.DoubleDown && gauge.Ammo >= 2 && IsOffCooldown(GNB.DoubleDown)) + if (level >= GNB.Levels.DoubleDown && gauge.Ammo >= 2 && IsCooldownUsable(GNB.DoubleDown)) return GNB.DoubleDown; } } diff --git a/XIVComboExpanded/Combos/MCH.cs b/XIVComboExpanded/Combos/MCH.cs index 69733e2a..8898569c 100644 --- a/XIVComboExpanded/Combos/MCH.cs +++ b/XIVComboExpanded/Combos/MCH.cs @@ -138,9 +138,7 @@ internal class MachinistGaussRoundRicochet : CustomCombo protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level) { - var richochet = level < MCH.Levels.CheckMate ? MCH.Ricochet : MCH.Checkmate; - var gaussRound = level < MCH.Levels.DoubleCheck ? MCH.GaussRound : MCH.DoubleCheck; - if (actionID == MCH.GaussRound || actionID == richochet) + if (actionID == MCH.GaussRound || actionID == MCH.Ricochet) { var gauge = GetJobGauge(); @@ -151,9 +149,9 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim } if (level >= MCH.Levels.Ricochet) - return CalcBestAction(actionID, gaussRound, richochet); + return OriginalHook(CalcBestAction(actionID, MCH.GaussRound, MCH.Ricochet)); - return gaussRound; + return OriginalHook(MCH.Ricochet); } return actionID; @@ -168,10 +166,10 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (actionID == MCH.Hypercharge) { - if (level >= MCH.Levels.Wildfire && IsOffCooldown(MCH.Wildfire) && HasTarget()) + if (level >= MCH.Levels.Wildfire && IsCooldownUsable(MCH.Wildfire) && HasTarget()) return MCH.Wildfire; - if (level >= MCH.Levels.Wildfire && IsOnCooldown(MCH.Hypercharge) && !IsOriginal(MCH.Wildfire)) + if (level >= MCH.Levels.Wildfire && !IsCooldownUsable(MCH.Hypercharge) && !IsOriginal(MCH.Wildfire)) return MCH.Detonator; } @@ -191,7 +189,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.MachinistHyperfireFeature)) { - if (level >= MCH.Levels.Wildfire && IsOffCooldown(MCH.Wildfire) && HasTarget()) + if (level >= MCH.Levels.Wildfire && IsCooldownUsable(MCH.Wildfire) && HasTarget()) return MCH.Wildfire; } diff --git a/XIVComboExpanded/Combos/PCT.cs b/XIVComboExpanded/Combos/PCT.cs index 894d7dad..ff5a377c 100644 --- a/XIVComboExpanded/Combos/PCT.cs +++ b/XIVComboExpanded/Combos/PCT.cs @@ -304,7 +304,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (gauge.MooglePortraitReady || gauge.MadeenPortraitReady) { - if (IsOffCooldown(PCT.MogOftheAges)) + if (IsCooldownUsable(PCT.MogOftheAges)) return OriginalHook(PCT.MogOftheAges); } } diff --git a/XIVComboExpanded/Combos/PLD.cs b/XIVComboExpanded/Combos/PLD.cs index 895a24e3..f5db14ad 100644 --- a/XIVComboExpanded/Combos/PLD.cs +++ b/XIVComboExpanded/Combos/PLD.cs @@ -402,7 +402,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (actionID == PLD.ShieldBash) { - if (level >= PLD.Levels.LowBlow && IsOffCooldown(PLD.LowBlow)) + if (level >= PLD.Levels.LowBlow && IsCooldownUsable(PLD.LowBlow)) return PLD.LowBlow; } diff --git a/XIVComboExpanded/Combos/RDM.cs b/XIVComboExpanded/Combos/RDM.cs index ecb9679c..f1ca5bdb 100644 --- a/XIVComboExpanded/Combos/RDM.cs +++ b/XIVComboExpanded/Combos/RDM.cs @@ -386,14 +386,14 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.RedMageAccelerationSwiftcastOption)) { - if (IsOffCooldown(RDM.Acceleration) && IsOffCooldown(ADV.Swiftcast)) + if (IsCooldownUsable(RDM.Acceleration) && IsCooldownUsable(ADV.Swiftcast)) return ADV.Swiftcast; } - if (IsOffCooldown(RDM.Acceleration)) + if (IsCooldownUsable(RDM.Acceleration)) return RDM.Acceleration; - if (IsOffCooldown(ADV.Swiftcast)) + if (IsCooldownUsable(ADV.Swiftcast)) return ADV.Swiftcast; } @@ -416,7 +416,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (actionID == RDM.Embolden) { - if (level >= RDM.Levels.Manafication && IsOffCooldown(RDM.Manafication) && !IsOffCooldown(RDM.Embolden)) + if (level >= RDM.Levels.Manafication && IsCooldownUsable(RDM.Manafication) && !IsCooldownUsable(RDM.Embolden)) return RDM.Manafication; } diff --git a/XIVComboExpanded/Combos/RPR.cs b/XIVComboExpanded/Combos/RPR.cs index e3ee3e67..aca626a8 100644 --- a/XIVComboExpanded/Combos/RPR.cs +++ b/XIVComboExpanded/Combos/RPR.cs @@ -361,7 +361,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.ReaperBloodStalkGluttonyFeature)) { - if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && gauge.EnshroudedTimeRemaining == 0 && IsOffCooldown(RPR.Gluttony)) + if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && gauge.EnshroudedTimeRemaining == 0 && IsCooldownUsable(RPR.Gluttony)) return RPR.Gluttony; } @@ -389,7 +389,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.ReaperGrimSwatheGluttonyFeature)) { - if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && gauge.EnshroudedTimeRemaining == 0 && IsOffCooldown(RPR.Gluttony)) + if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && gauge.EnshroudedTimeRemaining == 0 && IsCooldownUsable(RPR.Gluttony)) return RPR.Gluttony; } @@ -414,7 +414,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.ReaperBloodStalkGluttonyFeature)) { - if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && IsOffCooldown(RPR.Gluttony) && gauge.EnshroudedTimeRemaining == 0) + if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && IsCooldownUsable(RPR.Gluttony) && gauge.EnshroudedTimeRemaining == 0) return RPR.Gluttony; } @@ -441,7 +441,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.ReaperGrimSwatheGluttonyFeature)) { - if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && IsOffCooldown(RPR.Gluttony) && gauge.EnshroudedTimeRemaining == 0) + if (level >= RPR.Levels.Gluttony && gauge.Soul >= 50 && IsCooldownUsable(RPR.Gluttony) && gauge.EnshroudedTimeRemaining == 0) return RPR.Gluttony; } diff --git a/XIVComboExpanded/Combos/SAM.cs b/XIVComboExpanded/Combos/SAM.cs index 29e827c7..b9d5553d 100644 --- a/XIVComboExpanded/Combos/SAM.cs +++ b/XIVComboExpanded/Combos/SAM.cs @@ -294,12 +294,12 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.SamuraiShintenSeneiFeature)) { - if (level >= SAM.Levels.HissatsuSenei && IsOffCooldown(SAM.HissatsuSenei)) + if (level >= SAM.Levels.HissatsuSenei && IsCooldownUsable(SAM.HissatsuSenei)) return SAM.HissatsuSenei; if (IsEnabled(CustomComboPreset.SamuraiSeneiGurenFeature)) { - if (level >= SAM.Levels.HissatsuGuren && level < SAM.Levels.HissatsuSenei && IsOffCooldown(SAM.HissatsuGuren)) + if (level >= SAM.Levels.HissatsuGuren && level < SAM.Levels.HissatsuSenei && IsCooldownUsable(SAM.HissatsuGuren)) return SAM.HissatsuGuren; } } @@ -340,7 +340,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.SamuraiKyutenGurenFeature)) { - if (level >= SAM.Levels.HissatsuGuren && IsOffCooldown(SAM.HissatsuGuren)) + if (level >= SAM.Levels.HissatsuGuren && IsCooldownUsable(SAM.HissatsuGuren)) return SAM.HissatsuGuren; } } diff --git a/XIVComboExpanded/Combos/SCH.cs b/XIVComboExpanded/Combos/SCH.cs index a9d5627d..caa691eb 100644 --- a/XIVComboExpanded/Combos/SCH.cs +++ b/XIVComboExpanded/Combos/SCH.cs @@ -91,13 +91,13 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.ScholarExcogitationRecitationFeature)) { - if (level >= SCH.Levels.Recitation && IsOffCooldown(SCH.Recitation)) + if (level >= SCH.Levels.Recitation && IsCooldownUsable(SCH.Recitation)) return SCH.Recitation; } if (IsEnabled(CustomComboPreset.ScholarExcogitationLustrateFeature)) { - if (level < SCH.Levels.Excogitation || IsOnCooldown(SCH.Excogitation)) + if (level < SCH.Levels.Excogitation || !IsCooldownUsable(SCH.Excogitation)) return SCH.Lustrate; } } @@ -136,13 +136,13 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.ScholarLustrateRecitationFeature)) { - if (level >= SCH.Levels.Recitation && IsOffCooldown(SCH.Recitation)) + if (level >= SCH.Levels.Recitation && IsCooldownUsable(SCH.Recitation)) return SCH.Recitation; } if (IsEnabled(CustomComboPreset.ScholarLustrateExcogitationFeature)) { - if (level >= SCH.Levels.Excogitation && IsOffCooldown(SCH.Excogitation)) + if (level >= SCH.Levels.Excogitation && IsCooldownUsable(SCH.Excogitation)) return SCH.Excogitation; } diff --git a/XIVComboExpanded/Combos/SGE.cs b/XIVComboExpanded/Combos/SGE.cs index d05269f3..b6c408b0 100644 --- a/XIVComboExpanded/Combos/SGE.cs +++ b/XIVComboExpanded/Combos/SGE.cs @@ -108,7 +108,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim level >= SGE.Levels.Phlegma2 ? SGE.Phlegma2 : level >= SGE.Levels.Phlegma ? SGE.Phlegma : 0; - if (phlegma != 0 && HasCharges(phlegma)) + if (phlegma != 0 && IsCooldownUsable(phlegma)) return OriginalHook(SGE.Phlegma); } } @@ -127,7 +127,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (IsEnabled(CustomComboPreset.SageSoteriaKardionFeature)) { - if (!HasEffect(SGE.Buffs.Kardion) && IsOffCooldown(SGE.Soteria)) + if (!HasEffect(SGE.Buffs.Kardion) && IsCooldownUsable(SGE.Soteria)) return SGE.Kardia; } } @@ -154,7 +154,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.SageTaurocholeDruocholeFeature)) { - if (level >= SGE.Levels.Taurochole && IsOffCooldown(SGE.Taurochole)) + if (level >= SGE.Levels.Taurochole && IsCooldownUsable(SGE.Taurochole)) return SGE.Taurochole; return SGE.Druochole; @@ -183,7 +183,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.SageDruocholeTaurocholeFeature)) { - if (level >= SGE.Levels.Taurochole && IsOffCooldown(SGE.Taurochole)) + if (level >= SGE.Levels.Taurochole && IsCooldownUsable(SGE.Taurochole)) return SGE.Taurochole; } } @@ -257,7 +257,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim level >= SGE.Levels.Phlegma2 ? SGE.Phlegma2 : level >= SGE.Levels.Phlegma ? SGE.Phlegma : 0; - if (level >= SGE.Levels.Toxicon && phlegma != 0 && HasNoCharges(phlegma) && gauge.Addersting > 0) + if (level >= SGE.Levels.Toxicon && phlegma != 0 && !IsCooldownUsable(phlegma) && gauge.Addersting > 0) return OriginalHook(SGE.Toxikon); } @@ -268,7 +268,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim level >= SGE.Levels.Phlegma2 ? SGE.Phlegma2 : level >= SGE.Levels.Phlegma ? SGE.Phlegma : 0; - if (level >= SGE.Levels.Dyskrasia && phlegma != 0 && HasNoCharges(phlegma)) + if (level >= SGE.Levels.Dyskrasia && phlegma != 0 && !IsCooldownUsable(phlegma)) return OriginalHook(SGE.Dyskrasia); } } diff --git a/XIVComboExpanded/Combos/SMN.cs b/XIVComboExpanded/Combos/SMN.cs index 1356cc03..7d28cb7c 100644 --- a/XIVComboExpanded/Combos/SMN.cs +++ b/XIVComboExpanded/Combos/SMN.cs @@ -258,7 +258,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.SummonerDemiSearingLightFeature)) { - if (level >= SMN.Levels.SearingLight && (gauge.IsBahamutReady || gauge.IsPhoenixReady) && InCombat() && IsOffCooldown(SMN.SearingLight)) + if (level >= SMN.Levels.SearingLight && (gauge.IsBahamutReady || gauge.IsPhoenixReady) && InCombat() && IsCooldownUsable(SMN.SearingLight)) return SMN.SearingLight; } diff --git a/XIVComboExpanded/Combos/VPR.cs b/XIVComboExpanded/Combos/VPR.cs index 53a9a03d..188b867c 100644 --- a/XIVComboExpanded/Combos/VPR.cs +++ b/XIVComboExpanded/Combos/VPR.cs @@ -326,7 +326,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (actionID == VPR.DreadFangs) { // I think in this case if we're not in a combo (and something else isn't replacing Dread Fangs), we can just replace if we have charges - if (level >= VPR.Levels.Dreadwinder && IsOriginal(VPR.DreadFangs) && HasCharges(VPR.Dreadwinder) && IsOriginal(VPR.SerpentsTail)) // Add the check for Serpent's Tail to avoid stepping on other combo + if (level >= VPR.Levels.Dreadwinder && IsOriginal(VPR.DreadFangs) && IsCooldownUsable(VPR.Dreadwinder) && IsOriginal(VPR.SerpentsTail)) // Add the check for Serpent's Tail to avoid stepping on other combo return VPR.Dreadwinder; } @@ -342,7 +342,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (actionID == VPR.DreadMaw) { - if (level >= VPR.Levels.PitOfDread && IsOriginal(VPR.DreadMaw) && HasCharges(VPR.PitOfDread) && IsOriginal(VPR.SerpentsTail)) // Add the check for Serpent's Tail to avoid stepping on other combo + if (level >= VPR.Levels.PitOfDread && IsOriginal(VPR.DreadMaw) && IsCooldownUsable(VPR.PitOfDread) && IsOriginal(VPR.SerpentsTail)) // Add the check for Serpent's Tail to avoid stepping on other combo return VPR.PitOfDread; } diff --git a/XIVComboExpanded/Combos/WAR.cs b/XIVComboExpanded/Combos/WAR.cs index 867729a2..a103e2f9 100644 --- a/XIVComboExpanded/Combos/WAR.cs +++ b/XIVComboExpanded/Combos/WAR.cs @@ -291,19 +291,19 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { if (level >= WAR.Levels.Bloodwhetting) { - if (IsOffCooldown(WAR.Bloodwhetting)) + if (IsCooldownUsable(WAR.Bloodwhetting)) return WAR.Bloodwhetting; } else if (level >= WAR.Levels.RawIntuition) { - if (IsOffCooldown(WAR.RawIntuition)) + if (IsCooldownUsable(WAR.RawIntuition)) return WAR.RawIntuition; } - if (level >= WAR.Levels.ThrillOfBattle && IsOffCooldown(WAR.ThrillOfBattle)) + if (level >= WAR.Levels.ThrillOfBattle && IsCooldownUsable(WAR.ThrillOfBattle)) return WAR.ThrillOfBattle; - if (level >= WAR.Levels.Equilibrium && IsOffCooldown(WAR.Equilibrium)) + if (level >= WAR.Levels.Equilibrium && IsCooldownUsable(WAR.Equilibrium)) return WAR.Equilibrium; } } diff --git a/XIVComboExpanded/CooldownData.cs b/XIVComboExpanded/CooldownData.cs index 7df183a6..16010b99 100644 --- a/XIVComboExpanded/CooldownData.cs +++ b/XIVComboExpanded/CooldownData.cs @@ -22,114 +22,32 @@ internal struct CooldownData [FieldOffset(0xC)] private readonly float cooldownTotal; - /// - /// Gets the base cooldown time in seconds. - /// - public float BaseCooldown => ActionManager.GetAdjustedRecastTime(ActionType.Action, this.ActionID) / 1000f; - - /// - /// Gets the total cooldown calculated from AdjustedRecastTime in seconds. - /// - public float TotalBaseCooldown - { - get - { - var (cur, max) = Service.ComboCache.GetMaxCharges(this.ActionID); - - // Rebase to the current charge count - var total = this.BaseCooldown / max * cur; - - return total * cur; - } - } - - /// - /// Gets the total cooldown time. - /// - public float CooldownTotal - { - get - { - if (this.cooldownTotal == 0) - return 0; - - var (cur, max) = Service.ComboCache.GetMaxCharges(this.ActionID); - if (cur == max) - return this.cooldownTotal; - - // Rebase to the current charge count - var total = this.cooldownTotal / max * cur; - - if (this.cooldownElapsed > total) - return 0; - - return total; - } - } - - /// - /// Gets a value indicating whether the action is on cooldown. - /// - public bool IsCooldown - { - get - { - return this.cooldownElapsed > 0 && this.cooldownElapsed < this.BaseCooldown; - } - } - - /// - /// Gets a value indicating whether all charges are capped. - /// - public bool IsCapped - { - get - { - return this.cooldownElapsed == 0; - } - } - /// /// Gets the action ID on cooldown. /// public uint ActionID => this.actionID; /// - /// Gets the elapsed cooldown time limited to an active charge (0 if a charge is available). + /// Gets the cast time in seconds, adjusted by spell cast time modifiers (ex. spell speed/skill speed). /// - public float CooldownElapsed - { - get - { - if (this.cooldownElapsed > this.BaseCooldown) - return 0; - - return this.cooldownElapsed; - } - } + public unsafe float CastTime => ActionManager.GetAdjustedCastTime(ActionType.Action, this.ActionID) / 1000f; /// - /// Gets the elapsed cooldown time across the total cooldown (total cooldown time - total cooldown already regained). + /// Gets the resource cost of the action. /// - public float TotalCooldownElapsed => this.cooldownElapsed; + public unsafe float Cost => ActionManager.GetActionCost(ActionType.Action, this.ActionID, 1, 0, 0, 0); /// - /// Gets the cooldown time remaining until all charges are replenished. + /// Gets the base cooldown time of an action in seconds, adjusted for spell recast modifiers + /// (ex. spell speed, if relevant) /// - public float TotalCooldownRemaining => this.TotalBaseCooldown - this.TotalCooldownElapsed; + public float BaseCooldown => ActionManager.GetAdjustedRecastTime(ActionType.Action, this.ActionID) / 1000f; /// - /// Gets the cooldown time remaining until the current cooldown has recovered. + /// Gets the total cooldown of an action across all charges, which is equivalent to the BaseCooldown multiplied + /// by the MaxCharges. /// - public float CooldownRemaining - { - get - { - var (cur, _) = Service.ComboCache.GetMaxCharges(this.ActionID); - - return this.TotalCooldownRemaining % (this.TotalBaseCooldown / cur); - } - } + public float TotalBaseCooldown => this.BaseCooldown * this.MaxCharges; /// /// Gets the maximum number of charges for an action at the current level. @@ -138,78 +56,53 @@ public float CooldownRemaining public ushort MaxCharges => Service.ComboCache.GetMaxCharges(this.ActionID).Current; /// - /// Gets a value indicating whether the action has charges, not charges available. + /// Gets a value indicating whether an action utilizes charges, not whether charges are currently available. /// - public bool HasCharges => this.MaxCharges > 1; + public bool UsesCharges => this.MaxCharges > 1; /// - /// Gets the remaining number of charges for an action. + /// Gets the currently remaining (ie. usable) number of charges for an action. /// - public ushort RemainingCharges - { - get - { - var (cur, _) = Service.ComboCache.GetMaxCharges(this.ActionID); - - if (this.TotalCooldownElapsed == 0) - { - return this.MaxCharges; - } - - return (ushort)(this.TotalCooldownElapsed / (this.TotalBaseCooldown / this.MaxCharges)); - } - } + public ushort RemainingCharges => !this.isCooldown ? this.MaxCharges : (ushort)(this.TotalCooldownElapsed / this.BaseCooldown); /// - /// Gets a value indicating whether gets value indicating whether this action has at least one charge out of however many it has total, even if it can only have one "charge". + /// Gets a value indicating whether this action is off cooldown, or for charge-based actions, if the action + /// has at least one usable charge available. /// - public bool Available => this.CooldownRemaining == 0 || this.RemainingCharges > 0; + public bool Available => !this.isCooldown || this.RemainingCharges > 0; /// - /// Gets the time since the cooldown was spent in seconds (only fuctional if actionID is not charge based). + /// Gets a value indicating whether the action is on cooldown, or for charge-based actions, if any charges + /// are currently recharging. IsCooldown being true is NOT the same as the action being unavailable, as a + /// charged-based action can be both currently recovering a charge and also available for use. /// - public float CooldownDuration => this.BaseCooldown - this.CooldownRemaining; + public bool IsCooldown => this.isCooldown; /// - /// Gets the cooldown time remaining until the next charge. + /// Gets the cooldown time remaining until all charges are replenished. /// - public float ChargeCooldownRemaining - { - get - { - var (cur, _) = Service.ComboCache.GetMaxCharges(this.ActionID); - - return this.TotalCooldownRemaining % (this.TotalBaseCooldown / cur); - } - } + public float TotalCooldownRemaining => !this.isCooldown ? 0 : this.TotalBaseCooldown - this.cooldownElapsed; /// - /// Gets the recovery time in seconds if action is used when cooldown is off. + /// Gets the cooldown time remaining until the currently recharging charge is replenished. For actions that are + /// not charge-based, this is mechanically equivalent to TotalCooldownRemaining. /// - public float RecoveryTime => this.CooldownRemaining + this.BaseCooldown; + public float CooldownRemaining => this.TotalCooldownRemaining % this.BaseCooldown; /// - /// Gets the time until another charge is available after using the currently refreshing charge. + /// Gets the overall elapsed cooldown. The value will range from 0, immediately after all charges are used, + /// up to the TotalBaseCooldown. It is not known at this time if a return value of exactly 0 is possible. + /// For abilities with charges, this will equal the time elapsed on the current charge's recharge, plus the + /// BaseCooldown multiplied by the number of charges currently available. + /// As an example, if an ability with 2 charges and a 20s recharge had 1 charge used 5 seconds ago + /// (so it has 1 charge available, and 15s remaining until another charge is available), this field would + /// return 25s (20 + 5). If another charge were used at that exact moment, it would then return 5. /// - public float ChargeRecoveryTime - { - get - { - if ((this.RemainingCharges - 1) >= 0) - { - return this.ChargeCooldownRemaining; - } - - return this.ChargeCooldownRemaining + this.BaseCooldown; - } - } + public float TotalCooldownElapsed => !this.isCooldown ? this.TotalBaseCooldown : this.cooldownElapsed; /// - /// Gets the cooldown time remaining until all charges of ability are replenished. + /// Gets the elapsed time on the recharge of only the currently recharging charge. For actions that are not + /// charge-based, this is mechanically equivalent to TotalCooldownElapsed. /// - public float TotalChargeCooldownRemaining => this.MaxCharges - this.RemainingCharges > 0 - ? this.MaxCharges - this.RemainingCharges == 1 - ? this.ChargeCooldownRemaining - : (this.BaseCooldown * ((this.MaxCharges - this.RemainingCharges) - 1)) + this.ChargeCooldownRemaining - : 0; -} \ No newline at end of file + public float CooldownElapsed => this.TotalCooldownElapsed % this.BaseCooldown; +} diff --git a/XIVComboExpanded/CustomCombo.cs b/XIVComboExpanded/CustomCombo.cs index 5624057f..72291734 100644 --- a/XIVComboExpanded/CustomCombo.cs +++ b/XIVComboExpanded/CustomCombo.cs @@ -108,53 +108,26 @@ protected static uint CalcBestAction(uint original, params uint[] actions) (uint ActionID, CooldownData Data) a1, (uint ActionID, CooldownData Data) a2) { - // Neither, return the first parameter - if (!a1.Data.IsCooldown && !a2.Data.IsCooldown) - { - return original == a1.ActionID ? a1 : - original == a2.ActionID ? a2 : - a1; - } - - // Both, return soonest available - if (a1.Data.IsCooldown && a2.Data.IsCooldown) - { - if (a1.Data.HasCharges && a2.Data.HasCharges) - { - if (a1.Data.RemainingCharges == a2.Data.RemainingCharges) - { - return a1.Data.ChargeCooldownRemaining < a2.Data.ChargeCooldownRemaining - ? a1 : a2; - } - - return a1.Data.RemainingCharges > a2.Data.RemainingCharges - ? a1 : a2; - } - else if (a1.Data.HasCharges) - { - if (a1.Data.RemainingCharges > 0) - return a1; - - return a1.Data.ChargeCooldownRemaining < a2.Data.CooldownRemaining - ? a1 : a2; - } - else if (a2.Data.HasCharges) - { - if (a2.Data.RemainingCharges > 0) - return a2; - - return a2.Data.ChargeCooldownRemaining < a1.Data.CooldownRemaining - ? a2 : a1; - } - else - { - return a1.Data.CooldownRemaining <= a2.Data.CooldownRemaining - ? a1 : a2; - } - } - - // One or the other - return a1.Data.IsCooldown ? a2 : a1; + // This intent of this priority algorithm is to generate a single unified number that results in the + // following behaviors: + // * Any ability that is off cooldown and at maximum charges has maximum (and equal) priority. + // * If only one of the two abilities is currently usable, it has a higher priority. + // * If both abilities are usable but recharging, the one that will cap soonest has higher priority. + // * If neither ability is usable, the one that will be usable soonest has higher priority. + // + // Mechanically, if the ability is not available, the result will be a negative number representing the + // seconds until it is available, so the closer to zero (ie. more positive) the number, the sooner it + // will be usable. If the ability IS currently usable, the result will be a positive number (so always + // higher priority than an ability that is not currently usable), adjusted such that the ability with + // the shortest time until it reaches charge cap having the largest priority value. + // Any ability not currently cooling down will have a priority of 1000. + var a1Priority = a1.Data.Available ? (1000 - a1.Data.TotalCooldownRemaining) : -a1.Data.CooldownRemaining; + var a2Priority = a2.Data.Available ? (1000 - a2.Data.TotalCooldownRemaining) : -a2.Data.CooldownRemaining; + + if (a1Priority == a2Priority) + return original == a1.ActionID ? a1 : (original == a2.ActionID ? a2 : a1); + + return a1Priority > a2Priority ? a1 : a2; } static (uint ActionID, CooldownData Data) Selector(uint actionID) @@ -394,36 +367,24 @@ protected static bool IsInParty() => Service.PartyList.Count > 0 ? true : false; /// - /// Gets a value indicating whether an action is on cooldown. + /// Gets a value indicating whether an action is currently usable based on its cooldown + /// For a charge-based ability, this returns true if the ability has any charges available. /// /// Action ID to check. /// True or false. - protected static bool IsOnCooldown(uint actionID) - => GetCooldown(actionID).IsCooldown; - - /// - /// Gets a value indicating whether an action is off cooldown. - /// - /// Action ID to check. - /// True or false. - protected static bool IsOffCooldown(uint actionID) - => !GetCooldown(actionID).IsCooldown; + protected static bool IsCooldownUsable(uint actionID) + => GetCooldown(actionID).Available; /// - /// Gets a value indicating whether an action has any available charges. + /// Gets a value indicating whether an action is currently recharging. + /// For a non-charge-based ability, this is equivalent to !IsCooldownUsable() + /// For a charge-based ability, this returns true if the ability has less than maximum charges, + /// so a charge-based ability may still be usable if this returns true. /// /// Action ID to check. /// True or false. - protected static bool HasCharges(uint actionID) - => GetCooldown(actionID).RemainingCharges > 0; - - /// - /// Gets a value indicating whether an action has no available charges. - /// - /// Action ID to check. - /// True or false. - protected static bool HasNoCharges(uint actionID) - => GetCooldown(actionID).RemainingCharges == 0; + protected static bool IsRecharging(uint actionID) + => GetCooldown(actionID).IsCooldown; /// /// Get the current number of charges remaining for an action.