Merge branch 'master' into arr-msq-part4

arr-p5
Liza 2024-07-16 14:44:51 +02:00
commit 7d026fa07c
Signed by: liza
GPG Key ID: 7199F8D727D55F67
78 changed files with 3970 additions and 143 deletions

View File

@ -79,6 +79,9 @@ public class QuestSourceGenerator : ISourceGenerator
} }
var quest = questNode.Deserialize<QuestRoot>()!; var quest = questNode.Deserialize<QuestRoot>()!;
if (quest.Disabled)
continue;
quests.Add((id, quest)); quests.Add((id, quest));
} }

View File

@ -173,6 +173,8 @@ public static class RoslynShortcuts
SyntaxNodeList( SyntaxNodeList(
Assignment(nameof(ComplexCombatData.DataId), complexCombatData.DataId, default(uint)) Assignment(nameof(ComplexCombatData.DataId), complexCombatData.DataId, default(uint))
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(ComplexCombatData.MinimumKillCount), complexCombatData.MinimumKillCount, null)
.AsSyntaxNodeOrToken(),
Assignment(nameof(ComplexCombatData.RewardItemId), complexCombatData.RewardItemId, null) Assignment(nameof(ComplexCombatData.RewardItemId), complexCombatData.RewardItemId, null)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(ComplexCombatData.RewardItemCount), complexCombatData.RewardItemCount, null) Assignment(nameof(ComplexCombatData.RewardItemCount), complexCombatData.RewardItemCount, null)

6
QuestPaths/.editorconfig Normal file
View File

@ -0,0 +1,6 @@
root = true
[*.json]
indent_size = 2
indent_style = space
insert_final_newline = true

View File

@ -1,6 +1,7 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"Disabled": true,
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,

View File

@ -1,6 +1,7 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"Disabled": true,
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,

View File

@ -1,6 +1,7 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"Disabled": true,
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,
@ -20,6 +21,21 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "Steps": [
{
"DataId": 1032169,
"Position": {
"X": -423.33105,
"Y": 25.599815,
"Z": 265.94946
},
"TerritoryId": 816,
"InteractionType": "Interact",
"Fly": true,
"SkipIf": [
"NotTargetable"
],
"$": "Only if QW: 0 48 0 0 0 0"
},
{ {
"DataId": 1032167, "DataId": 1032167,
"Position": { "Position": {
@ -29,7 +45,10 @@
}, },
"TerritoryId": 816, "TerritoryId": 816,
"InteractionType": "Interact", "InteractionType": "Interact",
"Fly": true "Fly": true,
"SkipIf": [
"NotTargetable"
]
} }
] ]
}, },

View File

@ -0,0 +1,8 @@
## Raiders of the Lost Pork
QuestWork:
```
0 x 0 0 0 0
48 → Elegant Eulmoran (1032169)
?? → 1032167
```

View File

@ -1,6 +1,7 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"Disabled": true,
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,
@ -28,19 +29,39 @@
"Z": -55.77173 "Z": -55.77173
}, },
"TerritoryId": 816, "TerritoryId": 816,
"InteractionType": "Combat", "InteractionType": "Interact",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11451
],
"Fly": true, "Fly": true,
"Comment": "TODO Combat is optional, check where we should walk" "$": "Only if QW: 0 0 ??? 0 0 0",
"SkipIf": ["NotTargetable"]
},
{
"DataId": 2010902,
"Position": {
"X": -405.9358,
"Y": -0.07635498,
"Z": -28.397034
},
"TerritoryId": 816,
"InteractionType": "Interact",
"$": "Only if QW: 0 0 1 0 0 0 → if complete, 0 16 0 0 0 0",
"SkipIf": ["NotTargetable"]
} }
] ]
}, },
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"Position": {
"X": -398.9776,
"Y": 0.82966614,
"Z": 8.668919
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Mount": false,
"DisableNavmesh": true
},
{ {
"DataId": 1031809, "DataId": 1031809,
"Position": { "Position": {

View File

@ -0,0 +1,66 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"Disabled": true,
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -115.77283,
"Y": 7.1942587,
"Z": 130.61378
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 2010911,
"Position": {
"X": -113.93915,
"Y": -36.087585,
"Z": 87.6936
},
"TerritoryId": 816,
"InteractionType": "Interact",
"DisableNavmesh": true,
"$": "QW: 0 2 0 0 0 0"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true
}
]
}
]
}

View File

@ -79,7 +79,6 @@
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CompleteQuest", "InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true "Fly": true
} }
] ]

View File

@ -65,16 +65,6 @@
"TerritoryId": 1187, "TerritoryId": 1187,
"InteractionType": "CompleteQuest", "InteractionType": "CompleteQuest",
"AetheryteShortcut": "Urqopacha - Wachunpelo" "AetheryteShortcut": "Urqopacha - Wachunpelo"
},
{
"DataId": 1050684,
"Position": {
"X": 391.37854,
"Y": -156.07434,
"Z": -388.50995
},
"TerritoryId": 1187,
"InteractionType": "CompleteQuest"
} }
] ]
} }

View File

@ -0,0 +1,60 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1049786,
"Position": {
"X": 340.13867,
"Y": 50.75,
"Z": 231.37244
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1049787,
"Position": {
"X": 364.3396,
"Y": 60.125,
"Z": 357.1068
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_KINGRA101_04960_SYSTEM_100_030",
"Yes": true
}
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1049788,
"Position": {
"X": 1.6021729,
"Y": 0,
"Z": -6.088379
},
"StopDistance": 5,
"TerritoryId": 1224,
"InteractionType": "CompleteQuest",
"NextQuestId": 4961
}
]
}
]
}

View File

@ -0,0 +1,52 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1225
],
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1049788,
"Position": {
"X": 1.6021729,
"Y": 0,
"Z": -6.088379
},
"StopDistance": 5,
"TerritoryId": 1224,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"TerritoryId": 1224,
"InteractionType": "Duty",
"ContentFinderConditionId": 985
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050476,
"Position": {
"X": 0.1373291,
"Y": -3.3667622E-13,
"Z": -9.658997
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "CompleteQuest",
"NextQuestId": 4962
}
]
}
]
}

View File

@ -0,0 +1,68 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1227
],
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050476,
"Position": {
"X": 0.1373291,
"Y": -3.3667622E-13,
"Z": -9.658997
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"TerritoryId": 1224,
"InteractionType": "Duty",
"ContentFinderConditionId": 987
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1050476,
"Position": {
"X": 0.1373291,
"Y": -3.3667622E-13,
"Z": -9.658997
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050477,
"Position": {
"X": 1.663208,
"Y": -1.9688797E-12,
"Z": -10.727112
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "CompleteQuest",
"NextQuestId": 4963
}
]
}
]
}

View File

@ -0,0 +1,55 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050477,
"Position": {
"X": 1.663208,
"Y": -1.9688797E-12,
"Z": -10.727112
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1049790,
"Position": {
"X": 494.7433,
"Y": 59.55,
"Z": 125.10864
},
"StopDistance": 5,
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1049789,
"Position": {
"X": 2.3651123,
"Y": -4.334177E-12,
"Z": -14.206177
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "CompleteQuest",
"NextQuestId": 4964
}
]
}
]
}

View File

@ -0,0 +1,68 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1229
],
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1049789,
"Position": {
"X": 2.3651123,
"Y": -4.334177E-12,
"Z": -14.206177
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"TerritoryId": 1224,
"InteractionType": "Duty",
"ContentFinderConditionId": 989
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1050477,
"Position": {
"X": 1.663208,
"Y": -1.9688797E-12,
"Z": -10.727112
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050477,
"Position": {
"X": 1.663208,
"Y": -1.9688797E-12,
"Z": -10.727112
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "CompleteQuest",
"NextQuestId": 4965
}
]
}
]
}

View File

@ -0,0 +1,96 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"TerritoryBlacklist": [
1231
],
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1049792,
"Position": {
"X": 1.663208,
"Y": -1.9688797E-12,
"Z": -10.727112
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"TerritoryId": 1224,
"InteractionType": "Duty",
"ContentFinderConditionId": 991
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1050477,
"Position": {
"X": 1.663208,
"Y": -1.9688797E-12,
"Z": -10.727112
},
"StopDistance": 7,
"TerritoryId": 1224,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 2013722,
"Position": {
"X": -0.07635498,
"Y": 1.0527954,
"Z": 8.102478
},
"TerritoryId": 1224,
"InteractionType": "Interact",
"TargetTerritoryId": 1186
},
{
"DataId": 1049790,
"Position": {
"X": 494.7433,
"Y": 59.55,
"Z": 125.10864
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Solution Nine] The Arcadion",
"[Solution Nine] True Vue"
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1049790,
"Position": {
"X": 494.7433,
"Y": 59.55,
"Z": 125.10864
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -50,7 +50,7 @@
}, },
"StopDistance": 0.5, "StopDistance": 0.5,
"TerritoryId": 1187, "TerritoryId": 1187,
"InteractionType": "AcceptQuest", "InteractionType": "CompleteQuest",
"AetheryteShortcut": "Urqopacha - Wachunpelo", "AetheryteShortcut": "Urqopacha - Wachunpelo",
"Fly": true "Fly": true
} }

View File

@ -0,0 +1,56 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048960,
"Position": {
"X": -405.84424,
"Y": 23.562847,
"Z": -476.61554
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -455.06216,
"Y": 20.027777,
"Z": 46.272446
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "OverworldEnemies",
"KillEnemyDataIds": [
17268
],
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048960,
"Position": {
"X": -405.84424,
"Y": 23.562847,
"Z": -476.61554
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Iq Br'aax"
}
]
}
]
}

View File

