Skip to content

Commit

Permalink
Merge pull request #563 from FFXIV-CombatReborn/chaoticHeal
Browse files Browse the repository at this point in the history
Chaotic heal
  • Loading branch information
LTS-FFXIV authored Jan 12, 2025
2 parents d2ecbb4 + c7981f9 commit 551cf9a
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 51 deletions.
2 changes: 1 addition & 1 deletion BasicRotations/Healer/SGE_Default.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ private bool ChoiceEukrasia(out IAction? act)

// If the last action performed matches any of a list of specific actions, it clears the Eukrasia aim.
// This serves as a reset/cleanup mechanism to ensure the decision logic starts fresh for the next cycle.
if (IsLastGCD(false, EukrasianPrognosisIiPvE, EukrasianPrognosisPvE,
if (IsLastGCD(true, EukrasianPrognosisIiPvE, EukrasianPrognosisPvE,
EukrasianDiagnosisPvE, EukrasianDyskrasiaPvE, EukrasianDosisIiiPvE, EukrasianDosisIiPvE,
EukrasianDosisPvE) || !InCombat)
{
Expand Down
68 changes: 54 additions & 14 deletions BasicRotations/Melee/MNK_Default.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ public enum RiddleOfFireFirst : byte
[Description("Perfect Balance")] PerfectBalance,
}

public enum MasterfulBlitzUse : byte
{
[Description("Use Immediately")] UseAsAble,

[Description("With ROF burst logic")] RiddleOfFireUse,
}

[RotationConfig(CombatType.PvE, Name = "Use Form Shift")]
public bool AutoFormShift { get; set; } = true;

Expand All @@ -32,6 +39,9 @@ public enum RiddleOfFireFirst : byte
[RotationConfig(CombatType.PvE, Name = "Enable TEA Checker.")]
public bool EnableTEAChecker { get; set; } = false;

[RotationConfig(CombatType.PvE, Name = "Use Masterful Blitz abilites as soon as they are available.")]
public MasterfulBlitzUse MBAbilities { get; set; } = MasterfulBlitzUse.RiddleOfFireUse;

[RotationConfig(CombatType.PvE, Name = "Use Riddle of Fire after this ability")]
public RiddleOfFireFirst ROFFirst { get; set; } = RiddleOfFireFirst.Brotherhood;
#endregion
Expand Down Expand Up @@ -91,13 +101,18 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act)
// 'If you are in brotherhood and forbidden chakra is available, use it.'
if (TheForbiddenChakraPvE.CanUse(out act)) return true;
}
if (!InBrotherhood)
else
{
// 'If you are not in brotherhood and brotherhood is about to be available, hold for burst.'
if (BrotherhoodPvE.Cooldown.WillHaveOneChargeGCD(1) && TheForbiddenChakraPvE.CanUse(out act)) return false;
if (BrotherhoodPvE.Cooldown.WillHaveOneChargeGCD(1) && TheForbiddenChakraPvE.CanUse(out act)) return true;
// 'If you are not in brotherhood use it.'
if (TheForbiddenChakraPvE.CanUse(out act)) return true;
}
if (!BrotherhoodPvE.EnoughLevel)
{
// 'If you are not high enough level for brotherhood, use it.'
if (TheForbiddenChakraPvE.CanUse(out act)) return true;
}
if (!TheForbiddenChakraPvE.EnoughLevel)
{
// 'If you are not high enough level for TheForbiddenChakra, use immediately at 5 chakra.'
Expand Down Expand Up @@ -239,22 +254,47 @@ protected override bool GeneralGCD(out IAction? act)

// bullet proofed finisher - use when during burst
// or if burst was missed, and next burst is not arriving in time, use it better than waste it, otherwise, hold it for next rof
if (!BeastChakras.Contains(BeastChakra.NONE) && (Player.HasStatus(true, StatusID.RiddleOfFire) || RiddleOfFirePvE.Cooldown.JustUsedAfter(42)))
if (!BeastChakras.Contains(BeastChakra.NONE))
{
// Both Nadi and 3 beasts
if (PhantomRushPvE.CanUse(out act)) return true;
if (TornadoKickPvE.CanUse(out act)) return true;
switch (MBAbilities)
{
case MasterfulBlitzUse.UseAsAble:
default:
if (PhantomRushPvE.CanUse(out act)) return true;
if (TornadoKickPvE.CanUse(out act)) return true;

// Needing Solar Nadi and has 3 different beasts
if (RisingPhoenixPvE.CanUse(out act)) return true;
if (FlintStrikePvE.CanUse(out act)) return true;

// Needing Lunar Nadi and has 3 of the same beasts
if (ElixirBurstPvE.CanUse(out act)) return true;
if (ElixirFieldPvE.CanUse(out act)) return true;

// No Nadi and 3 beasts
if (CelestialRevolutionPvE.CanUse(out act)) return true;
break;

case MasterfulBlitzUse.RiddleOfFireUse:
if (Player.HasStatus(true, StatusID.RiddleOfFire) || RiddleOfFirePvE.Cooldown.JustUsedAfter(42))
{
// Both Nadi and 3 beasts
if (PhantomRushPvE.CanUse(out act)) return true;
if (TornadoKickPvE.CanUse(out act)) return true;

// Needing Solar Nadi and has 3 different beasts
if (RisingPhoenixPvE.CanUse(out act)) return true;
if (FlintStrikePvE.CanUse(out act)) return true;
// Needing Solar Nadi and has 3 different beasts
if (RisingPhoenixPvE.CanUse(out act)) return true;
if (FlintStrikePvE.CanUse(out act)) return true;

// Needing Lunar Nadi and has 3 of the same beasts
if (ElixirBurstPvE.CanUse(out act)) return true;
if (ElixirFieldPvE.CanUse(out act)) return true;
// Needing Lunar Nadi and has 3 of the same beasts
if (ElixirBurstPvE.CanUse(out act)) return true;
if (ElixirFieldPvE.CanUse(out act)) return true;

// No Nadi and 3 beasts
if (CelestialRevolutionPvE.CanUse(out act)) return true;
// No Nadi and 3 beasts
if (CelestialRevolutionPvE.CanUse(out act)) return true;
}
break;
}
}

// 'Because Fire¡¯s Reply grants formless, we have an imposed restriction that we prefer not to use it while under PB, or if we have a formless already.' + 'Cast Fire's Reply after an opo gcd'
Expand Down
2 changes: 1 addition & 1 deletion RotationSolver.Basic/Actions/ActionTargetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public bool GeneralCheck(IBattleChara gameObject, bool skipStatusProvideCheck)
{
if (!gameObject.IsTargetable) return false;

if (Service.Config.RaiseType == RaiseType.PartyOnly && gameObject.IsAlliance() && !gameObject.IsParty())
if (Service.Config.RaiseType == RaiseType.PartyOnly && !gameObject.IsParty())
{
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions RotationSolver.Basic/Actions/BaseAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private bool IsLastAbilityUsable()
{
return IsLastAbilityv2Usable();
}
return DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD <= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.isLastAbilityTimer);
return DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD <= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsLastAbilityTimer);
}

