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 Categories); private sealed record FilteredCategory(JournalData.Category Category, List Genres); private sealed record FilteredGenre(JournalData.Genre Genre, List 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 _genreCounts = new Dictionary(); private readonly Dictionary _categoryCounts = new Dictionary(); private readonly Dictionary _sectionCounts = new Dictionary(); 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 _logger; private List _filteredSections = new List(); private readonly Dictionary _displayCache = new Dictionary(); private List _flatRows = new List(); 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 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 list = new List(); 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 enumerable; if (!_configuration.General.HideSeasonalEventsFromJournalProgress || !_journalData.SeasonalEventsCategoryId.HasValue) { IEnumerable categories = section.Categories; enumerable = categories; } else { enumerable = section.Categories.Where((JournalData.Category c) => c.Id != _journalData.SeasonalEventsCategoryId.Value); } IEnumerable 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 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 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 genres = _journalData.Genres; int num = Math.Min(_refreshGenreIndex + 20, genres.Count); for (int i = _refreshGenreIndex; i < num; i++) { JournalData.Genre genre = genres[i]; List 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 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 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 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 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 GetGenreCountsForCategory(JournalData.Category category) { List list = new List(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 item in _genreCounts.ToList()) { _genreCounts[item.Key] = new JournalCounts(item.Value.Available, item.Value.Total, item.Value.Obtainable, 0); } foreach (KeyValuePair item2 in _categoryCounts.ToList()) { _categoryCounts[item2.Key] = new JournalCounts(item2.Value.Available, item2.Value.Total, item2.Value.Obtainable, 0); } foreach (KeyValuePair 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; } }