Add Statistics/Progress window

pull/14/head
Liza 2024-07-29 16:54:18 +02:00
parent ba0edc562b
commit 0d2ce03164
Signed by: liza
GPG Key ID: 7199F8D727D55F67
10 changed files with 601 additions and 143 deletions

View File

@ -44,6 +44,8 @@ internal sealed class QuestRegistry
public int ValidationIssueCount => _questValidator.IssueCount; public int ValidationIssueCount => _questValidator.IssueCount;
public int ValidationErrorCount => _questValidator.ErrorCount; public int ValidationErrorCount => _questValidator.ErrorCount;
public event EventHandler? Reloaded;
public void Reload() public void Reload()
{ {
_questValidator.Reset(); _questValidator.Reset();
@ -63,6 +65,7 @@ internal sealed class QuestRegistry
} }
ValidateQuests(); ValidateQuests();
Reloaded?.Invoke(this, EventArgs.Empty);
_logger.LogInformation("Loaded {Count} quests in total", _quests.Count); _logger.LogInformation("Loaded {Count} quests in total", _quests.Count);
} }

View File

@ -30,7 +30,8 @@ internal sealed class DalamudInitializer : IDisposable
DebugOverlay debugOverlay, DebugOverlay debugOverlay,
ConfigWindow configWindow, ConfigWindow configWindow,
QuestSelectionWindow questSelectionWindow, QuestSelectionWindow questSelectionWindow,
QuestValidationWindow questValidationWindow) QuestValidationWindow questValidationWindow,
JournalProgressWindow journalProgressWindow)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_framework = framework; _framework = framework;
@ -46,6 +47,7 @@ internal sealed class DalamudInitializer : IDisposable
_windowSystem.AddWindow(debugOverlay); _windowSystem.AddWindow(debugOverlay);
_windowSystem.AddWindow(questSelectionWindow); _windowSystem.AddWindow(questSelectionWindow);
_windowSystem.AddWindow(questValidationWindow); _windowSystem.AddWindow(questValidationWindow);
_windowSystem.AddWindow(journalProgressWindow);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw; _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle; _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;

View File

@ -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<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);
}
}

View File

@ -17,6 +17,7 @@ internal sealed class QuestData
_quests = dataManager.GetExcelSheet<Quest>()! _quests = dataManager.GetExcelSheet<Quest>()!
.Where(x => x.RowId > 0) .Where(x => x.RowId > 0)
.Where(x => x.IssuerLocation.Row > 0) .Where(x => x.IssuerLocation.Row > 0)
.Where(x => x.Festival.Row == 0)
.Select(x => new QuestInfo(x)) .Select(x => new QuestInfo(x))
.ToImmutableDictionary(x => x.QuestId, x => 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 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();
}
} }

View File

