1
0
Fork 0
forked from aly/qstbak
qstbak/Questionable/Questionable.Controller.Steps.Shared/WarpInteract.cs
2026-08-17 20:29:32 +10:00

207 lines
6.1 KiB
C#

using System;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.GameUi;
using Questionable.Functions;
using SmartNav;
namespace Questionable.Controller.Steps.Shared;
internal static class WarpInteract
{
internal sealed record Task(uint NpcDataId, uint WarpRowId, ushort SourceTerritoryId, ushort DestTerritoryId) : ITask
{
public bool ShouldRedoOnInterrupt()
{
return true;
}
public override string ToString()
{
return $"WarpInteract({NpcDataId}, warp={WarpRowId}, {SourceTerritoryId}->{DestTerritoryId})";
}
}
internal sealed class DoWarpInteract(GameFunctions gameFunctions, InteractionUiController interactionUiController, ICondition condition, IClientState clientState, IObjectTable objectTable, WarpFailureTracker warpFailureTracker, ILogger<DoWarpInteract> logger) : TaskExecutor<Task>(), IConditionChangeAware, ITaskExecutor, IStoppableTaskExecutor
{
private enum EWarpPhase
{
None,
Unmounting,
Interacting,
WaitingForSettle
}
private EWarpPhase _phase;
private Vector3 _startPosition;
private long _interactingStartedAt;
private long _settleAt;
private bool _abortToReroute;
protected override bool Start()
{
_phase = EWarpPhase.None;
_abortToReroute = false;
_startPosition = objectTable.LocalPlayer?.Position ?? Vector3.Zero;
warpFailureTracker.RecordAttempt(base.Task.WarpRowId);
if (warpFailureTracker.IsBlacklisted(base.Task.WarpRowId))
{
logger.LogWarning("WarpInteract: warp {WarpRowId} blacklisted after repeated failures, retrying step to re-route", base.Task.WarpRowId);
_abortToReroute = true;
return true;
}
interactionUiController.SmartNavWarpRowId = base.Task.WarpRowId;
interactionUiController.SmartNavDestTerritoryId = base.Task.DestTerritoryId;
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.NpcDataId);
if (gameObject == null)
{
logger.LogDebug("WarpInteract: NPC with DataId {DataId} not found yet, waiting", base.Task.NpcDataId);
return true;
}
if (condition[ConditionFlag.Mounted])
{
logger.LogDebug("WarpInteract: Unmounting before warp interaction");
gameFunctions.Unmount();
_phase = EWarpPhase.Unmounting;
return true;
}
if (gameFunctions.InteractWith(gameObject))
{
logger.LogDebug("WarpInteract: Interacting with NPC {DataId}", base.Task.NpcDataId);
_phase = EWarpPhase.Interacting;
_interactingStartedAt = Environment.TickCount64;
}
return true;
}
public override ETaskResult Update()
{
if (_abortToReroute)
{
ClearContext();
return ETaskResult.RetryStep;
}
switch (_phase)
{
case EWarpPhase.Unmounting:
{
if (condition[ConditionFlag.Mounted])
{
return ETaskResult.StillRunning;
}
IGameObject gameObject2 = gameFunctions.FindObjectByDataId(base.Task.NpcDataId);
if (gameObject2 == null)
{
logger.LogDebug("WarpInteract: NPC not found after unmount, waiting");
_phase = EWarpPhase.None;
return ETaskResult.StillRunning;
}
if (gameFunctions.InteractWith(gameObject2))
{
logger.LogDebug("WarpInteract: Interacting with NPC {DataId} after unmount", base.Task.NpcDataId);
_phase = EWarpPhase.Interacting;
_interactingStartedAt = Environment.TickCount64;
}
return ETaskResult.StillRunning;
}
case EWarpPhase.Interacting:
{
if (clientState.TerritoryType != base.Task.SourceTerritoryId)
{
logger.LogDebug("WarpInteract: Territory changed to {Territory}, warp successful", clientState.TerritoryType);
warpFailureTracker.RecordSuccess(base.Task.WarpRowId);
_phase = EWarpPhase.WaitingForSettle;
_settleAt = Environment.TickCount64 + 500;
return ETaskResult.StillRunning;
}
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer != null && Vector3.Distance(localPlayer.Position, _startPosition) > 2f)
{
logger.LogDebug("WarpInteract: Position changed significantly, same-zone warp detected");
warpFailureTracker.RecordSuccess(base.Task.WarpRowId);
_phase = EWarpPhase.WaitingForSettle;
_settleAt = Environment.TickCount64 + 500;
return ETaskResult.StillRunning;
}
if (Environment.TickCount64 - _interactingStartedAt > 30000)
{
logger.LogWarning("WarpInteract: Timed out waiting for warp transition, retrying step");
ClearContext();
return ETaskResult.RetryStep;
}
return ETaskResult.StillRunning;
}
case EWarpPhase.WaitingForSettle:
if (Environment.TickCount64 >= _settleAt)
{
logger.LogDebug("WarpInteract: Warp complete");
ClearContext();
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
case EWarpPhase.None:
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(base.Task.NpcDataId);
if (gameObject == null)
{
return ETaskResult.StillRunning;
}
_startPosition = objectTable.LocalPlayer?.Position ?? _startPosition;
if (condition[ConditionFlag.Mounted])
{
gameFunctions.Unmount();
_phase = EWarpPhase.Unmounting;
return ETaskResult.StillRunning;
}
if (gameFunctions.InteractWith(gameObject))
{
logger.LogDebug("WarpInteract: Interacting with NPC {DataId}", base.Task.NpcDataId);
_phase = EWarpPhase.Interacting;
_interactingStartedAt = Environment.TickCount64;
}
return ETaskResult.StillRunning;
}
default:
return ETaskResult.StillRunning;
}
}
public void OnConditionChange(ConditionFlag flag, bool value)
{
bool flag2 = _phase == EWarpPhase.Interacting;
if (flag2)
{
bool flag3 = (uint)(flag - 31) <= 1u;
flag2 = flag3;
}
if (flag2 && value)
{
logger.LogDebug("WarpInteract: Interaction confirmed via condition flag");
}
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
public void StopNow()
{
ClearContext();
}
private void ClearContext()
{
interactionUiController.SmartNavWarpRowId = null;
interactionUiController.SmartNavDestTerritoryId = null;
}
}
}