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.Plugin.Services; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Movement; using Questionable.Functions; using SmartNav.Data; using SmartNav.Model; namespace Questionable.Controller.Steps.Shared; internal static class AetheryteTeleport { internal sealed record Task(EAetheryteLocation TargetAetheryte, ushort ExpectedTerritoryId) : ISkippableTask, ITask { public override string ToString() { return $"UseAetheryte({TargetAetheryte})"; } } internal sealed class AetheryteTeleportExecutor(ILogger logger, AetheryteFunctions aetheryteFunctions, IClientState clientState, IObjectTable objectTable, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData) : TaskExecutor() { private bool _teleported; private long _retryTeleportAt; private int _teleportAttempts; private const int MaxTeleportAttempts = 3; protected override bool Start() { return true; } public override ETaskResult Update() { if (!_teleported) { if (Environment.TickCount64 < _retryTeleportAt) { return ETaskResult.StillRunning; } _teleported = DoTeleport(); return ETaskResult.StillRunning; } if (clientState.TerritoryType == base.Task.ExpectedTerritoryId) { IPlayerCharacter localPlayer = objectTable.LocalPlayer; if (localPlayer == null || aetheryteData.CalculateDistance(localPlayer.Position, clientState.TerritoryType, base.Task.TargetAetheryte) <= 50f) { ResetProgressTimer(); return ETaskResult.TaskComplete; } } if (base.ProgressContext != null) { if (base.ProgressContext.WasInterrupted()) { logger.LogDebug("Teleport to {Aetheryte} was interrupted", base.Task.TargetAetheryte); return ETaskResult.StillRunning; } if (base.ProgressContext.WasSuccessful()) { ResetProgressTimer(); } } return ETaskResult.StillRunning; } private bool DoTeleport() { if (!aetheryteFunctions.CanTeleport(base.Task.TargetAetheryte)) { if (!aetheryteFunctions.IsTeleportUnlocked()) { throw new TaskException("Teleport is not unlocked, attune to any aetheryte first."); } _retryTeleportAt = Environment.TickCount64 + 1000; logger.LogTrace("Waiting for teleport cooldown..."); return false; } if (!aetheryteFunctions.IsAetheryteUnlocked(base.Task.TargetAetheryte)) { chatGui.PrintError($"Aetheryte {base.Task.TargetAetheryte} is not unlocked.", "Questionable", 576); throw new TaskException("Aetheryte is not unlocked"); } base.ProgressContext = InteractionProgressContext.FromActionUseOrDefault(() => aetheryteFunctions.TeleportAetheryte(base.Task.TargetAetheryte)); if (base.ProgressContext != null) { logger.LogInformation("Travelling via aetheryte..."); return true; } if (++_teleportAttempts < 3) { _retryTeleportAt = Environment.TickCount64 + 1000; logger.LogWarning("Teleport did not start (attempt {Attempt}/{Max}), retrying...", _teleportAttempts, 3); return false; } chatGui.Print("Unable to teleport to aetheryte.", "Questionable", 576); throw new TaskException("Unable to teleport to aetheryte"); } public override bool WasInterrupted() { if (!condition[ConditionFlag.InCombat]) { return base.WasInterrupted(); } return true; } public override bool ShouldInterruptOnDamage() { return true; } } internal sealed record MoveAwayFromAetheryte(EAetheryteLocation TargetAetheryte) : ITask { public override string ToString() { return $"MoveAway({TargetAetheryte})"; } } internal sealed class MoveAwayFromAetheryteExecutor(MoveExecutor moveExecutor, AetheryteData aetheryteData, IClientState clientState, IObjectTable objectTable) : TaskExecutor() { private static readonly Dictionary> AetherytesToMoveFrom; public static bool AppliesTo(EAetheryteLocation location) { return AetherytesToMoveFrom.ContainsKey(location); } protected override bool Start() { IPlayerCharacter localPlayer = objectTable.LocalPlayer; if (localPlayer == null) { return false; } Vector3 playerPosition = localPlayer.Position; if (aetheryteData.CalculateDistance(playerPosition, clientState.TerritoryType, base.Task.TargetAetheryte) >= 20f) { return false; } Vector3 destination = AetherytesToMoveFrom[base.Task.TargetAetheryte].MinBy((Vector3 x) => Vector3.Distance(x, playerPosition)); MoveTask task = new MoveTask(aetheryteData.TerritoryIds[base.Task.TargetAetheryte], destination, false, MountRequired: false, DismountRequired: false, 0.25f, null, DisableNavmesh: true, null, Fly: false, Land: false, IgnoreDistanceToObject: false, RestartNavigation: false); return moveExecutor.Start(task); } public override ETaskResult Update() { return moveExecutor.Update(); } public override bool ShouldInterruptOnDamage() { return true; } static MoveAwayFromAetheryteExecutor() { Dictionary> dictionary = new Dictionary>(); int num = 4; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span span = CollectionsMarshal.AsSpan(list); span[0] = new Vector3(0f, 8.8f, 15.5f); span[1] = new Vector3(0f, 8.8f, -15.5f); span[2] = new Vector3(15.5f, 8.8f, 0f); span[3] = new Vector3(-15.5f, 8.8f, 0f); dictionary.Add(EAetheryteLocation.SolutionNine, list); AetherytesToMoveFrom = dictionary; } } }