1
0
forked from liza/ARDiscard

Compare commits

...

14 Commits

25 changed files with 1763 additions and 413 deletions

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=AutoRetainerAPI/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=ECommons/@EntryIndexedValue">ExplicitlyExcluded</s:String></wpf:ResourceDictionary>

1017
ARDiscard/.editorconfig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Dalamud.NET.Sdk/11.0.0">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <Version>7.0</Version>
<Version>4.7</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>
<PropertyGroup> <Import Project="..\LLib\LLib.targets"/>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath> <Import Project="..\LLib\RenameZip.targets"/>
</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,7 +2,13 @@
"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": "", "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)",
"RepoUrl": "https://git.carvel.li/liza/ARDiscard", "RepoUrl": "https://git.carvel.li/liza/ARDiscard",
"IconUrl": "https://plugins.carvel.li/icons/ARDiscard.png" "IconUrl": "https://plugins.carvel.li/icons/ARDiscard.png",
"Tags": [
"autoretainer",
"automation",
"discard",
"yeet"
]
} }

View File

@ -11,7 +11,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; using ECommons.Automation.NeoTaskManager;
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 +20,14 @@ using LLib;
namespace ARDiscard; namespace ARDiscard;
[SuppressMessage("ReSharper", "UnusedType.Global")] [SuppressMessage("ReSharper", "UnusedType.Global")]
public class AutoDiscardPlogon : IDalamudPlugin public sealed 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 DalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _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,21 +35,26 @@ public 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 readonly AutoDiscardIpc _autoDiscardIpc;
private DateTime _cancelDiscardAfter = DateTime.MaxValue; private DateTime _cancelDiscardAfter = DateTime.MaxValue;
public AutoDiscardPlogon(DalamudPluginInterface pluginInterface, ICommandManager commandManager, IChatGui chatGui, [SuppressMessage("Maintainability", "CA1506")]
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) IGameGui gameGui, ITextureProvider textureProvider, IContextMenu contextMenu)
{ {
ItemCache itemCache = new ItemCache(dataManager); ArgumentNullException.ThrowIfNull(dataManager);
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration(); _configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? Configuration.CreateNew();
MigrateConfiguration(_configuration); MigrateConfiguration(_configuration);
_chatGui = chatGui; _chatGui = chatGui;
_clientState = clientState; _clientState = clientState;
@ -68,28 +73,38 @@ public 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, clientState, condition); _configWindow = new(_pluginInterface, _configuration, itemCache, listManager, 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(_pluginInterface, _chatGui, itemCache, _configuration, _configWindow, _gameGui); _contextMenuIntegration = new(_chatGui, itemCache, _configuration, listManager, _configWindow, _gameGui, contextMenu);
_autoDiscardIpc = new(_pluginInterface, _configuration); _autoDiscardIpc = new(_pluginInterface, _configuration);
_clientState.Login += _discardWindow.Login; _clientState.Login += _discardWindow.Login;
@ -109,6 +124,14 @@ public class AutoDiscardPlogon : IDalamudPlugin
configuration.Version = 2; configuration.Version = 2;
_pluginInterface.SavePluginConfig(configuration); _pluginInterface.SavePluginConfig(configuration);
} }
if (configuration.Version == 2)
{
if (!configuration.BlacklistedItems.Contains(2820))
configuration.BlacklistedItems.Add(2820);
configuration.Version = 3;
_pluginInterface.SavePluginConfig(configuration);
}
} }
private void CheckRetainerPostProcess(string retainerName) => private void CheckRetainerPostProcess(string retainerName) =>
@ -143,11 +166,13 @@ public 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));
} }
@ -160,10 +185,13 @@ public 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) private void OpenDiscardWindow(string command, string arguments) => OpenDiscardUi();
private void OpenDiscardUi()
{ {
_discardWindow.IsOpen = !_discardWindow.IsOpen; _discardWindow.IsOpen = !_discardWindow.IsOpen;
} }
@ -184,11 +212,11 @@ public 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.DelayNext(20); _taskManager.EnqueueDelay(20);
_taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot)); _taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot));
} }
} }
@ -203,7 +231,7 @@ public class AutoDiscardPlogon : IDalamudPlugin
((AddonSelectYesno*)addon)->YesButton->AtkComponentBase.SetEnabledState(true); ((AddonSelectYesno*)addon)->YesButton->AtkComponentBase.SetEnabledState(true);
addon->FireCallbackInt(0); addon->FireCallbackInt(0);
_taskManager.DelayNext(20); _taskManager.EnqueueDelay(20);
_taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot)); _taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot));
} }
else else
@ -218,14 +246,14 @@ public 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.DelayNext(100); _taskManager.EnqueueDelay(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.DelayNext(100); _taskManager.EnqueueDelay(100);
_taskManager.Enqueue(() => DiscardNextItem(type, itemFilter)); _taskManager.Enqueue(() => DiscardNextItem(type, itemFilter));
} }
} }
@ -251,14 +279,14 @@ public class AutoDiscardPlogon : IDalamudPlugin
{ {
_pluginLog.Verbose( _pluginLog.Verbose(
$"ContinueAfterDiscard: Waiting for server response until {_cancelDiscardAfter}"); $"ContinueAfterDiscard: Waiting for server response until {_cancelDiscardAfter}");
_taskManager.DelayNext(20); _taskManager.EnqueueDelay(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.EnqueueImmediate(() => DiscardNextItem(type, itemFilter)); _taskManager.Enqueue(() => DiscardNextItem(type, itemFilter));
} }
} }
@ -296,6 +324,7 @@ public class AutoDiscardPlogon : IDalamudPlugin
_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");
@ -315,7 +344,7 @@ public 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 (text.StartsWith("Discard")) if (_gameStrings.DiscardItem.IsMatch(text) || _gameStrings.DiscardCollectable.IsMatch(text))
{ {
return addon; return addon;
} }

