muffin v7.5.5
This commit is contained in:
parent
a8f2c1df37
commit
6266f9e6b7
110 changed files with 14907 additions and 6073 deletions
|
|
@ -103,6 +103,7 @@ internal sealed class CommandHandler : IDisposable
|
|||
_chatGui.Print("/qst help-all - displays all available commands", "Questionable", 576);
|
||||
_chatGui.Print("/qst config - opens the configuration window", "Questionable", 576);
|
||||
_chatGui.Print("/qst changelog - opens the changelog window", "Questionable", 576);
|
||||
_chatGui.Print("/qst changelog - opens the changelog window", "Questionable", 576);
|
||||
_chatGui.Print("/qst start - starts doing quests", "Questionable", 576);
|
||||
_chatGui.Print("/qst stop - stops doing quests", "Questionable", 576);
|
||||
_chatGui.Print("/qst reload - reload all quest data", "Questionable", 576);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ namespace Questionable.Controller;
|
|||
|
||||
internal sealed class QuestRegistry
|
||||
{
|
||||
internal sealed record FailedQuestLoad(ElementId QuestId, string? FilePath, string ErrorMessage, Quest.ESource Source);
|
||||
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
private readonly QuestData _questData;
|
||||
|
|
@ -49,8 +51,12 @@ internal sealed class QuestRegistry
|
|||
|
||||
private readonly Dictionary<ElementId, string> _questFolderNames = new Dictionary<ElementId, string>();
|
||||
|
||||
private readonly Dictionary<ElementId, FailedQuestLoad> _failedLoads = new Dictionary<ElementId, FailedQuestLoad>();
|
||||
|
||||
public IEnumerable<Quest> AllQuests => _quests.Values;
|
||||
|
||||
public IReadOnlyDictionary<ElementId, FailedQuestLoad> FailedLoads => _failedLoads;
|
||||
|
||||
public int Count => _quests.Count<KeyValuePair<ElementId, Quest>>((KeyValuePair<ElementId, Quest> x) => !x.Value.Root.Disabled);
|
||||
|
||||
public int ValidationIssueCount => _questValidator.IssueCount;
|
||||
|
|
@ -77,6 +83,7 @@ internal sealed class QuestRegistry
|
|||
{
|
||||
_questValidator.Reset();
|
||||
_quests.Clear();
|
||||
_failedLoads.Clear();
|
||||
_contentFinderConditionIds.Clear();
|
||||
_lowPriorityContentFinderConditionQuests.Clear();
|
||||
_questFolderNames.Clear();
|
||||
|
|
@ -244,7 +251,7 @@ internal sealed class QuestRegistry
|
|||
_questValidator.Validate(_quests.Values.Where((Quest x) => x.Source != Quest.ESource.Assembly).ToList());
|
||||
}
|
||||
|
||||
private void LoadQuestFromStream(string fileName, Stream stream, Quest.ESource source, string directoryName)
|
||||
private void LoadQuestFromStream(string fileName, Stream stream, Quest.ESource source, string directoryName, string? filePath = null)
|
||||
{
|
||||
if (source == Quest.ESource.UserDirectory)
|
||||
{
|
||||
|
|
@ -259,9 +266,26 @@ internal sealed class QuestRegistry
|
|||
try
|
||||
{
|
||||
jsonNode = JsonNode.Parse(stream);
|
||||
string text = FindDuplicateKey(jsonNode);
|
||||
if (text != null)
|
||||
{
|
||||
string errorMessage = "Duplicate JSON property '" + text + "'. Remove the duplicate key.";
|
||||
_questValidator.AddValidationIssue(new ValidationIssue
|
||||
{
|
||||
ElementId = elementId,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSyntax,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"Duplicate JSON property '{text}' in file '{fileName}'. Remove the duplicate key."
|
||||
});
|
||||
_failedLoads[elementId] = new FailedQuestLoad(elementId, filePath, errorMessage, source);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
string errorMessage2 = "JSON syntax error: " + ex.Message;
|
||||
ValidationIssue issue = new ValidationIssue
|
||||
{
|
||||
ElementId = elementId,
|
||||
|
|
@ -269,9 +293,10 @@ internal sealed class QuestRegistry
|
|||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSyntax,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"JSON parsing error in file '{fileName}': {ex.Message}\n\nThis usually indicates a syntax error such as:\n\ufffd Missing comma between properties\n\ufffd Unclosed quotes or brackets\n\ufffd Invalid escape sequences\n\ufffd Trailing commas where not allowed\n\nPlease check the JSON syntax around the indicated position."
|
||||
Description = $"JSON parsing error in file '{fileName}': {ex.Message}\n\nThis usually indicates a syntax error such as:\n■ Missing comma between properties\n■ Unclosed quotes or brackets\n■ Invalid escape sequences\n■ Trailing commas where not allowed\n\nPlease check the JSON syntax around the indicated position."
|
||||
};
|
||||
_questValidator.AddValidationIssue(issue);
|
||||
_failedLoads[elementId] = new FailedQuestLoad(elementId, filePath, errorMessage2, source);
|
||||
return;
|
||||
}
|
||||
_jsonSchemaValidator.Enqueue(elementId, jsonNode);
|
||||
|
|
@ -312,7 +337,26 @@ internal sealed class QuestRegistry
|
|||
}
|
||||
}
|
||||
}
|
||||
QuestRoot root = jsonNode.Deserialize<QuestRoot>();
|
||||
QuestRoot root;
|
||||
try
|
||||
{
|
||||
root = jsonNode.Deserialize<QuestRoot>();
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
string errorMessage3 = "Failed to deserialize quest data: " + ex2.Message;
|
||||
_questValidator.AddValidationIssue(new ValidationIssue
|
||||
{
|
||||
ElementId = elementId,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSchema,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = "Failed to deserialize '" + fileName + "': " + ex2.Message
|
||||
});
|
||||
_failedLoads[elementId] = new FailedQuestLoad(elementId, filePath, errorMessage3, source);
|
||||
return;
|
||||
}
|
||||
if (!_questData.TryGetQuestInfo(elementId, out IQuestInfo questInfo))
|
||||
{
|
||||
if (!(elementId is UnlockLinkId unlockLinkId))
|
||||
|
|
@ -323,9 +367,9 @@ internal sealed class QuestRegistry
|
|||
string name;
|
||||
try
|
||||
{
|
||||
string text = fileName.Substring(0, fileName.Length - ".json".Length);
|
||||
int num = text.IndexOf('_', StringComparison.Ordinal);
|
||||
name = ((num >= 0 && num + 1 < text.Length) ? text.Substring(num + 1) : text);
|
||||
string text2 = fileName.Substring(0, fileName.Length - ".json".Length);
|
||||
int num = text2.IndexOf('_', StringComparison.Ordinal);
|
||||
name = ((num >= 0 && num + 1 < text2.Length) ? text2.Substring(num + 1) : text2);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -398,11 +442,17 @@ internal sealed class QuestRegistry
|
|||
try
|
||||
{
|
||||
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
|
||||
LoadQuestFromStream(fileInfo.Name, stream, source, directory.Name);
|
||||
LoadQuestFromStream(fileInfo.Name, stream, source, directory.Name, fileInfo.FullName);
|
||||
}
|
||||
catch (Exception innerException)
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidDataException("Unable to load file " + fileInfo.FullName, innerException);
|
||||
ElementId elementId = ExtractQuestIdFromName(fileInfo.Name);
|
||||
string errorMessage = "Unexpected error loading file: " + ex.Message;
|
||||
_logger.LogError(ex, "Failed to load quest from '{FileName}', skipping", fileInfo.FullName);
|
||||
if (elementId != null)
|
||||
{
|
||||
_failedLoads[elementId] = new FailedQuestLoad(elementId, fileInfo.FullName, errorMessage, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
DirectoryInfo[] directories = directory.GetDirectories();
|
||||
|
|
@ -412,6 +462,46 @@ internal sealed class QuestRegistry
|
|||
}
|
||||
}
|
||||
|
||||
private static string? FindDuplicateKey(JsonNode? node)
|
||||
{
|
||||
if (node is JsonObject jsonObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var (text2, node2) in jsonObject)
|
||||
{
|
||||
if (!hashSet.Add(text2))
|
||||
{
|
||||
return text2;
|
||||
}
|
||||
string text3 = FindDuplicateKey(node2);
|
||||
if (text3 != null)
|
||||
{
|
||||
return text3;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ArgumentException ex) when (ex.Message.Contains("same key", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Match match = Regex.Match(ex.Message, "Key: (.+)");
|
||||
return match.Success ? match.Groups[1].Value.Trim() : "unknown";
|
||||
}
|
||||
}
|
||||
else if (node is JsonArray jsonArray)
|
||||
{
|
||||
foreach (JsonNode item in jsonArray)
|
||||
{
|
||||
string text4 = FindDuplicateKey(item);
|
||||
if (text4 != null)
|
||||
{
|
||||
return text4;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ElementId? ExtractQuestIdFromName(string resourceName)
|
||||
{
|
||||
string text = resourceName.Substring(0, resourceName.Length - ".json".Length);
|
||||
|
|
@ -428,6 +518,11 @@ internal sealed class QuestRegistry
|
|||
return _quests.ContainsKey(questId);
|
||||
}
|
||||
|
||||
public bool IsFailedLoad(ElementId questId)
|
||||
{
|
||||
return _failedLoads.ContainsKey(questId);
|
||||
}
|
||||
|
||||
public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest)
|
||||
{
|
||||
return _quests.TryGetValue(questId, out quest);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue