Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

23 changed files with 388 additions and 1893 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,67 @@
<Project Sdk="Dalamud.NET.Sdk/9.0.2"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<Version>6.2</Version> <TargetFramework>net7.0-windows</TargetFramework>
<Version>4.2</Version>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>dist</OutputPath> <OutputPath>dist</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DebugType>portable</DebugType>
</PropertyGroup> </PropertyGroup>
<Import Project="..\LLib\LLib.targets"/> <PropertyGroup>
<Import Project="..\LLib\RenameZip.targets"/> <DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj"/> <ProjectReference Include="..\AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj"/>
<ProjectReference Include="..\ECommons\ECommons\ECommons.csproj"/> <ProjectReference Include="..\ECommons\ECommons\ECommons.csproj"/>
<ProjectReference Include="..\LLib\LLib.csproj"/> <ProjectReference Include="..\LLib\LLib.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Dalamud.ContextMenu" Version="1.3.1"/>
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
</ItemGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="ImGui.NET">
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin">
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip"/>
</Target>
</Project> </Project>

View File

@ -2,13 +2,7 @@
"Name": "Discard Helper", "Name": "Discard Helper",
"Author": "Liza Carvelli", "Author": "Liza Carvelli",
"Punchline": "Discard items automatically via a command or as a post-venture task for AutoRetainer", "Punchline": "Discard items automatically via a command or as a post-venture task for AutoRetainer",
"Description": "Small plugin to help keep your inventory organized (especially when automating retainers or submersibles) by discarding items you don't want (e.g. Stuffed Alphas)", "Description": "",
"RepoUrl": "https://git.carvel.li/liza/ARDiscard", "RepoUrl": "https://git.carvel.li/liza/ARDiscard",
"IconUrl": "https://plugins.carvel.li/icons/ARDiscard.png", "IconUrl": "https://git.carvel.li/liza/plugin-repo/raw/branch/master/dist/ARDiscard.png"
"Tags": [
"autoretainer",
"automation",
"discard",
"yeet"
]
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using ARDiscard.External;
using ARDiscard.GameData; using ARDiscard.GameData;
using ARDiscard.Windows; using ARDiscard.Windows;
using AutoRetainerAPI; using AutoRetainerAPI;
@ -11,7 +10,7 @@ using Dalamud.Memory;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ECommons; using ECommons;
using ECommons.Automation.NeoTaskManager; using ECommons.Automation;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
@ -20,14 +19,14 @@ using LLib;
namespace ARDiscard; namespace ARDiscard;
[SuppressMessage("ReSharper", "UnusedType.Global")] [SuppressMessage("ReSharper", "UnusedType.Global")]
public sealed class AutoDiscardPlogon : IDalamudPlugin public class AutoDiscardPlogon : IDalamudPlugin
{ {
private readonly WindowSystem _windowSystem = new(nameof(AutoDiscardPlogon)); private readonly WindowSystem _windowSystem = new(nameof(AutoDiscardPlogon));
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ConfigWindow _configWindow; private readonly ConfigWindow _configWindow;
private readonly DiscardWindow _discardWindow; private readonly DiscardWindow _discardWindow;
private readonly IDalamudPluginInterface _pluginInterface; private readonly DalamudPluginInterface _pluginInterface;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly IPluginLog _pluginLog; private readonly IPluginLog _pluginLog;
@ -35,27 +34,20 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
private readonly ICommandManager _commandManager; private readonly ICommandManager _commandManager;
private readonly InventoryUtils _inventoryUtils; private readonly InventoryUtils _inventoryUtils;
private readonly IconCache _iconCache; private readonly IconCache _iconCache;
private readonly GameStrings _gameStrings;
private readonly AutoRetainerApi _autoRetainerApi; private readonly AutoRetainerApi _autoRetainerApi;
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Obsolete in ECommons")]
private readonly TaskManager _taskManager; private readonly TaskManager _taskManager;
private readonly ContextMenuIntegration _contextMenuIntegration; private readonly ContextMenuIntegration _contextMenuIntegration;
private readonly AutoDiscardIpc _autoDiscardIpc;
private DateTime _cancelDiscardAfter = DateTime.MaxValue; private DateTime _cancelDiscardAfter = DateTime.MaxValue;
[SuppressMessage("Maintainability", "CA1506")] public AutoDiscardPlogon(DalamudPluginInterface pluginInterface, ICommandManager commandManager, IChatGui chatGui,
public AutoDiscardPlogon(IDalamudPluginInterface pluginInterface, ICommandManager commandManager, IChatGui chatGui,
IDataManager dataManager, IClientState clientState, ICondition condition, IPluginLog pluginLog, IDataManager dataManager, IClientState clientState, ICondition condition, IPluginLog pluginLog,
IGameGui gameGui, ITextureProvider textureProvider, IContextMenu contextMenu) IGameGui gameGui, ITextureProvider textureProvider)
{ {
ArgumentNullException.ThrowIfNull(dataManager); ItemCache itemCache = new ItemCache(dataManager);
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration(); _configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
MigrateConfiguration(_configuration);
_chatGui = chatGui; _chatGui = chatGui;
_clientState = clientState; _clientState = clientState;
_pluginLog = pluginLog; _pluginLog = pluginLog;
@ -73,39 +65,28 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
{ {
HelpMessage = "Show what will be discarded with your current configuration", 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); _iconCache = new IconCache(textureProvider);
_gameStrings = new GameStrings(dataManager, pluginLog);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw; _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenMainUi += OpenDiscardUi;
_pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi; _pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
_discardWindow = new(_inventoryUtils, itemCache, _iconCache, clientState, condition, _configuration); _discardWindow = new(_inventoryUtils, itemCache, _iconCache, clientState, condition, _configuration);
_windowSystem.AddWindow(_discardWindow); _windowSystem.AddWindow(_discardWindow);
_configWindow = new(_pluginInterface, _configuration, itemCache, listManager, clientState, condition); _configWindow = new(_pluginInterface, _configuration, itemCache, clientState, condition);
_windowSystem.AddWindow(_configWindow); _windowSystem.AddWindow(_configWindow);
_configWindow.DiscardNowClicked += (_, _) => OpenDiscardWindow(string.Empty, string.Empty); _configWindow.DiscardNowClicked += (_, _) => OpenDiscardWindow(string.Empty, string.Empty);
_configWindow.ConfigSaved += (_, _) => _discardWindow.RefreshInventory(true); _configWindow.ConfigSaved += (_, _) => _discardWindow.RefreshInventory(true);
_discardWindow.OpenConfigurationClicked += (_, _) => OpenConfigUi(); _discardWindow.OpenConfigurationClicked += (_, _) => OpenConfigUi();
_discardWindow.DiscardAllClicked += (_, filter) => _discardWindow.DiscardAllClicked += (_, filter) =>
{ _taskManager!.Enqueue(() => DiscardNextItem(PostProcessType.ManuallyStarted, filter));
_taskManager?.Abort();
_taskManager?.Enqueue(() => DiscardNextItem(PostProcessType.ManuallyStarted, filter));
};
ECommonsMain.Init(_pluginInterface, this); ECommonsMain.Init(_pluginInterface, this);
_autoRetainerApi = new(); _autoRetainerApi = new();
_taskManager = new(); _taskManager = new();
_contextMenuIntegration = new(_chatGui, itemCache, _configuration, listManager, _configWindow, _gameGui, contextMenu); _contextMenuIntegration = new(_pluginInterface, _chatGui, itemCache, _configuration, _configWindow, _gameGui);
_autoDiscardIpc = new(_pluginInterface, _configuration);
_clientState.Login += _discardWindow.Login; _clientState.Login += _discardWindow.Login;
_clientState.Logout += _discardWindow.Logout; _clientState.Logout += _discardWindow.Logout;
@ -115,17 +96,6 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
_autoRetainerApi.OnCharacterReadyToPostProcess += DoCharacterPostProcess; _autoRetainerApi.OnCharacterReadyToPostProcess += DoCharacterPostProcess;
} }
private void MigrateConfiguration(Configuration configuration)
{
if (configuration.Version == 1)
{
configuration.ContextMenu.Enabled = true;
configuration.ContextMenu.OnlyWhenConfigIsOpen = false;
configuration.Version = 2;
_pluginInterface.SavePluginConfig(configuration);
}
}
private void CheckRetainerPostProcess(string retainerName) => private void CheckRetainerPostProcess(string retainerName) =>
CheckPostProcessInternal(PostProcessType.Retainer, retainerName, _configuration.RunAfterVenture); CheckPostProcessInternal(PostProcessType.Retainer, retainerName, _configuration.RunAfterVenture);
@ -158,13 +128,11 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
private void DoRetainerPostProcess(string retainerName) private void DoRetainerPostProcess(string retainerName)
{ {
_taskManager.Abort();
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Retainer, ItemFilter.None)); _taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Retainer, ItemFilter.None));
} }
private void DoCharacterPostProcess() private void DoCharacterPostProcess()
{ {
_taskManager.Abort();
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Character, ItemFilter.None)); _taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Character, ItemFilter.None));
} }
@ -177,13 +145,10 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
private void DiscardAll(string command, string arguments) private void DiscardAll(string command, string arguments)
{ {
_taskManager.Abort();
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.ManuallyStarted, ItemFilter.None)); _taskManager.Enqueue(() => DiscardNextItem(PostProcessType.ManuallyStarted, ItemFilter.None));
} }
private void OpenDiscardWindow(string command, string arguments) => OpenDiscardUi(); private void OpenDiscardWindow(string command, string arguments)
private void OpenDiscardUi()
{ {
_discardWindow.IsOpen = !_discardWindow.IsOpen; _discardWindow.IsOpen = !_discardWindow.IsOpen;
} }
@ -204,11 +169,11 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
var (inventoryType, slot) = (nextItem->Container, nextItem->Slot); var (inventoryType, slot) = (nextItem->Container, nextItem->Slot);
_pluginLog.Information( _pluginLog.Information(
$"Discarding itemId {nextItem->ItemId} in slot {nextItem->Slot} of container {nextItem->Container}."); $"Discarding itemId {nextItem->ItemID} in slot {nextItem->Slot} of container {nextItem->Container}.");
_inventoryUtils.Discard(nextItem); _inventoryUtils.Discard(nextItem);
_cancelDiscardAfter = DateTime.Now.AddSeconds(15); _cancelDiscardAfter = DateTime.Now.AddSeconds(15);
_taskManager.EnqueueDelay(20); _taskManager.DelayNext(20);
_taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot)); _taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot));
} }
} }
@ -219,11 +184,11 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
var addon = GetDiscardAddon(); var addon = GetDiscardAddon();
if (addon != null) if (addon != null)
{ {
_pluginLog.Verbose("Addon is visible, clicking 'yes'"); _pluginLog.Information("Addon is visible, clicking 'yes'");
((AddonSelectYesno*)addon)->YesButton->AtkComponentBase.SetEnabledState(true); ((AddonSelectYesno*)addon)->YesButton->AtkComponentBase.SetEnabledState(true);
addon->FireCallbackInt(0); addon->FireCallbackInt(0);
_taskManager.EnqueueDelay(20); _taskManager.DelayNext(20);
_taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot)); _taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot));
} }
else else
@ -238,14 +203,14 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
{ {
_pluginLog.Information( _pluginLog.Information(
$"Addon is not (yet) visible, still trying to discard item in slot {slot} in inventory {inventoryType}"); $"Addon is not (yet) visible, still trying to discard item in slot {slot} in inventory {inventoryType}");
_taskManager.EnqueueDelay(100); _taskManager.DelayNext(100);
_taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot)); _taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot));
} }
else else
{ {
_pluginLog.Information( _pluginLog.Information(
$"Addon is not (yet) visible, but slot or inventory type changed, retrying from start"); $"Addon is not (yet) visible, but slot or inventory type changed, retrying from start");
_taskManager.EnqueueDelay(100); _taskManager.DelayNext(100);
_taskManager.Enqueue(() => DiscardNextItem(type, itemFilter)); _taskManager.Enqueue(() => DiscardNextItem(type, itemFilter));
} }
} }
@ -269,16 +234,16 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
} }
else else
{ {
_pluginLog.Verbose( _pluginLog.Information(
$"ContinueAfterDiscard: Waiting for server response until {_cancelDiscardAfter}"); $"ContinueAfterDiscard: Waiting for server response until {_cancelDiscardAfter}");
_taskManager.EnqueueDelay(20); _taskManager.DelayNext(20);
_taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot)); _taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot));
} }
} }
else else
{ {
_pluginLog.Information("ContinueAfterDiscard: Discovered different item to discard"); _pluginLog.Information($"ContinueAfterDiscard: Discovered different item to discard");
_taskManager.Enqueue(() => DiscardNextItem(type, itemFilter)); _taskManager.EnqueueImmediate(() => DiscardNextItem(type, itemFilter));
} }
} }
@ -309,14 +274,12 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
_clientState.Login -= _discardWindow.Login; _clientState.Login -= _discardWindow.Login;
_clientState.Logout -= _discardWindow.Logout; _clientState.Logout -= _discardWindow.Logout;
_autoDiscardIpc.Dispose();
_contextMenuIntegration.Dispose(); _contextMenuIntegration.Dispose();
_autoRetainerApi.Dispose(); _autoRetainerApi.Dispose();
ECommonsMain.Dispose(); ECommonsMain.Dispose();
_iconCache.Dispose(); _iconCache.Dispose();
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi; _pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
_pluginInterface.UiBuilder.OpenMainUi -= OpenDiscardUi;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_commandManager.RemoveHandler("/discard"); _commandManager.RemoveHandler("/discard");
_commandManager.RemoveHandler("/discardall"); _commandManager.RemoveHandler("/discardall");
@ -336,7 +299,7 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
var textNode = addon->UldManager.NodeList[15]->GetAsAtkTextNode(); var textNode = addon->UldManager.NodeList[15]->GetAsAtkTextNode();
var text = MemoryHelper.ReadSeString(&textNode->NodeText).ExtractText(); var text = MemoryHelper.ReadSeString(&textNode->NodeText).ExtractText();
_pluginLog.Information($"YesNo prompt: {text}"); _pluginLog.Information($"YesNo prompt: {text}");
if (_gameStrings.DiscardItem.IsMatch(text) || _gameStrings.DiscardCollectable.IsMatch(text)) if (text.StartsWith("Discard"))
{ {
return addon; return addon;
} }

View File

@ -5,18 +5,16 @@ namespace ARDiscard;
internal sealed class Configuration : IPluginConfiguration internal sealed class Configuration : IPluginConfiguration
{ {
public int Version { get; set; } = 2; public int Version { get; set; } = 1;
public bool RunAfterVenture { get; set; } public bool RunAfterVenture { get; set; }
public bool RunBeforeLogout { get; set; } public bool RunBeforeLogout { get; set; }
public List<uint> DiscardingItems { get; set; } = new(); public List<uint> DiscardingItems { get; set; } = new();
public List<uint> BlacklistedItems { get; set; } = new();
public List<CharacterInfo> ExcludedCharacters { get; set; } = new(); public List<CharacterInfo> ExcludedCharacters { get; set; } = new();
public ArmouryConfiguration Armoury { get; set; } = new(); public ArmouryConfiguration Armoury { get; set; } = new();
public ContextMenuConfiguration ContextMenu { get; set; } = new(); public ContextMenuConfiguration ContextMenu { get; set; } = new();
public PreviewConfiguration Preview { get; set; } = new(); public PreviewConfiguration Preview { get; set; } = new();
public uint IgnoreItemCountWhenAbove { get; set; } = 50; public uint IgnoreItemCountWhenAbove { get; set; } = 50;
public bool IgnoreItemWithSignature { get; set; }
public sealed class CharacterInfo public sealed class CharacterInfo
{ {
@ -27,17 +25,16 @@ internal sealed class Configuration : IPluginConfiguration
public sealed class ArmouryConfiguration public sealed class ArmouryConfiguration
{ {
public bool DiscardFromArmouryChest { get; set; } public bool DiscardFromArmouryChest { get; set; } = false;
public bool CheckMainHandOffHand { get; set; } public bool CheckLeftSideGear { get; set; } = false;
public bool CheckLeftSideGear { get; set; } public bool CheckRightSideGear { get; set; } = false;
public bool CheckRightSideGear { get; set; }
public int MaximumGearItemLevel { get; set; } = 45; public int MaximumGearItemLevel { get; set; } = 45;
} }
public sealed class ContextMenuConfiguration public sealed class ContextMenuConfiguration
{ {
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = false;
public bool OnlyWhenConfigIsOpen { get; set; } public bool OnlyWhenConfigIsOpen { get; set; } = true;
} }
public sealed class PreviewConfiguration public sealed class PreviewConfiguration

View File

@ -1,10 +1,10 @@
using System; using System;
using ARDiscard.GameData; using ARDiscard.GameData;
using ARDiscard.Windows; using ARDiscard.Windows;
using Dalamud.Game.Gui.ContextMenu; using Dalamud.ContextMenu;
using Dalamud.Game.Text;
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.Services; using Dalamud.Plugin.Services;
namespace ARDiscard; namespace ARDiscard;
@ -14,100 +14,78 @@ internal sealed class ContextMenuIntegration : IDisposable
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly ItemCache _itemCache; private readonly ItemCache _itemCache;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly IListManager _listManager;
private readonly ConfigWindow _configWindow; private readonly ConfigWindow _configWindow;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly IContextMenu _contextMenu; private readonly SeString _addItemPayload;
private readonly MenuItem _addInventoryItem; private readonly SeString _removeItemPayload;
private readonly MenuItem _removeInventoryItem; private readonly InventoryContextMenuItem _addInventoryItem;
private readonly InventoryContextMenuItem _removeInventoryItem;
private readonly DalamudContextMenu _dalamudContextMenu;
public ContextMenuIntegration(IChatGui chatGui, ItemCache itemCache, Configuration configuration, public ContextMenuIntegration(DalamudPluginInterface pluginInterface, IChatGui chatGui, ItemCache itemCache,
IListManager listManager, ConfigWindow configWindow, IGameGui gameGui, IContextMenu contextMenu) Configuration configuration, ConfigWindow configWindow, IGameGui gameGui)
{ {
_chatGui = chatGui; _chatGui = chatGui;
_itemCache = itemCache; _itemCache = itemCache;
_configuration = configuration; _configuration = configuration;
_listManager = listManager;
_configWindow = configWindow; _configWindow = configWindow;
_gameGui = gameGui; _gameGui = gameGui;
_contextMenu = contextMenu; _addItemPayload =
_addInventoryItem = new MenuItem new SeString(new UIForegroundPayload(52))
{ .Append($"\ue05f ")
Prefix = (SeIconChar)57439, .Append(new UIForegroundPayload(0)).Append("Add to Auto Discard List");
PrefixColor = 52, _removeItemPayload = new SeString(new UIForegroundPayload(52))
Name = "Add to Auto Discard List", .Append($"\ue05f ")
OnClicked = AddToDiscardList, .Append(new UIForegroundPayload(0)).Append("Remove from Auto Discard List");
}; _addInventoryItem = new InventoryContextMenuItem(_addItemPayload, AddToDiscardList);
_removeInventoryItem = new MenuItem _removeInventoryItem = new InventoryContextMenuItem(_removeItemPayload, RemoveFromDiscardList);
{
Prefix = (SeIconChar)57439,
PrefixColor = 52,
Name = "Remove from Auto Discard List",
OnClicked = RemoveFromDiscardList,
};
_contextMenu.OnMenuOpened += MenuOpened; _dalamudContextMenu = new(pluginInterface);
_dalamudContextMenu.OnOpenInventoryContextMenu += OpenInventoryContextMenu;
_dalamudContextMenu.OnOpenGameObjectContextMenu += OpenGameObjectContextMenu;
} }
private void MenuOpened(IMenuOpenedArgs args) private void OpenInventoryContextMenu(InventoryContextMenuOpenArgs args)
{ {
if (!IsEnabled()) if (!IsEnabled())
return; return;
if (args.Target is MenuTargetInventory targetInventory) if (args.ParentAddonName is not ("Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard"))
{ return;
if (args.AddonName is not ("Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard") ||
targetInventory.TargetItem == null)
return;
var item = targetInventory.TargetItem.Value; if (!_configWindow.CanItemBeConfigured(args.ItemId))
if (!_configWindow.CanItemBeConfigured(item.ItemId)) return;
return;
if (_configuration.DiscardingItems.Contains(item.ItemId)) if (_configuration.DiscardingItems.Contains(args.ItemId))
args.AddMenuItem(_removeInventoryItem); args.AddCustomItem(_removeInventoryItem);
else if (_itemCache.TryGetItem(item.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) && else if (_itemCache.TryGetItem(args.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded(_listManager)) cachedItemInfo.CanBeDiscarded())
args.AddMenuItem(_addInventoryItem); args.AddCustomItem(_addInventoryItem);
}
else
{
if (args.AddonName is not "ChatLog")
return;
uint itemId = (uint)_gameGui.HoveredItem;
if (itemId > 1_000_000)
itemId -= 1_000_000;
if (itemId > 500_000)
itemId -= 500_000;
if (_configuration.DiscardingItems.Contains(itemId))
{
args.AddMenuItem(new MenuItem
{
Prefix = _removeInventoryItem.Prefix,
PrefixColor = _removeInventoryItem.PrefixColor,
Name = _removeInventoryItem.Name,
OnClicked = _ => RemoveFromDiscardList(itemId),
});
}
else if (_itemCache.TryGetItem(itemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded(_listManager))
{
args.AddMenuItem(new MenuItem
{
Prefix = _addInventoryItem.Prefix,
PrefixColor = _addInventoryItem.PrefixColor,
Name = _addInventoryItem.Name,
OnClicked = _ => AddToDiscardList(itemId),
});
}
}
} }
private void AddToDiscardList(IMenuItemClickedArgs args) => private void OpenGameObjectContextMenu(GameObjectContextMenuOpenArgs args)
AddToDiscardList(((MenuTargetInventory)args.Target).TargetItem!.Value.ItemId); {
if (!IsEnabled())
return;
if (args.ParentAddonName is not "ChatLog")
return;
uint itemId = (uint)_gameGui.HoveredItem;
if (itemId > 1_000_000)
itemId -= 1_000_000;
if (itemId > 500_000)
itemId -= 500_000;
if (_configuration.DiscardingItems.Contains(itemId))
args.AddCustomItem(new GameObjectContextMenuItem(_removeItemPayload, _ => RemoveFromDiscardList(itemId)));
else if (_itemCache.TryGetItem(itemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded())
args.AddCustomItem(new GameObjectContextMenuItem(_addItemPayload, _ => AddToDiscardList(itemId)));
}
private void AddToDiscardList(InventoryContextMenuItemSelectedArgs args) => AddToDiscardList(args.ItemId);
private void AddToDiscardList(uint itemId) private void AddToDiscardList(uint itemId)
{ {
@ -126,8 +104,7 @@ internal sealed class ContextMenuIntegration : IDisposable
} }
} }
private void RemoveFromDiscardList(IMenuItemClickedArgs args) => private void RemoveFromDiscardList(InventoryContextMenuItemSelectedArgs args) => RemoveFromDiscardList(args.ItemId);
RemoveFromDiscardList(((MenuTargetInventory)args.Target).TargetItem!.Value.ItemId);
private void RemoveFromDiscardList(uint itemId) private void RemoveFromDiscardList(uint itemId)
{ {
@ -159,6 +136,8 @@ internal sealed class ContextMenuIntegration : IDisposable
public void Dispose() public void Dispose()
{ {
_contextMenu.OnMenuOpened -= MenuOpened; _dalamudContextMenu.OnOpenGameObjectContextMenu -= OpenGameObjectContextMenu;
_dalamudContextMenu.OnOpenInventoryContextMenu -= OpenInventoryContextMenu;
_dalamudContextMenu.Dispose();
} }
} }

View File

@ -1,32 +0,0 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
namespace ARDiscard.External;
internal sealed class AutoDiscardIpc
{
private const string ItemsToDiscard = "ARDiscard.GetItemsToDiscard";
private readonly Configuration _configuration;
private readonly ICallGateProvider<IReadOnlySet<uint>> _getItemsToDiscard;
public AutoDiscardIpc(IDalamudPluginInterface pluginInterface, Configuration configuration)
{
_configuration = configuration;
_getItemsToDiscard = pluginInterface.GetIpcProvider<IReadOnlySet<uint>>(ItemsToDiscard);
_getItemsToDiscard.RegisterFunc(GetItemsToDiscard);
}
public void Dispose()
{
_getItemsToDiscard.UnregisterFunc();
}
private IReadOnlySet<uint> GetItemsToDiscard()
{
return _configuration.DiscardingItems.ToImmutableHashSet();
}
}

View File

@ -1,21 +0,0 @@
using System.Data;
using System.Text.RegularExpressions;
using Dalamud.Plugin.Services;
using LLib;
using Lumina.Excel.GeneratedSheets;
namespace ARDiscard.GameData;
internal sealed class GameStrings
{
public GameStrings(IDataManager dataManager, IPluginLog pluginLog)
{
DiscardItem = dataManager.GetRegex<Addon>(110, addon => addon.Text, pluginLog)
?? throw new ConstraintException($"Unable to resolve {nameof(DiscardItem)}");
DiscardCollectable = dataManager.GetRegex<Addon>(153, addon => addon.Text, pluginLog)
?? throw new ConstraintException($"Unable to resolve {nameof(DiscardCollectable)}");
}
public Regex DiscardItem { get; }
public Regex DiscardCollectable { get; }
}

View File

@ -1,12 +0,0 @@
using System.Collections.Generic;
namespace ARDiscard.GameData;
internal interface IListManager
{
bool IsBlacklisted(uint itemId, bool checkConfiguration = true);
IReadOnlyList<uint> GetInternalBlacklist();
bool IsWhitelisted(uint itemId);
}

View File

@ -1,22 +1,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
namespace ARDiscard.GameData; namespace ARDiscard.GameData;
internal sealed class ListManager : IListManager internal static class InternalConfiguration
{ {
/// <summary> /// <summary>
/// Not all of these *can* be discarded, but we shouldn't attempt it either. /// Not all of these *can* be discarded, but we shouldn't attempt it either.
/// </summary> /// </summary>
private ISet<uint> _blacklistedItems = new List<uint> public static readonly IReadOnlyList<uint> BlacklistedItems = new List<uint>
{ {
2820, // red onion helm 2820, // red onion helm
16039, // ala mhigan earrings 16039, // ala mhigan earrings
24589, // aetheryte earrings 24589, // aetheryte earrings
33648, // menphina's earrings 33648, // menphina's earrings
41081, // azeyma's earrings
10155, // Ceruleum Tank 10155, // Ceruleum Tank
10373, // Magitek Repair Materials 10373, // Magitek Repair Materials
@ -28,13 +26,14 @@ internal sealed class ListManager : IListManager
38951, // TOP token 38951, // TOP token
} }
.Concat(Enumerable.Range(1, 99).Select(x => (uint)x)) .Concat(Enumerable.Range(1, 99).Select(x => (uint)x))
.ToHashSet(); .ToList()
.AsReadOnly();
/// <summary> /// <summary>
/// Items that are unique/untradeable, but should still be possible to discard. This is moreso because /// 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. /// 99% of the unique/untradeable items should NOT be selectable for discard, but these are OK.
/// </summary> /// </summary>
private ISet<uint> _whitelistedItems = new HashSet<uint>() public static readonly IReadOnlyList<uint> WhitelistedItems = new List<uint>
{ {
2962, // Onion Doublet 2962, // Onion Doublet
3279, // Onion Gaskins 3279, // Onion Gaskins
@ -54,7 +53,6 @@ internal sealed class ListManager : IListManager
15772, // Contemporary Warfare: Defense 15772, // Contemporary Warfare: Defense
15773, // Contemporary Warfare: Offense 15773, // Contemporary Warfare: Offense
15774, // Contemporary Warfare: Magicks 15774, // Contemporary Warfare: Magicks
4572, // Company-issue Tonic
20790, // High Grade Company-issue Tonic 20790, // High Grade Company-issue Tonic
#region Fate drops used in tribal quests #region Fate drops used in tribal quests
@ -189,134 +187,8 @@ internal sealed class ListManager : IListManager
40300, // Unsung Chausses of Anabaseios 40300, // Unsung Chausses of Anabaseios
40301, // Unsung Greaves of Anabaseios 40301, // Unsung Greaves of Anabaseios
40302, // Unsung Ring of Anabaseios 40302, // Unsung Ring of Anabaseios
40317, // Unsung Blade of Anabaseios // 40317, // Unsung Blade of Anabaseios // TODO: Add when the weekly restriction is removed
#endregion #endregion
}.AsReadOnly();
#region Cracked crystals/clusters
15110, // V
16783, // V
18030, // VI
18031, // VI
26819, // VII
26820, // VII
26821, // VIII
26822, // VIII
36025, // IX
36026, // IX
36027, // X
36028, // X
#endregion
#region Eureka Lootboxes
21831, // Sharlayan Cabinet
21832, // Sharlayan Wardrobe
22306, // Eurekan Potion
#endregion
#region Unapproved Firmament materials
32005, // Switch
32006, // Hemp
32007, // Iron Ore
32008, // Iron Sand
32009, // Mahogany Log
32010, // Sesame
32011, // Cotton
32012, // Ore
32013, // Rock Salt
32014, // Mythrite Sand
32015, // Spruce Log
32016, // Mistletoe
32017, // Toad
32018, // Vine
32019, // Tea Leaves
32020, // Electrum Ore
32021, // Alumen
32022, // Spring Water
32023, // Gold Sand
32024, // Ragstone
32025, // White Cedar Log
32026, // Primordial Resin
32027, // Wheat
32028, // Gossamer Cotton Boll
32029, // Tortoise
32030, // Gold Ore
32031, // Finest Rock Salt
32032, // Truespring Water
32034, // Bluespirit Ore
32033, // Mineral Sand
32035, // Log
32036, // Raspberry
32037, // Caiman
32038, // Cocoon
32039, // Barbgrass
32040, // Cloudstone
32041, // Spring Water
32042, // Ice Stalagmite
32043, // Silex
32044, // Prismstone
32045, // Galewood Branch
32046, // Umbral Dirtleaf
32047, // Umbral Flarerock
32048, // Umbral Levinsand
#endregion
#region Irregular Tomestones (after the event is over)
24909, // Philosophy
26536, // Mythology
28648, // Soldiery
30272, // Law
31339, // Esoterics
33329, // Pageantry
33330, // Lore
35834, // Scripture
36658, // Verity
38211, // Creation
39365, // Mendacity
39919, // Tenfold Pageantry
41305, // Genesis I
41306, // Genesis II
#endregion
};
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<uint> 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);
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
@ -12,48 +11,40 @@ namespace ARDiscard.GameData;
internal sealed class InventoryUtils internal sealed class InventoryUtils
{ {
private static readonly InventoryType[] DefaultInventoryTypes = private static readonly InventoryType[] DefaultInventoryTypes =
[ {
InventoryType.Inventory1, InventoryType.Inventory1,
InventoryType.Inventory2, InventoryType.Inventory2,
InventoryType.Inventory3, InventoryType.Inventory3,
InventoryType.Inventory4 InventoryType.Inventory4
]; };
private static readonly InventoryType[] MainHandOffHandInventoryTypes =
[
InventoryType.ArmoryMainHand,
InventoryType.ArmoryOffHand
];
private static readonly InventoryType[] LeftSideGearInventoryTypes = private static readonly InventoryType[] LeftSideGearInventoryTypes =
[ {
InventoryType.ArmoryHead, InventoryType.ArmoryHead,
InventoryType.ArmoryBody, InventoryType.ArmoryBody,
InventoryType.ArmoryHands, InventoryType.ArmoryHands,
InventoryType.ArmoryLegs, InventoryType.ArmoryLegs,
InventoryType.ArmoryFeets InventoryType.ArmoryFeets
]; };
private static readonly InventoryType[] RightSideGearInventoryTypes = private static readonly InventoryType[] RightSideGearInventoryTypes =
[ {
InventoryType.ArmoryEar, InventoryType.ArmoryEar,
InventoryType.ArmoryNeck, InventoryType.ArmoryNeck,
InventoryType.ArmoryWrist, InventoryType.ArmoryWrist,
InventoryType.ArmoryRings InventoryType.ArmoryRings
]; };
private static readonly IReadOnlyList<uint> NoGearsetItems = new List<uint>(); 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 IListManager _listManager;
private readonly IPluginLog _pluginLog; private readonly IPluginLog _pluginLog;
public InventoryUtils(Configuration configuration, ItemCache itemCache, IListManager listManager, IPluginLog pluginLog) public InventoryUtils(Configuration configuration, ItemCache itemCache, IPluginLog pluginLog)
{ {
_configuration = configuration; _configuration = configuration;
_itemCache = itemCache; _itemCache = itemCache;
_listManager = listManager;
_pluginLog = pluginLog; _pluginLog = pluginLog;
} }
@ -69,41 +60,36 @@ internal sealed class InventoryUtils
if (_configuration.Armoury.DiscardFromArmouryChest) if (_configuration.Armoury.DiscardFromArmouryChest)
{ {
var gearsetItems = GetAllGearsetItems(); var gearsetItems = GetAllGearsetItems();
toDiscard.AddRange(GetArmouryItemsToDiscard(_configuration.Armoury.CheckMainHandOffHand, inventoryManager,
MainHandOffHandInventoryTypes, itemCounts, gearsetItems)); if (_configuration.Armoury.CheckLeftSideGear)
toDiscard.AddRange(GetArmouryItemsToDiscard(_configuration.Armoury.CheckLeftSideGear, inventoryManager, {
LeftSideGearInventoryTypes, itemCounts, gearsetItems)); foreach (InventoryType inventoryType in LeftSideGearInventoryTypes)
toDiscard.AddRange(GetArmouryItemsToDiscard(_configuration.Armoury.CheckRightSideGear, inventoryManager, toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts,
RightSideGearInventoryTypes, itemCounts, gearsetItems)); gearsetItems));
}
if (_configuration.Armoury.CheckRightSideGear)
{
foreach (InventoryType inventoryType in RightSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts,
gearsetItems));
}
} }
return toDiscard return toDiscard
.Where(x => itemCounts[x.InventoryItem->ItemId] < _configuration.IgnoreItemCountWhenAbove) .Where(x => itemCounts[x.InventoryItem->ItemID] < _configuration.IgnoreItemCountWhenAbove)
.ToList(); .ToList();
} }
private unsafe ReadOnlyCollection<ItemWrapper> GetArmouryItemsToDiscard(bool condition, InventoryManager* inventoryManager,
InventoryType[] inventoryTypes, Dictionary<uint, uint> itemCounts, List<uint>? gearsetItems)
{
List<ItemWrapper> items = new();
if (condition)
{
foreach (InventoryType inventoryType in inventoryTypes)
items.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems));
}
return items.AsReadOnly();
}
public unsafe InventoryItem* GetNextItemToDiscard(ItemFilter? itemFilter) public unsafe InventoryItem* GetNextItemToDiscard(ItemFilter? itemFilter)
{ {
List<ItemWrapper> allItemsToDiscard = GetAllItemsToDiscard(); List<ItemWrapper> allItemsToDiscard = GetAllItemsToDiscard();
ItemWrapper? toDiscard = allItemsToDiscard.FirstOrDefault(x => ItemWrapper? toDiscard = allItemsToDiscard.FirstOrDefault(x =>
itemFilter == null || itemFilter.ItemIds.Contains(x.InventoryItem->ItemId)); itemFilter == null || itemFilter.ItemIds.Contains(x.InventoryItem->ItemID));
return toDiscard != null ? toDiscard.InventoryItem : null; return toDiscard != null ? toDiscard.InventoryItem : null;
} }
private unsafe ReadOnlyCollection<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager, private unsafe IReadOnlyList<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager,
InventoryType inventoryType, Dictionary<uint, uint> itemCounts, InventoryType inventoryType, Dictionary<uint, uint> itemCounts,
IReadOnlyList<uint>? gearsetItems) IReadOnlyList<uint>? gearsetItems)
{ {
@ -113,36 +99,32 @@ internal sealed class InventoryUtils
for (int i = 0; i < container->Size; ++i) for (int i = 0; i < container->Size; ++i)
{ {
var item = container->GetInventorySlot(i); var item = container->GetInventorySlot(i);
if (item != null && item->ItemId != 0) if (item != null && item->ItemID != 0)
{ {
if (itemCounts.TryGetValue(item->ItemId, out uint itemCount)) if (itemCounts.TryGetValue(item->ItemID, out uint itemCount))
itemCounts[item->ItemId] = itemCount + item->Quantity; itemCounts[item->ItemID] = itemCount + item->Quantity;
else else
itemCounts[item->ItemId] = item->Quantity; itemCounts[item->ItemID] = item->Quantity;
if (_listManager.IsBlacklisted(item->ItemId)) if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID))
continue; continue;
if (!_itemCache.TryGetItem(item->ItemId, out ItemCache.CachedItemInfo? itemInfo) || if (!_itemCache.TryGetItem(item->ItemID, out ItemCache.CachedItemInfo? itemInfo) || !itemInfo.CanBeDiscarded())
!itemInfo.CanBeDiscarded(_listManager))
continue; // no info, who knows what that item is 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 // 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))) if (itemInfo.EquipSlotCategory > 0 && (gearsetItems == null || gearsetItems.Contains(item->ItemID)))
continue; continue;
if (itemInfo is { EquipSlotCategory: > 0, CanBeBoughtFromCalamitySalvager: false } && if (itemInfo is { EquipSlotCategory: > 0, CanBeBoughtFromCalamitySalvager: false } &&
itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel) itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel)
continue; continue;
if (_configuration.IgnoreItemWithSignature && item->CrafterContentId != 0)
continue;
//PluginLog.Verbose($"{i} → {item->ItemID}"); //PluginLog.Verbose($"{i} → {item->ItemID}");
if (_configuration.DiscardingItems.Contains(item->ItemId)) if (_configuration.DiscardingItems.Contains(item->ItemID))
{ {
_pluginLog.Verbose( _pluginLog.Information(
$"Found item {item->ItemId} to discard in inventory {inventoryType} in slot {i}"); $"Found item {item->ItemID} to discard in inventory {inventoryType} in slot {i}");
toDiscard.Add(new ItemWrapper { InventoryItem = item }); toDiscard.Add(new ItemWrapper { InventoryItem = item });
} }
} }
@ -152,7 +134,7 @@ internal sealed class InventoryUtils
} }
} }
return toDiscard.AsReadOnly(); return toDiscard;
} }
private unsafe List<uint>? GetAllGearsetItems() private unsafe List<uint>? GetAllGearsetItems()
@ -169,23 +151,23 @@ internal sealed class InventoryUtils
{ {
var gearsetItems = new[] var gearsetItems = new[]
{ {
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.MainHand), gearset->MainHand,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.OffHand), gearset->OffHand,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Head), gearset->Head,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Body), gearset->Body,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Hands), gearset->Hands,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Legs), gearset->Legs,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Feet), gearset->Feet,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Ears), gearset->Ears,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Neck), gearset->Neck,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Wrists), gearset->Wrists,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.RingLeft), gearset->RingRight,
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.RingRight), gearset->RingLeft,
}; };
foreach (var gearsetItem in gearsetItems) foreach (var gearsetItem in gearsetItems)
{ {
if (gearsetItem.ItemId != 0) if (gearsetItem.ItemID != 0)
allGearsetItems.Add(gearsetItem.ItemId); allGearsetItems.Add(gearsetItem.ItemID);
} }
} }
} }
@ -195,8 +177,8 @@ internal sealed class InventoryUtils
public unsafe void Discard(InventoryItem* item) public unsafe void Discard(InventoryItem* item)
{ {
if (_listManager.IsBlacklisted(item->ItemId)) if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID))
throw new ArgumentException($"Can't discard {item->ItemId}", nameof(item)); throw new Exception($"Can't discard {item->ItemID}");
AgentInventoryContext.Instance()->DiscardItem(item, item->Container, item->Slot, 0); AgentInventoryContext.Instance()->DiscardItem(item, item->Container, item->Slot, 0);
} }

View File

@ -11,7 +11,7 @@ internal sealed class ItemCache
{ {
private readonly Dictionary<uint, CachedItemInfo> _items = new(); private readonly Dictionary<uint, CachedItemInfo> _items = new();
public ItemCache(IDataManager dataManager, ListManager listManager) public ItemCache(IDataManager dataManager)
{ {
foreach (var item in dataManager.GetExcelSheet<Item>()!) foreach (var item in dataManager.GetExcelSheet<Item>()!)
{ {
@ -33,12 +33,6 @@ internal sealed class ItemCache
UiCategoryName = item.ItemUICategory.Value!.Name.ToString(), UiCategoryName = item.ItemUICategory.Value!.Name.ToString(),
EquipSlotCategory = item.EquipSlotCategory.Row, EquipSlotCategory = item.EquipSlotCategory.Row,
}; };
if (item is { Rarity: 3, MateriaSlotCount: 3, RowId: < 33154 or > 33358 })
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<GilShopItem>()!) foreach (var shopItem in dataManager.GetExcelSheet<GilShopItem>()!)
@ -49,7 +43,7 @@ internal sealed class ItemCache
// the item can be discarded already // the item can be discarded already
if (!_items.TryGetValue(shopItem.Item.Row, out CachedItemInfo? cachedItemInfo) || if (!_items.TryGetValue(shopItem.Item.Row, out CachedItemInfo? cachedItemInfo) ||
cachedItemInfo.CanBeDiscarded(listManager)) cachedItemInfo.CanBeDiscarded())
continue; continue;
if (shopItem.AchievementRequired.Row != 0) if (shopItem.AchievementRequired.Row != 0)
@ -61,25 +55,8 @@ internal sealed class ItemCache
cachedItemInfo.CanBeBoughtFromCalamitySalvager = true; cachedItemInfo.CanBeBoughtFromCalamitySalvager = true;
} }
// only look at msq + regional side quests
foreach (var quest in dataManager.GetExcelSheet<Quest>()!.Where(x => x.JournalGenre.Value?.JournalCategory.Value?.JournalSection.Row is 0 or 1 or 3))
{
foreach (var itemId in quest.ItemReward.Where(x => x > 0))
{
var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId);
if (item is { Rarity: 1, ItemAction.Row: 388 } && item.RowId != 38809 && item.RowId != 29679)
listManager.AddToInternalWhitelist(item.RowId);
}
}
MaxDungeonItemLevel = _items.Values.Where(x => x.Rarity == 2)
.Select(x => (int)x.ILvl)
.Max();
} }
public int MaxDungeonItemLevel { get; }
private bool CanDiscardItemsFromQuest(LazyRow<Quest> quest) private bool CanDiscardItemsFromQuest(LazyRow<Quest> quest)
{ {
return quest.Row > 0 && return quest.Row > 0 &&
@ -136,15 +113,18 @@ internal sealed class ItemCache
public required string UiCategoryName { get; init; } public required string UiCategoryName { get; init; }
public required uint EquipSlotCategory { get; init; } public required uint EquipSlotCategory { get; init; }
public bool CanBeDiscarded(IListManager listManager, bool checkConfiguration = true) public bool CanBeDiscarded()
{ {
if (listManager.IsBlacklisted(ItemId, checkConfiguration)) if (InternalConfiguration.BlacklistedItems.Contains(ItemId))
return false; return false;
if (UiCategory is UiCategories.Currency or UiCategories.Crystals or UiCategories.Unobtainable) if (UiCategory is UiCategories.Currency or UiCategories.Crystals or UiCategories.Unobtainable)
return false; return false;
if (listManager.IsWhitelisted(ItemId)) if (EquipSlotCategory is 1 or 2 or 13 or 14)
return false;
if (InternalConfiguration.WhitelistedItems.Contains(ItemId))
return true; return true;
return CanBeBoughtFromCalamitySalvager || return CanBeBoughtFromCalamitySalvager ||

View File

@ -2,9 +2,9 @@
namespace ARDiscard.GameData; namespace ARDiscard.GameData;
internal sealed class ItemFilter internal class ItemFilter
{ {
public const ItemFilter? None = null; public static ItemFilter? None = null;
public required List<uint> ItemIds { get; init; } public required List<uint> ItemIds { get; init; }
} }

View File

@ -8,35 +8,38 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ECommons;
using ImGuiNET; using ImGuiNET;
using LLib.ImGui; using LLib;
namespace ARDiscard.Windows; namespace ARDiscard.Windows;
internal sealed class ConfigWindow : LWindow internal sealed class ConfigWindow : LImGui.LWindow
{ {
private readonly IDalamudPluginInterface _pluginInterface; private const int ResultLimit = 200;
private readonly DalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ItemCache _itemCache; private readonly ItemCache _itemCache;
private readonly IListManager _listManager;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly DiscardListTab _discardListTab; private string _itemName = string.Empty;
private readonly ExcludedListTab _excludedListTab;
private List<(uint ItemId, string Name)>? _allItems; private List<(uint ItemId, string Name)> _searchResults = new();
private List<(uint ItemId, string Name)> _discarding = new();
private List<(uint ItemId, string Name)>? _allItems = null;
private bool _resetKeyboardFocus = true;
public event EventHandler? DiscardNowClicked; public event EventHandler? DiscardNowClicked;
public event EventHandler? ConfigSaved; public event EventHandler? ConfigSaved;
public ConfigWindow(IDalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache, public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache,
IListManager listManager, IClientState clientState, ICondition condition) IClientState clientState, ICondition condition)
: base("Auto Discard###AutoDiscardConfig") : base("Auto Discard###AutoDiscardConfig")
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_configuration = configuration; _configuration = configuration;
_itemCache = itemCache; _itemCache = itemCache;
_listManager = listManager;
_clientState = clientState; _clientState = clientState;
_condition = condition; _condition = condition;
@ -49,11 +52,8 @@ internal sealed class ConfigWindow : LWindow
MaximumSize = new Vector2(9999, 9999), MaximumSize = new Vector2(9999, 9999),
}; };
_excludedListTab = new ExcludedListTab(this, itemCache, _configuration.BlacklistedItems, listManager); _discarding.AddRange(_configuration.DiscardingItems
_discardListTab = new DiscardListTab(this, itemCache, _configuration.DiscardingItems) .Select(x => (x, itemCache.GetItemName(x))).ToList());
{
ExcludedTab = _excludedListTab,
};
} }
public override void Draw() public override void Draw()
@ -65,10 +65,7 @@ internal sealed class ConfigWindow : LWindow
Save(); Save();
} }
ImGui.SameLine(ImGui.GetContentRegionAvail().X + ImGui.SameLine(ImGui.GetWindowWidth() - 115 * ImGuiHelpers.GlobalScale);
ImGui.GetStyle().WindowPadding.X -
ImGui.CalcTextSize("Preview Discards").X -
ImGui.GetStyle().ItemSpacing.X);
ImGui.BeginDisabled(!_clientState.IsLoggedIn || ImGui.BeginDisabled(!_clientState.IsLoggedIn ||
!(_condition[ConditionFlag.NormalConditions] || _condition[ConditionFlag.Mounted]) || !(_condition[ConditionFlag.NormalConditions] || _condition[ConditionFlag.Mounted]) ||
DiscardNowClicked == null); DiscardNowClicked == null);
@ -87,7 +84,6 @@ internal sealed class ConfigWindow : LWindow
{ {
DrawDiscardList(); DrawDiscardList();
DrawExcludedCharacters(); DrawExcludedCharacters();
DrawExcludedItems();
DrawExperimentalSettings(); DrawExperimentalSettings();
ImGui.EndTabBar(); ImGui.EndTabBar();
@ -98,7 +94,100 @@ internal sealed class ConfigWindow : LWindow
{ {
if (ImGui.BeginTabItem("Items to Discard")) if (ImGui.BeginTabItem("Items to Discard"))
{ {
_discardListTab.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 (_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.ToLower()))
{
if (ImGui.Selectable(name, true))
toRemove.Add((id, name));
}
if (toRemove.Count > 0)
{
foreach (var tr in toRemove)
_discarding.Remove(tr);
Save();
}
}
ImGui.EndChild();
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
} }
@ -180,19 +269,6 @@ 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() private void DrawExperimentalSettings()
{ {
if (ImGui.BeginTabItem("Experimental Settings")) if (ImGui.BeginTabItem("Experimental Settings"))
@ -207,14 +283,6 @@ internal sealed class ConfigWindow : LWindow
ImGui.BeginDisabled(!discardFromArmouryChest); ImGui.BeginDisabled(!discardFromArmouryChest);
ImGui.Indent(30); ImGui.Indent(30);
bool mainHandOffHand = _configuration.Armoury.CheckMainHandOffHand;
if (ImGui.Checkbox("Discard when items are found in Main Hand/Off Hand (Weapons and Tools)",
ref mainHandOffHand))
{
_configuration.Armoury.CheckMainHandOffHand = mainHandOffHand;
Save();
}
bool leftSideGear = _configuration.Armoury.CheckLeftSideGear; bool leftSideGear = _configuration.Armoury.CheckLeftSideGear;
if (ImGui.Checkbox("Discard when items are found in Head/Body/Hands/Legs/Feet", ref leftSideGear)) if (ImGui.Checkbox("Discard when items are found in Head/Body/Hands/Legs/Feet", ref leftSideGear))
{ {
@ -234,8 +302,7 @@ internal sealed class ConfigWindow : LWindow
if (ImGui.InputInt("Ignore items >= this ilvl (Armoury Chest only)", if (ImGui.InputInt("Ignore items >= this ilvl (Armoury Chest only)",
ref maximumItemLevel)) ref maximumItemLevel))
{ {
_configuration.Armoury.MaximumGearItemLevel = _configuration.Armoury.MaximumGearItemLevel = Math.Max(0, Math.Min(625, maximumItemLevel));
Math.Max(0, Math.Min(_itemCache.MaxDungeonItemLevel, maximumItemLevel));
Save(); Save();
} }
@ -274,15 +341,6 @@ internal sealed class ConfigWindow : LWindow
Save(); Save();
} }
bool ignoreItemWithSignature = _configuration.IgnoreItemWithSignature;
if (ImGui.Checkbox(
"Ignore items with a 'crafted by' signature (manually crafted by yourself or someone else)",
ref ignoreItemWithSignature))
{
_configuration.IgnoreItemWithSignature = ignoreItemWithSignature;
Save();
}
ImGui.Separator(); ImGui.Separator();
bool groupPreviewByCategory = _configuration.Preview.GroupByCategory; bool groupPreviewByCategory = _configuration.Preview.GroupByCategory;
@ -303,12 +361,26 @@ internal sealed class ConfigWindow : LWindow
} }
} }
internal List<(uint ItemId, string Name)> EnsureAllItemsLoaded() 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()
{ {
if (_allItems == null) if (_allItems == null)
{ {
_allItems = _itemCache.AllItems _allItems = _itemCache.AllItems
.Where(x => x.CanBeDiscarded(_listManager, false)) .Where(x => x.CanBeDiscarded())
.Select(x => (x.ItemId, x.Name.ToString())) .Select(x => (x.ItemId, x.Name.ToString()))
.ToList(); .ToList();
} }
@ -316,18 +388,37 @@ internal sealed class ConfigWindow : LWindow
return _allItems; return _allItems;
} }
internal void Save() private void Save()
{ {
_configuration.DiscardingItems = _discardListTab.ToSavedItems().ToList(); _configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList();
_configuration.BlacklistedItems = _excludedListTab.ToSavedItems().ToList();
_pluginInterface.SavePluginConfig(_configuration); _pluginInterface.SavePluginConfig(_configuration);
ConfigSaved?.Invoke(this, EventArgs.Empty); ConfigSaved?.Invoke(this, EventArgs.Empty);
} }
internal bool AddToDiscardList(uint itemId) => _discardListTab.AddToDiscardList(itemId); 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 RemoveFromDiscardList(uint itemId) => _discardListTab.RemoveFromDiscardList(itemId); return false;
}
internal bool RemoveFromDiscardList(uint itemId)
{
if (_discarding.RemoveAll(x => x.ItemId == itemId) > 0)
{
Save();
return true;
}
return false;
}
public bool CanItemBeConfigured(uint itemId) public bool CanItemBeConfigured(uint itemId)
{ {

View File

@ -1,55 +0,0 @@
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<uint> initialItems)
: base(parent, itemCache, initialItems)
{
}
protected override string RightSideLabel => "Items that will be automatically discarded";
internal required ExcludedListTab ExcludedTab { private get; init; }
public IEnumerable<uint> 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;
}
}
}

View File

@ -1,22 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using ARDiscard.GameData; using ARDiscard.GameData;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Common.Math; using FFXIVClientStructs.FFXIV.Common.Math;
using ImGuiNET; using ImGuiNET;
using LLib; using LLib;
using LLib.ImGui;
namespace ARDiscard.Windows; namespace ARDiscard.Windows;
internal sealed class DiscardWindow : LWindow internal sealed class DiscardWindow : LImGui.LWindow
{ {
private readonly InventoryUtils _inventoryUtils; private readonly InventoryUtils _inventoryUtils;
private readonly ItemCache _itemCache; private readonly ItemCache _itemCache;
@ -51,7 +47,7 @@ internal sealed class DiscardWindow : LWindow
}; };
} }
public bool Locked { get; set; } public bool Locked { get; set; } = false;
public override void Draw() public override void Draw()
{ {
@ -98,15 +94,11 @@ internal sealed class DiscardWindow : LWindow
if (ImGui.Button("Open Configuration")) if (ImGui.Button("Open Configuration"))
OpenConfigurationClicked!.Invoke(this, EventArgs.Empty); OpenConfigurationClicked!.Invoke(this, EventArgs.Empty);
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.SameLine(ImGui.GetWindowWidth() - 160 * ImGuiHelpers.GlobalScale);
ImGui.SameLine(ImGui.GetContentRegionAvail().X +
ImGui.GetStyle().WindowPadding.X -
ImGui.CalcTextSize("Discard all selected items").X -
ImGui.GetStyle().ItemSpacing.X);
ImGui.BeginDisabled(Locked || ImGui.BeginDisabled(Locked ||
!_clientState.IsLoggedIn || !_clientState.IsLoggedIn ||
!(_condition[ConditionFlag.NormalConditions] || _condition[ConditionFlag.Mounted]) || !(_condition[ConditionFlag.NormalConditions] || _condition[ConditionFlag.Mounted]) ||
!_displayedItems.Any(x => x.Selected) || _displayedItems.Count(x => x.Selected) == 0 ||
DiscardAllClicked == null); DiscardAllClicked == null);
if (ImGui.Button("Discard all selected items")) if (ImGui.Button("Discard all selected items"))
{ {
@ -123,14 +115,12 @@ internal sealed class DiscardWindow : LWindow
{ {
if (_configuration.Preview.ShowIcons) if (_configuration.Preview.ShowIcons)
{ {
ISharedImmediateTexture icon = _iconCache.GetIcon(displayedItem.IconId); IDalamudTextureWrap? icon = _iconCache.GetIcon(displayedItem.IconId);
if (icon.TryGetWrap(out IDalamudTextureWrap? wrap, out _)) if (icon != null)
{ {
ImGui.Image(wrap.ImGuiHandle, new Vector2(23, 23)); ImGui.Image(icon.ImGuiHandle, new Vector2(23, 23));
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
wrap.Dispose();
} }
} }
@ -158,8 +148,8 @@ internal sealed class DiscardWindow : LWindow
_displayedItems = _inventoryUtils.GetAllItemsToDiscard() _displayedItems = _inventoryUtils.GetAllItemsToDiscard()
.GroupBy(x => new .GroupBy(x => new
{ {
ItemId = x.InventoryItem->ItemId, ItemId = x.InventoryItem->ItemID,
ItemInfo = _itemCache.GetItem(x.InventoryItem->ItemId), ItemInfo = _itemCache.GetItem(x.InventoryItem->ItemID),
}) })
.Where(x => x.Key.ItemInfo != null) .Where(x => x.Key.ItemInfo != null)
.Select(x => new SelectableItem .Select(x => new SelectableItem
@ -172,7 +162,7 @@ internal sealed class DiscardWindow : LWindow
UiCategoryName = x.Key.ItemInfo!.UiCategoryName, UiCategoryName = x.Key.ItemInfo!.UiCategoryName,
Selected = !notSelected.Contains(x.Key.ItemId), Selected = !notSelected.Contains(x.Key.ItemId),
}) })
.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase) .OrderBy(x => x.Name.ToLower())
.ToList(); .ToList();
} }

View File

@ -1,35 +0,0 @@
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<uint> 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 IEnumerable<uint> 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));
}