@ -14,7 +14,23 @@ internal sealed class QuestInfo
public QuestInfo(ExcelQuest quest) public QuestInfo(ExcelQuest quest)
{ {
QuestId = (ushort)(quest.RowId & 0xFFFF); 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; Level = quest.ClassJobLevel0;
IssuerDataId = quest.IssuerStart; IssuerDataId = quest.IssuerStart;
IsRepeatable = quest.IsRepeatable; IsRepeatable = quest.IsRepeatable;
@ -22,6 +38,8 @@ internal sealed class QuestInfo
PreviousQuestJoin = (QuestJoin)quest.PreviousQuestJoin; PreviousQuestJoin = (QuestJoin)quest.PreviousQuestJoin;
QuestLocks = quest.QuestLock.Select(x => (ushort)(x.Row & 0xFFFFF)).Where(x => x != 0).ToImmutableList(); QuestLocks = quest.QuestLock.Select(x => (ushort)(x.Row & 0xFFFFF)).Where(x => x != 0).ToImmutableList();
QuestLockJoin = (QuestJoin)quest.QuestLockJoin; QuestLockJoin = (QuestJoin)quest.QuestLockJoin;
JournalGenre = quest.JournalGenre?.Row;
SortKey = quest.SortKey;
IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1; IsMainScenarioQuest = quest.JournalGenre?.Value?.JournalCategory?.Value?.JournalSection?.Row is 0 or 1;
CompletesInstantly = quest.ToDoCompleteSeq[0] == 0; CompletesInstantly = quest.ToDoCompleteSeq[0] == 0;
PreviousInstanceContent = quest.InstanceContent.Select(x => (ushort)x.Row).Where(x => x != 0).ToList(); 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 QuestJoin QuestLockJoin { get; }
public List<ushort> PreviousInstanceContent { get; } public List<ushort> PreviousInstanceContent { get; }
public QuestJoin PreviousInstanceContentJoin { get; } public QuestJoin PreviousInstanceContentJoin { get; }
public uint? JournalGenre { get; }
public ushort SortKey { get; set; }
public bool IsMainScenarioQuest { get; } public bool IsMainScenarioQuest { get; }
public bool CompletesInstantly { get; } public bool CompletesInstantly { get; }
public GrandCompany GrandCompany { get; } public GrandCompany GrandCompany { get; }

View File

@ -89,6 +89,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ChatFunctions>(); serviceCollection.AddSingleton<ChatFunctions>();
serviceCollection.AddSingleton<AetherCurrentData>(); serviceCollection.AddSingleton<AetherCurrentData>();
serviceCollection.AddSingleton<AetheryteData>(); serviceCollection.AddSingleton<AetheryteData>();
serviceCollection.AddSingleton<JournalData>();
serviceCollection.AddSingleton<QuestData>(); serviceCollection.AddSingleton<QuestData>();
serviceCollection.AddSingleton<TerritoryData>(); serviceCollection.AddSingleton<TerritoryData>();
serviceCollection.AddSingleton<NavmeshIpc>(); serviceCollection.AddSingleton<NavmeshIpc>();
@ -159,6 +160,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ActiveQuestComponent>(); serviceCollection.AddSingleton<ActiveQuestComponent>();
serviceCollection.AddSingleton<ARealmRebornComponent>(); serviceCollection.AddSingleton<ARealmRebornComponent>();
serviceCollection.AddSingleton<CreationUtilsComponent>(); serviceCollection.AddSingleton<CreationUtilsComponent>();
serviceCollection.AddSingleton<QuestTooltipComponent>();
serviceCollection.AddSingleton<QuickAccessButtonsComponent>(); serviceCollection.AddSingleton<QuickAccessButtonsComponent>();
serviceCollection.AddSingleton<RemainingTasksComponent>(); serviceCollection.AddSingleton<RemainingTasksComponent>();
@ -167,6 +169,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<DebugOverlay>(); serviceCollection.AddSingleton<DebugOverlay>();
serviceCollection.AddSingleton<QuestSelectionWindow>(); serviceCollection.AddSingleton<QuestSelectionWindow>();
serviceCollection.AddSingleton<QuestValidationWindow>(); serviceCollection.AddSingleton<QuestValidationWindow>();
serviceCollection.AddSingleton<JournalProgressWindow>();
} }
private static void AddQuestValidators(ServiceCollection serviceCollection) private static void AddQuestValidators(ServiceCollection serviceCollection)

View File

@ -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<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;
}
}

View File

@ -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})";
}
}

View File

