diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs index bc58ec7f6..e11fbf3e2 100644 --- a/BasicRotations/Healer/SGE_Default.cs +++ b/BasicRotations/Healer/SGE_Default.cs @@ -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) { diff --git a/BasicRotations/Melee/MNK_Default.cs b/BasicRotations/Melee/MNK_Default.cs index 30cbf85ec..c35bbf36e 100644 --- a/BasicRotations/Melee/MNK_Default.cs +++ b/BasicRotations/Melee/MNK_Default.cs @@ -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; @@ -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 @@ -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.' @@ -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' diff --git a/RotationSolver.Basic/Actions/ActionTargetInfo.cs b/RotationSolver.Basic/Actions/ActionTargetInfo.cs index 422b7bed8..4e092f85a 100644 --- a/RotationSolver.Basic/Actions/ActionTargetInfo.cs +++ b/RotationSolver.Basic/Actions/ActionTargetInfo.cs @@ -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; } diff --git a/RotationSolver.Basic/Actions/BaseAction.cs b/RotationSolver.Basic/Actions/BaseAction.cs index 8bda2184c..c1d0ddd95 100644 --- a/RotationSolver.Basic/Actions/BaseAction.cs +++ b/RotationSolver.Basic/Actions/BaseAction.cs @@ -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() @@ -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() diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 6e054810d..a96da4f05 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -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))] @@ -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; diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index 54dba1eb0..45d633b2b 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -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 + { + 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; @@ -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; @@ -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]; @@ -322,13 +336,13 @@ internal static float RaidTimeRaw } } - public static List PartyMembers { get; set; } = new(); + public static List PartyMembers { get; set; } = []; - public static List AllianceMembers { get; set; } = new(); + public static List AllianceMembers { get; set; } = []; - public static List FriendlyNPCMembers { get; set; } = new(); + public static List FriendlyNPCMembers { get; set; } = []; - public static List AllHostileTargets { get; set; } = new(); + public static List AllHostileTargets { get; set; } = []; public static IBattleChara? InterruptTarget { get; set; } @@ -338,7 +352,7 @@ internal static float RaidTimeRaw public static IBattleChara? DispelTarget { get; set; } - public static List AllTargets { get; set; } = new(); + public static List AllTargets { get; set; } = []; public static ulong[] TreasureCharas { diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs index 154925a55..f07acbcd6 100644 --- a/RotationSolver.Basic/Helpers/ObjectHelper.cs +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -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; @@ -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; @@ -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(); @@ -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; @@ -301,7 +308,8 @@ internal static bool IsAlive(this IGameObject obj) /// /// /// - 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; /// /// Determines whether the specified game object is a top priority hostile target based on its name being listed. @@ -334,7 +342,7 @@ internal static bool IsTopPriorityNamedHostile(this IGameObject obj) /// 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; diff --git a/RotationSolver.Basic/Rotations/Basic/SageRotation.cs b/RotationSolver.Basic/Rotations/Basic/SageRotation.cs index 4c8a16acb..61a9290db 100644 --- a/RotationSolver.Basic/Rotations/Basic/SageRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/SageRotation.cs @@ -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, diff --git a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs index 2dad865b1..6ab48033d 100644 --- a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs +++ b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs @@ -84,7 +84,7 @@ partial class CustomRotation /// Whether the number of party members is 8. /// [Description("Is Full Party")] - public static bool IsFullParty => PartyMembers.Count() is 8; + public static bool IsFullParty => PartyMembers.Count() is 8 or 9; /// /// party members HP. @@ -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; + /// + /// Is player in a normal or chaotic Alliance Raid. + /// + [Description("Is in an Alliance Raid (including Chaotic)")] + public static bool IsInAllianceRaid => DataCenter.IsInAllianceRaid; + /// /// Is player in UCoB duty. /// @@ -327,11 +333,17 @@ public static bool IsLongerThan(float time) [Description("Is in FRU duty")] public static bool IsInFRU => DataCenter.IsInFRU; + /// + /// Is player in COD duty. + /// + [Description("Is in FRU duty")] + public static bool IsInCOD => DataCenter.IsInCOD; + /// - /// Is player in duty. + /// Is player in any instanced duty. /// [Description("Is player in duty")] - public static bool IsInDuty => Svc.Condition[ConditionFlag.BoundByDuty]; + public static bool IsInDuty => DataCenter.IsInDuty; /// /// Your ping. diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 17c0124e7..484a7e5a5 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -2709,6 +2709,7 @@ private static void DrawDebugRotationStatus() private static unsafe void DrawStatus() { + ImGui.Text($"Merged Status: {DataCenter.MergedStatus}"); if ((IntPtr)FateManager.Instance() != IntPtr.Zero) { ImGui.Text($"Fate: {DataCenter.FateId}"); @@ -2729,7 +2730,6 @@ private static unsafe void DrawStatus() ImGui.Text($"Stop Moving: {DataCenter.StopMovingRaw}"); ImGui.Text($"CountDownTime: {Service.CountDownTime}"); ImGui.Text($"Combo Time: {DataCenter.ComboTime}"); - ImGui.Text($"Merged Status: {DataCenter.MergedStatus}"); ImGui.Text($"TargetingType: {DataCenter.TargetingType}"); ImGui.Text($"DeathTarget: {DataCenter.DeathTarget}"); foreach (var item in DataCenter.AttackedTargets) @@ -2839,8 +2839,8 @@ private static unsafe void DrawStatus() private static unsafe void DrawParty() { ImGui.Text($"Your combat state: {DataCenter.InCombat}"); - ImGui.Text($"Number of Party Members: {DataCenter.PartyMembers.Count}"); + ImGui.Text($"Is in Alliance Raid: {DataCenter.IsInAllianceRaid}"); ImGui.Text($"Number of Alliance Members: {DataCenter.AllianceMembers.Count}"); ImGui.Text($"Average Party HP Percent: {DataCenter.PartyMembersAverHP * 100}"); foreach (var p in Svc.Party) @@ -2872,6 +2872,7 @@ private static unsafe void DrawTargetData() if (target is IBattleChara battleChara) { ImGui.Text($"HP: {battleChara.CurrentHp} / {battleChara.MaxHp}"); + ImGui.Text($"Is Current Focus Target: {battleChara.IsFocusTarget()}"); ImGui.Text($"TTK: {battleChara.GetTimeToKill()}"); ImGui.Text($"Is Boss TTK: {battleChara.IsBossFromTTK()}"); ImGui.Text($"Is Boss Icon: {battleChara.IsBossFromIcon()}"); @@ -2881,7 +2882,7 @@ private static unsafe void DrawTargetData() ImGui.Text($"Is Alive: {battleChara.IsAlive()}"); ImGui.Text($"Is Party: {battleChara.IsParty()}"); ImGui.Text($"Is Healer: {battleChara.IsJobCategory(JobRole.Healer)}"); - ImGui.Text($"Is Alliance: {battleChara.IsAlliance()}"); + ImGui.Text($"Is Alliance: {battleChara.IsAllianceMember()}"); ImGui.Text($"Is Enemy: {battleChara.IsEnemy()}"); ImGui.Text($"Distance To Player: {battleChara.DistanceToPlayer()}"); ImGui.Text($"Is In EnemiesList: {battleChara.IsInEnemiesList()}"); diff --git a/RotationSolver/Updaters/TargetUpdater.cs b/RotationSolver/Updaters/TargetUpdater.cs index f15bfe083..8c0902f75 100644 --- a/RotationSolver/Updaters/TargetUpdater.cs +++ b/RotationSolver/Updaters/TargetUpdater.cs @@ -76,7 +76,7 @@ private static unsafe List GetAllianceMembers() { foreach (var target in DataCenter.AllTargets) { - if (ObjectHelper.IsAlliance(target) && target.Character() != null && + if (ObjectHelper.IsAllianceMember(target) && target.Character() != null && target.Character()->CharacterData.OnlineStatus != 15 && target.Character()->CharacterData.OnlineStatus != 5 && target.IsTargetable) {