using System; using System.Numerics; using Dalamud.Plugin.Services; namespace QuestionableCompanion.Services; public class MovementMonitorService : IDisposable { private readonly IClientState clientState; private readonly IPluginLog log; private readonly ICommandManager commandManager; private readonly IFramework framework; private readonly Configuration config; private ChauffeurModeService? chauffeurMode; private Vector3 lastPosition = Vector3.Zero; private DateTime lastMovementTime = DateTime.Now; private DateTime lastCheckTime = DateTime.MinValue; private bool isMonitoring; private const float MovementThreshold = 0.1f; public bool IsMonitoring => isMonitoring; public MovementMonitorService(IClientState clientState, IPluginLog log, ICommandManager commandManager, IFramework framework, Configuration config) { this.clientState = clientState; this.log = log; this.commandManager = commandManager; this.framework = framework; this.config = config; log.Information("[MovementMonitor] Service initialized"); } public void SetChauffeurMode(ChauffeurModeService service) { chauffeurMode = service; log.Information("[MovementMonitor] ChauffeurMode service linked for failsafe"); } public void StartMonitoring() { if (!isMonitoring) { isMonitoring = true; lastMovementTime = DateTime.Now; lastCheckTime = DateTime.Now; lastPosition = Vector3.Zero; framework.Update += OnFrameworkUpdate; log.Information("[MovementMonitor] Movement monitoring started"); } } public void StopMonitoring() { if (isMonitoring) { isMonitoring = false; framework.Update -= OnFrameworkUpdate; log.Information("[MovementMonitor] Movement monitoring stopped"); } } public void ResetMovementTimer() { lastMovementTime = DateTime.Now; if (clientState.LocalPlayer != null) { lastPosition = clientState.LocalPlayer.Position; } } private void OnFrameworkUpdate(IFramework framework) { if (!isMonitoring) { return; } DateTime now = DateTime.Now; if ((now - lastCheckTime).TotalSeconds < (double)config.MovementCheckInterval) { return; } lastCheckTime = now; if (clientState.LocalPlayer == null || !clientState.IsLoggedIn) { return; } try { Vector3 currentPosition = clientState.LocalPlayer.Position; if (lastPosition == Vector3.Zero) { lastPosition = currentPosition; lastMovementTime = now; return; } if (Vector3.Distance(lastPosition, currentPosition) > 0.1f) { lastMovementTime = now; lastPosition = currentPosition; return; } double timeSinceMovement = (now - lastMovementTime).TotalSeconds; if (!(timeSinceMovement >= (double)config.MovementStuckThreshold)) { return; } log.Warning("[MovementMonitor] ========================================"); log.Warning("[MovementMonitor] === PLAYER STUCK DETECTED ==="); log.Warning("[MovementMonitor] ========================================"); log.Warning($"[MovementMonitor] No movement for {timeSinceMovement:F1} seconds"); log.Warning($"[MovementMonitor] Position: {currentPosition}"); if (chauffeurMode != null && (chauffeurMode.IsWaitingForHelper || chauffeurMode.IsTransportingQuester)) { log.Warning("[MovementMonitor] FAILSAFE: Resetting Chauffeur Mode due to stuck detection!"); chauffeurMode.ResetChauffeurState(); } log.Warning("[MovementMonitor] Sending /qst reload command..."); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/qst reload"); log.Information("[MovementMonitor] /qst reload command sent"); } catch (Exception ex2) { log.Error("[MovementMonitor] Failed to send reload command: " + ex2.Message); } }, TimeSpan.FromMilliseconds(100L, 0L)); framework.RunOnTick(delegate { try { commandManager.ProcessCommand("/qst start"); log.Information("[MovementMonitor] /qst start command sent"); } catch (Exception ex2) { log.Error("[MovementMonitor] Failed to send start command: " + ex2.Message); } }, TimeSpan.FromSeconds(1L)); lastMovementTime = now; lastPosition = currentPosition; log.Information("[MovementMonitor] Movement timer reset - monitoring continues..."); } catch (Exception ex) { log.Error("[MovementMonitor] Error checking movement: " + ex.Message); } } public void Dispose() { StopMonitoring(); log.Information("[MovementMonitor] Service disposed"); } }