1
0
forked from liza/Questionable

Rework logic for next/simulated quests., handle dialogue choices when starting quests, tuliyollal/solution nine side quests

This commit is contained in:
Liza 2024-07-11 02:56:42 +02:00
parent 48f4045e77
commit 7072df6fcf
Signed by: liza
GPG Key ID: 7199F8D727D55F67
62 changed files with 3252 additions and 283 deletions

View File

@ -38,7 +38,7 @@ public class QuestSourceGenerator : ISourceGenerator
public void Execute(GeneratorExecutionContext context) public void Execute(GeneratorExecutionContext context)
{ {
List<(ushort, QuestData)> quests = []; List<(ushort, QuestRoot)> quests = [];
// Find schema definition // Find schema definition
AdditionalText jsonSchemaFile = AdditionalText jsonSchemaFile =
@ -75,7 +75,7 @@ public class QuestSourceGenerator : ISourceGenerator
context.ReportDiagnostic(error); context.ReportDiagnostic(error);
} }
var quest = questNode.Deserialize<QuestData>()!; var quest = questNode.Deserialize<QuestRoot>()!;
quests.Add((id, quest)); quests.Add((id, quest));
} }
@ -196,7 +196,7 @@ public class QuestSourceGenerator : ISourceGenerator
context.AddSource("AssemblyQuestLoader.g.cs", code.ToFullString()); context.AddSource("AssemblyQuestLoader.g.cs", code.ToFullString());
} }
private static IEnumerable<SyntaxNodeOrToken> CreateQuestInitializer(ushort questId, QuestData quest) private static IEnumerable<SyntaxNodeOrToken> CreateQuestInitializer(ushort questId, QuestRoot quest)
{ {
return new SyntaxNodeOrToken[] return new SyntaxNodeOrToken[]
{ {
@ -210,23 +210,23 @@ public class QuestSourceGenerator : ISourceGenerator
Literal(questId)), Literal(questId)),
Token(SyntaxKind.CommaToken), Token(SyntaxKind.CommaToken),
ObjectCreationExpression( ObjectCreationExpression(
IdentifierName(nameof(QuestData))) IdentifierName(nameof(QuestRoot)))
.WithInitializer( .WithInitializer(
InitializerExpression( InitializerExpression(
SyntaxKind.ObjectInitializerExpression, SyntaxKind.ObjectInitializerExpression,
SeparatedList<ExpressionSyntax>( SeparatedList<ExpressionSyntax>(
SyntaxNodeList( SyntaxNodeList(
Assignment(nameof(QuestData.Author), quest.Author, null) Assignment(nameof(QuestRoot.Author), quest.Author, null)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
AssignmentList(nameof(QuestData.Contributors), quest.Contributors) AssignmentList(nameof(QuestRoot.Contributors), quest.Contributors)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestData.Comment), quest.Comment, null) Assignment(nameof(QuestRoot.Comment), quest.Comment, null)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
AssignmentList(nameof(QuestData.TerritoryBlacklist), AssignmentList(nameof(QuestRoot.TerritoryBlacklist),
quest.TerritoryBlacklist).AsSyntaxNodeOrToken(), quest.TerritoryBlacklist).AsSyntaxNodeOrToken(),
AssignmentExpression( AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression, SyntaxKind.SimpleAssignmentExpression,
IdentifierName(nameof(QuestData.QuestSequence)), IdentifierName(nameof(QuestRoot.QuestSequence)),
CreateQuestSequence(quest.QuestSequence)) CreateQuestSequence(quest.QuestSequence))
)))) ))))
})), })),
@ -354,7 +354,11 @@ public class QuestSourceGenerator : ISourceGenerator
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
AssignmentList(nameof(QuestStep.PointMenuChoices), step.PointMenuChoices) AssignmentList(nameof(QuestStep.PointMenuChoices), step.PointMenuChoices)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.QuestId), step.QuestId, emptyStep.QuestId) Assignment(nameof(QuestStep.PickupQuestId), step.PickupQuestId, emptyStep.PickupQuestId)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.TurnInQuestId), step.TurnInQuestId, emptyStep.TurnInQuestId)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.NextQuestId), step.NextQuestId, emptyStep.NextQuestId)
.AsSyntaxNodeOrToken()))))), .AsSyntaxNodeOrToken()))))),
Token(SyntaxKind.CommaToken), Token(SyntaxKind.CommaToken),
}.ToArray()))); }.ToArray())));

View File

@ -14,7 +14,11 @@
}, },
"StopDistance": 7, "StopDistance": 7,
"TerritoryId": 962, "TerritoryId": 962,
"InteractionType": "AcceptQuest" "InteractionType": "AcceptQuest",
"AetheryteShortcut": "Old Sharlayan",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
} }
] ]
}, },

View File

@ -13,7 +13,11 @@
"Z": 201.28174 "Z": 201.28174
}, },
"TerritoryId": 1185, "TerritoryId": 1185,
"InteractionType": "AcceptQuest" "InteractionType": "AcceptQuest",
"AetheryteShortcut": "Tuliyollal",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
} }
] ]
}, },
@ -187,7 +191,8 @@
"Z": 111.436646 "Z": 111.436646
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4825
} }
] ]
} }

View File

@ -13,7 +13,11 @@
"Z": 111.436646 "Z": 111.436646
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "AcceptQuest" "InteractionType": "AcceptQuest",
"AetheryteShortcut": "Limsa Lominsa",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
} }
] ]
}, },
@ -115,7 +119,8 @@
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "CompleteQuest", "InteractionType": "CompleteQuest",
"AetheryteShortcut": "Limsa Lominsa" "AetheryteShortcut": "Limsa Lominsa",
"NextQuestId": 4826
} }
] ]
} }

View File

@ -13,7 +13,11 @@
"Z": 111.436646 "Z": 111.436646
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "AcceptQuest" "InteractionType": "AcceptQuest",
"AetheryteShortcut": "Limsa Lominsa",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
} }
] ]
}, },
@ -99,7 +103,8 @@
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "CompleteQuest", "InteractionType": "CompleteQuest",
"AetheryteShortcut": "Limsa Lominsa" "AetheryteShortcut": "Limsa Lominsa",
"NextQuestId": 4827
} }
] ]
} }

View File

@ -13,7 +13,11 @@
"Z": 111.436646 "Z": 111.436646
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "AcceptQuest" "InteractionType": "AcceptQuest",
"AetheryteShortcut": "Limsa Lominsa",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
} }
] ]
}, },
@ -137,7 +141,9 @@
"Z": 111.436646 "Z": 111.436646
}, },
"TerritoryId": 129, "TerritoryId": 129,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"AetheryteShortcut": "Limsa Lominsa",
"NextQuestId": 4828
} }
] ]
} }

View File

@ -0,0 +1,151 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1046290,
"Position": {
"X": -114.091736,
"Y": 20,
"Z": 111.436646
},
"TerritoryId": 129,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "Limsa Lominsa",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1046317,
"Position": {
"X": 570.18384,
"Y": 20.721313,
"Z": 451.34656
},
"TerritoryId": 137,
"InteractionType": "Interact",
"AetheryteShortcut": "Eastern La Noscea - Costa Del Sol",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGBA241_04828_Q1_000_013",
"Answer": "TEXT_KINGBA241_04828_A1_000_001"
}
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1046317,
"Position": {
"X": 570.18384,
"Y": 20.721313,
"Z": 451.34656
},
"TerritoryId": 137,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 2013561,
"Position": {
"X": -220.44714,
"Y": 35.78235,
"Z": 268.05518
},
"StopDistance": 0.25,
"TerritoryId": 137,
"InteractionType": "Combat",
"EnemySpawnType": "AfterInteraction",
"KillEnemyDataIds": [
17614,
17615,
17616
],
"AetheryteShortcut": "Eastern La Noscea - Wineport",
"Fly": true,
"Comment": "two waves of enemies"
}
]
},
{
"Sequence": 4,
"Steps": [
{
"DataId": 1046318,
"Position": {
"X": -98.95477,
"Y": 34.20764,
"Z": 220.44702
},
"TerritoryId": 137,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 5,
"Steps": [
{
"DataId": 1046318,
"Position": {
"X": -98.95477,
"Y": 34.20764,
"Z": 220.44702
},
"TerritoryId": 137,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 6,
"Steps": [
{
"DataId": 1046317,
"Position": {
"X": 570.18384,
"Y": 20.721313,
"Z": 451.34656
},
"TerritoryId": 137,
"InteractionType": "Interact",
"AetheryteShortcut": "Eastern La Noscea - Costa Del Sol"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1046290,
"Position": {
"X": -114.091736,
"Y": 20,
"Z": 111.436646
},
"TerritoryId": 129,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Limsa Lominsa",
"NextQuestId": 4829
}
]
}
]
}

View File

@ -0,0 +1,25 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1046290,
"Position": {
"X": -114.091736,
"Y": 20,
"Z": 111.436646
},
"TerritoryId": 129,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "Limsa Lominsa",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
}
]
}
]
}

View File

@ -75,7 +75,8 @@
"Z": 628.3817 "Z": 628.3817
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4843
} }
] ]
} }

View File

@ -133,7 +133,8 @@
"Z": 628.3817 "Z": 628.3817
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4844
} }
] ]
} }

View File

@ -155,7 +155,8 @@
"Z": 628.3817 "Z": 628.3817
}, },
"TerritoryId": 957, "TerritoryId": 957,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4845
} }
] ]
} }

View File

@ -13,7 +13,105 @@
"Z": 203.38745 "Z": 203.38745
}, },
"TerritoryId": 1185, "TerritoryId": 1185,
"InteractionType": "AcceptQuest" "InteractionType": "AcceptQuest",
"AetheryteShortcut": "Tuliyollal",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1020193,
"Position": {
"X": 26.108154,
"Y": 0,
"Z": 25.253662
},
"TerritoryId": 635,
"InteractionType": "Interact",
"AetheryteShortcut": "Rhalgr's Reach",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1019483,
"Position": {
"X": -37.521973,
"Y": 1.2530026,
"Z": 36.301147
},
"TerritoryId": 635,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 1019484,
"Position": {
"X": -66.5141,
"Y": -5.9571908E-15,
"Z": -22.964844
},
"TerritoryId": 635,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1048311,
"Position": {
"X": 460.86804,
"Y": 41.92747,
"Z": 335.10327
},
"TerritoryId": 612,
"InteractionType": "Interact",
"AetheryteShortcut": "Fringes - Peering Stones",
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048309,
"Position": {
"X": 19.638367,
"Y": -0.11837298,
"Z": 47.226562
},
"TerritoryId": 635,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Rhalgr's Reach",
"NextQuestId": 4837
} }
] ]
} }

View File

@ -0,0 +1,25 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048309,
"Position": {
"X": 19.638367,
"Y": -0.11837298,
"Z": 47.226562
},
"TerritoryId": 635,
"InteractionType": "AcceptQuest",
"AetheryteShortcut": "Rhalgr's Reach",
"SkipIf": [
"AetheryteShortcutIfInSameTerritory"
]
}
]
}
]
}

View File

@ -96,7 +96,8 @@
"Z": -48.05072 "Z": -48.05072
}, },
"TerritoryId": 418, "TerritoryId": 418,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4819
} }
] ]
} }

View File

@ -113,7 +113,8 @@
"Z": -48.05072 "Z": -48.05072
}, },
"TerritoryId": 418, "TerritoryId": 418,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4820
} }
] ]
} }

View File

@ -143,7 +143,8 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Ishgard] Aetheryte Plaza", "[Ishgard] Aetheryte Plaza",
"[Ishgard] The Forgotten Knight" "[Ishgard] The Forgotten Knight"
] ],
"NextQuestId": 4821
} }
] ]
} }

View File

@ -115,7 +115,8 @@
}, },
"StopDistance": 5, "StopDistance": 5,
"TerritoryId": 418, "TerritoryId": 418,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"NextQuestId": 4822
} }
] ]
} }

View File

@ -137,7 +137,8 @@
"Prompt": "TEXT_KINGBA141_04822_Q3_000_000", "Prompt": "TEXT_KINGBA141_04822_Q3_000_000",
"Answer": "TEXT_KINGBA141_04822_A3_000_002" "Answer": "TEXT_KINGBA141_04822_A3_000_002"
} }
] ],
"NextQuestId": 4823
} }
] ]
} }

View File

@ -0,0 +1,88 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051388,
"Position": {
"X": -185.47345,
"Y": 0.66,
"Z": -42.648987
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -445.9273,
"Y": 13.999773,
"Z": 192.44756
},
"TerritoryId": 1186,
"InteractionType": "WalkTo",
"AethernetShortcut": [
"[Solution Nine] Nexus Arcade",
"[Solution Nine] Residential Sector"
]
},
{
"DataId": 1050492,
"Position": {
"X": -446.25012,
"Y": 13.999722,
"Z": 195.23914
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"Position": {
"X": -445.1605,
"Y": 13.999999,
"Z": 162.65598
},
"TerritoryId": 1186,
"InteractionType": "WalkTo"
},
{
"Position": {
"X": -440.51483,
"Y": 13.999909,
"Z": 154.73817
},
"TerritoryId": 1186,
"InteractionType": "WalkTo",
"DisableNavmesh": true,
"Comment": "For some reason navmesh wanders off through the whole area"
},
{
"DataId": 1051388,
"Position": {
"X": -185.47345,
"Y": 0.66,
"Z": -42.648987
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Solution Nine] Residential Sector",
"[Solution Nine] Nexus Arcade"
]
}
]
}
]
}

View File

@ -0,0 +1,133 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051389,
"Position": {
"X": 501.12158,
"Y": 59.999813,
"Z": 163.83606
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZG002_05165_Q1_000_000",
"Answer": "TEXT_KINGZG002_05165_A1_000_002"
}
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014339,
"Position": {
"X": 483.08533,
"Y": 61.386963,
"Z": 196.09363
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2014340,
"Position": {
"X": 444.3275,
"Y": 61.356445,
"Z": 242.38953
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 2014338,
"Position": {
"X": 456.2904,
"Y": 61.41748,
"Z": 68.89441
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051390,
"Position": {
"X": 500.08386,
"Y": 59.999813,
"Z": 161.4862
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051391,
"Position": {
"X": 497.94763,
"Y": 59.99981,
"Z": 160.84534
},
"TerritoryId": 1186,
"InteractionType": "Emote",
"Emote": "doubt"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051390,
"Position": {
"X": 500.08386,
"Y": 59.999813,
"Z": 161.4862
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,94 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051393,
"Position": {
"X": -315.4193,
"Y": 14,
"Z": 155.7793
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051393,
"Position": {
"X": -315.4193,
"Y": 14,
"Z": 155.7793
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051393,
"Position": {
"X": -206.10861,
"Y": 2.1999774,
"Z": 213.03752
},
"StopDistance": 0.25,
"TerritoryId": 1186,
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZG003_05166_Q2_000_000",
"Answer": "TEXT_KINGZG003_05166_A2_000_001"
}
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051393,
"Position": {
"X": -23.899967,
"Y": -5.8484287,
"Z": 230.24123
},
"StopDistance": 0.25,
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051395,
"Position": {
"X": -3.55542,
"Y": 0.0005434508,
"Z": -85.89307
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Solution Nine] Information Center",
"[Solution Nine] Aetheryte Plaza"
]
}
]
}
]
}

View File

@ -0,0 +1,92 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051396,
"Position": {
"X": -346.7918,
"Y": 9.519508,
"Z": 18.20398
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": -306.07123,
"Y": 10.014936,
"Z": 1.7894125
},
"TerritoryId": 1186,
"InteractionType": "WalkTo"
},
{
"DataId": 1049242,
"Position": {
"X": -303.7309,
"Y": 10.014902,
"Z": 1.7241821
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": 292.01248,
"Y": 51.401478,
"Z": 200.04631
},
"TerritoryId": 1186,
"InteractionType": "WalkTo",
"AethernetShortcut": [
"[Solution Nine] Residential Sector",
"[Solution Nine] Neon Stein"
]
},
{
"DataId": 1048081,
"Position": {
"X": 292.16443,
"Y": 51.401485,
"Z": 202.65503
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051396,
"Position": {
"X": -346.7918,
"Y": 9.519508,
"Z": 18.20398
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Solution Nine] Neon Stein",
"[Solution Nine] Residential Sector"
]
}
]
}
]
}

View File

@ -0,0 +1,95 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051397,
"Position": {
"X": -40.024475,
"Y": 38.80659,
"Z": -459.49493
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051400,
"Position": {
"X": -56.351562,
"Y": 38.0566,
"Z": -347.1275
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 1051399,
"Position": {
"X": -26.230347,
"Y": 38.056416,
"Z": -310.81104
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1051398,
"Position": {
"X": 38.254395,
"Y": 38.0566,
"Z": -357.99194
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051397,
"Position": {
"X": -40.024475,
"Y": 38.80659,
"Z": -459.49493
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,87 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051401,
"Position": {
"X": 383.90173,
"Y": 60,
"Z": 56.839844
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 292.01248,
"Y": 51.401478,
"Z": 200.04631
},
"TerritoryId": 1186,
"InteractionType": "WalkTo",
"AethernetShortcut": [
"[Solution Nine] True Vue",
"[Solution Nine] Neon Stein"
]
},
{
"DataId": 1048081,
"Position": {
"X": 292.16443,
"Y": 51.401485,
"Z": 202.65503
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051402,
"Position": {
"X": -205.58484,
"Y": 1.9200057,
"Z": 7.583679
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Solution Nine] Neon Stein",
"[Solution Nine] Nexus Arcade"
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051403,
"Position": {
"X": 13.992493,
"Y": -5.845003,
"Z": 269.91675
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Solution Nine] Nexus Arcade",
"[Solution Nine] Information Center"
]
}
]
}
]
}

View File

@ -0,0 +1,100 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051404,
"Position": {
"X": -157.33582,
"Y": 2.1999714,
"Z": 229.69409
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"Position": {
"X": 322.70325,
"Y": 52.41666,
"Z": 211.24818
},
"TerritoryId": 1186,
"InteractionType": "WalkTo",
"AethernetShortcut": [
"[Solution Nine] Information Center",
"[Solution Nine] True Vue"
]
},
{
"DataId": 1049154,
"Position": {
"X": 320.27148,
"Y": 52.41666,
"Z": 211.32214
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051405,
"Position": {
"X": -156.2677,
"Y": 2.199971,
"Z": 231.79968
},
"TerritoryId": 1186,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Solution Nine] True Vue",
"[Solution Nine] Information Center"
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051409,
"Position": {
"X": -157.24426,
"Y": 2.1999712,
"Z": 231.21985
},
"StopDistance": 4,
"TerritoryId": 1186,
"InteractionType": "Emote",
"Emote": "psych"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051405,
"Position": {
"X": -156.2677,
"Y": 2.199971,
"Z": 231.79968
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,51 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051407,
"Position": {
"X": -182.29956,
"Y": 1.1335879E-05,
"Z": 45.059814
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014345,
"Position": {
"X": -206.92761,
"Y": 0.25933838,
"Z": -106.58429
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051407,
"Position": {
"X": -182.29956,
"Y": 1.1335879E-05,
"Z": 45.059814
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,73 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051069,
"Position": {
"X": -313.43555,
"Y": 4.6900077,
"Z": -36.57599
},
"TerritoryId": 1186,
"InteractionType": "AcceptQuest",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZG009_05172_Q1_000_000",
"Answer": "TEXT_KINGZG009_05172_A1_000_003"
}
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014292,
"Position": {
"X": -375.93652,
"Y": 13.992493,
"Z": 144.06042
},
"TerritoryId": 1186,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": -375.93652,
"Y": 13.992493,
"Z": 144.06042
},
"TerritoryId": 1186,
"InteractionType": "Instruction",
"Comment": "Catch the cat. Good luck!"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051069,
"Position": {
"X": -313.43555,
"Y": 4.6900077,
"Z": -36.57599
},
"TerritoryId": 1186,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,51 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050728,
"Position": {
"X": 50.125854,
"Y": 47,
"Z": -330.40363
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050729,
"Position": {
"X": 187.51807,
"Y": 42.40001,
"Z": -350.36243
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050728,
"Position": {
"X": 50.125854,
"Y": 47,
"Z": -330.40363
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,81 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048459,
"Position": {
"X": -394.43048,
"Y": 11,
"Z": 3.5552979
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050733,
"Position": {
"X": -16.83075,
"Y": -10.000011,
"Z": 93.12573
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Tuliyollal] Dirigible Landing",
"[Tuliyollal] Bayside Bevy Marketplace"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1050734,
"Position": {
"X": 79.301025,
"Y": -17.964588,
"Z": 199.57275
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZA002_05018_Q1_000_000",
"Answer": "TEXT_KINGZA002_05018_A1_000_003"
}
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048459,
"Position": {
"X": -394.43048,
"Y": 11,
"Z": 3.5552979
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] Bayside Bevy Marketplace",
"[Tuliyollal] Dirigible Landing"
]
}
]
}
]
}

View File

@ -0,0 +1,82 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1050735,
"Position": {
"X": 121.11084,
"Y": -17.972874,
"Z": 74.021484
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1050736,
"Position": {
"X": 31.47937,
"Y": -17.9643,
"Z": 167.46765
},
"StopDistance": 2,
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2014232,
"Position": {
"X": 33.310425,
"Y": -16.678162,
"Z": 167.98657
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1050737,
"Position": {
"X": 87.57141,
"Y": -14,
"Z": 52.384155
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1050737,
"Position": {
"X": 87.57141,
"Y": -14,
"Z": 52.384155
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,126 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051038,
"Position": {
"X": 153.94824,
"Y": -17.9645,
"Z": 95.384155
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051025,
"Position": {
"X": 153.94824,
"Y": -17.9645,
"Z": 95.384155
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051026,
"Position": {
"X": 109.910645,
"Y": -14,
"Z": 39.505493
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 1051027,
"Position": {
"X": 12.527649,
"Y": -10.00001,
"Z": 35.599243
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1051028,
"Position": {
"X": -69.77954,
"Y": -10.00001,
"Z": 119.79846
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051025,
"Position": {
"X": -124.85585,
"Y": -5.0000095,
"Z": 130.06908
},
"StopDistance": 0.25,
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051029,
"Position": {
"X": -249.77496,
"Y": -9.2517585E-06,
"Z": 138.01782
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,51 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051030,
"Position": {
"X": -227.64941,
"Y": 40.075134,
"Z": -25.436829
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051030,
"Position": {
"X": -227.64941,
"Y": 40.075134,
"Z": -25.436829
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051031,
"Position": {
"X": -56.6568,
"Y": 52.057556,
"Z": 20.767456
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,91 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051032,
"Position": {
"X": -259.05243,
"Y": 2.127327E-05,
"Z": 116.13635
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051033,
"Position": {
"X": -282.49036,
"Y": 14.508167,
"Z": 178.72888
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 2,
"Steps": [
{
"Position": {
"X": -290.98425,
"Y": -9.417534E-06,
"Z": 179.37634
},
"TerritoryId": 1185,
"InteractionType": "WalkTo",
"DisableNavmesh": true
},
{
"DataId": 2014234,
"Position": {
"X": -272.84656,
"Y": -0.015319824,
"Z": 191.39392
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051033,
"Position": {
"X": -282.49036,
"Y": 14.508167,
"Z": 178.72888
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051032,
"Position": {
"X": -259.05243,
"Y": 2.127327E-05,
"Z": 116.13635
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,85 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051034,
"Position": {
"X": 15.457336,
"Y": -10.00001,
"Z": 64.43884
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051036,
"Position": {
"X": -134.72198,
"Y": 45.35523,
"Z": 52.20105
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Tuliyollal] Aetheryte Plaza",
"[Tuliyollal] The Resplendent Quarter"
],
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1051035,
"Position": {
"X": -104.38696,
"Y": 54.09999,
"Z": 19.33313
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051034,
"Position": {
"X": 15.457336,
"Y": -10.00001,
"Z": 64.43884
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] The Resplendent Quarter",
"[Tuliyollal] Aetheryte Plaza"
]
}
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048509,
"Position": {
"X": 181.44495,
"Y": 40.99999,
"Z": -66.63617
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1048431,
"Position": {
"X": 88.48706,
"Y": -14,
"Z": 56.687256
},
"StopDistance": 4,
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1051037,
"Position": {
"X": -35.29419,
"Y": -17.97287,
"Z": 169.02417
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048509,
"Position": {
"X": 181.44495,
"Y": 40.99999,
"Z": -66.63617
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -0,0 +1,66 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051039,
"Position": {
"X": 17.593567,
"Y": -17.942173,
"Z": 153.06323
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZA009_05025_Q1_000_000",
"Answer": "TEXT_KINGZA009_05025_A1_000_001"
}
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051040,
"Position": {
"X": -169.93976,
"Y": 59.999985,
"Z": -54.703613
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Tuliyollal] Bayside Bevy Marketplace",
"[Tuliyollal] The Resplendent Quarter"
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051039,
"Position": {
"X": 17.593567,
"Y": -17.942173,
"Z": 153.06323
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] The Resplendent Quarter",
"[Tuliyollal] Bayside Bevy Marketplace"
]
}
]
}
]
}

View File

@ -0,0 +1,94 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051042,
"Position": {
"X": 100.38904,
"Y": 47,
"Z": -230.27393
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014235,
"Position": {
"X": -80.094604,
"Y": -19.333252,
"Z": 179.49182
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Tuliyollal] Brightploom Post",
"[Tuliyollal] The For'ard Cabins"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2014236,
"Position": {
"X": -154.34503,
"Y": -14.084106,
"Z": 548.24133
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 2014237,
"Position": {
"X": -124.77307,
"Y": -5.0202637,
"Z": 113.11511
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"AetheryteShortcut": "Tuliyollal",
"AethernetShortcut": [
"[Tuliyollal] Aetheryte Plaza",
"[Tuliyollal] The For'ard Cabins"
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051042,
"Position": {
"X": 100.38904,
"Y": 47,
"Z": -230.27393
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] The For'ard Cabins",
"[Tuliyollal] Brightploom Post"
]
}
]
}
]
}

View File

@ -0,0 +1,87 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051043,
"Position": {
"X": -276.99707,
"Y": 1.9748375E-05,
"Z": 71.97681
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051044,
"Position": {
"X": -307.6372,
"Y": 3,
"Z": -50.8584
},
"TerritoryId": 1185,
"InteractionType": "Emote",
"Emote": "soothe",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 1051045,
"Position": {
"X": -49.42401,
"Y": -2.7939677E-09,
"Z": 16.647583
},
"TerritoryId": 1185,
"InteractionType": "Emote",
"Emote": "soothe",
"AethernetShortcut": [
"[Tuliyollal] Dirigible Landing",
"[Tuliyollal] Aetheryte Plaza"
],
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051043,
"Position": {
"X": -276.99707,
"Y": 1.9748375E-05,
"Z": 71.97681
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] Aetheryte Plaza",
"[Tuliyollal] The Resplendent Quarter"
]
}
]
}
]
}

View File

@ -0,0 +1,140 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051046,
"Position": {
"X": 69.38269,
"Y": -14,
"Z": 91.081055
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 2014238,
"Position": {
"X": 75.12012,
"Y": -17.990417,
"Z": 154.52808
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
},
{
"DataId": 2014239,
"Position": {
"X": 64.530396,
"Y": -17.990417,
"Z": 189.56274
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 2014240,
"Position": {
"X": 131.91418,
"Y": -17.990417,
"Z": 112.2301
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051046,
"Position": {
"X": 69.38269,
"Y": -14,
"Z": 91.081055
},
"TerritoryId": 1185,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051048,
"Position": {
"X": -227.95453,
"Y": 40.04104,
"Z": -42.648987
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Tuliyollal] Aetheryte Plaza",
"[Tuliyollal] The Resplendent Quarter"
],
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZA012_05028_Q1_000_000",
"Answer": "TEXT_KINGZA012_05028_A1_000_002"
}
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051046,
"Position": {
"X": 69.38269,
"Y": -14,
"Z": 91.081055
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] The Resplendent Quarter",
"[Tuliyollal] Aetheryte Plaza"
]
}
]
}
]
}

View File

@ -0,0 +1,119 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1048537,
"Position": {
"X": -61.753296,
"Y": 99.99999,
"Z": -188.98297
},
"TerritoryId": 1185,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1046521,
"Position": {
"X": -46.616333,
"Y": -17.97287,
"Z": 180.3158
},
"StopDistance": 5,
"TerritoryId": 1185,
"InteractionType": "Interact",
"AethernetShortcut": [
"[Tuliyollal] Vollok Shoonsa",
"[Tuliyollal] Bayside Bevy Marketplace"
]
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1048547,
"Position": {
"X": -39.35309,
"Y": -17.972866,
"Z": 198.38245
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 1048418,
"Position": {
"X": -54.154297,
"Y": -17.972868,
"Z": 198.44348
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
]
},
{
"DataId": 1048546,
"Position": {
"X": -60.83777,
"Y": -17.972868,
"Z": 197.07019
},
"TerritoryId": 1185,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1048537,
"Position": {
"X": -61.753296,
"Y": 99.99999,
"Z": -188.98297
},
"TerritoryId": 1185,
"InteractionType": "CompleteQuest",
"AethernetShortcut": [
"[Tuliyollal] Bayside Bevy Marketplace",
"[Tuliyollal] Vollok Shoonsa"
]
}
]
}
]
}

View File

@ -0,0 +1,86 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051195,
"Position": {
"X": 383.29138,
"Y": -154.50243,
"Z": -420.49292
},
"TerritoryId": 1187,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051199,
"Position": {
"X": 442.95398,
"Y": -116.815155,
"Z": -178.63745
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 1051200,
"Position": {
"X": 563.83606,
"Y": -49.515564,
"Z": 49.54602
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1048773,
"Position": {
"X": 576.2875,
"Y": -49.16388,
"Z": 37.00305
},
"TerritoryId": 1187,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051195,
"Position": {
"X": 383.29138,
"Y": -154.50243,
"Z": -420.49292
},
"TerritoryId": 1187,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Urqopacha - Wachunpelo",
"Fly": true,
"NextQuestId": 5053
}
]
}
]
}

View File

