Skip to content

Commit

Permalink
Merge pull request #120 from FFXIV-CombatReborn/mergeWIP2
Browse files Browse the repository at this point in the history
Sync with vbm 0.0.0.158
  • Loading branch information
CarnifexOptimus authored Jun 5, 2024
2 parents d2d7180 + 78f4a1a commit ee40802
Show file tree
Hide file tree
Showing 18 changed files with 861 additions and 257 deletions.
41 changes: 32 additions & 9 deletions BossMod/ActionTweaks/AnimationLockTweak.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,44 @@
public sealed class AnimationLockTweak
{
private readonly ActionManagerConfig _config = Service.Config.Get<ActionManagerConfig>();
private float _lastReqInitialAnimLock;
private int _lastReqSequence = -1;

public float DelaySmoothing = 0.8f; // TODO tweak
public float DelayAverage { get; private set; } = 0.1f; // smoothed delay between client request and server response
public float DelayEstimate => _config.RemoveAnimationLockDelay ? 0 : MathF.Min(DelayAverage * 1.5f, 0.1f); // this is a conservative estimate

// record initial animation lock after action request
public void RecordRequest(uint expectedSequence, float initialAnimLock)
{
_lastReqInitialAnimLock = initialAnimLock;
_lastReqSequence = (int)expectedSequence;
}

// apply the tweak: calculate animation lock delay and calculate how much animation lock should be reduced
public float Apply(uint sequence, float gamePrevAnimLock, float gameCurrAnimLock, float packetPrevAnimLock, float packetCurrAnimLock, out float delay)
{
delay = _lastReqInitialAnimLock - gamePrevAnimLock;
if (delay < 0)
Service.Log($"[ALT] Prev anim lock {gamePrevAnimLock:f3} is larger than initial {_lastReqInitialAnimLock:f3}, something is wrong");
if (_lastReqSequence != sequence && gameCurrAnimLock != gamePrevAnimLock)
Service.Log($"[ALT] Animation lock updated by action with unexpected sequence ID #{sequence}: {gamePrevAnimLock:f3} -> {gameCurrAnimLock:f3}");

float reduction = 0;
if (_lastReqSequence == sequence && _lastReqInitialAnimLock > 0)
{
SanityCheck(packetPrevAnimLock, packetCurrAnimLock);
DelayAverage = delay * (1 - DelaySmoothing) + DelayAverage * DelaySmoothing; // update the average
// the result will be subtracted from current anim lock (and thus from adjusted lock delay)
reduction = _config.RemoveCooldownDelay ? Math.Clamp(delay /* - DelayMax */, 0, gameCurrAnimLock) : 0;
}
_lastReqInitialAnimLock = 0;
_lastReqSequence = -1;
return reduction;
}

// perform sanity check to detect conflicting plugins: disable the tweak if condition is false
public void SanityCheck(float originalAnimLock, float modifiedAnimLock)
private void SanityCheck(float originalAnimLock, float modifiedAnimLock)
{
if (!_config.RemoveAnimationLockDelay)
return; // nothing to do, tweak is already disabled
Expand All @@ -32,12 +63,4 @@ public void SanityCheck(float originalAnimLock, float modifiedAnimLock)
Service.Log($"[ALT] Unexpected animation lock {originalAnimLock:f} -> {modifiedAnimLock:f}, disabling anim lock tweak feature");
_config.RemoveAnimationLockDelay = false; // disable the tweak (but don't save the config, in case this condition is temporary)
}

// apply tweak: given the delay, calculate how much it should be reduced
public float Apply(float current, float delay)
{
DelayAverage = delay * (1 - DelaySmoothing) + DelayAverage * DelaySmoothing; // update the average
// the result will be subtracted from current anim lock (and thus from adjusted lock delay)
return _config.RemoveCooldownDelay ? Math.Clamp(delay /* - DelayMax */, 0, current) : 0;
}
}
30 changes: 30 additions & 0 deletions BossMod/ActionTweaks/CooldownDelayTweak.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace BossMod;

