Configure a few early MSQ battles

This commit is contained in:
Liza 2025-02-22 00:03:06 +01:00
parent 71e0b01dbc
commit a70e195a93
Signed by: liza
GPG Key ID: 2C41B84815CF6445
58 changed files with 462 additions and 166 deletions

View File

@ -108,7 +108,7 @@ internal static class QuestStepExtensions
AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData) AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.CombatItemUse), step.CombatItemUse, Assignment(nameof(QuestStep.CombatItemUse), step.CombatItemUse,
emptyStep.CombatItemUse) emptyStep.CombatItemUse)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.CombatDelaySecondsAtStart), Assignment(nameof(QuestStep.CombatDelaySecondsAtStart),
step.CombatDelaySecondsAtStart, step.CombatDelaySecondsAtStart,
@ -123,14 +123,8 @@ internal static class QuestStepExtensions
Assignment(nameof(QuestStep.AutoDutyEnabled), Assignment(nameof(QuestStep.AutoDutyEnabled),
step.AutoDutyEnabled, emptyStep.AutoDutyEnabled) step.AutoDutyEnabled, emptyStep.AutoDutyEnabled)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.BossModEnabled), Assignment(nameof(QuestStep.SinglePlayerDutyOptions), step.SinglePlayerDutyOptions,
step.BossModEnabled, emptyStep.BossModEnabled) emptyStep.SinglePlayerDutyOptions)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.BossModNotes),
step.BossModNotes, emptyStep.BossModNotes)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.SinglePlayerDutyIndex),
step.SinglePlayerDutyIndex, emptyStep.SinglePlayerDutyIndex)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions, Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions,
emptyStep.SkipConditions) emptyStep.SkipConditions)

View File

@ -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<ExpressionSyntax>(
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()))));
}
}

View File

@ -62,6 +62,7 @@ public static class RoslynShortcuts
ComplexCombatData complexCombatData => complexCombatData.ToExpressionSyntax(), ComplexCombatData complexCombatData => complexCombatData.ToExpressionSyntax(),
QuestWorkValue questWorkValue => questWorkValue.ToExpressionSyntax(), QuestWorkValue questWorkValue => questWorkValue.ToExpressionSyntax(),
List<QuestWorkValue> list => list.ToExpressionSyntax(), // TODO fix in AssignmentList List<QuestWorkValue> list => list.ToExpressionSyntax(), // TODO fix in AssignmentList
SinglePlayerDutyOptions dutyOptions => dutyOptions.ToExpressionSyntax(),
SkipConditions skipConditions => skipConditions.ToExpressionSyntax(), SkipConditions skipConditions => skipConditions.ToExpressionSyntax(),
SkipStepConditions skipStepConditions => skipStepConditions.ToExpressionSyntax(), SkipStepConditions skipStepConditions => skipStepConditions.ToExpressionSyntax(),
SkipItemConditions skipItemCondition => skipItemCondition.ToExpressionSyntax(), SkipItemConditions skipItemCondition => skipItemCondition.ToExpressionSyntax(),

View File

@ -57,7 +57,9 @@
}, },
"TerritoryId": 153, "TerritoryId": 153,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyIndex": 1, "SinglePlayerDutyOptions": {
"Index": 1
},
"Fly": true "Fly": true
} }
] ]

View File

@ -62,7 +62,9 @@
}, },
"TerritoryId": 154, "TerritoryId": 154,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyIndex": 1, "SinglePlayerDutyOptions": {
"Index": 1
},
"AetheryteShortcut": "North Shroud - Fallgourd Float", "AetheryteShortcut": "North Shroud - Fallgourd Float",
"Fly": true "Fly": true
} }

View File

@ -120,7 +120,9 @@
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyIndex": 1 "SinglePlayerDutyOptions": {
"Index": 1
}
} }
] ]
}, },

View File

@ -140,6 +140,9 @@
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"Fly": true "Fly": true
} }
] ]

View File

@ -92,7 +92,9 @@
}, },
"TerritoryId": 130, "TerritoryId": 130,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyIndex": 1, "SinglePlayerDutyOptions": {
"Index": 1
},
"AetheryteShortcut": "Ul'dah" "AetheryteShortcut": "Ul'dah"
} }
] ]

View File

@ -35,6 +35,9 @@
}, },
"TerritoryId": 137, "TerritoryId": 137,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"AetheryteShortcut": "Eastern La Noscea - Wineport", "AetheryteShortcut": "Eastern La Noscea - Wineport",
"Fly": true "Fly": true
} }

View File

@ -116,6 +116,9 @@
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"Fly": true "Fly": true
} }
] ]

View File

@ -65,7 +65,8 @@
"AetheryteShortcut": "East Shroud - Hawthorne Hut", "AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": { "SkipConditions": {
"AetheryteShortcutIf": { "AetheryteShortcutIf": {
"InSameTerritory": true "InSameTerritory": true,
"AetheryteLocked": "East Shroud - Hawthorne Hut"
} }
} }
} }
@ -116,7 +117,10 @@
"Z": 35.568726 "Z": 35.568726
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -33,6 +33,39 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "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": { "Position": {
"X": -276.804, "X": -276.804,
@ -42,7 +75,12 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "WalkTo", "InteractionType": "WalkTo",
"AetheryteShortcut": "East Shroud - Hawthorne Hut", "AetheryteShortcut": "East Shroud - Hawthorne Hut",
"Fly": true "Fly": true,
"SkipConditions": {
"AetheryteShortcutIf": {
"AetheryteLocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"DataId": 2000889, "DataId": 2000889,
@ -212,6 +250,9 @@
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"Fly": true "Fly": true
} }
] ]

View File

@ -138,7 +138,10 @@
"Z": 192.2179 "Z": 192.2179
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -111,7 +111,13 @@
"Z": 295.52136 "Z": 295.52136
}, },
"TerritoryId": 148, "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"
]
}
} }
] ]
}, },

View File

@ -29,10 +29,12 @@
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": false, "SinglePlayerDutyOptions": {
"BossModNotes": [ "Enabled": false,
"AI doesn't automatically target newly spawning adds and dies until after the boss died (tested on CNJ)" "Notes": [
] "AI doesn't automatically target newly spawning adds until after the boss died, and dies (tested on CNJ)"
]
}
} }
] ]
}, },

View File

@ -77,6 +77,12 @@
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty", "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" "AetheryteShortcut": "Central Shroud - Bentbranch Meadows"
} }
] ]

View File

@ -69,6 +69,12 @@
}, },
"TerritoryId": 135, "TerritoryId": 135,
"InteractionType": "SinglePlayerDuty", "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" "AetheryteShortcut": "Lower La Noscea - Moraby Drydocks"
} }
] ]

View File

@ -45,8 +45,11 @@
"TerritoryId": 134, "TerritoryId": 134,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea", "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "ComplexCombatData": [
52 {
"DataId": 52,
"IgnoreQuestMarker": true
}
] ]
}, },
{ {

View File

@ -73,7 +73,10 @@
"Z": -432.15082 "Z": -432.15082
}, },
"TerritoryId": 134, "TerritoryId": 134,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -28,7 +28,13 @@
"Z": -141.7716 "Z": -141.7716
}, },
"TerritoryId": 134, "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)"
]
}
} }
] ]
}, },

View File

@ -58,6 +58,12 @@
}, },
"TerritoryId": 138, "TerritoryId": 138,
"InteractionType": "SinglePlayerDuty", "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" "AetheryteShortcut": "Western La Noscea - Swiftperch"
} }
] ]

View File

