Compare commits

...

14 Commits

Author SHA1 Message Date
Plogon Enjoyer
6fe30badb2 test new quest schema
(cherry picked from commit 1bd92659c3)
(cherry picked from commit a5c51b73dc)
(cherry picked from commit f7e77dd9a9)
2024-09-01 22:16:15 +08:00
Plogon Enjoyer
79375ad2a4 Added "Aspected Benefic" to the list of Actions 2024-09-01 22:15:46 +08:00
7f348207d3
Add experimental combat module for Magiteknical Failure (aether current quest) 2024-09-01 15:14:37 +02:00
d7aa4243d7
Add EnemySpawnType - AfterAction 2024-09-01 14:21:26 +02:00
86f764aca3
Reduce the max melee distance for RSR 2024-08-31 22:31:06 +02:00
842715337c
Update HW paths + include aether current quests in MSQ 2024-08-31 22:21:36 +02:00
d76242adfe
Fix emote use in 'Familiar Faces' once and for all 2024-08-31 21:50:50 +02:00
c193789d3c
Ignore NextQuestId for job quests 2024-08-31 19:41:59 +02:00
aaad336c64
Fix next quest display 2024-08-31 14:04:07 +02:00
1572ff11a8
Add IPC methods IsRunning/GetCurrentQuestId 2024-08-31 14:03:53 +02:00
de7cf2a94f
Handle accepting quests via 'PickupQuestId' if NPC offers multiple quests 2024-08-30 21:40:42 +02:00
a5d75eb0f5
Update some late ARR quests 2024-08-30 20:35:58 +02:00
308d4b8253
Update source gen 2024-08-30 20:35:37 +02:00
ded6f6bbb2
Always hide 'Cannot execute at this time' while moving, not only during diving 2024-08-30 17:10:54 +02:00
74 changed files with 682 additions and 107 deletions

View File

@ -62,6 +62,9 @@ internal static class QuestStepExtensions
Assignment(nameof(QuestStep.IgnoreDistanceToObject),
step.IgnoreDistanceToObject, emptyStep.IgnoreDistanceToObject)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.RestartNavigationIfCancelled),
step.RestartNavigationIfCancelled, emptyStep.RestartNavigationIfCancelled)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.Comment), step.Comment, emptyStep.Comment)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.Aetheryte), step.Aetheryte, emptyStep.Aetheryte)

View File

@ -36,7 +36,7 @@ public static class Utils
if (questSchemaFile != null)
{
SchemaRegistry.Global.Register(
new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json"),
new Uri("https://git.carvel.li/plogon_enjoyer/Questionable/raw/branch/temp/QuestPaths/quest-v1.json"),
JsonSchema.FromText(questSchemaFile.GetText()!.ToString()));
}

View File

@ -53,6 +53,7 @@
"Y": -0.63573146,
"Z": -166.33862
},
"StopDistance": 5,
"TerritoryId": 141,
"InteractionType": "Interact"
}

View File

