AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.CombatItemUse), step.CombatItemUse,
- emptyStep.CombatItemUse)
+ emptyStep.CombatItemUse)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.CombatDelaySecondsAtStart),
step.CombatDelaySecondsAtStart,
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)
--- /dev/null
+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()))));
+ }
+}
ComplexCombatData complexCombatData => complexCombatData.ToExpressionSyntax(),
QuestWorkValue questWorkValue => questWorkValue.ToExpressionSyntax(),
List<QuestWorkValue> list => list.ToExpressionSyntax(), // TODO fix in AssignmentList
+ SinglePlayerDutyOptions dutyOptions => dutyOptions.ToExpressionSyntax(),
SkipConditions skipConditions => skipConditions.ToExpressionSyntax(),
SkipStepConditions skipStepConditions => skipStepConditions.ToExpressionSyntax(),
SkipItemConditions skipItemCondition => skipItemCondition.ToExpressionSyntax(),
},
"TerritoryId": 153,
"InteractionType": "SinglePlayerDuty",
- "SinglePlayerDutyIndex": 1,
+ "SinglePlayerDutyOptions": {
+ "Index": 1
+ },
"Fly": true
}
]
},\r
"TerritoryId": 154,\r
"InteractionType": "SinglePlayerDuty",\r
- "SinglePlayerDutyIndex": 1,\r
+ "SinglePlayerDutyOptions": {\r
+ "Index": 1\r
+ },\r
"AetheryteShortcut": "North Shroud - Fallgourd Float",\r
"Fly": true\r
}\r
},\r
"TerritoryId": 152,\r
"InteractionType": "SinglePlayerDuty",\r
- "SinglePlayerDutyIndex": 1\r
+ "SinglePlayerDutyOptions": {\r
+ "Index": 1\r
+ }\r
}\r
]\r
},\r
},
"TerritoryId": 141,
"InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
"Fly": true
}
]
},
"TerritoryId": 130,
"InteractionType": "SinglePlayerDuty",
- "SinglePlayerDutyIndex": 1,
+ "SinglePlayerDutyOptions": {
+ "Index": 1
+ },
"AetheryteShortcut": "Ul'dah"
}
]
},
"TerritoryId": 137,
"InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
"AetheryteShortcut": "Eastern La Noscea - Wineport",
"Fly": true
}
},
"TerritoryId": 152,
"InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
"Fly": true
}
]
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"AetheryteShortcutIf": {
- "InSameTerritory": true
+ "InSameTerritory": true,
+ "AetheryteLocked": "East Shroud - Hawthorne Hut"
}
}
}
"Z": 35.568726
},
"TerritoryId": 152,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
{\r
"Sequence": 1,\r
"Steps": [\r
+ {\r
+ "DataId": 1001263,\r
+ "Position": {\r
+ "X": 181.41443,\r
+ "Y": -2.3519497,\r
+ "Z": -240.40594\r
+ },\r
+ "TerritoryId": 133,\r
+ "InteractionType": "Interact",\r
+ "TargetTerritoryId": 152,\r
+ "AethernetShortcut": [\r
+ "[Gridania] Conjurers' Guild",\r
+ "[Gridania] Lancers' Guild"\r
+ ],\r
+ "SkipConditions": {\r
+ "StepIf": {\r
+ "AetheryteUnlocked": "East Shroud - Hawthorne Hut",\r
+ "InTerritory": [\r
+ 152\r
+ ]\r
+ }\r
+ }\r
+ },\r
+ {\r
+ "TerritoryId": 152,\r
+ "InteractionType": "AttuneAetheryte",\r
+ "Aetheryte": "East Shroud - Hawthorne Hut",\r
+ "SkipConditions": {\r
+ "StepIf": {\r
+ "AetheryteUnlocked": "East Shroud - Hawthorne Hut"\r
+ }\r
+ }\r
+ },\r
{\r
"Position": {\r
"X": -276.804,\r
"TerritoryId": 152,\r
"InteractionType": "WalkTo",\r
"AetheryteShortcut": "East Shroud - Hawthorne Hut",\r
- "Fly": true\r
+ "Fly": true,\r
+ "SkipConditions": {\r
+ "AetheryteShortcutIf": {\r
+ "AetheryteLocked": "East Shroud - Hawthorne Hut"\r
+ }\r
+ }\r
},\r
{\r
"DataId": 2000889,\r
},\r
"TerritoryId": 152,\r
"InteractionType": "SinglePlayerDuty",\r
+ "SinglePlayerDutyOptions": {\r
+ "Enabled": true\r
+ },\r
"Fly": true\r
}\r
]\r
"Z": 192.2179
},
"TerritoryId": 148,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"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"
+ ]
+ }
}
]
},
},
"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)"
+ ]
+ }
}
]
},
},
"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"
}
]
},
"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"
}
]
"TerritoryId": 134,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
- "KillEnemyDataIds": [
- 52
+ "ComplexCombatData": [
+ {
+ "DataId": 52,
+ "IgnoreQuestMarker": true
+ }
]
},
{
"Z": -432.15082
},
"TerritoryId": 134,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"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)"
+ ]
+ }
}
]
},
},
"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"
}
]
"Z": -242.51166
},
"TerritoryId": 145,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
},
"TerritoryId": 130,
"InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
"AetheryteShortcut": "Ul'dah",
"AethernetShortcut": [
"[Ul'dah] Aetheryte Plaza",
}
]
},
+ {
+ "Sequence": 5
+ },
{
"Sequence": 255,
"Steps": [
"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,
"Z": 475.30322
},
"TerritoryId": 152,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
+ "AetheryteShortcut": "East Shroud - Hawthorne Hut",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
},
"TerritoryId": 135,
"InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
"AethernetShortcut": [
"[Limsa Lominsa] The Aftcastle",
"[Limsa Lominsa] Tempest Gate (Lower La Noscea)"
},
"TerritoryId": 140,
"InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ },
"AetheryteShortcut": "Western Thanalan - Horizon"
}
]
},
"StopDistance": 7,
"TerritoryId": 141,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DelaySecondsAtStart": 2
}
]
},
"Z": 117.29602
},
"TerritoryId": 141,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
{
"Sequence": 255,
"Steps": [
+ {
+ "Position": {
+ "X": -174.73444,
+ "Y": 15.450659,
+ "Z": -266.76144
+ },
+ "TerritoryId": 140,
+ "InteractionType": "WalkTo"
+ },
{
"Position": {
"X": -289.1099,
"Z": -293.1411
},
"TerritoryId": 140,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
},
"TerritoryId": 141,
"InteractionType": "Combat",
- "EnemySpawnType": "OverworldEnemies",
+ "EnemySpawnType": "FinishCombatIfAny",
"KillEnemyDataIds": [
352,
353
{
"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": {
"Z": 536.88855
},
"TerritoryId": 141,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"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"
}
]
},
"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,
"Z": 283.4973
},
"TerritoryId": 152,
- "InteractionType": "CompleteQuest"
+ "InteractionType": "CompleteQuest",
+ "AetheryteShortcut": "East Shroud - Hawthorne Hut",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
}
"Z": -39.383606
},
"TerritoryId": 152,
- "InteractionType": "SinglePlayerDuty"
+ "InteractionType": "SinglePlayerDuty",
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"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"
+ ]
+ }
}
]
},
},
"TerritoryId": 1053,
"InteractionType": "SinglePlayerDuty",
- "BossModEnabled": false,
- "BossModNotes": [
- "Doesn't handle death properly"
- ]
+ "SinglePlayerDutyOptions": {
+ "Enabled": false,
+ "Notes": [
+ "Doesn't handle death properly"
+ ]
+ }
}
]
},
"TerritoryId": 138,
"InteractionType": "SinglePlayerDuty",
"Fly": true,
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
},
"TerritoryId": 401,
"InteractionType": "SinglePlayerDuty",
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"[Ishgard] The Forgotten Knight",
"[Ishgard] The Tribunal"
],
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
},
"TerritoryId": 145,
"InteractionType": "SinglePlayerDuty",
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"TerritoryId": 397,
"InteractionType": "SinglePlayerDuty",
"DisableNavmesh": true,
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
},
"TerritoryId": 418,
"InteractionType": "SinglePlayerDuty",
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"InteractionType": "SinglePlayerDuty",
"Emote": "lookout",
"StopDistance": 0.25,
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
],
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
},
"TerritoryId": 402,
"InteractionType": "SinglePlayerDuty",
- "BossModEnabled": true
+ "SinglePlayerDutyOptions": {
+ "Enabled": true
+ }
}
]
},
"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"
}
]
},
},
"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",
"StopDistance": 5,
"TerritoryId": 829,
"InteractionType": "SinglePlayerDuty",
- "SinglePlayerDutyIndex": 1,
+ "SinglePlayerDutyOptions": {
+ "Index": 1
+ },
"DialogueChoices": [
{
"Type": "List",
},
"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
}
}
}
public JumpDestination? JumpDestination { get; set; }
public uint? ContentFinderConditionId { get; set; }
public bool AutoDutyEnabled { get; set; }
- public bool BossModEnabled { get; set; }
- public List<string> 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<List<QuestWorkValue>?> RequiredQuestVariables { get; set; } = new();
--- /dev/null
+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; }
+}
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, ...".
//
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,
};
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");
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:
ClearPreset();
}
- public bool IsConfiguredToRunSoloInstance(ElementId questId, byte dutyIndex, bool enabledByDefault)
+ public bool IsConfiguredToRunSoloInstance(ElementId questId, SinglePlayerDutyOptions? dutyOptions)
{
if (!IsSupported())
return false;
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))
if (_configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
return true;
- return enabledByDefault;
+ return dutyOptions.Enabled;
}
}
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);
-
- if (cfcData.ContentFinderConditionId is 332 or 333 or 313 or 334)
+ var dutyInfo = new SinglePlayerDutyInfo(name, questInfo, cfcData, options, enabled);
+
+ 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);
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);
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)
.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<JournalGenre>().GetRow(journalGenreId);
string genreName = journalGenre.Name.ExtractText();
string categoryName = journalCategory.Name.ExtractText();
- return $"{categoryName} {SeIconChar.ArrowRight.ToIconString()} {genreName}";
+ return $"{categoryName} \u203B {genreName}";
}
public override void DrawTab()
{
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())
ImGui.TextUnformatted(dutyInfo.Name);
ImGui.Separator();
ImGui.BulletText($"TerritoryId: {dutyInfo.TerritoryId}");
- ImGui.BulletText($"ContentFinderConditionId: {dutyInfo.CfcId}");
+ ImGui.BulletText($"ContentFinderConditionId: {dutyInfo.ContentFinderConditionId}");
}
}
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())
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();
}
}
private sealed record SinglePlayerDutyInfo(
- uint CfcId,
- uint TerritoryId,
string Name,
- EExpansionVersion Expansion,
- uint JournalGenreId,
- ushort SortKey,
- byte Index,
- bool Enabled,
- bool BossModEnabledByDefault,
- List<string> 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<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;
+ }
}