@ -25,15 +25,25 @@ internal sealed class QuickAccessButtonsComponent
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
private readonly NavmeshIpc _navmeshIpc; private readonly NavmeshIpc _navmeshIpc;
private readonly QuestValidationWindow _questValidationWindow; private readonly QuestValidationWindow _questValidationWindow;
private readonly JournalProgressWindow _journalProgressWindow;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly ICommandManager _commandManager; private readonly ICommandManager _commandManager;
public QuickAccessButtonsComponent(QuestController questController, MovementController movementController, public QuickAccessButtonsComponent(QuestController questController,
GameUiController gameUiController, GameFunctions gameFunctions, ChatFunctions chatFunctions, MovementController movementController,
QuestRegistry questRegistry, NavmeshIpc navmeshIpc, QuestValidationWindow questValidationWindow, GameUiController gameUiController,
IClientState clientState, ICondition condition, IFramework framework, ICommandManager commandManager) GameFunctions gameFunctions,
ChatFunctions chatFunctions,
QuestRegistry questRegistry,
NavmeshIpc navmeshIpc,
QuestValidationWindow questValidationWindow,
JournalProgressWindow journalProgressWindow,
IClientState clientState,
ICondition condition,
IFramework framework,
ICommandManager commandManager)
{ {
_questController = questController; _questController = questController;
_movementController = movementController; _movementController = movementController;
@ -43,6 +53,7 @@ internal sealed class QuickAccessButtonsComponent
_questRegistry = questRegistry; _questRegistry = questRegistry;
_navmeshIpc = navmeshIpc; _navmeshIpc = navmeshIpc;
_questValidationWindow = questValidationWindow; _questValidationWindow = questValidationWindow;
_journalProgressWindow = journalProgressWindow;
_clientState = clientState; _clientState = clientState;
_condition = condition; _condition = condition;
_framework = framework; _framework = framework;
@ -80,6 +91,11 @@ internal sealed class QuickAccessButtonsComponent
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.RedoAlt,"Reload Data")) if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.RedoAlt,"Reload Data"))
Reload(); Reload();
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.ChartColumn))
_journalProgressWindow.IsOpen = true;
if (_questRegistry.ValidationIssueCount > 0) if (_questRegistry.ValidationIssueCount > 0)
{ {
ImGui.SameLine(); ImGui.SameLine();

View File

@ -20,6 +20,7 @@ using Questionable.Controller;
using Questionable.Data; using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.V1; using Questionable.Model.V1;
using Questionable.Windows.QuestComponents;
namespace Questionable.Windows; namespace Questionable.Windows;
@ -36,6 +37,7 @@ internal sealed class QuestSelectionWindow : LWindow
private readonly TerritoryData _territoryData; private readonly TerritoryData _territoryData;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly UiUtils _uiUtils; private readonly UiUtils _uiUtils;
private readonly QuestTooltipComponent _questTooltipComponent;
private List<QuestInfo> _quests = []; private List<QuestInfo> _quests = [];
private List<QuestInfo> _offeredQuests = []; private List<QuestInfo> _offeredQuests = [];
@ -43,7 +45,8 @@ internal sealed class QuestSelectionWindow : LWindow
public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions, public QuestSelectionWindow(QuestData questData, IGameGui gameGui, IChatGui chatGui, GameFunctions gameFunctions,
QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface, QuestController questController, QuestRegistry questRegistry, IDalamudPluginInterface pluginInterface,
TerritoryData territoryData, IClientState clientState, UiUtils uiUtils) TerritoryData territoryData, IClientState clientState, UiUtils uiUtils,
QuestTooltipComponent questTooltipComponent)
: base($"Quest Selection{WindowId}") : base($"Quest Selection{WindowId}")
{ {
_questData = questData; _questData = questData;
@ -56,6 +59,7 @@ internal sealed class QuestSelectionWindow : LWindow
_territoryData = territoryData; _territoryData = territoryData;
_clientState = clientState; _clientState = clientState;
_uiUtils = uiUtils; _uiUtils = uiUtils;
_questTooltipComponent = questTooltipComponent;
Size = new Vector2(500, 200); Size = new Vector2(500, 200);
SizeCondition = ImGuiCond.Once; SizeCondition = ImGuiCond.Once;
@ -169,7 +173,7 @@ internal sealed class QuestSelectionWindow : LWindow
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
{ {
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
var (color, icon, tooltipText) = _uiUtils.GetQuestStyle(quest.QuestId); var (color, icon, _) = _uiUtils.GetQuestStyle(quest.QuestId);
using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{ {
if (isKnownQuest) if (isKnownQuest)
@ -179,32 +183,7 @@ internal sealed class QuestSelectionWindow : LWindow
} }
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ _questTooltipComponent.Draw(quest);
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);
}
}
} }
if (ImGui.TableNextColumn()) if (ImGui.TableNextColumn())
@ -272,113 +251,4 @@ internal sealed class QuestSelectionWindow : LWindow
ImGui.SetClipboardText(fileName); ImGui.SetClipboardText(fileName);
_chatGui.Print($"Copied '{fileName}' to clipboard"); _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})";
}
} }