using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using System.Text.RegularExpressions; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin; using Questionable.Data; using Questionable.Model; using Questionable.Model.Questing; using Questionable.Validation; namespace Questionable.Windows.QuestComponents; internal sealed class ValidationDetailsRenderer { private readonly QuestData _questData; private readonly IDalamudPluginInterface _pluginInterface; private readonly Dictionary _storedIssues = new Dictionary(); private readonly Dictionary _openDetailWindows = new Dictionary(); private static readonly Regex UnicodeEscapeRegex = new Regex("\\\\u([0-9A-Fa-f]{4})", RegexOptions.Compiled); private static readonly Regex JsonEscapeRegex = new Regex("\\\\(.)", RegexOptions.Compiled); private static readonly Regex ConsecutiveQuotesRegex = new Regex("\"{2,}", RegexOptions.Compiled); public ValidationDetailsRenderer(QuestData questData, IDalamudPluginInterface pluginInterface) { _questData = questData; _pluginInterface = pluginInterface; } public static string CleanJsonText(string text) { if (string.IsNullOrEmpty(text)) { return text; } text = UnicodeEscapeRegex.Replace(text, (Match match) => int.TryParse(match.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result) ? ((char)result).ToString() : match.Value); text = JsonEscapeRegex.Replace(text, (Match match) => match.Groups[1].Value switch { "\"" => "\"", "\\" => "\\", "/" => "/", "b" => "\b", "f" => "\f", "n" => "\n", "r" => "\r", "t" => "\t", _ => match.Value, }); if (text.Contains("\"\"", StringComparison.Ordinal)) { text = ConsecutiveQuotesRegex.Replace(text, "\""); text = text.Replace("Expected \"\"", "Expected \"\"", StringComparison.Ordinal); } return text; } public void OpenDetails(ValidationIssue issue, int index) { _storedIssues[index] = issue; _openDetailWindows[index] = true; } public void DrawDetailWindows() { List list = new List(); foreach (KeyValuePair item in _openDetailWindows.ToList()) { if (!item.Value || !_storedIssues.TryGetValue(item.Key, out ValidationIssue value)) { continue; } string text = $"Validation Details##{item.Key}"; bool open = true; ImGui.SetNextWindowSize(new Vector2(800f, 600f), ImGuiCond.FirstUseEver); ImGui.SetNextWindowSizeConstraints(new Vector2(500f, 300f), new Vector2(1200f, 800f)); using (ThemedWindow.PushTheme()) { if (ImGui.Begin(text, ref open)) { DrawIssueDetails(value); } ImGui.End(); } if (!open) { list.Add(item.Key); } } foreach (int item2 in list) { _openDetailWindows.Remove(item2); _storedIssues.Remove(item2); } } private void DrawIssueDetails(ValidationIssue issue) { Vector4 color = issue.Severity switch { EIssueSeverity.Error => ImGuiColors.DalamudRed, EIssueSeverity.Warning => ImGuiColors.DalamudYellow, _ => ImGuiColors.DalamudOrange, }; FontAwesomeIcon icon = issue.Severity switch { EIssueSeverity.Error => FontAwesomeIcon.ExclamationTriangle, EIssueSeverity.Warning => FontAwesomeIcon.ExclamationCircle, _ => FontAwesomeIcon.InfoCircle, }; using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) { using (ImRaii.PushColor(ImGuiCol.Text, color)) { ImGui.TextUnformatted(icon.ToIconString()); } } ImGui.SameLine(); using (ImRaii.PushColor(ImGuiCol.Text, color)) { ImU8String text = new ImU8String(2, 2); text.AppendFormatted(issue.Severity); text.AppendLiteral(": "); text.AppendFormatted(issue.Type); ImGui.Text(text); } ImGui.Separator(); ImU8String text2 = new ImU8String(6, 1); text2.AppendLiteral("Type: "); text2.AppendFormatted(issue.PathType); ImGui.Text(text2); if (issue.Id != null) { if (issue.PathType == EPathType.Quest && ElementId.TryFromString(issue.Id, out ElementId elementId) && elementId != null) { try { IQuestInfo questInfo = _questData.GetQuestInfo(elementId); ImU8String text3 = new ImU8String(10, 2); text3.AppendLiteral("Quest: "); text3.AppendFormatted(issue.Id); text3.AppendLiteral(" - "); text3.AppendFormatted(questInfo.Name); ImGui.Text(text3); } catch { ImU8String text4 = new ImU8String(4, 1); text4.AppendLiteral("ID: "); text4.AppendFormatted(issue.Id); ImGui.Text(text4); } } else { ImU8String text5 = new ImU8String(4, 1); text5.AppendLiteral("ID: "); text5.AppendFormatted(issue.Id); ImGui.Text(text5); } } if (issue.Location != null) { ImU8String text6 = new ImU8String(10, 1); text6.AppendLiteral("Location: "); text6.AppendFormatted(issue.Location); ImGui.Text(text6); } ImGui.Separator(); ImGui.Text("Description:"); string description = CleanJsonText(issue.Description ?? "(no description)"); if (issue.Type == EIssueType.QuestDisabled && issue.Id == null) { DrawDisabledTribesDetails(description); } else if (issue.Type == EIssueType.InvalidJsonSyntax) { DrawJsonSyntaxErrorDetails(description); } else { DrawGenericDetails(description); } } private static void DrawJsonSyntaxErrorDetails(string description) { string[] array = description.Split('\n'); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrEmpty(text)) { ImGui.Spacing(); } else if (text.StartsWith("JSON parsing error", StringComparison.Ordinal)) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed)) { ImGui.TextWrapped(text); } } else if (text.StartsWith("This usually indicates", StringComparison.Ordinal) || text.StartsWith("Please check", StringComparison.Ordinal)) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen)) { ImGui.TextWrapped(text); } } else if (text.StartsWith("• ", StringComparison.Ordinal)) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow)) { ImGui.TextWrapped(text); } } else { ImGui.TextWrapped(text); } } } private void DrawDisabledTribesDetails(string description) { string[] array = description.Split(':', 2); if (array.Length < 2) { ImGui.TextWrapped(description); return; } ImGui.TextWrapped(array[0]); ImGui.Spacing(); List list = (from x in array[1].Split(',', StringSplitOptions.RemoveEmptyEntries) select x.Trim() into x where !string.IsNullOrEmpty(x) select x).Distinct().ToList(); if (list.Count == 0) { ImGui.TextWrapped("(no disabled quests listed)"); return; } ImGui.Text("Disabled Quests:"); ImGui.Indent(); Vector4[] array2 = new Vector4[6] { ImGuiColors.TankBlue, ImGuiColors.HealerGreen, ImGuiColors.DPSRed, ImGuiColors.ParsedGreen, ImGuiColors.ParsedBlue, ImGuiColors.DalamudViolet }; for (int num = 0; num < list.Count; num++) { string value = list[num]; Vector4 color = array2[num % array2.Length]; using (ImRaii.PushColor(ImGuiCol.Text, color)) { if (ElementId.TryFromString(value, out ElementId elementId) && elementId != null) { try { IQuestInfo questInfo = _questData.GetQuestInfo(elementId); ImU8String text = new ImU8String(5, 2); text.AppendLiteral("• "); text.AppendFormatted(value); text.AppendLiteral(" - "); text.AppendFormatted(questInfo.Name); ImGui.TextWrapped(text); } catch { ImU8String text2 = new ImU8String(18, 1); text2.AppendLiteral("• "); text2.AppendFormatted(value); text2.AppendLiteral(" (unknown quest)"); ImGui.TextWrapped(text2); } } else { ImU8String text3 = new ImU8String(2, 1); text3.AppendLiteral("• "); text3.AppendFormatted(value); ImGui.TextWrapped(text3); } } } ImGui.Unindent(); } private static void DrawGenericDetails(string description) { string[] array = description.Split('\n'); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrEmpty(text)) { ImGui.Spacing(); } else if (text.StartsWith("Error:", StringComparison.Ordinal) || text.StartsWith("Invalid", StringComparison.Ordinal) || text.StartsWith("Missing", StringComparison.Ordinal)) { using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed)) { ImGui.TextWrapped(text); } } else if (text.Contains(':', StringComparison.Ordinal)) { int num = text.IndexOf(':', StringComparison.Ordinal); string text2 = text.Substring(0, num); string text3 = text.Substring(num + 1).TrimStart(); using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue)) { ImGui.Text(text2 + ":"); } ImGui.SameLine(); ImGui.TextWrapped(text3); } else { ImGui.TextWrapped(text); } } } }