using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin; using Questionable.Controller; using Questionable.Data; using Questionable.Model; using Questionable.Windows.QuestComponents; namespace Questionable.Windows.JournalComponents; internal sealed class QuestRewardComponent { private enum RowType : byte { Group, Item } private readonly record struct FlatRow(RowType Type, int GroupIndex, ItemReward? Item); private sealed record ItemGroup(string Label, EItemRewardType Type, List Items); private readonly QuestRegistry _questRegistry; private readonly QuestData _questData; private readonly QuestTooltipComponent _questTooltipComponent; private readonly UiUtils _uiUtils; private readonly IDalamudPluginInterface _pluginInterface; private bool _showEventRewards; private string _searchText = string.Empty; private List _filteredGroups = new List(); private List _flatRows = new List(); private readonly HashSet _expandedGroups = new HashSet(); private bool _initialized; private static readonly int CountPadWidth = 9999.ToString(CultureInfo.CurrentCulture).Length; public QuestRewardComponent(QuestRegistry questRegistry, QuestData questData, QuestTooltipComponent questTooltipComponent, UiUtils uiUtils, IDalamudPluginInterface pluginInterface) { _questRegistry = questRegistry; _questData = questData; _questTooltipComponent = questTooltipComponent; _uiUtils = uiUtils; _pluginInterface = pluginInterface; } public unsafe void DrawItemRewards() { if (!_initialized) { UpdateFilter(); _initialized = true; } float x = ImGui.GetStyle().ItemSpacing.X; float num = ImGui.GetFrameHeight() + ImGui.CalcTextSize("Show seasonal event rewards").X + 3f * x; if (UiThemeUtils.SearchInput("##RewardSearch", ref _searchText, 0f - num, "Search items...")) { UpdateFilter(); } ImGui.SameLine(); if (UiThemeUtils.WrappedCheckbox("Show seasonal event rewards", ref _showEventRewards)) { UpdateFilter(); } ImGui.Spacing(); ImGui.TextColored(in UiThemeUtils.MutedTextColor, "Only untradeable items are listed (e.g. the Wind-up Airship can be sold on the market board)."); ImGui.Separator(); if (_flatRows.Count == 0) { UiThemeUtils.EmptyState(FontAwesomeIcon.Gift, "No items match your search."); return; } float x2; using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) { x2 = ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X; } float fontGlobalScale = ImGui.GetIO().FontGlobalScale; using ImRaii.TableDisposable tableDisposable = UiThemeUtils.BeginThemedTable("ItemRewards", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.ScrollY); if (!tableDisposable) { return; } ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("Obtained", 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], x2)) { flag = true; } } } } finally { imGuiListClipperPtr.Destroy(); } if (flag) { RebuildFlatRows(); } } private bool DrawFlatRow(FlatRow row, float statusIconSpacing) { ImGui.TableNextRow(); ImGui.TableNextColumn(); if (row.Type == RowType.Group) { ItemGroup itemGroup = _filteredGroups[row.GroupIndex]; bool flag = _expandedGroups.Contains(itemGroup.Label); UiThemeUtils.DrawTrackerGroupCaret(_pluginInterface.UiBuilder.IconFontFixedWidthHandle, flag); ImGui.SameLine(); bool result = false; ImU8String label = new ImU8String(5, 2); label.AppendFormatted(itemGroup.Label); label.AppendLiteral("##grp"); label.AppendFormatted(row.GroupIndex); if (ImGui.Selectable(label)) { if (flag) { _expandedGroups.Remove(itemGroup.Label); } else { _expandedGroups.Add(itemGroup.Label); } result = true; } int num = itemGroup.Items.Count((ItemReward x) => x.IsUnlocked()); int count = itemGroup.Items.Count; ImGui.TableNextColumn(); UiThemeUtils.DrawTrackerCount(FormatCount(num, count), num == count && count > 0, count == 0); return result; } ItemReward item = row.Item; float indentSpacing = ImGui.GetStyle().IndentSpacing; ImGui.SetCursorPosX(ImGui.GetCursorPosX() + indentSpacing); bool flag2 = item.IsUnlocked(); Vector4 color = ((!_questRegistry.IsKnownQuest(item.ElementId)) ? UiThemeUtils.StatusUnobtainable : (flag2 ? UiThemeUtils.StatusComplete : UiThemeUtils.StatusLocked)); IQuestInfo questInfo; bool flag3 = _questData.TryGetQuestInfo(item.ElementId, out questInfo); string text = item.Name; if (questInfo is QuestInfo { IsSeasonalEvent: not false }) { text = text + " " + SeIconChar.Clock.ToIconString(); } ImGui.TreeNodeEx(text, ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.SpanFullWidth); bool num2 = ImGui.IsItemHovered(); ImGui.TableNextColumn(); float num3 = ImGui.GetColumnWidth() / 2f - statusIconSpacing; ImGui.SetCursorPosX(ImGui.GetCursorPosX() + num3); FontAwesomeIcon icon = (flag2 ? FontAwesomeIcon.Check : FontAwesomeIcon.Times); if ((num2 | _uiUtils.ChecklistItem(string.Empty, color, icon)) && flag3) { using ImRaii.TooltipDisposable tooltipDisposable = ImRaii.Tooltip(); if (tooltipDisposable.Alive) { ImU8String text2 = new ImU8String(15, 1); text2.AppendLiteral("Obtained from: "); text2.AppendFormatted(questInfo.Name); ImGui.Text(text2); using (ImRaii.PushIndent()) { _questTooltipComponent.DrawInner(questInfo, showItemRewards: false); } } } return false; } private static string FormatCount(int n, int d) { if (d != 0) { return n.ToString(CultureInfo.CurrentCulture).PadLeft(CountPadWidth) + " / " + d.ToString(CultureInfo.CurrentCulture).PadLeft(CountPadWidth); } return "-".PadLeft(CountPadWidth) + " / " + "-".PadLeft(CountPadWidth); } private void RebuildFlatRows() { List list = new List(); for (int i = 0; i < _filteredGroups.Count; i++) { ItemGroup itemGroup = _filteredGroups[i]; list.Add(new FlatRow(RowType.Group, i, null)); if (!_expandedGroups.Contains(itemGroup.Label)) { continue; } foreach (ItemReward item in itemGroup.Items) { if (_questData.TryGetQuestInfo(item.ElementId, out IQuestInfo _)) { list.Add(new FlatRow(RowType.Item, i, item)); } } } _flatRows = list; } private void UpdateFilter() { List list = new List(); AddGroup(list, "Mounts", EItemRewardType.Mount); AddGroup(list, "Minions", EItemRewardType.Minion); AddGroup(list, "Orchestrion Rolls", EItemRewardType.OrchestrionRoll); AddGroup(list, "Triple Triad Cards", EItemRewardType.TripleTriadCard); AddGroup(list, "Fashion Accessories", EItemRewardType.FashionAccessory); AddGroup(list, "Unlock Links", EItemRewardType.UnlockLink); _filteredGroups = list; RebuildFlatRows(); } private void AddGroup(List groups, string label, EItemRewardType type) { List filteredItems = GetFilteredItems(type); if (filteredItems.Count > 0) { groups.Add(new ItemGroup(label, type, filteredItems)); } } private List GetFilteredItems(EItemRewardType type) { return (from x in _questData.RedeemableItems where x.Type == type where x.IsUntradable select x).Where(delegate(ItemReward x) { if (!_showEventRewards && _questData.TryGetQuestInfo(x.ElementId, out IQuestInfo questInfo) && questInfo is QuestInfo { IsSeasonalEvent: not false }) { return false; } return (string.IsNullOrEmpty(_searchText) || x.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase)) ? true : false; }).OrderBy((ItemReward x) => x.Name, StringComparer.CurrentCultureIgnoreCase).ToList(); } }