@ -35,6 +35,7 @@
},
"TerritoryId": 155,
"InteractionType": "CompleteQuest",
"Mount": true,
"DialogueChoices": [
{
"Type": "YesNo",

View File

@ -337,16 +337,6 @@
"InteractionType": "WalkTo",
"Fly": true
},
{
"Position": {
"X": 233.0817,
"Y": 8,
"Z": -21.83023
},
"TerritoryId": 146,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 2000077,
"Position": {
@ -360,18 +350,16 @@
"KillEnemyDataIds": [
9489
],
"$": "0 0 0 0 0 0 -> 16 17 0 0 0 128"
},
{
"Position": {
"X": 614.4023,
"Y": 301.81046,
"Z": -101.94888
},
"TerritoryId": 155,
"InteractionType": "WalkTo",
"Fly": true,
"AetheryteShortcut": "Coerthas Central Highlands - Camp Dragonhead"
"$": "0 0 0 0 0 0 -> 16 17 0 0 0 128",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
],
"Fly": true
},
{
"DataId": 2000078,
@ -386,7 +374,17 @@
"KillEnemyDataIds": [
9490
],
"$": "16 17 0 0 0 128 -> 0 17 0 0 0 0"
"Fly": true,
"AetheryteShortcut": "Coerthas Central Highlands - Camp Dragonhead",
"$": "16 17 0 0 0 128 -> 0 17 0 0 0 0",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},

View File

@ -191,6 +191,15 @@
"AethernetShard": "[Gold Saucer] Cactpot Board",
"DelaySecondsAtStart": 3
},
{
"Position": {
"X": 111.36922,
"Y": 13.000123,
"Z": -24.209782
},
"TerritoryId": 144,
"InteractionType": "WalkTo"
},
{
"DataId": 1011079,
"Position": {
@ -207,6 +216,15 @@
{
"Sequence": 6,
"Steps": [
{
"Position": {
"X": 111.36922,
"Y": 13.000123,
"Z": -24.209782
},
"TerritoryId": 144,
"InteractionType": "WalkTo"
},
{
"TerritoryId": 144,
"InteractionType": "None",

View File

@ -196,6 +196,17 @@
{
"Sequence": 255,
"Steps": [
{
"Position": {
"X": -465.66104,
"Y": 43.041187,
"Z": 380.90747
},
"TerritoryId": 400,
"InteractionType": "WalkTo",
"Mount": true,
"DisableNavmesh": true
},
{
"DataId": 1013420,
"Position": {
@ -204,8 +215,7 @@
"Z": 347.0968
},
"TerritoryId": 400,
"InteractionType": "CompleteQuest",
"DisableNavmesh": true
"InteractionType": "CompleteQuest"
}
]
}

View File

@ -111,6 +111,7 @@
},
"TerritoryId": 398,
"InteractionType": "WalkTo",
"Mount": true,
"SkipConditions": {
"StepIf": {
"CompletionQuestVariablesFlags": [

View File

@ -20,6 +20,15 @@
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 67.682434,
"Y": -49.685223,
"Z": -148.21802
},
"TerritoryId": 398,
"InteractionType": "WalkTo"
},
{
"DataId": 4001,
"Position": {

View File

@ -118,6 +118,7 @@
},
"TerritoryId": 398,
"InteractionType": "WalkTo",
"Mount": true,
"SkipConditions": {
"StepIf": {
"CompletionQuestVariablesFlags": [

View File

@ -152,7 +152,8 @@
"Z": 22.9953
},
"TerritoryId": 398,
"InteractionType": "CompleteQuest"
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "The Dravanian Forelands - Anyx Trine"
}
]
}

View File

@ -98,6 +98,7 @@
"Y": 205.6815,
"Z": 31.631958
},
"StopDistance": 5,
"TerritoryId": 478,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Idyllshire"

View File

@ -30,6 +30,17 @@
"InteractionType": "WalkTo",
"TargetTerritoryId": 399
},
{
"DataId": 2006210,
"Position": {
"X": -487.48004,
"Y": 144.64026,
"Z": -285.359
},
"TerritoryId": 399,
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818088
},
{
"DataId": 1013651,
"Position": {

View File

@ -20,6 +20,15 @@
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -44.83911,
"Y": -1.6023016,
"Z": -655.73804
},
"TerritoryId": 401,
"InteractionType": "WalkTo"
},
{
"DataId": 2005824,
"Position": {

View File

@ -135,6 +135,20 @@
{
"Sequence": 3,
"Steps": [
{
"Position": {
"X": 423.53406,
"Y": -26.253891,
"Z": -550.60297
},
"TerritoryId": 398,
"InteractionType": "WalkTo",
"SkipConditions": {
"StepIf": {
"Flying": "Unlocked"
}
}
},
{
"DataId": 2005573,
"Position": {
@ -177,7 +191,8 @@
"StepIf": {
"Flying": "Unlocked"
}
}
},
"DisableNavmesh": true
}
]
},

View File

@ -12,6 +12,7 @@
"Y": 0.014982708,
"Z": -0.07635498
},
"StopDistance": 7,
"TerritoryId": 395,
"InteractionType": "AcceptQuest"
}

View File

@ -114,8 +114,7 @@
"Z": 761.01306
},
"TerritoryId": 401,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "The Sea of Clouds - Camp Cloudtop"
"InteractionType": "CompleteQuest"
}
]
}

View File

@ -83,6 +83,15 @@
128
]
},
{
"Position": {
"X": -368.08698,
"Y": -185.05266,
"Z": 792.836
},
"TerritoryId": 401,
"InteractionType": "WalkTo"
},
{
"DataId": 1012360,
"Position": {

View File

@ -20,6 +20,23 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1011952,
"Position": {
"X": -277.63788,
"Y": -184.59735,
"Z": 741.60376
},
"TerritoryId": 401,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "The Sea of Clouds - Camp Cloudtop",
"PickUpQuestId": 1748,
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
},
{
"DataId": 1001029,
"Position": {

View File

@ -47,6 +47,7 @@
"Y": 16.009666,
"Z": -9.567444
},
"StopDistance": 7,
"TerritoryId": 419,
"InteractionType": "Interact",
"TargetTerritoryId": 433
@ -63,6 +64,7 @@
"Y": 0.022254243,
"Z": -4.409851
},
"StopDistance": 5,
"TerritoryId": 433,
"InteractionType": "CompleteQuest",
"DialogueChoices": [

View File

@ -12,6 +12,7 @@
"Y": 0.022254243,
"Z": -4.409851
},
"StopDistance": 5,
"TerritoryId": 433,
"InteractionType": "AcceptQuest"
}
@ -27,7 +28,6 @@
"Y": 1.1443481,
"Z": 13.199036
},
"StopDistance": 7,
"TerritoryId": 433,
"InteractionType": "Interact",
"TargetTerritoryId": 419

View File

