diff --git a/RotationSolver.Basic/Actions/BaseAction_Target.cs b/RotationSolver.Basic/Actions/BaseAction_Target.cs index 2320c25d1..176815109 100644 --- a/RotationSolver.Basic/Actions/BaseAction_Target.cs +++ b/RotationSolver.Basic/Actions/BaseAction_Target.cs @@ -17,6 +17,11 @@ public partial class BaseAction /// public byte AOECount { private get; init; } = 3; + /// + /// How many time does this ation need the target keep in live. + /// + public float TimeToDie { get; init; } = 0; + /// /// Is this action's target dead? /// @@ -341,8 +346,10 @@ private bool TargetHostileManual(BattleChara b, bool mustUse, int aoeCount, out { if (!mustUse) { + var time = DataCenter.GetDeadTime(b); + //No need to dot. - if (TargetStatus != null && !ObjectHelper.CanDot(b)) return false; + if (TargetStatus != null && !float.IsNaN(time) && time < TimeToDie) return false; //Already has status. if (!CheckStatus(b)) return false; @@ -500,7 +507,11 @@ private IEnumerable TargetFilterFuncEot(IEnumerable ta if (TargetStatus == null || !IsEot) return tars; var dontHave = tars.Where(CheckStatus); - var canDot = dontHave.Where(ObjectHelper.CanDot); + var canDot = dontHave.Where(b => + { + var time = DataCenter.GetDeadTime(b); + return float.IsNaN(time) || time >= TimeToDie; + }); if (mustUse) { diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 7a3d6aa19..439b721ad 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -256,7 +256,7 @@ public enum PluginConfigBool : byte [Default(true)] DrawMeleeOffset, [Default(true)] ShowMoveTarget, - [Default(false)] ShowHealthRatio, + [Default(false)] ShowTargetDeadTime, [Default(true)] ShowTarget, [Default(true)] ChooseAttackMark, [Default(false)] CanAttackMarkAOE, @@ -388,9 +388,8 @@ public enum PluginConfigFloat : byte [Default(0.6f, 0.5f, 0.7f)] CountDownAhead, [Default(24f)] MoveTargetAngle, - [Default(1.85f, 0f, 10f)] HealthRatioBoss, - [Default(0.8f, 0f, 10f)] HealthRatioDying, - [Default(1.2f, 0f, 10f)] HealthRatHealthRatioDotioBoss, + [Default(60, 10f, 1800f)] DeadTimeBoss, + [Default(10, 0f, 60)] DeadTimeDying, [Default(16f, 9.6f, 96f)] CooldownFontSize, @@ -401,8 +400,6 @@ public enum PluginConfigFloat : byte [Default(8f)] ControlProgressHeight, [Default(1.2f, 0f, 30f)] DistanceForMoving, [Default(0.2f, 0.01f, 0.5f)] MaxPing, - - [Default(1.8f)] HealthRatioDot, } public enum PluginConfigVector4 : byte diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index e744296f1..7a7154532 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -6,7 +6,6 @@ using ECommons.GameHelpers; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Fate; -using Lumina.Excel.GeneratedSheets; using Action = Lumina.Excel.GeneratedSheets.Action; using CharacterManager = FFXIVClientStructs.FFXIV.Client.Game.Character.CharacterManager; @@ -14,6 +13,47 @@ namespace RotationSolver.Basic; internal static class DataCenter { + private static readonly TimeSpan CheckSpan = TimeSpan.FromSeconds(2.5); + + /// + /// How many seconds will the target die. + /// + /// + /// whole time to die. + /// + internal static float GetDeadTime(BattleChara b, bool wholeTime = false) + { + if (b == null) return float.NaN; + var objectId = b.ObjectId; + + DateTime startTime = DateTime.MinValue; + float thatTimeRatio = 0; + foreach (var (time, hpRatios) in RecordedHP) + { + if(hpRatios.TryGetValue(objectId, out var ratio) && ratio != 1) + { + startTime = time; + thatTimeRatio = ratio; + break; + } + } + + var timespan = DateTime.Now - startTime; + if(startTime == DateTime.MinValue || timespan < CheckSpan) return float.NaN; + + var ratioNow = b.GetHealthRatio(); + + var ratioReduce = thatTimeRatio - ratioNow; + if (ratioReduce <= 0) return float.NaN; + + return (float)timespan.TotalSeconds / ratioReduce * (wholeTime ? 1 : ratioNow); + } + /// + /// Only recorded 15s hps. + /// + public const int HP_RECORD_TIME = 150; + internal static Queue<(DateTime time, SortedList hpRatios)> RecordedHP { get; } = new(HP_RECORD_TIME + 1); + internal static bool NoPoslock => Svc.Condition[ConditionFlag.OccupiedInEvent] || !Service.Config.GetValue(Configuration.PluginConfigBool.PoslockCasting) //Key cancel. diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs index fcfcfc837..a32ade1dd 100644 --- a/RotationSolver.Basic/Helpers/ObjectHelper.cs +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -149,8 +149,8 @@ internal static bool CanInterrupt(this BattleChara b) public static bool IsBoss(this BattleChara obj) { if (obj == null) return false; - if (obj.IsDummy() && !Service.Config.GetValue(Configuration.PluginConfigBool.ShowHealthRatio)) return true; - return obj.MaxHp >= GetHealthFromMulty(Service.Config.GetValue(Configuration.PluginConfigFloat.HealthRatioBoss)) + if (obj.IsDummy() && !Service.Config.GetValue(Configuration.PluginConfigBool.ShowTargetDeadTime)) return true; + return DataCenter.GetDeadTime(obj, true) >= Service.Config.GetValue(Configuration.PluginConfigFloat.DeadTimeBoss) || !(obj.GetObjectNPC()?.IsTargetLine ?? true); } @@ -162,8 +162,8 @@ public static bool IsBoss(this BattleChara obj) public static bool IsDying(this BattleChara b) { if (b == null) return false; - if (b.IsDummy() && !Service.Config.GetValue(Configuration.PluginConfigBool.ShowHealthRatio)) return false; - return b.CurrentHp <= GetHealthFromMulty(Service.Config.GetValue(Configuration.PluginConfigFloat.HealthRatioDying)) || b.GetHealthRatio() < 0.02f; + if (b.IsDummy() && !Service.Config.GetValue(Configuration.PluginConfigBool.ShowTargetDeadTime)) return false; + return DataCenter.GetDeadTime(b) <= Service.Config.GetValue(Configuration.PluginConfigFloat.DeadTimeDying) || b.GetHealthRatio() < 0.02f; } /// @@ -174,22 +174,10 @@ public static bool IsDying(this BattleChara b) public static float GetHealthRatio(this BattleChara b) { if (b == null) return 0; - if(DataCenter.RefinedHP.TryGetValue(b.ObjectId, out var hp)) return hp; + if (DataCenter.RefinedHP.TryGetValue(b.ObjectId, out var hp)) return hp; return (float)b.CurrentHp / b.MaxHp; } - /// - /// Can use dot on the target. - /// - /// - /// - public static bool CanDot(this BattleChara b) - { - if (b == null) return false; - if (b.IsDummy() && !Service.Config.GetValue(Configuration.PluginConfigBool.ShowHealthRatio)) return true; - return b.CurrentHp >= GetHealthFromMulty(Service.Config.GetValue(Configuration.PluginConfigFloat.HealthRatioDot)); - } - internal static EnemyPositional FindEnemyPositional(this GameObject enemy) { Vector3 pPosition = enemy.Position; @@ -206,31 +194,31 @@ internal static EnemyPositional FindEnemyPositional(this GameObject enemy) return EnemyPositional.Flank; } - internal static uint GetHealthFromMulty(float mult) - { - if (!Player.Available) return 0; - - var role = Service.GetSheet().GetRow( - Player.Object.ClassJob.Id).GetJobRole(); - float multi = mult * role switch - { - JobRole.Tank => 1, - JobRole.Healer => 1.6f, - _ => 1.5f, - }; - - var partyCount = DataCenter.PartyMembers.Count(); - if (partyCount > 4) - { - multi *= 6.4f; - } - else if (partyCount > 1) - { - multi *= 3.5f; - } - - return (uint)(multi * Player.Object.MaxHp); - } + //internal static uint GetHealthFromMulty(float mult) + //{ + // if (!Player.Available) return 0; + + // var role = Service.GetSheet().GetRow( + // Player.Object.ClassJob.Id).GetJobRole(); + // float multi = mult * role switch + // { + // JobRole.Tank => 1, + // JobRole.Healer => 1.6f, + // _ => 1.5f, + // }; + + // var partyCount = DataCenter.PartyMembers.Count(); + // if (partyCount > 4) + // { + // multi *= 6.4f; + // } + // else if (partyCount > 1) + // { + // multi *= 3.5f; + // } + + // return (uint)(multi * Player.Object.MaxHp); + //} /// /// The distance from to the player diff --git a/RotationSolver/Localization/ConfigTranslation.cs b/RotationSolver/Localization/ConfigTranslation.cs index e7042f126..6349644e4 100644 --- a/RotationSolver/Localization/ConfigTranslation.cs +++ b/RotationSolver/Localization/ConfigTranslation.cs @@ -120,12 +120,12 @@ internal static class ConfigTranslation PluginConfigBool.TargetFatePriority => LocalizationManager.RightLang.ConfigWindow_Param_TargetFatePriority, PluginConfigBool.TargetHuntingRelicLevePriority => LocalizationManager.RightLang.ConfigWindow_Param_TargetHuntingRelicLevePriority, PluginConfigBool.TargetQuestPriority => LocalizationManager.RightLang.ConfigWindow_Param_TargetQuestPriority, + PluginConfigBool.ShowTargetDeadTime => LocalizationManager.RightLang.ConfigWindow_Param_ShowTargetDeadTime, // extra PluginConfigBool.SayOutStateChanged => LocalizationManager.RightLang.ConfigWindow_Param_SayOutStateChanged, PluginConfigBool.PoslockCasting => LocalizationManager.RightLang.ConfigWindow_Param_PoslockCasting, - PluginConfigBool.ShowHealthRatio => LocalizationManager.RightLang.ConfigWindow_Param_ShowHealthRatio, PluginConfigBool.ShowTooltips => LocalizationManager.RightLang.ConfigWindow_Param_ShowTooltips, PluginConfigBool.InDebug => LocalizationManager.RightLang.ConfigWindow_Param_InDebug, PluginConfigBool.AutoOpenChest => "Auto Open the treasure chest", @@ -179,10 +179,9 @@ internal static class ConfigTranslation PluginConfigFloat.HealthHealerRatio => LocalizationManager.RightLang.ConfigWindow_Param_HealthHealerRatio, PluginConfigFloat.HealthTankRatio => LocalizationManager.RightLang.ConfigWindow_Param_HealthTankRatio, - // extra - PluginConfigFloat.HealthRatioBoss => LocalizationManager.RightLang.ConfigWindow_Param_HealthRatioBoss, - PluginConfigFloat.HealthRatioDying => LocalizationManager.RightLang.ConfigWindow_Param_HealthRatioDying, - PluginConfigFloat.HealthRatioDot => LocalizationManager.RightLang.ConfigWindow_Param_HealthRatioDot, + // target + PluginConfigFloat.DeadTimeBoss => LocalizationManager.RightLang.ConfigWindow_Param_DeadTimeBoss, + PluginConfigFloat.DeadTimeDying => LocalizationManager.RightLang.ConfigWindow_Param_DeadTimeDying, _ => string.Empty, }; diff --git a/RotationSolver/Localization/Strings.cs b/RotationSolver/Localization/Strings.cs index 0e887f37b..de6e71746 100644 --- a/RotationSolver/Localization/Strings.cs +++ b/RotationSolver/Localization/Strings.cs @@ -122,12 +122,11 @@ internal partial class Strings public string ConfigWindow_Param_ShowTooltips { get; set; } = "Show tooltips"; public string ConfigWindow_Param_InDebug { get; set; } = "Debug Mode"; - public string ConfigWindow_Param_ShowHealthRatio { get; set; } = "Show the health ratio for the check of Boss, Dying, Dot."; + public string ConfigWindow_Param_ShowTargetDeadTime { get; set; } = "Show the targets' dead time."; - public string ConfigWindow_Param_HealthRatioBoss { get; set; } = "If target's max health ratio is higher than this, regard it as Boss."; + public string ConfigWindow_Param_DeadTimeBoss { get; set; } = "If target's whole dead time is higher than this, regard it as Boss."; - public string ConfigWindow_Param_HealthRatioDying { get; set; } = "If target's current health ratio is lower than this, regard it is dying."; - public string ConfigWindow_Param_HealthRatioDot { get; set; } = "If target's current health ratio is higher than this, regard it can be dot."; + public string ConfigWindow_Param_DeadTimeDying { get; set; } = "If target's dead time is lower than this, regard it is dying."; public string ConfigWindow_Param_PoslockModifier { get; set; } = "Set the modifier key to unlock the movement temporary"; public string ConfigWindow_Param_PoslockDescription { get; set; } = "LT is for gamepad player"; public string ConfigWindow_Param_TeachingMode { get; set; } = "Teaching mode"; diff --git a/RotationSolver/UI/PainterManager.cs b/RotationSolver/UI/PainterManager.cs index 364960597..445f9c9ec 100644 --- a/RotationSolver/UI/PainterManager.cs +++ b/RotationSolver/UI/PainterManager.cs @@ -139,9 +139,7 @@ public override void UpdateOnFrame(XIVPainter.XIVPainter painter) ((Drawing3DText)SubItems[i]).Text = string.Empty; } - if (!Service.Config.GetValue(Basic.Configuration.PluginConfigBool.ShowHealthRatio)) return; - - var calHealth = (double)ObjectHelper.GetHealthFromMulty(1); + if (!Service.Config.GetValue(PluginConfigBool.ShowTargetDeadTime)) return; int index = 0; foreach (GameObject t in DataCenter.AllTargets.OrderBy(ObjectHelper.DistanceToPlayer)) @@ -150,7 +148,7 @@ public override void UpdateOnFrame(XIVPainter.XIVPainter painter) var item = (Drawing3DText)SubItems[index++]; - item.Text = $"Health Ratio: {b.CurrentHp / calHealth:F2} / {b.MaxHp / calHealth:F2}"; + item.Text = $"Health Ratio: {DataCenter.GetDeadTime(b):F2}s / {DataCenter.GetDeadTime(b, true):F2}s"; item.Color = HealthRatioColor; item.Position = b.Position; diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 4c89a678b..6b6953f2c 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -458,7 +458,7 @@ private static void DrawAbout() ImGui.PopStyleColor(); var width = ImGui.GetWindowWidth(); - if (IconSet.GetTexture("https://discordapp.com/api/guilds/1064448004498653245/embed.png?style=banner4", out var icon) && TextureButton(icon, width, width)) + if (IconSet.GetTexture("https://discordapp.com/api/guilds/1064448004498653245/embed.png?style=banner2", out var icon) && TextureButton(icon, width, width)) { Util.OpenLink("https://discord.gg/4fECHunam9"); } diff --git a/RotationSolver/UI/RotationConfigWindow_Config.cs b/RotationSolver/UI/RotationConfigWindow_Config.cs index 7bdbda94d..51c8e3347 100644 --- a/RotationSolver/UI/RotationConfigWindow_Config.cs +++ b/RotationSolver/UI/RotationConfigWindow_Config.cs @@ -222,6 +222,8 @@ private static void DrawUI() new ColorEditSearchPlugin(PluginConfigVector4.HoveredBeneficialPositionColor) ), + new CheckBoxSearchPlugin(PluginConfigBool.ShowTargetDeadTime), + new CheckBoxSearchPlugin(PluginConfigBool.ShowTarget, new DragFloatSearchPlugin(PluginConfigFloat.TargetIconSize, 0.002f), new ColorEditSearchPlugin(PluginConfigVector4.TargetColor), @@ -604,12 +606,13 @@ private static void DrawTargetConfig() }), new DragFloatRangeSearchPlugin(PluginConfigFloat.HostileDelayMin, PluginConfigFloat.HostileDelayMax, 0.002f), - - }; private static readonly ISearchable[] _targetHostileSelectSearchable = new ISearchable[] { + new DragFloatSearchPlugin(PluginConfigFloat.DeadTimeBoss, 0.02f), + new DragFloatSearchPlugin(PluginConfigFloat.DeadTimeDying, 0.02f), + new CheckBoxSearchPlugin(PluginConfigBool.OnlyAttackInView), new CheckBoxSearchPlugin(PluginConfigBool.ChangeTargetForFate), new CheckBoxSearchPlugin(PluginConfigBool.TargetFatePriority), @@ -728,14 +731,6 @@ private static void DrawExtra() Action = ActionID.Improvisation }), - new CheckBoxSearchPlugin(PluginConfigBool.ShowHealthRatio, new ISearchable[] - { - new DragFloatSearchPlugin(PluginConfigFloat.HealthRatioBoss, 0.02f), - new DragFloatSearchPlugin(PluginConfigFloat.HealthRatioDying, 0.02f), - new DragFloatSearchPlugin(PluginConfigFloat.HealthRatioDot, 0.02f), - - }), - new CheckBoxSearchPlugin(PluginConfigBool.UseStopCasting,new ISearchable[] { new DragFloatRangeSearchPlugin(PluginConfigFloat.StopCastingDelayMin, PluginConfigFloat.StopCastingDelayMin, 0.002f) diff --git a/RotationSolver/Updaters/TargetUpdater.cs b/RotationSolver/Updaters/TargetUpdater.cs index 4579b3e06..34e1cc6bf 100644 --- a/RotationSolver/Updaters/TargetUpdater.cs +++ b/RotationSolver/Updaters/TargetUpdater.cs @@ -26,6 +26,21 @@ internal unsafe static void UpdateTarget() UpdateNamePlate(Svc.Objects.OfType()); } + private static DateTime _lastUpdateDeadTime = DateTime.MinValue; + private static readonly TimeSpan _deadTimeSpan = TimeSpan.FromSeconds(0.1); + private static void UpdateDeadTime(IEnumerable allTargets) + { + var now = DateTime.Now; + if (now - _lastUpdateDeadTime < _deadTimeSpan) return; + + if(DataCenter.RecordedHP.Count >= DataCenter.HP_RECORD_TIME) + { + DataCenter.RecordedHP.Dequeue(); + } + + DataCenter.RecordedHP.Enqueue((now, new SortedList(allTargets.Where(b => b != null && b.CurrentHp != 0).ToDictionary(b => b.ObjectId, b => b.GetHealthRatio())))); + } + internal static void ClearTarget() { var empty = Array.Empty(); @@ -68,7 +83,7 @@ private static float JobRange private unsafe static void UpdateHostileTargets(IEnumerable allTargets) { - DataCenter.AllHostileTargets = allTargets.Where(b => + allTargets = allTargets.Where(b => { if (!b.IsNPCEnemy()) return false; @@ -77,17 +92,27 @@ private unsafe static void UpdateHostileTargets(IEnumerable allTarg if (!b.IsTargetable()) return false; + return true; + }); + + UpdateDeadTime(allTargets); + + DataCenter.AllHostileTargets = allTargets.Where(b => + { if (b.StatusList.Any(StatusHelper.IsInvincible)) return false; + return true; + }); + DataCenter.HostileTargets.Delay(GetHostileTargets(DataCenter.AllHostileTargets.Where(b => + { if (Service.Config.GetValue(PluginConfigBool.OnlyAttackInView)) { if (!Svc.GameGui.WorldToScreen(b.Position, out _)) return false; } return true; - }); + }))); - DataCenter.HostileTargets.Delay(GetHostileTargets(DataCenter.AllHostileTargets)); DataCenter.CanInterruptTargets.Delay(DataCenter.HostileTargets.Where(ObjectHelper.CanInterrupt)); DataCenter.TarOnMeTargets = DataCenter.HostileTargets.Where(tar => tar.TargetObjectId == Player.Object.ObjectId);