Skip to content

Commit

Permalink
[FEATURE] Paladin update/refactor
Browse files Browse the repository at this point in the history
- Removed the FoF Goring Blade feature, as it is now baseline.
- The prior check on Requiescat for including Goring Blade now depends on (and respects) the player's selection for whether FoF changes into Goring Blade in the base game.
- Implemented new features for including Goring Blade, when available, on Royal Authority and Prominence buttons.
- Implemented new features for including the Confiteor combo, when available, on Royal Authority and Prominence.
- Atonement usage outside of FoF is now delayed until it would be overwritten by Royal Authority, per current guidance in The Balance.
  - It will also be used if it would otherwise expire
- During FoF, Atonement or Holy Spirit are now intelligently prioritized based on whether the higher-potency Sepulchre can be fit inside the remaining duration.
- During both FoF and regular usage, Holy Spirit will automatically be inserted if the player is not in melee range of the target, provided Divine Might is active.
- If the player has remaining Requiescat charges (ie. under level 90, when the Blades combo followup to Confiteor is learned), Holy Spirit will be used to burn off the charges.
- Corrected MP values for spells, due to Divine Magic Mastery, added in Endwalker, halving all of the prior mana costs.
  - Technically, this returns incorrect values under level 64, but since the only mana-consuming spell learned before 64 is Clemency, and it is never used in a combo, this is irrelevant.
- Simplified some of the combo checks.  Since lastComboMove is only non-null when comboTime > 0, we can skip checking comboTime if we're already checking lastComboMove.
  • Loading branch information
Kaedys committed Jul 11, 2024
1 parent eef45e2 commit 56a23c4
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 88 deletions.
186 changes: 101 additions & 85 deletions XIVComboExpanded/Combos/PLD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public const ushort
FightOrFlight = 76,
IronWill = 79,
Requiescat = 1368,
SwordOath = 1902,
DivineMight = 2673,
ConfiteorReady = 3019,
GoringBladeReady = 3847,
AtonementReady = 1902,
SupplicationReady = 3827,
SepulchreReady = 3828,
GoringBladeReady = 3847,
ConfiteorReady = 3019,
BladeOfHonorReady = 3831;
}

Expand Down Expand Up @@ -80,8 +80,6 @@ public const byte
Confiteor = 80,
Expiacion = 86,
BladeOfFaith = 90,
BladeOfTruth = 90,
BladeOfValor = 90,
BladeOfHonor = 100;
}
}
Expand All @@ -94,15 +92,15 @@ protected bool HasMp(uint spell)
switch (spell)
{
case PLD.Clemency:
cost = 4000;
cost = 2000;
break;
case PLD.HolySpirit:
case PLD.HolyCircle:
case PLD.Confiteor:
case PLD.BladeOfFaith:
case PLD.BladeOfTruth:
case PLD.BladeOfValor:
cost = 2000;
cost = 1000;
break;
default:
cost = 0;
Expand All @@ -124,60 +122,87 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim
{
if (actionID == PLD.RageOfHalone || actionID == PLD.RoyalAuthority)
{
var inMeleeRange = InMeleeRange(); // Only calculate this once, to save some CPU cycles

if (IsEnabled(CustomComboPreset.PaladinRoyalAuthorityGoringBladeComboFeature) && HasEffect(PLD.Buffs.GoringBladeReady))
return PLD.GoringBlade;

if (level >= PLD.Levels.Confiteor && IsEnabled(CustomComboPreset.PaladinRoyalAuthorityConfiteorComboFeature))
{
var original = OriginalHook(PLD.Confiteor);
if (original != PLD.Confiteor)
return original;

if (HasEffect(PLD.Buffs.BladeOfHonorReady))
return OriginalHook(PLD.Imperator);

if (HasEffect(PLD.Buffs.ConfiteorReady))
return OriginalHook(PLD.Confiteor);
}

// During FoF, prioritize the higher-potency Divine Might cast over Atonement and the normal combo chain
if (IsEnabled(CustomComboPreset.PaladinRoyalAuthorityFightOrFlightFeature))
{
if (HasEffect(PLD.Buffs.FightOrFlight) && HasEffect(PLD.Buffs.DivineMight))
var fof = FindEffect(PLD.Buffs.FightOrFlight);
if (fof != null)
{
if (level >= PLD.Levels.HolySpirit && this.HasMp(PLD.HolySpirit))
// Inside FoF, the Atonement combo has priority over Holy Spirit due to potency, but only if we can fit in Sepulchre (480p), vs Holy Spirit 470p.
// Basically, for the extra 3 GCDs in FoF after the Blades combo, using Atonement -> Supplication -> Sepulchre is 10 more potency than using
// Holy Spirit -> Atonement -> Supplication. If we can't fit in Sepulchre, however, Holy Spirit is better.
// Note: Technically, this is only true after level 94's Melee Mastery II trait, as prior to that, Holy Spirit with Divine Might has 10 more
// potency than Sepulchre, rather than the other way around. However, since optimizing gains or losses of only 10p doesn't really matter at all
// while leveling, we don't bother to adjust this logic for lower levels, except where Atonement isn't yet learned.
if (level >= PLD.Levels.Atonement && IsEnabled(CustomComboPreset.PaladinRoyalAuthorityAtonementComboFeature))
{
// These use a fixed 2.5s for the GCD, because I don't know how to pull the actual GCD length.
// Technically, this can fail to use the correct ability if the GCD is less than 2.5s and the
// remaining duration of FoF is just barely enough, but since latency is a thing,
// it's probably good to have this type of buffer anyway.
if (HasEffect(PLD.Buffs.SepulchreReady) && inMeleeRange)
return OriginalHook(PLD.Atonement);

if (HasEffect(PLD.Buffs.SupplicationReady) && inMeleeRange && (fof.RemainingTime > 2.5 || !HasEffect(PLD.Buffs.DivineMight)))
return OriginalHook(PLD.Atonement);

if (HasEffect(PLD.Buffs.AtonementReady) && inMeleeRange && (fof.RemainingTime > 5 || !HasEffect(PLD.Buffs.DivineMight)))
return OriginalHook(PLD.Atonement);
}

if ((HasEffect(PLD.Buffs.DivineMight) || HasEffect(PLD.Buffs.Requiescat)) && this.HasMp(PLD.HolySpirit))
return PLD.HolySpirit;
}
}

if (IsEnabled(CustomComboPreset.PaladinRoyalAuthorityAtonementComboFeature))
if (level >= PLD.Levels.Atonement && IsEnabled(CustomComboPreset.PaladinRoyalAuthorityAtonementComboFeature))
{
if (level >= PLD.Levels.Atonement && lastComboMove != PLD.FastBlade && lastComboMove != PLD.RiotBlade)
{
if (HasEffect(PLD.Buffs.SwordOath))
{
return PLD.Atonement;
}
var sepulchre = FindEffect(PLD.Buffs.SepulchreReady);
if (sepulchre != null && inMeleeRange && (lastComboMove == PLD.RiotBlade || sepulchre.RemainingTime <= 2.5))
return OriginalHook(PLD.Atonement);

if (HasEffect(PLD.Buffs.SupplicationReady))
{
return PLD.Supplication;
}
var supplication = FindEffect(PLD.Buffs.SupplicationReady);
if (supplication != null && inMeleeRange && (lastComboMove == PLD.RiotBlade || supplication.RemainingTime <= 5))
return OriginalHook(PLD.Atonement);

if (HasEffect(PLD.Buffs.SepulchreReady))
{
return PLD.Sepulchre;
}
}
var AtonementReady = FindEffect(PLD.Buffs.AtonementReady);
if (AtonementReady != null && inMeleeRange && (lastComboMove == PLD.RiotBlade || AtonementReady.RemainingTime <= 7.5))
return OriginalHook(PLD.Atonement);
}

if (IsEnabled(CustomComboPreset.PaladinRoyalAuthorityCombo))
if (level >= PLD.Levels.HolySpirit && IsEnabled(CustomComboPreset.PaladinRoyalAuthorityDivineMightFeature))
{
if (comboTime > 0)
{
if (lastComboMove == PLD.RiotBlade && level >= PLD.Levels.RageOfHalone)
{
if (IsEnabled(CustomComboPreset.PaladinRoyalAuthorityDivineMightFeature))
{
if (HasEffect(PLD.Buffs.DivineMight))
{
if (level >= PLD.Levels.HolySpirit && this.HasMp(PLD.HolySpirit))
return PLD.HolySpirit;
}
}

// Royal Authority
return OriginalHook(PLD.RageOfHalone);
}
var divineMight = FindEffect(PLD.Buffs.DivineMight);
if (this.HasMp(PLD.HolySpirit) && divineMight != null && (lastComboMove == PLD.RiotBlade || divineMight.RemainingTime <= 2.5 || !inMeleeRange))
return PLD.HolySpirit;
}

if (lastComboMove == PLD.FastBlade && level >= PLD.Levels.RiotBlade)
return PLD.RiotBlade;
}
if (IsEnabled(CustomComboPreset.PaladinRoyalAuthorityCombo))
{
// Royal Authority
if (lastComboMove == PLD.RiotBlade && level >= PLD.Levels.RageOfHalone)
return OriginalHook(PLD.RageOfHalone);

if (lastComboMove == PLD.FastBlade && level >= PLD.Levels.RiotBlade)
return PLD.RiotBlade;
return PLD.FastBlade;
}
}
Expand All @@ -194,31 +219,41 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim
{
if (actionID == PLD.Prominence)
{
if (IsEnabled(CustomComboPreset.PaladinProminenceGoringBladeComboFeature) && HasEffect(PLD.Buffs.GoringBladeReady))
return PLD.GoringBlade;

if (level >= PLD.Levels.Confiteor && IsEnabled(CustomComboPreset.PaladinProminenceConfiteorComboFeature))
{
var original = OriginalHook(PLD.Confiteor);
if (original != PLD.Confiteor)
return original;

if (HasEffect(PLD.Buffs.BladeOfHonorReady))
return OriginalHook(PLD.Imperator);

if (HasEffect(PLD.Buffs.ConfiteorReady))
return OriginalHook(PLD.Confiteor);
}

// During FoF, prioritize the higher-potency Divine Might cast over the normal combo chain
if (IsEnabled(CustomComboPreset.PaladinProminenceDivineMightFeature))
{
if (HasEffect(PLD.Buffs.FightOrFlight) && HasEffect(PLD.Buffs.DivineMight))
if (level >= PLD.Levels.HolyCircle && this.HasMp(PLD.HolyCircle))
{
if (level >= PLD.Levels.HolyCircle && this.HasMp(PLD.HolyCircle))
if (HasEffect(PLD.Buffs.FightOrFlight) && (HasEffect(PLD.Buffs.DivineMight) || HasEffect(PLD.Buffs.Requiescat)))
return PLD.HolyCircle;
}
}

if (comboTime > 0)
if (lastComboMove == PLD.TotalEclipse && level >= PLD.Levels.Prominence)
{
if (lastComboMove == PLD.TotalEclipse && level >= PLD.Levels.Prominence)
if (IsEnabled(CustomComboPreset.PaladinProminenceDivineMightFeature))
{
if (IsEnabled(CustomComboPreset.PaladinProminenceDivineMightFeature))
{
if (HasEffect(PLD.Buffs.DivineMight))
{
if (level >= PLD.Levels.HolyCircle && this.HasMp(PLD.HolyCircle))
return PLD.HolyCircle;
}
}

return PLD.Prominence;
if (level >= PLD.Levels.HolyCircle && HasEffect(PLD.Buffs.DivineMight) && this.HasMp(PLD.HolyCircle))
return PLD.HolyCircle;
}

return PLD.Prominence;
}

return PLD.TotalEclipse;
Expand Down Expand Up @@ -263,27 +298,6 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim
}
}

internal class PaladinFightOrFlight : PaladinCombo
{
protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.PldAny;

protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level)
{
if (actionID == PLD.FightOrFlight)
{
if (IsEnabled(CustomComboPreset.PaladinFightOrFlightGoringBladeFeature))
{
if (HasEffect(PLD.Buffs.GoringBladeReady))
{
return PLD.GoringBlade;
}
}
}

return actionID;
}
}

internal class PaladinRequiescat : PaladinCombo
{
protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.PldAny;
Expand All @@ -307,15 +321,15 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim
// net reduction in potency, and *may* in fact increase it if someone is slightly late in applying
// their party buffs, as it shifts the high-potency Confiteor cast back into the party buff window by a
// single GCD.
if (IsEnabled(CustomComboPreset.PaladinRequiescatFightOrFlightFeature) && IsEnabled(CustomComboPreset.PaladinFightOrFlightGoringBladeFeature))
if (IsEnabled(CustomComboPreset.PaladinRequiescatFightOrFlightFeature))
{
// Don't use Goring Blade until Requiescat is on cooldown, unless it somehow got desynced and is >5s left on its cooldown.
// This prevents an odd effect where shortly after casting Fight or Flight, Goring Blade would be cast instead of Requiescat, causing drift.
var req = GetCooldown(PLD.Requiescat);
if (HasEffect(PLD.Buffs.GoringBladeReady) && req.CooldownElapsed > 0 && req.CooldownElapsed < 55)
{
// This also respects the user's selection in the Actions and Trait window for whether FoF turns into Goring Blade, only including it on
// Requiescat if FoF would also include it.
// Lastly, if the user is not currently in melee range, Goring Blade will be skipped, usually in favor of the Confiteor combo below.
if (OriginalHook(PLD.FightOrFlight) == PLD.GoringBlade && GetCooldown(PLD.Requiescat).CooldownRemaining > 5 && InMeleeRange())
return PLD.GoringBlade;
}
}

if (level >= PLD.Levels.Confiteor)
Expand All @@ -332,6 +346,8 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim
return OriginalHook(PLD.Confiteor);
}

// This should only occur if the user is below the level for the full 4-part Confiteor combo (level 90), as after that level, all 4
// stacks of Requiescat will be consumed by the Confiteor combo.
if (level >= PLD.Levels.Requiescat && HasEffect(PLD.Buffs.Requiescat))
return PLD.HolySpirit;
}
Expand Down
19 changes: 16 additions & 3 deletions XIVComboExpanded/CustomComboPreset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -676,22 +676,35 @@ public enum CustomComboPreset
[CustomComboInfo("Royal Authority Atonement Feature", "Replace Royal Authority with Atonement, Supplication & Sepulchre when under the effect of the corresponding buffs.", PLD.JobID)]
PaladinRoyalAuthorityAtonementComboFeature = 1903,

[ParentCombo(PaladinRoyalAuthorityCombo)]
[CustomComboInfo("Royal Authority Confiteor Feature", "Replace Royal Authority with Confiteor and its combo chain when under the effect of Requiescat.", PLD.JobID)]
PaladinRoyalAuthorityConfiteorComboFeature = 1917,

[ParentCombo(PaladinRoyalAuthorityCombo)]
[CustomComboInfo("Royal Authority Goring Blade Feature", "Replace Royal Authority with Goring Blade when available.", PLD.JobID)]
PaladinRoyalAuthorityGoringBladeComboFeature = 1918,

[CustomComboInfo("Prominence Combo", "Replace Prominence with its combo chain.", PLD.JobID)]
PaladinProminenceCombo = 1904,

[ParentCombo(PaladinProminenceCombo)]
[CustomComboInfo("Prominence Divine Might Feature", "Replace Prominence with Holy Circle when Divine Might is active.", PLD.JobID)]
PaladinProminenceDivineMightFeature = 1913,

[ParentCombo(PaladinProminenceCombo)]
[CustomComboInfo("Prominence Confiteor Feature", "Replace Prominence with Confiteor and its combo chain when under the effect of Requiescat.", PLD.JobID)]
PaladinProminenceConfiteorComboFeature = 1919,

