From: Liza Carvelli Date: Mon, 4 Aug 2025 20:24:42 +0000 (+0200) Subject: Add chocobo porter unlocks for early Limsa X-Git-Tag: v6.0~12 X-Git-Url: https://git.jacobcasper.com/?a=commitdiff_plain;h=beaee9a365616639b7b98a9be3a81a9d91bb6e7d;p=Questionable.git Add chocobo porter unlocks for early Limsa --- diff --git a/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs b/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs index 00a36dbe..525c4d6e 100644 --- a/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs +++ b/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs @@ -100,6 +100,9 @@ internal static class QuestStepExtensions Assignment(nameof(QuestStep.TargetClass), step.TargetClass, emptyStep.TargetClass) .AsSyntaxNodeOrToken(), + Assignment(nameof(QuestStep.TaxiStandId), step.TaxiStandId, + emptyStep.TaxiStandId) + .AsSyntaxNodeOrToken(), Assignment(nameof(QuestStep.EnemySpawnType), step.EnemySpawnType, emptyStep.EnemySpawnType) .AsSyntaxNodeOrToken(), diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/397_Sky-high.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/397_Sky-high.json index eb1b292a..b5abc78f 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/397_Sky-high.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/397_Sky-high.json @@ -20,6 +20,18 @@ { "Sequence": 255, "Steps": [ + { + "DataId": 1002721, + "Position": { + "X": 187.9148, + "Y": 98.5214, + "Z": -193.19452 + }, + "TerritoryId": 134, + "InteractionType": "UnlockTaxiStand", + "TaxiStandId": 22, + "AetheryteShortcut": "Middle La Noscea - Summerford Farms" + }, { "DataId": 1003239, "Position": { @@ -28,11 +40,7 @@ "Z": -249.34778 }, "TerritoryId": 134, - "InteractionType": "CompleteQuest", - "AethernetShortcut": [ - "[Limsa Lominsa] The Aftcastle", - "[Limsa Lominsa] Zephyr Gate (Middle La Noscea)" - ] + "InteractionType": "CompleteQuest" } ] } diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/402_Thanks a Million.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/402_Thanks a Million.json index 88a45ef3..4fe80ee7 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/402_Thanks a Million.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/402_Thanks a Million.json @@ -47,6 +47,17 @@ "InteractionType": "WalkTo", "TargetTerritoryId": 138 }, + { + "DataId": 1002722, + "Position": { + "X": 667.68884, + "Y": 9.882242, + "Z": 487.32727 + }, + "TerritoryId": 138, + "InteractionType": "UnlockTaxiStand", + "TaxiStandId": 23 + }, { "TerritoryId": 138, "InteractionType": "AttuneAetheryte", diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/406_On to the Drydocks.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/406_On to the Drydocks.json index e028ed70..86a3c015 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/406_On to the Drydocks.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/406_On to the Drydocks.json @@ -30,6 +30,17 @@ "InteractionType": "WalkTo", "TargetTerritoryId": 135 }, + { + "DataId": 1002720, + "Position": { + "X": 49.271362, + "Y": 29.315498, + "Z": 605.27954 + }, + "TerritoryId": 135, + "InteractionType": "UnlockTaxiStand", + "TaxiStandId": 27 + }, { "TerritoryId": 135, "InteractionType": "AttuneAetheryte", 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 2104a8f0..e6a02192 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 @@ -79,6 +79,17 @@ "InteractionType": "AttuneAetheryte", "Aetheryte": "Western La Noscea - Aleport" }, + { + "DataId": 1002723, + "Position": { + "X": 298.63428, + "Y": -25.004364, + "Z": 233.14258 + }, + "TerritoryId": 138, + "InteractionType": "UnlockTaxiStand", + "TaxiStandId": 24 + }, { "DataId": 1017075, "Position": { @@ -102,7 +113,13 @@ "Z": 17.135864 }, "TerritoryId": 138, - "InteractionType": "Interact" + "InteractionType": "Interact", + "AetheryteShortcut": "Western La Noscea - Aleport", + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } } ] }, diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index 84d6af8f..72f765a3 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -137,6 +137,7 @@ "Gather", "Snipe", "SwitchClass", + "UnlockTaxiStand", "Instruction", "AcceptQuest", "CompleteQuest", @@ -1410,6 +1411,25 @@ } } }, + { + "if": { + "properties": { + "InteractionType": { + "const": "UnlockTaxiStand" + } + } + }, + "then": { + "properties": { + "TaxiStandId": { + "type": "number" + } + }, + "required": [ + "TaxiStandId" + ] + } + }, { "if": { "properties": { diff --git a/Questionable.Model/Questing/Converter/InteractionTypeConverter.cs b/Questionable.Model/Questing/Converter/InteractionTypeConverter.cs index 9cfe2e30..d5ebaaa4 100644 --- a/Questionable.Model/Questing/Converter/InteractionTypeConverter.cs +++ b/Questionable.Model/Questing/Converter/InteractionTypeConverter.cs @@ -32,6 +32,7 @@ public sealed class InteractionTypeConverter() : EnumConverter { EInteractionType.Gather, "Gather" }, { EInteractionType.Snipe, "Snipe" }, { EInteractionType.SwitchClass, "SwitchClass" }, + { EInteractionType.UnlockTaxiStand, "UnlockTaxiStand" }, { EInteractionType.Instruction, "Instruction" }, { EInteractionType.AcceptQuest, "AcceptQuest" }, { EInteractionType.CompleteQuest, "CompleteQuest" }, diff --git a/Questionable.Model/Questing/EInteractionType.cs b/Questionable.Model/Questing/EInteractionType.cs index f1270d27..a0514cd6 100644 --- a/Questionable.Model/Questing/EInteractionType.cs +++ b/Questionable.Model/Questing/EInteractionType.cs @@ -31,6 +31,7 @@ public enum EInteractionType Gather, Snipe, SwitchClass, + UnlockTaxiStand, /// /// Needs to be manually continued. diff --git a/Questionable.Model/Questing/QuestStep.cs b/Questionable.Model/Questing/QuestStep.cs index 26148ce2..374c8251 100644 --- a/Questionable.Model/Questing/QuestStep.cs +++ b/Questionable.Model/Questing/QuestStep.cs @@ -65,6 +65,7 @@ public sealed class QuestStep public EAction? Action { get; set; } public EStatus? Status { get; set; } public EExtendedClassJob TargetClass { get; set; } = EExtendedClassJob.None; + public byte? TaxiStandId { get; set; } public EEnemySpawnType? EnemySpawnType { get; set; } public List KillEnemyDataIds { get; set; } = []; diff --git a/Questionable/Controller/CommandHandler.cs b/Questionable/Controller/CommandHandler.cs index d3878eb1..2c208b55 100644 --- a/Questionable/Controller/CommandHandler.cs +++ b/Questionable/Controller/CommandHandler.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.Command; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.UI; using Lumina.Excel.Sheets; using Questionable.Functions; using Questionable.Model.Questing; @@ -177,6 +179,24 @@ internal sealed class CommandHandler : IDisposable else _chatGui.PrintError("Could not query unlock links.", MessageTag, TagColor); break; + + case "taxi": + unsafe + { + List taxiStands = []; + var taxiStandNames = _dataManager.GetExcelSheet(); + var uiState = UIState.Instance(); + for (byte i = 0; i < uiState->ChocoboTaxiStandsBitmask.Length * 8; ++ i) + { + if (uiState->IsChocoboTaxiStandUnlocked(i)) + taxiStands.Add($"{taxiStandNames.GetRow(i + 0x120000u).PlaceName} ({i})"); + } + + _chatGui.Print("Unlocked taxi stands:", MessageTag, TagColor); + foreach (var taxiStand in taxiStands) + _chatGui.Print($"- {taxiStand}", MessageTag, TagColor); + } + break; } } diff --git a/Questionable/Controller/GameUi/InteractionUiController.cs b/Questionable/Controller/GameUi/InteractionUiController.cs index 67df02b2..212c5dcc 100644 --- a/Questionable/Controller/GameUi/InteractionUiController.cs +++ b/Questionable/Controller/GameUi/InteractionUiController.cs @@ -307,12 +307,15 @@ internal sealed class InteractionUiController : IDisposable if (currentQuest != null) { var quest = currentQuest.Quest; + bool isTaxiStandUnlock = false; if (checkAllSteps) { var sequence = quest.FindSequence(currentQuest.Sequence); var choices = sequence?.Steps.SelectMany(x => x.DialogueChoices); if (choices != null) dialogueChoices.AddRange(choices.Select(x => new DialogueChoiceInfo(quest, x))); + + isTaxiStandUnlock = sequence?.Steps.Any(x => x.InteractionType == EInteractionType.UnlockTaxiStand) ?? false; } else { @@ -339,9 +342,23 @@ internal sealed class InteractionUiController : IDisposable Prompt = null, Answer = step.PurchaseMenu.Key, })); + + isTaxiStandUnlock = step.InteractionType == EInteractionType.UnlockTaxiStand; } } + if (isTaxiStandUnlock) + { + _logger.LogInformation("Adding chocobo taxi stand unlock dialogue choices"); + dialogueChoices.Add(new DialogueChoiceInfo(quest, new DialogueChoice + { + Type = EDialogChoiceType.List, + ExcelSheet = "transport/ChocoboTaxiStand", + Prompt = ExcelRef.FromKey("TEXT_CHOCOBOTAXISTAND_00000_Q1_000_1"), + Answer = ExcelRef.FromKey("TEXT_CHOCOBOTAXISTAND_00000_A1_000_3") + })); + } + // add all travel dialogue choices var targetTerritoryId = FindTargetTerritoryFromQuestStep(currentQuest); if (targetTerritoryId != null) @@ -635,6 +652,12 @@ internal sealed class InteractionUiController : IDisposable _logger.LogInformation("SinglePlayerDutyYesNo: probably Single Player Duty"); return true; } + else + { + _logger.LogInformation("SinglePlayerDuty: not enabled"); + return false; + } + } return false; } diff --git a/Questionable/Controller/Steps/Interactions/Interact.cs b/Questionable/Controller/Steps/Interactions/Interact.cs index 6c19e0bf..b6dfc365 100644 --- a/Questionable/Controller/Steps/Interactions/Interact.cs +++ b/Questionable/Controller/Steps/Interactions/Interact.cs @@ -5,6 +5,7 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.UI; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Shared; using Questionable.Controller.Utils; @@ -46,6 +47,11 @@ internal static class Interact if (!automatonIpc.IsAutoSnipeEnabled) yield break; } + else if (step.InteractionType == EInteractionType.UnlockTaxiStand) + { + if (step.TaxiStandId == null) + yield break; + } else if (step.InteractionType != EInteractionType.Interact) yield break; @@ -55,10 +61,16 @@ internal static class Interact if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0) yield return new WaitAtEnd.WaitDelay(); - yield return new Task(step.DataId.Value, quest, step.InteractionType, - step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId || - step.SkipConditions is { StepIf.Never: true } || step.InteractionType == EInteractionType.PurchaseItem || step.DataId == 1052475, - step.PickUpItemId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags); + yield return new Task( + DataId: step.DataId.Value, + Quest: quest, + InteractionType: step.InteractionType, + SkipMarkerCheck: step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId || + step.SkipConditions is { StepIf.Never: true } || step.InteractionType == EInteractionType.PurchaseItem || step.DataId == 1052475, + PickUpItemId: step.PickUpItemId, + TaxiStandId: step.TaxiStandId, + SkipConditions: step.SkipConditions?.StepIf, + CompletionQuestVariablesFlags: step.CompletionQuestVariablesFlags); } } @@ -68,6 +80,7 @@ internal static class Interact EInteractionType InteractionType, bool SkipMarkerCheck = false, uint? PickUpItemId = null, + byte? TaxiStandId = null, SkipStepConditions? SkipConditions = null, List? CompletionQuestVariablesFlags = null) : ITask { @@ -159,12 +172,21 @@ internal static class Interact _needsUnmount = false; } - if (Task.PickUpItemId != null) + if (Task.PickUpItemId is { } pickUpItemId) { unsafe { InventoryManager* inventoryManager = InventoryManager.Instance(); - if (inventoryManager->GetInventoryItemCount(Task.PickUpItemId.Value) > 0) + if (inventoryManager->GetInventoryItemCount(pickUpItemId) > 0) + return ETaskResult.TaskComplete; + } + } + else if (Task.TaxiStandId is { } taxiStandId) + { + unsafe + { + UIState* uiState = UIState.Instance(); + if (uiState->IsChocoboTaxiStandUnlocked(taxiStandId)) return ETaskResult.TaskComplete; } } diff --git a/Questionable/Controller/Steps/Shared/SkipCondition.cs b/Questionable/Controller/Steps/Shared/SkipCondition.cs index ad2500f4..f9f47b21 100644 --- a/Questionable/Controller/Steps/Shared/SkipCondition.cs +++ b/Questionable/Controller/Steps/Shared/SkipCondition.cs @@ -31,6 +31,7 @@ internal static class SkipCondition if ((skipConditions == null || !skipConditions.HasSkipConditions()) && !QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags) && step.RequiredQuestVariables.Count == 0 && + step.TaxiStandId == null && step.PickUpQuestId == null && step.NextQuestId == null && step.RequiredCurrentJob.Count == 0 && @@ -118,6 +119,9 @@ internal static class SkipCondition if (CheckPickUpTurnInQuestIds(step)) return true; + if (CheckTaxiStandUnlocked(step)) + return true; + return false; } @@ -445,6 +449,19 @@ internal static class SkipCondition return false; } + private unsafe bool CheckTaxiStandUnlocked(QuestStep step) + { + UIState* uiState = UIState.Instance(); + if (step.TaxiStandId is { } taxiStandId && + uiState->IsChocoboTaxiStandUnlocked(taxiStandId)) + { + logger.LogInformation("Skipping step, as taxi stand {TaxiStandId} is unlocked", taxiStandId); + return true; + } + + return false; + } + public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep; public override bool ShouldInterruptOnDamage() => false;