qstbak/Questionable/Questionable.Controller.Steps.Shared/AethernetShortcut.cs
2025-10-09 07:47:19 +10:00

236 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.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<ITask> 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<UseAethernetShortcut> logger, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, AetheryteData aetheryteData, TerritoryData territoryData, LifestreamIpc lifestreamIpc, MovementController movementController, ICondition condition) : TaskExecutor<Task>()
{
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<Vector3> list = new List<Vector3>(num);
CollectionsMarshal.SetCount(list, num);
Span<Vector3> 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;
}
}
}