public int ValidationIssueCount => _questValidator.IssueCount;
public int ValidationErrorCount => _questValidator.ErrorCount;
+ public event EventHandler? Reloaded;
+
public void Reload()
{
_questValidator.Reset();
}
ValidateQuests();
+ Reloaded?.Invoke(this, EventArgs.Empty);
_logger.LogInformation("Loaded {Count} quests in total", _quests.Count);
}
DebugOverlay debugOverlay,
ConfigWindow configWindow,
QuestSelectionWindow questSelectionWindow,
- QuestValidationWindow questValidationWindow)
+ QuestValidationWindow questValidationWindow,
+ JournalProgressWindow journalProgressWindow)
{
_pluginInterface = pluginInterface;
_framework = framework;
_windowSystem.AddWindow(debugOverlay);
_windowSystem.AddWindow(questSelectionWindow);
_windowSystem.AddWindow(questValidationWindow);
+ _windowSystem.AddWindow(journalProgressWindow);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;
--- /dev/null
+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<JournalGenre>()!
+ .Where(x => x.RowId > 0 && x.Icon > 0)
+ .Select(x => new Genre(x, questData.GetAllByJournalGenre(x.RowId)))
+ .ToList();
+
+ var limsaStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(1)!;
+ var gridaniaStart = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(2)!;
+ var uldahStart = dataManager.GetExcelSheet<QuestRedo>()!.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<JournalCategory>()!
+ .Where(x => x.RowId > 0)
+ .Select(x => new Category(x, Genres.Where(y => y.CategoryId == x.RowId).ToList()))
+ .ToList()
+ .AsReadOnly();
+ Sections = dataManager.GetExcelSheet<JournalSection>()!
+ .Select(x => new Section(x, Categories.Where(y => y.SectionId == x.RowId).ToList()))
+ .ToList();
+ }
+
+ public IReadOnlyList<Genre> Genres { get; }
+ public IReadOnlyList<Category> Categories { get; }
+ public List<Section> Sections { get; set; }
+
+ internal sealed class Genre
+ {
+ public Genre(JournalGenre journalGenre, List<QuestInfo> quests)
+ {
+ Id = journalGenre.RowId;
+ Name = journalGenre.Name.ToString();
+ CategoryId = journalGenre.JournalCategory.Row;
+ Quests = quests;
+ }
+
+ public Genre(uint id, string name, uint categoryId, List<QuestInfo> quests)
+ {
+ Id = id;
+ Name = name;
+ CategoryId = categoryId;
+ Quests = quests;
+ }
+
+ public uint Id { get; }
+ public string Name { get; }
+ public uint CategoryId { get; }
+ public List<QuestInfo> Quests { get; }
+ public int QuestCount => Quests.Count;
+ }
+
+ internal sealed class Category(JournalCategory journalCategory, IReadOnlyList<Genre> genres)
+ {
+ public uint Id { get; } = journalCategory.RowId;
+ public string Name { get; } = journalCategory.Name.ToString();
+ public uint SectionId { get; } = journalCategory.JournalSection.Row;
+ public IReadOnlyList<Genre> Genres { get; } = genres;
+ public int QuestCount => Genres.Sum(x => x.QuestCount);
+ }
+
+ internal sealed class Section(JournalSection journalSection, IReadOnlyList<Category> categories)
+ {
+ public uint Id { get; } = journalSection.RowId;
+ public string Name { get; } = journalSection.Name.ToString();
+ public IReadOnlyList<Category> Categories { get; } = categories;
+ public int QuestCount => Categories.Sum(x => x.QuestCount);
+ }
+}
_quests = dataManager.GetExcelSheet<Quest>()!
.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);
}
}
public bool IsIssuerOfAnyQuest(uint targetId) => _quests.Values.Any(x => x.IssuerDataId == targetId);
+
+ public List<QuestInfo> GetAllByJournalGenre(uint journalGenre)
+ {
+ return _quests.Values
+ .Where(x => x.JournalGenre == journalGenre)
+ .OrderBy(x => x.SortKey)
+ .ThenBy(x => x.QuestId)
+ .ToList();
+ }
}
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;
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();
public QuestJoin QuestLockJoin { get; }
public List<ushort> 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; }
serviceCollection.AddSingleton<ChatFunctions>();
serviceCollection.AddSingleton<AetherCurrentData>();
serviceCollection.AddSingleton<AetheryteData>();
+ serviceCollection.AddSingleton<JournalData>();
serviceCollection.AddSingleton<QuestData>();
serviceCollection.AddSingleton<TerritoryData>();
serviceCollection.AddSingleton<NavmeshIpc>();
serviceCollection.AddSingleton<ActiveQuestComponent>();
serviceCollection.AddSingleton<ARealmRebornComponent>();
serviceCollection.AddSingleton<CreationUtilsComponent>();
+ serviceCollection.AddSingleton<QuestTooltipComponent>();
serviceCollection.AddSingleton<QuickAccessButtonsComponent>();
serviceCollection.AddSingleton<RemainingTasksComponent>();
serviceCollection.AddSingleton<DebugOverlay>();
serviceCollection.AddSingleton<QuestSelectionWindow>();
serviceCollection.AddSingleton<QuestValidationWindow>();
+ serviceCollection.AddSingleton<JournalProgressWindow>();
}
private static void AddQuestValidators(ServiceCollection serviceCollection)
--- /dev/null
+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<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();
+
+ 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<string> 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;
+ }
+}
--- /dev/null
+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})";
+ }
+}
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;
_questRegistry = questRegistry;
_navmeshIpc = navmeshIpc;
_questValidationWindow = questValidationWindow;
+ _journalProgressWindow = journalProgressWindow;
_clientState = clientState;
_condition = condition;
_framework = framework;
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.RedoAlt,"Reload Data"))
Reload();
+ ImGui.SameLine();
+ if (ImGuiComponents.IconButton(FontAwesomeIcon.ChartColumn))
+ _journalProgressWindow.IsOpen = true;
+
+
if (_questRegistry.ValidationIssueCount > 0)
{
ImGui.SameLine();
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.V1;
+using Questionable.Windows.QuestComponents;
namespace Questionable.Windows;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private readonly UiUtils _uiUtils;
+ private readonly QuestTooltipComponent _questTooltipComponent;
private List<QuestInfo> _quests = [];
private List<QuestInfo> _offeredQuests = [];
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;
_territoryData = territoryData;
_clientState = clientState;
_uiUtils = uiUtils;
+ _questTooltipComponent = questTooltipComponent;
Size = new Vector2(500, 200);
SizeCondition = ImGuiCond.Once;
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)
}
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())
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})";
- }
}