@ -0,0 +1,132 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051195,
"Position": {
"X": 383.29138,
"Y": -154.50243,
"Z": -420.49292
},
"TerritoryId": 1187,
"InteractionType": "AcceptQuest",
"DialogueChoices": [
{
"Type": "List",
"Prompt": "TEXT_KINGZB203_05053_Q1_000_000",
"Answer": "TEXT_KINGZB203_05053_A1_000_003"
}
]
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1048629,
"Position": {
"X": 353.35315,
"Y": -158.06824,
"Z": -423.72784
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
},
{
"DataId": 1051202,
"Position": {
"X": 338.55188,
"Y": -160.20284,
"Z": -450.9804
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
],
"Fly": true
},
{
"DataId": 1051203,
"Position": {
"X": 283.22266,
"Y": -168.30641,
"Z": -452.26215
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
],
"Fly": true
},
{
"DataId": 1048660,
"Position": {
"X": 246.3263,
"Y": -168.27,
"Z": -487.20532
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
16
],
"Fly": true
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051195,
"Position": {
"X": 383.29138,
"Y": -154.50243,
"Z": -420.49292
},
"TerritoryId": 1187,
"InteractionType": "CompleteQuest",
"Fly": true,
"NextQuestId": 5054,
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_KINGZB203_05053_Q2_000_000",
"Yes": true
}
]
}
]
}
]
}

View File

@ -0,0 +1,92 @@
{
"$schema": "https://carvel.li/questionable/quest-1.0",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1051195,
"Position": {
"X": 383.29138,
"Y": -154.50243,
"Z": -420.49292
},
"TerritoryId": 1187,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1051200,
"Position": {
"X": 563.83606,
"Y": -49.515564,
"Z": 49.54602
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"Fly": true
}
]
},
{
"Sequence": 2,
"Steps": [
{
"DataId": 2014271,
"Position": {
"X": 560.9064,
"Y": -54.459473,
"Z": 22.781677
},
"TerritoryId": 1187,
"InteractionType": "Interact",
"Fly": true,
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_KINGZB204_05054_Q1_000_000",
"Yes": true
}
]
}
]
},
{
"Sequence": 3,
"Steps": [
{
"DataId": 1051204,
"Position": {
"X": 562.7374,
"Y": -54.457336,
"Z": 21.530457
},
"StopDistance": 5,
"TerritoryId": 1187,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1051195,
"Position": {
"X": 383.29138,
"Y": -154.50243,
"Z": -420.49292
},
"TerritoryId": 1187,
"InteractionType": "CompleteQuest",
"AetheryteShortcut": "Urqopacha - Wachunpelo"
}
]
}
]
}

View File

@ -651,7 +651,8 @@
"cheer", "cheer",
"happy", "happy",
"poke", "poke",
"flex" "flex",
"soothe"
] ]
} }
} }
@ -948,7 +949,7 @@
}, },
"then": { "then": {
"properties": { "properties": {
"QuestId": { "PickUpQuestId": {
"type": [ "type": [
"null", "null",
"number" "number"
@ -957,6 +958,33 @@
} }
} }
} }
},
{
"if": {
"properties": {
"InteractionType": {
"const": "CompleteQuest"
}
}
},
"then": {
"properties": {
"TurnInQuestId": {
"type": [
"null",
"number"
],
"description": "Determines the quest which should be turned in. If empty/null, turns in the quest corresponding to the file name."
},
"NextQuestId": {
"type": [
"null",
"number"
],
"description": "For quest chains (e.g. DT healer role quests) Which quest to do next, given that you meet the required level."
}
}
}
} }
] ]
} }

View File

@ -18,5 +18,6 @@ public sealed class EmoteConverter() : EnumConverter<EEmote>(Values)
{ EEmote.Happy, "happy" }, { EEmote.Happy, "happy" },
{ EEmote.Poke, "poke" }, { EEmote.Poke, "poke" },
{ EEmote.Flex, "flex" }, { EEmote.Flex, "flex" },
{ EEmote.Soothe, "soothe" },
}; };
} }

View File

@ -20,4 +20,5 @@ public enum EEmote
Happy = 48, Happy = 48,
Poke = 28, Poke = 28,
Flex = 139, Flex = 139,
Soothe = 35,
} }

View File

@ -2,7 +2,7 @@
namespace Questionable.Model.V1; namespace Questionable.Model.V1;
public sealed class QuestData public sealed class QuestRoot
{ {
public string Author { get; set; } = null!; public string Author { get; set; } = null!;
public List<string> Contributors { get; set; } = new(); public List<string> Contributors { get; set; } = new();

View File

@ -53,7 +53,12 @@ public sealed class QuestStep
public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>(); public IList<short?> CompletionQuestVariablesFlags { get; set; } = new List<short?>();
public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>(); public IList<DialogueChoice> DialogueChoices { get; set; } = new List<DialogueChoice>();
public IList<uint> PointMenuChoices { get; set; } = new List<uint>(); public IList<uint> PointMenuChoices { get; set; } = new List<uint>();
public ushort? QuestId { get; set; }
// TODO: Not implemented
public ushort? PickupQuestId { get; set; }
public ushort? TurnInQuestId { get; set; }
public ushort? NextQuestId { get; set; }
[JsonConstructor] [JsonConstructor]
public QuestStep() public QuestStep()

View File

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.Command;
using Dalamud.Plugin.Services;
using Questionable.Data;
using Questionable.Model;
using Questionable.Windows;
namespace Questionable.Controller;
internal sealed class CommandHandler : IDisposable
{
private readonly ICommandManager _commandManager;
private readonly IChatGui _chatGui;
private readonly QuestController _questController;
private readonly MovementController _movementController;
private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
private readonly ConfigWindow _configWindow;
private readonly DebugOverlay _debugOverlay;
private readonly QuestWindow _questWindow;
public CommandHandler(ICommandManager commandManager, IChatGui chatGui, QuestController questController,
MovementController movementController, QuestRegistry questRegistry, QuestData questData,
ConfigWindow configWindow, DebugOverlay debugOverlay, QuestWindow questWindow)
{
_commandManager = commandManager;
_chatGui = chatGui;
_questController = questController;
_movementController = movementController;
_questRegistry = questRegistry;
_questData = questData;
_configWindow = configWindow;
_debugOverlay = debugOverlay;
_questWindow = questWindow;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
HelpMessage = "Opens the Questing window"
});
}
private void ProcessCommand(string command, string arguments)
{
string[] parts = arguments.Split(' ');
switch (parts[0])
{
case "c":
case "config":
_configWindow.Toggle();
break;
case "start":
_questController.ExecuteNextStep(true);
break;
case "stop":
_movementController.Stop();
_questController.Stop("Stop command");
break;
case "do":
ConfigureDebugOverlay(parts.Skip(1).ToArray());
break;
case "next":
SetNextQuest(parts.Skip(1).ToArray());
break;
case "sim":
SetSimulatedQuest(parts.Skip(1).ToArray());
break;
case "which":
_questData.ShowQuestsIssuedByTarget();
break;
default:
_questWindow.Toggle();
break;
}
}
private void ConfigureDebugOverlay(string[] arguments)
{
if (!_debugOverlay.DrawConditions())
{
_chatGui.PrintError("[Questionable] You don't have the debug overlay enabled.");
return;
}
if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
{
if (_questRegistry.IsKnownQuest(questId))
{
_debugOverlay.HighlightedQuest = questId;
_chatGui.Print($"[Questionable] Set highlighted quest to {questId}.");
}
else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
}
else
{
_debugOverlay.HighlightedQuest = null;
_chatGui.Print("[Questionable] Cleared highlighted quest.");
}
}
private void SetNextQuest(string[] arguments)
{
if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
{
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_questController.SetNextQuest(quest);
_chatGui.Print($"[Questionable] Set next quest to {questId}.");
}
else
{
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
}
}
else
{
_questController.SetNextQuest(null);
_chatGui.Print("[Questionable] Cleared next quest.");
}
}
private void SetSimulatedQuest(string[] arguments)
{
if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
{
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_questController.SimulateQuest(quest);
_chatGui.Print($"[Questionable] Simulating quest {questId}.");
}
else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
}
else
{
_questController.SimulateQuest(null);
_chatGui.Print("[Questionable] Cleared simulated quest.");
}
}
public void Dispose()
{
_commandManager.RemoveHandler("/qst");
}
}

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
@ -13,6 +12,7 @@ using LLib;
using LLib.GameUI; using LLib.GameUI;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Model.V1; using Questionable.Model.V1;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
@ -25,19 +25,23 @@ internal sealed class GameUiController : IDisposable
private readonly IDataManager _dataManager; private readonly IDataManager _dataManager;
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly ILogger<GameUiController> _logger; private readonly ILogger<GameUiController> _logger;
private readonly Regex _returnRegex; private readonly Regex _returnRegex;
public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions, public GameUiController(IAddonLifecycle addonLifecycle, IDataManager dataManager, GameFunctions gameFunctions,
QuestController questController, IGameGui gameGui, ITargetManager targetManager, IPluginLog pluginLog, QuestController questController, QuestRegistry questRegistry, QuestData questData, IGameGui gameGui,
ILogger<GameUiController> logger) ITargetManager targetManager, IPluginLog pluginLog, ILogger<GameUiController> logger)
{ {
_addonLifecycle = addonLifecycle; _addonLifecycle = addonLifecycle;
_dataManager = dataManager; _dataManager = dataManager;
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_questController = questController; _questController = questController;
_questRegistry = questRegistry;
_questData = questData;
_gameGui = gameGui; _gameGui = gameGui;
_targetManager = targetManager; _targetManager = targetManager;
_logger = logger; _logger = logger;
@ -162,7 +166,7 @@ internal sealed class GameUiController : IDisposable
if (currentQuest != null && actualPrompt == null) if (currentQuest != null && actualPrompt == null)
{ {
// it is possible for this to be a quest selection // it is possible for this to be a quest selection
string questName = currentQuest.Quest.Name; string questName = currentQuest.Quest.Info.Name;
int questSelection = answers.FindIndex(x => GameStringEquals(questName, x)); int questSelection = answers.FindIndex(x => GameStringEquals(questName, x));
if (questSelection >= 0) if (questSelection >= 0)
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection); addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
@ -172,33 +176,55 @@ internal sealed class GameUiController : IDisposable
private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps) private int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
{ {
List<DialogueChoiceInfo> dialogueChoices = [];
var currentQuest = _questController.CurrentQuest; var currentQuest = _questController.CurrentQuest;
if (currentQuest == null) if (currentQuest != null)
{ {
_logger.LogInformation("Ignoring list choice, no active quest"); var quest = currentQuest.Quest;
return null; if (checkAllSteps)
} {
var sequence = quest.FindSequence(currentQuest.Sequence);
var quest = currentQuest.Quest; var choices = sequence?.Steps.SelectMany(x => x.DialogueChoices);
IList<DialogueChoice> dialogueChoices; if (choices != null)
if (checkAllSteps) dialogueChoices.AddRange(choices.Select(x => new DialogueChoiceInfo(quest, x)));
{ }
var sequence = quest.FindSequence(currentQuest.Sequence); else
dialogueChoices = sequence?.Steps.SelectMany(x => x.DialogueChoices).ToList() ?? new List<DialogueChoice>(); {
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null)
_logger.LogInformation("Ignoring current quest dialogue choices, no active step");
else
dialogueChoices.AddRange(step.DialogueChoices.Select(x => new DialogueChoiceInfo(quest, x)));
}
} }
else else
{ _logger.LogInformation("Ignoring current quest dialogue choices, no active quest");
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step == null)
{
_logger.LogInformation("Ignoring list choice, no active step");
return null;
}
dialogueChoices = step.DialogueChoices; // add all quests that start with the targeted npc
var target = _targetManager.Target;
if (target != null)
{
foreach (var questInfo in _questData.GetAllByIssuerDataId(target.DataId))
{
if (_gameFunctions.IsReadyToAcceptQuest(questInfo.QuestId) &&
_questRegistry.TryGetQuest(questInfo.QuestId, out Quest? knownQuest))
{
var questChoices = knownQuest.FindSequence(0)?.Steps
.SelectMany(x => x.DialogueChoices)
.ToList();
if (questChoices != null && questChoices.Count > 0)
{
_logger.LogInformation("Adding {Count} dialogue choices from not accepted quest {QuestName}", questChoices.Count, questInfo.Name);
dialogueChoices.AddRange(questChoices.Select(x => new DialogueChoiceInfo(knownQuest, x)));
}
}
}
} }
foreach (var dialogueChoice in dialogueChoices) if (dialogueChoices.Count == 0)
return null;
foreach (var (quest, dialogueChoice) in dialogueChoices)
{ {
if (dialogueChoice.Type != EDialogChoiceType.List) if (dialogueChoice.Type != EDialogChoiceType.List)
continue; continue;
@ -433,13 +459,7 @@ internal sealed class GameUiController : IDisposable
}; };
addonPointMenu->FireCallback(2, selectChoice); addonPointMenu->FireCallback(2, selectChoice);
_questController.CurrentQuest = currentQuest with currentQuest.IncreasePointMenuCounter();
{
StepProgress = currentQuest.StepProgress with
{
PointMenuCounter = counter + 1,
}
};
} }
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args) private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
@ -519,4 +539,6 @@ internal sealed class GameUiController : IDisposable
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup); _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectString", SelectStringPostSetup);
} }
private sealed record DialogueChoiceInfo(Quest Quest, DialogueChoice DialogueChoice);
} }

