1
0
Fork 0
forked from aly/qstbak
qstbak/Questionable/Questionable.Windows.JournalComponents/DutyJournalComponent.cs
2026-08-17 20:29:32 +10:00

439 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Windows.JournalComponents;
internal sealed class DutyJournalComponent
{
private enum RowType : byte
{
Category,
Duty
}
private readonly record struct FlatRow(RowType Type, int GroupIndex, DutyInfo? Duty, string Label, DutyCounts Counts);
private sealed record DutyCategory(string Name, List<DutyInfo> Duties);
private sealed record DutyCounts(int Unlocked, int Completed, int CompletionTrackable, int Total)
{
public string FormattedUnlocked { get; } = FormatPair(Unlocked, Total);
public string FormattedCompleted { get; } = FormatPair(Completed, CompletionTrackable);
public bool IsUnlockedComplete
{
get
{
if (Unlocked == Total)
{
return Total > 0;
}
return false;
}
}
public bool IsCompletedComplete
{
get
{
if (Completed == CompletionTrackable)
{
return CompletionTrackable > 0;
}
return false;
}
}
private static readonly int PadWidth = 9999.ToString(CultureInfo.CurrentCulture).Length;
public DutyCounts()
: this(0, 0, 0, 0)
{
}
private static string FormatPair(int n, int d)
{
if (d != 0)
{
return n.ToString(CultureInfo.CurrentCulture).PadLeft(PadWidth) + " / " + d.ToString(CultureInfo.CurrentCulture).PadLeft(PadWidth);
}
return "-".PadLeft(PadWidth) + " / " + "-".PadLeft(PadWidth);
}
[CompilerGenerated]
private DutyCounts(DutyCounts original)
{
Unlocked = original.Unlocked;
Completed = original.Completed;
CompletionTrackable = original.CompletionTrackable;
Total = original.Total;
FormattedUnlocked = original.FormattedUnlocked;
FormattedCompleted = original.FormattedCompleted;
}
}
private readonly DutyUnlockData _dutyUnlockData;
private readonly QuestData _questData;
private readonly QuestFunctions _questFunctions;
private readonly UiUtils _uiUtils;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly ILogger<DutyJournalComponent> _logger;
private List<DutyCategory> _filteredCategories = new List<DutyCategory>();
private Dictionary<string, DutyCounts> _categoryCounts = new Dictionary<string, DutyCounts>();
private List<FlatRow> _flatRows = new List<FlatRow>();
private readonly HashSet<string> _expandedGroups = new HashSet<string>();
private string _searchText = string.Empty;
private bool _showLockedOnly;
private bool _showUnlockedOnly;
public DutyJournalComponent(DutyUnlockData dutyUnlockData, QuestData questData, QuestFunctions questFunctions, UiUtils uiUtils, IDalamudPluginInterface pluginInterface, ILogger<DutyJournalComponent> logger)
{
_dutyUnlockData = dutyUnlockData;
_questData = questData;
_questFunctions = questFunctions;
_uiUtils = uiUtils;
_pluginInterface = pluginInterface;
_logger = logger;
}
public unsafe void DrawDuties()
{
DrawFilterControls();
ImGui.Separator();
if (_flatRows.Count > 0)
{
float x;
using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{
x = ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X;
}
float fontGlobalScale = ImGui.GetIO().FontGlobalScale;
using ImRaii.TableDisposable tableDisposable = UiThemeUtils.BeginThemedTable("Duties", 3, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.ScrollY);
if (!tableDisposable)
{
return;
}
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.NoHide);
ImGui.TableSetupColumn("Unlocked", ImGuiTableColumnFlags.WidthFixed, 120f * fontGlobalScale);
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 120f * fontGlobalScale);
ImGui.TableHeadersRow();
float textLineHeightWithSpacing = ImGui.GetTextLineHeightWithSpacing();
bool flag = false;
ImGuiListClipper* intPtr = ImGuiNative.ImGuiListClipper();
ImGuiNative.Begin(intPtr, _flatRows.Count, textLineHeightWithSpacing);
ImGuiListClipperPtr imGuiListClipperPtr = intPtr;
try
{
while (imGuiListClipperPtr.Step())
{
for (int i = imGuiListClipperPtr.DisplayStart; i < imGuiListClipperPtr.DisplayEnd; i++)
{
if (DrawFlatRow(_flatRows[i], x))
{
flag = true;
}
}
}
}
finally
{
imGuiListClipperPtr.Destroy();
}
if (flag)
{
RebuildFlatRows();
}
return;
}
UiThemeUtils.EmptyState(FontAwesomeIcon.Dungeon, "No duties match your search.");
}
private bool DrawFlatRow(FlatRow row, float statusIconSpacing)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
bool result = false;
if (row.Type == RowType.Duty)
{
float indentSpacing = ImGui.GetStyle().IndentSpacing;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + indentSpacing);
string text = $"Lv{row.Duty.Level} {row.Duty.Name}";
if (row.Duty.ItemLevel > 0)
{
text += $" (i{row.Duty.ItemLevel})";
}
ImGui.TreeNodeEx(text, ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.SpanFullWidth);
if (ImGui.IsItemHovered())
{
DrawDutyTooltip(row.Duty);
}
ImGui.TableNextColumn();
DrawDutyStatus(row.Duty.IsUnlocked, statusIconSpacing);
ImGui.TableNextColumn();
DrawDutyStatus(row.Duty.IsCompleted, statusIconSpacing);
}
else
{
bool flag = _expandedGroups.Contains(row.Label);
UiThemeUtils.DrawTrackerGroupCaret(_pluginInterface.UiBuilder.IconFontFixedWidthHandle, flag);
ImGui.SameLine();
ImU8String label = new ImU8String(5, 2);
label.AppendFormatted(row.Label);
label.AppendLiteral("##grp");
label.AppendFormatted(row.GroupIndex);
if (ImGui.Selectable(label))
{
if (flag)
{
_expandedGroups.Remove(row.Label);
}
else
{
_expandedGroups.Add(row.Label);
}
result = true;
}
ImGui.TableNextColumn();
UiThemeUtils.DrawTrackerCount(row.Counts.FormattedUnlocked, row.Counts.IsUnlockedComplete, row.Counts.Total == 0);
ImGui.TableNextColumn();
UiThemeUtils.DrawTrackerCount(row.Counts.FormattedCompleted, row.Counts.IsCompletedComplete, row.Counts.CompletionTrackable == 0);
}
return result;
}
private void DrawFilterControls()
{
float x = ImGui.GetStyle().ItemSpacing.X;
float num = 2f * ImGui.GetFrameHeight() + ImGui.CalcTextSize("Locked Only").X + ImGui.CalcTextSize("Unlocked Only").X + 5f * x;
if (UiThemeUtils.SearchInput("##DutySearch", ref _searchText, 0f - num, "Search duties..."))
{
UpdateFilter();
}
ImGui.SameLine();
if (UiThemeUtils.WrappedCheckbox("Locked Only", ref _showLockedOnly))
{
if (_showLockedOnly)
{
_showUnlockedOnly = false;
}
UpdateFilter();
}
ImGui.SameLine();
if (UiThemeUtils.WrappedCheckbox("Unlocked Only", ref _showUnlockedOnly))
{
if (_showUnlockedOnly)
{
_showLockedOnly = false;
}
UpdateFilter();
}
}
private void DrawDutyTooltip(DutyInfo duty)
{
using ImRaii.TooltipDisposable tooltipDisposable = ImRaii.Tooltip();
if (!tooltipDisposable.Alive)
{
return;
}
ImGui.TextColored(in UiThemeUtils.PrimaryTextColor, duty.Name);
ImU8String text = new ImU8String(12, 1);
text.AppendLiteral("Content ID: ");
text.AppendFormatted(duty.ContentFinderConditionId);
ImGui.TextColored(in UiThemeUtils.DimTextColor, text);
ImGui.Separator();
ImU8String text2 = new ImU8String(16, 1);
text2.AppendLiteral("Level Required: ");
text2.AppendFormatted(duty.Level);
ImGui.Text(text2);
if (duty.ItemLevel > 0)
{
ImU8String text3 = new ImU8String(21, 1);
text3.AppendLiteral("Item Level Required: ");
text3.AppendFormatted(duty.ItemLevel);
ImGui.Text(text3);
}
ImU8String text4 = new ImU8String(6, 1);
text4.AppendLiteral("Type: ");
text4.AppendFormatted(duty.ContentTypeName);
ImGui.Text(text4);
if (duty.IsHighEndDuty)
{
ImGui.SameLine();
ImGui.TextColored(in UiThemeUtils.StatusActive, "(High-End)");
}
ImGui.Separator();
if (duty.IsUnlocked)
{
ImGui.TextColored(in UiThemeUtils.StatusComplete, "Status: Unlocked");
}
else
{
ImGui.TextColored(in UiThemeUtils.StatusLocked, "Status: Locked");
}
if (duty.UnlockQuests.Count > 0)
{
ImGui.Separator();
ImGui.Text("Unlock Quest(s):");
{
foreach (QuestId unlockQuest in duty.UnlockQuests)
{
if (_questData.TryGetQuestInfo(unlockQuest, out IQuestInfo questInfo))
{
var (color, icon, _) = _uiUtils.GetQuestStyle(unlockQuest);
_uiUtils.ChecklistItem($"{questInfo.Name} ({unlockQuest})", color, icon);
}
else
{
_uiUtils.ChecklistItem($"Unknown Quest ({unlockQuest})", UiThemeUtils.StatusUnobtainable, FontAwesomeIcon.Question);
}
}
return;
}
}
ImGui.Separator();
ImGui.TextColored(in UiThemeUtils.DimTextColor, "No unlock quest data available.");
}
private void DrawDutyStatus(bool? status, float statusIconSpacing)
{
float num = ImGui.GetColumnWidth() / 2f - statusIconSpacing;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num);
if (!status.HasValue)
{
_uiUtils.ChecklistItem(string.Empty, UiThemeUtils.StatusUnobtainable, FontAwesomeIcon.Minus);
}
else if (status.Value)
{
_uiUtils.ChecklistItem(string.Empty, UiThemeUtils.StatusComplete, FontAwesomeIcon.Check);
}
else
{
_uiUtils.ChecklistItem(string.Empty, UiThemeUtils.StatusLocked, FontAwesomeIcon.Times);
}
}
private void RebuildFlatRows()
{
List<FlatRow> list = new List<FlatRow>();
foreach (DutyCategory filteredCategory in _filteredCategories)
{
DutyCounts valueOrDefault = _categoryCounts.GetValueOrDefault(filteredCategory.Name, new DutyCounts());
if (valueOrDefault.Total == 0)
{
continue;
}
list.Add(new FlatRow(RowType.Category, 0, null, filteredCategory.Name, valueOrDefault));
if (!_expandedGroups.Contains(filteredCategory.Name))
{
continue;
}
foreach (DutyInfo duty in filteredCategory.Duties)
{
list.Add(new FlatRow(RowType.Duty, 0, duty, string.Empty, new DutyCounts()));
}
}
_flatRows = list;
}
public void UpdateFilter()
{
List<DutyCategory> list = new List<DutyCategory>();
AddCategory(list, "Dungeons", _dutyUnlockData.GetDungeons());
AddCategory(list, "Hard Dungeons", _dutyUnlockData.GetHardDungeons());
AddCategory(list, "Guildhests", _dutyUnlockData.GetGuildhests());
AddCategory(list, "Trials", _dutyUnlockData.GetTrials());
AddCategory(list, "Hard Trials", _dutyUnlockData.GetHardTrials());
AddCategory(list, "Extreme Trials", _dutyUnlockData.GetExtremeTrials());
AddCategory(list, "Unreal Trials", _dutyUnlockData.GetUnrealTrials());
AddCategory(list, "Normal Raids", _dutyUnlockData.GetNormalRaids());
AddCategory(list, "Savage Raids", _dutyUnlockData.GetSavageRaids());
AddCategory(list, "Alliance Raids", _dutyUnlockData.GetAllianceRaids());
AddCategory(list, "Chaotic Alliance Raids", _dutyUnlockData.GetChaoticAllianceRaids());
AddCategory(list, "Ultimate Raids", _dutyUnlockData.GetUltimateRaids());
AddCategory(list, "Deep Dungeons", _dutyUnlockData.GetDeepDungeons());
AddCategory(list, "Variant Dungeons", _dutyUnlockData.GetVariantDungeons());
AddCategory(list, "Criterion Dungeons", _dutyUnlockData.GetCriterionDungeons());
AddCategory(list, "Criterion Savage", _dutyUnlockData.GetCriterionSavageDungeons());
_filteredCategories = list.Where((DutyCategory c) => c.Duties.Count > 0).ToList();
RefreshCounts();
RebuildFlatRows();
}
private void AddCategory(List<DutyCategory> categories, string name, IEnumerable<DutyInfo> duties)
{
List<DutyInfo> list = (from d in duties
where MatchesFilter(d)
orderby d.Level, d.ItemLevel, d.ContentFinderConditionId
select d).ToList();
if (list.Count > 0)
{
categories.Add(new DutyCategory(name, list));
}
}
private bool MatchesFilter(DutyInfo duty)
{
if (!string.IsNullOrEmpty(_searchText) && !duty.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (_showLockedOnly && duty.IsUnlocked)
{
return false;
}
if (_showUnlockedOnly && !duty.IsUnlocked)
{
return false;
}
return true;
}
public void RefreshCounts()
{
_categoryCounts.Clear();
foreach (DutyCategory filteredCategory in _filteredCategories)
{
int unlocked = filteredCategory.Duties.Count((DutyInfo d) => d.IsUnlocked);
int completed = filteredCategory.Duties.Count((DutyInfo d) => d.IsCompleted == true);
int completionTrackable = filteredCategory.Duties.Count((DutyInfo d) => d.IsCompleted.HasValue);
int count = filteredCategory.Duties.Count;
_categoryCounts[filteredCategory.Name] = new DutyCounts(unlocked, completed, completionTrackable, count);
}
RebuildFlatRows();
}
public void ClearCounts(int type, int code)
{
foreach (string item in _categoryCounts.Keys.ToList())
{
_categoryCounts[item] = new DutyCounts(0, 0, 0, _categoryCounts[item].Total);
}
RebuildFlatRows();
}
}