View File

@ -5,10 +5,11 @@ namespace ARDiscard;
internal sealed class Configuration : IPluginConfiguration internal sealed class Configuration : IPluginConfiguration
{ {
public int Version { get; set; } = 2; public int Version { get; set; } = 3;
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();
@ -26,17 +27,17 @@ internal sealed class Configuration : IPluginConfiguration
public sealed class ArmouryConfiguration public sealed class ArmouryConfiguration
{ {
public bool DiscardFromArmouryChest { get; set; } = false; public bool DiscardFromArmouryChest { get; set; }
public bool CheckMainHandOffHand { 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; } = true;
public bool OnlyWhenConfigIsOpen { get; set; } = false; public bool OnlyWhenConfigIsOpen { get; set; }
} }
public sealed class PreviewConfiguration public sealed class PreviewConfiguration
@ -44,4 +45,12 @@ internal sealed class Configuration : IPluginConfiguration
public bool GroupByCategory { get; set; } = true; public bool GroupByCategory { get; set; } = true;
public bool ShowIcons { get; set; } = true; public bool ShowIcons { get; set; } = true;
} }
public static Configuration CreateNew()
{
return new Configuration
{
BlacklistedItems = [2820]
};
}
} }

View File

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

View File

@ -16,6 +16,6 @@
AssemblyName="$(AssemblyName)" AssemblyName="$(AssemblyName)"
MakeZip="true" MakeZip="true"
VersionComponents="2" VersionComponents="2"
Exclude="ARDiscard.deps.json;AutoRetainerAPI.pdb;ClickLib.pdb;ClickLib.xml;ECommons.pdb;ECommons.xml"/> Exclude="ARDiscard.deps.json;AutoRetainerAPI.pdb;ECommons.pdb;ECommons.xml"/>
</Target> </Target>
</Project> </Project>

View File