@ -44,7 +44,10 @@
"Z": -242.51166 "Z": -242.51166
}, },
"TerritoryId": 145, "TerritoryId": 145,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -79,6 +79,9 @@
}, },
"TerritoryId": 130, "TerritoryId": 130,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"AetheryteShortcut": "Ul'dah", "AetheryteShortcut": "Ul'dah",
"AethernetShortcut": [ "AethernetShortcut": [
"[Ul'dah] Aetheryte Plaza", "[Ul'dah] Aetheryte Plaza",
@ -87,6 +90,9 @@
} }
] ]
}, },
{
"Sequence": 5
},
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [

View File

@ -63,12 +63,22 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Gridania] Aetheryte Plaza", "[Gridania] Aetheryte Plaza",
"[Gridania] Lancers' Guild" "[Gridania] Lancers' Guild"
] ],
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "AttuneAetheryte", "InteractionType": "AttuneAetheryte",
"Aetheryte": "East Shroud - Hawthorne Hut" "Aetheryte": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"DataId": 1004886, "DataId": 1004886,
@ -78,7 +88,16 @@
"Z": 475.30322 "Z": 475.30322
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
} }
] ]
}, },

View File

@ -64,6 +64,9 @@
}, },
"TerritoryId": 135, "TerritoryId": 135,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"AethernetShortcut": [ "AethernetShortcut": [
"[Limsa Lominsa] The Aftcastle", "[Limsa Lominsa] The Aftcastle",
"[Limsa Lominsa] Tempest Gate (Lower La Noscea)" "[Limsa Lominsa] Tempest Gate (Lower La Noscea)"

View File

@ -59,6 +59,9 @@
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
},
"AetheryteShortcut": "Western Thanalan - Horizon" "AetheryteShortcut": "Western Thanalan - Horizon"
} }
] ]

View File

@ -46,7 +46,8 @@
}, },
"StopDistance": 7, "StopDistance": 7,
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "Interact" "InteractionType": "Interact",
"DelaySecondsAtStart": 2
} }
] ]
}, },

View File

@ -158,7 +158,10 @@
"Z": 117.29602 "Z": 117.29602
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -21,6 +21,15 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"Position": {
"X": -174.73444,
"Y": 15.450659,
"Z": -266.76144
},
"TerritoryId": 140,
"InteractionType": "WalkTo"
},
{ {
"Position": { "Position": {
"X": -289.1099, "X": -289.1099,

View File

@ -37,7 +37,10 @@
"Z": -293.1411 "Z": -293.1411
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -29,7 +29,7 @@
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "OverworldEnemies", "EnemySpawnType": "FinishCombatIfAny",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
352, 352,
353 353
@ -53,6 +53,25 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "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, "DataId": 1001605,
"Position": { "Position": {

View File

@ -28,7 +28,10 @@
"Z": 536.88855 "Z": 536.88855
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -64,7 +64,13 @@
"Z": -131.48706 "Z": -131.48706
}, },
"TerritoryId": 141, "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" "AetheryteShortcut": "Central Thanalan - Black Brush Station"
} }
] ]

View File

@ -73,13 +73,23 @@
}, },
"TerritoryId": 133, "TerritoryId": 133,
"InteractionType": "Interact", "InteractionType": "Interact",
"TargetTerritoryId": 152 "TargetTerritoryId": 152,
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "AttuneAetheryte", "InteractionType": "AttuneAetheryte",
"Aetheryte": "East Shroud - Hawthorne Hut", "Aetheryte": "East Shroud - Hawthorne Hut",
"StopDistance": 5 "StopDistance": 5,
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"DataId": 1006188, "DataId": 1006188,
@ -89,7 +99,13 @@
"Z": 283.4973 "Z": 283.4973
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
} }
] ]
} }

View File

@ -64,7 +64,10 @@
"Z": -39.383606 "Z": -39.383606
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -83,7 +83,13 @@
"Z": -12.985474 "Z": -12.985474
}, },
"TerritoryId": 153, "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"
]
}
} }
] ]
}, },

View File

@ -104,10 +104,12 @@
}, },
"TerritoryId": 1053, "TerritoryId": 1053,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": false, "SinglePlayerDutyOptions": {
"BossModNotes": [ "Enabled": false,
"Doesn't handle death properly" "Notes": [
] "Doesn't handle death properly"
]
}
} }
] ]
}, },

View File

@ -96,7 +96,9 @@
"TerritoryId": 138, "TerritoryId": 138,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Fly": true, "Fly": true,
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -59,7 +59,9 @@
}, },
"TerritoryId": 401, "TerritoryId": 401,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -79,7 +79,9 @@
"[Ishgard] The Forgotten Knight", "[Ishgard] The Forgotten Knight",
"[Ishgard] The Tribunal" "[Ishgard] The Tribunal"
], ],
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -29,7 +29,9 @@
}, },
"TerritoryId": 145, "TerritoryId": 145,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -79,7 +79,9 @@
"TerritoryId": 397, "TerritoryId": 397,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"DisableNavmesh": true, "DisableNavmesh": true,
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -75,7 +75,9 @@
}, },
"TerritoryId": 418, "TerritoryId": 418,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -57,7 +57,9 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Emote": "lookout", "Emote": "lookout",
"StopDistance": 0.25, "StopDistance": 0.25,
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -48,7 +48,9 @@
"[Idyllshire] Aetheryte Plaza", "[Idyllshire] Aetheryte Plaza",
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)" "[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
], ],
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -69,7 +69,9 @@
}, },
"TerritoryId": 402, "TerritoryId": 402,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": true "SinglePlayerDutyOptions": {
"Enabled": true
}
} }
] ]
}, },

View File

@ -85,14 +85,16 @@
"TerritoryId": 351, "TerritoryId": 351,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Comment": "Estinien vs. Arch Ultima", "Comment": "Estinien vs. Arch Ultima",
"BossModEnabled": false, "SinglePlayerDutyOptions": {
"BossModNotes": [ "Enabled": false,
"AI doesn't move automatically for the first boss", "Notes": [
"AI doesn't move automatically for the dialogue with gaius on the bridge", "AI doesn't move automatically for the first boss",
"After walking downstairs automatically, AI tries to run back towards the stairs (ignoring the arena boudnary)", "AI doesn't move automatically for the dialogue with gaius on the bridge",
"After moving from the arena boundary, AI doesn't move into melee range and stops too far away when initially attacking" "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" ]
},
"$": "This doesn't have a duty confirmation dialog, so we're treating TEXT_LUCKMG110_03682_Q1_100_125 as one"
} }
] ]
}, },

View File

@ -46,10 +46,12 @@
}, },
"TerritoryId": 817, "TerritoryId": 817,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"BossModEnabled": false, "SinglePlayerDutyOptions": {
"BossModNotes": [ "Enabled": false,
"Doesn't walk to the teleporter to finish the duty" "Notes": [
], "Doesn't walk to the teleporter to finish the duty"
]
},
"Fly": true, "Fly": true,
"Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)", "Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)",
"$": "The dialogue choices and data ids here are recycled", "$": "The dialogue choices and data ids here are recycled",

View File

@ -104,7 +104,9 @@
"StopDistance": 5, "StopDistance": 5,
"TerritoryId": 829, "TerritoryId": 829,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyIndex": 1, "SinglePlayerDutyOptions": {
"Index": 1
},
"DialogueChoices": [ "DialogueChoices": [
{ {
"Type": "List", "Type": "List",

View File

@ -1267,20 +1267,32 @@
}, },
"then": { "then": {
"properties": { "properties": {
"BossModEnabled": { "SinglePlayerDutyOptions": {
"type": "boolean" "type": "object",
}, "properties": {
"BossModNotes": { "Enabled": {
"type": "array", "type": "boolean"
"items": { },
"type": "string" "Notes": {
} "type": "array",
}, "items": {
"SinglePlayerDutyIndex": { "type": "string"
"type": "integer", }
"minimum": 0, },
"maximum": 1, "Index": {
"description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is" "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
} }
} }
} }

View File

@ -75,9 +75,8 @@ public sealed class QuestStep
public JumpDestination? JumpDestination { get; set; } public JumpDestination? JumpDestination { get; set; }
public uint? ContentFinderConditionId { get; set; } public uint? ContentFinderConditionId { get; set; }
public bool AutoDutyEnabled { get; set; } public bool AutoDutyEnabled { get; set; }
public bool BossModEnabled { get; set; } public SinglePlayerDutyOptions? SinglePlayerDutyOptions { get; set; }
public List<string> BossModNotes { get; set; } = []; public byte SinglePlayerDutyIndex => SinglePlayerDutyOptions?.Index ?? 0;
public byte SinglePlayerDutyIndex { get; set; }
public SkipConditions? SkipConditions { get; set; } public SkipConditions? SkipConditions { get; set; }
public List<List<QuestWorkValue>?> RequiredQuestVariables { get; set; } = new(); public List<List<QuestWorkValue>?> RequiredQuestVariables { get; set; } = new();

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Questionable.Model.Questing;
public sealed class SinglePlayerDutyOptions
{
public bool Enabled { get; set; }
public List<string> Notes { get; set; } = [];
public byte Index { get; set; }
}

View File

@ -691,7 +691,7 @@ internal sealed class InteractionUiController : IDisposable
private bool CheckSinglePlayerDutyYesNo(ElementId questId, QuestStep? step) private bool CheckSinglePlayerDutyYesNo(ElementId questId, QuestStep? step)
{ {
if (step is { InteractionType: EInteractionType.SinglePlayerDuty } && 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, ...". // Most of these are yes/no dialogs "Duty calls, ...".
// //

View File

@ -27,7 +27,7 @@ internal static class SendNotification
new Task(step.InteractionType, step.ContentFinderConditionId.HasValue new Task(step.InteractionType, step.ContentFinderConditionId.HasValue
? territoryData.GetContentFinderCondition(step.ContentFinderConditionId.Value)?.Name ? territoryData.GetContentFinderCondition(step.ContentFinderConditionId.Value)?.Name
: step.Comment), : 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), new Task(step.InteractionType, quest.Info.Name),
_ => null, _ => null,
}; };

View File

@ -21,7 +21,7 @@ internal static class SinglePlayerDuty
if (step.InteractionType != EInteractionType.SinglePlayerDuty) if (step.InteractionType != EInteractionType.SinglePlayerDuty)
yield break; 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)) if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex, out var cfcData))
throw new TaskException("Failed to get content finder condition for solo instance"); throw new TaskException("Failed to get content finder condition for solo instance");

View File

@ -54,7 +54,7 @@ internal static class WaitAtEnd
return [new WaitNextStepOrSequence()]; return [new WaitNextStepOrSequence()];
case EInteractionType.Duty when !autoDutyIpc.IsConfiguredToRunContent(step.ContentFinderConditionId, step.AutoDutyEnabled): 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()]; return [new EndAutomation()];
case EInteractionType.WalkTo: case EInteractionType.WalkTo:

View File

@ -82,7 +82,7 @@ internal sealed class BossModIpc
ClearPreset(); ClearPreset();
} }
public bool IsConfiguredToRunSoloInstance(ElementId questId, byte dutyIndex, bool enabledByDefault) public bool IsConfiguredToRunSoloInstance(ElementId questId, SinglePlayerDutyOptions? dutyOptions)
{ {
if (!IsSupported()) if (!IsSupported())
return false; return false;
@ -90,7 +90,8 @@ internal sealed class BossModIpc
if (!_configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod) if (!_configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod)
return false; return false;
if (!_territoryData.TryGetContentFinderConditionForSoloInstance(questId, dutyIndex, out var cfcData)) dutyOptions ??= new();
if (!_territoryData.TryGetContentFinderConditionForSoloInstance(questId, dutyOptions.Index, out var cfcData))
return false; return false;
if (_configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId)) if (_configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
@ -99,6 +100,6 @@ internal sealed class BossModIpc
if (_configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId)) if (_configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
return true; return true;
return enabledByDefault; return dutyOptions.Enabled;
} }
} }

