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

261 lines
10 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.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<ITask> 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<UseAetheryteShortcut> logger, AetheryteFunctions aetheryteFunctions, QuestFunctions questFunctions, IClientState clientState, IChatGui chatGui, ICondition condition, AetheryteData aetheryteData, ExtraConditionUtils extraConditionUtils) : TaskExecutor<Task>()
{
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<MoveAwayFromAetheryte>()
{
private static readonly Dictionary<EAetheryteLocation, List<Vector3>> 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<EAetheryteLocation, List<Vector3>> dictionary = new Dictionary<EAetheryteLocation, List<Vector3>>();
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.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;
}
}
}