Add basic quest validation
This commit is contained in:
parent
320ce14aed
commit
a0e675cbdc
@ -65,16 +65,6 @@
|
|||||||
"TerritoryId": 1187,
|
"TerritoryId": 1187,
|
||||||
"InteractionType": "CompleteQuest",
|
"InteractionType": "CompleteQuest",
|
||||||
"AetheryteShortcut": "Urqopacha - Wachunpelo"
|
"AetheryteShortcut": "Urqopacha - Wachunpelo"
|
||||||
},
|
|
||||||
{
|
|
||||||
"DataId": 1050684,
|
|
||||||
"Position": {
|
|
||||||
"X": 391.37854,
|
|
||||||
"Y": -156.07434,
|
|
||||||
"Z": -388.50995
|
|
||||||
},
|
|
||||||
"TerritoryId": 1187,
|
|
||||||
"InteractionType": "CompleteQuest"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
},
|
},
|
||||||
"StopDistance": 0.5,
|
"StopDistance": 0.5,
|
||||||
"TerritoryId": 1187,
|
"TerritoryId": 1187,
|
||||||
"InteractionType": "AcceptQuest",
|
"InteractionType": "CompleteQuest",
|
||||||
"AetheryteShortcut": "Urqopacha - Wachunpelo",
|
"AetheryteShortcut": "Urqopacha - Wachunpelo",
|
||||||
"Fly": true
|
"Fly": true
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"Z": -52.99463
|
"Z": -52.99463
|
||||||
},
|
},
|
||||||
"TerritoryId": 1186,
|
"TerritoryId": 1186,
|
||||||
"InteractionType": "Interact",
|
"InteractionType": "AcceptQuest",
|
||||||
"Comment": "Quest is completed instantly"
|
"Comment": "Quest is completed instantly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"Z": -38.132385
|
"Z": -38.132385
|
||||||
},
|
},
|
||||||
"TerritoryId": 1186,
|
"TerritoryId": 1186,
|
||||||
"InteractionType": "Interact",
|
"InteractionType": "AcceptQuest",
|
||||||
"Comment": "Quest is completed instantly"
|
"Comment": "Quest is completed instantly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Questionable.Model.V1;
|
using Questionable.Model.V1;
|
||||||
|
|
||||||
#if RELEASE
|
|
||||||
namespace Questionable.QuestPaths;
|
namespace Questionable.QuestPaths;
|
||||||
|
|
||||||
public static partial class AssemblyQuestLoader
|
public static partial class AssemblyQuestLoader
|
||||||
{
|
{
|
||||||
public static IReadOnlyDictionary<ushort, QuestRoot> GetQuests() => Quests;
|
public static IReadOnlyDictionary<ushort, QuestRoot> GetQuests() =>
|
||||||
}
|
#if RELEASE
|
||||||
|
Quests;
|
||||||
|
#else
|
||||||
|
new Dictionary<ushort, QuestRoot>();
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Controller.Utils;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.V1;
|
using Questionable.Model.V1;
|
||||||
@ -18,15 +22,17 @@ internal sealed class QuestRegistry
|
|||||||
{
|
{
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
|
private readonly IChatGui _chatGui;
|
||||||
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, QuestData questData,
|
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData, IChatGui chatGui,
|
||||||
ILogger<QuestRegistry> logger)
|
ILogger<QuestRegistry> logger)
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
|
_chatGui = chatGui;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +42,26 @@ internal sealed class QuestRegistry
|
|||||||
{
|
{
|
||||||
_quests.Clear();
|
_quests.Clear();
|
||||||
|
|
||||||
#if RELEASE
|
LoadQuestsFromAssembly();
|
||||||
|
LoadQuestsFromProjectDirectory();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e,
|
||||||
|
"Failed to load all quests from user directory (some may have been successfully loaded)");
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateQuests();
|
||||||
|
_logger.LogInformation("Loaded {Count} quests", _quests.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("RELEASE")]
|
||||||
|
private void LoadQuestsFromAssembly()
|
||||||
|
{
|
||||||
_logger.LogInformation("Loading quests from assembly");
|
_logger.LogInformation("Loading quests from assembly");
|
||||||
|
|
||||||
foreach ((ushort questId, QuestRoot questRoot) in QuestPaths.AssemblyQuestLoader.GetQuests())
|
foreach ((ushort questId, QuestRoot questRoot) in QuestPaths.AssemblyQuestLoader.GetQuests())
|
||||||
@ -49,7 +74,11 @@ internal sealed class QuestRegistry
|
|||||||
};
|
};
|
||||||
_quests[questId] = quest;
|
_quests[questId] = quest;
|
||||||
}
|
}
|
||||||
#else
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void LoadQuestsFromProjectDirectory()
|
||||||
|
{
|
||||||
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
|
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent;
|
||||||
if (solutionDirectory != null)
|
if (solutionDirectory != null)
|
||||||
{
|
{
|
||||||
@ -75,27 +104,116 @@ internal sealed class QuestRegistry
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void ValidateQuests()
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LoadFromDirectory(new DirectoryInfo(Path.Combine(_pluginInterface.ConfigDirectory.FullName, "Quests")));
|
int foundProblems = 0;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Quest has missing steps: {QuestId} / {QuestName} → {Count}", quest.QuestId,
|
||||||
|
quest.Info.Name, missingSteps);
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalSequenceCount = quest.Root.QuestSequence.Count;
|
||||||
|
var distinctSequenceCount = quest.Root.QuestSequence.Select(x => x.Sequence).Distinct().Count();
|
||||||
|
if (totalSequenceCount != distinctSequenceCount)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Quest has duplicate sequence numbers: {QuestId} / {QuestName}", quest.QuestId,
|
||||||
|
quest.Info.Name);
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var sequence in quest.Root.QuestSequence)
|
||||||
|
{
|
||||||
|
if (sequence.Sequence == 0 &&
|
||||||
|
sequence.Steps.LastOrDefault()?.InteractionType != EInteractionType.AcceptQuest)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Quest likely has AcceptQuest configured wrong: {QuestId} / {QuestName} → {Sequence} / {Step}",
|
||||||
|
quest.QuestId, quest.Info.Name, sequence.Sequence, sequence.Steps.Count - 1);
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
else if (sequence.Sequence == 255 &&
|
||||||
|
sequence.Steps.LastOrDefault()?.InteractionType != EInteractionType.CompleteQuest)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Quest likely has CompleteQuest configured wrong: {QuestId} / {QuestName} → {Sequence} / {Step}",
|
||||||
|
quest.QuestId, quest.Info.Name, sequence.Sequence, sequence.Steps.Count - 1);
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var acceptQuestSteps = sequence.Steps
|
||||||
|
.Where(x => x is { InteractionType: EInteractionType.AcceptQuest, PickupQuestId: null })
|
||||||
|
.Where(x => sequence.Sequence != 0 || x != sequence.Steps.Last());
|
||||||
|
foreach (var step in acceptQuestSteps)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Quest has unexpected AcceptQuest steps: {QuestId} / {QuestName} → {Sequence} / {Step}",
|
||||||
|
quest.QuestId, quest.Info.Name, sequence.Sequence, sequence.Steps.IndexOf(step));
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
|
||||||
|
var completeQuestSteps = sequence.Steps
|
||||||
|
.Where(x => x is { InteractionType: EInteractionType.CompleteQuest, TurnInQuestId: null })
|
||||||
|
.Where(x => sequence.Sequence != 255 || x != sequence.Steps.Last());
|
||||||
|
foreach (var step in completeQuestSteps)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Quest has unexpected CompleteQuest steps: {QuestId} / {QuestName} → {Sequence} / {Step}",
|
||||||
|
quest.QuestId, quest.Info.Name, sequence.Sequence, sequence.Steps.IndexOf(step));
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
|
||||||
|
var completionFlags = sequence.Steps.Select(x => x.CompletionQuestVariablesFlags)
|
||||||
|
.Where(QuestWorkUtils.HasCompletionFlags)
|
||||||
|
.GroupBy(x =>
|
||||||
|
{
|
||||||
|
return Enumerable.Range(0, 6).Select(y =>
|
||||||
|
{
|
||||||
|
short? value = x[y];
|
||||||
|
if (value == null || value.Value < 0)
|
||||||
|
return (long)0;
|
||||||
|
return (long)BitOperations.RotateLeft((ulong)value.Value, 8 * y);
|
||||||
|
})
|
||||||
|
.Sum();
|
||||||
|
})
|
||||||
|
.Where(x => x.Key != 0)
|
||||||
|
.Where(x => x.Count() > 1);
|
||||||
|
foreach (var duplicate in completionFlags)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Quest step has duplicate completion flags: {QuestId} / {QuestName} → {Sequence} → {Flags}",
|
||||||
|
quest.QuestId, quest.Info.Name, sequence.Sequence, string.Join(", ", duplicate.First()));
|
||||||
|
++foundProblems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundProblems > 0)
|
||||||
|
{
|
||||||
|
_chatGui.Print(
|
||||||
|
$"[Questionable] Quest validation has found {foundProblems} problems. Check the log for details.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "Failed to load all quests from user directory (some may have been successfully loaded)");
|
_logger.LogError(e, "Unable to validate quests");
|
||||||
|
_chatGui.PrintError(
|
||||||
|
$"[Questionable] Unable to validate quests. Check the log for details.");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
#if !RELEASE
|
|
||||||
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)
|
|
||||||
_logger.LogWarning("Quest has missing steps: {QuestId} / {QuestName} → {Count}", quest.QuestId, quest.Info.Name, missingSteps);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_logger.LogInformation("Loaded {Count} quests", _quests.Count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user