Include gathering points in statistics window
This commit is contained in:
parent
bcab19b809
commit
17cedb6f87
@ -24,4 +24,13 @@ public static class ExpansionData
|
||||
{ EExpansionVersion.Endwalker, "6.x - Endwalker" },
|
||||
{ EExpansionVersion.Dawntrail, "7.x - Dawntrail" }
|
||||
};
|
||||
|
||||
public static string ToFriendlyString(this EExpansionVersion expansionVersion)
|
||||
{
|
||||
return expansionVersion switch
|
||||
{
|
||||
EExpansionVersion.ARealmReborn => "A Realm Reborn",
|
||||
_ => expansionVersion.ToString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ using Questionable.Functions;
|
||||
using Questionable.Validation;
|
||||
using Questionable.Validation.Validators;
|
||||
using Questionable.Windows;
|
||||
using Questionable.Windows.JournalComponents;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
using Action = Questionable.Controller.Steps.Interactions.Action;
|
||||
|
||||
@ -211,6 +212,9 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<QuickAccessButtonsComponent>();
|
||||
serviceCollection.AddSingleton<RemainingTasksComponent>();
|
||||
|
||||
serviceCollection.AddSingleton<QuestJournalComponent>();
|
||||
serviceCollection.AddSingleton<GatheringJournalComponent>();
|
||||
|
||||
serviceCollection.AddSingleton<QuestWindow>();
|
||||
serviceCollection.AddSingleton<ConfigWindow>();
|
||||
serviceCollection.AddSingleton<DebugOverlay>();
|
||||
|
@ -0,0 +1,380 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using ImGuiNET;
|
||||
using LLib.GameData;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Gathering;
|
||||
|
||||
namespace Questionable.Windows.JournalComponents;
|
||||
|
||||
internal sealed class GatheringJournalComponent
|
||||
{
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly UiUtils _uiUtils;
|
||||
private readonly GatheringPointRegistry _gatheringPointRegistry;
|
||||
private readonly Dictionary<int, string> _gatheringItems;
|
||||
private readonly List<ExpansionPoints> _gatheringPoints;
|
||||
private readonly List<ushort> _gatheredItems = [];
|
||||
|
||||
private delegate byte GetIsGatheringItemGatheredDelegate(ushort item);
|
||||
|
||||
[Signature("48 89 5C 24 ?? 57 48 83 EC 20 8B D9 8B F9")]
|
||||
private GetIsGatheringItemGatheredDelegate _getIsGatheringItemGathered = null!;
|
||||
|
||||
internal bool IsGatheringItemGathered(uint item) => _getIsGatheringItemGathered((ushort)item) != 0;
|
||||
|
||||
public GatheringJournalComponent(IDataManager dataManager, IDalamudPluginInterface pluginInterface, UiUtils uiUtils,
|
||||
IGameInteropProvider gameInteropProvider, GatheringPointRegistry gatheringPointRegistry)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
_uiUtils = uiUtils;
|
||||
_gatheringPointRegistry = gatheringPointRegistry;
|
||||
var routeToGatheringPoint = dataManager.GetExcelSheet<GatheringLeveRoute>()!
|
||||
.Where(x => x.UnkData0[0].GatheringPoint != 0)
|
||||
.SelectMany(x => x.UnkData0
|
||||
.Where(y => y.GatheringPoint != 0)
|
||||
.Select(y => new
|
||||
{
|
||||
RouteId = x.RowId,
|
||||
GatheringPointId = y.GatheringPoint
|
||||
}))
|
||||
.GroupBy(x => x.RouteId)
|
||||
.ToDictionary(x => x.Key, x => x.Select(y => y.GatheringPointId).ToList());
|
||||
var gatheringLeveSheet = dataManager.GetExcelSheet<GatheringLeve>()!;
|
||||
var territoryTypeSheet = dataManager.GetExcelSheet<TerritoryType>()!;
|
||||
var gatheringPointToLeve = dataManager.GetExcelSheet<Leve>()!
|
||||
.Where(x => x.RowId > 0)
|
||||
.Select(x =>
|
||||
{
|
||||
uint startZonePlaceName = x.PlaceNameStartZone.Row;
|
||||
startZonePlaceName = startZonePlaceName switch
|
||||
{
|
||||
27 => 28, // limsa
|
||||
39 => 52, // gridania
|
||||
51 => 40, // uldah
|
||||
62 => 2300, // ishgard
|
||||
_ => startZonePlaceName
|
||||
};
|
||||
|
||||
var territoryType = territoryTypeSheet.FirstOrDefault(y => startZonePlaceName == y.PlaceName.Row)
|
||||
?? throw new InvalidOperationException($"Unable to use {startZonePlaceName}");
|
||||
return new
|
||||
{
|
||||
LeveId = x.RowId,
|
||||
LeveName = x.Name.ToString(),
|
||||
TerritoryType = (ushort)territoryType.RowId,
|
||||
TerritoryName = territoryType.Name.ToString(),
|
||||
GatheringLeve = gatheringLeveSheet.GetRow((uint)x.DataId),
|
||||
};
|
||||
})
|
||||
.Where(x => x.GatheringLeve != null)
|
||||
.Select(x => new
|
||||
{
|
||||
x.LeveId,
|
||||
x.LeveName,
|
||||
x.TerritoryType,
|
||||
x.TerritoryName,
|
||||
GatheringPoints = x.GatheringLeve!.Route
|
||||
.Where(y => y.Row != 0)
|
||||
.SelectMany(y => routeToGatheringPoint[y.Row]),
|
||||
})
|
||||
.SelectMany(x => x.GatheringPoints.Select(y => new
|
||||
{
|
||||
x.LeveId,
|
||||
x.LeveName,
|
||||
x.TerritoryType,
|
||||
x.TerritoryName,
|
||||
GatheringPointId = y
|
||||
}))
|
||||
.GroupBy(x => x.GatheringPointId)
|
||||
.ToDictionary(x => x.Key, x => x.First());
|
||||
|
||||
var itemSheet = dataManager.GetExcelSheet<Item>()!;
|
||||
|
||||
_gatheringItems = dataManager.GetExcelSheet<GatheringItem>()!
|
||||
.Where(x => x.RowId != 0 && x.GatheringItemLevel.Row != 0)
|
||||
.Select(x => new
|
||||
{
|
||||
GatheringItemId = (int)x.RowId,
|
||||
Name = itemSheet.GetRow((uint)x.Item)?.Name.ToString()
|
||||
})
|
||||
.Where(x => !string.IsNullOrEmpty(x.Name))
|
||||
.ToDictionary(x => x.GatheringItemId, x => x.Name!);
|
||||
|
||||
_gatheringPoints = dataManager.GetExcelSheet<GatheringPoint>()!
|
||||
.Where(x => x.GatheringPointBase.Row != 0)
|
||||
.DistinctBy(x => x.GatheringPointBase.Row)
|
||||
.Select(x => new
|
||||
{
|
||||
GatheringPointId = x.RowId,
|
||||
Point = new DefaultGatheringPoint(new GatheringPointId((ushort)x.GatheringPointBase.Row),
|
||||
x.GatheringPointBase.Value!.GatheringType.Row switch
|
||||
{
|
||||
0 or 1 => EClassJob.Miner,
|
||||
2 or 3 => EClassJob.Botanist,
|
||||
_ => EClassJob.Fisher
|
||||
},
|
||||
x.GatheringPointBase.Value.GatheringLevel,
|
||||
x.GatheringPointBase.Value.Item.Where(y => y != 0).Select(y => (ushort)y).ToList(),
|
||||
(EExpansionVersion?)x.TerritoryType.Value?.ExVersion.Row ?? (EExpansionVersion)byte.MaxValue,
|
||||
(ushort)x.TerritoryType.Row,
|
||||
x.TerritoryType.Value?.PlaceName.Value?.Name.ToString(),
|
||||
$"{x.GatheringPointBase.Row} - {x.PlaceName.Value?.Name}")
|
||||
})
|
||||
.Where(x => x.Point.ClassJob != EClassJob.Fisher)
|
||||
.Select(x =>
|
||||
{
|
||||
if (gatheringPointToLeve.TryGetValue((int)x.GatheringPointId, out var leve))
|
||||
{
|
||||
// it's a leve
|
||||
return x.Point with
|
||||
{
|
||||
Expansion = EExpansionVersion.Shadowbringers,
|
||||
TerritoryType = leve.TerritoryType,
|
||||
TerritoryName = leve.TerritoryName,
|
||||
PlaceName = leve.LeveName,
|
||||
};
|
||||
}
|
||||
else if (x.Point.TerritoryType == 1 && _gatheringPointRegistry.TryGetGatheringPoint(x.Point.Id, out GatheringRoot? gatheringRoot))
|
||||
{
|
||||
// for some reason the game doesn't know where this gathering location is
|
||||
var territoryType = territoryTypeSheet.GetRow(gatheringRoot.Steps.Last().TerritoryId)!;
|
||||
return x.Point with
|
||||
{
|
||||
Expansion = (EExpansionVersion)territoryType.ExVersion.Row,
|
||||
TerritoryType = (ushort)territoryType.RowId,
|
||||
TerritoryName = territoryType.PlaceName.Value?.Name.ToString(),
|
||||
};
|
||||
}
|
||||
else
|
||||
return x.Point;
|
||||
})
|
||||
.Where(x => x.Expansion != (EExpansionVersion)byte.MaxValue)
|
||||
.Where(x => x.GatheringItemIds.Count > 0)
|
||||
.GroupBy(x => x.Expansion)
|
||||
.Select(x => new ExpansionPoints(x.Key, x
|
||||
.GroupBy(y => new
|
||||
{
|
||||
y.TerritoryType,
|
||||
TerritoryName = $"{(!string.IsNullOrEmpty(y.TerritoryName) ? y.TerritoryName : "???")} ({y.TerritoryType})"
|
||||
})
|
||||
.Select(y => new TerritoryPoints(y.Key.TerritoryType, y.Key.TerritoryName, y.ToList()))
|
||||
.Where(y => y.Points.Count > 0)
|
||||
.ToList()))
|
||||
.OrderBy(x => x.Expansion)
|
||||
.ToList();
|
||||
|
||||
gameInteropProvider.InitializeFromAttributes(this);
|
||||
}
|
||||
|
||||
public void DrawGatheringItems()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Gathering Points");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("GatheringPoints", 3, ImGuiTableFlags.NoSavedSettings);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
|
||||
ImGui.TableSetupColumn("Supported", ImGuiTableColumnFlags.WidthFixed, 100 * ImGui.GetIO().FontGlobalScale);
|
||||
ImGui.TableSetupColumn("Collected", ImGuiTableColumnFlags.WidthFixed, 100 * ImGui.GetIO().FontGlobalScale);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var expansion in _gatheringPoints)
|
||||
DrawExpansion(expansion);
|
||||
}
|
||||
|
||||
private void DrawExpansion(ExpansionPoints expansion)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(expansion.Expansion.ToFriendlyString(), ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(expansion.CompletedPoints, expansion.TotalPoints);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(expansion.CompletedItems, expansion.TotalItems);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var territory in expansion.PointsByTerritories)
|
||||
DrawTerritory(territory);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTerritory(TerritoryPoints territory)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(territory.ToFriendlyString(), ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(territory.CompletedPoints, territory.TotalPoints);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(territory.CompletedItems, territory.TotalItems);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var point in territory.Points)
|
||||
DrawPoint(point);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPoint(DefaultGatheringPoint point)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx($"{point.PlaceName} ({point.ClassJob} Lv. {point.Level})",
|
||||
ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
float spacing;
|
||||
// ReSharper disable once UnusedVariable
|
||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
spacing = ImGui.GetColumnWidth() / 2 - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
|
||||
}
|
||||
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + spacing);
|
||||
_uiUtils.ChecklistItem(string.Empty, point.IsComplete);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(point.CompletedItems, point.TotalItems);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var item in point.GatheringItemIds)
|
||||
DrawItem(item);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawItem(ushort item)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TreeNodeEx(_gatheringItems.GetValueOrDefault(item, "???"),
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
float spacing;
|
||||
// ReSharper disable once UnusedVariable
|
||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
spacing = ImGui.GetColumnWidth() / 2 - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
|
||||
}
|
||||
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + spacing);
|
||||
if (item < 10_000)
|
||||
_uiUtils.ChecklistItem(string.Empty, _gatheredItems.Contains(item));
|
||||
else
|
||||
_uiUtils.ChecklistItem(string.Empty, ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus);
|
||||
}
|
||||
|
||||
private static void DrawCount(int count, int total)
|
||||
{
|
||||
string len = 999.ToString(CultureInfo.CurrentCulture);
|
||||
ImGui.PushFont(UiBuilder.MonoFont);
|
||||
|
||||
string text =
|
||||
$"{count.ToString(CultureInfo.CurrentCulture).PadLeft(len.Length)} / {total.ToString(CultureInfo.CurrentCulture).PadLeft(len.Length)}";
|
||||
if (count == total)
|
||||
ImGui.TextColored(ImGuiColors.ParsedGreen, text);
|
||||
else
|
||||
ImGui.TextUnformatted(text);
|
||||
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
internal void RefreshCounts()
|
||||
{
|
||||
_gatheredItems.Clear();
|
||||
foreach (ushort key in _gatheringItems.Keys)
|
||||
{
|
||||
if (IsGatheringItemGathered(key))
|
||||
_gatheredItems.Add(key);
|
||||
}
|
||||
|
||||
foreach (var expansion in _gatheringPoints)
|
||||
{
|
||||
foreach (var territory in expansion.PointsByTerritories)
|
||||
{
|
||||
foreach (var point in territory.Points)
|
||||
{
|
||||
point.TotalItems = point.GatheringItemIds.Count(x => x < 10_000);
|
||||
point.CompletedItems = point.GatheringItemIds.Count(_gatheredItems.Contains);
|
||||
point.IsComplete = _gatheringPointRegistry.TryGetGatheringPoint(point.Id, out _);
|
||||
}
|
||||
|
||||
territory.TotalItems = territory.Points.Sum(x => x.TotalItems);
|
||||
territory.CompletedItems = territory.Points.Sum(x => x.CompletedItems);
|
||||
territory.CompletedPoints = territory.Points.Count(x => x.IsComplete);
|
||||
}
|
||||
|
||||
expansion.TotalItems = expansion.PointsByTerritories.Sum(x => x.TotalItems);
|
||||
expansion.CompletedItems = expansion.PointsByTerritories.Sum(x => x.CompletedItems);
|
||||
expansion.TotalPoints = expansion.PointsByTerritories.Sum(x => x.TotalPoints);
|
||||
expansion.CompletedPoints = expansion.PointsByTerritories.Sum(x => x.CompletedPoints);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record ExpansionPoints(EExpansionVersion Expansion, List<TerritoryPoints> PointsByTerritories)
|
||||
{
|
||||
public int TotalItems { get; set; }
|
||||
public int TotalPoints { get; set; }
|
||||
public int CompletedItems { get; set; }
|
||||
public int CompletedPoints { get; set; }
|
||||
}
|
||||
|
||||
private sealed record TerritoryPoints(
|
||||
ushort TerritoryType,
|
||||
string TerritoryName,
|
||||
List<DefaultGatheringPoint> Points)
|
||||
{
|
||||
public int TotalItems { get; set; }
|
||||
public int TotalPoints => Points.Count;
|
||||
public int CompletedItems { get; set; }
|
||||
public int CompletedPoints { get; set; }
|
||||
public string ToFriendlyString() =>
|
||||
!string.IsNullOrEmpty(TerritoryName) ? TerritoryName : $"??? ({TerritoryType})";
|
||||
}
|
||||
|
||||
private sealed record DefaultGatheringPoint(
|
||||
GatheringPointId Id,
|
||||
EClassJob ClassJob,
|
||||
byte Level,
|
||||
List<ushort> GatheringItemIds,
|
||||
EExpansionVersion Expansion,
|
||||
ushort TerritoryType,
|
||||
string? TerritoryName,
|
||||
string? PlaceName)
|
||||
{
|
||||
public int TotalItems { get; set; }
|
||||
public int CompletedItems { get; set; }
|
||||
public bool IsComplete { get; set; }
|
||||
}
|
||||
}
|
351
Questionable/Windows/JournalComponents/QuestJournalComponent.cs
Normal file
351
Questionable/Windows/JournalComponents/QuestJournalComponent.cs
Normal file
@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiNET;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
|
||||
namespace Questionable.Windows.JournalComponents;
|
||||
|
||||
internal sealed class QuestJournalComponent
|
||||
{
|
||||
private readonly Dictionary<JournalData.Genre, (int Available, int Completed)> _genreCounts = new();
|
||||
private readonly Dictionary<JournalData.Category, (int Available, int Completed)> _categoryCounts = new();
|
||||
private readonly Dictionary<JournalData.Section, (int Available, int Completed)> _sectionCounts = new();
|
||||
|
||||
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 ICommandManager _commandManager;
|
||||
|
||||
private List<FilteredSection> _filteredSections = [];
|
||||
private string _searchText = string.Empty;
|
||||
|
||||
public QuestJournalComponent(JournalData journalData, QuestRegistry questRegistry, QuestFunctions questFunctions,
|
||||
UiUtils uiUtils, QuestTooltipComponent questTooltipComponent, IDalamudPluginInterface pluginInterface,
|
||||
ICommandManager commandManager)
|
||||
{
|
||||
_journalData = journalData;
|
||||
_questRegistry = questRegistry;
|
||||
_questFunctions = questFunctions;
|
||||
_uiUtils = uiUtils;
|
||||
_questTooltipComponent = questTooltipComponent;
|
||||
_pluginInterface = pluginInterface;
|
||||
_commandManager = commandManager;
|
||||
}
|
||||
|
||||
public void DrawQuests()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Quests");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
if (ImGui.CollapsingHeader("Explanation", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
ImGui.Text("The list below contains all quests that appear in your journal.");
|
||||
ImGui.BulletText("'Supported' lists quests that Questionable can do for you");
|
||||
ImGui.BulletText("'Completed' lists quests your current character has completed.");
|
||||
ImGui.BulletText(
|
||||
"Not all quests can be completed even if they're listed as available, e.g. starting city quest chains.");
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputTextWithHint(string.Empty, "Search quests and categories", ref _searchText, 256))
|
||||
UpdateFilter();
|
||||
|
||||
if (_filteredSections.Count > 0)
|
||||
{
|
||||
using var table = ImRaii.Table("Quests", 3, ImGuiTableFlags.NoSavedSettings);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
|
||||
ImGui.TableSetupColumn("Supported", ImGuiTableColumnFlags.WidthFixed, 120 * ImGui.GetIO().FontGlobalScale);
|
||||
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 120 * ImGui.GetIO().FontGlobalScale);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var section in _filteredSections)
|
||||
DrawSection(section);
|
||||
}
|
||||
else
|
||||
ImGui.Text("No quest or category matches your search text.");
|
||||
}
|
||||
|
||||
private void DrawSection(FilteredSection filter)
|
||||
{
|
||||
if (filter.Section.QuestCount == 0)
|
||||
return;
|
||||
|
||||
(int supported, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(filter.Section.Name, ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(supported, filter.Section.QuestCount);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(completed, filter.Section.QuestCount);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var category in filter.Categories)
|
||||
DrawCategory(category);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCategory(FilteredCategory filter)
|
||||
{
|
||||
if (filter.Category.QuestCount == 0)
|
||||
return;
|
||||
|
||||
(int supported, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(filter.Category.Name, ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(supported, filter.Category.QuestCount);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(completed, filter.Category.QuestCount);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var genre in filter.Genres)
|
||||
DrawGenre(genre);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGenre(FilteredGenre filter)
|
||||
{
|
||||
if (filter.Genre.QuestCount == 0)
|
||||
return;
|
||||
|
||||
(int supported, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(filter.Genre.Name, ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(supported, filter.Genre.QuestCount);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(completed, filter.Genre.QuestCount);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var quest in filter.Quests)
|
||||
DrawQuest(quest);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawQuest(IQuestInfo questInfo)
|
||||
{
|
||||
_questRegistry.TryGetQuest(questInfo.QuestId, out var quest);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TreeNodeEx(questInfo.Name,
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
|
||||
if (questInfo is QuestInfo && ImGui.IsItemClicked() &&
|
||||
_commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
|
||||
{
|
||||
_commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty, commandInfo);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
_questTooltipComponent.Draw(questInfo);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
float spacing;
|
||||
// ReSharper disable once UnusedVariable
|
||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
spacing = ImGui.GetColumnWidth() / 2 - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
|
||||
}
|
||||
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + spacing);
|
||||
_uiUtils.ChecklistItem(string.Empty, quest is { Root.Disabled: false });
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var (color, icon, text) = _uiUtils.GetQuestStyle(questInfo.QuestId);
|
||||
_uiUtils.ChecklistItem(text, color, icon);
|
||||
}
|
||||
|
||||
private static void DrawCount(int count, int total)
|
||||
{
|
||||
string len = 9999.ToString(CultureInfo.CurrentCulture);
|
||||
ImGui.PushFont(UiBuilder.MonoFont);
|
||||
|
||||
string text =
|
||||
$"{count.ToString(CultureInfo.CurrentCulture).PadLeft(len.Length)} / {total.ToString(CultureInfo.CurrentCulture).PadLeft(len.Length)}";
|
||||
if (count == total)
|
||||
ImGui.TextColored(ImGuiColors.ParsedGreen, text);
|
||||
else
|
||||
ImGui.TextUnformatted(text);
|
||||
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
public void UpdateFilter()
|
||||
{
|
||||
Predicate<string> match;
|
||||
if (string.IsNullOrWhiteSpace(_searchText))
|
||||
match = _ => true;
|
||||
else
|
||||
match = x => x.Contains(_searchText, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
_filteredSections = _journalData.Sections
|
||||
.Select(section => FilterSection(section, match))
|
||||
.Where(x => x != null)
|
||||
.Cast<FilteredSection>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static FilteredSection? FilterSection(JournalData.Section section, Predicate<string> match)
|
||||
{
|
||||
if (match(section.Name))
|
||||
{
|
||||
return new FilteredSection(section,
|
||||
section.Categories
|
||||
.Select(x => FilterCategory(x, _ => true))
|
||||
.Cast<FilteredCategory>()
|
||||
.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
List<FilteredCategory> filteredCategories = section.Categories
|
||||
.Select(category => FilterCategory(category, match))
|
||||
.Where(x => x != null)
|
||||
.Cast<FilteredCategory>()
|
||||
.ToList();
|
||||
if (filteredCategories.Count > 0)
|
||||
return new FilteredSection(section, filteredCategories);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FilteredCategory? FilterCategory(JournalData.Category category, Predicate<string> match)
|
||||
{
|
||||
if (match(category.Name))
|
||||
{
|
||||
return new FilteredCategory(category,
|
||||
category.Genres
|
||||
.Select(x => FilterGenre(x, _ => true))
|
||||
.Cast<FilteredGenre>()
|
||||
.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
List<FilteredGenre> filteredGenres = category.Genres
|
||||
.Select(genre => FilterGenre(genre, match))
|
||||
.Where(x => x != null)
|
||||
.Cast<FilteredGenre>()
|
||||
.ToList();
|
||||
if (filteredGenres.Count > 0)
|
||||
return new FilteredCategory(category, filteredGenres);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FilteredGenre? FilterGenre(JournalData.Genre genre, Predicate<string> match)
|
||||
{
|
||||
if (match(genre.Name))
|
||||
return new FilteredGenre(genre, genre.Quests);
|
||||
else
|
||||
{
|
||||
List<IQuestInfo> filteredQuests = genre.Quests
|
||||
.Where(x => match(x.Name))
|
||||
.ToList();
|
||||
if (filteredQuests.Count > 0)
|
||||
return new FilteredGenre(genre, filteredQuests);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void RefreshCounts()
|
||||
{
|
||||
_genreCounts.Clear();
|
||||
_categoryCounts.Clear();
|
||||
_sectionCounts.Clear();
|
||||
|
||||
foreach (var genre in _journalData.Genres)
|
||||
{
|
||||
int available = genre.Quests.Count(x =>
|
||||
_questRegistry.TryGetQuest(x.QuestId, out var quest) && !quest.Root.Disabled);
|
||||
int completed = genre.Quests.Count(x => _questFunctions.IsQuestComplete(x.QuestId));
|
||||
_genreCounts[genre] = (available, completed);
|
||||
}
|
||||
|
||||
foreach (var category in _journalData.Categories)
|
||||
{
|
||||
var counts = _genreCounts
|
||||
.Where(x => category.Genres.Contains(x.Key))
|
||||
.Select(x => x.Value)
|
||||
.ToList();
|
||||
int available = counts.Sum(x => x.Available);
|
||||
int completed = counts.Sum(x => x.Completed);
|
||||
_categoryCounts[category] = (available, completed);
|
||||
}
|
||||
|
||||
foreach (var section in _journalData.Sections)
|
||||
{
|
||||
var counts = _categoryCounts
|
||||
.Where(x => section.Categories.Contains(x.Key))
|
||||
.Select(x => x.Value)
|
||||
.ToList();
|
||||
int available = counts.Sum(x => x.Available);
|
||||
int completed = counts.Sum(x => x.Completed);
|
||||
_sectionCounts[section] = (available, completed);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearCounts()
|
||||
{
|
||||
foreach (var genreCount in _genreCounts.ToList())
|
||||
_genreCounts[genreCount.Key] = (genreCount.Value.Available, 0);
|
||||
|
||||
foreach (var categoryCount in _categoryCounts.ToList())
|
||||
_categoryCounts[categoryCount.Key] = (categoryCount.Value.Available, 0);
|
||||
|
||||
foreach (var sectionCount in _sectionCounts.ToList())
|
||||
_sectionCounts[sectionCount.Key] = (sectionCount.Value.Available, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -14,49 +14,33 @@ using Questionable.Controller;
|
||||
using Questionable.Data;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Windows.JournalComponents;
|
||||
using Questionable.Windows.QuestComponents;
|
||||
|
||||
namespace Questionable.Windows;
|
||||
|
||||
internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
{
|
||||
private readonly JournalData _journalData;
|
||||
private readonly QuestJournalComponent _questJournalComponent;
|
||||
private readonly GatheringJournalComponent _gatheringJournalComponent;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestFunctions _questFunctions;
|
||||
private readonly UiUtils _uiUtils;
|
||||
private readonly QuestTooltipComponent _questTooltipComponent;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly ICommandManager _commandManager;
|
||||
|
||||
private readonly Dictionary<JournalData.Genre, (int Available, int Completed)> _genreCounts = new();
|
||||
private readonly Dictionary<JournalData.Category, (int Available, int Completed)> _categoryCounts = new();
|
||||
private readonly Dictionary<JournalData.Section, (int Available, int Completed)> _sectionCounts = new();
|
||||
|
||||
private List<FilteredSection> _filteredSections = [];
|
||||
private string _searchText = string.Empty;
|
||||
|
||||
public JournalProgressWindow(JournalData journalData,
|
||||
public JournalProgressWindow(
|
||||
QuestJournalComponent questJournalComponent,
|
||||
GatheringJournalComponent gatheringJournalComponent,
|
||||
QuestRegistry questRegistry,
|
||||
QuestFunctions questFunctions,
|
||||
UiUtils uiUtils,
|
||||
QuestTooltipComponent questTooltipComponent,
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
IClientState clientState,
|
||||
ICommandManager commandManager)
|
||||
IClientState clientState)
|
||||
: base("Journal Progress###QuestionableJournalProgress")
|
||||
{
|
||||
_journalData = journalData;
|
||||
_questJournalComponent = questJournalComponent;
|
||||
_gatheringJournalComponent = gatheringJournalComponent;
|
||||
_questRegistry = questRegistry;
|
||||
_questFunctions = questFunctions;
|
||||
_uiUtils = uiUtils;
|
||||
_questTooltipComponent = questTooltipComponent;
|
||||
_pluginInterface = pluginInterface;
|
||||
_clientState = clientState;
|
||||
_commandManager = commandManager;
|
||||
|
||||
_clientState.Login += RefreshCounts;
|
||||
_clientState.Logout -= ClearCounts;
|
||||
_clientState.Login += _questJournalComponent.RefreshCounts;
|
||||
_clientState.Login += _gatheringJournalComponent.RefreshCounts;
|
||||
_clientState.Logout -= _questJournalComponent.ClearCounts;
|
||||
_questRegistry.Reloaded += OnQuestsReloaded;
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
@ -65,318 +49,34 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
private void OnQuestsReloaded(object? sender, EventArgs e) => RefreshCounts();
|
||||
private void OnQuestsReloaded(object? sender, EventArgs e)
|
||||
{
|
||||
_questJournalComponent.RefreshCounts();
|
||||
_gatheringJournalComponent.RefreshCounts();
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
UpdateFilter();
|
||||
RefreshCounts();
|
||||
_questJournalComponent.UpdateFilter();
|
||||
_questJournalComponent.RefreshCounts();
|
||||
_gatheringJournalComponent.RefreshCounts();
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (ImGui.CollapsingHeader("Explanation", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
ImGui.Text("The list below contains all quests that appear in your journal.");
|
||||
ImGui.BulletText("'Supported' lists quests that Questionable can do for you");
|
||||
ImGui.BulletText("'Completed' lists quests your current character has completed.");
|
||||
ImGui.BulletText(
|
||||
"Not all quests can be completed even if they're listed as available, e.g. starting city quest chains.");
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputTextWithHint(string.Empty, "Search quests and categories", ref _searchText, 256))
|
||||
UpdateFilter();
|
||||
|
||||
if (_filteredSections.Count > 0)
|
||||
{
|
||||
using var table = ImRaii.Table("Quests", 3, ImGuiTableFlags.NoSavedSettings);
|
||||
if (!table)
|
||||
using var tabBar = ImRaii.TabBar("Journal");
|
||||
if (!tabBar)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
|
||||
ImGui.TableSetupColumn("Supported", ImGuiTableColumnFlags.WidthFixed, 120 * ImGui.GetIO().FontGlobalScale);
|
||||
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 120 * ImGui.GetIO().FontGlobalScale);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var section in _filteredSections)
|
||||
{
|
||||
DrawSection(section);
|
||||
}
|
||||
}
|
||||
else
|
||||
ImGui.Text("No quest or category matches your search text.");
|
||||
}
|
||||
|
||||
private void DrawSection(FilteredSection filter)
|
||||
{
|
||||
if (filter.Section.QuestCount == 0)
|
||||
return;
|
||||
|
||||
(int supported, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(filter.Section.Name, ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(supported, filter.Section.QuestCount);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(completed, filter.Section.QuestCount);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var category in filter.Categories)
|
||||
DrawCategory(category);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCategory(FilteredCategory filter)
|
||||
{
|
||||
if (filter.Category.QuestCount == 0)
|
||||
return;
|
||||
|
||||
(int supported, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(filter.Category.Name, ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(supported, filter.Category.QuestCount);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(completed, filter.Category.QuestCount);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var genre in filter.Genres)
|
||||
DrawGenre(genre);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGenre(FilteredGenre filter)
|
||||
{
|
||||
if (filter.Genre.QuestCount == 0)
|
||||
return;
|
||||
|
||||
(int supported, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
bool open = ImGui.TreeNodeEx(filter.Genre.Name, ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(supported, filter.Genre.QuestCount);
|
||||
ImGui.TableNextColumn();
|
||||
DrawCount(completed, filter.Genre.QuestCount);
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var quest in filter.Quests)
|
||||
DrawQuest(quest);
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawQuest(IQuestInfo questInfo)
|
||||
{
|
||||
_questRegistry.TryGetQuest(questInfo.QuestId, out var quest);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TreeNodeEx(questInfo.Name,
|
||||
ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.SpanFullWidth);
|
||||
|
||||
|
||||
if (questInfo is QuestInfo && ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
|
||||
{
|
||||
_commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty, commandInfo);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
_questTooltipComponent.Draw(questInfo);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
float spacing;
|
||||
// ReSharper disable once UnusedVariable
|
||||
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
|
||||
{
|
||||
spacing = ImGui.GetColumnWidth() / 2 - ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
|
||||
}
|
||||
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + spacing);
|
||||
_uiUtils.ChecklistItem(string.Empty, quest is { Root.Disabled: false });
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var (color, icon, text) = _uiUtils.GetQuestStyle(questInfo.QuestId);
|
||||
_uiUtils.ChecklistItem(text, color, icon);
|
||||
}
|
||||
|
||||
private static void DrawCount(int count, int total)
|
||||
{
|
||||
string len = 9999.ToString(CultureInfo.CurrentCulture);
|
||||
ImGui.PushFont(UiBuilder.MonoFont);
|
||||
|
||||
string text =
|
||||
$"{count.ToString(CultureInfo.CurrentCulture).PadLeft(len.Length)} / {total.ToString(CultureInfo.CurrentCulture).PadLeft(len.Length)}";
|
||||
if (count == total)
|
||||
ImGui.TextColored(ImGuiColors.ParsedGreen, text);
|
||||
else
|
||||
ImGui.TextUnformatted(text);
|
||||
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
private void UpdateFilter()
|
||||
{
|
||||
Predicate<string> match;
|
||||
if (string.IsNullOrWhiteSpace(_searchText))
|
||||
match = _ => true;
|
||||
else
|
||||
match = x => x.Contains(_searchText, StringComparison.CurrentCultureIgnoreCase);
|
||||
|
||||
_filteredSections = _journalData.Sections
|
||||
.Select(section => FilterSection(section, match))
|
||||
.Where(x => x != null)
|
||||
.Cast<FilteredSection>()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static FilteredSection? FilterSection(JournalData.Section section, Predicate<string> match)
|
||||
{
|
||||
if (match(section.Name))
|
||||
{
|
||||
return new FilteredSection(section,
|
||||
section.Categories
|
||||
.Select(x => FilterCategory(x, _ => true))
|
||||
.Cast<FilteredCategory>()
|
||||
.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
List<FilteredCategory> filteredCategories = section.Categories
|
||||
.Select(category => FilterCategory(category, match))
|
||||
.Where(x => x != null)
|
||||
.Cast<FilteredCategory>()
|
||||
.ToList();
|
||||
if (filteredCategories.Count > 0)
|
||||
return new FilteredSection(section, filteredCategories);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FilteredCategory? FilterCategory(JournalData.Category category, Predicate<string> match)
|
||||
{
|
||||
if (match(category.Name))
|
||||
{
|
||||
return new FilteredCategory(category,
|
||||
category.Genres
|
||||
.Select(x => FilterGenre(x, _ => true))
|
||||
.Cast<FilteredGenre>()
|
||||
.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
List<FilteredGenre> filteredGenres = category.Genres
|
||||
.Select(genre => FilterGenre(genre, match))
|
||||
.Where(x => x != null)
|
||||
.Cast<FilteredGenre>()
|
||||
.ToList();
|
||||
if (filteredGenres.Count > 0)
|
||||
return new FilteredCategory(category, filteredGenres);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static FilteredGenre? FilterGenre(JournalData.Genre genre, Predicate<string> match)
|
||||
{
|
||||
if (match(genre.Name))
|
||||
return new FilteredGenre(genre, genre.Quests);
|
||||
else
|
||||
{
|
||||
List<IQuestInfo> filteredQuests = genre.Quests
|
||||
.Where(x => match(x.Name))
|
||||
.ToList();
|
||||
if (filteredQuests.Count > 0)
|
||||
return new FilteredGenre(genre, filteredQuests);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void RefreshCounts()
|
||||
{
|
||||
_genreCounts.Clear();
|
||||
_categoryCounts.Clear();
|
||||
_sectionCounts.Clear();
|
||||
|
||||
foreach (var genre in _journalData.Genres)
|
||||
{
|
||||
int available = genre.Quests.Count(x =>
|
||||
_questRegistry.TryGetQuest(x.QuestId, out var quest) && !quest.Root.Disabled);
|
||||
int completed = genre.Quests.Count(x => _questFunctions.IsQuestComplete(x.QuestId));
|
||||
_genreCounts[genre] = (available, completed);
|
||||
}
|
||||
|
||||
foreach (var category in _journalData.Categories)
|
||||
{
|
||||
var counts = _genreCounts
|
||||
.Where(x => category.Genres.Contains(x.Key))
|
||||
.Select(x => x.Value)
|
||||
.ToList();
|
||||
int available = counts.Sum(x => x.Available);
|
||||
int completed = counts.Sum(x => x.Completed);
|
||||
_categoryCounts[category] = (available, completed);
|
||||
}
|
||||
|
||||
foreach (var section in _journalData.Sections)
|
||||
{
|
||||
var counts = _categoryCounts
|
||||
.Where(x => section.Categories.Contains(x.Key))
|
||||
.Select(x => x.Value)
|
||||
.ToList();
|
||||
int available = counts.Sum(x => x.Available);
|
||||
int completed = counts.Sum(x => x.Completed);
|
||||
_sectionCounts[section] = (available, completed);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCounts()
|
||||
{
|
||||
foreach (var genreCount in _genreCounts.ToList())
|
||||
_genreCounts[genreCount.Key] = (genreCount.Value.Available, 0);
|
||||
|
||||
foreach (var categoryCount in _categoryCounts.ToList())
|
||||
_categoryCounts[categoryCount.Key] = (categoryCount.Value.Available, 0);
|
||||
|
||||
foreach (var sectionCount in _sectionCounts.ToList())
|
||||
_sectionCounts[sectionCount.Key] = (sectionCount.Value.Available, 0);
|
||||
_questJournalComponent.DrawQuests();
|
||||
_gatheringJournalComponent.DrawGatheringItems();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_questRegistry.Reloaded -= OnQuestsReloaded;
|
||||
_clientState.Logout -= ClearCounts;
|
||||
_clientState.Login -= RefreshCounts;
|
||||
_clientState.Logout -= _questJournalComponent.ClearCounts;
|
||||
_clientState.Login -= _gatheringJournalComponent.RefreshCounts;
|
||||
_clientState.Login -= _questJournalComponent.RefreshCounts;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user