Compare commits

...

18 Commits
v4.4 ... master

Author SHA1 Message Date
b92bff2455
Version bump 2024-11-09 12:19:09 +01:00
6437692c0a
Update submodules 2024-11-09 12:18:43 +01:00
de4c2ab9f3
Add collectables to discardable items 2024-11-09 12:14:58 +01:00
3bd6b6fbca
Remove Red Onion Helm from internal blacklist 2024-11-09 12:05:37 +01:00
cfbd064d4a
Automatically adjust max gear item level based on the highest ilvl green item (dungeon drop) 2024-07-19 19:20:31 +02:00
d730e2c039
Fix confirm check 2024-07-05 21:48:21 +02:00
ed2f8b9c86
API 10 2024-07-02 19:11:28 +02:00
33c9facad4
Replace deprecated gearset accessors 2024-06-26 01:54:12 +02:00
32775ac409
Allow auto-discarding irregular tomestones 2024-06-26 01:53:05 +02:00
51d94c923a
Ensure no two discard processes run at the same time 2024-04-20 22:16:25 +02:00
7396aa97d1
Changes for Patch 6.58 items 2024-04-14 16:23:51 +02:00
e0e69e9b6a
Add sub parts to internal blacklist, add config for additional blacklisted items 2024-03-30 19:29:43 +01:00
3c986562b2
NET 8 2024-03-21 21:59:58 +01:00
1ee347f0ba
Add Sharlayan Cabinet/Wardrobe as discardable 2024-02-29 11:03:37 +01:00
56f6e949fc
Adjust log levels 2024-02-06 18:26:37 +01:00
a94c5077fc
Allow gear coffers to be discarded 2024-02-04 09:38:58 +01:00
0d7f9f4aa4
Add an option to never discard items crafted manually 2024-02-03 21:14:16 +01:00
24b76b1b49
Fix not all armoury items being discarded 2024-01-25 09:48:50 +01:00
23 changed files with 1772 additions and 388 deletions

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/9.0.2">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <Version>6.3</Version>
<Version>4.4</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));
} }
} }
@ -199,11 +227,11 @@ public class AutoDiscardPlogon : IDalamudPlugin
var addon = GetDiscardAddon(); var addon = GetDiscardAddon();
if (addon != null) if (addon != null)
{ {
_pluginLog.Information("Addon is visible, clicking 'yes'"); _pluginLog.Verbose("Addon is visible, clicking 'yes'");
((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));
} }
} }
@ -249,16 +277,16 @@ public class AutoDiscardPlogon : IDalamudPlugin
} }
else else
{ {
_pluginLog.Information( _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,16 +5,18 @@ 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();
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
{ {
@ -25,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
@ -43,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,61 +14,65 @@ 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 (!_configWindow.CanItemBeConfigured(args.ItemId))
return;
if (_configuration.DiscardingItems.Contains(args.ItemId))
args.AddCustomItem(_removeInventoryItem);
else if (_itemCache.TryGetItem(args.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded())
args.AddCustomItem(_addInventoryItem);
}
private void OpenGameObjectContextMenu(GameObjectContextMenuOpenArgs args)
{ {
if (!IsEnabled()) if (args.AddonName is not ("Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard") ||
targetInventory.TargetItem == null)
return; return;
if (args.ParentAddonName is not "ChatLog") var item = targetInventory.TargetItem.Value;
if (!_configWindow.CanItemBeConfigured(item.ItemId))
return;
if (_configuration.DiscardingItems.Contains(item.ItemId))
args.AddMenuItem(_removeInventoryItem);
else if (_itemCache.TryGetItem(item.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded(_listManager))
args.AddMenuItem(_addInventoryItem);
}
else
{
if (args.AddonName is not "ChatLog")
return; return;
uint itemId = (uint)_gameGui.HoveredItem; uint itemId = (uint)_gameGui.HoveredItem;
@ -79,13 +83,31 @@ internal sealed class ContextMenuIntegration : IDisposable
itemId -= 500_000; itemId -= 500_000;
if (_configuration.DiscardingItems.Contains(itemId)) if (_configuration.DiscardingItems.Contains(itemId))
args.AddCustomItem(new GameObjectContextMenuItem(_removeItemPayload, _ => RemoveFromDiscardList(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) && else if (_itemCache.TryGetItem(itemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded()) cachedItemInfo.CanBeDiscarded(_listManager))
args.AddCustomItem(new GameObjectContextMenuItem(_addItemPayload, _ => AddToDiscardList(itemId))); {
args.AddMenuItem(new MenuItem
{
Prefix = _addInventoryItem.Prefix,
PrefixColor = _addInventoryItem.PrefixColor,
Name = _addInventoryItem.Name,
OnClicked = _ => AddToDiscardList(itemId),
});
}
}
} }
private void AddToDiscardList(InventoryContextMenuItemSelectedArgs args) => AddToDiscardList(args.ItemId); private void AddToDiscardList(IMenuItemClickedArgs args) =>
AddToDiscardList(((MenuTargetInventory)args.Target).TargetItem!.Value.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

@ -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.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

@ -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,46 +12,48 @@ 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;
} }
@ -75,31 +78,32 @@ 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, uint> itemCounts, List<uint>? gearsetItems)
{ {
List<ItemWrapper> items = new();
if (condition) if (condition)
{ {
foreach (InventoryType inventoryType in inventoryTypes) foreach (InventoryType inventoryType in inventoryTypes)
return GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems); items.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems));
} }
return new List<ItemWrapper>(); 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, uint> itemCounts,
IReadOnlyList<uint>? gearsetItems) IReadOnlyList<uint>? gearsetItems)
{ {
@ -109,34 +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 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 (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)
continue;
//PluginLog.Verbose($"{i} → {item->ItemID}"); //PluginLog.Verbose($"{i} → {item->ItemID}");
if (_configuration.DiscardingItems.Contains(item->ItemID)) if (_configuration.DiscardingItems.Contains(item->ItemId))
{ {
_pluginLog.Information( _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 });
} }
} }
@ -146,7 +152,7 @@ internal sealed class InventoryUtils
} }
} }
return toDiscard; return toDiscard.AsReadOnly();
} }
private unsafe List<uint>? GetAllGearsetItems() private unsafe List<uint>? GetAllGearsetItems()
@ -163,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);
} }
} }
} }
@ -189,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

