From b46d0868bf797696da6c65fcdacd7e75662e9137 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Mon, 3 Mar 2025 23:07:55 +0100 Subject: [PATCH] Add dubious free fantasia handling --- .../SkipConditionsExtensions.cs | 6 +- .../5237_A Princely Persona.json | 37 +++- QuestPaths/quest-v1.json | 9 +- .../Converter/SkipConditionConverter.cs | 1 + .../Questing/EExtraSkipCondition.cs | 2 + .../Questing/SkipAetheryteCondition.cs | 1 + Questionable/Configuration.cs | 3 + .../Controller/Steps/Interactions/Interact.cs | 2 +- .../Steps/Shared/AetheryteShortcut.cs | 10 +- .../Steps/Shared/ExtraConditionUtils.cs | 75 ++++++++ .../Controller/Steps/Shared/SkipCondition.cs | 172 ++++++++++++++---- Questionable/QuestionablePlugin.cs | 1 + .../GeneralConfigComponent.cs | 14 ++ .../QuestComponents/EventInfoComponent.cs | 2 +- 14 files changed, 288 insertions(+), 47 deletions(-) create mode 100644 Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs diff --git a/QuestPathGenerator/RoslynElements/SkipConditionsExtensions.cs b/QuestPathGenerator/RoslynElements/SkipConditionsExtensions.cs index 01106bda..3ddda6df 100644 --- a/QuestPathGenerator/RoslynElements/SkipConditionsExtensions.cs +++ b/QuestPathGenerator/RoslynElements/SkipConditionsExtensions.cs @@ -152,6 +152,10 @@ internal static class SkipConditionsExtensions emptyAetheryte.RequiredQuestVariablesNotMet) .AsSyntaxNodeOrToken(), Assignment(nameof(skipAetheryteCondition.NearPosition), skipAetheryteCondition.NearPosition, - emptyAetheryte.NearPosition).AsSyntaxNodeOrToken())))); + emptyAetheryte.NearPosition) + .AsSyntaxNodeOrToken(), + Assignment(nameof(skipAetheryteCondition.ExtraCondition), skipAetheryteCondition.ExtraCondition, + emptyAetheryte.ExtraCondition) + .AsSyntaxNodeOrToken())))); } } diff --git a/QuestPaths/7.x - Dawntrail/Seasonal Events/Little Ladies' Day (2025)/5237_A Princely Persona.json b/QuestPaths/7.x - Dawntrail/Seasonal Events/Little Ladies' Day (2025)/5237_A Princely Persona.json index 81b2dcba..13efe336 100644 --- a/QuestPaths/7.x - Dawntrail/Seasonal Events/Little Ladies' Day (2025)/5237_A Princely Persona.json +++ b/QuestPaths/7.x - Dawntrail/Seasonal Events/Little Ladies' Day (2025)/5237_A Princely Persona.json @@ -26,6 +26,33 @@ { "Sequence": 1, "Steps": [ + { + "DataId": 1052475, + "Position": { + "X": -22.354492, + "Y": 10.13581, + "Z": -241.41296 + }, + "TerritoryId": 133, + "InteractionType": "Interact", + "AetheryteShortcut": "Gridania", + "AethernetShortcut": [ + "[Gridania] Aetheryte Plaza", + "[Gridania] Mih Khetto's Amphitheatre" + ], + "SkipConditions": { + "AetheryteShortcutIf": { + "ExtraCondition": "SkipFreeFantasia", + "InSameTerritory": true, + "InTerritory": [ + 133 + ] + }, + "StepIf": { + "ExtraCondition": "SkipFreeFantasia" + } + } + }, { "DataId": 1051889, "Position": { @@ -35,10 +62,16 @@ }, "TerritoryId": 131, "InteractionType": "Interact", + "AetheryteShortcut": "Ul'dah", "AethernetShortcut": [ - "[Ul'dah] Adventurers' Guild", + "[Ul'dah] Aetheryte Plaza", "[Ul'dah] Goldsmiths' Guild" - ] + ], + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } } ] }, diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index 24116573..fbebaf50 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -303,7 +303,8 @@ "RisingStonesSolar", "RoguesGuild", "NotRoguesGuild", - "DockStorehouse" + "DockStorehouse", + "SkipFreeFantasia" ] } }, @@ -370,6 +371,12 @@ "TerritoryId" ], "additionalProperties": false + }, + "ExtraCondition": { + "type": "string", + "enum": [ + "SkipFreeFantasia" + ] } }, "additionalProperties": false diff --git a/Questionable.Model/Questing/Converter/SkipConditionConverter.cs b/Questionable.Model/Questing/Converter/SkipConditionConverter.cs index b49a1cfb..8833655a 100644 --- a/Questionable.Model/Questing/Converter/SkipConditionConverter.cs +++ b/Questionable.Model/Questing/Converter/SkipConditionConverter.cs @@ -13,5 +13,6 @@ public sealed class SkipConditionConverter() : EnumConverter DockStorehouse, + + SkipFreeFantasia, } diff --git a/Questionable.Model/Questing/SkipAetheryteCondition.cs b/Questionable.Model/Questing/SkipAetheryteCondition.cs index 70e13474..70f0facb 100644 --- a/Questionable.Model/Questing/SkipAetheryteCondition.cs +++ b/Questionable.Model/Questing/SkipAetheryteCondition.cs @@ -21,4 +21,5 @@ public sealed class SkipAetheryteCondition public EAetheryteLocation? AetheryteUnlocked { get; set; } public bool RequiredQuestVariablesNotMet { get; set; } public NearPositionCondition? NearPosition { get; set; } + public EExtraSkipCondition? ExtraCondition { get; set; } } diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 4a13462e..3ee57379 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -34,6 +34,9 @@ internal sealed class Configuration : IPluginConfiguration public bool UseEscToCancelQuesting { get; set; } = true; public bool ShowIncompleteSeasonalEvents { get; set; } = true; public bool ConfigureTextAdvance { get; set; } = true; + + // TODO Temporary setting for 7.1 + public bool PickUpFreeFantasia { get; set; } = true; } internal sealed class DutyConfiguration diff --git a/Questionable/Controller/Steps/Interactions/Interact.cs b/Questionable/Controller/Steps/Interactions/Interact.cs index 2e44caf3..00e81f30 100644 --- a/Questionable/Controller/Steps/Interactions/Interact.cs +++ b/Questionable/Controller/Steps/Interactions/Interact.cs @@ -58,7 +58,7 @@ internal static class Interact 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.SkipConditions is { StepIf.Never: true } || step.InteractionType == EInteractionType.PurchaseItem || step.DataId == 1052475, step.PickUpItemId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags); } } diff --git a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs index 4a56e929..0dc58985 100644 --- a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs @@ -58,7 +58,8 @@ internal static class AetheryteShortcut IClientState clientState, IChatGui chatGui, ICondition condition, - AetheryteData aetheryteData) : TaskExecutor + AetheryteData aetheryteData, + ExtraConditionUtils extraConditionUtils) : TaskExecutor { private bool _teleported; private DateTime _continueAt; @@ -148,6 +149,13 @@ internal static class AetheryteShortcut return true; } } + + if (skipConditions.ExtraCondition != null && skipConditions.ExtraCondition != EExtraSkipCondition.None && + extraConditionUtils.MatchesExtraCondition(skipConditions.ExtraCondition.Value)) + { + logger.LogInformation("Skipping step, extra condition {} matches", skipConditions.ExtraCondition); + return true; + } } if (Task.ExpectedTerritoryId == territoryType) diff --git a/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs b/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs new file mode 100644 index 00000000..f15d313f --- /dev/null +++ b/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs @@ -0,0 +1,75 @@ +using System; +using System.Numerics; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game; +using Microsoft.Extensions.Logging; +using Questionable.Model.Questing; + +namespace Questionable.Controller.Steps.Shared; + +internal sealed class ExtraConditionUtils +{ + private readonly Configuration _configuration; + private readonly IClientState _clientState; + private readonly ILogger _logger; + + public ExtraConditionUtils( + Configuration configuration, + IClientState clientState, + ILogger logger) + { + _configuration = configuration; + _clientState = clientState; + _logger = logger; + } + + public bool MatchesExtraCondition(EExtraSkipCondition skipCondition) + { + var position = _clientState.LocalPlayer?.Position; + return position != null && + _clientState.TerritoryType != 0 && + MatchesExtraCondition(skipCondition, position.Value, _clientState.TerritoryType); + } + + public bool MatchesExtraCondition(EExtraSkipCondition skipCondition, Vector3 position, ushort territoryType) + { + return skipCondition switch + { + EExtraSkipCondition.WakingSandsMainArea => territoryType == 212 && position.X < 24, + EExtraSkipCondition.WakingSandsSolar => territoryType == 212 && position.X >= 24, + EExtraSkipCondition.RisingStonesSolar => territoryType == 351 && position.Z <= -28, + EExtraSkipCondition.RoguesGuild => territoryType == 129 && position.Y <= -115, + EExtraSkipCondition.NotRoguesGuild => territoryType == 129 && position.Y > -115, + EExtraSkipCondition.DockStorehouse => territoryType == 137 && position.Y <= -20, + EExtraSkipCondition.SkipFreeFantasia => ShouldSkipFreeFantasia(), + _ => throw new ArgumentOutOfRangeException(nameof(skipCondition), skipCondition, null) + }; + } + + private unsafe bool ShouldSkipFreeFantasia() + { + if (!_configuration.General.PickUpFreeFantasia) + { + _logger.LogInformation("Skipping fantasia step, as free fantasia is disabled in the configuration"); + return true; + } + + bool foundFestival = false; + for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; ++i) + { + if (GameMain.Instance()->ActiveFestivals[i].Id == 160) + { + foundFestival = true; + break; + } + } + + if (!foundFestival) + { + _logger.LogInformation("Skipping fantasia step, as free fantasia moogle is not available"); + return true; + } + + return false; + } +} diff --git a/Questionable/Controller/Steps/Shared/SkipCondition.cs b/Questionable/Controller/Steps/Shared/SkipCondition.cs index b651165f..f0f2f9c3 100644 --- a/Questionable/Controller/Steps/Shared/SkipCondition.cs +++ b/Questionable/Controller/Steps/Shared/SkipCondition.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Game.ClientState.Conditions; @@ -56,9 +55,10 @@ internal static class SkipCondition GameFunctions gameFunctions, QuestFunctions questFunctions, IClientState clientState, - ICondition condition) : TaskExecutor + ICondition condition, + ExtraConditionUtils extraConditionUtils) : TaskExecutor { - protected override unsafe bool Start() + protected override bool Start() { var skipConditions = Task.SkipConditions; var step = Task.Step; @@ -66,6 +66,60 @@ internal static class SkipCondition logger.LogInformation("Checking skip conditions; {ConfiguredConditions}", string.Join(",", skipConditions)); + if (CheckFlyingCondition(step, skipConditions)) + return true; + + if (CheckUnlockedMountCondition(skipConditions)) + return true; + + if (CheckDivingCondition(skipConditions)) + return true; + + if (CheckTerritoryCondition(skipConditions)) + return true; + + if (CheckQuestConditions(skipConditions)) + return true; + + if (CheckTargetableCondition(step, skipConditions)) + return true; + + if (CheckNameplateCondition(step, skipConditions)) + return true; + + if (CheckItemCondition(step, skipConditions)) + return true; + + if (CheckAetheryteCondition(step, skipConditions)) + return true; + + if (CheckAethernetCondition(step)) + return true; + + if (CheckQuestWorkConditions(elementId, step)) + return true; + + if (CheckJobCondition(step)) + return true; + + if (CheckPositionCondition(skipConditions)) + return true; + + if (skipConditions.ExtraCondition != null && skipConditions.ExtraCondition != EExtraSkipCondition.None && + extraConditionUtils.MatchesExtraCondition(skipConditions.ExtraCondition.Value)) + { + logger.LogInformation("Skipping step, extra condition {} matches", skipConditions.ExtraCondition); + return true; + } + + if (CheckPickUpTurnInQuestIds(step)) + return true; + + return false; + } + + private bool CheckFlyingCondition(QuestStep step, SkipStepConditions skipConditions) + { if (skipConditions.Flying == ELockedSkipCondition.Unlocked && gameFunctions.IsFlyingUnlocked(step.TerritoryId)) { @@ -80,6 +134,11 @@ internal static class SkipCondition return true; } + return false; + } + + private unsafe bool CheckUnlockedMountCondition(SkipStepConditions skipConditions) + { if (skipConditions.Chocobo == ELockedSkipCondition.Unlocked && PlayerState.Instance()->IsMountUnlocked(1)) { @@ -87,32 +146,47 @@ internal static class SkipCondition return true; } - if (skipConditions.Diving == true && condition[ConditionFlag.Diving]) + return false; + } + + private bool CheckTerritoryCondition(SkipStepConditions skipConditions) + { + if (skipConditions.InTerritory.Count > 0 && + skipConditions.InTerritory.Contains(clientState.TerritoryType)) { - logger.LogInformation("Skipping step, as you're currently diving underwater"); + logger.LogInformation("Skipping step, as in a skip.InTerritory"); return true; } - if (skipConditions.Diving == false && !condition[ConditionFlag.Diving]) + if (skipConditions.NotInTerritory.Count > 0 && + !skipConditions.NotInTerritory.Contains(clientState.TerritoryType)) { - logger.LogInformation("Skipping step, as you're not currently diving underwater"); + logger.LogInformation("Skipping step, as not in a skip.NotInTerritory"); return true; } - if (skipConditions.InTerritory.Count > 0 && - skipConditions.InTerritory.Contains(clientState.TerritoryType)) + return false; + } + + private bool CheckDivingCondition(SkipStepConditions skipConditions) + { + if (skipConditions.Diving == true && condition[ConditionFlag.Diving]) { - logger.LogInformation("Skipping step, as in a skip.InTerritory"); + logger.LogInformation("Skipping step, as you're currently diving underwater"); return true; } - if (skipConditions.NotInTerritory.Count > 0 && - !skipConditions.NotInTerritory.Contains(clientState.TerritoryType)) + if (skipConditions.Diving == false && !condition[ConditionFlag.Diving]) { - logger.LogInformation("Skipping step, as not in a skip.NotInTerritory"); + logger.LogInformation("Skipping step, as you're not currently diving underwater"); return true; } + return false; + } + + private bool CheckQuestConditions(SkipStepConditions skipConditions) + { if (skipConditions.QuestsCompleted.Count > 0 && skipConditions.QuestsCompleted.All(questFunctions.IsQuestComplete)) { @@ -127,6 +201,11 @@ internal static class SkipCondition return true; } + return false; + } + + private bool CheckTargetableCondition(QuestStep step, SkipStepConditions skipConditions) + { if (skipConditions.NotTargetable && step is { DataId: not null }) { @@ -146,6 +225,11 @@ internal static class SkipCondition } } + return false; + } + + private unsafe bool CheckNameplateCondition(QuestStep step, SkipStepConditions skipConditions) + { if (skipConditions.NotNamePlateIconId.Count > 0 && step is { DataId: not null }) { @@ -162,6 +246,11 @@ internal static class SkipCondition } } + return false; + } + + private unsafe bool CheckItemCondition(QuestStep step, SkipStepConditions skipConditions) + { if (skipConditions.Item is { NotInInventory: true } && step is { ItemId: not null }) { InventoryManager* inventoryManager = InventoryManager.Instance(); @@ -174,6 +263,11 @@ internal static class SkipCondition } } + return false; + } + + private bool CheckAetheryteCondition(QuestStep step, SkipStepConditions skipConditions) + { if (step is { DataId: not null, @@ -199,6 +293,11 @@ internal static class SkipCondition return true; } + return false; + } + + private bool CheckAethernetCondition(QuestStep step) + { if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } && gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value)) { @@ -206,6 +305,11 @@ internal static class SkipCondition return true; } + return false; + } + + private bool CheckQuestWorkConditions(ElementId elementId, QuestStep step) + { QuestProgressInfo? questWork = questFunctions.GetQuestProgressInfo(elementId); if (questWork != null) { @@ -249,6 +353,11 @@ internal static class SkipCondition } } + return false; + } + + private bool CheckJobCondition(QuestStep step) + { if (step is { RequiredCurrentJob.Count: > 0 }) { List expectedJobs = @@ -263,6 +372,11 @@ internal static class SkipCondition } } + return false; + } + + private bool CheckPositionCondition(SkipStepConditions skipConditions) + { if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == nearPosition.TerritoryId) { @@ -274,19 +388,11 @@ internal static class SkipCondition } } - if (skipConditions.ExtraCondition != null && skipConditions.ExtraCondition != EExtraSkipCondition.None) - { - var position = clientState.LocalPlayer?.Position; - if (position != null && - clientState.TerritoryType != 0 && - MatchesExtraCondition(skipConditions.ExtraCondition.Value, position.Value, - clientState.TerritoryType)) - { - logger.LogInformation("Skipping step, extra condition {} matches", skipConditions.ExtraCondition); - return true; - } - } + return false; + } + private bool CheckPickUpTurnInQuestIds(QuestStep step) + { if (step.PickUpQuestId != null && questFunctions.IsQuestAcceptedOrComplete(step.PickUpQuestId)) { logger.LogInformation("Skipping step, as we have already picked up the relevant quest"); @@ -302,20 +408,6 @@ internal static class SkipCondition return false; } - private static bool MatchesExtraCondition(EExtraSkipCondition condition, Vector3 position, ushort territoryType) - { - return condition switch - { - EExtraSkipCondition.WakingSandsMainArea => territoryType == 212 && position.X < 24, - EExtraSkipCondition.WakingSandsSolar => territoryType == 212 && position.X >= 24, - EExtraSkipCondition.RisingStonesSolar => territoryType == 351 && position.Z <= -28, - EExtraSkipCondition.RoguesGuild => territoryType == 129 && position.Y <= -115, - EExtraSkipCondition.NotRoguesGuild => territoryType == 129 && position.Y > -115, - EExtraSkipCondition.DockStorehouse => territoryType == 137 && position.Y <= -20, - _ => throw new ArgumentOutOfRangeException(nameof(condition), condition, null) - }; - } - public override ETaskResult Update() => ETaskResult.SkipRemainingTasksForStep; public override bool ShouldInterruptOnDamage() => false; diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index 865718cb..e53bafa1 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -247,6 +247,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddTaskExecutor(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); } private static void AddControllers(ServiceCollection serviceCollection) diff --git a/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs b/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs index 9f71d7a6..d396ad9a 100644 --- a/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs +++ b/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Dalamud.Interface.Colors; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin; using Dalamud.Plugin.Services; @@ -110,5 +111,18 @@ internal sealed class GeneralConfigComponent : ConfigComponent Configuration.General.ConfigureTextAdvance = configureTextAdvance; Save(); } + + ImGui.Separator(); + ImGui.TextColored(ImGuiColors.DalamudYellow, "Patch 7.1 exclusive content"); + using (_ = ImRaii.PushIndent()) + { + bool pickUpFreeFantasia = Configuration.General.PickUpFreeFantasia; + if (ImGui.Checkbox("Try to pick up free limited-time fantasia during 'Little Ladies' Day' quests", + ref pickUpFreeFantasia)) + { + Configuration.General.PickUpFreeFantasia = pickUpFreeFantasia; + Save(); + } + } } } diff --git a/Questionable/Windows/QuestComponents/EventInfoComponent.cs b/Questionable/Windows/QuestComponents/EventInfoComponent.cs index 27165d69..5d6bbc95 100644 --- a/Questionable/Windows/QuestComponents/EventInfoComponent.cs +++ b/Questionable/Windows/QuestComponents/EventInfoComponent.cs @@ -22,7 +22,7 @@ internal sealed class EventInfoComponent [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] private readonly List _eventQuests = [ - new("Valentione's Day", [new(5251)], AtDailyReset(new(2025, 2, 17))), + new("Valentione's Day", [new(5237), new(5238)], AtDailyReset(new(2025, 3, 17))), ]; private readonly QuestData _questData; -- 2.20.1