233 lines
5.8 KiB
C#
233 lines
5.8 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Numerics;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
|
using FFXIVClientStructs.FFXIV.Common.Math;
|
|
|
|
namespace QuestionableCompanion.Services;
|
|
|
|
public class CharacterSafeWaitService
|
|
{
|
|
private readonly IClientState clientState;
|
|
|
|
private readonly IPluginLog log;
|
|
|
|
private readonly IFramework framework;
|
|
|
|
private readonly ICondition condition;
|
|
|
|
private readonly IGameGui gameGui;
|
|
|
|
public CharacterSafeWaitService(IClientState clientState, IPluginLog log, IFramework framework, ICondition condition, IGameGui gameGui)
|
|
{
|
|
this.clientState = clientState;
|
|
this.log = log;
|
|
this.framework = framework;
|
|
this.condition = condition;
|
|
this.gameGui = gameGui;
|
|
}
|
|
|
|
public unsafe Task<bool> WaitForCharacterFullyLoadedAsync(int timeoutSeconds = 5, int checkIntervalMs = 200)
|
|
{
|
|
return Task.Run(delegate
|
|
{
|
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
log.Information("[CharLoad] Waiting for character to fully load...");
|
|
TimeSpan timeSpan = TimeSpan.FromSeconds(timeoutSeconds);
|
|
while (stopwatch.Elapsed < timeSpan)
|
|
{
|
|
try
|
|
{
|
|
if (Control.GetLocalPlayer() == null || !clientState.IsLoggedIn)
|
|
{
|
|
Thread.Sleep(checkIntervalMs);
|
|
}
|
|
else
|
|
{
|
|
if (!condition[ConditionFlag.BetweenAreas] && !condition[ConditionFlag.BetweenAreas51] && !condition[ConditionFlag.OccupiedInCutSceneEvent])
|
|
{
|
|
stopwatch.Stop();
|
|
log.Information($"[CharLoad] Character fully loaded in {stopwatch.ElapsedMilliseconds}ms!");
|
|
return true;
|
|
}
|
|
Thread.Sleep(checkIntervalMs);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[CharLoad] Error during load check: " + ex.Message);
|
|
Thread.Sleep(checkIntervalMs);
|
|
}
|
|
}
|
|
stopwatch.Stop();
|
|
log.Warning($"[CharLoad] Character load timeout after {stopwatch.ElapsedMilliseconds}ms");
|
|
return false;
|
|
});
|
|
}
|
|
|
|
public bool WaitForCharacterFullyLoaded(int timeoutSeconds = 5)
|
|
{
|
|
return WaitForCharacterFullyLoadedAsync(timeoutSeconds).GetAwaiter().GetResult();
|
|
}
|
|
|
|
public async Task PerformSafeWaitAsync()
|
|
{
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
log.Information("[SafeWait] Starting character stabilization...");
|
|
try
|
|
{
|
|
if (!(await WaitForCharacterFullyLoadedAsync(120)))
|
|
{
|
|
log.Warning("[SafeWait] Character not fully loaded, proceeding anyway");
|
|
}
|
|
await Task.Delay(250);
|
|
await WaitForMovementEndAsync();
|
|
await Task.Delay(250);
|
|
await WaitForActionsCompleteAsync();
|
|
await Task.Delay(500);
|
|
sw.Stop();
|
|
log.Information($"[SafeWait] Character stabilization complete in {sw.ElapsedMilliseconds}ms");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sw.Stop();
|
|
log.Error($"[SafeWait] Error during safe wait after {sw.ElapsedMilliseconds}ms: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public void PerformSafeWait()
|
|
{
|
|
PerformSafeWaitAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private async Task WaitForMovementEndAsync(int maxWaitMs = 5000, int checkIntervalMs = 100)
|
|
{
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
while (sw.ElapsedMilliseconds < maxWaitMs)
|
|
{
|
|
try
|
|
{
|
|
if (!IsPlayerMoving())
|
|
{
|
|
log.Debug($"[SafeWait] Movement ended after {sw.ElapsedMilliseconds}ms");
|
|
return;
|
|
}
|
|
await Task.Delay(checkIntervalMs);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[SafeWait] Error checking movement: " + ex.Message);
|
|
await Task.Delay(checkIntervalMs);
|
|
}
|
|
}
|
|
log.Warning($"[SafeWait] Movement wait timeout after {sw.ElapsedMilliseconds}ms");
|
|
}
|
|
|
|
private void WaitForMovementEnd()
|
|
{
|
|
WaitForMovementEndAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private async Task WaitForActionsCompleteAsync(int maxWaitMs = 10000, int checkIntervalMs = 100)
|
|
{
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
while (sw.ElapsedMilliseconds < maxWaitMs)
|
|
{
|
|
try
|
|
{
|
|
if (!IsPlayerInAction())
|
|
{
|
|
log.Debug($"[SafeWait] Actions completed after {sw.ElapsedMilliseconds}ms");
|
|
return;
|
|
}
|
|
await Task.Delay(checkIntervalMs);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.Error("[SafeWait] Error checking actions: " + ex.Message);
|
|
await Task.Delay(checkIntervalMs);
|
|
}
|
|
}
|
|
log.Warning($"[SafeWait] Action wait timeout after {sw.ElapsedMilliseconds}ms");
|
|
}
|
|
|
|
private void WaitForActionsComplete()
|
|
{
|
|
WaitForActionsCompleteAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private unsafe bool IsPlayerMoving()
|
|
{
|
|
try
|
|
{
|
|
BattleChara* player = Control.GetLocalPlayer();
|
|
if (player == null)
|
|
{
|
|
return false;
|
|
}
|
|
FFXIVClientStructs.FFXIV.Common.Math.Vector3 currentPos = player->Character.GameObject.Position;
|
|
Thread.Sleep(50);
|
|
player = Control.GetLocalPlayer();
|
|
if (player == null)
|
|
{
|
|
return false;
|
|
}
|
|
FFXIVClientStructs.FFXIV.Common.Math.Vector3 newPos = player->Character.GameObject.Position;
|
|
return System.Numerics.Vector3.Distance(currentPos, newPos) > 0.01f;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private unsafe bool IsPlayerInAction()
|
|
{
|
|
try
|
|
{
|
|
BattleChara* player = Control.GetLocalPlayer();
|
|
if (player == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (player->Character.IsCasting)
|
|
{
|
|
log.Debug("[SafeWait] Player is casting");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task PerformQuickSafeWaitAsync()
|
|
{
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
log.Debug("[SafeWait] Performing quick safe wait...");
|
|
try
|
|
{
|
|
await WaitForMovementEndAsync(3000);
|
|
await Task.Delay(1000);
|
|
sw.Stop();
|
|
log.Debug($"[SafeWait] Quick safe wait complete in {sw.ElapsedMilliseconds}ms");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
sw.Stop();
|
|
log.Error("[SafeWait] Error during quick safe wait: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
public void PerformQuickSafeWait()
|
|
{
|
|
PerformQuickSafeWaitAsync().GetAwaiter().GetResult();
|
|
}
|
|
}
|