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