@ -87,6 +87,17 @@
{
"Sequence": 4,
"Steps": [
{
"DataId": 1011240,
"Position": {
"X": 493.15625,
"Y": 200.2377,
"Z": 663.01965
},
"TerritoryId": 397,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1744
},
{
"DataId": 2005536,
"Position": {

View File

@ -20,6 +20,39 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1011910,
"Position": {
"X": -298.26813,
"Y": 126.67049,
"Z": -1.4191895
},
"TerritoryId": 397,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1759
},
{
"DataId": 1011907,
"Position": {
"X": -288.8686,
"Y": 127.06639,
"Z": 13.199036
},
"TerritoryId": 397,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1760
},
{
"DataId": 1011911,
"Position": {
"X": -279.56055,
"Y": 127.08131,
"Z": 13.595764
},
"TerritoryId": 397,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 2111
},
{
"Position": {
"X": -365.48965,

View File

@ -20,6 +20,16 @@
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -826.9342,
"Y": 117.95439,
"Z": -642.92413
},
"TerritoryId": 397,
"InteractionType": "WalkTo",
"Fly": true
},
{
"Position": {
"X": -850.3646,

View File

@ -35,6 +35,17 @@
{
"Sequence": 2,
"Steps": [
{
"DataId": 1011916,
"Position": {
"X": 470.02356,
"Y": -49.89133,
"Z": 20.370789
},
"TerritoryId": 398,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1771
},
{
"Position": {
"X": 634.0371,

View File

@ -105,6 +105,17 @@
{
"Sequence": 3,
"Steps": [
{
"DataId": 1011929,
"Position": {
"X": 70.81714,
"Y": -49.2083,
"Z": -141.55798
},
"TerritoryId": 398,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1790
},
{
"Position": {
"X": 70.535545,

View File

@ -90,13 +90,52 @@
"Sequence": 255,
"Steps": [
{
"DataId": 1011916,
"Position": {
"X": 123.44939,
"Y": -60.88712,
"Z": -118.79085
"X": 470.02356,
"Y": -49.89133,
"Z": 20.370789
},
"TerritoryId": 398,
"InteractionType": "WalkTo"
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1797,
"AetheryteShortcut": "The Dravanian Forelands - Tailfeather",
"SkipConditions": {
"AetheryteShortcutIf": {
"QuestsCompleted": [
1797
]
}
}
},
{
"DataId": 1011937,
"Position": {
"X": -305.56195,
"Y": 39.04307,
"Z": 22.9953
},
"TerritoryId": 398,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1802,
"AetheryteShortcut": "The Dravanian Forelands - Anyx Trine",
"SkipConditions": {
"AetheryteShortcutIf": {
"QuestsCompleted": [
1802
]
}
}
},
{
"Position": {
"X": 22.235865,
"Y": -24.754946,
"Z": -119.65716
},
"TerritoryId": 398,
"InteractionType": "WalkTo",
"AetheryteShortcut": "The Dravanian Forelands - Anyx Trine"
},
{
"DataId": 1014544,
@ -106,7 +145,8 @@
"Z": -142.50409
},
"TerritoryId": 398,
"InteractionType": "CompleteQuest"
"InteractionType": "CompleteQuest",
"DisableNavmesh": true
}
]
}

View File

@ -12,7 +12,7 @@
"Y": -50.325172,
"Z": -146.95972
},
"StopDistance": 5,
"StopDistance": 7,
"TerritoryId": 398,
"InteractionType": "AcceptQuest"
}

View File

@ -12,7 +12,7 @@
"Y": -69.42934,
"Z": 693.5072
},
"StopDistance": 5,
"StopDistance": 6.5,
"TerritoryId": 400,
"InteractionType": "AcceptQuest"
}

View File

@ -31,6 +31,17 @@
"DisableNavmesh": true,
"Mount": true
},
{
"DataId": 1012284,
"Position": {
"X": 363.24097,
"Y": -73.25598,
"Z": 678.4314
},
"TerritoryId": 400,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1819
},
{
"Position": {
"X": 286.46622,

View File

@ -155,7 +155,7 @@
"Y": -69.42934,
"Z": 693.5072
},
"StopDistance": 5,
"StopDistance": 7,
"TerritoryId": 400,
"InteractionType": "CompleteQuest"
}

View File

@ -12,7 +12,7 @@
"Y": -69.42934,
"Z": 693.5072
},
"StopDistance": 5,
"StopDistance": 7,
"TerritoryId": 400,
"InteractionType": "AcceptQuest"
}

View File

@ -58,6 +58,25 @@
{
"Sequence": 255,
"Steps": [
{
"DataId": 1012285,
"Position": {
"X": 247.11987,
"Y": -42.33362,
"Z": 565.3314
},
"TerritoryId": 400,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1823,
"AetheryteShortcut": "The Churning Mists - Moghome",
"SkipConditions": {
"AetheryteShortcutIf": {
"QuestsCompleted": [
1823
]
}
}
},
{
"DataId": 1013202,
"Position": {

View File

@ -85,6 +85,7 @@
"Y": 123.72873,
"Z": 210.74231
},
"StopDistance": 7,
"TerritoryId": 400,
"InteractionType": "CompleteQuest"
}

View File

@ -12,6 +12,7 @@
"Y": 123.72873,
"Z": 210.74231
},
"StopDistance": 7,
"TerritoryId": 400,
"InteractionType": "AcceptQuest"
}

View File

@ -20,6 +20,17 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1013434,
"Position": {
"X": 517.9064,
"Y": -1.1917055,
"Z": -354.63495
},
"TerritoryId": 400,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1835
},
{
"DataId": 1012720,
"Position": {

View File

@ -12,6 +12,7 @@
"Y": 16.979584,
"Z": -37.521973
},
"StopDistance": 7,
"TerritoryId": 418,
"InteractionType": "AcceptQuest"
}
@ -58,6 +59,7 @@
"Y": 0,
"Z": -3.1281738
},
"StopDistance": 7,
"TerritoryId": 212,
"InteractionType": "Interact"
}

View File

@ -21,6 +21,25 @@
{
"Sequence": 255,
"Steps": [
{
"DataId": 1013420,
"Position": {
"X": -511.46716,
"Y": 50,
"Z": 347.0968
},
"TerritoryId": 400,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "The Churning Mists - Zenith",
"PickUpQuestId": 1828,
"SkipConditions": {
"AetheryteShortcutIf": {
"QuestsCompleted": [
1828
]
}
}
},
{
"DataId": 1013172,
"Position": {
@ -30,7 +49,6 @@
},
"TerritoryId": 400,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "The Churning Mists - Zenith",
"Fly": true
}
]

View File

@ -21,6 +21,18 @@
{
"Sequence": 255,
"Steps": [
{
"DataId": 1014720,
"Position": {
"X": 13.809326,
"Y": 15.96505,
"Z": -13.870483
},
"StopDistance": 7,
"TerritoryId": 419,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1874
},
{
"DataId": 1011223,
"Position": {
@ -37,8 +49,9 @@
"Yes": true
}
],
"AetheryteShortcut": "Ishgard",
"AethernetShortcut": [
"[Ishgard] The Last Vigil",
"[Ishgard] Aetheryte Plaza",
"[Ishgard] The Forgotten Knight"
]
},

View File

@ -38,7 +38,7 @@
"Z": 113.54166
},
"TerritoryId": 401,
"InteractionType": "SinglePlayerDuty",
"InteractionType": "Emote",
"Emote": "lookout",
"StopDistance": 0.25
}
@ -54,10 +54,9 @@
"Z": 77.39858
},
"TerritoryId": 401,
"InteractionType": "Emote",
"InteractionType": "SinglePlayerDuty",
"Emote": "lookout",
"StopDistance": 0.25,
"Comment": "Solo Duty triggered by emoting?"
"StopDistance": 0.25
}
]
},

View File

@ -20,6 +20,17 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1012069,
"Position": {
"X": -647.0283,
"Y": -51.05719,
"Z": -417.74628
},
"TerritoryId": 401,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1909
},
{
"DataId": 1012064,
"Position": {
@ -35,6 +46,17 @@
{
"Sequence": 2,
"Steps": [
{
"DataId": 1012068,
"Position": {
"X": -597.0398,
"Y": -51.05185,
"Z": -387.0451
},
"TerritoryId": 401,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1910
},
{
"DataId": 2006231,
"Position": {
@ -68,6 +90,7 @@
"Y": -14.730623,
"Z": -537.56006
},
"StopDistance": 7,
"TerritoryId": 401,
"InteractionType": "Interact",
"DialogueChoices": [
@ -100,7 +123,7 @@
"Y": -14.153766,
"Z": -543.0228
},
"StopDistance": 5,
"StopDistance": 7,
"TerritoryId": 401,
"InteractionType": "CompleteQuest"
}

View File

@ -12,7 +12,7 @@
"Y": -14.153766,
"Z": -543.0228
},
"StopDistance": 5,
"StopDistance": 7,
"TerritoryId": 401,
"InteractionType": "AcceptQuest"
}

View File

@ -21,6 +21,15 @@
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 43.620056,
"Y": -7.9655867,
"Z": 103.63884
},
"TerritoryId": 132,
"InteractionType": "WalkTo"
},
{
"DataId": 1000691,
"Position": {

View File

@ -45,7 +45,8 @@
},
"TerritoryId": 398,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "The Dravanian Forelands - Tailfeather"
"AetheryteShortcut": "The Dravanian Forelands - Tailfeather",
"Fly": true
}
]
}

View File

@ -27,6 +27,7 @@
"Y": 205.62819,
"Z": 26.901611
},
"StopDistance": 7,
"TerritoryId": 478,
"InteractionType": "Interact"
}
@ -47,7 +48,15 @@
"$": "0 0 0 0 0 0 -> 1 0 0 0 0 64",
"ChatMessage": {
"Key": "TEXT_HEAVNA607_01656_SAYTODO_000"
}
},
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1012413,
@ -60,7 +69,15 @@
"InteractionType": "Say",
"ChatMessage": {
"Key": "TEXT_HEAVNA607_01656_SAYTODO_000"
}
},
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},

View File

