punish v6.8.18.0
This commit is contained in:
commit
cfb4dea47e
316 changed files with 554088 additions and 0 deletions
|
@ -0,0 +1,236 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
139
Questionable/Questionable.Controller.Steps.Shared/Craft.cs
Normal file
139
Questionable/Questionable.Controller.Steps.Shared/Craft.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameData;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.External;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class Craft
|
||||
{
|
||||
internal sealed class Factory : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Questionable.Model.Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Craft)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.ItemId, "step.ItemId");
|
||||
ArgumentNullException.ThrowIfNull(step.ItemCount, "step.ItemCount");
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
|
||||
{
|
||||
new Questionable.Controller.Steps.Common.Mount.UnmountTask(),
|
||||
new CraftTask(step.ItemId.Value, step.ItemCount.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record CraftTask(uint ItemId, int ItemCount) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Craft {ItemCount}x {ItemId} (with Artisan)";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoCraft(IDataManager dataManager, IClientState clientState, ArtisanIpc artisanIpc, ILogger<DoCraft> logger) : TaskExecutor<CraftTask>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
if (HasRequestedItems())
|
||||
{
|
||||
logger.LogInformation("Already own {ItemCount}x {ItemId}", base.Task.ItemCount, base.Task.ItemId);
|
||||
return false;
|
||||
}
|
||||
RecipeLookup? rowOrDefault = dataManager.GetExcelSheet<RecipeLookup>().GetRowOrDefault(base.Task.ItemId);
|
||||
if (!rowOrDefault.HasValue)
|
||||
{
|
||||
throw new TaskException($"Item {base.Task.ItemId} is not craftable");
|
||||
}
|
||||
uint num = (EClassJob)clientState.LocalPlayer.ClassJob.RowId switch
|
||||
{
|
||||
EClassJob.Carpenter => rowOrDefault.Value.CRP.RowId,
|
||||
EClassJob.Blacksmith => rowOrDefault.Value.BSM.RowId,
|
||||
EClassJob.Armorer => rowOrDefault.Value.ARM.RowId,
|
||||
EClassJob.Goldsmith => rowOrDefault.Value.GSM.RowId,
|
||||
EClassJob.Leatherworker => rowOrDefault.Value.LTW.RowId,
|
||||
EClassJob.Weaver => rowOrDefault.Value.WVR.RowId,
|
||||
EClassJob.Alchemist => rowOrDefault.Value.ALC.RowId,
|
||||
EClassJob.Culinarian => rowOrDefault.Value.CUL.RowId,
|
||||
_ => 0u,
|
||||
};
|
||||
if (num == 0)
|
||||
{
|
||||
num = new uint[8]
|
||||
{
|
||||
rowOrDefault.Value.CRP.RowId,
|
||||
rowOrDefault.Value.BSM.RowId,
|
||||
rowOrDefault.Value.ARM.RowId,
|
||||
rowOrDefault.Value.GSM.RowId,
|
||||
rowOrDefault.Value.LTW.RowId,
|
||||
rowOrDefault.Value.WVR.RowId,
|
||||
rowOrDefault.Value.ALC.RowId,
|
||||
rowOrDefault.Value.WVR.RowId
|
||||
}.FirstOrDefault((uint x) => x != 0);
|
||||
}
|
||||
if (num == 0)
|
||||
{
|
||||
throw new TaskException($"Unable to determine recipe for item {base.Task.ItemId}");
|
||||
}
|
||||
int num2 = base.Task.ItemCount - GetOwnedItemCount();
|
||||
logger.LogInformation("Starting craft for item {ItemId} with recipe {RecipeId} for {RemainingItemCount} items", base.Task.ItemId, num, num2);
|
||||
if (!artisanIpc.CraftItem((ushort)num, num2))
|
||||
{
|
||||
throw new TaskException($"Failed to start Artisan craft for recipe {num}");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public unsafe override ETaskResult Update()
|
||||
{
|
||||
if (HasRequestedItems() && !artisanIpc.IsCrafting())
|
||||
{
|
||||
AgentRecipeNote* ptr = AgentRecipeNote.Instance();
|
||||
if (ptr != null && ptr->IsAgentActive())
|
||||
{
|
||||
uint addonId = ptr->GetAddonId();
|
||||
if (addonId == 0)
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
AtkUnitBase* addonById = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonById((ushort)addonId);
|
||||
if (addonById != null)
|
||||
{
|
||||
logger.LogInformation("Closing crafting window");
|
||||
addonById->FireCallbackInt(-1);
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
private bool HasRequestedItems()
|
||||
{
|
||||
return GetOwnedItemCount() >= base.Task.ItemCount;
|
||||
}
|
||||
|
||||
private unsafe int GetOwnedItemCount()
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
return ptr->GetInventoryItemCount(base.Task.ItemId, isHq: false, checkEquipped: false, checkArmory: true, 0) + ptr->GetInventoryItemCount(base.Task.ItemId, isHq: true, checkEquipped: false, checkArmory: true, 0);
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal sealed class ExtraConditionUtils
|
||||
{
|
||||
private readonly IClientState _clientState;
|
||||
|
||||
public ExtraConditionUtils(IClientState clientState)
|
||||
{
|
||||
_clientState = clientState;
|
||||
}
|
||||
|
||||
public bool MatchesExtraCondition(EExtraSkipCondition skipCondition)
|
||||
{
|
||||
Vector3? vector = _clientState.LocalPlayer?.Position;
|
||||
if (vector.HasValue && _clientState.TerritoryType != 0)
|
||||
{
|
||||
return MatchesExtraCondition(skipCondition, vector.Value, _clientState.TerritoryType);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool MatchesExtraCondition(EExtraSkipCondition skipCondition, Vector3 position, ushort territoryType)
|
||||
{
|
||||
return skipCondition switch
|
||||
{
|
||||
EExtraSkipCondition.WakingSandsMainArea => territoryType == 212 && position.X < 24f,
|
||||
EExtraSkipCondition.WakingSandsSolar => territoryType == 212 && position.X >= 24f,
|
||||
EExtraSkipCondition.RisingStonesSolar => territoryType == 351 && position.Z <= -28f,
|
||||
EExtraSkipCondition.RoguesGuild => territoryType == 129 && position.Y <= -115f,
|
||||
EExtraSkipCondition.NotRoguesGuild => territoryType == 129 && position.Y > -115f,
|
||||
EExtraSkipCondition.DockStorehouse => territoryType == 137 && position.Y <= -20f,
|
||||
_ => throw new ArgumentOutOfRangeException("skipCondition", skipCondition, null),
|
||||
};
|
||||
}
|
||||
}
|
192
Questionable/Questionable.Controller.Steps.Shared/Gather.cs
Normal file
192
Questionable/Questionable.Controller.Steps.Shared/Gather.cs
Normal file
|
@ -0,0 +1,192 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using LLib.GameData;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Gathering;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class Gather
|
||||
{
|
||||
internal sealed class Factory : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.Gather)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (GatheredItem item in step.ItemsToGather)
|
||||
{
|
||||
yield return new DelayedGatheringTask(item, quest, sequence.Sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record DelayedGatheringTask(GatheredItem GatheredItem, Quest Quest, byte Sequence) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Gathering(pending for {GatheredItem.ItemId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DelayedGatheringExecutor(GatheringPointRegistry gatheringPointRegistry, TerritoryData territoryData, IClientState clientState, IServiceProvider serviceProvider, ILogger<DelayedGatheringExecutor> logger) : TaskExecutor<DelayedGatheringTask>(), IExtraTaskCreator, ITaskExecutor
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.CreateNewTasks;
|
||||
}
|
||||
|
||||
public IEnumerable<ITask> CreateExtraTasks()
|
||||
{
|
||||
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
|
||||
if (!gatheringPointRegistry.TryGetGatheringPointId(base.Task.GatheredItem.ItemId, rowId, out GatheringPointId gatheringPointId))
|
||||
{
|
||||
throw new TaskException($"No gathering point found for item {base.Task.GatheredItem.ItemId}");
|
||||
}
|
||||
if (!gatheringPointRegistry.TryGetGatheringPoint(gatheringPointId, out GatheringRoot gatheringRoot))
|
||||
{
|
||||
throw new TaskException($"No path found for gathering point {gatheringPointId}");
|
||||
}
|
||||
if (HasRequiredItems(base.Task.GatheredItem))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
switch (rowId)
|
||||
{
|
||||
case EClassJob.Miner:
|
||||
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Prospect, EAction.Prospect);
|
||||
break;
|
||||
case EClassJob.Botanist:
|
||||
yield return new Questionable.Controller.Steps.Interactions.Action.TriggerStatusIfMissing(EStatus.Triangulate, EAction.Triangulate);
|
||||
break;
|
||||
}
|
||||
using (logger.BeginScope("Gathering(inner)"))
|
||||
{
|
||||
QuestSequence gatheringSequence = new QuestSequence
|
||||
{
|
||||
Sequence = 0,
|
||||
Steps = gatheringRoot.Steps
|
||||
};
|
||||
foreach (QuestStep step in gatheringSequence.Steps)
|
||||
{
|
||||
foreach (ITask item in serviceProvider.GetRequiredService<TaskCreator>().CreateTasks(base.Task.Quest, base.Task.Sequence, gatheringSequence, step))
|
||||
{
|
||||
if (item is WaitAtEnd.NextStep)
|
||||
{
|
||||
yield return new SkipMarker();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ushort territoryId = gatheringRoot.Steps.Last().TerritoryId;
|
||||
yield return new WaitCondition.Task(() => clientState.TerritoryType == territoryId, "Wait(territory: " + territoryData.GetNameAndId(territoryId) + ")");
|
||||
yield return new WaitNavmesh.Task();
|
||||
yield return new GatheringTask(gatheringPointId, base.Task.GatheredItem);
|
||||
yield return new WaitAtEnd.WaitDelay();
|
||||
}
|
||||
|
||||
private unsafe bool HasRequiredItems(GatheredItem itemToGather)
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr != null)
|
||||
{
|
||||
return ptr->GetInventoryItemCount(itemToGather.ItemId, isHq: false, checkEquipped: true, checkArmory: true, (short)itemToGather.Collectability) >= itemToGather.ItemCount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record GatheringTask(GatheringPointId gatheringPointId, GatheredItem gatheredItem) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
if (gatheredItem.Collectability != 0)
|
||||
{
|
||||
return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {gatheredItem.Collectability})";
|
||||
}
|
||||
return $"Gather({gatheredItem.ItemCount}x {gatheredItem.ItemId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StartGathering(GatheringController gatheringController) : TaskExecutor<GatheringTask>(), IToastAware, ITaskExecutor
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return gatheringController.Start(new GatheringController.GatheringRequest(base.Task.gatheringPointId, base.Task.gatheredItem.ItemId, base.Task.gatheredItem.AlternativeItemId, base.Task.gatheredItem.ItemCount, base.Task.gatheredItem.Collectability));
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (gatheringController.Update() == GatheringController.EStatus.Complete)
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public bool OnErrorToast(SeString message)
|
||||
{
|
||||
bool isHandled = false;
|
||||
gatheringController.OnErrorToast(ref message, ref isHandled);
|
||||
return isHandled;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SkipMarker : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Gather/SkipMarker";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DoSkip : TaskExecutor<SkipMarker>
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class RedeemRewardItems
|
||||
{
|
||||
internal sealed class Factory(QuestData questData) : ITaskFactory
|
||||
{
|
||||
public unsafe IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.AcceptQuest)
|
||||
{
|
||||
return Array.Empty<ITask>();
|
||||
}
|
||||
List<ITask> list = new List<ITask>();
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr == null)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
foreach (ItemReward redeemableItem in questData.RedeemableItems)
|
||||
{
|
||||
if (ptr->GetInventoryItemCount(redeemableItem.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0 && !redeemableItem.IsUnlocked())
|
||||
{
|
||||
list.Add(new Task(redeemableItem));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Task(ItemReward ItemReward) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "TryRedeem(" + ItemReward.Name + ")";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Executor(GameFunctions gameFunctions, ICondition condition) : TaskExecutor<Task>()
|
||||
{
|
||||
private static readonly TimeSpan MinimumCastTime = TimeSpan.FromSeconds(4L);
|
||||
|
||||
private DateTime _continueAt;
|
||||
|
||||
protected override bool Start()
|
||||
{
|
||||
if (condition[ConditionFlag.Mounted])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TimeSpan timeSpan = base.Task.ItemReward.CastTime;
|
||||
if (timeSpan < MinimumCastTime)
|
||||
{
|
||||
timeSpan = MinimumCastTime;
|
||||
}
|
||||
_continueAt = DateTime.Now.Add(timeSpan).AddSeconds(3.0);
|
||||
return gameFunctions.UseItem(base.Task.ItemReward.ItemId);
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (condition[ConditionFlag.Casting])
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
if (!(DateTime.Now <= _continueAt))
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,457 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using LLib.GameData;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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 SkipCondition
|
||||
{
|
||||
internal sealed class Factory(Configuration configuration) : SimpleTaskFactory()
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
SkipStepConditions skipStepConditions = step.SkipConditions?.StepIf;
|
||||
if (skipStepConditions != null && skipStepConditions.Never)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if ((skipStepConditions == null || !skipStepConditions.HasSkipConditions()) && !QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) && step.RequiredQuestVariables.Count == 0 && !step.TaxiStandId.HasValue && step.PickUpQuestId == null && step.NextQuestId == null && step.RequiredCurrentJob.Count == 0 && step.RequiredQuestAcceptedJob.Count == 0 && (step.InteractionType != EInteractionType.AttuneAetherCurrent || !configuration.Advanced.SkipAetherCurrents))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new SkipTask(step, skipStepConditions ?? new SkipStepConditions(), quest.Id);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record SkipTask(QuestStep Step, SkipStepConditions SkipConditions, ElementId ElementId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "CheckSkip";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CheckSkip(ILogger<CheckSkip> logger, Configuration configuration, AetheryteFunctions aetheryteFunctions, GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, ICondition condition, ExtraConditionUtils extraConditionUtils, ClassJobUtils classJobUtils) : TaskExecutor<SkipTask>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
SkipStepConditions skipConditions = base.Task.SkipConditions;
|
||||
QuestStep step = base.Task.Step;
|
||||
ElementId elementId = base.Task.ElementId;
|
||||
logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions));
|
||||
if (CheckFlyingCondition(step, skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckUnlockedMountCondition(skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckDivingCondition(skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckTerritoryCondition(skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckQuestConditions(skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckTargetableCondition(step, skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckNameplateCondition(step, skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckItemCondition(step, skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckAetheryteCondition(step, skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckAetherCurrentCondition(step))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckQuestWorkConditions(elementId, step))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckJobCondition(elementId, step))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckPositionCondition(skipConditions))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (skipConditions.ExtraCondition.HasValue && skipConditions.ExtraCondition != EExtraSkipCondition.None && extraConditionUtils.MatchesExtraCondition(skipConditions.ExtraCondition.Value))
|
||||
{
|
||||
logger.LogInformation("Skipping step, extra condition {} matches", skipConditions.ExtraCondition);
|
||||
return true;
|
||||
}
|
||||
if (CheckPickUpTurnInQuestIds(step))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CheckTaxiStandUnlocked(step))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckFlyingCondition(QuestStep step, SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.Flying == ELockedSkipCondition.Unlocked && gameFunctions.IsFlyingUnlocked(step.TerritoryId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as flying is unlocked");
|
||||
return true;
|
||||
}
|
||||
if (skipConditions.Flying == ELockedSkipCondition.Locked && !gameFunctions.IsFlyingUnlocked(step.TerritoryId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as flying is locked");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool CheckUnlockedMountCondition(SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.Chocobo == ELockedSkipCondition.Unlocked && PlayerState.Instance()->IsMountUnlocked(1u))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as chocobo is unlocked");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckTerritoryCondition(SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.InTerritory.Count > 0 && skipConditions.InTerritory.Contains(clientState.TerritoryType))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as in a skip.InTerritory");
|
||||
return true;
|
||||
}
|
||||
if (skipConditions.NotInTerritory.Count > 0 && !skipConditions.NotInTerritory.Contains(clientState.TerritoryType))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as not in a skip.NotInTerritory");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivingCondition(SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.Diving == true && condition[ConditionFlag.Diving])
|
||||
{
|
||||
logger.LogInformation("Skipping step, as you're currently diving underwater");
|
||||
return true;
|
||||
}
|
||||
if (skipConditions.Diving == false && !condition[ConditionFlag.Diving])
|
||||
{
|
||||
logger.LogInformation("Skipping step, as you're not currently diving underwater");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckQuestConditions(SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.QuestsCompleted.Count > 0 && skipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete))
|
||||
{
|
||||
logger.LogInformation("Skipping step, all prequisite quests are complete");
|
||||
return true;
|
||||
}
|
||||
if (skipConditions.QuestsAccepted.Count > 0 && skipConditions.QuestsAccepted.All(questFunctions.IsQuestAccepted))
|
||||
{
|
||||
logger.LogInformation("Skipping step, all prequisite quests are accepted");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckTargetableCondition(QuestStep step, SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.NotTargetable && step != null && step.DataId.HasValue)
|
||||
{
|
||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
|
||||
if (gameObject == null)
|
||||
{
|
||||
if ((step.Position.GetValueOrDefault() - clientState.LocalPlayer.Position).Length() < 100f)
|
||||
{
|
||||
logger.LogInformation("Skipping step, object is not nearby (but we are)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (!gameObject.IsTargetable)
|
||||
{
|
||||
logger.LogInformation("Skipping step, object is not targetable");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool CheckNameplateCondition(QuestStep step, SkipStepConditions skipConditions)
|
||||
{
|
||||
if (skipConditions.NotNamePlateIconId.Count > 0 && step != null && step.DataId.HasValue)
|
||||
{
|
||||
IGameObject gameObject = gameFunctions.FindObjectByDataId(step.DataId.Value);
|
||||
if (gameObject != null)
|
||||
{
|
||||
GameObject* address = (GameObject*)gameObject.Address;
|
||||
if (!skipConditions.NotNamePlateIconId.Contains(address->NamePlateIconId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, object has icon id {IconId}", address->NamePlateIconId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool CheckItemCondition(QuestStep step, SkipStepConditions skipConditions)
|
||||
{
|
||||
SkipItemConditions item = skipConditions.Item;
|
||||
if (item != null && item.NotInInventory && step != null && step.ItemId.HasValue)
|
||||
{
|
||||
InventoryManager* ptr = InventoryManager.Instance();
|
||||
if (ptr->GetInventoryItemCount(step.ItemId.Value, isHq: false, checkEquipped: true, checkArmory: true, 0) == 0 && ptr->GetInventoryItemCount(step.ItemId.Value, isHq: true, checkEquipped: true, checkArmory: true, 0) == 0)
|
||||
{
|
||||
logger.LogInformation("Skipping step, no item with itemId {ItemId} in inventory", step.ItemId.Value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckAetheryteCondition(QuestStep step, SkipStepConditions skipConditions)
|
||||
{
|
||||
if (step != null)
|
||||
{
|
||||
EAetheryteLocation? aetheryte = step.Aetheryte;
|
||||
if (aetheryte.HasValue)
|
||||
{
|
||||
EAetheryteLocation valueOrDefault = aetheryte.GetValueOrDefault();
|
||||
if (step.InteractionType == EInteractionType.AttuneAetheryte && aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as aetheryte is unlocked");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (step != null)
|
||||
{
|
||||
EAetheryteLocation? aetheryte = step.Aetheryte;
|
||||
if (aetheryte.HasValue)
|
||||
{
|
||||
EAetheryteLocation valueOrDefault2 = aetheryte.GetValueOrDefault();
|
||||
if (step.InteractionType == EInteractionType.AttuneAethernetShard && aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault2))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as aethernet shard is unlocked");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (step != null)
|
||||
{
|
||||
EAetheryteLocation? aetheryte = step.Aetheryte;
|
||||
if (aetheryte.HasValue)
|
||||
{
|
||||
EAetheryteLocation valueOrDefault3 = aetheryte.GetValueOrDefault();
|
||||
if (step.InteractionType == EInteractionType.RegisterFreeOrFavoredAetheryte && aetheryteFunctions.CanRegisterFreeOrFavoriteAetheryte(valueOrDefault3) == AetheryteRegistrationResult.NotPossible)
|
||||
{
|
||||
logger.LogInformation("Skipping step, already registered all possible free or favored aetherytes");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (skipConditions.AetheryteLocked.HasValue && !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as aetheryte is locked");
|
||||
return true;
|
||||
}
|
||||
if (skipConditions.AetheryteUnlocked.HasValue && aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as aetheryte is unlocked");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckAetherCurrentCondition(QuestStep step)
|
||||
{
|
||||
if (step != null && step.DataId.HasValue && step.InteractionType == EInteractionType.AttuneAetherCurrent && gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as current is unlocked");
|
||||
return true;
|
||||
}
|
||||
if (step != null && step.InteractionType == EInteractionType.AttuneAetherCurrent && configuration.Advanced.SkipAetherCurrents)
|
||||
{
|
||||
logger.LogInformation("Skipping step, as aether currents should be skipped");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckQuestWorkConditions(ElementId elementId, QuestStep step)
|
||||
{
|
||||
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(elementId);
|
||||
if (questProgressInfo != null)
|
||||
{
|
||||
if (QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) && QuestWorkUtils.MatchesQuestWork(step.CompletionQuestVariablesFlags, questProgressInfo))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as quest variables match (step is complete)");
|
||||
return true;
|
||||
}
|
||||
if (step != null)
|
||||
{
|
||||
SkipConditions skipConditions = step.SkipConditions;
|
||||
if (skipConditions != null)
|
||||
{
|
||||
SkipStepConditions stepIf = skipConditions.StepIf;
|
||||
if (stepIf != null && QuestWorkUtils.MatchesQuestWork(stepIf.CompletionQuestVariablesFlags, questProgressInfo))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as quest variables match (step can be skipped)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (step != null)
|
||||
{
|
||||
List<List<QuestWorkValue>> requiredQuestVariables = step.RequiredQuestVariables;
|
||||
if (requiredQuestVariables != null && !QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questProgressInfo, logger))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as required variables do not match");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (step != null)
|
||||
{
|
||||
List<EExtendedClassJob> requiredQuestAcceptedJob = step.RequiredQuestAcceptedJob;
|
||||
if (requiredQuestAcceptedJob != null && requiredQuestAcceptedJob.Count > 0)
|
||||
{
|
||||
List<EClassJob> list = step.RequiredQuestAcceptedJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
|
||||
EClassJob classJob = questProgressInfo.ClassJob;
|
||||
logger.LogInformation("Checking quest job {QuestJob} against {ExpectedJobs}", classJob, string.Join(",", list));
|
||||
if (classJob != EClassJob.Adventurer && !list.Contains(classJob))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as quest was accepted on a different job");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckJobCondition(ElementId elementId, QuestStep step)
|
||||
{
|
||||
if (step != null)
|
||||
{
|
||||
List<EExtendedClassJob> requiredCurrentJob = step.RequiredCurrentJob;
|
||||
if (requiredCurrentJob != null && requiredCurrentJob.Count > 0)
|
||||
{
|
||||
List<EClassJob> list = step.RequiredCurrentJob.SelectMany((EExtendedClassJob x) => classJobUtils.AsIndividualJobs(x, elementId)).ToList();
|
||||
EClassJob rowId = (EClassJob)clientState.LocalPlayer.ClassJob.RowId;
|
||||
logger.LogInformation("Checking current job {CurrentJob} against {ExpectedJobs}", rowId, string.Join(",", list));
|
||||
if (!list.Contains(rowId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as step requires a different job");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckPositionCondition(SkipStepConditions skipConditions)
|
||||
{
|
||||
NearPositionCondition nearPosition = skipConditions.NearPosition;
|
||||
if (nearPosition != null && clientState.TerritoryType == nearPosition.TerritoryId && Vector3.Distance(nearPosition.Position, clientState.LocalPlayer.Position) <= nearPosition.MaximumDistance)
|
||||
{
|
||||
logger.LogInformation("Skipping step, as we're near the position");
|
||||
return true;
|
||||
}
|
||||
NearPositionCondition notNearPosition = skipConditions.NotNearPosition;
|
||||
if (notNearPosition != null && clientState.TerritoryType == notNearPosition.TerritoryId && notNearPosition.MaximumDistance <= Vector3.Distance(notNearPosition.Position, clientState.LocalPlayer.Position))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as we're not near the position");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckPickUpTurnInQuestIds(QuestStep step)
|
||||
{
|
||||
if (step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(step.PickUpQuestId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as we have already picked up the relevant quest");
|
||||
return true;
|
||||
}
|
||||
if (step.TurnInQuestId != null && questFunctions.IsQuestComplete(step.TurnInQuestId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as we have already completed the relevant quest");
|
||||
return true;
|
||||
}
|
||||
if (step.PickUpQuestId != null && configuration.Advanced.SkipAetherCurrents && QuestData.AetherCurrentQuests.Contains(step.PickUpQuestId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as aether current quests should be skipped");
|
||||
return true;
|
||||
}
|
||||
if (step.PickUpQuestId != null && configuration.Advanced.SkipARealmRebornHardModePrimals && QuestData.HardModePrimals.Contains(step.PickUpQuestId))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as hard mode primal quests should be skipped");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool CheckTaxiStandUnlocked(QuestStep step)
|
||||
{
|
||||
UIState* ptr = UIState.Instance();
|
||||
byte? taxiStandId = step.TaxiStandId;
|
||||
if (taxiStandId.HasValue)
|
||||
{
|
||||
byte valueOrDefault = taxiStandId.GetValueOrDefault();
|
||||
if (ptr->IsChocoboTaxiStandUnlocked(valueOrDefault))
|
||||
{
|
||||
logger.LogInformation("Skipping step, as taxi stand {TaxiStandId} is unlocked", valueOrDefault);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.SkipRemainingTasksForStep;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class StepDisabled
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (!step.Disabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new SkipRemainingTasks();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SkipRemainingTasks : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "StepDisabled";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SkipDisabledStepsExecutor(ILogger<SkipRemainingTasks> logger) : TaskExecutor<SkipRemainingTasks>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
logger.LogInformation("Skipping step, as it is disabled");
|
||||
return ETaskResult.SkipRemainingTasksForStep;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using LLib.GameData;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class SwitchClassJob
|
||||
{
|
||||
internal sealed class Factory(ClassJobUtils classJobUtils) : SimpleTaskFactory()
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.InteractionType != EInteractionType.SwitchClass)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new Task(classJobUtils.AsIndividualJobs(step.TargetClass, quest.Id).Single());
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record Task(EClassJob ClassJob) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"SwitchJob({ClassJob})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SwitchClassJobExecutor(IClientState clientState) : AbstractDelayedTaskExecutor<Task>()
|
||||
{
|
||||
protected unsafe override bool StartInternal()
|
||||
{
|
||||
if (clientState.LocalPlayer.ClassJob.RowId == (uint)base.Task.ClassJob)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RaptureGearsetModule* ptr = RaptureGearsetModule.Instance();
|
||||
if (ptr != null)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
RaptureGearsetModule.GearsetEntry* gearset = ptr->GetGearset(i);
|
||||
if (gearset->ClassJob == (byte)base.Task.ClassJob)
|
||||
{
|
||||
ptr->EquipGearset(gearset->Id, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new TaskException($"No gearset found for {base.Task.ClassJob}");
|
||||
}
|
||||
|
||||
protected override ETaskResult UpdateInternal()
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
405
Questionable/Questionable.Controller.Steps.Shared/WaitAtEnd.cs
Normal file
405
Questionable/Questionable.Controller.Steps.Shared/WaitAtEnd.cs
Normal file
|
@ -0,0 +1,405 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class WaitAtEnd
|
||||
{
|
||||
internal sealed class Factory(IClientState clientState, ICondition condition, TerritoryData territoryData, AutoDutyIpc autoDutyIpc, BossModIpc bossModIpc) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
||||
{
|
||||
WaitForCompletionFlags waitForCompletionFlags = new WaitForCompletionFlags((QuestId)quest.Id, step);
|
||||
WaitDelay waitDelay = new WaitDelay();
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
|
||||
{
|
||||
waitForCompletionFlags,
|
||||
waitDelay,
|
||||
Next(quest, sequence)
|
||||
});
|
||||
}
|
||||
ITask task;
|
||||
switch (step.InteractionType)
|
||||
{
|
||||
case EInteractionType.Combat:
|
||||
{
|
||||
if (step.EnemySpawnType == EEnemySpawnType.FinishCombatIfAny)
|
||||
{
|
||||
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(Next(quest, sequence));
|
||||
}
|
||||
WaitCondition.Task task2 = new WaitCondition.Task(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[4]
|
||||
{
|
||||
new WaitDelay(),
|
||||
task2,
|
||||
new WaitDelay(),
|
||||
Next(quest, sequence)
|
||||
});
|
||||
}
|
||||
case EInteractionType.WaitForManualProgress:
|
||||
case EInteractionType.Snipe:
|
||||
case EInteractionType.Instruction:
|
||||
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new WaitNextStepOrSequence());
|
||||
case EInteractionType.Duty:
|
||||
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
|
||||
{
|
||||
break;
|
||||
}
|
||||
goto IL_019d;
|
||||
case EInteractionType.SinglePlayerDuty:
|
||||
if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions))
|
||||
{
|
||||
break;
|
||||
}
|
||||
goto IL_019d;
|
||||
case EInteractionType.WalkTo:
|
||||
case EInteractionType.Jump:
|
||||
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(Next(quest, sequence));
|
||||
case EInteractionType.WaitForObjectAtPosition:
|
||||
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
|
||||
ArgumentNullException.ThrowIfNull(step.Position, "step.Position");
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
|
||||
{
|
||||
new WaitObjectAtPosition(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.5f),
|
||||
new WaitDelay(),
|
||||
Next(quest, sequence)
|
||||
});
|
||||
case EInteractionType.Interact:
|
||||
if (!step.TargetTerritoryId.HasValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
goto IL_0284;
|
||||
case EInteractionType.UseItem:
|
||||
if (!step.TargetTerritoryId.HasValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
goto IL_0284;
|
||||
case EInteractionType.AcceptQuest:
|
||||
{
|
||||
WaitQuestAccepted waitQuestAccepted = new WaitQuestAccepted(step.PickUpQuestId ?? quest.Id);
|
||||
WaitDelay waitDelay3 = new WaitDelay();
|
||||
if (step.PickUpQuestId != null)
|
||||
{
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
|
||||
{
|
||||
waitQuestAccepted,
|
||||
waitDelay3,
|
||||
Next(quest, sequence)
|
||||
});
|
||||
}
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { waitQuestAccepted, waitDelay3 });
|
||||
}
|
||||
case EInteractionType.CompleteQuest:
|
||||
{
|
||||
WaitQuestCompleted waitQuestCompleted = new WaitQuestCompleted(step.TurnInQuestId ?? quest.Id);
|
||||
WaitDelay waitDelay2 = new WaitDelay();
|
||||
if (step.TurnInQuestId != null)
|
||||
{
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
|
||||
{
|
||||
waitQuestCompleted,
|
||||
waitDelay2,
|
||||
Next(quest, sequence)
|
||||
});
|
||||
}
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2] { waitQuestCompleted, waitDelay2 });
|
||||
}
|
||||
IL_019d:
|
||||
return new global::_003C_003Ez__ReadOnlySingleElementList<ITask>(new EndAutomation());
|
||||
IL_0284:
|
||||
if (step.TerritoryId != step.TargetTerritoryId)
|
||||
{
|
||||
task = new WaitCondition.Task(() => clientState.TerritoryType == step.TargetTerritoryId, "Wait(tp to territory: " + territoryData.GetNameAndId(step.TargetTerritoryId.Value) + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
||||
task = new WaitCondition.Task(delegate
|
||||
{
|
||||
Vector3? vector = clientState.LocalPlayer?.Position;
|
||||
return vector.HasValue && (lastPosition - vector.Value).Length() > 2f;
|
||||
}, "Wait(tp away from " + lastPosition.ToString("G", CultureInfo.InvariantCulture) + ")");
|
||||
}
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[3]
|
||||
{
|
||||
task,
|
||||
new WaitDelay(),
|
||||
Next(quest, sequence)
|
||||
});
|
||||
}
|
||||
return new global::_003C_003Ez__ReadOnlyArray<ITask>(new ITask[2]
|
||||
{
|
||||
new WaitDelay(),
|
||||
Next(quest, sequence)
|
||||
});
|
||||
}
|
||||
|
||||
private static NextStep Next(Quest quest, QuestSequence sequence)
|
||||
{
|
||||
return new NextStep(quest.Id, sequence.Sequence);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitDelay(TimeSpan Delay) : ITask
|
||||
{
|
||||
public WaitDelay()
|
||||
: this(TimeSpan.FromSeconds(1L))
|
||||
{
|
||||
}
|
||||
|
||||
public bool ShouldRedoOnInterrupt()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Wait(seconds: {Delay.TotalSeconds})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitDelayExecutor : AbstractDelayedTaskExecutor<WaitDelay>
|
||||
{
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
base.Delay = base.Task.Delay;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitNextStepOrSequence : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Wait(next step or sequence)";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitNextStepOrSequenceExecutor : TaskExecutor<WaitNextStepOrSequence>
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitForCompletionFlags(QuestId Quest, QuestStep Step) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "Wait(QW: " + string.Join(", ", Step.CompletionQuestVariablesFlags.Select((QuestWorkValue x) => x?.ToString() ?? "-")) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitForCompletionFlagsExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitForCompletionFlags>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
QuestProgressInfo questProgressInfo = questFunctions.GetQuestProgressInfo(base.Task.Quest);
|
||||
if (questProgressInfo == null || !QuestWorkUtils.MatchesQuestWork(base.Task.Step.CompletionQuestVariablesFlags, questProgressInfo))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitObjectAtPosition(uint DataId, Vector3 Destination, float Distance) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)} < {Distance})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitObjectAtPositionExecutor(GameFunctions gameFunctions) : TaskExecutor<WaitObjectAtPosition>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!gameFunctions.IsObjectAtPosition(base.Task.DataId, base.Task.Destination, base.Task.Distance))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitQuestAccepted(ElementId ElementId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"WaitQuestAccepted({ElementId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitQuestAcceptedExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitQuestAccepted>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!questFunctions.IsQuestAccepted(base.Task.ElementId))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitQuestCompleted(ElementId ElementId) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"WaitQuestComplete({ElementId})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitQuestCompletedExecutor(QuestFunctions questFunctions) : TaskExecutor<WaitQuestCompleted>()
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (!questFunctions.IsQuestComplete(base.Task.ElementId))
|
||||
{
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record NextStep(ElementId ElementId, int Sequence) : ILastTask, ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "NextStep";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NextStepExecutor : TaskExecutor<NextStep>
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.NextStep;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EndAutomation : ILastTask, ITask
|
||||
{
|
||||
public ElementId ElementId
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public int Sequence
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "EndAutomation";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EndAutomationExecutor : TaskExecutor<EndAutomation>
|
||||
{
|
||||
protected override bool Start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
return ETaskResult.End;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Steps.Shared;
|
||||
|
||||
internal static class WaitAtStart
|
||||
{
|
||||
internal sealed class Factory : SimpleTaskFactory
|
||||
{
|
||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
if (!step.DelaySecondsAtStart.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new WaitDelay(TimeSpan.FromSeconds(step.DelaySecondsAtStart.Value));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitDelay(TimeSpan Delay) : ITask
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Wait[S](seconds: {Delay.TotalSeconds})";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WaitDelayExecutor : AbstractDelayedTaskExecutor<WaitDelay>
|
||||
{
|
||||
protected override bool StartInternal()
|
||||
{
|
||||
base.Delay = base.Task.Delay;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue