From ce37130390762ab1342c975ccddb76092a13653d Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Thu, 7 Dec 2023 16:38:00 +0100 Subject: [PATCH] Refactoring, allow unique/untradeable items that can be purchased after completing quests to be discarded --- ARDiscard/ARDiscard.csproj | 2 +- ARDiscard/ContextMenuIntegration.cs | 19 +------ .../GameData/Agents/AgentArmouryBoard.cs | 12 ---- ARDiscard/GameData/InternalConfiguration.cs | 3 + ARDiscard/GameData/InventoryUtils.cs | 28 ++++----- ARDiscard/GameData/ItemCache.cs | 57 +++++++++++++++++++ ARDiscard/Windows/ConfigWindow.cs | 6 +- 7 files changed, 79 insertions(+), 48 deletions(-) delete mode 100644 ARDiscard/GameData/Agents/AgentArmouryBoard.cs diff --git a/ARDiscard/ARDiscard.csproj b/ARDiscard/ARDiscard.csproj index 49d083a..9c0425a 100644 --- a/ARDiscard/ARDiscard.csproj +++ b/ARDiscard/ARDiscard.csproj @@ -1,7 +1,7 @@ net7.0-windows - 3.8 + 4.0 11.0 enable true diff --git a/ARDiscard/ContextMenuIntegration.cs b/ARDiscard/ContextMenuIntegration.cs index 6366981..4ab4ada 100644 --- a/ARDiscard/ContextMenuIntegration.cs +++ b/ARDiscard/ContextMenuIntegration.cs @@ -1,14 +1,11 @@ using System; -using System.Linq; using ARDiscard.GameData; -using ARDiscard.GameData.Agents; using ARDiscard.Windows; using Dalamud.ContextMenu; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Plugin; using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; namespace ARDiscard; @@ -52,18 +49,7 @@ internal sealed class ContextMenuIntegration : IDisposable if (_configuration.ContextMenu.OnlyWhenConfigIsOpen && !_configWindow.IsOpen) return; - if (args.ParentAddonName == "ArmouryBoard") - { - var agent = AgentModule.Instance()->GetAgentByInternalId(AgentId.ArmouryBoard); - if (agent == null || !agent->IsAgentActive()) - return; - - // don't add it in the main/off hand weapon tabs, as we don't use these for discarding - var agentArmouryBoard = (AgentArmouryBoard*)agent; - if (agentArmouryBoard->CurrentTab is 0 or 6) - return; - } - else if (!(args.ParentAddonName is "Inventory" or "InventoryExpansion" or "InventoryLarge")) + if (!(args.ParentAddonName is "Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard")) return; if (!_configWindow.CanItemBeConfigured(args.ItemId)) @@ -71,7 +57,8 @@ internal sealed class ContextMenuIntegration : IDisposable if (_configuration.DiscardingItems.Contains(args.ItemId)) args.AddCustomItem(_removeItem); - else if (!InternalConfiguration.BlacklistedItems.Contains(args.ItemId)) + else if (_itemCache.TryGetItem(args.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) && + cachedItemInfo.CanBeDiscarded()) args.AddCustomItem(_addItem); } diff --git a/ARDiscard/GameData/Agents/AgentArmouryBoard.cs b/ARDiscard/GameData/Agents/AgentArmouryBoard.cs deleted file mode 100644 index 396ff4e..0000000 --- a/ARDiscard/GameData/Agents/AgentArmouryBoard.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; -using FFXIVClientStructs.Attributes; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; - -namespace ARDiscard.GameData.Agents; - -[Agent(AgentId.ArmouryBoard)] -[StructLayout(LayoutKind.Explicit, Size = 0x2E)] -public struct AgentArmouryBoard -{ - [FieldOffset(0x2C)] public byte CurrentTab; -} diff --git a/ARDiscard/GameData/InternalConfiguration.cs b/ARDiscard/GameData/InternalConfiguration.cs index c36008f..74ecdd4 100644 --- a/ARDiscard/GameData/InternalConfiguration.cs +++ b/ARDiscard/GameData/InternalConfiguration.cs @@ -42,6 +42,9 @@ internal static class InternalConfiguration 9390, // Antique Breeches 9391, // Antique Sollerets + 6223, // Mended Imperial Pot Helm + 6224, // Mended Imperial Short Robe + #region Fate drops used in tribal quests 7001, // Flamefang Choker (Amalj'aa) diff --git a/ARDiscard/GameData/InventoryUtils.cs b/ARDiscard/GameData/InventoryUtils.cs index a571aaa..0a83969 100644 --- a/ARDiscard/GameData/InventoryUtils.cs +++ b/ARDiscard/GameData/InventoryUtils.cs @@ -35,6 +35,8 @@ internal sealed class InventoryUtils InventoryType.ArmoryRings }; + private static readonly IReadOnlyList NoGearsetItems = new List(); + private readonly Configuration _configuration; private readonly ItemCache _itemCache; private readonly IPluginLog _pluginLog; @@ -53,7 +55,7 @@ internal sealed class InventoryUtils InventoryManager* inventoryManager = InventoryManager.Instance(); foreach (InventoryType inventoryType in DefaultInventoryTypes) - toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, false, null)); + toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, NoGearsetItems)); if (_configuration.Armoury.DiscardFromArmouryChest) { @@ -62,14 +64,14 @@ internal sealed class InventoryUtils if (_configuration.Armoury.CheckLeftSideGear) { foreach (InventoryType inventoryType in LeftSideGearInventoryTypes) - toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, true, + toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems)); } if (_configuration.Armoury.CheckRightSideGear) { foreach (InventoryType inventoryType in RightSideGearInventoryTypes) - toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, true, + toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems)); } } @@ -88,7 +90,7 @@ internal sealed class InventoryUtils } private unsafe IReadOnlyList GetItemsToDiscard(InventoryManager* inventoryManager, - InventoryType inventoryType, Dictionary itemCounts, bool doGearChecks, + InventoryType inventoryType, Dictionary itemCounts, IReadOnlyList? gearsetItems) { List toDiscard = new List(); @@ -107,18 +109,16 @@ internal sealed class InventoryUtils if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID)) continue; - if (doGearChecks) - { - if (gearsetItems == null || gearsetItems.Contains(item->ItemID)) - continue; + if (!_itemCache.TryGetItem(item->ItemID, out ItemCache.CachedItemInfo? itemInfo) || !itemInfo.CanBeDiscarded()) + continue; // no info, who knows what that item is - ItemCache.CachedItemInfo? itemInfo = _itemCache.GetItem(item->ItemID); - if (itemInfo == null) - 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 + if (itemInfo.EquipSlotCategory > 0 && (gearsetItems == null || gearsetItems.Contains(item->ItemID))) + continue; - if (itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel) - continue; - } + if (itemInfo is { EquipSlotCategory: > 0, CanBeBoughtFromCalamitySalvager: false } && + itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel) + continue; //PluginLog.Verbose($"{i} → {item->ItemID}"); if (_configuration.DiscardingItems.Contains(item->ItemID)) diff --git a/ARDiscard/GameData/ItemCache.cs b/ARDiscard/GameData/ItemCache.cs index 11181ba..6e13428 100644 --- a/ARDiscard/GameData/ItemCache.cs +++ b/ARDiscard/GameData/ItemCache.cs @@ -1,5 +1,8 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using Dalamud.Plugin.Services; +using Lumina.Excel; using Lumina.Excel.GeneratedSheets; namespace ARDiscard.GameData; @@ -28,12 +31,45 @@ internal sealed class ItemCache Level = item.LevelEquip, UiCategory = item.ItemUICategory.Row, UiCategoryName = item.ItemUICategory.Value!.Name.ToString(), + EquipSlotCategory = item.EquipSlotCategory.Row, }; } + + foreach (var shopItem in dataManager.GetExcelSheet()!) + { + // exclude base ARR relics, not strictly necessary since we don't allow discarding weapons anyway + if (shopItem.Item.Value!.Rarity == 4) + continue; + + // the item can be discarded already + if (!_items.TryGetValue(shopItem.Item.Row, out CachedItemInfo? cachedItemInfo) || + cachedItemInfo.CanBeDiscarded()) + continue; + + if (shopItem.AchievementRequired.Row != 0) + continue; + + // has a quest required to unlock from the shop + if (!shopItem.QuestRequired.Any(CanDiscardItemsFromQuest)) + continue; + + cachedItemInfo.CanBeBoughtFromCalamitySalvager = true; + } + } + + private bool CanDiscardItemsFromQuest(LazyRow quest) + { + return quest.Row > 0 && + quest.Value?.JournalGenre.Value?.JournalCategory.Value?.JournalSection + .Row is 0 or 1 or 6; // pre-EW MSQ, EW MSQ or Job/Class quest } public IEnumerable AllItems => _items.Values; + + public bool TryGetItem(uint itemId, [NotNullWhen(true)] out CachedItemInfo? item) + => _items.TryGetValue(itemId, out item); + public CachedItemInfo? GetItem(uint itemId) { if (_items.TryGetValue(itemId, out var item)) @@ -71,7 +107,28 @@ internal sealed class ItemCache /// public required bool IsIndisposable { get; init; } + public bool CanBeBoughtFromCalamitySalvager { get; set; } + public required uint UiCategory { get; init; } public required string UiCategoryName { get; init; } + public required uint EquipSlotCategory { get; init; } + + public bool CanBeDiscarded() + { + if (InternalConfiguration.BlacklistedItems.Contains(ItemId)) + return false; + + if (UiCategory is UiCategories.Currency or UiCategories.Crystals or UiCategories.Unobtainable) + return false; + + if (EquipSlotCategory is 1 or 2 or 13 or 14) + return false; + + if (InternalConfiguration.WhitelistedItems.Contains(ItemId)) + return true; + + return CanBeBoughtFromCalamitySalvager || + this is { IsUnique: false, IsUntradable: false, IsIndisposable: false }; + } } } diff --git a/ARDiscard/Windows/ConfigWindow.cs b/ARDiscard/Windows/ConfigWindow.cs index b373942..ace758c 100644 --- a/ARDiscard/Windows/ConfigWindow.cs +++ b/ARDiscard/Windows/ConfigWindow.cs @@ -380,11 +380,7 @@ internal sealed class ConfigWindow : LImGui.LWindow if (_allItems == null) { _allItems = _itemCache.AllItems - .Where(x => !InternalConfiguration.BlacklistedItems.Contains(x.ItemId)) - .Where(x => InternalConfiguration.WhitelistedItems.Contains(x.ItemId) || - x is { IsUnique: false, IsUntradable: false, IsIndisposable: false }) - .Where(x => x.UiCategory != UiCategories.Currency && x.UiCategory != UiCategories.Crystals && - x.UiCategory != UiCategories.Unobtainable) + .Where(x => x.CanBeDiscarded()) .Select(x => (x.ItemId, x.Name.ToString())) .ToList(); }