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.Data; using Questionable.External; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Common; using Questionable.Model.Common.Converter; using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; internal static class AethernetShortcut { internal sealed class Factory(AetheryteData aetheryteData, TerritoryData territoryData, IClientState clientState) : ITaskFactory { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { if (step.AethernetShortcut == null) { yield break; } yield return new WaitNavmesh.Task(); yield return new Task(step.AethernetShortcut.From, step.AethernetShortcut.To, step.SkipConditions?.AethernetShortcutIf ?? new SkipAetheryteCondition()); if (AetheryteShortcut.MoveAwayFromAetheryteExecutor.AppliesTo(step.AethernetShortcut.To)) { yield return new WaitCondition.Task(() => clientState.TerritoryType == aetheryteData.TerritoryIds[step.AethernetShortcut.To], "Wait(territory: " + territoryData.GetNameAndId(aetheryteData.TerritoryIds[step.AethernetShortcut.To]) + ")"); yield return new AetheryteShortcut.MoveAwayFromAetheryte(step.AethernetShortcut.To); } } } internal sealed record Task(EAetheryteLocation From, EAetheryteLocation To, SkipAetheryteCondition SkipConditions) : ISkippableTask, ITask { public Task(EAetheryteLocation from, EAetheryteLocation to) : this(from, to, new SkipAetheryteCondition()) { } public override string ToString() { return $"UseAethernet({From} -> {To})"; } } internal sealed class UseAethernetShortcut(ILogger logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, AetheryteData aetheryteData, TerritoryData territoryData, LifestreamIpc lifestreamIpc, MovementController movementController, ICondition condition) : TaskExecutor() { private bool _moving; private bool _teleported; private bool _triedMounting; private DateTime _continueAt = DateTime.MinValue; protected override bool Start() { if (!base.Task.SkipConditions.Never) { if (base.Task.SkipConditions.InSameTerritory && clientState.TerritoryType == aetheryteData.TerritoryIds[base.Task.To]) { logger.LogInformation("Skipping aethernet shortcut because the target is in the same territory"); return false; } if (base.Task.SkipConditions.InTerritory.Contains(clientState.TerritoryType)) { logger.LogInformation("Skipping aethernet shortcut because the target is in the specified territory"); return false; } if (base.Task.SkipConditions.QuestsCompleted.Count > 0 && base.Task.SkipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete)) { logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are complete"); return true; } if (base.Task.SkipConditions.QuestsAccepted.Count > 0 && base.Task.SkipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted)) { logger.LogInformation("Skipping aethernet shortcut, all prequisite quests are accepted"); return true; } if (base.Task.SkipConditions.AetheryteLocked.HasValue && !aetheryteFunctions.IsAetheryteUnlocked(base.Task.SkipConditions.AetheryteLocked.Value)) { logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked"); return false; } if (base.Task.SkipConditions.AetheryteUnlocked.HasValue && aetheryteFunctions.IsAetheryteUnlocked(base.Task.SkipConditions.AetheryteUnlocked.Value)) { logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked"); return false; } } if (aetheryteFunctions.IsAetheryteUnlocked(base.Task.From) && aetheryteFunctions.IsAetheryteUnlocked(base.Task.To)) { ushort territoryType = clientState.TerritoryType; Vector3 playerPosition = clientState.LocalPlayer.Position; if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.To)) { if (aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) < (base.Task.From.IsFirmamentAetheryte() ? 11f : 4f)) { DoTeleport(); return true; } if (base.Task.From == EAetheryteLocation.SolutionNine) { logger.LogInformation("Moving to S9 aetheryte"); 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.442986f, 9f); num2++; span[num2] = new Vector3(9f, 8.442986f, 0f); num2++; span[num2] = new Vector3(-9f, 8.442986f, 0f); num2++; span[num2] = new Vector3(0f, 8.442986f, -9f); Vector3 to = list.MinBy((Vector3 x) => Vector3.Distance(playerPosition, x)); _moving = true; movementController.NavigateTo(EMovementType.Quest, (uint)base.Task.From, to, fly: false, sprint: true, 0.25f); return true; } if (territoryData.CanUseMount(territoryType) && aetheryteData.CalculateDistance(playerPosition, territoryType, base.Task.From) > 30f && !gameFunctions.HasStatusPreventingMount()) { _triedMounting = gameFunctions.Mount(); if (_triedMounting) { _continueAt = DateTime.Now.AddSeconds(0.5); return true; } } MoveTo(); return true; } } else { if (clientState.TerritoryType != aetheryteData.TerritoryIds[base.Task.To]) { throw new TaskException($"Aethernet shortcut not unlocked (from: {base.Task.From}, to: {base.Task.To})"); } logger.LogWarning("Aethernet shortcut not unlocked (from: {FromAetheryte}, to: {ToAetheryte}), skipping as we are already in the destination territory", base.Task.From, base.Task.To); } return false; } private void MoveTo() { logger.LogInformation("Moving to aethernet shortcut"); _moving = true; 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 DoTeleport() { logger.LogInformation("Using lifestream to teleport to {Destination}", base.Task.To); lifestreamIpc.Teleport(base.Task.To); _teleported = true; } public override ETaskResult Update() { if (DateTime.Now < _continueAt) { return ETaskResult.StillRunning; } if (_triedMounting) { if (condition[ConditionFlag.Mounted]) { _triedMounting = false; MoveTo(); return ETaskResult.StillRunning; } return ETaskResult.StillRunning; } if (_moving) { DateTime movementStartedAt = movementController.MovementStartedAt; if (movementStartedAt == DateTime.MaxValue || movementStartedAt.AddSeconds(2.0) >= DateTime.Now) { return ETaskResult.StillRunning; } if (!movementController.IsPathfinding && !movementController.IsPathRunning) { _moving = false; } return ETaskResult.StillRunning; } if (!_teleported) { DoTeleport(); return ETaskResult.StillRunning; } Vector3? vector = clientState.LocalPlayer?.Position; if (!vector.HasValue) { return ETaskResult.StillRunning; } if (aetheryteData.IsAirshipLanding(base.Task.To)) { if (aetheryteData.CalculateAirshipLandingDistance(vector.Value, clientState.TerritoryType, base.Task.To) > 5f) { return ETaskResult.StillRunning; } } else if (aetheryteData.IsCityAetheryte(base.Task.To) || aetheryteData.IsGoldSaucerAetheryte(base.Task.To)) { if (aetheryteData.CalculateDistance(vector.Value, clientState.TerritoryType, base.Task.To) > 20f) { return ETaskResult.StillRunning; } } else if (clientState.TerritoryType != aetheryteData.TerritoryIds[base.Task.To]) { return ETaskResult.StillRunning; } return ETaskResult.TaskComplete; } public override bool ShouldInterruptOnDamage() { return true; } } }