273 lines
9.9 KiB
C#
273 lines
9.9 KiB
C#
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 FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Questionable.Controller.Steps.BaseTasks;
|
|
using Questionable.Model;
|
|
using Questionable.Model.V1;
|
|
|
|
namespace Questionable.Controller.Steps.BaseFactory;
|
|
|
|
internal static class WaitAtEnd
|
|
{
|
|
internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState, ICondition condition)
|
|
: ITaskFactory
|
|
{
|
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
|
{
|
|
if (step.CompletionQuestVariablesFlags.Count == 6 && step.CompletionQuestVariablesFlags.Any(x => x is > 0))
|
|
{
|
|
var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
|
|
.With(quest, step);
|
|
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
|
return [task, delay, Next(quest, sequence)];
|
|
}
|
|
|
|
switch (step.InteractionType)
|
|
{
|
|
case EInteractionType.Combat:
|
|
var notInCombat =
|
|
new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
|
|
return
|
|
[
|
|
serviceProvider.GetRequiredService<WaitDelay>(),
|
|
notInCombat,
|
|
serviceProvider.GetRequiredService<WaitDelay>(),
|
|
Next(quest, sequence)
|
|
];
|
|
|
|
case EInteractionType.WaitForManualProgress:
|
|
case EInteractionType.ShouldBeAJump:
|
|
case EInteractionType.Instruction:
|
|
return [serviceProvider.GetRequiredService<WaitNextStepOrSequence>()];
|
|
|
|
case EInteractionType.Duty:
|
|
case EInteractionType.SinglePlayerDuty:
|
|
return [new EndAutomation()];
|
|
|
|
case EInteractionType.WalkTo:
|
|
case EInteractionType.Jump:
|
|
// no need to wait if we're just moving around
|
|
return [Next(quest, sequence)];
|
|
|
|
case EInteractionType.WaitForObjectAtPosition:
|
|
ArgumentNullException.ThrowIfNull(step.DataId);
|
|
ArgumentNullException.ThrowIfNull(step.Position);
|
|
|
|
return
|
|
[
|
|
serviceProvider.GetRequiredService<WaitObjectAtPosition>()
|
|
.With(step.DataId.Value, step.Position.Value, step.NpcWaitDistance ?? 0.05f),
|
|
serviceProvider.GetRequiredService<WaitDelay>(),
|
|
Next(quest, sequence)
|
|
];
|
|
|
|
case EInteractionType.Interact when step.TargetTerritoryId != null:
|
|
case EInteractionType.UseItem when step.TargetTerritoryId != null:
|
|
ITask waitInteraction;
|
|
if (step.TerritoryId != step.TargetTerritoryId)
|
|
{
|
|
// interaction moves to a different territory
|
|
waitInteraction = new WaitConditionTask(
|
|
() => clientState.TerritoryType == step.TargetTerritoryId,
|
|
$"Wait(tp to territory: {step.TargetTerritoryId})");
|
|
}
|
|
else
|
|
{
|
|
Vector3 lastPosition = step.Position ?? clientState.LocalPlayer?.Position ?? Vector3.Zero;
|
|
waitInteraction = new WaitConditionTask(() =>
|
|
{
|
|
Vector3? currentPosition = clientState.LocalPlayer?.Position;
|
|
if (currentPosition == null)
|
|
return false;
|
|
|
|
// interaction moved to elsewhere in the zone
|
|
// the 'closest' locations are probably
|
|
// - waking sands' solar
|
|
// - rising stones' solar + dawn's respite
|
|
return (lastPosition - currentPosition.Value).Length() > 2;
|
|
}, $"Wait(tp away from {lastPosition.ToString("G", CultureInfo.InvariantCulture)})");
|
|
}
|
|
|
|
return
|
|
[
|
|
waitInteraction,
|
|
serviceProvider.GetRequiredService<WaitDelay>(),
|
|
Next(quest, sequence)
|
|
];
|
|
|
|
case EInteractionType.AcceptQuest:
|
|
return
|
|
[
|
|
serviceProvider.GetRequiredService<WaitQuestAccepted>().With(step.QuestId ?? quest.QuestId),
|
|
serviceProvider.GetRequiredService<WaitDelay>()
|
|
];
|
|
|
|
case EInteractionType.CompleteQuest:
|
|
return
|
|
[
|
|
serviceProvider.GetRequiredService<WaitQuestCompleted>().With(step.QuestId ?? quest.QuestId),
|
|
serviceProvider.GetRequiredService<WaitDelay>()
|
|
];
|
|
|
|
case EInteractionType.Interact:
|
|
default:
|
|
return [serviceProvider.GetRequiredService<WaitDelay>(), Next(quest, sequence)];
|
|
}
|
|
}
|
|
|
|
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
|
=> throw new InvalidOperationException();
|
|
|
|
private static NextStep Next(Quest quest, QuestSequence sequence)
|
|
{
|
|
return new NextStep(quest.QuestId, sequence.Sequence);
|
|
}
|
|
}
|
|
|
|
internal sealed class WaitDelay() : AbstractDelayedTask(TimeSpan.FromSeconds(1))
|
|
{
|
|
protected override bool StartInternal() => true;
|
|
|
|
public override string ToString() => $"Wait(seconds: {Delay.TotalSeconds})";
|
|
}
|
|
|
|
internal sealed class WaitNextStepOrSequence : ITask
|
|
{
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update() => ETaskResult.StillRunning;
|
|
|
|
public override string ToString() => "Wait(next step or sequence)";
|
|
}
|
|
|
|
internal sealed class WaitForCompletionFlags(GameFunctions gameFunctions) : ITask
|
|
{
|
|
public Quest Quest { get; set; } = null!;
|
|
public QuestStep Step { get; set; } = null!;
|
|
public IList<short?> Flags { get; set; } = null!;
|
|
|
|
public ITask With(Quest quest, QuestStep step)
|
|
{
|
|
Quest = quest;
|
|
Step = step;
|
|
Flags = step.CompletionQuestVariablesFlags;
|
|
return this;
|
|
}
|
|
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update()
|
|
{
|
|
QuestWork? questWork = gameFunctions.GetQuestEx(Quest.QuestId);
|
|
return questWork != null && Step.MatchesQuestVariables(questWork.Value, false)
|
|
? ETaskResult.TaskComplete
|
|
: ETaskResult.StillRunning;
|
|
}
|
|
|
|
public override string ToString() =>
|
|
$"WaitCF({string.Join(", ", Flags.Select(x => x?.ToString(CultureInfo.InvariantCulture) ?? "-"))})";
|
|
}
|
|
|
|
internal sealed class WaitObjectAtPosition(GameFunctions gameFunctions) : ITask
|
|
{
|
|
public uint DataId { get; set; }
|
|
public Vector3 Destination { get; set; }
|
|
public float Distance { get; set; }
|
|
|
|
public ITask With(uint dataId, Vector3 destination, float distance)
|
|
{
|
|
DataId = dataId;
|
|
Destination = destination;
|
|
Distance = distance;
|
|
return this;
|
|
}
|
|
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update() =>
|
|
gameFunctions.IsObjectAtPosition(DataId, Destination, Distance)
|
|
? ETaskResult.TaskComplete
|
|
: ETaskResult.StillRunning;
|
|
|
|
public override string ToString() =>
|
|
$"WaitObj({DataId} at {Destination.ToString("G", CultureInfo.InvariantCulture)})";
|
|
}
|
|
|
|
internal sealed class WaitQuestAccepted : ITask
|
|
{
|
|
public ushort QuestId { get; set; }
|
|
|
|
public ITask With(ushort questId)
|
|
{
|
|
QuestId = questId;
|
|
return this;
|
|
}
|
|
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update()
|
|
{
|
|
unsafe
|
|
{
|
|
var questManager = QuestManager.Instance();
|
|
return questManager != null && questManager->IsQuestAccepted(QuestId)
|
|
? ETaskResult.TaskComplete
|
|
: ETaskResult.StillRunning;
|
|
}
|
|
}
|
|
|
|
public override string ToString() => $"WaitQuestAccepted({QuestId})";
|
|
}
|
|
|
|
internal sealed class WaitQuestCompleted : ITask
|
|
{
|
|
public ushort QuestId { get; set; }
|
|
|
|
public ITask With(ushort questId)
|
|
{
|
|
QuestId = questId;
|
|
return this;
|
|
}
|
|
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update()
|
|
{
|
|
return QuestManager.IsQuestComplete(QuestId) ? ETaskResult.TaskComplete : ETaskResult.StillRunning;
|
|
}
|
|
|
|
public override string ToString() => $"WaitQuestComplete({QuestId})";
|
|
}
|
|
|
|
internal sealed class NextStep(ushort questId, int sequence) : ILastTask
|
|
{
|
|
public ushort QuestId { get; } = questId;
|
|
public int Sequence { get; } = sequence;
|
|
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update() => ETaskResult.NextStep;
|
|
|
|
public override string ToString() => "NextStep";
|
|
}
|
|
|
|
internal sealed class EndAutomation : ILastTask
|
|
{
|
|
public ushort QuestId => throw new InvalidOperationException();
|
|
public int Sequence => throw new InvalidOperationException();
|
|
|
|
public bool Start() => true;
|
|
|
|
public ETaskResult Update() => ETaskResult.End;
|
|
|
|
public override string ToString() => "EndAutomation";
|
|
}
|
|
}
|