@ -0,0 +1,102 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051071,
"Position": {
"X": -554.03986,
"Y": 1.3156406,
"Z": -489.3416
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -596.55597,
"Y": 2.3708515,
"Z": -493.64908
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true
},
{
"Position": {
"X": -615.2386,
"Y": -44.093876,
"Z": -495.7548
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"DisableNavmesh": true
},
{
"DataId": 2014307,
"Position": {
"X": -729.7932,
"Y": -107.83557,
"Z": -674.8608
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"Position": {
"X": -615.2386,
"Y": -44.093876,
"Z": -495.7548
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true
},
{
"Position": {
"X": -596.55597,
"Y": 2.3708515,
"Z": -493.64908
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"DisableNavmesh": true
},
{
"DataId": 1051071,
"Position": {
"X": -554.03986,
"Y": 1.3156406,
"Z": -489.3416
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true,
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZD002_05091_Q1_000_000",
"Answer": "TEXT_KINGZD002_05091_A1_000_001"
}
]
}
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051057,
"Position": {
"X": -343.67902,
"Y": 18.885578,
"Z": -404.22675
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 158.97704,
"Y": 0.80940175,
"Z": -773.5188
},
"StopDistance": 1,
"TerritoryId": 1189,
"InteractionType": "Combat",
"AetheryteShortcut": "Tuliyollal",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
17663
],
"AethernetShortcut": [
"[Tuliyollal] Aetheryte Plaza",
"[Tuliyollal] Dirigible Landing (Yak T'el)"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051058,
"Position": {
"X": 159.47205,
"Y": 0.81595546,
"Z": -770.19916
},
"StopDistance": 7,
"TerritoryId": 1189,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051057,
"Position": {
"X": -343.67902,
"Y": 18.885578,
"Z": -404.22675
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Iq Br'aax",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,67 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048928,
"Position": {
"X": 36.301147,
"Y": 8.205902,
"Z": -645.1362
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051072,
"Position": {
"X": -39.07837,
"Y": 4.3051004,
"Z": -332.87555
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051072,
"Position": {
"X": -39.07837,
"Y": 4.3051004,
"Z": -332.87555
},
"TerritoryId": 1189,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048928,
"Position": {
"X": 36.301147,
"Y": 8.205902,
"Z": -645.1362
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,75 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051061,
"Position": {
"X": -407.79742,
"Y": 28.068892,
"Z": -360.55542
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051062,
"Position": {
"X": -489.5247,
"Y": -29.96456,
"Z": -94.83484
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17664,
17665
],
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051062,
"Position": {
"X": -489.5247,
"Y": -29.96456,
"Z": -94.83484
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"StopDistance": 7
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051061,
"Position": {
"X": -407.79742,
"Y": 28.068892,
"Z": -360.55542
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Iq Br'aax",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,99 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051065,
"Position": {
"X": 380.7583,
"Y": 21.437008,
"Z": -496.84903
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014264,
"Position": {
"X": 405.1422,
"Y": 20.85907,
"Z": -482.10883
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2014266,
"Position": {
"X": 430.83838,
"Y": 20.126648,
"Z": -456.71783
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2014265,
"Position": {
"X": 395.92578,
"Y": 20.34021,
"Z": -453.02515
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17666
],
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051065,
"Position": {
"X": 380.7583,
"Y": 21.437008,
"Z": -496.84903
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,103 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051364,
"Position": {
"X": -9.353821,
"Y": 8.205902,
"Z": -652.9183
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1048961,
"Position": {
"X": -444.38855,
"Y": 28.068893,
"Z": -363.66827
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"AetheryteShortcut": "Yak T'el - Iq Br'aax",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"Position": {
"X": -632.6718,
"Y": 25.245409,
"Z": -159.94264
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-128
]
},
{
"DataId": 1048995,
"Position": {
"X": -639.27673,
"Y": 26.716875,
"Z": -161.05902
},
"StopDistance": 7,
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051410,
"Position": {
"X": -9.140198,
"Y": 8.205902,
"Z": -652.9183
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Tuliyollal",
"AethernetShortcut": [
"[Tuliyollal] Aetheryte Plaza",
"[Tuliyollal] Dirigible Landing (Yak T'el)"
]
}
]
}
]
}

View File

@ -0,0 +1,185 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048996,
"Position": {
"X": -618.43286,
"Y": 25.709108,
"Z": -153.91785
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051365,
"Position": {
"X": -738.3383,
"Y": 22.137896,
"Z": -85.404785
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": -580.44604,
"Y": 22.457048,
"Z": -3.286837
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-128
]
},
{
"DataId": 2014313,
"Position": {
"X": -581.75024,
"Y": 23.697266,
"Z": -2.9450073
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003683,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"Position": {
"X": -506.6366,
"Y": 22.817732,
"Z": 32.985153
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-64
]
},
{
"DataId": 2014314,
"Position": {
"X": -505.9129,
"Y": 23.88031,
"Z": 32.455933
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003683,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"Position": {
"X": -414.84598,
"Y": 20.493914,
"Z": 74.74898
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-32
]
},
{
"DataId": 2014315,
"Position": {
"X": -413.96204,
"Y": 21.896606,
"Z": 74.021484
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003683,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051365,
"Position": {
"X": -738.3383,
"Y": 22.137896,
"Z": -85.404785
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048996,
"Position": {
"X": -618.43286,
"Y": 25.709108,
"Z": -153.91785
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,52 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051366,
"Position": {
"X": -399.95422,
"Y": 20.145584,
"Z": -402.4262
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051367,
"Position": {
"X": -503.95975,
"Y": 29.007706,
"Z": -394.43048
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051368,
"Position": {
"X": -501.27414,
"Y": 28.982998,
"Z": -394.3084
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,53 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048982,
"Position": {
"X": 595.45276,
"Y": -137.17401,
"Z": 564.8126
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014250,
"Position": {
"X": 598.3214,
"Y": -137.19391,
"Z": 557.7018
},
"TerritoryId": 1189,
"InteractionType": "Instruction",
"Comment": "Inspect Flag, Red Head, Blue Head"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048982,
"Position": {
"X": 595.45276,
"Y": -137.17401,
"Z": 564.8126
},
"StopDistance": 7,
"TerritoryId": 1189,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,152 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048984,
"Position": {
"X": 673.06006,
"Y": -135.17874,
"Z": 577.47766
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -92.00457,
"Y": -212.91975,
"Z": 624.4216
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-128
]
},
{
"DataId": 1051049,
"Position": {
"X": -89.49426,
"Y": -213.64497,
"Z": 625.574
},
"StopDistance": 4,
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-128
]
},
{
"Position": {
"X": -122.188805,
"Y": -214.08376,
"Z": 674.54083
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-32
]
},
{
"DataId": 1051051,
"Position": {
"X": -122.88098,
"Y": -213.78055,
"Z": 673.39575
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"Position": {
"X": -142.65875,
"Y": -213.42487,
"Z": 608.63165
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-64
]
},
{
"DataId": 1051050,
"Position": {
"X": -143.63324,
"Y": -213.18237,
"Z": 609.0028
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048984,
"Position": {
"X": 673.06006,
"Y": -135.17874,
"Z": 577.47766
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,116 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048988,
"Position": {
"X": 584.4967,
"Y": -143.46597,
"Z": 648.64575
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014369,
"Position": {
"X": 328.20618,
"Y": -157.21375,
"Z": 429.4651
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
16
]
},
{
"DataId": 2014368,
"Position": {
"X": 323.7201,
"Y": -157.76306,
"Z": 434.19543
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2014256,
"Position": {
"X": 139.17749,
"Y": -162.24927,
"Z": 679.95715
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2014257,
"Position": {
"X": 144.67078,
"Y": -163.25635,
"Z": 663.84375
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048988,
"Position": {
"X": 584.4967,
"Y": -143.46597,
"Z": 648.64575
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,163 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048987,
"Position": {
"X": 596.97876,
"Y": -142.60623,
"Z": 441.7334
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051053,
"Position": {
"X": 257.1908,
"Y": -165.78062,
"Z": 146.74597
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_KINGZD105_05104_SYSTEM_100_004",
"Yes": true
}
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": 272.75598,
"Y": -161.77667,
"Z": 117.129616
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Mount": true,
"DisableNavmesh": true
},
{
"Position": {
"X": 255.7041,
"Y": -161.34225,
"Z": 101.95002
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"DisableNavmesh": true
},
{
"DataId": 1051053,
"Position": {
"X": 218.3051,
"Y": -160.72849,
"Z": 78.95244
},
"TerritoryId": 1189,
"InteractionType": "WaitForNpcAtPosition",
"DisableNavmesh": true
},
{
"DataId": 1051053,
"Position": {
"X": 168.0448,
"Y": -158.83824,
"Z": 31.171473
},
"TerritoryId": 1189,
"InteractionType": "WaitForNpcAtPosition",
"DisableNavmesh": true
},
{
"DataId": 1051053,
"Position": {
"X": 144.163,
"Y": -156.25887,
"Z": -57.818764
},
"TerritoryId": 1189,
"InteractionType": "WaitForNpcAtPosition",
"DisableNavmesh": true
},
{
"Position": {
"X": 83.32947,
"Y": -157.21858,
"Z": -67.64331
},
"StopDistance": 1,
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"DisableNavmesh": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051054,
"Position": {
"X": 83.32947,
"Y": -157.21858,
"Z": -67.64331
},
"TerritoryId": 1189,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 2014259,
"Position": {
"X": 65.049194,
"Y": -184.1612,
"Z": -70.237305
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"DisableNavmesh": true,
"Mount": false
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048987,
"Position": {
"X": 596.97876,
"Y": -142.60623,
"Z": 441.7334
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,92 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051055,
"Position": {
"X": 563.5918,
"Y": -144.04688,
"Z": 702.26587
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -45.87303,
"Y": -174.86252,
"Z": 649.25995
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 1051056,
"Position": {
"X": -49.149292,
"Y": -174.58734,
"Z": 651.4839
},
"StopDistance": 5,
"TerritoryId": 1189,
"InteractionType": "Interact",
"DisableNavmesh": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051056,
"Position": {
"X": -49.149292,
"Y": -174.58734,
"Z": 651.4839
},
"StopDistance": 5,
"TerritoryId": 1189,
"InteractionType": "Say",
"ChatMessage": {
"Key": "TEXT_KINGZD106_05105_SAYTODO_000_011"
}
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051055,
"Position": {
"X": 563.5918,
"Y": -144.04688,
"Z": 702.26587
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,90 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048991,
"Position": {
"X": 211.10852,
"Y": -160.27475,
"Z": 432.4253
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 178.30347,
"Y": -195.42366,
"Z": 241.28633
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 2014260,
"Position": {
"X": 177.44714,
"Y": -194.11005,
"Z": 239.61243
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003667,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2014261,
"Position": {
"X": 164.47693,
"Y": -194.2627,
"Z": 237.50659
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003667,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048991,
"Position": {
"X": 211.10852,
"Y": -160.27475,
"Z": 432.4253
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,107 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048998,
"Position": {
"X": 220.29443,
"Y": -156.80957,
"Z": 457.02295
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051408,
"Position": {
"X": 631.2809,
"Y": -137.12654,
"Z": 509.6665
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1048974,
"Position": {
"X": 621.51514,
"Y": -135.12726,
"Z": 531.1207
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 1048989,
"Position": {
"X": 523.4302,
"Y": -135.12724,
"Z": 578.14905
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048998,
"Position": {
"X": 220.29443,
"Y": -156.80957,
"Z": 457.02295
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true,
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZD108_05107_Q1_000_000",
"Answer": "TEXT_KINGZD108_05107_A1_000_001"
}
]
}
]
}
]
}

View File

@ -0,0 +1,60 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051067,
"Position": {
"X": -372.70166,
"Y": -162.24626,
"Z": 546.25757
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest",
"Fly": true
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014262,
"Position": {
"X": -443.83917,
"Y": -158.1903,
"Z": 343.34326
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17743,
17744,
17745
],
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051067,
"Position": {
"X": -372.70166,
"Y": -162.24626,
"Z": 546.25757
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,97 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048997,
"Position": {
"X": -363.9124,
"Y": -162.23582,
"Z": 550.01135
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest",
"Fly": true
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051387,
"Position": {
"X": -383.4745,
"Y": -166.23917,
"Z": 662.95874
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": -433.617,
"Y": -156.24463,
"Z": 776.9229
},
"TerritoryId": 1189,
"InteractionType": "Instruction",
"Comment": "TODO Needs item use; manual for now (also unsure on health percent)",
"EnemySpawnType": "OverworldEnemies",
"ComplexCombatData": [
{
"DataId": 17267,
"MinimumKillCount": 2,
"ItemId": 2003669,
"ItemUseHealthMaxPercent": 25,
"RewardItemId": 2003670,
"RewardItemCount": 2
}
],
"Fly": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051387,
"Position": {
"X": -383.4745,
"Y": -166.23917,
"Z": 662.95874
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048997,
"Position": {
"X": -363.9124,
"Y": -162.23582,
"Z": 550.01135
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,97 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050873,
"Position": {
"X": -455.10034,
"Y": 17.027555,
"Z": -478.8739
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050874,
"Position": {
"X": -508.87314,
"Y": 29.61908,
"Z": -350.1183
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1050876,
"Position": {
"X": -455.92438,
"Y": 30.374397,
"Z": -423.148
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 1050875,
"Position": {
"X": -391.01245,
"Y": 28.068893,
"Z": -365.0111
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050874,
"Position": {
"X": -508.87314,
"Y": 29.61908,
"Z": -350.1183
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true,
"NextQuestId": 5112
}
]
}
]
}

View File

@ -0,0 +1,77 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050874,
"Position": {
"X": -508.87314,
"Y": 29.61908,
"Z": -350.1183
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZD203_05112_Q1_000_000",
"Answer": "TEXT_KINGZD203_05112_A1_000_001"
}
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050877,
"Position": {
"X": -39.871887,
"Y": 0.35915944,
"Z": -312.1233
},
"StopDistance": 1,
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2014144,
"Position": {
"X": -43.10681,
"Y": 0.503479,
"Z": -312.36743
},
"TerritoryId": 1189,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050878,
"Position": {
"X": 475.30322,
"Y": 18.197147,
"Z": -326.95508
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"Fly": true,
"NextQuestId": 5113
}
]
}
]
}

View File

@ -0,0 +1,90 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050878,
"Position": {
"X": 475.30322,
"Y": 18.197147,
"Z": -326.95508
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050879,
"Position": {
"X": 595.7274,
"Y": 18.8562,
"Z": -85.55737
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": 705.4334,
"Y": 28.302685,
"Z": 2.3153868
},
"StopDistance": 1,
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
17671,
17672
],
"Fly": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1050881,
"Position": {
"X": 595.6052,
"Y": 18.84498,
"Z": -85.55737
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050882,
"Position": {
"X": -408.31616,
"Y": 20.383144,
"Z": -399.28284
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Iq Br'aax"
}
]
}
]
}

View File

@ -0,0 +1,125 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050888,
"Position": {
"X": 515.58704,
"Y": -146.73436,
"Z": 477.0122
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014147,
"Position": {
"X": 440.66528,
"Y": -150.77448,
"Z": 427.7256
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17674
],
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2014148,
"Position": {
"X": 395.4679,
"Y": -152.42242,
"Z": 372.7931
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17674
],
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2014149,
"Position": {
"X": 267.9026,
"Y": -160.05188,
"Z": 354.45166
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2014150,
"Position": {
"X": 236.95728,
"Y": -167.52887,
"Z": 307.97278
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050890,
"Position": {
"X": 661.7991,
"Y": -137.174,
"Z": 557.76294
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true,
"NextQuestId": 5116
}
]
}
]
}

View File

@ -0,0 +1,80 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1048985,
"Position": {
"X": 595.11694,
"Y": -143.83003,
"Z": 710.68884
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": -61.729744,
"Y": -212.69492,
"Z": 523.0088
},
"TerritoryId": 1189,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 2014151,
"Position": {
"X": -60.95984,
"Y": -210.83392,
"Z": 522.02637
},
"TerritoryId": 1189,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true,
"NextQuestId": 5117
}
]
}
]
}

View File

@ -0,0 +1,86 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014153,
"Position": {
"X": 500.5111,
"Y": -150.04199,
"Z": 704.12744
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003624,
"GroundTarget": true,
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2014152,
"Position": {
"X": 486.90015,
"Y": -150.04199,
"Z": 355.8556
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003624,
"GroundTarget": true,
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true,
"NextQuestId": 5118
}
]
}
]
}

View File

@ -0,0 +1,219 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050897,
"Position": {
"X": 755.94714,
"Y": -132.59648,
"Z": 505.94324
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZD401_05118_Q1_000_000",
"Answer": "TEXT_KINGZD401_05118_A1_000_001"
}
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1050901,
"Position": {
"X": 627.25244,
"Y": -137.174,
"Z": 591.8517
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1050900,
"Position": {
"X": 575.55493,
"Y": -137.174,
"Z": 540.0929
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 1048989,
"Position": {
"X": 523.4302,
"Y": -135.12724,
"Z": 578.14905
},
"TerritoryId": 1189,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 5,
"Steps": [
{
"DataId": 1050891,
"Position": {
"X": 662.8672,
"Y": -137.17401,
"Z": 556.115
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 6,
"Steps": [
{
"DataId": 1050904,
"Position": {
"X": 263.84375,
"Y": -157.31726,
"Z": 738.6129
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 7,
"Steps": [
{
"DataId": 2014154,
"Position": {
"X": 255.9701,
"Y": -150.68292,
"Z": 807.64465
},
"TerritoryId": 1189,
"InteractionType": "UseItem",
"ItemId": 2003625,
"Fly": true
}
]
},
{
"Sequence": 8,
"Steps": [
{
"DataId": 2014248,
"Position": {
"X": 255.9701,
"Y": -150.68292,
"Z": 807.64465
},
"TerritoryId": 1189,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17675
]
}
]
},
{
"Sequence": 9,
"Steps": [
{
"DataId": 1050904,
"Position": {
"X": 263.84375,
"Y": -157.31726,
"Z": 738.6129
},
"TerritoryId": 1189,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050903,
"Position": {
"X": 663.9962,
"Y": -137.17401,
"Z": 554.589
},
"TerritoryId": 1189,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Yak T'el - Mamook",
"Fly": true
}
]
}
]
}

View File

@ -13,7 +13,7 @@
"Z": -52.99463 "Z": -52.99463
}, },
"TerritoryId": 1186, "TerritoryId": 1186,
"InteractionType": "Interact", "InteractionType": "AcceptQuest",
"Comment": "Quest is completed instantly" "Comment": "Quest is completed instantly"
} }
] ]

View File

@ -13,7 +13,7 @@
"Z": -38.132385 "Z": -38.132385
}, },
"TerritoryId": 1186, "TerritoryId": 1186,
"InteractionType": "Interact", "InteractionType": "AcceptQuest",
"Comment": "Quest is completed instantly" "Comment": "Quest is completed instantly"
} }
] ]

View File

@ -1,11 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Questionable.Model.V1; using Questionable.Model.V1;
#if RELEASE
namespace Questionable.QuestPaths; namespace Questionable.QuestPaths;
[SuppressMessage("ReSharper", "PartialTypeWithSinglePart", Justification = "Required for RELEASE")]
public static partial class AssemblyQuestLoader public static partial class AssemblyQuestLoader
{ {
public static IReadOnlyDictionary<ushort, QuestRoot> GetQuests() => Quests; public static IReadOnlyDictionary<ushort, QuestRoot> GetQuests() =>
} #if RELEASE
Quests;
#else
new Dictionary<ushort, QuestRoot>();
#endif #endif
}

View File

@ -19,6 +19,9 @@
"type": "string" "type": "string"
} }
}, },
"Disabled": {
"type": "boolean"
},
"Comment": { "Comment": {
"type": "string" "type": "string"
}, },
@ -550,6 +553,10 @@
"description": "The enemy data id which is supposed to be killed", "description": "The enemy data id which is supposed to be killed",
"type": "integer" "type": "integer"
}, },
"MinimumKillCount": {
"description": "Overworld mobs: If this number of mobs has been killed, will wait a bit before attempting to pull another mob to see if the quest progresses",
"type": "integer"
},
"RewardItemId": { "RewardItemId": {
"type": "integer" "type": "integer"
}, },

View File

@ -5,6 +5,10 @@ namespace Questionable.Model.V1;
public sealed class ComplexCombatData public sealed class ComplexCombatData
{ {
public uint DataId { get; set; } public uint DataId { get; set; }
// TODO Use this
public uint? MinimumKillCount { get; set; }
public uint? RewardItemId { get; set; } public uint? RewardItemId { get; set; }
public int? RewardItemCount { get; set; } public int? RewardItemCount { get; set; }
public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>(); public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();

View File

@ -8,12 +8,12 @@ public sealed class ExcelRefConverter : JsonConverter<ExcelRef>
{ {
public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.TokenType == JsonTokenType.String) return reader.TokenType switch
return new ExcelRef(reader.GetString()!); {
else if (reader.TokenType == JsonTokenType.Number) JsonTokenType.String => ExcelRef.FromKey(reader.GetString()!),
return new ExcelRef(reader.GetUInt32()); JsonTokenType.Number => ExcelRef.FromRowId(reader.GetUInt32()),
else _ => null
return null; };
} }
public override void Write(Utf8JsonWriter writer, ExcelRef? value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, ExcelRef? value, JsonSerializerOptions options)

View File

@ -21,20 +21,16 @@ public class ExcelRef
Type = EType.RowId; Type = EType.RowId;
} }
/// <summary> private ExcelRef(string? stringValue, uint? rowIdValue, EType type)
/// Only used internally (not serialized) with specific values that have been read from the sheets already.
/// </summary>
private ExcelRef(string value, bool v)
{ {
if (!v) _stringValue = stringValue;
throw new ArgumentException(nameof(v)); _rowIdValue = rowIdValue;
Type = type;
_stringValue = value;
_rowIdValue = null;
Type = EType.RawString;
} }
public static ExcelRef FromSheetValue(string value) => new(value, true); public static ExcelRef FromKey(string value) => new(value, null, EType.Key);
public static ExcelRef FromRowId(uint rowId) => new(null, rowId, EType.RowId);
public static ExcelRef FromSheetValue(string value) => new(value, null, EType.RawString);
public EType Type { get; } public EType Type { get; }

View File

@ -6,6 +6,12 @@ public sealed class QuestRoot
{ {
public string Author { get; set; } = null!; public string Author { get; set; } = null!;
public List<string> Contributors { get; set; } = new(); public List<string> Contributors { get; set; } = new();
/// <summary>
/// This is only relevant for release builds.
/// </summary>
public bool Disabled { get; set; }
public string? Comment { get; set; } public string? Comment { get; set; }
public List<ushort> TerritoryBlacklist { get; set; } = new(); public List<ushort> TerritoryBlacklist { get; set; } = new();
public List<QuestSequence> QuestSequence { get; set; } = new(); public List<QuestSequence> QuestSequence { get; set; } = new();

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
@ -104,6 +105,7 @@ internal sealed class CombatController
return _condition[ConditionFlag.InCombat]; return _condition[ConditionFlag.InCombat];
} }
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private IGameObject? FindNextTarget() private IGameObject? FindNextTarget()
{ {
if (_currentFight == null) if (_currentFight == null)

View File

@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Windows; using Questionable.Windows;
@ -22,11 +20,12 @@ internal sealed class CommandHandler : IDisposable
private readonly QuestWindow _questWindow; private readonly QuestWindow _questWindow;
private readonly QuestSelectionWindow _questSelectionWindow; private readonly QuestSelectionWindow _questSelectionWindow;
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly GameFunctions _gameFunctions;
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController, public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController,
MovementController movementController, QuestRegistry questRegistry, MovementController movementController, QuestRegistry questRegistry,
ConfigWindow configWindow, DebugOverlay debugOverlay, QuestWindow questWindow, ConfigWindow configWindow, DebugOverlay debugOverlay, QuestWindow questWindow,
QuestSelectionWindow questSelectionWindow, ITargetManager targetManager) QuestSelectionWindow questSelectionWindow, ITargetManager targetManager, GameFunctions gameFunctions)
{ {
_commandManager = commandManager; _commandManager = commandManager;
_chatGui = chatGui; _chatGui = chatGui;
@ -38,6 +37,7 @@ internal sealed class CommandHandler : IDisposable
_questWindow = questWindow; _questWindow = questWindow;
_questSelectionWindow = questSelectionWindow; _questSelectionWindow = questSelectionWindow;
_targetManager = targetManager; _targetManager = targetManager;
_gameFunctions = gameFunctions;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand) _commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{ {
@ -77,7 +77,12 @@ internal sealed class CommandHandler : IDisposable
break; break;
case "which": case "which":
_questSelectionWindow.Open(_targetManager.Target); _questSelectionWindow.OpenForTarget(_targetManager.Target);
break;
case "z":
case "zone":
_questSelectionWindow.OpenForCurrentZone();
break; break;
default: default:
@ -96,7 +101,9 @@ internal sealed class CommandHandler : IDisposable
if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId)) if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
{ {
if (_questRegistry.TryGetQuest(questId, out Quest? quest)) if (_gameFunctions.IsQuestLocked(questId, 0))
_chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{ {
_debugOverlay.HighlightedQuest = questId; _debugOverlay.HighlightedQuest = questId;
_chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name})."); _chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name}).");

View File

@ -146,6 +146,7 @@ internal sealed class GameUiController : IDisposable
SelectIconStringPostSetup(addonSelectIconString, false); SelectIconStringPostSetup(addonSelectIconString, false);
} }
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private unsafe void SelectIconStringPostSetup(AddonSelectIconString* addonSelectIconString, bool checkAllSteps) private unsafe void SelectIconStringPostSetup(AddonSelectIconString* addonSelectIconString, bool checkAllSteps)
{ {
string? actualPrompt = addonSelectIconString->AtkUnitBase.AtkValues[3].ReadAtkString(); string? actualPrompt = addonSelectIconString->AtkUnitBase.AtkValues[3].ReadAtkString();

View File

@ -1,16 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.IO; using System.IO;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Data; using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
using Questionable.Validation;
namespace Questionable.Controller; namespace Questionable.Controller;
@ -18,25 +19,49 @@ internal sealed class QuestRegistry
{ {
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestData _questData; private readonly QuestData _questData;
private readonly QuestValidator _questValidator;
private readonly ILogger<QuestRegistry> _logger; private readonly ILogger<QuestRegistry> _logger;
private readonly Dictionary<ushort, Quest> _quests = new(); private readonly Dictionary<ushort, Quest> _quests = new();
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData, public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
ILogger<QuestRegistry> logger) QuestValidator questValidator, ILogger<QuestRegistry> logger)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_questData = questData; _questData = questData;
_questValidator = questValidator;
_logger = logger; _logger = logger;
} }
public IEnumerable<Quest> AllQuests => _quests.Values;
public int Count => _quests.Count; public int Count => _quests.Count;
public int ValidationIssueCount => _questValidator.IssueCount;
public int ValidationErrorCount => _questValidator.ErrorCount;
public void Reload() public void Reload()
{ {
_quests.Clear(); _quests.Clear();
#if RELEASE LoadQuestsFromAssembly();
LoadQuestsFromProjectDirectory();
try
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
}
catch (Exception e)
{
_logger.LogError(e,
"Failed to load all quests from user directory (some may have been successfully loaded)");
}
ValidateQuests();
_logger.LogInformation("Loaded {Count} quests", _quests.Count);
}
[Conditional("RELEASE")]
private void LoadQuestsFromAssembly()
{
_logger.LogInformation("Loading quests from assembly"); _logger.LogInformation("Loading quests from assembly");
foreach ((ushort questId, QuestRoot questRoot) in QuestPaths.AssemblyQuestLoader.GetQuests()) foreach ((ushort questId, QuestRoot questRoot) in QuestPaths.AssemblyQuestLoader.GetQuests())
@ -46,10 +71,15 @@ internal sealed class QuestRegistry
QuestId = questId, QuestId = questId,
Root = questRoot, Root = questRoot,
Info = _questData.GetQuestInfo(questId), Info = _questData.GetQuestInfo(questId),
ReadOnly = true,
}; };
_quests[questId] = quest; _quests[questId] = quest;
} }
#else }
[Conditional("DEBUG")]
private void LoadQuestsFromProjectDirectory()
{
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent; DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
if (solutionDirectory != null) if (solutionDirectory != null)
{ {
@ -75,29 +105,13 @@ internal sealed class QuestRegistry
} }
} }
} }
#endif
try
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load all quests from user directory (some may have been successfully loaded)");
}
#if !RELEASE
foreach (var quest in _quests.Values)
{
int missingSteps = quest.Root.QuestSequence.Where(x => x.Sequence < 255).Max(x => x.Sequence) - quest.Root.QuestSequence.Count(x => x.Sequence < 255) + 1;
if (missingSteps != 0)
_logger.LogWarning("Quest has missing steps: {QuestId} / {QuestName} → {Count}", quest.QuestId, quest.Info.Name, missingSteps);
}
#endif
_logger.LogInformation("Loaded {Count} quests", _quests.Count);
} }
private void ValidateQuests()
{
_questValidator.ClearIssues();
_questValidator.Validate(_quests.Values.Where(x => !x.ReadOnly));
}
private void LoadQuestFromStream(string fileName, Stream stream) private void LoadQuestFromStream(string fileName, Stream stream)
{ {
@ -111,6 +125,7 @@ internal sealed class QuestRegistry
QuestId = questId.Value, QuestId = questId.Value,
Root = JsonSerializer.Deserialize<QuestRoot>(stream)!, Root = JsonSerializer.Deserialize<QuestRoot>(stream)!,
Info = _questData.GetQuestInfo(questId.Value), Info = _questData.GetQuestInfo(questId.Value),
ReadOnly = false,
}; };
_quests[questId.Value] = quest; _quests[questId.Value] = quest;
} }

View File

@ -18,24 +18,33 @@ internal static class NextQuest
if (step.NextQuestId == null) if (step.NextQuestId == null)
return null; return null;
if (step.NextQuestId.Value == quest.QuestId)
return null;
return serviceProvider.GetRequiredService<SetQuest>() return serviceProvider.GetRequiredService<SetQuest>()
.With(step.NextQuestId.Value); .With(step.NextQuestId.Value, quest.QuestId);
} }
} }
internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, ILogger<SetQuest> logger) : ITask internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, GameFunctions gameFunctions, ILogger<SetQuest> logger) : ITask
{ {
public ushort NextQuestId { get; set; } public ushort NextQuestId { get; set; }
public ushort CurrentQuestId { get; set; }
public ITask With(ushort nextQuestId) public ITask With(ushort nextQuestId, ushort currentQuestId)
{ {
NextQuestId = nextQuestId; NextQuestId = nextQuestId;
CurrentQuestId = currentQuestId;
return this; return this;
} }
public bool Start() public bool Start()
{ {
if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest)) if (gameFunctions.IsQuestLocked(NextQuestId, CurrentQuestId))
{
logger.LogInformation("Can't set next quest to {QuestId}, quest is locked", NextQuestId);
}
else if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest))
{ {
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name); logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name);
questController.SetNextQuest(quest); questController.SetNextQuest(quest);

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
using AethernetShortcut = Questionable.Controller.Steps.Shared.AethernetShortcut;
namespace Questionable.Controller.Steps.Interactions; namespace Questionable.Controller.Steps.Interactions;
@ -13,7 +16,7 @@ internal static class UseItem
{ {
public const int VesperBayAetheryteTicket = 30362; public const int VesperBayAetheryteTicket = 30362;
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory internal sealed class Factory(IServiceProvider serviceProvider, ILogger<Factory> logger) : ITaskFactory
{ {
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{ {
@ -22,6 +25,16 @@ internal static class UseItem
ArgumentNullException.ThrowIfNull(step.ItemId); ArgumentNullException.ThrowIfNull(step.ItemId);
if (step.ItemId == VesperBayAetheryteTicket)
{
unsafe
{
InventoryManager* inventoryManager = InventoryManager.Instance();
if (inventoryManager->GetInventoryItemCount(step.ItemId.Value) == 0)
return CreateVesperBayFallbackTask();
}
}
var unmount = serviceProvider.GetRequiredService<UnmountTask>(); var unmount = serviceProvider.GetRequiredService<UnmountTask>();
if (step.GroundTarget == true) if (step.GroundTarget == true)
{ {
@ -47,6 +60,23 @@ internal static class UseItem
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step) public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
private IEnumerable<ITask> CreateVesperBayFallbackTask()
{
logger.LogWarning("No vesper bay aetheryte tickets in inventory, navigating via ferry in Limsa instead");
uint npcId = 1003540;
ushort territoryId = 129;
Vector3 destination = new(-360.9217f, 8f, 38.92566f);
yield return serviceProvider.GetRequiredService<AetheryteShortcut.UseAetheryteShortcut>()
.With(null, EAetheryteLocation.Limsa, territoryId);
yield return serviceProvider.GetRequiredService<AethernetShortcut.UseAethernetShortcut>()
.With(EAetheryteLocation.Limsa, EAetheryteLocation.LimsaArcanist);
yield return serviceProvider.GetRequiredService<Move.MoveInternal>()
.With(territoryId, destination, dataId: npcId, sprint: false);
yield return serviceProvider.GetRequiredService<Interact.DoInteract>()
.With(npcId, true);
}
} }
internal abstract class UseItemBase(ILogger logger) : ITask internal abstract class UseItemBase(ILogger logger) : ITask

View File

@ -45,7 +45,7 @@ internal static class AetheryteShortcut
{ {
private DateTime _continueAt; private DateTime _continueAt;
public QuestStep Step { get; set; } = null!; public QuestStep? Step { get; set; }
public EAetheryteLocation TargetAetheryte { get; set; } public EAetheryteLocation TargetAetheryte { get; set; }
/// <summary> /// <summary>
@ -54,7 +54,7 @@ internal static class AetheryteShortcut
/// </summary> /// </summary>
public ushort ExpectedTerritoryId { get; set; } public ushort ExpectedTerritoryId { get; set; }
public ITask With(QuestStep step, EAetheryteLocation targetAetheryte, ushort expectedTerritoryId) public ITask With(QuestStep? step, EAetheryteLocation targetAetheryte, ushort expectedTerritoryId)
{ {
Step = step; Step = step;
TargetAetheryte = targetAetheryte; TargetAetheryte = targetAetheryte;
@ -66,7 +66,7 @@ internal static class AetheryteShortcut
{ {
_continueAt = DateTime.Now.AddSeconds(8); _continueAt = DateTime.Now.AddSeconds(8);
ushort territoryType = clientState.TerritoryType; ushort territoryType = clientState.TerritoryType;
if (ExpectedTerritoryId == territoryType) if (Step != null && ExpectedTerritoryId == territoryType)
{ {
if (Step.SkipIf.Contains(ESkipCondition.AetheryteShortcutIfInSameTerritory)) if (Step.SkipIf.Contains(ESkipCondition.AetheryteShortcutIfInSameTerritory))
{ {

View File

@ -99,15 +99,7 @@ internal static class Move
if (actualDistance > distance) if (actualDistance > distance)
{ {
yield return serviceProvider.GetRequiredService<MoveInternal>() yield return serviceProvider.GetRequiredService<MoveInternal>()
.With(Destination, m => .With(Step, Destination);
{
m.NavigateTo(EMovementType.Quest, Step.DataId, Destination,
fly: Step.Fly == true && gameFunctions.IsFlyingUnlocked(Step.TerritoryId),
sprint: Step.Sprint != false,
stopDistance: distance,
ignoreDistanceToObject: Step.IgnoreDistanceToObject == true,
land: Step.Land == true);
});
} }
} }
else else
@ -116,14 +108,7 @@ internal static class Move
if (actualDistance > distance) if (actualDistance > distance)
{ {
yield return serviceProvider.GetRequiredService<MoveInternal>() yield return serviceProvider.GetRequiredService<MoveInternal>()
.With(Destination, m => .With(Step, Destination);
{
m.NavigateTo(EMovementType.Quest, Step.DataId, [Destination],
fly: Step.Fly == true && gameFunctions.IsFlyingUnlockedInCurrentZone(),
sprint: Step.Sprint != false,
stopDistance: distance,
land: Step.Land == true);
});
} }
} }
@ -132,22 +117,68 @@ internal static class Move
} }
} }
internal sealed class MoveInternal(MovementController movementController, ILogger<MoveInternal> logger) : ITask internal sealed class MoveInternal(
MovementController movementController,
GameFunctions gameFunctions,
ILogger<MoveInternal> logger) : ITask
{ {
public Action<MovementController> StartAction { get; set; } = null!; public Action StartAction { get; set; } = null!;
public Vector3 Destination { get; set; } public Vector3 Destination { get; set; }
public ITask With(Vector3 destination, Action<MovementController> startAction) public ITask With(QuestStep step, Vector3 destination)
{
return With(
territoryId: step.TerritoryId,
destination: destination,
stopDistance: step.StopDistance,
dataId: step.DataId,
disableNavMesh: step.DisableNavmesh,
sprint: step.Sprint != false,
fly: step.Fly == true,
land: step.Land == true,
ignoreDistanceToObject: step.IgnoreDistanceToObject == true);
}
public ITask With(ushort territoryId, Vector3 destination, float? stopDistance = null, uint? dataId = null,
bool disableNavMesh = false, bool sprint = true, bool fly = false, bool land = false,
bool ignoreDistanceToObject = false)
{ {
Destination = destination; Destination = destination;
StartAction = startAction;
if (!gameFunctions.IsFlyingUnlocked(territoryId))
{
fly = false;
land = false;
}
if (!disableNavMesh)
{
StartAction = () =>
movementController.NavigateTo(EMovementType.Quest, dataId, Destination,
fly: fly,
sprint: sprint,
stopDistance: stopDistance,
ignoreDistanceToObject: ignoreDistanceToObject,
land: land);
}
else
{
StartAction = () =>
movementController.NavigateTo(EMovementType.Quest, dataId, [Destination],
fly: fly,
sprint: sprint,
stopDistance: stopDistance,
ignoreDistanceToObject: ignoreDistanceToObject,
land: land);
}
return this; return this;
} }
public bool Start() public bool Start()
{ {
logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture)); logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture));
StartAction(movementController); StartAction();
return true; return true;
} }

View File

@ -29,7 +29,8 @@ internal sealed class DalamudInitializer : IDisposable
QuestWindow questWindow, QuestWindow questWindow,
DebugOverlay debugOverlay, DebugOverlay debugOverlay,
ConfigWindow configWindow, ConfigWindow configWindow,
QuestSelectionWindow questSelectionWindow) QuestSelectionWindow questSelectionWindow,
QuestValidationWindow questValidationWindow)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_framework = framework; _framework = framework;
@ -44,6 +45,7 @@ internal sealed class DalamudInitializer : IDisposable
_windowSystem.AddWindow(configWindow); _windowSystem.AddWindow(configWindow);
_windowSystem.AddWindow(debugOverlay); _windowSystem.AddWindow(debugOverlay);
_windowSystem.AddWindow(questSelectionWindow); _windowSystem.AddWindow(questSelectionWindow);
_windowSystem.AddWindow(questValidationWindow);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw; _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle; _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
@ -21,6 +22,8 @@ using Lumina.Excel.CustomSheets;
using Lumina.Excel.GeneratedSheets2; using Lumina.Excel.GeneratedSheets2;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
using Action = Lumina.Excel.GeneratedSheets2.Action; using Action = Lumina.Excel.GeneratedSheets2.Action;
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara; using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
@ -45,6 +48,7 @@ internal sealed unsafe class GameFunctions
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ILogger<GameFunctions> _logger; private readonly ILogger<GameFunctions> _logger;
@ -55,6 +59,7 @@ internal sealed unsafe class GameFunctions
ICondition condition, ICondition condition,
IClientState clientState, IClientState clientState,
QuestRegistry questRegistry, QuestRegistry questRegistry,
QuestData questData,
IGameGui gameGui, IGameGui gameGui,
Configuration configuration, Configuration configuration,
ILogger<GameFunctions> logger) ILogger<GameFunctions> logger)
@ -65,6 +70,7 @@ internal sealed unsafe class GameFunctions
_condition = condition; _condition = condition;
_clientState = clientState; _clientState = clientState;
_questRegistry = questRegistry; _questRegistry = questRegistry;
_questData = questData;
_gameGui = gameGui; _gameGui = gameGui;
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
@ -246,7 +252,7 @@ internal sealed unsafe class GameFunctions
public bool IsQuestAcceptedOrComplete(ushort questId) public bool IsQuestAcceptedOrComplete(ushort questId)
{ {
return QuestManager.IsQuestComplete(questId) || IsQuestAccepted(questId); return IsQuestComplete(questId) || IsQuestAccepted(questId);
} }
public bool IsQuestAccepted(ushort questId) public bool IsQuestAccepted(ushort questId)
@ -255,6 +261,39 @@ internal sealed unsafe class GameFunctions
return questManager->IsQuestAccepted(questId); return questManager->IsQuestAccepted(questId);
} }
[SuppressMessage("Performance", "CA1822")]
public bool IsQuestComplete(ushort questId)
{
return QuestManager.IsQuestComplete(questId);
}
public bool IsQuestLocked(ushort questId, ushort? extraCompletedQuest = null)
{
var questInfo = _questData.GetQuestInfo(questId);
if (questInfo.QuestLocks.Count > 0)
{
var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x == extraCompletedQuest);
if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.All && questInfo.QuestLocks.Count == completedQuests)
return true;
else if (questInfo.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
return true;
}
if (questInfo.PreviousQuests.Count > 0)
{
var completedQuests = questInfo.PreviousQuests.Count(x => IsQuestComplete(x) || x == extraCompletedQuest);
if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.All &&
questInfo.PreviousQuests.Count == completedQuests)
return false;
else if (questInfo.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne && completedQuests > 0)
return false;
else
return true;
}
return false;
}
public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex) public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
{ {
subIndex = 0; subIndex = 0;

View File

@ -1,4 +1,5 @@
using System.Linq; using System.Collections.Generic;
using System.Linq;
using Questionable.Model.V1; using Questionable.Model.V1;
namespace Questionable.Model; namespace Questionable.Model;
@ -8,7 +9,22 @@ internal sealed class Quest
public required ushort QuestId { get; init; } public required ushort QuestId { get; init; }
public required QuestRoot Root { get; init; } public required QuestRoot Root { get; init; }
public required QuestInfo Info { get; init; } public required QuestInfo Info { get; init; }
public required bool ReadOnly { get; init; }
public QuestSequence? FindSequence(byte currentSequence) public QuestSequence? FindSequence(byte currentSequence)
=> Root.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence); => Root.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence);
public IEnumerable<QuestSequence> AllSequences() => Root.QuestSequence;
public IEnumerable<(QuestSequence Sequence, int StepId, QuestStep Step)> AllSteps()
{
foreach (var sequence in Root.QuestSequence)
{
for (int i = 0; i < sequence.Steps.Count; ++i)
{
var step = sequence.Steps[i];
yield return (sequence, i, step);
}
}
}
} }

View File

@ -1,5 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using JetBrains.Annotations;
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest; using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
namespace Questionable.Model; namespace Questionable.Model;
@ -13,6 +17,11 @@ internal sealed class QuestInfo
Level = quest.ClassJobLevel0; Level = quest.ClassJobLevel0;
IssuerDataId = quest.IssuerStart; IssuerDataId = quest.IssuerStart;
IsRepeatable = quest.IsRepeatable; IsRepeatable = quest.IsRepeatable;
PreviousQuests = quest.PreviousQuest.Select(x => (ushort)(x.Row & 0xFFFF)).Where(x => x != 0).ToImmutableList();
PreviousQuestJoin = (QuestJoin)quest.PreviousQuestJoin;
QuestLocks = quest.QuestLock.Select(x => (ushort)(x.Row & 0xFFFFF)).Where(x => x != 0).ToImmutableList();
QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1;
} }
public ushort QuestId { get; } public ushort QuestId { get; }
@ -20,7 +29,20 @@ internal sealed class QuestInfo
public ushort Level { get; } public ushort Level { get; }
public uint IssuerDataId { get; } public uint IssuerDataId { get; }
public bool IsRepeatable { get; } public bool IsRepeatable { get; }
public ImmutableList<ushort> PreviousQuests { get; }
public QuestJoin PreviousQuestJoin { get; }
public bool IsMainScenarioQuest { get; }
public ImmutableList<ushort> QuestLocks { get; set; }
public QuestJoin QuestLockJoin { get; set; }
public string SimplifiedName => Name public string SimplifiedName => Name
.TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' '); .TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' ');
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
public enum QuestJoin : byte
{
None = 0,
All = 1,
AtLeastOne = 2,
}
} }

View File

@ -1,6 +1,6 @@
<Project Sdk="Dalamud.NET.Sdk/9.0.2"> <Project Sdk="Dalamud.NET.Sdk/9.0.2">
<PropertyGroup> <PropertyGroup>
<Version>1.7</Version> <Version>1.8</Version>
<OutputPath>dist</OutputPath> <OutputPath>dist</OutputPath>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap> <PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
</PropertyGroup> </PropertyGroup>

View File

@ -16,6 +16,8 @@ using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Interactions;
using Questionable.Data; using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Validation;
using Questionable.Validation.Validators;
using Questionable.Windows; using Questionable.Windows;
using Action = Questionable.Controller.Steps.Interactions.Action; using Action = Questionable.Controller.Steps.Interactions.Action;
@ -128,6 +130,14 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ConfigWindow>(); serviceCollection.AddSingleton<ConfigWindow>();
serviceCollection.AddSingleton<DebugOverlay>(); serviceCollection.AddSingleton<DebugOverlay>();
serviceCollection.AddSingleton<QuestSelectionWindow>(); serviceCollection.AddSingleton<QuestSelectionWindow>();
serviceCollection.AddSingleton<QuestValidationWindow>();
serviceCollection.AddSingleton<QuestValidator>();
serviceCollection.AddSingleton<IQuestValidator, QuestDisabledValidator>();
serviceCollection.AddSingleton<IQuestValidator, BasicSequenceValidator>();
serviceCollection.AddSingleton<IQuestValidator, UniqueStartStopValidator>();
serviceCollection.AddSingleton<IQuestValidator, NextQuestValidator>();
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
serviceCollection.AddSingleton<CommandHandler>(); serviceCollection.AddSingleton<CommandHandler>();
serviceCollection.AddSingleton<DalamudInitializer>(); serviceCollection.AddSingleton<DalamudInitializer>();

View File

@ -0,0 +1,7 @@
namespace Questionable.Validation;
internal enum EIssueSeverity
{
None,
Error,
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
using Questionable.Model;
namespace Questionable.Validation;
internal interface IQuestValidator
{
IEnumerable<ValidationIssue> Validate(Quest quest);
}

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Questionable.Model;
namespace Questionable.Validation;
internal sealed class QuestValidator
{
private readonly IReadOnlyList<IQuestValidator> _validators;
private readonly ILogger<QuestValidator> _logger;
private List<ValidationIssue> _validationIssues = new();
public QuestValidator(IEnumerable<IQuestValidator> validators, ILogger<QuestValidator> logger)
{
_validators = validators.ToList();
_logger = logger;
_logger.LogInformation("Validators: {Validators}",
string.Join(", ", _validators.Select(x => x.GetType().Name)));
}
public IReadOnlyList<ValidationIssue> Issues => _validationIssues;
public int IssueCount => _validationIssues.Count;
public int ErrorCount => _validationIssues.Count(x => x.Severity == EIssueSeverity.Error);
public void ClearIssues() => _validationIssues.Clear();
public void Validate(IEnumerable<Quest> quests)
{
Task.Run(() =>
{
foreach (var quest in quests)
{
foreach (var validator in _validators)
{
foreach (var issue in validator.Validate(quest))
{
_logger.LogWarning(
"Validation failed: {QuestId} ({QuestName}) / {QuestSequence} / {QuestStep} - {Description}",
issue.QuestId, quest.Info.Name, issue.Sequence, issue.Step, issue.Description);
_validationIssues.Add(issue);
}
}
}
_validationIssues = _validationIssues.OrderBy(x => x.QuestId)
.ThenBy(x => x.Sequence)
.ThenBy(x => x.Step)
.ThenBy(x => x.Description)
.ToList();
});
}
}

View File

@ -0,0 +1,10 @@
namespace Questionable.Validation;
internal sealed record ValidationIssue
{
public required ushort QuestId { get; init; }
public required byte? Sequence { get; init; }
public required int? Step { get; init; }
public required EIssueSeverity Severity { get; init; }
public required string Description { get; init; }
}

View File

@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.Linq;
using Questionable.Model;
using Questionable.Model.V1;
namespace Questionable.Validation.Validators;
internal sealed class BasicSequenceValidator : IQuestValidator
{
/// <summary>
/// A quest should have sequences from 0 to N, and (if more than 'AcceptQuest' exists), a 255 sequence.
/// </summary>
public IEnumerable<ValidationIssue> Validate(Quest quest)
{
var sequences = quest.Root.QuestSequence;
var foundStart = sequences.FirstOrDefault(x => x.Sequence == 0);
if (foundStart == null)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = 0,
Step = null,
Severity = EIssueSeverity.Error,
Description = "Missing quest start",
};
yield break;
}
int maxSequence = sequences.Select(x => x.Sequence)
.Where(x => x != 255)
.Max();
for (int i = 0; i < maxSequence; i++)
{
var foundSequences = sequences.Where(x => x.Sequence == i).ToList();
var issue = ValidateSequences(quest, i, foundSequences);
if (issue != null)
yield return issue;
}
// some quests finish instantly
if (maxSequence > 0 || foundStart.Steps.Count > 1)
{
var foundEnding = sequences.Where(x => x.Sequence == 255).ToList();
var endingIssue = ValidateSequences(quest, 255, foundEnding);
if (endingIssue != null)
yield return endingIssue;
}
}
private static ValidationIssue? ValidateSequences(Quest quest, int sequenceNo, List<QuestSequence> foundSequences)
{
if (foundSequences.Count == 0)
{
return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = (byte)sequenceNo,
Step = null,
Severity = EIssueSeverity.Error,
Description = "Missing sequence",
};
}
else if (foundSequences.Count == 2)
{
return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = (byte)sequenceNo,
Step = null,
Severity = EIssueSeverity.Error,
Description = "Duplicate sequence",
};
}
else
return null;
}
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Questionable.Controller.Utils;
using Questionable.Model;
namespace Questionable.Validation.Validators;
internal sealed class CompletionFlagsValidator : IQuestValidator
{
public IEnumerable<ValidationIssue> Validate(Quest quest)
{
foreach (var sequence in quest.AllSequences())
{
var mappedCompletionFlags = sequence.Steps
.Select(x =>
{
if (QuestWorkUtils.HasCompletionFlags(x.CompletionQuestVariablesFlags))
{
return Enumerable.Range(0, 6).Select(y =>
{
short? value = x.CompletionQuestVariablesFlags[y];
if (value == null || value.Value < 0)
return 0;
return (long)BitOperations.RotateLeft((ulong)value.Value, 8 * y);
})
.Sum();
}
else
return 0;
})
.ToList();
for (int i = 0; i < sequence.Steps.Count; ++i)
{
var flags = mappedCompletionFlags[i];
if (flags == 0)
continue;
if (mappedCompletionFlags.Count(x => x == flags) >= 2)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = (byte)sequence.Sequence,
Step = i,
Severity = EIssueSeverity.Error,
Description = $"Duplicate completion flags: {string.Join(", ", sequence.Steps[i].CompletionQuestVariablesFlags)}",
};
}
}
}
}
}

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using Questionable.Model;
namespace Questionable.Validation.Validators;
internal sealed class NextQuestValidator : IQuestValidator
{
public IEnumerable<ValidationIssue> Validate(Quest quest)
{
foreach (var invalidNextQuest in quest.AllSteps().Where(x => x.Step.NextQuestId == quest.QuestId))
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = (byte)invalidNextQuest.Sequence.Sequence,
Step = invalidNextQuest.StepId,
Severity = EIssueSeverity.Error,
Description = "Next quest should not reference itself",
};
}
}
}

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
using Questionable.Model;
namespace Questionable.Validation.Validators;
internal sealed class QuestDisabledValidator : IQuestValidator
{
public IEnumerable<ValidationIssue> Validate(Quest quest)
{
if (quest.Root.Disabled)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = null,
Step = null,
Severity = EIssueSeverity.None,
Description = "Quest is disabled",
};
}
}
}

View File

@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using Questionable.Model;
using Questionable.Model.V1;
namespace Questionable.Validation.Validators;
internal sealed class UniqueStartStopValidator : IQuestValidator
{
public IEnumerable<ValidationIssue> Validate(Quest quest)
{
var questAccepts = FindQuestStepsWithInteractionType(quest, EInteractionType.AcceptQuest)
.Where(x => x.Step.PickupQuestId == null)
.ToList();
foreach (var accept in questAccepts)
{
if (accept.Sequence.Sequence != 0 || accept.StepId != quest.FindSequence(0)!.Steps.Count - 1)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = (byte)accept.Sequence.Sequence,
Step = accept.StepId,
Severity = EIssueSeverity.Error,
Description = "Unexpected AcceptQuest step",
};
}
}
if (quest.FindSequence(0) != null && questAccepts.Count == 0)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = 0,
Step = null,
Severity = EIssueSeverity.Error,
Description = "No AcceptQuest step",
};
}
var questCompletes = FindQuestStepsWithInteractionType(quest, EInteractionType.CompleteQuest)
.Where(x => x.Step.TurnInQuestId == null)
.ToList();
foreach (var complete in questCompletes)
{
if (complete.Sequence.Sequence != 255 || complete.StepId != quest.FindSequence(255)!.Steps.Count - 1)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = (byte)complete.Sequence.Sequence,
Step = complete.StepId,
Severity = EIssueSeverity.Error,
Description = "Unexpected CompleteQuest step",
};
}
}
if (quest.FindSequence(255) != null && questCompletes.Count == 0)
{
yield return new ValidationIssue
{
QuestId = quest.QuestId,
Sequence = 255,
Step = null,
Severity = EIssueSeverity.Error,
Description = "No CompleteQuest step",
};
}
}
private static IEnumerable<(QuestSequence Sequence, int StepId, QuestStep Step)> FindQuestStepsWithInteractionType(
Quest quest, EInteractionType interactionType)
=> quest.AllSteps().Where(x => x.Step.InteractionType == interactionType);
}

View File

@ -2,6 +2,7 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
@ -18,10 +19,11 @@ internal sealed class DebugOverlay : Window
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ICondition _condition;
private readonly Configuration _configuration; private readonly Configuration _configuration;
public DebugOverlay(QuestController questController, QuestRegistry questRegistry, IGameGui gameGui, public DebugOverlay(QuestController questController, QuestRegistry questRegistry, IGameGui gameGui,
IClientState clientState, Configuration configuration) IClientState clientState, ICondition condition, Configuration configuration)
: base("Questionable Debug Overlay###QuestionableDebugOverlay", : base("Questionable Debug Overlay###QuestionableDebugOverlay",
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground |
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings, true) ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings, true)
@ -30,6 +32,7 @@ internal sealed class DebugOverlay : Window
_questRegistry = questRegistry; _questRegistry = questRegistry;
_gameGui = gameGui; _gameGui = gameGui;
_clientState = clientState; _clientState = clientState;
_condition = condition;
_configuration = configuration; _configuration = configuration;
Position = Vector2.Zero; Position = Vector2.Zero;
@ -44,7 +47,8 @@ internal sealed class DebugOverlay : Window
public override bool DrawConditions() public override bool DrawConditions()
{ {
return _configuration.Advanced.DebugOverlay && _clientState is return _configuration.Advanced.DebugOverlay && _clientState is
{ IsLoggedIn: true, LocalPlayer: not null, IsPvPExcludingDen: false }; { IsLoggedIn: true, LocalPlayer: not null, IsPvPExcludingDen: false } &&
!_condition[ConditionFlag.OccupiedInCutSceneEvent];
} }
public override void PreDraw() public override void PreDraw()

View File

@ -9,6 +9,7 @@ using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using ImGuiNET; using ImGuiNET;
using LLib.GameUI; using LLib.GameUI;
@ -29,13 +30,16 @@ internal sealed class QuestSelectionWindow : LWindow
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private List<QuestInfo> _quests = []; private List<QuestInfo> _quests = [];
private List<QuestInfo> _offeredQuests = []; private List<QuestInfo> _offeredQuests = [];
private bool _onlyAvailableQuests = true; private bool _onlyAvailableQuests = true;
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions, public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface) QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
TerritoryData territoryData, IClientState clientState)
: base($"Quest Selection{WindowId}") : base($"Quest Selection{WindowId}")
{ {
_questData = questData; _questData = questData;
@ -45,6 +49,8 @@ internal sealed class QuestSelectionWindow : LWindow
_questController = questController; _questController = questController;
_questRegistry = questRegistry; _questRegistry = questRegistry;
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_territoryData = territoryData;
_clientState = clientState;
Size = new Vector2(500, 200); Size = new Vector2(500, 200);
SizeCondition = ImGuiCond.Once; SizeCondition = ImGuiCond.Once;
@ -54,18 +60,15 @@ internal sealed class QuestSelectionWindow : LWindow
}; };
} }
public uint TargetId { get; private set; } public unsafe void OpenForTarget(IGameObject? gameObject)
public string TargetName { get; private set; } = string.Empty;
public unsafe void Open(IGameObject? gameObject)
{ {
if (gameObject != null) if (gameObject != null)
{ {
TargetId = gameObject.DataId; var targetId = gameObject.DataId;
TargetName = gameObject.Name.ToString(); var targetName = gameObject.Name.ToString();
WindowName = $"Quests starting with {TargetName} [{TargetId}]{WindowId}"; WindowName = $"Quests starting with {targetName} [{targetId}]{WindowId}";
_quests = _questData.GetAllByIssuerDataId(TargetId); _quests = _questData.GetAllByIssuerDataId(targetId);
if (_gameGui.TryGetAddonByName<AddonSelectIconString>("SelectIconString", out var addonSelectIconString)) if (_gameGui.TryGetAddonByName<AddonSelectIconString>("SelectIconString", out var addonSelectIconString))
{ {
var answers = GameUiController.GetChoices(addonSelectIconString); var answers = GameUiController.GetChoices(addonSelectIconString);
@ -85,6 +88,34 @@ internal sealed class QuestSelectionWindow : LWindow
IsOpen = _quests.Count > 0; IsOpen = _quests.Count > 0;
} }
public unsafe void OpenForCurrentZone()
{
var territoryId = _clientState.TerritoryType;
var territoryName = _territoryData.GetNameAndId(territoryId);
WindowName = $"Quests starting in {territoryName}{WindowId}";
_quests = _questRegistry.AllQuests
.Where(x => x.FindSequence(0)?.FindStep(0)?.TerritoryId == territoryId)
.Select(x => _questData.GetQuestInfo(x.QuestId))
.ToList();
foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers)
{
ushort questId = (ushort)(unacceptedQuest.ObjectiveId & 0xFFFF);
if (_quests.All(q => q.QuestId != questId))
_quests.Add(_questData.GetQuestInfo(questId));
}
_offeredQuests = [];
IsOpen = true;
}
public override void OnClose()
{
_quests = [];
_offeredQuests = [];
}
public override void Draw() public override void Draw()
{ {
if (_offeredQuests.Count != 0) if (_offeredQuests.Count != 0)
@ -119,30 +150,40 @@ internal sealed class QuestSelectionWindow : LWindow
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
{ {
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
using var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push(); var (color, icon, tooltipText) = GetQuestStyle(quest.QuestId);
using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
FontAwesomeIcon icon;
Vector4 color;
if (_gameFunctions.IsQuestAccepted(quest.QuestId))
{ {
color = ImGuiColors.DalamudYellow; if (isKnownQuest)
icon = FontAwesomeIcon.Running; ImGui.TextColored(color, icon.ToIconString());
} else
else if (_gameFunctions.IsQuestAcceptedOrComplete(quest.QuestId)) ImGui.TextColored(ImGuiColors.DalamudGrey, icon.ToIconString());
{
color = ImGuiColors.ParsedGreen;
icon = FontAwesomeIcon.Check;
}
else
{
color = ImGuiColors.DalamudRed;
icon = FontAwesomeIcon.Times;
} }
if (isKnownQuest) if (ImGui.IsItemHovered())
ImGui.TextColored(color, icon.ToIconString()); {
else using var tooltip = ImRaii.Tooltip();
ImGui.TextColored(ImGuiColors.DalamudGrey, icon.ToIconString()); if (tooltip)
{
ImGui.TextColored(color, tooltipText);
if (quest.IsRepeatable)
{
ImGui.SameLine();
ImGui.TextUnformatted("Repeatable");
}
if (!_questRegistry.IsKnownQuest(quest.QuestId))
{
ImGui.SameLine();
ImGui.TextUnformatted("NoQuestPath");
}
if (quest.PreviousQuests.Count > 0)
{
ImGui.Separator();
DrawQuestUnlocks(quest, 0);
}
}
}
} }
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
@ -165,7 +206,9 @@ internal sealed class QuestSelectionWindow : LWindow
ImGui.SameLine(); ImGui.SameLine();
if (knownQuest != null && !_gameFunctions.IsQuestAccepted(quest.QuestId) && if (knownQuest != null &&
!_gameFunctions.IsQuestAccepted(quest.QuestId) &&
!_gameFunctions.IsQuestLocked(quest.QuestId) &&
(quest.IsRepeatable || !_gameFunctions.IsQuestAcceptedOrComplete(quest.QuestId))) (quest.IsRepeatable || !_gameFunctions.IsQuestAcceptedOrComplete(quest.QuestId)))
{ {
ImGui.BeginDisabled(_questController.NextQuest != null || _questController.SimulatedQuest != null); ImGui.BeginDisabled(_questController.NextQuest != null || _questController.SimulatedQuest != null);
@ -199,4 +242,99 @@ internal sealed class QuestSelectionWindow : LWindow
ImGui.SetClipboardText(fileName); ImGui.SetClipboardText(fileName);
_chatGui.Print($"Copied '{fileName}' to clipboard"); _chatGui.Print($"Copied '{fileName}' to clipboard");
} }
private (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ushort questId)
{
if (_gameFunctions.IsQuestAccepted(questId))
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");
else if (_gameFunctions.IsQuestAcceptedOrComplete(questId))
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
else if (_gameFunctions.IsQuestLocked(questId))
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
else
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Available");
}
private void DrawQuestUnlocks(QuestInfo quest, int counter)
{
if (counter >= 10)
return;
if (counter != 0 && quest.IsMainScenarioQuest)
return;
if (counter > 0)
ImGui.Indent();
if (quest.PreviousQuests.Count > 0)
{
if (quest.PreviousQuests.Count > 1)
{
if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.All)
ImGui.Text("Requires all:");
else if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne)
ImGui.Text("Requires one:");
}
foreach (var q in quest.PreviousQuests)
{
var qInfo = _questData.GetQuestInfo(q);
var (iconColor, icon, _) = GetQuestStyle(q);
// ReSharper disable once UnusedVariable
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
if (_questRegistry.IsKnownQuest(qInfo.QuestId))
ImGui.TextColored(iconColor, icon.ToIconString());
else
ImGui.TextColored(ImGuiColors.DalamudGrey, icon.ToIconString());
}
ImGui.SameLine();
ImGui.TextUnformatted(FormatQuestUnlockName(qInfo));
DrawQuestUnlocks(qInfo, counter + 1);
}
}
if (counter == 0 && quest.QuestLocks.Count > 0)
{
if (quest.QuestLocks.Count > 1)
{
if (quest.QuestLockJoin == QuestInfo.QuestJoin.All)
ImGui.Text("Blocked if all completed:");
else if (quest.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne)
ImGui.Text("Blocked if at least completed:");
}
else
ImGui.Text("Blocked by (if completed):");
foreach (var q in quest.QuestLocks)
{
var qInfo = _questData.GetQuestInfo(q);
var (iconColor, icon, _) = GetQuestStyle(q);
// ReSharper disable once UnusedVariable
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
if (_questRegistry.IsKnownQuest(qInfo.QuestId))
ImGui.TextColored(iconColor, icon.ToIconString());
else
ImGui.TextColored(ImGuiColors.DalamudGrey, icon.ToIconString());
}
ImGui.SameLine();
ImGui.TextUnformatted(FormatQuestUnlockName(qInfo));
}
}
if (counter > 0)
ImGui.Unindent();
}
private static string FormatQuestUnlockName(QuestInfo questInfo)
{
if (questInfo.IsMainScenarioQuest)
return $"{questInfo.Name} ({questInfo.QuestId}, MSQ)";
else
return $"{questInfo.Name} ({questInfo.QuestId})";
}
} }

View File

@ -0,0 +1,89 @@
using System.Globalization;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using FFXIVClientStructs.FFXIV.Common.Math;
using ImGuiNET;
using LLib.ImGui;
using Questionable.Data;
using Questionable.Model;
using Questionable.Validation;
namespace Questionable.Windows;
internal sealed class QuestValidationWindow : LWindow
{
private readonly QuestValidator _questValidator;
private readonly QuestData _questData;
private readonly IDalamudPluginInterface _pluginInterface;
public QuestValidationWindow(QuestValidator questValidator, QuestData questData, IDalamudPluginInterface pluginInterface) : base("Quest Validation###QuestionableValidator")
{
_questValidator = questValidator;
_questData = questData;
_pluginInterface = pluginInterface;
Size = new Vector2(600, 200);
SizeCondition = ImGuiCond.Once;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(600, 200),
};
}
public override void Draw()
{
using var table = ImRaii.Table("QuestSelection", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY);
if (!table)
{
ImGui.Text("Not table");
return;
}
ImGui.TableSetupColumn("Quest", ImGuiTableColumnFlags.WidthFixed, 50);
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 200);
ImGui.TableSetupColumn("Seq", ImGuiTableColumnFlags.WidthFixed, 30);
ImGui.TableSetupColumn("Step", ImGuiTableColumnFlags.WidthFixed, 30);
ImGui.TableSetupColumn("Issue", ImGuiTableColumnFlags.None, 200);
ImGui.TableHeadersRow();
foreach (ValidationIssue validationIssue in _questValidator.Issues)
{
ImGui.TableNextRow();
if (ImGui.TableNextColumn())
ImGui.TextUnformatted(validationIssue.QuestId.ToString(CultureInfo.InvariantCulture));
if (ImGui.TableNextColumn())
ImGui.TextUnformatted(_questData.GetQuestInfo(validationIssue.QuestId).Name);
if (ImGui.TableNextColumn())
ImGui.TextUnformatted(validationIssue.Sequence?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
if (ImGui.TableNextColumn())
ImGui.TextUnformatted(validationIssue.Step?.ToString(CultureInfo.InvariantCulture) ?? string.Empty);
if (ImGui.TableNextColumn())
{
// ReSharper disable once UnusedVariable
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
if (validationIssue.Severity == EIssueSeverity.Error)
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.TextUnformatted(FontAwesomeIcon.TimesCircle.ToIconString());
}
else
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue);
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
}
}
ImGui.SameLine();
ImGui.TextUnformatted(validationIssue.Description);
}
}
}
}

View File

@ -48,6 +48,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly QuestSelectionWindow _questSelectionWindow; private readonly QuestSelectionWindow _questSelectionWindow;
private readonly QuestValidationWindow _questValidationWindow;
private readonly ILogger<QuestWindow> _logger; private readonly ILogger<QuestWindow> _logger;
public QuestWindow(IDalamudPluginInterface pluginInterface, public QuestWindow(IDalamudPluginInterface pluginInterface,
@ -68,6 +69,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ICondition condition, ICondition condition,
IGameGui gameGui, IGameGui gameGui,
QuestSelectionWindow questSelectionWindow, QuestSelectionWindow questSelectionWindow,
QuestValidationWindow questValidationWindow,
ILogger<QuestWindow> logger) ILogger<QuestWindow> logger)
: base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize) : base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{ {
@ -89,6 +91,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
_condition = condition; _condition = condition;
_gameGui = gameGui; _gameGui = gameGui;
_questSelectionWindow = questSelectionWindow; _questSelectionWindow = questSelectionWindow;
_questValidationWindow = questValidationWindow;
_logger = logger; _logger = logger;
#if DEBUG #if DEBUG
@ -414,7 +417,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
$"Target: {_targetManager.Target.Name} ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})")); $"Target: {_targetManager.Target.Name} ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})"));
GameObject* gameObject = (GameObject*)_targetManager.Target.Address; GameObject* gameObject = (GameObject*)_targetManager.Target.Address;
ImGui.Text(string.Create(CultureInfo.InvariantCulture, $"Distance: {(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2}")); ImGui.Text(string.Create(CultureInfo.InvariantCulture,
$"Distance: {(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2}"));
ImGui.SameLine(); ImGui.SameLine();
float verticalDistance = _targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y; float verticalDistance = _targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y;
@ -452,7 +456,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Show all Quests starting with your current target."); ImGui.SetTooltip("Show all Quests starting with your current target.");
if (showQuests) if (showQuests)
_questSelectionWindow.Open(_targetManager.Target); _questSelectionWindow.OpenForTarget(_targetManager.Target);
ImGui.EndDisabled(); ImGui.EndDisabled();
@ -471,7 +475,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy); bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Left click: Copy target position as JSON.\nRight click: Copy target position as C# code."); ImGui.SetTooltip(
"Left click: Copy target position as JSON.\nRight click: Copy target position as C# code.");
if (copy) if (copy)
{ {
string interactionType = gameObject->NamePlateIconId switch string interactionType = gameObject->NamePlateIconId switch
@ -509,7 +514,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
{ {
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy); bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Left click: Copy your position as JSON.\nRight click: Copy your position as C# code."); ImGui.SetTooltip(
"Left click: Copy your position as JSON.\nRight click: Copy your position as C# code.");
if (copy) if (copy)
{ {
ImGui.SetClipboardText($$""" ImGui.SetClipboardText($$"""
@ -570,6 +576,22 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
_framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(), _framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
TimeSpan.FromMilliseconds(200)); TimeSpan.FromMilliseconds(200));
} }
if (_questRegistry.ValidationIssueCount > 0)
{
ImGui.SameLine();
bool colored = _questRegistry.ValidationErrorCount > 0;
if (colored)
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Flag,
$"{_questRegistry.ValidationIssueCount}"))
_questValidationWindow.IsOpen = true;
if (colored)
ImGui.PopStyleColor();
}
} }
private void DrawRemainingTasks() private void DrawRemainingTasks()