Handle configured dialogue prompts; hide UI in blacklisted territories; path updates

Automatically handles:
- SelectString
- CutSceneSelectString
- SelectYesno
- Credit
- Closing Unending Codex during 'Newfound Adventure'
arr-p5 v0.3
Liza 2024-06-03 23:17:35 +02:00
parent 51e5faae69
commit a45cfda2e6
Signed by: liza
GPG Key ID: 7199F8D727D55F67
36 changed files with 652 additions and 108 deletions

View File

@ -44,6 +44,14 @@
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact", "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
],
"$": "QuestVariables after: 16 1 0 0 0 128" "$": "QuestVariables after: 16 1 0 0 0 128"
}, },
{ {
@ -53,12 +61,28 @@
"Z": -159.90234 "Z": -159.90234
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "WalkTo" "InteractionType": "WalkTo",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}, },
{ {
"DataId": 2011914, "DataId": 2011914,
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact", "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
],
"$": "QuestVariables after: 32 17 0 0 0 160" "$": "QuestVariables after: 32 17 0 0 0 160"
}, },
{ {
@ -69,7 +93,15 @@
"Z": -157.09167 "Z": -157.09167
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "Interact" "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
} }
] ]
}, },

View File

@ -43,15 +43,21 @@
"Z": 799.2217 "Z": 799.2217
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CutsceneSelectString", "InteractionType": "Interact",
"DialogueChoices": [ "DialogueChoices": [
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q2_000_086",
"Answer": "TEXT_AKTKMA114_04370_A2_000_088" "Answer": "TEXT_AKTKMA114_04370_A2_000_088"
}, },
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q3_000_096",
"Answer": "TEXT_AKTKMA114_04370_A3_000_098" "Answer": "TEXT_AKTKMA114_04370_A3_000_098"
}, },
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q5_000_106",
"Answer": "TEXT_AKTKMA114_04370_A5_000_107" "Answer": "TEXT_AKTKMA114_04370_A5_000_107"
} }
] ]
@ -69,15 +75,21 @@
"Z": 681.7273 "Z": 681.7273
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CutsceneSelectString", "InteractionType": "Interact",
"DialogueChoices": [ "DialogueChoices": [
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q6_000_147",
"Answer": "TEXT_AKTKMA114_04370_A6_000_149" "Answer": "TEXT_AKTKMA114_04370_A6_000_149"
}, },
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q7_000_157",
"Answer": "TEXT_AKTKMA114_04370_A7_000_158" "Answer": "TEXT_AKTKMA114_04370_A7_000_158"
}, },
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q8_000_162",
"Answer": "TEXT_AKTKMA114_04370_A8_000_164" "Answer": "TEXT_AKTKMA114_04370_A8_000_164"
} }
] ]
@ -95,15 +107,21 @@
"Z": 517.72327 "Z": 517.72327
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CutsceneSelectString", "InteractionType": "Interact",
"DialogueChoices": [ "DialogueChoices": [
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q9_000_198",
"Answer": "TEXT_AKTKMA114_04370_A9_000_200" "Answer": "TEXT_AKTKMA114_04370_A9_000_200"
}, },
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q10_000_207",
"Answer": "TEXT_AKTKMA114_04370_A10_000_209" "Answer": "TEXT_AKTKMA114_04370_A10_000_209"
}, },
{ {
"Type": "List",
"Prompt": "TEXT_AKTKMA114_04370_Q11_000_216",
"Answer": "TEXT_AKTKMA114_04370_A11_000_218" "Answer": "TEXT_AKTKMA114_04370_A11_000_218"
} }
] ]

View File

@ -1,6 +1,9 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"TerritoryBlacklist": [
1097
],
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,

View File

@ -1,6 +1,9 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"TerritoryBlacklist": [
1095
],
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,

View File

@ -82,7 +82,14 @@
}, },
"StopDistance": 5, "StopDistance": 5,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMK102_04736_Q2_000_094",
"Yes": true
}
]
} }
] ]
}, },

View File

@ -135,7 +135,6 @@
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact", "InteractionType": "Interact",
"Comment": "TODO Check flags",
"CompletionQuestVariablesFlags": [ "CompletionQuestVariablesFlags": [
null, null,
null, null,
@ -150,6 +149,18 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"Position": {
"X": 534.8861,
"Y": -36.65,
"Z": -245.12135
},
"TerritoryId": 958,
"InteractionType": "WalkTo",
"SkipIf": [
"FlyingLocked"
]
},
{ {
"DataId": 1045430, "DataId": 1045430,
"Position": { "Position": {

View File

@ -30,7 +30,8 @@
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact", "InteractionType": "Interact",
"Fly": true "Fly": true,
"TargetTerritoryId": 1160
} }
] ]
}, },
@ -61,7 +62,6 @@
}, },
"TerritoryId": 1160, "TerritoryId": 1160,
"InteractionType": "Interact", "InteractionType": "Interact",
"Comment": "TODO Check Flags",
"CompletionQuestVariablesFlags": [ "CompletionQuestVariablesFlags": [
null, null,
null, null,

View File

@ -64,6 +64,7 @@
"Y": 10.8, "Y": 10.8,
"Z": -231.61676 "Z": -231.61676
}, },
"StopDistance": 5,
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact" "InteractionType": "Interact"
} }

View File

@ -64,8 +64,23 @@
"TerritoryId": 959, "TerritoryId": 959,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Mare Lamentorum - Bestways Burrow", "AetheryteShortcut": "Mare Lamentorum - Bestways Burrow",
"SkipIf": ["FlyingUnlocked"], "SkipIf": [
"Comment": "Check if the flying unlocked check is good enough" "FlyingUnlocked"
]
},
{
"Position": {
"X": -19.779482,
"Y": -56.63768,
"Z": -464.9354
},
"StopDistance": 1,
"TerritoryId": 959,
"InteractionType": "WalkTo",
"SkipIf": [
"FlyingLocked"
],
"Fly": true
}, },
{ {
"DataId": 1039686, "DataId": 1039686,
@ -91,7 +106,8 @@
"Z": -620.3861 "Z": -620.3861
}, },
"TerritoryId": 959, "TerritoryId": 959,
"InteractionType": "Interact" "InteractionType": "Interact",
"Fly": true
} }
] ]
}, },
@ -156,8 +172,7 @@
null, null,
null, null,
32 32
], ]
"Comment": "TODO Check Flags"
}, },
{ {
"DataId": 1045473, "DataId": 1045473,
@ -179,7 +194,8 @@
16 16
] ]
} }
] ],
"Comment": "TODO Check Flags (32)"
}, },
{ {
"Sequence": 255, "Sequence": 255,

View File

@ -53,8 +53,7 @@
null, null,
null, null,
128 128
], ]
"Comment": "TODO Check Flags"
}, },
{ {
"DataId": 2013355, "DataId": 2013355,

View File

@ -1,6 +1,9 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"TerritoryBlacklist": [
1140
],
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,

View File

@ -31,7 +31,23 @@
"TerritoryId": 959, "TerritoryId": 959,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Mare Lamentorum - Bestways Burrow", "AetheryteShortcut": "Mare Lamentorum - Bestways Burrow",
"SkipIf": ["FlyingUnlocked"] "SkipIf": [
"FlyingUnlocked"
]
},
{
"Position": {
"X": -19.779482,
"Y": -56.63768,
"Z": -464.9354
},
"StopDistance": 1,
"TerritoryId": 959,
"InteractionType": "WalkTo",
"SkipIf": [
"FlyingLocked"
],
"Fly": true
}, },
{ {
"DataId": 1045466, "DataId": 1045466,
@ -62,6 +78,13 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Radz-at-Han] Aetheryte Plaza", "[Radz-at-Han] Aetheryte Plaza",
"[Radz-at-Han] Meghaduta" "[Radz-at-Han] Meghaduta"
],
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMK109_04743_Q1_000_000",
"Yes": true
}
] ]
} }
] ]

View File

@ -27,7 +27,7 @@
"Y": 55, "Y": 55,
"Z": -68.61987 "Z": -68.61987
}, },
"StopDistance": 5, "StopDistance": 7,
"TerritoryId": 963, "TerritoryId": 963,
"InteractionType": "Interact" "InteractionType": "Interact"
} }

View File

@ -1,6 +1,10 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"TerritoryBlacklist": [
1164,
1168
],
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,
@ -39,7 +43,14 @@
"Z": -440.63483 "Z": -440.63483
}, },
"TerritoryId": 1184, "TerritoryId": 1184,
"InteractionType": "Interact" "InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKML105_04748_SYSTEM_000_406",
"Yes": true
}
]
} }
] ]
}, },

View File