View File

@ -1,161 +0,0 @@
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<uint> 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 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 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();
}

View File

@ -1,95 +1,30 @@
{ {
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net8.0-windows7.0": { "net7.0-windows7.0": {
"Dalamud.ContextMenu": {
"type": "Direct",
"requested": "[1.3.1, )",
"resolved": "1.3.1",
"contentHash": "ptAxut5PiLnzZ4G/KQdHJVcyklC/BF3otHJ7zYVUPiKBjsOCoF0n/6h2jK7e+8ev2Y1yAY3Wtx2GuXLFQgt9Uw=="
},
"DalamudPackager": { "DalamudPackager": {
"type": "Direct", "type": "Direct",
"requested": "[2.1.13, )", "requested": "[2.1.12, )",
"resolved": "2.1.13", "resolved": "2.1.12",
"contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.1.1, )",
"resolved": "1.1.1",
"contentHash": "+H2t/t34h6mhEoUvHi8yGXyuZ2GjSovcGYehJrS2MDm2XgmPfZL2Sdxg+uL2lKgZ4M6tTwKHIlxOob2bgh0NRQ==",
"dependencies": {
"Microsoft.SourceLink.AzureRepos.Git": "1.1.1",
"Microsoft.SourceLink.Bitbucket.Git": "1.1.1",
"Microsoft.SourceLink.GitHub": "1.1.1",
"Microsoft.SourceLink.GitLab": "1.1.1"
}
},
"Microsoft.SourceLink.Gitea": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "KOBodmDnlWGIqZt2hT47Q69TIoGhIApDVLCyyj9TT5ct8ju16AbHYcB4XeknoHX562wO1pMS/1DfBIZK+V+sxg==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.SourceLink.AzureRepos.Git": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "qB5urvw9LO2bG3eVAkuL+2ughxz2rR7aYgm2iyrB8Rlk9cp2ndvGRCvehk3rNIhRuNtQaeKwctOl1KvWiklv5w==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"Microsoft.SourceLink.Bitbucket.Git": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "cDzxXwlyWpLWaH0em4Idj0H3AmVo3L/6xRXKssYemx+7W52iNskj/SQ4FOmfCb8YQt39otTDNMveCZzYtMoucQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
},
"Microsoft.SourceLink.GitLab": {
"type": "Transitive",
"resolved": "1.1.1",
"contentHash": "tvsg47DDLqqedlPeYVE2lmiTpND8F0hkrealQ5hYltSmvruy/Gr5nHAKSsjyw5L3NeM/HLMI5ORv7on/M4qyZw==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "1.1.1",
"Microsoft.SourceLink.Common": "1.1.1"
}
}, },
"autoretainerapi": { "autoretainerapi": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"ECommons": "[2.2.0.2, )" "ECommons": "[2.1.0, )"
} }
}, },
"ecommons": { "ecommons": {
"type": "Project" "type": "Project"
}, },
"llib": { "llib": {
"type": "Project", "type": "Project"
"dependencies": {
"DalamudPackager": "[2.1.13, )"
}
} }
} }
} }

@ -1 +1 @@
Subproject commit a63c8e7154e272374ffa03d5c801736d4229e38a Subproject commit 7cb54772e3a4a60ad02520e898d1ed0e82b2a751

@ -1 +1 @@
Subproject commit 38080f2a3733aa19b6928f4d2984fac7b9a7fab7 Subproject commit f1c688a0599b41d70230021328a575da7351cf91

2
LLib

@ -1 +1 @@
Subproject commit 7027d291efbbff6a55944dd521d3907210ddecbe Subproject commit 865a6080319f8ccbcd5fd5b0004404822b6e60d4

View File

@ -1,7 +0,0 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}