// Framerate-dependent cooldown reduction.
// Imagine game is running at exactly 100fps (10ms frame time), and action is queued when remaining cooldown is 5ms.
// On next frame (+10ms), cooldown will be reduced and clamped to 0, action will be executed and it's cooldown set to X ms - so next time it can be pressed at X+10 ms.
// If we were running with infinite fps, cooldown would be reduced to 0 and action would be executed slightly (5ms) earlier.
// We can't fix that easily, but at least we can fix the cooldown after action execution - so that next time it can be pressed at X+5ms.
// We do that by reducing actual cooldown by difference between previously-remaining cooldown and frame delta, if action is executed at first opportunity.
public sealed class CooldownDelayTweak
{
private readonly ActionManagerConfig _config = Service.Config.Get<ActionManagerConfig>();

public float Adjustment { get; private set; } // if >0 while using an action, cooldown/anim lock will be reduced by this amount as if action was used a bit in the past

public void StartAdjustment(float prevAnimLock, float prevRemainingCooldown, float dt) => Adjustment = CalculateAdjustment(prevAnimLock, prevRemainingCooldown, dt);
public void StopAdjustment() => Adjustment = 0;

private float CalculateAdjustment(float prevAnimLock, float prevRemainingCooldown, float dt)
{
if (!_config.RemoveCooldownDelay)
return 0; // tweak is disabled, so no adjustment

var maxDelay = Math.Max(prevAnimLock, prevRemainingCooldown);
if (maxDelay <= 0)
return 0; // nothing prevented us from executing the action on previous frame, so no adjustment

var overflow = dt - maxDelay; // both cooldown and animation lock should expire this much before current frame start
return Math.Clamp(overflow, 0, 0.1f); // use upper limit for time adjustment (if you have dogshit fps, adjusting too much could be suspicious)
}
}
45 changes: 45 additions & 0 deletions BossMod/ActionTweaks/RestoreRotationTweak.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace BossMod;

// Preserving character facing direction tweak.
// When any action is executed, character is automatically rotated to face the target (this can be disabled in-game, but it would simply block an action if not facing target instead).
// This makes maintaining uptime during gaze mechanics unnecessarily complicated (requiring either moving or rotating mouse back-and-forth in non-legacy camera mode).
// This feature remembers original rotation before executing an action and then attempts to restore it.
// Just like any 'manual' way, it is not 100% reliable:
// * client rate-limits rotation updates, so even for instant casts there is a short window of time (~0.1s) following action execution when character faces a target on server
// * for movement-affecting abilities (jumps, charges, etc) rotation can't be restored until animation ends
// * for casted abilities, rotation isn't restored until slidecast window starts, as otherwise cast is interrupted
public sealed class RestoreRotationTweak
{
private readonly ActionManagerConfig _config = Service.Config.Get<ActionManagerConfig>();
private Angle _modified; // rotation immediately after action execution; as long as it's not unchanged, we'll try restoring (otherwise we assume player changed facing manually and abort)
private Angle _original; // rotation immediately before action execution; this is what we're trying to restore
private int _numRetries; // for some reason, sometimes after successfully restoring rotation it is snapped back on next frame; in this case we retry again - TODO investigate why this happens
private bool _pending;

public void Preserve(Angle original, Angle modified)
{
if (_config.RestoreRotation && original != modified)
{
_modified = modified;
_original = original;
_numRetries = 2;
_pending = true;
//Service.Log($"[RRT] Restore start: {modified.Rad} -> {original.Rad}");
}
}

public bool TryRestore(Angle current, out Angle updated)
{
//Service.Log($"[RRT] Restore rotation: {current.Rad}: {_modified.Rad}->{_original.Rad}");
updated = _original;
if (!_pending)
return false; // we don't have any pending rotation to restore

if (_modified.AlmostEqual(current, 0.01f))
return true; // we still have the 'post' rotation, try restoring

if (--_numRetries == 0)
_pending = false; // we have unexpected rotation and we're out of retries, stop trying
return false;
}
}
2 changes: 1 addition & 1 deletion BossMod/Autorotation/Autorotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ sealed class Autorotation : IDisposable
public Actor? SecondaryTarget; // this is usually a mouseover, but AI can override; typically used for heal and utility abilities
public AIHints Hints = new();
public float EffAnimLock => ActionManagerEx.Instance!.EffectiveAnimationLock;
public float AnimLockDelay => ActionManagerEx.Instance!.AnimLockTweak.DelayEstimate;
public float AnimLockDelay => ActionManagerEx.Instance!.AnimationLockDelayEstimate;

private static readonly ActionID IDSprintGeneral = new(ActionType.General, 4);

Expand Down
2 changes: 1 addition & 1 deletion BossMod/Autorotation/CommonActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ protected void FillCommonPlayerState(CommonRotation.PlayerState s)
s.TargetingEnemy = Autorot.PrimaryTarget != null && Autorot.PrimaryTarget.Type is ActorType.Enemy or ActorType.Part && !Autorot.PrimaryTarget.IsAlly;
s.RangeToTarget = Autorot.PrimaryTarget != null ? (Autorot.PrimaryTarget.Position - Player.Position).Length() - Autorot.PrimaryTarget.HitboxRadius - Player.HitboxRadius : float.MaxValue;
s.AnimationLock = am.EffectiveAnimationLock;
s.AnimationLockDelay = am.AnimLockTweak.DelayEstimate;
s.AnimationLockDelay = am.AnimationLockDelayEstimate;
s.ComboTimeLeft = am.ComboTimeLeft;
s.ComboLastAction = am.ComboLastMove;
s.LimitBreakLevel = Autorot.WorldState.Party.LimitBreakMax > 0 ? Autorot.WorldState.Party.LimitBreakCur / Autorot.WorldState.Party.LimitBreakMax : 0;
Expand Down
Loading

0 comments on commit ee40802

Please sign in to comment.