207 lines
6.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|