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);