View File

@ -25,6 +25,10 @@ internal sealed class QuestController
private readonly object _lock = new(); private readonly object _lock = new();
private QuestProgress? _startedQuest;
private QuestProgress? _nextQuest;
private QuestProgress? _simulatedQuest;
private readonly Queue<ITask> _taskQueue = new(); private readonly Queue<ITask> _taskQueue = new();
private ITask? _currentTask; private ITask? _currentTask;
private bool _automatic; private bool _automatic;
@ -51,9 +55,22 @@ internal sealed class QuestController
_taskFactories = taskFactories.ToList().AsReadOnly(); _taskFactories = taskFactories.ToList().AsReadOnly();
} }
public QuestProgress? CurrentQuest
{
get
{
if (_simulatedQuest != null)
return _simulatedQuest;
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.QuestId))
return _nextQuest;
else
return _startedQuest;
}
}
public QuestProgress? SimulatedQuest => _simulatedQuest;
public QuestProgress? NextQuest => _nextQuest;
public QuestProgress? CurrentQuest { get; set; }
public SimulationProgress? SimulatedQuest { get; set; }
public string? DebugState { get; private set; } public string? DebugState { get; private set; }
public string? Comment { get; private set; } public string? Comment { get; private set; }
@ -61,7 +78,10 @@ internal sealed class QuestController
{ {
lock (_lock) lock (_lock)
{ {
CurrentQuest = null; _startedQuest = null;
_nextQuest = null;
_simulatedQuest = null;
DebugState = null; DebugState = null;
_questRegistry.Reload(); _questRegistry.Reload();
@ -81,7 +101,7 @@ internal sealed class QuestController
} }
} }
if (CurrentQuest != null && CurrentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType)) if (CurrentQuest != null && CurrentQuest.Quest.Root.TerritoryBlacklist.Contains(_clientState.TerritoryType))
return; return;
// not verified to work // not verified to work
@ -92,10 +112,7 @@ internal sealed class QuestController
lock (_lock) lock (_lock)
{ {
_logger.LogWarning("Quest accept apparently didn't work out, resetting progress"); _logger.LogWarning("Quest accept apparently didn't work out, resetting progress");
CurrentQuest = CurrentQuest with CurrentQuest.SetStep(0);
{
Step = 0
};
} }
ExecuteNextStep(true); ExecuteNextStep(true);
@ -111,50 +128,64 @@ internal sealed class QuestController
{ {
DebugState = null; DebugState = null;
ushort currentQuestId; byte currentSequence = 0;
byte currentSequence; if (_simulatedQuest != null)
if (SimulatedQuest != null)
{ {
currentQuestId = SimulatedQuest.Quest.QuestId; currentSequence = _simulatedQuest.Sequence;
currentSequence = SimulatedQuest.Sequence;
} }
else else if (_nextQuest != null)
(currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
if (currentQuestId == 0)
{ {
if (CurrentQuest != null) // if the quest is accepted, we no longer track it
if (_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.QuestId))
{ {
_logger.LogInformation("No current quest, resetting data"); _nextQuest = null;
CurrentQuest = null; currentSequence = 0;
Stop("Resetting current quest"); }
else
{
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
} }
} }
else if (CurrentQuest == null || CurrentQuest.Quest.QuestId != currentQuestId)
if (_simulatedQuest == null && _nextQuest == null)
{ {
if (_questRegistry.TryGetQuest(currentQuestId, out var quest)) (ushort currentQuestId, currentSequence) = _gameFunctions.GetCurrentQuest();
if (currentQuestId == 0)
{ {
_logger.LogInformation("New quest: {QuestName}", quest.Name); if (_startedQuest != null)
CurrentQuest = new QuestProgress(quest, currentSequence, 0); {
_logger.LogInformation("No current quest, resetting data");
bool continueAutomatically = _configuration.General.AutoAcceptNextQuest; _startedQuest = null;
Stop("Resetting current quest");
if (_clientState.LocalPlayer?.Level < quest.Level) }
continueAutomatically = false;
Stop("Different Quest", continueAutomatically);
} }
else if (CurrentQuest != null) else if (_startedQuest == null || _startedQuest.Quest.QuestId != currentQuestId)
{ {
_logger.LogInformation("No active quest anymore? Not sure what happened..."); if (_questRegistry.TryGetQuest(currentQuestId, out var quest))
CurrentQuest = null; {
Stop("No active Quest"); _logger.LogInformation("New quest: {QuestName}", quest.Info.Name);
} _startedQuest = new QuestProgress(quest, currentSequence);
return; bool continueAutomatically = _configuration.General.AutoAcceptNextQuest;
if (_clientState.LocalPlayer?.Level < quest.Info.Level)
continueAutomatically = false;
Stop("Different Quest", continueAutomatically);
}
else if (_startedQuest != null)
{
_logger.LogInformation("No active quest anymore? Not sure what happened...");
_startedQuest = null;
Stop("No active Quest");
}
return;
}
} }
if (CurrentQuest == null) var questToRun = CurrentQuest;
if (questToRun == null)
{ {
DebugState = "No quest active"; DebugState = "No quest active";
Comment = null; Comment = null;
@ -179,14 +210,14 @@ internal sealed class QuestController
return; return;
} }
if (CurrentQuest.Sequence != currentSequence) if (questToRun.Sequence != currentSequence)
{ {
CurrentQuest = CurrentQuest with { Sequence = currentSequence, Step = 0 }; questToRun.SetSequence(currentSequence);
Stop("New sequence", continueIfAutomatic: true); Stop("New sequence", continueIfAutomatic: true);
} }
var q = CurrentQuest.Quest; var q = questToRun.Quest;
var sequence = q.FindSequence(CurrentQuest.Sequence); var sequence = q.FindSequence(questToRun.Sequence);
if (sequence == null) if (sequence == null)
{ {
DebugState = "Sequence not found"; DebugState = "Sequence not found";
@ -195,7 +226,7 @@ internal sealed class QuestController
return; return;
} }
if (CurrentQuest.Step == 255) if (questToRun.Step == 255)
{ {
DebugState = "Step completed"; DebugState = "Step completed";
Comment = null; Comment = null;
@ -204,7 +235,7 @@ internal sealed class QuestController
return; return;
} }
if (CurrentQuest.Step >= sequence.Steps.Count) if (questToRun.Step >= sequence.Steps.Count)
{ {
DebugState = "Step not found"; DebugState = "Step not found";
Comment = null; Comment = null;
@ -212,9 +243,9 @@ internal sealed class QuestController
return; return;
} }
var step = sequence.Steps[CurrentQuest.Step]; var step = sequence.Steps[questToRun.Step];
DebugState = null; DebugState = null;
Comment = step.Comment ?? sequence.Comment ?? q.Data.Comment; Comment = step.Comment ?? sequence.Comment ?? q.Root.Comment;
} }
} }
@ -262,21 +293,9 @@ internal sealed class QuestController
_logger.LogInformation("Increasing step count from {CurrentValue}", CurrentQuest.Step); _logger.LogInformation("Increasing step count from {CurrentValue}", CurrentQuest.Step);
if (CurrentQuest.Step + 1 < seq.Steps.Count) if (CurrentQuest.Step + 1 < seq.Steps.Count)
{ CurrentQuest.SetStep(CurrentQuest.Step + 1);
CurrentQuest = CurrentQuest with
{
Step = CurrentQuest.Step + 1,
StepProgress = new(DateTime.Now),
};
}
else else
{ CurrentQuest.SetStep(255);
CurrentQuest = CurrentQuest with
{
Step = 255,
StepProgress = new(DateTime.Now),
};
}
} }
if (shouldContinue && _automatic) if (shouldContinue && _automatic)
@ -310,14 +329,26 @@ internal sealed class QuestController
_logger.LogInformation("Stopping automatic questing"); _logger.LogInformation("Stopping automatic questing");
_automatic = false; _automatic = false;
} }
_nextQuest = null;
} }
public void SimulateQuest(Quest? quest) public void SimulateQuest(Quest? quest)
{ {
_logger.LogInformation("SimulateQuest: {QuestId}", quest?.QuestId);
if (quest != null) if (quest != null)
SimulatedQuest = new SimulationProgress(quest, 0); _simulatedQuest = new QuestProgress(quest);
else else
SimulatedQuest = null; _simulatedQuest = null;
}
public void SetNextQuest(Quest? quest)
{
_logger.LogInformation("NextQuest: {QuestId}", quest?.QuestId);
if (quest != null)
_nextQuest = new QuestProgress(quest);
else
_nextQuest = null;
} }
private void UpdateCurrentTask() private void UpdateCurrentTask()
@ -470,20 +501,40 @@ internal sealed class QuestController
public bool IsRunning => _currentTask != null || _taskQueue.Count > 0; public bool IsRunning => _currentTask != null || _taskQueue.Count > 0;
public sealed record QuestProgress( public sealed class QuestProgress
Quest Quest,
byte Sequence,
int Step,
StepProgress StepProgress)
{ {
public QuestProgress(Quest quest, byte sequence, int step) public Quest Quest { get; }
: this(quest, sequence, step, new StepProgress(DateTime.Now)) public byte Sequence { get; private set; }
public int Step { get; private set; }
public StepProgress StepProgress { get; private set; } = new(DateTime.Now);
public QuestProgress(Quest quest, byte sequence = 0)
{ {
Quest = quest;
SetSequence(sequence);
}
public void SetSequence(byte sequence)
{
Sequence = sequence;
SetStep(0);
}
public void SetStep(int step)
{
Step = step;
StepProgress = new StepProgress(DateTime.Now);
}
public void IncreasePointMenuCounter()
{
StepProgress = StepProgress with
{
PointMenuCounter = StepProgress.PointMenuCounter + 1,
};
} }
} }
public sealed record SimulationProgress(Quest Quest, byte Sequence);
public void Skip(ushort questQuestId, byte currentQuestSequence) public void Skip(ushort questQuestId, byte currentQuestSequence)
{ {
lock (_lock) lock (_lock)

View File

@ -8,6 +8,7 @@ using System.Text.Json;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
@ -16,16 +17,16 @@ namespace Questionable.Controller;
internal sealed class QuestRegistry internal sealed class QuestRegistry
{ {
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly IDataManager _dataManager; private readonly QuestData _questData;
private readonly ILogger<QuestRegistry> _logger; private readonly ILogger<QuestRegistry> _logger;
private readonly Dictionary<ushort, Quest> _quests = new(); private readonly Dictionary<ushort, Quest> _quests = new();
public QuestRegistry(IDalamudPluginInterface pluginInterface, IDataManager dataManager, public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
ILogger<QuestRegistry> logger) ILogger<QuestRegistry> logger)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_dataManager = dataManager; _questData = questData;
_logger = logger; _logger = logger;
} }
@ -47,7 +48,7 @@ internal sealed class QuestRegistry
_quests[questId] = quest; _quests[questId] = quest;
} }
#else #else
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation?.Directory?.Parent?.Parent; DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
if (solutionDirectory != null) if (solutionDirectory != null)
{ {
DirectoryInfo pathProjectDirectory = DirectoryInfo pathProjectDirectory =
@ -64,22 +65,14 @@ internal sealed class QuestRegistry
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests"))); LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
foreach (var (questId, quest) in _quests)
{
var questData =
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Quest>()!.GetRow((uint)questId + 0x10000);
if (questData == null)
continue;
quest.Name = questData.Name.ToString();
quest.Level = questData.ClassJobLevel0;
#if !RELEASE #if !RELEASE
int missingSteps = quest.Data.QuestSequence.Where(x => x.Sequence < 255).Max(x => x.Sequence) - quest.Data.QuestSequence.Count(x => x.Sequence < 255) + 1; foreach (var quest in _quests.Values)
{
int missingSteps = quest.Root.QuestSequence.Where(x => x.Sequence < 255).Max(x => x.Sequence) - quest.Root.QuestSequence.Count(x => x.Sequence < 255) + 1;
if (missingSteps != 0) if (missingSteps != 0)
_logger.LogWarning("Quest has missing steps: {QuestId} / {QuestName} → {Count}", quest.QuestId, quest.Name, missingSteps); _logger.LogWarning("Quest has missing steps: {QuestId} / {QuestName} → {Count}", quest.QuestId, quest.Info.Name, missingSteps);
#endif
} }
#endif
_logger.LogInformation("Loaded {Count} quests", _quests.Count); _logger.LogInformation("Loaded {Count} quests", _quests.Count);
} }
@ -88,12 +81,12 @@ internal sealed class QuestRegistry
private void LoadQuestFromStream(string fileName, Stream stream) private void LoadQuestFromStream(string fileName, Stream stream)
{ {
_logger.LogTrace("Loading quest from '{FileName}'", fileName); _logger.LogTrace("Loading quest from '{FileName}'", fileName);
var (questId, name) = ExtractQuestDataFromName(fileName); var questId = ExtractQuestIdFromName(fileName);
Quest quest = new Quest Quest quest = new Quest
{ {
QuestId = questId, QuestId = questId,
Name = name, Root = JsonSerializer.Deserialize<QuestRoot>(stream)!,
Data = JsonSerializer.Deserialize<QuestData>(stream)!, Info = _questData.GetQuestInfo(questId),
}; };
_quests[questId] = quest; _quests[questId] = quest;
} }
@ -124,13 +117,13 @@ internal sealed class QuestRegistry
LoadFromDirectory(childDirectory); LoadFromDirectory(childDirectory);
} }
private static (ushort QuestId, string Name) ExtractQuestDataFromName(string resourceName) private static ushort ExtractQuestIdFromName(string resourceName)
{ {
string name = resourceName.Substring(0, resourceName.Length - ".json".Length); string name = resourceName.Substring(0, resourceName.Length - ".json".Length);
name = name.Substring(name.LastIndexOf('.') + 1); name = name.Substring(name.LastIndexOf('.') + 1);
string[] parts = name.Split('_', 2); string[] parts = name.Split('_', 2);
return (ushort.Parse(parts[0], CultureInfo.InvariantCulture), parts[1]); return ushort.Parse(parts[0], CultureInfo.InvariantCulture);
} }
public bool IsKnownQuest(ushort questId) => _quests.ContainsKey(questId); public bool IsKnownQuest(ushort questId) => _quests.ContainsKey(questId);