@ -12,6 +12,7 @@
"Y": 56.66061, "Y": 56.66061,
"Z": 467.39905 "Z": 467.39905
}, },
"StopDistance": 15,
"TerritoryId": 1162, "TerritoryId": 1162,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -43,7 +44,8 @@
"Z": -191.51605 "Z": -191.51605
}, },
"TerritoryId": 958, "TerritoryId": 958,
"InteractionType": "Interact" "InteractionType": "Interact",
"AetheryteShortcut": "Garlemald - Tertium"
} }
] ]
}, },
@ -59,6 +61,7 @@
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [ "AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza", "[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Rostra" "[Old Sharlayan] The Rostra"

View File

@ -12,6 +12,7 @@
"Y": 41.530136, "Y": 41.530136,
"Z": -165.27051 "Z": -165.27051
}, },
"StopDistance": 7,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -29,9 +30,17 @@
}, },
"TerritoryId": 819, "TerritoryId": 819,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Crystarium",
"AethernetShortcut": [ "AethernetShortcut": [
"[Crystarium] Aetheryte Plaza", "[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate" "[Crystarium] The Dossal Gate"
],
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKML107_04750_SYSTEM_000_101",
"Yes": true
}
] ]
} }
] ]
@ -51,6 +60,22 @@
} }
] ]
}, },
{
"Sequence": 3,
"Steps": [
{
"DataId": 1045684,
"Position": {
"X": -0.96136475,
"Y": 0,
"Z": -3.3417358
},
"StopDistance": 5,
"TerritoryId": 844,
"InteractionType": "Interact"
}
]
},
{ {
"Sequence": 4, "Sequence": 4,
"Steps": [ "Steps": [
@ -63,6 +88,7 @@
}, },
"TerritoryId": 963, "TerritoryId": 963,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Radz-at-Han",
"AethernetShortcut": [ "AethernetShortcut": [
"[Radz-at-Han] Aetheryte Plaza", "[Radz-at-Han] Aetheryte Plaza",
"[Radz-at-Han] Meghaduta" "[Radz-at-Han] Meghaduta"
@ -89,17 +115,17 @@
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{ {
"DataId": 196, "DataId": 1039645,
"Position": { "Position": {
"X": -42.61847, "X": -338.33832,
"Y": -0.015319824, "Y": 55,
"Z": -197.61963 "Z": -68.40625
}, },
"TerritoryId": 963, "TerritoryId": 963,
"InteractionType": "Interact", "InteractionType": "Interact",
"AethernetShortcut": [ "AethernetShortcut": [
"[Radz-at-Han] Mehryde's Meyhane", "[Radz-at-Han] Mehryde's Meyhane",
"[Radz-at-Han] Aetheryte Plaza" "[Radz-at-Han] Meghaduta"
] ]
} }
] ]

View File

@ -13,7 +13,14 @@
"Z": -68.40625 "Z": -68.40625
}, },
"TerritoryId": 963, "TerritoryId": 963,
"InteractionType": "Interact" "InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMM103_04753_Q1_000_000",
"Answer": "TEXT_AKTKMM103_04753_A1_000_001"
}
]
} }
] ]
}, },
@ -27,8 +34,10 @@
"Y": 4.357494, "Y": 4.357494,
"Z": 0.7476196 "Z": 0.7476196
}, },
"StopDistance": 7,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Old Sharlayan",
"AethernetShortcut": [ "AethernetShortcut": [
"[Old Sharlayan] Aetheryte Plaza", "[Old Sharlayan] Aetheryte Plaza",
"[Old Sharlayan] The Baldesion Annex" "[Old Sharlayan] The Baldesion Annex"

View File

@ -1,6 +1,9 @@
{ {
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"TerritoryBlacklist": [
1177
],
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 0, "Sequence": 0,
@ -62,6 +65,7 @@
"Y": -14.169313, "Y": -14.169313,
"Z": 105.30249 "Z": 105.30249
}, },
"StopDistance": 7,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -77,6 +81,7 @@
"Y": -15.127002, "Y": -15.127002,
"Z": 139.42163 "Z": 139.42163
}, },
"StopDistance": 5,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }

View File

