Handle configured dialogue prompts; hide UI in blacklisted territories; path updates

Automatically handles:
- SelectString
- CutSceneSelectString
- SelectYesno
- Credit
- Closing Unending Codex during 'Newfound Adventure'
pull/5/head v0.3
Liza 2024-06-03 23:17:35 +02:00
parent 51e5faae69
commit a45cfda2e6
Signed by: liza
GPG Key ID: 7199F8D727D55F67
36 changed files with 652 additions and 108 deletions

View File

@ -44,6 +44,14 @@
},
"TerritoryId": 957,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
],
"$": "QuestVariables after: 16 1 0 0 0 128"
},
{
@ -53,12 +61,28 @@
"Z": -159.90234
},
"TerritoryId": 957,
"InteractionType": "WalkTo"
"InteractionType": "WalkTo",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 2011914,
"TerritoryId": 957,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
],
"$": "QuestVariables after: 32 17 0 0 0 160"
},
{
@ -69,7 +93,15 @@
"Z": -157.09167
},
"TerritoryId": 957,
"InteractionType": "Interact"
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},

View File

@ -43,15 +43,21 @@
"Z": 799.2217
},
"TerritoryId": 957,
"InteractionType": "CutsceneSelectString",
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q2_000_086",
"Answer": "TEXT_AKTKMA114_04370_A2_000_088"
},
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q3_000_096",
"Answer": "TEXT_AKTKMA114_04370_A3_000_098"
},
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q5_000_106",
"Answer": "TEXT_AKTKMA114_04370_A5_000_107"
}
]
@ -69,15 +75,21 @@
"Z": 681.7273
},
"TerritoryId": 957,
"InteractionType": "CutsceneSelectString",
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q6_000_147",
"Answer": "TEXT_AKTKMA114_04370_A6_000_149"
},
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q7_000_157",
"Answer": "TEXT_AKTKMA114_04370_A7_000_158"
},
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q8_000_162",
"Answer": "TEXT_AKTKMA114_04370_A8_000_164"
}
]
@ -95,15 +107,21 @@
"Z": 517.72327
},
"TerritoryId": 957,
"InteractionType": "CutsceneSelectString",
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q9_000_198",
"Answer": "TEXT_AKTKMA114_04370_A9_000_200"
},
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q10_000_207",
"Answer": "TEXT_AKTKMA114_04370_A10_000_209"
},
{
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q11_000_216",
"Answer": "TEXT_AKTKMA114_04370_A11_000_218"
}
]

View File

@ -1,6 +1,9 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1097
],
"QuestSequence": [
{
"Sequence": 0,

View File

@ -1,6 +1,9 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1095
],
"QuestSequence": [
{
"Sequence": 0,

View File

@ -82,7 +82,14 @@
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMK102_04736_Q2_000_094",
"Yes": true
}
]
}
]
},

View File

@ -135,7 +135,6 @@
},
"TerritoryId": 958,
"InteractionType": "Interact",
"Comment": "TODO Check flags",
"CompletionQuestVariablesFlags": [
null,
null,
@ -150,6 +149,18 @@
{
"Sequence": 255,
"Steps": [
{
"Position": {
"X": 534.8861,
"Y": -36.65,
"Z": -245.12135
},
"TerritoryId": 958,
"InteractionType": "WalkTo",
"SkipIf": [
"FlyingLocked"
]
},
{
"DataId": 1045430,
"Position": {

View File

@ -30,7 +30,8 @@
},
"TerritoryId": 958,
"InteractionType": "Interact",
"Fly": true
"Fly": true,
"TargetTerritoryId": 1160
}
]
},
@ -61,7 +62,6 @@
},
"TerritoryId": 1160,
"InteractionType": "Interact",
"Comment": "TODO Check Flags",
"CompletionQuestVariablesFlags": [
null,
null,

View File

@ -64,6 +64,7 @@
"Y": 10.8,
"Z": -231.61676
},
"StopDistance": 5,
"TerritoryId": 958,
"InteractionType": "Interact"
}

View File

@ -64,8 +64,23 @@
"TerritoryId": 959,
"InteractionType": "Interact",
"AetheryteShortcut": "Mare Lamentorum - Bestways Burrow",
"SkipIf": ["FlyingUnlocked"],
"Comment": "Check if the flying unlocked check is good enough"
"SkipIf": [
"FlyingUnlocked"
]
},
{
"Position": {
"X": -19.779482,
"Y": -56.63768,
"Z": -464.9354
},
"StopDistance": 1,
"TerritoryId": 959,
"InteractionType": "WalkTo",
"SkipIf": [
"FlyingLocked"
],
"Fly": true
},
{
"DataId": 1039686,
@ -91,7 +106,8 @@
"Z": -620.3861
},
"TerritoryId": 959,
"InteractionType": "Interact"
"InteractionType": "Interact",
"Fly": true
}
]
},
@ -156,8 +172,7 @@
null,
null,
32
],
"Comment": "TODO Check Flags"
]
},
{
"DataId": 1045473,
@ -179,7 +194,8 @@
16
]
}
]
],
"Comment": "TODO Check Flags (32)"
},
{
"Sequence": 255,

View File

@ -53,8 +53,7 @@
null,
null,
128
],
"Comment": "TODO Check Flags"
]
},
{
"DataId": 2013355,

View File

@ -1,6 +1,9 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1140
],
"QuestSequence": [
{
"Sequence": 0,

View File

@ -31,7 +31,23 @@
"TerritoryId": 959,
"InteractionType": "Interact",
"AetheryteShortcut": "Mare Lamentorum - Bestways Burrow",
"SkipIf": ["FlyingUnlocked"]
"SkipIf": [
"FlyingUnlocked"
]
},
{
"Position": {
"X": -19.779482,
"Y": -56.63768,
"Z": -464.9354
},
"StopDistance": 1,
"TerritoryId": 959,
"InteractionType": "WalkTo",
"SkipIf": [
"FlyingLocked"
],
"Fly": true
},
{
"DataId": 1045466,
@ -62,6 +78,13 @@
"AethernetShortcut": [
"[Radz-at-Han] Aetheryte Plaza",
"[Radz-at-Han] Meghaduta"
],
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMK109_04743_Q1_000_000",
"Yes": true
}
]
}
]

View File

@ -27,7 +27,7 @@
"Y": 55,
"Z": -68.61987
},
"StopDistance": 5,
"StopDistance": 7,
"TerritoryId": 963,
"InteractionType": "Interact"
}

View File

@ -1,6 +1,10 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1164,
1168
],
"QuestSequence": [
{
"Sequence": 0,
@ -39,7 +43,14 @@
"Z": -440.63483
},
"TerritoryId": 1184,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKML105_04748_SYSTEM_000_406",
"Yes": true
}
]
}
]
},

View File

@ -12,6 +12,7 @@
"Y": 56.66061,
"Z": 467.39905
},
"StopDistance": 15,
"TerritoryId": 1162,
"InteractionType": "Interact"
}
@ -43,7 +44,8 @@
"Z": -191.51605
},
"TerritoryId": 958,
"InteractionType": "Interact"
"InteractionType": "Interact",
"AetheryteShortcut": "Garlemald - Tertium"
}
]
},
@ -59,6 +61,7 @@
},
"TerritoryId": 962,
"InteractionType": "Interact",
"AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Rostra"

View File

