1
0
forked from liza/Questionable

Very experimental combat implementation (requires RSR)

This commit is contained in:
Liza 2024-07-14 03:42:30 +02:00
parent 7d0b49645d
commit b4be834f13
Signed by: liza
GPG Key ID: 7199F8D727D55F67
43 changed files with 2298 additions and 59 deletions

View File

@ -55,6 +55,9 @@ public class QuestSourceGenerator : ISourceGenerator
continue; continue;
string name = Path.GetFileName(additionalFile.Path); string name = Path.GetFileName(additionalFile.Path);
if (!name.Contains('_'))
continue;
ushort id = ushort.Parse(name.Substring(0, name.IndexOf('_'))); ushort id = ushort.Parse(name.Substring(0, name.IndexOf('_')));
var text = additionalFile.GetText(); var text = additionalFile.GetText();

View File

@ -0,0 +1,56 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010859,
"Position": {
"X": -743.86206,
"Y": 78.96533,
"Z": 457.14502
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11443
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,57 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1032140,
"Position": {
"X": -459.89166,
"Y": 42.65948,
"Z": -305.71454
},
"TerritoryId": 816,
"InteractionType": "Say",
"ChatMessage": {
"Key": "TEXT_BANPIX103_03691_SAYTODO_000_030"
},
"Fly": true
}
]
},
{
"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

@ -0,0 +1,106 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Comment": "TODO Has multiple possible targets, unsure if QW works",
"Steps": [
{
"Position": {
"X": -342.05676,
"Y": 37.5036,
"Z": 434.53723
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-128
]
},
{
"DataId": 2010860,
"Position": {
"X": -340.38306,
"Y": 38.77307,
"Z": 434.07336
},
"TerritoryId": 816,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2010861,
"Position": {
"X": -332.44836,
"Y": 44.47998,
"Z": 462.15002
},
"TerritoryId": 816,
"InteractionType": "Interact",
"SkipIf": [
"NotTargetable"
]
},
{
"DataId": 2010863,
"Position": {
"X": -293.08008,
"Y": 42.70996,
"Z": 463.61487
},
"TerritoryId": 816,
"InteractionType": "Interact",
"SkipIf": [
"NotTargetable"
],
"IgnoreDistanceToObject": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,57 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010864,
"Position": {
"X": 3.8909912,
"Y": 13.90094,
"Z": 637.23206
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11444
],
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,75 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"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": -338.70084,
"Y": 9.922639,
"Z": -181.90271
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Fly": true
},
{
"Position": {
"X": -332.49713,
"Y": -44.52391,
"Z": -242.4296
},
"TerritoryId": 816,
"InteractionType": "WalkTo",
"Fly": true,
"DisableNavmesh": true
},
{
"DataId": 2010866,
"Position": {
"X": -335.59174,
"Y": -54.154297,
"Z": -293.41577
},
"TerritoryId": 816,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"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

@ -0,0 +1,58 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010868,
"Position": {
"X": -504.32596,
"Y": 77.22583,
"Z": -410.11676
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11445
],
"Fly": true
}
]
},
{
"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

@ -0,0 +1,81 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010888,
"Position": {
"X": -171.58777,
"Y": 5.2338257,
"Z": -252.88782
},
"TerritoryId": 816,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2010889,
"Position": {
"X": -170.1839,
"Y": 4.9591064,
"Z": -283.0396
},
"TerritoryId": 816,
"InteractionType": "Interact",
"Fly": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},
{
"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

@ -0,0 +1,59 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010890,
"Position": {
"X": 10.0251465,
"Y": 32.944214,
"Z": -608.6061
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11446
],
"AetheryteShortcut": "Il Mheg - Wolekdorf",
"Fly": true
}
]
},
{
"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

@ -0,0 +1,53 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1032167,
"Position": {
"X": -416.52557,
"Y": 20.303928,
"Z": 221.75928
},
"TerritoryId": 816,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,59 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010901,
"Position": {
"X": -413.96204,
"Y": -0.10687256,
"Z": -55.77173
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11451
],
"Fly": true,
"Comment": "TODO Combat is optional, check where we should walk"
}
]
},
{
"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

@ -0,0 +1,57 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2010909,
"Position": {
"X": -242.08441,
"Y": 0.83917236,
"Z": 223.80408
},
"TerritoryId": 816,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
11450
],
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,43 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031809,
"Position": {
"X": -454.3069,
"Y": 71.43217,
"Z": 575.1278
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,231 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031806,
"Position": {
"X": -464.59143,
"Y": 71.76874,
"Z": 573.8766
},
"TerritoryId": 816,
"InteractionType": "AcceptQuest",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_BANPIX003_03685_Q1_000_000",
"Answer": "TEXT_BANPIX003_03685_A1_000_003"
}
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1031892,
"Position": {
"X": -461.5702,
"Y": 72.51754,
"Z": 586.48047
},
"TerritoryId": 816,
"InteractionType": "Interact",
"TargetTerritoryId": 891
},
{
"DataId": 1031866,
"Position": {
"X": 78.690796,
"Y": 0.15887591,
"Z": 50.0343
},
"TerritoryId": 891,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1031865,
"Position": {
"X": 76.37134,
"Y": 0.15887591,
"Z": 51.987427
},
"TerritoryId": 891,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1031867,
"Position": {
"X": 303.15088,
"Y": 1.4685827,
"Z": -313.34406
},
"TerritoryId": 815,
"InteractionType": "Interact",
"AetheryteShortcut": "Amh Araeng - Mord Souq",
"Fly": true
}
]
},
{
"Sequence": 4,
"Steps": [
{
"Position": {
"X": 201.67809,
"Y": 7.1558266,
"Z": -137.17564
},
"TerritoryId": 815,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 1031869,
"Position": {
"X": 201.06812,
"Y": 7.1558266,
"Z": -138.81134
},
"TerritoryId": 815,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 5,
"Steps": [
{
"Position": {
"X": 355.25076,
"Y": -19.54202,
"Z": -4.2170615
},
"StopDistance": 0.5,
"TerritoryId": 815,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
11439
]
}
]
},
{
"Sequence": 6,
"Steps": [
{
"DataId": 1031871,
"Position": {
"X": 350.05713,
"Y": -18.544811,
"Z": -15.793152
},
"StopDistance": 7,
"TerritoryId": 815,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 7,
"Steps": [
{
"Position": {
"X": 201.67809,
"Y": 7.1558266,
"Z": -137.17564
},
"TerritoryId": 815,
"InteractionType": "WalkTo",
"Fly": true
},
{
"DataId": 1031869,
"Position": {
"X": 201.06812,
"Y": 7.1558266,
"Z": -138.81134
},
"TerritoryId": 815,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 8,
"Steps": [
{
"DataId": 1031892,
"Position": {
"X": -461.5702,
"Y": 72.51754,
"Z": 586.48047
},
"TerritoryId": 816,
"InteractionType": "Interact",
"TargetTerritoryId": 891,
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true
},
{
"DataId": 1032030,
"Position": {
"X": 90.40967,
"Y": 40.45613,
"Z": -105.48566
},
"TerritoryId": 891,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 9,
"Steps": [
{
"DataId": 1032352,
"Position": {
"X": 95.994385,
"Y": 38.906254,
"Z": -89.37213
},
"TerritoryId": 891,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031806,
"Position": {
"X": -464.59143,
"Y": 71.76874,
"Z": 573.8766
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,214 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1031839,
"Position": {
"X": 95.628296,
"Y": 1.490116E-08,
"Z": 204.11987
},
"TerritoryId": 819,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1031843,
"Position": {
"X": 152.48328,
"Y": 0.21766767,
"Z": 655.4512
},
"TerritoryId": 813,
"InteractionType": "Interact",
"AetheryteShortcut": "Lakeland - Fort Jobb",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1031845,
"Position": {
"X": -200.09155,
"Y": 2.2048368,
"Z": 737.8804
},
"TerritoryId": 813,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"Position": {
"X": 234.24565,
"Y": 10.83118,
"Z": 738.46594
},
"StopDistance": 1,
"TerritoryId": 813,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
11437
],
"Fly": true
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 1031847,
"Position": {
"X": 231.92188,
"Y": 9.887029,
"Z": 726.74133
},
"StopDistance": 7,
"TerritoryId": 813,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 5,
"Steps": [
{
"DataId": 1031846,
"Position": {
"X": -204.76086,
"Y": 2.4649847,
"Z": 739.9557
},
"TerritoryId": 813,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 6,
"Steps": [
{
"DataId": 1031808,
"Position": {
"X": -461.53967,
"Y": 72.51729,
"Z": 586.48047
},
"TerritoryId": 816,
"InteractionType": "Interact",
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true,
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_BANPIX001_03683_EVENTAREA_WARP_000_133",
"Yes": true
}
]
}
]
},
{
"Sequence": 7,
"Steps": [
{
"Position": {
"X": 0,
"Y": 0,
"Z": 0
},
"TerritoryId": 1,
"InteractionType": "WalkTo",
"Comment": "Filler"
}
]
},
{
"Sequence": 8,
"Steps": [
{
"DataId": 1031850,
"Position": {
"X": 55.588623,
"Y": -1.6532946,
"Z": 48.599854
},
"TerritoryId": 889,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 9,
"Steps": [
{
"Position": {
"X": 0,
"Y": 0,
"Z": 0
},
"TerritoryId": 1,
"InteractionType": "WalkTo",
"Comment": "Filler"
}
]
},
{
"Sequence": 10,
"Steps": [
{
"DataId": 1032348,
"Position": {
"X": -18.295654,
"Y": 6.8822618,
"Z": -67.338135
},
"TerritoryId": 890,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1031806,
"Position": {
"X": -464.59143,
"Y": 71.76874,
"Z": 573.8766
},
"TerritoryId": 816,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Il Mheg - Lydha Lran",
"Fly": true,
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_BANPIX001_03683_Q3_000_000",
"Answer": "TEXT_BANPIX001_03683_A3_000_001"
}
]
}
]
}
]
}

View File

@ -0,0 +1,70 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1037644,
"Position": {
"X": -293.72095,
"Y": 1.4600283,
"Z": 551.0491
},
"TerritoryId": 957,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1042366,
"Position": {
"X": 185.56494,
"Y": 1.8742322,
"Z": 760.4332
},
"TerritoryId": 957,
"InteractionType": "Interact",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,58 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2012850,
"Position": {
"X": -399.74066,
"Y": 58.91504,
"Z": -354.97064
},
"TerritoryId": 957,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
14674,
14675
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,110 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1042367,
"Position": {
"X": 507.01135,
"Y": 12.589098,
"Z": -482.96332
},
"TerritoryId": 957,
"InteractionType": "Interact",
"AetheryteShortcut": "Thavnair - Palaka's Stand",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2012851,
"Position": {
"X": 427.4204,
"Y": 18.44812,
"Z": -451.37714
},
"TerritoryId": 957,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 2012851,
"Position": {
"X": 427.4204,
"Y": 18.44812,
"Z": -451.37714
},
"TerritoryId": 957,
"InteractionType": "Interact",
"SkipIf": [
"NotTargetable"
]
},
{
"Position": {
"X": 436.76804,
"Y": 21.84539,
"Z": -466.46533
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"DisableNavmesh": true
},
{
"DataId": 1042367,
"Position": {
"X": 507.01135,
"Y": 12.589098,
"Z": -482.96332
},
"TerritoryId": 957,
"InteractionType": "Interact",
"DisableNavmesh": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,175 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1042302,
"Position": {
"X": -102.43384,
"Y": 40.00001,
"Z": 331.89893
},
"TerritoryId": 957,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": 436.3157,
"Y": 3.1168795,
"Z": -241.61731
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"Fly": true,
"Land": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-32
]
},
{
"DataId": 1042372,
"Position": {
"X": 441.062,
"Y": 3.5405273,
"Z": -238.78845
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Action",
"Action": "Yellow Gulal",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"Position": {
"X": 378.90213,
"Y": 3.1168797,
"Z": -226.82733
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"Fly": true,
"Land": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-64
]
},
{
"DataId": 1042371,
"Position": {
"X": 376.7605,
"Y": 3.3540652,
"Z": -225.33002
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Action",
"Action": "Blue Gulal",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"Position": {
"X": 404.06558,
"Y": 13.027411,
"Z": -307.05457
},
"TerritoryId": 957,
"InteractionType": "WalkTo",
"Fly": true,
"Land": true,
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
-128
]
},
{
"DataId": 1042373,
"Position": {
"X": 406.1189,
"Y": 13.0274105,
"Z": -311.0857
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Action",
"Action": "Red Gulal",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,70 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1042376,
"Position": {
"X": 205.34058,
"Y": 63.981293,
"Z": -640.4059
},
"TerritoryId": 957,
"InteractionType": "Interact",
"AetheryteShortcut": "Thavnair - Palaka's Stand",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2012908,
"Position": {
"X": 208.23987,
"Y": 65.62903,
"Z": -642.1149
},
"TerritoryId": 957,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]
}
]
}

View File

@ -0,0 +1,73 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -605.46277,
"Y": 2.1626635,
"Z": -336.87347
},
"StopDistance": 0.25,
"TerritoryId": 957,
"InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [
14676
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1042378,
"Position": {
"X": -605.46277,
"Y": 2.1626635,
"Z": -336.87347
},
"StopDistance": 5,
"TerritoryId": 957,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Thavnair - Yedlihmad",
"Fly": true
}
]
}
]
}