@ -12,7 +12,7 @@ internal sealed class AutoDiscardIpc
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ICallGateProvider<IReadOnlySet<uint>> _getItemsToDiscard; private readonly ICallGateProvider<IReadOnlySet<uint>> _getItemsToDiscard;
public AutoDiscardIpc(DalamudPluginInterface pluginInterface, Configuration configuration) public AutoDiscardIpc(IDalamudPluginInterface pluginInterface, Configuration configuration)
{ {
_configuration = configuration; _configuration = configuration;

View File

@ -0,0 +1,21 @@
using System.Data;
using System.Text.RegularExpressions;
using Dalamud.Plugin.Services;
using LLib;
using Lumina.Excel.Sheets;
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

@ -0,0 +1,12 @@
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,5 +1,6 @@
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;
@ -11,53 +12,55 @@ 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 = private static readonly InventoryType[] MainHandOffHandInventoryTypes =
{ [
InventoryType.ArmoryMainHand, InventoryType.ArmoryMainHand,
InventoryType.ArmoryOffHand, 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, IPluginLog pluginLog) public InventoryUtils(Configuration configuration, ItemCache itemCache, IListManager listManager, IPluginLog pluginLog)
{ {
_configuration = configuration; _configuration = configuration;
_itemCache = itemCache; _itemCache = itemCache;
_listManager = listManager;
_pluginLog = pluginLog; _pluginLog = pluginLog;
} }
public unsafe List<ItemWrapper> GetAllItemsToDiscard() public unsafe List<ItemWrapper> GetAllItemsToDiscard()
{ {
List<ItemWrapper> toDiscard = new List<ItemWrapper>(); List<ItemWrapper> toDiscard = new List<ItemWrapper>();
Dictionary<uint, uint> itemCounts = new(); Dictionary<uint, int> itemCounts = new();
InventoryManager* inventoryManager = InventoryManager.Instance(); InventoryManager* inventoryManager = InventoryManager.Instance();
foreach (InventoryType inventoryType in DefaultInventoryTypes) foreach (InventoryType inventoryType in DefaultInventoryTypes)
@ -75,12 +78,12 @@ internal sealed class InventoryUtils
} }
return toDiscard return toDiscard
.Where(x => itemCounts[x.InventoryItem->ItemID] < _configuration.IgnoreItemCountWhenAbove) .Where(x => itemCounts[x.InventoryItem->ItemId] < _configuration.IgnoreItemCountWhenAbove)
.ToList(); .ToList();
} }
private unsafe IEnumerable<ItemWrapper> GetArmouryItemsToDiscard(bool condition, InventoryManager* inventoryManager, private unsafe ReadOnlyCollection<ItemWrapper> GetArmouryItemsToDiscard(bool condition, InventoryManager* inventoryManager,
InventoryType[] inventoryTypes, Dictionary<uint, uint> itemCounts, List<uint>? gearsetItems) InventoryType[] inventoryTypes, Dictionary<uint, int> itemCounts, List<uint>? gearsetItems)
{ {
List<ItemWrapper> items = new(); List<ItemWrapper> items = new();
if (condition) if (condition)
@ -89,19 +92,19 @@ internal sealed class InventoryUtils
items.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems)); items.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems));
} }
return items; 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 IReadOnlyList<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager, private unsafe ReadOnlyCollection<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager,
InventoryType inventoryType, Dictionary<uint, uint> itemCounts, InventoryType inventoryType, Dictionary<uint, int> itemCounts,
IReadOnlyList<uint>? gearsetItems) IReadOnlyList<uint>? gearsetItems)
{ {
List<ItemWrapper> toDiscard = new List<ItemWrapper>(); List<ItemWrapper> toDiscard = new List<ItemWrapper>();
@ -110,37 +113,36 @@ 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 int 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 (InternalConfiguration.BlacklistedItems.Contains(item->ItemID) || if (_listManager.IsBlacklisted(item->ItemId))
InternalConfiguration.UltimateWeapons.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) if (_configuration.IgnoreItemWithSignature && item->CrafterContentId != 0)
continue; 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.Verbose(
$"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 });
} }
} }
@ -150,7 +152,7 @@ internal sealed class InventoryUtils
} }
} }
return toDiscard; return toDiscard.AsReadOnly();
} }
private unsafe List<uint>? GetAllGearsetItems() private unsafe List<uint>? GetAllGearsetItems()
@ -167,23 +169,23 @@ internal sealed class InventoryUtils
{ {
var gearsetItems = new[] var gearsetItems = new[]
{ {
gearset->MainHand, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.MainHand),
gearset->OffHand, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.OffHand),
gearset->Head, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Head),
gearset->Body, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Body),
gearset->Hands, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Hands),
gearset->Legs, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Legs),
gearset->Feet, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Feet),
gearset->Ears, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Ears),
gearset->Neck, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Neck),
gearset->Wrists, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Wrists),
gearset->RingRight, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.RingLeft),
gearset->RingLeft, gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.RingRight),
}; };
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);
} }
} }
} }
@ -193,9 +195,8 @@ internal sealed class InventoryUtils
public unsafe void Discard(InventoryItem* item) public unsafe void Discard(InventoryItem* item)
{ {
if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID) || if (_listManager.IsBlacklisted(item->ItemId))
InternalConfiguration.UltimateWeapons.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

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
namespace ARDiscard.GameData; namespace ARDiscard.GameData;
@ -11,9 +11,9 @@ internal sealed class ItemCache
{ {
private readonly Dictionary<uint, CachedItemInfo> _items = new(); private readonly Dictionary<uint, CachedItemInfo> _items = new();
public ItemCache(IDataManager dataManager) public ItemCache(IDataManager dataManager, ListManager listManager)
{ {
foreach (var item in dataManager.GetExcelSheet<Item>()!) foreach (var item in dataManager.GetExcelSheet<Item>())
{ {
if (item.RowId == 0) if (item.RowId == 0)
continue; continue;
@ -23,35 +23,36 @@ internal sealed class ItemCache
ItemId = item.RowId, ItemId = item.RowId,
Name = item.Name.ToString(), Name = item.Name.ToString(),
IconId = item.Icon, IconId = item.Icon,
ILvl = item.LevelItem.Row, ILvl = item.LevelItem.RowId,
Rarity = item.Rarity, Rarity = item.Rarity,
IsUnique = item.IsUnique, IsUnique = item.IsUnique,
IsUntradable = item.IsUntradable, IsUntradable = item.IsUntradable,
IsIndisposable = item.IsIndisposable, IsIndisposable = item.IsIndisposable,
Level = item.LevelEquip, Level = item.LevelEquip,
UiCategory = item.ItemUICategory.Row, UiCategory = item.ItemUICategory.RowId,
UiCategoryName = item.ItemUICategory.Value!.Name.ToString(), UiCategoryName = item.ItemUICategory.Value.Name.ToString(),
EquipSlotCategory = item.EquipSlotCategory.Row, EquipSlotCategory = item.EquipSlotCategory.RowId,
}; };
if (item is { Rarity: 3, MateriaSlotCount: 3, RowId: < 33154 or > 33358 }) if (item is { Rarity: 3, MateriaSlotCount: 3, RowId: < 33154 or > 33358 })
{ listManager.AddToInternalBlacklist(item.RowId);
InternalConfiguration.UltimateWeapons.Add(item.RowId);
} if (item is { ItemSearchCategory.RowId: 79, ItemUICategory.RowId: >= 101 and <= 104 })
listManager.AddToInternalBlacklist(item.RowId);
} }
foreach (var shopItem in dataManager.GetExcelSheet<GilShopItem>()!) foreach (var shopItem in dataManager.GetSubrowExcelSheet<GilShopItem>().SelectMany(x => x))
{ {
// exclude base ARR relics, not strictly necessary since we don't allow discarding weapons anyway // exclude base ARR relics, not strictly necessary since we don't allow discarding weapons anyway
if (shopItem.Item.Value!.Rarity == 4) if (shopItem.Item.Value.Rarity == 4)
continue; continue;
// 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.RowId, out CachedItemInfo? cachedItemInfo) ||
cachedItemInfo.CanBeDiscarded()) cachedItemInfo.CanBeDiscarded(listManager))
continue; continue;
if (shopItem.AchievementRequired.Row != 0) if (shopItem.AchievementRequired.RowId != 0)
continue; continue;
// has a quest required to unlock from the shop // has a quest required to unlock from the shop
@ -61,23 +62,38 @@ internal sealed class ItemCache
cachedItemInfo.CanBeBoughtFromCalamitySalvager = true; cachedItemInfo.CanBeBoughtFromCalamitySalvager = true;
} }
// only look at msq + regional side quests foreach (var collectableItem in dataManager.GetSubrowExcelSheet<CollectablesShopItem>().SelectMany(x => x))
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)) if (collectableItem.RowId == 0)
continue;
listManager.AddToInternalWhitelist(collectableItem.Item.RowId);
}
// only look at msq + regional side quests
foreach (var quest in dataManager.GetExcelSheet<Quest>().Where(x =>
x.JournalGenre.ValueNullable?.JournalCategory.ValueNullable?.JournalSection.RowId is 0 or 1 or 3))
{
foreach (var itemRef in quest.Reward.Where(x => x.RowId > 0))
{ {
var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId); var item = itemRef.GetValueOrDefault<Item>();
if (item is { Rarity: 1, ItemAction.Row: 388 } && item.RowId != 38809 && item.RowId != 29679) if (item is { Rarity: 1, ItemAction.RowId: 388 } && item.Value.RowId != 38809 && item.Value.RowId != 29679)
InternalConfiguration.DiscardableGearCoffers.Add(item.RowId); listManager.AddToInternalWhitelist(item.Value.RowId);
} }
} }
MaxDungeonItemLevel = _items.Values.Where(x => x.Rarity == 2)
.Select(x => (int)x.ILvl)
.Max();
} }
private bool CanDiscardItemsFromQuest(LazyRow<Quest> quest) public int MaxDungeonItemLevel { get; }
private bool CanDiscardItemsFromQuest(RowRef<Quest> quest)
{ {
return quest.Row > 0 && return quest is { RowId: > 0, IsValid: true } &&
quest.Value?.JournalGenre.Value?.JournalCategory.Value?.JournalSection quest.ValueNullable?.JournalGenre.ValueNullable?.JournalCategory.ValueNullable?.JournalSection
.Row is 0 or 1 or 6; // pre-EW MSQ, EW MSQ or Job/Class quest .RowId 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;
@ -129,17 +145,15 @@ 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() public bool CanBeDiscarded(IListManager listManager, bool checkConfiguration = true)
{ {
if (InternalConfiguration.BlacklistedItems.Contains(ItemId) || if (listManager.IsBlacklisted(ItemId, checkConfiguration))
InternalConfiguration.UltimateWeapons.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 (InternalConfiguration.WhitelistedItems.Contains(ItemId) || if (listManager.IsWhitelisted(ItemId))
InternalConfiguration.DiscardableGearCoffers.Contains(ItemId))
return true; return true;
return CanBeBoughtFromCalamitySalvager || return CanBeBoughtFromCalamitySalvager ||

View File

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

View File

@ -1,20 +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 static class InternalConfiguration internal sealed class ListManager : IListManager
{ {
/// <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>
public static readonly IReadOnlyList<uint> BlacklistedItems = new List<uint> private ISet<uint> _blacklistedItems = new List<uint>
{ {
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
@ -26,16 +26,13 @@ internal static class InternalConfiguration
38951, // TOP token 38951, // TOP token
} }
.Concat(Enumerable.Range(1, 99).Select(x => (uint)x)) .Concat(Enumerable.Range(1, 99).Select(x => (uint)x))
.ToList() .ToHashSet();
.AsReadOnly();
public static readonly IList<uint> UltimateWeapons = new List<uint>();
/// <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>
public static readonly IReadOnlyList<uint> WhitelistedItems = new List<uint> private ISet<uint> _whitelistedItems = new HashSet<uint>()
{ {
2962, // Onion Doublet 2962, // Onion Doublet
3279, // Onion Gaskins 3279, // Onion Gaskins
@ -190,7 +187,7 @@ internal static class InternalConfiguration
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 // TODO: Add when the weekly restriction is removed 40317, // Unsung Blade of Anabaseios
#endregion #endregion
@ -268,7 +265,56 @@ internal static class InternalConfiguration
32048, // Umbral Levinsand 32048, // Umbral Levinsand
#endregion #endregion
}.AsReadOnly();
public static readonly IList<uint> DiscardableGearCoffers = new List<uint>(); #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

@ -8,38 +8,35 @@ 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; using LLib.ImGui;
namespace ARDiscard.Windows; namespace ARDiscard.Windows;
internal sealed class ConfigWindow : LImGui.LWindow internal sealed class ConfigWindow : LWindow
{ {
private const int ResultLimit = 200; private readonly IDalamudPluginInterface _pluginInterface;
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 string _itemName = string.Empty; private readonly DiscardListTab _discardListTab;
private readonly ExcludedListTab _excludedListTab;
private List<(uint ItemId, string Name)> _searchResults = new(); private List<(uint ItemId, string Name)>? _allItems;
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(DalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache, public ConfigWindow(IDalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache,
IClientState clientState, ICondition condition) IListManager listManager, 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;
@ -52,8 +49,11 @@ internal sealed class ConfigWindow : LImGui.LWindow
MaximumSize = new Vector2(9999, 9999), MaximumSize = new Vector2(9999, 9999),
}; };
_discarding.AddRange(_configuration.DiscardingItems _excludedListTab = new ExcludedListTab(this, itemCache, _configuration.BlacklistedItems, listManager);
.Select(x => (x, itemCache.GetItemName(x))).ToList()); _discardListTab = new DiscardListTab(this, itemCache, _configuration.DiscardingItems)
{
ExcludedTab = _excludedListTab,
};
} }
public override void Draw() public override void Draw()
@ -65,7 +65,10 @@ internal sealed class ConfigWindow : LImGui.LWindow
Save(); Save();
} }
ImGui.SameLine(ImGui.GetWindowWidth() - 115 * ImGuiHelpers.GlobalScale); ImGui.SameLine(ImGui.GetContentRegionAvail().X +
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);
@ -84,6 +87,7 @@ internal sealed class ConfigWindow : LImGui.LWindow
{ {
DrawDiscardList(); DrawDiscardList();
DrawExcludedCharacters(); DrawExcludedCharacters();
DrawExcludedItems();
DrawExperimentalSettings(); DrawExperimentalSettings();
ImGui.EndTabBar(); ImGui.EndTabBar();
@ -94,100 +98,7 @@ internal sealed class ConfigWindow : LImGui.LWindow
{ {
if (ImGui.BeginTabItem("Items to Discard")) if (ImGui.BeginTabItem("Items to Discard"))
{ {
var ws = ImGui.GetWindowSize(); _discardListTab.Draw();
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();
} }
} }
@ -198,7 +109,7 @@ internal sealed class ConfigWindow : LImGui.LWindow
{ {
if (_clientState is { IsLoggedIn: true, LocalContentId: > 0 }) if (_clientState is { IsLoggedIn: true, LocalContentId: > 0 })
{ {
string worldName = _clientState.LocalPlayer?.HomeWorld.GameData?.Name ?? "??"; string worldName = _clientState.LocalPlayer?.HomeWorld.ValueNullable?.Name .ToString() ?? "??";
ImGui.TextWrapped( ImGui.TextWrapped(
$"Current Character: {_clientState.LocalPlayer?.Name} @ {worldName} ({_clientState.LocalContentId:X})"); $"Current Character: {_clientState.LocalPlayer?.Name} @ {worldName} ({_clientState.LocalContentId:X})");
ImGui.Indent(30); ImGui.Indent(30);
@ -269,6 +180,19 @@ internal sealed class ConfigWindow : LImGui.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"))
@ -310,7 +234,8 @@ internal sealed class ConfigWindow : LImGui.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 = Math.Max(0, Math.Min(625, maximumItemLevel)); _configuration.Armoury.MaximumGearItemLevel =
Math.Max(0, Math.Min(_itemCache.MaxDungeonItemLevel, maximumItemLevel));
Save(); Save();
} }
@ -378,26 +303,12 @@ internal sealed class ConfigWindow : LImGui.LWindow
} }
} }
private void UpdateResults() internal List<(uint ItemId, string Name)> EnsureAllItemsLoaded()
{
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()) .Where(x => x.CanBeDiscarded(_listManager, false))
.Select(x => (x.ItemId, x.Name.ToString())) .Select(x => (x.ItemId, x.Name.ToString()))
.ToList(); .ToList();
} }
@ -405,37 +316,18 @@ internal sealed class ConfigWindow : LImGui.LWindow
return _allItems; return _allItems;
} }
private void Save() internal void Save()
{ {
_configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList(); _configuration.DiscardingItems = _discardListTab.ToSavedItems().ToList();
_configuration.BlacklistedItems = _excludedListTab.ToSavedItems().ToList();
_pluginInterface.SavePluginConfig(_configuration); _pluginInterface.SavePluginConfig(_configuration);
ConfigSaved?.Invoke(this, EventArgs.Empty); ConfigSaved?.Invoke(this, EventArgs.Empty);
} }
internal bool AddToDiscardList(uint itemId) internal bool AddToDiscardList(uint itemId) => _discardListTab.AddToDiscardList(itemId);
{
var item = EnsureAllItemsLoaded().SingleOrDefault(x => x.ItemId == itemId);
if (item.ItemId != 0)
{
_discarding.Add(item);
Save();
return true;
}
return false; internal bool RemoveFromDiscardList(uint itemId) => _discardListTab.RemoveFromDiscardList(itemId);
}
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

@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using ARDiscard.GameData;
namespace ARDiscard.Windows
{
internal sealed class DiscardListTab : ItemListTab
{
public DiscardListTab(ConfigWindow parent, ItemCache itemCache, List<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,18 +1,22 @@
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 : LImGui.LWindow internal sealed class DiscardWindow : LWindow
{ {
private readonly InventoryUtils _inventoryUtils; private readonly InventoryUtils _inventoryUtils;
private readonly ItemCache _itemCache; private readonly ItemCache _itemCache;
@ -47,7 +51,7 @@ internal sealed class DiscardWindow : LImGui.LWindow
}; };
} }
public bool Locked { get; set; } = false; public bool Locked { get; set; }
public override void Draw() public override void Draw()
{ {
@ -94,11 +98,15 @@ internal sealed class DiscardWindow : LImGui.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.Count(x => x.Selected) == 0 || !_displayedItems.Any(x => x.Selected) ||
DiscardAllClicked == null); DiscardAllClicked == null);
if (ImGui.Button("Discard all selected items")) if (ImGui.Button("Discard all selected items"))
{ {
@ -115,7 +123,7 @@ internal sealed class DiscardWindow : LImGui.LWindow
{ {
if (_configuration.Preview.ShowIcons) if (_configuration.Preview.ShowIcons)
{ {
IDalamudTextureWrap? icon = _iconCache.GetIcon(displayedItem.IconId); using IDalamudTextureWrap? icon = _iconCache.GetIcon(displayedItem.IconId);
if (icon != null) if (icon != null)
{ {
ImGui.Image(icon.ImGuiHandle, new Vector2(23, 23)); ImGui.Image(icon.ImGuiHandle, new Vector2(23, 23));
@ -148,8 +156,8 @@ internal sealed class DiscardWindow : LImGui.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
@ -162,7 +170,7 @@ internal sealed class DiscardWindow : LImGui.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.ToLower()) .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
} }
@ -187,5 +195,5 @@ internal sealed class DiscardWindow : LImGui.LWindow
public void Login() => RefreshInventory(false); public void Login() => RefreshInventory(false);
public void Logout() => _displayedItems.Clear(); public void Logout(int type, int code) => _displayedItems.Clear();
} }

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using ARDiscard.GameData;
namespace ARDiscard.Windows;
internal sealed class ExcludedListTab : ItemListTab
{
private readonly IListManager _listManager;
public ExcludedListTab(ConfigWindow parent, ItemCache itemCache, List<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

@ -0,0 +1,161 @@
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,30 +1,95 @@
{ {
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net7.0-windows7.0": { "net8.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.12, )", "requested": "[11.0.0, )",
"resolved": "2.1.12", "resolved": "11.0.0",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" "contentHash": "bjT7XUlhIJSmsE/O76b7weUX+evvGQctbQB8aKXt94o+oPWxHpCepxAGMs7Thow3AzCyqWs7cOpp9/2wcgRRQA=="
},
"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.1.0, )" "ECommons": "[2.2.0.2, )"
} }
}, },
"ecommons": { "ecommons": {
"type": "Project" "type": "Project"
}, },
"llib": { "llib": {
"type": "Project" "type": "Project",
"dependencies": {
"DalamudPackager": "[11.0.0, )"
}
} }
} }
} }

@ -1 +1 @@
Subproject commit 7cb54772e3a4a60ad02520e898d1ed0e82b2a751 Subproject commit 069cf988b5da8657bc13ade73bfcbc2675601023

@ -1 +1 @@
Subproject commit f1c688a0599b41d70230021328a575da7351cf91 Subproject commit 974a0ef35536b7f0a10a278539f7136d6966baa3

2
LLib

@ -1 +1 @@
Subproject commit 865a6080319f8ccbcd5fd5b0004404822b6e60d4 Subproject commit e4bbc05ede6f6f01e7028b24614ed8cb333e909c

7
global.json Normal file
View File

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