@ -12,6 +12,7 @@
"Y": 41.530136,
"Z": -165.27051
},
"StopDistance": 7,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -29,9 +30,17 @@
},
"TerritoryId": 819,
"InteractionType": "Interact",
"AetheryteShortcut": "Crystarium",
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
],
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKML107_04750_SYSTEM_000_101",
"Yes": true
}
]
}
]
@ -51,6 +60,22 @@
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1045684,
"Position": {
"X": -0.96136475,
"Y": 0,
"Z": -3.3417358
},
"StopDistance": 5,
"TerritoryId": 844,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 4,
"Steps": [
@ -63,6 +88,7 @@
},
"TerritoryId": 963,
"InteractionType": "Interact",
"AetheryteShortcut": "Radz-at-Han",
"AethernetShortcut": [
"[Radz-at-Han] Aetheryte Plaza",
"[Radz-at-Han] Meghaduta"
@ -89,17 +115,17 @@
"Sequence": 255,
"Steps": [
{
"DataId": 196,
"DataId": 1039645,
"Position": {
"X": -42.61847,
"Y": -0.015319824,
"Z": -197.61963
"X": -338.33832,
"Y": 55,
"Z": -68.40625
},
"TerritoryId": 963,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Radz-at-Han] Mehryde's Meyhane",
"[Radz-at-Han] Aetheryte Plaza"
"[Radz-at-Han] Meghaduta"
]
}
]

View File

@ -13,7 +13,14 @@
"Z": -68.40625
},
"TerritoryId": 963,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMM103_04753_Q1_000_000",
"Answer": "TEXT_AKTKMM103_04753_A1_000_001"
}
]
}
]
},
@ -27,8 +34,10 @@
"Y": 4.357494,
"Z": 0.7476196
},
"StopDistance": 7,
"TerritoryId": 962,
"InteractionType": "Interact",
"AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Baldesion Annex"

View File

@ -1,6 +1,9 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1177
],
"QuestSequence": [
{
"Sequence": 0,
@ -62,6 +65,7 @@
"Y": -14.169313,
"Z": 105.30249
},
"StopDistance": 7,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -77,6 +81,7 @@
"Y": -15.127002,
"Z": 139.42163
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}

View File

@ -12,6 +12,7 @@
"Y": -15.127001,
"Z": 139.45215
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}
@ -32,6 +33,14 @@
"AethernetShortcut": [
"[Old Sharlayan] Scholar's Harbor",
"[Old Sharlayan] The Studium"
],
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
16
]
},
{
@ -42,7 +51,15 @@
"Z": 103.28821
},
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 2013417,
@ -52,7 +69,15 @@
"Z": 59.00659
},
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2013416,
@ -62,7 +87,15 @@
"Z": 20.523315
},
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
@ -111,7 +144,14 @@
"Z": 0.7476196
},
"TerritoryId": 962,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMM103_04753_SYSTEM_000_302",
"Yes": true
}
]
}
]
},
@ -151,6 +191,7 @@
"Y": 4.357494,
"Z": 0.7476196
},
"StopDistance": 5,
"TerritoryId": 962,
"InteractionType": "Interact"
}

View File

@ -2,6 +2,10 @@
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"Comment": "TODO Missing Quest Start",
"TerritoryBlacklist": [
838,
847
],
"QuestSequence": [
{
"Sequence": 7,

View File

@ -13,7 +13,14 @@
"Z": -9.10968
},
"TerritoryId": 351,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_LUCKMG101_03673_Q1_000_500",
"Answer": "TEXT_LUCKMG101_03673_A1_000_500"
}
]
}
]
},
@ -38,7 +45,8 @@
"Z": -656.1909
},
"TerritoryId": 156,
"InteractionType": "WalkTo"
"InteractionType": "WalkTo",
"Mount": true
},
{
"DataId": 1018433,

View File

@ -26,6 +26,7 @@
"Y": -0.67464465,
"Z": 653.1527
},
"StopDistance": 0.5,
"TerritoryId": 813,
"InteractionType": "WalkTo",
"AetheryteShortcut": "Lakeland - Fort Jobb",

View File

@ -0,0 +1,13 @@
Currying Flavor:
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMK101_04735_Q1_000_000",
"Answer": "TEXT_AKTKMK101_04735_A1_000_003"
},
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMK101_04735_Q2_000_182",
"Yes": true
}
]

View File

@ -100,7 +100,6 @@
"Duty",
"SinglePlayerDuty",
"Jump",
"CutsceneSelectString",
"ShouldBeAJump",
"Instruction"
]
@ -648,7 +647,7 @@
"if": {
"properties": {
"InteractionType": {
"const": "CutsceneSelectString"
"const": "Interact"
}
}
},
@ -659,9 +658,55 @@
"items": {
"type": "object",
"properties": {
"Type": {
"type": "string",
"enum": [
"YesNo",
"List"
]
},
"ExcelSheet": {
"type": "string"
},
"Prompt": {
"type": "string"
}
},
"required": [
"Type",
"Prompt"
],
"allOf": [
{
"if": {
"properties": {
"Type": {
"const": "YesNo"
}
}
},
"then": {
"properties": {
"Yes": {
"type": "boolean",
"default": true
}
},
"required": [
"Yes"
]
}
},
{
"if": {
"properties": {
"Type": {
"const": "List"
}
}
},
"then": {
"properties": {
"Answer": {
"type": "string"
}
@ -671,11 +716,11 @@
]
}
}
},
"required": [
"DialogueChoices"
]
}
}
}
}
},
{
"if": {

View File

@ -0,0 +1,240 @@
using System;
using System.Linq;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Lumina.Excel.GeneratedSheets;
using Questionable.Model.V1;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller;
internal sealed class GameUiController : IDisposable
{
private readonly IClientState _clientState;
private readonly IAddonLifecycle _addonLifecycle;
private readonly IDataManager _dataManager;
private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController;
private readonly IPluginLog _pluginLog;
public GameUiController(IClientState clientState, IAddonLifecycle addonLifecycle, IDataManager dataManager,
GameFunctions gameFunctions, QuestController questController, IPluginLog pluginLog)
{
_clientState = clientState;
_addonLifecycle = addonLifecycle;
_dataManager = dataManager;
_gameFunctions = gameFunctions;
_questController = questController;
_pluginLog = pluginLog;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup);
}
private unsafe void SelectStringPostSetup(AddonEvent type, AddonArgs args)
{
AddonSelectString* addonSelectString = (AddonSelectString*)args.Addon;
string? actualPrompt = addonSelectString->AtkUnitBase.AtkValues[2].ReadAtkString();
if (actualPrompt == null)
return;
var currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null)
return;
foreach (var dialogueChoice in step.DialogueChoices)
{
if (dialogueChoice.Answer == null)
continue;
string? excelPrompt =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
string? excelAnswer =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
if (excelPrompt == null || actualPrompt != excelPrompt)
continue;
for (ushort i = 7; i <= addonSelectString->AtkUnitBase.AtkValuesCount; ++i)
{
string? actualAnswer = addonSelectString->AtkUnitBase.AtkValues[i].ReadAtkString();
if (actualAnswer == null || actualAnswer != excelAnswer)
continue;
_questController.IncreaseDialogueChoicesSelected();
addonSelectString->AtkUnitBase.FireCallbackInt(i - 7);
return;
}
}
}
private unsafe void CutsceneSelectStringPostSetup(AddonEvent type, AddonArgs args)
{
AddonCutSceneSelectString* addonCutSceneSelectString = (AddonCutSceneSelectString*)args.Addon;
string? actualPrompt = addonCutSceneSelectString->AtkUnitBase.AtkValues[2].ReadAtkString();
if (actualPrompt == null)
return;
var currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null)
return;
foreach (DialogueChoice dialogueChoice in step.DialogueChoices)
{
if (dialogueChoice.Answer == null)
continue;
string? excelPrompt =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
string? excelAnswer =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
if (excelPrompt == null || actualPrompt != excelPrompt)
continue;
for (int i = 5; i < addonCutSceneSelectString->AtkUnitBase.AtkValuesCount; ++i)
{
string? actualAnswer = addonCutSceneSelectString->AtkUnitBase.AtkValues[i].ReadAtkString();
if (actualAnswer == null || actualAnswer != excelAnswer)
continue;
_questController.IncreaseDialogueChoicesSelected();
addonCutSceneSelectString->AtkUnitBase.FireCallbackInt(i - 5);
return;
}
}
}
private unsafe void SelectYesnoPostSetup(AddonEvent type, AddonArgs args)
{
AddonSelectYesno* addonSelectYesno = (AddonSelectYesno*)args.Addon;
string? actualPrompt = addonSelectYesno->AtkUnitBase.AtkValues[0].ReadAtkString();
if (actualPrompt == null)
return;
_pluginLog.Verbose($"Prompt: '{actualPrompt}'");
var currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step, actualPrompt))
return;
HandleTravelYesNo(addonSelectYesno, currentQuest, actualPrompt);
}
private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest, QuestStep step,
string actualPrompt)
{
_pluginLog.Verbose($"DefaultYesNo: Choice count: {step.DialogueChoices.Count}");
foreach (var dialogueChoice in step.DialogueChoices)
{
string? excelPrompt =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
if (excelPrompt == null || actualPrompt != excelPrompt)
continue;
addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1);
_questController.IncreaseDialogueChoicesSelected();
return true;
}
return false;
}
private unsafe bool HandleTravelYesNo(AddonSelectYesno* addonSelectYesno,
QuestController.QuestProgress currentQuest, string actualPrompt)
{
// this can be triggered either manually (in which case we should increase the step counter), or automatically
// (in which case it is ~1 frame later, and the step counter has already been increased)
var sequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
if (sequence == null)
return false;
bool increaseStepCount = true;
QuestStep? step = sequence.FindStep(currentQuest.Step);
if (step != null)
_pluginLog.Verbose($"Current step: {step.TerritoryId}, {step.TargetTerritoryId}");
if (step == null || step.TargetTerritoryId == null || step.TerritoryId != _clientState.TerritoryType)
{
_pluginLog.Verbose("TravelYesNo: Checking previous step...");
step = sequence.FindStep(currentQuest.Step == 255 ? (sequence.Steps.Count - 1) : (currentQuest.Step - 1));
increaseStepCount = false;
if (step != null)
_pluginLog.Verbose($"Previous step: {step.TerritoryId}, {step.TargetTerritoryId}");
}
if (step == null || step.TargetTerritoryId == null || step.TerritoryId != _clientState.TerritoryType)
{
_pluginLog.Verbose("TravelYesNo: Not found");
return false;
}
var warps = _dataManager.GetExcelSheet<Warp>()!
.Where(x => x.RowId > 0 && x.TerritoryType.Row == step.TargetTerritoryId)
.Where(x => x.ConfirmEvent.Row == 0); // unsure if this is needed
foreach (var entry in warps)
{
string? excelPrompt = entry.Question?.ToString();
if (excelPrompt == null || excelPrompt != actualPrompt)
{
_pluginLog.Information($"Ignoring prompt '{excelPrompt}'");
continue;
}
_pluginLog.Information($"Using warp {entry.RowId}, {excelPrompt}");
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
if (increaseStepCount)
_questController.IncreaseStepCount();
return true;
}
return false;
}
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
{
_pluginLog.Information("Closing Credits sequence");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
addon->FireCallbackInt(-2);
}
private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args)
{
if (_questController.CurrentQuest?.Quest.QuestId == 4526)
{
_pluginLog.Information("Closing Unending Codex");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
addon->FireCallbackInt(-2);
}
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
}
}

