282 lines
8.7 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|