1
0
forked from liza/Questionable

Add 'action' for using weapon skills/abilities on targets; part of healer role quests

This commit is contained in:
Liza 2024-07-10 21:01:41 +02:00
parent c50008d9e6
commit 48f4045e77
Signed by: liza
GPG Key ID: 7199F8D727D55F67
14 changed files with 712 additions and 0 deletions

View File

@ -332,6 +332,8 @@ public class QuestSourceGenerator : ISourceGenerator
Assignment(nameof(QuestStep.ChatMessage), step.ChatMessage, Assignment(nameof(QuestStep.ChatMessage), step.ChatMessage,
emptyStep.ChatMessage) emptyStep.ChatMessage)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.Action), step.Action, emptyStep.Action)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.EnemySpawnType), step.EnemySpawnType, Assignment(nameof(QuestStep.EnemySpawnType), step.EnemySpawnType,
emptyStep.EnemySpawnType) emptyStep.EnemySpawnType)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),

View File

@ -16,6 +16,180 @@
"InteractionType": "AcceptQuest" "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"
}
]
} }
] ]
} }

View File

@ -0,0 +1,123 @@
{
"$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"
}
]
}
]
}

View File

@ -0,0 +1,107 @@
{
"$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"
}
]
}
]
}

View File

@ -0,0 +1,145 @@
{
"$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"
}
]
}
]
}

View File

@ -96,6 +96,7 @@
"EquipItem", "EquipItem",
"Say", "Say",
"Emote", "Emote",
"Action",
"WaitForNpcAtPosition", "WaitForNpcAtPosition",
"WaitForManualProgress", "WaitForManualProgress",
"Duty", "Duty",
@ -702,6 +703,29 @@
] ]
} }
}, },
{
"if": {
"properties": {
"InteractionType": {
"const": "Action"
}
}
},
"then": {
"properties": {
"Action": {
"type": "string",
"description": "The action to use",
"enum": [
"Esuna"
]
}
},
"required": [
"Action"
]
}
},
{ {
"if": { "if": {
"properties": { "properties": {

View File

@ -0,0 +1,11 @@
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" },
};
}

View File

@ -16,6 +16,7 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
{ EInteractionType.EquipItem, "EquipItem" }, { EInteractionType.EquipItem, "EquipItem" },
{ EInteractionType.Say, "Say" }, { EInteractionType.Say, "Say" },
{ EInteractionType.Emote, "Emote" }, { EInteractionType.Emote, "Emote" },
{ EInteractionType.Action, "Action" },
{ EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" }, { EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" },
{ EInteractionType.WaitForManualProgress, "WaitForManualProgress" }, { EInteractionType.WaitForManualProgress, "WaitForManualProgress" },
{ EInteractionType.Duty, "Duty" }, { EInteractionType.Duty, "Duty" },

View File

@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
using Questionable.Model.V1.Converter;
namespace Questionable.Model.V1;
[JsonConverter(typeof(ActionConverter))]
public enum EAction
{
Esuna = 7568,
}

View File

@ -16,6 +16,7 @@ public enum EInteractionType
EquipItem, EquipItem,
Say, Say,
Emote, Emote,
Action,
WaitForObjectAtPosition, WaitForObjectAtPosition,
WaitForManualProgress, WaitForManualProgress,
Duty, Duty,

View File

@ -41,6 +41,7 @@ public sealed class QuestStep
public EEmote? Emote { get; set; } public EEmote? Emote { get; set; }
public ChatMessage? ChatMessage { get; set; } public ChatMessage? ChatMessage { get; set; }
public EAction? Action { get; set; }
public EEnemySpawnType? EnemySpawnType { get; set; } public EEnemySpawnType? EnemySpawnType { get; set; }
public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>(); public IList<uint> KillEnemyDataIds { get; set; } = new List<uint>();

View File

@ -0,0 +1,91 @@
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})";
}
}

View File

@ -356,6 +356,26 @@ internal sealed unsafe class GameFunctions
return false; 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) public bool IsObjectAtPosition(uint dataId, Vector3 position, float distance)
{ {
IGameObject? gameObject = FindObjectByDataId(dataId); IGameObject? gameObject = FindObjectByDataId(dataId);

View File

@ -16,6 +16,7 @@ using Questionable.Controller.Steps.InteractionFactory;
using Questionable.Data; using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Windows; using Questionable.Windows;
using Action = Questionable.Controller.Steps.InteractionFactory.Action;
namespace Questionable; namespace Questionable;
@ -91,6 +92,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ITaskFactory, Combat.Factory>(); serviceCollection.AddSingleton<ITaskFactory, Combat.Factory>();
serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>(); serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>();
serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>(); serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>();
serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
serviceCollection.AddTaskWithFactory<Interact.Factory, Interact.DoInteract>(); serviceCollection.AddTaskWithFactory<Interact.Factory, Interact.DoInteract>();
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.DoJump>(); serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.DoJump>();
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>(); serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();