1
0
forked from liza/Questionable

Add search field to statistics/progress window

This commit is contained in:
Liza 2024-07-29 17:40:42 +02:00
parent 0d2ce03164
commit 65b7643b3e
Signed by: liza
GPG Key ID: 7199F8D727D55F67
3 changed files with 219 additions and 54 deletions

View File

@ -0,0 +1,51 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json",
"Author": "liza",
"QuestSequence": [
{
"Sequence": 0,
"Steps": [
{
"DataId": 1026746,
"Position": {
"X": -55.588684,
"Y": 206.50021,
"Z": 18.57019
},
"TerritoryId": 478,
"InteractionType": "AcceptQuest"
}
]
},
{
"Sequence": 1,
"Steps": [
{
"DataId": 1026747,
"Position": {
"X": 63.126587,
"Y": 205.63266,
"Z": 22.049255
},
"TerritoryId": 478,
"InteractionType": "Interact"
}
]
},
{
"Sequence": 255,
"Steps": [
{
"DataId": 1026750,
"Position": {
"X": -62.6994,
"Y": 206.50021,
"Z": 16.617004
},
"TerritoryId": 478,
"InteractionType": "CompleteQuest"
}
]
}
]
}

View File

@ -17,14 +17,14 @@ internal sealed class QuestInfo
string suffix = QuestId switch string suffix = QuestId switch
{ {
85 => " (LNC)", 85 => " (Lancer)",
108 => " (MRD)", 108 => " (Marauder)",
109 => " (ACN)", 109 => " (Arcanist)",
123 => " (ARC)", 123 => " (Archer)",
124 => " (CNJ)", 124 => " (Conjurer)",
568 => " (GLA)", 568 => " (Gladiator)",
569 => " (PGL)", 569 => " (Pugilist)",
570 => " (THM)", 570 => " (Thaumaturge)",
673 => " (Ul'dah)", 673 => " (Ul'dah)",
674 => " (Limsa/Gridania)", 674 => " (Limsa/Gridania)",
_ => "", _ => "",

View File

@ -6,6 +6,7 @@ using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ImGuiNET; using ImGuiNET;
using LLib.ImGui; using LLib.ImGui;
@ -23,6 +24,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
private readonly GameFunctions _gameFunctions; private readonly GameFunctions _gameFunctions;
private readonly UiUtils _uiUtils; private readonly UiUtils _uiUtils;
private readonly QuestTooltipComponent _questTooltipComponent; private readonly QuestTooltipComponent _questTooltipComponent;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ICommandManager _commandManager; private readonly ICommandManager _commandManager;
@ -30,11 +32,15 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
private readonly Dictionary<JournalData.Category, (int Available, int Completed)> _categoryCounts = 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 Dictionary<JournalData.Section, (int Available, int Completed)> _sectionCounts = new();
private List<FilteredSection> _filteredSections = [];
private string _searchText = string.Empty;
public JournalProgressWindow(JournalData journalData, public JournalProgressWindow(JournalData journalData,
QuestRegistry questRegistry, QuestRegistry questRegistry,
GameFunctions gameFunctions, GameFunctions gameFunctions,
UiUtils uiUtils, UiUtils uiUtils,
QuestTooltipComponent questTooltipComponent, QuestTooltipComponent questTooltipComponent,
IDalamudPluginInterface pluginInterface,
IClientState clientState, IClientState clientState,
ICommandManager commandManager) ICommandManager commandManager)
: base("Journal Progress###QuestionableJournalProgress") : base("Journal Progress###QuestionableJournalProgress")
@ -44,6 +50,7 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
_gameFunctions = gameFunctions; _gameFunctions = gameFunctions;
_uiUtils = uiUtils; _uiUtils = uiUtils;
_questTooltipComponent = questTooltipComponent; _questTooltipComponent = questTooltipComponent;
_pluginInterface = pluginInterface;
_clientState = clientState; _clientState = clientState;
_commandManager = commandManager; _commandManager = commandManager;
@ -59,107 +66,123 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
private void OnQuestsReloaded(object? sender, EventArgs e) => RefreshCounts(); private void OnQuestsReloaded(object? sender, EventArgs e) => RefreshCounts();
public override void OnOpen() => RefreshCounts(); public override void OnOpen()
{
UpdateFilter();
RefreshCounts();
}
public override void Draw() public override void Draw()
{ {
ImGui.Text("The list below contains all quests that appear in your journal."); if (ImGui.CollapsingHeader("Explanation", ImGuiTreeNodeFlags.DefaultOpen))
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();
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 _journalData.Sections)
{ {
DrawSection(section); 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(JournalData.Section section) private void DrawSection(FilteredSection filter)
{ {
if (section.QuestCount == 0) if (filter.Section.QuestCount == 0)
return; return;
(int supported, int completed) = _sectionCounts.GetValueOrDefault(section); (int supported, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
bool open = ImGui.TreeNodeEx(section.Name, ImGuiTreeNodeFlags.SpanFullWidth); bool open = ImGui.TreeNodeEx(filter.Section.Name, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawCount(supported, section.QuestCount); DrawCount(supported, filter.Section.QuestCount);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawCount(completed, section.QuestCount); DrawCount(completed, filter.Section.QuestCount);
if (open) if (open)
{ {
foreach (var category in section.Categories) foreach (var category in filter.Categories)
DrawCategory(category); DrawCategory(category);
ImGui.TreePop(); ImGui.TreePop();
} }
} }
private void DrawCategory(JournalData.Category category) private void DrawCategory(FilteredCategory filter)
{ {
if (category.QuestCount == 0) if (filter.Category.QuestCount == 0)
return; return;
(int supported, int completed) = _categoryCounts.GetValueOrDefault(category); (int supported, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
bool open = ImGui.TreeNodeEx(category.Name, ImGuiTreeNodeFlags.SpanFullWidth); bool open = ImGui.TreeNodeEx(filter.Category.Name, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawCount(supported, category.QuestCount); DrawCount(supported, filter.Category.QuestCount);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawCount(completed, category.QuestCount); DrawCount(completed, filter.Category.QuestCount);
if (open) if (open)
{ {
foreach (var genre in category.Genres) foreach (var genre in filter.Genres)
DrawGenre(genre); DrawGenre(genre);
ImGui.TreePop(); ImGui.TreePop();
} }
} }
private void DrawGenre(JournalData.Genre genre) private void DrawGenre(FilteredGenre filter)
{ {
if (genre.QuestCount == 0) if (filter.Genre.QuestCount == 0)
return; return;
(int supported, int completed) = _genreCounts.GetValueOrDefault(genre); (int supported, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
ImGui.TableNextRow(); ImGui.TableNextRow();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
bool open = ImGui.TreeNodeEx(genre.Name, ImGuiTreeNodeFlags.SpanFullWidth); bool open = ImGui.TreeNodeEx(filter.Genre.Name, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawCount(supported, genre.QuestCount); DrawCount(supported, filter.Genre.QuestCount);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
DrawCount(completed, genre.QuestCount); DrawCount(completed, filter.Genre.QuestCount);
if (open) if (open)
{ {
foreach (var quest in genre.Quests) foreach (var quest in filter.Quests)
DrawQuest(quest); DrawQuest(quest);
ImGui.TreePop(); ImGui.TreePop();
@ -186,9 +209,15 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
_questTooltipComponent.Draw(questInfo); _questTooltipComponent.Draw(questInfo);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
List<string> authors = quest?.Root.Author ?? []; float spacing;
_uiUtils.ChecklistItem(authors.Count > 0 ? string.Join(", ", authors) : string.Empty, // ReSharper disable once UnusedVariable
quest is { Root.Disabled: false }); 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(); ImGui.TableNextColumn();
var (color, icon, text) = _uiUtils.GetQuestStyle(questInfo.QuestId); var (color, icon, text) = _uiUtils.GetQuestStyle(questInfo.QuestId);
@ -210,6 +239,85 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
ImGui.PopFont(); 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<QuestInfo> filteredQuests = genre.Quests
.Where(x => match(x.Name))
.ToList();
if (filteredQuests.Count > 0)
return new FilteredGenre(genre, filteredQuests);
}
return null;
}
private void RefreshCounts() private void RefreshCounts()
{ {
_genreCounts.Clear(); _genreCounts.Clear();
@ -265,4 +373,10 @@ internal sealed class JournalProgressWindow : LWindow, IDisposable
_clientState.Logout -= ClearCounts; _clientState.Logout -= ClearCounts;
_clientState.Login -= RefreshCounts; _clientState.Login -= 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<QuestInfo> Quests);
} }