using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Movement; using Questionable.Controller.Utils; using Questionable.Data; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Common; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; internal static class AetheryteShortcut { internal sealed class Factory(AetheryteData aetheryteData, TerritoryData territoryData, IClientState clientState) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (!step.AetheryteShortcut.HasValue) { yield break; } yield return new Task(step, quest.Id, step.AetheryteShortcut.Value, aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]); yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5)); if (MoveAwayFromAetheryteExecutor.AppliesTo(step.AetheryteShortcut.Value) && step.AethernetShortcut?.From != step.AetheryteShortcut.Value) { yield return new WaitCondition.Task(() => clientState.TerritoryType == aetheryteData.TerritoryIds[step.AetheryteShortcut.Value], "Wait(territory: " + territoryData.GetNameAndId(aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]) + ")"); yield return new MoveAwayFromAetheryte(step.AetheryteShortcut.Value); } } } internal sealed record Task(QuestStep? Step, ElementId? ElementId, EAetheryteLocation TargetAetheryte, ushort ExpectedTerritoryId) : ISkippableTask, ITask { public override string ToString() { return $"UseAetheryte({TargetAetheryte})"; } } internal sealed class UseAetheryteShortcut(ILogger logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, IClientState clientState, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData, ExtraConditionUtils extraConditionUtils) : TaskExecutor() { private bool _teleported; private DateTime _continueAt; protected override bool Start() { return !ShouldSkipTeleport(); } public override ETaskResult Update() { if (DateTime.Now < _continueAt) { return ETaskResult.StillRunning; } if (!_teleported) { _teleported = DoTeleport(); return ETaskResult.StillRunning; } if (clientState.TerritoryType == base.Task.ExpectedTerritoryId) { return ETaskResult.TaskComplete; } return ETaskResult.StillRunning; } private bool ShouldSkipTeleport() { ushort territoryType = clientState.TerritoryType; if (base.Task.Step != null) { SkipAetheryteCondition skipAetheryteCondition = base.Task.Step.SkipConditions?.AetheryteShortcutIf ?? new SkipAetheryteCondition(); if (skipAetheryteCondition != null && !skipAetheryteCondition.Never) { if (skipAetheryteCondition.InTerritory.Contains(territoryType)) { logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InTerritory)"); return true; } if (skipAetheryteCondition.QuestsCompleted.Count > 0 && skipAetheryteCondition.QuestsCompleted.All(questFunctions.IsQuestComplete)) { logger.LogInformation("Skipping aetheryte, all prequisite quests are complete"); return true; } if (skipAetheryteCondition.QuestsAccepted.Count > 0 && skipAetheryteCondition.QuestsAccepted.All(questFunctions.IsQuestAccepted)) { logger.LogInformation("Skipping aetheryte, all prequisite quests are accepted"); return true; } if (skipAetheryteCondition.AetheryteLocked.HasValue && !aetheryteFunctions.IsAetheryteUnlocked(skipAetheryteCondition.AetheryteLocked.Value)) { logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteLocked)"); return true; } if (skipAetheryteCondition.AetheryteUnlocked.HasValue && aetheryteFunctions.IsAetheryteUnlocked(skipAetheryteCondition.AetheryteUnlocked.Value)) { logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteUnlocked)"); return true; } if (base.Task.ElementId != null) { QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.ElementId); if (skipAetheryteCondition.RequiredQuestVariablesNotMet && questProgressInfo != null && !QuestWorkUtils.MatchesRequiredQuestWorkConfig(base.Task.Step.RequiredQuestVariables, questProgressInfo, logger)) { logger.LogInformation("Skipping aetheryte teleport, as required variables do not match"); return true; } } NearPositionCondition nearPosition = skipAetheryteCondition.NearPosition; if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance) { logger.LogInformation("Skipping aetheryte shortcut, as we're near the position"); return true; } NearPositionCondition notNearPosition = skipAetheryteCondition.NotNearPosition; if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position)) { logger.LogInformation("Skipping aetheryte shortcut, as we're not near the position"); return true; } if (skipAetheryteCondition.ExtraCondition.HasValue && skipAetheryteCondition.ExtraCondition != EExtraSkipCondition.None && extraConditionUtils.MatchesExtraCondition(skipAetheryteCondition.ExtraCondition.Value)) { logger.LogInformation("Skipping step, extra condition {} matches", skipAetheryteCondition.ExtraCondition); return true; } } if (base.Task.ExpectedTerritoryId == territoryType && !skipAetheryteCondition.Never) { if (skipAetheryteCondition != null && skipAetheryteCondition.InSameTerritory) { logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (InSameTerritory)"); return true; } Vector3 position = clientState.LocalPlayer.Position; if (base.Task.Step.Position.HasValue && (position - base.Task.Step.Position.Value).Length() < base.Task.Step.CalculateActualStopDistance()) { logger.LogInformation("Skipping aetheryte teleport, we're near the target"); return true; } if (aetheryteData.CalculateDistance(position, territoryType, base.Task.TargetAetheryte) < 20f || (base.Task.Step.AethernetShortcut != null && (aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.From) < 20f || aetheryteData.CalculateDistance(position, territoryType, base.Task.Step.AethernetShortcut.To) < 20f))) { logger.LogInformation("Skipping aetheryte teleport"); return true; } } } return false; } private bool DoTeleport() { if (!aetheryteFunctions.CanTeleport(base.Task.TargetAetheryte)) { if (!aetheryteFunctions.IsTeleportUnlocked()) { throw new TaskException("Teleport is not unlocked, attune to any aetheryte first."); } _continueAt = DateTime.Now.AddSeconds(1.0); logger.LogTrace("Waiting for teleport cooldown..."); return false; } _continueAt = DateTime.Now.AddSeconds(8.0); 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; } 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) : TaskExecutor() { private static readonly Dictionary> AetherytesToMoveFrom; public static bool AppliesTo(EAetheryteLocation location) { return AetherytesToMoveFrom.ContainsKey(location); } protected override bool Start() { Vector3 playerPosition = clientState.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, 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); int num2 = 0; span[num2] = new Vector3(0f, 8.8f, 15.5f); num2++; span[num2] = new Vector3(0f, 8.8f, -15.5f); num2++; span[num2] = new Vector3(15.5f, 8.8f, 0f); num2++; span[num2] = new Vector3(-15.5f, 8.8f, 0f); dictionary.Add(EAetheryteLocation.SolutionNine, list); AetherytesToMoveFrom = dictionary; } } }