Add quest battle for 'It's Probably a Trap'
authorLiza Carvelli <liza@carvel.li>
Mon, 10 Mar 2025 20:19:10 +0000 (21:19 +0100)
committerLiza Carvelli <liza@carvel.li>
Mon, 10 Mar 2025 20:19:10 +0000 (21:19 +0100)
QuestPaths/4.x - Stormblood/MSQ/A2-Kugane/2474_It's Probably a Trap.json
Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs
Questionable/External/BossModIpc.cs

index a5a7d30cd506d343e0a5537140a1b529c26357a7..67e65dd87f0d77bb0fb0389279751a4e148dba0d 100644 (file)
           },
           "TerritoryId": 628,
           "InteractionType": "SinglePlayerDuty",
+          "SinglePlayerDutyOptions": {
+            "Enabled": true,
+            "TestedBossModVersion": "0.1.0.1"
+          },
           "AethernetShortcut": [
             "[Kugane] The Ruby Bazaar",
             "[Kugane] Kogane Dori Markets"
       ]
     }
   ]
-}
\ No newline at end of file
+}
index ef61cb46140224fd45b445908a5f22645df15bee..264d812824ddf7573c1a60f2c64c16883e77e0d4 100644 (file)
@@ -6,6 +6,8 @@ using Dalamud.Game.ClientState.Objects;
 using Dalamud.Game.ClientState.Objects.Types;
 using Dalamud.Plugin.Services;
 using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Event;
+using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
 using Questionable.Controller.Steps.Common;
 using Questionable.Controller.Steps.Shared;
 using Questionable.Data;
@@ -17,7 +19,11 @@ namespace Questionable.Controller.Steps.Interactions;
 
 internal static class SinglePlayerDuty
 {
-    public const int LahabreaTerritoryId = 1052;
+    private static class SpecialTerritories
+    {
+        public const ushort Lahabrea = 1052;
+        public const ushort ItsProbablyATrap = 665;
+    }
 
     internal sealed class Factory(
         BossModIpc bossModIpc,
@@ -32,24 +38,42 @@ internal static class SinglePlayerDuty
 
             if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions))
             {
-                if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex, out var cfcData))
+                if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex,
+                        out var cfcData))
                     throw new TaskException("Failed to get content finder condition for solo instance");
 
                 yield return new StartSinglePlayerDuty(cfcData.ContentFinderConditionId);
                 yield return new EnableAi();
-                if (cfcData.TerritoryId == LahabreaTerritoryId)
+                if (cfcData.TerritoryId == SpecialTerritories.Lahabrea)
                 {
                     yield return new SetTarget(14643);
-                    yield return new WaitCondition.Task(() => condition[ConditionFlag.Unconscious] || clientState.TerritoryType != LahabreaTerritoryId, "Wait(death)");
+                    yield return new WaitCondition.Task(
+                        () => condition[ConditionFlag.Unconscious] || clientState.TerritoryType != SpecialTerritories.Lahabrea,
+                        "Wait(death)");
                     yield return new DisableAi();
-                    yield return new WaitCondition.Task(() => !condition[ConditionFlag.Unconscious] || clientState.TerritoryType != LahabreaTerritoryId, "Wait(resurrection)");
+                    yield return new WaitCondition.Task(
+                        () => !condition[ConditionFlag.Unconscious] || clientState.TerritoryType != SpecialTerritories.Lahabrea,
+                        "Wait(resurrection)");
                     yield return new EnableAi();
                 }
+                else if (cfcData.TerritoryId == SpecialTerritories.ItsProbablyATrap)
+                {
+                    yield return new WaitCondition.Task(() => DutyActionsAvailable() || clientState.TerritoryType != SpecialTerritories.ItsProbablyATrap,
+                        "Wait(Phase 2)");
+                    yield return new EnableAi(true);
+                }
+
                 yield return new WaitSinglePlayerDuty(cfcData.ContentFinderConditionId);
                 yield return new DisableAi();
                 yield return new WaitAtEnd.WaitNextStepOrSequence();
             }
         }
+
+        private unsafe bool DutyActionsAvailable()
+        {
+            ContentDirector* contentDirector = EventFramework.Instance()->GetContentDirector();
+            return contentDirector != null && contentDirector->DutyActionManager.ActionsPresent;
+        }
     }
 
     internal sealed record StartSinglePlayerDuty(uint ContentFinderConditionId) : ITask
@@ -71,9 +95,9 @@ internal static class SinglePlayerDuty
         public override bool ShouldInterruptOnDamage() => false;
     }
 
-    internal sealed record EnableAi : ITask
+    internal sealed record EnableAi(bool Passive = false) : ITask
     {
-        public override string ToString() => "BossMod.EnableAi";
+        public override string ToString() => $"BossMod.EnableAi({(Passive ? "Passive" : "AutoPull")})";
     }
 
     internal sealed class EnableAiExecutor(
@@ -81,7 +105,7 @@ internal static class SinglePlayerDuty
     {
         protected override bool Start()
         {
-            bossModIpc.EnableAi();
+            bossModIpc.EnableAi(Task.Passive);
             return true;
         }
 
index ca732105f6bc1d92763fadfcb19d976b5914f4fc..05e8257cbdfd102090b7f5a380dda146d082a2cf 100644 (file)
@@ -72,11 +72,11 @@ internal sealed class BossModIpc
     }
 
     // TODO this should use your actual rotation plugin, not always vbm
-    public void EnableAi()
+    public void EnableAi(bool passive)
     {
         _commandManager.ProcessCommand("/vbmai on");
         _commandManager.ProcessCommand("/vbm cfg ZoneModuleConfig EnableQuestBattles true");
-        SetPreset(EPreset.QuestBattle);
+        SetPreset(passive ? EPreset.Overworld : EPreset.QuestBattle);
     }
 
     public void DisableAi()