From 48f4045e771e36a8a1f7d4d1ab05777d620be2bc Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Wed, 10 Jul 2024 21:01:41 +0200 Subject: [PATCH] Add 'action' for using weapon skills/abilities on targets; part of healer role quests --- QuestPathGenerator/QuestSourceGenerator.cs | 2 + .../Healer/4824_In the Sting of Things.json | 174 ++++++++++++++++++ .../4825_Causing Problems on Purpose.json | 123 +++++++++++++ .../Healer/4826_Living among the Deadly.json | 107 +++++++++++ .../4827_Taste of a Toxin Paradise.json | 145 +++++++++++++++ QuestPaths/quest-v1.json | 24 +++ .../V1/Converter/ActionConverter.cs | 11 ++ .../V1/Converter/InteractionTypeConverter.cs | 1 + Questionable.Model/V1/EAction.cs | 10 + Questionable.Model/V1/EInteractionType.cs | 1 + Questionable.Model/V1/QuestStep.cs | 1 + .../Steps/InteractionFactory/Action.cs | 91 +++++++++ Questionable/GameFunctions.cs | 20 ++ Questionable/QuestionablePlugin.cs | 2 + 14 files changed, 712 insertions(+) create mode 100644 QuestPaths/Dawntrail/RoleQuests/Healer/4825_Causing Problems on Purpose.json create mode 100644 QuestPaths/Dawntrail/RoleQuests/Healer/4826_Living among the Deadly.json create mode 100644 QuestPaths/Dawntrail/RoleQuests/Healer/4827_Taste of a Toxin Paradise.json create mode 100644 Questionable.Model/V1/Converter/ActionConverter.cs create mode 100644 Questionable.Model/V1/EAction.cs create mode 100644 Questionable/Controller/Steps/InteractionFactory/Action.cs diff --git a/QuestPathGenerator/QuestSourceGenerator.cs b/QuestPathGenerator/QuestSourceGenerator.cs index 060d6254..be47a0be 100644 --- a/QuestPathGenerator/QuestSourceGenerator.cs +++ b/QuestPathGenerator/QuestSourceGenerator.cs @@ -332,6 +332,8 @@ public class QuestSourceGenerator : ISourceGenerator 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(), diff --git a/QuestPaths/Dawntrail/RoleQuests/Healer/4824_In the Sting of Things.json b/QuestPaths/Dawntrail/RoleQuests/Healer/4824_In the Sting of Things.json index 777a0ba0..f1687aa7 100644 --- a/QuestPaths/Dawntrail/RoleQuests/Healer/4824_In the Sting of Things.json +++ b/QuestPaths/Dawntrail/RoleQuests/Healer/4824_In the Sting of Things.json @@ -16,6 +16,180 @@ "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" + } + ] } ] } diff --git a/QuestPaths/Dawntrail/RoleQuests/Healer/4825_Causing Problems on Purpose.json b/QuestPaths/Dawntrail/RoleQuests/Healer/4825_Causing Problems on Purpose.json new file mode 100644 index 00000000..b3fdcec6 --- /dev/null +++ b/QuestPaths/Dawntrail/RoleQuests/Healer/4825_Causing Problems on Purpose.json @@ -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" + } + ] + } + ] +} diff --git a/QuestPaths/Dawntrail/RoleQuests/Healer/4826_Living among the Deadly.json b/QuestPaths/Dawntrail/RoleQuests/Healer/4826_Living among the Deadly.json new file mode 100644 index 00000000..30debaf9 --- /dev/null +++ b/QuestPaths/Dawntrail/RoleQuests/Healer/4826_Living among the Deadly.json @@ -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" + } + ] + } + ] +} diff --git a/QuestPaths/Dawntrail/RoleQuests/Healer/4827_Taste of a Toxin Paradise.json b/QuestPaths/Dawntrail/RoleQuests/Healer/4827_Taste of a Toxin Paradise.json new file mode 100644 index 00000000..b49047ec --- /dev/null +++ b/QuestPaths/Dawntrail/RoleQuests/Healer/4827_Taste of a Toxin Paradise.json @@ -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" + } + ] + } + ] +} diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index 0ebe0e63..ee2996d4 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -96,6 +96,7 @@ "EquipItem", "Say", "Emote", + "Action", "WaitForNpcAtPosition", "WaitForManualProgress", "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": { "properties": { diff --git a/Questionable.Model/V1/Converter/ActionConverter.cs b/Questionable.Model/V1/Converter/ActionConverter.cs new file mode 100644 index 00000000..61846594 --- /dev/null +++ b/Questionable.Model/V1/Converter/ActionConverter.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Questionable.Model.V1.Converter; + +public sealed class ActionConverter() : EnumConverter(Values) +{ + private static readonly Dictionary Values = new() + { + { EAction.Esuna, "Esuna" }, + }; +} diff --git a/Questionable.Model/V1/Converter/InteractionTypeConverter.cs b/Questionable.Model/V1/Converter/InteractionTypeConverter.cs index 820c2156..0fa5f1d1 100644 --- a/Questionable.Model/V1/Converter/InteractionTypeConverter.cs +++ b/Questionable.Model/V1/Converter/InteractionTypeConverter.cs @@ -16,6 +16,7 @@ public sealed class InteractionTypeConverter() : EnumConverter { EInteractionType.EquipItem, "EquipItem" }, { EInteractionType.Say, "Say" }, { EInteractionType.Emote, "Emote" }, + { EInteractionType.Action, "Action" }, { EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" }, { EInteractionType.WaitForManualProgress, "WaitForManualProgress" }, { EInteractionType.Duty, "Duty" }, diff --git a/Questionable.Model/V1/EAction.cs b/Questionable.Model/V1/EAction.cs new file mode 100644 index 00000000..b5bf2ff0 --- /dev/null +++ b/Questionable.Model/V1/EAction.cs @@ -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, +} diff --git a/Questionable.Model/V1/EInteractionType.cs b/Questionable.Model/V1/EInteractionType.cs index 4fb2cb87..af128130 100644 --- a/Questionable.Model/V1/EInteractionType.cs +++ b/Questionable.Model/V1/EInteractionType.cs @@ -16,6 +16,7 @@ public enum EInteractionType EquipItem, Say, Emote, + Action, WaitForObjectAtPosition, WaitForManualProgress, Duty, diff --git a/Questionable.Model/V1/QuestStep.cs b/Questionable.Model/V1/QuestStep.cs index 7b5bc5f2..377c9cd7 100644 --- a/Questionable.Model/V1/QuestStep.cs +++ b/Questionable.Model/V1/QuestStep.cs @@ -41,6 +41,7 @@ public sealed class QuestStep public EEmote? Emote { get; set; } public ChatMessage? ChatMessage { get; set; } + public EAction? Action { get; set; } public EEnemySpawnType? EnemySpawnType { get; set; } public IList KillEnemyDataIds { get; set; } = new List(); diff --git a/Questionable/Controller/Steps/InteractionFactory/Action.cs b/Questionable/Controller/Steps/InteractionFactory/Action.cs new file mode 100644 index 00000000..a94f7063 --- /dev/null +++ b/Questionable/Controller/Steps/InteractionFactory/Action.cs @@ -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 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(); + var task = serviceProvider.GetRequiredService() + .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 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})"; + } +} diff --git a/Questionable/GameFunctions.cs b/Questionable/GameFunctions.cs index 790c6768..5e42ec4e 100644 --- a/Questionable/GameFunctions.cs +++ b/Questionable/GameFunctions.cs @@ -356,6 +356,26 @@ internal sealed unsafe class GameFunctions 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); diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index d7bf229f..75a1c295 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -16,6 +16,7 @@ using Questionable.Controller.Steps.InteractionFactory; using Questionable.Data; using Questionable.External; using Questionable.Windows; +using Action = Questionable.Controller.Steps.InteractionFactory.Action; namespace Questionable; @@ -91,6 +92,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); + serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory();