punish v6.8.18.0

This commit is contained in:
alydev 2025-10-09 07:47:19 +10:00
commit cfb4dea47e
316 changed files with 554088 additions and 0 deletions

View file

@ -0,0 +1,11 @@
namespace Questionable.Controller.Steps;
internal enum ETaskResult
{
StillRunning,
TaskComplete,
SkipRemainingTasksForStep,
CreateNewTasks,
NextStep,
End
}

View file

@ -0,0 +1,8 @@
using Dalamud.Game.ClientState.Conditions;
namespace Questionable.Controller.Steps;
internal interface IConditionChangeAware : ITaskExecutor
{
void OnConditionChange(ConditionFlag flag, bool value);
}

View file

@ -0,0 +1,6 @@
namespace Questionable.Controller.Steps;
internal interface IDebugStateProvider : ITaskExecutor
{
string? GetDebugState();
}

View file

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Questionable.Controller.Steps;
internal interface IExtraTaskCreator : ITaskExecutor
{
IEnumerable<ITask> CreateExtraTasks();
}

View file

@ -0,0 +1,10 @@
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps;
internal interface ILastTask : ITask
{
ElementId ElementId { get; }
int Sequence { get; }
}

View file

@ -0,0 +1,6 @@
namespace Questionable.Controller.Steps;
internal interface IRevisitAware
{
void OnRevisit();
}

View file

@ -0,0 +1,5 @@
namespace Questionable.Controller.Steps;
internal interface ISkippableTask : ITask
{
}

View file

@ -0,0 +1,6 @@
namespace Questionable.Controller.Steps;
internal interface IStoppableTaskExecutor : ITaskExecutor
{
void StopNow();
}

View file

