muffin v6.12
This commit is contained in:
parent
e786325cda
commit
0950798597
64 changed files with 40100 additions and 58121 deletions
|
@ -55,7 +55,7 @@ internal sealed class ActiveQuestComponent
|
|||
[GeneratedCode("System.Text.RegularExpressions.Generator", "9.0.12.41916")]
|
||||
private static Regex MultipleWhitespaceRegex()
|
||||
{
|
||||
return _003CRegexGenerator_g_003EFF909AF37F4C319C8940E7DA0E71D9E470824ECE485FE299B23B08984F5D534F6__MultipleWhitespaceRegex_0.Instance;
|
||||
return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance;
|
||||
}
|
||||
|
||||
public ActiveQuestComponent(QuestController questController, MovementController movementController, CombatController combatController, GatheringController gatheringController, QuestFunctions questFunctions, ICommandManager commandManager, Configuration configuration, QuestRegistry questRegistry, PriorityWindow priorityWindow, UiUtils uiUtils, IClientState clientState, IChatGui chatGui, ILogger<ActiveQuestComponent> logger)
|
||||
|
@ -236,7 +236,8 @@ internal sealed class ActiveQuestComponent
|
|||
}
|
||||
bool flag = _configuration.Stop.Enabled && _configuration.Stop.LevelToStopAfter;
|
||||
bool flag2 = _configuration.Stop.Enabled && _configuration.Stop.QuestsToStopAfter.Any((ElementId x) => !_questFunctions.IsQuestComplete(x) && !_questFunctions.IsQuestUnobtainable(x));
|
||||
if (flag || flag2)
|
||||
bool flag3 = _configuration.Stop.Enabled && _configuration.Stop.SequenceToStopAfter;
|
||||
if (flag || flag2 || flag3)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
Vector4 col = ImGuiColors.ParsedPurple;
|
||||
|
@ -252,6 +253,10 @@ internal sealed class ActiveQuestComponent
|
|||
col = ImGuiColors.ParsedBlue;
|
||||
}
|
||||
}
|
||||
if (flag3)
|
||||
{
|
||||
col = ((startedQuest.Sequence < _configuration.Stop.TargetSequence) ? ImGuiColors.ParsedBlue : ImGuiColors.ParsedGreen);
|
||||
}
|
||||
ImGui.TextColored(in col, SeIconChar.Clock.ToIconString());
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
|
@ -290,12 +295,43 @@ internal sealed class ActiveQuestComponent
|
|||
}
|
||||
}
|
||||
}
|
||||
if (flag2)
|
||||
if (flag3)
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
}
|
||||
int sequence = startedQuest.Sequence;
|
||||
text = new ImU8String(23, 1);
|
||||
text.AppendLiteral("Stop at quest sequence ");
|
||||
text.AppendFormatted(_configuration.Stop.TargetSequence);
|
||||
ImGui.BulletText(text);
|
||||
ImGui.SameLine();
|
||||
if (sequence >= _configuration.Stop.TargetSequence)
|
||||
{
|
||||
Vector4 col2 = ImGuiColors.ParsedGreen;
|
||||
text = new ImU8String(22, 1);
|
||||
text.AppendLiteral("(Current: ");
|
||||
text.AppendFormatted(sequence);
|
||||
text.AppendLiteral(" - Reached!)");
|
||||
ImGui.TextColored(in col2, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector4 col2 = ImGuiColors.ParsedBlue;
|
||||
text = new ImU8String(11, 1);
|
||||
text.AppendLiteral("(Current: ");
|
||||
text.AppendFormatted(sequence);
|
||||
text.AppendLiteral(")");
|
||||
ImGui.TextColored(in col2, text);
|
||||
}
|
||||
}
|
||||
if (flag2)
|
||||
{
|
||||
if (flag || flag3)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
}
|
||||
ImGui.BulletText("Stop after completing any of these quests:");
|
||||
ImGui.Indent();
|
||||
foreach (ElementId item in _configuration.Stop.QuestsToStopAfter)
|
||||
|
@ -415,7 +451,6 @@ internal sealed class ActiveQuestComponent
|
|||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(questProgressInfo.Tooltip);
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.Text(FontAwesomeIcon.Copy.ToIconString());
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
|
@ -20,9 +19,7 @@ namespace Questionable.Windows.QuestComponents;
|
|||
|
||||
internal sealed class EventInfoComponent
|
||||
{
|
||||
private sealed record EventQuest(string Name, List<ElementId> QuestIds, DateTime EndsAtUtc);
|
||||
|
||||
private readonly List<EventQuest> _eventQuests;
|
||||
private sealed record EventQuest(string Name, List<ElementId> QuestIds, DateTime EndsAtUtc, string? Patch);
|
||||
|
||||
private readonly QuestData _questData;
|
||||
|
||||
|
@ -38,42 +35,33 @@ internal sealed class EventInfoComponent
|
|||
|
||||
private readonly Configuration _configuration;
|
||||
|
||||
private readonly IDataManager _dataManager;
|
||||
|
||||
private List<IQuestInfo> _cachedActiveSeasonalQuests = new List<IQuestInfo>();
|
||||
|
||||
private DateTime _cachedAtUtc = DateTime.MinValue;
|
||||
|
||||
private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5L);
|
||||
|
||||
private readonly ILogger<EventInfoComponent> _logger;
|
||||
|
||||
private readonly HashSet<ushort> _alreadyLoggedActiveSeasonalSkip = new HashSet<ushort>();
|
||||
|
||||
public bool ShouldDraw
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_configuration.General.ShowIncompleteSeasonalEvents)
|
||||
if (!_configuration.General.ShowIncompleteSeasonalEvents)
|
||||
{
|
||||
return _eventQuests.Any(IsIncomplete);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
UpdateCacheIfNeeded();
|
||||
return _cachedActiveSeasonalQuests.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public EventInfoComponent(QuestData questData, QuestRegistry questRegistry, QuestFunctions questFunctions, UiUtils uiUtils, QuestController questController, QuestTooltipComponent questTooltipComponent, Configuration configuration)
|
||||
public EventInfoComponent(QuestData questData, QuestRegistry questRegistry, QuestFunctions questFunctions, UiUtils uiUtils, QuestController questController, QuestTooltipComponent questTooltipComponent, Configuration configuration, IDataManager dataManager, ILogger<EventInfoComponent> logger)
|
||||
{
|
||||
int num = 2;
|
||||
List<EventQuest> list = new List<EventQuest>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
Span<EventQuest> span = CollectionsMarshal.AsSpan(list);
|
||||
int num2 = 0;
|
||||
ref EventQuest reference = ref span[num2];
|
||||
int num3 = 1;
|
||||
List<ElementId> list2 = new List<ElementId>(num3);
|
||||
CollectionsMarshal.SetCount(list2, num3);
|
||||
CollectionsMarshal.AsSpan(list2)[0] = new UnlockLinkId(568);
|
||||
reference = new EventQuest("Limited Time Items", list2, DateTime.MaxValue);
|
||||
ref EventQuest reference2 = ref span[num2 + 1];
|
||||
int num4 = 2;
|
||||
List<ElementId> list3 = new List<ElementId>(num4);
|
||||
CollectionsMarshal.SetCount(list3, num4);
|
||||
Span<ElementId> span2 = CollectionsMarshal.AsSpan(list3);
|
||||
num3 = 0;
|
||||
span2[num3] = new QuestId(5297);
|
||||
span2[num3 + 1] = new QuestId(5298);
|
||||
reference2 = new EventQuest("The Rising 2025", list3, AtDailyReset(new DateOnly(2025, 9, 11)));
|
||||
_eventQuests = list;
|
||||
base._002Ector();
|
||||
_questData = questData;
|
||||
_questRegistry = questRegistry;
|
||||
_questFunctions = questFunctions;
|
||||
|
@ -81,39 +69,97 @@ internal sealed class EventInfoComponent
|
|||
_questController = questController;
|
||||
_questTooltipComponent = questTooltipComponent;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
private static DateTime AtDailyReset(DateOnly date)
|
||||
{
|
||||
return new DateTime(date, new TimeOnly(14, 59), DateTimeKind.Utc);
|
||||
_dataManager = dataManager;
|
||||
_logger = logger ?? throw new ArgumentNullException("logger");
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
foreach (EventQuest eventQuest in _eventQuests)
|
||||
UpdateCacheIfNeeded();
|
||||
foreach (IGrouping<string, IQuestInfo> item in _cachedActiveSeasonalQuests.GroupBy(delegate(IQuestInfo q)
|
||||
{
|
||||
if (IsIncomplete(eventQuest))
|
||||
if (q.QuestId is UnlockLinkId)
|
||||
{
|
||||
DrawEventQuest(eventQuest);
|
||||
return "Limited Unlocks";
|
||||
}
|
||||
if (_questRegistry.TryGetQuestFolderName(q.QuestId, out string folderName) && !string.IsNullOrEmpty(folderName))
|
||||
{
|
||||
return folderName;
|
||||
}
|
||||
return q.JournalGenre.HasValue ? GetJournalGenreName(q.JournalGenre.Value) : q.Name;
|
||||
}))
|
||||
{
|
||||
if (item.All((IQuestInfo q) => _questFunctions.IsQuestComplete(q.QuestId)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
DateTime endsAtUtc = item.Select(delegate(IQuestInfo q)
|
||||
{
|
||||
DateTime? dateTime = (q as QuestInfo)?.SeasonalQuestExpiry ?? ((q is UnlockLinkQuestInfo unlockLinkQuestInfo) ? unlockLinkQuestInfo.QuestExpiry : ((DateTime?)null));
|
||||
if (dateTime.HasValue)
|
||||
{
|
||||
DateTime valueOrDefault = dateTime.GetValueOrDefault();
|
||||
return NormalizeExpiry(valueOrDefault);
|
||||
}
|
||||
return DateTime.MaxValue;
|
||||
}).DefaultIfEmpty(DateTime.MaxValue).Min();
|
||||
List<string> list = (from q in item
|
||||
select (q as UnlockLinkQuestInfo)?.Patch into p
|
||||
where !string.IsNullOrEmpty(p)
|
||||
select p).Distinct().ToList();
|
||||
string patch = ((list.Count == 1) ? list[0] : null);
|
||||
EventQuest eventQuest = new EventQuest(item.Key, item.Select((IQuestInfo q) => q.QuestId).ToList(), endsAtUtc, patch);
|
||||
DrawEventQuest(eventQuest);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetJournalGenreName(uint journalGenreId)
|
||||
{
|
||||
try
|
||||
{
|
||||
JournalGenre row = _dataManager.GetExcelSheet<JournalGenre>().GetRow(journalGenreId);
|
||||
if (!row.Equals(default(JournalGenre)))
|
||||
{
|
||||
return row.Name.ExtractText();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "Failed to get journal genre name for id {JournalGenreId}", journalGenreId);
|
||||
}
|
||||
return $"Event {journalGenreId}";
|
||||
}
|
||||
|
||||
private void DrawEventQuest(EventQuest eventQuest)
|
||||
{
|
||||
string text = eventQuest.Name;
|
||||
if (!string.IsNullOrEmpty(eventQuest.Patch))
|
||||
{
|
||||
text = text + " [" + eventQuest.Patch + "]";
|
||||
}
|
||||
if (eventQuest.EndsAtUtc != DateTime.MaxValue)
|
||||
{
|
||||
string value = (eventQuest.EndsAtUtc - DateTime.UtcNow).Humanize(1, CultureInfo.InvariantCulture, TimeUnit.Day, TimeUnit.Minute);
|
||||
ImU8String text = new ImU8String(3, 2);
|
||||
text.AppendFormatted(eventQuest.Name);
|
||||
text.AppendLiteral(" (");
|
||||
text.AppendFormatted(value);
|
||||
text.AppendLiteral(")");
|
||||
ImGui.Text(text);
|
||||
TimeSpan timeSpan = eventQuest.EndsAtUtc - DateTime.UtcNow;
|
||||
if (timeSpan < TimeSpan.Zero)
|
||||
{
|
||||
timeSpan = TimeSpan.Zero;
|
||||
}
|
||||
string value = FormatRemainingDays(timeSpan);
|
||||
string text2 = FormatRemainingFull(timeSpan);
|
||||
ImU8String text3 = new ImU8String(3, 2);
|
||||
text3.AppendFormatted(text);
|
||||
text3.AppendLiteral(" (");
|
||||
text3.AppendFormatted(value);
|
||||
text3.AppendLiteral(")");
|
||||
ImGui.Text(text3);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip(text2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text(eventQuest.Name);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
List<ElementId> list = eventQuest.QuestIds.Where((ElementId x) => _questRegistry.IsKnownQuest(x) && _questFunctions.IsReadyToAcceptQuest(x) && x != _questController.StartedQuest?.Quest.Id && x != _questController.NextQuest?.Quest.Id).ToList();
|
||||
foreach (ElementId questId in eventQuest.QuestIds)
|
||||
|
@ -122,13 +168,13 @@ internal sealed class EventInfoComponent
|
|||
{
|
||||
continue;
|
||||
}
|
||||
ImU8String text = new ImU8String(21, 1);
|
||||
text.AppendLiteral("##EventQuestSelection");
|
||||
text.AppendFormatted(questId);
|
||||
using (ImRaii.PushId(text))
|
||||
ImU8String text3 = new ImU8String(21, 1);
|
||||
text3.AppendLiteral("##EventQuestSelection");
|
||||
text3.AppendFormatted(questId);
|
||||
using (ImRaii.PushId(text3))
|
||||
{
|
||||
string name = _questData.GetQuestInfo(questId).Name;
|
||||
if (list.Contains(questId) && _questRegistry.TryGetQuest(questId, out Quest quest))
|
||||
if (list.Contains(questId) && _questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest))
|
||||
{
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
|
||||
{
|
||||
|
@ -157,18 +203,23 @@ internal sealed class EventInfoComponent
|
|||
}
|
||||
}
|
||||
|
||||
private bool IsIncomplete(EventQuest eventQuest)
|
||||
{
|
||||
if (eventQuest.EndsAtUtc <= DateTime.UtcNow)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return eventQuest.QuestIds.Any(ShouldShowQuest);
|
||||
}
|
||||
|
||||
public IEnumerable<ElementId> GetCurrentlyActiveEventQuests()
|
||||
{
|
||||
return _eventQuests.Where((EventQuest x) => x.EndsAtUtc >= DateTime.UtcNow).SelectMany((EventQuest x) => x.QuestIds).Where(ShouldShowQuest);
|
||||
UpdateCacheIfNeeded();
|
||||
return (from q in _cachedActiveSeasonalQuests.Where(delegate(IQuestInfo q)
|
||||
{
|
||||
DateTime? dateTime = (q as QuestInfo)?.SeasonalQuestExpiry;
|
||||
if (dateTime.HasValue)
|
||||
{
|
||||
DateTime valueOrDefault = dateTime.GetValueOrDefault();
|
||||
if (NormalizeExpiry(valueOrDefault) >= DateTime.UtcNow)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (q is UnlockLinkQuestInfo { QuestExpiry: { } questExpiry } && NormalizeExpiry(questExpiry) >= DateTime.UtcNow) ? true : false;
|
||||
})
|
||||
select q.QuestId).Where(ShouldShowQuest);
|
||||
}
|
||||
|
||||
private bool ShouldShowQuest(ElementId elementId)
|
||||
|
@ -179,4 +230,208 @@ internal sealed class EventInfoComponent
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<IQuestInfo> GetActiveSeasonalQuestsNoCache()
|
||||
{
|
||||
IEnumerable<ElementId> allQuestIds = _questRegistry.GetAllQuestIds();
|
||||
foreach (ElementId item in allQuestIds)
|
||||
{
|
||||
if (!_questData.TryGetQuestInfo(item, out IQuestInfo questInfo))
|
||||
{
|
||||
if (!_questRegistry.TryGetQuest(item, out Questionable.Model.Quest quest))
|
||||
{
|
||||
if (_alreadyLoggedActiveSeasonalSkip.Add(item.Value))
|
||||
{
|
||||
_logger.LogDebug("Skipping quest {QuestId}: no QuestInfo", item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
questInfo = quest.Info;
|
||||
}
|
||||
try
|
||||
{
|
||||
bool flag = false;
|
||||
DateTime? dateTime = null;
|
||||
if (questInfo is QuestInfo questInfo2)
|
||||
{
|
||||
flag = questInfo2.IsSeasonalQuest || questInfo2.IsSeasonalEvent || questInfo2.SeasonalQuestExpiry is DateTime || (questInfo2.JournalGenre >= 234 && questInfo2.JournalGenre <= 247);
|
||||
dateTime = questInfo2.SeasonalQuestExpiry;
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
if (dateTime.HasValue)
|
||||
{
|
||||
DateTime valueOrDefault = dateTime.GetValueOrDefault();
|
||||
DateTime dateTime2 = NormalizeExpiry(valueOrDefault);
|
||||
_logger.LogInformation("Seasonal details: Quest {QuestId} '{Name}' rawExpiry={Raw:o} Kind={Kind} TimeOfDay={TimeOfDay} normalizedUtc={Normalized:o}", questInfo.QuestId, questInfo.Name, valueOrDefault, valueOrDefault.Kind, valueOrDefault.TimeOfDay, dateTime2);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Seasonal details: Quest {QuestId} '{Name}' has no expiry (seasonal flag present). IsSeasonalEvent={IsSeasonalEvent} IsSeasonalQuest={IsSeasonalQuest} JournalGenre={JournalGenre} SeasonalQuestExpiry={SeasonalQuestExpiry}", questInfo.QuestId, questInfo.Name, questInfo is QuestInfo questInfo3 && questInfo3.IsSeasonalEvent, questInfo is QuestInfo questInfo4 && questInfo4.IsSeasonalQuest, (questInfo is QuestInfo questInfo5) ? questInfo5.JournalGenre : ((uint?)null), (questInfo is QuestInfo questInfo6) ? questInfo6.SeasonalQuestExpiry : ((DateTime?)null));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogDebug(exception, "Failed to log seasonal details for {QuestId}", questInfo.QuestId);
|
||||
}
|
||||
if (_questFunctions.IsQuestUnobtainable(questInfo.QuestId))
|
||||
{
|
||||
if (_alreadyLoggedActiveSeasonalSkip.Add(questInfo.QuestId.Value))
|
||||
{
|
||||
_logger.LogDebug("Skipping quest {QuestId} '{Name}': marked unobtainable", questInfo.QuestId, questInfo.Name);
|
||||
}
|
||||
}
|
||||
else if (questInfo is UnlockLinkQuestInfo { QuestExpiry: var questExpiry })
|
||||
{
|
||||
if (questExpiry.HasValue)
|
||||
{
|
||||
DateTime valueOrDefault2 = questExpiry.GetValueOrDefault();
|
||||
DateTime dateTime3 = NormalizeExpiry(valueOrDefault2);
|
||||
if (dateTime3 > DateTime.UtcNow)
|
||||
{
|
||||
yield return questInfo;
|
||||
}
|
||||
else if (_alreadyLoggedActiveSeasonalSkip.Add(questInfo.QuestId.Value))
|
||||
{
|
||||
_logger.LogDebug("Skipping UnlockLink quest {QuestId} '{Name}': expiry {Expiry:o} UTC is not in the future", questInfo.QuestId, questInfo.Name, dateTime3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return questInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(questInfo is QuestInfo { SeasonalQuestExpiry: var seasonalQuestExpiry } questInfo7))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (seasonalQuestExpiry.HasValue)
|
||||
{
|
||||
DateTime valueOrDefault3 = seasonalQuestExpiry.GetValueOrDefault();
|
||||
DateTime dateTime4 = NormalizeExpiry(valueOrDefault3);
|
||||
if (dateTime4 > DateTime.UtcNow)
|
||||
{
|
||||
yield return questInfo;
|
||||
}
|
||||
else if (_alreadyLoggedActiveSeasonalSkip.Add(questInfo.QuestId.Value))
|
||||
{
|
||||
_logger.LogDebug("Skipping quest {QuestId} '{Name}': seasonal expiry {Expiry:o} UTC is not in the future", questInfo.QuestId, questInfo.Name, dateTime4);
|
||||
}
|
||||
}
|
||||
else if (questInfo7.IsSeasonalQuest && !questInfo7.SeasonalQuestExpiry.HasValue)
|
||||
{
|
||||
yield return questInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCacheIfNeeded()
|
||||
{
|
||||
if (DateTime.UtcNow - _cachedAtUtc < _cacheDuration)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_cachedActiveSeasonalQuests = GetActiveSeasonalQuestsNoCache().ToList();
|
||||
_cachedAtUtc = DateTime.UtcNow;
|
||||
_logger.LogDebug("Refreshed seasonal quest cache: {Count} active seasonal quests (UTC now {UtcNow:o})", _cachedActiveSeasonalQuests.Count, _cachedAtUtc);
|
||||
foreach (IGrouping<string, IQuestInfo> item in _cachedActiveSeasonalQuests.GroupBy(delegate(IQuestInfo q)
|
||||
{
|
||||
if (q.QuestId is UnlockLinkId)
|
||||
{
|
||||
return "Limited Unlocks";
|
||||
}
|
||||
if (_questRegistry.TryGetQuestFolderName(q.QuestId, out string folderName) && !string.IsNullOrEmpty(folderName))
|
||||
{
|
||||
return folderName;
|
||||
}
|
||||
return q.JournalGenre.HasValue ? GetJournalGenreName(q.JournalGenre.Value) : q.Name;
|
||||
}))
|
||||
{
|
||||
DateTime dateTime = item.Select(delegate(IQuestInfo q)
|
||||
{
|
||||
DateTime? dateTime2 = (q as QuestInfo)?.SeasonalQuestExpiry ?? ((q is UnlockLinkQuestInfo unlockLinkQuestInfo) ? unlockLinkQuestInfo.QuestExpiry : ((DateTime?)null));
|
||||
if (dateTime2.HasValue)
|
||||
{
|
||||
DateTime valueOrDefault = dateTime2.GetValueOrDefault();
|
||||
return NormalizeExpiry(valueOrDefault);
|
||||
}
|
||||
return DateTime.MaxValue;
|
||||
}).DefaultIfEmpty(DateTime.MaxValue).Min();
|
||||
List<string> list = (from q in item
|
||||
select (q as UnlockLinkQuestInfo)?.Patch into p
|
||||
where !string.IsNullOrEmpty(p)
|
||||
select p).Distinct().ToList();
|
||||
string text = ((list.Count == 1) ? list[0] : null);
|
||||
if (dateTime != DateTime.MaxValue)
|
||||
{
|
||||
_logger.LogInformation("Seasonal event '{Name}' ends at {Expiry:o} UTC (patch={Patch})", item.Key, dateTime, text ?? "n/a");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Seasonal event '{Name}' has no expiry (patch={Patch})", item.Key, text ?? "n/a");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshAndLogSeasonalExpiries()
|
||||
{
|
||||
_cachedAtUtc = DateTime.MinValue;
|
||||
UpdateCacheIfNeeded();
|
||||
}
|
||||
|
||||
public static DateTime AtDailyReset(DateOnly date)
|
||||
{
|
||||
return new DateTime(date, new TimeOnly(14, 59, 59), DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
private static DateTime NormalizeExpiry(DateTime d)
|
||||
{
|
||||
TimeSpan timeOfDay = d.TimeOfDay;
|
||||
TimeSpan timeSpan = new TimeSpan(23, 59, 59);
|
||||
if (timeOfDay == TimeSpan.Zero || timeOfDay == timeSpan)
|
||||
{
|
||||
return AtDailyReset(DateOnly.FromDateTime(d));
|
||||
}
|
||||
if (d.Kind != DateTimeKind.Utc)
|
||||
{
|
||||
return d.ToUniversalTime();
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
private static string FormatRemainingDays(TimeSpan remaining)
|
||||
{
|
||||
int num = (int)Math.Ceiling(Math.Max(0.0, remaining.TotalSeconds));
|
||||
int num2 = num / 86400;
|
||||
if (num2 >= 1)
|
||||
{
|
||||
if (num2 != 1)
|
||||
{
|
||||
return $"{num2} days";
|
||||
}
|
||||
return "1 day";
|
||||
}
|
||||
int value = num % 86400 / 3600;
|
||||
int value2 = num % 3600 / 60;
|
||||
int value3 = num % 60;
|
||||
return $"{value:D2}:{value2:D2}:{value3:D2}";
|
||||
}
|
||||
|
||||
private static string FormatRemainingFull(TimeSpan remaining)
|
||||
{
|
||||
int num = (int)Math.Ceiling(Math.Max(0.0, remaining.TotalSeconds));
|
||||
int num2 = num / 86400;
|
||||
int value = num % 86400 / 3600;
|
||||
int value2 = num % 3600 / 60;
|
||||
int value3 = num % 60;
|
||||
if (num2 < 1)
|
||||
{
|
||||
return $"Ends in {value:D2}d {value2:D2}m {value3:D2}s";
|
||||
}
|
||||
return $"Ends in {num2}d {value:D2}h {value2:D2}m {value3:D2}s";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Questionable.Windows.Utils;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
|
||||
internal sealed class ManualPriorityComponent
|
||||
{
|
||||
private readonly QuestController _questController;
|
||||
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
|
||||
private readonly QuestSelector _questSelector;
|
||||
|
||||
private readonly QuestTooltipComponent _questTooltipComponent;
|
||||
|
||||
private readonly UiUtils _uiUtils;
|
||||
|
||||
private readonly IChatGui _chatGui;
|
||||
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
private ElementId? _draggedItem;
|
||||
|
||||
public ManualPriorityComponent(QuestController questController, QuestFunctions questFunctions, QuestSelector questSelector, QuestTooltipComponent questTooltipComponent, UiUtils uiUtils, IChatGui chatGui, IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
ManualPriorityComponent manualPriorityComponent = this;
|
||||
_questController = questController;
|
||||
_questFunctions = questFunctions;
|
||||
_questSelector = questSelector;
|
||||
_questTooltipComponent = questTooltipComponent;
|
||||
_uiUtils = uiUtils;
|
||||
_chatGui = chatGui;
|
||||
_pluginInterface = pluginInterface;
|
||||
_questSelector.SuggestionPredicate = (Quest quest) => !quest.Info.IsMainScenarioQuest && !questFunctions.IsQuestUnobtainable(quest.Id) && questController.ManualPriorityQuests.All((Quest x) => x.Id != quest.Id);
|
||||
_questSelector.DefaultPredicate = (Quest quest) => questFunctions.IsQuestAccepted(quest.Id);
|
||||
_questSelector.QuestSelected = delegate(Quest quest)
|
||||
{
|
||||
manualPriorityComponent._questController.ManualPriorityQuests.Add(quest);
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.TextWrapped("When you have an active Main Scenario Quest, Questionable will prioritise quests in this order:");
|
||||
ImGui.BulletText("Priority quests: class quests, A Realm Reborn primals and raids");
|
||||
ImGui.BulletText("Supported quests from your Journal's To-Do list (always visible on-screen quests)");
|
||||
ImGui.BulletText("Main Scenario Quest (if available and not marked as ignored in your Journal)");
|
||||
ImGui.TextWrapped("Without an active Main Scenario Quest, it will always try to pick up the next MSQ first.");
|
||||
ImGui.Spacing();
|
||||
ImGui.Text("Custom priority quests:");
|
||||
_questSelector.DrawSelection();
|
||||
ImGui.Spacing();
|
||||
if (_questController.ManualPriorityQuests.Count > 0)
|
||||
{
|
||||
ImGui.Text("Priority queue (drag arrow buttons to reorder):");
|
||||
}
|
||||
using (ImRaii.IEndObject endObject = ImRaii.Child("ManualPriorityList", new Vector2(-1f, -27f), border: true))
|
||||
{
|
||||
if (endObject)
|
||||
{
|
||||
DrawQuestList();
|
||||
}
|
||||
}
|
||||
List<ElementId> list = ParseClipboardItems();
|
||||
using (ImRaii.Disabled(list.Count == 0))
|
||||
{
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Download, "Import from Clipboard"))
|
||||
{
|
||||
ImportFromClipboard(list);
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.Disabled(_questController.ManualPriorityQuests.Count == 0))
|
||||
{
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Upload, "Export to Clipboard"))
|
||||
{
|
||||
ExportToClipboard();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Remove finished Quests"))
|
||||
{
|
||||
_questController.ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id));
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
|
||||
{
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, "Clear All"))
|
||||
{
|
||||
_questController.ClearQuestPriority();
|
||||
}
|
||||
}
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
{
|
||||
ImGui.SetTooltip("Hold CTRL to enable this button.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawQuestList()
|
||||
{
|
||||
List<Quest> manualPriorityQuests = _questController.ManualPriorityQuests;
|
||||
if (manualPriorityQuests.Count == 0)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||
ImGui.TextWrapped("No quests in priority queue. Add quests using the selector above to prioritise them over other available quests.");
|
||||
ImGui.PopStyleColor();
|
||||
return;
|
||||
}
|
||||
Quest quest = null;
|
||||
Quest quest2 = null;
|
||||
int index = 0;
|
||||
float x = ImGui.GetContentRegionAvail().X;
|
||||
List<(Vector2, Vector2)> list = new List<(Vector2, Vector2)>();
|
||||
for (int i = 0; i < manualPriorityQuests.Count; i++)
|
||||
{
|
||||
Vector2 item = ImGui.GetCursorScreenPos() + new Vector2(0f, (0f - ImGui.GetStyle().ItemSpacing.Y) / 2f);
|
||||
Quest quest3 = manualPriorityQuests[i];
|
||||
ImU8String id = new ImU8String(5, 1);
|
||||
id.AppendLiteral("Quest");
|
||||
id.AppendFormatted(quest3.Id);
|
||||
using (ImRaii.PushId(id))
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
id = new ImU8String(1, 1);
|
||||
id.AppendFormatted(i + 1);
|
||||
id.AppendLiteral(".");
|
||||
ImGui.Text(id);
|
||||
ImGui.SameLine();
|
||||
(Vector4, FontAwesomeIcon, string) questStyle = _uiUtils.GetQuestStyle(quest3.Id);
|
||||
bool flag;
|
||||
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextColored(in questStyle.Item1, questStyle.Item2.ToIconString());
|
||||
flag = ImGui.IsItemHovered();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text(quest3.Info.Name);
|
||||
flag |= ImGui.IsItemHovered();
|
||||
if (flag)
|
||||
{
|
||||
_questTooltipComponent.Draw(quest3.Info);
|
||||
}
|
||||
if (manualPriorityQuests.Count > 1)
|
||||
{
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X + ImGui.GetStyle().WindowPadding.X - ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X - ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 4f - ImGui.GetStyle().ItemSpacing.X);
|
||||
}
|
||||
if (_draggedItem == quest3.Id)
|
||||
{
|
||||
ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown, ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown);
|
||||
}
|
||||
if (_draggedItem == null && ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
_draggedItem = quest3.Id;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X + ImGui.GetStyle().WindowPadding.X - ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X - ImGui.GetStyle().FramePadding.X * 2f);
|
||||
}
|
||||
}
|
||||
if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
|
||||
{
|
||||
quest = quest3;
|
||||
}
|
||||
}
|
||||
Vector2 item2 = new Vector2(item.X + x, ImGui.GetCursorScreenPos().Y - ImGui.GetStyle().ItemSpacing.Y + 2f);
|
||||
list.Add((item, item2));
|
||||
}
|
||||
if (!ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
_draggedItem = null;
|
||||
}
|
||||
else if (_draggedItem != null)
|
||||
{
|
||||
Quest item3 = manualPriorityQuests.Single((Quest quest4) => quest4.Id == _draggedItem);
|
||||
int num = manualPriorityQuests.IndexOf(item3);
|
||||
var (pMin, pMax) = list[num];
|
||||
ImGui.GetWindowDrawList().AddRect(pMin, pMax, ImGui.GetColorU32(ImGuiColors.DalamudGrey), 3f, ImDrawFlags.RoundCornersAll);
|
||||
int num2 = list.FindIndex(((Vector2 TopLeft, Vector2 BottomRight) tuple2) => ImGui.IsMouseHoveringRect(tuple2.TopLeft, tuple2.BottomRight, clip: true));
|
||||
if (num2 >= 0 && num != num2)
|
||||
{
|
||||
quest2 = manualPriorityQuests.Single((Quest quest4) => quest4.Id == _draggedItem);
|
||||
index = num2;
|
||||
}
|
||||
}
|
||||
if (quest != null)
|
||||
{
|
||||
manualPriorityQuests.Remove(quest);
|
||||
}
|
||||
if (quest2 != null)
|
||||
{
|
||||
manualPriorityQuests.Remove(quest2);
|
||||
manualPriorityQuests.Insert(index, quest2);
|
||||
}
|
||||
}
|
||||
|
||||
public string EncodeQuestPriority()
|
||||
{
|
||||
return "qst:priority:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(';', _questController.ManualPriorityQuests.Select((Quest x) => x.Id.ToString()))));
|
||||
}
|
||||
|
||||
private static List<ElementId> ParseClipboardItems()
|
||||
{
|
||||
return PriorityWindow.DecodeQuestPriority(ImGui.GetClipboardText().Trim());
|
||||
}
|
||||
|
||||
private void ExportToClipboard()
|
||||
{
|
||||
ImGui.SetClipboardText(EncodeQuestPriority());
|
||||
_chatGui.Print("Copied quests to clipboard.", "Questionable", 576);
|
||||
}
|
||||
|
||||
private void ImportFromClipboard(List<ElementId> questElements)
|
||||
{
|
||||
_questController.ImportQuestPriority(questElements);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,788 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
|
||||
internal sealed class PresetBuilderComponent
|
||||
{
|
||||
private sealed class QuestPreset
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
|
||||
public required string Description { get; init; }
|
||||
|
||||
public required int DisplayOrder { get; init; }
|
||||
|
||||
public required List<ElementId> QuestIds { get; init; }
|
||||
|
||||
public List<ElementId> GetQuestIds()
|
||||
{
|
||||
return QuestIds;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly QuestController _questController;
|
||||
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
|
||||
private readonly QuestData _questData;
|
||||
|
||||
private readonly AetheryteFunctions _aetheryteFunctions;
|
||||
|
||||
private readonly IClientState _clientState;
|
||||
|
||||
private readonly IChatGui _chatGui;
|
||||
|
||||
private readonly UiUtils _uiUtils;
|
||||
|
||||
private readonly QuestTooltipComponent _questTooltipComponent;
|
||||
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
|
||||
private readonly ILogger<PresetBuilderComponent> _logger;
|
||||
|
||||
private readonly Dictionary<string, QuestPreset> _availablePresets;
|
||||
|
||||
public PresetBuilderComponent(QuestController questController, QuestFunctions questFunctions, QuestData questData, AetheryteFunctions aetheryteFunctions, IClientState clientState, IChatGui chatGui, UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, QuestRegistry questRegistry, ILogger<PresetBuilderComponent> logger)
|
||||
{
|
||||
_questController = questController;
|
||||
_questFunctions = questFunctions;
|
||||
_questData = questData;
|
||||
_aetheryteFunctions = aetheryteFunctions;
|
||||
_clientState = clientState;
|
||||
_chatGui = chatGui;
|
||||
_uiUtils = uiUtils;
|
||||
_questTooltipComponent = questTooltipComponent;
|
||||
_questRegistry = questRegistry;
|
||||
_logger = logger;
|
||||
_availablePresets = BuildPresetList();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.TextWrapped("Quest presets allow you to quickly add related quests to your priority list. These are useful for unlocking content that may be required later.");
|
||||
ImGui.Spacing();
|
||||
using (ImRaii.IEndObject endObject = ImRaii.Child("PresetList", new Vector2(-1f, -27f), border: true))
|
||||
{
|
||||
if (endObject)
|
||||
{
|
||||
DrawPresetGroups();
|
||||
}
|
||||
}
|
||||
List<ElementId> list = (from questId in GetAllPresetQuests()
|
||||
where _questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
|
||||
select questId).ToList();
|
||||
using (ImRaii.Disabled(list.Count == 0))
|
||||
{
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, $"Clear All Preset Quests ({list.Count})"))
|
||||
{
|
||||
ClearAllPresetQuests(list);
|
||||
}
|
||||
}
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
{
|
||||
if (list.Count == 0)
|
||||
{
|
||||
ImGui.SetTooltip("No preset quests are currently in the priority list.");
|
||||
return;
|
||||
}
|
||||
ImU8String tooltip = new ImU8String(59, 1);
|
||||
tooltip.AppendLiteral("Remove all ");
|
||||
tooltip.AppendFormatted(list.Count);
|
||||
tooltip.AppendLiteral(" preset-related quest(s) from the priority list.");
|
||||
ImGui.SetTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPresetGroups()
|
||||
{
|
||||
IOrderedEnumerable<KeyValuePair<string, QuestPreset>> orderedEnumerable = from x in _availablePresets
|
||||
where x.Key.StartsWith("aether_currents_", StringComparison.Ordinal)
|
||||
orderby x.Value.DisplayOrder
|
||||
select x;
|
||||
IOrderedEnumerable<KeyValuePair<string, QuestPreset>> orderedEnumerable2 = from x in _availablePresets
|
||||
where x.Key.StartsWith("aethernet_", StringComparison.Ordinal)
|
||||
orderby x.Value.DisplayOrder
|
||||
select x;
|
||||
IOrderedEnumerable<KeyValuePair<string, QuestPreset>> orderedEnumerable3 = from x in _availablePresets
|
||||
where !x.Key.StartsWith("aether_currents_", StringComparison.Ordinal) && !x.Key.StartsWith("aethernet_", StringComparison.Ordinal)
|
||||
orderby x.Value.DisplayOrder
|
||||
select x;
|
||||
string key;
|
||||
QuestPreset value;
|
||||
if (DrawGroupHeader("Aether Currents", "Unlock aether currents in various expansion zones to enable flying.", orderedEnumerable))
|
||||
{
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
foreach (KeyValuePair<string, QuestPreset> item in orderedEnumerable)
|
||||
{
|
||||
item.Deconstruct(out key, out value);
|
||||
string key2 = key;
|
||||
QuestPreset preset = value;
|
||||
DrawPreset(key2, preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DrawGroupHeader("City Aethernet", "Unlock aethernet shards in major cities to enable city teleports.", orderedEnumerable2))
|
||||
{
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
foreach (KeyValuePair<string, QuestPreset> item2 in orderedEnumerable2)
|
||||
{
|
||||
item2.Deconstruct(out key, out value);
|
||||
string key3 = key;
|
||||
QuestPreset preset2 = value;
|
||||
DrawPreset(key3, preset2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!orderedEnumerable3.Any() || !DrawGroupHeader("Content Unlocks", "Essential quest series and unlocks that may be required for progression.", orderedEnumerable3))
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
foreach (KeyValuePair<string, QuestPreset> item3 in orderedEnumerable3)
|
||||
{
|
||||
item3.Deconstruct(out key, out value);
|
||||
string key4 = key;
|
||||
QuestPreset preset3 = value;
|
||||
DrawPreset(key4, preset3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool DrawGroupHeader(string groupName, string groupDescription, IEnumerable<KeyValuePair<string, QuestPreset>> presets)
|
||||
{
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
foreach (KeyValuePair<string, QuestPreset> preset2 in presets)
|
||||
{
|
||||
preset2.Deconstruct(out var _, out var value);
|
||||
QuestPreset preset = value;
|
||||
num += GetAvailableQuestsForPreset(preset).Count;
|
||||
num2 += GetCompletedQuestsForPreset(preset).Count;
|
||||
num3 += GetAlreadyPriorityQuestsForPreset(preset).Count;
|
||||
}
|
||||
string text = groupName;
|
||||
if (num > 0 || num2 > 0 || num3 > 0)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
if (num > 0)
|
||||
{
|
||||
list.Add($"{num} available");
|
||||
}
|
||||
if (num3 > 0)
|
||||
{
|
||||
list.Add($"{num3} priority");
|
||||
}
|
||||
if (num2 > 0)
|
||||
{
|
||||
list.Add($"{num2} completed");
|
||||
}
|
||||
if (list.Count > 0)
|
||||
{
|
||||
text = text + " (" + string.Join(", ", list) + ")";
|
||||
}
|
||||
}
|
||||
ImU8String label = new ImU8String(9, 2);
|
||||
label.AppendFormatted(text);
|
||||
label.AppendLiteral("###Group_");
|
||||
label.AppendFormatted(groupName);
|
||||
bool result = ImGui.CollapsingHeader(label);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(groupDescription);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void DrawPreset(string key, QuestPreset preset)
|
||||
{
|
||||
using (ImRaii.PushId(key))
|
||||
{
|
||||
List<ElementId> availableQuestsForPreset = GetAvailableQuestsForPreset(preset);
|
||||
List<ElementId> completedQuestsForPreset = GetCompletedQuestsForPreset(preset);
|
||||
List<ElementId> alreadyPriorityQuestsForPreset = GetAlreadyPriorityQuestsForPreset(preset);
|
||||
string text = preset.Name;
|
||||
if (availableQuestsForPreset.Count > 0 || completedQuestsForPreset.Count > 0 || alreadyPriorityQuestsForPreset.Count > 0)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
if (availableQuestsForPreset.Count > 0)
|
||||
{
|
||||
list.Add($"{availableQuestsForPreset.Count} available");
|
||||
}
|
||||
if (alreadyPriorityQuestsForPreset.Count > 0)
|
||||
{
|
||||
list.Add($"{alreadyPriorityQuestsForPreset.Count} priority");
|
||||
}
|
||||
if (completedQuestsForPreset.Count > 0)
|
||||
{
|
||||
list.Add($"{completedQuestsForPreset.Count} completed");
|
||||
}
|
||||
if (list.Count > 0)
|
||||
{
|
||||
text = text + " (" + string.Join(", ", list) + ")";
|
||||
}
|
||||
}
|
||||
ImU8String label = new ImU8String(3, 2);
|
||||
label.AppendFormatted(text);
|
||||
label.AppendLiteral("###");
|
||||
label.AppendFormatted(key);
|
||||
bool num = ImGui.CollapsingHeader(label);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.TextUnformatted(preset.Description);
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
if (!num)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
ImGui.TextWrapped(preset.Description);
|
||||
ImGui.Spacing();
|
||||
bool flag = key.StartsWith("aethernet_", StringComparison.Ordinal);
|
||||
if (flag && availableQuestsForPreset.Count == 0 && completedQuestsForPreset.Count == 0 && alreadyPriorityQuestsForPreset.Count == 0)
|
||||
{
|
||||
EAetheryteLocation? mainAetheryteForAethernetPreset = GetMainAetheryteForAethernetPreset(key);
|
||||
if (mainAetheryteForAethernetPreset.HasValue && !_aetheryteFunctions.IsAetheryteUnlocked(mainAetheryteForAethernetPreset.Value))
|
||||
{
|
||||
_uiUtils.ChecklistItem("Main aetheryte must be attuned first", ImGuiColors.DalamudRed, FontAwesomeIcon.ExclamationTriangle);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
if (availableQuestsForPreset.Count > 0)
|
||||
{
|
||||
_uiUtils.ChecklistItem($"{availableQuestsForPreset.Count} {((availableQuestsForPreset.Count == 1) ? "quest" : "quests")} available", ImGuiColors.DalamudYellow, FontAwesomeIcon.Running);
|
||||
}
|
||||
if (alreadyPriorityQuestsForPreset.Count > 0)
|
||||
{
|
||||
_uiUtils.ChecklistItem($"{alreadyPriorityQuestsForPreset.Count} {((alreadyPriorityQuestsForPreset.Count == 1) ? "quest" : "quests")} already in priority list", ImGuiColors.DalamudOrange, FontAwesomeIcon.PersonWalkingArrowRight);
|
||||
}
|
||||
if (completedQuestsForPreset.Count > 0)
|
||||
{
|
||||
_uiUtils.ChecklistItem($"{completedQuestsForPreset.Count} {((completedQuestsForPreset.Count == 1) ? "quest" : "quests")} already completed", ImGuiColors.ParsedGreen, FontAwesomeIcon.Check);
|
||||
}
|
||||
if (availableQuestsForPreset.Count == 0 && completedQuestsForPreset.Count == 0 && alreadyPriorityQuestsForPreset.Count == 0)
|
||||
{
|
||||
if (!flag)
|
||||
{
|
||||
goto IL_03b8;
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
EAetheryteLocation? mainAetheryteForAethernetPreset2 = GetMainAetheryteForAethernetPreset(key);
|
||||
if (mainAetheryteForAethernetPreset2.HasValue && _aetheryteFunctions.IsAetheryteUnlocked(mainAetheryteForAethernetPreset2.Value))
|
||||
{
|
||||
goto IL_03b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
goto IL_03d8;
|
||||
IL_03d8:
|
||||
if (availableQuestsForPreset.Count > 0)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
string text2 = ((availableQuestsForPreset.Count == 1) ? "Add Quest to Priority" : $"Add All {availableQuestsForPreset.Count} Quests to Priority");
|
||||
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Plus, text2))
|
||||
{
|
||||
AddPresetToPriority(preset, availableQuestsForPreset);
|
||||
}
|
||||
}
|
||||
if (availableQuestsForPreset.Count > 0 || completedQuestsForPreset.Count > 0 || alreadyPriorityQuestsForPreset.Count > 0)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
foreach (ElementId item in availableQuestsForPreset)
|
||||
{
|
||||
if (_questData.TryGetQuestInfo(item, out IQuestInfo questInfo))
|
||||
{
|
||||
if (_uiUtils.ChecklistItem($"{questInfo.Name} ({item})", ImGuiColors.DalamudYellow, FontAwesomeIcon.Running))
|
||||
{
|
||||
_questTooltipComponent.Draw(questInfo);
|
||||
}
|
||||
ImGui.SameLine(0f, 5f);
|
||||
if (ImGuiComponents.IconButton($"##AddQuest{item}", FontAwesomeIcon.Plus))
|
||||
{
|
||||
AddIndividualQuestToPriority(questInfo, item);
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
label = new ImU8String(23, 1);
|
||||
label.AppendLiteral("Add '");
|
||||
label.AppendFormatted(questInfo.Name);
|
||||
label.AppendLiteral("' to priority list");
|
||||
ImGui.SetTooltip(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (ElementId item2 in alreadyPriorityQuestsForPreset)
|
||||
{
|
||||
if (_questData.TryGetQuestInfo(item2, out IQuestInfo questInfo2) && _uiUtils.ChecklistItem($"{questInfo2.Name} ({item2})", ImGuiColors.DalamudOrange, FontAwesomeIcon.PersonWalkingArrowRight))
|
||||
{
|
||||
_questTooltipComponent.Draw(questInfo2);
|
||||
}
|
||||
}
|
||||
foreach (ElementId item3 in completedQuestsForPreset)
|
||||
{
|
||||
if (_questData.TryGetQuestInfo(item3, out IQuestInfo questInfo3) && _uiUtils.ChecklistItem($"{questInfo3.Name} ({item3})", ImGuiColors.ParsedGreen, FontAwesomeIcon.Check))
|
||||
{
|
||||
_questTooltipComponent.Draw(questInfo3);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Spacing();
|
||||
return;
|
||||
IL_03b8:
|
||||
_uiUtils.ChecklistItem("No applicable quests found", ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus);
|
||||
goto IL_03d8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ElementId> GetAvailableQuestsForPreset(QuestPreset preset)
|
||||
{
|
||||
return (from questId in preset.GetQuestIds()
|
||||
where _questFunctions.IsReadyToAcceptQuest(questId) || _questFunctions.IsQuestAccepted(questId)
|
||||
where !_questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
|
||||
where _questRegistry.IsKnownQuest(questId)
|
||||
select questId).ToList();
|
||||
}
|
||||
|
||||
private List<ElementId> GetCompletedQuestsForPreset(QuestPreset preset)
|
||||
{
|
||||
return (from questId in preset.GetQuestIds()
|
||||
where _questFunctions.IsQuestComplete(questId)
|
||||
select questId).ToList();
|
||||
}
|
||||
|
||||
private List<ElementId> GetAlreadyPriorityQuestsForPreset(QuestPreset preset)
|
||||
{
|
||||
return (from questId in preset.GetQuestIds()
|
||||
where _questController.ManualPriorityQuests.Any((Quest q) => q.Id.Equals(questId))
|
||||
where !_questFunctions.IsQuestComplete(questId)
|
||||
select questId).ToList();
|
||||
}
|
||||
|
||||
private List<ElementId> GetAllPresetQuests()
|
||||
{
|
||||
return _availablePresets.Values.SelectMany((QuestPreset preset) => preset.GetQuestIds()).Distinct().ToList();
|
||||
}
|
||||
|
||||
private void ClearAllPresetQuests(List<ElementId> questsToRemove)
|
||||
{
|
||||
int num = 0;
|
||||
foreach (ElementId questId in questsToRemove)
|
||||
{
|
||||
Quest quest = _questController.ManualPriorityQuests.FirstOrDefault((Quest q) => q.Id.Equals(questId));
|
||||
if (quest != null)
|
||||
{
|
||||
_questController.ManualPriorityQuests.Remove(quest);
|
||||
num++;
|
||||
}
|
||||
}
|
||||
_logger.LogInformation("Removed {Count} preset quests from priority list", num);
|
||||
}
|
||||
|
||||
private void AddPresetToPriority(QuestPreset preset, List<ElementId> questIds)
|
||||
{
|
||||
int num = 0;
|
||||
foreach (ElementId questId in questIds)
|
||||
{
|
||||
if (_questController.AddQuestPriority(questId))
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
_logger.LogInformation("Added {Count} quests from preset '{PresetName}' to priority list", num, preset.Name);
|
||||
}
|
||||
|
||||
private void AddIndividualQuestToPriority(IQuestInfo questInfo, ElementId questId)
|
||||
{
|
||||
if (_questController.AddQuestPriority(questId))
|
||||
{
|
||||
_logger.LogInformation("Added individual quest '{QuestName}' ({QuestId}) to priority list", questInfo.Name, questId);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, QuestPreset> BuildPresetList()
|
||||
{
|
||||
return new Dictionary<string, QuestPreset>
|
||||
{
|
||||
["aether_currents_hw"] = new QuestPreset
|
||||
{
|
||||
Name = "Heavensward",
|
||||
Description = "Unlock all missed aether currents in Heavensward zones to enable flying.",
|
||||
DisplayOrder = 10,
|
||||
QuestIds = GetAetherCurrentQuestsByExpansion(EExpansionVersion.Heavensward)
|
||||
},
|
||||
["aether_currents_sb"] = new QuestPreset
|
||||
{
|
||||
Name = "Stormblood",
|
||||
Description = "Unlock all missed aether currents in Stormblood zones to enable flying.",
|
||||
DisplayOrder = 11,
|
||||
QuestIds = GetAetherCurrentQuestsByExpansion(EExpansionVersion.Stormblood)
|
||||
},
|
||||
["aether_currents_shb"] = new QuestPreset
|
||||
{
|
||||
Name = "Shadowbringers",
|
||||
Description = "Unlock all missed aether currents in Shadowbringers zones to enable flying.",
|
||||
DisplayOrder = 12,
|
||||
QuestIds = GetAetherCurrentQuestsByExpansion(EExpansionVersion.Shadowbringers)
|
||||
},
|
||||
["aether_currents_ew"] = new QuestPreset
|
||||
{
|
||||
Name = "Endwalker",
|
||||
Description = "Unlock all missed aether currents in Endwalker zones to enable flying.",
|
||||
DisplayOrder = 13,
|
||||
QuestIds = GetAetherCurrentQuestsByExpansion(EExpansionVersion.Endwalker)
|
||||
},
|
||||
["aether_currents_dt"] = new QuestPreset
|
||||
{
|
||||
Name = "Dawntrail",
|
||||
Description = "Unlock all missed aether currents in Dawntrail zones to enable flying.",
|
||||
DisplayOrder = 14,
|
||||
QuestIds = GetAetherCurrentQuestsByExpansion(EExpansionVersion.Dawntrail)
|
||||
},
|
||||
["aethernet_limsa"] = new QuestPreset
|
||||
{
|
||||
Name = "Limsa Lominsa",
|
||||
Description = "Unlock all aethernet shards in Limsa Lominsa for convenient city travel.",
|
||||
DisplayOrder = 20,
|
||||
QuestIds = GetAethernetQuestsByCity("Limsa")
|
||||
},
|
||||
["aethernet_gridania"] = new QuestPreset
|
||||
{
|
||||
Name = "Gridania",
|
||||
Description = "Unlock all aethernet shards in Gridania for convenient city travel.",
|
||||
DisplayOrder = 21,
|
||||
QuestIds = GetAethernetQuestsByCity("Gridania")
|
||||
},
|
||||
["aethernet_uldah"] = new QuestPreset
|
||||
{
|
||||
Name = "Ul'dah",
|
||||
Description = "Unlock all aethernet shards in Ul'dah for convenient city travel.",
|
||||
DisplayOrder = 22,
|
||||
QuestIds = GetAethernetQuestsByCity("Uldah")
|
||||
},
|
||||
["aethernet_goldsaucer"] = new QuestPreset
|
||||
{
|
||||
Name = "The Gold Saucer",
|
||||
Description = "Unlock all aethernet shards in The Gold Saucer for convenient city travel.",
|
||||
DisplayOrder = 23,
|
||||
QuestIds = GetAethernetQuestsByCity("GoldSaucer")
|
||||
},
|
||||
["aethernet_ishgard"] = new QuestPreset
|
||||
{
|
||||
Name = "Ishgard",
|
||||
Description = "Unlock all aethernet shards in Ishgard for convenient city travel.",
|
||||
DisplayOrder = 24,
|
||||
QuestIds = GetAethernetQuestsByCity("Ishgard")
|
||||
},
|
||||
["aethernet_idyllshire"] = new QuestPreset
|
||||
{
|
||||
Name = "Idyllshire",
|
||||
Description = "Unlock all aethernet shards in Idyllshire for convenient city travel.",
|
||||
DisplayOrder = 25,
|
||||
QuestIds = GetAethernetQuestsByCity("Idyllshire")
|
||||
},
|
||||
["aethernet_rhalgrs_reach"] = new QuestPreset
|
||||
{
|
||||
Name = "Rhalgr's Reach",
|
||||
Description = "Unlock all aethernet shards in Rhalgr's Reach for convenient city travel.",
|
||||
DisplayOrder = 26,
|
||||
QuestIds = GetAethernetQuestsByCity("Rhalgr's Reach")
|
||||
},
|
||||
["aethernet_kugane"] = new QuestPreset
|
||||
{
|
||||
Name = "Kugane",
|
||||
Description = "Unlock all aethernet shards in Kugane for convenient city travel.",
|
||||
DisplayOrder = 27,
|
||||
QuestIds = GetAethernetQuestsByCity("Kugane")
|
||||
},
|
||||
["aethernet_doman_enclave"] = new QuestPreset
|
||||
{
|
||||
Name = "Doman Enclave",
|
||||
Description = "Unlock all aethernet shards in Doman Enclave for convenient city travel.",
|
||||
DisplayOrder = 28,
|
||||
QuestIds = GetAethernetQuestsByCity("Doman Enclave")
|
||||
},
|
||||
["aethernet_the_crystarium"] = new QuestPreset
|
||||
{
|
||||
Name = "The Crystarium",
|
||||
Description = "Unlock all aethernet shards in The Crystarium for convenient city travel.",
|
||||
DisplayOrder = 29,
|
||||
QuestIds = GetAethernetQuestsByCity("The Crystarium")
|
||||
},
|
||||
["aethernet_eulmore"] = new QuestPreset
|
||||
{
|
||||
Name = "Eulmore",
|
||||
Description = "Unlock all aethernet shards in Eulmore for convenient city travel.",
|
||||
DisplayOrder = 30,
|
||||
QuestIds = GetAethernetQuestsByCity("Eulmore")
|
||||
},
|
||||
["aethernet_old_sharlayan"] = new QuestPreset
|
||||
{
|
||||
Name = "Old Sharlayan",
|
||||
Description = "Unlock all aethernet shards in Old Sharlayan for convenient city travel.",
|
||||
DisplayOrder = 31,
|
||||
QuestIds = GetAethernetQuestsByCity("Old Sharlayan")
|
||||
},
|
||||
["aethernet_radz_at_han"] = new QuestPreset
|
||||
{
|
||||
Name = "Radz-at-Han",
|
||||
Description = "Unlock all aethernet shards in Radz-at-Han for convenient city travel.",
|
||||
DisplayOrder = 32,
|
||||
QuestIds = GetAethernetQuestsByCity("Radz-at-Han")
|
||||
},
|
||||
["aethernet_tuliyollal"] = new QuestPreset
|
||||
{
|
||||
Name = "Tuliyollal",
|
||||
Description = "Unlock all aethernet shards in Tuliyollal for convenient city travel.",
|
||||
DisplayOrder = 33,
|
||||
QuestIds = GetAethernetQuestsByCity("Tuliyollal")
|
||||
},
|
||||
["aethernet_solution_nine"] = new QuestPreset
|
||||
{
|
||||
Name = "Solution Nine",
|
||||
Description = "Unlock all aethernet shards in Solution Nine for convenient city travel.",
|
||||
DisplayOrder = 34,
|
||||
QuestIds = GetAethernetQuestsByCity("Solution Nine")
|
||||
},
|
||||
["crystal_tower"] = new QuestPreset
|
||||
{
|
||||
Name = "Crystal Tower Raids",
|
||||
Description = "Complete the Crystal Tower raid series (required for A Realm Reborn MSQ).",
|
||||
DisplayOrder = 40,
|
||||
QuestIds = QuestData.CrystalTowerQuests.Cast<ElementId>().ToList()
|
||||
},
|
||||
["hard_primals"] = new QuestPreset
|
||||
{
|
||||
Name = "A Realm Reborn Hard Mode Primals",
|
||||
Description = "Unlock hard mode primal fights from A Realm Reborn.",
|
||||
DisplayOrder = 41,
|
||||
QuestIds = QuestData.HardModePrimals.Cast<ElementId>().ToList()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<ElementId> GetAetherCurrentQuestsByExpansion(EExpansionVersion expansion)
|
||||
{
|
||||
uint[] territoryRanges = expansion switch
|
||||
{
|
||||
EExpansionVersion.Heavensward => new uint[5] { 397u, 398u, 399u, 400u, 401u },
|
||||
EExpansionVersion.Stormblood => new uint[6] { 612u, 613u, 614u, 620u, 621u, 622u },
|
||||
EExpansionVersion.Shadowbringers => new uint[6] { 813u, 814u, 815u, 816u, 817u, 818u },
|
||||
EExpansionVersion.Endwalker => new uint[6] { 956u, 957u, 958u, 959u, 960u, 961u },
|
||||
EExpansionVersion.Dawntrail => new uint[6] { 1187u, 1188u, 1189u, 1190u, 1191u, 1192u },
|
||||
_ => Array.Empty<uint>(),
|
||||
};
|
||||
return QuestData.AetherCurrentQuestsByTerritory.Where<KeyValuePair<uint, ImmutableList<QuestId>>>((KeyValuePair<uint, ImmutableList<QuestId>> kvp) => territoryRanges.Contains(kvp.Key)).SelectMany((KeyValuePair<uint, ImmutableList<QuestId>> kvp) => kvp.Value).Cast<ElementId>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static List<ElementId> GetAethernetQuestsByCity(string cityName)
|
||||
{
|
||||
switch (cityName)
|
||||
{
|
||||
case "Limsa":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list15 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list15, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list15);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(1);
|
||||
return list15;
|
||||
}
|
||||
case "Gridania":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list14 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list14, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list14);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(2);
|
||||
return list14;
|
||||
}
|
||||
case "Uldah":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list13 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list13, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list13);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(3);
|
||||
return list13;
|
||||
}
|
||||
case "GoldSaucer":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list12 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list12, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list12);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(4);
|
||||
return list12;
|
||||
}
|
||||
case "Ishgard":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list11 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list11, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list11);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(5);
|
||||
return list11;
|
||||
}
|
||||
case "Idyllshire":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list10 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list10, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list10);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(6);
|
||||
return list10;
|
||||
}
|
||||
case "Rhalgr's Reach":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list9 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list9, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list9);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(7);
|
||||
return list9;
|
||||
}
|
||||
case "Kugane":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list8 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list8, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list8);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(8);
|
||||
return list8;
|
||||
}
|
||||
case "Doman Enclave":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list7 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list7, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list7);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(9);
|
||||
return list7;
|
||||
}
|
||||
case "The Crystarium":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list6 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list6, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list6);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(10);
|
||||
return list6;
|
||||
}
|
||||
case "Eulmore":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list5 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list5, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list5);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(11);
|
||||
return list5;
|
||||
}
|
||||
case "Old Sharlayan":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list4 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list4, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list4);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(12);
|
||||
return list4;
|
||||
}
|
||||
case "Radz-at-Han":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list3 = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list3, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list3);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(13);
|
||||
return list3;
|
||||
}
|
||||
case "Tuliyollal":
|
||||
{
|
||||
int index = 1;
|
||||
List<ElementId> list2 = new List<ElementId>(index);
|
||||
CollectionsMarshal.SetCount(list2, index);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list2);
|
||||
int num = 0;
|
||||
span[num] = new AethernetId(14);
|
||||
return list2;
|
||||
}
|
||||
case "Solution Nine":
|
||||
{
|
||||
int num = 1;
|
||||
List<ElementId> list = new List<ElementId>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
Span<ElementId> span = CollectionsMarshal.AsSpan(list);
|
||||
int index = 0;
|
||||
span[index] = new AethernetId(15);
|
||||
return list;
|
||||
}
|
||||
default:
|
||||
return new List<ElementId>();
|
||||
}
|
||||
}
|
||||
|
||||
private static EAetheryteLocation? GetMainAetheryteForAethernetPreset(string presetKey)
|
||||
{
|
||||
return presetKey switch
|
||||
{
|
||||
"aethernet_limsa" => EAetheryteLocation.Limsa,
|
||||
"aethernet_gridania" => EAetheryteLocation.Gridania,
|
||||
"aethernet_uldah" => EAetheryteLocation.Uldah,
|
||||
"aethernet_goldsaucer" => EAetheryteLocation.GoldSaucer,
|
||||
"aethernet_ishgard" => EAetheryteLocation.Ishgard,
|
||||
"aethernet_idyllshire" => EAetheryteLocation.Idyllshire,
|
||||
"aethernet_rhalgrs_reach" => EAetheryteLocation.RhalgrsReach,
|
||||
"aethernet_kugane" => EAetheryteLocation.Kugane,
|
||||
"aethernet_doman_enclave" => EAetheryteLocation.DomanEnclave,
|
||||
"aethernet_the_crystarium" => EAetheryteLocation.Crystarium,
|
||||
"aethernet_eulmore" => EAetheryteLocation.Eulmore,
|
||||
"aethernet_old_sharlayan" => EAetheryteLocation.OldSharlayan,
|
||||
"aethernet_radz_at_han" => EAetheryteLocation.RadzAtHan,
|
||||
"aethernet_tuliyollal" => EAetheryteLocation.Tuliyollal,
|
||||
"aethernet_solution_nine" => EAetheryteLocation.SolutionNine,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin;
|
||||
using Questionable.Data;
|
||||
using Questionable.Validation;
|
||||
|
||||
namespace Questionable.Windows.QuestComponents;
|
||||
|
||||
internal sealed class QuestValidationComponent
|
||||
{
|
||||
private readonly QuestValidator _questValidator;
|
||||
|
||||
private readonly QuestData _questData;
|
||||
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
private readonly ValidationDetailsRenderer _detailsRenderer;
|
||||
|
||||
public bool ShouldDraw => _questValidator.Issues.Count > 0;
|
||||
|
||||
public QuestValidationComponent(QuestValidator questValidator, QuestData questData, IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
_questValidator = questValidator;
|
||||
_questData = questData;
|
||||
_pluginInterface = pluginInterface;
|
||||
_detailsRenderer = new ValidationDetailsRenderer(questData, pluginInterface);
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
DrawSummaryHeader();
|
||||
ImGui.Separator();
|
||||
DrawValidationTable();
|
||||
_detailsRenderer.DrawDetailWindows();
|
||||
}
|
||||
|
||||
private void DrawSummaryHeader()
|
||||
{
|
||||
int issueCount = _questValidator.IssueCount;
|
||||
int errorCount = _questValidator.ErrorCount;
|
||||
int num = issueCount - errorCount;
|
||||
ImU8String text = new ImU8String(33, 1);
|
||||
text.AppendLiteral("Validation Results: ");
|
||||
text.AppendFormatted(issueCount);
|
||||
text.AppendLiteral(" total issues");
|
||||
ImGui.Text(text);
|
||||
if (errorCount > 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("(");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
text = new ImU8String(7, 1);
|
||||
text.AppendFormatted(errorCount);
|
||||
text.AppendLiteral(" errors");
|
||||
ImGui.Text(text);
|
||||
}
|
||||
}
|
||||
if (num > 0)
|
||||
{
|
||||
if (errorCount > 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange))
|
||||
{
|
||||
text = new ImU8String(12, 1);
|
||||
text.AppendLiteral(", ");
|
||||
text.AppendFormatted(num);
|
||||
text.AppendLiteral(" warnings)");
|
||||
ImGui.Text(text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("(");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange))
|
||||
{
|
||||
text = new ImU8String(10, 1);
|
||||
text.AppendFormatted(num);
|
||||
text.AppendLiteral(" warnings)");
|
||||
ImGui.Text(text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (errorCount > 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(")");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawValidationTable()
|
||||
{
|
||||
using ImRaii.IEndObject endObject = ImRaii.Table("ValidationIssues", 6, ImGuiTableFlags.Borders | ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY);
|
||||
if (!(!endObject))
|
||||
{
|
||||
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, 60f);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 250f);
|
||||
ImGui.TableSetupColumn("Seq", ImGuiTableColumnFlags.WidthFixed, 40f);
|
||||
ImGui.TableSetupColumn("Step", ImGuiTableColumnFlags.WidthFixed, 40f);
|
||||
ImGui.TableSetupColumn("Issue", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 60f);
|
||||
ImGui.TableHeadersRow();
|
||||
IReadOnlyList<ValidationIssue> issues = _questValidator.Issues;
|
||||
for (int i = 0; i < issues.Count; i++)
|
||||
{
|
||||
DrawValidationRow(issues[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawValidationRow(ValidationIssue issue, int index)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
ImGui.TextUnformatted(issue.ElementId?.ToString() ?? string.Empty);
|
||||
}
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
ImGui.TextUnformatted((issue.ElementId != null) ? _questData.GetQuestInfo(issue.ElementId).Name : issue.AlliedSociety.ToString());
|
||||
}
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
if (issue.Sequence.HasValue)
|
||||
{
|
||||
ImGui.TextUnformatted(issue.Sequence.Value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey2))
|
||||
{
|
||||
ImGui.TextUnformatted("\ufffd");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
if (issue.Step.HasValue)
|
||||
{
|
||||
ImGui.TextUnformatted(issue.Step.Value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey2))
|
||||
{
|
||||
ImGui.TextUnformatted("\ufffd");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
DrawIssueCell(issue, index);
|
||||
}
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
DrawActionsCell(issue, index);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawIssueCell(ValidationIssue issue, int index)
|
||||
{
|
||||
Vector4 color = ((issue.Severity == EIssueSeverity.Error) ? ImGuiColors.DalamudRed : ImGuiColors.DalamudOrange);
|
||||
FontAwesomeIcon icon = ((issue.Severity == EIssueSeverity.Error) ? FontAwesomeIcon.ExclamationTriangle : FontAwesomeIcon.InfoCircle);
|
||||
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, color))
|
||||
{
|
||||
ImGui.TextUnformatted(icon.ToIconString());
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
string issueSummary = GetIssueSummary(issue);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, color))
|
||||
{
|
||||
ImGui.TextWrapped(issueSummary);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawActionsCell(ValidationIssue issue, int index)
|
||||
{
|
||||
if (HasDetailedDescription(issue) && ImGui.SmallButton($"Details##{index}"))
|
||||
{
|
||||
_detailsRenderer.OpenDetails(issue, index);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetIssueSummary(ValidationIssue issue)
|
||||
{
|
||||
string text = ValidationDetailsRenderer.CleanJsonText(issue.Description ?? string.Empty);
|
||||
if (issue.Type == EIssueType.QuestDisabled && issue.ElementId == null)
|
||||
{
|
||||
string[] array = text.Split(':', 2);
|
||||
if (array.Length == 0)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return array[0];
|
||||
}
|
||||
if (issue.Type == EIssueType.InvalidJsonSchema)
|
||||
{
|
||||
string[] array2 = text.Split('\n', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (array2.Length == 0)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return array2[0];
|
||||
}
|
||||
if (issue.Type == EIssueType.InvalidJsonSyntax)
|
||||
{
|
||||
string[] array3 = text.Split('\n', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (array3.Length == 0)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return array3[0];
|
||||
}
|
||||
string[] array4 = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (array4.Length <= 2)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return array4[0] + ((array4.Length > 1) ? "..." : "");
|
||||
}
|
||||
|
||||
private static bool HasDetailedDescription(ValidationIssue issue)
|
||||
{
|
||||
string text = issue.Description ?? string.Empty;
|
||||
if (issue.Type == EIssueType.QuestDisabled && issue.ElementId == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (issue.Type == EIssueType.InvalidJsonSchema)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (issue.Type == EIssueType.InvalidJsonSyntax)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!text.Contains('\n', StringComparison.Ordinal))
|
||||
{
|
||||
return text.Length > 100;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,567 @@
|
|||
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 sealed record JsonValidationError
|
||||
{
|
||||
public string Path { get; init; } = string.Empty;
|
||||
|
||||
public List<string> Messages { get; init; } = new List<string>();
|
||||
}
|
||||
|
||||
private readonly QuestData _questData;
|
||||
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
private readonly Dictionary<int, ValidationIssue> _storedIssues = new Dictionary<int, ValidationIssue>();
|
||||
|
||||
private readonly Dictionary<int, bool> _openDetailWindows = new Dictionary<int, bool>();
|
||||
|
||||
private static readonly Regex JsonPropertyPathRegex = new Regex("#/([^:]*)", RegexOptions.Compiled);
|
||||
|
||||
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<int> list = new List<int>();
|
||||
foreach (KeyValuePair<int, bool> item in _openDetailWindows.ToList())
|
||||
{
|
||||
if (item.Value && _storedIssues.TryGetValue(item.Key, out ValidationIssue value))
|
||||
{
|
||||
string obj = $"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));
|
||||
if (ImGui.Begin(obj, 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 == EIssueSeverity.Error) ? ImGuiColors.DalamudRed : ImGuiColors.DalamudOrange);
|
||||
FontAwesomeIcon icon = ((issue.Severity == EIssueSeverity.Error) ? FontAwesomeIcon.ExclamationTriangle : 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();
|
||||
if (issue.ElementId != null)
|
||||
{
|
||||
IQuestInfo questInfo = _questData.GetQuestInfo(issue.ElementId);
|
||||
ImU8String text = new ImU8String(10, 2);
|
||||
text.AppendLiteral("Quest: ");
|
||||
text.AppendFormatted(issue.ElementId);
|
||||
text.AppendLiteral(" - ");
|
||||
text.AppendFormatted(questInfo.Name);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
else if (issue.AlliedSociety != EAlliedSociety.None)
|
||||
{
|
||||
ImU8String text = new ImU8String(16, 1);
|
||||
text.AppendLiteral("Allied Society: ");
|
||||
text.AppendFormatted(issue.AlliedSociety);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
if (issue.Sequence.HasValue)
|
||||
{
|
||||
ImU8String text = new ImU8String(10, 1);
|
||||
text.AppendLiteral("Sequence: ");
|
||||
text.AppendFormatted(issue.Sequence);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
if (issue.Step.HasValue)
|
||||
{
|
||||
ImU8String text = new ImU8String(6, 1);
|
||||
text.AppendLiteral("Step: ");
|
||||
text.AppendFormatted(issue.Step);
|
||||
ImGui.Text(text);
|
||||
}
|
||||
ImGui.Separator();
|
||||
ImGui.Text("Description:");
|
||||
string description = CleanJsonText(issue.Description ?? "(no description)");
|
||||
if (issue.Type == EIssueType.QuestDisabled && issue.ElementId == null)
|
||||
{
|
||||
DrawDisabledTribesDetails(description);
|
||||
}
|
||||
else if (issue.Type == EIssueType.InvalidJsonSchema)
|
||||
{
|
||||
DrawEnhancedJsonSchemaDetails(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("\ufffd ", 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<string> 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("\ufffd ");
|
||||
text.AppendFormatted(value);
|
||||
text.AppendLiteral(" - ");
|
||||
text.AppendFormatted(questInfo.Name);
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ImU8String text = new ImU8String(18, 1);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(value);
|
||||
text.AppendLiteral(" (unknown quest)");
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImU8String text = new ImU8String(2, 1);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(value);
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Unindent();
|
||||
}
|
||||
|
||||
private void DrawEnhancedJsonSchemaDetails(string description)
|
||||
{
|
||||
if (description.Split('\n').Length == 0)
|
||||
{
|
||||
ImGui.TextWrapped("No validation details available.");
|
||||
return;
|
||||
}
|
||||
List<JsonValidationError> list = ParseJsonValidationErrors(description);
|
||||
if (list.Count > 0)
|
||||
{
|
||||
ImGui.Text("JSON Schema Validation Errors:");
|
||||
ImGui.Spacing();
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
DrawJsonValidationError(list[i], i);
|
||||
if (i < list.Count - 1)
|
||||
{
|
||||
ImGui.Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSimpleJsonSchemaDetails(description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJsonValidationError(JsonValidationError error, int index)
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow))
|
||||
{
|
||||
ImU8String text = new ImU8String(8, 1);
|
||||
text.AppendLiteral("Error #");
|
||||
text.AppendFormatted(index + 1);
|
||||
text.AppendLiteral(":");
|
||||
ImGui.Text(text);
|
||||
}
|
||||
ImGui.Indent(12f);
|
||||
if (!string.IsNullOrEmpty(error.Path))
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text("Location:");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue))
|
||||
{
|
||||
ImGui.TextWrapped(FormatJsonPath(error.Path));
|
||||
}
|
||||
}
|
||||
if (error.Messages.Count > 0)
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Text((error.Messages.Count == 1) ? "Issue:" : "Issues:");
|
||||
foreach (string message in error.Messages)
|
||||
{
|
||||
ImGui.Indent(12f);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
string text2 = CleanJsonText(message);
|
||||
if (string.Equals(text2, "validation failed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text2 = "JSON schema validation failed - check that all properties match the expected format and values";
|
||||
}
|
||||
ImU8String text = new ImU8String(2, 1);
|
||||
text.AppendLiteral("\ufffd ");
|
||||
text.AppendFormatted(text2);
|
||||
ImGui.TextWrapped(text);
|
||||
ImGui.Unindent(12f);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<string> validationSuggestions = GetValidationSuggestions(error);
|
||||
if (validationSuggestions.Count > 0)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen))
|
||||
{
|
||||
ImGui.Text("Suggestions:");
|
||||
foreach (string item in validationSuggestions)
|
||||
{
|
||||
ImGui.Indent(12f);
|
||||
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow))
|
||||
{
|
||||
ImGui.Text(FontAwesomeIcon.Lightbulb.ToIconString());
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.HealerGreen))
|
||||
{
|
||||
ImGui.TextWrapped(item);
|
||||
ImGui.Unindent(12f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Unindent(12f);
|
||||
}
|
||||
|
||||
private static void DrawSimpleJsonSchemaDetails(string description)
|
||||
{
|
||||
string[] array = description.Split('\n');
|
||||
foreach (string text in array)
|
||||
{
|
||||
if (text.StartsWith("JSON Validation failed:", StringComparison.Ordinal))
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(text);
|
||||
}
|
||||
}
|
||||
else if (text.StartsWith(" - ", StringComparison.Ordinal))
|
||||
{
|
||||
int num = text.IndexOf(':', 3);
|
||||
if (num > 0)
|
||||
{
|
||||
string path = text.Substring(3, num - 3).Trim();
|
||||
string text2 = CleanJsonText(text.Substring(num + 1).Trim());
|
||||
if (string.Equals(text2, "validation failed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text2 = "Schema validation failed - check property format and values";
|
||||
}
|
||||
ImGui.Text("\ufffd");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedBlue))
|
||||
{
|
||||
ImGui.Text(FormatJsonPath(path));
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.Text(":");
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(text2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
|
||||
{
|
||||
ImGui.TextWrapped(CleanJsonText(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextWrapped(CleanJsonText(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<JsonValidationError> ParseJsonValidationErrors(string description)
|
||||
{
|
||||
List<JsonValidationError> list = new List<JsonValidationError>();
|
||||
string[] array = description.Split('\n');
|
||||
foreach (string text in array)
|
||||
{
|
||||
if (!text.StartsWith(" - ", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int num = text.IndexOf(':', 3);
|
||||
if (num > 0)
|
||||
{
|
||||
string path = text.Substring(3, num - 3).Trim();
|
||||
List<string> messages = (from m in text.Substring(num + 1).Trim().Split(';', StringSplitOptions.RemoveEmptyEntries)
|
||||
select CleanJsonText(m.Trim()) into m
|
||||
where !string.IsNullOrEmpty(m)
|
||||
select m).ToList();
|
||||
list.Add(new JsonValidationError
|
||||
{
|
||||
Path = path,
|
||||
Messages = messages
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string FormatJsonPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return "<root>";
|
||||
}
|
||||
Match match = JsonPropertyPathRegex.Match(path);
|
||||
if (match.Success)
|
||||
{
|
||||
string value = match.Groups[1].Value;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return "<root>";
|
||||
}
|
||||
return value.Replace('/', '.');
|
||||
}
|
||||
if (!(path == "<root>"))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return "<root>";
|
||||
}
|
||||
|
||||
private static List<string> GetValidationSuggestions(JsonValidationError error)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
foreach (string message in error.Messages)
|
||||
{
|
||||
string text = message.ToUpperInvariant();
|
||||
if (text.Contains("REQUIRED", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Add the missing required property to your JSON.");
|
||||
}
|
||||
else if (text.Contains("TYPE", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Check that the property value has the correct data type (string, number, boolean, etc.).");
|
||||
}
|
||||
else if (text.Contains("ENUM", StringComparison.Ordinal) || text.Contains("ALLOWED VALUES", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Use one of the allowed enumeration values for this property.");
|
||||
}
|
||||
else if (text.Contains("FORMAT", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Ensure the property value follows the expected format.");
|
||||
}
|
||||
else if (text.Contains("MINIMUM", StringComparison.Ordinal) || text.Contains("MAXIMUM", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Check that numeric values are within the allowed range.");
|
||||
}
|
||||
else if (text.Contains("ADDITIONAL", StringComparison.Ordinal) && text.Contains("NOT ALLOWED", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Remove any extra properties that are not defined in the schema.");
|
||||
}
|
||||
else if (text.Contains("VALIDATION FAILED", StringComparison.Ordinal))
|
||||
{
|
||||
list.Add("Review the JSON structure and ensure all properties match the expected schema format.");
|
||||
}
|
||||
}
|
||||
return list.Distinct().ToList();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue