forked from liza/Questionable
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
59793d19dc | |||
2ada2fa9dc | |||
fe1d86bf5b | |||
71b40496fb | |||
224825b071 | |||
ed797143b3 | |||
22aa81cf75 | |||
dcdc288b08 | |||
a70e195a93 |
@ -1,5 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup Condition="$(MSBuildProjectName) != 'GatheringPathRenderer'">
|
||||
<Version>4.19</Version>
|
||||
<Version>4.20</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,30 @@
|
||||
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(),
|
||||
AssignmentList(nameof(SinglePlayerDutyOptions.Notes), dutyOptions.Notes)
|
||||
.AsSyntaxNodeOrToken(),
|
||||
Assignment(nameof(SinglePlayerDutyOptions.Index),
|
||||
dutyOptions.Index, emptyOptions.Index)
|
||||
.AsSyntaxNodeOrToken()))));
|
||||
}
|
||||
}
|
@ -62,6 +62,7 @@ public static class RoslynShortcuts
|
||||
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(),
|
||||
|
@ -57,7 +57,9 @@
|
||||
},
|
||||
"TerritoryId": 153,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyIndex": 1,
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Index": 1
|
||||
},
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -62,7 +62,9 @@
|
||||
},
|
||||
"TerritoryId": 154,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyIndex": 1,
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Index": 1
|
||||
},
|
||||
"AetheryteShortcut": "North Shroud - Fallgourd Float",
|
||||
"Fly": true
|
||||
}
|
||||
|
@ -120,7 +120,9 @@
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyIndex": 1
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Index": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -140,6 +140,10 @@
|
||||
},
|
||||
"TerritoryId": 141,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -92,7 +92,9 @@
|
||||
},
|
||||
"TerritoryId": 130,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyIndex": 1,
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Index": 1
|
||||
},
|
||||
"AetheryteShortcut": "Ul'dah"
|
||||
}
|
||||
]
|
||||
|
@ -35,6 +35,10 @@
|
||||
},
|
||||
"TerritoryId": 137,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"AetheryteShortcut": "Eastern La Noscea - Wineport",
|
||||
"Fly": true
|
||||
}
|
||||
|
@ -116,6 +116,10 @@
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -65,7 +65,8 @@
|
||||
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
|
||||
"SkipConditions": {
|
||||
"AetheryteShortcutIf": {
|
||||
"InSameTerritory": true
|
||||
"InSameTerritory": true,
|
||||
"AetheryteLocked": "East Shroud - Hawthorne Hut"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,7 +117,11 @@
|
||||
"Z": 35.568726
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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,10 @@
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -138,7 +138,11 @@
|
||||
"Z": 192.2179
|
||||
},
|
||||
"TerritoryId": 148,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -111,7 +111,14 @@
|
||||
"Z": 295.52136
|
||||
},
|
||||
"TerritoryId": 148,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"Notes": [
|
||||
"Healer NPC is only killed after the boss dies; all NPCs need to be killed for the duty to complete"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -29,10 +29,13 @@
|
||||
},
|
||||
"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,
|
||||
"TestedBossModVersion": 292,
|
||||
"Notes": [
|
||||
"AI doesn't automatically target newly spawning adds until after the boss died, and dies (tested on CNJ)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -77,6 +77,13 @@
|
||||
},
|
||||
"TerritoryId": 148,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
@ -69,6 +69,13 @@
|
||||
},
|
||||
"TerritoryId": 135,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": false,
|
||||
"TestedBossModVersion": 292,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
@ -45,8 +45,11 @@
|
||||
"TerritoryId": 134,
|
||||
"InteractionType": "Combat",
|
||||
"EnemySpawnType": "AutoOnEnterArea",
|
||||
"KillEnemyDataIds": [
|
||||
52
|
||||
"ComplexCombatData": [
|
||||
{
|
||||
"DataId": 52,
|
||||
"IgnoreQuestMarker": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -73,7 +73,11 @@
|
||||
"Z": -432.15082
|
||||
},
|
||||
"TerritoryId": 134,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -28,7 +28,14 @@
|
||||
"Z": -141.7716
|
||||
},
|
||||
"TerritoryId": 134,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": false,
|
||||
"TestedBossModVersion": 292,
|
||||
"Notes": [
|
||||
"AI doesn't automatically target newly spawning adds until after the boss died (requires healing luck on ACN)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -58,6 +58,13 @@
|
||||
},
|
||||
"TerritoryId": 138,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
@ -44,7 +44,11 @@
|
||||
"Z": -242.51166
|
||||
},
|
||||
"TerritoryId": 145,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -79,6 +79,10 @@
|
||||
},
|
||||
"TerritoryId": 130,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"AetheryteShortcut": "Ul'dah",
|
||||
"AethernetShortcut": [
|
||||
"[Ul'dah] Aetheryte Plaza",
|
||||
@ -87,6 +91,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sequence": 5
|
||||
},
|
||||
{
|
||||
"Sequence": 255,
|
||||
"Steps": [
|
||||
|
@ -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,17 @@
|
||||
"Z": 475.30322
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
|
||||
"SkipConditions": {
|
||||
"AetheryteShortcutIf": {
|
||||
"InSameTerritory": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -64,6 +64,10 @@
|
||||
},
|
||||
"TerritoryId": 135,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"AethernetShortcut": [
|
||||
"[Limsa Lominsa] The Aftcastle",
|
||||
"[Limsa Lominsa] Tempest Gate (Lower La Noscea)"
|
||||
|
@ -59,6 +59,10 @@
|
||||
},
|
||||
"TerritoryId": 140,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"AetheryteShortcut": "Western Thanalan - Horizon"
|
||||
}
|
||||
]
|
||||
|
@ -46,7 +46,8 @@
|
||||
},
|
||||
"StopDistance": 7,
|
||||
"TerritoryId": 141,
|
||||
"InteractionType": "Interact"
|
||||
"InteractionType": "Interact",
|
||||
"DelaySecondsAtStart": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -158,7 +158,11 @@
|
||||
"Z": 117.29602
|
||||
},
|
||||
"TerritoryId": 141,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -37,7 +37,11 @@
|
||||
"Z": -293.1411
|
||||
},
|
||||
"TerritoryId": 140,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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": {
|
||||
|
@ -28,7 +28,11 @@
|
||||
"Z": 536.88855
|
||||
},
|
||||
"TerritoryId": 141,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -64,7 +64,14 @@
|
||||
"Z": -131.48706
|
||||
},
|
||||
"TerritoryId": 141,
|
||||
"InteractionType": "Interact",
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -64,7 +64,11 @@
|
||||
"Z": -39.383606
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -83,7 +83,14 @@
|
||||
"Z": -12.985474
|
||||
},
|
||||
"TerritoryId": 153,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -159,7 +159,11 @@
|
||||
"Z": -805.478
|
||||
},
|
||||
"TerritoryId": 140,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -104,10 +104,10 @@
|
||||
},
|
||||
"TerritoryId": 1053,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": false,
|
||||
"BossModNotes": [
|
||||
"Doesn't handle death properly"
|
||||
]
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -68,6 +68,15 @@
|
||||
{
|
||||
"Sequence": 3,
|
||||
"Steps": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -561.9863,
|
||||
"Y": 9.919454,
|
||||
"Z": 66.29564
|
||||
},
|
||||
"TerritoryId": 152,
|
||||
"InteractionType": "WalkTo"
|
||||
},
|
||||
{
|
||||
"DataId": 1008276,
|
||||
"Position": {
|
||||
|
@ -78,6 +78,10 @@
|
||||
"StopDistance": 1,
|
||||
"TerritoryId": 156,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -100,6 +100,28 @@
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": 86.662384,
|
||||
"Y": 28.34813,
|
||||
"Z": -627.5218
|
||||
},
|
||||
"TerritoryId": 156,
|
||||
"InteractionType": "WalkTo",
|
||||
"Fly": true,
|
||||
"SkipConditions": {
|
||||
"StepIf": {
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
32
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DataId": 1009143,
|
||||
"Position": {
|
||||
@ -109,7 +131,6 @@
|
||||
},
|
||||
"TerritoryId": 156,
|
||||
"InteractionType": "Interact",
|
||||
"Fly": true,
|
||||
"$": "1 112 0 0 0 2 -> 2 96 0 0 0 34",
|
||||
"CompletionQuestVariablesFlags": [
|
||||
null,
|
||||
|
@ -71,6 +71,14 @@
|
||||
},
|
||||
"TerritoryId": 147,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"Notes": [
|
||||
"Will target Eline first (other NPCs later), and move to some -other- group of NPCs; only re-targets once they're at 1 HP (for Eline) or die",
|
||||
"If the target isn't in melee range but other NPCs are, whether any AOEs are used for nearby enemies seems random"
|
||||
]
|
||||
},
|
||||
"Fly": true,
|
||||
"AetheryteShortcut": "Northern Thanalan - Ceruleum Processing Plant"
|
||||
}
|
||||
|
@ -28,7 +28,16 @@
|
||||
"Z": -328.66406
|
||||
},
|
||||
"TerritoryId": 155,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": false,
|
||||
"TestedBossModVersion": 292,
|
||||
"Notes": [
|
||||
"WIP: Needs to be re-tested",
|
||||
"AI doesn't move after starting the instance, so enemies won't be triggered",
|
||||
"(First Barrier) If the player is too far south, after being stunned by Vishap's roar, AI doesn't move out of the AOE and dies to the Cauterize"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -46,6 +46,10 @@
|
||||
},
|
||||
"TerritoryId": 155,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
},
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -96,7 +96,10 @@
|
||||
"TerritoryId": 138,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"Fly": true,
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -30,7 +30,11 @@
|
||||
},
|
||||
"TerritoryId": 397,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"Comment": "Walk straight to Gorgagne Mills basement, ignore footprints"
|
||||
"Comment": "Walk straight to Gorgagne Mills basement, ignore footprints",
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -59,7 +59,10 @@
|
||||
},
|
||||
"TerritoryId": 401,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -79,7 +79,10 @@
|
||||
"[Ishgard] The Forgotten Knight",
|
||||
"[Ishgard] The Tribunal"
|
||||
],
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -29,7 +29,13 @@
|
||||
},
|
||||
"TerritoryId": 145,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292,
|
||||
"Notes": [
|
||||
"Will not move into melee range to kill the gate; Alphinaud will kill it after a while"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -79,7 +79,10 @@
|
||||
"TerritoryId": 397,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"DisableNavmesh": true,
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -75,7 +75,10 @@
|
||||
},
|
||||
"TerritoryId": 418,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -57,7 +57,10 @@
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"Emote": "lookout",
|
||||
"StopDistance": 0.25,
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -48,7 +48,10 @@
|
||||
"[Idyllshire] Aetheryte Plaza",
|
||||
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
|
||||
],
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -69,7 +69,10 @@
|
||||
},
|
||||
"TerritoryId": 402,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": true
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": true,
|
||||
"TestedBossModVersion": 292
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -85,14 +85,17 @@
|
||||
"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,
|
||||
"TestedBossModVersion": 292,
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -46,10 +46,13 @@
|
||||
},
|
||||
"TerritoryId": 817,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": false,
|
||||
"BossModNotes": [
|
||||
"Doesn't walk to the teleporter to finish the duty"
|
||||
],
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Enabled": false,
|
||||
"TestedBossModVersion": 292,
|
||||
"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",
|
||||
|
@ -104,7 +104,9 @@
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 829,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"SinglePlayerDutyIndex": 1,
|
||||
"SinglePlayerDutyOptions": {
|
||||
"Index": 1
|
||||
},
|
||||
"DialogueChoices": [
|
||||
{
|
||||
"Type": "List",
|
||||
|
@ -1267,20 +1267,36 @@
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"TestedBossModVersion": {
|
||||
"type": "number",
|
||||
"minimum": 292
|
||||
},
|
||||
"$": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"TODO_required": [
|
||||
"Enabled"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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();
|
||||
|
10
Questionable.Model/Questing/SinglePlayerDutyOptions.cs
Normal file
10
Questionable.Model/Questing/SinglePlayerDutyOptions.cs
Normal 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; }
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.Text;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
@ -45,7 +46,10 @@ internal sealed class Configuration : IPluginConfiguration
|
||||
internal sealed class SinglePlayerDutyConfiguration
|
||||
{
|
||||
public bool RunSoloInstancesWithBossMod { get; set; }
|
||||
public byte RetryDifficulty { get; set; } = 2;
|
||||
|
||||
[SuppressMessage("Performance", "CA1822", Justification = "Will be fixed when no longer WIP")]
|
||||
public byte RetryDifficulty => 0;
|
||||
|
||||
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||
}
|
||||
|
@ -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, ...".
|
||||
//
|
||||
|
@ -10,6 +10,7 @@ using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
@ -200,7 +201,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
|
||||
if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious])
|
||||
{
|
||||
if (!_taskQueue.AllTasksComplete)
|
||||
if (_condition[ConditionFlag.Unconscious] &&
|
||||
_condition[ConditionFlag.SufferingStatusAffliction63] &&
|
||||
_clientState.TerritoryType == SinglePlayerDuty.LahabreaTerritoryId)
|
||||
{
|
||||
// ignore, we're in the lahabrea fight
|
||||
}
|
||||
else if (!_taskQueue.AllTasksComplete)
|
||||
{
|
||||
Stop("HP = 0");
|
||||
_movementController.Stop();
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
@ -21,7 +23,7 @@ internal static class NextQuest
|
||||
return null;
|
||||
|
||||
// probably irrelevant, since pick up is handled elsewhere (and, in particular, checks for aetherytes and stuff)
|
||||
if (questFunctions.GetPriorityQuests().Contains(step.NextQuestId))
|
||||
if (questFunctions.GetPriorityQuests(onlyClassAndRoleQuests: true).Contains(step.NextQuestId))
|
||||
return null;
|
||||
|
||||
return new SetQuestTask(step.NextQuestId, quest.Id);
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -96,6 +96,12 @@ internal static class Interact
|
||||
private EInteractionState _interactionState = EInteractionState.None;
|
||||
private DateTime _continueAt = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// A slight delay when we think an interaction has ended, to make sure that we're processing "Action cancelled"
|
||||
/// prior to the next step (in case we're attacked).
|
||||
/// </summary>
|
||||
private bool delayedFinalCheck;
|
||||
|
||||
public Quest? Quest => Task.Quest;
|
||||
public EInteractionType InteractionType { get; set; }
|
||||
|
||||
@ -179,7 +185,14 @@ internal static class Interact
|
||||
return ETaskResult.StillRunning;
|
||||
else if (ProgressContext.WasSuccessful() ||
|
||||
_interactionState == EInteractionState.InteractionConfirmed)
|
||||
return ETaskResult.TaskComplete;
|
||||
{
|
||||
if (delayedFinalCheck)
|
||||
return ETaskResult.TaskComplete;
|
||||
|
||||
_continueAt = DateTime.Now.AddSeconds(0.2);
|
||||
delayedFinalCheck = true;
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
}
|
||||
|
||||
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId);
|
||||
|
@ -1,7 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
@ -12,22 +17,34 @@ namespace Questionable.Controller.Steps.Interactions;
|
||||
|
||||
internal static class SinglePlayerDuty
|
||||
{
|
||||
public const int LahabreaTerritoryId = 1052;
|
||||
|
||||
internal sealed class Factory(
|
||||
BossModIpc bossModIpc,
|
||||
TerritoryData territoryData) : ITaskFactory
|
||||
TerritoryData territoryData,
|
||||
ICondition condition,
|
||||
IClientState clientState) : ITaskFactory
|
||||
{
|
||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
{
|
||||
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");
|
||||
|
||||
yield return new StartSinglePlayerDuty(cfcData.ContentFinderConditionId);
|
||||
yield return new EnableAi();
|
||||
if (cfcData.TerritoryId == LahabreaTerritoryId)
|
||||
{
|
||||
yield return new SetTarget(14643);
|
||||
yield return new WaitCondition.Task(() => condition[ConditionFlag.Unconscious] || clientState.TerritoryType != LahabreaTerritoryId, "Wait(death)");
|
||||
yield return new DisableAi();
|
||||
yield return new WaitCondition.Task(() => !condition[ConditionFlag.Unconscious] || clientState.TerritoryType != LahabreaTerritoryId, "Wait(resurrection)");
|
||||
yield return new EnableAi();
|
||||
}
|
||||
yield return new WaitSinglePlayerDuty(cfcData.ContentFinderConditionId);
|
||||
yield return new DisableAi();
|
||||
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
||||
@ -113,4 +130,32 @@ internal static class SinglePlayerDuty
|
||||
|
||||
public override bool ShouldInterruptOnDamage() => false;
|
||||
}
|
||||
|
||||
// TODO this should be handled in VBM
|
||||
internal sealed record SetTarget(uint DataId) : ITask
|
||||
{
|
||||
public override string ToString() => $"SetTarget({DataId})";
|
||||
}
|
||||
|
||||
internal sealed class SetTargetExecutor(
|
||||
ITargetManager targetManager,
|
||||
IObjectTable objectTable) : TaskExecutor<SetTarget>
|
||||
{
|
||||
protected override bool Start() => true;
|
||||
|
||||
public override ETaskResult Update()
|
||||
{
|
||||
if (targetManager.Target?.DataId == Task.DataId)
|
||||
return ETaskResult.TaskComplete;
|
||||
|
||||
IGameObject? gameObject = objectTable.FirstOrDefault(x => x.DataId == Task.DataId);
|
||||
if (gameObject == null)
|
||||
return ETaskResult.StillRunning;
|
||||
|
||||
targetManager.Target = gameObject;
|
||||
return ETaskResult.StillRunning;
|
||||
}
|
||||
|
||||
public override bool ShouldInterruptOnDamage() => false;
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
169
Questionable/Controller/Utils/PartyWatchDog.cs
Normal file
169
Questionable/Controller/Utils/PartyWatchDog.cs
Normal file
@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Questionable.Controller.Utils;
|
||||
|
||||
internal sealed class PartyWatchDog : IDisposable
|
||||
{
|
||||
private readonly QuestController _questController;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly IChatGui _chatGui;
|
||||
private readonly ILogger<PartyWatchDog> _logger;
|
||||
|
||||
private ushort? _uncheckedTeritoryId;
|
||||
|
||||
public PartyWatchDog(QuestController questController, IClientState clientState, IChatGui chatGui,
|
||||
ILogger<PartyWatchDog> logger)
|
||||
{
|
||||
_questController = questController;
|
||||
_clientState = clientState;
|
||||
_chatGui = chatGui;
|
||||
_logger = logger;
|
||||
|
||||
_clientState.TerritoryChanged += TerritoryChanged;
|
||||
}
|
||||
|
||||
private unsafe void TerritoryChanged(ushort newTerritoryId)
|
||||
{
|
||||
var intendedUse = (ETerritoryIntendedUseEnum)GameMain.Instance()->CurrentTerritoryIntendedUseId;
|
||||
switch (intendedUse)
|
||||
{
|
||||
case ETerritoryIntendedUseEnum.Gaol:
|
||||
case ETerritoryIntendedUseEnum.Frontline:
|
||||
case ETerritoryIntendedUseEnum.LordOfVerminion:
|
||||
case ETerritoryIntendedUseEnum.Diadem:
|
||||
case ETerritoryIntendedUseEnum.CrystallineConflict:
|
||||
case ETerritoryIntendedUseEnum.Battlehall:
|
||||
case ETerritoryIntendedUseEnum.CrystallineConflict2:
|
||||
case ETerritoryIntendedUseEnum.DeepDungeon:
|
||||
case ETerritoryIntendedUseEnum.TreasureMapDuty:
|
||||
case ETerritoryIntendedUseEnum.Diadem2:
|
||||
case ETerritoryIntendedUseEnum.RivalWings:
|
||||
case ETerritoryIntendedUseEnum.Eureka:
|
||||
case ETerritoryIntendedUseEnum.LeapOfFaith:
|
||||
case ETerritoryIntendedUseEnum.OceanFishing:
|
||||
case ETerritoryIntendedUseEnum.Diadem3:
|
||||
case ETerritoryIntendedUseEnum.Bozja:
|
||||
case ETerritoryIntendedUseEnum.Battlehall2:
|
||||
case ETerritoryIntendedUseEnum.Battlehall3:
|
||||
case ETerritoryIntendedUseEnum.LargeScaleRaid:
|
||||
case ETerritoryIntendedUseEnum.LargeScaleSavageRaid:
|
||||
case ETerritoryIntendedUseEnum.Blunderville:
|
||||
StopIfRunning($"Unsupported Area entered ({newTerritoryId})");
|
||||
break;
|
||||
|
||||
case ETerritoryIntendedUseEnum.Dungeon:
|
||||
case ETerritoryIntendedUseEnum.VariantDungeon:
|
||||
case ETerritoryIntendedUseEnum.AllianceRaid:
|
||||
case ETerritoryIntendedUseEnum.Trial:
|
||||
case ETerritoryIntendedUseEnum.Raid:
|
||||
case ETerritoryIntendedUseEnum.Raid2:
|
||||
case ETerritoryIntendedUseEnum.SeasonalEvent:
|
||||
case ETerritoryIntendedUseEnum.SeasonalEvent2:
|
||||
case ETerritoryIntendedUseEnum.CriterionDuty:
|
||||
case ETerritoryIntendedUseEnum.CriterionSavageDuty:
|
||||
_uncheckedTeritoryId = newTerritoryId;
|
||||
_logger.LogInformation("Will check territory {TerritoryId} after loading", newTerritoryId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
if (_uncheckedTeritoryId == _clientState.TerritoryType && GameMain.Instance()->TerritoryLoadState == 2)
|
||||
{
|
||||
var groupManager = GroupManager.Instance();
|
||||
if (groupManager == null)
|
||||
return;
|
||||
|
||||
byte memberCount = groupManager->MainGroup.MemberCount;
|
||||
bool isInAlliance = groupManager->MainGroup.IsAlliance;
|
||||
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}",
|
||||
_uncheckedTeritoryId, memberCount, isInAlliance);
|
||||
if (memberCount > 1 || isInAlliance)
|
||||
StopIfRunning("Other party members present");
|
||||
|
||||
_uncheckedTeritoryId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopIfRunning(string reason)
|
||||
{
|
||||
if (_questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual)
|
||||
{
|
||||
_chatGui.PrintError(
|
||||
$"Stopping Questionable: {reason}. If you believe this to be correct, please restart Questionable manually.",
|
||||
CommandHandler.MessageTag, CommandHandler.TagColor);
|
||||
_questController.Stop(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientState.TerritoryChanged -= TerritoryChanged;
|
||||
}
|
||||
|
||||
// from https://github.com/NightmareXIV/ECommons/blob/f69e460e95134c72592654059843b138b4c01a9e/ECommons/ExcelServices/TerritoryIntendedUseEnum.cs#L5
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.Members, Reason = "game data")]
|
||||
private enum ETerritoryIntendedUseEnum : byte
|
||||
{
|
||||
CityArea = 0,
|
||||
OpenWorld = 1,
|
||||
Inn = 2,
|
||||
Dungeon = 3,
|
||||
VariantDungeon = 4,
|
||||
Gaol = 5,
|
||||
StartingArea = 6,
|
||||
QuestArea = 7,
|
||||
AllianceRaid = 8,
|
||||
QuestBattle = 9,
|
||||
Trial = 10,
|
||||
QuestArea2 = 12,
|
||||
ResidentialArea = 13,
|
||||
HousingInstances = 14,
|
||||
QuestArea3 = 15,
|
||||
Raid = 16,
|
||||
Raid2 = 17,
|
||||
Frontline = 18,
|
||||
ChocoboSquare = 20,
|
||||
RestorationEvent = 21,
|
||||
Sanctum = 22,
|
||||
GoldSaucer = 23,
|
||||
LordOfVerminion = 25,
|
||||
Diadem = 26,
|
||||
HallOfTheNovice = 27,
|
||||
CrystallineConflict = 28,
|
||||
QuestBattle2 = 29,
|
||||
Barracks = 30,
|
||||
DeepDungeon = 31,
|
||||
SeasonalEvent = 32,
|
||||
TreasureMapDuty = 33,
|
||||
SeasonalEventDuty = 34,
|
||||
Battlehall = 35,
|
||||
CrystallineConflict2 = 37,
|
||||
Diadem2 = 38,
|
||||
RivalWings = 39,
|
||||
Unknown1 = 40,
|
||||
Eureka = 41,
|
||||
SeasonalEvent2 = 43,
|
||||
LeapOfFaith = 44,
|
||||
MaskedCarnivale = 45,
|
||||
OceanFishing = 46,
|
||||
Diadem3 = 47,
|
||||
Bozja = 48,
|
||||
IslandSanctuary = 49,
|
||||
Battlehall2 = 50,
|
||||
Battlehall3 = 51,
|
||||
LargeScaleRaid = 52,
|
||||
LargeScaleSavageRaid = 53,
|
||||
QuestArea4 = 54,
|
||||
TribalInstance = 56,
|
||||
CriterionDuty = 57,
|
||||
CriterionSavageDuty = 58,
|
||||
Blunderville = 59,
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Controller.GameUi;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Windows;
|
||||
|
||||
namespace Questionable;
|
||||
@ -23,6 +24,7 @@ internal sealed class DalamudInitializer : IDisposable
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private readonly IToastGui _toastGui;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly PartyWatchDog _partyWatchDog;
|
||||
private readonly ILogger<DalamudInitializer> _logger;
|
||||
|
||||
public DalamudInitializer(
|
||||
@ -42,6 +44,7 @@ internal sealed class DalamudInitializer : IDisposable
|
||||
PriorityWindow priorityWindow,
|
||||
IToastGui toastGui,
|
||||
Configuration configuration,
|
||||
PartyWatchDog partyWatchDog,
|
||||
ILogger<DalamudInitializer> logger)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
@ -54,6 +57,7 @@ internal sealed class DalamudInitializer : IDisposable
|
||||
_configWindow = configWindow;
|
||||
_toastGui = toastGui;
|
||||
_configuration = configuration;
|
||||
_partyWatchDog = partyWatchDog;
|
||||
_logger = logger;
|
||||
|
||||
_windowSystem.AddWindow(oneTimeSetupWindow);
|
||||
@ -77,6 +81,7 @@ internal sealed class DalamudInitializer : IDisposable
|
||||
|
||||
private void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
_partyWatchDog.Update();
|
||||
_questController.Update();
|
||||
|
||||
try
|
||||
|
7
Questionable/External/BossModIpc.cs
vendored
7
Questionable/External/BossModIpc.cs
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using LLib.GameUI;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Action = Lumina.Excel.Sheets.Action;
|
||||
@ -427,6 +428,11 @@ internal sealed unsafe class GameFunctions
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_condition[ConditionFlag.Unconscious] &&
|
||||
_condition[ConditionFlag.SufferingStatusAffliction63] &&
|
||||
_clientState.TerritoryType == SinglePlayerDuty.LahabreaTerritoryId)
|
||||
return false; // needed to process the tasks
|
||||
|
||||
return _condition[ConditionFlag.Occupied] || _condition[ConditionFlag.Occupied30] ||
|
||||
_condition[ConditionFlag.Occupied33] || _condition[ConditionFlag.Occupied38] ||
|
||||
_condition[ConditionFlag.Occupied39] || _condition[ConditionFlag.OccupiedInEvent] ||
|
||||
|
@ -401,14 +401,15 @@ internal sealed unsafe class QuestFunctions
|
||||
return 1000 * quest.AllSteps().Count(x => x.Step.AetheryteShortcut != null);
|
||||
}
|
||||
|
||||
public List<ElementId> GetPriorityQuests()
|
||||
public List<ElementId> GetPriorityQuests(bool onlyClassAndRoleQuests = false)
|
||||
{
|
||||
List<ElementId> priorityQuests =
|
||||
[
|
||||
new QuestId(1157), // Garuda (Hard)
|
||||
new QuestId(1158), // Titan (Hard)
|
||||
..QuestData.CrystalTowerQuests
|
||||
];
|
||||
List<ElementId> priorityQuests = [];
|
||||
if (!onlyClassAndRoleQuests)
|
||||
{
|
||||
priorityQuests.Add(new QuestId(1157)); // Garuda (Hard)
|
||||
priorityQuests.Add(new QuestId(1158)); // Titan (Hard)
|
||||
priorityQuests.AddRange(QuestData.CrystalTowerQuests);
|
||||
}
|
||||
|
||||
EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer;
|
||||
uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();
|
||||
|
@ -20,6 +20,7 @@ using Questionable.Controller.Steps.Common;
|
||||
using Questionable.Controller.Steps.Gathering;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Controller.Steps.Leves;
|
||||
using Questionable.Controller.Utils;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
@ -231,6 +232,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection
|
||||
.AddTaskExecutor<SinglePlayerDuty.WaitSinglePlayerDuty, SinglePlayerDuty.WaitSinglePlayerDutyExecutor>();
|
||||
serviceCollection.AddTaskExecutor<SinglePlayerDuty.DisableAi, SinglePlayerDuty.DisableAiExecutor>();
|
||||
serviceCollection.AddTaskExecutor<SinglePlayerDuty.SetTarget, SinglePlayerDuty.SetTargetExecutor>();
|
||||
|
||||
serviceCollection.AddTaskExecutor<WaitCondition.Task, WaitCondition.WaitConditionExecutor>();
|
||||
serviceCollection.AddTaskFactory<WaitAtEnd.Factory>();
|
||||
@ -259,6 +261,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<ShopController>();
|
||||
serviceCollection.AddSingleton<InterruptHandler>();
|
||||
|
||||
serviceCollection.AddSingleton<PartyWatchDog>();
|
||||
|
||||
serviceCollection.AddSingleton<CraftworksSupplyController>();
|
||||
serviceCollection.AddSingleton<CreditsController>();
|
||||
serviceCollection.AddSingleton<HelpUiController>();
|
||||
|
@ -9,7 +9,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
if (quest.Id is SatisfactionSupplyNpcId)
|
||||
if (quest.Id is SatisfactionSupplyNpcId or AlliedSocietyDailyId)
|
||||
yield break;
|
||||
|
||||
var questAccepts =
|
||||
|
@ -34,7 +34,9 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
(EClassJob.BlackMage, "Magical Ranged Role Quests"),
|
||||
];
|
||||
|
||||
#if false
|
||||
private readonly string[] _retryDifficulties = ["Normal", "Easy", "Very Easy"];
|
||||
#endif
|
||||
|
||||
private readonly TerritoryData _territoryData;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
@ -122,70 +124,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 +152,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 +176,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 +198,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<JournalGenre>().GetRow(journalGenreId);
|
||||
@ -250,7 +247,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()
|
||||
@ -268,12 +265,19 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X))
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed,
|
||||
"Work in Progress: For now, this will always use BossMod for combat.");
|
||||
using (_ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextUnformatted("Work in Progress:");
|
||||
ImGui.BulletText("Will always use BossMod for combat (ignoring the configured combat module).");
|
||||
ImGui.BulletText("Only a small subset of quest battles have been tested - most of which are in the MSQ.");
|
||||
ImGui.BulletText("When retrying a failed battle, it will always start at 'Normal' difficulty.");
|
||||
ImGui.BulletText("Please don't enable this option when using a BossMod fork (such as Reborn);\nwith the missing combat module configuration, it is unlikely to be compatible.");
|
||||
}
|
||||
|
||||
#if false
|
||||
using (ImRaii.Disabled(!runSoloInstancesWithBossMod))
|
||||
{
|
||||
ImGui.Spacing();
|
||||
int retryDifficulty = Configuration.SinglePlayerDuties.RetryDifficulty;
|
||||
if (ImGui.Combo("Difficulty when retrying a quest battle", ref retryDifficulty, _retryDifficulties,
|
||||
_retryDifficulties.Length))
|
||||
@ -282,6 +286,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
Save();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
@ -423,13 +428,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 +450,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 +462,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 +490,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 +531,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<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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user