Add dubious free fantasia handling
authorLiza Carvelli <liza@carvel.li>
Mon, 3 Mar 2025 22:07:55 +0000 (23:07 +0100)
committerLiza Carvelli <liza@carvel.li>
Mon, 3 Mar 2025 22:07:55 +0000 (23:07 +0100)
14 files changed:
QuestPathGenerator/RoslynElements/SkipConditionsExtensions.cs
QuestPaths/7.x - Dawntrail/Seasonal Events/Little Ladies' Day (2025)/5237_A Princely Persona.json
QuestPaths/quest-v1.json
Questionable.Model/Questing/Converter/SkipConditionConverter.cs
Questionable.Model/Questing/EExtraSkipCondition.cs
Questionable.Model/Questing/SkipAetheryteCondition.cs
Questionable/Configuration.cs
Questionable/Controller/Steps/Interactions/Interact.cs
Questionable/Controller/Steps/Shared/AetheryteShortcut.cs
Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs [new file with mode: 0644]
Questionable/Controller/Steps/Shared/SkipCondition.cs
Questionable/QuestionablePlugin.cs
Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs
Questionable/Windows/QuestComponents/EventInfoComponent.cs

index 01106bd..3ddda6d 100644 (file)
@@ -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()))));
     }
 }
index 81b2dcb..13efe33 100644 (file)
     {
       "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": {
           },
           "TerritoryId": 131,
           "InteractionType": "Interact",
+          "AetheryteShortcut": "Ul'dah",
           "AethernetShortcut": [
-            "[Ul'dah] Adventurers' Guild",
+            "[Ul'dah] Aetheryte Plaza",
             "[Ul'dah] Goldsmiths' Guild"
-          ]
+          ],
+          "SkipConditions": {
+            "AetheryteShortcutIf": {
+              "InSameTerritory": true
+            }
+          }
         }
       ]
     },
index 2411657..fbebaf5 100644 (file)
                     "RisingStonesSolar",
                     "RoguesGuild",
                     "NotRoguesGuild",
-                    "DockStorehouse"
+                    "DockStorehouse",
+                    "SkipFreeFantasia"
                   ]
                 }
               },
                     "TerritoryId"
                   ],
                   "additionalProperties": false
+                },
+                "ExtraCondition": {
+                  "type": "string",
+                  "enum": [
+                    "SkipFreeFantasia"
+                  ]
                 }
               },
               "additionalProperties": false
index b49a1cf..8833655 100644 (file)
@@ -13,5 +13,6 @@ public sealed class SkipConditionConverter() : EnumConverter<EExtraSkipCondition
         { EExtraSkipCondition.RoguesGuild, "RoguesGuild"},
         { EExtraSkipCondition.NotRoguesGuild, "NotRoguesGuild"},
         { EExtraSkipCondition.DockStorehouse, "DockStorehouse"},
+        { EExtraSkipCondition.SkipFreeFantasia, "SkipFreeFantasia" },
     };
 }
index 47de211..06c0c4e 100644 (file)
@@ -21,4 +21,6 @@ public enum EExtraSkipCondition
     /// Location for NIN quests in Eastern La Noscea; located far underneath the actual zone.
     /// </summary>
     DockStorehouse,
+
+    SkipFreeFantasia,
 }
index 70e1347..70f0fac 100644 (file)
@@ -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; }
 }
index 4a13462..3ee5737 100644 (file)
@@ -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
index 2e44caf..00e81f3 100644 (file)
@@ -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);
         }
     }
index 4a56e92..0dc5898 100644 (file)
@@ -58,7 +58,8 @@ internal static class AetheryteShortcut
         IClientState clientState,
         IChatGui chatGui,
         ICondition condition,
-        AetheryteData aetheryteData) : TaskExecutor<Task>
+        AetheryteData aetheryteData,
+        ExtraConditionUtils extraConditionUtils) : TaskExecutor<Task>
     {
         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 (file)
index 0000000..f15d313
--- /dev/null
@@ -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<ExtraConditionUtils> _logger;
+
+    public ExtraConditionUtils(
+        Configuration configuration,
+        IClientState clientState,
+        ILogger<ExtraConditionUtils> 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;
+    }
+}
index b651165..f0f2f9c 100644 (file)
@@ -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<SkipTask>
+        ICondition condition,
+        ExtraConditionUtils extraConditionUtils) : TaskExecutor<SkipTask>
     {
-        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<EClassJob> 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;
index 865718c..e53bafa 100644 (file)
@@ -247,6 +247,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddTaskExecutor<WaitAtEnd.EndAutomation, WaitAtEnd.EndAutomationExecutor>();
 
         serviceCollection.AddSingleton<TaskCreator>();
+        serviceCollection.AddSingleton<ExtraConditionUtils>();
     }
 
     private static void AddControllers(ServiceCollection serviceCollection)
index 9f71d7a..d396ad9 100644 (file)
@@ -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();
+            }
+        }
     }
 }
index 27165d6..5d6bbc9 100644 (file)
@@ -22,7 +22,7 @@ internal sealed class EventInfoComponent
     [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
     private readonly List<EventQuest> _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;