Assignment(nameof(QuestStep.ChatMessage), step.ChatMessage,
emptyStep.ChatMessage)
.AsSyntaxNodeOrToken(),
+ Assignment(nameof(QuestStep.Action), step.Action, emptyStep.Action)
+ .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.EnemySpawnType), step.EnemySpawnType,
emptyStep.EnemySpawnType)
.AsSyntaxNodeOrToken(),
"InteractionType": "AcceptQuest"
}
]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1046291,
+ "Position": {
+ "X": -409.07916,
+ "Y": 3.9999695,
+ "Z": 14.846985
+ },
+ "TerritoryId": 129,
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Arcanists' Guild"
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "Position": {
+ "X": -387.69412,
+ "Y": 5.999984,
+ "Z": 41.170013
+ },
+ "TerritoryId": 129,
+ "InteractionType": "WalkTo"
+ },
+ {
+ "DataId": 1046292,
+ "Position": {
+ "X": -195.36127,
+ "Y": 19.999954,
+ "Z": 112.962524
+ },
+ "TerritoryId": 129,
+ "InteractionType": "Interact",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Arcanists' Guild",
+ "[Limsa Lominsa] Hawkers' Alley"
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 3,
+ "Steps": [
+ {
+ "DataId": 1001540,
+ "Position": {
+ "X": -202.68567,
+ "Y": 16,
+ "Z": 56.99243
+ },
+ "TerritoryId": 129,
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
+ },
+ {
+ "DataId": 1003272,
+ "Position": {
+ "X": -262.92822,
+ "Y": 16.2,
+ "Z": 51.407593
+ },
+ "TerritoryId": 129,
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
+ },
+ {
+ "DataId": 1003277,
+ "Position": {
+ "X": -136.67511,
+ "Y": 18.2,
+ "Z": 16.494995
+ },
+ "TerritoryId": 129,
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 4,
+ "Steps": [
+ {
+ "DataId": 1046293,
+ "Position": {
+ "X": -143.35852,
+ "Y": 3.9999998,
+ "Z": 189.6543
+ },
+ "TerritoryId": 129,
+ "InteractionType": "Interact",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Fishermens' Guild"
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 5,
+ "Steps": [
+ {
+ "DataId": 1046294,
+ "Position": {
+ "X": -115.19043,
+ "Y": 20,
+ "Z": 111.95532
+ },
+ "StopDistance": 1,
+ "TerritoryId": 129,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 6,
+ "Steps": [
+ {
+ "DataId": 1046296,
+ "Position": {
+ "X": -114.42743,
+ "Y": 20,
+ "Z": 111.283936
+ },
+ "StopDistance": 10,
+ "TerritoryId": 129,
+ "InteractionType": "Action",
+ "Action": "Esuna"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "CompleteQuest"
+ }
+ ]
}
]
}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1047092,
+ "Position": {
+ "X": 297.38306,
+ "Y": -33.02986,
+ "Z": 284.99268
+ },
+ "TerritoryId": 138,
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Western La Noscea - Aleport"
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1046297,
+ "Position": {
+ "X": 211.68835,
+ "Y": -25.006758,
+ "Z": 230.85376
+ },
+ "TerritoryId": 138,
+ "InteractionType": "Interact",
+ "Fly": true
+ }
+ ]
+ },
+ {
+ "Sequence": 3,
+ "Steps": [
+ {
+ "Position": {
+ "X": 465.86716,
+ "Y": 11.231914,
+ "Z": 326.10486
+ },
+ "StopDistance": 0.25,
+ "TerritoryId": 138,
+ "InteractionType": "Combat",
+ "EnemySpawnType": "AutoOnEnterArea",
+ "KillEnemyDataIds": [
+ 17612,
+ 17613
+ ],
+ "Fly": true
+ }
+ ]
+ },
+ {
+ "Sequence": 4,
+ "Steps": [
+ {
+ "DataId": 1046302,
+ "Position": {
+ "X": 465.50684,
+ "Y": 11.444184,
+ "Z": 330.89185
+ },
+ "StopDistance": 7,
+ "TerritoryId": 138,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 5,
+ "Steps": [
+ {
+ "DataId": 1046303,
+ "Position": {
+ "X": 462.39404,
+ "Y": 11.569952,
+ "Z": 329.57947
+ },
+ "StopDistance": 10,
+ "TerritoryId": 138,
+ "InteractionType": "Action",
+ "Action": "Esuna"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "CompleteQuest",
+ "AetheryteShortcut": "Limsa Lominsa"
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1046307,
+ "Position": {
+ "X": 216.84595,
+ "Y": 14.096056,
+ "Z": 660.3646
+ },
+ "TerritoryId": 135,
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Lower La Noscea - Moraby Drydocks"
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1046309,
+ "Position": {
+ "X": 106.7063,
+ "Y": 22.880846,
+ "Z": 618.4634
+ },
+ "TerritoryId": 135,
+ "InteractionType": "Interact",
+ "Fly": true
+ }
+ ]
+ },
+ {
+ "Sequence": 3,
+ "Steps": [
+ {
+ "DataId": 1046308,
+ "Position": {
+ "X": 217.39526,
+ "Y": 14.096056,
+ "Z": 658.7776
+ },
+ "TerritoryId": 135,
+ "InteractionType": "Interact",
+ "Fly": true
+ }
+ ]
+ },
+ {
+ "Sequence": 4,
+ "Steps": [
+ {
+ "DataId": 1046307,
+ "Position": {
+ "X": 216.84595,
+ "Y": 14.096056,
+ "Z": 660.3646
+ },
+ "TerritoryId": 135,
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_KINGBA221_04826_Q1_000_048",
+ "Answer": "TEXT_KINGBA221_04826_A1_000_002"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "CompleteQuest",
+ "AetheryteShortcut": "Limsa Lominsa"
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$schema": "https://carvel.li/questionable/quest-1.0",
+ "Author": "liza",
+ "QuestSequence": [
+ {
+ "Sequence": 0,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "AcceptQuest"
+ }
+ ]
+ },
+ {
+ "Sequence": 1,
+ "Steps": [
+ {
+ "DataId": 1046310,
+ "Position": {
+ "X": 268.39087,
+ "Y": -25,
+ "Z": 264.05737
+ },
+ "TerritoryId": 138,
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Western La Noscea - Aleport"
+ }
+ ]
+ },
+ {
+ "Sequence": 2,
+ "Steps": [
+ {
+ "DataId": 1046311,
+ "Position": {
+ "X": 384.60352,
+ "Y": 0.14576934,
+ "Z": 74.32666
+ },
+ "TerritoryId": 139,
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Upper La Noscea - Camp Bronze Lake"
+ }
+ ]
+ },
+ {
+ "Sequence": 3,
+ "Steps": [
+ {
+ "DataId": 1046314,
+ "Position": {
+ "X": 457.60278,
+ "Y": 4.1072555,
+ "Z": 103.89868
+ },
+ "TerritoryId": 139,
+ "InteractionType": "Action",
+ "Action": "Esuna",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
+ },
+ {
+ "DataId": 1046313,
+ "Position": {
+ "X": 432.6084,
+ "Y": 8.108173,
+ "Z": 133.80627
+ },
+ "TerritoryId": 139,
+ "InteractionType": "Action",
+ "Action": "Esuna",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
+ },
+ {
+ "DataId": 1046312,
+ "Position": {
+ "X": 413.0464,
+ "Y": 3.616333,
+ "Z": 113.969604
+ },
+ "TerritoryId": 139,
+ "InteractionType": "Action",
+ "Action": "Esuna",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
+ }
+ ]
+ },
+ {
+ "Sequence": 4,
+ "Steps": [
+ {
+ "DataId": 1046316,
+ "Position": {
+ "X": 415.8236,
+ "Y": 8.12099,
+ "Z": 40.72632
+ },
+ "TerritoryId": 139,
+ "InteractionType": "Interact"
+ }
+ ]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1046290,
+ "Position": {
+ "X": -114.091736,
+ "Y": 20,
+ "Z": 111.436646
+ },
+ "TerritoryId": 129,
+ "InteractionType": "CompleteQuest"
+ }
+ ]
+ }
+ ]
+}
"EquipItem",
"Say",
"Emote",
+ "Action",
"WaitForNpcAtPosition",
"WaitForManualProgress",
"Duty",
]
}
},
+ {
+ "if": {
+ "properties": {
+ "InteractionType": {
+ "const": "Action"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "Action": {
+ "type": "string",
+ "description": "The action to use",
+ "enum": [
+ "Esuna"
+ ]
+ }
+ },
+ "required": [
+ "Action"
+ ]
+ }
+ },
{
"if": {
"properties": {
--- /dev/null
+using System.Collections.Generic;
+
+namespace Questionable.Model.V1.Converter;
+
+public sealed class ActionConverter() : EnumConverter<EAction>(Values)
+{
+ private static readonly Dictionary<EAction, string> Values = new()
+ {
+ { EAction.Esuna, "Esuna" },
+ };
+}
{ EInteractionType.EquipItem, "EquipItem" },
{ EInteractionType.Say, "Say" },
{ EInteractionType.Emote, "Emote" },
+ { EInteractionType.Action, "Action" },
{ EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" },
{ EInteractionType.WaitForManualProgress, "WaitForManualProgress" },
{ EInteractionType.Duty, "Duty" },
--- /dev/null
+using System.Text.Json.Serialization;
+using Questionable.Model.V1.Converter;
+
+namespace Questionable.Model.V1;
+
+[JsonConverter(typeof(ActionConverter))]
+public enum EAction
+{
+ Esuna = 7568,
+}
EquipItem,
Say,
Emote,
+ Action,
WaitForObjectAtPosition,
WaitForManualProgress,
Duty,
public EEmote? Emote { get; set; }
public ChatMessage? ChatMessage { get; set; }
+ public EAction? Action { get; set; }
public EEnemySpawnType? EnemySpawnType { get; set; }
public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>();
--- /dev/null
+using System;
+using System.Collections.Generic;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Game.ClientState.Objects.Types;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Questionable.Controller.Steps.BaseTasks;
+using Questionable.Model;
+using Questionable.Model.V1;
+
+namespace Questionable.Controller.Steps.InteractionFactory;
+
+internal static class Action
+{
+ internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
+ {
+ public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
+ {
+ if (step.InteractionType != EInteractionType.Action)
+ return [];
+
+ ArgumentNullException.ThrowIfNull(step.DataId);
+ ArgumentNullException.ThrowIfNull(step.Action);
+
+ var unmount = serviceProvider.GetRequiredService<UnmountTask>();
+ var task = serviceProvider.GetRequiredService<UseOnObject>()
+ .With(step.DataId.Value, step.Action.Value);
+ return [unmount, task];
+ }
+
+ public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+ => throw new InvalidOperationException();
+ }
+
+ internal sealed class UseOnObject(GameFunctions gameFunctions, ILogger<UseOnObject> logger) : ITask
+ {
+ private bool _usedAction;
+ private DateTime _continueAt = DateTime.MinValue;
+
+ public uint DataId { get; set; }
+ public EAction Action { get; set; }
+
+ public ITask With(uint dataId, EAction action)
+ {
+ DataId = dataId;
+ Action = action;
+ return this;
+ }
+
+ public bool Start()
+ {
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
+ if (gameObject == null)
+ {
+ logger.LogWarning("No game object with dataId {DataId}", DataId);
+ return false;
+ }
+
+ if (gameObject.IsTargetable)
+ {
+ _usedAction = gameFunctions.UseAction(gameObject, Action);
+ _continueAt = DateTime.Now.AddSeconds(0.5);
+ return true;
+ }
+
+ return true;
+ }
+
+ public ETaskResult Update()
+ {
+ if (DateTime.Now <= _continueAt)
+ return ETaskResult.StillRunning;
+
+ if (!_usedAction)
+ {
+ IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId);
+ if (gameObject == null || !gameObject.IsTargetable)
+ return ETaskResult.StillRunning;
+
+ _usedAction = gameFunctions.UseAction(gameObject, Action);
+ _continueAt = DateTime.Now.AddSeconds(0.5);
+ return ETaskResult.StillRunning;
+ }
+
+ return ETaskResult.TaskComplete;
+ }
+
+ public override string ToString() => $"Action({Action})";
+ }
+}
return false;
}
+ public bool UseAction(IGameObject gameObject, EAction action)
+ {
+ if (!ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address))
+ {
+ _logger.LogWarning("Can not use action {Action} on target {Target}", action, gameObject);
+ return false;
+ }
+
+ _targetManager.Target = gameObject;
+ if (ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action, gameObject.GameObjectId) == 0)
+ {
+ bool result = ActionManager.Instance()->UseAction(ActionType.Action, (uint)action, gameObject.GameObjectId);
+ _logger.LogInformation("UseAction {Action} on target {Target} result: {Result}", action, gameObject, result);
+
+ return result;
+ }
+
+ return false;
+ }
+
public bool IsObjectAtPosition(uint dataId, Vector3 position, float distance)
{
IGameObject? gameObject = FindObjectByDataId(dataId);
using Questionable.Data;
using Questionable.External;
using Questionable.Windows;
+using Action = Questionable.Controller.Steps.InteractionFactory.Action;
namespace Questionable;
serviceCollection.AddSingleton<ITaskFactory, Combat.Factory>();
serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>();
serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>();
+ serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
serviceCollection.AddTaskWithFactory<Interact.Factory, Interact.DoInteract>();
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.DoJump>();
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();