forked from aly/qstbak
801 lines
26 KiB
C#
801 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Plugin;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller;
|
|
using Questionable.Controller.Utils;
|
|
using Questionable.Data;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
using Questionable.Validation;
|
|
using Questionable.Windows.QuestComponents;
|
|
|
|
namespace Questionable.Windows.JournalComponents;
|
|
|
|
internal sealed class QuestJournalComponent
|
|
{
|
|
private enum RowType : byte
|
|
{
|
|
Section,
|
|
Category,
|
|
Genre,
|
|
Quest
|
|
}
|
|
|
|
private readonly record struct FlatRow(RowType Type, int Depth, int GroupIndex, IQuestInfo? Quest, string Label, JournalCounts Counts);
|
|
|
|
private sealed record FilteredSection(JournalData.Section Section, List<FilteredCategory> Categories);
|
|
|
|
private sealed record FilteredCategory(JournalData.Category Category, List<FilteredGenre> Genres);
|
|
|
|
private sealed record FilteredGenre(JournalData.Genre Genre, List<IQuestInfo> Quests);
|
|
|
|
private sealed record JournalCounts(int Available, int Total, int Obtainable, int Completed)
|
|
{
|
|
public string FormattedSupported { get; } = FormatPair(Available, Total);
|
|
|
|
public string FormattedCompleted { get; } = FormatPair(Completed, Obtainable);
|
|
|
|
public bool IsSupportedComplete
|
|
{
|
|
get
|
|
{
|
|
if (Available == Total)
|
|
{
|
|
return Total > 0;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool IsCompletedComplete
|
|
{
|
|
get
|
|
{
|
|
if (Completed == Obtainable)
|
|
{
|
|
return Obtainable > 0;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static readonly int PadWidth = 9999.ToString(CultureInfo.CurrentCulture).Length;
|
|
|
|
public JournalCounts()
|
|
: this(0, 0, 0, 0)
|
|
{
|
|
}
|
|
|
|
private static string FormatPair(int n, int d)
|
|
{
|
|
if (d != 0)
|
|
{
|
|
return n.ToString(CultureInfo.CurrentCulture).PadLeft(PadWidth) + " / " + d.ToString(CultureInfo.CurrentCulture).PadLeft(PadWidth);
|
|
}
|
|
return "-".PadLeft(PadWidth) + " / " + "-".PadLeft(PadWidth);
|
|
}
|
|
|
|
[CompilerGenerated]
|
|
private JournalCounts(JournalCounts original)
|
|
{
|
|
Available = original.Available;
|
|
Total = original.Total;
|
|
Obtainable = original.Obtainable;
|
|
Completed = original.Completed;
|
|
FormattedSupported = original.FormattedSupported;
|
|
FormattedCompleted = original.FormattedCompleted;
|
|
}
|
|
}
|
|
|
|
private readonly record struct CachedQuestDisplay(Vector4 SupportColor, FontAwesomeIcon SupportIcon, Vector4 CompletionColor, FontAwesomeIcon CompletionIcon, string CompletionText);
|
|
|
|
internal sealed class FilterConfiguration
|
|
{
|
|
public string SearchText = string.Empty;
|
|
|
|
public bool AvailableOnly;
|
|
|
|
public bool HideNoPaths;
|
|
|
|
public bool HideUnobtainable;
|
|
|
|
public bool AdvancedFiltersActive
|
|
{
|
|
get
|
|
{
|
|
if (!AvailableOnly && !HideNoPaths)
|
|
{
|
|
return HideUnobtainable;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public FilterConfiguration WithoutName()
|
|
{
|
|
return new FilterConfiguration
|
|
{
|
|
AvailableOnly = AvailableOnly,
|
|
HideNoPaths = HideNoPaths,
|
|
HideUnobtainable = HideUnobtainable
|
|
};
|
|
}
|
|
}
|
|
|
|
private readonly Dictionary<JournalData.Genre, JournalCounts> _genreCounts = new Dictionary<JournalData.Genre, JournalCounts>();
|
|
|
|
private readonly Dictionary<JournalData.Category, JournalCounts> _categoryCounts = new Dictionary<JournalData.Category, JournalCounts>();
|
|
|
|
private readonly Dictionary<JournalData.Section, JournalCounts> _sectionCounts = new Dictionary<JournalData.Section, JournalCounts>();
|
|
|
|
private int _refreshGenreIndex = -1;
|
|
|
|
private int _refreshDisplayIndex = -1;
|
|
|
|
private bool _refreshHideSeasonal;
|
|
|
|
private const int GenresPerFrame = 20;
|
|
|
|
private readonly JournalData _journalData;
|
|
|
|
private readonly QuestRegistry _questRegistry;
|
|
|
|
private readonly QuestFunctions _questFunctions;
|
|
|
|
private readonly UiUtils _uiUtils;
|
|
|
|
private readonly QuestTooltipComponent _questTooltipComponent;
|
|
|
|
private readonly IDalamudPluginInterface _pluginInterface;
|
|
|
|
private readonly QuestJournalUtils _questJournalUtils;
|
|
|
|
private readonly QuestValidator _questValidator;
|
|
|
|
private readonly Configuration _configuration;
|
|
|
|
private readonly ILogger<QuestJournalComponent> _logger;
|
|
|
|
private List<FilteredSection> _filteredSections = new List<FilteredSection>();
|
|
|
|
private readonly Dictionary<ElementId, CachedQuestDisplay> _displayCache = new Dictionary<ElementId, CachedQuestDisplay>();
|
|
|
|
private List<FlatRow> _flatRows = new List<FlatRow>();
|
|
|
|
private readonly HashSet<(RowType Type, int GroupIndex)> _expandedGroups = new HashSet<(RowType, int)>();
|
|
|
|
private bool _lastHideSeasonalGlobally;
|
|
|
|
internal FilterConfiguration Filter { get; } = new FilterConfiguration();
|
|
|
|
public QuestJournalComponent(JournalData journalData, QuestRegistry questRegistry, QuestFunctions questFunctions, UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, IDalamudPluginInterface pluginInterface, QuestJournalUtils questJournalUtils, QuestValidator questValidator, Configuration configuration, ILogger<QuestJournalComponent> logger)
|
|
{
|
|
_journalData = journalData;
|
|
_questRegistry = questRegistry;
|
|
_questFunctions = questFunctions;
|
|
_uiUtils = uiUtils;
|
|
_questTooltipComponent = questTooltipComponent;
|
|
_pluginInterface = pluginInterface;
|
|
_questJournalUtils = questJournalUtils;
|
|
_questValidator = questValidator;
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
_lastHideSeasonalGlobally = _configuration.General.HideSeasonalEventsFromJournalProgress;
|
|
}
|
|
|
|
public unsafe void DrawQuests()
|
|
{
|
|
if (_refreshGenreIndex >= 0 || _refreshDisplayIndex >= 0)
|
|
{
|
|
TickRefresh();
|
|
}
|
|
bool hideSeasonalEventsFromJournalProgress = _configuration.General.HideSeasonalEventsFromJournalProgress;
|
|
if (hideSeasonalEventsFromJournalProgress != _lastHideSeasonalGlobally)
|
|
{
|
|
_lastHideSeasonalGlobally = hideSeasonalEventsFromJournalProgress;
|
|
_logger.LogDebug("Configuration change detected: HideSeasonalEventsFromJournalProgress={Hide} - refreshing journal", hideSeasonalEventsFromJournalProgress);
|
|
UpdateFilter();
|
|
}
|
|
_questJournalUtils.AddAllAvailableQuests();
|
|
ImGui.SameLine();
|
|
if (UiThemeUtils.SearchInput("##QuestSearch", ref Filter.SearchText, -1E-45f, "Search quests and categories"))
|
|
{
|
|
UpdateFilter();
|
|
}
|
|
int num = 0 | (UiThemeUtils.WrappedCheckbox("Available only", ref Filter.AvailableOnly) ? 1 : 0);
|
|
ImGui.SameLine();
|
|
int num2 = num | (UiThemeUtils.WrappedCheckbox("Hide without paths", ref Filter.HideNoPaths) ? 1 : 0);
|
|
ImGui.SameLine();
|
|
if (((uint)num2 | (UiThemeUtils.WrappedCheckbox("Hide unobtainable", ref Filter.HideUnobtainable) ? 1u : 0u)) != 0)
|
|
{
|
|
UpdateFilter();
|
|
}
|
|
ImGui.Separator();
|
|
bool flag = _refreshGenreIndex >= 0 || _refreshDisplayIndex >= 0;
|
|
if (flag)
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.StatusActive, "Loading...");
|
|
}
|
|
if (_flatRows.Count > 0)
|
|
{
|
|
float x;
|
|
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
|
{
|
|
x = ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
|
|
}
|
|
float fontGlobalScale = ImGui.GetIO().FontGlobalScale;
|
|
using ImRaii.TableDisposable tableDisposable = UiThemeUtils.BeginThemedTable("Quests", 3, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.ScrollY);
|
|
if (!tableDisposable)
|
|
{
|
|
return;
|
|
}
|
|
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
|
|
ImGui.TableSetupColumn("Supported", ImGuiTableColumnFlags.WidthFixed, 120f * fontGlobalScale);
|
|
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 120f * fontGlobalScale);
|
|
ImGui.TableHeadersRow();
|
|
float textLineHeightWithSpacing = ImGui.GetTextLineHeightWithSpacing();
|
|
bool flag2 = false;
|
|
ImGuiListClipper* intPtr = ImGuiNative.ImGuiListClipper();
|
|
ImGuiNative.Begin(intPtr, _flatRows.Count, textLineHeightWithSpacing);
|
|
ImGuiListClipperPtr imGuiListClipperPtr = intPtr;
|
|
try
|
|
{
|
|
while (imGuiListClipperPtr.Step())
|
|
{
|
|
for (int i = imGuiListClipperPtr.DisplayStart; i < imGuiListClipperPtr.DisplayEnd; i++)
|
|
{
|
|
if (DrawFlatRow(_flatRows[i], x))
|
|
{
|
|
flag2 = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
imGuiListClipperPtr.Destroy();
|
|
}
|
|
if (flag2)
|
|
{
|
|
RebuildFlatRows();
|
|
}
|
|
return;
|
|
}
|
|
if (!flag)
|
|
{
|
|
UiThemeUtils.EmptyState(FontAwesomeIcon.Book, "No quest or category matches your search.");
|
|
}
|
|
}
|
|
|
|
private bool DrawFlatRow(FlatRow row, float supportIconSpacing)
|
|
{
|
|
ImGui.TableNextRow();
|
|
ImGui.TableNextColumn();
|
|
bool result = false;
|
|
float num = (float)row.Depth * ImGui.GetStyle().IndentSpacing;
|
|
if (row.Type == RowType.Quest)
|
|
{
|
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
|
|
DrawQuestName(row.Quest, out var expansionHovered);
|
|
_questRegistry.TryGetQuest(row.Quest.QuestId, out Quest quest);
|
|
DrawQuestTooltipAndContextMenu(row.Quest, quest, expansionHovered);
|
|
ImGui.TableNextColumn();
|
|
DrawQuestSupportStatus(row.Quest, supportIconSpacing);
|
|
ImGui.TableNextColumn();
|
|
DrawQuestCompletionStatus(row.Quest);
|
|
}
|
|
else
|
|
{
|
|
(RowType, int) item = (row.Type, row.GroupIndex);
|
|
bool flag = _expandedGroups.Contains(item);
|
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
|
|
UiThemeUtils.DrawTrackerGroupCaret(_pluginInterface.UiBuilder.IconFontFixedWidthHandle, flag);
|
|
ImGui.SameLine();
|
|
ImU8String label = new ImU8String(5, 2);
|
|
label.AppendFormatted(row.Label);
|
|
label.AppendLiteral("##grp");
|
|
label.AppendFormatted(row.GroupIndex);
|
|
if (ImGui.Selectable(label))
|
|
{
|
|
if (flag)
|
|
{
|
|
_expandedGroups.Remove(item);
|
|
}
|
|
else
|
|
{
|
|
_expandedGroups.Add(item);
|
|
}
|
|
result = true;
|
|
}
|
|
ImGui.TableNextColumn();
|
|
UiThemeUtils.DrawTrackerCount(row.Counts.FormattedSupported, row.Counts.IsSupportedComplete, row.Counts.Total == 0);
|
|
ImGui.TableNextColumn();
|
|
UiThemeUtils.DrawTrackerCount(row.Counts.FormattedCompleted, row.Counts.IsCompletedComplete, row.Counts.Obtainable == 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static void DrawQuestName(IQuestInfo questInfo, out bool expansionHovered)
|
|
{
|
|
expansionHovered = false;
|
|
if (questInfo.Expansion != (EExpansionVersion)255)
|
|
{
|
|
ImGui.PushFont(UiBuilder.MonoFont);
|
|
ImGui.PushStyleColor(ImGuiCol.Text, questInfo.Expansion.GetExpansionColor());
|
|
ImGui.TextUnformatted(questInfo.Expansion.ToAbbreviation().PadRight(3));
|
|
ImGui.PopStyleColor();
|
|
ImGui.PopFont();
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
expansionHovered = true;
|
|
}
|
|
ImGui.SameLine();
|
|
}
|
|
ImU8String id = new ImU8String(3, 2);
|
|
id.AppendFormatted(questInfo.Name);
|
|
id.AppendLiteral(" (");
|
|
id.AppendFormatted(questInfo.QuestId);
|
|
id.AppendLiteral(")");
|
|
ImGui.TreeNodeEx(id, ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.SpanFullWidth);
|
|
}
|
|
|
|
private void DrawQuestTooltipAndContextMenu(IQuestInfo questInfo, Quest? quest, bool expansionHovered)
|
|
{
|
|
if ((ImGui.IsItemHovered() || expansionHovered) && questInfo.Expansion != (EExpansionVersion)255)
|
|
{
|
|
ImGui.BeginTooltip();
|
|
ImGui.PushStyleColor(ImGuiCol.Text, questInfo.Expansion.GetExpansionColor());
|
|
ImGui.TextUnformatted(questInfo.Expansion.ToFriendlyString());
|
|
ImGui.PopStyleColor();
|
|
ImGui.Separator();
|
|
_questTooltipComponent.Draw(questInfo);
|
|
ImGui.EndTooltip();
|
|
}
|
|
else if (ImGui.IsItemHovered() && questInfo.Expansion == (EExpansionVersion)255)
|
|
{
|
|
_questTooltipComponent.Draw(questInfo);
|
|
}
|
|
_questJournalUtils.ShowContextMenu(questInfo, quest, "QuestJournalComponent");
|
|
}
|
|
|
|
private void DrawQuestSupportStatus(IQuestInfo questInfo, float supportIconSpacing)
|
|
{
|
|
float num = ImGui.GetColumnWidth() / 2f - supportIconSpacing;
|
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
|
|
if (_displayCache.TryGetValue(questInfo.QuestId, out var value))
|
|
{
|
|
_uiUtils.ChecklistItem(string.Empty, value.SupportColor, value.SupportIcon);
|
|
}
|
|
else
|
|
{
|
|
_uiUtils.ChecklistItem(string.Empty, complete: false);
|
|
}
|
|
}
|
|
|
|
private void DrawQuestCompletionStatus(IQuestInfo questInfo)
|
|
{
|
|
if (_displayCache.TryGetValue(questInfo.QuestId, out var value))
|
|
{
|
|
_uiUtils.ChecklistItem(value.CompletionText, value.CompletionColor, value.CompletionIcon);
|
|
}
|
|
else
|
|
{
|
|
_uiUtils.ChecklistItem("Unknown", UiThemeUtils.StatusUnobtainable, FontAwesomeIcon.Question);
|
|
}
|
|
}
|
|
|
|
private static bool IsQuestExpired(IQuestInfo questInfo)
|
|
{
|
|
DateTime? seasonalQuestExpiry = questInfo.SeasonalQuestExpiry;
|
|
if (seasonalQuestExpiry.HasValue)
|
|
{
|
|
DateTime valueOrDefault = seasonalQuestExpiry.GetValueOrDefault();
|
|
return DateTime.UtcNow > ExpiryUtils.NormalizeExpiry(valueOrDefault);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void RebuildFlatRows()
|
|
{
|
|
List<FlatRow> list = new List<FlatRow>();
|
|
foreach (FilteredSection filteredSection in _filteredSections)
|
|
{
|
|
JournalCounts valueOrDefault = _sectionCounts.GetValueOrDefault(filteredSection.Section, new JournalCounts());
|
|
if (valueOrDefault.Total == 0)
|
|
{
|
|
continue;
|
|
}
|
|
int id = (int)filteredSection.Section.Id;
|
|
list.Add(new FlatRow(RowType.Section, 0, id, null, filteredSection.Section.Name, valueOrDefault));
|
|
if (!_expandedGroups.Contains((RowType.Section, id)))
|
|
{
|
|
continue;
|
|
}
|
|
foreach (FilteredCategory category in filteredSection.Categories)
|
|
{
|
|
JournalCounts valueOrDefault2 = _categoryCounts.GetValueOrDefault(category.Category, new JournalCounts());
|
|
if (valueOrDefault2.Total == 0)
|
|
{
|
|
continue;
|
|
}
|
|
int id2 = (int)category.Category.Id;
|
|
list.Add(new FlatRow(RowType.Category, 1, id2, null, category.Category.Name, valueOrDefault2));
|
|
if (!_expandedGroups.Contains((RowType.Category, id2)))
|
|
{
|
|
continue;
|
|
}
|
|
foreach (FilteredGenre genre in category.Genres)
|
|
{
|
|
JournalCounts valueOrDefault3 = _genreCounts.GetValueOrDefault(genre.Genre, new JournalCounts());
|
|
if (valueOrDefault3.Total == 0)
|
|
{
|
|
continue;
|
|
}
|
|
int id3 = (int)genre.Genre.Id;
|
|
list.Add(new FlatRow(RowType.Genre, 2, id3, null, genre.Genre.Name, valueOrDefault3));
|
|
if (!_expandedGroups.Contains((RowType.Genre, id3)))
|
|
{
|
|
continue;
|
|
}
|
|
foreach (IQuestInfo quest in genre.Quests)
|
|
{
|
|
list.Add(new FlatRow(RowType.Quest, 3, 0, quest, string.Empty, new JournalCounts()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_flatRows = list;
|
|
}
|
|
|
|
public void UpdateFilter()
|
|
{
|
|
_filteredSections = (from x in _journalData.Sections
|
|
select FilterSection(x, Filter) into x
|
|
where x.Categories.Count > 0
|
|
select x).ToList();
|
|
RefreshCounts();
|
|
RebuildFlatRows();
|
|
}
|
|
|
|
private FilteredSection FilterSection(JournalData.Section section, FilterConfiguration filter)
|
|
{
|
|
IEnumerable<JournalData.Category> enumerable;
|
|
if (!_configuration.General.HideSeasonalEventsFromJournalProgress || !_journalData.SeasonalEventsCategoryId.HasValue)
|
|
{
|
|
IEnumerable<JournalData.Category> categories = section.Categories;
|
|
enumerable = categories;
|
|
}
|
|
else
|
|
{
|
|
enumerable = section.Categories.Where((JournalData.Category c) => c.Id != _journalData.SeasonalEventsCategoryId.Value);
|
|
}
|
|
IEnumerable<JournalData.Category> source = enumerable;
|
|
return new FilteredSection(Categories: ((!IsCategorySectionGenreMatch(filter, section.Name)) ? source.Select((JournalData.Category category) => FilterCategory(category, filter, section)) : source.Select((JournalData.Category x) => FilterCategory(x, filter.WithoutName(), section))).Where((FilteredCategory x) => x.Genres.Count > 0).ToList(), Section: section);
|
|
}
|
|
|
|
private FilteredCategory FilterCategory(JournalData.Category category, FilterConfiguration filter, JournalData.Section? parentSection = null)
|
|
{
|
|
IEnumerable<FilteredGenre> source = ((!IsCategorySectionGenreMatch(filter, category.Name)) ? category.Genres.Select((JournalData.Genre genre) => FilterGenre(genre, filter, parentSection)) : category.Genres.Select((JournalData.Genre x) => FilterGenre(x, filter.WithoutName(), parentSection)));
|
|
return new FilteredCategory(category, source.Where((FilteredGenre x) => x.Quests.Count > 0).ToList());
|
|
}
|
|
|
|
private FilteredGenre FilterGenre(JournalData.Genre genre, FilterConfiguration filter, JournalData.Section? parentSection = null)
|
|
{
|
|
bool hideSeasonalEventsFromJournalProgress = _configuration.General.HideSeasonalEventsFromJournalProgress;
|
|
IEnumerable<IQuestInfo> source = ((!IsCategorySectionGenreMatch(filter, genre.Name)) ? genre.Quests.Where((IQuestInfo x) => IsQuestMatch(filter, x)) : genre.Quests.Where((IQuestInfo x) => IsQuestMatch(filter.WithoutName(), x)));
|
|
if (hideSeasonalEventsFromJournalProgress && _journalData.SeasonalEventsCategoryId.HasValue && genre.CategoryId == _journalData.SeasonalEventsCategoryId.Value)
|
|
{
|
|
source = source.Where((IQuestInfo q) => !IsSeasonal(q));
|
|
}
|
|
return new FilteredGenre(genre, source.ToList());
|
|
}
|
|
|
|
internal void RefreshCounts()
|
|
{
|
|
StartRefresh();
|
|
}
|
|
|
|
private void StartRefresh()
|
|
{
|
|
_refreshHideSeasonal = _configuration.General.HideSeasonalEventsFromJournalProgress;
|
|
_refreshGenreIndex = 0;
|
|
_refreshDisplayIndex = -1;
|
|
_logger.LogDebug("Starting incremental journal refresh. HideSeasonalEventsFromJournalProgress={Hide}", _refreshHideSeasonal);
|
|
}
|
|
|
|
private void TickRefresh()
|
|
{
|
|
if (_refreshGenreIndex >= 0)
|
|
{
|
|
List<JournalData.Genre> genres = _journalData.Genres;
|
|
int num = Math.Min(_refreshGenreIndex + 20, genres.Count);
|
|
for (int i = _refreshGenreIndex; i < num; i++)
|
|
{
|
|
JournalData.Genre genre = genres[i];
|
|
List<IQuestInfo> obj = ((_refreshHideSeasonal && _journalData.SeasonalEventsCategoryId.HasValue && genre.CategoryId == _journalData.SeasonalEventsCategoryId.Value) ? genre.Quests.Where((IQuestInfo q) => !IsSeasonal(q)).ToList() : genre.Quests.ToList());
|
|
int num2 = 0;
|
|
int num3 = 0;
|
|
int num4 = 0;
|
|
int num5 = 0;
|
|
foreach (IQuestInfo item in obj)
|
|
{
|
|
if (!_questFunctions.IsQuestRemoved(item.QuestId))
|
|
{
|
|
num3++;
|
|
if (_questRegistry.TryGetQuest(item.QuestId, out Quest quest) && !quest.Root.Disabled)
|
|
{
|
|
num2++;
|
|
}
|
|
}
|
|
if (!_questFunctions.IsQuestUnobtainable(item.QuestId))
|
|
{
|
|
num4++;
|
|
}
|
|
if (_questFunctions.IsQuestComplete(item.QuestId))
|
|
{
|
|
num5++;
|
|
}
|
|
}
|
|
_genreCounts[genre] = new JournalCounts(num2, num3, num4, num5);
|
|
}
|
|
_refreshGenreIndex = num;
|
|
if (_refreshGenreIndex >= genres.Count)
|
|
{
|
|
_refreshGenreIndex = -1;
|
|
AggregateCategoriesAndSections();
|
|
RebuildFlatRows();
|
|
_refreshDisplayIndex = 0;
|
|
}
|
|
}
|
|
else if (_refreshDisplayIndex >= 0)
|
|
{
|
|
TickDisplayCache();
|
|
}
|
|
}
|
|
|
|
private void AggregateCategoriesAndSections()
|
|
{
|
|
_categoryCounts.Clear();
|
|
_sectionCounts.Clear();
|
|
foreach (JournalData.Category category in _journalData.Categories)
|
|
{
|
|
if (!_refreshHideSeasonal || !_journalData.SeasonalEventsCategoryId.HasValue || category.Id != _journalData.SeasonalEventsCategoryId.Value)
|
|
{
|
|
List<JournalCounts> genreCountsForCategory = GetGenreCountsForCategory(category);
|
|
int available = genreCountsForCategory.Sum((JournalCounts x) => x.Available);
|
|
int total = genreCountsForCategory.Sum((JournalCounts x) => x.Total);
|
|
int obtainable = genreCountsForCategory.Sum((JournalCounts x) => x.Obtainable);
|
|
int completed = genreCountsForCategory.Sum((JournalCounts x) => x.Completed);
|
|
_categoryCounts[category] = new JournalCounts(available, total, obtainable, completed);
|
|
}
|
|
}
|
|
foreach (JournalData.Section section in _journalData.Sections)
|
|
{
|
|
List<JournalCounts> source = (from x in _categoryCounts
|
|
where section.Categories.Contains(x.Key)
|
|
select x.Value).ToList();
|
|
int available2 = source.Sum((JournalCounts x) => x.Available);
|
|
int total2 = source.Sum((JournalCounts x) => x.Total);
|
|
int obtainable2 = source.Sum((JournalCounts x) => x.Obtainable);
|
|
int completed2 = source.Sum((JournalCounts x) => x.Completed);
|
|
_sectionCounts[section] = new JournalCounts(available2, total2, obtainable2, completed2);
|
|
}
|
|
int num = _sectionCounts.Values.Sum((JournalCounts x) => x.Total);
|
|
_logger.LogDebug("RefreshCounts complete. Sections={Sections}, Categories={Categories}, Genres={Genres}, TotalQuests={Total}", _sectionCounts.Count, _categoryCounts.Count, _genreCounts.Count, num);
|
|
}
|
|
|
|
private void TickDisplayCache()
|
|
{
|
|
List<JournalData.Genre> genres = _journalData.Genres;
|
|
int num = 0;
|
|
while (_refreshDisplayIndex < genres.Count && num < 20)
|
|
{
|
|
foreach (IQuestInfo quest2 in genres[_refreshDisplayIndex].Quests)
|
|
{
|
|
_questRegistry.TryGetQuest(quest2.QuestId, out Quest quest);
|
|
Vector4 supportColor;
|
|
FontAwesomeIcon supportIcon;
|
|
if (_questFunctions.IsQuestRemoved(quest2.QuestId))
|
|
{
|
|
supportColor = UiThemeUtils.StatusUnobtainable;
|
|
supportIcon = FontAwesomeIcon.Minus;
|
|
}
|
|
else
|
|
{
|
|
if (quest != null)
|
|
{
|
|
QuestRoot root = quest.Root;
|
|
if (root != null && !root.Disabled)
|
|
{
|
|
List<ValidationIssue> issues = _questValidator.GetIssues(EPathType.Quest, quest.Id.ToString());
|
|
if (issues.Any((ValidationIssue x) => x.Severity == EIssueSeverity.Error))
|
|
{
|
|
supportColor = UiThemeUtils.StatusLocked;
|
|
supportIcon = FontAwesomeIcon.ExclamationTriangle;
|
|
}
|
|
else if (issues.Count > 0)
|
|
{
|
|
supportColor = UiThemeUtils.StatusAvailableComplete;
|
|
supportIcon = FontAwesomeIcon.InfoCircle;
|
|
}
|
|
else
|
|
{
|
|
supportColor = UiThemeUtils.StatusComplete;
|
|
supportIcon = FontAwesomeIcon.Check;
|
|
}
|
|
goto IL_011b;
|
|
}
|
|
}
|
|
supportColor = UiThemeUtils.StatusLocked;
|
|
supportIcon = FontAwesomeIcon.Times;
|
|
}
|
|
goto IL_011b;
|
|
IL_011b:
|
|
Vector4 completionColor;
|
|
FontAwesomeIcon completionIcon;
|
|
string completionText;
|
|
if (_questFunctions.IsQuestAccepted(quest2.QuestId))
|
|
{
|
|
completionColor = UiThemeUtils.StatusActive;
|
|
completionIcon = FontAwesomeIcon.PersonWalkingArrowRight;
|
|
completionText = "Active";
|
|
}
|
|
else if (_questFunctions.IsQuestComplete(quest2.QuestId))
|
|
{
|
|
if (!quest2.IsRepeatable)
|
|
{
|
|
completionColor = UiThemeUtils.StatusComplete;
|
|
completionIcon = FontAwesomeIcon.Check;
|
|
completionText = "Complete";
|
|
}
|
|
else
|
|
{
|
|
bool num2 = _questFunctions.IsQuestLocked(quest2.QuestId);
|
|
bool flag = _questFunctions.IsReadyToAcceptQuest(quest2.QuestId);
|
|
if (!num2 && flag)
|
|
{
|
|
completionColor = UiThemeUtils.StatusAvailableComplete;
|
|
completionIcon = FontAwesomeIcon.Running;
|
|
completionText = "Available";
|
|
}
|
|
else
|
|
{
|
|
completionColor = UiThemeUtils.StatusComplete;
|
|
completionIcon = FontAwesomeIcon.Check;
|
|
completionText = "Complete";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool num3 = IsQuestExpired(quest2);
|
|
bool flag2 = _questFunctions.IsQuestUnobtainable(quest2.QuestId);
|
|
bool flag3 = _questFunctions.IsQuestBlacklisted(quest2.QuestId);
|
|
bool flag4 = _questFunctions.IsQuestLocked(quest2.QuestId);
|
|
bool flag5 = _questFunctions.IsReadyToAcceptQuest(quest2.QuestId);
|
|
if (num3 || flag2)
|
|
{
|
|
completionColor = UiThemeUtils.StatusUnobtainable;
|
|
completionIcon = FontAwesomeIcon.Minus;
|
|
completionText = "Unobtainable";
|
|
}
|
|
else if (flag3)
|
|
{
|
|
completionColor = UiThemeUtils.StatusBlacklisted;
|
|
completionIcon = FontAwesomeIcon.Ban;
|
|
completionText = "Blacklisted";
|
|
}
|
|
else if (flag4 || !flag5 || !_questRegistry.IsKnownQuest(quest2.QuestId))
|
|
{
|
|
completionColor = UiThemeUtils.StatusLocked;
|
|
completionIcon = FontAwesomeIcon.Times;
|
|
completionText = "Locked";
|
|
}
|
|
else
|
|
{
|
|
completionColor = UiThemeUtils.StatusAvailable;
|
|
completionIcon = FontAwesomeIcon.Running;
|
|
completionText = "Available";
|
|
}
|
|
}
|
|
_displayCache[quest2.QuestId] = new CachedQuestDisplay(supportColor, supportIcon, completionColor, completionIcon, completionText);
|
|
}
|
|
_refreshDisplayIndex++;
|
|
num++;
|
|
}
|
|
if (_refreshDisplayIndex >= genres.Count)
|
|
{
|
|
_refreshDisplayIndex = -1;
|
|
_logger.LogDebug("Display cache refresh complete. Cached={Count}", _displayCache.Count);
|
|
}
|
|
}
|
|
|
|
private List<JournalCounts> GetGenreCountsForCategory(JournalData.Category category)
|
|
{
|
|
List<JournalCounts> list = new List<JournalCounts>(category.Genres.Count);
|
|
foreach (JournalData.Genre genre in category.Genres)
|
|
{
|
|
if (_genreCounts.TryGetValue(genre, out JournalCounts value))
|
|
{
|
|
list.Add(value);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
internal void ClearCounts(int type, int code)
|
|
{
|
|
_refreshGenreIndex = -1;
|
|
_refreshDisplayIndex = -1;
|
|
foreach (KeyValuePair<JournalData.Genre, JournalCounts> item in _genreCounts.ToList())
|
|
{
|
|
_genreCounts[item.Key] = new JournalCounts(item.Value.Available, item.Value.Total, item.Value.Obtainable, 0);
|
|
}
|
|
foreach (KeyValuePair<JournalData.Category, JournalCounts> item2 in _categoryCounts.ToList())
|
|
{
|
|
_categoryCounts[item2.Key] = new JournalCounts(item2.Value.Available, item2.Value.Total, item2.Value.Obtainable, 0);
|
|
}
|
|
foreach (KeyValuePair<JournalData.Section, JournalCounts> item3 in _sectionCounts.ToList())
|
|
{
|
|
_sectionCounts[item3.Key] = new JournalCounts(item3.Value.Available, item3.Value.Total, item3.Value.Obtainable, 0);
|
|
}
|
|
_displayCache.Clear();
|
|
RebuildFlatRows();
|
|
}
|
|
|
|
private static bool IsCategorySectionGenreMatch(FilterConfiguration filter, string name)
|
|
{
|
|
if (!string.IsNullOrEmpty(filter.SearchText))
|
|
{
|
|
return name.Contains(filter.SearchText, StringComparison.CurrentCultureIgnoreCase);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool IsQuestMatch(FilterConfiguration filter, IQuestInfo questInfo)
|
|
{
|
|
if (!string.IsNullOrEmpty(filter.SearchText) && !questInfo.Name.Contains(filter.SearchText, StringComparison.CurrentCultureIgnoreCase) && !(questInfo.QuestId.ToString() == filter.SearchText))
|
|
{
|
|
return false;
|
|
}
|
|
if (filter.AvailableOnly && !_questFunctions.IsReadyToAcceptQuest(questInfo.QuestId))
|
|
{
|
|
return false;
|
|
}
|
|
if (filter.HideNoPaths && (!_questRegistry.TryGetQuest(questInfo.QuestId, out Quest quest) || quest.Root.Disabled))
|
|
{
|
|
return false;
|
|
}
|
|
if (filter.HideUnobtainable && _questFunctions.IsQuestUnobtainable(questInfo.QuestId))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool IsSeasonal(IQuestInfo q)
|
|
{
|
|
if (q.IsSeasonalQuest)
|
|
{
|
|
return true;
|
|
}
|
|
if (q.SeasonalQuestExpiry.HasValue)
|
|
{
|
|
return true;
|
|
}
|
|
if (q is UnlockLinkQuestInfo { QuestExpiry: not null })
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|