From 320ce14aedd08587c08f58da1dcbe49e1bb08c13 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sun, 14 Jul 2024 23:26:06 +0200 Subject: [PATCH] Make 'TargetTerritoryId' auto-pick warps for SelectString/SelectIconString (except for lifts in Ul'dah/Limsa, since airship landings are in normal territories) --- LLib | 2 +- .../Shared/245_It's Probably Pirates.json | 10 +- ...80_The Company You Keep (Twin Adders).json | 8 -- Questionable.Model/V1/ExcelRef.cs | 24 ++++ Questionable/Controller/GameUiController.cs | 133 +++++++++++++----- 5 files changed, 126 insertions(+), 51 deletions(-) diff --git a/LLib b/LLib index 93fac6ef..aec507a8 160000 --- a/LLib +++ b/LLib @@ -1 +1 @@ -Subproject commit 93fac6efb01a1272192d929fd863328271512ea4 +Subproject commit aec507a840b7f0a20635c6ddbc7862e9025cea4f diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/245_It's Probably Pirates.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/245_It's Probably Pirates.json index e301276b..26246353 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/245_It's Probably Pirates.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/245_It's Probably Pirates.json @@ -85,15 +85,7 @@ "StopDistance": 7, "TerritoryId": 129, "InteractionType": "Interact", - "TargetTerritoryId": 138, - "DialogueChoices": [ - { - "Type": "List", - "ExcelSheet": "Warp", - "Prompt": null, - "Answer": 131109 - } - ] + "TargetTerritoryId": 138 }, { "DataId": 14, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json index f93905c7..b3411d73 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json @@ -63,14 +63,6 @@ "AethernetShortcut": [ "[Gridania] Aetheryte Plaza", "[Gridania] Lancers' Guild" - ], - "DialogueChoices": [ - { - "Type": "List", - "ExcelSheet": "Warp", - "Prompt": null, - "Answer": 131077 - } ] }, { diff --git a/Questionable.Model/V1/ExcelRef.cs b/Questionable.Model/V1/ExcelRef.cs index c6451ac2..979a1897 100644 --- a/Questionable.Model/V1/ExcelRef.cs +++ b/Questionable.Model/V1/ExcelRef.cs @@ -21,6 +21,21 @@ public class ExcelRef Type = EType.RowId; } + /// + /// Only used internally (not serialized) with specific values that have been read from the sheets already. + /// + private ExcelRef(string value, bool v) + { + if (!v) + throw new ArgumentException(nameof(v)); + + _stringValue = value; + _rowIdValue = null; + Type = EType.RawString; + } + + public static ExcelRef FromSheetValue(string value) => new(value, true); + public EType Type { get; } public string AsKey() @@ -39,10 +54,19 @@ public class ExcelRef return _rowIdValue!.Value; } + public string AsRawString() + { + if (Type != EType.RawString) + throw new InvalidOperationException(); + + return _stringValue!; + } + public enum EType { None, Key, RowId, + RawString, } } diff --git a/Questionable/Controller/GameUiController.cs b/Questionable/Controller/GameUiController.cs index fa9f1afc..dba30ec0 100644 --- a/Questionable/Controller/GameUiController.cs +++ b/Questionable/Controller/GameUiController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using Dalamud.Game.Addon.Lifecycle; @@ -179,7 +180,8 @@ internal sealed class GameUiController : IDisposable } } - private unsafe bool CheckQuestSelection(AddonSelectIconString* addonSelectIconString, Quest quest, List answers) + private unsafe bool CheckQuestSelection(AddonSelectIconString* addonSelectIconString, Quest quest, + List answers) { // it is possible for this to be a quest selection string questName = quest.Info.Name; @@ -197,7 +199,7 @@ internal sealed class GameUiController : IDisposable { List answers = new(); for (ushort i = 0; i < addonSelectIconString->AtkUnitBase.AtkValues[5].Int; i++) - answers.Add( addonSelectIconString->AtkValues[i * 3 + 7].ReadAtkString()); + answers.Add(addonSelectIconString->AtkValues[i * 3 + 7].ReadAtkString()); return answers; } @@ -205,7 +207,7 @@ internal sealed class GameUiController : IDisposable private int? HandleListChoice(string? actualPrompt, List answers, bool checkAllSteps) { List dialogueChoices = []; - var currentQuest = _questController.StartedQuest; + var currentQuest = _questController.SimulatedQuest ?? _questController.StartedQuest; if (currentQuest != null) { var quest = currentQuest.Quest; @@ -224,6 +226,29 @@ internal sealed class GameUiController : IDisposable else dialogueChoices.AddRange(step.DialogueChoices.Select(x => new DialogueChoiceInfo(quest, x))); } + + // add all travel dialogue choices + var targetTerritoryId = FindTargetTerritoryFromQuestStep(currentQuest); + if (targetTerritoryId != null) + { + foreach (string? answer in answers) + { + if (answer == null) + continue; + + if (TryFindWarp(targetTerritoryId.Value, answer, out uint? warpId, out string? warpText)) + { + _logger.LogInformation("Adding warp {Id}, {Prompt}", warpId, warpText); + dialogueChoices.Add(new DialogueChoiceInfo(quest, new DialogueChoice + { + Type = EDialogChoiceType.List, + ExcelSheet = null, + Prompt = null, + Answer = ExcelRef.FromSheetValue(warpText), + })); + } + } + } } else _logger.LogDebug("Ignoring current quest dialogue choices, no active quest"); @@ -242,7 +267,8 @@ internal sealed class GameUiController : IDisposable .ToList(); if (questChoices != null && questChoices.Count > 0) { - _logger.LogInformation("Adding {Count} dialogue choices from not accepted quest {QuestName}", questChoices.Count, questInfo.Name); + _logger.LogInformation("Adding {Count} dialogue choices from not accepted quest {QuestName}", + questChoices.Count, questInfo.Name); dialogueChoices.AddRange(questChoices.Select(x => new DialogueChoiceInfo(knownQuest, x))); } } @@ -334,25 +360,30 @@ internal sealed class GameUiController : IDisposable _logger.LogTrace("Prompt: '{Prompt}'", actualPrompt); var currentQuest = _questController.StartedQuest; - if (currentQuest == null) - return; + if (currentQuest != null) + { + var quest = currentQuest.Quest; + if (checkAllSteps) + { + var sequence = quest.FindSequence(currentQuest.Sequence); + if (sequence != null && HandleDefaultYesNo(addonSelectYesno, quest, + sequence.Steps.SelectMany(x => x.DialogueChoices).ToList(), actualPrompt)) + return; + } + else + { + var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step); + if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step.DialogueChoices, actualPrompt)) + return; + } - var quest = currentQuest.Quest; - if (checkAllSteps) - { - var sequence = quest.FindSequence(currentQuest.Sequence); - if (sequence != null && HandleDefaultYesNo(addonSelectYesno, quest, - sequence.Steps.SelectMany(x => x.DialogueChoices).ToList(), actualPrompt)) - return; - } - else - { - var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step); - if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step.DialogueChoices, actualPrompt)) + if (HandleTravelYesNo(addonSelectYesno, currentQuest, actualPrompt)) return; } - HandleTravelYesNo(addonSelectYesno, currentQuest, actualPrompt); + var simulatedQuest = _questController.SimulatedQuest; + if (simulatedQuest != null) + HandleTravelYesNo(addonSelectYesno, simulatedQuest, actualPrompt); } private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest, @@ -387,21 +418,35 @@ internal sealed class GameUiController : IDisposable return false; } - private unsafe void HandleTravelYesNo(AddonSelectYesno* addonSelectYesno, + private unsafe bool HandleTravelYesNo(AddonSelectYesno* addonSelectYesno, QuestController.QuestProgress currentQuest, string actualPrompt) { if (_gameFunctions.ReturnRequestedAt >= DateTime.Now.AddSeconds(-2) && _returnRegex.IsMatch(actualPrompt)) { _logger.LogInformation("Automatically confirming return..."); addonSelectYesno->AtkUnitBase.FireCallbackInt(0); - return; + return true; } + var targetTerritoryId = FindTargetTerritoryFromQuestStep(currentQuest); + if (targetTerritoryId != null && + TryFindWarp(targetTerritoryId.Value, actualPrompt, out uint? warpId, out string? warpText)) + { + _logger.LogInformation("Using warp {Id}, {Prompt}", warpId, warpText); + addonSelectYesno->AtkUnitBase.FireCallbackInt(0); + return true; + } + + return false; + } + + private ushort? FindTargetTerritoryFromQuestStep(QuestController.QuestProgress currentQuest) + { // 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; + return null; QuestStep? step = sequence.FindStep(currentQuest.Step); if (step != null) @@ -421,24 +466,44 @@ internal sealed class GameUiController : IDisposable if (step == null || step.TargetTerritoryId == null) { _logger.LogTrace("TravelYesNo: Not found"); - return; + return null; } + _logger.LogDebug("Target territory for quest step: {TargetTerritory}", step.TargetTerritoryId); + return step.TargetTerritoryId; + } + + private bool TryFindWarp(ushort targetTerritoryId, string actualPrompt, [NotNullWhen(true)] out uint? warpId, + [NotNullWhen(true)] out string? warpText) + { var warps = _dataManager.GetExcelSheet()! - .Where(x => x.RowId > 0 && x.TerritoryType.Row == step.TargetTerritoryId); + .Where(x => x.RowId > 0 && x.TerritoryType.Row == targetTerritoryId); foreach (var entry in warps) { - string? excelPrompt = entry.Question?.ToString(); - if (excelPrompt == null || !GameStringEquals(excelPrompt, actualPrompt)) - { - _logger.LogDebug("Ignoring prompt '{Prompt}'", excelPrompt); - continue; - } + string? excelName = entry.Name?.ToString(); + string? excelQuestion = entry.Question?.ToString(); - _logger.LogInformation("Using warp {Id}, {Prompt}", entry.RowId, excelPrompt); - addonSelectYesno->AtkUnitBase.FireCallbackInt(0); - return; + if (excelQuestion != null && GameStringEquals(excelQuestion, actualPrompt)) + { + warpId = entry.RowId; + warpText = excelQuestion; + return true; + } + else if (excelName != null && GameStringEquals(excelName, actualPrompt)) + { + warpId = entry.RowId; + warpText = excelName; + return true; + } + else + { + _logger.LogDebug("Ignoring prompt '{Prompt}'", excelQuestion); + } } + + warpId = null; + warpText = null; + return false; } private unsafe void PointMenuPostSetup(AddonEvent type, AddonArgs args) @@ -551,6 +616,8 @@ internal sealed class GameUiController : IDisposable return _gameFunctions.GetDialogueText(quest, excelSheet, excelRef.AsKey()); else if (excelRef.Type == ExcelRef.EType.RowId) return _gameFunctions.GetDialogueTextByRowId(excelSheet, excelRef.AsRowId()); + else if (excelRef.Type == ExcelRef.EType.RawString) + return excelRef.AsRawString(); return null; }