View File

@ -13,7 +13,7 @@
"Z": 321.06494 "Z": 321.06494
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "AcceptQuest"
} }
] ]
}, },

View File

@ -13,7 +13,7 @@
"Z": 321.06494 "Z": 321.06494
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "AcceptQuest"
} }
] ]
}, },

View File

@ -13,7 +13,7 @@
"Z": 321.06494 "Z": 321.06494
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "AcceptQuest"
} }
] ]
}, },

View File

@ -13,7 +13,7 @@
"Z": 321.06494 "Z": 321.06494
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "AcceptQuest"
} }
] ]
}, },

View File

@ -13,7 +13,7 @@
"Z": 321.06494 "Z": 321.06494
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "AcceptQuest"
} }
] ]
}, },

View File

@ -13,7 +13,7 @@
"Z": 321.06494 "Z": 321.06494
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "AcceptQuest"
} }
] ]
}, },

View File

@ -0,0 +1,43 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1042301,
"Position": {
"X": -66.02582,
"Y": 39.994705,
"Z": 321.06494
},
"TerritoryId": 957,
"InteractionType": "CompleteQuest",
"Fly": true
}
]
}
]
}

View File

@ -1009,12 +1009,14 @@
}, },
"required": [ "required": [
"Sequence" "Sequence"
] ],
"additionalProperties": false
} }
} }
}, },
"required": [ "required": [
"QuestSequence", "QuestSequence",
"Author" "Author"
] ],
"additionalProperties": false
} }

