From 653a50c4a22e762c594acafe0c56ea4ed469ebcc Mon Sep 17 00:00:00 2001 From: LTS-FFXIV <127939494+LTS-FFXIV@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:17:53 -0600 Subject: [PATCH] new prioritization system in the Auto menu to sort which states are prioritized over others when multiple states are active at once. added logic for instant self esuna on BRD, adjusted some MNK logic to clean up badclicks --- BasicRotations/Melee/MNK_Default.cs | 3 +- BasicRotations/Ranged/BRD_Default.cs | 5 + Resources/AnimationLockTime.json | 11 +- Resources/HostileCastingArea.json | 19 +- RotationSolver.Basic/Configuration/Configs.cs | 5 + RotationSolver.Basic/Data/AutoStatus.cs | 24 +- .../Rotations/Basic/MonkRotation.cs | 24 +- .../Rotations/CustomRotation_GCD.cs | 8 +- RotationSolver/UI/RotationConfigWindow.cs | 71 +++ .../UI/RotationConfigWindow_Config.cs | 3 +- RotationSolver/Updaters/StateUpdater.cs | 404 ++++++++++++------ 11 files changed, 421 insertions(+), 156 deletions(-) diff --git a/BasicRotations/Melee/MNK_Default.cs b/BasicRotations/Melee/MNK_Default.cs index 5575800ed..bff52edf8 100644 --- a/BasicRotations/Melee/MNK_Default.cs +++ b/BasicRotations/Melee/MNK_Default.cs @@ -179,7 +179,6 @@ 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 - // i'm clever and i can do kame hame ha, so i won't stand still and keep refreshing form shift if (EnlightenmentPvE.CanUse(out act, skipAoeCheck: HowlingSingle)) return true; // Enlightment if (HowlingFistPvE.CanUse(out act, skipAoeCheck: HowlingSingle)) return true; // Howling Fist @@ -267,7 +266,7 @@ protected override bool GeneralGCD(out IAction? act) if (OpoOpoForm(out act)) return true; // out of range or nothing to do, recharge chakra first - if (Chakra < 5 && (ForbiddenMeditationPvE.CanUse(out act) || SteeledMeditationPvE.CanUse(out act))) return true; + if (Chakra < 5 && (EnlightenedMeditationPvE.CanUse(out act) || ForbiddenMeditationPvE.CanUse(out act))) return true; // out of range or nothing to do, refresh buff second, but dont keep refreshing or it draws too much attention if (AutoFormShift && !Player.HasStatus(true, StatusID.PerfectBalance) && !Player.HasStatus(true, StatusID.FormlessFist) && FormShiftPvE.CanUse(out act)) return true; // Form Shift GCD use diff --git a/BasicRotations/Ranged/BRD_Default.cs b/BasicRotations/Ranged/BRD_Default.cs index 3d6446b6b..94d9f14ee 100644 --- a/BasicRotations/Ranged/BRD_Default.cs +++ b/BasicRotations/Ranged/BRD_Default.cs @@ -53,6 +53,11 @@ public sealed class BRD_Default : BardRotation #region oGCD Logic protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) { + if (Player.HasStatus(true, StatusID.Doom)) + { + if (TheWardensPaeanPvE.CanUse(out act)) return true; + } + if (nextGCD.IsTheSameTo(true, StraightShotPvE, VenomousBitePvE, WindbitePvE, IronJawsPvE)) { return base.EmergencyAbility(nextGCD, out act); diff --git a/Resources/AnimationLockTime.json b/Resources/AnimationLockTime.json index ec5ac2b88..fdcbc3fa4 100644 --- a/Resources/AnimationLockTime.json +++ b/Resources/AnimationLockTime.json @@ -116,6 +116,7 @@ "273": 0.6, "290": 0.6, "295": 0.6, + "1533": 3.1, "1694": 0.1, "1695": 2.1, "2240": 0.6, @@ -362,6 +363,7 @@ "7910": 0.6, "7911": 0.6, "8166": 2.1, + "8324": 0.6, "8341": 0.1, "8570": 0.1, "8701": 2.1, @@ -503,6 +505,7 @@ "16555": 0.6, "16556": 0.6, "16557": 0.6, + "16558": 0.6, "16559": 0.6, "16766": 0.6, "16889": 0.6, @@ -753,8 +756,8 @@ "28901": 2.1, "28917": 2.1, "28924": 2.1, - "29054": 0.46499997, - "29056": 0.24499997, + "29054": 0.46599996, + "29056": 0.24599996, "29223": 0.1, "29224": 0.1, "29226": 0.6, @@ -787,7 +790,7 @@ "29484": 0.24099997, "29485": 0.4190002, "29709": 0.1, - "29711": 0.37399998, + "29711": 0.37699994, "29716": 0.1, "29731": 0.1, "29732": 0.6, @@ -898,7 +901,7 @@ "40109": 2.6, "40110": 2.6, "40111": 2.6, - "41467": 0.112999976, + "41467": 0.556, "41494": 0.1, "41496": 0.1, "41498": 0.15499976, diff --git a/Resources/HostileCastingArea.json b/Resources/HostileCastingArea.json index 37b9ff28c..074fb55c3 100644 --- a/Resources/HostileCastingArea.json +++ b/Resources/HostileCastingArea.json @@ -700,5 +700,22 @@ 37846, 14334, 14349, - 22096 + 22096, + 36608, + 36607, + 33243, + 21845, + 4863, + 37339, + 22214, + 21937, + 9407, + 35990, + 6092, + 6190, + 4198, + 4205, + 40516, + 40522, + 40509 ] \ No newline at end of file diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 4dfff96be..de6d8178f 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -25,6 +25,11 @@ public const string List2 = "List2", Debug = "Debug"; + public List AutoStatusOrder { get; set; } = Enum.GetValues(typeof(AutoStatus)) + .Cast() + .Where(status => status != AutoStatus.None) + .ToList(); + public const int CurrentVersion = 12; public int Version { get; set; } = CurrentVersion; diff --git a/RotationSolver.Basic/Data/AutoStatus.cs b/RotationSolver.Basic/Data/AutoStatus.cs index 87daf8acf..651325cec 100644 --- a/RotationSolver.Basic/Data/AutoStatus.cs +++ b/RotationSolver.Basic/Data/AutoStatus.cs @@ -26,45 +26,45 @@ public enum AutoStatus : uint /// Provoke = 1 << 2, + /// + /// We should dispel. + /// + Dispel = 1 << 3, + /// /// We should use defense single. /// - DefenseSingle = 1 << 3, + DefenseSingle = 1 << 4, /// /// We should use defense area. /// - DefenseArea = 1 << 4, + DefenseArea = 1 << 5, /// /// We should heal single by ability. /// - HealSingleAbility = 1 << 5, + HealSingleAbility = 1 << 6, /// /// We should heal single by spell. /// - HealSingleSpell = 1 << 6, + HealSingleSpell = 1 << 7, /// /// We should heal area by ability. /// - HealAreaAbility = 1 << 7, + HealAreaAbility = 1 << 8, /// /// We should heal area by spell. /// - HealAreaSpell = 1 << 8, + HealAreaSpell = 1 << 9, /// /// We should raise. /// - Raise = 1 << 9, - - /// - /// We should dispel. - /// - Dispel = 1 << 10, + Raise = 1 << 10, /// /// We should use positional abilities. diff --git a/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs b/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs index 066c38be4..2c03c8dbb 100644 --- a/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/MonkRotation.cs @@ -78,12 +78,12 @@ static partial void ModifySnapPunchPvE(ref ActionSetting setting) static partial void ModifySteeledMeditationPvE(ref ActionSetting setting) { - + setting.ActionCheck = () => Chakra < 5; } static partial void ModifySteelPeakPvE(ref ActionSetting setting) { - setting.ActionCheck = () => InCombat && Chakra >= 5; + setting.ActionCheck = () => InCombat && Chakra == 5; setting.UnlockedByQuestID = 66094; } @@ -129,12 +129,12 @@ static partial void ModifyThunderclapPvE(ref ActionSetting setting) static partial void ModifyInspiritedMeditationPvE(ref ActionSetting setting) { - + setting.ActionCheck = () => Chakra < 5; } static partial void ModifyHowlingFistPvE(ref ActionSetting setting) { - setting.ActionCheck = () => InCombat && Chakra >= 5; + setting.ActionCheck = () => InCombat && Chakra == 5; setting.UnlockedByQuestID = 66599; setting.CreateConfig = () => new ActionConfig() { @@ -183,12 +183,12 @@ static partial void ModifyFormShiftPvE(ref ActionSetting setting) static partial void ModifyForbiddenMeditationPvE(ref ActionSetting setting) { - + setting.ActionCheck = () => Chakra < 5; } static partial void ModifyTheForbiddenChakraPvE(ref ActionSetting setting) { - setting.ActionCheck = () => InCombat && Chakra >= 5; + setting.ActionCheck = () => InCombat && Chakra == 5; setting.UnlockedByQuestID = 67564; } @@ -245,6 +245,10 @@ static partial void ModifyRiddleOfEarthPvE(ref ActionSetting setting) static partial void ModifyEarthsReplyPvE(ref ActionSetting setting) { setting.StatusNeed = [StatusID.EarthsRumination]; + setting.CreateConfig = () => new ActionConfig() + { + AoeCount = 1, + }; } static partial void ModifyRiddleOfFirePvE(ref ActionSetting setting) @@ -277,21 +281,21 @@ static partial void ModifyRiddleOfWindPvE(ref ActionSetting setting) static partial void ModifyEnlightenedMeditationPvE(ref ActionSetting setting) { - + setting.ActionCheck = () => Chakra < 5; } static partial void ModifyEnlightenmentPvE(ref ActionSetting setting) { - setting.ActionCheck = () => InCombat && Chakra >= 5; + setting.ActionCheck = () => InCombat && Chakra == 5; setting.CreateConfig = () => new ActionConfig() { - AoeCount = 1, + AoeCount = 3, }; } static partial void ModifySixsidedStarPvE(ref ActionSetting setting) { - setting.ActionCheck = () => InCombat && Chakra >= 5; + setting.ActionCheck = () => InCombat && Chakra >= 1; setting.StatusProvide = [StatusID.SixsidedStar]; } diff --git a/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs b/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs index 2922bc281..ca5de595a 100644 --- a/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs +++ b/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs @@ -29,6 +29,10 @@ partial class CustomRotation if (MyInterruptGCD(out act)) return act; } + IBaseAction.TargetOverride = TargetType.Dispel; + if (DataCenter.MergedStatus.HasFlag(AutoStatus.Dispel) + && DispelGCD(out act)) return act; + IBaseAction.TargetOverride = TargetType.Death; if (Service.Config.RaisePlayerFirst) @@ -81,10 +85,6 @@ partial class CustomRotation if (DataCenter.MergedStatus.HasFlag(AutoStatus.DefenseSingle) && DefenseSingleGCD(out act)) return act; - IBaseAction.TargetOverride = TargetType.Dispel; - if (DataCenter.MergedStatus.HasFlag(AutoStatus.Dispel) - && DispelGCD(out act)) return act; - IBaseAction.TargetOverride = TargetType.Death; if (!Service.Config.RaisePlayerFirst) diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index a9ee8b160..588d132aa 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -758,6 +758,77 @@ private static void DrawAbout() _aboutHeaders.Draw(); } + private void DrawAutoStatusOrderConfig() + { + ImGui.Text("Reorder AutoStatus Priorities:"); + ImGui.Spacing(); + + if (ImGui.Button("Reset to Default")) + { + Service.Config.AutoStatusOrder = Enum.GetValues(typeof(AutoStatus)) + .Cast() + .Where(status => status != AutoStatus.None) + .ToList(); + Service.Config.Save(); + } + ImGui.Spacing(); + + var autoStatusOrder = Service.Config.AutoStatusOrder; + bool orderChanged = false; + + // Begin a child window to contain the list + ImGui.BeginChild("AutoStatusOrderList", new Vector2(0, 200 * Scale), true); + + int itemCount = autoStatusOrder.Count; + + for (int i = 0; i < itemCount; i++) + { + var item = autoStatusOrder[i]; + + // Draw up button + if (ImGuiEx.IconButton(FontAwesomeIcon.ArrowUp, $"##Up{i}") && i > 0) + { + // Swap with the previous item + var temp = autoStatusOrder[i - 1]; + autoStatusOrder[i - 1] = autoStatusOrder[i]; + autoStatusOrder[i] = temp; + orderChanged = true; + } + + ImGui.SameLine(); + + // Draw down button + if (ImGuiEx.IconButton(FontAwesomeIcon.ArrowDown, $"##Down{i}") && i < itemCount - 1) + { + // Swap with the next item + var temp = autoStatusOrder[i + 1]; + autoStatusOrder[i + 1] = autoStatusOrder[i]; + autoStatusOrder[i] = temp; + orderChanged = true; + } + + ImGui.SameLine(); + + // Draw the item + ImGui.Text(item.ToString()); + + // Optionally, add tooltips or additional information + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text($"Priority: {i + 1}"); + ImGui.EndTooltip(); + } + } + + ImGui.EndChild(); + + if (orderChanged) + { + Service.Config.Save(); + } + } + private static readonly CollapsingHeaderGroup _aboutHeaders = new(new() { { UiString.ConfigWindow_About_Macros.GetDescription, DrawAboutMacros }, diff --git a/RotationSolver/UI/RotationConfigWindow_Config.cs b/RotationSolver/UI/RotationConfigWindow_Config.cs index 1ba312803..6fd434552 100644 --- a/RotationSolver/UI/RotationConfigWindow_Config.cs +++ b/RotationSolver/UI/RotationConfigWindow_Config.cs @@ -279,9 +279,10 @@ private static void DrawUI() /// /// Draws the auto section of the configuration window. /// - private static void DrawAuto() + private void DrawAuto() { ImGui.TextWrapped(UiString.ConfigWindow_Auto_Description.GetDescription()); + DrawAutoStatusOrderConfig(); _autoHeader?.Draw(); } diff --git a/RotationSolver/Updaters/StateUpdater.cs b/RotationSolver/Updaters/StateUpdater.cs index c913c0a6f..3f61e6482 100644 --- a/RotationSolver/Updaters/StateUpdater.cs +++ b/RotationSolver/Updaters/StateUpdater.cs @@ -5,13 +5,13 @@ namespace RotationSolver.Updaters; internal static class StateUpdater { private static bool CanUseHealAction => - //PvP - (DataCenter.IsPvP) - //Job - || (DataCenter.Role == JobRole.Healer || Service.Config.UseHealWhenNotAHealer) + // PvP + DataCenter.IsPvP + // Job + || ((DataCenter.Role == JobRole.Healer || Service.Config.UseHealWhenNotAHealer) && Service.Config.AutoHeal && (DataCenter.InCombat && CustomRotation.IsLongerThan(Service.Config.AutoHealTimeToKill) - || Service.Config.HealOutOfCombat); + || Service.Config.HealOutOfCombat)); public static void UpdateState() { @@ -23,12 +23,119 @@ private static AutoStatus StatusFromAutomatic() { AutoStatus status = AutoStatus.None; - if (DataCenter.DeathTarget is not null) + // Get the user-defined order of AutoStatus flags + var autoStatusOrder = Service.Config.AutoStatusOrder; + + foreach (var autoStatus in autoStatusOrder) + { + switch (autoStatus) + { + case AutoStatus.Dispel: + if (ShouldAddDispel()) + status |= AutoStatus.Dispel; + break; + + case AutoStatus.Interrupt: + if (ShouldAddInterrupt()) + status |= AutoStatus.Interrupt; + break; + + case AutoStatus.AntiKnockback: + if (ShouldAddAntiKnockback()) + status |= AutoStatus.AntiKnockback; + break; + + case AutoStatus.Positional: + if (ShouldAddPositional()) + status |= AutoStatus.Positional; + break; + + case AutoStatus.HealAreaAbility: + if (ShouldAddHealAreaAbility()) + status |= AutoStatus.HealAreaAbility; + break; + + case AutoStatus.HealAreaSpell: + if (ShouldAddHealAreaSpell()) + status |= AutoStatus.HealAreaSpell; + break; + + case AutoStatus.HealSingleAbility: + if (ShouldAddHealSingleAbility()) + status |= AutoStatus.HealSingleAbility; + break; + + case AutoStatus.HealSingleSpell: + if (ShouldAddHealSingleSpell()) + status |= AutoStatus.HealSingleSpell; + break; + + case AutoStatus.DefenseArea: + if (ShouldAddDefenseArea()) + status |= AutoStatus.DefenseArea; + break; + + case AutoStatus.DefenseSingle: + if (ShouldAddDefenseSingle()) + status |= AutoStatus.DefenseSingle; + break; + + case AutoStatus.Raise: + if (ShouldAddRaise()) + status |= AutoStatus.Raise; + break; + + case AutoStatus.Provoke: + if (ShouldAddProvoke()) + status |= AutoStatus.Provoke; + break; + + case AutoStatus.TankStance: + if (ShouldAddTankStance()) + status |= AutoStatus.TankStance; + break; + + case AutoStatus.Speed: + if (ShouldAddSpeed()) + status |= AutoStatus.Speed; + break; + + // Add other cases as needed + default: + break; + } + } + + return status; + } + + // Condition methods for each AutoStatus flag + + private static bool ShouldAddDispel() + { + if (DataCenter.DispelTarget != null) { - status |= AutoStatus.Raise; + if (DataCenter.DispelTarget.StatusList.Any(StatusHelper.IsDangerous)) + { + return true; + } + else if (!DataCenter.HasHostilesInRange || Service.Config.DispelAll + || DataCenter.IsPvP) + { + return true; + } } + return false; + } + + private static bool ShouldAddRaise() + { + return DataCenter.DeathTarget != null; + } - if (DataCenter.Role is JobRole.Melee && ActionUpdater.NextGCDAction != null + private static bool ShouldAddPositional() + { + if (DataCenter.Role == JobRole.Melee && ActionUpdater.NextGCDAction != null && Service.Config.AutoUseTrueNorth) { var id = ActionUpdater.NextGCDAction.ID; @@ -36,160 +143,212 @@ private static AutoStatus StatusFromAutomatic() && positional != ActionUpdater.NextGCDAction.Target.Target?.FindEnemyPositional() && (ActionUpdater.NextGCDAction.Target.Target?.HasPositional() ?? false)) { - status |= AutoStatus.Positional; + return true; } } + return false; + } + + private static bool ShouldAddHealAreaAbility() + { + if (!DataCenter.HPNotFull || !CanUseHealAction) + return false; + + var singleAbility = ShouldHealSingle(StatusHelper.SingleHots, + Service.Config.HealthSingleAbility, + Service.Config.HealthSingleAbilityHot); - if (DataCenter.HPNotFull && CanUseHealAction) + var canHealAreaAbility = singleAbility > 2; + + if (DataCenter.PartyMembers.Count() > 2) + { + var ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); + + if (!canHealAreaAbility) + canHealAreaAbility = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference + && DataCenter.PartyMembersAverHP < Lerp(Service.Config.HealthAreaAbility, Service.Config.HealthAreaAbilityHot, ratio); + } + + return canHealAreaAbility; + } + + private static bool ShouldAddHealAreaSpell() + { + if (!DataCenter.HPNotFull || !CanUseHealAction) + return false; + + var singleSpell = ShouldHealSingle(StatusHelper.SingleHots, + Service.Config.HealthSingleSpell, + Service.Config.HealthSingleSpellHot); + + var canHealAreaSpell = singleSpell > 2; + + if (DataCenter.PartyMembers.Count() > 2) + { + var ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); + + if (!canHealAreaSpell) + canHealAreaSpell = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference + && DataCenter.PartyMembersAverHP < Lerp(Service.Config.HealthAreaSpell, Service.Config.HealthAreaSpellHot, ratio); + } + + return canHealAreaSpell; + } + + private static bool ShouldAddHealSingleAbility() + { + if (!DataCenter.HPNotFull || !CanUseHealAction) + return false; + + var onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer + && DataCenter.Role != JobRole.Healer; + + if (onlyHealSelf) + { + return ShouldHealSingle(Player.Object, StatusHelper.SingleHots, + Service.Config.HealthSingleAbility, Service.Config.HealthSingleAbilityHot); + } + else { var singleAbility = ShouldHealSingle(StatusHelper.SingleHots, Service.Config.HealthSingleAbility, Service.Config.HealthSingleAbilityHot); + return singleAbility > 0; + } + } + + private static bool ShouldAddHealSingleSpell() + { + if (!DataCenter.HPNotFull || !CanUseHealAction) + return false; + + var onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer + && DataCenter.Role != JobRole.Healer; + + if (onlyHealSelf) + { + return ShouldHealSingle(Player.Object, StatusHelper.SingleHots, + Service.Config.HealthSingleSpell, Service.Config.HealthSingleSpellHot); + } + else + { var singleSpell = ShouldHealSingle(StatusHelper.SingleHots, Service.Config.HealthSingleSpell, Service.Config.HealthSingleSpellHot); - var onlyHealSelf = Service.Config.OnlyHealSelfWhenNoHealer - && DataCenter.Role != JobRole.Healer; + return singleSpell > 0; + } + } - var canHealSingleAbility = onlyHealSelf ? ShouldHealSingle(Player.Object, StatusHelper.SingleHots, - Service.Config.HealthSingleAbility, Service.Config.HealthSingleAbilityHot) - : singleAbility > 0; + private static bool ShouldAddDefenseArea() + { + if (!DataCenter.InCombat || !Service.Config.UseDefenseAbility) + return false; - var canHealSingleSpell = onlyHealSelf ? ShouldHealSingle(Player.Object, StatusHelper.SingleHots, - Service.Config.HealthSingleSpell, Service.Config.HealthSingleSpellHot) - : singleSpell > 0; + return DataCenter.IsHostileCastingAOE; + } - var canHealAreaAbility = singleAbility > 2; - var canHealAreaSpell = singleSpell > 2; + private static bool ShouldAddDefenseSingle() + { + if (!DataCenter.InCombat || !Service.Config.UseDefenseAbility) + return false; - if (DataCenter.PartyMembers.Count() > 2) + if (DataCenter.Role == JobRole.Healer) + { + if (DataCenter.PartyMembers.Any((tank) => { - //TODO: Beneficial area status. - var ratio = GetHealingOfTimeRatio(Player.Object, StatusHelper.AreaHots); + var attackingTankObj = DataCenter.AllHostileTargets.Where(t => t.TargetObjectId == tank.GameObjectId); - if (!canHealAreaAbility) - canHealAreaAbility = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference && DataCenter.PartyMembersAverHP < Lerp(Service.Config.HealthAreaAbility, Service.Config.HealthAreaAbilityHot, ratio); + if (attackingTankObj.Count() != 1) + return false; - if (!canHealAreaSpell) - canHealAreaSpell = DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference && DataCenter.PartyMembersAverHP < Lerp(Service.Config.HealthAreaSpell, Service.Config.HealthAreaSpellHot, ratio); - } - - if (canHealAreaAbility) - { - status |= AutoStatus.HealAreaAbility; - } - if (canHealAreaSpell) + return DataCenter.IsHostileCastingToTank; + })) { - status |= AutoStatus.HealAreaSpell; - } - if (canHealSingleAbility) - { - status |= AutoStatus.HealSingleAbility; - } - if (canHealSingleSpell) - { - status |= AutoStatus.HealSingleSpell; + return true; } } - if (DataCenter.InCombat) + if (DataCenter.Role == JobRole.Tank) { - if (Service.Config.UseDefenseAbility) - { - if (DataCenter.IsHostileCastingAOE) - { - status |= AutoStatus.DefenseArea; - } - - if (DataCenter.AreHostilesCastingKnockback && Service.Config.UseKnockback) - { - status |= AutoStatus.AntiKnockback; - } - - if (DataCenter.Role == JobRole.Healer) // Healers protecting Tanks. - { - if (DataCenter.PartyMembers.Any((tank) => - { - var attackingTankObj = DataCenter.AllHostileTargets.Where(t => t.TargetObjectId == tank.GameObjectId); - - if (attackingTankObj.Count() != 1) return false; - - return DataCenter.IsHostileCastingToTank; - })) - { - status |= AutoStatus.DefenseSingle; - } - } - - if (DataCenter.Role == JobRole.Tank) // Tank defensive abilties. - { - - 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; - //A lot targets are targeting on me. - if (tarOnMeCount >= Service.Config.AutoDefenseNumber - && Player.Object.GetHealthRatio() <= Service.Config.HealthForAutoDefense - && movingHere && attacked) - { - status |= AutoStatus.DefenseSingle; - } + var movingHere = (float)DataCenter.NumberOfHostilesInRange / DataCenter.NumberOfHostilesInMaxRange > 0.3f; - //Big damage casting action. - if (DataCenter.IsHostileCastingToTank) - { - status |= AutoStatus.DefenseSingle; - } - } - } + 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 (DataCenter.Role == JobRole.Tank - && (Service.Config.AutoProvokeForTank - || DataCenter.AllianceMembers.Count(o => o.IsJobCategory(JobRole.Tank)) < 2) - && DataCenter.ProvokeTarget != null) + if (tarOnMeCount >= Service.Config.AutoDefenseNumber + && Player.Object.GetHealthRatio() <= Service.Config.HealthForAutoDefense + && movingHere && attacked) { - status |= AutoStatus.Provoke; + return true; } - } - if (DataCenter.DispelTarget != null) - { - if (DataCenter.DispelTarget.StatusList.Any(StatusHelper.IsDangerous)) + if (DataCenter.IsHostileCastingToTank) { - status |= AutoStatus.Dispel; - } - else if (!DataCenter.HasHostilesInRange || Service.Config.DispelAll - || (DataCenter.IsPvP)) - { - status |= AutoStatus.Dispel; + return true; } } - if (DataCenter.InterruptTarget != null && Service.Config.InterruptibleMoreCheck) + return false; + } + + private static bool ShouldAddAntiKnockback() + { + if (!DataCenter.InCombat || !Service.Config.UseKnockback) + return false; + + return DataCenter.AreHostilesCastingKnockback; + } + + private static bool ShouldAddProvoke() + { + if (!DataCenter.InCombat) + return false; + + if (DataCenter.Role == JobRole.Tank + && (Service.Config.AutoProvokeForTank + || DataCenter.AllianceMembers.Count(o => o.IsJobCategory(JobRole.Tank)) < 2) + && DataCenter.ProvokeTarget != null) { - status |= AutoStatus.Interrupt; + return true; } - if (Service.Config.AutoTankStance && DataCenter.Role == JobRole.Tank - && !DataCenter.AllianceMembers.Any(t => t.IsJobCategory(JobRole.Tank) && t.CurrentHp != 0 && t.HasStatus(false, StatusHelper.TankStanceStatus)) + return false; + } + + private static bool ShouldAddInterrupt() + { + if (!DataCenter.InCombat) + return false; + + return DataCenter.InterruptTarget != null && Service.Config.InterruptibleMoreCheck; + } + + 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) { - status |= AutoStatus.TankStance; + return true; } - if (DataCenter.IsMoving && DataCenter.NotInCombatDelay && Service.Config.AutoSpeedOutOfCombat) - { - status |= AutoStatus.Speed; - } + return false; + } - return status; + private static bool ShouldAddSpeed() + { + return DataCenter.IsMoving && DataCenter.NotInCombatDelay && Service.Config.AutoSpeedOutOfCombat; } + + // Helper methods used in condition methods + static float GetHealingOfTimeRatio(IBattleChara target, params StatusID[] statusIds) { const float buffWholeTime = 15; @@ -199,7 +358,8 @@ static float GetHealingOfTimeRatio(IBattleChara target, params StatusID[] status return Math.Min(1, buffTime / buffWholeTime); } - static int ShouldHealSingle(StatusID[] hotStatus, float healSingle, float healSingleHot) => DataCenter.PartyMembers.Count(p => ShouldHealSingle(p, hotStatus, healSingle, healSingleHot)); + static int ShouldHealSingle(StatusID[] hotStatus, float healSingle, float healSingleHot) + => DataCenter.PartyMembers.Count(p => ShouldHealSingle(p, hotStatus, healSingle, healSingleHot)); static bool ShouldHealSingle(IBattleChara target, StatusID[] hotStatus, float healSingle, float healSingleHot) { @@ -271,7 +431,7 @@ private static void AddStatus(ref AutoStatus status, AutoStatus flag, ConditionS private static void AddStatus(ref AutoStatus status, AutoStatus flag, Func getValue) { - if (status.HasFlag(flag) | !getValue()) return; + if (status.HasFlag(flag) || !getValue()) return; status |= flag; }