diff --git a/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs b/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs index 7e57e1ae..1ff4fbc1 100644 --- a/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs +++ b/QuestPathGenerator/RoslynElements/QuestStepExtensions.cs @@ -108,7 +108,7 @@ internal static class QuestStepExtensions AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData) .AsSyntaxNodeOrToken(), Assignment(nameof(QuestStep.CombatItemUse), step.CombatItemUse, - emptyStep.CombatItemUse) + emptyStep.CombatItemUse) .AsSyntaxNodeOrToken(), Assignment(nameof(QuestStep.CombatDelaySecondsAtStart), step.CombatDelaySecondsAtStart, @@ -123,14 +123,8 @@ internal static class QuestStepExtensions Assignment(nameof(QuestStep.AutoDutyEnabled), step.AutoDutyEnabled, emptyStep.AutoDutyEnabled) .AsSyntaxNodeOrToken(), - Assignment(nameof(QuestStep.BossModEnabled), - step.BossModEnabled, emptyStep.BossModEnabled) - .AsSyntaxNodeOrToken(), - Assignment(nameof(QuestStep.BossModNotes), - step.BossModNotes, emptyStep.BossModNotes) - .AsSyntaxNodeOrToken(), - Assignment(nameof(QuestStep.SinglePlayerDutyIndex), - step.SinglePlayerDutyIndex, emptyStep.SinglePlayerDutyIndex) + Assignment(nameof(QuestStep.SinglePlayerDutyOptions), step.SinglePlayerDutyOptions, + emptyStep.SinglePlayerDutyOptions) .AsSyntaxNodeOrToken(), Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions, emptyStep.SkipConditions) diff --git a/QuestPathGenerator/RoslynElements/SinglePlayerDutyOptionsExtensions.cs b/QuestPathGenerator/RoslynElements/SinglePlayerDutyOptionsExtensions.cs new file mode 100644 index 00000000..7a545318 --- /dev/null +++ b/QuestPathGenerator/RoslynElements/SinglePlayerDutyOptionsExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Questionable.Model.Questing; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Questionable.QuestPathGenerator.RoslynShortcuts; + +namespace Questionable.QuestPathGenerator.RoslynElements; + +internal static class SinglePlayerDutyOptionsExtensions +{ + public static ExpressionSyntax ToExpressionSyntax(this SinglePlayerDutyOptions dutyOptions) + { + var emptyOptions = new SinglePlayerDutyOptions(); + return ObjectCreationExpression( + IdentifierName(nameof(SinglePlayerDutyOptions))) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + SyntaxNodeList( + Assignment(nameof(SinglePlayerDutyOptions.Enabled), + dutyOptions.Enabled, emptyOptions.Enabled) + .AsSyntaxNodeOrToken(), + Assignment(nameof(SinglePlayerDutyOptions.Notes), + dutyOptions.Notes, emptyOptions.Notes) + .AsSyntaxNodeOrToken(), + Assignment(nameof(SinglePlayerDutyOptions.Index), + dutyOptions.Index, emptyOptions.Index) + .AsSyntaxNodeOrToken())))); + } +} diff --git a/QuestPathGenerator/RoslynShortcuts.cs b/QuestPathGenerator/RoslynShortcuts.cs index 2c1df09f..70cae35d 100644 --- a/QuestPathGenerator/RoslynShortcuts.cs +++ b/QuestPathGenerator/RoslynShortcuts.cs @@ -62,6 +62,7 @@ public static class RoslynShortcuts ComplexCombatData complexCombatData => complexCombatData.ToExpressionSyntax(), QuestWorkValue questWorkValue => questWorkValue.ToExpressionSyntax(), List list => list.ToExpressionSyntax(), // TODO fix in AssignmentList + SinglePlayerDutyOptions dutyOptions => dutyOptions.ToExpressionSyntax(), SkipConditions skipConditions => skipConditions.ToExpressionSyntax(), SkipStepConditions skipStepConditions => skipStepConditions.ToExpressionSyntax(), SkipItemConditions skipItemCondition => skipItemCondition.ToExpressionSyntax(), diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/BRD/76_The One That Got Away.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/BRD/76_The One That Got Away.json index f374ebd7..3e3fc39b 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/BRD/76_The One That Got Away.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/BRD/76_The One That Got Away.json @@ -57,7 +57,9 @@ }, "TerritoryId": 153, "InteractionType": "SinglePlayerDuty", - "SinglePlayerDutyIndex": 1, + "SinglePlayerDutyOptions": { + "Index": 1 + }, "Fly": true } ] diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/439_Proof of Might.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/439_Proof of Might.json index c9af0007..bbbf6ded 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/439_Proof of Might.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/439_Proof of Might.json @@ -62,7 +62,9 @@ }, "TerritoryId": 154, "InteractionType": "SinglePlayerDuty", - "SinglePlayerDutyIndex": 1, + "SinglePlayerDutyOptions": { + "Index": 1 + }, "AetheryteShortcut": "North Shroud - Fallgourd Float", "Fly": true } diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/56_Lance of Destiny.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/56_Lance of Destiny.json index b588274c..0b0d4559 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/56_Lance of Destiny.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/DRG/56_Lance of Destiny.json @@ -120,7 +120,9 @@ }, "TerritoryId": 152, "InteractionType": "SinglePlayerDuty", - "SinglePlayerDutyIndex": 1 + "SinglePlayerDutyOptions": { + "Index": 1 + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/558_The Spirit Is Willing.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/558_The Spirit Is Willing.json index 8924e6fd..f45e4e63 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/558_The Spirit Is Willing.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/558_The Spirit Is Willing.json @@ -140,6 +140,9 @@ }, "TerritoryId": 141, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "Fly": true } ] diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/567_Return of the Holyfist.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/567_Return of the Holyfist.json index b8d8505f..50706944 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/567_Return of the Holyfist.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/MNK/567_Return of the Holyfist.json @@ -92,7 +92,9 @@ }, "TerritoryId": 130, "InteractionType": "SinglePlayerDuty", - "SinglePlayerDutyIndex": 1, + "SinglePlayerDutyOptions": { + "Index": 1 + }, "AetheryteShortcut": "Ul'dah" } ] diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1054_How to Quit You.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1054_How to Quit You.json index 08758166..6abfe268 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1054_How to Quit You.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1054_How to Quit You.json @@ -35,6 +35,9 @@ }, "TerritoryId": 137, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "AetheryteShortcut": "Eastern La Noscea - Wineport", "Fly": true } diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/147_Trial by Wind.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/147_Trial by Wind.json index 0dfda145..b54256d6 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/147_Trial by Wind.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/147_Trial by Wind.json @@ -116,6 +116,9 @@ }, "TerritoryId": 152, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "Fly": true } ] diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/91_Trial by Wind.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/91_Trial by Wind.json index a176c3b2..c110a643 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/91_Trial by Wind.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/91_Trial by Wind.json @@ -65,7 +65,8 @@ "AetheryteShortcut": "East Shroud - Hawthorne Hut", "SkipConditions": { "AetheryteShortcutIf": { - "InSameTerritory": true + "InSameTerritory": true, + "AetheryteLocked": "East Shroud - Hawthorne Hut" } } } @@ -116,7 +117,10 @@ "Z": 35.568726 }, "TerritoryId": 152, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/92_Trial by Water.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/92_Trial by Water.json index cc3a7491..aebc9138 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/92_Trial by Water.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/WHM/92_Trial by Water.json @@ -33,6 +33,39 @@ { "Sequence": 1, "Steps": [ + { + "DataId": 1001263, + "Position": { + "X": 181.41443, + "Y": -2.3519497, + "Z": -240.40594 + }, + "TerritoryId": 133, + "InteractionType": "Interact", + "TargetTerritoryId": 152, + "AethernetShortcut": [ + "[Gridania] Conjurers' Guild", + "[Gridania] Lancers' Guild" + ], + "SkipConditions": { + "StepIf": { + "AetheryteUnlocked": "East Shroud - Hawthorne Hut", + "InTerritory": [ + 152 + ] + } + } + }, + { + "TerritoryId": 152, + "InteractionType": "AttuneAetheryte", + "Aetheryte": "East Shroud - Hawthorne Hut", + "SkipConditions": { + "StepIf": { + "AetheryteUnlocked": "East Shroud - Hawthorne Hut" + } + } + }, { "Position": { "X": -276.804, @@ -42,7 +75,12 @@ "TerritoryId": 152, "InteractionType": "WalkTo", "AetheryteShortcut": "East Shroud - Hawthorne Hut", - "Fly": true + "Fly": true, + "SkipConditions": { + "AetheryteShortcutIf": { + "AetheryteLocked": "East Shroud - Hawthorne Hut" + } + } }, { "DataId": 2000889, @@ -212,6 +250,9 @@ }, "TerritoryId": 152, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "Fly": true } ] diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/129_Spirithold Broken.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/129_Spirithold Broken.json index cdb594ab..3bc3081b 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/129_Spirithold Broken.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/129_Spirithold Broken.json @@ -138,7 +138,10 @@ "Z": 192.2179 }, "TerritoryId": 148, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/161_Leia's Legacy.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/161_Leia's Legacy.json index 9bd7daf8..981c73c5 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/161_Leia's Legacy.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/161_Leia's Legacy.json @@ -111,7 +111,13 @@ "Z": 295.52136 }, "TerritoryId": 148, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true, + "Notes": [ + "Healer NPC is only killed after the boss dies; all NPCs need to be killed for the duty to complete" + ] + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/445_Chasing Shadows.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/445_Chasing Shadows.json index bb4aa655..769e2f5f 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/445_Chasing Shadows.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/445_Chasing Shadows.json @@ -29,10 +29,12 @@ }, "TerritoryId": 148, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": false, - "BossModNotes": [ - "AI doesn't automatically target newly spawning adds and dies until after the boss died (tested on CNJ)" - ] + "SinglePlayerDutyOptions": { + "Enabled": false, + "Notes": [ + "AI doesn't automatically target newly spawning adds until after the boss died, and dies (tested on CNJ)" + ] + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/447_To Guard a Guardian.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/447_To Guard a Guardian.json index aa50f006..d8b6848a 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/447_To Guard a Guardian.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/447_To Guard a Guardian.json @@ -77,6 +77,12 @@ }, "TerritoryId": 148, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true, + "Notes": [ + "(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete" + ] + }, "AetheryteShortcut": "Central Shroud - Bentbranch Meadows" } ] diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/414_Victory in Peril.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/414_Victory in Peril.json index 05886053..236b0f94 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/414_Victory in Peril.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/414_Victory in Peril.json @@ -69,6 +69,12 @@ }, "TerritoryId": 135, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": false, + "Notes": [ + "(Phase 1, second enemy group) Stuck with enemy being out of sight -- but still able to attack you (tested on ACN)" + ] + }, "AetheryteShortcut": "Lower La Noscea - Moraby Drydocks" } ] diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/466_Double Dealing.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/466_Double Dealing.json index 72ba9d7a..ee2f686b 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/466_Double Dealing.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/466_Double Dealing.json @@ -45,8 +45,11 @@ "TerritoryId": 134, "InteractionType": "Combat", "EnemySpawnType": "AutoOnEnterArea", - "KillEnemyDataIds": [ - 52 + "ComplexCombatData": [ + { + "DataId": 52, + "IgnoreQuestMarker": true + } ] }, { diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/469_Just Deserts.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/469_Just Deserts.json index 62476d4e..54199db0 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/469_Just Deserts.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/469_Just Deserts.json @@ -73,7 +73,10 @@ "Z": -432.15082 }, "TerritoryId": 134, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/543_Lurkers in the Grotto.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/543_Lurkers in the Grotto.json index 8e6aad40..901d4078 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/543_Lurkers in the Grotto.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/543_Lurkers in the Grotto.json @@ -28,7 +28,13 @@ "Z": -141.7716 }, "TerritoryId": 134, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": false, + "Notes": [ + "AI doesn't automatically target newly spawning adds until after the boss died (requires healing luck on ACN)" + ] + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/544_Feint and Strike.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/544_Feint and Strike.json index e812ae0f..6ffa3d58 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/544_Feint and Strike.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/544_Feint and Strike.json @@ -58,6 +58,12 @@ }, "TerritoryId": 138, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true, + "Notes": [ + "(Phase 1) Kills PGL NPCs and then the boss - allied NPCs will kill most other NPCs eventually; all NPCs need to be killed for the duty to complete" + ] + }, "AetheryteShortcut": "Western La Noscea - Swiftperch" } ] diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/343_Lord of the Inferno.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/343_Lord of the Inferno.json index cab6933d..c236b60b 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/343_Lord of the Inferno.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/343_Lord of the Inferno.json @@ -44,7 +44,10 @@ "Z": -242.51166 }, "TerritoryId": 145, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/660_Into a Copper Hell.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/660_Into a Copper Hell.json index e5c21738..4141d320 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/660_Into a Copper Hell.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/660_Into a Copper Hell.json @@ -79,6 +79,9 @@ }, "TerritoryId": 130, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "AetheryteShortcut": "Ul'dah", "AethernetShortcut": [ "[Ul'dah] Aetheryte Plaza", @@ -87,6 +90,9 @@ } ] }, + { + "Sequence": 5 + }, { "Sequence": 255, "Steps": [ diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json index 4874da87..310b20cc 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/680_The Company You Keep (Twin Adders).json @@ -63,12 +63,22 @@ "AethernetShortcut": [ "[Gridania] Aetheryte Plaza", "[Gridania] Lancers' Guild" - ] + ], + "SkipConditions": { + "StepIf": { + "AetheryteUnlocked": "East Shroud - Hawthorne Hut" + } + } }, { "TerritoryId": 152, "InteractionType": "AttuneAetheryte", - "Aetheryte": "East Shroud - Hawthorne Hut" + "Aetheryte": "East Shroud - Hawthorne Hut", + "SkipConditions": { + "StepIf": { + "AetheryteUnlocked": "East Shroud - Hawthorne Hut" + } + } }, { "DataId": 1004886, @@ -78,7 +88,16 @@ "Z": 475.30322 }, "TerritoryId": 152, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, + "AetheryteShortcut": "East Shroud - Hawthorne Hut", + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/681_The Company You Keep (Maelstrom).json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/681_The Company You Keep (Maelstrom).json index 4d2c5e40..dae94b36 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/681_The Company You Keep (Maelstrom).json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/681_The Company You Keep (Maelstrom).json @@ -64,6 +64,9 @@ }, "TerritoryId": 135, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "AethernetShortcut": [ "[Limsa Lominsa] The Aftcastle", "[Limsa Lominsa] Tempest Gate (Lower La Noscea)" diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/682_The Company You Keep (Immortal Flames).json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/682_The Company You Keep (Immortal Flames).json index ba6b4a76..9bcf3680 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/682_The Company You Keep (Immortal Flames).json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/682_The Company You Keep (Immortal Flames).json @@ -59,6 +59,9 @@ }, "TerritoryId": 140, "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + }, "AetheryteShortcut": "Western Thanalan - Horizon" } ] diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/303_Step Nine.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/303_Step Nine.json index 0e262818..4c8290b1 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/303_Step Nine.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/303_Step Nine.json @@ -46,7 +46,8 @@ }, "StopDistance": 7, "TerritoryId": 141, - "InteractionType": "Interact" + "InteractionType": "Interact", + "DelaySecondsAtStart": 2 } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/320_Way Down in the Hole.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/320_Way Down in the Hole.json index 42a2b633..a61bf978 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/320_Way Down in the Hole.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/320_Way Down in the Hole.json @@ -158,7 +158,10 @@ "Z": 117.29602 }, "TerritoryId": 141, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/334_Storms on the Horizon.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/334_Storms on the Horizon.json index 7c6a109b..0074bc2b 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/334_Storms on the Horizon.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/334_Storms on the Horizon.json @@ -21,6 +21,15 @@ { "Sequence": 255, "Steps": [ + { + "Position": { + "X": -174.73444, + "Y": 15.450659, + "Z": -266.76144 + }, + "TerritoryId": 140, + "InteractionType": "WalkTo" + }, { "Position": { "X": -289.1099, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/336_Oh Captain, My Captain.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/336_Oh Captain, My Captain.json index b35b8ea3..0b89ddcc 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/336_Oh Captain, My Captain.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/336_Oh Captain, My Captain.json @@ -37,7 +37,10 @@ "Z": -293.1411 }, "TerritoryId": 140, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/3853_Heir Today, Gone Tomorrow.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/3853_Heir Today, Gone Tomorrow.json index 14305617..6cc4dc5b 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/3853_Heir Today, Gone Tomorrow.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/3853_Heir Today, Gone Tomorrow.json @@ -29,7 +29,7 @@ }, "TerritoryId": 141, "InteractionType": "Combat", - "EnemySpawnType": "OverworldEnemies", + "EnemySpawnType": "FinishCombatIfAny", "KillEnemyDataIds": [ 352, 353 @@ -53,6 +53,25 @@ { "Sequence": 255, "Steps": [ + { + "Position": { + "X": 131.78122, + "Y": 20.119337, + "Z": -115.27284 + }, + "TerritoryId": 141, + "InteractionType": "WalkTo" + }, + { + "Position": { + "X": 127.7017, + "Y": -0.15994573, + "Z": -161.89238 + }, + "TerritoryId": 141, + "InteractionType": "WalkTo", + "DisableNavmesh": true + }, { "DataId": 1001605, "Position": { diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/550_Underneath the Sultantree.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/550_Underneath the Sultantree.json index c12bda58..c211fef2 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/550_Underneath the Sultantree.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/550_Underneath the Sultantree.json @@ -28,7 +28,10 @@ "Z": 536.88855 }, "TerritoryId": 141, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/551_Duty, Honor, Country.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/551_Duty, Honor, Country.json index c16c22b9..841ded18 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/551_Duty, Honor, Country.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/551_Duty, Honor, Country.json @@ -64,7 +64,13 @@ "Z": -131.48706 }, "TerritoryId": 141, - "InteractionType": "Interact", + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true, + "Notes": [ + "(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete" + ] + }, "AetheryteShortcut": "Central Thanalan - Black Brush Station" } ] diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A0-Gridania to East Shroud/3856_We Come in Peace.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A0-Gridania to East Shroud/3856_We Come in Peace.json index a7fddd2e..b0e58b23 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A0-Gridania to East Shroud/3856_We Come in Peace.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A0-Gridania to East Shroud/3856_We Come in Peace.json @@ -73,13 +73,23 @@ }, "TerritoryId": 133, "InteractionType": "Interact", - "TargetTerritoryId": 152 + "TargetTerritoryId": 152, + "SkipConditions": { + "StepIf": { + "AetheryteUnlocked": "East Shroud - Hawthorne Hut" + } + } }, { "TerritoryId": 152, "InteractionType": "AttuneAetheryte", "Aetheryte": "East Shroud - Hawthorne Hut", - "StopDistance": 5 + "StopDistance": 5, + "SkipConditions": { + "StepIf": { + "AetheryteUnlocked": "East Shroud - Hawthorne Hut" + } + } }, { "DataId": 1006188, @@ -89,7 +99,13 @@ "Z": 283.4973 }, "TerritoryId": 152, - "InteractionType": "CompleteQuest" + "InteractionType": "CompleteQuest", + "AetheryteShortcut": "East Shroud - Hawthorne Hut", + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } } ] } diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A2-East Shroud to South Shroud/724_Brotherly Love.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A2-East Shroud to South Shroud/724_Brotherly Love.json index 2b4fbd24..0a54ca73 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A2-East Shroud to South Shroud/724_Brotherly Love.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A2-East Shroud to South Shroud/724_Brotherly Love.json @@ -64,7 +64,10 @@ "Z": -39.383606 }, "TerritoryId": 152, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3862_Nouveau Riche.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3862_Nouveau Riche.json index dfda0e54..3b413785 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3862_Nouveau Riche.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3862_Nouveau Riche.json @@ -83,7 +83,13 @@ "Z": -12.985474 }, "TerritoryId": 153, - "InteractionType": "SinglePlayerDuty" + "InteractionType": "SinglePlayerDuty", + "SinglePlayerDutyOptions": { + "Enabled": true, + "Notes": [ + "AI will kill initial adds before the boss, but not switch target whenever new enemies spawn; all NPCs need to be killed for the duty to complete" + ] + } } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/C9-Ultimate Weapon/4522_The Ultimate Weapon.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/C9-Ultimate Weapon/4522_The Ultimate Weapon.json index cd396c51..8d6f72e7 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/C9-Ultimate Weapon/4522_The Ultimate Weapon.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/C9-Ultimate Weapon/4522_The Ultimate Weapon.json @@ -104,10 +104,12 @@ }, "TerritoryId": 1053, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": false, - "BossModNotes": [ - "Doesn't handle death properly" - ] + "SinglePlayerDutyOptions": { + "Enabled": false, + "Notes": [ + "Doesn't handle death properly" + ] + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/Class Quests/WAR/601_And My Axe.json b/QuestPaths/3.x - Heavensward/Class Quests/WAR/601_And My Axe.json index f0598d7b..b4f80942 100644 --- a/QuestPaths/3.x - Heavensward/Class Quests/WAR/601_And My Axe.json +++ b/QuestPaths/3.x - Heavensward/Class Quests/WAR/601_And My Axe.json @@ -96,7 +96,9 @@ "TerritoryId": 138, "InteractionType": "SinglePlayerDuty", "Fly": true, - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1595_A Series of Unfortunate Events.json b/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1595_A Series of Unfortunate Events.json index 10c0755f..a72b2334 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1595_A Series of Unfortunate Events.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1595_A Series of Unfortunate Events.json @@ -59,7 +59,9 @@ }, "TerritoryId": 401, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1597_Divine Intervention.json b/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1597_Divine Intervention.json index e3a1d105..d1e8cd47 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1597_Divine Intervention.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1597_Divine Intervention.json @@ -79,7 +79,9 @@ "[Ishgard] The Forgotten Knight", "[Ishgard] The Tribunal" ], - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A2-Raubahn/1601_Keeping the Flame Alive.json b/QuestPaths/3.x - Heavensward/MSQ/A2-Raubahn/1601_Keeping the Flame Alive.json index 6e052e8b..92b63e22 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A2-Raubahn/1601_Keeping the Flame Alive.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A2-Raubahn/1601_Keeping the Flame Alive.json @@ -29,7 +29,9 @@ }, "TerritoryId": 145, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A3.1-Coerthas Western Highlands 2/1606_Sounding Out the Amphitheatre.json b/QuestPaths/3.x - Heavensward/MSQ/A3.1-Coerthas Western Highlands 2/1606_Sounding Out the Amphitheatre.json index 3503cfee..c7fd5ede 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A3.1-Coerthas Western Highlands 2/1606_Sounding Out the Amphitheatre.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A3.1-Coerthas Western Highlands 2/1606_Sounding Out the Amphitheatre.json @@ -79,7 +79,9 @@ "TerritoryId": 397, "InteractionType": "SinglePlayerDuty", "DisableNavmesh": true, - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A4-Ishgard/1639_Fire and Blood.json b/QuestPaths/3.x - Heavensward/MSQ/A4-Ishgard/1639_Fire and Blood.json index eb9876a6..15f79ef8 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A4-Ishgard/1639_Fire and Blood.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A4-Ishgard/1639_Fire and Blood.json @@ -75,7 +75,9 @@ }, "TerritoryId": 418, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A5-Sea of Clouds/1644_Familiar Faces.json b/QuestPaths/3.x - Heavensward/MSQ/A5-Sea of Clouds/1644_Familiar Faces.json index b156029f..30809e01 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A5-Sea of Clouds/1644_Familiar Faces.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A5-Sea of Clouds/1644_Familiar Faces.json @@ -57,7 +57,9 @@ "InteractionType": "SinglePlayerDuty", "Emote": "lookout", "StopDistance": 0.25, - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1657_An Illuminati Incident.json b/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1657_An Illuminati Incident.json index 31218140..0b5e5d49 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1657_An Illuminati Incident.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1657_An Illuminati Incident.json @@ -48,7 +48,9 @@ "[Idyllshire] Aetheryte Plaza", "[Idyllshire] Epilogue Gate (Eastern Hinterlands)" ], - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A7-Azys Lla/1667_Close Encounters of the VIth Kind.json b/QuestPaths/3.x - Heavensward/MSQ/A7-Azys Lla/1667_Close Encounters of the VIth Kind.json index a498f657..18809a5e 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A7-Azys Lla/1667_Close Encounters of the VIth Kind.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A7-Azys Lla/1667_Close Encounters of the VIth Kind.json @@ -69,7 +69,9 @@ }, "TerritoryId": 402, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": true + "SinglePlayerDutyOptions": { + "Enabled": true + } } ] }, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/G-5.1/3682_Vows of Virtue, Deeds of Cruelty.json b/QuestPaths/5.x - Shadowbringers/MSQ/G-5.1/3682_Vows of Virtue, Deeds of Cruelty.json index 51863a11..2465f30c 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/G-5.1/3682_Vows of Virtue, Deeds of Cruelty.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/G-5.1/3682_Vows of Virtue, Deeds of Cruelty.json @@ -85,14 +85,16 @@ "TerritoryId": 351, "InteractionType": "SinglePlayerDuty", "Comment": "Estinien vs. Arch Ultima", - "BossModEnabled": false, - "BossModNotes": [ - "AI doesn't move automatically for the first boss", - "AI doesn't move automatically for the dialogue with gaius on the bridge", - "After walking downstairs automatically, AI tries to run back towards the stairs (ignoring the arena boudnary)", - "After moving from the arena boundary, AI doesn't move into melee range and stops too far away when initially attacking" - ], - "$.1": "This doesn't have a duty confirmation dialog, so we're treating TEXT_LUCKMG110_03682_Q1_100_125 as one" + "SinglePlayerDutyOptions": { + "Enabled": false, + "Notes": [ + "AI doesn't move automatically for the first boss", + "AI doesn't move automatically for the dialogue with gaius on the bridge", + "After walking downstairs automatically, AI tries to run back towards the stairs (ignoring the arena boudnary)", + "After moving from the arena boundary, AI doesn't move into melee range and stops too far away when initially attacking" + ] + }, + "$": "This doesn't have a duty confirmation dialog, so we're treating TEXT_LUCKMG110_03682_Q1_100_125 as one" } ] }, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/H-5.2/3765_A Sleep Disturbed.json b/QuestPaths/5.x - Shadowbringers/MSQ/H-5.2/3765_A Sleep Disturbed.json index 49a09891..4fab7756 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/H-5.2/3765_A Sleep Disturbed.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/H-5.2/3765_A Sleep Disturbed.json @@ -46,10 +46,12 @@ }, "TerritoryId": 817, "InteractionType": "SinglePlayerDuty", - "BossModEnabled": false, - "BossModNotes": [ - "Doesn't walk to the teleporter to finish the duty" - ], + "SinglePlayerDutyOptions": { + "Enabled": false, + "Notes": [ + "Doesn't walk to the teleporter to finish the duty" + ] + }, "Fly": true, "Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)", "$": "The dialogue choices and data ids here are recycled", diff --git a/QuestPaths/5.x - Shadowbringers/Trial Quests/3895_Sleep Now in Sapphire.json b/QuestPaths/5.x - Shadowbringers/Trial Quests/3895_Sleep Now in Sapphire.json index 160a2970..206d7457 100644 --- a/QuestPaths/5.x - Shadowbringers/Trial Quests/3895_Sleep Now in Sapphire.json +++ b/QuestPaths/5.x - Shadowbringers/Trial Quests/3895_Sleep Now in Sapphire.json @@ -104,7 +104,9 @@ "StopDistance": 5, "TerritoryId": 829, "InteractionType": "SinglePlayerDuty", - "SinglePlayerDutyIndex": 1, + "SinglePlayerDutyOptions": { + "Index": 1 + }, "DialogueChoices": [ { "Type": "List", diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index f5df18c8..40204b39 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -1267,20 +1267,32 @@ }, "then": { "properties": { - "BossModEnabled": { - "type": "boolean" - }, - "BossModNotes": { - "type": "array", - "items": { - "type": "string" - } - }, - "SinglePlayerDutyIndex": { - "type": "integer", - "minimum": 0, - "maximum": 1, - "description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is" + "SinglePlayerDutyOptions": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + }, + "Notes": { + "type": "array", + "items": { + "type": "string" + } + }, + "Index": { + "type": "integer", + "minimum": 0, + "maximum": 1, + "description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is" + }, + "$": { + "type": "string" + } + }, + "TODO_required": [ + "Enabled" + ], + "additionalProperties": false } } } diff --git a/Questionable.Model/Questing/QuestStep.cs b/Questionable.Model/Questing/QuestStep.cs index 8d9d31ec..b96a30a2 100644 --- a/Questionable.Model/Questing/QuestStep.cs +++ b/Questionable.Model/Questing/QuestStep.cs @@ -75,9 +75,8 @@ public sealed class QuestStep public JumpDestination? JumpDestination { get; set; } public uint? ContentFinderConditionId { get; set; } public bool AutoDutyEnabled { get; set; } - public bool BossModEnabled { get; set; } - public List BossModNotes { get; set; } = []; - public byte SinglePlayerDutyIndex { get; set; } + public SinglePlayerDutyOptions? SinglePlayerDutyOptions { get; set; } + public byte SinglePlayerDutyIndex => SinglePlayerDutyOptions?.Index ?? 0; public SkipConditions? SkipConditions { get; set; } public List?> RequiredQuestVariables { get; set; } = new(); diff --git a/Questionable.Model/Questing/SinglePlayerDutyOptions.cs b/Questionable.Model/Questing/SinglePlayerDutyOptions.cs new file mode 100644 index 00000000..874fc5c0 --- /dev/null +++ b/Questionable.Model/Questing/SinglePlayerDutyOptions.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Questionable.Model.Questing; + +public sealed class SinglePlayerDutyOptions +{ + public bool Enabled { get; set; } + public List Notes { get; set; } = []; + public byte Index { get; set; } +} diff --git a/Questionable/Controller/GameUi/InteractionUiController.cs b/Questionable/Controller/GameUi/InteractionUiController.cs index 1825b4f7..c4c70b98 100644 --- a/Questionable/Controller/GameUi/InteractionUiController.cs +++ b/Questionable/Controller/GameUi/InteractionUiController.cs @@ -691,7 +691,7 @@ internal sealed class InteractionUiController : IDisposable private bool CheckSinglePlayerDutyYesNo(ElementId questId, QuestStep? step) { if (step is { InteractionType: EInteractionType.SinglePlayerDuty } && - _bossModIpc.IsConfiguredToRunSoloInstance(questId, step.SinglePlayerDutyIndex, step.BossModEnabled)) + _bossModIpc.IsConfiguredToRunSoloInstance(questId, step.SinglePlayerDutyOptions)) { // Most of these are yes/no dialogs "Duty calls, ...". // diff --git a/Questionable/Controller/Steps/Common/SendNotification.cs b/Questionable/Controller/Steps/Common/SendNotification.cs index b2d146f0..97089936 100644 --- a/Questionable/Controller/Steps/Common/SendNotification.cs +++ b/Questionable/Controller/Steps/Common/SendNotification.cs @@ -27,7 +27,7 @@ internal static class SendNotification new Task(step.InteractionType, step.ContentFinderConditionId.HasValue ? territoryData.GetContentFinderCondition(step.ContentFinderConditionId.Value)?.Name : step.Comment), - EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled) => + EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions) => new Task(step.InteractionType, quest.Info.Name), _ => null, }; diff --git a/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs b/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs index b8bf39fe..188729be 100644 --- a/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs +++ b/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs @@ -21,7 +21,7 @@ internal static class SinglePlayerDuty if (step.InteractionType != EInteractionType.SinglePlayerDuty) yield break; - if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled)) + if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions)) { if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex, out var cfcData)) throw new TaskException("Failed to get content finder condition for solo instance"); diff --git a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs index 1476ed3c..86e5d47d 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs @@ -54,7 +54,7 @@ internal static class WaitAtEnd return [new WaitNextStepOrSequence()]; case EInteractionType.Duty when !autoDutyIpc.IsConfiguredToRunContent(step.ContentFinderConditionId, step.AutoDutyEnabled): - case EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled): + case EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions): return [new EndAutomation()]; case EInteractionType.WalkTo: diff --git a/Questionable/External/BossModIpc.cs b/Questionable/External/BossModIpc.cs index e73e1863..d157622e 100644 --- a/Questionable/External/BossModIpc.cs +++ b/Questionable/External/BossModIpc.cs @@ -82,7 +82,7 @@ internal sealed class BossModIpc ClearPreset(); } - public bool IsConfiguredToRunSoloInstance(ElementId questId, byte dutyIndex, bool enabledByDefault) + public bool IsConfiguredToRunSoloInstance(ElementId questId, SinglePlayerDutyOptions? dutyOptions) { if (!IsSupported()) return false; @@ -90,7 +90,8 @@ internal sealed class BossModIpc if (!_configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod) return false; - if (!_territoryData.TryGetContentFinderConditionForSoloInstance(questId, dutyIndex, out var cfcData)) + dutyOptions ??= new(); + if (!_territoryData.TryGetContentFinderConditionForSoloInstance(questId, dutyOptions.Index, out var cfcData)) return false; if (_configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId)) @@ -99,6 +100,6 @@ internal sealed class BossModIpc if (_configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId)) return true; - return enabledByDefault; + return dutyOptions.Enabled; } } diff --git a/Questionable/Windows/ConfigComponents/SinglePlayerDutyConfigComponent.cs b/Questionable/Windows/ConfigComponents/SinglePlayerDutyConfigComponent.cs index 07149deb..c49a2c81 100644 --- a/Questionable/Windows/ConfigComponents/SinglePlayerDutyConfigComponent.cs +++ b/Questionable/Windows/ConfigComponents/SinglePlayerDutyConfigComponent.cs @@ -122,70 +122,24 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent foreach (var (questId, index, cfcData) in _territoryData.GetAllQuestsWithQuestBattles()) { IQuestInfo questInfo = _questData.GetQuestInfo(questId); - QuestStep questStep = new QuestStep - { - SinglePlayerDutyIndex = 0, - BossModEnabled = false, - }; - bool enabled; - if (_questRegistry.TryGetQuest(questId, out var quest)) - { - if (quest.Root.Disabled) - { - _logger.LogDebug("Disabling quest battle for quest {QuestId}, quest is disabled", questId); - enabled = false; - } - else - { - var foundStep = quest.AllSteps().FirstOrDefault(x => - x.Step.InteractionType == EInteractionType.SinglePlayerDuty && - x.Step.SinglePlayerDutyIndex == index); - if (foundStep == default) - { - _logger.LogWarning( - "Disabling quest battle for quest {QuestId}, no battle with index {Index} found", questId, - index); - enabled = false; - } - else - { - questStep = foundStep.Step; - enabled = true; - } - } - } - else - { - _logger.LogDebug("Disabling quest battle for quest {QuestId}, unknown quest", questId); - enabled = false; - } + (bool enabled, SinglePlayerDutyOptions options) = FindDutyOptions(questId, index); string name = $"{FormatLevel(questInfo.Level)} {questInfo.Name}"; if (!string.IsNullOrEmpty(cfcData.Name) && !questInfo.Name.EndsWith(cfcData.Name, StringComparison.Ordinal)) name += $" ({cfcData.Name})"; if (questsWithMultipleBattles.Contains(questId)) - name += $" (Part {questStep.SinglePlayerDutyIndex + 1})"; + name += $" (Part {options.Index + 1})"; else if (cfcData.ContentFinderConditionId is 674 or 691) name += " (Melee/Phys. Ranged)"; - var dutyInfo = new SinglePlayerDutyInfo( - cfcData.ContentFinderConditionId, - cfcData.TerritoryId, - name, - questInfo.Expansion, - questInfo.JournalGenre ?? uint.MaxValue, - questInfo.SortKey, - questStep.SinglePlayerDutyIndex, - enabled, - questStep.BossModEnabled, - questStep.BossModNotes); + var dutyInfo = new SinglePlayerDutyInfo(name, questInfo, cfcData, options, enabled); - if (cfcData.ContentFinderConditionId is 332 or 333 or 313 or 334) + if (dutyInfo.IsLimsaStart) startingCityBattles[EAetheryteLocation.Limsa].Add(dutyInfo); - else if (cfcData.ContentFinderConditionId is 296 or 297 or 299 or 298) + else if (dutyInfo.IsGridaniaStart) startingCityBattles[EAetheryteLocation.Gridania].Add(dutyInfo); - else if (cfcData.ContentFinderConditionId is 335 or 312 or 337 or 336) + else if (dutyInfo.IsUldahStart) startingCityBattles[EAetheryteLocation.Uldah].Add(dutyInfo); else if (questInfo.IsMainScenarioQuest) mainScenarioBattles.Add(dutyInfo); @@ -196,7 +150,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent foreach (var roleClassJob in classJobs) roleQuestBattles[roleClassJob].Add(dutyInfo); } - else if (dutyInfo.CfcId is 845 or 1016) + else if (dutyInfo.IsOtherRoleQuest) otherRoleQuestBattles.Add(dutyInfo); else otherBattles.Add(dutyInfo); @@ -220,7 +174,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent x => x.Value // level 10 quests use the same quest battle for [you started as this class] and [you picked this class up later] - .DistinctBy(y => y.CfcId) + .DistinctBy(y => y.ContentFinderConditionId) .OrderBy(y => y.JournalGenreId) .ThenBy(y => y.SortKey) .ThenBy(y => y.Index) @@ -242,6 +196,47 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent .ToImmutableList(); } + private (bool Enabled, SinglePlayerDutyOptions Options) FindDutyOptions(ElementId questId, byte index) + { + SinglePlayerDutyOptions options = new() + { + Index = 0, + Enabled = false, + }; + if (_questRegistry.TryGetQuest(questId, out var quest)) + { + if (quest.Root.Disabled) + { + _logger.LogDebug("Disabling quest battle for quest {QuestId}, quest is disabled", questId); + return (false, options); + } + else + { + var foundStep = quest.AllSteps() + .Select(x => x.Step) + .FirstOrDefault(x => + x.InteractionType == EInteractionType.SinglePlayerDuty && + x.SinglePlayerDutyIndex == index); + if (foundStep == null) + { + _logger.LogWarning( + "Disabling quest battle for quest {QuestId}, no battle with index {Index} found", questId, + index); + return (false, options); + } + else + { + return (true, foundStep.SinglePlayerDutyOptions ?? options); + } + } + } + else + { + _logger.LogDebug("Disabling quest battle for quest {QuestId}, unknown quest", questId); + return (false, options); + } + } + private string BuildJournalGenreLabel(uint journalGenreId) { var journalGenre = _dataManager.GetExcelSheet().GetRow(journalGenreId); @@ -250,7 +245,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent string genreName = journalGenre.Name.ExtractText(); string categoryName = journalCategory.Name.ExtractText(); - return $"{categoryName} {SeIconChar.ArrowRight.ToIconString()} {genreName}"; + return $"{categoryName} \u203B {genreName}"; } public override void DrawTab() @@ -423,13 +418,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent { ImGui.TableNextRow(); - string[] labels = dutyInfo.BossModEnabledByDefault + string[] labels = dutyInfo.EnabledByDefault ? SupportedCfcOptions : UnsupportedCfcOptions; int value = 0; - if (Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(dutyInfo.CfcId)) + if (Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(dutyInfo.ContentFinderConditionId)) value = 1; - if (Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(dutyInfo.CfcId)) + if (Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(dutyInfo.ContentFinderConditionId)) value = 2; if (ImGui.TableNextColumn()) @@ -445,7 +440,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent ImGui.TextUnformatted(dutyInfo.Name); ImGui.Separator(); ImGui.BulletText($"TerritoryId: {dutyInfo.TerritoryId}"); - ImGui.BulletText($"ContentFinderConditionId: {dutyInfo.CfcId}"); + ImGui.BulletText($"ContentFinderConditionId: {dutyInfo.ContentFinderConditionId}"); } } @@ -457,11 +452,18 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent else if (dutyInfo.Notes.Count > 0) { using var color = new ImRaii.Color(); - color.Push(ImGuiCol.TextDisabled, ImGuiColors.DalamudYellow); + if (!dutyInfo.EnabledByDefault) + color.Push(ImGuiCol.TextDisabled, ImGuiColors.DalamudYellow); + else + color.Push(ImGuiCol.TextDisabled, ImGuiColors.ParsedBlue); + ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.IconFont)) { - ImGui.TextDisabled(FontAwesomeIcon.ExclamationTriangle.ToIconString()); + if (!dutyInfo.EnabledByDefault) + ImGui.TextDisabled(FontAwesomeIcon.ExclamationTriangle.ToIconString()); + else + ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); } if (ImGui.IsItemHovered()) @@ -478,19 +480,19 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent if (ImGui.TableNextColumn()) { - using var _ = ImRaii.PushId($"##Duty{dutyInfo.CfcId}"); + using var _ = ImRaii.PushId($"##Duty{dutyInfo.ContentFinderConditionId}"); using (ImRaii.Disabled(!dutyInfo.Enabled)) { ImGui.SetNextItemWidth(200); if (ImGui.Combo(string.Empty, ref value, labels, labels.Length)) { - Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Remove(dutyInfo.CfcId); - Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Remove(dutyInfo.CfcId); + Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Remove(dutyInfo.ContentFinderConditionId); + Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Remove(dutyInfo.ContentFinderConditionId); if (value == 1) - Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Add(dutyInfo.CfcId); + Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Add(dutyInfo.ContentFinderConditionId); else if (value == 2) - Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Add(dutyInfo.CfcId); + Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Add(dutyInfo.ContentFinderConditionId); Save(); } @@ -519,14 +521,28 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent } private sealed record SinglePlayerDutyInfo( - uint CfcId, - uint TerritoryId, string Name, - EExpansionVersion Expansion, - uint JournalGenreId, - ushort SortKey, - byte Index, - bool Enabled, - bool BossModEnabledByDefault, - List Notes); + IQuestInfo QuestInfo, + TerritoryData.ContentFinderConditionData ContentFinderConditionData, + SinglePlayerDutyOptions Options, + bool Enabled) + { + public EExpansionVersion Expansion => QuestInfo.Expansion; + public uint JournalGenreId => QuestInfo.JournalGenre ?? uint.MaxValue; + public ushort SortKey => QuestInfo.SortKey; + public uint ContentFinderConditionId => ContentFinderConditionData.ContentFinderConditionId; + public uint TerritoryId => ContentFinderConditionData.TerritoryId; + public byte Index => Options.Index; + public bool EnabledByDefault => Options.Enabled; + public IReadOnlyList Notes => Options.Notes; + + public bool IsLimsaStart => ContentFinderConditionId is 332 or 333 or 313 or 334; + public bool IsGridaniaStart => ContentFinderConditionId is 296 or 297 or 299 or 298; + public bool IsUldahStart => ContentFinderConditionId is 335 or 312 or 337 or 336; + + /// + /// 'Other' role quest is the post-EW/DT role quests. + /// + public bool IsOtherRoleQuest => ContentFinderConditionId is 845 or 1016; + } }