View File

@ -0,0 +1,155 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Microsoft.Extensions.Logging;
using Questionable.Controller.CombatModules;
namespace Questionable.Controller;
internal sealed class CombatController
{
private readonly List<ICombatModule> _combatModules;
private readonly ITargetManager _targetManager;
private readonly IObjectTable _objectTable;
private readonly ICondition _condition;
private readonly IClientState _clientState;
private readonly ILogger<CombatController> _logger;
private CurrentFight? _currentFight;
public CombatController(IEnumerable<ICombatModule> combatModules, ITargetManager targetManager,
IObjectTable objectTable, ICondition condition, IClientState clientState, ILogger<CombatController> logger)
{
_combatModules = combatModules.ToList();
_targetManager = targetManager;
_objectTable = objectTable;
_condition = condition;
_clientState = clientState;
_logger = logger;
}
public bool IsRunning => _currentFight != null;
public bool Start(CombatData combatData)
{
Stop();
var combatModule = _combatModules.FirstOrDefault(x => x.IsLoaded);
if (combatModule == null)
return false;
if (combatModule.Start())
{
_currentFight = new CurrentFight
{
Module = combatModule,
Data = combatData,
};
return true;
}
else
return false;
}
/// <returns>true if still in combat, false otherwise</returns>
public bool Update()
{
if (_currentFight == null)
return false;
var target = _targetManager.Target;
if (target != null)
{
if (IsEnemyToKill(target))
return true;
var nextTarget = FindNextTarget();
if (nextTarget != null)
{
_logger.LogInformation("Changing next target to {TargetName} ({TargetId:X8})",
nextTarget.Name.ToString(), nextTarget.GameObjectId);
_targetManager.Target = nextTarget;
_currentFight.Module.SetTarget(nextTarget);
}
else
{
_logger.LogInformation("Resetting next target");
_targetManager.Target = null;
}
}
else
{
var nextTarget = FindNextTarget();
if (nextTarget != null)
{
_logger.LogInformation("Setting next target to {TargetName} ({TargetId:X8})",
nextTarget.Name.ToString(), nextTarget.GameObjectId);
_targetManager.Target = nextTarget;
_currentFight.Module.SetTarget(nextTarget);
}
}
return _condition[ConditionFlag.InCombat];
}
private IGameObject? FindNextTarget()
{
return _objectTable.Where(IsEnemyToKill).MinBy(x => (x.Position - _clientState.LocalPlayer!.Position).Length());
}
private unsafe bool IsEnemyToKill(IGameObject gameObject)
{
if (gameObject is IBattleChara battleChara)
{
if (battleChara.IsDead)
return false;
if (!battleChara.IsTargetable)
return false;
if (battleChara.TargetObjectId == _clientState.LocalPlayer?.GameObjectId)
return true;
if (_currentFight != null && _currentFight.Data.KillEnemyDataIds.Contains(battleChara.DataId))
return true;
if (battleChara.StatusFlags.HasFlag(StatusFlags.Hostile))
{
var gameObjectStruct = (GameObject*)gameObject.Address;
return gameObjectStruct->NamePlateIconId != 0;
}
else
return false;
}
else
return false;
}
public void Stop()
{
if (_currentFight != null)
{
_logger.LogInformation("Stopping current fight");
_currentFight.Module.Stop();
}
_currentFight = null;
}
private sealed class CurrentFight
{
public required ICombatModule Module { get; init; }
public required CombatData Data { get; init; }
}
public sealed class CombatData
{
public required ReadOnlyCollection<uint> KillEnemyDataIds { get; init; }
}
}

