muffin v7.5.5
This commit is contained in:
parent
a8f2c1df37
commit
6266f9e6b7
110 changed files with 14907 additions and 6073 deletions
|
|
@ -317,6 +317,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||
private unsafe int? HandleListChoice(string? actualPrompt, List<string?> answers, bool checkAllSteps)
|
||||
{
|
||||
List<DialogueChoiceInfo> list = new List<DialogueChoiceInfo>();
|
||||
List<int> list2 = new List<int>();
|
||||
QuestController.QuestProgress questProgress = _questController.SimulatedQuest ?? _questController.GatheringQuest ?? _questController.StartedQuest;
|
||||
if (questProgress != null)
|
||||
{
|
||||
|
|
@ -347,6 +348,15 @@ internal sealed class InteractionUiController : IDisposable
|
|||
{
|
||||
questStep = quest.FindSequence(questProgress.Sequence)?.FindStep(questProgress.Step);
|
||||
}
|
||||
if (questStep == null && questProgress.Step == 255)
|
||||
{
|
||||
QuestSequence questSequence2 = quest.FindSequence(questProgress.Sequence);
|
||||
if (questSequence2 != null && questSequence2.Steps.Count > 0)
|
||||
{
|
||||
List<QuestStep> steps = questSequence2.Steps;
|
||||
questStep = steps[steps.Count - 1];
|
||||
}
|
||||
}
|
||||
if (questStep == null)
|
||||
{
|
||||
_logger.LogDebug("Ignoring current quest dialogue choices, no active step");
|
||||
|
|
@ -371,10 +381,10 @@ internal sealed class InteractionUiController : IDisposable
|
|||
{
|
||||
EAetheryteLocation valueOrDefault = aetheryte.GetValueOrDefault();
|
||||
int num = 1;
|
||||
List<EAetheryteLocation> list2 = new List<EAetheryteLocation>(num);
|
||||
CollectionsMarshal.SetCount(list2, num);
|
||||
CollectionsMarshal.AsSpan(list2)[0] = valueOrDefault;
|
||||
source = list2;
|
||||
List<EAetheryteLocation> list3 = new List<EAetheryteLocation>(num);
|
||||
CollectionsMarshal.SetCount(list3, num);
|
||||
CollectionsMarshal.AsSpan(list3)[0] = valueOrDefault;
|
||||
source = list3;
|
||||
}
|
||||
}
|
||||
flag = questStep.InteractionType == EInteractionType.UnlockTaxiStand;
|
||||
|
|
@ -451,30 +461,30 @@ internal sealed class InteractionUiController : IDisposable
|
|||
{
|
||||
continue;
|
||||
}
|
||||
List<DialogueChoice> list3 = knownQuest.FindSequence(0)?.Steps.SelectMany((QuestStep x) => x.DialogueChoices).ToList();
|
||||
if (list3 != null && list3.Count > 0)
|
||||
List<DialogueChoice> list4 = knownQuest.FindSequence(0)?.Steps.SelectMany((QuestStep x) => x.DialogueChoices).ToList();
|
||||
if (list4 != null && list4.Count > 0)
|
||||
{
|
||||
_logger.LogInformation("Adding {Count} dialogue choices from not accepted quest {QuestName}", list3.Count, item.Name);
|
||||
list.AddRange(list3.Select((DialogueChoice x) => new DialogueChoiceInfo(knownQuest, x)));
|
||||
_logger.LogInformation("Adding {Count} dialogue choices from not accepted quest {QuestName}", list4.Count, item.Name);
|
||||
list.AddRange(list4.Select((DialogueChoice x) => new DialogueChoiceInfo(knownQuest, x)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_fateController.IsRunning)
|
||||
{
|
||||
List<DialogueChoice> list4 = _fateController.CurrentFate?.TransformDialogueChoices;
|
||||
if (list4 != null)
|
||||
List<DialogueChoice> list5 = _fateController.CurrentFate?.TransformDialogueChoices;
|
||||
if (list5 != null)
|
||||
{
|
||||
_logger.LogInformation("Adding {Count} dialogue choices from active FATE", list4.Count);
|
||||
list.AddRange(list4.Select((DialogueChoice x) => new DialogueChoiceInfo(null, x)));
|
||||
_logger.LogInformation("Adding {Count} dialogue choices from active FATE", list5.Count);
|
||||
list.AddRange(list5.Select((DialogueChoice x) => new DialogueChoiceInfo(null, x)));
|
||||
}
|
||||
}
|
||||
if (_seasonalDutyController.IsRunning)
|
||||
{
|
||||
List<DialogueChoice> list5 = _seasonalDutyController.CurrentDuty?.DialogueChoices;
|
||||
if (list5 != null && list5.Count > 0)
|
||||
List<DialogueChoice> list6 = _seasonalDutyController.CurrentDuty?.DialogueChoices;
|
||||
if (list6 != null && list6.Count > 0)
|
||||
{
|
||||
_logger.LogInformation("Adding {Count} dialogue choices from active seasonal duty", list5.Count);
|
||||
list.AddRange(list5.Select((DialogueChoice x) => new DialogueChoiceInfo(null, x)));
|
||||
_logger.LogInformation("Adding {Count} dialogue choices from active seasonal duty", list6.Count);
|
||||
list.AddRange(list6.Select((DialogueChoice x) => new DialogueChoiceInfo(null, x)));
|
||||
}
|
||||
}
|
||||
if (list.Count == 0)
|
||||
|
|
@ -522,19 +532,24 @@ internal sealed class InteractionUiController : IDisposable
|
|||
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}", stringOrRegex);
|
||||
continue;
|
||||
}
|
||||
if (actualPrompt != null && (stringOrRegex == null || !IsMatch(actualPrompt, stringOrRegex)))
|
||||
if (actualPrompt != null && stringOrRegex != null && !IsMatch(actualPrompt, stringOrRegex))
|
||||
{
|
||||
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}", stringOrRegex, actualPrompt);
|
||||
int? num3 = TryFindAnswerMatchIndex(answers, stringOrRegex2, dialogueChoice2.AnswerIsRegularExpression, quest3, dialogueChoice2);
|
||||
if (num3.HasValue)
|
||||
{
|
||||
list2.Add(num3.Value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (dialogueChoice2.AnswerIsRegularExpression && stringOrRegex2 != null)
|
||||
{
|
||||
int? num3 = FindBestRegexMatch(answers, stringOrRegex2, quest3, dialogueChoice2);
|
||||
if (!num3.HasValue)
|
||||
int? num4 = FindBestRegexMatch(answers, stringOrRegex2, quest3, dialogueChoice2);
|
||||
if (!num4.HasValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}' (best regex match)", num3.Value, answers[num3.Value], actualPrompt);
|
||||
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}' (best regex match)", num4.Value, answers[num4.Value], actualPrompt);
|
||||
if (quest3?.Id is SatisfactionSupplyNpcId)
|
||||
{
|
||||
if (_questController.GatheringQuest == null || _questController.GatheringQuest.Sequence == byte.MaxValue)
|
||||
|
|
@ -544,16 +559,16 @@ internal sealed class InteractionUiController : IDisposable
|
|||
_questController.GatheringQuest.SetSequence(1);
|
||||
_questController.StartGatheringQuest("SatisfactionSupply turn in");
|
||||
}
|
||||
return num3.Value;
|
||||
return num4.Value;
|
||||
}
|
||||
for (int num4 = 0; num4 < answers.Count; num4++)
|
||||
for (int num5 = 0; num5 < answers.Count; num5++)
|
||||
{
|
||||
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}", answers[num4], stringOrRegex2);
|
||||
if (!IsMatch(answers[num4], stringOrRegex2))
|
||||
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}", answers[num5], stringOrRegex2);
|
||||
if (!IsMatch(answers[num5], stringOrRegex2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'", num4, answers[num4], actualPrompt);
|
||||
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'", num5, answers[num5], actualPrompt);
|
||||
if (quest3?.Id is SatisfactionSupplyNpcId)
|
||||
{
|
||||
if (_questController.GatheringQuest == null || _questController.GatheringQuest.Sequence == byte.MaxValue)
|
||||
|
|
@ -563,13 +578,39 @@ internal sealed class InteractionUiController : IDisposable
|
|||
_questController.GatheringQuest.SetSequence(1);
|
||||
_questController.StartGatheringQuest("SatisfactionSupply turn in");
|
||||
}
|
||||
return num4;
|
||||
return num5;
|
||||
}
|
||||
}
|
||||
int[] array = list2.Distinct().ToArray();
|
||||
if (array.Length == 1)
|
||||
{
|
||||
_logger.LogWarning("Using answer-only fallback for prompt '{Prompt}'. Selected index {Index}: '{Answer}'", actualPrompt, array[0], answers[array[0]]);
|
||||
return array[0];
|
||||
}
|
||||
_logger.LogInformation("No matching answer found for {Prompt}.", actualPrompt);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? TryFindAnswerMatchIndex(List<string?> answers, StringOrRegex? excelAnswer, bool answerIsRegularExpression, Questionable.Model.Quest? quest, DialogueChoice dialogueChoice)
|
||||
{
|
||||
if (excelAnswer == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (answerIsRegularExpression)
|
||||
{
|
||||
return FindBestRegexMatch(answers, excelAnswer, quest, dialogueChoice);
|
||||
}
|
||||
for (int i = 0; i < answers.Count; i++)
|
||||
{
|
||||
if (IsMatch(answers[i], excelAnswer))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? FindBestRegexMatch(List<string?> answers, StringOrRegex expectedAnswer, Questionable.Model.Quest? quest, DialogueChoice dialogueChoice)
|
||||
{
|
||||
List<int> list = (from x in answers.Select((string answer, int index) => new { answer, index })
|
||||
|
|
@ -731,6 +772,15 @@ internal sealed class InteractionUiController : IDisposable
|
|||
else
|
||||
{
|
||||
QuestStep questStep = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
|
||||
if (questStep == null && currentQuest.Step == 255)
|
||||
{
|
||||
QuestSequence questSequence2 = quest.FindSequence(currentQuest.Sequence);
|
||||
if (questSequence2 != null && questSequence2.Steps.Count > 0)
|
||||
{
|
||||
List<QuestStep> steps = questSequence2.Steps;
|
||||
questStep = steps[steps.Count - 1];
|
||||
}
|
||||
}
|
||||
if (questStep != null && HandleDefaultYesNo(addonSelectYesno, quest, questStep, questStep.DialogueChoices, actualPrompt))
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal static class Duty
|
|||
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
|
||||
{
|
||||
AutoDutyIpc.DutyMode dutyMode = GetDutyMode(step.DutyOptions.ContentFinderConditionId, step.DutyOptions.DutyMode);
|
||||
if (dutyMode == AutoDutyIpc.DutyMode.UnsyncRegular && (step.DutyOptions.DutyMode == EDutyMode.UnsyncParty || (!step.DutyOptions.DutyMode.HasValue && configuration.Duties.DutyModeOverrides.TryGetValue(step.DutyOptions.ContentFinderConditionId, out var value) && value == EDutyMode.UnsyncParty) || (!step.DutyOptions.DutyMode.HasValue && !configuration.Duties.DutyModeOverrides.ContainsKey(step.DutyOptions.ContentFinderConditionId) && configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty)))
|
||||
if (dutyMode == AutoDutyIpc.DutyMode.UnsyncRegular && (step.DutyOptions.DutyMode == EDutyMode.UnsyncParty || (!step.DutyOptions.DutyMode.HasValue && configuration.Duties.DutyModeOverrides.TryGetValue(step.DutyOptions.ContentFinderConditionId, out var value) && value == Configuration.EDutyMode.UnsyncParty) || (!step.DutyOptions.DutyMode.HasValue && !configuration.Duties.DutyModeOverrides.ContainsKey(step.DutyOptions.ContentFinderConditionId) && configuration.Duties.DefaultDutyMode == Configuration.EDutyMode.UnsyncParty)))
|
||||
{
|
||||
yield return new WaitForPartyTask();
|
||||
}
|
||||
|
|
@ -71,6 +71,17 @@ internal static class Duty
|
|||
_ => AutoDutyIpc.DutyMode.Support,
|
||||
};
|
||||
}
|
||||
|
||||
private static AutoDutyIpc.DutyMode ConvertToAutoDutyMode(Configuration.EDutyMode mode)
|
||||
{
|
||||
return mode switch
|
||||
{
|
||||
Configuration.EDutyMode.Support => AutoDutyIpc.DutyMode.Support,
|
||||
Configuration.EDutyMode.UnsyncSolo => AutoDutyIpc.DutyMode.UnsyncRegular,
|
||||
Configuration.EDutyMode.UnsyncParty => AutoDutyIpc.DutyMode.UnsyncRegular,
|
||||
_ => AutoDutyIpc.DutyMode.Support,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record WaitForPartyTask : ITask
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ internal static class Interact
|
|||
{
|
||||
yield break;
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(step.DataId, "step.DataId");
|
||||
if (!step.DataId.HasValue)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
if (sequence.Sequence == 0 && sequence.Steps.IndexOf(step) == 0)
|
||||
{
|
||||
yield return new WaitAtEnd.WaitDelay();
|
||||
|
|
@ -72,22 +75,21 @@ internal static class Interact
|
|||
SkipStepConditions stepIf = skipConditions.StepIf;
|
||||
if (stepIf != null && stepIf.Never)
|
||||
{
|
||||
goto IL_025f;
|
||||
goto IL_025d;
|
||||
}
|
||||
}
|
||||
if (step.InteractionType != EInteractionType.PurchaseItem)
|
||||
{
|
||||
skipMarkerCheck = ((step.DataId == 1052475) ? 1 : 0);
|
||||
goto IL_0260;
|
||||
goto IL_025e;
|
||||
}
|
||||
}
|
||||
goto IL_025f;
|
||||
IL_0260:
|
||||
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId ?? step.GCPurchase?.ItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
|
||||
yield break;
|
||||
IL_025f:
|
||||
goto IL_025d;
|
||||
IL_025d:
|
||||
skipMarkerCheck = 1;
|
||||
goto IL_0260;
|
||||
goto IL_025e;
|
||||
IL_025e:
|
||||
yield return new Task(value, quest, interactionType2, (byte)skipMarkerCheck != 0, step.PickUpItemId ?? step.GCPurchase?.ItemId, step.TaxiStandId, step.SkipConditions?.StepIf, step.CompletionQuestVariablesFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,20 +230,11 @@ internal static class Interact
|
|||
}
|
||||
_needsUnmount = false;
|
||||
}
|
||||
EStatus? completionStatusId = base.Task.CompletionStatusId;
|
||||
if (completionStatusId.HasValue)
|
||||
{
|
||||
EStatus valueOrDefault = completionStatusId.GetValueOrDefault();
|
||||
if (gameFunctions.HasStatus(valueOrDefault))
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
}
|
||||
uint? pickUpItemId = base.Task.PickUpItemId;
|
||||
if (pickUpItemId.HasValue)
|
||||
{
|
||||
uint valueOrDefault2 = pickUpItemId.GetValueOrDefault();
|
||||
if (InventoryManager.Instance()->GetInventoryItemCount(valueOrDefault2, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
|
||||
uint valueOrDefault = pickUpItemId.GetValueOrDefault();
|
||||
if (InventoryManager.Instance()->GetInventoryItemCount(valueOrDefault, isHq: false, checkEquipped: true, checkArmory: true, 0) > 0)
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
|
@ -251,8 +244,8 @@ internal static class Interact
|
|||
byte? taxiStandId = base.Task.TaxiStandId;
|
||||
if (taxiStandId.HasValue)
|
||||
{
|
||||
byte valueOrDefault3 = taxiStandId.GetValueOrDefault();
|
||||
if (UIState.Instance()->IsChocoboTaxiStandUnlocked((uint)(valueOrDefault3 + 1179648)))
|
||||
byte valueOrDefault2 = taxiStandId.GetValueOrDefault();
|
||||
if (UIState.Instance()->IsChocoboTaxiStandUnlocked((uint)(valueOrDefault2 + 1179648)))
|
||||
{
|
||||
return ETaskResult.TaskComplete;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ internal static class MoveTo
|
|||
}
|
||||
if (step.Position.HasValue)
|
||||
{
|
||||
return CreateMoveTasks(step, step.Position.Value);
|
||||
List<LandingZone> landingZones = step.LandingZones;
|
||||
Vector3 destination = ((landingZones == null || landingZones.Count <= 0) ? step.Position.Value : LandingZoneMath.CalculateLandingLocation(step.LandingZones));
|
||||
return CreateMoveTasks(step, destination);
|
||||
}
|
||||
if (step != null && step.DataId.HasValue && step.StopDistance.HasValue)
|
||||
{
|
||||
|
|
@ -46,13 +48,13 @@ internal static class MoveTo
|
|||
{
|
||||
valueOrDefault = aetheryte.GetValueOrDefault();
|
||||
flag = true;
|
||||
goto IL_00e2;
|
||||
goto IL_0108;
|
||||
}
|
||||
}
|
||||
}
|
||||
flag = false;
|
||||
goto IL_00e2;
|
||||
IL_00e2:
|
||||
goto IL_0108;
|
||||
IL_0108:
|
||||
if (flag)
|
||||
{
|
||||
return CreateMoveTasks(step, aetheryteData.Locations[valueOrDefault]);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
|||
using LLib.GameData;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller.Utils;
|
||||
|
||||
|
|
@ -145,9 +144,9 @@ internal sealed class PartyWatchdog : IDisposable
|
|||
}
|
||||
if (_configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
|
||||
{
|
||||
return value == EDutyMode.UnsyncParty;
|
||||
return value == Configuration.EDutyMode.UnsyncParty;
|
||||
}
|
||||
return _configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty;
|
||||
return _configuration.Duties.DefaultDutyMode == Configuration.EDutyMode.UnsyncParty;
|
||||
}
|
||||
|
||||
private void StopIfRunning(string reason)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -700,11 +700,11 @@ internal sealed class QuestionableIpc : IDisposable
|
|||
|
||||
private bool SetDefaultDutyMode(int dutyMode)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(EDutyMode), dutyMode))
|
||||
if (!Enum.IsDefined(typeof(Configuration.EDutyMode), dutyMode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_configuration.Duties.DefaultDutyMode = (EDutyMode)dutyMode;
|
||||
_configuration.Duties.DefaultDutyMode = (Configuration.EDutyMode)dutyMode;
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -720,11 +720,11 @@ internal sealed class QuestionableIpc : IDisposable
|
|||
|
||||
private bool SetDutyModeOverride(uint contentFinderConditionId, int dutyMode)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(EDutyMode), dutyMode))
|
||||
if (!Enum.IsDefined(typeof(Configuration.EDutyMode), dutyMode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_configuration.Duties.DutyModeOverrides[contentFinderConditionId] = (EDutyMode)dutyMode;
|
||||
_configuration.Duties.DutyModeOverrides[contentFinderConditionId] = (Configuration.EDutyMode)dutyMode;
|
||||
_pluginInterface.SavePluginConfig(_configuration);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,13 +123,17 @@ internal sealed class GameFunctions
|
|||
|
||||
public IGameObject? FindObjectByDataId(uint dataId, Dalamud.Game.ClientState.Objects.Enums.ObjectKind? kind = null, bool warnIfMissing = true)
|
||||
{
|
||||
foreach (IGameObject item in _objectTable)
|
||||
for (int i = 0; i < _objectTable.Length; i++)
|
||||
{
|
||||
Dalamud.Game.ClientState.Objects.Enums.ObjectKind objectKind = item.ObjectKind;
|
||||
bool flag = ((objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Pc || objectKind - 8 <= Dalamud.Game.ClientState.Objects.Enums.ObjectKind.BattleNpc || objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.HousingEventObject) ? true : false);
|
||||
if (!flag && (item == null || item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.GatheringPoint || item.IsTargetable) && item.BaseId == dataId && (!kind.HasValue || kind.Value == item.ObjectKind))
|
||||
IGameObject gameObject = _objectTable[i];
|
||||
if (gameObject != null)
|
||||
{
|
||||
return item;
|
||||
Dalamud.Game.ClientState.Objects.Enums.ObjectKind objectKind = gameObject.ObjectKind;
|
||||
bool flag = ((objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Pc || objectKind - 8 <= Dalamud.Game.ClientState.Objects.Enums.ObjectKind.BattleNpc || objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.HousingEventObject) ? true : false);
|
||||
if (!flag && (gameObject == null || gameObject.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.GatheringPoint || gameObject.IsTargetable) && gameObject.BaseId == dataId && (!kind.HasValue || kind.Value == gameObject.ObjectKind))
|
||||
{
|
||||
return gameObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (warnIfMissing)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ internal sealed class Quest
|
|||
|
||||
public QuestSequence? FindSequence(byte currentSequence)
|
||||
{
|
||||
return Root.QuestSequence.SingleOrDefault((QuestSequence seq) => seq.Sequence == currentSequence);
|
||||
return Root.QuestSequence.FirstOrDefault((QuestSequence seq) => seq.Sequence == currentSequence);
|
||||
}
|
||||
|
||||
public IEnumerable<QuestSequence> AllSequences()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Text.RegularExpressions;
|
||||
using Json.Schema;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
|
@ -15,8 +20,21 @@ namespace Questionable.Validation.Validators;
|
|||
|
||||
internal sealed class JsonSchemaValidator : IQuestValidator
|
||||
{
|
||||
private const string QuestSchemaUri = "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/QuestPaths/quest-v1.json";
|
||||
|
||||
private readonly Dictionary<ElementId, JsonNode> _questNodes = new Dictionary<ElementId, JsonNode>();
|
||||
|
||||
private static readonly JsonSerializerOptions ValidationSerializationOptions = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
|
||||
IncludeFields = false,
|
||||
WriteIndented = false,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver
|
||||
{
|
||||
Modifiers = { (Action<JsonTypeInfo>)NoEmptyCollectionModifier }
|
||||
}
|
||||
};
|
||||
|
||||
private JsonSchema? _questSchema;
|
||||
|
||||
public JsonSchemaValidator()
|
||||
|
|
@ -100,58 +118,205 @@ internal sealed class JsonSchemaValidator : IQuestValidator
|
|||
{
|
||||
yield break;
|
||||
}
|
||||
EvaluationResults evaluationResults = _questSchema.Evaluate(value, new EvaluationOptions
|
||||
EvaluationResults evaluationResults = null;
|
||||
ValidationIssue validationIssue = null;
|
||||
try
|
||||
{
|
||||
Culture = CultureInfo.InvariantCulture,
|
||||
OutputFormat = OutputFormat.List
|
||||
});
|
||||
if (evaluationResults.IsValid)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
var array = (from r in GetInvalidResults(evaluationResults).ToArray()
|
||||
group r by r.InstanceLocation?.ToString() ?? "<root>").Select(delegate(IGrouping<string, EvaluationResults> g)
|
||||
{
|
||||
string[] messages = (from m in g.SelectMany((EvaluationResults r) => r.Errors?.Values ?? Enumerable.Empty<string>())
|
||||
where !string.IsNullOrWhiteSpace(m)
|
||||
select m.Trim()).Distinct().ToArray();
|
||||
return new
|
||||
evaluationResults = _questSchema.Evaluate(value, new EvaluationOptions
|
||||
{
|
||||
Path = g.Key,
|
||||
Messages = messages
|
||||
};
|
||||
}).ToArray();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine("JSON Validation failed:");
|
||||
if (array.Length == 0)
|
||||
Culture = CultureInfo.InvariantCulture,
|
||||
OutputFormat = OutputFormat.List
|
||||
});
|
||||
}
|
||||
catch (ArgumentException ex) when (ex.Message.Contains("same key", StringComparison.Ordinal))
|
||||
{
|
||||
stringBuilder.AppendLine(" - <unknown>: validation failed");
|
||||
Match match = Regex.Match(ex.Message, "Key: (.+)");
|
||||
string text = (match.Success ? match.Groups[1].Value.Trim() : "unknown");
|
||||
validationIssue = new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSyntax,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = "Duplicate JSON property '" + text + "' in quest file. Remove the duplicate key."
|
||||
};
|
||||
}
|
||||
if (validationIssue != null)
|
||||
{
|
||||
yield return validationIssue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var array2 = array;
|
||||
foreach (var anon in array2)
|
||||
if (evaluationResults == null || evaluationResults.IsValid)
|
||||
{
|
||||
string value2 = ((anon.Messages.Length != 0) ? string.Join("; ", anon.Messages) : "validation failed");
|
||||
StringBuilder stringBuilder2 = stringBuilder;
|
||||
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
|
||||
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(5, 2, stringBuilder2, invariantCulture);
|
||||
handler.AppendLiteral(" - ");
|
||||
handler.AppendFormatted(anon.Path);
|
||||
handler.AppendLiteral(": ");
|
||||
handler.AppendFormatted(value2);
|
||||
stringBuilder2.AppendLine(invariantCulture, ref handler);
|
||||
yield break;
|
||||
}
|
||||
var array = (from r in GetInvalidResults(evaluationResults).ToArray()
|
||||
where !(r.EvaluationPath?.ToString() ?? string.Empty).Contains("/if", StringComparison.Ordinal)
|
||||
group r by r.InstanceLocation?.ToString() ?? "<root>").Select(delegate(IGrouping<string, EvaluationResults> g)
|
||||
{
|
||||
string[] messages = (from m in g.SelectMany((EvaluationResults r) => r.Errors?.Values ?? Enumerable.Empty<string>())
|
||||
where !string.IsNullOrWhiteSpace(m)
|
||||
select m.Trim()).Distinct().ToArray();
|
||||
return new
|
||||
{
|
||||
Path = g.Key,
|
||||
Messages = messages
|
||||
};
|
||||
}).ToArray();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine("JSON Validation failed:");
|
||||
if (array.Length == 0)
|
||||
{
|
||||
stringBuilder.AppendLine(" - <unknown>: validation failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
var array2 = array;
|
||||
foreach (var anon in array2)
|
||||
{
|
||||
string value2 = ((anon.Messages.Length != 0) ? string.Join("; ", anon.Messages) : "validation failed");
|
||||
StringBuilder stringBuilder2 = stringBuilder;
|
||||
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
|
||||
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(5, 2, stringBuilder2, invariantCulture);
|
||||
handler.AppendLiteral(" - ");
|
||||
handler.AppendFormatted(anon.Path);
|
||||
handler.AppendLiteral(": ");
|
||||
handler.AppendFormatted(value2);
|
||||
stringBuilder2.AppendLine(invariantCulture, ref handler);
|
||||
}
|
||||
}
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSchema,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = stringBuilder.ToString().TrimEnd()
|
||||
};
|
||||
}
|
||||
static IEnumerable<EvaluationResults> GetInvalidResults(EvaluationResults result)
|
||||
{
|
||||
if (!result.IsValid)
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
if (result.HasDetails)
|
||||
{
|
||||
foreach (EvaluationResults detail in result.Details)
|
||||
{
|
||||
foreach (EvaluationResults invalidResult in GetInvalidResults(detail))
|
||||
{
|
||||
yield return invalidResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return new ValidationIssue
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationIssue> ValidateFromQuestRoot(Quest quest)
|
||||
{
|
||||
if (!(JsonSerializer.SerializeToNode(quest.Root, ValidationSerializationOptions) is JsonObject jsonObject))
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSchema,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = stringBuilder.ToString().TrimEnd()
|
||||
};
|
||||
return Array.Empty<ValidationIssue>();
|
||||
}
|
||||
JsonObject jsonObject2 = new JsonObject { ["$schema"] = "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/QuestPaths/quest-v1.json" };
|
||||
foreach (KeyValuePair<string, JsonNode> item in jsonObject)
|
||||
{
|
||||
item.Deconstruct(out var key, out var value);
|
||||
string propertyName = key;
|
||||
jsonObject2[propertyName] = value?.DeepClone();
|
||||
}
|
||||
return EvaluateQuestNode(quest, jsonObject2);
|
||||
}
|
||||
|
||||
private IEnumerable<ValidationIssue> EvaluateQuestNode(Quest quest, JsonNode questNode)
|
||||
{
|
||||
if (_questSchema == null)
|
||||
{
|
||||
_questSchema = JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result;
|
||||
}
|
||||
EvaluationResults evaluationResults = null;
|
||||
ValidationIssue validationIssue = null;
|
||||
try
|
||||
{
|
||||
evaluationResults = _questSchema.Evaluate(questNode, new EvaluationOptions
|
||||
{
|
||||
Culture = CultureInfo.InvariantCulture,
|
||||
OutputFormat = OutputFormat.List
|
||||
});
|
||||
}
|
||||
catch (ArgumentException ex) when (ex.Message.Contains("same key", StringComparison.Ordinal))
|
||||
{
|
||||
Match match = Regex.Match(ex.Message, "Key: (.+)");
|
||||
string text = (match.Success ? match.Groups[1].Value.Trim() : "unknown");
|
||||
validationIssue = new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSyntax,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = "Duplicate JSON property '" + text + "' in quest file. Remove the duplicate key."
|
||||
};
|
||||
}
|
||||
if (validationIssue != null)
|
||||
{
|
||||
yield return validationIssue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evaluationResults == null || evaluationResults.IsValid)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
var array = (from r in GetInvalidResults(evaluationResults).ToArray()
|
||||
where !(r.EvaluationPath?.ToString() ?? string.Empty).Contains("/if", StringComparison.Ordinal)
|
||||
group r by r.InstanceLocation?.ToString() ?? "<root>").Select(delegate(IGrouping<string, EvaluationResults> g)
|
||||
{
|
||||
string[] messages = (from m in g.SelectMany((EvaluationResults r) => r.Errors?.Values ?? Enumerable.Empty<string>())
|
||||
where !string.IsNullOrWhiteSpace(m)
|
||||
select m.Trim()).Distinct().ToArray();
|
||||
return new
|
||||
{
|
||||
Path = g.Key,
|
||||
Messages = messages
|
||||
};
|
||||
}).ToArray();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine("JSON Validation failed:");
|
||||
if (array.Length == 0)
|
||||
{
|
||||
stringBuilder.AppendLine(" - <unknown>: validation failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
var array2 = array;
|
||||
foreach (var anon in array2)
|
||||
{
|
||||
string value = ((anon.Messages.Length != 0) ? string.Join("; ", anon.Messages) : "validation failed");
|
||||
StringBuilder stringBuilder2 = stringBuilder;
|
||||
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
|
||||
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(5, 2, stringBuilder2, invariantCulture);
|
||||
handler.AppendLiteral(" - ");
|
||||
handler.AppendFormatted(anon.Path);
|
||||
handler.AppendLiteral(": ");
|
||||
handler.AppendFormatted(value);
|
||||
stringBuilder2.AppendLine(invariantCulture, ref handler);
|
||||
}
|
||||
}
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.InvalidJsonSchema,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = stringBuilder.ToString().TrimEnd()
|
||||
};
|
||||
}
|
||||
static IEnumerable<EvaluationResults> GetInvalidResults(EvaluationResults result)
|
||||
{
|
||||
if (!result.IsValid)
|
||||
|
|
@ -180,4 +345,15 @@ internal sealed class JsonSchemaValidator : IQuestValidator
|
|||
{
|
||||
_questNodes.Clear();
|
||||
}
|
||||
|
||||
private static void NoEmptyCollectionModifier(JsonTypeInfo typeInfo)
|
||||
{
|
||||
foreach (JsonPropertyInfo property in typeInfo.Properties)
|
||||
{
|
||||
if (typeof(ICollection).IsAssignableFrom(property.PropertyType))
|
||||
{
|
||||
property.ShouldSerialize = (object _, object? val) => val is ICollection collection && collection.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Validation.Validators;
|
||||
|
||||
internal sealed class LandingZoneValidator : IQuestValidator
|
||||
{
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
foreach (var stepInfo in quest.AllSteps().Where<(QuestSequence, int, QuestStep)>(delegate((QuestSequence Sequence, int StepId, QuestStep Step) x)
|
||||
{
|
||||
List<LandingZone> landingZones = x.Step.LandingZones;
|
||||
return landingZones != null && landingZones.Count > 0;
|
||||
}))
|
||||
{
|
||||
for (int z = 0; z < stepInfo.Item3.LandingZones.Count; z++)
|
||||
{
|
||||
LandingZone zone = stepInfo.Item3.LandingZones[z];
|
||||
if (zone.Vertices.Count < 3)
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = stepInfo.Item1.Sequence,
|
||||
Step = stepInfo.Item2,
|
||||
Type = EIssueType.InvalidLandingZone,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"Landing zone {z} has fewer than 3 vertices"
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if (!LandingZoneMath.IsConvex(zone.Vertices))
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = stepInfo.Item1.Sequence,
|
||||
Step = stepInfo.Item2,
|
||||
Type = EIssueType.InvalidLandingZone,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"Landing zone {z} is not convex"
|
||||
};
|
||||
}
|
||||
if (LandingZoneMath.CalculatePolygonArea(zone.Vertices) < 0.01f)
|
||||
{
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = stepInfo.Item1.Sequence,
|
||||
Step = stepInfo.Item2,
|
||||
Type = EIssueType.InvalidLandingZone,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"Landing zone {z} has zero area (collinear vertices)"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,6 @@ namespace Questionable.Validation;
|
|||
internal enum EIssueSeverity
|
||||
{
|
||||
None,
|
||||
Error
|
||||
Error,
|
||||
Warning
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,5 +21,6 @@ public enum EIssueType
|
|||
ClassQuestWithoutAetheryteShortcut,
|
||||
DuplicateSinglePlayerInstance,
|
||||
UnusedSinglePlayerInstance,
|
||||
InvalidChatMessage
|
||||
InvalidChatMessage,
|
||||
InvalidLandingZone
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Questionable.Validation.Validators;
|
||||
|
||||
namespace Questionable.Validation;
|
||||
|
||||
|
|
@ -111,4 +112,24 @@ internal sealed class QuestValidator
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
public List<ValidationIssue> ValidateQuestSync(Quest quest)
|
||||
{
|
||||
List<ValidationIssue> list = new List<ValidationIssue>();
|
||||
foreach (IQuestValidator validator in _validators)
|
||||
{
|
||||
foreach (ValidationIssue item in (validator is JsonSchemaValidator jsonSchemaValidator) ? jsonSchemaValidator.ValidateFromQuestRoot(quest) : validator.Validate(quest))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
return (from x in list
|
||||
orderby x.Sequence, x.Step, x.Description
|
||||
select x).ToList();
|
||||
}
|
||||
|
||||
public void ClearIssuesForQuest(ElementId questId)
|
||||
{
|
||||
_validationIssues.RemoveAll((ValidationIssue issue) => issue.ElementId == questId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,15 +62,23 @@ internal sealed class DebugConfigComponent : ConfigComponent
|
|||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker("The Party Watchdog stops Questionable when entering certain zones with other party members, or when entering unsupported content. Disabling this allows Questionable to continue working while in a party, but may cause unexpected behavior in group content.");
|
||||
bool v6 = base.Configuration.Advanced.ShowWindowInInstances;
|
||||
if (ImGui.Checkbox("Show quest window in instances (ignore 'Hide in all instances' setting)", ref v6))
|
||||
{
|
||||
base.Configuration.Advanced.ShowWindowInInstances = v6;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker("When enabled, the quest window will always be shown in instanced duties, ignoring the 'Hide quest window in all instanced duties' setting in General.");
|
||||
ImGui.Separator();
|
||||
ImGui.Text("AutoDuty Settings");
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
bool v6 = base.Configuration.Advanced.DisableAutoDutyBareMode;
|
||||
if (ImGui.Checkbox("Use Pre-Loop/Loop/Post-Loop settings", ref v6))
|
||||
bool v7 = base.Configuration.Advanced.DisableAutoDutyBareMode;
|
||||
if (ImGui.Checkbox("Use Pre-Loop/Loop/Post-Loop settings", ref v7))
|
||||
{
|
||||
base.Configuration.Advanced.DisableAutoDutyBareMode = v6;
|
||||
base.Configuration.Advanced.DisableAutoDutyBareMode = v7;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
|
|
@ -80,42 +88,42 @@ internal sealed class DebugConfigComponent : ConfigComponent
|
|||
ImGui.Text("Quest/Interaction Skips");
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
bool v7 = base.Configuration.Advanced.SkipAetherCurrents;
|
||||
if (ImGui.Checkbox("Don't pick up aether currents/aether current quests", ref v7))
|
||||
bool v8 = base.Configuration.Advanced.SkipAetherCurrents;
|
||||
if (ImGui.Checkbox("Don't pick up aether currents/aether current quests", ref v8))
|
||||
{
|
||||
base.Configuration.Advanced.SkipAetherCurrents = v7;
|
||||
base.Configuration.Advanced.SkipAetherCurrents = v8;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker("If not done during the MSQ by Questionable, you have to manually pick up any missed aether currents/quests. There is no way to automatically pick up all missing aether currents.");
|
||||
bool v8 = base.Configuration.Advanced.SkipClassJobQuests;
|
||||
if (ImGui.Checkbox("Don't pick up class/job/role quests", ref v8))
|
||||
bool v9 = base.Configuration.Advanced.SkipClassJobQuests;
|
||||
if (ImGui.Checkbox("Don't pick up class/job/role quests", ref v9))
|
||||
{
|
||||
base.Configuration.Advanced.SkipClassJobQuests = v8;
|
||||
base.Configuration.Advanced.SkipClassJobQuests = v9;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker("Class and job skills for A Realm Reborn, Heavensward and (for the Lv70 skills) Stormblood are locked behind quests. Not recommended if you plan on queueing for instances with duty finder/party finder.\n\nNote: This setting is ignored for the first class/job quest if your character is not high enough level to start the level 4 MSQ.");
|
||||
bool v9 = base.Configuration.Advanced.SkipARealmRebornHardModePrimals;
|
||||
if (ImGui.Checkbox("Don't pick up ARR hard mode primal quests", ref v9))
|
||||
bool v10 = base.Configuration.Advanced.SkipARealmRebornHardModePrimals;
|
||||
if (ImGui.Checkbox("Don't pick up ARR hard mode primal quests", ref v10))
|
||||
{
|
||||
base.Configuration.Advanced.SkipARealmRebornHardModePrimals = v9;
|
||||
base.Configuration.Advanced.SkipARealmRebornHardModePrimals = v10;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker("Hard mode Ifrit/Garuda/Titan are required for the Patch 2.5 quest 'Good Intentions' and to start Heavensward.");
|
||||
bool v10 = base.Configuration.Advanced.SkipCrystalTowerRaids;
|
||||
if (ImGui.Checkbox("Don't pick up Crystal Tower quests", ref v10))
|
||||
bool v11 = base.Configuration.Advanced.SkipCrystalTowerRaids;
|
||||
if (ImGui.Checkbox("Don't pick up Crystal Tower quests", ref v11))
|
||||
{
|
||||
base.Configuration.Advanced.SkipCrystalTowerRaids = v10;
|
||||
base.Configuration.Advanced.SkipCrystalTowerRaids = v11;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGuiComponents.HelpMarker("Crystal Tower raids are required for the Patch 2.55 quest 'A Time to Every Purpose' and to start Heavensward.");
|
||||
bool v11 = base.Configuration.Advanced.PreventQuestCompletion;
|
||||
if (ImGui.Checkbox("Prevent quest completion", ref v11))
|
||||
bool v12 = base.Configuration.Advanced.PreventQuestCompletion;
|
||||
if (ImGui.Checkbox("Prevent quest completion", ref v12))
|
||||
{
|
||||
base.Configuration.Advanced.PreventQuestCompletion = v11;
|
||||
base.Configuration.Advanced.PreventQuestCompletion = v12;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
|||
ImGui.SetNextItemWidth(200f);
|
||||
if (ImGui.Combo((ImU8String)"##DefaultDutyMode", ref currentItem, (ReadOnlySpan<string>)DutyModeLabels, DutyModeLabels.Length))
|
||||
{
|
||||
base.Configuration.Duties.DefaultDutyMode = (EDutyMode)currentItem;
|
||||
base.Configuration.Duties.DefaultDutyMode = (Configuration.EDutyMode)currentItem;
|
||||
Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
|
|
@ -345,9 +345,9 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
|||
id2.AppendFormatted(num);
|
||||
using (ImRaii.PushId(id2))
|
||||
{
|
||||
EDutyMode value3;
|
||||
Configuration.EDutyMode value3;
|
||||
bool flag2 = base.Configuration.Duties.DutyModeOverrides.TryGetValue(num, out value3);
|
||||
EDutyMode num2 = (flag2 ? value3 : ((EDutyMode)(-1)));
|
||||
Configuration.EDutyMode num2 = (flag2 ? value3 : ((Configuration.EDutyMode)(-1)));
|
||||
Name = "Use Default";
|
||||
string[] dutyModeLabels = DutyModeLabels;
|
||||
int num3 = 0;
|
||||
|
|
@ -368,13 +368,13 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
|||
}
|
||||
else
|
||||
{
|
||||
base.Configuration.Duties.DutyModeOverrides[num] = (EDutyMode)(currentItem2 - 1);
|
||||
base.Configuration.Duties.DutyModeOverrides[num] = (Configuration.EDutyMode)(currentItem2 - 1);
|
||||
}
|
||||
Save();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
EDutyMode eDutyMode = (flag2 ? value3 : base.Configuration.Duties.DefaultDutyMode);
|
||||
Configuration.EDutyMode eDutyMode = (flag2 ? value3 : base.Configuration.Duties.DefaultDutyMode);
|
||||
ImGui.SetTooltip(flag2 ? ("Override: " + DutyModeLabels[(int)eDutyMode]) : ("Using default: " + DutyModeLabels[(int)eDutyMode]));
|
||||
}
|
||||
}
|
||||
|
|
@ -447,7 +447,7 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
|||
{
|
||||
IEnumerable<string> first = base.Configuration.Duties.WhitelistedDutyCfcIds.Select((uint x) => $"{"+"}{x}");
|
||||
IEnumerable<string> second = base.Configuration.Duties.BlacklistedDutyCfcIds.Select((uint x) => $"{"-"}{x}");
|
||||
IEnumerable<string> second2 = base.Configuration.Duties.DutyModeOverrides.Select((KeyValuePair<uint, EDutyMode> x) => $"{"M:"}{x.Key}:{x.Value}");
|
||||
IEnumerable<string> second2 = base.Configuration.Duties.DutyModeOverrides.Select((KeyValuePair<uint, Configuration.EDutyMode> x) => $"{"M:"}{x.Key}:{x.Value}");
|
||||
ImGui.SetClipboardText("qst:duty:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(";", first.Concat(second).Concat(second2)))));
|
||||
}
|
||||
}
|
||||
|
|
@ -481,7 +481,7 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
|||
int num2 = span.IndexOf(':');
|
||||
if (num2 > 0 && uint.TryParse(span.Slice(0, num2), CultureInfo.InvariantCulture, out var result3) && int.TryParse(span.Slice(num2 + 1), CultureInfo.InvariantCulture, out var result4) && Enum.IsDefined(typeof(EDutyMode), result4))
|
||||
{
|
||||
base.Configuration.Duties.DutyModeOverrides[result3] = (EDutyMode)result4;
|
||||
base.Configuration.Duties.DutyModeOverrides[result3] = (Configuration.EDutyMode)result4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -498,7 +498,7 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
|||
base.Configuration.Duties.WhitelistedDutyCfcIds.Clear();
|
||||
base.Configuration.Duties.BlacklistedDutyCfcIds.Clear();
|
||||
base.Configuration.Duties.DutyModeOverrides.Clear();
|
||||
base.Configuration.Duties.DefaultDutyMode = EDutyMode.Support;
|
||||
base.Configuration.Duties.DefaultDutyMode = Questionable.Configuration.EDutyMode.Support;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ internal sealed class ActiveQuestComponent
|
|||
public event EventHandler? Reload;
|
||||
|
||||
[GeneratedRegex("\\s\\s+", RegexOptions.IgnoreCase, "en-US")]
|
||||
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.14.23019")]
|
||||
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.14.27113")]
|
||||
private static Regex MultipleWhitespaceRegex()
|
||||
{
|
||||
return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
|
|
@ -154,30 +153,31 @@ internal sealed class DebugOverlay : Window
|
|||
{
|
||||
return;
|
||||
}
|
||||
foreach (IGameObject item3 in _objectTable.Skip(1))
|
||||
for (int i = 1; i < _objectTable.Length; i++)
|
||||
{
|
||||
if (item3 is IBattleNpc && _gameGui.WorldToScreen(item3.Position, out var screenPos))
|
||||
IGameObject gameObject = _objectTable[i];
|
||||
if (gameObject != null && gameObject is IBattleNpc battleNpc && _gameGui.WorldToScreen(battleNpc.Position, out var screenPos))
|
||||
{
|
||||
(int Priority, string Reason) killPriority = _combatController.GetKillPriority(item3);
|
||||
(int Priority, string Reason) killPriority = _combatController.GetKillPriority(battleNpc);
|
||||
int item = killPriority.Priority;
|
||||
string item2 = killPriority.Reason;
|
||||
ImDrawListPtr windowDrawList = ImGui.GetWindowDrawList();
|
||||
Vector2 pos = screenPos + new Vector2(10f, -8f);
|
||||
int col = ((item > 0) ? (-16711936) : (-1));
|
||||
ImU8String text = new ImU8String(12, 7);
|
||||
text.AppendFormatted(item3.Name);
|
||||
text.AppendFormatted(battleNpc.Name);
|
||||
text.AppendLiteral("/");
|
||||
text.AppendFormatted(item3.GameObjectId, "X");
|
||||
text.AppendFormatted(battleNpc.GameObjectId, "X");
|
||||
text.AppendLiteral(", ");
|
||||
text.AppendFormatted(item3.BaseId);
|
||||
text.AppendFormatted(battleNpc.BaseId);
|
||||
text.AppendLiteral(", ");
|
||||
text.AppendFormatted(item);
|
||||
text.AppendLiteral(" - ");
|
||||
text.AppendFormatted(item2);
|
||||
text.AppendLiteral(", ");
|
||||
text.AppendFormatted(Vector3.Distance(item3.Position, _objectTable.LocalPlayer.Position), "N2");
|
||||
text.AppendFormatted(Vector3.Distance(battleNpc.Position, _objectTable.LocalPlayer.Position), "N2");
|
||||
text.AppendLiteral(", ");
|
||||
text.AppendFormatted(item3.IsTargetable);
|
||||
text.AppendFormatted(battleNpc.IsTargetable);
|
||||
windowDrawList.AddText(pos, (uint)col, text);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -343,7 +343,22 @@ internal sealed class FateSelectionWindow : LWindow
|
|||
|
||||
private void DrawFateRowActions(FateDefinition fate, bool disabled)
|
||||
{
|
||||
bool flag = fate.RequiredQuestId.HasValue && !_questFunctions.IsQuestComplete(new QuestId(fate.RequiredQuestId.Value));
|
||||
uint? num = fate.RequiredQuestId;
|
||||
object obj;
|
||||
if (num.HasValue)
|
||||
{
|
||||
uint valueOrDefault = num.GetValueOrDefault();
|
||||
if (valueOrDefault <= 65535)
|
||||
{
|
||||
obj = new QuestId((ushort)valueOrDefault);
|
||||
goto IL_004d;
|
||||
}
|
||||
}
|
||||
obj = null;
|
||||
goto IL_004d;
|
||||
IL_004d:
|
||||
QuestId questId = (QuestId)obj;
|
||||
bool flag = questId != null && !_questFunctions.IsQuestComplete(questId);
|
||||
ImU8String id = new ImU8String(5, 1);
|
||||
id.AppendLiteral("fate_");
|
||||
id.AppendFormatted(fate.Name);
|
||||
|
|
@ -358,7 +373,7 @@ internal sealed class FateSelectionWindow : LWindow
|
|||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
IQuestInfo questInfo;
|
||||
string value = (_questData.TryGetQuestInfo(new QuestId(fate.RequiredQuestId.Value), out questInfo) ? questInfo.Name : fate.RequiredQuestId.Value.ToString(CultureInfo.InvariantCulture));
|
||||
string value = (_questData.TryGetQuestInfo(questId, out questInfo) ? questInfo.Name : (num?.ToString(CultureInfo.InvariantCulture) ?? "Unknown"));
|
||||
ImU8String tooltip = new ImU8String(33, 1);
|
||||
tooltip.AppendLiteral("Requires \"");
|
||||
tooltip.AppendFormatted(value);
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
|
|||
{
|
||||
return false;
|
||||
}
|
||||
if (_configuration.General.HideInAllInstances && _territoryData.IsDutyInstance(_clientState.TerritoryType))
|
||||
if (_configuration.General.HideInAllInstances && !_configuration.Advanced.ShowWindowInInstances && _territoryData.IsDutyInstance(_clientState.TerritoryType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,25 +21,25 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Dalamud.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="LLib">
|
||||
<HintPath>..\..\LLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\FFXIVClientStructs.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Questionable.Model">
|
||||
<HintPath>..\..\Questionable.Model.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions">
|
||||
<HintPath>..\..\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Serilog">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Serilog.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Serilog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection">
|
||||
<HintPath>..\..\Microsoft.Extensions.DependencyInjection.dll</HintPath>
|
||||
|
|
@ -48,19 +48,19 @@
|
|||
<HintPath>..\..\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Dalamud.Bindings.ImGui">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Dalamud.Bindings.ImGui.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Dalamud.Bindings.ImGui.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Lumina.Excel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\Lumina.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="JsonSchema.Net">
|
||||
<HintPath>..\..\JsonSchema.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="InteropGenerator.Runtime">
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\dev\InteropGenerator.Runtime.dll</HintPath>
|
||||
<HintPath>C:\Users\Aly\AppData\Roaming\XIVLauncher\addon\Hooks\15.0.3.2\InteropGenerator.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Logging">
|
||||
<HintPath>..\..\Microsoft.Extensions.Logging.dll</HintPath>
|
||||
|
|
|
|||
|
|
@ -84,17 +84,17 @@ internal sealed class Configuration : IPluginConfiguration
|
|||
[JsonProperty(ItemConverterType = typeof(ElementIdNConverter))]
|
||||
public List<ElementId> QuestsToStopAfter { get; set; } = new List<ElementId>();
|
||||
|
||||
public Dictionary<string, int?> QuestSequences { get; set; } = new Dictionary<string, int?>();
|
||||
|
||||
public Dictionary<string, EStopConditionMode> QuestStopModes { get; set; } = new Dictionary<string, EStopConditionMode>();
|
||||
public int TargetLevel { get; set; } = 50;
|
||||
|
||||
public EStopConditionMode LevelStopMode { get; set; }
|
||||
|
||||
public int TargetLevel { get; set; } = 50;
|
||||
|
||||
public EStopConditionMode SequenceStopMode { get; set; }
|
||||
|
||||
public int TargetSequence { get; set; } = 1;
|
||||
|
||||
public Dictionary<string, int?> QuestSequences { get; set; } = new Dictionary<string, int?>();
|
||||
|
||||
public Dictionary<string, EStopConditionMode> QuestStopModes { get; set; } = new Dictionary<string, EStopConditionMode>();
|
||||
}
|
||||
|
||||
internal sealed class DutyConfiguration
|
||||
|
|
@ -142,6 +142,8 @@ internal sealed class Configuration : IPluginConfiguration
|
|||
|
||||
public bool NeverFly { get; set; }
|
||||
|
||||
public bool ShowWindowInInstances { get; set; }
|
||||
|
||||
public bool AdditionalStatusInformation { get; set; }
|
||||
|
||||
public bool DisablePartyWatchdog { get; set; }
|
||||
|
|
@ -183,6 +185,20 @@ internal sealed class Configuration : IPluginConfiguration
|
|||
Stop
|
||||
}
|
||||
|
||||
internal enum EJsonColorTheme
|
||||
{
|
||||
VsCodeDark,
|
||||
Dracula,
|
||||
CatppuccinMocha
|
||||
}
|
||||
|
||||
internal enum EDutyMode
|
||||
{
|
||||
Support,
|
||||
UnsyncSolo,
|
||||
UnsyncParty
|
||||
}
|
||||
|
||||
public sealed class ElementIdNConverter : JsonConverter<ElementId>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, ElementId? value, JsonSerializer serializer)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
|
|||
{
|
||||
private readonly ServiceProvider? _serviceProvider;
|
||||
|
||||
public QuestionablePlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager, IAddonLifecycle addonLifecycle, IKeyState keyState, IContextMenu contextMenu, IToastGui toastGui, IGameInteropProvider gameInteropProvider, IAetheryteList aetheryteList, IGameConfig gameConfig)
|
||||
public QuestionablePlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, ITargetManager targetManager, IFramework framework, IGameGui gameGui, IDataManager dataManager, ISigScanner sigScanner, IObjectTable objectTable, IPluginLog pluginLog, ICondition condition, IChatGui chatGui, ICommandManager commandManager, IAddonLifecycle addonLifecycle, IKeyState keyState, IContextMenu contextMenu, IToastGui toastGui, IGameInteropProvider gameInteropProvider, IAetheryteList aetheryteList, IGameConfig gameConfig, ITextureProvider textureProvider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pluginInterface, "pluginInterface");
|
||||
ArgumentNullException.ThrowIfNull(chatGui, "chatGui");
|
||||
|
|
@ -68,6 +68,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
|
|||
serviceCollection.AddSingleton(gameInteropProvider);
|
||||
serviceCollection.AddSingleton(aetheryteList);
|
||||
serviceCollection.AddSingleton(gameConfig);
|
||||
serviceCollection.AddSingleton(textureProvider);
|
||||
serviceCollection.AddSingleton(new WindowSystem("Questionable"));
|
||||
serviceCollection.AddSingleton(((Configuration)pluginInterface.GetPluginConfig()) ?? new Configuration());
|
||||
AddBasicFunctionsAndData(serviceCollection);
|
||||
|
|
@ -296,6 +297,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
|
|||
serviceCollection.AddSingleton<IQuestValidator, SinglePlayerInstanceValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, UniqueSinglePlayerInstanceValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, SayValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, LandingZoneValidator>();
|
||||
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
||||
((IServiceCollection)serviceCollection).AddSingleton((Func<IServiceProvider, IQuestValidator>)((IServiceProvider sp) => sp.GetRequiredService<JsonSchemaValidator>()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
namespace System.Text.RegularExpressions.Generated;
|
||||
|
||||
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.14.23019")]
|
||||
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.14.27113")]
|
||||
[SkipLocalsInit]
|
||||
internal sealed class _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0 : Regex
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.CodeDom.Compiler;
|
|||
|
||||
namespace System.Text.RegularExpressions.Generated;
|
||||
|
||||
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.14.23019")]
|
||||
[GeneratedCode("System.Text.RegularExpressions.Generator", "10.0.14.27113")]
|
||||
internal static class _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__Utilities
|
||||
{
|
||||
internal static readonly TimeSpan s_defaultTimeout = ((AppContext.GetData("REGEX_DEFAULT_MATCH_TIMEOUT") is TimeSpan timeSpan) ? timeSpan : Regex.InfiniteMatchTimeout);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue