1
0
forked from liza/Questionable

Merge branch 'master' into arr-msq-part4

This commit is contained in:
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>()!;
if (quest.Disabled)
continue;
quests.Add((id, quest));
}

View File

@ -173,6 +173,8 @@ public static class RoslynShortcuts
SyntaxNodeList(
Assignment(nameof(ComplexCombatData.DataId), complexCombatData.DataId, default(uint))
.AsSyntaxNodeOrToken(),
Assignment(nameof(ComplexCombatData.MinimumKillCount), complexCombatData.MinimumKillCount, null)
.AsSyntaxNodeOrToken(),
Assignment(nameof(ComplexCombatData.RewardItemId), complexCombatData.RewardItemId, null)
.AsSyntaxNodeOrToken(),
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",
"Author": "liza",
"Disabled": true,
"QuestSequence": [
{
"Sequence": 0,

View File

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

View File

@ -1,6 +1,7 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"Disabled": true,
"QuestSequence": [
{
"Sequence": 0,
@ -20,6 +21,21 @@
{
"Sequence": 1,
"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,
"Position": {
@ -29,7 +45,10 @@
},
"TerritoryId": 816,
"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",
"Author": "liza",
"Disabled": true,
"QuestSequence": [
{
"Sequence": 0,
@ -28,19 +29,39 @@
"Z": -55.77173
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11451
],
"InteractionType": "Interact",
"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,
"Steps": [
{
"Position": {
"X": -398.9776,
"Y": 0.82966614,
"Z": 8.668919
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Mount": false,
"DisableNavmesh": true
},
{
"DataId": 1031809,
"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,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]

View File

@ -65,16 +65,6 @@
"TerritoryId": 1187,
"InteractionType": "CompleteQuest",
"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,
"TerritoryId": 1187,
"InteractionType": "AcceptQuest",
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Urqopacha - Wachunpelo",
"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
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"InteractionType": "AcceptQuest",
"Comment": "Quest is completed instantly"
}
]

View File

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

View File

@ -1,11 +1,16 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Questionable.Model.V1;
#if RELEASE
namespace Questionable.QuestPaths;
[SuppressMessage("ReSharper", "PartialTypeWithSinglePart", Justification = "Required for RELEASE")]
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
}

View File

@ -19,6 +19,9 @@
"type": "string"
}
},
"Disabled": {
"type": "boolean"
},
"Comment": {
"type": "string"
},
@ -550,6 +553,10 @@
"description": "The enemy data id which is supposed to be killed",
"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": {
"type": "integer"
},

View File

@ -5,6 +5,10 @@ namespace Questionable.Model.V1;
public sealed class ComplexCombatData
{
public uint DataId { get; set; }
// TODO Use this
public uint? MinimumKillCount { get; set; }
public uint? RewardItemId { get; set; }
public int? RewardItemCount { get; set; }
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)
{
if (reader.TokenType == JsonTokenType.String)
return new ExcelRef(reader.GetString()!);
else if (reader.TokenType == JsonTokenType.Number)
return new ExcelRef(reader.GetUInt32());
else
return null;
return reader.TokenType switch
{
JsonTokenType.String => ExcelRef.FromKey(reader.GetString()!),
JsonTokenType.Number => ExcelRef.FromRowId(reader.GetUInt32()),
_ => null
};
}
public override void Write(Utf8JsonWriter writer, ExcelRef? value, JsonSerializerOptions options)

View File

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

View File

@ -6,6 +6,12 @@ public sealed class QuestRoot
{
public string Author { get; set; } = null!;
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 List<ushort> TerritoryBlacklist { get; set; } = new();
public List<QuestSequence> QuestSequence { get; set; } = new();

View File

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

View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
using Questionable.Data;
using Questionable.Model;
using Questionable.Windows;
@ -22,11 +20,12 @@ internal sealed class CommandHandler : IDisposable
private readonly QuestWindow _questWindow;
private readonly QuestSelectionWindow _questSelectionWindow;
private readonly ITargetManager _targetManager;
private readonly GameFunctions _gameFunctions;
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController,
MovementController movementController, QuestRegistry questRegistry,
ConfigWindow configWindow, DebugOverlay debugOverlay, QuestWindow questWindow,
QuestSelectionWindow questSelectionWindow, ITargetManager targetManager)
QuestSelectionWindow questSelectionWindow, ITargetManager targetManager, GameFunctions gameFunctions)
{
_commandManager = commandManager;
_chatGui = chatGui;
@ -38,6 +37,7 @@ internal sealed class CommandHandler : IDisposable
_questWindow = questWindow;
_questSelectionWindow = questSelectionWindow;
_targetManager = targetManager;
_gameFunctions = gameFunctions;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
@ -77,7 +77,12 @@ internal sealed class CommandHandler : IDisposable
break;
case "which":
_questSelectionWindow.Open(_targetManager.Target);
_questSelectionWindow.OpenForTarget(_targetManager.Target);
break;
case "z":
case "zone":
_questSelectionWindow.OpenForCurrentZone();
break;
default:
@ -96,7 +101,9 @@ internal sealed class CommandHandler : IDisposable
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;
_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);
}
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private unsafe void SelectIconStringPostSetup(AddonSelectIconString* addonSelectIconString, bool checkAllSteps)
{
string? actualPrompt = addonSelectIconString->AtkUnitBase.AtkValues[3].ReadAtkString();

View File

@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.IO;
using System.Linq;
using System.Text.Json;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.V1;
using Questionable.Validation;
namespace Questionable.Controller;
@ -18,25 +19,49 @@ internal sealed class QuestRegistry
{
private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestData _questData;
private readonly QuestValidator _questValidator;
private readonly ILogger<QuestRegistry> _logger;
private readonly Dictionary<ushort, Quest> _quests = new();
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
ILogger<QuestRegistry> logger)
QuestValidator questValidator, ILogger<QuestRegistry> logger)
{
_pluginInterface = pluginInterface;
_questData = questData;
_questValidator = questValidator;
_logger = logger;
}
public IEnumerable<Quest> AllQuests => _quests.Values;
public int Count => _quests.Count;
public int ValidationIssueCount => _questValidator.IssueCount;
public int ValidationErrorCount => _questValidator.ErrorCount;
public void Reload()
{
_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");
foreach ((ushort questId, QuestRoot questRoot) in QuestPaths.AssemblyQuestLoader.GetQuests())
@ -46,10 +71,15 @@ internal sealed class QuestRegistry
QuestId = questId,
Root = questRoot,
Info = _questData.GetQuestInfo(questId),
ReadOnly = true,
};
_quests[questId] = quest;
}
#else
}
[Conditional("DEBUG")]
private void LoadQuestsFromProjectDirectory()
{
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
if (solutionDirectory != null)
{
@ -75,29 +105,13 @@ internal sealed class QuestRegistry
}
}
}
#endif
}
try
private void ValidateQuests()
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
_questValidator.ClearIssues();
_questValidator.Validate(_quests.Values.Where(x => !x.ReadOnly));
}
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 LoadQuestFromStream(string fileName, Stream stream)
{
@ -111,6 +125,7 @@ internal sealed class QuestRegistry
QuestId = questId.Value,
Root = JsonSerializer.Deserialize<QuestRoot>(stream)!,
Info = _questData.GetQuestInfo(questId.Value),
ReadOnly = false,
};
_quests[questId.Value] = quest;
}

View File

@ -18,24 +18,33 @@ internal static class NextQuest
if (step.NextQuestId == null)
return null;
if (step.NextQuestId.Value == quest.QuestId)
return null;
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 CurrentQuestId { get; set; }
public ITask With(ushort nextQuestId)
public ITask With(ushort nextQuestId, ushort currentQuestId)
{
NextQuestId = nextQuestId;
CurrentQuestId = currentQuestId;
return this;
}
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);
questController.SetNextQuest(quest);

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Model;
using Questionable.Model.V1;
using AethernetShortcut = Questionable.Controller.Steps.Shared.AethernetShortcut;
namespace Questionable.Controller.Steps.Interactions;
@ -13,7 +16,7 @@ internal static class UseItem
{
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)
{
@ -22,6 +25,16 @@ internal static class UseItem
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>();
if (step.GroundTarget == true)
{
@ -47,6 +60,23 @@ internal static class UseItem
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
=> 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

View File

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

View File

@ -99,15 +99,7 @@ internal static class Move
if (actualDistance > distance)
{
yield return serviceProvider.GetRequiredService<MoveInternal>()
.With(Destination, m =>
{
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);
});
.With(Step, Destination);
}
}
else
@ -116,14 +108,7 @@ internal static class Move
if (actualDistance > distance)
{
yield return serviceProvider.GetRequiredService<MoveInternal>()
.With(Destination, m =>
{
m.NavigateTo(EMovementType.Quest, Step.DataId, [Destination],
fly: Step.Fly == true && gameFunctions.IsFlyingUnlockedInCurrentZone(),
sprint: Step.Sprint != false,
stopDistance: distance,
land: Step.Land == true);
});
.With(Step, Destination);
}
}
@ -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 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;
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;
}
public bool Start()
{
logger.LogInformation("Moving to {Destination}", Destination.ToString("G", CultureInfo.InvariantCulture));
StartAction(movementController);
StartAction();
return true;
}

View File

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

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
@ -21,6 +22,8 @@ using Lumina.Excel.CustomSheets;
using Lumina.Excel.GeneratedSheets2;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.V1;
using Action = Lumina.Excel.GeneratedSheets2.Action;
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
@ -45,6 +48,7 @@ internal sealed unsafe class GameFunctions
private readonly ICondition _condition;
private readonly IClientState _clientState;
private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
private readonly IGameGui _gameGui;
private readonly Configuration _configuration;
private readonly ILogger<GameFunctions> _logger;
@ -55,6 +59,7 @@ internal sealed unsafe class GameFunctions
ICondition condition,
IClientState clientState,
QuestRegistry questRegistry,
QuestData questData,
IGameGui gameGui,
Configuration configuration,
ILogger<GameFunctions> logger)
@ -65,6 +70,7 @@ internal sealed unsafe class GameFunctions
_condition = condition;
_clientState = clientState;
_questRegistry = questRegistry;
_questData = questData;
_gameGui = gameGui;
_configuration = configuration;
_logger = logger;
@ -246,7 +252,7 @@ internal sealed unsafe class GameFunctions
public bool IsQuestAcceptedOrComplete(ushort questId)
{
return QuestManager.IsQuestComplete(questId) || IsQuestAccepted(questId);
return IsQuestComplete(questId) || IsQuestAccepted(questId);
}
public bool IsQuestAccepted(ushort questId)
@ -255,6 +261,39 @@ internal sealed unsafe class GameFunctions
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)
{
subIndex = 0;

View File

@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using Questionable.Model.V1;
namespace Questionable.Model;
@ -8,7 +9,22 @@ internal sealed class Quest
public required ushort QuestId { get; init; }
public required QuestRoot Root { get; init; }
public required QuestInfo Info { get; init; }
public required bool ReadOnly { get; init; }
public QuestSequence? FindSequence(byte 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Dalamud.Game.Text;
using JetBrains.Annotations;
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
namespace Questionable.Model;
@ -13,6 +17,11 @@ internal sealed class QuestInfo
Level = quest.ClassJobLevel0;
IssuerDataId = quest.IssuerStart;
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; }
@ -20,7 +29,20 @@ internal sealed class QuestInfo
public ushort Level { get; }
public uint IssuerDataId { 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
.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">
<PropertyGroup>
<Version>1.7</Version>
<Version>1.8</Version>
<OutputPath>dist</OutputPath>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
</PropertyGroup>

View File

@ -16,6 +16,8 @@ using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.External;
using Questionable.Validation;
using Questionable.Validation.Validators;
using Questionable.Windows;
using Action = Questionable.Controller.Steps.Interactions.Action;
@ -128,6 +130,14 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ConfigWindow>();
serviceCollection.AddSingleton<DebugOverlay>();
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<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.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
@ -18,10 +19,11 @@ internal sealed class DebugOverlay : Window
private readonly QuestRegistry _questRegistry;
private readonly IGameGui _gameGui;
private readonly IClientState _clientState;
private readonly ICondition _condition;
private readonly Configuration _configuration;
public DebugOverlay(QuestController questController, QuestRegistry questRegistry, IGameGui gameGui,
IClientState clientState, Configuration configuration)
IClientState clientState, ICondition condition, Configuration configuration)
: base("Questionable Debug Overlay###QuestionableDebugOverlay",
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground |
ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings, true)
@ -30,6 +32,7 @@ internal sealed class DebugOverlay : Window
_questRegistry = questRegistry;
_gameGui = gameGui;
_clientState = clientState;
_condition = condition;
_configuration = configuration;
Position = Vector2.Zero;
@ -44,7 +47,8 @@ internal sealed class DebugOverlay : Window
public override bool DrawConditions()
{
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()

View File

@ -9,6 +9,7 @@ using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI;
using ImGuiNET;
using LLib.GameUI;
@ -29,13 +30,16 @@ internal sealed class QuestSelectionWindow : LWindow
private readonly QuestController _questController;
private readonly QuestRegistry _questRegistry;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private List<QuestInfo> _quests = [];
private List<QuestInfo> _offeredQuests = [];
private bool _onlyAvailableQuests = true;
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}")
{
_questData = questData;
@ -45,6 +49,8 @@ internal sealed class QuestSelectionWindow : LWindow
_questController = questController;
_questRegistry = questRegistry;
_pluginInterface = pluginInterface;
_territoryData = territoryData;
_clientState = clientState;
Size = new Vector2(500, 200);
SizeCondition = ImGuiCond.Once;
@ -54,18 +60,15 @@ internal sealed class QuestSelectionWindow : LWindow
};
}
public uint TargetId { get; private set; }
public string TargetName { get; private set; } = string.Empty;
public unsafe void Open(IGameObject? gameObject)
public unsafe void OpenForTarget(IGameObject? gameObject)
{
if (gameObject != null)
{
TargetId = gameObject.DataId;
TargetName = gameObject.Name.ToString();
WindowName = $"Quests starting with {TargetName} [{TargetId}]{WindowId}";
var targetId = gameObject.DataId;
var targetName = gameObject.Name.ToString();
WindowName = $"Quests starting with {targetName} [{targetId}]{WindowId}";
_quests = _questData.GetAllByIssuerDataId(TargetId);
_quests = _questData.GetAllByIssuerDataId(targetId);
if (_gameGui.TryGetAddonByName<AddonSelectIconString>("SelectIconString", out var addonSelectIconString))
{
var answers = GameUiController.GetChoices(addonSelectIconString);
@ -85,6 +88,34 @@ internal sealed class QuestSelectionWindow : LWindow
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()
{
if (_offeredQuests.Count != 0)
@ -119,32 +150,42 @@ internal sealed class QuestSelectionWindow : LWindow
if (ImGui.TableNextColumn())
{
ImGui.AlignTextToFramePadding();
using var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push();
FontAwesomeIcon icon;
Vector4 color;
if (_gameFunctions.IsQuestAccepted(quest.QuestId))
var (color, icon, tooltipText) = GetQuestStyle(quest.QuestId);
using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
color = ImGuiColors.DalamudYellow;
icon = FontAwesomeIcon.Running;
}
else if (_gameFunctions.IsQuestAcceptedOrComplete(quest.QuestId))
{
color = ImGuiColors.ParsedGreen;
icon = FontAwesomeIcon.Check;
}
else
{
color = ImGuiColors.DalamudRed;
icon = FontAwesomeIcon.Times;
}
if (isKnownQuest)
ImGui.TextColored(color, icon.ToIconString());
else
ImGui.TextColored(ImGuiColors.DalamudGrey, icon.ToIconString());
}
if (ImGui.IsItemHovered())
{
using var tooltip = ImRaii.Tooltip();
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())
{
ImGui.AlignTextToFramePadding();
@ -165,7 +206,9 @@ internal sealed class QuestSelectionWindow : LWindow
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)))
{
ImGui.BeginDisabled(_questController.NextQuest != null || _questController.SimulatedQuest != null);
@ -199,4 +242,99 @@ internal sealed class QuestSelectionWindow : LWindow
ImGui.SetClipboardText(fileName);
_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 IGameGui _gameGui;
private readonly QuestSelectionWindow _questSelectionWindow;
private readonly QuestValidationWindow _questValidationWindow;
private readonly ILogger<QuestWindow> _logger;
public QuestWindow(IDalamudPluginInterface pluginInterface,
@ -68,6 +69,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ICondition condition,
IGameGui gameGui,
QuestSelectionWindow questSelectionWindow,
QuestValidationWindow questValidationWindow,
ILogger<QuestWindow> logger)
: base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{
@ -89,6 +91,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
_condition = condition;
_gameGui = gameGui;
_questSelectionWindow = questSelectionWindow;
_questValidationWindow = questValidationWindow;
_logger = logger;
#if DEBUG
@ -414,7 +417,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
$"Target: {_targetManager.Target.Name} ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})"));
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();
float verticalDistance = _targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y;
@ -452,7 +456,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Show all Quests starting with your current target.");
if (showQuests)
_questSelectionWindow.Open(_targetManager.Target);
_questSelectionWindow.OpenForTarget(_targetManager.Target);
ImGui.EndDisabled();
@ -471,7 +475,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
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)
{
string interactionType = gameObject->NamePlateIconId switch
@ -509,7 +514,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
{
bool copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
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)
{
ImGui.SetClipboardText($$"""
@ -570,6 +576,22 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
_framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
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()