diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs index 4512d352a..a93bef70f 100644 --- a/BasicRotations/Healer/SGE_Default.cs +++ b/BasicRotations/Healer/SGE_Default.cs @@ -154,6 +154,8 @@ protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) [RotationDesc(ActionID.KeracholePvE, ActionID.PhysisPvE, ActionID.HolosPvE, ActionID.IxocholePvE)] protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) { + if ((!MergedStatus.HasFlag(AutoStatus.DefenseArea) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle)) && PepsisPvE.CanUse(out act)) return true; + if (KeracholePvE.CanUse(out act) && EnhancedKeracholeTrait.EnoughLevel) return true; if (IxocholePvE.CanUse(out act)) return true; @@ -213,8 +215,6 @@ protected override bool GeneralAbility(IAction nextGCD, out IAction? act) if (SoteriaPvE.CanUse(out act) && PartyMembers.Any(b => b.HasStatus(true, StatusID.Kardion) && b.GetHealthRatio() < HealthSingleAbility)) return true; - if (PepsisPvE.CanUse(out act)) return true; - return base.GeneralAbility(nextGCD, out act); } #endregion diff --git a/BasicRotations/Magical/PCT_Default.cs b/BasicRotations/Magical/PCT_Default.cs index 09f14940a..e64642a78 100644 --- a/BasicRotations/Magical/PCT_Default.cs +++ b/BasicRotations/Magical/PCT_Default.cs @@ -5,8 +5,6 @@ [Api(4)] public sealed class PCT_Default : PictomancerRotation { - private const float CountdownBuffer = 0.2f; - #region Config Options [RotationConfig(CombatType.PvE, Name = "Use HolyInWhite or CometInBlack while moving")] public bool HolyCometMoving { get; set; } = true; @@ -41,7 +39,7 @@ public sealed class PCT_Default : PictomancerRotation if (!LandscapeMotifDrawn && StarrySkyMotifPvE.CanUse(out act) && !HasHyperphantasia) return act; } - if (remainTime <= RainbowDripPvE.Info.CastTime + CountdownBuffer + CountDownAhead && RainbowDripPvE.CanUse(out act)) + if (remainTime <= RainbowDripPvE.Info.CastTime + CountDownAhead && RainbowDripPvE.CanUse(out act)) { return act; } @@ -146,7 +144,7 @@ protected override bool GeneralGCD(out IAction? act) if (Paint == HolyCometMax && HolyInWhitePvE.CanUse(out act)) return true; // Landscape Paining Burst - if (RainbowDripPvE.CanUse(out act)) return true; + if (HasRainbowBright && RainbowDripPvE.CanUse(out act, skipCastingCheck: HasRainbowBright)) return true; if (StarPrismPvE.CanUse(out act)) return true; // timings for motif casting diff --git a/BasicRotations/Magical/SMN_Default.cs b/BasicRotations/Magical/SMN_Default.cs index 955e557a1..da1f319ee 100644 --- a/BasicRotations/Magical/SMN_Default.cs +++ b/BasicRotations/Magical/SMN_Default.cs @@ -42,7 +42,7 @@ public enum SummonOrderType : byte [RotationConfig(CombatType.PvE, Name = "Use this if there's no other raid buff in your party")] public bool SecondTypeOpenerLogic { get; set; } = false; - [RotationConfig(CombatType.PvE, Name = "Use Physick")] + [RotationConfig(CombatType.PvE, Name = "Use Physick above level 30")] public bool Healbot { get; set; } = false; #endregion @@ -169,7 +169,7 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) [RotationDesc(ActionID.PhysickPvE)] protected override bool HealSingleGCD(out IAction? act) { - if ((Healbot || Player.Level <= 20) && PhysickPvE.CanUse(out act)) return true; + if ((Healbot || Player.Level <= 30) && PhysickPvE.CanUse(out act)) return true; return base.HealSingleGCD(out act); } diff --git a/BasicRotations/Melee/NIN_Default.cs b/BasicRotations/Melee/NIN_Default.cs index 85a163591..a736ebdcf 100644 --- a/BasicRotations/Melee/NIN_Default.cs +++ b/BasicRotations/Melee/NIN_Default.cs @@ -19,13 +19,15 @@ public sealed class NIN_Default : NinjaRotation [RotationConfig(CombatType.PvE, Name = "Use Mudras Outside of Combat when enemies are near")] public bool CommbatMudra { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Forked Raiju instead of Fleeting Raiju if you are outside of range (Dangerous)")] + public bool ForkedUse { get; set; } = false; #endregion #region Tracking Properties // Properties to track RabbitMediumPvE failures and related information. private int _rabbitMediumFailures = 0; private IBaseAction? _lastNinActionAim = null; - private IAction? _followUpGCDAction = null; #endregion #region CountDown Logic @@ -399,13 +401,17 @@ protected override bool GeneralGCD(out IAction? act) { act = null; - if (RabbitMediumPvE.CanUse(out act)) return true; + if (_ninActionAim == null && RabbitMediumPvE.CanUse(out act)) return true; if (!IsExecutingMudra && (InTrickAttack || InMug) && NoNinjutsu && !HasRaijuReady && !Player.HasStatus(true, StatusID.TenChiJin) && PhantomKamaitachiPvE.CanUse(out act)) return true; - if (!IsExecutingMudra && FleetingRaijuPvE.CanUse(out act)) return true; + if (!IsExecutingMudra) + { + if (FleetingRaijuPvE.CanUse(out act)) return true; + if (ForkedUse && ForkedRaijuPvE.CanUse(out act)) return true; + } if ((InCombat || (CommbatMudra && HasHostilesInMaxRange)) && ChoiceNinjutsu(out act)) return true; if (((!InCombat && CommbatMudra && HasHostilesInMaxRange) || !CombatElapsedLess(7)) && DoNinjutsu(out act)) return true; diff --git a/Resources/InvincibleStatus.json b/Resources/InvincibleStatus.json index febef3b0c..2b72fdfa2 100644 --- a/Resources/InvincibleStatus.json +++ b/Resources/InvincibleStatus.json @@ -1,7 +1,6 @@ [ 151, 198, - 385, 469, 592, 1240, diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 375eca895..f073238d6 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -375,10 +375,6 @@ public const string [ConditionBool, UI("Record knockback actions", Filter = List2)] private static readonly bool _recordKnockbackies = false; - [JobConfig, UI("Override Action Ahead Timer", Description = "If you don't know what this does, you don't need to modify it", - Filter = BasicTimer)] - private static readonly bool _overrideActionAheadTimer = false; - [UI("Use additional conditions", Filter = BasicParams)] public bool UseAdditionalConditions { get; set; } = false; @@ -634,9 +630,9 @@ public const string public int AutoDefenseNumber { get; set; } = 2; #endregion - + #region PvP - + [JobConfig, UI("Ignore Invincibility for PvP purposes.", Filter = PvPSpecificControls)] private readonly bool _ignorePvPInvincibility = false; @@ -687,15 +683,19 @@ public const string PvEFilter = JobFilterType.Tank)] private readonly float _healthForAutoDefense = 1; + [JobConfig, UI("Engage settings", Filter = TargetConfig, PvPFilter = JobFilterType.NoJob)] + private readonly TargetHostileType _hostileType = TargetHostileType.AllTargetsWhenSoloInDuty; + + [JobConfig, UI("Override Action Ahead Timer", Description = "If you don't know what this does, you don't need to modify it", + Filter = BasicTimer)] + private readonly bool _overrideActionAheadTimer = false; + [JobConfig, Range(0, 1.0f, ConfigUnitType.Seconds)] [UI("Action Ahead (How far in advance of GCD being available RSR will try to queue the next GCD)", - Description = "This setting controls how many oGCDs RSR will try to fit in a single GCD window\nLower numbers mean more oGCDs, but potentially more GCD clipping", - Parent = nameof(OverrideActionAheadTimer))] + Description = "This setting controls how many oGCDs RSR will try to fit in a single GCD window\nLower numbers mean more oGCDs, but potentially more GCD clipping", + Parent = nameof(OverrideActionAheadTimer))] private readonly float _action4head = 0.4f; - [JobConfig, UI("Engage settings", Filter = TargetConfig, PvPFilter = JobFilterType.NoJob)] - private readonly TargetHostileType _hostileType = TargetHostileType.AllTargetsWhenSoloInDuty; - [JobConfig] private readonly string _PvPRotationChoice = string.Empty; diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs index 6f767c7e1..bf231309a 100644 --- a/RotationSolver.Basic/Helpers/ObjectHelper.cs +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -134,12 +134,12 @@ internal static bool IsAttackable(this IBattleChara battleChara) { TargetHostileType.AllTargetsCanAttack => true, TargetHostileType.TargetsHaveTarget => battleChara.TargetObject is IBattleChara, - TargetHostileType.AllTargetsWhenSolo => DataCenter.PartyMembers.Count() < 2 || battleChara.TargetObject is IBattleChara, - TargetHostileType.AllTargetsWhenSoloInDuty => (DataCenter.PartyMembers.Count() < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56])) + TargetHostileType.AllTargetsWhenSolo => DataCenter.PartyMembers.Count < 2 || battleChara.TargetObject is IBattleChara, + TargetHostileType.AllTargetsWhenSoloInDuty => (DataCenter.PartyMembers.Count < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56])) || battleChara.TargetObject is IBattleChara, TargetHostileType.TargetIsInEnemiesList => battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), - TargetHostileType.AllTargetsWhenSoloTargetIsInEnemiesList => (DataCenter.PartyMembers.Count() < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56])) || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), - TargetHostileType.AllTargetsWhenSoloInDutyTargetIsInEnemiesList => DataCenter.PartyMembers.Count() < 2 || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), + TargetHostileType.AllTargetsWhenSoloTargetIsInEnemiesList => (DataCenter.PartyMembers.Count < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56])) || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), + TargetHostileType.AllTargetsWhenSoloInDutyTargetIsInEnemiesList => DataCenter.PartyMembers.Count < 2 || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), _ => true, }; } diff --git a/RotationSolver.Basic/Rotations/Basic/NinjaRotation.cs b/RotationSolver.Basic/Rotations/Basic/NinjaRotation.cs index 47b8d4574..fa66fa064 100644 --- a/RotationSolver.Basic/Rotations/Basic/NinjaRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/NinjaRotation.cs @@ -26,7 +26,7 @@ partial class NinjaRotation /// /// Do you need to prep or currently use shadowwalker /// - public bool ShadowWalkerNeeded => (TrickAttackPvE.EnoughLevel && TrickAttackPvE.Cooldown.WillHaveOneCharge(18)) || (KunaisBanePvE.EnoughLevel && KunaisBanePvE.Cooldown.WillHaveOneCharge(18)) || (MeisuiPvE.EnoughLevel && MeisuiPvE.Cooldown.WillHaveOneCharge(18)); + public bool ShadowWalkerNeeded => (TrickAttackPvE.EnoughLevel && TrickAttackPvE.Cooldown.WillHaveOneCharge(18)) || (KunaisBanePvE.EnoughLevel && KunaisBanePvE.Cooldown.WillHaveOneCharge(18)); /// /// Determines if Trick Attack is in its effective period. diff --git a/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs b/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs index cf9518bb8..ab82c9b86 100644 --- a/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs @@ -347,6 +347,11 @@ public override void DisplayStatus() /// public static bool HasStarryMuse => !Player.WillStatusEnd(0, true, StatusID.StarryMuse); + /// + /// Indicates if the player has Rainbow Bright. + /// + public static bool HasRainbowBright => !Player.WillStatusEnd(0, true, StatusID.RainbowBright); + /// /// Holds the remaining amount of HammerTime stacks /// @@ -500,7 +505,6 @@ static partial void ModifyCometInBlackPvE(ref ActionSetting setting) static partial void ModifyRainbowDripPvE(ref ActionSetting setting) { - setting.StatusNeed = [StatusID.RainbowBright]; setting.CreateConfig = () => new ActionConfig() { AoeCount = 1, diff --git a/RotationSolver.Basic/Service.cs b/RotationSolver.Basic/Service.cs index 75e4e5a28..f97ee8214 100644 --- a/RotationSolver.Basic/Service.cs +++ b/RotationSolver.Basic/Service.cs @@ -25,10 +25,8 @@ internal class Service : IDisposable private EzHook actorVfxCreateHook = null!; private unsafe delegate IntPtr ActorVfxCreateDelegate2(char* a1, nint a2, nint a3, float a4, char a5, ushort a6, char a7); - // From https://GitHub.com/PunishXIV/Orbwalker/blame/master/Orbwalker/Memory.cs#L74-L76 - [Signature("F3 0F 10 05 ?? ?? ?? ?? 0F 2E C7", ScanType = ScanType.StaticAddress, - Fallibility = Fallibility.Infallible)] + [Signature("F3 0F 10 05 ?? ?? ?? ?? 0F 2E C7", ScanType = ScanType.StaticAddress, Fallibility = Fallibility.Infallible)] static IntPtr forceDisableMovementPtr = IntPtr.Zero; private static unsafe ref int ForceDisableMovement => ref *(int*)(forceDisableMovementPtr + 4); @@ -69,8 +67,6 @@ private unsafe IntPtr ActorVfxCreateDetour(char* a1, nint a2, nint a3, float a4, throw new Exception("Failed to create object reference during VfxCreateDetour"); } - //Svc.Log.Verbose($"{obj.Name} is casting {path}"); - var newVfx = new VfxNewData(obj.GameObjectId, path); DataCenter.VfxDataQueue.Add(newVfx); } @@ -122,7 +118,6 @@ public static ActionID GetAdjustedActionId(ActionID id) public static unsafe uint GetAdjustedActionId(uint id) => ActionManager.Instance()->GetAdjustedActionId(id); - private static readonly ConcurrentDictionary AddonCache = new(); /// @@ -159,11 +154,23 @@ public static IEnumerable GetAddons() where T : struct /// /// Releases unmanaged resources and performs other cleanup operations. /// - public void Dispose() + protected virtual void Dispose(bool disposing) { - if (!_canMove && ForceDisableMovement > 0) + if (disposing) { - ForceDisableMovement--; + if (!_canMove && ForceDisableMovement > 0) + { + ForceDisableMovement--; + } } } + + /// + /// Releases unmanaged resources and performs other cleanup operations. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } \ No newline at end of file diff --git a/RotationSolver/Commands/RSCommands_Actions.cs b/RotationSolver/Commands/RSCommands_Actions.cs index 97a53ebd1..a8b470dd9 100644 --- a/RotationSolver/Commands/RSCommands_Actions.cs +++ b/RotationSolver/Commands/RSCommands_Actions.cs @@ -2,7 +2,6 @@ using ECommons.DalamudServices; using ECommons.ExcelServices; using ECommons.GameHelpers; -using FFXIVClientStructs.FFXIV.Client.Game.Character; using RotationSolver.Basic.Configuration; using RotationSolver.Updaters; @@ -17,6 +16,7 @@ public static partial class RSCommands internal static uint _lastActionID; static float _lastCountdownTime = 0; static Job _previousJob = Job.ADV; + static readonly Random random = new(); public static void IncrementState() { @@ -38,7 +38,7 @@ internal static unsafe bool CanDoAnAction(bool isGCD) if (!Player.Available) return false; // Do not click the button in random time. - if (DateTime.Now - _lastClickTime < TimeSpan.FromMilliseconds(new Random().Next( + if (DateTime.Now - _lastClickTime < TimeSpan.FromMilliseconds(random.Next( (int)(Service.Config.ClickingDelay.X * 1000), (int)(Service.Config.ClickingDelay.Y * 1000)))) return false; _lastClickTime = DateTime.Now; @@ -127,7 +127,7 @@ static void PulseSimulation(uint id) started = true; try { - int pulseCount = new Random().Next((int)Service.Config.KeyboardNoise.X, (int)Service.Config.KeyboardNoise.Y); + int pulseCount = random.Next((int)Service.Config.KeyboardNoise.X, (int)Service.Config.KeyboardNoise.Y); PulseAction(id, pulseCount); } catch (Exception ex) @@ -151,17 +151,14 @@ static void PulseAction(uint id, int remainingPulses) } PreviewUpdater.PulseActionBar(id); - var time = Service.Config.ClickingDelay.X + new Random().NextDouble() * (Service.Config.ClickingDelay.Y - Service.Config.ClickingDelay.X); + var time = Service.Config.ClickingDelay.X + random.NextDouble() * (Service.Config.ClickingDelay.Y - Service.Config.ClickingDelay.X); Svc.Framework.RunOnTick(() => { PulseAction(id, remainingPulses - 1); }, TimeSpan.FromSeconds(time)); } - internal static void ResetSpecial() - { - DoSpecialCommandType(SpecialCommandType.EndSpecial, false); - } + internal static void ResetSpecial() => DoSpecialCommandType(SpecialCommandType.EndSpecial, false); internal static void CancelState() { @@ -181,62 +178,28 @@ internal static void UpdateRotationState() var target = DataCenter.AllHostileTargets .FirstOrDefault(t => t != null && t.TargetObjectId == Player.Object?.GameObjectId); - if (Svc.Condition[ConditionFlag.LoggingOut]) - { - CancelState(); - } - else if (Service.Config.AutoOffWhenDead - && Player.Available - && Player.Object?.CurrentHp == 0) - { - CancelState(); - } - else if (Service.Config.AutoOffWhenDeadPvP && DataCenter.Territory?.IsPvP == true - && Player.Available - && Player.Object?.CurrentHp == 0) - { - CancelState(); - } - else if (Service.Config.AutoOffCutScene - && Svc.Condition[ConditionFlag.OccupiedInCutSceneEvent]) - { - CancelState(); - } - else if (Service.Config.AutoOffSwitchClass - && Player.Job != _previousJob) - { - _previousJob = Player.Job; - CancelState(); - } - else if (Service.Config.AutoOffBetweenArea - && (Svc.Condition[ConditionFlag.BetweenAreas] - || Svc.Condition[ConditionFlag.BetweenAreas51])) + if (Svc.Condition[ConditionFlag.LoggingOut] || + (Service.Config.AutoOffWhenDead && Player.Available && Player.Object?.CurrentHp == 0) || + (Service.Config.AutoOffWhenDeadPvP && DataCenter.Territory?.IsPvP == true && Player.Available && Player.Object?.CurrentHp == 0) || + (Service.Config.AutoOffCutScene && Svc.Condition[ConditionFlag.OccupiedInCutSceneEvent]) || + (Service.Config.AutoOffSwitchClass && Player.Job != _previousJob) || + (Service.Config.AutoOffBetweenArea && (Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51])) || + (Service.Config.CancelStateOnCombatBeforeCountdown && Service.CountDownTime > 0.2f && DataCenter.InCombat) || + (ActionUpdater.AutoCancelTime != DateTime.MinValue && DateTime.Now > ActionUpdater.AutoCancelTime) || + (DataCenter.RightSet.SwitchCancelConditionSet?.IsTrue(DataCenter.RightNowRotation) ?? false)) { CancelState(); + if (Player.Job != _previousJob) _previousJob = Player.Job; + if (ActionUpdater.AutoCancelTime != DateTime.MinValue) ActionUpdater.AutoCancelTime = DateTime.MinValue; } - - // Auto manual on being attacked by someone. - else if (Service.Config.StartOnAttackedBySomeone - && target != null - && !target.IsDummy()) + else if (Service.Config.StartOnAttackedBySomeone && target != null && !target.IsDummy()) { if (!DataCenter.State) { DoStateCommandType(StateCommandType.Manual); } } - - // Cancel state if combat starts before countdown is finished - else if (Service.Config.CancelStateOnCombatBeforeCountdown - && Service.CountDownTime > 0.2f - && DataCenter.InCombat) - { - CancelState(); - } - - // Auto start at count down. - else if (Service.Config.StartOnCountdown - && Service.CountDownTime > 0) + else if (Service.Config.StartOnCountdown && Service.CountDownTime > 0) { _lastCountdownTime = Service.CountDownTime; if (!DataCenter.State) @@ -251,27 +214,11 @@ internal static void UpdateRotationState() } } } - - // Holds state if countdown is running and setting is enabled else if (Service.Config.StartOnCountdown && Service.CountDownTime == 0 && _lastCountdownTime > 0.2f) { _lastCountdownTime = 0; CancelState(); } - - // Cancel when after combat. - else if (ActionUpdater.AutoCancelTime != DateTime.MinValue - && DateTime.Now > ActionUpdater.AutoCancelTime) - { - CancelState(); - ActionUpdater.AutoCancelTime = DateTime.MinValue; - } - - // Auto switch conditions. - else if (DataCenter.RightSet.SwitchCancelConditionSet?.IsTrue(DataCenter.RightNowRotation) ?? false) - { - CancelState(); - } else if (DataCenter.RightSet.SwitchManualConditionSet?.IsTrue(DataCenter.RightNowRotation) ?? false) { if (!DataCenter.State) @@ -292,6 +239,5 @@ internal static void UpdateRotationState() Svc.Log.Error(ex, "Exception in UpdateRotationState"); } } - } -} \ No newline at end of file +} diff --git a/RotationSolver/Commands/RSCommands_BasicInfo.cs b/RotationSolver/Commands/RSCommands_BasicInfo.cs index f47446482..72bfa1be4 100644 --- a/RotationSolver/Commands/RSCommands_BasicInfo.cs +++ b/RotationSolver/Commands/RSCommands_BasicInfo.cs @@ -2,80 +2,88 @@ using ECommons.DalamudServices; using RotationSolver.Data; - -namespace RotationSolver.Commands; - -public static partial class RSCommands +namespace RotationSolver.Commands { - internal static void Enable() + public static partial class RSCommands { - Svc.Commands.AddHandler(Service.COMMAND, new CommandInfo(OnCommand) - { - HelpMessage = UiString.Commands_Rotation.GetDescription(), - ShowInHelp = true, - }); - Svc.Commands.AddHandler(Service.ALTCOMMAND, new CommandInfo(OnCommand) + internal static void Enable() { - HelpMessage = UiString.Commands_Rotation.GetDescription(), - ShowInHelp = true, - }); - } - - internal static void Disable() - { - Svc.Commands.RemoveHandler(Service.COMMAND); - Svc.Commands.RemoveHandler(Service.ALTCOMMAND); - } - - private static void OnCommand(string command, string arguments) - { - DoOneCommand(arguments); - } - - private static void DoOneCommand(string str) - { - if (str.ToLower() == "cancel") str = "off"; - if (TryGetOneEnum(str, out var stateType)) - { - var intStr = str.Split(' ', StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); - if (!int.TryParse(intStr, out var index)) index = -1; - DoStateCommandType(stateType, index); - } - else if (TryGetOneEnum(str, out var specialType)) - { - DoSpecialCommandType(specialType); + Svc.Commands.AddHandler(Service.COMMAND, new CommandInfo(OnCommand) + { + HelpMessage = UiString.Commands_Rotation.GetDescription(), + ShowInHelp = true, + }); + Svc.Commands.AddHandler(Service.ALTCOMMAND, new CommandInfo(OnCommand) + { + HelpMessage = UiString.Commands_Rotation.GetDescription(), + ShowInHelp = true, + }); } - else if (TryGetOneEnum(str, out var otherType)) + + internal static void Disable() { - DoOtherCommand(otherType, str[otherType.ToString().Length..].Trim()); + Svc.Commands.RemoveHandler(Service.COMMAND); + Svc.Commands.RemoveHandler(Service.ALTCOMMAND); } - else + + private static void OnCommand(string command, string arguments) { - RotationSolverPlugin.OpenConfigWindow(); + DoOneCommand(arguments); } - } - private static bool TryGetOneEnum(string str, out T type) where T : struct, Enum - { - type = default; - try + private static void DoOneCommand(string command) { - type = Enum.GetValues().First(c => str.StartsWith(c.ToString(), StringComparison.OrdinalIgnoreCase)); - return true; + if (command.Equals("cancel", StringComparison.OrdinalIgnoreCase)) + { + command = "off"; + } + + if (TryGetOneEnum(command, out var stateType)) + { + var indexStr = command.Split(' ', StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); + if (!int.TryParse(indexStr, out var index)) + { + index = -1; + } + DoStateCommandType(stateType, index); + } + else if (TryGetOneEnum(command, out var specialType)) + { + DoSpecialCommandType(specialType); + } + else if (TryGetOneEnum(command, out var otherType)) + { + var extraCommand = command.Substring(otherType.ToString().Length).Trim(); + DoOtherCommand(otherType, extraCommand); + } + else + { + RotationSolverPlugin.OpenConfigWindow(); + } } - catch + + private static bool TryGetOneEnum(string command, out T type) where T : struct, Enum { - return false; + type = default; + try + { + type = Enum.GetValues().First(c => command.StartsWith(c.ToString(), StringComparison.OrdinalIgnoreCase)); + return true; + } + catch (InvalidOperationException) + { + return false; + } } - } - internal static string GetCommandStr(this Enum command, string extraCommand = "") - { - var cmdStr = Service.COMMAND + " " + command.ToString(); - if (!string.IsNullOrEmpty(extraCommand)) + internal static string GetCommandStr(this Enum command, string extraCommand = "") { - cmdStr += " " + extraCommand; + var cmdStr = $"{Service.COMMAND} {command}"; + if (!string.IsNullOrEmpty(extraCommand)) + { + cmdStr += $" {extraCommand}"; + } + return cmdStr; } - return cmdStr; } -} +} \ No newline at end of file diff --git a/RotationSolver/Commands/RSCommands_OtherCommand.cs b/RotationSolver/Commands/RSCommands_OtherCommand.cs index ab2726848..e5caab6b5 100644 --- a/RotationSolver/Commands/RSCommands_OtherCommand.cs +++ b/RotationSolver/Commands/RSCommands_OtherCommand.cs @@ -1,8 +1,8 @@ using ECommons.DalamudServices; using RotationSolver.Basic.Configuration; using RotationSolver.Data; - using RotationSolver.Updaters; +using System.Reflection; namespace RotationSolver.Commands; @@ -13,10 +13,7 @@ private static void DoOtherCommand(OtherCommandType otherType, string str) switch (otherType) { case OtherCommandType.Rotations: - var customCombo = DataCenter.RightNowRotation; - if (customCombo == null) return; - - DoRotationCommand(customCombo, str); + ExecuteRotationCommand(str); break; case OtherCommandType.DoActions: @@ -37,6 +34,14 @@ private static void DoOtherCommand(OtherCommandType otherType, string str) } } + private static void ExecuteRotationCommand(string str) + { + var customCombo = DataCenter.RightNowRotation; + if (customCombo == null) return; + + DoRotationCommand(customCombo, str); + } + private static void DoSettingCommand(string str) { var strs = str.Split(' ', 3); @@ -57,65 +62,22 @@ private static void DoSettingCommand(string str) if (settingName.Equals("TargetingTypes", StringComparison.OrdinalIgnoreCase)) { - HandleTargetingTypesCommand(settingName, command); + HandleTargetingTypesCommand(command); return; } + UpdateSetting(settingName, command); + } + + private static void UpdateSetting(string settingName, string? command) + { foreach (var property in typeof(Configs).GetRuntimeProperties().Where(p => p.GetMethod?.IsPublic ?? false)) { if (!settingName.Equals(property.Name, StringComparison.OrdinalIgnoreCase)) continue; - var type = property.PropertyType; - if (type == typeof(ConditionBoolean)) - type = typeof(bool); - - object? convertedValue = null; - bool valueParsedSuccessfully = true; - - if (type.IsEnum) - { - valueParsedSuccessfully = Enum.TryParse(type, command, ignoreCase: true, out var parsedEnum); - if (valueParsedSuccessfully) - { - convertedValue = parsedEnum; - } - } - else - { - try - { - convertedValue = Convert.ChangeType(command, type); - } - catch - { - valueParsedSuccessfully = false; - } - } - - if (!valueParsedSuccessfully) - { - if (type == typeof(bool)) - { - var config = property.GetValue(Service.Config) as ConditionBoolean; - if (config != null) - { - config.Value = !config.Value; - convertedValue = config.Value; - } - } - else if (type.IsEnum) - { - // If invalid enum value provided - increment to the next enum value - var currentEnumValue = property.GetValue(Service.Config) as Enum; - if (currentEnumValue != null) - { - convertedValue = GetNextEnumValue(currentEnumValue); - } - } - } - - if (convertedValue == null) + var type = property.PropertyType == typeof(ConditionBoolean) ? typeof(bool) : property.PropertyType; + if (!TryConvertValue(type, command, out var convertedValue)) { Svc.Chat.PrintError("Failed to parse the value."); return; @@ -133,7 +95,7 @@ private static void DoSettingCommand(string str) if (Service.Config.ShowToggledActionInChat) { - Svc.Chat.Print(string.Format(UiString.CommandsChangeSettingsValue.GetDescription(), property.Name, command)); + Svc.Chat.Print($"Changed setting {property.Name} to {command}"); } return; @@ -142,7 +104,26 @@ private static void DoSettingCommand(string str) Svc.Chat.PrintError("Failed to find the config in this rotation, please check it."); } - private static void HandleTargetingTypesCommand(string settingName, string? command) + private static bool TryConvertValue(Type type, string? command, out object? convertedValue) + { + convertedValue = null; + if (type.IsEnum) + { + return Enum.TryParse(type, command, ignoreCase: true, out convertedValue); + } + + try + { + convertedValue = Convert.ChangeType(command, type); + return true; + } + catch + { + return false; + } + } + + private static void HandleTargetingTypesCommand(string? command) { if (string.IsNullOrEmpty(command)) { @@ -163,41 +144,11 @@ private static void HandleTargetingTypesCommand(string settingName, string? comm switch (action.ToLower()) { case "add": - if (string.IsNullOrEmpty(value) || !Enum.TryParse(typeof(TargetingType), value, true, out var parsedEnumAdd)) - { - Svc.Chat.PrintError("Invalid TargetingType value."); - return; - } - - var targetingTypeAdd = (TargetingType)parsedEnumAdd; - if (!Service.Config.TargetingTypes.Contains(targetingTypeAdd)) - { - Service.Config.TargetingTypes.Add(targetingTypeAdd); - Svc.Chat.Print($"Added {targetingTypeAdd} to TargetingTypes."); - } - else - { - Svc.Chat.Print($"{targetingTypeAdd} is already in TargetingTypes."); - } + AddTargetingType(value); break; case "remove": - if (string.IsNullOrEmpty(value) || !Enum.TryParse(typeof(TargetingType), value, true, out var parsedEnumRemove)) - { - Svc.Chat.PrintError("Invalid TargetingType value."); - return; - } - - var targetingTypeRemove = (TargetingType)parsedEnumRemove; - if (Service.Config.TargetingTypes.Contains(targetingTypeRemove)) - { - Service.Config.TargetingTypes.Remove(targetingTypeRemove); - Svc.Chat.Print($"Removed {targetingTypeRemove} from TargetingTypes."); - } - else - { - Svc.Chat.Print($"{targetingTypeRemove} is not in TargetingTypes."); - } + RemoveTargetingType(value); break; case "removeall": @@ -213,6 +164,46 @@ private static void HandleTargetingTypesCommand(string settingName, string? comm Service.Config.Save(); } + private static void AddTargetingType(string? value) + { + if (string.IsNullOrEmpty(value) || !Enum.TryParse(typeof(TargetingType), value, true, out var parsedEnumAdd)) + { + Svc.Chat.PrintError("Invalid TargetingType value."); + return; + } + + var targetingTypeAdd = (TargetingType)parsedEnumAdd; + if (!Service.Config.TargetingTypes.Contains(targetingTypeAdd)) + { + Service.Config.TargetingTypes.Add(targetingTypeAdd); + Svc.Chat.Print($"Added {targetingTypeAdd} to TargetingTypes."); + } + else + { + Svc.Chat.Print($"{targetingTypeAdd} is already in TargetingTypes."); + } + } + + private static void RemoveTargetingType(string? value) + { + if (string.IsNullOrEmpty(value) || !Enum.TryParse(typeof(TargetingType), value, true, out var parsedEnumRemove)) + { + Svc.Chat.PrintError("Invalid TargetingType value."); + return; + } + + var targetingTypeRemove = (TargetingType)parsedEnumRemove; + if (Service.Config.TargetingTypes.Contains(targetingTypeRemove)) + { + Service.Config.TargetingTypes.Remove(targetingTypeRemove); + Svc.Chat.Print($"Removed {targetingTypeRemove} from TargetingTypes."); + } + else + { + Svc.Chat.Print($"{targetingTypeRemove} is not in TargetingTypes."); + } + } + private static Enum GetNextEnumValue(Enum currentEnumValue) { var enumValues = Enum.GetValues(currentEnumValue.GetType()).Cast().ToArray(); @@ -228,7 +219,6 @@ private static void ToggleActionCommand(string str) if (str.StartsWith(act.Name)) { var flag = str[act.Name.Length..].Trim(); - act.IsEnabled = bool.TryParse(flag, out var parse) ? parse : !act.IsEnabled; if (Service.Config.ShowToggledActionInChat) @@ -263,7 +253,7 @@ private static void DoActionCommand(string str) if (Service.Config.ShowToastsAboutDoAction) { - Svc.Toasts.ShowQuest(string.Format(UiString.CommandsInsertAction.GetDescription(), time), + Svc.Toasts.ShowQuest($"Inserted action {iAct.Name} with time {time}", new Dalamud.Game.Gui.Toast.QuestToastOptions() { IconId = iAct.IconID, @@ -278,7 +268,6 @@ private static void DoActionCommand(string str) Svc.Chat.PrintError(UiString.CommandsInsertActionFailure.GetDescription()); } - private static void DoRotationCommand(ICustomRotation customCombo, string str) { var configs = customCombo.Configs; @@ -288,9 +277,7 @@ private static void DoRotationCommand(ICustomRotation customCombo, string str) { if (Service.Config.ShowToggledActionInChat) { - Svc.Chat.Print(string.Format(UiString.CommandsChangeSettingsValue.GetDescription(), - config.DisplayName, config.Value)); - + Svc.Chat.Print($"Changed setting {config.DisplayName} to {config.Value}"); return; } } diff --git a/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs b/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs index 2a5affcd0..0a657ba95 100644 --- a/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs +++ b/RotationSolver/Commands/RSCommands_StateSpecialCommand.cs @@ -10,13 +10,13 @@ public static partial class RSCommands { public static string _stateString = "Off", _specialString = string.Empty; - internal static string EntryString => _stateString + (DataCenter.SpecialTimeLeft < 0 ? string.Empty : $" - {_specialString}: {DataCenter.SpecialTimeLeft:F2}s"); + internal static string EntryString => $"{_stateString}{(DataCenter.SpecialTimeLeft < 0 ? string.Empty : $" - {_specialString}: {DataCenter.SpecialTimeLeft:F2}s")}"; private static void UpdateToast() { if (!Service.Config.ShowInfoOnToast) return; - Svc.Toasts.ShowQuest(" " + EntryString, new Dalamud.Game.Gui.Toast.QuestToastOptions() + Svc.Toasts.ShowQuest($" {EntryString}", new Dalamud.Game.Gui.Toast.QuestToastOptions { IconId = 101, }); @@ -26,32 +26,45 @@ public static unsafe void DoStateCommandType(StateCommandType stateType, int ind { if (DataCenter.State) { - if (DataCenter.IsManual && stateType == StateCommandType.Manual - && Service.Config.ToggleManual) + stateType = AdjustStateType(stateType, ref index); + } + + UpdateState(stateType, role); + return stateType; + }); + + private static StateCommandType AdjustStateType(StateCommandType stateType, ref int index) + { + if (DataCenter.IsManual && stateType == StateCommandType.Manual && Service.Config.ToggleManual) + { + return StateCommandType.Off; + } + else if (stateType == StateCommandType.Auto) + { + if (Service.Config.ToggleAuto) { - stateType = StateCommandType.Off; + return StateCommandType.Off; } - else if (stateType == StateCommandType.Auto) + else { - if (Service.Config.ToggleAuto) - { - stateType = StateCommandType.Off; - } - else - { - if (index == -1) - { - // Increment the TargetingIndex to cycle through the TargetingTypes - index = Service.Config.TargetingIndex + 1; - } - // Ensure the index wraps around if it exceeds the number of TargetingTypes - index %= Service.Config.TargetingTypes.Count; - // Update the TargetingIndex in the configuration - Service.Config.TargetingIndex = index; - } + UpdateTargetingIndex(ref index); } } + return stateType; + } + + private static void UpdateTargetingIndex(ref int index) + { + if (index == -1) + { + index = Service.Config.TargetingIndex + 1; + } + index %= Service.Config.TargetingTypes.Count; + Service.Config.TargetingIndex = index; + } + private static void UpdateState(StateCommandType stateType, JobRole role) + { switch (stateType) { case StateCommandType.Off: @@ -75,8 +88,7 @@ public static unsafe void DoStateCommandType(StateCommandType stateType, int ind _stateString = stateType.ToStateString(role); UpdateToast(); - return stateType; - }); + } private static void DoSpecialCommandType(SpecialCommandType specialType, bool sayout = true) => DoOneCommandType((type, role) => type.ToSpecialString(role), role => { @@ -89,15 +101,13 @@ private static void DoSpecialCommandType(SpecialCommandType specialType, bool sa private static void DoOneCommandType(Func sayout, Func doingSomething) where T : struct, Enum { - //Get job role. var role = Player.Object?.ClassJob.Value.GetJobRole() ?? JobRole.None; if (role == JobRole.None) return; T type = doingSomething(role); - //Saying out. if (Service.Config.SayOutStateChanged) SpeechHelper.Speak(sayout(type, role)); } } -} +} \ No newline at end of file diff --git a/RotationSolver/TextureItems/StatusTexture.cs b/RotationSolver/TextureItems/StatusTexture.cs index 2a63f9aec..21dd4a8ca 100644 --- a/RotationSolver/TextureItems/StatusTexture.cs +++ b/RotationSolver/TextureItems/StatusTexture.cs @@ -11,17 +11,44 @@ public StatusTexture(Status status) _status = status; } + /// + /// Gets the icon ID associated with the texture. + /// public uint IconID => _status.Icon; + + /// + /// Gets the ID of the status. + /// public StatusID ID => (StatusID)_status.RowId; + + /// + /// Gets the name of the status. + /// public string Name => $"{_status.Name} ({_status.RowId})"; + + /// + /// Gets the description of the status. + /// public string Description => _status.Description.ExtractText() ?? string.Empty; + + /// + /// Gets or sets a value indicating whether the texture is enabled. + /// public bool IsEnabled { get; set; } = true; + /// + /// Initializes a new instance of the class with the specified status ID. + /// + /// The ID of the status. public StatusTexture(StatusID id) : this((uint)id) { } + /// + /// Initializes a new instance of the class with the specified status ID. + /// + /// The ID of the status. public StatusTexture(uint id) : this(Service.GetSheet().GetRow(id)) { diff --git a/RotationSolver/UI/CollapsingHeaderGroup.cs b/RotationSolver/UI/CollapsingHeaderGroup.cs index fa49ba8bf..2a694ebbf 100644 --- a/RotationSolver/UI/CollapsingHeaderGroup.cs +++ b/RotationSolver/UI/CollapsingHeaderGroup.cs @@ -5,7 +5,7 @@ namespace RotationSolver.UI; internal class CollapsingHeaderGroup { - private readonly Dictionary, Action> _headers = new Dictionary, Action>(); + private readonly Dictionary, Action> _headers; private int _openedIndex = -1; public float HeaderSize { get; set; } = 24; @@ -17,14 +17,14 @@ public CollapsingHeaderGroup(Dictionary, Action> headers) public void AddCollapsingHeader(Func name, Action action) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (action == null) throw new ArgumentNullException(nameof(action)); - _headers.Add(name, action); + if (name is null) throw new ArgumentNullException(nameof(name)); + if (action is null) throw new ArgumentNullException(nameof(action)); + _headers[name] = action; } public void RemoveCollapsingHeader(Func name) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name is null) throw new ArgumentNullException(nameof(name)); _headers.Remove(name); } @@ -40,8 +40,7 @@ public void Draw() { index++; - if (header.Key == null) continue; - if (header.Value == null) continue; + if (header.Key is null || header.Value is null) continue; var name = header.Key(); if (string.IsNullOrEmpty(name)) continue; @@ -72,8 +71,8 @@ public void Draw() } catch (Exception ex) { - Svc.Log.Warning(ex, "An error occurred while drawing the header."); + Svc.Log.Warning($"An error occurred while drawing the header '{name}': {ex}"); } } } -} +} \ No newline at end of file diff --git a/RotationSolver/UI/CooldownWindow.cs b/RotationSolver/UI/CooldownWindow.cs index 3a52ff2dc..845135495 100644 --- a/RotationSolver/UI/CooldownWindow.cs +++ b/RotationSolver/UI/CooldownWindow.cs @@ -35,7 +35,15 @@ public override void Draw() uint itemIndex = 0; foreach (var item in showItems) { - ControlWindow.DrawIAction(item, width, 1f); + try + { + ControlWindow.DrawIAction(item, width, 1f); + } + catch (Exception ex) + { + // Log the exception or handle it as needed + Console.WriteLine($"Error drawing action: {ex.Message}"); + } itemIndex++; if (itemIndex % count != 0) { diff --git a/RotationSolver/UI/FontManager.cs b/RotationSolver/UI/FontManager.cs index 223926139..e8fc30dea 100644 --- a/RotationSolver/UI/FontManager.cs +++ b/RotationSolver/UI/FontManager.cs @@ -17,20 +17,18 @@ public unsafe static ImFontPtr GetFont(float size) try { // Lock the handle to get the font - using (var lockedHandle = handle.Lock()) - { - var font = lockedHandle.ImFont; - - // Check if the font pointer is valid - if (font.NativePtr == null) - { - return ImGui.GetFont(); - } + using var lockedHandle = handle.Lock(); + var font = lockedHandle.ImFont; - // Scale the font to the desired size - font.Scale = size / font.FontSize; - return font; + // Check if the font pointer is valid + if (font.NativePtr == null) + { + return ImGui.GetFont(); } + + // Scale the font to the desired size + font.Scale = size / font.FontSize; + return font; } catch (Exception) { diff --git a/RotationSolver/UI/ImGuiHelper.cs b/RotationSolver/UI/ImGuiHelper.cs index 70e9d4c96..b6b4d0cea 100644 --- a/RotationSolver/UI/ImGuiHelper.cs +++ b/RotationSolver/UI/ImGuiHelper.cs @@ -9,6 +9,9 @@ using RotationSolver.Basic.Configuration; using RotationSolver.Commands; using RotationSolver.Data; +using System; +using System.Collections.Generic; +using System.Numerics; namespace RotationSolver.UI; @@ -65,34 +68,16 @@ public static void DisplayMacro(this MacroInfo info) ImGui.SetNextItemWidth(50); // Display a draggable integer input for the macro index - if (ImGui.DragInt($"{UiString.ConfigWindow_Events_MacroIndex.GetDescription()}##MacroIndex{info.GetHashCode()}", - ref info.MacroIndex, 1, -1, 99)) + if (ImGui.DragInt($"{UiString.ConfigWindow_Events_MacroIndex.GetDescription()}##MacroIndex{info.GetHashCode()}", ref info.MacroIndex, 1, -1, 99)) { - // Save the configuration if the value changes - try - { - Service.Config.Save(); - } - catch (Exception ex) - { - Svc.Log.Warning(ex, "Failed to save configuration."); - } + SaveConfig(); } // Display a checkbox for the shared macro option ImGui.SameLine(); - if (ImGui.Checkbox($"{UiString.ConfigWindow_Events_ShareMacro.GetDescription()}##ShareMacro{info.GetHashCode()}", - ref info.IsShared)) + if (ImGui.Checkbox($"{UiString.ConfigWindow_Events_ShareMacro.GetDescription()}##ShareMacro{info.GetHashCode()}", ref info.IsShared)) { - // Save the configuration if the value changes - try - { - Service.Config.Save(); - } - catch (Exception ex) - { - Svc.Log.Warning(ex, "Failed to save configuration."); - } + SaveConfig(); } } @@ -102,7 +87,7 @@ public static void DisplayEvent(this ActionEventInfo info) if (ImGui.InputText($"{UiString.ConfigWindow_Events_ActionName.GetDescription()}##ActionName{info.GetHashCode()}", ref name, 100)) { info.Name = name; - Service.Config.Save(); + SaveConfig(); } info.DisplayMacro(); @@ -131,16 +116,21 @@ public static void SearchCombo(string popId, string name, ref string searchTx var searchingKey = searchTxt; - var members = items.Select(m => (m, getSearchName(m))) - .OrderByDescending(s => SearchableCollection.Similarity(s.Item2, searchingKey)); + var members = new List<(T, string)>(); + foreach (var item in items) + { + members.Add((item, getSearchName(item))); + } - ImGui.SetNextItemWidth(Math.Max(50 * ImGuiHelpers.GlobalScale, members.Max(i => ImGuiHelpers.GetButtonSize(i.Item2).X))); + members.Sort((x, y) => SearchableCollection.Similarity(y.Item2, searchingKey).CompareTo(SearchableCollection.Similarity(x.Item2, searchingKey))); + + ImGui.SetNextItemWidth(Math.Max(50 * ImGuiHelpers.GlobalScale, GetMaxButtonSize(members))); ImGui.InputTextWithHint("##Searching the member", searchingHint, ref searchTxt, 128); ImGui.Spacing(); ImRaii.IEndObject? child = null; - if (members.Count() >= 15) + if (members.Count >= 15) { ImGui.SetNextWindowSizeConstraints(new Vector2(0, 300), new Vector2(500, 300)); child = ImRaii.Child(popId); @@ -151,13 +141,27 @@ public static void SearchCombo(string popId, string name, ref string searchTx { if (ImGui.Selectable(member.Item2)) { - selectAction?.Invoke(member.m); + selectAction?.Invoke(member.Item1); ImGui.CloseCurrentPopup(); } } child?.Dispose(); } + private static float GetMaxButtonSize(List<(T, string)> members) + { + float maxSize = 0; + foreach (var member in members) + { + var size = ImGuiHelpers.GetButtonSize(member.Item2).X; + if (size > maxSize) + { + maxSize = size; + } + } + return maxSize; + } + public static unsafe bool SelectableCombo(string popUp, string[] items, ref int index, ImFontPtr? font = null, Vector4? color = null) { var count = items.Length; @@ -315,10 +319,10 @@ internal static void TextShade(Vector2 pos, string text, float width = 1.5f) { var offsets = new Vector2[] { - new Vector2(0, -width), - new Vector2(0, width), - new Vector2(-width, 0), - new Vector2(width, 0) + new Vector2(0, -width), + new Vector2(0, width), + new Vector2(-width, 0), + new Vector2(width, 0) }; var drawList = ImGui.GetWindowDrawList(); @@ -339,11 +343,8 @@ internal static void DrawActionOverlay(Vector2 cursor, float width, float percen { ImGui.SetCursorPos(cursor - new Vector2(pixPerUnit * 3, pixPerUnit * 4)); - //var step = new Vector2(88f / cover.Width, 96f / cover.Height); var start = new Vector2((96f * 0 + 4f) / cover.Width, (96f * 2) / cover.Height); - //Out Size is 88, 96 - //Inner Size is 82, 82 ImGui.Image(cover.ImGuiHandle, new Vector2(pixPerUnit * 88, pixPerUnit * 94), start, start + new Vector2(88f / cover.Width, 94f / cover.Height)); } @@ -356,12 +357,9 @@ internal static void DrawActionOverlay(Vector2 cursor, float width, float percen var P = (int)(percent * 81); - var step = new Vector2(88f / cover.Width, 96f / cover.Height); var start = new Vector2(P % 9 * step.X, P / 9 * step.Y); - //Out Size is 88, 96 - //Inner Size is 82, 82 ImGui.Image(cover.ImGuiHandle, new Vector2(pixPerUnit * 88, pixPerUnit * 94), start, start + new Vector2(88f / cover.Width, 94f / cover.Height)); } @@ -370,11 +368,8 @@ internal static void DrawActionOverlay(Vector2 cursor, float width, float percen { if (IconSet.GetTexture("ui/uld/icona_frame_hr1.tex", out var cover)) { - ImGui.SetCursorPos(cursor - new Vector2(pixPerUnit * 3, pixPerUnit * 4)); - //Out Size is 88, 96 - //Inner Size is 82, 82 ImGui.Image(cover.ImGuiHandle, new Vector2(pixPerUnit * 88, pixPerUnit * 94), new Vector2(4f / cover.Width, 0f / cover.Height), new Vector2(92f / cover.Width, 94f / cover.Height)); @@ -392,8 +387,6 @@ internal static void DrawActionOverlay(Vector2 cursor, float width, float percen var step = new Vector2(88f / cover.Width, 96f / cover.Height); var start = new Vector2((P % 9 + 9) * step.X, P / 9 * step.Y); - //Out Size is 88, 96 - //Inner Size is 82, 82 ImGui.Image(cover.ImGuiHandle, new Vector2(pixPerUnit * 88, pixPerUnit * 94), start, start + new Vector2(88f / cover.Width, 94f / cover.Height)); } @@ -480,7 +473,7 @@ private static void CopyCommand(string command) Notify.Success($"\"{command}\" copied to clipboard."); } - private static readonly SortedList _lastChecked = []; + private static readonly Dictionary _lastChecked = new(); private static void ExecuteHotKeys(Action action, params VirtualKey[] keys) { if (action == null) return; @@ -489,7 +482,15 @@ private static void ExecuteHotKeys(Action action, params VirtualKey[] keys) var name = string.Join(' ', keys); if (!_lastChecked.TryGetValue(name, out var last)) last = false; - var now = keys.All(k => Svc.KeyState[k]); + var now = true; + foreach (var key in keys) + { + if (!Svc.KeyState[key]) + { + now = false; + break; + } + } _lastChecked[name] = now; if (!last && now) action(); @@ -526,7 +527,7 @@ public static bool IsInRect(Vector2 leftTop, Vector2 size) ConfigUnitType.Degree => " °", ConfigUnitType.Pixels => " p", ConfigUnitType.Yalms => " y", - ConfigUnitType.Percent => " %%", + ConfigUnitType.Percent => " %", _ => string.Empty, }; @@ -551,4 +552,16 @@ public static void Draw(this CombatType type) ImGui.TextColored(ImGuiColors.DalamudRed, " None of PvE or PvP!"); } } + + private static void SaveConfig() + { + try + { + Service.Config.Save(); + } + catch (Exception ex) + { + Svc.Log.Warning(ex, "Failed to save configuration."); + } + } } \ No newline at end of file diff --git a/RotationSolver/UI/ImguiTooltips.cs b/RotationSolver/UI/ImguiTooltips.cs index 648f801bd..2c395aaa5 100644 --- a/RotationSolver/UI/ImguiTooltips.cs +++ b/RotationSolver/UI/ImguiTooltips.cs @@ -6,7 +6,7 @@ namespace RotationSolver.UI; internal static class ImguiTooltips { - const ImGuiWindowFlags TOOLTIP_FLAG = + private const ImGuiWindowFlags TooltipFlag = ImGuiWindowFlags.Tooltip | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings | @@ -15,7 +15,7 @@ internal static class ImguiTooltips ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.AlwaysAutoResize; - const string TOOLTIP_ID = "RotationSolverReborn Tooltips"; + private const string TooltipId = "RotationSolverReborn Tooltips"; /// /// Displays a tooltip when the item is hovered. @@ -45,9 +45,9 @@ public static void ShowTooltip(string? text) /// Displays a tooltip with the specified action. /// /// The action to perform to render the tooltip content. - public static void ShowTooltip(Action act) + public static void ShowTooltip(Action? act) { - if (act == null || Service.Config == null || !Service.Config.ShowTooltips) return; + if (act == null || Service.Config.ShowTooltips != true) return; ImGui.SetNextWindowBgAlpha(1); @@ -55,9 +55,9 @@ public static void ShowTooltip(Action act) var globalScale = ImGuiHelpers.GlobalScale; ImGui.SetNextWindowSizeConstraints(new Vector2(150, 0) * globalScale, new Vector2(1200, 1500) * globalScale); - ImGui.SetWindowPos(TOOLTIP_ID, ImGui.GetIO().MousePos); + ImGui.SetWindowPos(TooltipId, ImGui.GetIO().MousePos); - if (ImGui.Begin(TOOLTIP_ID, TOOLTIP_FLAG)) + if (ImGui.Begin(TooltipId, TooltipFlag)) { act(); ImGui.End(); diff --git a/RotationSolver/UI/OverlayWindow.cs b/RotationSolver/UI/OverlayWindow.cs index 97310e743..70f893d24 100644 --- a/RotationSolver/UI/OverlayWindow.cs +++ b/RotationSolver/UI/OverlayWindow.cs @@ -2,6 +2,8 @@ using Dalamud.Interface.Windowing; using ECommons.DalamudServices; using RotationSolver.UI.HighlightTeachingMode; +using System.Linq; +using System.Threading.Tasks; namespace RotationSolver.UI; @@ -35,7 +37,7 @@ public override void PreDraw() base.PreDraw(); } - public override void Draw() + public override async void Draw() { if (!HotbarHighlightManager.Enable || Svc.ClientState == null || Svc.ClientState.LocalPlayer == null) return; @@ -44,24 +46,11 @@ public override void Draw() try { - if (!HotbarHighlightManager.UseTaskToAccelerate) - { - HotbarHighlightManager._drawingElements2D = HotbarHighlightManager.To2DAsync().Result; - } + await UpdateDrawingElementsAsync(); if (HotbarHighlightManager._drawingElements2D != null) { - foreach (var item in HotbarHighlightManager._drawingElements2D.OrderBy(drawing => - { - if (drawing is PolylineDrawing poly) - { - return poly._thickness == 0 ? 0 : 1; - } - else - { - return 2; - } - })) + foreach (var item in HotbarHighlightManager._drawingElements2D.OrderBy(GetDrawingOrder)) { item.Draw(); } @@ -73,6 +62,26 @@ public override void Draw() } } + private async Task UpdateDrawingElementsAsync() + { + if (!HotbarHighlightManager.UseTaskToAccelerate) + { + HotbarHighlightManager._drawingElements2D = await HotbarHighlightManager.To2DAsync(); + } + } + + private int GetDrawingOrder(object drawing) + { + if (drawing is PolylineDrawing poly) + { + return poly._thickness == 0 ? 0 : 1; + } + else + { + return 2; + } + } + public override void PostDraw() { ImGui.PopStyleVar(); diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 1b484c72c..07a83c21a 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -2829,8 +2829,8 @@ private static unsafe void DrawStatus() private static unsafe void DrawParty() { - ImGui.Text($"Party: {DataCenter.PartyMembers.Count()}"); - ImGui.Text($"Alliance: {DataCenter.AllianceMembers.Count()}"); + ImGui.Text($"Party: {DataCenter.PartyMembers.Count}"); + ImGui.Text($"Alliance: {DataCenter.AllianceMembers.Count}"); ImGui.Text($"PartyMembersAverHP: {DataCenter.PartyMembersAverHP}"); diff --git a/RotationSolver/UI/SearchableCollection.cs b/RotationSolver/UI/SearchableCollection.cs index 0f3d571dd..fe7d959a6 100644 --- a/RotationSolver/UI/SearchableCollection.cs +++ b/RotationSolver/UI/SearchableCollection.cs @@ -15,43 +15,32 @@ internal class SearchableCollection public SearchableCollection() { - // Retrieve properties from the Configs class var properties = typeof(Configs).GetRuntimeProperties().ToArray(); var pairs = new List(properties.Length); var parents = new Dictionary(properties.Length); - - // Cache attributes to avoid repeated reflection calls var attributes = new ConcurrentDictionary(); - // Iterate over each property foreach (var property in properties) { - // Get the UIAttribute for the property var ui = property.GetCustomAttribute(); if (ui == null) continue; - // Create an ISearchable instance for the property var item = CreateSearchable(property); if (item == null) continue; - // Set PvE and PvP filters item.PvEFilter = new(ui.PvEFilter); item.PvPFilter = new(ui.PvPFilter); - // Add the SearchPair to the list pairs.Add(new(ui, item)); - // If the item is a CheckBoxSearch, add it to the parents dictionary if (item is CheckBoxSearch search) { parents[property.Name] = search; } } - // Initialize the _items list _items = new List(pairs.Count); - // Organize items based on parent-child relationships foreach (var pair in pairs) { var parentName = pair.Attribute.Parent; @@ -69,21 +58,28 @@ public SearchableCollection() public void DrawItems(string filter) { bool isFirst = true; + var filteredItems = new Dictionary>(); - // Filter and group items based on the provided filter - var filteredItems = _items.Where(i => i.Attribute.Filter == filter) - .GroupBy(i => i.Attribute.Section); + foreach (var item in _items) + { + if (item.Attribute.Filter == filter) + { + if (!filteredItems.ContainsKey(item.Attribute.Section)) + { + filteredItems[item.Attribute.Section] = new List(); + } + filteredItems[item.Attribute.Section].Add(item); + } + } foreach (var grp in filteredItems) { - // Add a separator between groups if (!isFirst) { ImGui.Separator(); } - // Draw each item in the group, ordered by Attribute.Order - foreach (var item in grp.OrderBy(i => i.Attribute.Order)) + foreach (var item in grp.Value.OrderBy(i => i.Attribute.Order)) { item.Searchable.Draw(); } @@ -99,95 +95,82 @@ public ISearchable[] SearchItems(string searchingText) var results = new HashSet(); var finalResults = new List(MaxResultLength); - foreach (var searchable in _items.Select(i => i.Searchable).SelectMany(GetChildren)) + foreach (var pair in _items) { - var parent = GetParent(searchable); - if (results.Contains(parent)) continue; - - if (Similarity(searchable.SearchingKeys, searchingText) > 0) + foreach (var searchable in GetChildren(pair.Searchable)) { - results.Add(parent); - finalResults.Add(parent); - if (finalResults.Count >= MaxResultLength) break; + var parent = GetParent(searchable); + if (results.Contains(parent)) continue; + + if (Similarity(searchable.SearchingKeys, searchingText) > 0) + { + results.Add(parent); + finalResults.Add(parent); + if (finalResults.Count >= MaxResultLength) break; + } } } return finalResults.ToArray(); } - private static ISearchable? CreateSearchable(PropertyInfo property) + private static ISearchable? CreateSearchable(PropertyInfo property) => property.Name switch { - var type = property.PropertyType; - - // Create an instance of ISearchable based on the property type - return property.Name switch - { - // Special case for AutoHeal property - nameof(Configs.AutoHeal) => new AutoHealCheckBox(property), - - // Handle enum properties - _ when type.IsEnum => new EnumSearch(property), - - // Handle boolean properties without conditions - _ when type == typeof(bool) => new CheckBoxSearchNoCondition(property), - - // Handle ConditionBoolean properties - _ when type == typeof(ConditionBoolean) => new CheckBoxSearchCondition(property), - - // Handle float properties - _ when type == typeof(float) => new DragFloatSearch(property), - - // Handle int properties - _ when type == typeof(int) => new DragIntSearch(property), - - // Handle Vector2 properties - _ when type == typeof(Vector2) => new DragFloatRangeSearch(property), - - // Handle Vector2Int properties - _ when type == typeof(Vector2Int) => new DragIntRangeSearch(property), - - // Handle Vector4 properties - _ when type == typeof(Vector4) => new ColorEditSearch(property), - - // Return null for unsupported property types - _ => null - }; - } + nameof(Configs.AutoHeal) => new AutoHealCheckBox(property), + _ when property.PropertyType.IsEnum => new EnumSearch(property), + _ when property.PropertyType == typeof(bool) => new CheckBoxSearchNoCondition(property), + _ when property.PropertyType == typeof(ConditionBoolean) => new CheckBoxSearchCondition(property), + _ when property.PropertyType == typeof(float) => new DragFloatSearch(property), + _ when property.PropertyType == typeof(int) => new DragIntSearch(property), + _ when property.PropertyType == typeof(Vector2) => new DragFloatRangeSearch(property), + _ when property.PropertyType == typeof(Vector2Int) => new DragIntRangeSearch(property), + _ when property.PropertyType == typeof(Vector4) => new ColorEditSearch(property), + _ => null + }; private static IEnumerable GetChildren(ISearchable searchable) { - // Include the current searchable item yield return searchable; - // If the searchable item is a CheckBoxSearch and has children, recursively get all children if (searchable is CheckBoxSearch c && c.Children != null) { - foreach (var child in c.Children.SelectMany(GetChildren)) + foreach (var child in c.Children) { - yield return child; + foreach (var grandChild in GetChildren(child)) + { + yield return grandChild; + } } } } - private static ISearchable GetParent(ISearchable searchable) - { - // Recursively get the top-most parent - return searchable.Parent == null ? searchable : GetParent(searchable.Parent); - } + private static ISearchable GetParent(ISearchable searchable) => searchable.Parent == null ? searchable : GetParent(searchable.Parent); public static float Similarity(string text, string key) { if (string.IsNullOrEmpty(text)) return 0; - // Split the text and key into words using the specified delimiters var chars = text.Split(_splitChar, StringSplitOptions.RemoveEmptyEntries); var keys = key.Split(_splitChar, StringSplitOptions.RemoveEmptyEntries); - // Count the number of words that start with or contain the key - var startWithCount = chars.Count(i => keys.Any(k => i.StartsWith(k, StringComparison.OrdinalIgnoreCase))); - var containCount = chars.Count(i => keys.Any(k => i.Contains(k, StringComparison.OrdinalIgnoreCase))); + var startWithCount = 0; + var containCount = 0; + + foreach (var c in chars) + { + foreach (var k in keys) + { + if (c.StartsWith(k, StringComparison.OrdinalIgnoreCase)) + { + startWithCount++; + } + else if (c.Contains(k, StringComparison.OrdinalIgnoreCase)) + { + containCount++; + } + } + } - // Calculate the similarity score return startWithCount * 3 + containCount; } } \ No newline at end of file diff --git a/RotationSolver/Updaters/ActionSequencerUpdater.cs b/RotationSolver/Updaters/ActionSequencerUpdater.cs index ade481033..6681a6134 100644 --- a/RotationSolver/Updaters/ActionSequencerUpdater.cs +++ b/RotationSolver/Updaters/ActionSequencerUpdater.cs @@ -17,31 +17,30 @@ public static void UpdateActionSequencerAction() var set = DataCenter.RightSet; if (set == null) return; - DataCenter.DisabledActionSequencer = new HashSet(set.DisableConditionDict - .Where(pair => pair.Value.IsTrue(customRotation)) - .Select(pair => pair.Key)); + var disabledActions = new HashSet(); + foreach (var pair in set.DisableConditionDict) + { + if (pair.Value.IsTrue(customRotation)) + { + disabledActions.Add(pair.Key); + } + } + DataCenter.DisabledActionSequencer = disabledActions; - bool find = false; var conditions = set.ConditionDict; if (conditions != null) { foreach (var conditionPair in conditions) { var nextAct = allActions.FirstOrDefault(a => a.ID == conditionPair.Key); - if (nextAct == null) continue; - - if (!conditionPair.Value.IsTrue(customRotation)) continue; + if (nextAct == null || !conditionPair.Value.IsTrue(customRotation)) continue; DataCenter.ActionSequencerAction = nextAct; - find = true; - break; + return; } } - if (!find) - { - DataCenter.ActionSequencerAction = null; - } + DataCenter.ActionSequencerAction = null; } public static void Enable(string folder) @@ -57,13 +56,15 @@ public static void SaveFiles() if (_actionSequencerFolder == null) return; try { - Directory.Delete(_actionSequencerFolder); + Directory.Delete(_actionSequencerFolder, true); Directory.CreateDirectory(_actionSequencerFolder); } - catch + catch (Exception ex) { - + // Log the exception or handle it as needed + Console.WriteLine($"Error deleting directory: {ex.Message}"); } + foreach (var set in DataCenter.ConditionSets) { set.Save(_actionSequencerFolder); @@ -79,15 +80,39 @@ public static void LoadFiles() public static void AddNew() { - if (!DataCenter.ConditionSets.Any(c => c.IsUnnamed)) + bool hasUnnamed = false; + foreach (var conditionSet in DataCenter.ConditionSets) + { + if (conditionSet.IsUnnamed) + { + hasUnnamed = true; + break; + } + } + + if (!hasUnnamed) { - DataCenter.ConditionSets = [.. DataCenter.ConditionSets, new MajorConditionSet()]; + var newConditionSets = new List(DataCenter.ConditionSets) + { + new MajorConditionSet() + }; + DataCenter.ConditionSets = newConditionSets.ToArray(); } } public static void Delete(string name) { - DataCenter.ConditionSets = DataCenter.ConditionSets.Where(c => c.Name != name).ToArray(); - File.Delete(_actionSequencerFolder + $"\\{name}.json"); + var newConditionSets = new List(); + foreach (var conditionSet in DataCenter.ConditionSets) + { + if (conditionSet.Name != name) + { + newConditionSets.Add(conditionSet); + } + } + DataCenter.ConditionSets = newConditionSets.ToArray(); + + var filePath = Path.Combine(_actionSequencerFolder ?? string.Empty, $"{name}.json"); + File.Delete(filePath); } -} +} \ No newline at end of file diff --git a/RotationSolver/Updaters/ActionUpdater.cs b/RotationSolver/Updaters/ActionUpdater.cs index 22b031cf9..bfa98bb95 100644 --- a/RotationSolver/Updaters/ActionUpdater.cs +++ b/RotationSolver/Updaters/ActionUpdater.cs @@ -36,7 +36,7 @@ internal static IAction? NextAction } private static IBaseAction? _nextGCDAction; - const float gcdHeight = 5; + const float GcdHeight = 5; internal static IBaseAction? NextGCDAction { get => _nextGCDAction; @@ -73,9 +73,7 @@ internal static void UpdateNextAction() } catch (Exception ex) { -#pragma warning disable 0436 - WarningHelper.AddSystemWarning($"Failed to update the next action in the rotation because: {ex.Message}"); - Svc.Log.Error(ex, "Failed to update next action."); + LogError("Failed to update the next action in the rotation", ex); } NextAction = NextGCDAction = null; @@ -98,7 +96,6 @@ private static void SetAction(uint id) internal unsafe static void UpdateActionInfo() { SetAction(NextGCDAction?.AdjustedID ?? 0); - //UpdateWeaponTime(); UpdateCombatTime(); UpdateSlots(); UpdateMoving(); @@ -192,6 +189,20 @@ private static void UpdateMPTimer() } internal unsafe static bool CanDoAction() + { + if (IsPlayerOccupied() || Player.Object.CurrentHp == 0) return false; + + var nextAction = NextAction; + if (nextAction == null) return false; + + // Skip when casting + if (Player.Object.TotalCastTime - DataCenter.ActionAhead > 0) return false; + + // GCD + return RSCommands.CanDoAnAction(ActionHelper.CanUseGCD); + } + + private unsafe static bool IsPlayerOccupied() { if (Svc.Condition[ConditionFlag.OccupiedInQuestEvent] || Svc.Condition[ConditionFlag.OccupiedInCutSceneEvent] @@ -201,25 +212,32 @@ internal unsafe static bool CanDoAction() || Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51] || Svc.Condition[ConditionFlag.Mounted] - //|| Svc.Condition[ConditionFlag.SufferingStatusAffliction] //Because of BLU30! || Svc.Condition[ConditionFlag.SufferingStatusAffliction2] || Svc.Condition[ConditionFlag.RolePlaying] || Svc.Condition[ConditionFlag.InFlight] || Svc.Condition[ConditionFlag.Diving] || Svc.Condition[ConditionFlag.Swimming] || Svc.Condition[ConditionFlag.Unconscious] - || Svc.Condition[ConditionFlag.MeldingMateria] - || (ActionManager.Instance()->ActionQueued && NextAction != null - && ActionManager.Instance()->QueuedActionId != NextAction.AdjustedID) - || Player.Object.CurrentHp == 0) return false; + || Svc.Condition[ConditionFlag.MeldingMateria]) + { + return true; + } - var nextAction = NextAction; - if (nextAction == null) return false; + var actionManager = ActionManager.Instance(); + if (actionManager->ActionQueued && NextAction != null + && actionManager->QueuedActionId != NextAction.AdjustedID) + { + return true; + } - //Skip when casting - if (Player.Object.TotalCastTime - DataCenter.ActionAhead > 0) return false; + return false; + } - //GCD - return RSCommands.CanDoAnAction(ActionHelper.CanUseGCD); + private static void LogError(string message, Exception ex) + { +#pragma warning disable 0436 + + WarningHelper.AddSystemWarning($"{message} because: {ex.Message}"); + Svc.Log.Error(ex, message); } } \ No newline at end of file diff --git a/RotationSolver/Updaters/MajorUpdater.cs b/RotationSolver/Updaters/MajorUpdater.cs index 6b67ec9a4..fbb361cee 100644 --- a/RotationSolver/Updaters/MajorUpdater.cs +++ b/RotationSolver/Updaters/MajorUpdater.cs @@ -1,5 +1,4 @@ using Dalamud.Game.ClientState.Conditions; -using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Plugin.Services; using ECommons.DalamudServices; using ECommons.GameHelpers; @@ -10,7 +9,6 @@ using Lumina.Excel.Sheets; using RotationSolver.Commands; using RotationSolver.Data; - using RotationSolver.UI.HighlightTeachingMode; using System.Runtime.InteropServices; using static FFXIVClientStructs.FFXIV.Client.UI.Misc.RaptureHotbarModule; @@ -42,6 +40,10 @@ private unsafe static void RSRUpdate(IFramework framework) if (!IsValid) { + if (Service.Config.AutoOffBetweenArea) + { + RSCommands.DoStateCommandType(StateCommandType.Off); + } ActionUpdater.ClearNextAction(); CustomRotation.MoveTarget = null; return; @@ -84,7 +86,7 @@ private unsafe static void RSRUpdate(IFramework framework) private static void HandleSystemWarnings() { - if (DataCenter.SystemWarnings.Any()) + if (DataCenter.SystemWarnings.Count > 0) { var warningsToRemove = new List(); @@ -113,17 +115,17 @@ private static async Task HandleWorkUpdateAsync() try { - if (Service.Config.FrameworkStyle == FrameworkStyle.WorkTask) - { - await Task.Run(() => UpdateWork()); - } - else if (Service.Config.FrameworkStyle == FrameworkStyle.RunOnTick) + switch (Service.Config.FrameworkStyle) { - await Svc.Framework.RunOnTick(() => UpdateWork()); - } - else if (Service.Config.FrameworkStyle == FrameworkStyle.MainThread) - { - UpdateWork(); + case FrameworkStyle.WorkTask: + await Task.Run(() => UpdateWork()); + break; + case FrameworkStyle.RunOnTick: + await Svc.Framework.RunOnTick(() => UpdateWork()); + break; + case FrameworkStyle.MainThread: + UpdateWork(); + break; } } catch (Exception tEx) @@ -163,22 +165,7 @@ private static void UpdateWork() RSCommands.UpdateRotationState(); HotbarHighlightManager.UpdateSettings(); - // Collect expired VfxNewData items - var expiredVfx = new List(); - for (int i = 0; i < DataCenter.VfxDataQueue.Count; i++) - { - var vfx = DataCenter.VfxDataQueue[i]; - if (vfx.TimeDuration > TimeSpan.FromSeconds(10)) - { - expiredVfx.Add(vfx); - } - } - - // Remove expired VfxNewData items - foreach (var vfx in expiredVfx) - { - DataCenter.VfxDataQueue.Remove(vfx); - } + RemoveExpiredVfxData(); } catch (Exception ex) { @@ -189,14 +176,32 @@ private static void UpdateWork() } } + private static void RemoveExpiredVfxData() + { + var expiredVfx = new List(); + for (int i = 0; i < DataCenter.VfxDataQueue.Count; i++) + { + var vfx = DataCenter.VfxDataQueue[i]; + if (vfx.TimeDuration > TimeSpan.FromSeconds(10)) + { + expiredVfx.Add(vfx); + } + } + + foreach (var vfx in expiredVfx) + { + DataCenter.VfxDataQueue.Remove(vfx); + } + } + private static void UpdateHighlight() { if (!Service.Config.TeachingMode || ActionUpdater.NextAction is not IAction nextAction) return; HotbarID? hotbar = nextAction switch - { + { IBaseItem item => new HotbarID(HotbarSlotType.Item, item.ID), - IBaseAction baseAction when baseAction.Action.ActionCategory.RowId is 10 or 11 => Svc.Data.GetExcelSheet()?.FirstOrDefault(g => g.Action.RowId == baseAction.ID) is GeneralAction gAct ? new HotbarID(HotbarSlotType.GeneralAction, gAct.RowId) : null, + IBaseAction baseAction when baseAction.Action.ActionCategory.RowId is 10 or 11 => GetGeneralActionHotbarID(baseAction), IBaseAction baseAction => new HotbarID(HotbarSlotType.Action, baseAction.AdjustedID), _ => null }; @@ -207,6 +212,22 @@ private static void UpdateHighlight() } } + private static HotbarID? GetGeneralActionHotbarID(IBaseAction baseAction) + { + var generalActions = Svc.Data.GetExcelSheet(); + if (generalActions == null) return null; + + foreach (var gAct in generalActions) + { + if (gAct.Action.RowId == baseAction.ID) + { + return new HotbarID(HotbarSlotType.GeneralAction, gAct.RowId); + } + } + + return null; + } + private static void ShowWarning() { if (!Svc.PluginInterface.InstalledPlugins.Any(p => p.InternalName == "Avarice")) @@ -264,7 +285,7 @@ private unsafe static void OpenChest() if (dis > 0.5f) return false; var address = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(void*)o.Address; - if ((ObjectKind)address->ObjectKind != ObjectKind.Treasure) return false; + if (address->ObjectKind != FFXIVClientStructs.FFXIV.Client.Game.Object.ObjectKind.Treasure) return false; //Opened! foreach (var item in Loot.Instance()->Items) diff --git a/RotationSolver/Updaters/MovingUpdater.cs b/RotationSolver/Updaters/MovingUpdater.cs index 8273d9480..c1741a157 100644 --- a/RotationSolver/Updaters/MovingUpdater.cs +++ b/RotationSolver/Updaters/MovingUpdater.cs @@ -10,50 +10,66 @@ internal static class MovingUpdater { internal unsafe static void UpdateCanMove(bool doNextAction) { - //Special state. + // Special state. if (Svc.Condition[ConditionFlag.OccupiedInEvent]) { Service.CanMove = true; + return; } - //Casting the action in list. - else if (Svc.Condition[ConditionFlag.Casting] && Player.Available) + + // Casting the action in list. + if (Svc.Condition[ConditionFlag.Casting] && Player.Available) { Service.CanMove = ActionBasicInfo.ActionsNoNeedCasting.Contains(Player.Object.CastActionId); + return; + } + + // Special actions. + var statusList = new List(4); + var actionList = new List(4); + + if (Service.Config.PosFlameThrower) + { + statusList.Add(StatusID.Flamethrower); + actionList.Add(ActionID.FlameThrowerPvE); } - //Special actions. - else + if (Service.Config.PosPassageOfArms) { - var statusList = new List(4); - var actionList = new List(4); + statusList.Add(StatusID.PassageOfArms); + actionList.Add(ActionID.PassageOfArmsPvE); + } + if (Service.Config.PosImprovisation) + { + statusList.Add(StatusID.Improvisation); + actionList.Add(ActionID.ImprovisationPvE); + } - if (Service.Config.PosFlameThrower) - { - statusList.Add(StatusID.Flamethrower); - actionList.Add(ActionID.FlameThrowerPvE); - } - if (Service.Config.PosPassageOfArms) + // Action + var action = DateTime.Now - RSCommands._lastUsedTime < TimeSpan.FromMilliseconds(100) + ? (ActionID)RSCommands._lastActionID + : doNextAction ? (ActionID)(ActionUpdater.NextAction?.AdjustedID ?? 0) : 0; + + bool specialActions = ActionManager.GetAdjustedCastTime(ActionType.Action, (uint)action) > 0; + foreach (var id in actionList) + { + if (Service.GetAdjustedActionId(id) == action) { - statusList.Add(StatusID.PassageOfArms); - actionList.Add(ActionID.PassageOfArmsPvE); + specialActions = true; + break; } - if (Service.Config.PosImprovisation) + } + + // Status + bool specialStatus = false; + foreach (var status in statusList) + { + if (Player.Object.HasStatus(true, status)) { - statusList.Add(StatusID.Improvisation); - actionList.Add(ActionID.ImprovisationPvE); + specialStatus = true; + break; } - - //Action - var action = DateTime.Now - RSCommands._lastUsedTime < TimeSpan.FromMilliseconds(100) - ? (ActionID)RSCommands._lastActionID - : doNextAction ? (ActionID)(ActionUpdater.NextAction?.AdjustedID ?? 0) : 0; - - var specialActions = ActionManager.GetAdjustedCastTime(ActionType.Action, (uint)action) > 0 - || actionList.Any(id => Service.GetAdjustedActionId(id) == action); - - //Status - var specialStatus = Player.Object.HasStatus(true, [.. statusList]); - - Service.CanMove = !specialStatus && !specialActions; } + + Service.CanMove = !specialStatus && !specialActions; } -} +} \ No newline at end of file diff --git a/RotationSolver/Updaters/PreviewUpdater.cs b/RotationSolver/Updaters/PreviewUpdater.cs index a76fbd5c7..572240c78 100644 --- a/RotationSolver/Updaters/PreviewUpdater.cs +++ b/RotationSolver/Updaters/PreviewUpdater.cs @@ -21,41 +21,66 @@ internal static void UpdatePreview() UpdateCancelCast(); } - //public static byte Job => Job; static IDtrBarEntry? _dtrEntry; private static void UpdateEntry() { var showStr = RSCommands.EntryString; - var icon = Player.Job switch + var icon = GetJobIcon(Player.Job); + + if (Service.Config.ShowInfoOnDtr && !string.IsNullOrEmpty(showStr)) + { + try + { + _dtrEntry ??= Svc.DtrBar.Get("Rotation Solver Reborn"); + } + catch + { +#pragma warning disable 0436 + WarningHelper.AddSystemWarning("Unable to add server bar entry"); + return; + } + + if (!_dtrEntry.Shown) _dtrEntry.Shown = true; + + _dtrEntry.Text = new SeString( + new IconPayload(icon), + new TextPayload(showStr) + ); + _dtrEntry.OnClick = RSCommands.IncrementState; + } + else if (_dtrEntry != null && _dtrEntry.Shown) + { + _dtrEntry.Shown = false; + } + } + + private static BitmapFontIcon GetJobIcon(Job job) + { + return job switch { Job.WAR => BitmapFontIcon.Warrior, Job.PLD => BitmapFontIcon.Paladin, Job.DRK => BitmapFontIcon.DarkKnight, Job.GNB => BitmapFontIcon.Gunbreaker, - Job.AST => BitmapFontIcon.Astrologian, Job.WHM => BitmapFontIcon.WhiteMage, Job.SGE => BitmapFontIcon.Sage, Job.SCH => BitmapFontIcon.Scholar, - Job.BLM => BitmapFontIcon.BlackMage, Job.SMN => BitmapFontIcon.Summoner, Job.RDM => BitmapFontIcon.RedMage, Job.PCT => BitmapFontIcon.Pictomancer, Job.BLU => BitmapFontIcon.BlueMage, - Job.MNK => BitmapFontIcon.Monk, Job.SAM => BitmapFontIcon.Samurai, Job.DRG => BitmapFontIcon.Dragoon, Job.RPR => BitmapFontIcon.Reaper, Job.NIN => BitmapFontIcon.Ninja, Job.VPR => BitmapFontIcon.Viper, - Job.BRD => BitmapFontIcon.Bard, Job.MCH => BitmapFontIcon.Machinist, Job.DNC => BitmapFontIcon.Dancer, - Job.BSM => BitmapFontIcon.Blacksmith, Job.ARM => BitmapFontIcon.Armorer, Job.WVR => BitmapFontIcon.Weaver, @@ -64,11 +89,9 @@ private static void UpdateEntry() Job.LTW => BitmapFontIcon.Leatherworker, Job.CUL => BitmapFontIcon.Culinarian, Job.GSM => BitmapFontIcon.Goldsmith, - Job.FSH => BitmapFontIcon.Fisher, Job.MIN => BitmapFontIcon.Miner, Job.BTN => BitmapFontIcon.Botanist, - Job.GLA => BitmapFontIcon.Gladiator, Job.CNJ => BitmapFontIcon.Conjurer, Job.MRD => BitmapFontIcon.Marauder, @@ -78,35 +101,8 @@ private static void UpdateEntry() Job.ARC => BitmapFontIcon.Archer, Job.THM => BitmapFontIcon.Thaumaturge, Job.ACN => BitmapFontIcon.Arcanist, - _ => BitmapFontIcon.ExclamationRectangle, }; - - if (Service.Config.ShowInfoOnDtr && !string.IsNullOrEmpty(showStr)) - { - try - { - _dtrEntry ??= Svc.DtrBar.Get("Rotation Solver Reborn"); - } - catch - { -#pragma warning disable 0436 - WarningHelper.AddSystemWarning($"Unable to add server bar entry"); - return; - } - - if (!_dtrEntry.Shown) _dtrEntry.Shown = true; - - _dtrEntry.Text = new SeString( - new IconPayload(icon), - new TextPayload(showStr) - ); - _dtrEntry.OnClick = RSCommands.IncrementState; - } - else if (_dtrEntry != null && _dtrEntry.Shown) - { - _dtrEntry.Shown = false; - } } static RandomDelay _tarStopCastDelay = new(() => Service.Config.StopCastingDelay); @@ -119,9 +115,9 @@ private static unsafe void UpdateCancelCast() && Svc.Objects.SearchById(Player.Object.CastTargetObjectId) is IBattleChara b && b.IsEnemy() && b.CurrentHp == 0; - var statusTimes = Player.Object.StatusTimes(false, [.. OtherConfiguration.NoCastingStatus.Select(i => (StatusID)i)]); + var statusTimes = GetStatusTimes(); - var stopDueStatus = statusTimes.Any() && statusTimes.Min() > Player.Object.TotalCastTime - Player.Object.CurrentCastTime && statusTimes.Min() < 5; + var stopDueStatus = statusTimes.Length > 0 && statusTimes.Min() > Player.Object.TotalCastTime - Player.Object.CurrentCastTime && statusTimes.Min() < 5; if (_tarStopCastDelay.Delay(tarDead) || stopDueStatus) { @@ -129,6 +125,18 @@ private static unsafe void UpdateCancelCast() } } + private static float[] GetStatusTimes() + { + var statusTimes = new List(); + foreach (var status in Player.Object.StatusList) + { + if (OtherConfiguration.NoCastingStatus.Contains((uint)status.StatusId)) + { + statusTimes.Add(status.RemainingTime); + } + } + return statusTimes.ToArray(); + } internal static unsafe void PulseActionBar(uint actionID) { @@ -138,7 +146,6 @@ internal static unsafe void PulseActionBar(uint actionID) }); } - private unsafe static bool IsActionSlotRight(ActionBarSlot slot, RaptureHotbarModule.HotbarSlot? hot, uint actionID) { if (hot.HasValue) @@ -185,9 +192,8 @@ private static unsafe void LoopAllSlotBar(ActionBarAction doingSomething) } } - public unsafe static void Dispose() { } -} +} \ No newline at end of file diff --git a/RotationSolver/Updaters/StateUpdater.cs b/RotationSolver/Updaters/StateUpdater.cs index ef0d10842..b42acf424 100644 --- a/RotationSolver/Updaters/StateUpdater.cs +++ b/RotationSolver/Updaters/StateUpdater.cs @@ -126,12 +126,15 @@ private static bool ShouldAddDispel() { if (DataCenter.DispelTarget != null) { - if (DataCenter.DispelTarget.StatusList.Any(StatusHelper.IsDangerous)) + foreach (var status in DataCenter.DispelTarget.StatusList) { - return true; + if (StatusHelper.IsDangerous(status)) + { + return true; + } } - else if (!DataCenter.HasHostilesInRange || Service.Config.DispelAll - || DataCenter.IsPvP) + + if (!DataCenter.HasHostilesInRange || Service.Config.DispelAll || DataCenter.IsPvP) { return true; } @@ -146,39 +149,25 @@ private static bool ShouldAddRaise() private static bool ShouldAddPositional() { - // Check if the player's role is Melee and if there is a next GCD (Global Cooldown) action - // Also, check if the configuration allows automatic use of True North - if (DataCenter.Role == JobRole.Melee && ActionUpdater.NextGCDAction != null - && Service.Config.AutoUseTrueNorth) + if (DataCenter.Role == JobRole.Melee && ActionUpdater.NextGCDAction != null && Service.Config.AutoUseTrueNorth) { - // Get the ID of the next GCD action var id = ActionUpdater.NextGCDAction.ID; - // Get the target of the next GCD action var target = ActionUpdater.NextGCDAction.Target.Target; - // Check if the action ID has a positional requirement if (ConfigurationHelper.ActionPositional.TryGetValue((ActionID)id, out var positional) - // Check if the positional requirement is not met by the target's current position - && positional != target?.FindEnemyPositional() - // Check if the target has positional requirements - && target?.HasPositional() == true - // Check if the target does not have a status that turns target into a wallboss - && !target.HasStatus(true, StatusID.DirectionalDisregard)) + && positional != target?.FindEnemyPositional() + && target?.HasPositional() == true + && !target.HasStatus(true, StatusID.DirectionalDisregard)) { - // If all conditions are met, return true to add the Positional flag return true; } } - // If any condition is not met, return false return false; } private static bool ShouldAddDefenseArea() { - if (!DataCenter.InCombat || !Service.Config.UseDefenseAbility) - return false; - - return DataCenter.IsHostileCastingAOE; + return DataCenter.InCombat && Service.Config.UseDefenseAbility && DataCenter.IsHostileCastingAOE; } private static bool ShouldAddDefenseSingle() @@ -188,29 +177,43 @@ private static bool ShouldAddDefenseSingle() if (DataCenter.Role == JobRole.Healer) { - if (DataCenter.PartyMembers.Any((tank) => + foreach (var tank in DataCenter.PartyMembers) { - var attackingTankObj = DataCenter.AllHostileTargets.Where(t => t.TargetObjectId == tank.GameObjectId); - - if (attackingTankObj.Count() != 1) - return false; - - return DataCenter.IsHostileCastingToTank; - })) - { - return true; + int attackingTankCount = 0; + foreach (var hostile in DataCenter.AllHostileTargets) + { + if (hostile.TargetObjectId == tank.GameObjectId) + { + attackingTankCount++; + } + } + + if (attackingTankCount == 1 && DataCenter.IsHostileCastingToTank) + { + return true; + } } } if (DataCenter.Role == JobRole.Tank) { - var movingHere = (float)DataCenter.NumberOfHostilesInRange / DataCenter.NumberOfHostilesInMaxRange > 0.3f; + bool movingHere = (float)DataCenter.NumberOfHostilesInRange / DataCenter.NumberOfHostilesInMaxRange > 0.3f; + + int tarOnMeCount = 0; + int attackedCount = 0; + foreach (var hostile in DataCenter.AllHostileTargets) + { + if (hostile.DistanceToPlayer() <= 3 && hostile.TargetObject == Player.Object) + { + tarOnMeCount++; + if (ObjectHelper.IsAttacked(hostile)) + { + attackedCount++; + } + } + } - var tarOnMe = DataCenter.AllHostileTargets.Where(t => t.DistanceToPlayer() <= 3 - && t.TargetObject == Player.Object); - var tarOnMeCount = tarOnMe.Count(); - var attackedCount = tarOnMe.Count(ObjectHelper.IsAttacked); - var attacked = (float)attackedCount / tarOnMeCount > 0.7f; + bool attacked = (float)attackedCount / tarOnMeCount > 0.7f; if (tarOnMeCount >= Service.Config.AutoDefenseNumber && Player.Object.GetHealthRatio() <= Service.Config.HealthForAutoDefense @@ -233,15 +236,15 @@ private static bool ShouldAddHealAreaAbility() if (!DataCenter.HPNotFull || !CanUseHealAction) return false; - var singleAbility = ShouldHealSingle(StatusHelper.SingleHots, + int singleAbility = ShouldHealSingle(StatusHelper.SingleHots, Service.Config.HealthSingleAbility, Service.Config.HealthSingleAbilityHot); - var canHealAreaAbility = singleAbility > 2; + bool canHealAreaAbility = singleAbility > 2; - if (DataCenter.PartyMembers.Count() > 2) + if (DataCenter.PartyMembers.Count > 2) { - var ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); + float ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); if (!canHealAreaAbility) canHealAreaAbility = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference @@ -256,15 +259,15 @@ private static bool ShouldAddHealAreaSpell() if (!DataCenter.HPNotFull || !CanUseHealAction) return false; - var singleSpell = ShouldHealSingle(StatusHelper.SingleHots, + int singleSpell = ShouldHealSingle(StatusHelper.SingleHots, Service.Config.HealthSingleSpell, Service.Config.HealthSingleSpellHot); - var canHealAreaSpell = singleSpell > 2; + bool canHealAreaSpell = singleSpell > 2; - if (DataCenter.PartyMembers.Count() > 2) + if (DataCenter.PartyMembers.Count > 2) { - var ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); + float ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); if (!canHealAreaSpell) canHealAreaSpell = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference @@ -279,7 +282,7 @@ private static bool ShouldAddHealSingleAbility() if (!DataCenter.HPNotFull || !CanUseHealAction) return false; - var onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer + bool onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer && DataCenter.Role != JobRole.Healer; if (onlyHealSelf) @@ -289,7 +292,7 @@ private static bool ShouldAddHealSingleAbility() } else { - var singleAbility = ShouldHealSingle(StatusHelper.SingleHots, + int singleAbility = ShouldHealSingle(StatusHelper.SingleHots, Service.Config.HealthSingleAbility, Service.Config.HealthSingleAbilityHot); @@ -302,7 +305,7 @@ private static bool ShouldAddHealSingleSpell() if (!DataCenter.HPNotFull || !CanUseHealAction) return false; - var onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer + bool onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer && DataCenter.Role != JobRole.Healer; if (onlyHealSelf) @@ -312,7 +315,7 @@ private static bool ShouldAddHealSingleSpell() } else { - var singleSpell = ShouldHealSingle(StatusHelper.SingleHots, + int singleSpell = ShouldHealSingle(StatusHelper.SingleHots, Service.Config.HealthSingleSpell, Service.Config.HealthSingleSpellHot); @@ -322,10 +325,7 @@ private static bool ShouldAddHealSingleSpell() private static bool ShouldAddAntiKnockback() { - if (!DataCenter.InCombat || !Service.Config.UseKnockback) - return false; - - return DataCenter.AreHostilesCastingKnockback; + return DataCenter.InCombat && Service.Config.UseKnockback && DataCenter.AreHostilesCastingKnockback; } private static bool ShouldAddProvoke() @@ -335,7 +335,7 @@ private static bool ShouldAddProvoke() if (DataCenter.Role == JobRole.Tank && (Service.Config.AutoProvokeForTank - || DataCenter.AllianceMembers.Count(o => o.IsJobCategory(JobRole.Tank)) < 2) + || CountAllianceTanks() < 2) && DataCenter.ProvokeTarget != null) { return true; @@ -346,10 +346,7 @@ private static bool ShouldAddProvoke() private static bool ShouldAddInterrupt() { - if (!DataCenter.InCombat) - return false; - - return DataCenter.InterruptTarget != null && Service.Config.InterruptibleMoreCheck; + return DataCenter.InCombat && DataCenter.InterruptTarget != null && Service.Config.InterruptibleMoreCheck; } private static bool ShouldAddTankStance() @@ -357,8 +354,7 @@ private static bool ShouldAddTankStance() if (!Service.Config.AutoTankStance || DataCenter.Role != JobRole.Tank) return false; - if (!DataCenter.AllianceMembers.Any(t => t.IsJobCategory(JobRole.Tank) && t.CurrentHp != 0 && t.HasStatus(false, StatusHelper.TankStanceStatus)) - && !CustomRotation.HasTankStance) + if (!AnyAllianceTankWithStance() && !CustomRotation.HasTankStance) { return true; } @@ -377,21 +373,31 @@ static float GetHealingOfTimeRatio(IBattleChara target, params StatusID[] status { const float buffWholeTime = 15; - var buffTime = target.StatusTime(false, statusIds); + float buffTime = target.StatusTime(false, statusIds); return Math.Min(1, buffTime / buffWholeTime); } static int ShouldHealSingle(StatusID[] hotStatus, float healSingle, float healSingleHot) - => DataCenter.PartyMembers.Count(p => ShouldHealSingle(p, hotStatus, healSingle, healSingleHot)); + { + int count = 0; + foreach (var member in DataCenter.PartyMembers) + { + if (ShouldHealSingle(member, hotStatus, healSingle, healSingleHot)) + { + count++; + } + } + return count; + } static bool ShouldHealSingle(IBattleChara target, StatusID[] hotStatus, float healSingle, float healSingleHot) { if (target == null) return false; - var ratio = GetHealingOfTimeRatio(target, hotStatus); + float ratio = GetHealingOfTimeRatio(target, hotStatus); - var h = target.GetHealthRatio(); + float h = target.GetHealthRatio(); if (h == 0 || !target.NeedHealing()) return false; return h < Lerp(healSingle, healSingleHot, ratio); @@ -459,4 +465,29 @@ private static void AddStatus(ref AutoStatus status, AutoStatus flag, Func status |= flag; } + + private static int CountAllianceTanks() + { + int count = 0; + foreach (var member in DataCenter.AllianceMembers) + { + if (member.IsJobCategory(JobRole.Tank)) + { + count++; + } + } + return count; + } + + private static bool AnyAllianceTankWithStance() + { + foreach (var member in DataCenter.AllianceMembers) + { + if (member.IsJobCategory(JobRole.Tank) && member.CurrentHp != 0 && member.HasStatus(false, StatusHelper.TankStanceStatus)) + { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/RotationSolver/Updaters/TargetUpdater.cs b/RotationSolver/Updaters/TargetUpdater.cs index dd6551af9..f15bfe083 100644 --- a/RotationSolver/Updaters/TargetUpdater.cs +++ b/RotationSolver/Updaters/TargetUpdater.cs @@ -24,8 +24,8 @@ internal static void UpdateTargets() DataCenter.DeathTarget = GetDeathTarget(); DataCenter.DispelTarget = GetDispelTarget(); DataCenter.AllHostileTargets = GetAllHostileTargets(); - DataCenter.ProvokeTarget = DataCenter.AllHostileTargets.FirstOrDefault(ObjectHelper.CanProvoke); - DataCenter.InterruptTarget = DataCenter.AllHostileTargets.FirstOrDefault(ObjectHelper.CanInterrupt); + DataCenter.ProvokeTarget = GetFirstHostileTarget(ObjectHelper.CanProvoke); + DataCenter.InterruptTarget = GetFirstHostileTarget(ObjectHelper.CanInterrupt); UpdateTimeToKill(); } @@ -145,7 +145,8 @@ private static List GetAllHostileTargets() { if (target == null) continue; if (!target.IsEnemy() || !target.IsTargetable) continue; - if (target.StatusList?.Any(StatusHelper.IsInvincible) == true && (DataCenter.IsPvP && !Service.Config.IgnorePvPInvincibility || !DataCenter.IsPvP)) continue; + if (target.StatusList != null && target.StatusList.Any(StatusHelper.IsInvincible) && + (DataCenter.IsPvP && !Service.Config.IgnorePvPInvincibility || !DataCenter.IsPvP)) continue; if (target.HasStatus(true, StatusID.StrongOfShield) && strongOfShieldPositional != target.FindEnemyPositional()) continue; hostileTargets.Add(target); @@ -158,76 +159,82 @@ private static List GetAllHostileTargets() return hostileTargets; } + private static IBattleChara? GetFirstHostileTarget(Func predicate) + { + foreach (var target in DataCenter.AllHostileTargets) + { + if (predicate(target)) + { + return target; + } + } + return null; + } + private static IBattleChara? GetDeathTarget() { - var rotation = DataCenter.RightNowRotation; if (Player.Job == Job.WHM || Player.Job == Job.SCH || Player.Job == Job.AST || Player.Job == Job.SGE || Player.Job == Job.SMN || Player.Job == Job.RDM) { try { - var deathAll = DataCenter.AllianceMembers?.GetDeath() ?? new List(); - var deathParty = DataCenter.PartyMembers?.GetDeath() ?? new List(); - var deathNPC = DataCenter.FriendlyNPCMembers?.GetDeath() ?? new List(); + var deathAll = DataCenter.AllianceMembers?.GetDeath().ToList() ?? new List(); + var deathParty = DataCenter.PartyMembers?.GetDeath().ToList() ?? new List(); + var deathNPC = DataCenter.FriendlyNPCMembers?.GetDeath().ToList() ?? new List(); - if (deathParty.Any()) - { - var deathT = deathParty.GetJobCategory(JobRole.Tank).ToList(); - var deathH = deathParty.GetJobCategory(JobRole.Healer).ToList(); - - if (deathT.Count > 1) return deathT.FirstOrDefault(); - if (deathH.Any()) return deathH.FirstOrDefault(); - if (deathT.Any()) return deathT.FirstOrDefault(); + var deathTarget = GetPriorityDeathTarget(deathParty); + if (deathTarget != null) return deathTarget; - return deathParty.FirstOrDefault(); - } + deathTarget = GetPriorityDeathTarget(deathAll, Service.Config.RaiseType); + if (deathTarget != null) return deathTarget; - if (deathAll.Any()) + if (Service.Config.FriendlyPartyNpcHealRaise2) { - if (Service.Config.RaiseType == RaiseType.PartyAndAllianceHealers) - { - var deathAllH = deathAll.GetJobCategory(JobRole.Healer).ToList(); - if (deathAllH.Any()) return deathAllH.FirstOrDefault(); - } - - if (Service.Config.RaiseType == RaiseType.PartyAndAlliance) - { - var deathAllH = deathAll.GetJobCategory(JobRole.Healer).ToList(); - var deathAllT = deathAll.GetJobCategory(JobRole.Tank).ToList(); - - if (deathAllH.Any()) return deathAllH.FirstOrDefault(); - if (deathAllT.Any()) return deathAllT.FirstOrDefault(); - - return deathAll.FirstOrDefault(); - } + deathTarget = GetPriorityDeathTarget(deathNPC); + if (deathTarget != null) return deathTarget; } + } + catch (Exception ex) + { + Svc.Log.Error($"Error in GetDeathTarget: {ex.Message}"); + } + } + return null; + } - if (deathNPC.Any() && Service.Config.FriendlyPartyNpcHealRaise2) - { - var deathNPCT = deathNPC.GetJobCategory(JobRole.Tank).ToList(); - var deathNPCH = deathNPC.GetJobCategory(JobRole.Healer).ToList(); + private static IBattleChara? GetPriorityDeathTarget(List deathList, RaiseType raiseType = RaiseType.PartyOnly) + { + if (deathList.Count == 0) return null; - if (deathNPCT.Count > 1) return deathNPCT.FirstOrDefault(); - if (deathNPCH.Any()) return deathNPCH.FirstOrDefault(); - if (deathNPCT.Any()) return deathNPCT.FirstOrDefault(); + var deathTanks = new List(); + var deathHealers = new List(); - return deathNPC.FirstOrDefault(); - } + foreach (var chara in deathList) + { + if (chara.IsJobCategory(JobRole.Tank)) + { + deathTanks.Add(chara); } - catch (Exception ex) + else if (chara.IsJobCategory(JobRole.Healer)) { - Svc.Log.Error($"Error in GetDeathTarget: {ex.Message}"); + deathHealers.Add(chara); } + } - return null; + if (raiseType == RaiseType.PartyAndAllianceHealers && deathHealers.Count > 0) + { + return deathHealers[0]; } - return null; + if (deathTanks.Count > 1) return deathTanks[0]; + if (deathHealers.Count > 0) return deathHealers[0]; + if (deathTanks.Count > 0) return deathTanks[0]; + + return deathList[0]; } private static IBattleChara? GetDispelTarget() { - var rotation = DataCenter.RightNowRotation; if (Player.Job == Job.WHM || Player.Job == Job.SCH || Player.Job == Job.AST || Player.Job == Job.SGE || Player.Job == Job.BRD) { @@ -235,47 +242,51 @@ private static List GetAllHostileTargets() var weakenNPC = new List(); var dyingPeople = new List(); - if (DataCenter.PartyMembers != null) - { - foreach (var member in DataCenter.PartyMembers) - { - if (member is IBattleChara b && b.StatusList != null && - b.StatusList.Any(status => status != null && status.CanDispel())) - { - weakenPeople.Add(b); - } - } - } + AddDispelTargets(DataCenter.PartyMembers, weakenPeople); + AddDispelTargets(DataCenter.FriendlyNPCMembers, weakenNPC); - if (DataCenter.FriendlyNPCMembers != null) + foreach (var person in weakenPeople) { - foreach (var npc in DataCenter.FriendlyNPCMembers) + if (person.StatusList != null && person.StatusList.Any(status => status != null && status.IsDangerous())) { - if (npc is IBattleChara b && b.StatusList != null && - b.StatusList.Any(status => status != null && status.CanDispel())) - { - weakenNPC.Add(b); - } + dyingPeople.Add(person); } } - foreach (var person in weakenPeople) + return GetClosestTarget(dyingPeople) ?? GetClosestTarget(weakenPeople) ?? GetClosestTarget(weakenNPC); + } + return null; + } + + private static void AddDispelTargets(List? members, List targetList) + { + if (members == null) return; + + foreach (var member in members) + { + if (member.StatusList != null && member.StatusList.Any(status => status != null && status.CanDispel())) { - if (person is IBattleChara b && b.StatusList != null && - b.StatusList.Any(status => status != null && status.IsDangerous())) - { - dyingPeople.Add(b); - } + targetList.Add(member); } - - return dyingPeople.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault() - ?? weakenPeople.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault() - ?? weakenNPC.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault(); } - else + } + + private static IBattleChara? GetClosestTarget(List targets) + { + IBattleChara? closestTarget = null; + float closestDistance = float.MaxValue; + + foreach (var target in targets) { - return null; + var distance = ObjectHelper.DistanceToPlayer(target); + if (distance < closestDistance) + { + closestDistance = distance; + closestTarget = target; + } } + + return closestTarget; } private static void UpdateTimeToKill() @@ -300,4 +311,4 @@ private static void UpdateTimeToKill() DataCenter.RecordedHP.Enqueue((now, currentHPs)); } -} +} \ No newline at end of file