@ -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) public ItemCache(IDataManager dataManager, ListManager listManager)
{ {
foreach (var item in dataManager.GetExcelSheet<Item>()!) foreach (var item in dataManager.GetExcelSheet<Item>()!)
{ {
@ -35,9 +35,10 @@ internal sealed class ItemCache
}; };
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.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>()!)
@ -48,7 +49,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()) cachedItemInfo.CanBeDiscarded(listManager))
continue; continue;
if (shopItem.AchievementRequired.Row != 0) if (shopItem.AchievementRequired.Row != 0)
@ -60,8 +61,33 @@ internal sealed class ItemCache
cachedItemInfo.CanBeBoughtFromCalamitySalvager = true; cachedItemInfo.CanBeBoughtFromCalamitySalvager = true;
} }
foreach (var collectableItem in dataManager.GetExcelSheet<CollectablesShopItem>()!)
{
if (collectableItem.RowId == 0)
continue;
listManager.AddToInternalWhitelist(collectableItem.Item.Row);
} }
// 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 &&
@ -118,15 +144,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) || InternalConfiguration.UltimateWeapons.Contains(ItemId)) if (listManager.IsBlacklisted(ItemId, checkConfiguration))
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))
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
@ -211,6 +208,14 @@ internal static class InternalConfiguration
#endregion #endregion
#region Eureka Lootboxes
21831, // Sharlayan Cabinet
21832, // Sharlayan Wardrobe
22306, // Eurekan Potion
#endregion
#region Unapproved Firmament materials #region Unapproved Firmament materials
32005, // Switch 32005, // Switch
@ -260,5 +265,56 @@ internal static class InternalConfiguration
32048, // Umbral Levinsand 32048, // Umbral Levinsand
#endregion #endregion
}.AsReadOnly();
#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();
} }
} }
@ -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"))
@ -284,7 +208,8 @@ internal sealed class ConfigWindow : LImGui.LWindow
ImGui.Indent(30); ImGui.Indent(30);
bool mainHandOffHand = _configuration.Armoury.CheckMainHandOffHand; bool mainHandOffHand = _configuration.Armoury.CheckMainHandOffHand;
if (ImGui.Checkbox("Discard when items are found in Main Hand/Off Hand (Weapons and Tools)", ref mainHandOffHand)) if (ImGui.Checkbox("Discard when items are found in Main Hand/Off Hand (Weapons and Tools)",
ref mainHandOffHand))
{ {
_configuration.Armoury.CheckMainHandOffHand = mainHandOffHand; _configuration.Armoury.CheckMainHandOffHand = mainHandOffHand;
Save(); Save();
@ -309,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();
} }
@ -348,6 +274,15 @@ internal sealed class ConfigWindow : LImGui.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;
@ -368,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();
} }
@ -395,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();
} }

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": "[2.1.13, )",
"resolved": "2.1.12", "resolved": "2.1.13",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ=="
},
"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": "[2.1.13, )"
}
} }
} }
} }

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

@ -1 +1 @@
Subproject commit f1c688a0599b41d70230021328a575da7351cf91 Subproject commit 07c07e09101e0c8a5f770dbea2ca6eafea5ec705

2
LLib

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

7
global.json Normal file
View File

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