Make 'TargetTerritoryId' auto-pick warps for SelectString/SelectIconString (except...
authorLiza Carvelli <liza@carvel.li>
Sun, 14 Jul 2024 21:26:06 +0000 (23:26 +0200)
committerLiza Carvelli <liza@carvel.li>
Sun, 14 Jul 2024 21:26:06 +0000 (23:26 +0200)
LLib
QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/245_It's Probably Pirates.json
QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json
Questionable.Model/V1/ExcelRef.cs
Questionable/Controller/GameUiController.cs

diff --git a/LLib b/LLib
index 93fac6efb01a1272192d929fd863328271512ea4..aec507a840b7f0a20635c6ddbc7862e9025cea4f 160000 (submodule)
--- a/LLib
+++ b/LLib
@@ -1 +1 @@
-Subproject commit 93fac6efb01a1272192d929fd863328271512ea4
+Subproject commit aec507a840b7f0a20635c6ddbc7862e9025cea4f
index e301276b0a933ca7904ddbf9254b7952aef262ce..26246353e25e0e5c3cd6c7edbf77773815eb1b9e 100644 (file)
           "StopDistance": 7,
           "TerritoryId": 129,
           "InteractionType": "Interact",
-          "TargetTerritoryId": 138,
-          "DialogueChoices": [
-            {
-              "Type": "List",
-              "ExcelSheet": "Warp",
-              "Prompt": null,
-              "Answer": 131109
-            }
-          ]
+          "TargetTerritoryId": 138
         },
         {
           "DataId": 14,
index f93905c77dee79b0a5f75f136ec235473ed289b3..b3411d73fd0c6bc924908f288817470e22f2ce2e 100644 (file)
           "AethernetShortcut": [
             "[Gridania] Aetheryte Plaza",
             "[Gridania] Lancers' Guild"
-          ],
-          "DialogueChoices": [
-            {
-              "Type": "List",
-              "ExcelSheet": "Warp",
-              "Prompt": null,
-              "Answer": 131077
-            }
           ]
         },
         {
index c6451ac2bbde409fa00efa8991e0fdbb2d5fd331..979a1897a85329f79c92c72385b9eadc865fb8f3 100644 (file)
@@ -21,6 +21,21 @@ public class ExcelRef
         Type = EType.RowId;
     }
 
+    /// <summary>
+    /// Only used internally (not serialized) with specific values that have been read from the sheets already.
+    /// </summary>
+    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,
     }
 }
index fa9f1afcd67c11e0428c97b2a9868835422cf27d..dba30ec07bd102d88ed6fa04a9b8ed8f2974b206 100644 (file)
@@ -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<string?> answers)
+    private unsafe bool CheckQuestSelection(AddonSelectIconString* addonSelectIconString, Quest quest,
+        List<string?> 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<string?> 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<string?> answers, bool checkAllSteps)
     {
         List<DialogueChoiceInfo> 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;
-
-        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
+        if (currentQuest != null)
         {
-            var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
-            if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step.DialogueChoices, actualPrompt))
+            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;
+            }
+
+            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<Warp>()!
-            .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))
+            string? excelName = entry.Name?.ToString();
+            string? excelQuestion = entry.Question?.ToString();
+
+            if (excelQuestion != null && GameStringEquals(excelQuestion, actualPrompt))
             {
-                _logger.LogDebug("Ignoring prompt '{Prompt}'", excelPrompt);
-                continue;
+                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);
             }
-
-            _logger.LogInformation("Using warp {Id}, {Prompt}", entry.RowId, excelPrompt);
-            addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
-            return;
         }
+
+        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;
     }