@ -20,6 +20,21 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1012287,
"Position": {
"X": -28.397034,
"Y": 100.969696,
"Z": -186.4195
},
"TerritoryId": 399,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1936,
"AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza",
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
]
},
{
"DataId": 1012416,
"Position": {

View File

@ -20,6 +20,17 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1012102,
"Position": {
"X": 72.40405,
"Y": 205.6815,
"Z": 31.631958
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1945
},
{
"DataId": 1012419,
"Position": {

View File

@ -21,6 +21,17 @@
{
"Sequence": 1,
"Steps": [
{
"DataId": 1012133,
"Position": {
"X": -26.840637,
"Y": 206.49944,
"Z": 28.67163
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1963
},
{
"Position": {
"X": 71.86769,
@ -29,18 +40,15 @@
},
"TerritoryId": 478,
"InteractionType": "WalkTo",
"TargetTerritoryId": 399
},
{
"DataId": 2006210,
"Position": {
"X": -487.48004,
"Y": 144.64026,
"Z": -285.359
"TargetTerritoryId": 399,
"SkipConditions": {
"StepIf": {
"InTerritory": [
399
]
}
},
"TerritoryId": 399,
"InteractionType": "AttuneAetherCurrent",
"AetherCurrentId": 2818088
"$": "Skipped if already in the Hinterlands, since 'Taking Stock' already leaves you closer to the entrance to Matoya's cave"
},
{
"DataId": 2006214,

View File

@ -44,7 +44,7 @@
"Y": 38.43,
"Z": 3.5552979
},
"StopDistance": 7,
"StopDistance": 6,
"TerritoryId": 463,
"InteractionType": "Interact"
}
@ -53,6 +53,17 @@
{
"Sequence": 3,
"Steps": [
{
"DataId": 1012141,
"Position": {
"X": 35.690796,
"Y": 38.43,
"Z": 12.985352
},
"TerritoryId": 463,
"InteractionType": "AcceptQuest",
"PickUpQuestId": 1966
},
{
"DataId": 2005337,
"Position": {
@ -114,7 +125,8 @@
},
"TerritoryId": 399,
"InteractionType": "WalkTo",
"DisableNavmesh": true
"DisableNavmesh": true,
"Mount": true
},
{
"DataId": 2005336,

View File

@ -28,6 +28,7 @@
"Y": -14.52896,
"Z": 41.153564
},
"StopDistance": 7,
"TerritoryId": 419,
"InteractionType": "Interact"
}

View File

@ -61,7 +61,8 @@
"Z": -659.2356
},
"TerritoryId": 402,
"InteractionType": "Interact"
"InteractionType": "Interact",
"DisableNavmesh": true
}
]
},

View File

@ -35,6 +35,15 @@
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": 642.1002,
"Y": -32.964188,
"Z": -532.8383
},
"TerritoryId": 402,
"InteractionType": "WalkTo"
},
{
"DataId": 2005568,
"Position": {

View File

@ -49,8 +49,7 @@
},
"StopDistance": 0.5,
"TerritoryId": 612,
"InteractionType": "Instruction",
"Comment": "Manual combat",
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
7504
@ -69,8 +68,7 @@
},
"StopDistance": 2,
"TerritoryId": 612,
"InteractionType": "Instruction",
"Comment": "Manual combat",
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
7505

View File

@ -1,13 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"$id": "https://git.carvel.li/plogon_enjoyer/Questionable/raw/branch/temp/QuestPaths/quest-v1.json",
"title": "Questionable V1",
"description": "A series of quest sequences",
"type": "object",
"properties": {
"$schema": {
"type": "string",
"const": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json"
"enum":[
"https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"https://git.carvel.li/plogon_enjoyer/Questionable/raw/branch/temp/QuestPaths/quest-v1.json"
]
},
"Author": {
"description": "Author of the quest sequence",
@ -584,6 +587,7 @@
"AutoOnEnterArea",
"AfterInteraction",
"AfterItemUse",
"AfterAction",
"OverworldEnemies"
]
},
@ -859,6 +863,7 @@
"Cure II",
"Esuna",
"Physick",
"Aspected Benefic",
"Buffet (Sanuwa)",
"Buffet (Griffin)",
"Fumigate",

View File

@ -13,6 +13,7 @@ public sealed class ActionConverter() : EnumConverter<EAction>(Values)
{ EAction.Cure2, "Cure II" },
{ EAction.Esuna, "Esuna" },
{ EAction.Physick, "Physick" },
{ EAction.AspectedBenefic, "Aspected Benefic" },
{ EAction.BuffetSanuwa, "Buffet (Sanuwa)" },
{ EAction.BuffetGriffin, "Buffet (Griffin)" },
{ EAction.Fumigate, "Fumigate" },

View File

@ -9,6 +9,7 @@ public sealed class EnemySpawnTypeConverter() : EnumConverter<EEnemySpawnType>(V
{
{ EEnemySpawnType.AfterInteraction, "AfterInteraction" },
{ EEnemySpawnType.AfterItemUse, "AfterItemUse" },
{ EEnemySpawnType.AfterAction, "AfterAction" },
{ EEnemySpawnType.AutoOnEnterArea, "AutoOnEnterArea" },
{ EEnemySpawnType.OverworldEnemies, "OverworldEnemies" },
};

View File

@ -12,9 +12,12 @@ public enum EAction
Cure2 = 135,
Esuna = 7568,
Physick = 190,
AspectedBenefic = 3595,
BuffetSanuwa = 4931,
BuffetGriffin = 4583,
Fumigate = 5872,
MagitekPulse = 8624,
MagitekThunder = 8625,
SiphonSnout = 18187,
Cannonfire = 20121,
RedGulal = 29382,

View File

@ -9,6 +9,7 @@ public enum EEnemySpawnType
None = 0,
AfterInteraction,
AfterItemUse,
AfterAction,
AutoOnEnterArea,
OverworldEnemies,
}

View File

@ -191,6 +191,9 @@ internal sealed class CombatController : IDisposable
{
if (gameObject is IBattleNpc battleNpc)
{
if (_currentFight != null && !_currentFight.Module.CanAttack(battleNpc))
return 0;
// TODO this works as somewhat of a delay between killing enemies if certain items/flags are checked
// but also delays killing the next enemy a little
if (_currentFight == null || _currentFight.Data.SpawnType != EEnemySpawnType.OverworldEnemies ||

View File

@ -13,4 +13,6 @@ internal interface ICombatModule
void Update(IGameObject nextTarget);
void MoveToTarget(IGameObject nextTarget);
bool CanAttack(IBattleNpc target);
}

View File

@ -0,0 +1,51 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.CombatModules;
/// <summary>
/// Commandeered Magitek Armor; used in 'Magiteknical Failure' quest.
/// </summary>
internal sealed class Mount128Module : ICombatModule
{
public const ushort MountId = 128;
private readonly EAction[] _actions = [EAction.MagitekThunder, EAction.MagitekPulse];
private readonly MovementController _movementController;
private readonly GameFunctions _gameFunctions;
public Mount128Module(MovementController movementController, GameFunctions gameFunctions)
{
_movementController = movementController;
_gameFunctions = gameFunctions;
}
public bool IsLoaded => _gameFunctions.GetMountId() == MountId;
public bool Start() => true;
public bool Stop() => true;
public void Update(IGameObject gameObject)
{
if (_movementController.IsPathfinding || _movementController.IsPathRunning)
return;
foreach (EAction action in _actions)
{
if (_gameFunctions.UseAction(gameObject, action, checkCanUse: false))
return;
}
}
public void MoveToTarget(IGameObject gameObject)
{
}
public bool CanAttack(IBattleNpc target) => target.DataId is 7504 or 7505;
}

View File

@ -85,7 +85,7 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
float maxDistance = player.ClassJob.GameData?.Role is 3 or 4 ? 20f : 3f;
float maxDistance = player.ClassJob.GameData?.Role is 3 or 4 ? 20f : 2.9f;
if (actualDistance - hitboxOffset >= maxDistance)
{
if (actualDistance - hitboxOffset <= 5)
@ -119,6 +119,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
}
}
public bool CanAttack(IBattleNpc target) => true;
public void Dispose() => Stop();
[PublicAPI]

View File

@ -3,16 +3,20 @@ using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
using Lumina.Excel.GeneratedSheets;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows;
using Questionable.Windows.QuestComponents;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller;
internal sealed class CommandHandler : IDisposable
{
private const string MessageTag = "Questionable";
private const ushort TagColor = 576;
private readonly ICommandManager _commandManager;
private readonly IChatGui _chatGui;
private readonly QuestController _questController;
@ -24,6 +28,8 @@ internal sealed class CommandHandler : IDisposable
private readonly QuestSelectionWindow _questSelectionWindow;
private readonly ITargetManager _targetManager;
private readonly QuestFunctions _questFunctions;
private readonly GameFunctions _gameFunctions;
private readonly IDataManager _dataManager;
public CommandHandler(
ICommandManager commandManager,
@ -36,7 +42,9 @@ internal sealed class CommandHandler : IDisposable
QuestWindow questWindow,
QuestSelectionWindow questSelectionWindow,
ITargetManager targetManager,
QuestFunctions questFunctions)
QuestFunctions questFunctions,
GameFunctions gameFunctions,
IDataManager dataManager)
{
_commandManager = commandManager;
_chatGui = chatGui;
@ -49,6 +57,8 @@ internal sealed class CommandHandler : IDisposable
_questSelectionWindow = questSelectionWindow;
_targetManager = targetManager;
_questFunctions = questFunctions;
_gameFunctions = gameFunctions;
_dataManager = dataManager;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
@ -108,12 +118,16 @@ internal sealed class CommandHandler : IDisposable
_questSelectionWindow.OpenForCurrentZone();
break;
case "mountid":
PrintMountId();
break;
case "":
_questWindow.Toggle();
break;
default:
_chatGui.PrintError($"Unknown subcommand {parts[0]}", "Questionable");
_chatGui.PrintError($"Unknown subcommand {parts[0]}", MessageTag, TagColor);
break;
}
}
@ -122,7 +136,7 @@ internal sealed class CommandHandler : IDisposable
{
if (!_debugOverlay.DrawConditions())
{
_chatGui.PrintError("[Questionable] You don't have the debug overlay enabled.");
_chatGui.PrintError("You don't have the debug overlay enabled.", MessageTag, TagColor);
return;
}
@ -131,15 +145,15 @@ internal sealed class CommandHandler : IDisposable
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_debugOverlay.HighlightedQuest = quest.Id;
_chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name}).");
_chatGui.Print($"Set highlighted quest to {questId} ({quest.Info.Name}).", MessageTag, TagColor);
}
else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
_chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
}
else
{
_debugOverlay.HighlightedQuest = null;
_chatGui.Print("[Questionable] Cleared highlighted quest.");
_chatGui.Print("Cleared highlighted quest.", MessageTag, TagColor);
}
}
@ -148,21 +162,21 @@ internal sealed class CommandHandler : IDisposable
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
{
if (_questFunctions.IsQuestLocked(questId))
_chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
_chatGui.PrintError($"Quest {questId} is locked.", MessageTag, TagColor);
else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_questController.SetNextQuest(quest);
_chatGui.Print($"[Questionable] Set next quest to {questId} ({quest.Info.Name}).");
_chatGui.Print($"Set next quest to {questId} ({quest.Info.Name}).", MessageTag, TagColor);
}
else
{
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
_chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
}
}
else
{
_questController.SetNextQuest(null);
_chatGui.Print("[Questionable] Cleared next quest.");
_chatGui.Print("Cleared next quest.", MessageTag, TagColor);
}
}
@ -173,18 +187,32 @@ internal sealed class CommandHandler : IDisposable
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_questController.SimulateQuest(quest);
_chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name}).");
_chatGui.Print($"Simulating quest {questId} ({quest.Info.Name}).", MessageTag, TagColor);
}
else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
_chatGui.PrintError($"Unknown quest {questId}.", MessageTag, TagColor);
}
else
{
_questController.SimulateQuest(null);
_chatGui.Print("[Questionable] Cleared simulated quest.");
_chatGui.Print("Cleared simulated quest.", MessageTag, TagColor);
}
}
private void PrintMountId()
{
ushort? mountId = _gameFunctions.GetMountId();
if (mountId != null)
{
var row = _dataManager.GetExcelSheet<Mount>()!.GetRow(mountId.Value);
_chatGui.Print(
$"Mount ID: {mountId}, Name: {row?.Singular}, Obtainable: {(row?.Order == -1 ? "No" : "Yes")}",
MessageTag, TagColor);
}
else
_chatGui.Print("You are not mounted.", MessageTag, TagColor);
}
public void Dispose()
{
_commandManager.RemoveHandler("/qst");

View File

@ -235,6 +235,16 @@ internal sealed class InteractionUiController : IDisposable
_logger.LogInformation("Checking if current quest {Name} is on the list", currentQuest.Quest.Info.Name);
if (CheckQuestSelection(addonSelectIconString, currentQuest.Quest, answers))
return;
var sequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
QuestStep? step = sequence?.FindStep(currentQuest.Step);
if (step is { InteractionType: EInteractionType.AcceptQuest, PickUpQuestId: not null } &&
_questRegistry.TryGetQuest(step.PickUpQuestId, out Quest? pickupQuest))
{
_logger.LogInformation("Checking if current picked-up {Name} is on the list", pickupQuest.Info.Name);
if (CheckQuestSelection(addonSelectIconString, pickupQuest, answers))
return;
}
}
var nextQuest = _questController.NextQuest;

