diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index 9ba03e238..7a1aafc0f 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -317,181 +317,24 @@ internal static float RaidTimeRaw } } - public unsafe static IBattleChara[] PartyMembers => AllianceMembers.Where(ObjectHelper.IsParty) - .Where(b => b.Character()->CharacterData.OnlineStatus != 15 && b.IsTargetable).ToArray(); + public static List PartyMembers { get; set; } = []; - public unsafe static IBattleChara[] AllianceMembers => AllTargets.Where(ObjectHelper.IsAlliance) - .Where(b => b.Character()->CharacterData.OnlineStatus != 15 && b.IsTargetable).ToArray(); + public static List AllianceMembers { get; set; } = []; - public unsafe static IBattleChara[] FriendlyNPCMembers - { - get - { - // Check if the configuration setting is true - if (!Service.Config.FriendlyBattleNpcHeal && !Service.Config.FriendlyPartyNpcHealRaise2) - { - return Array.Empty(); - } - - try - { - // Ensure Svc.Objects is not null - if (Svc.Objects == null) - { - return Array.Empty(); - } - - // Filter and cast objects safely - var friendlyNpcs = Svc.Objects - .Where(obj => obj != null && obj.ObjectKind == ObjectKind.BattleNpc) - .Where(obj => - { - try - { - return obj.GetNameplateKind() == NameplateKind.FriendlyBattleNPC || - obj.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember; - } - catch (Exception ex) - { - // Log the exception for debugging purposes - Svc.Log.Error($"Error filtering object in get_FriendlyNPCMembers: {ex.Message}"); - return false; - } - }) - .OfType() - .ToArray(); - - return friendlyNpcs; - } - catch (Exception ex) - { - // Log the exception for debugging purposes - Svc.Log.Error($"Error in get_FriendlyNPCMembers: {ex.Message}"); - return Array.Empty(); - } - } - } - - - public static IBattleChara[] AllHostileTargets - { - get - { - var strongOfShieldPositional = EnemyPositional.Front; - - return AllTargets.Where(b => - { - // Check if the target is an enemy and targetable. - if (!b.IsEnemy() || !b.IsTargetable) return false; - - // Check if the target is invincible. - if (b.StatusList.Any(StatusHelper.IsInvincible)) return false; - - // Special exception for Jeuno raid Ark Angels. - if (b.IsWrongEpicFatedVaunted()) return false; - - // Special exception for the Strong of Shield status on Hansel and Gretel. - if (b.HasStatus(true, StatusID.StrongOfShield) && strongOfShieldPositional != b.FindEnemyPositional()) return false; - - // If all checks pass, the target is considered hostile. - return true; - }).ToArray(); - } - } - - public static IBattleChara? InterruptTarget => - AllHostileTargets.FirstOrDefault(ObjectHelper.CanInterrupt); - - public static IBattleChara? ProvokeTarget => AllHostileTargets.FirstOrDefault(ObjectHelper.CanProvoke); - - public static IBattleChara? DeathTarget - { - get - { - // Added so it only tracks deathtarget if you are on a raise job - 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) - { - // Ensure AllianceMembers and PartyMembers are not null - if (AllianceMembers == null || PartyMembers == null) return null; - - var deathAll = AllianceMembers.GetDeath(); - var deathParty = PartyMembers.GetDeath(); - var deathNPC = FriendlyNPCMembers.GetDeath(); - - // Check death in party members - 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(); + public static List FriendlyNPCMembers { get; set; } = []; - return deathParty.FirstOrDefault(); - } - // Check death in alliance members - if (deathAll.Any()) - { - 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(); - } - } + public static List AllHostileTargets { get; set; } = []; - // Check death in friendly NPC members - if (deathNPC.Any() && Service.Config.FriendlyPartyNpcHealRaise2) - { - var deathNPCT = deathNPC.GetJobCategory(JobRole.Tank).ToList(); - var deathNPCH = deathNPC.GetJobCategory(JobRole.Healer).ToList(); + public static IBattleChara? InterruptTarget { get; set; } - if (deathNPCT.Count > 1) return deathNPCT.FirstOrDefault(); - if (deathNPCH.Any()) return deathNPCH.FirstOrDefault(); - if (deathNPCT.Any()) return deathNPCT.FirstOrDefault(); + public static IBattleChara? ProvokeTarget { get; set; } - return deathNPC.FirstOrDefault(); - } - - return null; - } - return null; - } - } + public static IBattleChara? DeathTarget { get; set; } - public static IBattleChara? DispelTarget - { - get - { - var weakenPeople = DataCenter.PartyMembers? - .Where(o => o is IBattleChara b && b.StatusList != null && b.StatusList.Any(status => status != null && StatusHelper.CanDispel(status))) ?? Enumerable.Empty(); - var weakenNPC = DataCenter.FriendlyNPCMembers? - .Where(o => o is IBattleChara b && b.StatusList != null && b.StatusList.Any(status => status != null && StatusHelper.CanDispel(status))) ?? Enumerable.Empty(); - var dyingPeople = weakenPeople - .Where(o => o is IBattleChara b && b.StatusList != null && b.StatusList.Any(status => status != null && StatusHelper.IsDangerous(status))); - - return dyingPeople.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault() - ?? weakenPeople.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault() - ?? weakenNPC.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault(); - } - } + public static IBattleChara? DispelTarget { get; set; } - public static IBattleChara[] AllTargets => Svc.Objects.OfType().GetObjectInRadius(30) - .Where(o => !o.IsDummy() || !Service.Config.DisableTargetDummys).ToArray(); + public static List AllTargets { get; set; } = []; public static ulong[] TreasureCharas { diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs index 5cf0cf934..50e21b8e7 100644 --- a/RotationSolver.Basic/Helpers/ObjectHelper.cs +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -118,12 +118,12 @@ internal static bool IsAttackable(this IBattleChara battleChara) { TargetHostileType.AllTargetsCanAttack => true, TargetHostileType.TargetsHaveTarget => battleChara.TargetObject is IBattleChara, - TargetHostileType.AllTargetsWhenSolo => DataCenter.PartyMembers.Length < 2 || battleChara.TargetObject is IBattleChara, - TargetHostileType.AllTargetsWhenSoloInDuty => (DataCenter.PartyMembers.Length < 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.Length < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56])) || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(), - TargetHostileType.AllTargetsWhenSoloInDutyTargetIsInEnemiesList => DataCenter.PartyMembers.Length < 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/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 4f2a792f9..cf92866cc 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -2671,8 +2671,8 @@ private static unsafe void DrawStatus() private static unsafe void DrawParty() { - ImGui.Text($"Party: {DataCenter.PartyMembers.Length}"); - ImGui.Text($"Alliance: {DataCenter.AllianceMembers.Length}"); + ImGui.Text($"Party: {DataCenter.PartyMembers.Count()}"); + ImGui.Text($"Alliance: {DataCenter.AllianceMembers.Count()}"); ImGui.Text($"PartyMembersAverHP: {DataCenter.PartyMembersAverHP}"); @@ -2752,7 +2752,7 @@ private static unsafe void DrawTargetData() } ImGui.Text($"All: {DataCenter.AllTargets.Count()}"); - ImGui.Text($"Hostile: {DataCenter.AllHostileTargets.Length}"); + ImGui.Text($"Hostile: {DataCenter.AllHostileTargets.Count()}"); foreach (var item in DataCenter.AllHostileTargets) { ImGui.Text(item.Name.ToString()); diff --git a/RotationSolver/Updaters/MajorUpdater.cs b/RotationSolver/Updaters/MajorUpdater.cs index b82d16693..293532ef1 100644 --- a/RotationSolver/Updaters/MajorUpdater.cs +++ b/RotationSolver/Updaters/MajorUpdater.cs @@ -150,7 +150,7 @@ private static void UpdateWork() if (DataCenter.IsActivated()) { - TargetUpdater.UpdateTarget(); + TargetUpdater.UpdateTargets(); ActionSequencerUpdater.UpdateActionSequencerAction(); ActionUpdater.UpdateNextAction(); } diff --git a/RotationSolver/Updaters/StateUpdater.cs b/RotationSolver/Updaters/StateUpdater.cs index a9108d958..c0507f811 100644 --- a/RotationSolver/Updaters/StateUpdater.cs +++ b/RotationSolver/Updaters/StateUpdater.cs @@ -65,7 +65,7 @@ private static AutoStatus StatusFromAutomatic() var canHealAreaAbility = singleAbility > 2; var canHealAreaSpell = singleSpell > 2; - if (DataCenter.PartyMembers.Length > 2) + if (DataCenter.PartyMembers.Count() > 2) { //TODO: Beneficial area status. var ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); diff --git a/RotationSolver/Updaters/TargetUpdater.cs b/RotationSolver/Updaters/TargetUpdater.cs index 68d010fbe..a0840a60b 100644 --- a/RotationSolver/Updaters/TargetUpdater.cs +++ b/RotationSolver/Updaters/TargetUpdater.cs @@ -1,4 +1,11 @@ -namespace RotationSolver.Updaters; +using Dalamud.Game.ClientState.Objects.Enums; +using ECommons.DalamudServices; +using ECommons.ExcelServices; +using ECommons.GameFunctions; +using ECommons.GameHelpers; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; + +namespace RotationSolver.Updaters; internal static partial class TargetUpdater { @@ -9,11 +16,184 @@ private static readonly ObjectListDelay private static DateTime _lastUpdateTimeToKill = DateTime.MinValue; private static readonly TimeSpan TimeToKillUpdateInterval = TimeSpan.FromSeconds(0.5); - internal static void UpdateTarget() + internal static void UpdateTargets() { + DataCenter.AllTargets = Svc.Objects.OfType().GetObjectInRadius(30) + .Where(o => !o.IsDummy() || !Service.Config.DisableTargetDummys).ToList(); + DataCenter.DeathTarget = GetDeathTarget(); + DataCenter.DispelTarget = GetDispelTarget(); + DataCenter.ProvokeTarget = DataCenter.AllHostileTargets.FirstOrDefault(ObjectHelper.CanProvoke); + DataCenter.InterruptTarget = DataCenter.AllHostileTargets.FirstOrDefault(ObjectHelper.CanInterrupt); + DataCenter.AllHostileTargets = GetAllHostileTargets(); + DataCenter.FriendlyNPCMembers = GetFriendlyNPCs(); + DataCenter.AllianceMembers = GetAllianceMembers(); + DataCenter.PartyMembers = GetPartyMembers(); UpdateTimeToKill(); } + private static unsafe List GetPartyMembers() + { + return DataCenter.AllianceMembers.Where(ObjectHelper.IsParty) + .Where(b => b.Character()->CharacterData.OnlineStatus != 15 && b.IsTargetable).ToList(); + } + + private static unsafe List GetAllianceMembers() + { + return DataCenter.AllTargets.Where(ObjectHelper.IsAlliance) + .Where(b => b.Character()->CharacterData.OnlineStatus != 15 && b.IsTargetable).ToList(); + } + + private static List GetFriendlyNPCs() + { + // Check if the configuration setting is true + if (!Service.Config.FriendlyBattleNpcHeal && !Service.Config.FriendlyPartyNpcHealRaise2) + { + return []; + } + + try + { + // Ensure Svc.Objects is not null + if (Svc.Objects == null) + { + return []; + } + + // Filter and cast objects safely + var friendlyNpcs = Svc.Objects + .Where(obj => obj != null && obj.ObjectKind == ObjectKind.BattleNpc) + .Where(obj => + { + try + { + return obj.GetNameplateKind() == NameplateKind.FriendlyBattleNPC || + obj.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember; + } + catch (Exception ex) + { + // Log the exception for debugging purposes + Svc.Log.Error($"Error filtering object in get_FriendlyNPCMembers: {ex.Message}"); + return false; + } + }) + .OfType() + .ToList(); + + return friendlyNpcs; + } + catch (Exception ex) + { + // Log the exception for debugging purposes + Svc.Log.Error($"Error in get_FriendlyNPCMembers: {ex.Message}"); + return []; + } + } + + private static List GetAllHostileTargets() + { + var strongOfShieldPositional = EnemyPositional.Front; + + return DataCenter.AllTargets.Where(b => + { + // Check if the target is an enemy and targetable. + if (!b.IsEnemy() || !b.IsTargetable) return false; + + // Check if the target is invincible. + if (b.StatusList.Any(StatusHelper.IsInvincible)) return false; + + // Special exception for Jeuno raid Ark Angels. + if (b.IsWrongEpicFatedVaunted()) return false; + + // Special exception for the Strong of Shield status on Hansel and Gretel. + if (b.HasStatus(true, StatusID.StrongOfShield) && strongOfShieldPositional != b.FindEnemyPositional()) return false; + + // If all checks pass, the target is considered hostile. + return true; + }).ToList(); + } + + private static IBattleChara? GetDeathTarget() + { + // Added so it only tracks deathtarget if you are on a raise job + 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) + { + var deathAll = DataCenter.AllianceMembers.GetDeath(); + + var deathParty = DataCenter.PartyMembers.GetDeath(); + var deathNPC = DataCenter.FriendlyNPCMembers.GetDeath(); + + // Check death in party members + 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(); + + return deathParty.FirstOrDefault(); + } + + // Check death in alliance members + if (deathAll.Any()) + { + 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(); + } + } + + // Check death in friendly NPC members + if (deathNPC.Any() && Service.Config.FriendlyPartyNpcHealRaise2) + { + var deathNPCT = deathNPC.GetJobCategory(JobRole.Tank).ToList(); + var deathNPCH = deathNPC.GetJobCategory(JobRole.Healer).ToList(); + + if (deathNPCT.Count > 1) return deathNPCT.FirstOrDefault(); + if (deathNPCH.Any()) return deathNPCH.FirstOrDefault(); + if (deathNPCT.Any()) return deathNPCT.FirstOrDefault(); + + return deathNPC.FirstOrDefault(); + } + + return null; + } + + return null; + } + + private static IBattleChara? GetDispelTarget() + { + var weakenPeople = DataCenter.PartyMembers? + .Where(o => o is IBattleChara b && b.StatusList != null && + b.StatusList.Any(status => status != null && status.CanDispel())) ?? []; + var weakenNPC = DataCenter.FriendlyNPCMembers? + .Where(o => o is IBattleChara b && b.StatusList != null && + b.StatusList.Any(status => status != null && status.CanDispel())) ?? []; + var dyingPeople = weakenPeople + .Where(o => o is IBattleChara b && b.StatusList != null && + b.StatusList.Any(status => status != null && status.IsDangerous())); + + return dyingPeople.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault() + ?? weakenPeople.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault() + ?? weakenNPC.OrderBy(ObjectHelper.DistanceToPlayer).FirstOrDefault(); + } + private static void UpdateTimeToKill() { var now = DateTime.Now; diff --git a/RotationSolver/Watcher.cs b/RotationSolver/Watcher.cs index 1241d3d2f..02b944d94 100644 --- a/RotationSolver/Watcher.cs +++ b/RotationSolver/Watcher.cs @@ -91,13 +91,13 @@ private static void ActionFromEnemy(ActionEffectSet set) } } - if (set.Header.ActionType == ActionType.Action && DataCenter.PartyMembers.Length >= 4 && set.Action?.Cast100ms > 0) + if (set.Header.ActionType == ActionType.Action && DataCenter.PartyMembers.Count() >= 4 && set.Action?.Cast100ms > 0) { var type = set.Action?.GetActionCate(); if (type is ActionCate.Spell or ActionCate.Weaponskill or ActionCate.Ability) { - int partyMemberCount = DataCenter.PartyMembers.Length; + int partyMemberCount = DataCenter.PartyMembers.Count(); int damageEffectCount = 0; foreach (var effect in set.TargetEffects)