"Z": -464.7746
},
"TerritoryId": 956,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
},
{
"DataId": 1038716,
"Z": -458.2132
},
"TerritoryId": 956,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
}
]
},
"Z": -519.18823
},
"TerritoryId": 956,
- "InteractionType": "SinglePlayerDuty",
- "Comment": "Duty - Shoot Large Green Bird"
+ "InteractionType": "WaitForManualProgress",
+ "Comment": "Shoot Large Green Bird"
}
]
},
"Z": -108.537415
},
"TerritoryId": 956,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
},
{
"DataId": 1038707,
"Z": -150.04199
},
"TerritoryId": 956,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
},
{
"DataId": 1038708,
"Z": -130.11371
},
"TerritoryId": 956,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
}
]
},
"Comment": "A Frosty Reception",
"DialogueChoices": [
{
- "Type": "ContentTalkList",
- "Prompt": "264",
- "Answer": "267"
+ "Type": "List",
+ "Prompt": 264,
+ "Answer": 267
},
{
- "Type": "ContentTalkYesNo",
- "Prompt": "268",
+ "Type": "YesNo",
+ "Prompt": 268,
"Yes": true
}
]
},
"TerritoryId": 960,
"InteractionType": "WaitForManualProgress",
- "Comment": "Duty - Find Errant Omicron"
+ "Comment": "Find Errant Omicron"
}
]
},
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
},
{
"DataId": 1032121,
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
}
]
},
"TerritoryId": 815,
"InteractionType": "UseItem",
"ItemId": 2002904,
- "$.1": "QuestVariables if done first: 1 32 0 0 0 64"
+ "$.1": "QuestVariables if done first: 1 32 0 0 0 64",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
},
{
"DataId": 1027909,
"TerritoryId": 815,
"InteractionType": "UseItem",
"ItemId": 2002904,
- "$.1": "QuestVariables if done after [1]: 2 16 0 0 0 96"
+ "$.1": "QuestVariables if done after [1]: 2 16 0 0 0 96",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
},
{
"DataId": 1027939,
},
"TerritoryId": 815,
"InteractionType": "UseItem",
- "ItemId": 2002904
+ "ItemId": 2002904,
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
}
]
},
"Z": 305.19568
},
"TerritoryId": 815,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMG104_03676_Q2_000_100",
+ "Answer": "TEXT_LUCKMG104_03676_A1_000_100"
+ }
+ ]
}
]
},
"StopDistance": 1,
"TerritoryId": 815,
"InteractionType": "Interact",
- "$.1": "QuestVariables if done first: 16 16 16 0 0 32"
+ "$.1": "QuestVariables if done first: 16 16 16 0 0 32",
+ "Fly": true,
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
},
{
"DataId": 2010810,
},
"TerritoryId": 815,
"InteractionType": "Interact",
- "$.1": "QuestVariables if done after [1]: 33 16 32 0 0 96"
+ "$.1": "QuestVariables if done after [1]: 33 16 32 0 0 96",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
},
{
"DataId": 2010809,
},
"TerritoryId": 815,
"InteractionType": "Interact",
- "Comment": "Combat not necessary to progress quest"
+ "Comment": "Combat not necessary to progress quest",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
}
]
},
{
"Sequence": 4,
"Steps": [
+ {
+ "Position": {
+ "X": 47.593674,
+ "Y": 42.681213,
+ "Z": -511.2799
+ },
+ "TerritoryId": 815,
+ "InteractionType": "WalkTo",
+ "Comment": "Should be far enough to reset combat"
+ },
{
"DataId": 1031732,
"Position": {
},
"TerritoryId": 815,
"InteractionType": "Interact",
- "AetheryteShortcut": "Amh Araeng - Inn at Journey's Head"
+ "AetheryteShortcut": "Amh Araeng - Inn at Journey's Head",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMG104_03676_Q3_000_200",
+ "Answer": "TEXT_LUCKMG104_03676_A1_000_200"
+ }
+ ]
}
]
},
},
"TerritoryId": 820,
"InteractionType": "Interact",
- "$.1": "QuestValues if done first: 16 1 16 0 0 128"
+ "$.1": "QuestValues if done first: 16 1 16 0 0 128",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
},
{
"DataId": 1027602,
"[Eulmore] Aetheryte Plaza",
"[Eulmore] Southeast Derelicts"
],
- "$.1": "QuestValues if done after [1]: 32 17 16 16 0 160"
+ "$.1": "QuestValues if done after [1]: 32 17 16 16 0 160",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
},
{
"DataId": 1029990,
"AethernetShortcut": [
"[Eulmore] Southeast Derelicts",
"[Eulmore] The Glory Gate"
+ ],
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
]
}
]
"Z": -161.45575
},
"TerritoryId": 814,
- "InteractionType": "SinglePlayerDuty",
+ "InteractionType": "WaitForManualProgress",
"Comment": "Help Master Chai dodge enemies"
}
]
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
},
{
"DataId": 1032121,
},
"TerritoryId": 351,
"InteractionType": "SinglePlayerDuty",
- "Comment": "Estinien vs. Arch Ultima"
+ "Comment": "Estinien vs. Arch Ultima",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_LUCKMG110_03682_Q1_100_125",
+ "Yes": true
+ }
+ ]
}
]
},
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
},
{
"DataId": 1032121,
"Z": 14.22064
},
"TerritoryId": 844,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "TargetTerritoryId": 819
},
{
"DataId": 1030370,
"Z": 13.321045
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMH103_03763_Q1_000_500",
+ "Answer": "TEXT_LUCKMH103_03763_A1_000_500"
+ }
+ ]
}
]
},
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
},
{
"DataId": 1031722,
"Z": 14.206055
},
"TerritoryId": 844,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "TargetTerritoryId": 819
},
{
"DataId": 1027248,
"InteractionType": "Interact",
"Comment": "Chessamile",
"$.0": "[1]",
- "$.1": "QuestVariables if done first: 1 0 0 0 0 64"
+ "$.1": "QuestVariables if done first: 1 0 0 0 0 64",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
},
{
"DataId": 1027224,
"InteractionType": "Interact",
"Comment": "Bragi",
"$.0": "[2]",
- "$.1": "QuestVariables if done after [1]: 2 0 0 0 0 192"
+ "$.1": "QuestVariables if done after [1]: 2 0 0 0 0 192",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
},
{
"DataId": 1027322,
"InteractionType": "Interact",
"Comment": "Glynard",
"$.0": "[3]",
- "$.1": "QuestVariables if done after [1, 2]: 3 0 0 0 0 200"
+ "$.1": "QuestVariables if done after [1, 2]: 3 0 0 0 0 200",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 8
+ ]
},
{
"DataId": 1027232,
"[Crystarium] The Crystalline Mean"
],
"$.0": "[4]",
- "$.1": "QuestVariables if done after [1, 2, 3]: 4 0 0 0 0 216"
+ "$.1": "QuestVariables if done after [1, 2, 3]: 4 0 0 0 0 216",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 16
+ ]
},
{
"DataId": 1027226,
"AethernetShortcut": [
"[Crystarium] The Crystalline Mean",
"[Crystarium] The Cabinet of Curiosity"
+ ],
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
]
}
]
"TerritoryId": 817,
"InteractionType": "SinglePlayerDuty",
"Fly": true,
- "Comment": "Duty - A Sleep Disturbed (Opo-Opo, Wolf, Serpent)"
+ "Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)",
+ "$": "The dialogue choices and data ids here are recycled",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "DataId": 2011009,
+ "ExcelSheet": "GimmickYesNo",
+ "Prompt": 138,
+ "Yes": true
+ },
+ {
+ "Type": "YesNo",
+ "DataId": 2011006,
+ "ExcelSheet": "GimmickYesNo",
+ "Prompt": 139,
+ "Yes": true
+ },
+ {
+ "Type": "YesNo",
+ "DataId": 2011007,
+ "ExcelSheet": "GimmickYesNo",
+ "Prompt": 142,
+ "Yes": true
+ }
+ ]
}
]
},
{
"Sequence": 2,
"Steps": [
+ {
+ "Position": {
+ "X": -475.38354,
+ "Y": 400.55338,
+ "Z": -779.4299
+ },
+ "TerritoryId": 818,
+ "InteractionType": "WalkTo",
+ "Fly": true,
+ "SkipIf": [
+ "FlyingLocked"
+ ]
+ },
{
"Position": {
"X": -423.6145,
},
"TerritoryId": 813,
"InteractionType": "WalkTo",
- "AetheryteShortcut": "Lakeland - Ostall Imperative",
+ "AetheryteShortcut": "Lakeland - Fort Jobb",
"Fly": true
},
{
"TerritoryId": 813,
"InteractionType": "Interact",
"DisableNavmesh": true,
- "$.1": "QuestVariables if done first: 1 0 0 0 0 64"
+ "$.1": "QuestVariables if done first: 1 0 0 0 0 64",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
},
{
"DataId": 2010278,
},
"TerritoryId": 813,
"InteractionType": "Interact",
- "DisableNavmesh": true
+ "DisableNavmesh": true,
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
},
{
"DataId": 2010282,
},
"TerritoryId": 813,
"InteractionType": "Interact",
- "DisableNavmesh": true
+ "DisableNavmesh": true,
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ],
+ "Comment": "TODO Check if pathfinding works automatically now"
}
]
},
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
- "Comment": "TODO Missing quest end",
"TerritoryBlacklist": [
898
],
},
"TerritoryId": 814,
"InteractionType": "Interact",
+ "AetheryteShortcut": "Kholusia - Wright",
"Fly": true
}
]
"ContentFinderConditionId": 714
}
]
+ },
+ {
+ "Sequence": 255,
+ "Steps": [
+ {
+ "DataId": 1032549,
+ "Position": {
+ "X": -1.9074707,
+ "Y": -200.00002,
+ "Z": -425.10114
+ },
+ "TerritoryId": 918,
+ "InteractionType": "Interact"
+ }
+ ]
}
]
}
]
},
{
- "Sequence": 1,
- "Comment": "TODO verify this is the correct sequence and/or where sequence 2 went",
+ "Sequence": 2,
"Steps": [
{
"DataId": 1032529,
"AethernetShortcut": [
"[Crystarium] The Amaro Launch",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
},
{
"DataId": 1032121,
"Z": 14.206055
},
"TerritoryId": 844,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "TargetTerritoryId": 819
},
{
"DataId": 1030610,
"AethernetShortcut": [
"[Crystarium] The Dossal Gate",
"[Crystarium] The Pendants"
+ ],
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_LUCKMH110_03770_Q1_000_600",
+ "Yes": true
+ }
]
}
]
"Z": -277.7906
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMI101_03771_Q3_000_148",
+ "Answer": "TEXT_LUCKMI101_03771_A3_000_149"
+ }
+ ]
}
]
},
},
"TerritoryId": 819,
"InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ],
"$.1": "QuestVariables if done first: 1 16 0 0 0 64"
},
{
"StopDistance": 5,
"TerritoryId": 819,
"InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ],
"$.1": "QuestVariables if done after [1]: 2 32 0 0 0 192"
},
{
"Z": 173.38818
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
}
]
},
{
"Sequence": 255,
"Steps": [
- {
- "Position": {
- "X": -140.22343,
- "Y": 0.05337119,
- "Z": 34.20123
- },
- "TerritoryId": 819,
- "InteractionType": "WalkTo"
- },
{
"DataId": 1027248,
"Position": {
"Z": -51.438232
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Crystarium"
}
]
}
},
{
"Sequence": 2,
+ "Comment": "This isn't solving for the 'best' results, but for the closest waypoints",
"Steps": [
{
"DataId": 2011078,
],
"Fly": true,
"$.0": "[1]",
- "$.1": "QuestVariables if done first: 0 0 0 3 0 0 during fight; 16 1 0 2 16 8 after"
+ "$.1": "QuestVariables if done first: 0 0 0 3 0 0 during fight; 16 1 0 2 16 8 after",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 8
+ ]
},
{
"DataId": 2011076,
"InteractionType": "Combat",
"EnemySpawnType": "AfterItemUse",
"ItemId": 2003001,
- "KillEnemyDataIds": [],
- "Comment": "TODO Missing enemy ids",
+ "KillEnemyDataIds": [
+ 12166
+ ],
"Fly": true,
- "$.1": "QuestVariables if done after [1]: 34 1 0 1 48 40"
+ "$.1": "QuestVariables if done after [1]: 34 1 0 1 48 40",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
},
{
"DataId": 2011079,
"EnemySpawnType": "AfterItemUse",
"ItemId": 2003001,
"KillEnemyDataIds": [
+ 12168
],
"Comment": "TODO Missing enemy ids",
"Fly": true,
- "$.2": "QuestVariables if done after [1, 2]: 0 64 0 0 0 0"
+ "$.2": "QuestVariables if done after [1, 2]: irrelevant because it automatically progresses to the next step"
}
]
},
"AethernetShortcut": [
"[Crystarium] Aetheryte Plaza",
"[Crystarium] The Dossal Gate"
- ]
+ ],
+ "TargetTerritoryId": 844
},
{
"DataId": 1033819,
"Z": 604.27246
},
"TerritoryId": 814,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_LUCKMI105_03775_Q2_000_052",
+ "Yes": true
+ }
+ ]
}
]
},
"Z": 1.6021729
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMI108_03778_Q1_000_001",
+ "Answer": "TEXT_LUCKMI108_03778_A1_000_002"
+ }
+ ]
}
]
},
{
- "Sequence": 1,
+ "Sequence": 2,
"Steps": [
{
"TerritoryId": 931,
"Sequence": 5,
"Steps": [
{
+ "Position": {
+ "X": 0,
+ "Y": 0,
+ "Z": 0
+ },
"TerritoryId": 820,
- "InteractionType": "Interact",
+ "InteractionType": "WalkTo",
"AetheryteShortcut": "Eulmore"
}
]
"Z": 3.982544
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "AetheryteShortcut": "Crystarium",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMI111_03781_Q1_000_153",
+ "Answer": "TEXT_LUCKMI111_03781_A1_000_154"
+ }
+ ]
}
]
},
"Z": 7.156433
},
"TerritoryId": 819,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_LUCKMI112_03782_Q1_000_007",
+ "Yes": true
+ }
+ ]
},
{
"DataId": 1033888,
"Z": -5.081299
},
"TerritoryId": 844,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_LUCKMI112_03782_Q2_000_044",
+ "Yes": true
+ }
+ ]
}
]
},
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
12661
- ]
+ ],
+ "Fly": true
}
]
},
"Z": -6.9733887
},
"TerritoryId": 351,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "TargetTerritoryId": 351
},
{
"DataId": 2011332,
},
"StopDistance": 5,
"TerritoryId": 351,
- "InteractionType": "Interact"
+ "InteractionType": "Interact",
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMJ104_04010_Q1_000_000",
+ "Answer": "TEXT_LUCKMJ104_04010_A1_000_002"
+ }
+ ]
}
]
},
},
"TerritoryId": 129,
"InteractionType": "Interact",
- "AetheryteShortcut": "Limsa Lominsa"
+ "AetheryteShortcut": "Limsa Lominsa",
+ "DialogueChoices": [
+ {
+ "Type": "YesNo",
+ "Prompt": "TEXT_LUCKMJ108_04014_SYSTEM_100_010",
+ "Yes": true
+ }
+ ]
},
{
"DataId": 1002694,
{
"Sequence": 2,
"Steps": [
+ {
+ "Position": {
+ "X": 46.600548,
+ "Y": 77.45801,
+ "Z": -366.82053
+ },
+ "TerritoryId": 180,
+ "InteractionType": "WalkTo",
+ "Fly": true
+ },
+ {
+ "Position": {
+ "X": 111.927666,
+ "Y": 26.050894,
+ "Z": -612.8873
+ },
+ "TerritoryId": 180,
+ "InteractionType": "WalkTo",
+ "Fly": true
+ },
{
"Position": {
"X": 82.19566,
"TerritoryId": 402,
"InteractionType": "Interact",
"Fly": true,
- "$.1": "QuestVariables if done first: 1 16 0 0 0 64"
+ "$.1": "QuestVariables if done first: 1 16 0 0 0 64",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 64
+ ]
},
{
"DataId": 1036359,
"InteractionType": "Interact",
"Mount": true,
"Fly": true,
- "$.1": "QuestVariables if done after [1]: 2 16 0 0 0 96"
+ "$.1": "QuestVariables if done after [1]: 2 16 0 0 0 96",
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 32
+ ]
},
{
"DataId": 1036357,
},
"TerritoryId": 402,
"InteractionType": "Interact",
- "DisableNavmesh": true
+ "DisableNavmesh": true,
+ "CompletionQuestVariablesFlags": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ 128
+ ]
}
]
},
},
"TerritoryId": 402,
"InteractionType": "Interact",
- "Fly": true
+ "Fly": true,
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMK103_04060_Q1_000_100",
+ "Answer": "TEXT_LUCKMK103_04060_A2_000_100"
+ }
+ ]
}
]
},
"AethernetShortcut": [
"[Ul'dah] Aetheryte Plaza",
"[Ul'dah] Alchemists' Guild"
+ ],
+ "DialogueChoices": [
+ {
+ "Type": "List",
+ "Prompt": "TEXT_LUCKMK106_04063_Q1_000_100",
+ "Answer": "TEXT_LUCKMK106_04063_A2_000_100"
+ }
]
}
]
"type": "string",
"enum": [
"YesNo",
- "List",
- "ContentTalkYesNo",
- "ContentTalkList"
+ "List"
]
},
"ExcelSheet": {
"type": "string"
- },
- "Prompt": {
- "type": [
- "string",
- "null"
- ]
}
},
"required": [
- "Type",
- "Prompt"
+ "Type"
],
"allOf": [
{
"if": {
"properties": {
"Type": {
- "anyOf": [
- {
- "const": "YesNo"
- },
- {
- "const": "ContentTalkYesNo"
- }
- ]
+ "const": "YesNo"
}
}
},
"then": {
"properties": {
+ "Prompt": {
+ "type": [
+ "string",
+ "integer"
+ ]
+ },
"Yes": {
"type": "boolean",
"default": true
}
},
"required": [
+ "Prompt",
"Yes"
]
}
"if": {
"properties": {
"Type": {
- "anyOf": [
- {
- "const": "List"
- },
- {
- "const": "ContentTalkList"
- }
- ]
+ "const": "List"
}
}
},
"then": {
"properties": {
+ "Prompt": {
+ "type": [
+ "string",
+ "integer",
+ "null"
+ ]
+ },
"Answer": {
- "type": "string"
+ "type": [
+ "string",
+ "integer"
+ ]
}
},
"required": [
+ "Prompt",
"Answer"
]
}
internal sealed class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 1;
- public WindowConfig DebugWindowConfig { get; set; } = new();
+ public GeneralConfiguration General { get; } = new();
+ public AdvancedConfiguration Advanced { get; } = new();
+ public WindowConfig DebugWindowConfig { get; } = new();
+ public WindowConfig ConfigWindowConfig { get; } = new();
+
+ internal sealed class GeneralConfiguration
+ {
+ public bool AutoAcceptNextQuest { get; set; }
+ public uint MountId { get; set; } = 71;
+ }
+
+ internal sealed class AdvancedConfiguration
+ {
+ public bool NeverFly { get; set; }
+ }
}
using System.Linq;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController;
private readonly IGameGui _gameGui;
+ private readonly ITargetManager _targetManager;
private readonly ILogger<GameUiController> _logger;
public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions,
- QuestController questController, IGameGui gameGui, ILogger<GameUiController> logger)
+ QuestController questController, IGameGui gameGui, ITargetManager targetManager,
+ ILogger<GameUiController> logger)
{
_addonLifecycle = addonLifecycle;
_dataManager = dataManager;
_gameFunctions = gameFunctions;
_questController = questController;
_gameGui = gameGui;
+ _targetManager = targetManager;
_logger = logger;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
foreach (var dialogueChoice in dialogueChoices)
{
+ if (dialogueChoice.Type != EDialogChoiceType.List)
+ continue;
+
if (dialogueChoice.Answer == null)
{
- _logger.LogInformation("Ignoring entry in DialogueChoices, no answer");
+ _logger.LogDebug("Ignoring entry in DialogueChoices, no answer");
continue;
}
- string? excelPrompt = null, excelAnswer;
- switch (dialogueChoice.Type)
+ if (dialogueChoice.DataId != null && dialogueChoice.DataId != _targetManager.Target?.DataId)
{
- case EDialogChoiceType.ContentTalkList:
- if (dialogueChoice.Prompt != null)
- {
- excelPrompt =
- _gameFunctions.GetContentTalk(uint.Parse(dialogueChoice.Prompt,
- CultureInfo.InvariantCulture));
- }
-
- excelAnswer =
- _gameFunctions.GetContentTalk(uint.Parse(dialogueChoice.Answer, CultureInfo.InvariantCulture));
- break;
- case EDialogChoiceType.List:
- if (dialogueChoice.Prompt != null)
- {
- excelPrompt =
- _gameFunctions.GetDialogueText(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
- }
-
- excelAnswer =
- _gameFunctions.GetDialogueText(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
- break;
- default:
- continue;
+ _logger.LogDebug(
+ "Skipping entry in DialogueChoice expecting target dataId {ExpectedDataId}, actual target is {ActualTargetId}",
+ dialogueChoice.DataId, _targetManager.Target?.DataId);
+ continue;
}
+ string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
+ string? excelAnswer = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Answer);
+
if (actualPrompt == null && !string.IsNullOrEmpty(excelPrompt))
{
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}", excelPrompt);
_logger.LogTrace("DefaultYesNo: Choice count: {Count}", dialogueChoices.Count);
foreach (var dialogueChoice in dialogueChoices)
{
- string? excelPrompt;
- if (dialogueChoice.Prompt != null)
+ if (dialogueChoice.Type != EDialogChoiceType.YesNo)
+ continue;
+
+ if (dialogueChoice.DataId != null && dialogueChoice.DataId != _targetManager.Target?.DataId)
{
- switch (dialogueChoice.Type)
- {
- case EDialogChoiceType.ContentTalkYesNo:
- excelPrompt =
- _gameFunctions.GetContentTalk(uint.Parse(dialogueChoice.Prompt,
- CultureInfo.InvariantCulture));
- break;
- case EDialogChoiceType.YesNo:
- excelPrompt =
- _gameFunctions.GetDialogueText(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
- break;
- default:
- continue;
- }
+ _logger.LogDebug(
+ "Skipping entry in DialogueChoice expecting target dataId {ExpectedDataId}, actual target is {ActualTargetId}",
+ dialogueChoice.DataId, _targetManager.Target?.DataId);
+ continue;
}
- else
- excelPrompt = null;
+ string? excelPrompt = ResolveReference(quest, dialogueChoice.ExcelSheet, dialogueChoice.Prompt);
if (excelPrompt == null || !GameStringEquals(actualPrompt, excelPrompt))
+ {
+ _logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}",
+ excelPrompt, actualPrompt);
continue;
+ }
addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1);
if (!checkAllSteps)
increaseStepCount = false;
if (step != null)
- _logger.LogTrace("Previous step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId, step.TargetTerritoryId);
+ _logger.LogTrace("Previous step: {CurrentTerritory}, {TargetTerritory}", step.TerritoryId,
+ step.TargetTerritoryId);
}
if (step == null || step.TargetTerritoryId == null)
_logger.LogInformation("Using warp {Id}, {Prompt}", entry.RowId, excelPrompt);
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
//if (increaseStepCount)
- //_questController.IncreaseStepCount();
+ //_questController.IncreaseStepCount();
return;
}
}
return a.ReplaceLineEndings().Replace('\u2013', '-') == b.ReplaceLineEndings().Replace('\u2013', '-');
}
+ private string? ResolveReference(Quest quest, string? excelSheet, ExcelRef? excelRef)
+ {
+ if (excelRef == null)
+ return null;
+
+ if (excelRef.Type == ExcelRef.EType.Key)
+ return _gameFunctions.GetDialogueText(quest, excelSheet, excelRef.AsKey());
+ else if (excelRef.Type == ExcelRef.EType.RowId)
+ return _gameFunctions.GetDialogueTextByRowId(excelSheet, excelRef.AsRowId());
+
+ return null;
+ }
+
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "AkatsukiNote", UnendingCodexPostSetup);
}
}
else if (!Destination.IsFlying && !_condition[ConditionFlag.Mounted] && navPoints.Count > 0 &&
- !_gameFunctions.HasStatusPreventingSprintOrMount() && Destination.CanSprint)
+ !_gameFunctions.HasStatusPreventingSprintOrMount(true) && Destination.CanSprint)
{
float actualDistance = 0;
foreach (Vector3 end in navPoints)
_logger.LogInformation("Pathfinding to {Destination}", Destination);
_cancellationTokenSource = new();
- _cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
+ _cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(30));
_pathfindTask =
_navmeshIpc.Pathfind(_clientState.LocalPlayer!.Position, to, fly, _cancellationTokenSource.Token);
}
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps;
using Questionable.Data;
private readonly ILogger<QuestController> _logger;
private readonly QuestRegistry _questRegistry;
private readonly IKeyState _keyState;
+ private readonly Configuration _configuration;
private readonly IReadOnlyList<ITaskFactory> _taskFactories;
private readonly Queue<ITask> _taskQueue = new();
ILogger<QuestController> logger,
QuestRegistry questRegistry,
IKeyState keyState,
+ Configuration configuration,
IEnumerable<ITaskFactory> taskFactories)
{
_clientState = clientState;
_logger = logger;
_questRegistry = questRegistry;
_keyState = keyState;
+ _configuration = configuration;
_taskFactories = taskFactories.ToList().AsReadOnly();
}
if (CurrentQuest != null && CurrentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType))
return;
+ // not verified to work
+ if (_automatic && _currentTask == null && _taskQueue.Count == 0 && CurrentQuest is { Sequence: 0, Step: 255 }
+ && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
+ {
+ _logger.LogWarning("Quest accept apparently didn't work out, resetting progress");
+ CurrentQuest = CurrentQuest with
+ {
+ Step = 0
+ };
+
+ ExecuteNextStep(true);
+ return;
+ }
+
UpdateCurrentTask();
}
{
_logger.LogInformation("New quest: {QuestName}", quest.Name);
CurrentQuest = new QuestProgress(quest, currentSequence, 0);
- Stop("Different Quest");
+
+ bool continueAutomatically = _configuration.General.AutoAcceptNextQuest;
+
+ if (_clientState.LocalPlayer?.Level < quest.Level)
+ continueAutomatically = false;
+
+ Stop("Different Quest", continueAutomatically);
}
else if (CurrentQuest != null)
{
CurrentQuest = CurrentQuest with
{
Step = CurrentQuest.Step + 1,
- StepProgress = new()
+ StepProgress = new(DateTime.Now),
};
}
else
CurrentQuest = CurrentQuest with
{
Step = 255,
- StepProgress = new()
+ StepProgress = new(DateTime.Now),
};
}
public bool HasCurrentTaskMatching<T>() =>
_currentTask is T;
+ public bool IsRunning => _currentTask != null || _taskQueue.Count > 0;
+
public sealed record QuestProgress(
Quest Quest,
byte Sequence,
StepProgress StepProgress)
{
public QuestProgress(Quest quest, byte sequence, int step)
- : this(quest, sequence, step, new StepProgress())
+ : this(quest, sequence, step, new StepProgress(DateTime.Now))
{
}
}
// TODO is this still required?
public sealed record StepProgress(
+ DateTime StartedAt,
int DialogueChoicesSelected = 0);
}
continue;
quest.Name = questData.Name.ToString();
+ quest.Level = questData.ClassJobLevel0;
}
}
return ETaskResult.StillRunning;
}
- if (aetheryteData.IsCityAetheryte(To))
+ if (aetheryteData.IsAirshipLanding(To))
+ {
+ if (aetheryteData.CalculateAirshipLandingDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
+ clientState.TerritoryType, To) > 5)
+ return ETaskResult.StillRunning;
+ }
+ else if (aetheryteData.IsCityAetheryte(To))
{
if (aetheryteData.CalculateDistance(clientState.LocalPlayer?.Position ?? Vector3.Zero,
clientState.TerritoryType, To) > 11)
using System.Numerics;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
+using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.DependencyInjection;
using Questionable.Controller.Steps.BaseTasks;
using Questionable.Model;
var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
.With(quest, step);
var delay = serviceProvider.GetRequiredService<WaitDelay>();
- return [task, delay, new NextStep()];
+ return [task, delay, Next(quest, sequence, step)];
}
switch (step.InteractionType)
case EInteractionType.WalkTo:
case EInteractionType.Jump:
// no need to wait if we're just moving around
- return [new NextStep()];
+ return [Next(quest, sequence, step)];
case EInteractionType.WaitForObjectAtPosition:
ArgumentNullException.ThrowIfNull(step.DataId);
serviceProvider.GetRequiredService<WaitObjectAtPosition>()
.With(step.DataId.Value, step.Position.Value),
serviceProvider.GetRequiredService<WaitDelay>(),
- new NextStep()
+ Next(quest, sequence, step)
];
case EInteractionType.Interact when step.TargetTerritoryId != null:
[
waitInteraction,
serviceProvider.GetRequiredService<WaitDelay>(),
- new NextStep()
+ Next(quest, sequence, step)
];
case EInteractionType.Interact:
default:
- return [serviceProvider.GetRequiredService<WaitDelay>(), new NextStep()];
+ return [serviceProvider.GetRequiredService<WaitDelay>(), Next(quest, sequence, step)];
}
}
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
=> throw new InvalidOperationException();
+
+ public ITask Next(Quest quest, QuestSequence sequence, QuestStep step)
+ {
+ bool lastStep = step == sequence.Steps.LastOrDefault();
+ if (sequence.Sequence == 0 && lastStep)
+ {
+ return new WaitConditionTask(() =>
+ {
+ unsafe
+ {
+ var questManager = QuestManager.Instance();
+ return questManager != null && questManager->IsQuestAccepted(quest.QuestId);
+ }
+ }, "Wait(questAccepted)");
+ }
+ else if (sequence.Sequence == 255 && lastStep)
+ {
+ return new WaitConditionTask(() => QuestManager.IsQuestComplete(quest.QuestId),
+ "Wait(questComplete)");
+ }
+ else
+ return new NextStep();
+ }
}
internal sealed class WaitDelay() : AbstractDelayedTask(TimeSpan.FromSeconds(1))
-using Dalamud.Game.ClientState.Conditions;
+using System;
+using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
: ITask
{
private bool _unmountTriggered;
+ private DateTime _unmountedAt = DateTime.MinValue;
public bool Start()
{
logger.LogInformation("Step explicitly wants no mount, trying to unmount...");
_unmountTriggered = gameFunctions.Unmount();
+ if (_unmountTriggered)
+ _unmountedAt = DateTime.Now;
return true;
}
if (!_unmountTriggered)
{
_unmountTriggered = gameFunctions.Unmount();
+ if (_unmountTriggered)
+ _unmountedAt = DateTime.Now;
+
return ETaskResult.StillRunning;
}
+ if (DateTime.Now < _unmountedAt.AddSeconds(1))
+ return ETaskResult.StillRunning;
+
return condition[ConditionFlag.Mounted]
? ETaskResult.StillRunning
: ETaskResult.TaskComplete;
=> throw new InvalidOperationException();
}
+ internal abstract class UseItemBase : ITask
+ {
+ private bool _usedItem;
+ private DateTime _continueAt;
+
+ protected abstract bool UseItem();
+
+ public bool Start()
+ {
+ _usedItem = UseItem();
+ _continueAt = DateTime.Now.AddSeconds(2);
+ return true;
+ }
+
+ public ETaskResult Update()
+ {
+ if (DateTime.Now > _continueAt)
+ return ETaskResult.StillRunning;
+
+ if (!_usedItem)
+ {
+ _usedItem = UseItem();
+ _continueAt = DateTime.Now.AddSeconds(2);
+ return ETaskResult.StillRunning;
+ }
+
+ return ETaskResult.TaskComplete;
+ }
+ }
+
- internal sealed class UseOnGround(GameFunctions gameFunctions) : AbstractDelayedTask
+ internal sealed class UseOnGround(GameFunctions gameFunctions) : UseItemBase
{
public uint DataId { get; set; }
public uint ItemId { get; set; }
return this;
}
- protected override bool StartInternal()
- {
- gameFunctions.UseItemOnGround(DataId, ItemId);
- return true;
- }
+ protected override bool UseItem() => gameFunctions.UseItemOnGround(DataId, ItemId);
public override string ToString() => $"UseItem({ItemId} on ground at {DataId})";
}
- internal sealed class UseOnObject(GameFunctions gameFunctions) : AbstractDelayedTask
+ internal sealed class UseOnObject(GameFunctions gameFunctions) : UseItemBase
{
public uint DataId { get; set; }
public uint ItemId { get; set; }
return this;
}
- protected override bool StartInternal()
- {
- gameFunctions.UseItem(DataId, ItemId);
- return true;
- }
+ protected override bool UseItem() => gameFunctions.UseItem(DataId, ItemId);
public override string ToString() => $"UseItem({ItemId} on {DataId})";
}
- internal sealed class Use(GameFunctions gameFunctions) : AbstractDelayedTask
+ internal sealed class Use(GameFunctions gameFunctions) : UseItemBase
{
public uint ItemId { get; set; }
return this;
}
- protected override bool StartInternal()
- {
- gameFunctions.UseItem(ItemId);
- return true;
- }
+ protected override bool UseItem() => gameFunctions.UseItem(ItemId);
public override string ToString() => $"UseItem({ItemId})";
}
private readonly NavigationShortcutController _navigationShortcutController;
private readonly WindowSystem _windowSystem;
private readonly DebugWindow _debugWindow;
+ private readonly ConfigWindow _configWindow;
public DalamudInitializer(DalamudPluginInterface pluginInterface, IFramework framework,
ICommandManager commandManager, QuestController questController, MovementController movementController,
- GameUiController gameUiController, NavigationShortcutController navigationShortcutController, WindowSystem windowSystem, DebugWindow debugWindow)
+ GameUiController gameUiController, NavigationShortcutController navigationShortcutController,
+ WindowSystem windowSystem, DebugWindow debugWindow, ConfigWindow configWindow)
{
_pluginInterface = pluginInterface;
_framework = framework;
_navigationShortcutController = navigationShortcutController;
_windowSystem = windowSystem;
_debugWindow = debugWindow;
+ _configWindow = configWindow;
+
+ _windowSystem.AddWindow(debugWindow);
+ _windowSystem.AddWindow(configWindow);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += _debugWindow.Toggle;
+ _pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;
_framework.Update += FrameworkUpdate;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
private void ProcessCommand(string command, string arguments)
{
- _debugWindow.Toggle();
+ if (arguments is "c" or "config")
+ _configWindow.Toggle();
+ else
+ _debugWindow.Toggle();
}
public void Dispose()
_framework.Update -= FrameworkUpdate;
_pluginInterface.UiBuilder.OpenMainUi -= _debugWindow.Toggle;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
+
+ _windowSystem.RemoveAllWindows();
}
}
}
.AsReadOnly();
+ /// <summary>
+ /// Airship landings are special as they're one-way only (except for Radz-at-Han, which is a normal aetheryte).
+ /// </summary>
+ public ReadOnlyDictionary<EAetheryteLocation, Vector3> AirshipLandingLocations { get; } =
+ new Dictionary<EAetheryteLocation, Vector3>
+ {
+ { EAetheryteLocation.LimsaAirship, new(-19.44352f, 91.99999f, -9.892939f) },
+ { EAetheryteLocation.GridaniaAirship, new(24.86354f, -19.000002f, 96f) },
+ { EAetheryteLocation.UldahAirship, new(-16.954851f, 82.999985f, -9.421141f) },
+ { EAetheryteLocation.KuganeAirship, new(-55.72525f, 79.10602f, 46.23109f) },
+ }.AsReadOnly();
+
public ReadOnlyDictionary<EAetheryteLocation, string> AethernetNames { get; }
public ReadOnlyDictionary<EAetheryteLocation, ushort> TerritoryIds { get; }
public IReadOnlyList<ushort> TownTerritoryIds { get; set; }
return (fromPosition - toPosition).Length();
}
+ public float CalculateAirshipLandingDistance(Vector3 fromPosition, ushort fromTerritoryType, EAetheryteLocation to)
+ {
+ if (!TerritoryIds.TryGetValue(to, out ushort toTerritoryType) || fromTerritoryType != toTerritoryType)
+ return float.MaxValue;
+
+ if (!AirshipLandingLocations.TryGetValue(to, out Vector3 toPosition))
+ return float.MaxValue;
+
+ return (fromPosition - toPosition).Length();
+ }
+
public bool IsCityAetheryte(EAetheryteLocation aetheryte)
{
var territoryId = TerritoryIds[aetheryte];
return TownTerritoryIds.Contains(territoryId);
}
+
+ public bool IsAirshipLanding(EAetheryteLocation aetheryte) => AirshipLandingLocations.ContainsKey(aetheryte);
}
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Lumina.Excel.CustomSheets;
-using Lumina.Excel.GeneratedSheets;
+using Lumina.Excel.GeneratedSheets2;
using Microsoft.Extensions.Logging;
using Questionable.Controller;
using Questionable.Model.V1;
using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
+using ContentFinderCondition = Lumina.Excel.GeneratedSheets.ContentFinderCondition;
+using ContentTalk = Lumina.Excel.GeneratedSheets.ContentTalk;
+using Emote = Lumina.Excel.GeneratedSheets.Emote;
using GameObject = Dalamud.Game.ClientState.Objects.Types.GameObject;
using Quest = Questionable.Model.Quest;
+using TerritoryType = Lumina.Excel.GeneratedSheets.TerritoryType;
namespace Questionable;
private readonly IClientState _clientState;
private readonly QuestRegistry _questRegistry;
private readonly IGameGui _gameGui;
+ private readonly Configuration _configuration;
private readonly ILogger<GameFunctions> _logger;
public GameFunctions(IDataManager dataManager, IObjectTable objectTable, ISigScanner sigScanner,
ITargetManager targetManager, ICondition condition, IClientState clientState, QuestRegistry questRegistry,
- IGameGui gameGui, ILogger<GameFunctions> logger)
+ IGameGui gameGui, Configuration configuration, ILogger<GameFunctions> logger)
{
_dataManager = dataManager;
_objectTable = objectTable;
_clientState = clientState;
_questRegistry = questRegistry;
_gameGui = gameGui;
+ _configuration = configuration;
_logger = logger;
_processChatBox =
Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText(Signatures.SendChat));
return false;
}
- public void UseItem(uint itemId)
+ public bool UseItem(uint itemId)
{
- AgentInventoryContext.Instance()->UseItem(itemId);
+ return AgentInventoryContext.Instance()->UseItem(itemId) == 0;
}
- public void UseItem(uint dataId, uint itemId)
+ public bool UseItem(uint dataId, uint itemId)
{
GameObject? gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
_targetManager.Target = gameObject;
- AgentInventoryContext.Instance()->UseItem(itemId);
+ return AgentInventoryContext.Instance()->UseItem(itemId) == 0;
}
+
+ return false;
}
- public void UseItemOnGround(uint dataId, uint itemId)
+ public bool UseItemOnGround(uint dataId, uint itemId)
{
GameObject? gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
var position = (FFXIVClientStructs.FFXIV.Common.Math.Vector3)gameObject.Position;
- ActionManager.Instance()->UseActionLocation(ActionType.KeyItem, itemId, location: &position);
+ return ActionManager.Instance()->UseActionLocation(ActionType.KeyItem, itemId, location: &position);
}
+
+ return false;
}
public void UseEmote(uint dataId, EEmote emote)
return gameObject != null && (gameObject.Position - position).Length() < 0.05f;
}
- public bool HasStatusPreventingSprintOrMount()
+ public bool HasStatusPreventingSprintOrMount(bool skipConfigCheck = false)
{
+ if (!skipConfigCheck && _configuration.Advanced.NeverFly)
+ return true;
+
if (_condition[ConditionFlag.Swimming] && !IsFlyingUnlockedInCurrentZone())
return true;
return true;
var playerState = PlayerState.Instance();
- if (playerState != null && playerState->IsMountUnlocked(71))
+ if (playerState != null && _configuration.General.MountId != 0 &&
+ playerState->IsMountUnlocked(_configuration.General.MountId))
{
- if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, 71) == 0)
+ if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, _configuration.General.MountId) == 0)
{
- if (ActionManager.Instance()->UseAction(ActionType.Mount, 71))
+ if (ActionManager.Instance()->UseAction(ActionType.Mount, _configuration.General.MountId))
{
- _logger.LogInformation("Using SDS Fenrir as mount");
+ _logger.LogInformation("Using preferred mount");
return true;
}
return excelSheet.FirstOrDefault(x => x.Key == key)?.Value?.ToDalamudString().ToString();
}
- public string? GetContentTalk(uint rowId)
+ public string? GetDialogueTextByRowId(string? excelSheet, uint rowId)
{
- var questRow = _dataManager.GetExcelSheet<ContentTalk>()!.GetRow(rowId);
- return questRow?.Text?.ToString();
+ if (excelSheet == "GimmickYesNo")
+ {
+ var questRow = _dataManager.GetExcelSheet<GimmickYesNo>()!.GetRow(rowId);
+ return questRow?.Unknown0?.ToString();
+ }
+ else if (excelSheet is "ContentTalk" or null)
+ {
+ var questRow = _dataManager.GetExcelSheet<ContentTalk>()!.GetRow(rowId);
+ return questRow?.Text?.ToString();
+ }
+ else
+ throw new ArgumentOutOfRangeException(nameof(excelSheet), $"Unsupported excel sheet {excelSheet}");
}
public bool IsOccupied()
{
public required ushort QuestId { get; init; }
public required string Name { get; set; }
+ public ushort Level { get; set; }
public required QuestData Data { get; init; }
public QuestSequence? FindSequence(byte currentSequence)
{
{ EDialogChoiceType.YesNo, "YesNo" },
{ EDialogChoiceType.List, "List" },
- { EDialogChoiceType.ContentTalkYesNo, "ContentTalkYesNo" },
- { EDialogChoiceType.ContentTalkList, "ContentTalkList" },
};
}
--- /dev/null
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Questionable.Model.V1.Converter;
+
+internal sealed class ExcelRefConverter : JsonConverter<ExcelRef>
+{
+ public override ExcelRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ return new ExcelRef(reader.GetString()!);
+ else if (reader.TokenType == JsonTokenType.Number)
+ return new ExcelRef(reader.GetUInt32());
+ else
+ return null;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ExcelRef? value, JsonSerializerOptions options)
+ {
+ if (value == null)
+ writer.WriteNullValue();
+ else if (value.Type == ExcelRef.EType.Key)
+ writer.WriteStringValue(value.AsKey());
+ else if (value.Type == ExcelRef.EType.RowId)
+ writer.WriteNumberValue(value.AsRowId());
+ else
+ throw new JsonException();
+ }
+}
[JsonConverter(typeof(DialogueChoiceTypeConverter))]
public EDialogChoiceType Type { get; set; }
public string? ExcelSheet { get; set; }
- public string? Prompt { get; set; }
+
+ [JsonConverter(typeof(ExcelRefConverter))]
+ public ExcelRef? Prompt { get; set; }
+
public bool Yes { get; set; } = true;
- public string? Answer { get; set; }
+
+ [JsonConverter(typeof(ExcelRefConverter))]
+ public ExcelRef? Answer { get; set; }
+
+ /// <summary>
+ /// If set, only applies when focusing the given target id.
+ /// </summary>
+ public uint? DataId { get; set; }
}
None,
YesNo,
List,
- ContentTalkYesNo,
- ContentTalkList,
}
--- /dev/null
+using System;
+
+namespace Questionable.Model.V1;
+
+public class ExcelRef
+{
+ private readonly string? _stringValue;
+ private readonly uint? _rowIdValue;
+
+ public ExcelRef(string value)
+ {
+ _stringValue = value;
+ _rowIdValue = null;
+ Type = EType.Key;
+ }
+
+ public ExcelRef(uint value)
+ {
+ _stringValue = null;
+ _rowIdValue = value;
+ Type = EType.RowId;
+ }
+
+ public EType Type { get; }
+
+ public string AsKey()
+ {
+ if (Type != EType.Key)
+ throw new InvalidOperationException();
+
+ return _stringValue!;
+ }
+
+ public uint AsRowId()
+ {
+ if (Type != EType.RowId)
+ throw new InvalidOperationException();
+
+ return _rowIdValue!.Value;
+ }
+
+ public enum EType
+ {
+ None,
+ Key,
+ RowId,
+ }
+}
serviceCollection.AddSingleton<NavigationShortcutController>();
serviceCollection.AddSingleton<DebugWindow>();
+ serviceCollection.AddSingleton<ConfigWindow>();
serviceCollection.AddSingleton<DalamudInitializer>();
_serviceProvider = serviceCollection.BuildServiceProvider();
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Dalamud.Interface.Colors;
+using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
+using ImGuiNET;
+using LLib.ImGui;
+using Lumina.Excel.GeneratedSheets;
+
+namespace Questionable.Windows;
+
+internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
+{
+ private readonly DalamudPluginInterface _pluginInterface;
+ private readonly Configuration _configuration;
+
+ private readonly uint[] _mountIds;
+ private readonly string[] _mountNames;
+
+ [SuppressMessage("Performance", "CA1861", Justification = "One time initialization")]
+ public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager)
+ : base("Config - Questionable###QuestionableConfig", ImGuiWindowFlags.AlwaysAutoResize)
+ {
+ _pluginInterface = pluginInterface;
+ _configuration = configuration;
+
+ var mounts = dataManager.GetExcelSheet<Mount>()!
+ .Where(x => x is { RowId: > 0, Icon: > 0 })
+ .Select(x => (MountId: x.RowId, Name: x.Singular.ToString()))
+ .Where(x => !string.IsNullOrEmpty(x.Name))
+ .OrderBy(x => x.Name)
+ .ToList();
+ _mountIds = new uint[] { 0 }.Concat(mounts.Select(x => x.MountId)).ToArray();
+ _mountNames = new[] { "Mount Roulette" }.Concat(mounts.Select(x => x.Name)).ToArray();
+ }
+
+ public WindowConfig WindowConfig => _configuration.ConfigWindowConfig;
+
+ public override void Draw()
+ {
+ if (ImGui.BeginTabBar("QuestionableConfigTabs"))
+ {
+ if (ImGui.BeginTabItem("General"))
+ {
+ int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId);
+ if (selectedMount == -1)
+ {
+ selectedMount = 0;
+ _configuration.General.MountId = _mountIds[selectedMount];
+ Save();
+ }
+
+ if (ImGui.Combo("Preferred Mount", ref selectedMount, _mountNames, _mountNames.Length))
+ {
+ _configuration.General.MountId = _mountIds[selectedMount];
+ Save();
+ }
+
+ ImGui.EndTabItem();
+ }
+
+ if (ImGui.BeginTabItem("Advanced"))
+ {
+ ImGui.TextColored(ImGuiColors.DalamudRed,
+ "Enabling any option here may cause unexpected behavior. Use at your own risk.");
+
+ ImGui.Separator();
+
+ bool neverFly = _configuration.Advanced.NeverFly;
+ if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
+ {
+ _configuration.Advanced.NeverFly = neverFly;
+ Save();
+ }
+
+ ImGui.EndTabItem();
+ }
+
+ ImGui.EndTabBar();
+ }
+ }
+
+ private void Save() => _pluginInterface.SavePluginConfig(_configuration);
+
+ public void SaveWindowConfig() => Save();
+}
namespace Questionable.Windows;
-internal sealed class DebugWindow : LWindow, IPersistableWindowConfig, IDisposable
+internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
{
private readonly DalamudPluginInterface _pluginInterface;
- private readonly WindowSystem _windowSystem;
private readonly MovementController _movementController;
private readonly QuestController _questController;
private readonly GameFunctions _gameFunctions;
private readonly Configuration _configuration;
private readonly ILogger<DebugWindow> _logger;
- public DebugWindow(DalamudPluginInterface pluginInterface, WindowSystem windowSystem,
- MovementController movementController, QuestController questController, GameFunctions gameFunctions,
- IClientState clientState, IFramework framework, ITargetManager targetManager, GameUiController gameUiController,
- Configuration configuration, ILogger<DebugWindow> logger)
+ public DebugWindow(DalamudPluginInterface pluginInterface,
+ MovementController movementController,
+ QuestController questController,
+ GameFunctions gameFunctions,
+ IClientState clientState,
+ IFramework framework,
+ ITargetManager targetManager,
+ GameUiController gameUiController,
+ Configuration configuration,
+ ILogger<DebugWindow> logger)
: base("Questionable", ImGuiWindowFlags.AlwaysAutoResize)
{
_pluginInterface = pluginInterface;
- _windowSystem = windowSystem;
_movementController = movementController;
_questController = questController;
_gameFunctions = gameFunctions;
MinimumSize = new Vector2(200, 30),
MaximumSize = default
};
-
- _windowSystem.AddWindow(this);
}
public WindowConfig WindowConfig => _configuration.DebugWindowConfig;
ImGui.Text(_questController.ToStatString());
//ImGui.EndDisabled();
+ ImGui.BeginDisabled(_questController.IsRunning);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{
_questController.ExecuteNextStep(true);
_questController.ExecuteNextStep(false);
}
+ ImGui.EndDisabled();
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
.FindSequence(currentQuest.Sequence)
?.FindStep(currentQuest.Step);
bool colored = currentStep != null && currentStep.InteractionType == EInteractionType.Instruction
- && _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
+ && _questController
+ .HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
if (colored)
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
_questController.Stop("Manual");
_questController.IncreaseStepCount();
}
+
if (colored)
ImGui.PopStyleColor();
+
+ bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
+ if (ImGui.Checkbox("Automatically accept next quest", ref autoAcceptNextQuest))
+ {
+ _configuration.General.AutoAcceptNextQuest = autoAcceptNextQuest;
+ _pluginInterface.SavePluginConfig(_configuration);
+ }
}
else
ImGui.Text("No active quest");
}
else
{
- if (ImGui.Button($"Copy"))
+ ImGui.Button($"Copy");
+ if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
ImGui.SetClipboardText($$"""
"Position": {
"InteractionType": ""
""");
}
+ else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
+ {
+ Vector3 position = _clientState.LocalPlayer!.Position;
+ ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
+ $"new({position.X}f, {position.Y}f, {position.Z}f)"));
+ }
}
}
ImGui.EndDisabled();
}
}
-
- public void Dispose()
- {
- _windowSystem.RemoveWindow(this);
- }
}