From ed62c5bd627e87d191e6c6b27392823eb6a0497f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E6=B0=B4?= <1123993881@qq.com>
Date: Sun, 21 Jan 2024 16:52:09 +0800
Subject: [PATCH] refactor: rewrite the base action.
---
.../Actions/ActionBasicInfo.cs | 150 +++++++++++++++--
.../Actions/ActionCooldownInfo.cs | 158 ++++++++++++++++++
.../Actions/ActionTargetInfo.cs | 51 +++---
RotationSolver.Basic/Actions/BaseActionNew.cs | 99 +++++++++++
.../Actions/BaseAction_ActionInfo.cs | 2 +-
RotationSolver.Basic/Actions/IAction.cs | 12 --
.../Actions/IBaseActionNew.cs | 15 +-
7 files changed, 431 insertions(+), 56 deletions(-)
create mode 100644 RotationSolver.Basic/Actions/ActionCooldownInfo.cs
create mode 100644 RotationSolver.Basic/Actions/BaseActionNew.cs
diff --git a/RotationSolver.Basic/Actions/ActionBasicInfo.cs b/RotationSolver.Basic/Actions/ActionBasicInfo.cs
index 63cd453fa..b3214834d 100644
--- a/RotationSolver.Basic/Actions/ActionBasicInfo.cs
+++ b/RotationSolver.Basic/Actions/ActionBasicInfo.cs
@@ -1,32 +1,158 @@
-namespace RotationSolver.Basic.Actions;
+using ECommons.GameHelpers;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using RotationSolver.Basic.Configuration;
+
+namespace RotationSolver.Basic.Actions;
public struct ActionBasicInfo
{
- private readonly IBaseActionNew _action;
+ internal static readonly uint[] ActionsNoNeedCasting =
+ [
+ 5,
+ (uint)ActionID.PowerfulShotPvP,
+ (uint)ActionID.BlastChargePvP,
+ ];
+ private readonly IBaseActionNew _action;
+ public readonly string Name => _action.Action.Name;
public readonly uint ID => _action.Action.RowId;
+ public readonly uint IconID => ID == (uint)ActionID.SprintPvE ? 104u : _action.Action.Icon;
public readonly uint AdjustedID => (uint)Service.GetAdjustedActionId((ActionID)ID);
- public AttackType AttackType => (AttackType)(_action.Action.AttackType.Value?.RowId ?? byte.MaxValue);
+ public readonly AttackType AttackType => (AttackType)(_action.Action.AttackType.Value?.RowId ?? byte.MaxValue);
+
+ public readonly float AnimationLockTime => OtherConfiguration.AnimationLockTime?.TryGetValue(AdjustedID, out var time) ?? false ? time : 0.6f;
+
+ public readonly byte Level => _action.Action.ClassJobLevel;
+ public readonly bool EnoughLevel => Player.Level >= Level;
+ public readonly bool IsPvP => _action.Action.IsPvP;
+ ///
+ /// Casting time.
+ ///
+ public readonly unsafe float CastTime => ActionManager.GetAdjustedCastTime(ActionType.Action, AdjustedID) / 1000f;
+
+ public readonly bool IsOnSlot
+ {
+ get
+ {
+ if (IsDutyAction)
+ {
+ return DataCenter.DutyActions.Contains(AdjustedID);
+ }
+
+ return IsPvP == DataCenter.Territory?.IsPvpZone;
+ }
+ }
+ public bool IsLimitBreak { get; }
+ public bool IsGeneralGCD { get; }
+ public bool IsRealGCD { get; }
+ public bool IsDutyAction { get; }
+ public Aspect Aspect { get; }
public bool IsFriendly { get; set; }
+ public bool IsEnable { get; set; } = true;
+ internal ActionID[]? ComboIdsNot { get; set; }
- public ActionBasicInfo(IBaseActionNew action)
+ internal ActionID[]? ComboIds { get; set; }
+ ///
+ /// Status that this action provides.
+ ///
+ public StatusID[]? StatusProvide { get; set; } = null;
+
+ ///
+ /// Status that this action needs.
+ ///
+ public StatusID[]? StatusNeed { get; set; } = null;
+
+ public Func? ActionCheck { get; set; } = null;
+
+ public ActionBasicInfo(IBaseActionNew action, bool isDutyAction)
{
_action = action;
-
+ IsGeneralGCD = _action.Action.IsGeneralGCD();
+ IsRealGCD = _action.Action.IsRealGCD();
+ IsLimitBreak = _action.Action.ActionCategory?.Value?.RowId == 9;
+ IsDutyAction = isDutyAction;
+ Aspect = (Aspect)_action.Action.Aspect;
//TODO: better friendly check.
IsFriendly = _action.Action.CanTargetFriendly;
}
-}
-public enum ActionType : byte
-{
- Move,
- Heal,
- Defence,
- Attack,
+ internal readonly bool BasicCheck(bool skipStatusProvideCheck, bool skipCombo, bool ignoreCastingCheck)
+ {
+ if (!IsEnable || !IsOnSlot) return false;
+
+ //Disabled.
+ if (DataCenter.DisabledActionSequencer?.Contains(ID) ?? false) return false;
+
+ if (!EnoughLevel) return false;
+
+ var player = Player.Object;
+
+ if (StatusNeed != null)
+ {
+ if (!player.HasStatus(true, StatusNeed)) return false;
+ }
+
+ if (StatusProvide != null && !skipStatusProvideCheck)
+ {
+ if (player.HasStatus(true, StatusProvide)) return false;
+ }
+
+ if(!skipCombo && IsGeneralGCD)
+ {
+ if (!CheckForCombo()) return false;
+ }
+
+ //Need casting.
+ if (CastTime > 0 && !player.HasStatus(true,
+ [
+ StatusID.Swiftcast,
+ StatusID.Triplecast,
+ StatusID.Dualcast,
+ ])
+ && !ActionsNoNeedCasting.Contains(ID))
+ {
+ //Is knocking back.
+ if (DateTime.Now > DataCenter.KnockbackStart && DateTime.Now < DataCenter.KnockbackFinished) return false;
+
+ if (DataCenter.NoPoslock && DataCenter.IsMoving && !ignoreCastingCheck) return false;
+ }
+
+ if (IsGeneralGCD && StatusProvide?.Length > 0 && IsFriendly && IActionHelper.IsLastGCD(true, _action)
+ && DataCenter.TimeSinceLastAction.TotalSeconds < 3) return false;
+
+ if (!(ActionCheck?.Invoke() ?? true)) return false;
+
+ return true;
+ }
+
+ private readonly bool CheckForCombo()
+ {
+ if (ComboIdsNot != null)
+ {
+ if (ComboIdsNot.Contains(DataCenter.LastComboAction)) return false;
+ }
+
+ var comboActions = (_action.Action.ActionCombo?.Row ?? 0) != 0
+ ? new ActionID[] { (ActionID)_action.Action.ActionCombo!.Row }
+ : [];
+ if (ComboIds != null) comboActions = comboActions.Union(ComboIds).ToArray();
+
+ if (comboActions.Length > 0)
+ {
+ if (comboActions.Contains(DataCenter.LastComboAction))
+ {
+ if (DataCenter.ComboTime < DataCenter.WeaponRemain) return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/RotationSolver.Basic/Actions/ActionCooldownInfo.cs b/RotationSolver.Basic/Actions/ActionCooldownInfo.cs
new file mode 100644
index 000000000..725e4c79d
--- /dev/null
+++ b/RotationSolver.Basic/Actions/ActionCooldownInfo.cs
@@ -0,0 +1,158 @@
+using ECommons.GameHelpers;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using static Dalamud.Interface.Utility.Raii.ImRaii;
+
+namespace RotationSolver.Basic.Actions;
+public readonly struct ActionCooldownInfo
+{
+ private readonly IBaseActionNew _action;
+ public byte CoolDownGroup { get; }
+
+ unsafe RecastDetail* CoolDownDetail => ActionManager.Instance()->GetRecastGroupDetail(CoolDownGroup - 1);
+
+ private unsafe float RecastTime => CoolDownDetail == null ? 0 : CoolDownDetail->Total;
+
+ ///
+ ///
+ ///
+ public float RecastTimeElapsed => RecastTimeElapsedRaw - DataCenter.WeaponElapsed;
+
+ ///
+ ///
+ ///
+ unsafe float RecastTimeElapsedRaw => CoolDownDetail == null ? 0 : CoolDownDetail->Elapsed;
+
+ ///
+ ///
+ ///
+ public unsafe bool IsCoolingDown => CoolDownDetail != null && CoolDownDetail->IsActive != 0;
+
+ private float RecastTimeRemain => RecastTime - RecastTimeElapsedRaw;
+
+ ///
+ ///
+ ///
+ public unsafe ushort MaxCharges => Math.Max(ActionManager.GetMaxCharges(_action.Info.AdjustedID, (uint)Player.Level), (ushort)1);
+
+ ///
+ ///
+ ///
+ public bool HasOneCharge => !IsCoolingDown || RecastTimeElapsedRaw >= RecastTimeOneChargeRaw;
+
+ ///
+ ///
+ ///
+ public ushort CurrentCharges => IsCoolingDown ? (ushort)(RecastTimeElapsedRaw / RecastTimeOneChargeRaw) : MaxCharges;
+
+ private float RecastTimeOneChargeRaw => ActionManager.GetAdjustedRecastTime(ActionType.Action, _action.Info.AdjustedID) / 1000f;
+
+ ///
+ ///
+ ///
+ public float RecastTimeRemainOneCharge => RecastTimeRemainOneChargeRaw - DataCenter.WeaponRemain;
+
+ float RecastTimeRemainOneChargeRaw => RecastTimeRemain % RecastTimeOneChargeRaw;
+
+ ///
+ ///
+ ///
+ public float RecastTimeElapsedOneCharge => RecastTimeElapsedOneChargeRaw - DataCenter.WeaponElapsed;
+
+ float RecastTimeElapsedOneChargeRaw => RecastTimeElapsedRaw % RecastTimeOneChargeRaw;
+
+
+ public ActionCooldownInfo(IBaseActionNew action)
+ {
+ _action = action;
+ CoolDownGroup = _action.Action.GetCoolDownGroup();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool ElapsedOneChargeAfterGCD(uint gcdCount = 0, float offset = 0)
+ => ElapsedOneChargeAfter(DataCenter.GCDTime(gcdCount, offset));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool ElapsedOneChargeAfter(float time)
+ => IsCoolingDown && time <= RecastTimeElapsedOneCharge;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool ElapsedAfterGCD(uint gcdCount = 0, float offset = 0)
+ => ElapsedAfter(DataCenter.GCDTime(gcdCount, offset));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool ElapsedAfter(float time)
+ => IsCoolingDown && time <= RecastTimeElapsed;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool WillHaveOneChargeGCD(uint gcdCount = 0, float offset = 0)
+ => WillHaveOneCharge(DataCenter.GCDTime(gcdCount, offset));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool WillHaveOneCharge(float remain)
+ => HasOneCharge || RecastTimeRemainOneCharge <= remain;
+
+ internal bool CooldownCheck(bool isEmpty, bool onLastAbility, bool ignoreClippingCheck, byte gcdCountForAbility)
+ {
+ if (!_action.Info.IsGeneralGCD)
+ {
+ if (IsCoolingDown)
+ {
+ if (_action.Info.IsRealGCD)
+ {
+ if (!WillHaveOneChargeGCD(0, 0)) return false;
+ }
+ else
+ {
+ if (!HasOneCharge && RecastTimeRemainOneChargeRaw > DataCenter.ActionRemain) return false;
+ }
+ }
+
+ if (!isEmpty)
+ {
+ if (RecastTimeRemain > DataCenter.WeaponRemain + DataCenter.WeaponTotal * gcdCountForAbility)
+ return false;
+ }
+ }
+
+ if (!_action.Info.IsRealGCD)
+ {
+ if (onLastAbility)
+ {
+ if (DataCenter.NextAbilityToNextGCD > _action.Info.AnimationLockTime + DataCenter.Ping + DataCenter.MinAnimationLock) return false;
+ }
+ else if (!ignoreClippingCheck)
+ {
+ if (DataCenter.NextAbilityToNextGCD < _action.Info.AnimationLockTime) return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/RotationSolver.Basic/Actions/ActionTargetInfo.cs b/RotationSolver.Basic/Actions/ActionTargetInfo.cs
index 0a74c2897..b21d43880 100644
--- a/RotationSolver.Basic/Actions/ActionTargetInfo.cs
+++ b/RotationSolver.Basic/Actions/ActionTargetInfo.cs
@@ -15,13 +15,16 @@ public struct ActionTargetInfo
public bool ShouldCheckStatus { get; set; } = true;
public StatusID[]? TargetStatus { get; set; } = null;
public uint StatusGcdCount { get; set; } = 2;
- public byte AoeCount { get; set; }
- public float TimeToKill { get; set; }
+ public byte AoeCount { get; set; } = 3;
+ public float TimeToKill { get; set; } = 0;
public TargetType Type { get; set; }
+ public Func CanTarget { get; set; } = t => true;
+ public float AutoHealRatio { get; set; }
+ public readonly bool TargetArea => _action.Action.TargetArea;
- public readonly float Range => ActionManager.GetActionRange(_action.BasicInfo.ID);
+ public readonly float Range => ActionManager.GetActionRange(_action.Info.ID);
- public readonly float EffectRange => (ActionID)_action.BasicInfo.ID == ActionID.LiturgyOfTheBellPvE ? 20 : _action.Action.EffectRange;
+ public readonly float EffectRange => (ActionID)_action.Info.ID == ActionID.LiturgyOfTheBellPvE ? 20 : _action.Action.EffectRange;
public readonly bool IsSingleTarget => _action.Action.CastType == 1;
@@ -50,7 +53,7 @@ public readonly IEnumerable CanTargets
get
{
_canTargets.Delay(TargetFilter.GetObjectInRadius(DataCenter.AllTargets, Range)
- .Where(GeneralCheck).Where(CanUseTo).Where(InViewTarget));
+ .Where(GeneralCheck).Where(CanUseTo).Where(InViewTarget).Where(CanTarget));
return _canTargets;
}
}
@@ -60,7 +63,7 @@ public readonly IEnumerable CanAffects
get
{
if (EffectRange == 0) return [];
- return TargetFilter.GetObjectInRadius(_action.BasicInfo.IsFriendly
+ return TargetFilter.GetObjectInRadius(_action.Info.IsFriendly
? DataCenter.PartyMembers
: DataCenter.HostileTargets,
Range + EffectRange).Where(GeneralCheck);
@@ -90,8 +93,8 @@ private readonly unsafe bool CanUseTo(GameObject tar)
if (tar == null || !Player.Available) return false;
var tarAddress = tar.Struct();
- if ((ActionID)_action.BasicInfo.ID != ActionID.AethericMimicryPvE
- && !ActionManager.CanUseActionOnTarget(_action.BasicInfo.AdjustedID, tarAddress)) return false;
+ if ((ActionID)_action.Info.ID != ActionID.AethericMimicryPvE
+ && !ActionManager.CanUseActionOnTarget(_action.Info.AdjustedID, tarAddress)) return false;
return tar.CanSee();
}
@@ -112,7 +115,7 @@ private readonly bool CheckStatus(GameObject gameObject)
private readonly bool CheckResistance(GameObject gameObject)
{
- if (_action.BasicInfo.AttackType == AttackType.Magic) //TODO: special attack type resistance.
+ if (_action.Info.AttackType == AttackType.Magic) //TODO: special attack type resistance.
{
if (gameObject.HasStatus(false, StatusID.MagicResistance))
{
@@ -150,7 +153,7 @@ public ActionTargetInfo(IBaseActionNew action)
/// Take a little long time..
///
///
- public readonly TargetResult? FindTarget()
+ internal readonly TargetResult? FindTarget()
{
var range = Range;
var player = Player.Object;
@@ -165,7 +168,7 @@ public ActionTargetInfo(IBaseActionNew action)
if (_action.Action.TargetArea)
{
- return TargetArea(canTargets, canAffects, range, player);
+ return FindTargetArea(canTargets, canAffects, range, player);
}
var targets = GetMostCanTargetObjects(canTargets, canAffects, AoeCount);
@@ -175,28 +178,28 @@ public ActionTargetInfo(IBaseActionNew action)
return new(target, [.. GetAffects(target, canAffects)], target.Position);
}
- private readonly TargetResult? TargetArea(IEnumerable canTargets, IEnumerable canAffects,
+ private readonly TargetResult? FindTargetArea(IEnumerable canTargets, IEnumerable canAffects,
float range, PlayerCharacter player)
{
if (Type is TargetType.Move)
{
- return TargetAreaMove(range);
+ return FindTargetAreaMove(range);
}
- else if (_action.BasicInfo.IsFriendly)
+ else if (_action.Info.IsFriendly)
{
if (!Service.Config.GetValue(PluginConfigBool.UseGroundBeneficialAbility)) return null;
if (!Service.Config.GetValue(PluginConfigBool.UseGroundBeneficialAbilityWhenMoving) && DataCenter.IsMoving) return null;
- return TargetAreaFriend(range, canAffects, player);
+ return FindTargetAreaFriend(range, canAffects, player);
}
else
{
- return TargetAreaHostile(canTargets, canAffects, AoeCount);
+ return FindTargetAreaHostile(canTargets, canAffects, AoeCount);
}
}
- private readonly TargetResult? TargetAreaHostile(IEnumerable canTargets, IEnumerable canAffects, int aoeCount)
+ private readonly TargetResult? FindTargetAreaHostile(IEnumerable canTargets, IEnumerable canAffects, int aoeCount)
{
var target = GetMostCanTargetObjects(canTargets, canAffects, aoeCount)
.OrderByDescending(ObjectHelper.GetHealthRatio).FirstOrDefault();
@@ -204,7 +207,7 @@ public ActionTargetInfo(IBaseActionNew action)
return new(target, [..GetAffects(target, canAffects)], target.Position);
}
- private static TargetResult? TargetAreaMove(float range)
+ private static TargetResult? FindTargetAreaMove(float range)
{
if (Service.Config.GetValue(PluginConfigBool.MoveAreaActionFarthest))
{
@@ -237,7 +240,7 @@ public ActionTargetInfo(IBaseActionNew action)
}
- private readonly TargetResult? TargetAreaFriend(float range, IEnumerable canAffects, PlayerCharacter player)
+ private readonly TargetResult? FindTargetAreaFriend(float range, IEnumerable canAffects, PlayerCharacter player)
{
var strategy = Service.Config.GetValue(PluginConfigInt.BeneficialAreaStrategy);
switch (strategy)
@@ -342,7 +345,7 @@ private readonly IEnumerable GetAffects(GameObject tar, IEnumerable<
private readonly IEnumerable GetMostCanTargetObjects(IEnumerable canTargets, IEnumerable canAffects, int aoeCount)
{
if (IsSingleTarget || EffectRange <= 0) return canTargets;
- if (!_action.BasicInfo.IsFriendly && NoAOE) return [];
+ if (!_action.Info.IsFriendly && NoAOE) return [];
List objectMax = new(canTargets.Count());
@@ -418,7 +421,7 @@ private readonly bool CanGetTarget(GameObject target, GameObject subTarget)
#endregion
#region TargetFind
- private static GameObject? FindTargetByType(IEnumerable gameObjects, TargetType type)
+ private readonly GameObject? FindTargetByType(IEnumerable gameObjects, TargetType type)
{
switch (type) // Filter the objects.
{
@@ -440,7 +443,7 @@ private readonly bool CanGetTarget(GameObject target, GameObject subTarget)
TargetType.Weaken => FindWeakenPeople(),
TargetType.Death => FindDeathPeople(),
TargetType.Move => FindTargetForMoving(),
- TargetType.Heal => FindHealTarget(),
+ TargetType.Heal => FindHealTarget(AutoHealRatio),
TargetType.BeAttacked => FindBeAttackedTarget(),
_ => FindHostile(),
};
@@ -564,10 +567,12 @@ private readonly bool CanGetTarget(GameObject target, GameObject subTarget)
}
}
- GameObject? FindHealTarget()
+ GameObject? FindHealTarget(float healRatio)
{
if (!gameObjects.Any()) return null;
+ gameObjects = gameObjects.Where(o => o.GetHealthRatio() < healRatio);
+
var partyMembers = gameObjects.Where(ObjectHelper.IsParty);
return GeneralHealTarget(partyMembers)
diff --git a/RotationSolver.Basic/Actions/BaseActionNew.cs b/RotationSolver.Basic/Actions/BaseActionNew.cs
new file mode 100644
index 000000000..4c25030f5
--- /dev/null
+++ b/RotationSolver.Basic/Actions/BaseActionNew.cs
@@ -0,0 +1,99 @@
+using ECommons.DalamudServices;
+using ECommons.GameHelpers;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using Action = Lumina.Excel.GeneratedSheets.Action;
+
+namespace RotationSolver.Basic.Actions;
+public class BaseActionNew : IBaseActionNew
+{
+ public TargetResult? Target { get; private set; } = null;
+
+ public Action Action { get; }
+
+ public ActionTargetInfo TargetInfo { get; }
+
+ public ActionBasicInfo Info { get; }
+
+ public ActionCooldownInfo Cooldown { get; }
+
+ public uint ID => Info.ID;
+
+ public uint AdjustedID => Info.AdjustedID;
+
+ public float AnimationLockTime => Info.AnimationLockTime;
+
+ public uint SortKey => Cooldown.CoolDownGroup;
+
+ public bool IsCoolingDown => Cooldown.IsCoolingDown;
+
+
+ public uint IconID => Info.IconID;
+
+ public string Name => Info.Name;
+
+ public string Description => string.Empty;
+
+ public byte Level => Info.Level;
+ public bool IsEnabled { get; set; }
+ public bool IsInCooldown { get; set; }
+
+ public bool EnoughLevel => Info.EnoughLevel;
+ public virtual unsafe uint MPNeed
+ {
+ get
+ {
+ var mp = (uint)ActionManager.GetActionCost(ActionType.Action, AdjustedID, 0, 0, 0, 0);
+ if (mp < 100) return 0;
+ return mp;
+ }
+ }
+
+ public BaseActionNew(ActionID actionID, bool isDutyAction)
+ {
+ Action = Service.GetSheet().GetRow((uint)actionID)!;
+ TargetInfo = new(this);
+ Info = new(this, isDutyAction);
+ Cooldown = new(this);
+ }
+
+ public bool CanUse(out IAction act, bool skipStatusProvideCheck = false, bool skipCombo = false, bool ignoreCastingCheck = false,
+ bool isEmpty = false, bool onLastAbility = false, bool ignoreClippingCheck = false, byte gcdCountForAbility = 0)
+ {
+ act = this!;
+
+ if (!Info.BasicCheck(skipStatusProvideCheck, skipCombo, ignoreCastingCheck)) return false;
+ if (!Cooldown.CooldownCheck(isEmpty, onLastAbility, ignoreClippingCheck, gcdCountForAbility)) return false;
+
+ if (DataCenter.CurrentMp < MPNeed) return false;
+ if (Info.IsFriendly && DataCenter.AverageTimeToKill < TargetInfo.TimeToKill) return false;
+
+ Target = TargetInfo.FindTarget();
+ if (Target == null) return false;
+ return true;
+ }
+
+ public unsafe bool Use()
+ {
+ if (!Target.HasValue) return false;
+
+ var target = Target.Value;
+
+ var adjustId = AdjustedID;
+ if (TargetInfo.TargetArea)
+ {
+ if (adjustId != ID) return false;
+
+ var loc = (FFXIVClientStructs.FFXIV.Common.Math.Vector3)target.Position;
+
+ return ActionManager.Instance()->UseActionLocation(ActionType.Action, ID, Player.Object.ObjectId, &loc);
+ }
+ else if (Svc.Objects.SearchById(target.Target.ObjectId) == null)
+ {
+ return false;
+ }
+ else
+ {
+ return ActionManager.Instance()->UseAction(ActionType.Action, adjustId, target.Target.ObjectId);
+ }
+ }
+}
diff --git a/RotationSolver.Basic/Actions/BaseAction_ActionInfo.cs b/RotationSolver.Basic/Actions/BaseAction_ActionInfo.cs
index 6fe94d44d..2193bf278 100644
--- a/RotationSolver.Basic/Actions/BaseAction_ActionInfo.cs
+++ b/RotationSolver.Basic/Actions/BaseAction_ActionInfo.cs
@@ -35,7 +35,7 @@ public float AutoHealRatio
///
/// The effect range of the action.
///
- public float EffectRange => (ActionID)ID == ActionID.LiturgyOfTheBell_PvE ? 20 : _action?.EffectRange ?? 0;
+ public float EffectRange => (ActionID)ID == ActionID.LiturgyOfTheBellPvE ? 20 : _action?.EffectRange ?? 0;
internal ActionID[] ComboIdsNot { private get; init; } = null;
internal ActionID[] ComboIds { private get; init; } = null;
diff --git a/RotationSolver.Basic/Actions/IAction.cs b/RotationSolver.Basic/Actions/IAction.cs
index 45675a297..dedc1e31a 100644
--- a/RotationSolver.Basic/Actions/IAction.cs
+++ b/RotationSolver.Basic/Actions/IAction.cs
@@ -25,18 +25,6 @@ public interface IAction : ITexture, IEnoughLevel
///
uint SortKey { get; }
- ///
- /// Please don't use it.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal float RecastTimeElapsedRaw { get; }
-
- ///
- /// Please don't use it.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- internal float RecastTimeOneChargeRaw { get; }
-
///
/// Is action cooling down.
///
diff --git a/RotationSolver.Basic/Actions/IBaseActionNew.cs b/RotationSolver.Basic/Actions/IBaseActionNew.cs
index 8900119c3..cdb920287 100644
--- a/RotationSolver.Basic/Actions/IBaseActionNew.cs
+++ b/RotationSolver.Basic/Actions/IBaseActionNew.cs
@@ -1,15 +1,14 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Action = Lumina.Excel.GeneratedSheets.Action;
+using Action = Lumina.Excel.GeneratedSheets.Action;
namespace RotationSolver.Basic.Actions;
-public interface IBaseActionNew
+public interface IBaseActionNew : IAction
{
Action Action { get; }
ActionTargetInfo TargetInfo { get; }
- ActionBasicInfo BasicInfo { get; }
+ ActionBasicInfo Info { get; }
+ ActionCooldownInfo Cooldown { get; }
+
+ bool CanUse(out IAction act, bool skipStatusProvideCheck = false, bool skipCombo = false, bool ignoreCastingCheck = false,
+ bool isEmpty = false, bool onLastAbility = false, bool ignoreClippingCheck = false, byte gcdCountForAbility = 0);
}