@ -12,6 +12,7 @@
"Y": -15.127001, "Y": -15.127001,
"Z": 139.45215 "Z": 139.45215
}, },
"StopDistance": 5,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }
@ -32,6 +33,14 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Old Sharlayan] Scholar's Harbor", "[Old Sharlayan] Scholar's Harbor",
"[Old Sharlayan] The Studium" "[Old Sharlayan] The Studium"
],
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
16
] ]
}, },
{ {
@ -42,7 +51,15 @@
"Z": 103.28821 "Z": 103.28821
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}, },
{ {
"DataId": 2013417, "DataId": 2013417,
@ -52,7 +69,15 @@
"Z": 59.00659 "Z": 59.00659
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}, },
{ {
"DataId": 2013416, "DataId": 2013416,
@ -62,7 +87,15 @@
"Z": 20.523315 "Z": 20.523315
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
} }
] ]
}, },
@ -111,7 +144,14 @@
"Z": 0.7476196 "Z": 0.7476196
}, },
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMM103_04753_SYSTEM_000_302",
"Yes": true
}
]
} }
] ]
}, },
@ -151,6 +191,7 @@
"Y": 4.357494, "Y": 4.357494,
"Z": 0.7476196 "Z": 0.7476196
}, },
"StopDistance": 5,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "Interact" "InteractionType": "Interact"
} }

View File

@ -2,6 +2,10 @@
"$schema": "https://carvel.li/questionable/quest-1.0", "$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza", "Author": "liza",
"Comment": "TODO Missing Quest Start", "Comment": "TODO Missing Quest Start",
"TerritoryBlacklist": [
838,
847
],
"QuestSequence": [ "QuestSequence": [
{ {
"Sequence": 7, "Sequence": 7,

View File

@ -13,7 +13,14 @@
"Z": -9.10968 "Z": -9.10968
}, },
"TerritoryId": 351, "TerritoryId": 351,
"InteractionType": "Interact" "InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_LUCKMG101_03673_Q1_000_500",
"Answer": "TEXT_LUCKMG101_03673_A1_000_500"
}
]
} }
] ]
}, },
@ -38,7 +45,8 @@
"Z": -656.1909 "Z": -656.1909
}, },
"TerritoryId": 156, "TerritoryId": 156,
"InteractionType": "WalkTo" "InteractionType": "WalkTo",
"Mount": true
}, },
{ {
"DataId": 1018433, "DataId": 1018433,

View File

@ -26,6 +26,7 @@
"Y": -0.67464465, "Y": -0.67464465,
"Z": 653.1527 "Z": 653.1527
}, },
"StopDistance": 0.5,
"TerritoryId": 813, "TerritoryId": 813,
"InteractionType": "WalkTo", "InteractionType": "WalkTo",
"AetheryteShortcut": "Lakeland - Fort Jobb", "AetheryteShortcut": "Lakeland - Fort Jobb",

View File

@ -0,0 +1,13 @@
Currying Flavor:
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_AKTKMK101_04735_Q1_000_000",
"Answer": "TEXT_AKTKMK101_04735_A1_000_003"
},
{
"Type": "YesNo",
"Prompt": "TEXT_AKTKMK101_04735_Q2_000_182",
"Yes": true
}
]

View File

