From e0e69e9b6aad1dd7f023034ba4506d5539f85fbf Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sat, 30 Mar 2024 19:29:43 +0100 Subject: [PATCH] Add sub parts to internal blacklist, add config for additional blacklisted items --- ARDiscard/ARDiscard.csproj | 2 +- ARDiscard/AutoDiscardPlogon.cs | 12 +- ARDiscard/Configuration.cs | 1 + ARDiscard/ContextMenuIntegration.cs | 8 +- ARDiscard/GameData/IListManager.cs | 12 ++ ARDiscard/GameData/InventoryUtils.cs | 30 +-- ARDiscard/GameData/ItemCache.cs | 21 +-- ...nternalConfiguration.cs => ListManager.cs} | 47 ++++- ARDiscard/Windows/ConfigWindow.cs | 176 ++++-------------- ARDiscard/Windows/DiscardListTab.cs | 55 ++++++ ARDiscard/Windows/ExcludedListTab.cs | 35 ++++ ARDiscard/Windows/ItemListTab.cs | 165 ++++++++++++++++ 12 files changed, 377 insertions(+), 187 deletions(-) create mode 100644 ARDiscard/GameData/IListManager.cs rename ARDiscard/GameData/{InternalConfiguration.cs => ListManager.cs} (86%) create mode 100644 ARDiscard/Windows/DiscardListTab.cs create mode 100644 ARDiscard/Windows/ExcludedListTab.cs create mode 100644 ARDiscard/Windows/ItemListTab.cs diff --git a/ARDiscard/ARDiscard.csproj b/ARDiscard/ARDiscard.csproj index 5fc2a3d..3a00574 100644 --- a/ARDiscard/ARDiscard.csproj +++ b/ARDiscard/ARDiscard.csproj @@ -1,7 +1,7 @@ net8.0-windows - 5.0 + 5.1 12 enable true diff --git a/ARDiscard/AutoDiscardPlogon.cs b/ARDiscard/AutoDiscardPlogon.cs index f17f476..2620975 100644 --- a/ARDiscard/AutoDiscardPlogon.cs +++ b/ARDiscard/AutoDiscardPlogon.cs @@ -50,7 +50,6 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin IGameGui gameGui, ITextureProvider textureProvider, IContextMenu contextMenu) { ArgumentNullException.ThrowIfNull(dataManager); - ItemCache itemCache = new ItemCache(dataManager); _pluginInterface = pluginInterface; _configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration(); @@ -72,7 +71,12 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin { HelpMessage = "Show what will be discarded with your current configuration", }); - _inventoryUtils = new InventoryUtils(_configuration, itemCache, _pluginLog); + + ListManager listManager = new ListManager(_configuration); + ItemCache itemCache = new ItemCache(dataManager, listManager); + _inventoryUtils = new InventoryUtils(_configuration, itemCache, listManager, _pluginLog); + listManager.FinishInitialization(); + _iconCache = new IconCache(textureProvider); _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; @@ -81,7 +85,7 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin _discardWindow = new(_inventoryUtils, itemCache, _iconCache, clientState, condition, _configuration); _windowSystem.AddWindow(_discardWindow); - _configWindow = new(_pluginInterface, _configuration, itemCache, clientState, condition); + _configWindow = new(_pluginInterface, _configuration, itemCache, listManager, clientState, condition); _windowSystem.AddWindow(_configWindow); _configWindow.DiscardNowClicked += (_, _) => OpenDiscardWindow(string.Empty, string.Empty); @@ -93,7 +97,7 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin ECommonsMain.Init(_pluginInterface, this); _autoRetainerApi = new(); _taskManager = new(); - _contextMenuIntegration = new(_chatGui, itemCache, _configuration, _configWindow, _gameGui, contextMenu); + _contextMenuIntegration = new(_chatGui, itemCache, _configuration, listManager, _configWindow, _gameGui, contextMenu); _autoDiscardIpc = new(_pluginInterface, _configuration); _clientState.Login += _discardWindow.Login; diff --git a/ARDiscard/Configuration.cs b/ARDiscard/Configuration.cs index 20358ed..ab77923 100644 --- a/ARDiscard/Configuration.cs +++ b/ARDiscard/Configuration.cs @@ -9,6 +9,7 @@ internal sealed class Configuration : IPluginConfiguration public bool RunAfterVenture { get; set; } public bool RunBeforeLogout { get; set; } public List DiscardingItems { get; set; } = new(); + public List BlacklistedItems { get; set; } = new(); public List ExcludedCharacters { get; set; } = new(); public ArmouryConfiguration Armoury { get; set; } = new(); diff --git a/ARDiscard/ContextMenuIntegration.cs b/ARDiscard/ContextMenuIntegration.cs index 2eee57e..cb3b373 100644 --- a/ARDiscard/ContextMenuIntegration.cs +++ b/ARDiscard/ContextMenuIntegration.cs @@ -14,6 +14,7 @@ internal sealed class ContextMenuIntegration : IDisposable private readonly IChatGui _chatGui; private readonly ItemCache _itemCache; private readonly Configuration _configuration; + private readonly IListManager _listManager; private readonly ConfigWindow _configWindow; private readonly IGameGui _gameGui; private readonly IContextMenu _contextMenu; @@ -21,11 +22,12 @@ internal sealed class ContextMenuIntegration : IDisposable private readonly MenuItem _removeInventoryItem; public ContextMenuIntegration(IChatGui chatGui, ItemCache itemCache, Configuration configuration, - ConfigWindow configWindow, IGameGui gameGui, IContextMenu contextMenu) + IListManager listManager, ConfigWindow configWindow, IGameGui gameGui, IContextMenu contextMenu) { _chatGui = chatGui; _itemCache = itemCache; _configuration = configuration; + _listManager = listManager; _configWindow = configWindow; _gameGui = gameGui; _contextMenu = contextMenu; @@ -65,7 +67,7 @@ internal sealed class ContextMenuIntegration : IDisposable if (_configuration.DiscardingItems.Contains(item.ItemId)) args.AddMenuItem(_removeInventoryItem); else if (_itemCache.TryGetItem(item.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) && - cachedItemInfo.CanBeDiscarded()) + cachedItemInfo.CanBeDiscarded(_listManager)) args.AddMenuItem(_addInventoryItem); } else @@ -91,7 +93,7 @@ internal sealed class ContextMenuIntegration : IDisposable }); } else if (_itemCache.TryGetItem(itemId, out ItemCache.CachedItemInfo? cachedItemInfo) && - cachedItemInfo.CanBeDiscarded()) + cachedItemInfo.CanBeDiscarded(_listManager)) { args.AddMenuItem(new MenuItem { diff --git a/ARDiscard/GameData/IListManager.cs b/ARDiscard/GameData/IListManager.cs new file mode 100644 index 0000000..d01ee55 --- /dev/null +++ b/ARDiscard/GameData/IListManager.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace ARDiscard.GameData; + +internal interface IListManager +{ + bool IsBlacklisted(uint itemId, bool checkConfiguration = true); + + IReadOnlyList GetInternalBlacklist(); + + bool IsWhitelisted(uint itemId); +} diff --git a/ARDiscard/GameData/InventoryUtils.cs b/ARDiscard/GameData/InventoryUtils.cs index 0bcc82d..263ec25 100644 --- a/ARDiscard/GameData/InventoryUtils.cs +++ b/ARDiscard/GameData/InventoryUtils.cs @@ -12,46 +12,48 @@ namespace ARDiscard.GameData; internal sealed class InventoryUtils { private static readonly InventoryType[] DefaultInventoryTypes = - { + [ InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 - }; + ]; private static readonly InventoryType[] MainHandOffHandInventoryTypes = - { + [ InventoryType.ArmoryMainHand, - InventoryType.ArmoryOffHand, - }; + InventoryType.ArmoryOffHand + ]; private static readonly InventoryType[] LeftSideGearInventoryTypes = - { + [ InventoryType.ArmoryHead, InventoryType.ArmoryBody, InventoryType.ArmoryHands, InventoryType.ArmoryLegs, InventoryType.ArmoryFeets - }; + ]; private static readonly InventoryType[] RightSideGearInventoryTypes = - { + [ InventoryType.ArmoryEar, InventoryType.ArmoryNeck, InventoryType.ArmoryWrist, InventoryType.ArmoryRings - }; + ]; private static readonly IReadOnlyList NoGearsetItems = new List(); private readonly Configuration _configuration; private readonly ItemCache _itemCache; + private readonly IListManager _listManager; private readonly IPluginLog _pluginLog; - public InventoryUtils(Configuration configuration, ItemCache itemCache, IPluginLog pluginLog) + public InventoryUtils(Configuration configuration, ItemCache itemCache, IListManager listManager, IPluginLog pluginLog) { _configuration = configuration; _itemCache = itemCache; + _listManager = listManager; _pluginLog = pluginLog; } @@ -118,12 +120,11 @@ internal sealed class InventoryUtils else itemCounts[item->ItemID] = item->Quantity; - if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID) || - InternalConfiguration.UltimateWeapons.Contains(item->ItemID)) + if (_listManager.IsBlacklisted(item->ItemID)) continue; if (!_itemCache.TryGetItem(item->ItemID, out ItemCache.CachedItemInfo? itemInfo) || - !itemInfo.CanBeDiscarded()) + !itemInfo.CanBeDiscarded(_listManager)) continue; // no info, who knows what that item is // skip gear if we're unable to load gearsets or it is used in a gearset @@ -194,8 +195,7 @@ internal sealed class InventoryUtils public unsafe void Discard(InventoryItem* item) { - if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID) || - InternalConfiguration.UltimateWeapons.Contains(item->ItemID)) + if (_listManager.IsBlacklisted(item->ItemID)) throw new ArgumentException($"Can't discard {item->ItemID}", nameof(item)); AgentInventoryContext.Instance()->DiscardItem(item, item->Container, item->Slot, 0); diff --git a/ARDiscard/GameData/ItemCache.cs b/ARDiscard/GameData/ItemCache.cs index 01bcc83..49f6004 100644 --- a/ARDiscard/GameData/ItemCache.cs +++ b/ARDiscard/GameData/ItemCache.cs @@ -11,7 +11,7 @@ internal sealed class ItemCache { private readonly Dictionary _items = new(); - public ItemCache(IDataManager dataManager) + public ItemCache(IDataManager dataManager, ListManager listManager) { foreach (var item in dataManager.GetExcelSheet()!) { @@ -35,9 +35,10 @@ internal sealed class ItemCache }; if (item is { Rarity: 3, MateriaSlotCount: 3, RowId: < 33154 or > 33358 }) - { - InternalConfiguration.UltimateWeapons.Add(item.RowId); - } + listManager.AddToInternalBlacklist(item.RowId); + + if (item is { ItemSearchCategory.Row: 79, ItemUICategory.Row: >= 101 and <= 104 }) + listManager.AddToInternalBlacklist(item.RowId); } foreach (var shopItem in dataManager.GetExcelSheet()!) @@ -48,7 +49,7 @@ internal sealed class ItemCache // the item can be discarded already if (!_items.TryGetValue(shopItem.Item.Row, out CachedItemInfo? cachedItemInfo) || - cachedItemInfo.CanBeDiscarded()) + cachedItemInfo.CanBeDiscarded(listManager)) continue; if (shopItem.AchievementRequired.Row != 0) @@ -68,7 +69,7 @@ internal sealed class ItemCache { var item = dataManager.GetExcelSheet()!.GetRow(itemId); if (item is { Rarity: 1, ItemAction.Row: 388 } && item.RowId != 38809 && item.RowId != 29679) - InternalConfiguration.DiscardableGearCoffers.Add(item.RowId); + listManager.AddToInternalWhitelist(item.RowId); } } } @@ -129,17 +130,15 @@ internal sealed class ItemCache public required string UiCategoryName { get; init; } public required uint EquipSlotCategory { get; init; } - public bool CanBeDiscarded() + public bool CanBeDiscarded(IListManager listManager, bool checkConfiguration = true) { - if (InternalConfiguration.BlacklistedItems.Contains(ItemId) || - InternalConfiguration.UltimateWeapons.Contains(ItemId)) + if (listManager.IsBlacklisted(ItemId, checkConfiguration)) return false; if (UiCategory is UiCategories.Currency or UiCategories.Crystals or UiCategories.Unobtainable) return false; - if (InternalConfiguration.WhitelistedItems.Contains(ItemId) || - InternalConfiguration.DiscardableGearCoffers.Contains(ItemId)) + if (listManager.IsWhitelisted(ItemId)) return true; return CanBeBoughtFromCalamitySalvager || diff --git a/ARDiscard/GameData/InternalConfiguration.cs b/ARDiscard/GameData/ListManager.cs similarity index 86% rename from ARDiscard/GameData/InternalConfiguration.cs rename to ARDiscard/GameData/ListManager.cs index 941d690..6c09209 100644 --- a/ARDiscard/GameData/InternalConfiguration.cs +++ b/ARDiscard/GameData/ListManager.cs @@ -1,20 +1,22 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace ARDiscard.GameData; -internal static class InternalConfiguration +internal sealed class ListManager : IListManager { /// /// Not all of these *can* be discarded, but we shouldn't attempt it either. /// - public static readonly IReadOnlyList BlacklistedItems = new List + private ISet _blacklistedItems = new List { 2820, // red onion helm 16039, // ala mhigan earrings 24589, // aetheryte earrings 33648, // menphina's earrings + 41081, // azeyma's earrings 10155, // Ceruleum Tank 10373, // Magitek Repair Materials @@ -26,16 +28,13 @@ internal static class InternalConfiguration 38951, // TOP token } .Concat(Enumerable.Range(1, 99).Select(x => (uint)x)) - .ToList() - .AsReadOnly(); - - public static readonly IList UltimateWeapons = new List(); + .ToHashSet(); /// /// Items that are unique/untradeable, but should still be possible to discard. This is moreso because /// 99% of the unique/untradeable items should NOT be selectable for discard, but these are OK. /// - public static readonly IReadOnlyList WhitelistedItems = new List + private ISet _whitelistedItems = new HashSet() { 2962, // Onion Doublet 3279, // Onion Gaskins @@ -268,7 +267,37 @@ internal static class InternalConfiguration 32048, // Umbral Levinsand #endregion - }.AsReadOnly(); + }; - public static readonly IList DiscardableGearCoffers = new List(); + private readonly Configuration _configuration; + + public ListManager(Configuration configuration) + { + _configuration = configuration; + } + + public void FinishInitialization() + { + _blacklistedItems = _blacklistedItems.ToImmutableHashSet(); + _whitelistedItems = _whitelistedItems.ToImmutableHashSet(); + } + + public bool IsBlacklisted(uint itemId, bool checkConfiguration = true) + { + if (_blacklistedItems.Contains(itemId)) + return true; + + if (checkConfiguration && _configuration.BlacklistedItems.Contains(itemId)) + return true; + + return false; + } + + public IReadOnlyList GetInternalBlacklist() => _blacklistedItems.ToList().AsReadOnly(); + + public void AddToInternalBlacklist(uint itemId) => _blacklistedItems.Add(itemId); + + public bool IsWhitelisted(uint itemId) => _whitelistedItems.Contains(itemId); + + public void AddToInternalWhitelist(uint itemId) => _whitelistedItems.Add(itemId); } diff --git a/ARDiscard/Windows/ConfigWindow.cs b/ARDiscard/Windows/ConfigWindow.cs index df86d1d..53f72b1 100644 --- a/ARDiscard/Windows/ConfigWindow.cs +++ b/ARDiscard/Windows/ConfigWindow.cs @@ -8,7 +8,6 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Dalamud.Plugin.Services; -using ECommons; using ImGuiNET; using LLib.ImGui; @@ -16,30 +15,28 @@ namespace ARDiscard.Windows; internal sealed class ConfigWindow : LWindow { - private const int ResultLimit = 200; - private readonly DalamudPluginInterface _pluginInterface; private readonly Configuration _configuration; private readonly ItemCache _itemCache; + private readonly IListManager _listManager; private readonly IClientState _clientState; private readonly ICondition _condition; - private string _itemName = string.Empty; + private readonly DiscardListTab _discardListTab; + private readonly ExcludedListTab _excludedListTab; - private List<(uint ItemId, string Name)> _searchResults = new(); - private List<(uint ItemId, string Name)> _discarding = new(); private List<(uint ItemId, string Name)>? _allItems; - private bool _resetKeyboardFocus = true; public event EventHandler? DiscardNowClicked; public event EventHandler? ConfigSaved; public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache, - IClientState clientState, ICondition condition) + IListManager listManager, IClientState clientState, ICondition condition) : base("Auto Discard###AutoDiscardConfig") { _pluginInterface = pluginInterface; _configuration = configuration; _itemCache = itemCache; + _listManager = listManager; _clientState = clientState; _condition = condition; @@ -52,8 +49,11 @@ internal sealed class ConfigWindow : LWindow MaximumSize = new Vector2(9999, 9999), }; - _discarding.AddRange(_configuration.DiscardingItems - .Select(x => (x, itemCache.GetItemName(x))).ToList()); + _excludedListTab = new ExcludedListTab(this, itemCache, _configuration.BlacklistedItems, listManager); + _discardListTab = new DiscardListTab(this, itemCache, _configuration.DiscardingItems) + { + ExcludedTab = _excludedListTab, + }; } public override void Draw() @@ -84,6 +84,7 @@ internal sealed class ConfigWindow : LWindow { DrawDiscardList(); DrawExcludedCharacters(); + DrawExcludedItems(); DrawExperimentalSettings(); ImGui.EndTabBar(); @@ -94,100 +95,7 @@ internal sealed class ConfigWindow : LWindow { if (ImGui.BeginTabItem("Items to Discard")) { - var ws = ImGui.GetWindowSize(); - if (ImGui.BeginChild("Left", new Vector2(Math.Max(10, ws.X / 2), -1), true)) - { - if (!string.IsNullOrEmpty(_itemName)) - { - if (_searchResults.Count > ResultLimit) - ImGui.Text($"Search ({ResultLimit:N0} out of {_searchResults.Count:N0} matches)"); - else - ImGui.Text($"Search ({_searchResults.Count:N0} matches)"); - } - else - ImGui.Text("Search"); - - ImGui.SetNextItemWidth(ws.X / 2 - 20); - if (_resetKeyboardFocus) - { - ImGui.SetKeyboardFocusHere(); - _resetKeyboardFocus = false; - } - - string previousName = _itemName; - if (ImGui.InputText("", ref _itemName, 256, ImGuiInputTextFlags.EnterReturnsTrue)) - { - _resetKeyboardFocus = true; - if (_searchResults.Count > 0) - { - var itemToAdd = _searchResults.FirstOrDefault(); - if (_discarding.All(x => x.ItemId != itemToAdd.ItemId)) - { - _discarding.Add(itemToAdd); - } - else - { - _discarding.Remove(itemToAdd); - } - - Save(); - } - } - - if (previousName != _itemName) - UpdateResults(); - - ImGui.Separator(); - - if (string.IsNullOrEmpty(_itemName)) - { - ImGui.Text("Type item name..."); - } - - foreach (var (id, name) in _searchResults.Take(ResultLimit)) - { - bool selected = _discarding.Any(x => x.Item1 == id); - if (ImGui.Selectable(name, selected)) - { - if (!selected) - { - _discarding.Add((id, name)); - } - else - { - _discarding.Remove((id, name)); - } - - Save(); - } - } - } - - ImGui.EndChild(); - ImGui.SameLine(); - - if (ImGui.BeginChild("Right", new Vector2(-1, -1), true, ImGuiWindowFlags.NoSavedSettings)) - { - ImGui.Text("Items that will be automatically discarded"); - ImGui.Separator(); - - List<(uint, string)> toRemove = new(); - foreach (var (id, name) in _discarding.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)) - { - if (ImGui.Selectable(name, true)) - toRemove.Add((id, name)); - } - - if (toRemove.Count > 0) - { - foreach (var tr in toRemove) - _discarding.Remove(tr); - - Save(); - } - } - - ImGui.EndChild(); + _discardListTab.Draw(); ImGui.EndTabItem(); } } @@ -269,6 +177,19 @@ internal sealed class ConfigWindow : LWindow } } + private void DrawExcludedItems() + { + if (ImGui.BeginTabItem("Excluded Items")) + { + ImGui.Text( + "Items configured here will never be discarded, and have priority over the 'Items to Discard' tab."); + ImGui.Text("Some items (such as Ultimate weapons) can not be un-blacklisted."); + + _excludedListTab.Draw(); + ImGui.EndTabItem(); + } + } + private void DrawExperimentalSettings() { if (ImGui.BeginTabItem("Experimental Settings")) @@ -378,26 +299,12 @@ internal sealed class ConfigWindow : LWindow } } - private void UpdateResults() - { - if (string.IsNullOrEmpty(_itemName)) - _searchResults = new(); - else - { - _searchResults = EnsureAllItemsLoaded().Where(x => - x.Name.Contains(_itemName, StringComparison.CurrentCultureIgnoreCase) - || (uint.TryParse(_itemName, out uint itemId) && x.ItemId == itemId)) - .OrderBy(x => _itemName.EqualsIgnoreCase(x.Name) ? string.Empty : x.Name) - .ToList(); - } - } - - private List<(uint ItemId, string Name)> EnsureAllItemsLoaded() + internal List<(uint ItemId, string Name)> EnsureAllItemsLoaded() { if (_allItems == null) { _allItems = _itemCache.AllItems - .Where(x => x.CanBeDiscarded()) + .Where(x => x.CanBeDiscarded(_listManager, false)) .Select(x => (x.ItemId, x.Name.ToString())) .ToList(); } @@ -405,37 +312,18 @@ internal sealed class ConfigWindow : LWindow return _allItems; } - private void Save() + internal void Save() { - _configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList(); + _configuration.DiscardingItems = _discardListTab.ToSavedItems().ToList(); + _configuration.BlacklistedItems = _excludedListTab.ToSavedItems().ToList(); _pluginInterface.SavePluginConfig(_configuration); ConfigSaved?.Invoke(this, EventArgs.Empty); } - internal bool AddToDiscardList(uint itemId) - { - var item = EnsureAllItemsLoaded().SingleOrDefault(x => x.ItemId == itemId); - if (item.ItemId != 0) - { - _discarding.Add(item); - Save(); - return true; - } + internal bool AddToDiscardList(uint itemId) => _discardListTab.AddToDiscardList(itemId); - return false; - } - - internal bool RemoveFromDiscardList(uint itemId) - { - if (_discarding.RemoveAll(x => x.ItemId == itemId) > 0) - { - Save(); - return true; - } - - return false; - } + internal bool RemoveFromDiscardList(uint itemId) => _discardListTab.RemoveFromDiscardList(itemId); public bool CanItemBeConfigured(uint itemId) { diff --git a/ARDiscard/Windows/DiscardListTab.cs b/ARDiscard/Windows/DiscardListTab.cs new file mode 100644 index 0000000..27f32b8 --- /dev/null +++ b/ARDiscard/Windows/DiscardListTab.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using ARDiscard.GameData; + +namespace ARDiscard.Windows +{ + internal sealed class DiscardListTab : ItemListTab + { + public DiscardListTab(ConfigWindow parent, ItemCache itemCache, List initialItems) + : base(parent, itemCache, initialItems) + { + } + + protected override string RightSideLabel => "Items that will be automatically discarded"; + internal required ExcludedListTab ExcludedTab { private get; init; } + + public override IEnumerable ToSavedItems() + { + SelectedItems.RemoveAll(x => ExcludedTab.IsBlacklistedInConfiguration(x.ItemId)); + return SelectedItems.Select(x => x.ItemId); + } + + protected override (string, bool) AsLeftSideDisplay(uint itemId, string name) + { + if (ExcludedTab.IsBlacklistedInConfiguration(itemId)) + return ($"{name} (Blacklisted/Excluded Item)", false); + + return base.AsLeftSideDisplay(itemId, name); + } + + internal bool AddToDiscardList(uint itemId) + { + var item = EnsureAllItemsLoaded().SingleOrDefault(x => x.ItemId == itemId); + if (item.ItemId != 0) + { + SelectedItems.Add(item); + Save(); + return true; + } + + return false; + } + + internal bool RemoveFromDiscardList(uint itemId) + { + if (SelectedItems.RemoveAll(x => x.ItemId == itemId) > 0) + { + Save(); + return true; + } + + return false; + } + } +} diff --git a/ARDiscard/Windows/ExcludedListTab.cs b/ARDiscard/Windows/ExcludedListTab.cs new file mode 100644 index 0000000..92abdde --- /dev/null +++ b/ARDiscard/Windows/ExcludedListTab.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using ARDiscard.GameData; + +namespace ARDiscard.Windows; + +internal sealed class ExcludedListTab : ItemListTab +{ + private readonly IListManager _listManager; + + public ExcludedListTab(ConfigWindow parent, ItemCache itemCache, List initialItems, IListManager listManager) + : base(parent, itemCache, initialItems) + { + _listManager = listManager; + SelectedItems.AddRange( + listManager.GetInternalBlacklist() + .Select(x => (x, itemCache.GetItemName(x))) + .Where(x => x.Item1 >= 100 && !string.IsNullOrEmpty(x.Item2))); + } + + protected override string RightSideLabel => "Items that will never be discarded"; + + public override IEnumerable ToSavedItems() + { + return SelectedItems + .Select(x => x.ItemId) + .Where(x => !_listManager.IsBlacklisted(x, false)); + } + + public bool IsBlacklistedInConfiguration(uint itemId) + => !_listManager.IsBlacklisted(itemId, false) && SelectedItems.Any(x => x.ItemId == itemId); + + protected override (string Name, bool Enabled) AsLeftSideDisplay(uint itemId, string name) + => (name, !_listManager.IsBlacklisted(itemId, false)); +} diff --git a/ARDiscard/Windows/ItemListTab.cs b/ARDiscard/Windows/ItemListTab.cs new file mode 100644 index 0000000..c08913f --- /dev/null +++ b/ARDiscard/Windows/ItemListTab.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using ARDiscard.GameData; +using ImGuiNET; + +namespace ARDiscard.Windows; + +internal abstract class ItemListTab +{ + private const int ResultLimit = 200; + + private readonly ConfigWindow _parent; + + private List<(uint ItemId, string Name)> _searchResults = new(); + private string _itemName = string.Empty; + private bool _resetKeyboardFocus = true; + + protected ItemListTab(ConfigWindow parent, ItemCache itemCache, List initialItems) + { + _parent = parent; + SelectedItems.AddRange(initialItems.Select(x => (x, itemCache.GetItemName(x))).ToList()); + } + + protected abstract string RightSideLabel { get; } + protected List<(uint ItemId, string Name)> SelectedItems { get; } = new(); + + public abstract IEnumerable ToSavedItems(); + + public void Draw() + { + var ws = ImGui.GetWindowSize(); + if (ImGui.BeginChild("Left", new Vector2(Math.Max(10, ws.X / 2), -1), true)) + { + if (!string.IsNullOrEmpty(_itemName)) + { + if (_searchResults.Count > ResultLimit) + ImGui.Text($"Search ({ResultLimit:N0} out of {_searchResults.Count:N0} matches)"); + else + ImGui.Text($"Search ({_searchResults.Count:N0} matches)"); + } + else + ImGui.Text("Search"); + + ImGui.SetNextItemWidth(ws.X / 2 - 20); + if (_resetKeyboardFocus) + { + ImGui.SetKeyboardFocusHere(); + _resetKeyboardFocus = false; + } + + string previousName = _itemName; + if (ImGui.InputText("", ref _itemName, 256, ImGuiInputTextFlags.EnterReturnsTrue)) + { + _resetKeyboardFocus = true; + if (_searchResults.Count > 0) + { + var itemToAdd = _searchResults.FirstOrDefault(); + if (SelectedItems.All(x => x.ItemId != itemToAdd.ItemId)) + { + SelectedItems.Add(itemToAdd); + } + else + { + SelectedItems.Remove(itemToAdd); + } + + Save(); + } + } + + if (previousName != _itemName) + UpdateResults(); + + ImGui.Separator(); + + if (string.IsNullOrEmpty(_itemName)) + { + ImGui.Text("Type item name..."); + } + + foreach (var (id, name) in _searchResults.Take(ResultLimit)) + { + bool selected = SelectedItems.Any(x => x.Item1 == id); + + var display = AsLeftSideDisplay(id, name); + if (!display.Enabled) + ImGui.BeginDisabled(); + + if (ImGui.Selectable($"{display.Name}##Item{id}", selected)) + { + if (!selected) + { + SelectedItems.Add((id, name)); + } + else + { + SelectedItems.Remove((id, name)); + } + + Save(); + } + + if (!display.Enabled) + ImGui.EndDisabled(); + } + } + + ImGui.EndChild(); + ImGui.SameLine(); + + if (ImGui.BeginChild("Right", new Vector2(-1, -1), true, ImGuiWindowFlags.NoSavedSettings)) + { + ImGui.Text(RightSideLabel); + ImGui.Separator(); + + List<(uint, string)> toRemove = new(); + foreach (var (id, name) in SelectedItems.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)) + { + var display = AsLeftSideDisplay(id, name); + if (!display.Enabled) + ImGui.BeginDisabled(); + + if (ImGui.Selectable($"{display.Name}##Item{id}", true)) + toRemove.Add((id, name)); + + if (!display.Enabled) + ImGui.EndDisabled(); + } + + if (toRemove.Count > 0) + { + foreach (var tr in toRemove) + SelectedItems.Remove(tr); + + Save(); + } + } + + ImGui.EndChild(); + } + + protected virtual (string Name, bool Enabled) AsLeftSideDisplay(uint itemId, string name) => (name, true); + + protected virtual (string Name, bool Enabled) AsRightSideDisplay(uint itemId, string name) => (name, true); + + protected void Save() => _parent.Save(); + + private void UpdateResults() + { + if (string.IsNullOrEmpty(_itemName)) + _searchResults = new(); + else + { + _searchResults = EnsureAllItemsLoaded().Where(x => + x.Name.Contains(_itemName, StringComparison.CurrentCultureIgnoreCase) + || (uint.TryParse(_itemName, out uint itemId) && x.ItemId == itemId)) + .OrderBy(x => _itemName.Equals(x.Name, StringComparison.OrdinalIgnoreCase) ? string.Empty : x.Name) + .ToList(); + } + } + + protected List<(uint ItemId, string Name)> EnsureAllItemsLoaded() => _parent.EnsureAllItemsLoaded(); +}