View File

@ -107,14 +107,14 @@ internal static class WaitAtEnd
case EInteractionType.AcceptQuest: case EInteractionType.AcceptQuest:
return return
[ [
serviceProvider.GetRequiredService<WaitQuestAccepted>().With(step.QuestId ?? quest.QuestId), serviceProvider.GetRequiredService<WaitQuestAccepted>().With(step.PickupQuestId ?? quest.QuestId),
serviceProvider.GetRequiredService<WaitDelay>() serviceProvider.GetRequiredService<WaitDelay>()
]; ];
case EInteractionType.CompleteQuest: case EInteractionType.CompleteQuest:
return return
[ [
serviceProvider.GetRequiredService<WaitQuestCompleted>().With(step.QuestId ?? quest.QuestId), serviceProvider.GetRequiredService<WaitQuestCompleted>().With(step.TurnInQuestId ?? quest.QuestId),
serviceProvider.GetRequiredService<WaitDelay>() serviceProvider.GetRequiredService<WaitDelay>()
]; ];

View File

@ -0,0 +1,54 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.V1;
namespace Questionable.Controller.Steps.BaseTasks;
internal static class NextQuest
{
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
{
public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.CompleteQuest)
return null;
if (step.NextQuestId == null)
return null;
return serviceProvider.GetRequiredService<SetQuest>()
.With(step.NextQuestId.Value);
}
}
internal sealed class SetQuest(QuestRegistry questRegistry, QuestController questController, ILogger<SetQuest> logger) : ITask
{
public ushort NextQuestId { get; set; }
public ITask With(ushort nextQuestId)
{
NextQuestId = nextQuestId;
return this;
}
public bool Start()
{
if (questRegistry.TryGetQuest(NextQuestId, out Quest? quest))
{
logger.LogInformation("Setting next quest to {QuestId}: '{QuestName}'", NextQuestId, quest.Info.Name);
questController.SetNextQuest(quest);
}
else
{
logger.LogInformation("Next quest with id {QuestId} not found", NextQuestId);
questController.SetNextQuest(null);
}
return true;
}
public ETaskResult Update() => ETaskResult.TaskComplete;
}
}

View File

@ -1,12 +1,8 @@
using System; using System;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Data;
using Questionable.Model;
using Questionable.Windows; using Questionable.Windows;
namespace Questionable; namespace Questionable;
@ -15,35 +11,33 @@ internal sealed class DalamudInitializer : IDisposable
{ {
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly ICommandManager _commandManager;
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly NavigationShortcutController _navigationShortcutController; private readonly NavigationShortcutController _navigationShortcutController;
private readonly IChatGui _chatGui;
private readonly WindowSystem _windowSystem; private readonly WindowSystem _windowSystem;
private readonly QuestWindow _questWindow; private readonly QuestWindow _questWindow;
private readonly DebugOverlay _debugOverlay;
private readonly ConfigWindow _configWindow; private readonly ConfigWindow _configWindow;
private readonly QuestRegistry _questRegistry;
public DalamudInitializer(IDalamudPluginInterface pluginInterface, IFramework framework, public DalamudInitializer(
ICommandManager commandManager, QuestController questController, MovementController movementController, IDalamudPluginInterface pluginInterface,
GameUiController gameUiController, NavigationShortcutController navigationShortcutController, IChatGui chatGui, IFramework framework,
WindowSystem windowSystem, QuestWindow questWindow, DebugOverlay debugOverlay, ConfigWindow configWindow, QuestController questController,
QuestRegistry questRegistry) MovementController movementController,
GameUiController gameUiController,
NavigationShortcutController navigationShortcutController,
WindowSystem windowSystem,
QuestWindow questWindow,
DebugOverlay debugOverlay,
ConfigWindow configWindow)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_framework = framework; _framework = framework;
_commandManager = commandManager;
_questController = questController; _questController = questController;
_movementController = movementController; _movementController = movementController;
_navigationShortcutController = navigationShortcutController; _navigationShortcutController = navigationShortcutController;
_chatGui = chatGui;
_windowSystem = windowSystem; _windowSystem = windowSystem;
_questWindow = questWindow; _questWindow = questWindow;
_debugOverlay = debugOverlay;
_configWindow = configWindow; _configWindow = configWindow;
_questRegistry = questRegistry;
_windowSystem.AddWindow(questWindow); _windowSystem.AddWindow(questWindow);
_windowSystem.AddWindow(configWindow); _windowSystem.AddWindow(configWindow);
@ -53,11 +47,6 @@ internal sealed class DalamudInitializer : IDisposable
_pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle; _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;
_pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle; _pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;
_framework.Update += FrameworkUpdate; _framework.Update += FrameworkUpdate;
_commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
{
HelpMessage = "Opens the Questing window"
});
_framework.RunOnTick(gameUiController.HandleCurrentDialogueChoices, TimeSpan.FromMilliseconds(200)); _framework.RunOnTick(gameUiController.HandleCurrentDialogueChoices, TimeSpan.FromMilliseconds(200));
} }
@ -76,68 +65,10 @@ internal sealed class DalamudInitializer : IDisposable
} }
} }
private void ProcessCommand(string command, string arguments)
{
if (arguments is "c" or "config")
_configWindow.Toggle();
if (arguments is "start")
_questController.ExecuteNextStep(true);
else if (arguments is "stop")
{
_movementController.Stop();
_questController.Stop("Stop command");
}
else if (arguments.StartsWith("do", StringComparison.Ordinal))
{
if (!_debugOverlay.DrawConditions())
{
_chatGui.PrintError("[Questionable] You don't have the debug overlay enabled.");
return;
}
if (arguments.Length >= 4 && ushort.TryParse(arguments.AsSpan(3), out ushort questId))
{
if (_questRegistry.IsKnownQuest(questId))
{
_debugOverlay.HighlightedQuest = questId;
_chatGui.Print($"[Questionable] Set highlighted quest to {questId}.");
}
else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
}
else
{
_debugOverlay.HighlightedQuest = null;
_chatGui.Print("[Questionable] Cleared highlighted quest.");
}
}
else if (arguments.StartsWith("sim", StringComparison.InvariantCulture))
{
string[] parts = arguments.Split(' ');
if (parts.Length == 2 && ushort.TryParse(parts[1], out ushort questId))
{
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
{
_questController.SimulateQuest(quest);
_chatGui.Print($"[Questionable] Simulating quest {questId}.");
}
else
_chatGui.PrintError($"[Questionable] Unknown quest {questId}.");
}
else
{
_questController.SimulateQuest(null);
_chatGui.Print("[Questionable] Cleared simulated quest.");
}
}
else if (string.IsNullOrEmpty(arguments))
_questWindow.Toggle();
}
public void Dispose() public void Dispose()
{ {
_commandManager.RemoveHandler("/qst");
_framework.Update -= FrameworkUpdate; _framework.Update -= FrameworkUpdate;
_pluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle;
_pluginInterface.UiBuilder.OpenMainUi -= _questWindow.Toggle; _pluginInterface.UiBuilder.OpenMainUi -= _questWindow.Toggle;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin.Services;
using Questionable.Model;
using Quest = Lumina.Excel.GeneratedSheets.Quest;
namespace Questionable.Data;
internal sealed class QuestData
{
private readonly ITargetManager _targetManager;
private readonly IChatGui _chatGui;
private readonly ImmutableDictionary<ushort, QuestInfo> _quests;
public QuestData(IDataManager dataManager, ITargetManager targetManager, IChatGui chatGui)
{
_targetManager = targetManager;
_chatGui = chatGui;
_quests = dataManager.GetExcelSheet<Quest>()!
.Where(x => x.RowId > 0)
.Where(x => x.IssuerLocation.Row > 0)
.Select(x => new QuestInfo(x))
.ToImmutableDictionary(x => x.QuestId, x => x);
}
public QuestInfo GetQuestInfo(ushort questId)
{
return _quests[questId] ?? throw new ArgumentOutOfRangeException(nameof(questId));
}
public List<QuestInfo> GetAllByIssuerDataId(uint targetId)
{
return _quests.Values
.Where(x => x.IssuerDataId == targetId)
.ToList();
}
public void ShowQuestsIssuedByTarget()
{
var targetId = _targetManager.Target?.DataId;
if (targetId == null)
{
_chatGui.PrintError("[Questionable] No target selected.");
return;
}
List<QuestInfo> quests = GetAllByIssuerDataId(targetId.Value);
_chatGui.Print($"{quests.Count} quest(s) issued by target {_targetManager.Target?.Name}:");
foreach (QuestInfo quest in quests)
_chatGui.Print($" {quest.QuestId}: {quest.Name}");
}
}

View File

@ -206,7 +206,10 @@ internal sealed unsafe class GameFunctions
return default; return default;
// if we're not at a high enough level to continue, we also ignore it // if we're not at a high enough level to continue, we also ignore it
if (_questRegistry.TryGetQuest(currentQuest, out Quest? quest) && quest.Level > (_clientState.LocalPlayer?.Level ?? 0)) var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
if (currentLevel != 0 &&
_questRegistry.TryGetQuest(currentQuest, out Quest? quest)
&& quest.Info.Level > currentLevel)
return default; return default;
return (currentQuest, QuestManager.GetQuestSequence(currentQuest)); return (currentQuest, QuestManager.GetQuestSequence(currentQuest));
@ -218,6 +221,30 @@ internal sealed unsafe class GameFunctions
return questWork != null ? *questWork : null; return questWork != null ? *questWork : null;
} }
public bool IsReadyToAcceptQuest(ushort questId)
{
if (IsQuestAcceptedOrComplete(questId))
return false;
// if we're not at a high enough level to continue, we also ignore it
var currentLevel = _clientState.LocalPlayer?.Level ?? 0;
if (currentLevel != 0 &&
_questRegistry.TryGetQuest(questId, out Quest? quest) &&
quest.Info.Level > currentLevel)
return false;
return true;
}
public bool IsQuestAcceptedOrComplete(ushort questId)
{
if (QuestManager.IsQuestComplete(questId))
return true;
QuestManager* questManager = QuestManager.Instance();
return questManager->IsQuestAccepted(questId);
}
public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex) public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
{ {
subIndex = 0; subIndex = 0;
@ -548,6 +575,9 @@ internal sealed unsafe class GameFunctions
public bool IsOccupied() public bool IsOccupied()
{ {
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
return true;
if (IsLoadingScreenVisible()) if (IsLoadingScreenVisible())
return true; return true;

View File

@ -6,10 +6,9 @@ namespace Questionable.Model;
internal sealed class Quest internal sealed class Quest
{ {
public required ushort QuestId { get; init; } public required ushort QuestId { get; init; }
public required string Name { get; set; } public required QuestRoot Root { get; init; }
public ushort Level { get; set; } public required QuestInfo Info { get; init; }
public required QuestData Data { get; init; }
public QuestSequence? FindSequence(byte currentSequence) public QuestSequence? FindSequence(byte currentSequence)
=> Data.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence); => Root.QuestSequence.SingleOrDefault(seq => seq.Sequence == currentSequence);
} }

View File

@ -0,0 +1,19 @@
using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
namespace Questionable.Model;
internal sealed class QuestInfo
{
public QuestInfo(ExcelQuest quest)
{
QuestId = (ushort)(quest.RowId & 0xFFFF);
Name = quest.Name.ToString();
Level = quest.ClassJobLevel0;
IssuerDataId = quest.IssuerStart;
}
public ushort QuestId { get; }
public string Name { get; }
public ushort Level { get; }
public uint IssuerDataId { get; }
}

View File

@ -68,6 +68,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ChatFunctions>(); serviceCollection.AddSingleton<ChatFunctions>();
serviceCollection.AddSingleton<AetherCurrentData>(); serviceCollection.AddSingleton<AetherCurrentData>();
serviceCollection.AddSingleton<AetheryteData>(); serviceCollection.AddSingleton<AetheryteData>();
serviceCollection.AddSingleton<QuestData>();
serviceCollection.AddSingleton<TerritoryData>(); serviceCollection.AddSingleton<TerritoryData>();
serviceCollection.AddSingleton<NavmeshIpc>(); serviceCollection.AddSingleton<NavmeshIpc>();
serviceCollection.AddSingleton<LifestreamIpc>(); serviceCollection.AddSingleton<LifestreamIpc>();
@ -110,6 +111,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
WaitAtEnd.WaitObjectAtPosition>(); WaitAtEnd.WaitObjectAtPosition>();
serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>(); serviceCollection.AddTransient<WaitAtEnd.WaitQuestAccepted>();
serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>(); serviceCollection.AddTransient<WaitAtEnd.WaitQuestCompleted>();
serviceCollection.AddTaskWithFactory<NextQuest.Factory, NextQuest.SetQuest>();
serviceCollection.AddSingleton<MovementController>(); serviceCollection.AddSingleton<MovementController>();
serviceCollection.AddSingleton<QuestRegistry>(); serviceCollection.AddSingleton<QuestRegistry>();
@ -120,11 +122,12 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<QuestWindow>(); serviceCollection.AddSingleton<QuestWindow>();
serviceCollection.AddSingleton<ConfigWindow>(); serviceCollection.AddSingleton<ConfigWindow>();
serviceCollection.AddSingleton<DebugOverlay>(); serviceCollection.AddSingleton<DebugOverlay>();
serviceCollection.AddSingleton<CommandHandler>();
serviceCollection.AddSingleton<DalamudInitializer>(); serviceCollection.AddSingleton<DalamudInitializer>();
_serviceProvider = serviceCollection.BuildServiceProvider(); _serviceProvider = serviceCollection.BuildServiceProvider();
_serviceProvider.GetRequiredService<QuestRegistry>().Reload(); _serviceProvider.GetRequiredService<QuestRegistry>().Reload();
_serviceProvider.GetRequiredService<QuestWindow>(); _serviceProvider.GetRequiredService<CommandHandler>();
_serviceProvider.GetRequiredService<DalamudInitializer>(); _serviceProvider.GetRequiredService<DalamudInitializer>();
} }

View File

@ -76,7 +76,7 @@ internal sealed class DebugOverlay : Window
if (HighlightedQuest == null || !_questRegistry.TryGetQuest(HighlightedQuest.Value, out var quest)) if (HighlightedQuest == null || !_questRegistry.TryGetQuest(HighlightedQuest.Value, out var quest))
return; return;
foreach (var sequence in quest.Data.QuestSequence) foreach (var sequence in quest.Root.QuestSequence)
{ {
for (int i = 0; i < sequence.Steps.Count; ++i) for (int i = 0; i < sequence.Steps.Count; ++i)
{ {

View File

@ -41,6 +41,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly NavmeshIpc _navmeshIpc; private readonly NavmeshIpc _navmeshIpc;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
private readonly TerritoryData _territoryData; private readonly TerritoryData _territoryData;
private readonly ILogger<QuestWindow> _logger; private readonly ILogger<QuestWindow> _logger;
@ -56,6 +57,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
Configuration configuration, Configuration configuration,
NavmeshIpc navmeshIpc, NavmeshIpc navmeshIpc,
QuestRegistry questRegistry, QuestRegistry questRegistry,
QuestData questData,
TerritoryData territoryData, TerritoryData territoryData,
ILogger<QuestWindow> logger) ILogger<QuestWindow> logger)
: base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize) : base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
@ -72,6 +74,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
_configuration = configuration; _configuration = configuration;
_navmeshIpc = navmeshIpc; _navmeshIpc = navmeshIpc;
_questRegistry = questRegistry; _questRegistry = questRegistry;
_questData = questData;
_territoryData = territoryData; _territoryData = territoryData;
_logger = logger; _logger = logger;
@ -99,7 +102,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
return false; return false;
var currentQuest = _questController.CurrentQuest; var currentQuest = _questController.CurrentQuest;
return currentQuest == null || !currentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType); return currentQuest == null || !currentQuest.Quest.Root.TerritoryBlacklist.Contains(_clientState.TerritoryType);
} }
public override void Draw() public override void Draw()
@ -114,12 +117,12 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
DrawRemainingTasks(); DrawRemainingTasks();
} }
private unsafe void DrawQuest() private void DrawQuest()
{ {
var currentQuest = _questController.CurrentQuest; var currentQuest = _questController.CurrentQuest;
if (currentQuest != null) if (currentQuest != null)
{ {
ImGui.TextUnformatted($"Quest: {currentQuest.Quest.Name} / {currentQuest.Sequence} / {currentQuest.Step}"); ImGui.TextUnformatted($"Quest: {currentQuest.Quest.Info.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
ImGui.BeginDisabled(); ImGui.BeginDisabled();
var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId); var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId);
@ -144,7 +147,12 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ImGui.Text($"QW: {vars.Trim()}"); ImGui.Text($"QW: {vars.Trim()}");
} }
else else
ImGui.TextUnformatted("(Not accepted)"); {
if (currentQuest.Quest.QuestId == _questController.NextQuest?.Quest.QuestId)
ImGui.TextUnformatted("(Next quest in story line not accepted)");
else
ImGui.TextUnformatted("(Not accepted)");
}
ImGui.TextUnformatted(_questController.DebugState ?? "--"); ImGui.TextUnformatted(_questController.DebugState ?? "--");
ImGui.EndDisabled(); ImGui.EndDisabled();
@ -158,6 +166,10 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ImGui.BeginDisabled(_questController.IsRunning); ImGui.BeginDisabled(_questController.IsRunning);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{ {
// if we haven't accepted this quest, mark it as next quest so that we can optionally use aetherytes to travel
if (questWork == null)
_questController.SetNextQuest(currentQuest.Quest);
_questController.ExecuteNextStep(true); _questController.ExecuteNextStep(true);
} }
@ -210,84 +222,75 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
if (_questController.SimulatedQuest != null) if (_questController.SimulatedQuest != null)
{ {
var simulatedQuest = _questController.SimulatedQuest;
ImGui.Separator(); ImGui.Separator();
ImGui.TextColored(ImGuiColors.DalamudRed, "Quest sim active (experimental)"); ImGui.TextColored(ImGuiColors.DalamudRed, "Quest sim active (experimental)");
ImGui.Text($"Sequence: {_questController.SimulatedQuest.Sequence}"); ImGui.Text($"Sequence: {simulatedQuest.Sequence}");
ImGui.BeginDisabled(_questController.SimulatedQuest.Sequence == 0); ImGui.BeginDisabled(simulatedQuest.Sequence == 0);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus))
{ {
_movementController.Stop(); _movementController.Stop();
_questController.Stop("Sim-"); _questController.Stop("Sim-");
byte oldSequence = _questController.SimulatedQuest.Sequence; byte oldSequence = simulatedQuest.Sequence;
byte newSequence = _questController.SimulatedQuest.Quest.Data.QuestSequence byte newSequence = simulatedQuest.Quest.Root.QuestSequence
.Select(x => (byte)x.Sequence) .Select(x => (byte)x.Sequence)
.LastOrDefault(x => x < oldSequence, byte.MinValue); .LastOrDefault(x => x < oldSequence, byte.MinValue);
_questController.SimulatedQuest = _questController.SimulatedQuest with _questController.SimulatedQuest.SetSequence(newSequence);
{
Sequence = newSequence,
};
} }
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.SameLine(); ImGui.SameLine();
ImGui.BeginDisabled(_questController.SimulatedQuest.Sequence >= 255); ImGui.BeginDisabled(simulatedQuest.Sequence >= 255);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{ {
_movementController.Stop(); _movementController.Stop();
_questController.Stop("Sim+"); _questController.Stop("Sim+");
byte oldSequence = _questController.SimulatedQuest.Sequence; byte oldSequence = simulatedQuest.Sequence;
byte newSequence = _questController.SimulatedQuest.Quest.Data.QuestSequence byte newSequence = simulatedQuest.Quest.Root.QuestSequence
.Select(x => (byte)x.Sequence) .Select(x => (byte)x.Sequence)
.FirstOrDefault(x => x > oldSequence, byte.MaxValue); .FirstOrDefault(x => x > oldSequence, byte.MaxValue);
_questController.SimulatedQuest = _questController.SimulatedQuest with simulatedQuest.SetSequence(newSequence);
{
Sequence = newSequence,
};
} }
ImGui.EndDisabled(); ImGui.EndDisabled();
var simulatedSequence = var simulatedSequence = simulatedQuest.Quest.FindSequence(simulatedQuest.Sequence);
_questController.SimulatedQuest.Quest.FindSequence(_questController.SimulatedQuest.Sequence);
if (simulatedSequence != null) if (simulatedSequence != null)
{ {
using var _ = ImRaii.PushId("SimulatedStep"); using var _ = ImRaii.PushId("SimulatedStep");
ImGui.Text($"Step: {currentQuest.Step} / {simulatedSequence.Steps.Count - 1}"); ImGui.Text($"Step: {simulatedQuest.Step} / {simulatedSequence.Steps.Count - 1}");
ImGui.BeginDisabled(currentQuest.Step == 0); ImGui.BeginDisabled(simulatedQuest.Step == 0);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Minus))
{ {
_movementController.Stop(); _movementController.Stop();
_questController.Stop("SimStep-"); _questController.Stop("SimStep-");
_questController.CurrentQuest = currentQuest with simulatedQuest.SetStep(Math.Min(simulatedQuest.Step - 1,
{ simulatedSequence.Steps.Count - 1));
Step = Math.Min(currentQuest.Step - 1, simulatedSequence.Steps.Count - 1),
};
} }
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.SameLine(); ImGui.SameLine();
ImGui.BeginDisabled(currentQuest.Step >= simulatedSequence.Steps.Count); ImGui.BeginDisabled(simulatedQuest.Step >= simulatedSequence.Steps.Count);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{ {
_movementController.Stop(); _movementController.Stop();
_questController.Stop("SimStep+"); _questController.Stop("SimStep+");
_questController.CurrentQuest = currentQuest with simulatedQuest.SetStep(
{ simulatedQuest.Step == simulatedSequence.Steps.Count - 1
Step = currentQuest.Step == simulatedSequence.Steps.Count - 1
? 255 ? 255
: (currentQuest.Step + 1), : (simulatedQuest.Step + 1));
};
} }
ImGui.EndDisabled(); ImGui.EndDisabled();
@ -375,7 +378,13 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Interact")) ImGui.BeginDisabled(gameObject->NamePlateIconId == 0);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Bars))
_questData.ShowQuestsIssuedByTarget();
ImGui.EndDisabled();
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.MousePointer))
{ {
ulong result = TargetSystem.Instance()->InteractWithObject( ulong result = TargetSystem.Instance()->InteractWithObject(
(GameObject*)_targetManager.Target.Address, false); (GameObject*)_targetManager.Target.Address, false);
@ -384,7 +393,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
ImGui.SameLine(); ImGui.SameLine();
ImGui.Button("Copy"); ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{ {
string interactionType = gameObject->NamePlateIconId switch string interactionType = gameObject->NamePlateIconId switch
@ -414,7 +423,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
} }
else else
{ {
ImGui.Button($"Copy"); ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{ {
ImGui.SetClipboardText($$""" ImGui.SetClipboardText($$"""