@ -100,7 +100,6 @@
"Duty", "Duty",
"SinglePlayerDuty", "SinglePlayerDuty",
"Jump", "Jump",
"CutsceneSelectString",
"ShouldBeAJump", "ShouldBeAJump",
"Instruction" "Instruction"
] ]
@ -648,7 +647,7 @@
"if": { "if": {
"properties": { "properties": {
"InteractionType": { "InteractionType": {
"const": "CutsceneSelectString" "const": "Interact"
} }
} }
}, },
@ -659,22 +658,68 @@
"items": { "items": {
"type": "object", "type": "object",
"properties": { "properties": {
"Type": {
"type": "string",
"enum": [
"YesNo",
"List"
]
},
"ExcelSheet": { "ExcelSheet": {
"type": "string" "type": "string"
}, },
"Answer": { "Prompt": {
"type": "string" "type": "string"
} }
}, },
"required": [ "required": [
"Answer" "Type",
"Prompt"
],
"allOf": [
{
"if": {
"properties": {
"Type": {
"const": "YesNo"
}
}
},
"then": {
"properties": {
"Yes": {
"type": "boolean",
"default": true
}
},
"required": [
"Yes"
]
}
},
{
"if": {
"properties": {
"Type": {
"const": "List"
}
}
},
"then": {
"properties": {
"Answer": {
"type": "string"
}
},
"required": [
"Answer"
]
}
}
] ]
} }
} }
}, }
"required": [
"DialogueChoices"
]
} }
}, },
{ {

View File

@ -0,0 +1,240 @@
using System;
using System.Linq;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Lumina.Excel.GeneratedSheets;
using Questionable.Model.V1;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller;
internal sealed class GameUiController : IDisposable
{
private readonly IClientState _clientState;
private readonly IAddonLifecycle _addonLifecycle;
private readonly IDataManager _dataManager;
private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController;
private readonly IPluginLog _pluginLog;
public GameUiController(IClientState clientState, IAddonLifecycle addonLifecycle, IDataManager dataManager,
GameFunctions gameFunctions, QuestController questController, IPluginLog pluginLog)
{
_clientState = clientState;
_addonLifecycle = addonLifecycle;
_dataManager = dataManager;
_gameFunctions = gameFunctions;
_questController = questController;
_pluginLog = pluginLog;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup);
}
private unsafe void SelectStringPostSetup(AddonEvent type, AddonArgs args)
{
AddonSelectString* addonSelectString = (AddonSelectString*)args.Addon;
string? actualPrompt = addonSelectString->AtkUnitBase.AtkValues[2].ReadAtkString();
if (actualPrompt == null)
return;
var currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null)
return;
foreach (var dialogueChoice in step.DialogueChoices)
{
if (dialogueChoice.Answer == null)
continue;
string? excelPrompt =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
string? excelAnswer =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
if (excelPrompt == null || actualPrompt != excelPrompt)
continue;
for (ushort i = 7; i <= addonSelectString->AtkUnitBase.AtkValuesCount; ++i)
{
string? actualAnswer = addonSelectString->AtkUnitBase.AtkValues[i].ReadAtkString();
if (actualAnswer == null || actualAnswer != excelAnswer)
continue;
_questController.IncreaseDialogueChoicesSelected();
addonSelectString->AtkUnitBase.FireCallbackInt(i - 7);
return;
}
}
}
private unsafe void CutsceneSelectStringPostSetup(AddonEvent type, AddonArgs args)
{
AddonCutSceneSelectString* addonCutSceneSelectString = (AddonCutSceneSelectString*)args.Addon;
string? actualPrompt = addonCutSceneSelectString->AtkUnitBase.AtkValues[2].ReadAtkString();
if (actualPrompt == null)
return;
var currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null)
return;
foreach (DialogueChoice dialogueChoice in step.DialogueChoices)
{
if (dialogueChoice.Answer == null)
continue;
string? excelPrompt =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
string? excelAnswer =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
if (excelPrompt == null || actualPrompt != excelPrompt)
continue;
for (int i = 5; i < addonCutSceneSelectString->AtkUnitBase.AtkValuesCount; ++i)
{
string? actualAnswer = addonCutSceneSelectString->AtkUnitBase.AtkValues[i].ReadAtkString();
if (actualAnswer == null || actualAnswer != excelAnswer)
continue;
_questController.IncreaseDialogueChoicesSelected();
addonCutSceneSelectString->AtkUnitBase.FireCallbackInt(i - 5);
return;
}
}
}
private unsafe void SelectYesnoPostSetup(AddonEvent type, AddonArgs args)
{
AddonSelectYesno* addonSelectYesno = (AddonSelectYesno*)args.Addon;
string? actualPrompt = addonSelectYesno->AtkUnitBase.AtkValues[0].ReadAtkString();
if (actualPrompt == null)
return;
_pluginLog.Verbose($"Prompt: '{actualPrompt}'");
var currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step, actualPrompt))
return;
HandleTravelYesNo(addonSelectYesno, currentQuest, actualPrompt);
}
private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest, QuestStep step,
string actualPrompt)
{
_pluginLog.Verbose($"DefaultYesNo: Choice count: {step.DialogueChoices.Count}");
foreach (var dialogueChoice in step.DialogueChoices)
{
string? excelPrompt =
_gameFunctions.GetExcelString(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
if (excelPrompt == null || actualPrompt != excelPrompt)
continue;
addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1);
_questController.IncreaseDialogueChoicesSelected();
return true;
}
return false;
}
private unsafe bool HandleTravelYesNo(AddonSelectYesno* addonSelectYesno,
QuestController.QuestProgress currentQuest, string actualPrompt)
{
// this can be triggered either manually (in which case we should increase the step counter), or automatically
// (in which case it is ~1 frame later, and the step counter has already been increased)
var sequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
if (sequence == null)
return false;
bool increaseStepCount = true;
QuestStep? step = sequence.FindStep(currentQuest.Step);
if (step != null)
_pluginLog.Verbose($"Current step: {step.TerritoryId}, {step.TargetTerritoryId}");
if (step == null || step.TargetTerritoryId == null || step.TerritoryId != _clientState.TerritoryType)
{
_pluginLog.Verbose("TravelYesNo: Checking previous step...");
step = sequence.FindStep(currentQuest.Step == 255 ? (sequence.Steps.Count - 1) : (currentQuest.Step - 1));
increaseStepCount = false;
if (step != null)
_pluginLog.Verbose($"Previous step: {step.TerritoryId}, {step.TargetTerritoryId}");
}
if (step == null || step.TargetTerritoryId == null || step.TerritoryId != _clientState.TerritoryType)
{
_pluginLog.Verbose("TravelYesNo: Not found");
return false;
}
var warps = _dataManager.GetExcelSheet<Warp>()!
.Where(x => x.RowId > 0 && x.TerritoryType.Row == step.TargetTerritoryId)
.Where(x => x.ConfirmEvent.Row == 0); // unsure if this is needed
foreach (var entry in warps)
{
string? excelPrompt = entry.Question?.ToString();
if (excelPrompt == null || excelPrompt != actualPrompt)
{
_pluginLog.Information($"Ignoring prompt '{excelPrompt}'");
continue;
}
_pluginLog.Information($"Using warp {entry.RowId}, {excelPrompt}");
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
if (increaseStepCount)
_questController.IncreaseStepCount();
return true;
}
return false;
}
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
{
_pluginLog.Information("Closing Credits sequence");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
addon->FireCallbackInt(-2);
}
private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args)
{
if (_questController.CurrentQuest?.Quest.QuestId == 4526)
{
_pluginLog.Information("Closing Unending Codex");
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
addon->FireCallbackInt(-2);
}
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
}
}

View File

@ -10,14 +10,11 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using LLib.GameUI;
using Questionable.Data; using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
using Questionable.Model.V1.Converter; using Questionable.Model.V1.Converter;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
namespace Questionable.Controller; namespace Questionable.Controller;
@ -272,6 +269,27 @@ internal sealed class QuestController
} }
} }
public void IncreaseDialogueChoicesSelected()
{
(QuestSequence? seq, QuestStep? step) = GetNextStep();
if (CurrentQuest == null || seq == null || step == null)
{
_pluginLog.Warning("Unable to retrieve next quest step, not increasing dialogue choice count");
return;
}
CurrentQuest = CurrentQuest with
{
StepProgress = CurrentQuest.StepProgress with
{
DialogueChoicesSelected = CurrentQuest.StepProgress.DialogueChoicesSelected + 1
}
};
if (CurrentQuest.StepProgress.DialogueChoicesSelected >= step.DialogueChoices.Count)
IncreaseStepCount();
}
public unsafe void ExecuteNextStep() public unsafe void ExecuteNextStep()
{ {
(QuestSequence? seq, QuestStep? step) = GetNextStep(); (QuestSequence? seq, QuestStep? step) = GetNextStep();
@ -422,7 +440,8 @@ internal sealed class QuestController
} }
} }
else else
_pluginLog.Warning($"Aethernet shortcut not unlocked (from: {step.AethernetShortcut.From}, to: {step.AethernetShortcut.To}), walking manually"); _pluginLog.Warning(
$"Aethernet shortcut not unlocked (from: {step.AethernetShortcut.From}, to: {step.AethernetShortcut.To}), walking manually");
} }
if (step.TargetTerritoryId == _clientState.TerritoryType && !step.SkipIf.Contains(ESkipCondition.Never)) if (step.TargetTerritoryId == _clientState.TerritoryType && !step.SkipIf.Contains(ESkipCondition.Never))
@ -439,11 +458,6 @@ internal sealed class QuestController
{ {
_pluginLog.Information("We're at the jump destination, skipping movement"); _pluginLog.Information("We're at the jump destination, skipping movement");
} }
else if (step.InteractionType == EInteractionType.CutsceneSelectString &&
_condition[ConditionFlag.OccupiedInCutSceneEvent])
{
_pluginLog.Information("In cutscene selection, skipping movement");
}
else if (step.Position != null) else if (step.Position != null)
{ {
float distance; float distance;
@ -543,7 +557,10 @@ internal sealed class QuestController
} }
_gameFunctions.InteractWith(step.DataId.Value); _gameFunctions.InteractWith(step.DataId.Value);
IncreaseStepCount();
// if we have any dialogue, that is handled in GameUiController
if (step.DialogueChoices.Count == 0)
IncreaseStepCount();
} }
else else
_pluginLog.Warning("Not interacting on current step, DataId is null"); _pluginLog.Warning("Not interacting on current step, DataId is null");
@ -712,41 +729,6 @@ internal sealed class QuestController
// Need to manually forward // Need to manually forward
break; break;
case EInteractionType.CutsceneSelectString:
// to do this automatically, should likely be in Addon's post setup
if (_gameGui.TryGetAddonByName<AddonCutSceneSelectString>("CutSceneSelectString", out var addon) &&
LAddon.IsAddonReady(&addon->AtkUnitBase))
{
foreach (DialogueChoice dialogueChoice in step.DialogueChoices)
{
string? excelString = _gameFunctions.GetExcelString(CurrentQuest.Quest,
dialogueChoice.ExcelSheet, dialogueChoice.Answer);
if (excelString == null)
return;
_pluginLog.Verbose($"Looking for option '{excelString}'");
for (int i = 5; i < addon->AtkUnitBase.AtkValuesCount; ++i)
{
var atkValue = addon->AtkUnitBase.AtkValues[i];
if (atkValue.Type != ValueType.String)
continue;
string? atkString = atkValue.ReadAtkString();
_pluginLog.Verbose($"Option {i}: {atkString}");
if (excelString == atkString)
{
_pluginLog.Information($"Selecting option {i - 5}: {atkString}");
addon->AtkUnitBase.FireCallbackInt(i - 5);
return;
}
}
}
}
else if (step.DataId != null && !_condition[ConditionFlag.OccupiedInCutSceneEvent])
_gameFunctions.InteractWith(step.DataId.Value);
break;
default: default:
_pluginLog.Warning($"Action '{step.InteractionType}' is not implemented"); _pluginLog.Warning($"Action '{step.InteractionType}' is not implemented");
break; break;
@ -767,5 +749,6 @@ internal sealed class QuestController
public sealed record StepProgress( public sealed record StepProgress(
bool AetheryteShortcutUsed = false, bool AetheryteShortcutUsed = false,
bool AethernetShortcutUsed = false); bool AethernetShortcutUsed = false,
int DialogueChoicesSelected = 0);
} }