View File

@ -122,70 +122,24 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
foreach (var (questId, index, cfcData) in _territoryData.GetAllQuestsWithQuestBattles()) foreach (var (questId, index, cfcData) in _territoryData.GetAllQuestsWithQuestBattles())
{ {
IQuestInfo questInfo = _questData.GetQuestInfo(questId); IQuestInfo questInfo = _questData.GetQuestInfo(questId);
QuestStep questStep = new QuestStep (bool enabled, SinglePlayerDutyOptions options) = FindDutyOptions(questId, index);
{
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;
}
string name = $"{FormatLevel(questInfo.Level)} {questInfo.Name}"; string name = $"{FormatLevel(questInfo.Level)} {questInfo.Name}";
if (!string.IsNullOrEmpty(cfcData.Name) && !questInfo.Name.EndsWith(cfcData.Name, StringComparison.Ordinal)) if (!string.IsNullOrEmpty(cfcData.Name) && !questInfo.Name.EndsWith(cfcData.Name, StringComparison.Ordinal))
name += $" ({cfcData.Name})"; name += $" ({cfcData.Name})";
if (questsWithMultipleBattles.Contains(questId)) if (questsWithMultipleBattles.Contains(questId))
name += $" (Part {questStep.SinglePlayerDutyIndex + 1})"; name += $" (Part {options.Index + 1})";
else if (cfcData.ContentFinderConditionId is 674 or 691) else if (cfcData.ContentFinderConditionId is 674 or 691)
name += " (Melee/Phys. Ranged)"; name += " (Melee/Phys. Ranged)";
var dutyInfo = new SinglePlayerDutyInfo( var dutyInfo = new SinglePlayerDutyInfo(name, questInfo, cfcData, options, enabled);
cfcData.ContentFinderConditionId,
cfcData.TerritoryId,
name,
questInfo.Expansion,
questInfo.JournalGenre ?? uint.MaxValue,
questInfo.SortKey,
questStep.SinglePlayerDutyIndex,
enabled,
questStep.BossModEnabled,
questStep.BossModNotes);
if (cfcData.ContentFinderConditionId is 332 or 333 or 313 or 334) if (dutyInfo.IsLimsaStart)
startingCityBattles[EAetheryteLocation.Limsa].Add(dutyInfo); 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); 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); startingCityBattles[EAetheryteLocation.Uldah].Add(dutyInfo);
else if (questInfo.IsMainScenarioQuest) else if (questInfo.IsMainScenarioQuest)
mainScenarioBattles.Add(dutyInfo); mainScenarioBattles.Add(dutyInfo);
@ -196,7 +150,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
foreach (var roleClassJob in classJobs) foreach (var roleClassJob in classJobs)
roleQuestBattles[roleClassJob].Add(dutyInfo); roleQuestBattles[roleClassJob].Add(dutyInfo);
} }
else if (dutyInfo.CfcId is 845 or 1016) else if (dutyInfo.IsOtherRoleQuest)
otherRoleQuestBattles.Add(dutyInfo); otherRoleQuestBattles.Add(dutyInfo);
else else
otherBattles.Add(dutyInfo); otherBattles.Add(dutyInfo);
@ -220,7 +174,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
x => x =>
x.Value x.Value
// level 10 quests use the same quest battle for [you started as this class] and [you picked this class up later] // 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) .OrderBy(y => y.JournalGenreId)
.ThenBy(y => y.SortKey) .ThenBy(y => y.SortKey)
.ThenBy(y => y.Index) .ThenBy(y => y.Index)
@ -242,6 +196,47 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
.ToImmutableList(); .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) private string BuildJournalGenreLabel(uint journalGenreId)
{ {
var journalGenre = _dataManager.GetExcelSheet<JournalGenre>().GetRow(journalGenreId); var journalGenre = _dataManager.GetExcelSheet<JournalGenre>().GetRow(journalGenreId);
@ -250,7 +245,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
string genreName = journalGenre.Name.ExtractText(); string genreName = journalGenre.Name.ExtractText();
string categoryName = journalCategory.Name.ExtractText(); string categoryName = journalCategory.Name.ExtractText();
return $"{categoryName} {SeIconChar.ArrowRight.ToIconString()} {genreName}"; return $"{categoryName} \u203B {genreName}";
} }
public override void DrawTab() public override void DrawTab()
@ -423,13 +418,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
{ {
ImGui.TableNextRow(); ImGui.TableNextRow();
string[] labels = dutyInfo.BossModEnabledByDefault string[] labels = dutyInfo.EnabledByDefault
? SupportedCfcOptions ? SupportedCfcOptions
: UnsupportedCfcOptions; : UnsupportedCfcOptions;
int value = 0; int value = 0;
if (Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(dutyInfo.CfcId)) if (Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(dutyInfo.ContentFinderConditionId))
value = 1; value = 1;
if (Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(dutyInfo.CfcId)) if (Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(dutyInfo.ContentFinderConditionId))
value = 2; value = 2;
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
@ -445,7 +440,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
ImGui.TextUnformatted(dutyInfo.Name); ImGui.TextUnformatted(dutyInfo.Name);
ImGui.Separator(); ImGui.Separator();
ImGui.BulletText($"TerritoryId: {dutyInfo.TerritoryId}"); 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) else if (dutyInfo.Notes.Count > 0)
{ {
using var color = new ImRaii.Color(); 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(); ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont)) 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()) if (ImGui.IsItemHovered())
@ -478,19 +480,19 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
{ {
using var _ = ImRaii.PushId($"##Duty{dutyInfo.CfcId}"); using var _ = ImRaii.PushId($"##Duty{dutyInfo.ContentFinderConditionId}");
using (ImRaii.Disabled(!dutyInfo.Enabled)) using (ImRaii.Disabled(!dutyInfo.Enabled))
{ {
ImGui.SetNextItemWidth(200); ImGui.SetNextItemWidth(200);
if (ImGui.Combo(string.Empty, ref value, labels, labels.Length)) if (ImGui.Combo(string.Empty, ref value, labels, labels.Length))
{ {
Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Remove(dutyInfo.CfcId); Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Remove(dutyInfo.ContentFinderConditionId);
Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Remove(dutyInfo.CfcId); Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Remove(dutyInfo.ContentFinderConditionId);
if (value == 1) if (value == 1)
Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Add(dutyInfo.CfcId); Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Add(dutyInfo.ContentFinderConditionId);
else if (value == 2) else if (value == 2)
Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Add(dutyInfo.CfcId); Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Add(dutyInfo.ContentFinderConditionId);
Save(); Save();
} }
@ -519,14 +521,28 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
} }
private sealed record SinglePlayerDutyInfo( private sealed record SinglePlayerDutyInfo(
uint CfcId,
uint TerritoryId,
string Name, string Name,
EExpansionVersion Expansion, IQuestInfo QuestInfo,
uint JournalGenreId, TerritoryData.ContentFinderConditionData ContentFinderConditionData,
ushort SortKey, SinglePlayerDutyOptions Options,
byte Index, bool Enabled)
bool Enabled, {
bool BossModEnabledByDefault, public EExpansionVersion Expansion => QuestInfo.Expansion;
List<string> Notes); 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<string> 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;
/// <summary>
/// 'Other' role quest is the post-EW/DT role quests.
/// </summary>
public bool IsOtherRoleQuest => ContentFinderConditionId is 845 or 1016;
}
} }