continue;
string name = Path.GetFileName(additionalFile.Path);
+ if (!name.Contains('_'))
+ continue;
+
ushort id = ushort.Parse(name.Substring(0, name.IndexOf('_')));
var text = additionalFile.GetText();
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
"Z": 321.06494
},
"TerritoryId": 957,
- "InteractionType": "Interact"
+ "InteractionType": "AcceptQuest"
}
]
},
"Z": 321.06494
},
"TerritoryId": 957,
- "InteractionType": "Interact"
+ "InteractionType": "AcceptQuest"
}
]
},
"Z": 321.06494
},
"TerritoryId": 957,
- "InteractionType": "Interact"
+ "InteractionType": "AcceptQuest"
}
]
},
"Z": 321.06494
},
"TerritoryId": 957,
- "InteractionType": "Interact"
+ "InteractionType": "AcceptQuest"
}
]
},
"Z": 321.06494
},
"TerritoryId": 957,
- "InteractionType": "Interact"
+ "InteractionType": "AcceptQuest"
}
]
},
"Z": 321.06494
},
"TerritoryId": 957,
- "InteractionType": "Interact"
+ "InteractionType": "AcceptQuest"
}
]
},
--- /dev/null
+{
+ "$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
+ }
+ ]
+ }
+ ]
+}
},
"required": [
"Sequence"
- ]
+ ],
+ "additionalProperties": false
}
}
},
"required": [
"QuestSequence",
"Author"
- ]
+ ],
+ "additionalProperties": false
}
--- /dev/null
+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; }
+ }
+}
--- /dev/null
+using Dalamud.Game.ClientState.Objects.Types;
+
+namespace Questionable.Controller.CombatModules;
+
+internal interface ICombatModule
+{
+ bool IsLoaded { get; }
+
+ bool Start();
+
+ bool Stop();
+
+ void SetTarget(IGameObject nextTarget);
+}
--- /dev/null
+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,
+ }
+}
if (string.IsNullOrEmpty(actualPrompt))
actualPrompt = null;
- List<string?> answers = new();
- for (ushort i = 0; i < addonSelectIconString->AtkUnitBase.AtkValues[5].Int; i++)
- answers.Add(addonSelectIconString->AtkUnitBase.AtkValues[i * 3 + 7].ReadAtkString());
-
+ var answers = GetChoices(addonSelectIconString);
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps);
if (answer != null)
{
}
}
+ 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)
{
/// <summary>
/// Ensures characters like '-' are handled equally in both strings.
/// </summary>
- private static bool GameStringEquals(string? a, string? b)
+ public static bool GameStringEquals(string? a, string? b)
{
if (a == null)
return b == null;
private readonly IClientState _clientState;
private readonly GameFunctions _gameFunctions;
private readonly MovementController _movementController;
+ private readonly CombatController _combatController;
private readonly ILogger<QuestController> _logger;
private readonly QuestRegistry _questRegistry;
private readonly IKeyState _keyState;
IClientState clientState,
GameFunctions gameFunctions,
MovementController movementController,
+ CombatController combatController,
ILogger<QuestController> logger,
QuestRegistry questRegistry,
IKeyState keyState,
_clientState = clientState;
_gameFunctions = gameFunctions;
_movementController = movementController;
+ _combatController = combatController;
_logger = logger;
_questRegistry = questRegistry;
_keyState = keyState;
{
Stop("ESC pressed");
_movementController.Stop();
+ _combatController.Stop();
}
}
_taskQueue.Clear();
_yesAlreadyIpc.RestoreYesAlready();
+ _combatController.Stop();
}
public void Stop(string label, bool continueIfAutomatic = false)
}
_movementController.Stop();
+ _combatController.Stop();
var newTasks = _taskFactories
.SelectMany(x =>
new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "QuestPaths"));
if (pathProjectDirectory.Exists)
{
- LoadFromDirectory(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")));
+ try
+ {
+ LoadFromDirectory(
+ 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
- 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
foreach (var quest in _quests.Values)
private void LoadQuestFromStream(string fileName, Stream stream)
{
_logger.LogTrace("Loading quest from '{FileName}'", fileName);
- var questId = ExtractQuestIdFromName(fileName);
+ ushort? questId = ExtractQuestIdFromName(fileName);
+ if (questId == null)
+ return;
+
Quest quest = new Quest
{
- QuestId = questId,
+ QuestId = questId.Value,
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)
LoadFromDirectory(childDirectory);
}
- private static ushort ExtractQuestIdFromName(string resourceName)
+ private static ushort? ExtractQuestIdFromName(string resourceName)
{
string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1);
+ if (!name.Contains('_', StringComparison.Ordinal))
+ return null;
+
string[] parts = name.Split('_', 2);
return ushort.Parse(parts[0], CultureInfo.InvariantCulture);
}
using System;
using System.Collections.Generic;
+using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common;
+using Questionable.Controller.Utils;
using Questionable.Model;
using Questionable.Model.V1;
ArgumentNullException.ThrowIfNull(step.EnemySpawnType);
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>()
- .With(step.DataId.Value, true);
- return [unmount, task];
+ var interaction = serviceProvider.GetRequiredService<Interact.DoInteract>()
+ .With(step.DataId.Value, true);
+ return [unmount, interaction, CreateTask(quest, sequence, step)];
+ }
+
+ case EEnemySpawnType.AfterItemUse:
+ {
+ 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 if (step.EnemySpawnType == EEnemySpawnType.AfterItemUse)
+ }
+
+ public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
+ {
+ 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))
{
- ArgumentNullException.ThrowIfNull(step.DataId);
- ArgumentNullException.ThrowIfNull(step.ItemId);
+ var questWork = gameFunctions.GetQuestEx(_questId);
+ if (questWork == null)
+ return ETaskResult.StillRunning;
- var task = serviceProvider.GetRequiredService<UseItem.UseOnObject>()
- .With(step.DataId.Value, step.ItemId.Value);
- return [unmount, task];
+ 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
- // automatically triggered when entering area, i.e. only unmount
- return [unmount];
+ {
+ combatController.Stop();
+ return ETaskResult.TaskComplete;
+ }
}
- public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
- => throw new InvalidOperationException();
+ 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)";
+ }
}
}
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Questionable.Controller.Utils;
using Questionable.Model;
using Questionable.Model.V1;
}
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");
return true;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.Common;
+using Questionable.Controller.Utils;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.V1;
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)
: ITaskFactory
{
case EInteractionType.AcceptQuest:
return
[
- serviceProvider.GetRequiredService<WaitQuestAccepted>().With(step.PickupQuestId ?? quest.QuestId),
+ serviceProvider.GetRequiredService<WaitQuestAccepted>()
+ .With(step.PickupQuestId ?? quest.QuestId),
serviceProvider.GetRequiredService<WaitDelay>()
];
case EInteractionType.CompleteQuest:
return
[
- serviceProvider.GetRequiredService<WaitQuestCompleted>().With(step.TurnInQuestId ?? quest.QuestId),
+ serviceProvider.GetRequiredService<WaitQuestCompleted>()
+ .With(step.TurnInQuestId ?? quest.QuestId),
serviceProvider.GetRequiredService<WaitDelay>()
];
public ETaskResult Update()
{
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.StillRunning;
}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
+
+namespace Questionable.Controller.Utils;
+
+internal static class QuestWorkUtils
+{
+ public static bool HasCompletionFlags(IList<short?> completionQuestVariablesFlags)
+ {
+ return completionQuestVariablesFlags.Count == 6 && completionQuestVariablesFlags.Any(x => x != null);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public static bool MatchesQuestWork(IList<short?> completionQuestVariablesFlags, QuestWork questWork, bool forSkip)
+ {
+ if (!HasCompletionFlags(completionQuestVariablesFlags))
+ return false;
+
+ for (int i = 0; i < 6; ++i)
+ {
+ short? check = completionQuestVariablesFlags[i];
+ if (check == null)
+ continue;
+
+ byte actualValue = questWork.Variables[i];
+ byte checkByte = check > 0 ? (byte)check : (byte)-check;
+ if (forSkip)
+ {
+ byte expectedValue = (byte)Math.Abs(check.Value);
+ if ((actualValue & checkByte) != expectedValue)
+ return false;
+ }
+ else if (!forSkip && check > 0)
+ {
+ byte expectedValue = check > 0 ? (byte)check : (byte)0;
+ if ((actualValue & checkByte) != expectedValue)
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.UI;
+using LLib.GameUI;
+using Questionable.Controller;
using Questionable.Model;
using Quest = Lumina.Excel.GeneratedSheets.Quest;
{
private readonly ITargetManager _targetManager;
private readonly IChatGui _chatGui;
+ private readonly IGameGui _gameGui;
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;
_chatGui = chatGui;
+ _gameGui = gameGui;
_quests = dataManager.GetExcelSheet<Quest>()!
.Where(x => x.RowId > 0)
public bool IsIssuerOfAnyQuest(uint targetId) => _quests.Values.Any(x => x.IssuerDataId == targetId);
- public void ShowQuestsIssuedByTarget()
+ public unsafe void ShowQuestsIssuedByTarget()
{
var targetId = _targetManager.Target?.DataId;
if (targetId == null)
}
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)
- _chatGui.Print($" {quest.QuestId}_{quest.SimplifiedName}");
+ _chatGui.Print($" {quest.QuestId}_{quest.SimplifiedName}");
}
}
DebugWindow,
Shortcut,
Landing,
+ Combat,
}
+++ /dev/null
-using System;
-using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
-
-namespace Questionable.Model.V1;
-
-internal static class QuestStepExtensions
-{
- /// <summary>
- /// 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.
- /// </summary>
- public static unsafe bool MatchesQuestVariables(this QuestStep step, QuestWork questWork, bool forSkip)
- {
- if (step.CompletionQuestVariablesFlags.Count != 6)
- return false;
-
- for (int i = 0; i < 6; ++i)
- {
- short? check = step.CompletionQuestVariablesFlags[i];
- if (check == null)
- continue;
-
- byte actualValue = questWork.Variables[i];
- byte checkByte = check > 0 ? (byte)check : (byte)-check;
- if (forSkip)
- {
- byte expectedValue = (byte)Math.Abs(check.Value);
- if ((actualValue & checkByte) != expectedValue)
- return false;
- }
- else if (!forSkip && check > 0)
- {
- byte expectedValue = check > 0 ? (byte)check : (byte)0;
- if ((actualValue & checkByte) != expectedValue)
- return false;
- }
- }
-
- return true;
- }
-}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
+using Questionable.Controller.CombatModules;
using Questionable.Controller.NavigationOverrides;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Shared;
serviceCollection.AddTaskWithFactory<AetherCurrent.Factory, AetherCurrent.DoAttune>();
serviceCollection.AddTaskWithFactory<AethernetShard.Factory, AethernetShard.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<Emote.Factory, Emote.UseOnObject, Emote.Use>();
serviceCollection.AddTaskWithFactory<Action.Factory, Action.UseOnObject>();
serviceCollection.AddSingleton<QuestController>();
serviceCollection.AddSingleton<GameUiController>();
serviceCollection.AddSingleton<NavigationShortcutController>();
+ serviceCollection.AddSingleton<CombatController>();
+
+ serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
serviceCollection.AddSingleton<QuestWindow>();
serviceCollection.AddSingleton<ConfigWindow>();
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
private readonly IFramework _framework;
private readonly ITargetManager _targetManager;
private readonly GameUiController _gameUiController;
+ private readonly CombatController _combatController;
private readonly Configuration _configuration;
private readonly NavmeshIpc _navmeshIpc;
private readonly QuestRegistry _questRegistry;
IFramework framework,
ITargetManager targetManager,
GameUiController gameUiController,
+ CombatController combatController,
Configuration configuration,
NavmeshIpc navmeshIpc,
QuestRegistry questRegistry,
_framework = framework;
_targetManager = targetManager;
_gameUiController = gameUiController;
+ _combatController = combatController;
_configuration = configuration;
_navmeshIpc = navmeshIpc;
_questRegistry = questRegistry;
ImGui.TextUnformatted("(Not accepted)");
}
- ImGui.TextUnformatted(_questController.DebugState ?? "--");
ImGui.EndDisabled();
+
+ if (_combatController.IsRunning)
+ ImGui.TextColored(ImGuiColors.DalamudOrange, "In Combat");
+ else
+ {
+ ImGui.BeginDisabled();
+ ImGui.TextUnformatted(_questController.DebugState ?? "--");
+ ImGui.EndDisabled();
+ }
+
ImGui.TextUnformatted(_questController.Comment ?? "--");
//var nextStep = _questController.GetNextStep();
{
_movementController.Destination = null;
_chatFunctions.ExecuteCommand(
- $"/vnav {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
+ $"/vnav {(_condition[ConditionFlag.Mounted] && _gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
}
ImGui.EndDisabled();