View File

@ -20,6 +20,10 @@ internal static class NextQuest
if (step.NextQuestId == quest.Id)
return null;
// probably irrelevant, since pick up is handled elsewhere (and, in particular, checks for aetherytes and stuff)
if (questFunctions.GetPriorityQuests().Contains(step.NextQuestId))
return null;
return new SetQuest(step.NextQuestId, quest.Id, questRegistry, questController, questFunctions, loggerFactory.CreateLogger<SetQuest>());
}
}

View File

@ -24,13 +24,18 @@ internal static class Action
ArgumentNullException.ThrowIfNull(step.Action);
var task = new UseOnObject(step.DataId, step.Action.Value, gameFunctions,
loggerFactory.CreateLogger<UseOnObject>());
var task = OnObject(step.DataId, step.Action.Value);
if (step.Action.Value.RequiresMount())
return [task];
else
return [mountFactory.Unmount(), task];
}
public ITask OnObject(uint? dataId, EAction action)
{
return new UseOnObject(dataId, action, gameFunctions,
loggerFactory.CreateLogger<UseOnObject>());
}
}
private sealed class UseOnObject(

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.CombatModules;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Controller.Utils;
@ -18,7 +18,9 @@ internal static class Combat
Interact.Factory interactFactory,
Mount.Factory mountFactory,
UseItem.Factory useItemFactory,
QuestFunctions questFunctions) : ITaskFactory
Action.Factory actionFactory,
QuestFunctions questFunctions,
GameFunctions gameFunctions) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -27,7 +29,8 @@ internal static class Combat
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
yield return mountFactory.Unmount();
if (gameFunctions.GetMountId() != Mount128Module.MountId)
yield return mountFactory.Unmount();
if (step.CombatDelaySecondsAtStart != null)
{
@ -58,6 +61,19 @@ internal static class Combat
break;
}
case EEnemySpawnType.AfterAction:
{
ArgumentNullException.ThrowIfNull(step.DataId);
ArgumentNullException.ThrowIfNull(step.Action);
if (!step.Action.Value.RequiresMount())
yield return mountFactory.Unmount();
yield return actionFactory.OnObject(step.DataId.Value, step.Action.Value);
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));
yield return CreateTask(quest, sequence, step);
break;
}
case EEnemySpawnType.AutoOnEnterArea:
if (step.CombatDelaySecondsAtStart == null)
yield return new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(1));

View File

@ -14,7 +14,8 @@ internal static class Emote
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType is EInteractionType.AcceptQuest or EInteractionType.CompleteQuest)
if (step.InteractionType is EInteractionType.AcceptQuest or EInteractionType.CompleteQuest
or EInteractionType.SinglePlayerDuty)
{
if (step.Emote == null)
return [];

View File

@ -68,7 +68,7 @@ internal static class MoveTo
public ITask Move(MoveParams moveParams)
{
return new MoveInternal(moveParams, movementController, gameFunctions,
loggerFactory.CreateLogger<MoveInternal>(), condition, clientState, dataManager);
loggerFactory.CreateLogger<MoveInternal>(), clientState, dataManager);
}
public ITask Land()
@ -163,26 +163,22 @@ internal static class MoveTo
private readonly string _cannotExecuteAtThisTime;
private readonly MovementController _movementController;
private readonly ILogger<MoveInternal> _logger;
private readonly ICondition _condition;
private readonly IClientState _clientState;
private readonly Action _startAction;
private readonly Vector3 _destination;
private readonly MoveParams _moveParams;
private readonly bool _isUnderwaterInitially;
private bool _canRestart;
public MoveInternal(MoveParams moveParams,
MovementController movementController,
GameFunctions gameFunctions,
ILogger<MoveInternal> logger,
ICondition condition,
IClientState clientState,
IDataManager dataManager)
{
_movementController = movementController;
_logger = logger;
_condition = condition;
_clientState = clientState;
_cannotExecuteAtThisTime = dataManager.GetString<LogMessage>(579, x => x.Text)!;
@ -257,8 +253,7 @@ internal static class MoveTo
public bool OnErrorToast(SeString message)
{
if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue) &&
_condition[ConditionFlag.Diving])
if (GameFunctions.GameStringEquals(_cannotExecuteAtThisTime, message.TextValue))
return true;
return false;

View File

@ -0,0 +1,31 @@
using System;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Questionable.Controller;
namespace Questionable.External;
internal sealed class QuestionableIpc : IDisposable
{
private const string IpcIsRunning = "Questionable.IsRunning";
private const string IpcGetCurrentQuestId = "Questionable.GetCurrentQuestId";
private readonly ICallGateProvider<bool> _isRunning;
private readonly ICallGateProvider<string?> _getCurrentQuestId;
public QuestionableIpc(QuestController questController, IDalamudPluginInterface pluginInterface)
{
_isRunning = pluginInterface.GetIpcProvider<bool>(IpcIsRunning);
_isRunning.RegisterFunc(() => questController.IsRunning);
_getCurrentQuestId = pluginInterface.GetIpcProvider<string?>(IpcGetCurrentQuestId);
_getCurrentQuestId.RegisterFunc(() => questController.CurrentQuest?.Quest.Id.ToString());
}
public void Dispose()
{
_getCurrentQuestId.UnregisterFunc();
_isRunning.UnregisterFunc();
}
}

View File

@ -81,8 +81,9 @@ internal sealed unsafe class GameFunctions
if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted])
{
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
if (battleChara != null && battleChara->Mount.MountId == 198) // special quest amaro, not the normal one
// special quest amaro, not the normal one
// TODO Check if this also applies to beast tribe mounts
if (GetMountId() == 198)
return true;
}
@ -92,6 +93,15 @@ internal sealed unsafe class GameFunctions
playerState->IsAetherCurrentZoneComplete(aetherCurrentCompFlgSet);
}
public ushort? GetMountId()
{
BattleChara* battleChara = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
if (battleChara != null && battleChara->Mount.MountId != 0)
return battleChara->Mount.MountId;
else
return null;
}
public bool IsFlyingUnlockedInCurrentZone() => IsFlyingUnlocked(_clientState.TerritoryType);
public bool IsAetherCurrentUnlocked(uint aetherCurrentId)
@ -210,10 +220,10 @@ internal sealed unsafe class GameFunctions
return false;
}
public bool UseAction(IGameObject gameObject, EAction action)
public bool UseAction(IGameObject gameObject, EAction action, bool checkCanUse = true)
{
var actionRow = _dataManager.GetExcelSheet<Action>()!.GetRow((uint)action)!;
if (!ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address))
if (checkCanUse && !ActionManager.CanUseActionOnTarget((uint)action, (GameObject*)gameObject.Address))
{
_logger.LogWarning("Can not use action {Action} on target {Target}", action, gameObject);
return false;

View File

@ -254,7 +254,8 @@ internal sealed unsafe class QuestFunctions
InventoryManager* inventoryManager = InventoryManager.Instance();
int gil = inventoryManager->GetItemCountInContainer(1, InventoryType.Currency);
return GetPriorityQuestsThatCanBeAccepted()
return GetPriorityQuests()
.Where(IsReadyToAcceptQuest)
.Where(x =>
{
if (!_questRegistry.TryGetQuest(x, out Quest? quest))
@ -311,7 +312,7 @@ internal sealed unsafe class QuestFunctions
return 1000 * quest.AllSteps().Count(x => x.Step.AetheryteShortcut != null);
}
private List<ElementId> GetPriorityQuestsThatCanBeAccepted()
public List<ElementId> GetPriorityQuests()
{
List<ElementId> priorityQuests =
[
@ -349,7 +350,6 @@ internal sealed unsafe class QuestFunctions
return priorityQuests
.Where(_questRegistry.IsKnownQuest)
.Where(IsReadyToAcceptQuest)
.ToList();
}

View File

@ -120,6 +120,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<LifestreamIpc>();
serviceCollection.AddSingleton<YesAlreadyIpc>();
serviceCollection.AddSingleton<ArtisanIpc>();
serviceCollection.AddSingleton<QuestionableIpc>();
}
private static void AddTaskFactories(ServiceCollection serviceCollection)
@ -184,6 +185,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<InteractionUiController>();
serviceCollection.AddSingleton<LeveUiController>();
serviceCollection.AddSingleton<ICombatModule, Mount128Module>();
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
}
@ -235,6 +237,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceProvider.GetRequiredService<CreditsController>();
serviceProvider.GetRequiredService<HelpUiController>();
serviceProvider.GetRequiredService<LeveUiController>();
serviceProvider.GetRequiredService<QuestionableIpc>();
serviceProvider.GetRequiredService<DalamudInitializer>();
}

View File

@ -177,7 +177,7 @@ internal sealed partial class ActiveQuestComponent
{
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
ImGui.TextUnformatted(
$"Next Quest: {Shorten(currentQuest.Quest.Info.Name)} / {currentQuest.Sequence} / {currentQuest.Step}");
$"Next Quest: {Shorten(nextQuest.Quest.Info.Name)} / {nextQuest.Sequence} / {nextQuest.Step}");
}
}
}