Add basic support for gathering custom delivery items automatically
This commit is contained in:
parent
2f4f4e24e2
commit
09f11d1914
@ -92,12 +92,6 @@
|
||||
"ecommons": {
|
||||
"type": "Project"
|
||||
},
|
||||
"gatheringpaths": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Questionable.Model": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"questionable.model": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
|
115
GatheringPaths/4.x - Stormblood/Yanxia/731_Yuzuka Manor_BTN.json
Normal file
115
GatheringPaths/4.x - Stormblood/Yanxia/731_Yuzuka Manor_BTN.json
Normal file
@ -0,0 +1,115 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
|
||||
"Author": "liza",
|
||||
"TerritoryId": 614,
|
||||
"AetheryteShortcut": "Yanxia - Namai",
|
||||
"Groups": [
|
||||
{
|
||||
"Nodes": [
|
||||
{
|
||||
"DataId": 33334,
|
||||
"Locations": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -222.386,
|
||||
"Y": 23.28162,
|
||||
"Z": 425.76
|
||||
}
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": -209.1725,
|
||||
"Y": 22.35068,
|
||||
"Z": 425.5524
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"DataId": 33333,
|
||||
"Locations": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -219.9592,
|
||||
"Y": 22.78741,
|
||||
"Z": 431.5036
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Nodes": [
|
||||
{
|
||||
"DataId": 33335,
|
||||
"Locations": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -349.8553,
|
||||
"Y": 33.90925,
|
||||
"Z": 452.5893
|
||||
},
|
||||
"MinimumAngle": -90,
|
||||
"MaximumAngle": 90
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"DataId": 33336,
|
||||
"Locations": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -361.5062,
|
||||
"Y": 33.49068,
|
||||
"Z": 453.4639
|
||||
}
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": -359.826,
|
||||
"Y": 35.47207,
|
||||
"Z": 442.164
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Nodes": [
|
||||
{
|
||||
"DataId": 33331,
|
||||
"Locations": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -231.3864,
|
||||
"Y": 17.74118,
|
||||
"Z": 511.0694
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"DataId": 33332,
|
||||
"Locations": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -219.0789,
|
||||
"Y": 18.05494,
|
||||
"Z": 525.418
|
||||
}
|
||||
},
|
||||
{
|
||||
"Position": {
|
||||
"X": -220.9139,
|
||||
"Y": 17.97838,
|
||||
"Z": 514.0063
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -71.31451,
|
||||
"Y": 206.56206,
|
||||
"Z": 29.3684
|
||||
},
|
||||
"TerritoryId": 478,
|
||||
"InteractionType": "WalkTo",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Idyllshire"
|
||||
},
|
||||
{
|
||||
"DataId": 1019615,
|
||||
"Position": {
|
||||
"X": -71.763245,
|
||||
"Y": 206.50021,
|
||||
"Z": 32.638916
|
||||
},
|
||||
"StopDistance": 5,
|
||||
"TerritoryId": 478,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1018393,
|
||||
"Position": {
|
||||
"X": -60.380005,
|
||||
"Y": 206.50021,
|
||||
"Z": 26.16919
|
||||
},
|
||||
"TerritoryId": 478,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Idyllshire"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1025878,
|
||||
"Position": {
|
||||
"X": 343.984,
|
||||
"Y": -120.32947,
|
||||
"Z": -306.0197
|
||||
},
|
||||
"TerritoryId": 613,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Ruby Sea - Tamamizu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1020337,
|
||||
"Position": {
|
||||
"X": 171.31299,
|
||||
"Y": 13.02367,
|
||||
"Z": -89.951965
|
||||
},
|
||||
"TerritoryId": 635,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Rhalgr's Reach"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1035211,
|
||||
"Position": {
|
||||
"X": -116.96039,
|
||||
"Y": 0,
|
||||
"Z": -133.95898
|
||||
},
|
||||
"TerritoryId": 886,
|
||||
"InteractionType": "Interact",
|
||||
"AetheryteShortcut": "Ishgard",
|
||||
"AethernetShortcut": [
|
||||
"[Ishgard] Aetheryte Plaza",
|
||||
"[Ishgard] Firmament"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1033543,
|
||||
"Position": {
|
||||
"X": 113.38977,
|
||||
"Y": -20,
|
||||
"Z": -0.96136475
|
||||
},
|
||||
"TerritoryId": 886,
|
||||
"InteractionType": "Interact",
|
||||
"AetheryteShortcut": "Ishgard",
|
||||
"AethernetShortcut": [
|
||||
"[Ishgard] Aetheryte Plaza",
|
||||
"[Ishgard] Firmament"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1031801,
|
||||
"Position": {
|
||||
"X": 52.8114,
|
||||
"Y": 83.001076,
|
||||
"Z": -65.38495
|
||||
},
|
||||
"TerritoryId": 820,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Eulmore"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1042241,
|
||||
"Position": {
|
||||
"X": 222.85791,
|
||||
"Y": 24.942732,
|
||||
"Z": -197.77222
|
||||
},
|
||||
"TerritoryId": 962,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Old Sharlayan",
|
||||
"AethernetShortcut": [
|
||||
"[Old Sharlayan] Aetheryte Plaza",
|
||||
"[Old Sharlayan] The Leveilleur Estate"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"DataId": 1044547,
|
||||
"Position": {
|
||||
"X": -241.68768,
|
||||
"Y": 51.058994,
|
||||
"Z": 620.8744
|
||||
},
|
||||
"TerritoryId": 816,
|
||||
"InteractionType": "Interact",
|
||||
"RequiredGatheredItems": [],
|
||||
"AetheryteShortcut": "Il Mheg - Lydha Lran",
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
|
||||
"Author": "liza",
|
||||
"QuestSequence": [
|
||||
{
|
||||
"Sequence": 0,
|
||||
"Steps": [
|
||||
{
|
||||
"Position": {
|
||||
"X": -44.066154,
|
||||
"Y": -29.530005,
|
||||
"Z": -55.85129
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "WalkTo",
|
||||
"AetheryteShortcut": "Labyrinthos - Sharlayan Hamlet",
|
||||
"RequiredGatheredItems": [],
|
||||
"Fly": true
|
||||
},
|
||||
{
|
||||
"DataId": 1046073,
|
||||
"Position": {
|
||||
"X": -53.635498,
|
||||
"Y": -29.497286,
|
||||
"Z": -65.14081
|
||||
},
|
||||
"TerritoryId": 956,
|
||||
"InteractionType": "Interact"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -40,8 +40,4 @@
|
||||
<AdditionalFiles Include="6.x - Endwalker\**\*.json" />
|
||||
<AdditionalFiles Include="7.x - Dawntrail\**\*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="7.x - Dawntrail\Custom Deliveries\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1041,7 +1041,8 @@
|
||||
"PickUpQuestId": {
|
||||
"type": [
|
||||
"null",
|
||||
"number"
|
||||
"number",
|
||||
"string"
|
||||
],
|
||||
"description": "Determines the quest which should be accepted. If empty/null, accepts the quest corresponding to the file name."
|
||||
}
|
||||
@ -1061,14 +1062,16 @@
|
||||
"TurnInQuestId": {
|
||||
"type": [
|
||||
"null",
|
||||
"number"
|
||||
"number",
|
||||
"string"
|
||||
],
|
||||
"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"
|
||||
"number",
|
||||
"string"
|
||||
],
|
||||
"description": "For quest chains (e.g. DT healer role quests) Which quest to do next, given that you meet the required level."
|
||||
}
|
||||
|
@ -85,6 +85,7 @@ public enum EAetheryteLocation
|
||||
IshgardTribunal = 86,
|
||||
IshgardLastVigil = 87,
|
||||
IshgardGatesOfJudgement = 88,
|
||||
IshgardFirmament = 100001,
|
||||
|
||||
Idyllshire = 75,
|
||||
IdyllshireWest = 90,
|
||||
|
@ -56,6 +56,7 @@ public sealed class AethernetShardConverter() : EnumConverter<EAetheryteLocation
|
||||
{ EAetheryteLocation.IshgardTribunal, "[Ishgard] The Tribunal" },
|
||||
{ EAetheryteLocation.IshgardLastVigil, "[Ishgard] The Last Vigil" },
|
||||
{ EAetheryteLocation.IshgardGatesOfJudgement, "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)" },
|
||||
{ EAetheryteLocation.IshgardFirmament, "[Ishgard] Firmament" },
|
||||
|
||||
{ EAetheryteLocation.Idyllshire, "[Idyllshire] Aetheryte Plaza" },
|
||||
{ EAetheryteLocation.IdyllshireWest, "[Idyllshire] West Idyllshire" },
|
||||
|
@ -4,12 +4,14 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace Questionable.Model.Questing.Converter;
|
||||
|
||||
public class ElementIdConverter : JsonConverter<ElementId>
|
||||
public sealed class ElementIdConverter : JsonConverter<ElementId>
|
||||
{
|
||||
public override ElementId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
uint value = reader.GetUInt32();
|
||||
return ElementId.From(value);
|
||||
if (reader.TokenType == JsonTokenType.Number)
|
||||
return new QuestId(reader.GetUInt16());
|
||||
else
|
||||
return ElementId.FromString(reader.GetString() ?? throw new JsonException());
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ElementId value, JsonSerializerOptions options)
|
||||
|
@ -50,37 +50,51 @@ public abstract class ElementId : IComparable<ElementId>, IEquatable<ElementId>
|
||||
return !Equals(left, right);
|
||||
}
|
||||
|
||||
public static ElementId From(uint value)
|
||||
public static ElementId FromString(string value)
|
||||
{
|
||||
if (value >= 100_000 && value < 200_000)
|
||||
return new LeveId((ushort)(value - 100_000));
|
||||
if (value.StartsWith("L"))
|
||||
return new LeveId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture));
|
||||
else if (value.StartsWith("S"))
|
||||
return new SatisfactionSupplyNpcId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture));
|
||||
else
|
||||
return new QuestId((ushort)value);
|
||||
return new QuestId(ushort.Parse(value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public static bool TryFromString(string value, out ElementId? elementId)
|
||||
{
|
||||
try
|
||||
{
|
||||
elementId = FromString(value);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
elementId = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class QuestId : ElementId
|
||||
public sealed class QuestId(ushort value) : ElementId(value)
|
||||
{
|
||||
public QuestId(ushort value)
|
||||
: base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LeveId : ElementId
|
||||
public sealed class LeveId(ushort value) : ElementId(value)
|
||||
{
|
||||
public LeveId(ushort value)
|
||||
: base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "L" + Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SatisfactionSupplyNpcId(ushort value) : ElementId(value)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return "S" + Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
@ -161,6 +161,7 @@
|
||||
"[Ishgard] The Tribunal",
|
||||
"[Ishgard] The Last Vigil",
|
||||
"[Ishgard] The Gates of Judgement (Coerthas Central Highlands)",
|
||||
"[Ishgard] Firmament",
|
||||
"[Idyllshire] Aetheryte Plaza",
|
||||
"[Idyllshire] West Idyllshire",
|
||||
"[Idyllshire] Prologue Gate (Western Hinterlands)",
|
||||
|
@ -77,7 +77,7 @@ internal sealed class CommandHandler : IDisposable
|
||||
|
||||
case "start":
|
||||
_questWindow.IsOpen = true;
|
||||
_questController.ExecuteNextStep(true);
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
|
||||
break;
|
||||
|
||||
case "stop":
|
||||
@ -128,11 +128,11 @@ internal sealed class CommandHandler : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (arguments.Length >= 1 && uint.TryParse(arguments[0], out uint questId))
|
||||
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
|
||||
{
|
||||
if (_questRegistry.TryGetQuest(ElementId.From(questId), out Quest? quest))
|
||||
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
|
||||
{
|
||||
_debugOverlay.HighlightedQuest = quest.QuestElementId;
|
||||
_debugOverlay.HighlightedQuest = quest.Id;
|
||||
_chatGui.Print($"[Questionable] Set highlighted quest to {questId} ({quest.Info.Name}).");
|
||||
}
|
||||
else
|
||||
@ -147,11 +147,11 @@ internal sealed class CommandHandler : IDisposable
|
||||
|
||||
private void SetNextQuest(string[] arguments)
|
||||
{
|
||||
if (arguments.Length >= 1 && uint.TryParse(arguments[0], out uint questId))
|
||||
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
|
||||
{
|
||||
if (_gameFunctions.IsQuestLocked(ElementId.From(questId)))
|
||||
if (_gameFunctions.IsQuestLocked(questId))
|
||||
_chatGui.PrintError($"[Questionable] Quest {questId} is locked.");
|
||||
else if (_questRegistry.TryGetQuest(ElementId.From(questId), out Quest? quest))
|
||||
else if (_questRegistry.TryGetQuest(questId, out Quest? quest))
|
||||
{
|
||||
_questController.SetNextQuest(quest);
|
||||
_chatGui.Print($"[Questionable] Set next quest to {questId} ({quest.Info.Name}).");
|
||||
@ -170,9 +170,9 @@ internal sealed class CommandHandler : IDisposable
|
||||
|
||||
private void SetSimulatedQuest(string[] arguments)
|
||||
{
|
||||
if (arguments.Length >= 1 && ushort.TryParse(arguments[0], out ushort questId))
|
||||
if (arguments.Length >= 1 && ElementId.TryFromString(arguments[0], out ElementId? questId) && questId != null)
|
||||
{
|
||||
if (_questRegistry.TryGetQuest(ElementId.From(questId), out Quest? quest))
|
||||
if (_questRegistry.TryGetQuest(questId, out Quest? quest))
|
||||
{
|
||||
_questController.SimulateQuest(quest);
|
||||
_chatGui.Print($"[Questionable] Simulating quest {questId} ({quest.Info.Name}).");
|
||||
|
111
Questionable/Controller/ContextMenuController.cs
Normal file
111
Questionable/Controller/ContextMenuController.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.Gui.ContextMenu;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
|
||||
internal sealed class ContextMenuController : IDisposable
|
||||
{
|
||||
private readonly IContextMenu _contextMenu;
|
||||
private readonly QuestController _questController;
|
||||
private readonly GatheringData _gatheringData;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly IChatGui _chatGui;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly ILogger<ContextMenuController> _logger;
|
||||
|
||||
public ContextMenuController(
|
||||
IContextMenu contextMenu,
|
||||
QuestController questController,
|
||||
GatheringData gatheringData,
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
IGameGui gameGui,
|
||||
IChatGui chatGui,
|
||||
IClientState clientState,
|
||||
ILogger<ContextMenuController> logger)
|
||||
{
|
||||
_contextMenu = contextMenu;
|
||||
_questController = questController;
|
||||
_gatheringData = gatheringData;
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_gameGui = gameGui;
|
||||
_chatGui = chatGui;
|
||||
_clientState = clientState;
|
||||
_logger = logger;
|
||||
|
||||
_contextMenu.OnMenuOpened += MenuOpened;
|
||||
}
|
||||
|
||||
private void MenuOpened(IMenuOpenedArgs args)
|
||||
{
|
||||
uint itemId = (uint) _gameGui.HoveredItem;
|
||||
if (itemId == 0)
|
||||
return;
|
||||
|
||||
if (itemId > 1_000_000)
|
||||
itemId -= 1_000_000;
|
||||
|
||||
if (itemId >= 500_000)
|
||||
itemId -= 500_000;
|
||||
|
||||
if (!_gatheringData.TryGetGatheringPointId(itemId, _clientState.LocalPlayer!.ClassJob.Id, out _))
|
||||
{
|
||||
_logger.LogInformation("No gathering point found for current job.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gatheringData.TryGetCustomDeliveryNpc(itemId, out uint npcId))
|
||||
{
|
||||
ushort collectability = _gatheringData.GetRecommendedCollectability(itemId);
|
||||
int quantityToGather = collectability > 0 ? 6 : int.MaxValue;
|
||||
if (collectability == 0)
|
||||
return;
|
||||
|
||||
args.AddMenuItem(new MenuItem
|
||||
{
|
||||
Prefix = SeIconChar.Hyadelyn,
|
||||
PrefixColor = 52,
|
||||
Name = "Gather with Questionable",
|
||||
OnClicked = _ => StartGathering(npcId, itemId, quantityToGather, collectability),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void StartGathering(uint npcId, uint itemId, int quantity, ushort collectability)
|
||||
{
|
||||
var info = (SatisfactionSupplyInfo)_questData.GetAllByIssuerDataId(npcId).Single(x => x is SatisfactionSupplyInfo);
|
||||
if (_questRegistry.TryGetQuest(info.QuestId, out Quest? quest))
|
||||
{
|
||||
var step = quest.FindSequence(0)!.FindStep(0)!;
|
||||
step.RequiredGatheredItems =
|
||||
[
|
||||
new GatheredItem
|
||||
{
|
||||
ItemId = itemId,
|
||||
ItemCount = quantity,
|
||||
Collectability = collectability
|
||||
}
|
||||
];
|
||||
_questController.SetNextQuest(quest);
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.CurrentQuestOnly);
|
||||
}
|
||||
else
|
||||
_chatGui.PrintError($"No associated quest ({info.QuestId}).", "Questionable");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_contextMenu.OnMenuOpened -= MenuOpened;
|
||||
}
|
||||
}
|
@ -600,7 +600,7 @@ internal sealed class GameUiController : IDisposable
|
||||
|
||||
private unsafe void UnendingCodexPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (_questController.StartedQuest?.Quest.QuestElementId.Value == 4526)
|
||||
if (_questController.StartedQuest?.Quest.Id.Value == 4526)
|
||||
{
|
||||
_logger.LogInformation("Closing Unending Codex");
|
||||
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
|
||||
@ -610,7 +610,7 @@ internal sealed class GameUiController : IDisposable
|
||||
|
||||
private unsafe void ContentsTutorialPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (_questController.StartedQuest?.Quest.QuestElementId.Value == 245)
|
||||
if (_questController.StartedQuest?.Quest.Id.Value == 245)
|
||||
{
|
||||
_logger.LogInformation("Closing ContentsTutorial");
|
||||
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
|
||||
@ -623,7 +623,7 @@ internal sealed class GameUiController : IDisposable
|
||||
/// </summary>
|
||||
private unsafe void MultipleHelpWindowPostSetup(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
if (_questController.StartedQuest?.Quest.QuestElementId.Value == 245)
|
||||
if (_questController.StartedQuest?.Quest.Id.Value == 245)
|
||||
{
|
||||
_logger.LogInformation("Closing MultipleHelpWindow");
|
||||
AtkUnitBase* addon = (AtkUnitBase*)args.Addon;
|
||||
|
@ -33,7 +33,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
private QuestProgress? _startedQuest;
|
||||
private QuestProgress? _nextQuest;
|
||||
private QuestProgress? _simulatedQuest;
|
||||
private bool _automatic;
|
||||
private EAutomationType _automationType;
|
||||
|
||||
/// <summary>
|
||||
/// Some combat encounters finish relatively early (i.e. they're done as part of progressing the quest, but not
|
||||
@ -71,16 +71,16 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_taskFactories = taskFactories.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public (QuestProgress Progress, CurrentQuestType Type)? CurrentQuestDetails
|
||||
public (QuestProgress Progress, ECurrentQuestType Type)? CurrentQuestDetails
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_simulatedQuest != null)
|
||||
return (_simulatedQuest, CurrentQuestType.Simulated);
|
||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.QuestElementId))
|
||||
return (_nextQuest, CurrentQuestType.Next);
|
||||
return (_simulatedQuest, ECurrentQuestType.Simulated);
|
||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||
return (_nextQuest, ECurrentQuestType.Next);
|
||||
else if (_startedQuest != null)
|
||||
return (_startedQuest, CurrentQuestType.Normal);
|
||||
return (_startedQuest, ECurrentQuestType.Normal);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@ -153,10 +153,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
if (CurrentQuest != null && CurrentQuest.Quest.Root.TerritoryBlacklist.Contains(_clientState.TerritoryType))
|
||||
return;
|
||||
|
||||
if (_automatic && ((_currentTask == null && _taskQueue.Count == 0) ||
|
||||
_currentTask is WaitAtEnd.WaitQuestAccepted)
|
||||
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
|
||||
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
|
||||
if (_automationType == EAutomationType.Automatic &&
|
||||
((_currentTask == null && _taskQueue.Count == 0) ||
|
||||
_currentTask is WaitAtEnd.WaitQuestAccepted)
|
||||
&& CurrentQuest is { Sequence: 0, Step: 0 } or { Sequence: 0, Step: 255 }
|
||||
&& DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15))
|
||||
{
|
||||
lock (_progressLock)
|
||||
{
|
||||
@ -164,7 +165,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
CurrentQuest.SetStep(0);
|
||||
}
|
||||
|
||||
ExecuteNextStep(true);
|
||||
ExecuteNextStep(_automationType);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -182,13 +183,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
// if the quest is accepted, we no longer track it
|
||||
bool canUseNextQuest;
|
||||
if (_nextQuest.Quest.Info.IsRepeatable)
|
||||
canUseNextQuest = !_gameFunctions.IsQuestAccepted(_nextQuest.Quest.QuestElementId);
|
||||
canUseNextQuest = !_gameFunctions.IsQuestAccepted(_nextQuest.Quest.Id);
|
||||
else
|
||||
canUseNextQuest = !_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.QuestElementId);
|
||||
canUseNextQuest = !_gameFunctions.IsQuestAcceptedOrComplete(_nextQuest.Quest.Id);
|
||||
|
||||
if (!canUseNextQuest)
|
||||
{
|
||||
_logger.LogInformation("Next quest {QuestId} accepted or completed", _nextQuest.Quest.QuestElementId);
|
||||
_logger.LogInformation("Next quest {QuestId} accepted or completed",
|
||||
_nextQuest.Quest.Id);
|
||||
_nextQuest = null;
|
||||
}
|
||||
}
|
||||
@ -200,12 +202,15 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
currentSequence = _simulatedQuest.Sequence;
|
||||
questToRun = _simulatedQuest;
|
||||
}
|
||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.QuestElementId))
|
||||
else if (_nextQuest != null && _gameFunctions.IsReadyToAcceptQuest(_nextQuest.Quest.Id))
|
||||
{
|
||||
questToRun = _nextQuest;
|
||||
currentSequence = _nextQuest.Sequence; // by definition, this should always be 0
|
||||
if (_nextQuest.Step == 0 && _currentTask == null && _taskQueue.Count == 0 && _automatic)
|
||||
ExecuteNextStep(true);
|
||||
if (_nextQuest.Step == 0 &&
|
||||
_currentTask == null &&
|
||||
_taskQueue.Count == 0 &&
|
||||
_automationType == EAutomationType.Automatic)
|
||||
ExecuteNextStep(_automationType);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -221,7 +226,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
|
||||
questToRun = null;
|
||||
}
|
||||
else if (_startedQuest == null || _startedQuest.Quest.QuestElementId != currentQuestId)
|
||||
else if (_startedQuest == null || _startedQuest.Quest.Id != currentQuestId)
|
||||
{
|
||||
if (_questRegistry.TryGetQuest(currentQuestId, out var quest))
|
||||
{
|
||||
@ -341,11 +346,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
return;
|
||||
}
|
||||
|
||||
if (questId != null && CurrentQuest.Quest.QuestElementId != questId)
|
||||
if (questId != null && CurrentQuest.Quest.Id != questId)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Ignoring 'increase step count' for different quest (expected {ExpectedQuestId}, but we are at {CurrentQuestId}",
|
||||
questId, CurrentQuest.Quest.QuestElementId);
|
||||
questId, CurrentQuest.Quest.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -363,8 +368,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
CurrentQuest.SetStep(255);
|
||||
}
|
||||
|
||||
if (shouldContinue && _automatic)
|
||||
ExecuteNextStep(true);
|
||||
if (shouldContinue && _automationType != EAutomationType.Manual)
|
||||
ExecuteNextStep(_automationType);
|
||||
}
|
||||
|
||||
private void ClearTasksInternal()
|
||||
@ -387,17 +392,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
ClearTasksInternal();
|
||||
|
||||
// reset task queue
|
||||
if (continueIfAutomatic && _automatic)
|
||||
if (continueIfAutomatic && _automationType == EAutomationType.Automatic)
|
||||
{
|
||||
if (CurrentQuest?.Step is >= 0 and < 255)
|
||||
ExecuteNextStep(true);
|
||||
ExecuteNextStep(_automationType);
|
||||
else
|
||||
_logger.LogInformation("Couldn't execute next step during Stop() call");
|
||||
}
|
||||
else if (_automatic)
|
||||
else if (_automationType != EAutomationType.Manual)
|
||||
{
|
||||
_logger.LogInformation("Stopping automatic questing");
|
||||
_automatic = false;
|
||||
_automationType = EAutomationType.Manual;
|
||||
_nextQuest = null;
|
||||
}
|
||||
}
|
||||
@ -406,7 +411,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
|
||||
public void SimulateQuest(Quest? quest)
|
||||
{
|
||||
_logger.LogInformation("SimulateQuest: {QuestId}", quest?.QuestElementId);
|
||||
_logger.LogInformation("SimulateQuest: {QuestId}", quest?.Id);
|
||||
if (quest != null)
|
||||
_simulatedQuest = new QuestProgress(quest);
|
||||
else
|
||||
@ -415,7 +420,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
|
||||
public void SetNextQuest(Quest? quest)
|
||||
{
|
||||
_logger.LogInformation("NextQuest: {QuestId}", quest?.QuestElementId);
|
||||
_logger.LogInformation("NextQuest: {QuestId}", quest?.Id);
|
||||
if (quest != null)
|
||||
_nextQuest = new QuestProgress(quest);
|
||||
else
|
||||
@ -441,10 +446,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
IncreaseStepCount(task.QuestElementId, task.Sequence, true);
|
||||
}
|
||||
|
||||
public void ExecuteNextStep(bool automatic)
|
||||
public void ExecuteNextStep(EAutomationType automatic)
|
||||
{
|
||||
ClearTasksInternal();
|
||||
_automatic = automatic;
|
||||
_automationType = automatic;
|
||||
|
||||
if (TryPickPriorityQuest())
|
||||
_logger.LogInformation("Using priority quest over current quest");
|
||||
@ -452,8 +457,21 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
(QuestSequence? seq, QuestStep? step) = GetNextStep();
|
||||
if (CurrentQuest == null || seq == null || step == null)
|
||||
{
|
||||
_logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]",
|
||||
CurrentQuest?.Quest.QuestElementId, CurrentQuest?.Sequence, CurrentQuest?.Step);
|
||||
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId &&
|
||||
CurrentQuestDetails?.Progress.Sequence == 0 &&
|
||||
CurrentQuestDetails?.Progress.Step == 255 &&
|
||||
CurrentQuestDetails?.Type == ECurrentQuestType.Next)
|
||||
{
|
||||
_logger.LogInformation("Completed delivery quest");
|
||||
SetNextQuest(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]",
|
||||
CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -488,7 +506,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
}
|
||||
|
||||
_logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}",
|
||||
CurrentQuest.Quest.QuestElementId, seq.Sequence, seq.Steps.IndexOf(step),
|
||||
CurrentQuest.Quest.Id, seq.Sequence, seq.Steps.IndexOf(step),
|
||||
string.Join(", ", newTasks.Select(x => x.ToString())));
|
||||
foreach (var task in newTasks)
|
||||
_taskQueue.Enqueue(task);
|
||||
@ -587,7 +605,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
return false;
|
||||
|
||||
var (currentQuest, type) = details.Value;
|
||||
if (type != CurrentQuestType.Normal)
|
||||
if (type != ECurrentQuestType.Normal)
|
||||
return false;
|
||||
|
||||
QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
|
||||
@ -628,10 +646,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
DateTime StartedAt,
|
||||
int PointMenuCounter = 0);
|
||||
|
||||
public enum CurrentQuestType
|
||||
public enum ECurrentQuestType
|
||||
{
|
||||
Normal,
|
||||
Next,
|
||||
Simulated,
|
||||
}
|
||||
|
||||
public enum EAutomationType
|
||||
{
|
||||
Manual,
|
||||
Automatic,
|
||||
CurrentQuestOnly,
|
||||
}
|
||||
}
|
||||
|
@ -91,12 +91,12 @@ internal sealed class QuestRegistry
|
||||
{
|
||||
Quest quest = new()
|
||||
{
|
||||
QuestElementId = new QuestId(questId),
|
||||
Id = new QuestId(questId),
|
||||
Root = questRoot,
|
||||
Info = _questData.GetQuestInfo(new QuestId(questId)),
|
||||
ReadOnly = true,
|
||||
};
|
||||
_quests[quest.QuestElementId] = quest;
|
||||
_quests[quest.Id] = quest;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Loaded {Count} quests from assembly", _quests.Count);
|
||||
@ -145,12 +145,12 @@ internal sealed class QuestRegistry
|
||||
|
||||
Quest quest = new Quest
|
||||
{
|
||||
QuestElementId = questId,
|
||||
Id = questId,
|
||||
Root = questNode.Deserialize<QuestRoot>()!,
|
||||
Info = _questData.GetQuestInfo(questId),
|
||||
ReadOnly = false,
|
||||
};
|
||||
_quests[quest.QuestElementId] = quest;
|
||||
_quests[quest.Id] = quest;
|
||||
}
|
||||
|
||||
private void LoadFromDirectory(DirectoryInfo directory, LogLevel logLevel = LogLevel.Information)
|
||||
@ -188,30 +188,11 @@ internal sealed class QuestRegistry
|
||||
return null;
|
||||
|
||||
string[] parts = name.Split('_', 2);
|
||||
return ElementId.From(uint.Parse(parts[0], CultureInfo.InvariantCulture));
|
||||
return ElementId.FromString(parts[0]);
|
||||
}
|
||||
|
||||
public bool IsKnownQuest(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsKnownQuest(questId);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
public bool IsKnownQuest(ElementId questId) => _quests.ContainsKey(questId);
|
||||
|
||||
public bool IsKnownQuest(QuestId questId) => _quests.ContainsKey(questId);
|
||||
|
||||
public bool TryGetQuest(ElementId elementId, [NotNullWhen(true)] out Quest? quest)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return TryGetQuest(questId, out quest);
|
||||
else
|
||||
{
|
||||
quest = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetQuest(QuestId questId, [NotNullWhen(true)] out Quest? quest)
|
||||
public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest)
|
||||
=> _quests.TryGetValue(questId, out quest);
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ internal static class NextQuest
|
||||
if (step.NextQuestId == null)
|
||||
return null;
|
||||
|
||||
if (step.NextQuestId == quest.QuestElementId)
|
||||
if (step.NextQuestId == quest.Id)
|
||||
return null;
|
||||
|
||||
return serviceProvider.GetRequiredService<SetQuest>()
|
||||
.With(step.NextQuestId, quest.QuestElementId);
|
||||
.With(step.NextQuestId, quest.Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ internal static class Combat
|
||||
ArgumentNullException.ThrowIfNull(step.ItemId);
|
||||
|
||||
yield return serviceProvider.GetRequiredService<UseItem.UseOnObject>()
|
||||
.With(quest.QuestElementId, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
|
||||
.With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags,
|
||||
true);
|
||||
yield return CreateTask(quest, sequence, step);
|
||||
break;
|
||||
@ -73,7 +73,7 @@ internal static class Combat
|
||||
|
||||
bool isLastStep = sequence.Steps.Last() == step;
|
||||
return serviceProvider.GetRequiredService<HandleCombat>()
|
||||
.With(quest.QuestElementId, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
|
||||
.With(quest.Id, isLastStep, step.EnemySpawnType.Value, step.KillEnemyDataIds,
|
||||
step.CompletionQuestVariablesFlags, step.ComplexCombatData);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ internal static class Interact
|
||||
yield return serviceProvider.GetRequiredService<WaitAtEnd.WaitDelay>();
|
||||
|
||||
yield return serviceProvider.GetRequiredService<DoInteract>()
|
||||
.With(step.DataId.Value, step.TargetTerritoryId != null);
|
||||
.With(step.DataId.Value,
|
||||
step.TargetTerritoryId != null || quest.Id is SatisfactionSupplyNpcId);
|
||||
}
|
||||
|
||||
public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||
|
@ -48,7 +48,7 @@ internal static class UseItem
|
||||
}
|
||||
|
||||
var task = serviceProvider.GetRequiredService<Use>()
|
||||
.With(quest.QuestElementId, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
.With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
return
|
||||
[
|
||||
unmount, task,
|
||||
@ -65,12 +65,12 @@ internal static class UseItem
|
||||
ITask task;
|
||||
if (step.DataId != null)
|
||||
task = serviceProvider.GetRequiredService<UseOnGround>()
|
||||
.With(quest.QuestElementId, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
.With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
else
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(step.Position);
|
||||
task = serviceProvider.GetRequiredService<UseOnPosition>()
|
||||
.With(quest.QuestElementId, step.Position.Value, step.ItemId.Value,
|
||||
.With(quest.Id, step.Position.Value, step.ItemId.Value,
|
||||
step.CompletionQuestVariablesFlags);
|
||||
}
|
||||
|
||||
@ -79,13 +79,13 @@ internal static class UseItem
|
||||
else if (step.DataId != null)
|
||||
{
|
||||
var task = serviceProvider.GetRequiredService<UseOnObject>()
|
||||
.With(quest.QuestElementId, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
.With(quest.Id, step.DataId.Value, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
return [unmount, task];
|
||||
}
|
||||
else
|
||||
{
|
||||
var task = serviceProvider.GetRequiredService<Use>()
|
||||
.With(quest.QuestElementId, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
.With(quest.Id, step.ItemId.Value, step.CompletionQuestVariablesFlags);
|
||||
return [unmount, task];
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ internal static class GatheringRequiredItems
|
||||
|
||||
if (!AssemblyGatheringLocationLoader.GetLocations()
|
||||
.TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot))
|
||||
throw new TaskException("No path found for gathering point");
|
||||
throw new TaskException($"No path found for gathering point {gatheringPointId}");
|
||||
|
||||
if (HasRequiredItems(requiredGatheredItems))
|
||||
continue;
|
||||
|
@ -34,7 +34,7 @@ internal static class SkipCondition
|
||||
return null;
|
||||
|
||||
return serviceProvider.GetRequiredService<CheckSkip>()
|
||||
.With(step, skipConditions ?? new(), quest.QuestElementId);
|
||||
.With(step, skipConditions ?? new(), quest.Id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ internal static class WaitAtEnd
|
||||
if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
||||
{
|
||||
var task = serviceProvider.GetRequiredService<WaitForCompletionFlags>()
|
||||
.With((QuestId)quest.QuestElementId, step);
|
||||
.With((QuestId)quest.Id, step);
|
||||
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
||||
return [task, delay, Next(quest, sequence)];
|
||||
}
|
||||
@ -110,7 +110,7 @@ internal static class WaitAtEnd
|
||||
case EInteractionType.AcceptQuest:
|
||||
{
|
||||
var accept = serviceProvider.GetRequiredService<WaitQuestAccepted>()
|
||||
.With(step.PickUpQuestId ?? quest.QuestElementId);
|
||||
.With(step.PickUpQuestId ?? quest.Id);
|
||||
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
||||
if (step.PickUpQuestId != null)
|
||||
return [accept, delay, Next(quest, sequence)];
|
||||
@ -121,7 +121,7 @@ internal static class WaitAtEnd
|
||||
case EInteractionType.CompleteQuest:
|
||||
{
|
||||
var complete = serviceProvider.GetRequiredService<WaitQuestCompleted>()
|
||||
.With(step.TurnInQuestId ?? quest.QuestElementId);
|
||||
.With(step.TurnInQuestId ?? quest.Id);
|
||||
var delay = serviceProvider.GetRequiredService<WaitDelay>();
|
||||
if (step.TurnInQuestId != null)
|
||||
return [complete, delay, Next(quest, sequence)];
|
||||
@ -140,7 +140,7 @@ internal static class WaitAtEnd
|
||||
|
||||
private static NextStep Next(Quest quest, QuestSequence sequence)
|
||||
{
|
||||
return new NextStep(quest.QuestElementId, sequence.Sequence);
|
||||
return new NextStep(quest.Id, sequence.Sequence);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,10 @@ internal sealed class AetheryteData
|
||||
aethernetGroups[(EAetheryteLocation)aetheryte.RowId] = aetheryte.AethernetGroup;
|
||||
}
|
||||
|
||||
aethernetNames[EAetheryteLocation.IshgardFirmament] = "Firmament";
|
||||
territoryIds[EAetheryteLocation.IshgardFirmament] = 886;
|
||||
aethernetGroups[EAetheryteLocation.IshgardFirmament] = aethernetGroups[EAetheryteLocation.Ishgard];
|
||||
|
||||
AethernetNames = aethernetNames.AsReadOnly();
|
||||
TerritoryIds = territoryIds.AsReadOnly();
|
||||
AethernetGroups = aethernetGroups.AsReadOnly();
|
||||
@ -267,6 +271,7 @@ internal sealed class AetheryteData
|
||||
{ EAetheryteLocation.GridaniaAirship, new(24.86354f, -19.000002f, 96f) },
|
||||
{ EAetheryteLocation.UldahAirship, new(-16.954851f, 82.999985f, -9.421141f) },
|
||||
{ EAetheryteLocation.KuganeAirship, new(-55.72525f, 79.10602f, 46.23109f) },
|
||||
{ EAetheryteLocation.IshgardFirmament, new(9.92315f, -15.2f, 173.5059f) },
|
||||
}.AsReadOnly();
|
||||
|
||||
public ReadOnlyDictionary<EAetheryteLocation, string> AethernetNames { get; }
|
||||
@ -298,6 +303,9 @@ internal sealed class AetheryteData
|
||||
|
||||
public bool IsCityAetheryte(EAetheryteLocation aetheryte)
|
||||
{
|
||||
if (aetheryte == EAetheryteLocation.IshgardFirmament)
|
||||
return true;
|
||||
|
||||
var territoryId = TerritoryIds[aetheryte];
|
||||
return TownTerritoryIds.Contains(territoryId);
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Questionable.Data;
|
||||
|
||||
internal sealed class GatheringData
|
||||
{
|
||||
private readonly Dictionary<uint, uint> _gatheringItemToItem;
|
||||
private readonly Dictionary<uint, ushort> _minerGatheringPoints = [];
|
||||
private readonly Dictionary<uint, ushort> _botanistGatheringPoints = [];
|
||||
private readonly Dictionary<uint, ushort> _itemIdToCollectability;
|
||||
private readonly Dictionary<uint, uint> _npcForCustomDeliveries;
|
||||
|
||||
public GatheringData(IDataManager dataManager)
|
||||
{
|
||||
_gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()!
|
||||
Dictionary<uint, uint> gatheringItemToItem = dataManager.GetExcelSheet<GatheringItem>()!
|
||||
.Where(x => x.RowId != 0 && x.Item != 0)
|
||||
.ToDictionary(x => x.RowId, x => (uint)x.Item);
|
||||
|
||||
@ -22,7 +23,7 @@ internal sealed class GatheringData
|
||||
{
|
||||
foreach (var gatheringItemId in gatheringPointBase.Item.Where(x => x != 0))
|
||||
{
|
||||
if (_gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
|
||||
if (gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId))
|
||||
{
|
||||
if (gatheringPointBase.GatheringType.Row is 0 or 1)
|
||||
_minerGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId;
|
||||
@ -31,8 +32,31 @@ internal sealed class GatheringData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_itemIdToCollectability = dataManager.GetExcelSheet<SatisfactionSupply>()!
|
||||
.Where(x => x.RowId > 0)
|
||||
.Where(x => x.Slot is 2)
|
||||
.Select(x => new
|
||||
{
|
||||
ItemId = x.Item.Row,
|
||||
Collectability = x.CollectabilityHigh,
|
||||
})
|
||||
.Distinct()
|
||||
.ToDictionary(x => x.ItemId, x => x.Collectability);
|
||||
|
||||
_npcForCustomDeliveries = dataManager.GetExcelSheet<SatisfactionNpc>()!
|
||||
.Where(x => x.RowId > 0)
|
||||
.SelectMany(x => dataManager.GetExcelSheet<SatisfactionSupply>()!
|
||||
.Where(y => y.RowId == x.SupplyIndex.Last())
|
||||
.Select(y => new
|
||||
{
|
||||
ItemId = y.Item.Row,
|
||||
NpcId = x.Npc.Row
|
||||
}))
|
||||
.Where(x => x.ItemId > 0)
|
||||
.Distinct()
|
||||
.ToDictionary(x => x.ItemId, x => x.NpcId);
|
||||
}
|
||||
|
||||
public bool TryGetGatheringPointId(uint itemId, uint classJobId, out ushort gatheringPointId)
|
||||
{
|
||||
@ -46,4 +70,10 @@ internal sealed class GatheringData
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort GetRecommendedCollectability(uint itemId)
|
||||
=> _itemIdToCollectability.GetValueOrDefault(itemId);
|
||||
|
||||
public bool TryGetCustomDeliveryNpc(uint itemId, out uint npcId)
|
||||
=> _npcForCustomDeliveries.TryGetValue(itemId, out npcId);
|
||||
}
|
||||
|
@ -22,17 +22,17 @@ internal sealed class JournalData
|
||||
var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
|
||||
new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row))
|
||||
.Where(x => x != 0)
|
||||
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
||||
.Select(x => (QuestInfo)questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
||||
.ToList());
|
||||
var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
|
||||
new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row))
|
||||
.Where(x => x != 0)
|
||||
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
||||
.Select(x => (QuestInfo)questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
||||
.ToList());
|
||||
var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
|
||||
new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row))
|
||||
.Where(x => x != 0)
|
||||
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
||||
.Select(x => (QuestInfo)questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
||||
.ToList());
|
||||
genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
|
||||
genres.Single(x => x.Id == 1)
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Quest = Lumina.Excel.GeneratedSheets.Quest;
|
||||
@ -11,32 +12,30 @@ namespace Questionable.Data;
|
||||
|
||||
internal sealed class QuestData
|
||||
{
|
||||
private readonly ImmutableDictionary<QuestId, QuestInfo> _quests;
|
||||
private readonly Dictionary<ElementId, IQuestInfo> _quests;
|
||||
|
||||
public QuestData(IDataManager dataManager)
|
||||
{
|
||||
_quests = dataManager.GetExcelSheet<Quest>()!
|
||||
.Where(x => x.RowId > 0)
|
||||
.Where(x => x.IssuerLocation.Row > 0)
|
||||
.Where(x => x.Festival.Row == 0)
|
||||
.Select(x => new QuestInfo(x))
|
||||
.ToImmutableDictionary(x => x.QuestId, x => x);
|
||||
List<IQuestInfo> quests =
|
||||
[
|
||||
..dataManager.GetExcelSheet<Quest>()!
|
||||
.Where(x => x.RowId > 0)
|
||||
.Where(x => x.IssuerLocation.Row > 0)
|
||||
.Where(x => x.Festival.Row == 0)
|
||||
.Select(x => new QuestInfo(x)),
|
||||
..dataManager.GetExcelSheet<SatisfactionNpc>()!
|
||||
.Where(x => x.RowId > 0)
|
||||
.Select(x => new SatisfactionSupplyInfo(x))
|
||||
];
|
||||
_quests = quests.ToDictionary(x => x.QuestId, x => x);
|
||||
}
|
||||
|
||||
public QuestInfo GetQuestInfo(ElementId elementId)
|
||||
public IQuestInfo GetQuestInfo(ElementId elementId)
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return GetQuestInfo(questId);
|
||||
|
||||
throw new ArgumentException("Invalid id", nameof(elementId));
|
||||
return _quests[elementId] ?? throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public QuestInfo GetQuestInfo(QuestId questId)
|
||||
{
|
||||
return _quests[questId] ?? throw new ArgumentOutOfRangeException(nameof(questId));
|
||||
}
|
||||
|
||||
public List<QuestInfo> GetAllByIssuerDataId(uint targetId)
|
||||
public List<IQuestInfo> GetAllByIssuerDataId(uint targetId)
|
||||
{
|
||||
return _quests.Values
|
||||
.Where(x => x.IssuerDataId == targetId)
|
||||
@ -48,6 +47,8 @@ internal sealed class QuestData
|
||||
public List<QuestInfo> GetAllByJournalGenre(uint journalGenre)
|
||||
{
|
||||
return _quests.Values
|
||||
.Where(x => x is QuestInfo)
|
||||
.Cast<QuestInfo>()
|
||||
.Where(x => x.JournalGenre == journalGenre)
|
||||
.OrderBy(x => x.SortKey)
|
||||
.ThenBy(x => x.QuestId)
|
||||
|
6
Questionable/External/LifestreamIpc.cs
vendored
6
Questionable/External/LifestreamIpc.cs
vendored
@ -18,6 +18,12 @@ internal sealed class LifestreamIpc
|
||||
|
||||
public bool Teleport(EAetheryteLocation aetheryteLocation)
|
||||
{
|
||||
if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
|
||||
{
|
||||
// TODO does this even work on non-EN clients?
|
||||
return _aethernetTeleport.InvokeFunc("Firmament");
|
||||
}
|
||||
|
||||
if (!_aetheryteData.AethernetNames.TryGetValue(aetheryteLocation, out string? name))
|
||||
return false;
|
||||
|
||||
|
@ -246,7 +246,10 @@ internal sealed unsafe class GameFunctions
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsReadyToAcceptQuest(questId);
|
||||
return false;
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return true;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsReadyToAcceptQuest(QuestId questId)
|
||||
@ -283,7 +286,10 @@ internal sealed unsafe class GameFunctions
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestAccepted(questId);
|
||||
return false;
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsQuestAccepted(QuestId questId)
|
||||
@ -296,7 +302,10 @@ internal sealed unsafe class GameFunctions
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestComplete(questId);
|
||||
return false;
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
[SuppressMessage("Performance", "CA1822")]
|
||||
@ -309,12 +318,15 @@ internal sealed unsafe class GameFunctions
|
||||
{
|
||||
if (elementId is QuestId questId)
|
||||
return IsQuestLocked(questId, extraCompletedQuest);
|
||||
return false;
|
||||
else if (elementId is SatisfactionSupplyNpcId)
|
||||
return false;
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(elementId));
|
||||
}
|
||||
|
||||
public bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
|
||||
{
|
||||
var questInfo = _questData.GetQuestInfo(questId);
|
||||
var questInfo = (QuestInfo) _questData.GetQuestInfo(questId);
|
||||
if (questInfo.QuestLocks.Count > 0)
|
||||
{
|
||||
var completedQuests = questInfo.QuestLocks.Count(x => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
|
||||
@ -369,7 +381,11 @@ internal sealed unsafe class GameFunctions
|
||||
}
|
||||
|
||||
public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
|
||||
=> IsAetheryteUnlocked((uint)aetheryteLocation, out _);
|
||||
{
|
||||
if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
|
||||
return IsQuestComplete(new QuestId(3672));
|
||||
return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
|
||||
}
|
||||
|
||||
public bool CanTeleport(EAetheryteLocation aetheryteLocation)
|
||||
{
|
||||
@ -707,15 +723,15 @@ internal sealed unsafe class GameFunctions
|
||||
if (excelSheetName == null)
|
||||
{
|
||||
var questRow =
|
||||
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.QuestElementId.Value +
|
||||
_dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets2.Quest>()!.GetRow((uint)currentQuest.Id.Value +
|
||||
0x10000);
|
||||
if (questRow == null)
|
||||
{
|
||||
_logger.LogError("Could not find quest row for {QuestId}", currentQuest.QuestElementId);
|
||||
_logger.LogError("Could not find quest row for {QuestId}", currentQuest.Id);
|
||||
return null;
|
||||
}
|
||||
|
||||
excelSheetName = $"quest/{(currentQuest.QuestElementId.Value / 100):000}/{questRow.Id}";
|
||||
excelSheetName = $"quest/{(currentQuest.Id.Value / 100):000}/{questRow.Id}";
|
||||
}
|
||||
|
||||
var excelSheet = _dataManager.Excel.GetSheet<QuestDialogueText>(excelSheetName);
|
||||
|
20
Questionable/Model/IQuestInfo.cs
Normal file
20
Questionable/Model/IQuestInfo.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Dalamud.Game.Text;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Model;
|
||||
|
||||
public interface IQuestInfo
|
||||
{
|
||||
public ElementId QuestId { get; }
|
||||
public string Name { get; }
|
||||
public uint IssuerDataId { get; }
|
||||
public bool IsRepeatable { get; }
|
||||
public ushort Level { get; }
|
||||
public EBeastTribe BeastTribe { get; }
|
||||
public bool IsMainScenarioQuest { get; }
|
||||
|
||||
public string SimplifiedName => Name
|
||||
.Replace(".", "", StringComparison.Ordinal)
|
||||
.TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' ');
|
||||
}
|
@ -6,9 +6,9 @@ namespace Questionable.Model;
|
||||
|
||||
internal sealed class Quest
|
||||
{
|
||||
public required ElementId QuestElementId { get; init; }
|
||||
public required ElementId Id { get; init; }
|
||||
public required QuestRoot Root { get; init; }
|
||||
public required QuestInfo Info { get; init; }
|
||||
public required IQuestInfo Info { get; init; }
|
||||
public required bool ReadOnly { get; init; }
|
||||
|
||||
public QuestSequence? FindSequence(byte currentSequence)
|
||||
|
@ -10,7 +10,7 @@ using ExcelQuest = Lumina.Excel.GeneratedSheets.Quest;
|
||||
|
||||
namespace Questionable.Model;
|
||||
|
||||
internal sealed class QuestInfo
|
||||
internal sealed class QuestInfo : IQuestInfo
|
||||
{
|
||||
public QuestInfo(ExcelQuest quest)
|
||||
{
|
||||
@ -56,7 +56,7 @@ internal sealed class QuestInfo
|
||||
}
|
||||
|
||||
|
||||
public QuestId QuestId { get; }
|
||||
public ElementId QuestId { get; }
|
||||
public string Name { get; }
|
||||
public ushort Level { get; }
|
||||
public uint IssuerDataId { get; }
|
||||
@ -74,10 +74,6 @@ internal sealed class QuestInfo
|
||||
public GrandCompany GrandCompany { get; }
|
||||
public EBeastTribe BeastTribe { get; }
|
||||
|
||||
public string SimplifiedName => Name
|
||||
.Replace(".", "", StringComparison.Ordinal)
|
||||
.TrimStart(SeIconChar.QuestSync.ToIconChar(), SeIconChar.QuestRepeatable.ToIconChar(), ' ');
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
|
||||
public enum QuestJoin : byte
|
||||
{
|
||||
|
23
Questionable/Model/SatisfactionSupplyInfo.cs
Normal file
23
Questionable/Model/SatisfactionSupplyInfo.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Model;
|
||||
|
||||
internal sealed class SatisfactionSupplyInfo : IQuestInfo
|
||||
{
|
||||
public SatisfactionSupplyInfo(SatisfactionNpc npc)
|
||||
{
|
||||
QuestId = new SatisfactionSupplyNpcId((ushort)npc.RowId);
|
||||
Name = npc.Npc.Value!.Singular;
|
||||
IssuerDataId = npc.Npc.Row;
|
||||
Level = npc.LevelUnlock;
|
||||
}
|
||||
|
||||
public ElementId QuestId { get; }
|
||||
public string Name { get; }
|
||||
public uint IssuerDataId { get; }
|
||||
public bool IsRepeatable => true;
|
||||
public ushort Level { get; }
|
||||
public EBeastTribe BeastTribe => EBeastTribe.None;
|
||||
public bool IsMainScenarioQuest => false;
|
||||
}
|
@ -43,7 +43,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
IChatGui chatGui,
|
||||
ICommandManager commandManager,
|
||||
IAddonLifecycle addonLifecycle,
|
||||
IKeyState keyState)
|
||||
IKeyState keyState,
|
||||
IContextMenu contextMenu)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pluginInterface);
|
||||
|
||||
@ -66,6 +67,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton(commandManager);
|
||||
serviceCollection.AddSingleton(addonLifecycle);
|
||||
serviceCollection.AddSingleton(keyState);
|
||||
serviceCollection.AddSingleton(contextMenu);
|
||||
serviceCollection.AddSingleton(new WindowSystem(nameof(Questionable)));
|
||||
serviceCollection.AddSingleton((Configuration?)pluginInterface.GetPluginConfig() ?? new Configuration());
|
||||
|
||||
@ -81,6 +83,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
_serviceProvider.GetRequiredService<QuestRegistry>().Reload();
|
||||
_serviceProvider.GetRequiredService<CommandHandler>();
|
||||
_serviceProvider.GetRequiredService<ContextMenuController>();
|
||||
_serviceProvider.GetRequiredService<DalamudInitializer>();
|
||||
}
|
||||
|
||||
@ -156,6 +159,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<NavigationShortcutController>();
|
||||
serviceCollection.AddSingleton<CombatController>();
|
||||
serviceCollection.AddSingleton<GatheringController>();
|
||||
serviceCollection.AddSingleton<ContextMenuController>();
|
||||
|
||||
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ internal sealed class AethernetShortcutValidator : IQuestValidator
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
return quest.AllSteps()
|
||||
.Select(x => Validate(quest.QuestElementId, x.Sequence.Sequence, x.StepId, x.Step.AethernetShortcut))
|
||||
.Select(x => Validate(quest.Id, x.Sequence.Sequence, x.StepId, x.Step.AethernetShortcut))
|
||||
.Where(x => x != null)
|
||||
.Cast<ValidationIssue>();
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = 0,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingSequence0,
|
||||
@ -28,7 +28,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (quest.Info.CompletesInstantly)
|
||||
if (quest.Info is QuestInfo { CompletesInstantly: true })
|
||||
{
|
||||
foreach (var sequence in sequences)
|
||||
{
|
||||
@ -37,7 +37,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)sequence.Sequence,
|
||||
Step = null,
|
||||
Type = EIssueType.InstantQuestWithMultipleSteps,
|
||||
@ -46,7 +46,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (quest.Info is QuestInfo)
|
||||
{
|
||||
int maxSequence = sequences.Select(x => x.Sequence)
|
||||
.Where(x => x != 255)
|
||||
@ -73,7 +73,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)sequenceNo,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingSequence,
|
||||
@ -85,7 +85,7 @@ internal sealed class BasicSequenceValidator : IQuestValidator
|
||||
{
|
||||
return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)sequenceNo,
|
||||
Step = null,
|
||||
Type = EIssueType.DuplicateSequence,
|
||||
|
@ -45,7 +45,7 @@ internal sealed class CompletionFlagsValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)sequence.Sequence,
|
||||
Step = i,
|
||||
Type = EIssueType.DuplicateCompletionFlags,
|
||||
|
@ -25,7 +25,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
|
||||
{
|
||||
_questSchema ??= JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result;
|
||||
|
||||
if (_questNodes.TryGetValue(quest.QuestElementId, out JsonNode? questNode))
|
||||
if (_questNodes.TryGetValue(quest.Id, out JsonNode? questNode))
|
||||
{
|
||||
var evaluationResult = _questSchema.Evaluate(questNode, new EvaluationOptions
|
||||
{
|
||||
@ -36,7 +36,7 @@ internal sealed class JsonSchemaValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSchema,
|
||||
|
@ -8,11 +8,11 @@ internal sealed class NextQuestValidator : IQuestValidator
|
||||
{
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
foreach (var invalidNextQuest in quest.AllSteps().Where(x => x.Step.NextQuestId == quest.QuestElementId))
|
||||
foreach (var invalidNextQuest in quest.AllSteps().Where(x => x.Step.NextQuestId == quest.Id))
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)invalidNextQuest.Sequence.Sequence,
|
||||
Step = invalidNextQuest.StepId,
|
||||
Type = EIssueType.InvalidNextQuestId,
|
||||
|
@ -11,7 +11,7 @@ internal sealed class QuestDisabledValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.QuestDisabled,
|
||||
|
@ -9,6 +9,9 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
if (quest.Id is SatisfactionSupplyNpcId)
|
||||
yield break;
|
||||
|
||||
var questAccepts = FindQuestStepsWithInteractionType(quest, EInteractionType.AcceptQuest)
|
||||
.Where(x => x.Step.PickUpQuestId == null)
|
||||
.ToList();
|
||||
@ -18,7 +21,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)accept.Sequence.Sequence,
|
||||
Step = accept.StepId,
|
||||
Type = EIssueType.UnexpectedAcceptQuestStep,
|
||||
@ -32,7 +35,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = 0,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingQuestAccept,
|
||||
@ -50,7 +53,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = (byte)complete.Sequence.Sequence,
|
||||
Step = complete.StepId,
|
||||
Type = EIssueType.UnexpectedCompleteQuestStep,
|
||||
@ -64,7 +67,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
QuestId = quest.QuestElementId,
|
||||
QuestId = quest.Id,
|
||||
Sequence = 255,
|
||||
Step = null,
|
||||
Type = EIssueType.MissingQuestComplete,
|
||||
|
@ -103,7 +103,7 @@ internal sealed class DebugOverlay : Window
|
||||
QuestStep? step = sequence.FindStep(i);
|
||||
if (step != null && TryGetPosition(step, out Vector3? position))
|
||||
{
|
||||
DrawStep($"{quest.QuestElementId} / {sequence.Sequence} / {i}", step, position.Value, 0xFFFFFFFF);
|
||||
DrawStep($"{quest.Id} / {sequence.Sequence} / {i}", step, position.Value, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
|
||||
if (ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
|
||||
{
|
||||
_commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString(), commandInfo);
|
||||
_commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty, commandInfo);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
|
@ -58,7 +58,7 @@ internal sealed class ActiveQuestComponent
|
||||
{
|
||||
var currentQuestDetails = _questController.CurrentQuestDetails;
|
||||
QuestController.QuestProgress? currentQuest = currentQuestDetails?.Progress;
|
||||
QuestController.CurrentQuestType? currentQuestType = currentQuestDetails?.Type;
|
||||
QuestController.ECurrentQuestType? currentQuestType = currentQuestDetails?.Type;
|
||||
if (currentQuest != null)
|
||||
{
|
||||
DrawQuestNames(currentQuest, currentQuestType);
|
||||
@ -108,9 +108,9 @@ internal sealed class ActiveQuestComponent
|
||||
}
|
||||
|
||||
private void DrawQuestNames(QuestController.QuestProgress currentQuest,
|
||||
QuestController.CurrentQuestType? currentQuestType)
|
||||
QuestController.ECurrentQuestType? currentQuestType)
|
||||
{
|
||||
if (currentQuestType == QuestController.CurrentQuestType.Simulated)
|
||||
if (currentQuestType == QuestController.ECurrentQuestType.Simulated)
|
||||
{
|
||||
using var _ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
|
||||
ImGui.TextUnformatted(
|
||||
@ -151,7 +151,7 @@ internal sealed class ActiveQuestComponent
|
||||
|
||||
private QuestWork? DrawQuestWork(QuestController.QuestProgress currentQuest)
|
||||
{
|
||||
if (currentQuest.Quest.QuestElementId is not QuestId questId)
|
||||
if (currentQuest.Quest.Id is not QuestId questId)
|
||||
return null;
|
||||
|
||||
var questWork = _gameFunctions.GetQuestEx(questId);
|
||||
@ -210,7 +210,7 @@ internal sealed class ActiveQuestComponent
|
||||
{
|
||||
using var disabled = ImRaii.Disabled();
|
||||
|
||||
if (currentQuest.Quest.QuestElementId == _questController.NextQuest?.Quest.QuestElementId)
|
||||
if (currentQuest.Quest.Id == _questController.NextQuest?.Quest.Id)
|
||||
ImGui.TextUnformatted("(Next quest in story line not accepted)");
|
||||
else
|
||||
ImGui.TextUnformatted("(Not accepted)");
|
||||
@ -229,14 +229,14 @@ internal sealed class ActiveQuestComponent
|
||||
if (questWork == null)
|
||||
_questController.SetNextQuest(currentQuest.Quest);
|
||||
|
||||
_questController.ExecuteNextStep(true);
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step"))
|
||||
{
|
||||
_questController.ExecuteNextStep(false);
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.Manual);
|
||||
}
|
||||
|
||||
ImGui.EndDisabled();
|
||||
@ -262,7 +262,7 @@ internal sealed class ActiveQuestComponent
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
|
||||
{
|
||||
_movementController.Stop();
|
||||
_questController.Skip(currentQuest.Quest.QuestElementId, currentQuest.Sequence);
|
||||
_questController.Skip(currentQuest.Quest.Id, currentQuest.Sequence);
|
||||
}
|
||||
|
||||
if (colored)
|
||||
@ -274,7 +274,7 @@ internal sealed class ActiveQuestComponent
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Atlas))
|
||||
_commandManager.DispatchCommand("/questinfo",
|
||||
currentQuest.Quest.QuestElementId.ToString() ?? string.Empty, commandInfo);
|
||||
currentQuest.Quest.Id.ToString() ?? string.Empty, commandInfo);
|
||||
}
|
||||
|
||||
bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
|
||||
|
@ -6,6 +6,7 @@ using ImGuiNET;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
|
||||
@ -31,6 +32,12 @@ internal sealed class QuestTooltipComponent
|
||||
_uiUtils = uiUtils;
|
||||
}
|
||||
|
||||
public void Draw(IQuestInfo quest)
|
||||
{
|
||||
if (quest is QuestInfo questInfo)
|
||||
Draw(questInfo);
|
||||
}
|
||||
|
||||
public void Draw(QuestInfo quest)
|
||||
{
|
||||
using var tooltip = ImRaii.Tooltip();
|
||||
@ -93,8 +100,8 @@ internal sealed class QuestTooltipComponent
|
||||
|
||||
_uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon);
|
||||
|
||||
if (counter <= 2 || icon != FontAwesomeIcon.Check)
|
||||
DrawQuestUnlocks(qInfo, counter + 1);
|
||||
if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
|
||||
DrawQuestUnlocks(qstInfo, counter + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +169,7 @@ internal sealed class QuestTooltipComponent
|
||||
ImGui.Unindent();
|
||||
}
|
||||
|
||||
private static string FormatQuestUnlockName(QuestInfo questInfo)
|
||||
private static string FormatQuestUnlockName(IQuestInfo questInfo)
|
||||
{
|
||||
if (questInfo.IsMainScenarioQuest)
|
||||
return $"{questInfo.Name} ({questInfo.QuestId}, MSQ)";
|
||||
|
@ -39,8 +39,8 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
private readonly UiUtils _uiUtils;
|
||||
private readonly QuestTooltipComponent _questTooltipComponent;
|
||||
|
||||
private List<QuestInfo> _quests = [];
|
||||
private List<QuestInfo> _offeredQuests = [];
|
||||
private List<IQuestInfo> _quests = [];
|
||||
private List<IQuestInfo> _offeredQuests = [];
|
||||
private bool _onlyAvailableQuests = true;
|
||||
|
||||
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
|
||||
@ -105,7 +105,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
|
||||
_quests = _questRegistry.AllQuests
|
||||
.Where(x => x.FindSequence(0)?.FindStep(0)?.TerritoryId == territoryId)
|
||||
.Select(x => _questData.GetQuestInfo(x.QuestElementId))
|
||||
.Select(x => _questData.GetQuestInfo(x.Id))
|
||||
.ToList();
|
||||
|
||||
foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers)
|
||||
@ -157,11 +157,11 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, actionIconSize);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (QuestInfo quest in (_offeredQuests.Count != 0 && _onlyAvailableQuests) ? _offeredQuests : _quests)
|
||||
foreach (IQuestInfo quest in (_offeredQuests.Count != 0 && _onlyAvailableQuests) ? _offeredQuests : _quests)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
|
||||
string questId = quest.QuestId.ToString();
|
||||
string questId = quest.QuestId.ToString() ?? string.Empty;
|
||||
bool isKnownQuest = _questRegistry.TryGetQuest(quest.QuestId, out var knownQuest);
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
@ -228,7 +228,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
if (startNextQuest)
|
||||
{
|
||||
_questController.SetNextQuest(knownQuest);
|
||||
_questController.ExecuteNextStep(true);
|
||||
_questController.ExecuteNextStep(QuestController.EAutomationType.Automatic);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
@ -245,7 +245,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToClipboard(QuestInfo quest, bool suffix)
|
||||
private void CopyToClipboard(IQuestInfo quest, bool suffix)
|
||||
{
|
||||
string fileName = $"{quest.QuestId}_{quest.SimplifiedName}{(suffix ? ".json" : "")}";
|
||||
ImGui.SetClipboardText(fileName);
|
||||
|
@ -22,13 +22,13 @@ internal sealed class UiUtils
|
||||
public (Vector4 color, FontAwesomeIcon icon, string status) GetQuestStyle(ElementId questElementId)
|
||||
{
|
||||
if (_gameFunctions.IsQuestAccepted(questElementId))
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Active");
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
|
||||
else if (_gameFunctions.IsQuestAcceptedOrComplete(questElementId))
|
||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
|
||||
else if (_gameFunctions.IsQuestLocked(questElementId))
|
||||
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
|
||||
else
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Available");
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running, "Available");
|
||||
}
|
||||
|
||||
public static (Vector4 color, FontAwesomeIcon icon) GetInstanceStyle(ushort instanceId)
|
||||
@ -36,7 +36,7 @@ internal sealed class UiUtils
|
||||
if (UIState.IsInstanceContentCompleted(instanceId))
|
||||
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check);
|
||||
else if (UIState.IsInstanceContentUnlocked(instanceId))
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight);
|
||||
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.Running);
|
||||
else
|
||||
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user