diff --git a/BasicRotations/Melee/MNK_Default.cs b/BasicRotations/Melee/MNK_Default.cs index 0d1cbd758..dc2cb08b4 100644 --- a/BasicRotations/Melee/MNK_Default.cs +++ b/BasicRotations/Melee/MNK_Default.cs @@ -27,7 +27,7 @@ public enum RiddleOfFireFirst : byte public bool AutoPB_AOE { get; set; } = true; [RotationConfig(CombatType.PvE, Name = "Use Howling Fist/Enlightenment as a ranged attack verses single target enemies")] - public bool HowlingSingle { get; set; } = true; + public bool HowlingSingle { get; set; } = false; [RotationConfig(CombatType.PvE, Name = "Enable TEA Checker.")] public bool EnableTEAChecker { get; set; } = false; @@ -86,6 +86,26 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) } } + if (InBrotherhood) + { + // 'If you are in brotherhood, and you have 10 chakra, and forbidden chakra is available, use it.' + if (Chakra == 10 && TheForbiddenChakraPvE.CanUse(out act)) return true; + // 'If you are in brotherhood, and forbidden chakra is available, use it regardless of stacks.' + if (Player.WillStatusEndGCD(1, 0, true, StatusID.Brotherhood) && TheForbiddenChakraPvE.CanUse(out act)) return true; + } + if (!InBrotherhood) + { + // 'If you are not in brotherhood, have 5 chakra, and brotherhood is about to be available, hold.' + if (Chakra == 5 && BrotherhoodPvE.Cooldown.WillHaveOneChargeGCD(1) && TheForbiddenChakraPvE.CanUse(out act)) return false; + // 'If you are not in brotherhood, have 5 chakra, and brotherhood is in cooldown, use it.' + if (Chakra == 5 && BrotherhoodPvE.Cooldown.IsCoolingDown && TheForbiddenChakraPvE.CanUse(out act)) return true; + } + if (!TheForbiddenChakraPvE.EnoughLevel) + { + // 'If you are not high enough level for TheForbiddenChakra, use immediately at 5 chakra.' + if (Chakra == 5 && SteelPeakPvE.CanUse(out act)) return true; + } + return base.EmergencyAbility(nextGCD, out act); } @@ -165,11 +185,6 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act) if (PerfectBalancePvE.CanUse(out act, usedUp: true)) return true; } - if (EnlightenmentPvE.CanUse(out act)) return true; // Enlightment - if (HowlingFistPvE.CanUse(out act)) return true; // Howling Fist - if (TheForbiddenChakraPvE.CanUse(out act)) return true; - if (SteelPeakPvE.CanUse(out act)) return true; - // use bh when bh and rof are ready (opener) or ask bh to wait for rof's cd to be close and then use bh if (!CombatElapsedLessGCD(2) && ((BrotherhoodPvE.IsInCooldown && RiddleOfFirePvE.IsInCooldown) || Math.Abs(BrotherhoodPvE.Cooldown.CoolDownGroup - RiddleOfFirePvE.Cooldown.CoolDownGroup) < 3) @@ -180,8 +195,8 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act) // 'Use on cooldown, unless you know your killtime. You should aim to get as many casts of RoW as you can, and then shift those usages to align with burst as much as possible without losing a use.' if (!CombatElapsedLessGCD(3) && RiddleOfWindPvE.CanUse(out act)) return true; // Riddle Of Wind - if (HowlingSingle && EnlightenmentPvE.CanUse(out act, skipAoeCheck: true)) return true; // Enlightment - if (HowlingSingle && HowlingFistPvE.CanUse(out act, skipAoeCheck: true)) return true; // Howling Fist + if (EnlightenmentPvE.CanUse(out act, skipAoeCheck: HowlingSingle)) return true; // Enlightment + if (HowlingFistPvE.CanUse(out act, skipAoeCheck: HowlingSingle)) return true; // Howling Fist return base.AttackAbility(nextGCD, out act); } diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 9812a400e..621b69b22 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -297,8 +297,12 @@ public const string [ConditionBool, UI("Disable hostile actions if something is casting an action on the Gaze/Stop list (EXPEREMENTAL)", Filter = AutoActionUsage, Section = 4)] private static readonly bool _castingStop = false; - [ConditionBool, UI("Disable for the entire duration (Disabling this may lead to Gaze failure)", Filter = AutoActionUsage, Section = 4, Parent = nameof(CastingStop))] - private static readonly bool _castingStopCalculate = true; + [UI("Configurable amount of time before the cast finishes that RSR stops taking actions", Filter = AutoActionUsage, Section = 4, Parent = nameof(CastingStop))] + [Range(0, 15, ConfigUnitType.Seconds)] + public float CastingStopTime { get; set; } = 2.5f; + + [ConditionBool, UI("Disable for the entire duration (Enabling this will prevent your actions for the entire cast.)", Filter = AutoActionUsage, Section = 4, Parent = nameof(CastingStop))] + private static readonly bool _castingStopCalculate = false; [ConditionBool, UI("Automatic Healing Thresholds", Filter = HealingActionCondition, Section = 1, Order = 1)] private static readonly bool _autoHeal = true; diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index fc538ae2c..95c02bb69 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -1,6 +1,5 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Statuses; using ECommons.DalamudServices; using ECommons.ExcelServices; using ECommons.GameHelpers; @@ -11,7 +10,6 @@ using RotationSolver.Basic.Configuration; using RotationSolver.Basic.Configuration.Conditions; using RotationSolver.Basic.Rotations.Duties; -using System.Linq; using Action = Lumina.Excel.Sheets.Action; using CharacterManager = FFXIVClientStructs.FFXIV.Client.Game.Character.CharacterManager; @@ -41,9 +39,9 @@ public static MajorConditionSet RightSet { get { - if (ConditionSets == null || !ConditionSets.Any()) + if (ConditionSets == null || ConditionSets.Length == 0) { - ConditionSets = [new MajorConditionSet()]; + ConditionSets = new[] { new MajorConditionSet() }; } var index = Service.Config.ActionSequencerIndex; @@ -56,7 +54,7 @@ public static MajorConditionSet RightSet } } - internal static MajorConditionSet[] ConditionSets { get; set; } = []; + internal static MajorConditionSet[] ConditionSets { get; set; } = Array.Empty(); /// /// Only recorded 15s hps. @@ -86,15 +84,18 @@ public static MajorConditionSet RightSet internal static Queue<(ulong id, DateTime time)> AttackedTargets { get; } = new(AttackedTargetsCount); internal static bool InEffectTime => DateTime.Now >= EffectTime && DateTime.Now <= EffectEndTime; - internal static Dictionary HealHP { get; set; } = []; - internal static Dictionary ApplyStatus { get; set; } = []; + internal static Dictionary HealHP { get; set; } = new(); + internal static Dictionary ApplyStatus { get; set; } = new(); internal static uint MPGain { get; set; } internal static bool HasApplyStatus(ulong id, StatusID[] ids) { if (InEffectTime && ApplyStatus.TryGetValue(id, out var statusId)) { - if (ids.Any(s => (ushort)s == statusId)) return true; + foreach (var s in ids) + { + if ((ushort)s == statusId) return true; + } } return false; @@ -117,9 +118,9 @@ internal static bool HasApplyStatus(ulong id, StatusID[] ids) public static AutoStatus AutoStatus { get; set; } = AutoStatus.None; public static AutoStatus CommandStatus { get; set; } = AutoStatus.None; - public static HashSet DisabledActionSequencer { get; set; } = []; + public static HashSet DisabledActionSequencer { get; set; } = new(); - private static List NextActs = []; + private static List NextActs = new(); public static IAction? ActionSequencerAction { private get; set; } public static IAction? CommandNextAction @@ -152,7 +153,16 @@ public static JobRole Role internal static void AddCommandAction(IAction act, double time) { - var index = NextActs.FindIndex(i => i.Act.ID == act.ID); + var index = -1; + for (int i = 0; i < NextActs.Count; i++) + { + if (NextActs[i].Act.ID == act.ID) + { + index = i; + break; + } + } + var newItem = new NextAct(act, DateTime.Now.AddSeconds(time)); if (index < 0) { @@ -163,7 +173,7 @@ internal static void AddCommandAction(IAction act, double time) NextActs[index] = newItem; } - NextActs = [.. NextActs.OrderBy(i => i.DeadTime)]; + NextActs.Sort((a, b) => a.DeadTime.CompareTo(b.DeadTime)); } public static TargetHostileType RightNowTargetToHostileType => Service.Config.HostileType; @@ -308,14 +318,13 @@ internal static float RaidTimeRaw } } - public static List PartyMembers { get; set; } = []; - - public static List AllianceMembers { get; set; } = []; + public static List PartyMembers { get; set; } = new(); - public static List FriendlyNPCMembers { get; set; } = []; + public static List AllianceMembers { get; set; } = new(); + public static List FriendlyNPCMembers { get; set; } = new(); - public static List AllHostileTargets { get; set; } = []; + public static List AllHostileTargets { get; set; } = new(); public static IBattleChara? InterruptTarget { get; set; } @@ -325,7 +334,7 @@ internal static float RaidTimeRaw public static IBattleChara? DispelTarget { get; set; } - public static List AllTargets { get; set; } = []; + public static List AllTargets { get; set; } = new(); public static ulong[] TreasureCharas { @@ -346,15 +355,69 @@ public static ulong[] TreasureCharas public static bool HasHostilesInRange => NumberOfHostilesInRange > 0; public static bool HasHostilesInMaxRange => NumberOfHostilesInMaxRange > 0; - public static int NumberOfHostilesInRange => AllHostileTargets.Count(o => o.DistanceToPlayer() < JobRange); - public static int NumberOfHostilesInMaxRange => AllHostileTargets.Count(o => o.DistanceToPlayer() < 25); - public static int NumberOfAllHostilesInRange => AllHostileTargets.Count(o => o.DistanceToPlayer() < JobRange); - public static int NumberOfAllHostilesInMaxRange => AllHostileTargets.Count(o => o.DistanceToPlayer() < 25); + public static int NumberOfHostilesInRange + { + get + { + int count = 0; + foreach (var o in AllHostileTargets) + { + if (o.DistanceToPlayer() < JobRange) + { + count++; + } + } + return count; + } + } + public static int NumberOfHostilesInMaxRange + { + get + { + int count = 0; + foreach (var o in AllHostileTargets) + { + if (o.DistanceToPlayer() < 25) + { + count++; + } + } + return count; + } + } + public static int NumberOfAllHostilesInRange => NumberOfHostilesInRange; + public static int NumberOfAllHostilesInMaxRange => NumberOfHostilesInMaxRange; - public static bool MobsTime => AllHostileTargets.Count(o => o.DistanceToPlayer() < JobRange && o.CanSee()) - >= Service.Config.AutoDefenseNumber; + public static bool MobsTime + { + get + { + int count = 0; + foreach (var o in AllHostileTargets) + { + if (o.DistanceToPlayer() < JobRange && o.CanSee()) + { + count++; + } + } + return count >= Service.Config.AutoDefenseNumber; + } + } - public static bool AreHostilesCastingKnockback => AllHostileTargets.Any(IsHostileCastingKnockback); + public static bool AreHostilesCastingKnockback + { + get + { + foreach (var h in AllHostileTargets) + { + if (IsHostileCastingKnockback(h)) + { + return true; + } + } + return false; + } + } public static float JobRange { @@ -379,14 +442,18 @@ public static float AverageTimeToKill { get { - // Select the time to kill for each hostile target and filter out NaN values. - var validTimes = AllHostileTargets - .Select(b => b.GetTimeToKill()) - .Where(v => !float.IsNaN(v)) - .ToList(); - - // If there are valid times, return the average; otherwise, return 0. - return validTimes.Any() ? validTimes.Average() : 0; + float total = 0; + int count = 0; + foreach (var b in AllHostileTargets) + { + var timeToKill = b.GetTimeToKill(); + if (!float.IsNaN(timeToKill)) + { + total += timeToKill; + count++; + } + } + return count > 0 ? total / count : 0; } } @@ -416,7 +483,7 @@ public static bool IsHostileCastingStopBase(IBattleChara h, Func c if (h.IsCastInterruptible) return false; // Validate the cast time - if ((h.TotalCastTime - h.CurrentCastTime) > 2.5f) return false; + if ((h.TotalCastTime - h.CurrentCastTime) > (Service.Config.CastingStopCalculate ? 100 : Service.Config.CastingStopTime)) return false; // Get the action sheet var actionSheet = Service.GetSheet(); @@ -434,10 +501,16 @@ public static bool HasPet { get { - var mayPet = AllTargets.OfType().Where(npc => npc.OwnerId == Player.Object.GameObjectId); - var hasPet = mayPet.Any(npc => npc.BattleNpcKind == BattleNpcSubKind.Pet); - if (hasPet || - Svc.Condition[ConditionFlag.Mounted] || + foreach (var npc in AllTargets) + { + if (npc is IBattleNpc battleNpc && battleNpc.OwnerId == Player.Object.GameObjectId && battleNpc.BattleNpcKind == BattleNpcSubKind.Pet) + { + _petLastSeen = DateTime.Now; + return true; + } + } + + if (Svc.Condition[ConditionFlag.Mounted] || Svc.Condition[ConditionFlag.Mounted2] || Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51] || @@ -452,7 +525,7 @@ public static bool HasPet return true; } - if (!hasPet && _petLastSeen.AddSeconds(3) < DateTime.Now) + if (_petLastSeen.AddSeconds(3) < DateTime.Now) { return false; } @@ -489,8 +562,18 @@ public static unsafe bool HasCompanion #region HP - public static Dictionary RefinedHP => PartyMembers - .ToDictionary(p => p.GameObjectId, GetPartyMemberHPRatio); + public static Dictionary RefinedHP + { + get + { + var refinedHP = new Dictionary(); + foreach (var member in PartyMembers) + { + refinedHP[member.GameObjectId] = GetPartyMemberHPRatio(member); + } + return refinedHP; + } + } private static Dictionary _lastHp = new Dictionary(); @@ -520,14 +603,37 @@ private static float GetPartyMemberHPRatio(IBattleChara member) return (float)currentHp / member.MaxHp; } - public static IEnumerable PartyMembersHP => RefinedHP.Values.Where(r => r > 0); + public static IEnumerable PartyMembersHP + { + get + { + foreach (var hp in RefinedHP.Values) + { + if (hp > 0) + { + yield return hp; + } + } + } + } public static float PartyMembersMinHP { get { - var partyMembersHP = PartyMembersHP.ToList(); - return partyMembersHP.Count > 0 ? partyMembersHP.Min() : 0; + float minHP = float.MaxValue; + bool hasMembers = false; + + foreach (var hp in PartyMembersHP) + { + if (hp < minHP) + { + minHP = hp; + } + hasMembers = true; + } + + return hasMembers ? minHP : 0; } } @@ -535,8 +641,16 @@ public static float PartyMembersAverHP { get { - var partyMembersHP = PartyMembersHP.ToList(); - return partyMembersHP.Count > 0 ? partyMembersHP.Average() : 0; + float totalHP = 0; + int count = 0; + + foreach (var hp in PartyMembersHP) + { + totalHP += hp; + count++; + } + + return count > 0 ? totalHP / count : 0; } } @@ -544,12 +658,18 @@ public static float PartyMembersDifferHP { get { - var partyMembersHP = PartyMembersHP.ToList(); + var partyMembersHP = new List(PartyMembersHP); if (partyMembersHP.Count == 0) return 0; var averageHP = partyMembersHP.Average(); - var variance = partyMembersHP.Average(d => (d - averageHP) * (d - averageHP)); - return (float)Math.Sqrt(variance); + var variance = 0f; + + foreach (var hp in partyMembersHP) + { + variance += (hp - averageHP) * (hp - averageHP); + } + + return (float)Math.Sqrt(variance / partyMembersHP.Count); } } @@ -573,12 +693,18 @@ public static float DPSTaken { try { - var recs = _damages.Where(r => DateTime.Now - r.ReceiveTime < TimeSpan.FromMilliseconds(5)); + var recs = new List(); + foreach (var rec in _damages) + { + if (DateTime.Now - rec.ReceiveTime < TimeSpan.FromMilliseconds(5)) + { + recs.Add(rec); + } + } - if (!recs.Any()) return 0; + if (recs.Count == 0) return 0; var damages = recs.Sum(r => r.Ratio); - var time = recs.Last().ReceiveTime - recs.First().ReceiveTime + TimeSpan.FromMilliseconds(2.5f); return damages / (float)time.TotalSeconds; diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs index d1c67d4c4..33143d01c 100644 --- a/RotationSolver.Basic/Helpers/ObjectHelper.cs +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -45,8 +45,13 @@ internal static bool CanProvoke(this IGameObject target) //Removed the listed names. if (OtherConfiguration.NoProvokeNames.TryGetValue(Svc.ClientState.TerritoryType, out var ns1)) { - var names = ns1.Where(n => !string.IsNullOrEmpty(n) && new Regex(n).Match(target.Name.ExtractText()).Success); - if (names.Any()) return false; + foreach (var n in ns1) + { + if (!string.IsNullOrEmpty(n) && new Regex(n).Match(target.Name.ExtractText()).Success) + { + return false; + } + } } //Target can move or too big and has a target @@ -66,7 +71,7 @@ internal static bool CanProvoke(this IGameObject target) internal static bool HasPositional(this IGameObject obj) { - return obj != null && !(obj.GetObjectNPC()?.IsOmnidirectional ?? false) && obj.HasStatus(false, StatusID.DirectionalDisregard); // Unknown10 used to be the flag for no positional, believe this was changed to IsOmnidirectional + return obj != null && (!(obj.GetObjectNPC()?.IsOmnidirectional ?? false)); // Unknown10 used to be the flag for no positional, believe this was changed to IsOmnidirectional } internal static unsafe bool IsOthersPlayers(this IGameObject obj) @@ -85,7 +90,10 @@ internal static bool IsAttackable(this IBattleChara battleChara) // Check if the target is invincible. if (battleChara.IsJeunoBossImmune()) return false; - if (battleChara.StatusList.Any(status => StatusHelper.IsInvincible(status))) return false; + foreach (var status in battleChara.StatusList) + { + if (StatusHelper.IsInvincible(status)) return false; + } if (Svc.ClientState == null) return false; @@ -93,8 +101,13 @@ internal static bool IsAttackable(this IBattleChara battleChara) if (OtherConfiguration.NoHostileNames != null && OtherConfiguration.NoHostileNames.TryGetValue(Svc.ClientState.TerritoryType, out var ns1)) { - var names = ns1.Where(n => !string.IsNullOrEmpty(n) && new Regex(n).Match(battleChara.Name.TextValue).Success); - if (names.Any()) return false; + foreach (var n in ns1) + { + if (!string.IsNullOrEmpty(n) && new Regex(n).Match(battleChara.Name.TextValue).Success) + { + return false; + } + } } // Fate @@ -128,7 +141,7 @@ internal static bool IsAttackable(this IBattleChara battleChara) TargetHostileType.AllTargetsWhenSoloInDutyTargetIsInEnemiesList => DataCenter.PartyMembers.Count() < 2 || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), _ => true, }; - } + } private static string RemoveControlCharacters(string input) { @@ -248,11 +261,13 @@ internal static bool IsParty(this IGameObject gameObject) { // Accessing Player.Object and Svc.Party within the lock to ensure thread safety if (gameObject.GameObjectId == Player.Object?.GameObjectId) return true; - if (Svc.Party.Any(p => p.GameObject?.GameObjectId == gameObject.GameObjectId)) return true; + foreach (var p in Svc.Party) + { + if (p.GameObject?.GameObjectId == gameObject.GameObjectId) return true; + } if (Service.Config.FriendlyPartyNpcHealRaise2 && gameObject.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember) return true; if (Service.Config.ChocoboPartyMember && gameObject.GetNameplateKind() == NameplateKind.PlayerCharacterChocobo) return true; if (Service.Config.FriendlyBattleNpcHeal && gameObject.GetNameplateKind() == NameplateKind.FriendlyBattleNPC) return true; - } return false; } @@ -268,7 +283,10 @@ internal static bool IsDeathToRaise(this IGameObject obj) if (obj is IBattleChara b && b.CurrentHp != 0) return false; if (obj.HasStatus(false, StatusID.Raise)) return false; if (!Service.Config.RaiseBrinkOfDeath && obj.HasStatus(false, StatusID.BrinkOfDeath)) return false; - if (DataCenter.AllianceMembers.Any(c => c.CastTargetObjectId == obj.GameObjectId)) return false; + foreach (var c in DataCenter.AllianceMembers) + { + if (c.CastTargetObjectId == obj.GameObjectId) return false; + } return true; } @@ -296,7 +314,13 @@ internal static bool IsTopPriorityNamedHostile(this IGameObject obj) { if (obj == null) return false; - if (obj is IBattleChara npc && DataCenter.PrioritizedNameIds.Contains(npc.NameId)) return true; + if (obj is IBattleChara npc) + { + foreach (var id in DataCenter.PrioritizedNameIds) + { + if (npc.NameId == id) return true; + } + } return false; } @@ -314,14 +338,16 @@ internal static bool IsTopPriorityHostile(this IGameObject obj) var fateId = DataCenter.FateId; - if (obj is IBattleChara b) { if (Player.Object == null) return false; // Check IBattleChara against the priority target list of OIDs if (PriorityTargetHelper.IsPriorityTarget(b.DataId)) return true; // Ensure StatusList is not null before calling Any - if (b.StatusList != null && b.StatusList.Any(StatusHelper.IsPriority)) return true; + foreach (var status in b.StatusList) + { + if (StatusHelper.IsPriority(status)) return true; + } } if (Service.Config.ChooseAttackMark && MarkingHelper.GetAttackSignTargets().FirstOrDefault(id => id != 0) == (long)obj.GameObjectId && obj.IsEnemy()) return true; @@ -332,13 +358,13 @@ internal static bool IsTopPriorityHostile(this IGameObject obj) var icon = obj.GetNamePlateIcon(); // Hunting log and weapon - if (Service.Config.TargetHuntingRelicLevePriority && icon is 60092 or 60096 or 71244) return true; + if (Service.Config.TargetHuntingRelicLevePriority && (icon == 60092 || icon == 60096 || icon == 71244)) return true; //60092 Hunt Target //60096 Relic Weapon //71244 Leve Target // Quest - if (Service.Config.TargetQuestPriority && (icon is 71204 or 71144 or 71224 or 71344 || obj.GetEventType() is EventHandlerType.Quest)) return true; + if (Service.Config.TargetQuestPriority && (icon == 71204 || icon == 71144 || icon == 71224 || icon == 71344 || obj.GetEventType() == EventHandlerType.Quest)) return true; //71204 Main Quest //71144 Major Quest //71224 Other Quest @@ -380,7 +406,7 @@ internal static bool CanInterrupt(this IGameObject o) var act = Service.GetSheet().GetRow(b.CastActionId); if (act.RowId == 0) return _effectRangeCheck[id] = false; - if (act.CastType is 3 or 4 || (act.EffectRange is > 0 and < 8)) return _effectRangeCheck[id] = false; + if (act.CastType == 3 || act.CastType == 4 || (act.EffectRange > 0 && act.EffectRange < 8)) return _effectRangeCheck[id] = false; return _effectRangeCheck[id] = true; } @@ -685,4 +711,5 @@ public static float DistanceToPlayer(this IGameObject? obj) var distance = Vector3.Distance(player.Position, obj.Position) - (player.HitboxRadius + obj.HitboxRadius); return distance; } + } diff --git a/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs b/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs index 9f72ffdb8..88e298297 100644 --- a/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs @@ -40,11 +40,16 @@ partial class MonkRotation /// public static int RaptorFury => JobGauge.RaptorFury; - /// + /// Brotherhood /// Gets the amount of available Coeurl Fury stacks. /// public static int CoeurlFury => JobGauge.CoeurlFury; + /// Brotherhood + /// Brotherhood + /// + public static bool InBrotherhood => !Player.WillStatusEnd(0, true, StatusID.Brotherhood); + /// public override void DisplayStatus() { @@ -189,7 +194,7 @@ static partial void ModifyForbiddenMeditationPvE(ref ActionSetting setting) static partial void ModifyTheForbiddenChakraPvE(ref ActionSetting setting) { - setting.ActionCheck = () => InCombat && Chakra == 5; + setting.ActionCheck = () => InCombat; setting.UnlockedByQuestID = 67564; } diff --git a/RotationSolver.Basic/Service.cs b/RotationSolver.Basic/Service.cs index 2516d72a8..75e4e5a28 100644 --- a/RotationSolver.Basic/Service.cs +++ b/RotationSolver.Basic/Service.cs @@ -132,14 +132,21 @@ public static unsafe uint GetAdjustedActionId(uint id) /// A collection of addon pointers. public static IEnumerable GetAddons() where T : struct { - // Check the cache for the attribute or add it if not present var addon = AddonCache.GetOrAdd(typeof(T), t => t.GetCustomAttribute()); if (addon is null) return Array.Empty(); - return addon.AddonIdentifiers - .Select(str => Svc.GameGui.GetAddonByName(str, 1)) - .Where(ptr => ptr != IntPtr.Zero); + var addonPointers = new List(); + foreach (var str in addon.AddonIdentifiers) + { + var ptr = Svc.GameGui.GetAddonByName(str, 1); + if (ptr != IntPtr.Zero) + { + addonPointers.Add(ptr); + } + } + + return addonPointers; } /// diff --git a/RotationSolver/RotationSolverPlugin.cs b/RotationSolver/RotationSolverPlugin.cs index b8cca75e9..f68bca240 100644 --- a/RotationSolver/RotationSolverPlugin.cs +++ b/RotationSolver/RotationSolverPlugin.cs @@ -31,9 +31,9 @@ public sealed class RotationSolverPlugin : IDalamudPlugin, IDisposable static WelcomeWindow? _changelogWindow; static OverlayWindow? _overlayWindow; - static readonly List _dis = []; + static readonly List _dis = new(); public static string Name => "Rotation Solver Reborn"; - internal static readonly List _drawingElements = []; + internal static readonly List _drawingElements = new(); public static DalamudLinkPayload OpenLinkPayload { get; private set; } = null!; public static DalamudLinkPayload? HideWarningLinkPayload { get; private set; } @@ -257,10 +257,22 @@ internal static void UpdateDisplayWindow() isValid &= !Service.Config.OnlyShowWithHostileOrInDuty || Svc.Condition[ConditionFlag.BoundByDuty] - || DataCenter.AllHostileTargets.Any(o => o.DistanceToPlayer() < 25); + || AnyHostileTargetWithinDistance(25); _controlWindow!.IsOpen = isValid && Service.Config.ShowControlWindow; _cooldownWindow!.IsOpen = isValid && Service.Config.ShowCooldownWindow; _overlayWindow!.IsOpen = isValid && Service.Config.TeachingMode; } -} + + private static bool AnyHostileTargetWithinDistance(float distance) + { + foreach (var target in DataCenter.AllHostileTargets) + { + if (target.DistanceToPlayer() < distance) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/RotationSolver/Updaters/MajorUpdater.cs b/RotationSolver/Updaters/MajorUpdater.cs index 0c02bdaa1..13fd5a91b 100644 --- a/RotationSolver/Updaters/MajorUpdater.cs +++ b/RotationSolver/Updaters/MajorUpdater.cs @@ -158,9 +158,14 @@ private static void UpdateWork() HotbarHighlightManager.UpdateSettings(); // Collect expired VfxNewData items - var expiredVfx = DataCenter.VfxDataQueue - .Where(vfx => vfx.TimeDuration > TimeSpan.FromSeconds(10)) - .ToList(); + var expiredVfx = new List(); + foreach (var vfx in DataCenter.VfxDataQueue) + { + if (vfx.TimeDuration > TimeSpan.FromSeconds(10)) + { + expiredVfx.Add(vfx); + } + } // Remove expired VfxNewData items foreach (var vfx in expiredVfx) @@ -182,7 +187,7 @@ 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 => new HotbarID(HotbarSlotType.Action, baseAction.AdjustedID), diff --git a/RotationSolver/Updaters/StateUpdater.cs b/RotationSolver/Updaters/StateUpdater.cs index 900540a40..ef0d10842 100644 --- a/RotationSolver/Updaters/StateUpdater.cs +++ b/RotationSolver/Updaters/StateUpdater.cs @@ -146,17 +146,85 @@ 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) { + // 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) - && positional != ActionUpdater.NextGCDAction.Target.Target?.FindEnemyPositional() - && (ActionUpdater.NextGCDAction.Target.Target?.HasPositional() ?? false)) + // 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)) + { + // 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; + } + + private static bool ShouldAddDefenseSingle() + { + if (!DataCenter.InCombat || !Service.Config.UseDefenseAbility) + return false; + + if (DataCenter.Role == JobRole.Healer) + { + if (DataCenter.PartyMembers.Any((tank) => + { + var attackingTankObj = DataCenter.AllHostileTargets.Where(t => t.TargetObjectId == tank.GameObjectId); + + if (attackingTankObj.Count() != 1) + return false; + + return DataCenter.IsHostileCastingToTank; + })) + { + return true; + } + } + + if (DataCenter.Role == JobRole.Tank) + { + var movingHere = (float)DataCenter.NumberOfHostilesInRange / DataCenter.NumberOfHostilesInMaxRange > 0.3f; + + 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; + + if (tarOnMeCount >= Service.Config.AutoDefenseNumber + && Player.Object.GetHealthRatio() <= Service.Config.HealthForAutoDefense + && movingHere && attacked) + { + return true; + } + + if (DataCenter.IsHostileCastingToTank) { return true; } } + return false; } @@ -252,61 +320,6 @@ private static bool ShouldAddHealSingleSpell() } } - private static bool ShouldAddDefenseArea() - { - if (!DataCenter.InCombat || !Service.Config.UseDefenseAbility) - return false; - - return DataCenter.IsHostileCastingAOE; - } - - private static bool ShouldAddDefenseSingle() - { - if (!DataCenter.InCombat || !Service.Config.UseDefenseAbility) - return false; - - if (DataCenter.Role == JobRole.Healer) - { - if (DataCenter.PartyMembers.Any((tank) => - { - var attackingTankObj = DataCenter.AllHostileTargets.Where(t => t.TargetObjectId == tank.GameObjectId); - - if (attackingTankObj.Count() != 1) - return false; - - return DataCenter.IsHostileCastingToTank; - })) - { - return true; - } - } - - if (DataCenter.Role == JobRole.Tank) - { - var movingHere = (float)DataCenter.NumberOfHostilesInRange / DataCenter.NumberOfHostilesInMaxRange > 0.3f; - - 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; - - if (tarOnMeCount >= Service.Config.AutoDefenseNumber - && Player.Object.GetHealthRatio() <= Service.Config.HealthForAutoDefense - && movingHere && attacked) - { - return true; - } - - if (DataCenter.IsHostileCastingToTank) - { - return true; - } - } - - return false; - } - private static bool ShouldAddAntiKnockback() { if (!DataCenter.InCombat || !Service.Config.UseKnockback) diff --git a/RotationSolver/Watcher.cs b/RotationSolver/Watcher.cs index 02b944d94..b43dfd6c1 100644 --- a/RotationSolver/Watcher.cs +++ b/RotationSolver/Watcher.cs @@ -102,11 +102,15 @@ private static void ActionFromEnemy(ActionEffectSet set) foreach (var effect in set.TargetEffects) { - if (DataCenter.PartyMembers.Any(p => p.GameObjectId == effect.TargetID) && - effect.GetSpecificTypeEffect(ActionEffectType.Damage, out var damageEffect) && - (damageEffect.value > 0 || (damageEffect.param0 & 6) == 6)) + foreach (var partyMember in DataCenter.PartyMembers) { - damageEffectCount++; + if (partyMember.GameObjectId == effect.TargetID && + effect.GetSpecificTypeEffect(ActionEffectType.Damage, out var damageEffect) && + (damageEffect.value > 0 || (damageEffect.param0 & 6) == 6)) + { + damageEffectCount++; + break; + } } } @@ -145,7 +149,7 @@ private static void ActionFromSelf(ActionEffectSet set) OtherConfiguration.AnimationLockTime[id] = set.Header.AnimationLockTime; } - if (!set.TargetEffects.Any()) return; + if (set.TargetEffects.Length == 0) return; var action = set.Action; var tar = set.Target; @@ -160,9 +164,17 @@ private static void ActionFromSelf(ActionEffectSet set) { DataCenter.ApplyStatus[effect.Key] = effect.Value; } - DataCenter.MPGain = (uint)set.GetSpecificTypeEffect(ActionEffectType.MpGain) - .Where(i => i.Key == playerObject.GameObjectId) - .Sum(i => i.Value); + + uint mpGain = 0; + foreach (var effect in set.GetSpecificTypeEffect(ActionEffectType.MpGain)) + { + if (effect.Key == playerObject.GameObjectId) + { + mpGain += effect.Value; + } + } + DataCenter.MPGain = mpGain; + DataCenter.EffectTime = DateTime.Now; DataCenter.EffectEndTime = DateTime.Now.AddSeconds(set.Header.AnimationLockTime + 1);