View File

@ -0,0 +1,14 @@
using Dalamud.Game.ClientState.Objects.Types;
namespace Questionable.Controller.CombatModules;
internal interface ICombatModule
{
bool IsLoaded { get; }
bool Start();
bool Stop();
void SetTarget(IGameObject nextTarget);
}

View File

@ -0,0 +1,98 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Ipc.Exceptions;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Model;
namespace Questionable.Controller.CombatModules;
internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
{
private readonly ILogger<RotationSolverRebornModule> _logger;
private readonly MovementController _movementController;
private readonly IClientState _clientState;
private readonly ICallGateSubscriber<string, object> _test;
private readonly ICallGateSubscriber<StateCommandType, object> _changeOperationMode;
public RotationSolverRebornModule(ILogger<RotationSolverRebornModule> logger, MovementController movementController,
IClientState clientState, IDalamudPluginInterface pluginInterface)
{
_logger = logger;
_movementController = movementController;
_clientState = clientState;
_test = pluginInterface.GetIpcSubscriber<string, object>("RotationSolverReborn.Test");
_changeOperationMode =
pluginInterface.GetIpcSubscriber<StateCommandType, object>("RotationSolverReborn.ChangeOperatingMode");
}
public bool IsLoaded
{
get
{
try
{
_test.InvokeAction("Validate RSR is callable from Questionable");
return true;
}
catch (IpcError)
{
return false;
}
}
}
public bool Start()
{
try
{
_changeOperationMode.InvokeAction(StateCommandType.Manual);
return true;
}
catch (IpcError e)
{
_logger.LogWarning(e, "Could not start combat");
return false;
}
}
public bool Stop()
{
try
{
_changeOperationMode.InvokeAction(StateCommandType.Off);
return true;
}
catch (IpcError e)
{
_logger.LogWarning(e, "Could not turn off combat");
return false;
}
}
public void SetTarget(IGameObject gameObject)
{
var player = _clientState.LocalPlayer;
if (player == null)
return; // uh oh
float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
float maxDistance = player.ClassJob.GameData?.Role is 3 or 4 ? 25f : 3f;
if (actualDistance - hitboxOffset > maxDistance)
_movementController.NavigateTo(EMovementType.Combat, null, [gameObject.Position], false, false,
maxDistance + hitboxOffset - 0.25f, true);
}
public void Dispose() => Stop();
enum StateCommandType : byte
{
Off,
Auto,
Manual,
}
}

View File

@ -151,10 +151,7 @@ internal sealed class GameUiController : IDisposable
if (string.IsNullOrEmpty(actualPrompt)) if (string.IsNullOrEmpty(actualPrompt))
actualPrompt = null; actualPrompt = null;
List<string?> answers = new(); var answers = GetChoices(addonSelectIconString);
for (ushort i = 0; i < addonSelectIconString->AtkUnitBase.AtkValues[5].Int; i++)
answers.Add(addonSelectIconString->AtkUnitBase.AtkValues[i * 3 + 7].ReadAtkString());
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps); int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps);
if (answer != null) if (answer != null)
{ {
@ -173,6 +170,14 @@ internal sealed class GameUiController : IDisposable
} }
} }
public static unsafe List<string?> GetChoices(AddonSelectIconString* addonSelectIconString)
{
List<string?> answers = new();
for (ushort i = 0; i < addonSelectIconString->AtkUnitBase.AtkValues[5].Int; i++)
answers.Add( addonSelectIconString->AtkValues[i * 3 + 7].ReadAtkString());
return answers;
}
private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps) private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
{ {
@ -503,7 +508,7 @@ internal sealed class GameUiController : IDisposable
/// <summary> /// <summary>
/// Ensures characters like '-' are handled equally in both strings. /// Ensures characters like '-' are handled equally in both strings.
/// </summary> /// </summary>
private static bool GameStringEquals(string? a, string? b) public static bool GameStringEquals(string? a, string? b)
{ {
if (a == null) if (a == null)
return b == null; return b == null;

View File

@ -17,6 +17,7 @@ internal sealed class QuestController
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly CombatController _combatController;
private readonly ILogger<QuestController> _logger; private readonly ILogger<QuestController> _logger;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
private readonly IKeyState _keyState; private readonly IKeyState _keyState;
@ -38,6 +39,7 @@ internal sealed class QuestController
IClientState clientState, IClientState clientState,
GameFunctions gameFunctions, GameFunctions gameFunctions,
MovementController movementController, MovementController movementController,
CombatController combatController,
ILogger<QuestController> logger, ILogger<QuestController> logger,
QuestRegistry questRegistry, QuestRegistry questRegistry,
IKeyState keyState, IKeyState keyState,
@ -48,6 +50,7 @@ internal sealed class QuestController
_clientState = clientState; _clientState = clientState;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_movementController = movementController; _movementController = movementController;
_combatController = combatController;
_logger = logger; _logger = logger;
_questRegistry = questRegistry; _questRegistry = questRegistry;
_keyState = keyState; _keyState = keyState;
@ -106,6 +109,7 @@ internal sealed class QuestController
{ {
Stop("ESC pressed"); Stop("ESC pressed");
_movementController.Stop(); _movementController.Stop();
_combatController.Stop();
} }
} }
@ -322,6 +326,7 @@ internal sealed class QuestController
_taskQueue.Clear(); _taskQueue.Clear();
_yesAlreadyIpc.RestoreYesAlready(); _yesAlreadyIpc.RestoreYesAlready();
_combatController.Stop();
} }
public void Stop(string label, bool continueIfAutomatic = false) public void Stop(string label, bool continueIfAutomatic = false)
@ -473,6 +478,7 @@ internal sealed class QuestController
} }
_movementController.Stop(); _movementController.Stop();
_combatController.Stop();
var newTasks = _taskFactories var newTasks = _taskFactories
.SelectMany(x => .SelectMany(x =>

View File

@ -57,15 +57,34 @@ internal sealed class QuestRegistry
new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "QuestPaths")); new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "QuestPaths"));
if (pathProjectDirectory.Exists) if (pathProjectDirectory.Exists)
{ {
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "2.x - A Realm Reborn"))); try
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "5.x - Shadowbringers"))); {
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "6.x - Endwalker"))); LoadFromDirectory(
LoadFromDirectory(new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "7.x - Dawntrail"))); new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "2.x - A Realm Reborn")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "5.x - Shadowbringers")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "6.x - Endwalker")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "7.x - Dawntrail")));
}
catch (Exception e)
{
_quests.Clear();
_logger.LogError(e, "Failed to load quests from project directory");
}
} }
} }
#endif #endif
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests"))); try
{
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load all quests from user directory (some may have been successfully loaded)");
}
#if !RELEASE #if !RELEASE
foreach (var quest in _quests.Values) foreach (var quest in _quests.Values)
@ -83,14 +102,17 @@ internal sealed class QuestRegistry
private void LoadQuestFromStream(string fileName, Stream stream) private void LoadQuestFromStream(string fileName, Stream stream)
{ {
_logger.LogTrace("Loading quest from '{FileName}'", fileName); _logger.LogTrace("Loading quest from '{FileName}'", fileName);
var questId = ExtractQuestIdFromName(fileName); ushort? questId = ExtractQuestIdFromName(fileName);
if (questId == null)
return;
Quest quest = new Quest Quest quest = new Quest
{ {
QuestId = questId, QuestId = questId.Value,
Root = JsonSerializer.Deserialize<QuestRoot>(stream)!, Root = JsonSerializer.Deserialize<QuestRoot>(stream)!,
Info = _questData.GetQuestInfo(questId), Info = _questData.GetQuestInfo(questId.Value),
}; };
_quests[questId] = quest; _quests[questId.Value] = quest;
} }
private void LoadFromDirectory(DirectoryInfo directory) private void LoadFromDirectory(DirectoryInfo directory)
@ -119,11 +141,14 @@ internal sealed class QuestRegistry
LoadFromDirectory(childDirectory); LoadFromDirectory(childDirectory);
} }
private static ushort ExtractQuestIdFromName(string resourceName) private static ushort? ExtractQuestIdFromName(string resourceName)
{ {
string name = resourceName.Substring(0, resourceName.Length - ".json".Length); string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1); name = name.Substring(name.LastIndexOf('.') + 1);
if (!name.Contains('_', StringComparison.Ordinal))
return null;
string[] parts = name.Split('_', 2); string[] parts = name.Split('_', 2);
return ushort.Parse(parts[0], CultureInfo.InvariantCulture); return ushort.Parse(parts[0], CultureInfo.InvariantCulture);
} }

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Common;
using Questionable.Controller.Utils;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
@ -19,29 +21,105 @@ internal static class Combat
ArgumentNullException.ThrowIfNull(step.EnemySpawnType); ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
var unmount = serviceProvider.GetRequiredService<UnmountTask>(); var unmount = serviceProvider.GetRequiredService<UnmountTask>();
if (step.EnemySpawnType == EEnemySpawnType.AfterInteraction) switch (step.EnemySpawnType)
{ {
ArgumentNullException.ThrowIfNull(step.DataId); case EEnemySpawnType.AfterInteraction:
{
ArgumentNullException.ThrowIfNull(step.DataId);
var task = serviceProvider.GetRequiredService<Interact.DoInteract>() var interaction = serviceProvider.GetRequiredService<Interact.DoInteract>()
.With(step.DataId.Value, true); .With(step.DataId.Value, true);
return [unmount, task]; return [unmount, interaction, CreateTask(quest, sequence, step)];
} }
else if (step.EnemySpawnType == EEnemySpawnType.AfterItemUse)
{
ArgumentNullException.ThrowIfNull(step.DataId);
ArgumentNullException.ThrowIfNull(step.ItemId);
var task = serviceProvider.GetRequiredService<UseItem.UseOnObject>() case EEnemySpawnType.AfterItemUse:
.With(step.DataId.Value, step.ItemId.Value); {
return [unmount, task]; ArgumentNullException.ThrowIfNull(step.DataId);
ArgumentNullException.ThrowIfNull(step.ItemId);
var useItem = serviceProvider.GetRequiredService<UseItem.UseOnObject>()
.With(step.DataId.Value, step.ItemId.Value);
return [unmount, useItem, CreateTask(quest, sequence, step)];
}
case EEnemySpawnType.AutoOnEnterArea:
// automatically triggered when entering area, i.e. only unmount
return [unmount, CreateTask(quest, sequence, step)];
case EEnemySpawnType.OverworldEnemies:
// TODO currently not handled
return [unmount];
default:
throw new ArgumentOutOfRangeException(nameof(step), $"Unknown spawn type {step.EnemySpawnType}");
} }
else
// automatically triggered when entering area, i.e. only unmount
return [unmount];
} }
public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step) public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
=> throw new InvalidOperationException(); {
bool isLastStep = sequence.Steps.Last() == step;
return serviceProvider.GetRequiredService<HandleCombat>()
.With(quest.QuestId, isLastStep, step.KillEnemyDataIds, step.CompletionQuestVariablesFlags);
}
}
internal sealed class HandleCombat(CombatController combatController, GameFunctions gameFunctions) : ITask
{
private ushort _questId;
private bool _isLastStep;
private CombatController.CombatData _combatData = null!;
private IList<short?> _completionQuestVariableFlags = null!;
public ITask With(ushort questId, bool isLastStep, IList<uint> killEnemyDataIds,
IList<short?> completionQuestVariablesFlags)
{
_questId = questId;
_isLastStep = isLastStep;
_combatData = new CombatController.CombatData
{
KillEnemyDataIds = killEnemyDataIds.AsReadOnly(),
};
_completionQuestVariableFlags = completionQuestVariablesFlags;
return this;
}
public bool Start() => combatController.Start(_combatData);
public ETaskResult Update()
{
if (combatController.Update())
return ETaskResult.StillRunning;
// if our quest step has any completion flags, we need to check if they are set
if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags))
{
var questWork = gameFunctions.GetQuestEx(_questId);
if (questWork == null)
return ETaskResult.StillRunning;
if (!QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork.Value, false))
return ETaskResult.StillRunning;
}
// the last step, by definition, can only be progressed by the game recognizing we're in a new sequence,
// so this is an indefinite wait
if (_isLastStep)
return ETaskResult.StillRunning;
else
{
combatController.Stop();
return ETaskResult.TaskComplete;
}
}
public override string ToString()
{
if (QuestWorkUtils.HasCompletionFlags(_completionQuestVariableFlags))
return "HandleCombat(wait: QW flags)";
else if (_isLastStep)
return "HandleCombat(wait: next sequence)";
else
return "HandleCombat(wait: not in combat)";
}
} }
} }

