From 0d2ce0316440b1efb1a4d3cb6b610189d85c0493 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Mon, 29 Jul 2024 16:54:18 +0200 Subject: [PATCH] Add Statistics/Progress window --- Questionable/Controller/QuestRegistry.cs | 3 + Questionable/DalamudInitializer.cs | 4 +- Questionable/Data/JournalData.cs | 96 +++++++ Questionable/Data/QuestData.cs | 10 + Questionable/Model/QuestInfo.cs | 22 +- Questionable/QuestionablePlugin.cs | 3 + Questionable/Windows/JournalProgressWindow.cs | 268 ++++++++++++++++++ .../QuestComponents/QuestTooltipComponent.cs | 170 +++++++++++ .../QuickAccessButtonsComponent.cs | 24 +- Questionable/Windows/QuestSelectionWindow.cs | 144 +--------- 10 files changed, 601 insertions(+), 143 deletions(-) create mode 100644 Questionable/Data/JournalData.cs create mode 100644 Questionable/Windows/JournalProgressWindow.cs create mode 100644 Questionable/Windows/QuestComponents/QuestTooltipComponent.cs diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs index d0c02abd..71750a1c 100644 --- a/Questionable/Controller/QuestRegistry.cs +++ b/Questionable/Controller/QuestRegistry.cs @@ -44,6 +44,8 @@ internal sealed class QuestRegistry public int ValidationIssueCount => _questValidator.IssueCount; public int ValidationErrorCount => _questValidator.ErrorCount; + public event EventHandler? Reloaded; + public void Reload() { _questValidator.Reset(); @@ -63,6 +65,7 @@ internal sealed class QuestRegistry } ValidateQuests(); + Reloaded?.Invoke(this, EventArgs.Empty); _logger.LogInformation("Loaded {Count} quests in total", _quests.Count); } diff --git a/Questionable/DalamudInitializer.cs b/Questionable/DalamudInitializer.cs index d2cf7af4..76678de6 100644 --- a/Questionable/DalamudInitializer.cs +++ b/Questionable/DalamudInitializer.cs @@ -30,7 +30,8 @@ internal sealed class DalamudInitializer : IDisposable DebugOverlay debugOverlay, ConfigWindow configWindow, QuestSelectionWindow questSelectionWindow, - QuestValidationWindow questValidationWindow) + QuestValidationWindow questValidationWindow, + JournalProgressWindow journalProgressWindow) { _pluginInterface = pluginInterface; _framework = framework; @@ -46,6 +47,7 @@ internal sealed class DalamudInitializer : IDisposable _windowSystem.AddWindow(debugOverlay); _windowSystem.AddWindow(questSelectionWindow); _windowSystem.AddWindow(questValidationWindow); + _windowSystem.AddWindow(journalProgressWindow); _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle; diff --git a/Questionable/Data/JournalData.cs b/Questionable/Data/JournalData.cs new file mode 100644 index 00000000..a7bf55e3 --- /dev/null +++ b/Questionable/Data/JournalData.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Linq; +using Dalamud.Plugin.Services; +using Lumina.Excel.GeneratedSheets; +using Questionable.Model; + +namespace Questionable.Data; + +internal sealed class JournalData +{ + public JournalData(IDataManager dataManager, QuestData questData) + { + var genres = dataManager.GetExcelSheet()! + .Where(x => x.RowId > 0 && x.Icon > 0) + .Select(x => new Genre(x, questData.GetAllByJournalGenre(x.RowId))) + .ToList(); + + var limsaStart = dataManager.GetExcelSheet()!.GetRow(1)!; + var gridaniaStart = dataManager.GetExcelSheet()!.GetRow(2)!; + var uldahStart = dataManager.GetExcelSheet()!.GetRow(3)!; + var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1, + new uint[] { 108, 109 }.Concat(limsaStart.Quest.Select(x => x.Row)) + .Where(x => x != 0) + .Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF))).ToList()); + var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1, + new uint[] { 85, 123, 124 }.Concat(gridaniaStart.Quest.Select(x => x.Row)) + .Where(x => x != 0) + .Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF))).ToList()); + var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1, + new uint[] { 568, 569, 570 }.Concat(uldahStart.Quest.Select(x => x.Row)) + .Where(x => x != 0) + .Select(x => questData.GetQuestInfo((ushort)(x & 0xFFFF))) + .ToList()); + genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]); + genres.Single(x => x.Id == 1) + .Quests + .RemoveAll(x => + genreLimsa.Quests.Contains(x) || genreGridania.Quests.Contains(x) || genreUldah.Quests.Contains(x)); + + Genres = genres.AsReadOnly(); + Categories = dataManager.GetExcelSheet()! + .Where(x => x.RowId > 0) + .Select(x => new Category(x, Genres.Where(y => y.CategoryId == x.RowId).ToList())) + .ToList() + .AsReadOnly(); + Sections = dataManager.GetExcelSheet()! + .Select(x => new Section(x, Categories.Where(y => y.SectionId == x.RowId).ToList())) + .ToList(); + } + + public IReadOnlyList Genres { get; } + public IReadOnlyList Categories { get; } + public List
Sections { get; set; } + + internal sealed class Genre + { + public Genre(JournalGenre journalGenre, List quests) + { + Id = journalGenre.RowId; + Name = journalGenre.Name.ToString(); + CategoryId = journalGenre.JournalCategory.Row; + Quests = quests; + } + + public Genre(uint id, string name, uint categoryId, List quests) + { + Id = id; + Name = name; + CategoryId = categoryId; + Quests = quests; + } + + public uint Id { get; } + public string Name { get; } + public uint CategoryId { get; } + public List Quests { get; } + public int QuestCount => Quests.Count; + } + + internal sealed class Category(JournalCategory journalCategory, IReadOnlyList genres) + { + public uint Id { get; } = journalCategory.RowId; + public string Name { get; } = journalCategory.Name.ToString(); + public uint SectionId { get; } = journalCategory.JournalSection.Row; + public IReadOnlyList Genres { get; } = genres; + public int QuestCount => Genres.Sum(x => x.QuestCount); + } + + internal sealed class Section(JournalSection journalSection, IReadOnlyList categories) + { + public uint Id { get; } = journalSection.RowId; + public string Name { get; } = journalSection.Name.ToString(); + public IReadOnlyList Categories { get; } = categories; + public int QuestCount => Categories.Sum(x => x.QuestCount); + } +} diff --git a/Questionable/Data/QuestData.cs b/Questionable/Data/QuestData.cs index ac63dc7e..7b36aef5 100644 --- a/Questionable/Data/QuestData.cs +++ b/Questionable/Data/QuestData.cs @@ -17,6 +17,7 @@ internal sealed class QuestData _quests = dataManager.GetExcelSheet()! .Where(x => x.RowId > 0) .Where(x => x.IssuerLocation.Row > 0) + .Where(x => x.Festival.Row == 0) .Select(x => new QuestInfo(x)) .ToImmutableDictionary(x => x.QuestId, x => x); } @@ -34,4 +35,13 @@ internal sealed class QuestData } public bool IsIssuerOfAnyQuest(uint targetId) => _quests.Values.Any(x => x.IssuerDataId == targetId); + + public List GetAllByJournalGenre(uint journalGenre) + { + return _quests.Values + .Where(x => x.JournalGenre == journalGenre) + .OrderBy(x => x.SortKey) + .ThenBy(x => x.QuestId) + .ToList(); + } } diff --git a/Questionable/Model/QuestInfo.cs b/Questionable/Model/QuestInfo.cs index 54fd6fad..bdfa0a7f 100644 --- a/Questionable/Model/QuestInfo.cs +++ b/Questionable/Model/QuestInfo.cs @@ -14,7 +14,23 @@ internal sealed class QuestInfo public QuestInfo(ExcelQuest quest) { QuestId = (ushort)(quest.RowId & 0xFFFF); - Name = quest.Name.ToString(); + + string suffix = QuestId switch + { + 85 => " (LNC)", + 108 => " (MRD)", + 109 => " (ACN)", + 123 => " (ARC)", + 124 => " (CNJ)", + 568 => " (GLA)", + 569 => " (PGL)", + 570 => " (THM)", + 673 => " (Ul'dah)", + 674 => " (Limsa/Gridania)", + _ => "", + }; + + Name = $"{quest.Name}{suffix}"; Level = quest.ClassJobLevel0; IssuerDataId = quest.IssuerStart; IsRepeatable = quest.IsRepeatable; @@ -22,6 +38,8 @@ internal sealed class QuestInfo PreviousQuestJoin = (QuestJoin)quest.PreviousQuestJoin; QuestLocks = quest.QuestLock.Select(x => (ushort)(x.Row & 0xFFFFF)).Where(x => x != 0).ToImmutableList(); QuestLockJoin = (QuestJoin)quest.QuestLockJoin; + JournalGenre = quest.JournalGenre?.Row; + SortKey = quest.SortKey; IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1; CompletesInstantly = quest.ToDoCompleteSeq[0] == 0; PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.Row).Where(x => x != 0).ToList(); @@ -42,6 +60,8 @@ internal sealed class QuestInfo public QuestJoin QuestLockJoin { get; } public List PreviousInstanceContent { get; } public QuestJoin PreviousInstanceContentJoin { get; } + public uint? JournalGenre { get; } + public ushort SortKey { get; set; } public bool IsMainScenarioQuest { get; } public bool CompletesInstantly { get; } public GrandCompany GrandCompany { get; } diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index f44b41d7..df100963 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -89,6 +89,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -159,6 +160,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -167,6 +169,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); } private static void AddQuestValidators(ServiceCollection serviceCollection) diff --git a/Questionable/Windows/JournalProgressWindow.cs b/Questionable/Windows/JournalProgressWindow.cs new file mode 100644 index 00000000..5dd82b38 --- /dev/null +++ b/Questionable/Windows/JournalProgressWindow.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Plugin.Services; +using ImGuiNET; +using LLib.ImGui; +using Questionable.Controller; +using Questionable.Data; +using Questionable.Model; +using Questionable.Windows.QuestComponents; + +namespace Questionable.Windows; + +internal sealed class JournalProgressWindow : LWindow, IDisposable +{ + private readonly JournalData _journalData; + private readonly QuestRegistry _questRegistry; + private readonly GameFunctions _gameFunctions; + private readonly UiUtils _uiUtils; + private readonly QuestTooltipComponent _questTooltipComponent; + private readonly IClientState _clientState; + private readonly ICommandManager _commandManager; + + private readonly Dictionary _genreCounts = new(); + private readonly Dictionary _categoryCounts = new(); + private readonly Dictionary _sectionCounts = new(); + + public JournalProgressWindow(JournalData journalData, + QuestRegistry questRegistry, + GameFunctions gameFunctions, + UiUtils uiUtils, + QuestTooltipComponent questTooltipComponent, + IClientState clientState, + ICommandManager commandManager) + : base("Journal Progress###QuestionableJournalProgress") + { + _journalData = journalData; + _questRegistry = questRegistry; + _gameFunctions = gameFunctions; + _uiUtils = uiUtils; + _questTooltipComponent = questTooltipComponent; + _clientState = clientState; + _commandManager = commandManager; + + _clientState.Login += RefreshCounts; + _clientState.Logout -= ClearCounts; + _questRegistry.Reloaded += OnQuestsReloaded; + + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(700, 500) + }; + } + + private void OnQuestsReloaded(object? sender, EventArgs e) => RefreshCounts(); + + public override void OnOpen() => RefreshCounts(); + + public override void Draw() + { + 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(); + + 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); + } + } + + private void DrawSection(JournalData.Section section) + { + if (section.QuestCount == 0) + return; + + (int supported, int completed) = _sectionCounts.GetValueOrDefault(section); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + + bool open = ImGui.TreeNodeEx(section.Name, ImGuiTreeNodeFlags.SpanFullWidth); + + ImGui.TableNextColumn(); + DrawCount(supported, section.QuestCount); + ImGui.TableNextColumn(); + DrawCount(completed, section.QuestCount); + + if (open) + { + foreach (var category in section.Categories) + DrawCategory(category); + + ImGui.TreePop(); + } + } + + private void DrawCategory(JournalData.Category category) + { + if (category.QuestCount == 0) + return; + + (int supported, int completed) = _categoryCounts.GetValueOrDefault(category); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + + bool open = ImGui.TreeNodeEx(category.Name, ImGuiTreeNodeFlags.SpanFullWidth); + + ImGui.TableNextColumn(); + DrawCount(supported, category.QuestCount); + ImGui.TableNextColumn(); + DrawCount(completed, category.QuestCount); + + if (open) + { + foreach (var genre in category.Genres) + DrawGenre(genre); + + ImGui.TreePop(); + } + } + + private void DrawGenre(JournalData.Genre genre) + { + if (genre.QuestCount == 0) + return; + + (int supported, int completed) = _genreCounts.GetValueOrDefault(genre); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + + bool open = ImGui.TreeNodeEx(genre.Name, ImGuiTreeNodeFlags.SpanFullWidth); + + ImGui.TableNextColumn(); + DrawCount(supported, genre.QuestCount); + ImGui.TableNextColumn(); + DrawCount(completed, genre.QuestCount); + + if (open) + { + foreach (var quest in genre.Quests) + DrawQuest(quest); + + ImGui.TreePop(); + } + } + + private void DrawQuest(QuestInfo questInfo) + { + _questRegistry.TryGetQuest(questInfo.QuestId, out var quest); + + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.TreeNodeEx(questInfo.Name, + ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.SpanFullWidth); + + + if (ImGui.IsItemClicked() && _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo)) + { + _commandManager.DispatchCommand("/questinfo", + questInfo.QuestId.ToString(CultureInfo.InvariantCulture), commandInfo); + } + + if (ImGui.IsItemHovered()) + _questTooltipComponent.Draw(questInfo); + + ImGui.TableNextColumn(); + List authors = quest?.Root.Author ?? []; + _uiUtils.ChecklistItem(authors.Count > 0 ? string.Join(", ", authors) : 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 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 => _gameFunctions.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); + } + + public void Dispose() + { + _questRegistry.Reloaded -= OnQuestsReloaded; + _clientState.Logout -= ClearCounts; + _clientState.Login -= RefreshCounts; + } +} diff --git a/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs b/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs new file mode 100644 index 00000000..e8f5b5f7 --- /dev/null +++ b/Questionable/Windows/QuestComponents/QuestTooltipComponent.cs @@ -0,0 +1,170 @@ +using Dalamud.Interface.Colors; +using Dalamud.Interface.Utility.Raii; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using ImGuiNET; +using Questionable.Controller; +using Questionable.Data; +using Questionable.Model; + +namespace Questionable.Windows.QuestComponents; + +internal sealed class QuestTooltipComponent +{ + private readonly QuestRegistry _questRegistry; + private readonly QuestData _questData; + private readonly TerritoryData _territoryData; + private readonly GameFunctions _gameFunctions; + private readonly UiUtils _uiUtils; + + public QuestTooltipComponent( + QuestRegistry questRegistry, + QuestData questData, + TerritoryData territoryData, + GameFunctions gameFunctions, + UiUtils uiUtils) + { + _questRegistry = questRegistry; + _questData = questData; + _territoryData = territoryData; + _gameFunctions = gameFunctions; + _uiUtils = uiUtils; + } + + public void Draw(QuestInfo quest) + { + using var tooltip = ImRaii.Tooltip(); + if (tooltip) + { + var (color, _, tooltipText) = _uiUtils.GetQuestStyle(quest.QuestId); + ImGui.TextColored(color, tooltipText); + if (quest.IsRepeatable) + { + ImGui.SameLine(); + ImGui.TextUnformatted("Repeatable"); + } + + if (quest.CompletesInstantly) + { + ImGui.SameLine(); + ImGui.TextUnformatted("Instant"); + } + + if (!_questRegistry.IsKnownQuest(quest.QuestId)) + { + ImGui.SameLine(); + ImGui.TextUnformatted("NoQuestPath"); + } + + DrawQuestUnlocks(quest, 0); + } + } + + private void DrawQuestUnlocks(QuestInfo quest, int counter) + { + if (counter >= 10) + return; + + if (counter != 0 && quest.IsMainScenarioQuest) + return; + + if (counter > 0) + ImGui.Indent(); + + if (quest.PreviousQuests.Count > 0) + { + if (counter == 0) + ImGui.Separator(); + + if (quest.PreviousQuests.Count > 1) + { + if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.All) + ImGui.Text("Requires all:"); + else if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne) + ImGui.Text("Requires one:"); + } + + foreach (var q in quest.PreviousQuests) + { + var qInfo = _questData.GetQuestInfo(q); + var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q); + if (!_questRegistry.IsKnownQuest(qInfo.QuestId)) + iconColor = ImGuiColors.DalamudGrey; + + _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon); + + DrawQuestUnlocks(qInfo, counter + 1); + } + } + + if (counter == 0 && quest.QuestLocks.Count > 0) + { + ImGui.Separator(); + if (quest.QuestLocks.Count > 1) + { + if (quest.QuestLockJoin == QuestInfo.QuestJoin.All) + ImGui.Text("Blocked by (if all completed):"); + else if (quest.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne) + ImGui.Text("Blocked by (if at least completed):"); + } + else + ImGui.Text("Blocked by (if completed):"); + + foreach (var q in quest.QuestLocks) + { + var qInfo = _questData.GetQuestInfo(q); + var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q); + if (!_questRegistry.IsKnownQuest(qInfo.QuestId)) + iconColor = ImGuiColors.DalamudGrey; + + _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon); + } + } + + if (counter == 0 && quest.PreviousInstanceContent.Count > 0) + { + ImGui.Separator(); + if (quest.PreviousInstanceContent.Count > 1) + { + if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.All) + ImGui.Text("Requires all:"); + else if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne) + ImGui.Text("Requires one:"); + } + else + ImGui.Text("Requires:"); + + foreach (var instanceId in quest.PreviousInstanceContent) + { + string instanceName = _territoryData.GetInstanceName(instanceId) ?? "?"; + var (iconColor, icon) = UiUtils.GetInstanceStyle(instanceId); + _uiUtils.ChecklistItem(instanceName, iconColor, icon); + } + } + + if (counter == 0 && quest.GrandCompany != GrandCompany.None) + { + ImGui.Separator(); + string gcName = quest.GrandCompany switch + { + GrandCompany.Maelstrom => "Maelstrom", + GrandCompany.TwinAdder => "Twin Adder", + GrandCompany.ImmortalFlames => "Immortal Flames", + _ => "None", + }; + + GrandCompany currentGrandCompany = _gameFunctions.GetGrandCompany(); + _uiUtils.ChecklistItem($"Grand Company: {gcName}", quest.GrandCompany == currentGrandCompany); + } + + if (counter > 0) + ImGui.Unindent(); + } + + private static string FormatQuestUnlockName(QuestInfo questInfo) + { + if (questInfo.IsMainScenarioQuest) + return $"{questInfo.Name} ({questInfo.QuestId}, MSQ)"; + else + return $"{questInfo.Name} ({questInfo.QuestId})"; + } +} diff --git a/Questionable/Windows/QuestComponents/QuickAccessButtonsComponent.cs b/Questionable/Windows/QuestComponents/QuickAccessButtonsComponent.cs index 5a528a1e..7cdfb388 100644 --- a/Questionable/Windows/QuestComponents/QuickAccessButtonsComponent.cs +++ b/Questionable/Windows/QuestComponents/QuickAccessButtonsComponent.cs @@ -25,15 +25,25 @@ internal sealed class QuickAccessButtonsComponent private readonly QuestRegistry _questRegistry; private readonly NavmeshIpc _navmeshIpc; private readonly QuestValidationWindow _questValidationWindow; + private readonly JournalProgressWindow _journalProgressWindow; private readonly IClientState _clientState; private readonly ICondition _condition; private readonly IFramework _framework; private readonly ICommandManager _commandManager; - public QuickAccessButtonsComponent(QuestController questController, MovementController movementController, - GameUiController gameUiController, GameFunctions gameFunctions, ChatFunctions chatFunctions, - QuestRegistry questRegistry, NavmeshIpc navmeshIpc, QuestValidationWindow questValidationWindow, - IClientState clientState, ICondition condition, IFramework framework, ICommandManager commandManager) + public QuickAccessButtonsComponent(QuestController questController, + MovementController movementController, + GameUiController gameUiController, + GameFunctions gameFunctions, + ChatFunctions chatFunctions, + QuestRegistry questRegistry, + NavmeshIpc navmeshIpc, + QuestValidationWindow questValidationWindow, + JournalProgressWindow journalProgressWindow, + IClientState clientState, + ICondition condition, + IFramework framework, + ICommandManager commandManager) { _questController = questController; _movementController = movementController; @@ -43,6 +53,7 @@ internal sealed class QuickAccessButtonsComponent _questRegistry = questRegistry; _navmeshIpc = navmeshIpc; _questValidationWindow = questValidationWindow; + _journalProgressWindow = journalProgressWindow; _clientState = clientState; _condition = condition; _framework = framework; @@ -80,6 +91,11 @@ internal sealed class QuickAccessButtonsComponent if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.RedoAlt,"Reload Data")) Reload(); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.ChartColumn)) + _journalProgressWindow.IsOpen = true; + + if (_questRegistry.ValidationIssueCount > 0) { ImGui.SameLine(); diff --git a/Questionable/Windows/QuestSelectionWindow.cs b/Questionable/Windows/QuestSelectionWindow.cs index d9e0f870..6f420786 100644 --- a/Questionable/Windows/QuestSelectionWindow.cs +++ b/Questionable/Windows/QuestSelectionWindow.cs @@ -20,6 +20,7 @@ using Questionable.Controller; using Questionable.Data; using Questionable.Model; using Questionable.Model.V1; +using Questionable.Windows.QuestComponents; namespace Questionable.Windows; @@ -36,6 +37,7 @@ internal sealed class QuestSelectionWindow : LWindow private readonly TerritoryData _territoryData; private readonly IClientState _clientState; private readonly UiUtils _uiUtils; + private readonly QuestTooltipComponent _questTooltipComponent; private List _quests = []; private List _offeredQuests = []; @@ -43,7 +45,8 @@ internal sealed class QuestSelectionWindow : LWindow public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions, QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface, - TerritoryData territoryData, IClientState clientState, UiUtils uiUtils) + TerritoryData territoryData, IClientState clientState, UiUtils uiUtils, + QuestTooltipComponent questTooltipComponent) : base($"Quest Selection{WindowId}") { _questData = questData; @@ -56,6 +59,7 @@ internal sealed class QuestSelectionWindow : LWindow _territoryData = territoryData; _clientState = clientState; _uiUtils = uiUtils; + _questTooltipComponent = questTooltipComponent; Size = new Vector2(500, 200); SizeCondition = ImGuiCond.Once; @@ -169,7 +173,7 @@ internal sealed class QuestSelectionWindow : LWindow if (ImGui.TableNextColumn()) { ImGui.AlignTextToFramePadding(); - var (color, icon, tooltipText) = _uiUtils.GetQuestStyle(quest.QuestId); + var (color, icon, _) = _uiUtils.GetQuestStyle(quest.QuestId); using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) { if (isKnownQuest) @@ -179,32 +183,7 @@ internal sealed class QuestSelectionWindow : LWindow } if (ImGui.IsItemHovered()) - { - using var tooltip = ImRaii.Tooltip(); - if (tooltip) - { - ImGui.TextColored(color, tooltipText); - if (quest.IsRepeatable) - { - ImGui.SameLine(); - ImGui.TextUnformatted("Repeatable"); - } - - if (quest.CompletesInstantly) - { - ImGui.SameLine(); - ImGui.TextUnformatted("Instant"); - } - - if (!isKnownQuest) - { - ImGui.SameLine(); - ImGui.TextUnformatted("NoQuestPath"); - } - - DrawQuestUnlocks(quest, 0); - } - } + _questTooltipComponent.Draw(quest); } if (ImGui.TableNextColumn()) @@ -272,113 +251,4 @@ internal sealed class QuestSelectionWindow : LWindow ImGui.SetClipboardText(fileName); _chatGui.Print($"Copied '{fileName}' to clipboard"); } - - private void DrawQuestUnlocks(QuestInfo quest, int counter) - { - if (counter >= 10) - return; - - if (counter != 0 && quest.IsMainScenarioQuest) - return; - - if (counter > 0) - ImGui.Indent(); - - if (quest.PreviousQuests.Count > 0) - { - if (counter == 0) - ImGui.Separator(); - - if (quest.PreviousQuests.Count > 1) - { - if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.All) - ImGui.Text("Requires all:"); - else if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne) - ImGui.Text("Requires one:"); - } - - foreach (var q in quest.PreviousQuests) - { - var qInfo = _questData.GetQuestInfo(q); - var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q); - if (!_questRegistry.IsKnownQuest(qInfo.QuestId)) - iconColor = ImGuiColors.DalamudGrey; - - _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon); - - DrawQuestUnlocks(qInfo, counter + 1); - } - } - - if (counter == 0 && quest.QuestLocks.Count > 0) - { - ImGui.Separator(); - if (quest.QuestLocks.Count > 1) - { - if (quest.QuestLockJoin == QuestInfo.QuestJoin.All) - ImGui.Text("Blocked by (if all completed):"); - else if (quest.QuestLockJoin == QuestInfo.QuestJoin.AtLeastOne) - ImGui.Text("Blocked by (if at least completed):"); - } - else - ImGui.Text("Blocked by (if completed):"); - - foreach (var q in quest.QuestLocks) - { - var qInfo = _questData.GetQuestInfo(q); - var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q); - if (!_questRegistry.IsKnownQuest(qInfo.QuestId)) - iconColor = ImGuiColors.DalamudGrey; - - _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon); - } - } - - if (counter == 0 && quest.PreviousInstanceContent.Count > 0) - { - ImGui.Separator(); - if (quest.PreviousInstanceContent.Count > 1) - { - if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.All) - ImGui.Text("Requires all:"); - else if (quest.PreviousQuestJoin == QuestInfo.QuestJoin.AtLeastOne) - ImGui.Text("Requires one:"); - } - else - ImGui.Text("Requires:"); - - foreach (var instanceId in quest.PreviousInstanceContent) - { - string instanceName = _territoryData.GetInstanceName(instanceId) ?? "?"; - var (iconColor, icon) = UiUtils.GetInstanceStyle(instanceId); - _uiUtils.ChecklistItem(instanceName, iconColor, icon); - } - } - - if (counter == 0 && quest.GrandCompany != GrandCompany.None) - { - ImGui.Separator(); - string gcName = quest.GrandCompany switch - { - GrandCompany.Maelstrom => "Maelstrom", - GrandCompany.TwinAdder => "Twin Adder", - GrandCompany.ImmortalFlames => "Immortal Flames", - _ => "None", - }; - - GrandCompany currentGrandCompany = _gameFunctions.GetGrandCompany(); - _uiUtils.ChecklistItem($"Grand Company: {gcName}", quest.GrandCompany == currentGrandCompany); - } - - if (counter > 0) - ImGui.Unindent(); - } - - private static string FormatQuestUnlockName(QuestInfo questInfo) - { - if (questInfo.IsMainScenarioQuest) - return $"{questInfo.Name} ({questInfo.QuestId}, MSQ)"; - else - return $"{questInfo.Name} ({questInfo.QuestId})"; - } }