private bool IsFirstAbilityUsable()
Expand All @@ -182,7 +182,7 @@ private bool IsFirstAbilityUsable()
{
return IsFirstAbilityv2Usable();
}
return DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD >= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.isFirstAbilityTimer);
return DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD >= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsFirstAbilityTimer);
}

private bool IsLastAbilityv2Usable()
Expand Down
8 changes: 6 additions & 2 deletions RotationSolver.Basic/Configuration/Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,12 @@ public const string
[UI("isLastAbilityTimer", Description = "Don't fuck with this if you dont know what it does",
Filter = Extra)]
[Range(0.100f, 2.500f, ConfigUnitType.Seconds, 0.001f)]
public float isLastAbilityTimer { get; set; } = 0.800f;
public float IsLastAbilityTimer { get; set; } = 0.800f;

[UI("isFirstAbilityTimer", Description = "Don't fuck with this if you dont know what it does",
Filter = Extra)]
[Range(0.100f, 2.500f, ConfigUnitType.Seconds, 0.001f)]
public float isFirstAbilityTimer { get; set; } = 0.600f;
public float IsFirstAbilityTimer { get; set; } = 0.600f;

[UI("Auto turn off RSR when combat is over more for more then...",
Parent = nameof(AutoOffAfterCombat))]
Expand Down Expand Up @@ -478,6 +478,10 @@ public const string
Filter = HealingActionCondition, Section = 3)]
private static readonly bool _chocoboPartyMember = false;

[ConditionBool, UI("Treat focus targeted player as party member in alliance raids", Description = "Experimental, includes Chaotic.",
Filter = HealingActionCondition, Section = 3)]
private static readonly bool _focusTargetIsParty = false;

[ConditionBool, UI("Heal party members with GCD if there is nothing to do in combat.",
Filter = HealingActionCondition, Section = 3)]
private static readonly bool _healWhenNothingTodo = true;
Expand Down
28 changes: 21 additions & 7 deletions RotationSolver.Basic/DataCenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ internal static bool HasApplyStatus(ulong id, StatusID[] ids)
public static TerritoryInfo? Territory { get; set; }