@ -0,0 +1,9 @@
namespace Questionable.Controller.Steps;
internal interface ITask
{
bool ShouldRedoOnInterrupt()
{
return false;
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace Questionable.Controller.Steps;
internal interface ITaskExecutor
{
ITask CurrentTask { get; }
InteractionProgressContext? ProgressContext { get; }
Type GetTaskType();
bool Start(ITask task);
bool ShouldInterruptOnDamage();
bool WasInterrupted();
ETaskResult Update();
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps;
internal interface ITaskFactory
{
IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step);
}

View file

@ -0,0 +1,8 @@
using Dalamud.Game.Text.SeStringHandling;
namespace Questionable.Controller.Steps;
internal interface IToastAware : ITaskExecutor
{
bool OnErrorToast(SeString message);
}

View file

@ -0,0 +1,112 @@
using System;
using System.Runtime.CompilerServices;
using FFXIVClientStructs.FFXIV.Client.Game;
namespace Questionable.Controller.Steps;
internal sealed class InteractionProgressContext
{
private bool _firstUpdateDone;
public bool CheckSequence { get; private set; }
public int CurrentSequence { get; private set; }
private InteractionProgressContext(bool checkSequence, int currentSequence)
{
CheckSequence = checkSequence;
CurrentSequence = currentSequence;
}
public unsafe static InteractionProgressContext Create(bool checkSequence)
{
if (!checkSequence)
{
ActionManager.Instance()->CastTimeElapsed = ActionManager.Instance()->CastTimeTotal;
}
return new InteractionProgressContext(checkSequence, ActionManager.Instance()->LastUsedActionSequence);
}
private unsafe static (bool, InteractionProgressContext?) FromActionUseInternal(Func<bool> func)
{
int lastUsedActionSequence = ActionManager.Instance()->LastUsedActionSequence;
if (!func())
{
return (false, null);
}
int lastUsedActionSequence2 = ActionManager.Instance()->LastUsedActionSequence;
if (lastUsedActionSequence == lastUsedActionSequence2)
{
return (true, null);
}
return (true, Create(checkSequence: true));
}
public static InteractionProgressContext? FromActionUse(Func<bool> func)
{
return FromActionUseInternal(func).Item2;
}
public static InteractionProgressContext? FromActionUseOrDefault(Func<bool> func)
{
(bool, InteractionProgressContext) tuple = FromActionUseInternal(func);
if (!tuple.Item1)
{
return null;
}
return tuple.Item2 ?? Create(checkSequence: false);
}
public unsafe void Update()
{
if (!_firstUpdateDone)
{
int lastUsedActionSequence = ActionManager.Instance()->LastUsedActionSequence;
if (!CheckSequence && lastUsedActionSequence > CurrentSequence)
{
CheckSequence = true;
CurrentSequence = lastUsedActionSequence;
}
_firstUpdateDone = true;
}
}
public unsafe bool WasSuccessful()
{
if (CheckSequence && (CurrentSequence != ActionManager.Instance()->LastUsedActionSequence || CurrentSequence != ActionManager.Instance()->LastHandledActionSequence))
{
return false;
}
if (ActionManager.Instance()->CastTimeElapsed > 0f)
{
return Math.Abs(ActionManager.Instance()->CastTimeElapsed - ActionManager.Instance()->CastTimeTotal) < 0.001f;
}
return false;
}
public unsafe bool WasInterrupted()
{
if (CheckSequence && CurrentSequence == ActionManager.Instance()->LastHandledActionSequence && CurrentSequence == ActionManager.Instance()->LastUsedActionSequence)
{
return false;
}
if (ActionManager.Instance()->CastTimeElapsed == 0f)
{
return ActionManager.Instance()->CastTimeTotal > 0f;
}
return false;
}
public override string ToString()
{
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(12, 3);
defaultInterpolatedStringHandler.AppendLiteral("IPCtx(");
defaultInterpolatedStringHandler.AppendFormatted(CheckSequence ? ((object)CurrentSequence) : "-");
defaultInterpolatedStringHandler.AppendLiteral(" - ");
defaultInterpolatedStringHandler.AppendFormatted(WasSuccessful());
defaultInterpolatedStringHandler.AppendLiteral(", ");
defaultInterpolatedStringHandler.AppendFormatted(WasInterrupted());
defaultInterpolatedStringHandler.AppendLiteral(")");
return defaultInterpolatedStringHandler.ToStringAndClear();
}
}

View file

@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Common;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps;
internal static class QuestCleanUp
{
internal sealed class CheckAlliedSocietyMount(GameFunctions gameFunctions, AetheryteData aetheryteData, AlliedSocietyData alliedSocietyData, ILogger<CheckAlliedSocietyMount> logger) : SimpleTaskFactory()
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (sequence.Sequence == 0)
{
return null;
}
ushort? mountId = gameFunctions.GetMountId();
if (mountId.HasValue)
{
ushort valueOrDefault = mountId.GetValueOrDefault();
if (alliedSocietyData.Mounts.TryGetValue(valueOrDefault, out AlliedSocietyMountConfiguration mountConfiguration))
{
logger.LogInformation("We are on a known allied society mount with id = {MountId}", valueOrDefault);
EAetheryteLocation eAetheryteLocation = step.AetheryteShortcut ?? mountConfiguration.ClosestAetheryte;
AetheryteShortcut.Task result = new AetheryteShortcut.Task(null, quest.Id, eAetheryteLocation, aetheryteData.TerritoryIds[eAetheryteLocation]);
if (sequence.Sequence == byte.MaxValue)
{
logger.LogInformation("Mount can't be used to finish quest, teleporting to {Aetheryte}", mountConfiguration.ClosestAetheryte);
return result;
}
if (!quest.AllSteps().Any<(QuestSequence, int, QuestStep)>(delegate((QuestSequence Sequence, int StepId, QuestStep Step) x)
{
EAction? action = x.Step.Action;
if (action.HasValue)
{
EAction valueOrDefault2 = action.GetValueOrDefault();
if (valueOrDefault2.RequiresMount())
{
return true;
}
}
return x.Step.InteractionType == EInteractionType.Combat && x.Step.KillEnemyDataIds.Contains(8593u);
}))
{
logger.LogInformation("Quest doesn't use any mount actions, teleporting to {Aetheryte}", mountConfiguration.ClosestAetheryte);
return result;
}
if (!(from x in quest.AllSequences()
where x.Sequence > 0 && x.Sequence < sequence.Sequence
select x).SelectMany((QuestSequence x) => x.Steps).ToList().Any((QuestStep x) => x.DataId.HasValue && mountConfiguration.IssuerDataIds.Contains(x.DataId.Value)))
{
logger.LogInformation("Haven't talked to mount NPC for this allied society quest; {Aetheryte}", mountConfiguration.ClosestAetheryte);
return result;
}
}
}
return null;
}
}
internal sealed class CloseGatheringAddonFactory(IGameGui gameGui) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (IsAddonOpen("GatheringMasterpiece"))
{
yield return new CloseGatheringAddonTask("GatheringMasterpiece");
}
if (IsAddonOpen("Gathering"))
{
yield return new CloseGatheringAddonTask("Gathering");
}
}
private unsafe bool IsAddonOpen(string name)
{
if (gameGui.TryGetAddonByName<AtkUnitBase>(name, out var addonPtr))
{
return addonPtr->IsVisible;
}
return false;
}
}
internal sealed record CloseGatheringAddonTask(string AddonName) : ITask
{
public override string ToString()
{
return "CloseAddon(" + AddonName + ")";
}
}
internal sealed class DoCloseAddon(IGameGui gameGui) : TaskExecutor<CloseGatheringAddonTask>()
{
protected unsafe override bool Start()
{
if (gameGui.TryGetAddonByName<AtkUnitBase>(base.Task.AddonName, out var addonPtr))
{
addonPtr->FireCallbackInt(-1);
return true;
}
return false;
}
public override ETaskResult Update()
{
return ETaskResult.TaskComplete;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps;
internal abstract class SimpleTaskFactory : ITaskFactory
{
public abstract ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step);
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
ITask task = CreateTask(quest, sequence, step);
if (task != null)
{
yield return task;
}
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps;
internal sealed class TaskCreator
{
private readonly IServiceProvider _serviceProvider;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private readonly IChatGui _chatGui;
private readonly ILogger<TaskCreator> _logger;
public TaskCreator(IServiceProvider serviceProvider, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger<TaskCreator> logger)
{
_serviceProvider = serviceProvider;
_territoryData = territoryData;
_clientState = clientState;
_chatGui = chatGui;
_logger = logger;
}
public IReadOnlyList<ITask> CreateTasks(Quest quest, byte sequenceNumber, QuestSequence? sequence, QuestStep? step)
{
List<ITask> list2;
if (sequence == null)
{
_chatGui.PrintError($"Path for quest '{quest.Info.Name}' ({quest.Id}) does not contain sequence {sequenceNumber}, please report this: https://github.com/PunishXIV/Questionable/discussions/20", "Questionable", 576);
int num = 1;
List<ITask> list = new List<ITask>(num);
CollectionsMarshal.SetCount(list, num);
Span<ITask> span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = new WaitAtEnd.WaitNextStepOrSequence();
list2 = list;
}
else if (step == null)
{
int index = 1;
List<ITask> list3 = new List<ITask>(index);
CollectionsMarshal.SetCount(list3, index);
Span<ITask> span = CollectionsMarshal.AsSpan(list3);
int num = 0;
span[num] = new WaitAtEnd.WaitNextStepOrSequence();
list2 = list3;
}
else
{
using IServiceScope serviceScope = _serviceProvider.CreateScope();
list2 = serviceScope.ServiceProvider.GetRequiredService<IEnumerable<ITaskFactory>>().SelectMany(delegate(ITaskFactory x)
{
List<ITask> list4 = x.CreateAllTasks(quest, sequence, step).ToList();
if (list4.Count > 0 && _logger.IsEnabled(LogLevel.Trace))
{
string text = x.GetType().FullName ?? x.GetType().Name;
if (text.Contains('.', StringComparison.Ordinal))
{
string text2 = text;
int num3 = text.LastIndexOf('.') + 1;
text = text2.Substring(num3, text2.Length - num3);
}
_logger.LogTrace("Factory {FactoryName} created Task {TaskNames}", text, string.Join(", ", list4.Select((ITask y) => y.ToString())));
}
return list4;
}).ToList();
SinglePlayerDuty.StartSinglePlayerDuty startSinglePlayerDuty = list2.Where((ITask y) => y is SinglePlayerDuty.StartSinglePlayerDuty).Cast<SinglePlayerDuty.StartSinglePlayerDuty>().FirstOrDefault();
if (startSinglePlayerDuty != null && _territoryData.TryGetContentFinderCondition(startSinglePlayerDuty.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData) && _clientState.TerritoryType == contentFinderConditionData.TerritoryId)
{
int num2 = list2.IndexOf(startSinglePlayerDuty);
_logger.LogWarning("Skipping {SkippedTaskCount} out of {TotalCount} tasks, questionable was started while in single player duty", num2 + 1, list2.Count);
list2.RemoveRange(0, num2 + 1);
_logger.LogInformation("Next actual task: {NextTask}, total tasks left: {RemainingTaskCount}", list2.FirstOrDefault(), list2.Count);
}
}
if (list2.Count == 0)
{
_logger.LogInformation("Nothing to execute for step?");
}
else
{
_logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}", quest.Id, sequenceNumber, (step == null) ? ((int?)null) : sequence?.Steps.IndexOf(step), string.Join(", ", list2.Select((ITask x) => x.ToString())));
}
return list2;
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace Questionable.Controller.Steps;
public class TaskException : Exception
{
public TaskException()
{
}
public TaskException(string message)
: base(message)
{
}
public TaskException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View file

@ -0,0 +1,44 @@
using System;
namespace Questionable.Controller.Steps;
internal abstract class TaskExecutor<T> : ITaskExecutor where T : class, ITask
{
protected T Task { get; set; }
public InteractionProgressContext? ProgressContext { get; set; }
ITask ITaskExecutor.CurrentTask => Task;
public virtual bool WasInterrupted()
{
InteractionProgressContext progressContext = ProgressContext;
if (progressContext != null)
{
progressContext.Update();
return progressContext.WasInterrupted();
}
return false;
}
public Type GetTaskType()
{
return typeof(T);
}
protected abstract bool Start();
public bool Start(ITask task)
{
if (task is T task2)
{
Task = task2;
return Start();
}
throw new TaskException($"Unable to cast {task.GetType()} to {typeof(T)}");
}
public abstract ETaskResult Update();
public abstract bool ShouldInterruptOnDamage();
}

View file

@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Questionable.Controller.Steps;
internal sealed class TaskQueue
{
private readonly List<ITask> _completedTasks = new List<ITask>();
private readonly List<ITask> _tasks = new List<ITask>();
public ITaskExecutor? CurrentTaskExecutor { get; set; }
public IEnumerable<ITask> RemainingTasks => _tasks;
public bool AllTasksComplete
{
get
{
if (CurrentTaskExecutor == null)
{
return _tasks.Count == 0;
}
return false;
}
}
public void Enqueue(ITask task)
{
_tasks.Add(task);
}
public void EnqueueAll(IEnumerable<ITask> tasks)
{
_tasks.InsertRange(0, tasks);
}
public bool TryDequeue([NotNullWhen(true)] out ITask? task)
{
task = _tasks.FirstOrDefault();
if (task == null)
{
return false;
}
if (task.ShouldRedoOnInterrupt())
{
_completedTasks.Add(task);
}
_tasks.RemoveAt(0);
return true;
}
public bool TryPeek([NotNullWhen(true)] out ITask? task)
{
task = _tasks.FirstOrDefault();
return task != null;
}
public void Reset()
{
_tasks.Clear();
_completedTasks.Clear();
CurrentTaskExecutor = null;
}
public void InterruptWith(List<ITask> interruptionTasks)
{
List<ITask> list = new List<ITask>();
list.AddRange(interruptionTasks);
list.AddRange(_completedTasks.Where((ITask x) => x != CurrentTaskExecutor?.CurrentTask).ToList());
list.Add(CurrentTaskExecutor?.CurrentTask);
list.AddRange(_tasks);
List<ITask> source = list;
Reset();
_tasks.AddRange(source.Where((ITask x) => x != null).Cast<ITask>());
}
}