Refactoring, allow unique/untradeable items that can be purchased after completing quests to be discarded

This commit is contained in:
Liza 2023-12-07 16:38:00 +01:00
parent 5817f8c976
commit ce37130390
Signed by: liza
GPG Key ID: 7199F8D727D55F67
7 changed files with 79 additions and 48 deletions

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<Version>3.8</Version> <Version>4.0</Version>
<LangVersion>11.0</LangVersion> <LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -1,14 +1,11 @@
using System; using System;
using System.Linq;
using ARDiscard.GameData; using ARDiscard.GameData;
using ARDiscard.GameData.Agents;
using ARDiscard.Windows; using ARDiscard.Windows;
using Dalamud.ContextMenu; using Dalamud.ContextMenu;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace ARDiscard; namespace ARDiscard;
@ -52,18 +49,7 @@ internal sealed class ContextMenuIntegration : IDisposable
if (_configuration.ContextMenu.OnlyWhenConfigIsOpen && !_configWindow.IsOpen) if (_configuration.ContextMenu.OnlyWhenConfigIsOpen && !_configWindow.IsOpen)
return; return;
if (args.ParentAddonName == "ArmouryBoard") if (!(args.ParentAddonName is "Inventory" or "InventoryExpansion" or "InventoryLarge" or "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"))
return; return;
if (!_configWindow.CanItemBeConfigured(args.ItemId)) if (!_configWindow.CanItemBeConfigured(args.ItemId))
@ -71,7 +57,8 @@ internal sealed class ContextMenuIntegration : IDisposable
if (_configuration.DiscardingItems.Contains(args.ItemId)) if (_configuration.DiscardingItems.Contains(args.ItemId))
args.AddCustomItem(_removeItem); 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); args.AddCustomItem(_addItem);
} }

View File

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

View File

@ -42,6 +42,9 @@ internal static class InternalConfiguration
9390, // Antique Breeches 9390, // Antique Breeches
9391, // Antique Sollerets 9391, // Antique Sollerets
6223, // Mended Imperial Pot Helm
6224, // Mended Imperial Short Robe
#region Fate drops used in tribal quests #region Fate drops used in tribal quests
7001, // Flamefang Choker (Amalj'aa) 7001, // Flamefang Choker (Amalj'aa)

View File

@ -35,6 +35,8 @@ internal sealed class InventoryUtils
InventoryType.ArmoryRings InventoryType.ArmoryRings
}; };
private static readonly IReadOnlyList<uint> NoGearsetItems = new List<uint>();
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ItemCache _itemCache; private readonly ItemCache _itemCache;
private readonly IPluginLog _pluginLog; private readonly IPluginLog _pluginLog;
@ -53,7 +55,7 @@ internal sealed class InventoryUtils
InventoryManager* inventoryManager = InventoryManager.Instance(); InventoryManager* inventoryManager = InventoryManager.Instance();
foreach (InventoryType inventoryType in DefaultInventoryTypes) foreach (InventoryType inventoryType in DefaultInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, false, null)); toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, NoGearsetItems));
if (_configuration.Armoury.DiscardFromArmouryChest) if (_configuration.Armoury.DiscardFromArmouryChest)
{ {
@ -62,14 +64,14 @@ internal sealed class InventoryUtils
if (_configuration.Armoury.CheckLeftSideGear) if (_configuration.Armoury.CheckLeftSideGear)
{ {
foreach (InventoryType inventoryType in LeftSideGearInventoryTypes) foreach (InventoryType inventoryType in LeftSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, true, toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts,
gearsetItems)); gearsetItems));
} }
if (_configuration.Armoury.CheckRightSideGear) if (_configuration.Armoury.CheckRightSideGear)
{ {
foreach (InventoryType inventoryType in RightSideGearInventoryTypes) foreach (InventoryType inventoryType in RightSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, true, toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts,
gearsetItems)); gearsetItems));
} }
} }
@ -88,7 +90,7 @@ internal sealed class InventoryUtils
} }
private unsafe IReadOnlyList<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager, private unsafe IReadOnlyList<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager,
InventoryType inventoryType, Dictionary<uint, uint> itemCounts, bool doGearChecks, InventoryType inventoryType, Dictionary<uint, uint> itemCounts,
IReadOnlyList<uint>? gearsetItems) IReadOnlyList<uint>? gearsetItems)
{ {
List<ItemWrapper> toDiscard = new List<ItemWrapper>(); List<ItemWrapper> toDiscard = new List<ItemWrapper>();
@ -107,18 +109,16 @@ internal sealed class InventoryUtils
if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID)) if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID))
continue; continue;
if (doGearChecks) if (!_itemCache.TryGetItem(item->ItemID, out ItemCache.CachedItemInfo? itemInfo) || !itemInfo.CanBeDiscarded())
{
if (gearsetItems == null || gearsetItems.Contains(item->ItemID))
continue;
ItemCache.CachedItemInfo? itemInfo = _itemCache.GetItem(item->ItemID);
if (itemInfo == null)
continue; // no info, who knows what that item is continue; // no info, who knows what that item is
if (itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel) // 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 is { EquipSlotCategory: > 0, CanBeBoughtFromCalamitySalvager: false } &&
itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel)
continue; continue;
}
//PluginLog.Verbose($"{i} → {item->ItemID}"); //PluginLog.Verbose($"{i} → {item->ItemID}");
if (_configuration.DiscardingItems.Contains(item->ItemID)) if (_configuration.DiscardingItems.Contains(item->ItemID))

View File

@ -1,5 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
namespace ARDiscard.GameData; namespace ARDiscard.GameData;
@ -28,12 +31,45 @@ internal sealed class ItemCache
Level = item.LevelEquip, Level = item.LevelEquip,
UiCategory = item.ItemUICategory.Row, UiCategory = item.ItemUICategory.Row,
UiCategoryName = item.ItemUICategory.Value!.Name.ToString(), UiCategoryName = item.ItemUICategory.Value!.Name.ToString(),
EquipSlotCategory = item.EquipSlotCategory.Row,
}; };
} }
foreach (var shopItem in dataManager.GetExcelSheet<GilShopItem>()!)
{
// 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> 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<CachedItemInfo> AllItems => _items.Values; public IEnumerable<CachedItemInfo> AllItems => _items.Values;
public bool TryGetItem(uint itemId, [NotNullWhen(true)] out CachedItemInfo? item)
=> _items.TryGetValue(itemId, out item);
public CachedItemInfo? GetItem(uint itemId) public CachedItemInfo? GetItem(uint itemId)
{ {
if (_items.TryGetValue(itemId, out var item)) if (_items.TryGetValue(itemId, out var item))
@ -71,7 +107,28 @@ internal sealed class ItemCache
/// </summary> /// </summary>
public required bool IsIndisposable { get; init; } public required bool IsIndisposable { get; init; }
public bool CanBeBoughtFromCalamitySalvager { get; set; }
public required uint UiCategory { get; init; } public required uint UiCategory { get; init; }
public required string UiCategoryName { 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 };
}
} }
} }

View File

@ -380,11 +380,7 @@ internal sealed class ConfigWindow : LImGui.LWindow
if (_allItems == null) if (_allItems == null)
{ {
_allItems = _itemCache.AllItems _allItems = _itemCache.AllItems
.Where(x => !InternalConfiguration.BlacklistedItems.Contains(x.ItemId)) .Where(x => x.CanBeDiscarded())
.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)
.Select(x => (x.ItemId, x.Name.ToString())) .Select(x => (x.ItemId, x.Name.ToString()))
.ToList(); .ToList();
} }