public static bool IsPvP => Territory?.IsPvP ?? false;
public static bool IsInDuty => Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56];
public static bool IsInAllianceRaid
{
get
{
var allianceTerritoryIds = new HashSet<ushort>
{
151, 174, 372, 508, 556, 627, 734, 776, 826, 882, 917, 966, 1054, 1118, 1178, 1248, 1241
};
return allianceTerritoryIds.Contains(TerritoryID);
}
}

public static ushort TerritoryID => Svc.ClientState.TerritoryType;
public static bool IsInUCoB => TerritoryID == 733;
Expand All @@ -112,6 +124,8 @@ internal static bool HasApplyStatus(ulong id, StatusID[] ids)
public static bool IsInDSR => TerritoryID == 968;
public static bool IsInTOP => TerritoryID == 1122;
public static bool IsInFRU => TerritoryID == 1238;
public static bool IsInCOD => TerritoryID == 1241;


public static AutoStatus MergedStatus => AutoStatus | CommandStatus;

Expand Down Expand Up @@ -257,8 +271,8 @@ public static float GCDTime(uint gcdCount = 0, float offset = 0)
public static bool LastAbilityv2 => DataCenter.InCombat && !ActionHelper.CanUseGCD && (ActionManagerHelper.GetCurrentAnimationLock() == 0) && !Player.Object.IsCasting && (DataCenter.DefaultGCDElapsed >= DataCenter.DefaultGCDRemain);
public static bool FirstAbilityv2 => DataCenter.InCombat && !ActionHelper.CanUseGCD && (ActionManagerHelper.GetCurrentAnimationLock() == 0) && !Player.Object.IsCasting && (DataCenter.DefaultGCDRemain >= DataCenter.DefaultGCDElapsed);

public static bool LastAbilityorNot => DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD <= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.isLastAbilityTimer);
public static bool FirstAbilityorNot => DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD >= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.isFirstAbilityTimer);
public static bool LastAbilityorNot => DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD <= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsLastAbilityTimer);
public static bool FirstAbilityorNot => DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD >= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsFirstAbilityTimer);
#endregion

public static uint[] BluSlots { get; internal set; } = new uint[24];
Expand Down Expand Up @@ -322,13 +336,13 @@ internal static float RaidTimeRaw
}
}

public static List<IBattleChara> PartyMembers { get; set; } = new();
public static List<IBattleChara> PartyMembers { get; set; } = [];

public static List<IBattleChara> AllianceMembers { get; set; } = new();
public static List<IBattleChara> AllianceMembers { get; set; } = [];

public static List<IBattleChara> FriendlyNPCMembers { get; set; } = new();
public static List<IBattleChara> FriendlyNPCMembers { get; set; } = [];

public static List<IBattleChara> AllHostileTargets { get; set; } = new();
public static List<IBattleChara> AllHostileTargets { get; set; } = [];

public static IBattleChara? InterruptTarget { get; set; }

Expand All @@ -338,7 +352,7 @@ internal static float RaidTimeRaw

public static IBattleChara? DispelTarget { get; set; }

public static List<IBattleChara> AllTargets { get; set; } = new();
public static List<IBattleChara> AllTargets { get; set; } = [];

public static ulong[] TreasureCharas
{
Expand Down
22 changes: 15 additions & 7 deletions RotationSolver.Basic/Helpers/ObjectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel.Sheets;
using RotationSolver.Basic.Configuration;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -82,7 +83,7 @@ internal static unsafe bool IsOthersPlayers(this IGameObject obj)

internal static bool IsAttackable(this IBattleChara battleChara)
{
if (battleChara.IsAlliance() == true) return false;
if (battleChara.IsAllianceMember() == true) return false;

// Dead.
if (Service.Config.FilterOneHpInvincible && battleChara.CurrentHp <= 1) return false;
Expand Down Expand Up @@ -244,11 +245,10 @@ internal static unsafe bool IsEnemy(this IGameObject obj)
=> obj != null
&& ActionManager.CanUseActionOnTarget((uint)ActionID.BlizzardPvE, obj.Struct());

internal static unsafe bool IsAlliance(this IGameObject obj)
internal static unsafe bool IsAllianceMember(this IGameObject obj)
=> obj.GameObjectId is not 0
&& (!DataCenter.IsPvP && obj is IPlayerCharacter
|| ActionManager.CanUseActionOnTarget((uint)ActionID.CurePvE, obj.Struct()));

&& (!DataCenter.IsPvP && DataCenter.IsInAllianceRaid && obj is IPlayerCharacter
|| DataCenter.IsInAllianceRaid && ActionManager.CanUseActionOnTarget((uint)ActionID.CurePvE, obj.Struct()));

private static readonly object _lock = new object();

Expand All @@ -268,10 +268,17 @@ internal static bool IsParty(this IGameObject gameObject)
if (Service.Config.FriendlyPartyNpcHealRaise2 && gameObject.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember) return true;
if (Service.Config.ChocoboPartyMember && gameObject.GetNameplateKind() == NameplateKind.PlayerCharacterChocobo) return true;
if (Service.Config.FriendlyBattleNpcHeal && gameObject.GetNameplateKind() == NameplateKind.FriendlyBattleNPC) return true;
if (Service.Config.FocusTargetIsParty && gameObject.IsFocusTarget() == true && gameObject.IsAllianceMember() == true) return true;
}
return false;
}