View File

@ -474,22 +474,18 @@ internal sealed unsafe class GameFunctions
_pluginLog.Error($"Could not find content for content finder condition (cf: {contentFinderConditionId})"); _pluginLog.Error($"Could not find content for content finder condition (cf: {contentFinderConditionId})");
} }
public string? GetExcelString(Quest currentQuestQuest, string? excelSheetName, string key) public string? GetExcelString(Quest currentQuest, string? excelSheetName, string key)
{ {
if (excelSheetName == null) if (excelSheetName == null)
{ {
string questPrefix = $"quest/{(currentQuestQuest.QuestId / 100):000}/"; var questRow = _dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestId + 0x10000);
string questSuffix = $"_{currentQuestQuest.QuestId:00000}"; if (questRow == null)
excelSheetName = _dataManager.Excel
.GetSheetNames()
.SingleOrDefault(x =>
x.StartsWith(questPrefix, StringComparison.Ordinal) &&
x.EndsWith(questSuffix, StringComparison.Ordinal));
if (excelSheetName == null)
{ {
_pluginLog.Error($"Could not find sheet matching '{questPrefix}*{questSuffix}"); _pluginLog.Error($"Could not find quest row for {currentQuest.QuestId}");
return null; return null;
} }
excelSheetName = $"quest/{(currentQuest.QuestId / 100):000}/{questRow.Id}";
} }
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName); var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Questionable.Model.V1.Converter;
public sealed class DialogueChoiceTypeConverter() : EnumConverter<EDialogChoiceType>(Values)
{
private static readonly Dictionary<EDialogChoiceType, string> Values = new()
{
{ EDialogChoiceType.YesNo, "YesNo" },
{ EDialogChoiceType.List, "List" },
};
}

View File

@ -20,7 +20,6 @@ public sealed class InteractionTypeConverter() : EnumConverter<EInteractionType>
{ EInteractionType.Duty, "Duty" }, { EInteractionType.Duty, "Duty" },
{ EInteractionType.SinglePlayerDuty, "SinglePlayerDuty" }, { EInteractionType.SinglePlayerDuty, "SinglePlayerDuty" },
{ EInteractionType.Jump, "Jump" }, { EInteractionType.Jump, "Jump" },
{ EInteractionType.CutsceneSelectString, "CutsceneSelectString" },
{ EInteractionType.ShouldBeAJump, "ShouldBeAJump" }, { EInteractionType.ShouldBeAJump, "ShouldBeAJump" },
{ EInteractionType.Instruction, "Instruction" }, { EInteractionType.Instruction, "Instruction" },
}; };

View File

@ -1,7 +1,14 @@
namespace Questionable.Model.V1; using System.Text.Json.Serialization;
using Questionable.Model.V1.Converter;
public sealed class DialogueChoice namespace Questionable.Model.V1;
public class DialogueChoice
{ {
[JsonConverter(typeof(DialogueChoiceTypeConverter))]
public EDialogChoiceType Type { get; set; }
public string? ExcelSheet { get; set; } public string? ExcelSheet { get; set; }
public string Answer { get; set; } = null!; public string Prompt { get; set; } = null!;
public bool Yes { get; set; } = true;
public string? Answer { get; set; }
} }

View File

@ -0,0 +1,8 @@
namespace Questionable.Model.V1;
public enum EDialogChoiceType
{
None,
YesNo,
List
}

View File

@ -20,7 +20,6 @@ public enum EInteractionType
Duty, Duty,
SinglePlayerDuty, SinglePlayerDuty,
Jump, Jump,
CutsceneSelectString,
/// <summary> /// <summary>
/// Needs to be adjusted for coords etc. in the quest data. /// Needs to be adjusted for coords etc. in the quest data.

View File

@ -7,4 +7,12 @@ public class QuestSequence
public required int Sequence { get; set; } public required int Sequence { get; set; }
public string? Comment { get; set; } public string? Comment { get; set; }
public List<QuestStep> Steps { get; set; } = new(); public List<QuestStep> Steps { get; set; } = new();
public QuestStep? FindStep(int step)
{
if (step < 0 || step >= Steps.Count)
return null;
return Steps[step];
}
} }

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<Version>0.2</Version> <Version>0.3</Version>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -27,13 +27,13 @@ public sealed class QuestionablePlugin : IDalamudPlugin
private readonly ICommandManager _commandManager; private readonly ICommandManager _commandManager;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly GameUiController _gameUiController;
public QuestionablePlugin(DalamudPluginInterface pluginInterface, IClientState clientState, public QuestionablePlugin(DalamudPluginInterface pluginInterface, IClientState clientState,
ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager,
ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui,
ICommandManager commandManager) ICommandManager commandManager, IAddonLifecycle addonLifecycle)
{ {
ArgumentNullException.ThrowIfNull(pluginInterface); ArgumentNullException.ThrowIfNull(pluginInterface);
ArgumentNullException.ThrowIfNull(sigScanner); ArgumentNullException.ThrowIfNull(sigScanner);
@ -55,6 +55,9 @@ public sealed class QuestionablePlugin : IDalamudPlugin
new MovementController(navmeshIpc, clientState, _gameFunctions, condition, pluginLog); new MovementController(navmeshIpc, clientState, _gameFunctions, condition, pluginLog);
_questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions, _questController = new QuestController(pluginInterface, dataManager, _clientState, _gameFunctions,
_movementController, pluginLog, condition, chatGui, framework, gameGui, aetheryteData, lifestreamIpc); _movementController, pluginLog, condition, chatGui, framework, gameGui, aetheryteData, lifestreamIpc);
_gameUiController =
new GameUiController(clientState, addonLifecycle, dataManager, _gameFunctions, _questController, pluginLog);
_windowSystem.AddWindow(new DebugWindow(_movementController, _questController, _gameFunctions, clientState, _windowSystem.AddWindow(new DebugWindow(_movementController, _questController, _gameFunctions, clientState,
targetManager)); targetManager));
@ -100,6 +103,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
_framework.Update -= FrameworkUpdate; _framework.Update -= FrameworkUpdate;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_gameUiController.Dispose();
_movementController.Dispose(); _movementController.Dispose();
} }
} }

View File

@ -1,12 +1,9 @@
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Memory;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.Game.Control;
@ -45,6 +42,15 @@ internal sealed class DebugWindow : Window
}; };
} }
public override bool DrawConditions()
{
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
return false;
var currentQuest = _questController.CurrentQuest;
return currentQuest == null || !currentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType);
}
public override unsafe void Draw() public override unsafe void Draw()
{ {
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null) if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)