View File

@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Utils;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
@ -112,7 +113,8 @@ internal static class SkipCondition
} }
QuestWork? questWork = gameFunctions.GetQuestEx(QuestId); QuestWork? questWork = gameFunctions.GetQuestEx(QuestId);
if (questWork != null && Step.MatchesQuestVariables(questWork.Value, true)) if (questWork != null &&
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value, true))
{ {
logger.LogInformation("Skipping step, as quest variables match"); logger.LogInformation("Skipping step, as quest variables match");
return true; return true;

View File

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Common;
using Questionable.Controller.Utils;
using Questionable.Data; using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
@ -17,7 +18,10 @@ namespace Questionable.Controller.Steps.Shared;
internal static class WaitAtEnd internal static class WaitAtEnd
{ {
internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState, ICondition condition, internal sealed class Factory(
IServiceProvider serviceProvider,
IClientState clientState,
ICondition condition,
TerritoryData territoryData) TerritoryData territoryData)
: ITaskFactory : ITaskFactory
{ {
@ -106,14 +110,16 @@ internal static class WaitAtEnd
case EInteractionType.AcceptQuest: case EInteractionType.AcceptQuest:
return return
[ [
serviceProvider.GetRequiredService<WaitQuestAccepted>().With(step.PickupQuestId ?? quest.QuestId), serviceProvider.GetRequiredService<WaitQuestAccepted>()
.With(step.PickupQuestId ?? quest.QuestId),
serviceProvider.GetRequiredService<WaitDelay>() serviceProvider.GetRequiredService<WaitDelay>()
]; ];
case EInteractionType.CompleteQuest: case EInteractionType.CompleteQuest:
return return
[ [
serviceProvider.GetRequiredService<WaitQuestCompleted>().With(step.TurnInQuestId ?? quest.QuestId), serviceProvider.GetRequiredService<WaitQuestCompleted>()
.With(step.TurnInQuestId ?? quest.QuestId),
serviceProvider.GetRequiredService<WaitDelay>() serviceProvider.GetRequiredService<WaitDelay>()
]; ];
@ -167,7 +173,8 @@ internal static class WaitAtEnd
public ETaskResult Update() public ETaskResult Update()
{ {
QuestWork? questWork = gameFunctions.GetQuestEx(Quest.QuestId); QuestWork? questWork = gameFunctions.GetQuestEx(Quest.QuestId);
return questWork != null && Step.MatchesQuestVariables(questWork.Value, false) return questWork != null &&
QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value, false)
? ETaskResult.TaskComplete ? ETaskResult.TaskComplete
: ETaskResult.StillRunning; : ETaskResult.StillRunning;
} }

View File

@ -1,22 +1,29 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
namespace Questionable.Model.V1; namespace Questionable.Controller.Utils;
internal static class QuestStepExtensions internal static class QuestWorkUtils
{ {
public static bool HasCompletionFlags(IList<short?> completionQuestVariablesFlags)
{
return completionQuestVariablesFlags.Count == 6 && completionQuestVariablesFlags.Any(x => x != null);
}
/// <summary> /// <summary>
/// Positive values: Must be set to this value; will wait for the step to have these set. /// Positive values: Must be set to this value; will wait for the step to have these set.
/// Negative values: Will skip if set to this value, won't wait for this to be set. /// Negative values: Will skip if set to this value, won't wait for this to be set.
/// </summary> /// </summary>
public static unsafe bool MatchesQuestVariables(this QuestStep step, QuestWork questWork, bool forSkip) public static bool MatchesQuestWork(IList<short?> completionQuestVariablesFlags, QuestWork questWork, bool forSkip)
{ {
if (step.CompletionQuestVariablesFlags.Count != 6) if (!HasCompletionFlags(completionQuestVariablesFlags))
return false; return false;
for (int i = 0; i < 6; ++i) for (int i = 0; i < 6; ++i)
{ {
short? check = step.CompletionQuestVariablesFlags[i]; short? check = completionQuestVariablesFlags[i];
if (check == null) if (check == null)
continue; continue;

View File

@ -5,6 +5,9 @@ using System.Linq;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using LLib.GameUI;
using Questionable.Controller;
using Questionable.Model; using Questionable.Model;
using Quest = Lumina.Excel.GeneratedSheets.Quest; using Quest = Lumina.Excel.GeneratedSheets.Quest;
@ -14,13 +17,15 @@ internal sealed class QuestData
{ {
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly IGameGui _gameGui;
private readonly ImmutableDictionary<ushort, QuestInfo> _quests; private readonly ImmutableDictionary<ushort, QuestInfo> _quests;
public QuestData(IDataManager dataManager, ITargetManager targetManager, IChatGui chatGui) public QuestData(IDataManager dataManager, ITargetManager targetManager, IChatGui chatGui, IGameGui gameGui)
{ {
_targetManager = targetManager; _targetManager = targetManager;
_chatGui = chatGui; _chatGui = chatGui;
_gameGui = gameGui;
_quests = dataManager.GetExcelSheet<Quest>()! _quests = dataManager.GetExcelSheet<Quest>()!
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
@ -43,7 +48,7 @@ internal sealed class QuestData
public bool IsIssuerOfAnyQuest(uint targetId) => _quests.Values.Any(x => x.IssuerDataId == targetId); public bool IsIssuerOfAnyQuest(uint targetId) => _quests.Values.Any(x => x.IssuerDataId == targetId);
public void ShowQuestsIssuedByTarget() public unsafe void ShowQuestsIssuedByTarget()
{ {
var targetId = _targetManager.Target?.DataId; var targetId = _targetManager.Target?.DataId;
if (targetId == null) if (targetId == null)
@ -53,9 +58,20 @@ internal sealed class QuestData
} }
List<QuestInfo> quests = GetAllByIssuerDataId(targetId.Value); List<QuestInfo> quests = GetAllByIssuerDataId(targetId.Value);
_chatGui.Print($"{quests.Count} quest(s) issued by target {_targetManager.Target?.Name}:");
if (_gameGui.TryGetAddonByName<AddonSelectIconString>("SelectIconString", out var addonSelectIconString))
{
var answers = GameUiController.GetChoices(addonSelectIconString);
quests = quests.Where(x => answers.Any(y => GameUiController.GameStringEquals(x.Name, y))).ToList();
_chatGui.Print($"{quests.Count} quest(s) currently offered by target {_targetManager.Target?.Name}:");
}
else
{
_chatGui.Print($"{quests.Count} quest(s) issued by target {_targetManager.Target?.Name}:");
}
foreach (QuestInfo quest in quests) foreach (QuestInfo quest in quests)
_chatGui.Print($" {quest.QuestId}_{quest.SimplifiedName}"); _chatGui.Print($" {quest.QuestId}_{quest.SimplifiedName}");
} }
} }

View File

@ -7,4 +7,5 @@ public enum EMovementType
DebugWindow, DebugWindow,
Shortcut, Shortcut,
Landing, Landing,
Combat,
} }

View File

@ -9,6 +9,7 @@ using Dalamud.Plugin.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Controller.CombatModules;
using Questionable.Controller.NavigationOverrides; using Questionable.Controller.NavigationOverrides;
using Questionable.Controller.Steps; using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Shared;
@ -92,7 +93,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddTaskWithFactory<AetherCurrent.Factory, AetherCurrent.DoAttune>(); serviceCollection.AddTaskWithFactory<AetherCurrent.Factory, AetherCurrent.DoAttune>();
serviceCollection.AddTaskWithFactory<AethernetShard.Factory, AethernetShard.DoAttune>(); serviceCollection.AddTaskWithFactory<AethernetShard.Factory, AethernetShard.DoAttune>();
serviceCollection.AddTaskWithFactory<Aetheryte.Factory, Aetheryte.DoAttune>(); serviceCollection.AddTaskWithFactory<Aetheryte.Factory, Aetheryte.DoAttune>();
serviceCollection.AddSingleton<ITaskFactory, Combat.Factory>(); serviceCollection.AddTaskWithFactory<Combat.Factory, Combat.HandleCombat>();
serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>(); serviceCollection.AddTaskWithFactory<Duty.Factory, Duty.OpenDutyFinder>();
serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>(); serviceCollection.AddTaskWithFactory<Emote.Factory, Emote.UseOnObject, Emote.Use>();
serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>(); serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
@ -120,6 +121,9 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<QuestController>(); serviceCollection.AddSingleton<QuestController>();
serviceCollection.AddSingleton<GameUiController>(); serviceCollection.AddSingleton<GameUiController>();
serviceCollection.AddSingleton<NavigationShortcutController>(); serviceCollection.AddSingleton<NavigationShortcutController>();
serviceCollection.AddSingleton<CombatController>();
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
serviceCollection.AddSingleton<QuestWindow>(); serviceCollection.AddSingleton<QuestWindow>();
serviceCollection.AddSingleton<ConfigWindow>(); serviceCollection.AddSingleton<ConfigWindow>();

View File

@ -12,7 +12,6 @@ using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@ -40,6 +39,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly GameUiController _gameUiController; private readonly GameUiController _gameUiController;
private readonly CombatController _combatController;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly NavmeshIpc _navmeshIpc; private readonly NavmeshIpc _navmeshIpc;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
@ -57,6 +57,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
IFramework framework, IFramework framework,
ITargetManager targetManager, ITargetManager targetManager,
GameUiController gameUiController, GameUiController gameUiController,
CombatController combatController,
Configuration configuration, Configuration configuration,
NavmeshIpc navmeshIpc, NavmeshIpc navmeshIpc,
QuestRegistry questRegistry, QuestRegistry questRegistry,
@ -75,6 +76,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
_framework = framework; _framework = framework;
_targetManager = targetManager; _targetManager = targetManager;
_gameUiController = gameUiController; _gameUiController = gameUiController;
_combatController = combatController;
_configuration = configuration; _configuration = configuration;
_navmeshIpc = navmeshIpc; _navmeshIpc = navmeshIpc;
_questRegistry = questRegistry; _questRegistry = questRegistry;
@ -186,8 +188,17 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ImGui.TextUnformatted("(Not accepted)"); ImGui.TextUnformatted("(Not accepted)");
} }
ImGui.TextUnformatted(_questController.DebugState ?? "--");
ImGui.EndDisabled(); ImGui.EndDisabled();
if (_combatController.IsRunning)
ImGui.TextColored(ImGuiColors.DalamudOrange, "In Combat");
else
{
ImGui.BeginDisabled();
ImGui.TextUnformatted(_questController.DebugState ?? "--");
ImGui.EndDisabled();
}
ImGui.TextUnformatted(_questController.Comment ?? "--"); ImGui.TextUnformatted(_questController.Comment ?? "--");
//var nextStep = _questController.GetNextStep(); //var nextStep = _questController.GetNextStep();
@ -523,7 +534,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
{ {
_movementController.Destination = null; _movementController.Destination = null;
_chatFunctions.ExecuteCommand( _chatFunctions.ExecuteCommand(
$"/vnav {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}"); $"/vnav {(_condition[ConditionFlag.Mounted] && _gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
} }
ImGui.EndDisabled(); ImGui.EndDisabled();