internal static bool IsFocusTarget(this IGameObject gameObject)
{
var focusTarget = Svc.Targets.FocusTarget;
return focusTarget != null && focusTarget.GameObjectId == gameObject.GameObjectId;
}

internal static bool IsTargetOnSelf(this IBattleChara IBattleChara)
{
return IBattleChara.TargetObject?.TargetObject == IBattleChara;
Expand Down Expand Up @@ -301,7 +308,8 @@ internal static bool IsAlive(this IGameObject obj)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static unsafe ObjectKind GetObjectKind(this IGameObject obj) => (ObjectKind)obj.Struct()->ObjectKind;
public static unsafe Dalamud.Game.ClientState.Objects.Enums.ObjectKind GetObjectKind(this IGameObject obj)
=> (Dalamud.Game.ClientState.Objects.Enums.ObjectKind)obj.Struct()->ObjectKind;

/// <summary>
/// Determines whether the specified game object is a top priority hostile target based on its name being listed.
Expand Down Expand Up @@ -334,7 +342,7 @@ internal static bool IsTopPriorityNamedHostile(this IGameObject obj)
/// </returns>
internal static bool IsTopPriorityHostile(this IGameObject obj)
{
if (obj.IsAlliance() || obj.IsParty() || obj == null) return false;
if (obj.IsAllianceMember() || obj.IsParty() || obj == null) return false;

var fateId = DataCenter.FateId;

Expand Down
10 changes: 0 additions & 10 deletions RotationSolver.Basic/Rotations/Basic/SageRotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,6 @@ static partial void ModifyZoePvE(ref ActionSetting setting)

static partial void ModifyPepsisPvE(ref ActionSetting setting)
{
setting.ActionCheck = () =>
{
foreach (var chara in DataCenter.PartyMembers)
{
if (chara.HasStatus(true, StatusID.EukrasianDiagnosis, StatusID.EukrasianPrognosis)
&& chara.GetHealthRatio() < 0.9) return true;
}

return false;
};
setting.CreateConfig = () => new ActionConfig()
{
AoeCount = 1,
Expand Down
18 changes: 15 additions & 3 deletions RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ partial class CustomRotation
/// Whether the number of party members is 8.
/// </summary>
[Description("Is Full Party")]
public static bool IsFullParty => PartyMembers.Count() is 8;
public static bool IsFullParty => PartyMembers.Count() is 8 or 9;

/// <summary>
/// party members HP.
Expand Down Expand Up @@ -291,6 +291,12 @@ public static bool IsLongerThan(float time)
[Description("Is in the high-end duty")]
public static bool IsInHighEndDuty => DataCenter.Territory?.IsHighEndDuty ?? false;

/// <summary>
/// Is player in a normal or chaotic Alliance Raid.
/// </summary>
[Description("Is in an Alliance Raid (including Chaotic)")]
public static bool IsInAllianceRaid => DataCenter.IsInAllianceRaid;

/// <summary>
/// Is player in UCoB duty.
/// </summary>
Expand Down Expand Up @@ -327,11 +333,17 @@ public static bool IsLongerThan(float time)
[Description("Is in FRU duty")]
public static bool IsInFRU => DataCenter.IsInFRU;

///<summary>
/// Is player in COD duty.
///</summary>
[Description("Is in FRU duty")]
public static bool IsInCOD => DataCenter.IsInCOD;

/// <summary>
/// Is player in duty.
/// Is player in any instanced duty.
/// </summary>
[Description("Is player in duty")]
public static bool IsInDuty => Svc.Condition[ConditionFlag.BoundByDuty];
public static bool IsInDuty => DataCenter.IsInDuty;

/// <summary>
/// Your ping.
Expand Down
Loading

0 comments on commit 551cf9a

Please sign in to comment.