Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
refactor: rewrite the base action.
Browse files Browse the repository at this point in the history
  • Loading branch information
ArchiDog1998 committed Jan 21, 2024
1 parent 638afa1 commit ed62c5b
Show file tree
Hide file tree
Showing 7 changed files with 431 additions and 56 deletions.
150 changes: 138 additions & 12 deletions RotationSolver.Basic/Actions/ActionBasicInfo.cs
Original file line number Diff line number Diff line change
@@ -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;
/// <summary>
/// Casting time.
/// </summary>
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; }
/// <summary>
/// Status that this action provides.
/// </summary>
public StatusID[]? StatusProvide { get; set; } = null;

/// <summary>
/// Status that this action needs.
/// </summary>
public StatusID[]? StatusNeed { get; set; } = null;

public Func<bool>? 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;
}
}


158 changes: 158 additions & 0 deletions RotationSolver.Basic/Actions/ActionCooldownInfo.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
///
/// </summary>
public float RecastTimeElapsed => RecastTimeElapsedRaw - DataCenter.WeaponElapsed;

/// <summary>
///
/// </summary>
unsafe float RecastTimeElapsedRaw => CoolDownDetail == null ? 0 : CoolDownDetail->Elapsed;

/// <summary>
///
/// </summary>
public unsafe bool IsCoolingDown => CoolDownDetail != null && CoolDownDetail->IsActive != 0;

private float RecastTimeRemain => RecastTime - RecastTimeElapsedRaw;

/// <summary>
///
/// </summary>
public unsafe ushort MaxCharges => Math.Max(ActionManager.GetMaxCharges(_action.Info.AdjustedID, (uint)Player.Level), (ushort)1);

/// <summary>
///
/// </summary>
public bool HasOneCharge => !IsCoolingDown || RecastTimeElapsedRaw >= RecastTimeOneChargeRaw;

/// <summary>
///
/// </summary>
public ushort CurrentCharges => IsCoolingDown ? (ushort)(RecastTimeElapsedRaw / RecastTimeOneChargeRaw) : MaxCharges;

private float RecastTimeOneChargeRaw => ActionManager.GetAdjustedRecastTime(ActionType.Action, _action.Info.AdjustedID) / 1000f;

/// <summary>
///
/// </summary>
public float RecastTimeRemainOneCharge => RecastTimeRemainOneChargeRaw - DataCenter.WeaponRemain;

float RecastTimeRemainOneChargeRaw => RecastTimeRemain % RecastTimeOneChargeRaw;

/// <summary>
///
/// </summary>
public float RecastTimeElapsedOneCharge => RecastTimeElapsedOneChargeRaw - DataCenter.WeaponElapsed;

float RecastTimeElapsedOneChargeRaw => RecastTimeElapsedRaw % RecastTimeOneChargeRaw;


public ActionCooldownInfo(IBaseActionNew action)
{
_action = action;
CoolDownGroup = _action.Action.GetCoolDownGroup();
}

/// <summary>
///
/// </summary>
/// <param name="gcdCount"></param>
/// <param name="offset"></param>
/// <returns></returns>
public bool ElapsedOneChargeAfterGCD(uint gcdCount = 0, float offset = 0)
=> ElapsedOneChargeAfter(DataCenter.GCDTime(gcdCount, offset));

/// <summary>
///
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public bool ElapsedOneChargeAfter(float time)
=> IsCoolingDown && time <= RecastTimeElapsedOneCharge;

/// <summary>
///
/// </summary>
/// <param name="gcdCount"></param>
/// <param name="offset"></param>
/// <returns></returns>
public bool ElapsedAfterGCD(uint gcdCount = 0, float offset = 0)
=> ElapsedAfter(DataCenter.GCDTime(gcdCount, offset));

/// <summary>
///
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public bool ElapsedAfter(float time)
=> IsCoolingDown && time <= RecastTimeElapsed;

/// <summary>
///
/// </summary>
/// <param name="gcdCount"></param>
/// <param name="offset"></param>
/// <returns></returns>
public bool WillHaveOneChargeGCD(uint gcdCount = 0, float offset = 0)
=> WillHaveOneCharge(DataCenter.GCDTime(gcdCount, offset));

/// <summary>
///
/// </summary>
/// <param name="remain"></param>
/// <returns></returns>
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;
}
}
Loading

0 comments on commit ed62c5b

Please sign in to comment.