qstbak/Questionable/Questionable.Controller.Steps.Shared/AethernetRide.cs
2026-08-19 13:19:57 +10:00

282 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
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.Data;
using Questionable.Functions;
using Questionable.Model;
using SmartNav.Data;
using SmartNav.Model;
using SmartNav.Model.Converter;
namespace Questionable.Controller.Steps.Shared;
internal static class AethernetRide
{
internal sealed record Task(EAetheryteLocation From, EAetheryteLocation To) : ISkippableTask, ITask
{
public override string ToString()
{
return $"UseAethernet({From} -> {To})";
}
}
internal sealed class AethernetRideExecutor(ILogger<AethernetRideExecutor> logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, IClientState clientState, IObjectTable objectTable, AetheryteData aetheryteData, TerritoryData territoryData, AethernetTeleportService aethernetTeleportService, MovementController movementController, ICondition condition) : TaskExecutor<Task>(), IStoppableTaskExecutor, ITaskExecutor
{
private enum EAethernetPhase
{
None,
WaitingForPlayer,
Mounting,
Moving,
Unmounting,
WaitingForTargetable,
Interacting,
WaitingForArrival
}
private EAethernetPhase _phase;
private long _continueAt;
private long _phaseStartedAt;
private int _interactionAttempts;
private const long InteractionRetryMs = 3000L;
private const int MaxInteractionAttempts = 3;
private void SetPhase(EAethernetPhase phase)
{
_phase = phase;
_phaseStartedAt = Environment.TickCount64;
}
public void StopNow()
{
aethernetTeleportService.Reset();
}
public override bool HasTimedOut(float timeoutSeconds)
{
if (_phase == EAethernetPhase.None)
{
return false;
}
return Environment.TickCount64 - _phaseStartedAt > (long)(timeoutSeconds * 1000f);
}
public override void ResetTimeout()
{
base.ResetTimeout();
_phaseStartedAt = Environment.TickCount64;
}
protected override bool Start()
{
SetPhase(EAethernetPhase.None);
_interactionAttempts = 0;
aethernetTeleportService.Reset();
if (aetheryteFunctions.IsAetheryteUnlocked(base.Task.From) && aetheryteFunctions.IsAetheryteUnlocked(base.Task.To))
{
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
if (localPlayer == null)
{
SetPhase(EAethernetPhase.WaitingForPlayer);
return true;
}
Vector3 position = localPlayer.Position;
if (HasArrived(position))
{
return false;
}
StartFromCurrentPosition(position);
return true;
}
if (clientState.TerritoryType == aetheryteData.TerritoryIds[base.Task.To])
{
logger.LogWarning("Aethernet ride not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), skipping as we are already in the destination territory", base.Task.From, base.Task.To);
return false;
}
throw new TaskException($"Aethernet ride not unlocked (from: {base.Task.From}, to: {base.Task.To})");
}
private void StartFromCurrentPosition(Vector3 playerPosition)
{
uint territoryType = clientState.TerritoryType;
float num = (base.Task.From.IsFirmamentAetheryte() ? 11f : (AetheryteConverter.IsLargeAetheryte(base.Task.From) ? 11f : 4f));
if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < num)
{
BeginTeleport();
}
else if (base.Task.From == EAetheryteLocation.SolutionNine)
{
logger.LogDebug("Moving to S9 aetheryte");
int num2 = 4;
List<Vector3> list = new List<Vector3>(num2);
CollectionsMarshal.SetCount(list, num2);
Span<Vector3> span = CollectionsMarshal.AsSpan(list);
span[0] = new Vector3(0f, 8.442986f, 9f);
span[1] = new Vector3(9f, 8.442986f, 0f);
span[2] = new Vector3(-9f, 8.442986f, 0f);
span[3] = new Vector3(0f, 8.442986f, -9f);
Vector3 to = list.MinBy((Vector3 x) => Vector3.Distance(playerPosition, x));
SetPhase(EAethernetPhase.Moving);
movementController.NavigateTo(EMovementType.Quest, (uint)base.Task.From, to, fly: false, sprint: true, 0.25f);
}
else if (territoryData.CanUseMount(territoryType) && aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) > 30f && !gameFunctions.HasStatusPreventingMount() && gameFunctions.Mount())
{
SetPhase(EAethernetPhase.Mounting);
_continueAt = Environment.TickCount64 + 500;
}
else
{
StartMoving();
}
}
private void StartMoving()
{
logger.LogDebug("Moving to aethernet shard");
SetPhase(EAethernetPhase.Moving);
EAetheryteLocation eAetheryteLocation = base.Task.From;
float num = (base.Task.From.IsFirmamentAetheryte() ? 4.4f : ((eAetheryteLocation != EAetheryteLocation.UldahChamberOfRule) ? ((!AetheryteConverter.IsLargeAetheryte(base.Task.From)) ? 6.9f : 10.9f) : 5f));
float value = num;
bool flag = aetheryteData.IsGoldSaucerAetheryte(base.Task.From) && !AetheryteConverter.IsLargeAetheryte(base.Task.From);
movementController.NavigateTo(EMovementType.Quest, (uint)base.Task.From, aetheryteData.Locations[base.Task.From], fly: false, sprint: true, value, flag ? new float?(5f) : ((float?)null));
}
private void BeginTeleport()
{
logger.LogInformation("Initiating aethernet teleport to {Destination} via {From}", base.Task.To, base.Task.From);
if (condition[ConditionFlag.Mounted])
{
gameFunctions.Unmount();
SetPhase(EAethernetPhase.Unmounting);
}
else
{
SetPhase(EAethernetPhase.WaitingForTargetable);
}
}
private bool TryInteractWithShard()
{
IGameObject gameObject = gameFunctions.FindObjectByDataId((uint)base.Task.From, null, warnIfMissing: false);
if (gameObject == null || !gameObject.IsTargetable)
{
return false;
}
aethernetTeleportService.RequestTeleport(base.Task.To);
gameFunctions.InteractWith(gameObject);
_interactionAttempts++;
SetPhase(EAethernetPhase.Interacting);
return true;
}
public override ETaskResult Update()
{
if (Environment.TickCount64 < _continueAt)
{
return ETaskResult.StillRunning;
}
switch (_phase)
{
case EAethernetPhase.WaitingForPlayer:
{
Vector3? vector2 = objectTable.LocalPlayer?.Position;
if (!vector2.HasValue)
{
return ETaskResult.StillRunning;
}
if (HasArrived(vector2.Value))
{
return ETaskResult.TaskComplete;
}
StartFromCurrentPosition(vector2.Value);
return ETaskResult.StillRunning;
}
case EAethernetPhase.Mounting:
if (condition[ConditionFlag.Mounted])
{
StartMoving();
}
return ETaskResult.StillRunning;
case EAethernetPhase.Moving:
if (!movementController.HasBeenMovingForAtLeast(2000))
{
return ETaskResult.StillRunning;
}
if (!movementController.IsPathfinding && !movementController.IsPathRunning)
{
BeginTeleport();
}
return ETaskResult.StillRunning;
case EAethernetPhase.Unmounting:
if (!condition[ConditionFlag.Mounted])
{
SetPhase(EAethernetPhase.WaitingForTargetable);
}
return ETaskResult.StillRunning;
case EAethernetPhase.WaitingForTargetable:
TryInteractWithShard();
return ETaskResult.StillRunning;
case EAethernetPhase.Interacting:
if (aethernetTeleportService.State == AethernetTeleportService.ETeleportState.WaitingForAddon)
{
if (_interactionAttempts < 3 && Environment.TickCount64 - _phaseStartedAt >= 3000)
{
logger.LogWarning("Aethernet menu did not open; retrying interaction ({Attempt}/{Limit})", _interactionAttempts + 1, 3);
aethernetTeleportService.Reset();
SetPhase(EAethernetPhase.WaitingForTargetable);
}
return ETaskResult.StillRunning;
}
if (aethernetTeleportService.State == AethernetTeleportService.ETeleportState.Failed)
{
aethernetTeleportService.Reset();
throw new TaskException("Aethernet teleport failed");
}
SetPhase(EAethernetPhase.WaitingForArrival);
return ETaskResult.StillRunning;
case EAethernetPhase.WaitingForArrival:
{
Vector3? vector = objectTable.LocalPlayer?.Position;
if (!vector.HasValue)
{
return ETaskResult.StillRunning;
}
if (!HasArrived(vector.Value))
{
return ETaskResult.StillRunning;
}
aethernetTeleportService.Reset();
return ETaskResult.TaskComplete;
}
default:
return ETaskResult.StillRunning;
}
}
private bool HasArrived(Vector3 position)
{
if (!aetheryteData.IsAirshipLanding(base.Task.To))
{
return aetheryteData.CalculateDistance(position, clientState.TerritoryType, base.Task.To) <= 20f;
}
return aetheryteData.CalculateAirshipLandingDistance(position, clientState.TerritoryType, base.Task.To) <= 5f;
}
public override bool ShouldInterruptOnDamage()
{
return true;
}
}
}