[ParentCombo(PaladinProminenceCombo)]
[CustomComboInfo("Prominence Goring Blade Feature", "Replace Prominence with Goring Blade when available.", PLD.JobID)]
PaladinProminenceGoringBladeComboFeature = 1920,

[CustomComboInfo("Requiescat Fight or Flight Feature", "Replace Requiescat with Fight or Flight when off cooldown or if it will be ready sooner.", PLD.JobID)]
PaladinRequiescatFightOrFlightFeature = 1914,

[CustomComboInfo("Requiescat Confiteor", "Replace Requiescat with Confiteor and combo chain while under the effect of Requiescat, and then with Holy Spirit if there are remaining charges.", PLD.JobID)]
PaladinRequiescatCombo = 1905,

[CustomComboInfo("Fight or Flight Goring Blade Feature", "Replace Fight or Flight with Goring Blade while Fight or Flight is active. Also applies to Requiescat if the Requiescat Fight or Flight Feature is enabled.", PLD.JobID)]
PaladinFightOrFlightGoringBladeFeature = 1911,

[CustomComboInfo("Confiteor Feature", "Replace Holy Spirit/Circle with Confiteor while under the effect of Requiescat.", PLD.JobID)]
PaladinConfiteorFeature = 1907,

Expand Down

0 comments on commit 56a23c4

Please sign in to comment.