View File

@ -10,14 +10,11 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using LLib.GameUI;
using Questionable.Data;
using Questionable.External;
using Questionable.Model;
using Questionable.Model.V1;
using Questionable.Model.V1.Converter;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Questionable.Controller;
@ -272,6 +269,27 @@ internal sealed class QuestController
}
}
public void IncreaseDialogueChoicesSelected()
{
(QuestSequence? seq, QuestStep? step) = GetNextStep();
if (CurrentQuest == null || seq == null || step == null)
{
_pluginLog.Warning("Unable to retrieve next quest step, not increasing dialogue choice count");
return;
}
CurrentQuest = CurrentQuest with
{
StepProgress = CurrentQuest.StepProgress with
{
DialogueChoicesSelected = CurrentQuest.StepProgress.DialogueChoicesSelected + 1
}
};
if (CurrentQuest.StepProgress.DialogueChoicesSelected >= step.DialogueChoices.Count)
IncreaseStepCount();
}
public unsafe void ExecuteNextStep()
{
(QuestSequence? seq, QuestStep? step) = GetNextStep();
@ -422,7 +440,8 @@ internal sealed class QuestController
}
}
else
_pluginLog.Warning($"Aethernet shortcut not unlocked (from: {step.AethernetShortcut.From}, to: {step.AethernetShortcut.To}), walking manually");
_pluginLog.Warning(
$"Aethernet shortcut not unlocked (from: {step.AethernetShortcut.From}, to: {step.AethernetShortcut.To}), walking manually");
}
if (step.TargetTerritoryId == _clientState.TerritoryType && !step.SkipIf.Contains(ESkipCondition.Never))
@ -439,11 +458,6 @@ internal sealed class QuestController
{
_pluginLog.Information("We're at the jump destination, skipping movement");
}
else if (step.InteractionType == EInteractionType.CutsceneSelectString &&
_condition[ConditionFlag.OccupiedInCutSceneEvent])
{
_pluginLog.Information("In cutscene selection, skipping movement");
}
else if (step.Position != null)
{
float distance;
@ -543,6 +557,9 @@ internal sealed class QuestController
}
_gameFunctions.InteractWith(step.DataId.Value);
// if we have any dialogue, that is handled in GameUiController
if (step.DialogueChoices.Count == 0)
IncreaseStepCount();
}
else
@ -712,41 +729,6 @@ internal sealed class QuestController
// Need to manually forward
break;
case EInteractionType.CutsceneSelectString:
// to do this automatically, should likely be in Addon's post setup
if (_gameGui.TryGetAddonByName<AddonCutSceneSelectString>("CutSceneSelectString", out var addon) &&
LAddon.IsAddonReady(&addon->AtkUnitBase))
{
foreach (DialogueChoice dialogueChoice in step.DialogueChoices)
{
string? excelString = _gameFunctions.GetExcelString(CurrentQuest.Quest,
dialogueChoice.ExcelSheet, dialogueChoice.Answer);
if (excelString == null)
return;
_pluginLog.Verbose($"Looking for option '{excelString}'");
for (int i = 5; i < addon->AtkUnitBase.AtkValuesCount; ++i)
{
var atkValue = addon->AtkUnitBase.AtkValues[i];
if (atkValue.Type != ValueType.String)
continue;
string? atkString = atkValue.ReadAtkString();
_pluginLog.Verbose($"Option {i}: {atkString}");
if (excelString == atkString)
{
_pluginLog.Information($"Selecting option {i - 5}: {atkString}");
addon->AtkUnitBase.FireCallbackInt(i - 5);
return;
}
}
}
}
else if (step.DataId != null && !_condition[ConditionFlag.OccupiedInCutSceneEvent])
_gameFunctions.InteractWith(step.DataId.Value);
break;
default:
_pluginLog.Warning($"Action '{step.InteractionType}' is not implemented");
break;
@ -767,5 +749,6 @@ internal sealed class QuestController
public sealed record StepProgress(
bool AetheryteShortcutUsed = false,
bool AethernetShortcutUsed = false);
bool AethernetShortcutUsed = false,
int DialogueChoicesSelected = 0);
}

View File

@ -474,22 +474,18 @@ internal sealed unsafe class GameFunctions
_pluginLog.Error($"Could not find content for content finder condition (cf: {contentFinderConditionId})");
}
public string? GetExcelString(Quest currentQuestQuest, string? excelSheetName, string key)
public string? GetExcelString(Quest currentQuest, string? excelSheetName, string key)
{
if (excelSheetName == null)
{
string questPrefix = $"quest/{(currentQuestQuest.QuestId / 100):000}/";
string questSuffix = $"_{currentQuestQuest.QuestId:00000}";
excelSheetName = _dataManager.Excel
.GetSheetNames()
.SingleOrDefault(x =>
x.StartsWith(questPrefix, StringComparison.Ordinal) &&
x.EndsWith(questSuffix, StringComparison.Ordinal));
if (excelSheetName == null)
var questRow = _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId + 0x10000);
if (questRow == null)
{
_pluginLog.Error($"Could not find sheet matching '{questPrefix}*{questSuffix}");
_pluginLog.Error($"Could not find quest row for {currentQuest.QuestId}");
return null;
}
excelSheetName = $"quest/{(currentQuest.QuestId / 100):000}/{questRow.Id}";
}
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Questionable.Model.V1.Converter;
public sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values)
{
private static readonly Dictionary<EDialogChoiceType, string> Values = new()
{
{ EDialogChoiceType.YesNo, "YesNo" },
{ EDialogChoiceType.List, "List" },
};
}

View File

@ -20,7 +20,6 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
{ EInteractionType.Duty, "Duty" },
{ EInteractionType.SinglePlayerDuty, "SinglePlayerDuty" },
{ EInteractionType.Jump, "Jump" },
{ EInteractionType.CutsceneSelectString, "CutsceneSelectString" },
{ EInteractionType.ShouldBeAJump, "ShouldBeAJump" },
{ EInteractionType.Instruction, "Instruction" },
};

View File

@ -1,7 +1,14 @@
namespace Questionable.Model.V1;
using System.Text.Json.Serialization;
using Questionable.Model.V1.Converter;
public sealed class DialogueChoice
namespace Questionable.Model.V1;
public class DialogueChoice
{
[JsonConverter(typeof(DialogueChoiceTypeConverter))]
public EDialogChoiceType Type { get; set; }
public string? ExcelSheet { get; set; }
public string Answer { get; set; } = null!;
public string Prompt { get; set; } = null!;
public bool Yes { get; set; } = true;
public string? Answer { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Questionable.Model.V1;
public enum EDialogChoiceType
{
None,
YesNo,
List
}

View File

@ -20,7 +20,6 @@ public enum EInteractionType
Duty,
SinglePlayerDuty,
Jump,
CutsceneSelectString,
/// <summary>
/// Needs to be adjusted for coords etc. in the quest data.

View File

@ -7,4 +7,12 @@ public class QuestSequence
public required int Sequence { get; set; }
public string? Comment { get; set; }
public List<QuestStep> Steps { get; set; } = new();
public QuestStep? FindStep(int step)
{
if (step < 0 || step >= Steps.Count)
return null;
return Steps[step];
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Version>0.2</Version>
<Version>0.3</Version>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -27,13 +27,13 @@ public sealed class QuestionablePlugin : IDalamudPlugin
private readonly ICommandManager _commandManager;
private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController;
private readonly MovementController _movementController;
private readonly GameUiController _gameUiController;
public QuestionablePlugin(DalamudPluginInterface pluginInterface, IClientState clientState,
ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager,
ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui,
ICommandManager commandManager)
ICommandManager commandManager, IAddonLifecycle addonLifecycle)
{
ArgumentNullException.ThrowIfNull(pluginInterface);
ArgumentNullException.ThrowIfNull(sigScanner);
@ -55,6 +55,9 @@ public sealed class QuestionablePlugin : IDalamudPlugin
new MovementController(navmeshIpc, clientState, _gameFunctions, condition, pluginLog);
_questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions,
_movementController, pluginLog, condition, chatGui, framework, gameGui, aetheryteData, lifestreamIpc);
_gameUiController =
new GameUiController(clientState, addonLifecycle, dataManager, _gameFunctions, _questController, pluginLog);
_windowSystem.AddWindow(new DebugWindow(_movementController, _questController, _gameFunctions, clientState,
targetManager));
@ -100,6 +103,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
_framework.Update -= FrameworkUpdate;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_gameUiController.Dispose();
_movementController.Dispose();
}
}

View File

@ -1,12 +1,9 @@
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
@ -45,6 +42,15 @@ internal sealed class DebugWindow : Window
};
}
public override bool DrawConditions()
{
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
return false;
var currentQuest = _questController.CurrentQuest;
return currentQuest == null || !currentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType);
}
public override unsafe void Draw()
{
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)