Compare commits

..

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

23 changed files with 389 additions and 1914 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,67 @@
<Project Sdk="Dalamud.NET.Sdk/9.0.2">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>6.3</Version>
<TargetFramework>net7.0-windows</TargetFramework>
<Version>4.2</Version>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>dist</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DebugType>portable</DebugType>
</PropertyGroup>
<Import Project="..\LLib\LLib.targets"/>
<Import Project="..\LLib\RenameZip.targets"/>
<PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj"/>
<ProjectReference Include="..\ECommons\ECommons\ECommons.csproj"/>
<ProjectReference Include="..\LLib\LLib.csproj"/>
</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>

View File

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

View File

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

View File

@ -5,18 +5,16 @@ namespace ARDiscard;
internal sealed class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 3;
public int Version { get; set; } = 1;
public bool RunAfterVenture { get; set; }
public bool RunBeforeLogout { get; set; }
public List<uint> DiscardingItems { get; set; } = new();
public List<uint> BlacklistedItems { get; set; } = new();
public List<CharacterInfo> ExcludedCharacters { get; set; } = new();
public ArmouryConfiguration Armoury { get; set; } = new();
public ContextMenuConfiguration ContextMenu { get; set; } = new();
public PreviewConfiguration Preview { get; set; } = new();
public uint IgnoreItemCountWhenAbove { get; set; } = 50;
public bool IgnoreItemWithSignature { get; set; }
public sealed class CharacterInfo
{
@ -27,17 +25,16 @@ internal sealed class Configuration : IPluginConfiguration
public sealed class ArmouryConfiguration
{
public bool DiscardFromArmouryChest { get; set; }
public bool CheckMainHandOffHand { get; set; }
public bool CheckLeftSideGear { get; set; }
public bool CheckRightSideGear { get; set; }
public bool DiscardFromArmouryChest { get; set; } = false;
public bool CheckLeftSideGear { get; set; } = false;
public bool CheckRightSideGear { get; set; } = false;
public int MaximumGearItemLevel { get; set; } = 45;
}
public sealed class ContextMenuConfiguration
{
public bool Enabled { get; set; } = true;
public bool OnlyWhenConfigIsOpen { get; set; }
public bool Enabled { get; set; } = false;
public bool OnlyWhenConfigIsOpen { get; set; } = true;
}
public sealed class PreviewConfiguration
@ -45,12 +42,4 @@ internal sealed class Configuration : IPluginConfiguration
public bool GroupByCategory { 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 ARDiscard.GameData;
using ARDiscard.Windows;
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text;
using Dalamud.ContextMenu;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
namespace ARDiscard;
@ -14,100 +14,78 @@ internal sealed class ContextMenuIntegration : IDisposable
private readonly IChatGui _chatGui;
private readonly ItemCache _itemCache;
private readonly Configuration _configuration;
private readonly IListManager _listManager;
private readonly ConfigWindow _configWindow;
private readonly IGameGui _gameGui;
private readonly IContextMenu _contextMenu;
private readonly MenuItem _addInventoryItem;
private readonly MenuItem _removeInventoryItem;
private readonly SeString _addItemPayload;
private readonly SeString _removeItemPayload;
private readonly InventoryContextMenuItem _addInventoryItem;
private readonly InventoryContextMenuItem _removeInventoryItem;
private readonly DalamudContextMenu _dalamudContextMenu;
public ContextMenuIntegration(IChatGui chatGui, ItemCache itemCache, Configuration configuration,
IListManager listManager, ConfigWindow configWindow, IGameGui gameGui, IContextMenu contextMenu)
public ContextMenuIntegration(DalamudPluginInterface pluginInterface, IChatGui chatGui, ItemCache itemCache,
Configuration configuration, ConfigWindow configWindow, IGameGui gameGui)
{
_chatGui = chatGui;
_itemCache = itemCache;
_configuration = configuration;
_listManager = listManager;
_configWindow = configWindow;
_gameGui = gameGui;
_contextMenu = contextMenu;
_addInventoryItem = new MenuItem
{
Prefix = (SeIconChar)57439,
PrefixColor = 52,
Name = "Add to Auto Discard List",
OnClicked = AddToDiscardList,
};
_removeInventoryItem = new MenuItem
{
Prefix = (SeIconChar)57439,
PrefixColor = 52,
Name = "Remove from Auto Discard List",
OnClicked = RemoveFromDiscardList,
};
_addItemPayload =
new SeString(new UIForegroundPayload(52))
.Append($"\ue05f ")
.Append(new UIForegroundPayload(0)).Append("Add to Auto Discard List");
_removeItemPayload = new SeString(new UIForegroundPayload(52))
.Append($"\ue05f ")
.Append(new UIForegroundPayload(0)).Append("Remove from Auto Discard List");
_addInventoryItem = new InventoryContextMenuItem(_addItemPayload, AddToDiscardList);
_removeInventoryItem = new InventoryContextMenuItem(_removeItemPayload, RemoveFromDiscardList);
_contextMenu.OnMenuOpened += MenuOpened;
_dalamudContextMenu = new(pluginInterface);
_dalamudContextMenu.OnOpenInventoryContextMenu += OpenInventoryContextMenu;
_dalamudContextMenu.OnOpenGameObjectContextMenu += OpenGameObjectContextMenu;
}
private void MenuOpened(IMenuOpenedArgs args)
private void OpenInventoryContextMenu(InventoryContextMenuOpenArgs args)
{
if (!IsEnabled())
return;
if (args.Target is MenuTargetInventory targetInventory)
{
if (args.AddonName is not ("Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard") ||
targetInventory.TargetItem == null)
return;
if (args.ParentAddonName is not ("Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard"))
return;
var item = targetInventory.TargetItem.Value;
if (!_configWindow.CanItemBeConfigured(item.ItemId))
return;
if (!_configWindow.CanItemBeConfigured(args.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;
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),
});
}
}
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 AddToDiscardList(IMenuItemClickedArgs args) =>
AddToDiscardList(((MenuTargetInventory)args.Target).TargetItem!.Value.ItemId);
private void OpenGameObjectContextMenu(GameObjectContextMenuOpenArgs args)
{
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)
{
@ -126,8 +104,7 @@ internal sealed class ContextMenuIntegration : IDisposable
}
}
private void RemoveFromDiscardList(IMenuItemClickedArgs args) =>
RemoveFromDiscardList(((MenuTargetInventory)args.Target).TargetItem!.Value.ItemId);
private void RemoveFromDiscardList(InventoryContextMenuItemSelectedArgs args) => RemoveFromDiscardList(args.ItemId);
private void RemoveFromDiscardList(uint itemId)
{
@ -159,6 +136,8 @@ internal sealed class ContextMenuIntegration : IDisposable
public void Dispose()
{
_contextMenu.OnMenuOpened -= MenuOpened;
_dalamudContextMenu.OnOpenGameObjectContextMenu -= OpenGameObjectContextMenu;
_dalamudContextMenu.OnOpenInventoryContextMenu -= OpenInventoryContextMenu;
_dalamudContextMenu.Dispose();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -12,48 +11,40 @@ namespace ARDiscard.GameData;
internal sealed class InventoryUtils
{
private static readonly InventoryType[] DefaultInventoryTypes =
[
{
InventoryType.Inventory1,
InventoryType.Inventory2,
InventoryType.Inventory3,
InventoryType.Inventory4
];
private static readonly InventoryType[] MainHandOffHandInventoryTypes =
[
InventoryType.ArmoryMainHand,
InventoryType.ArmoryOffHand
];
};
private static readonly InventoryType[] LeftSideGearInventoryTypes =
[
{
InventoryType.ArmoryHead,
InventoryType.ArmoryBody,
InventoryType.ArmoryHands,
InventoryType.ArmoryLegs,
InventoryType.ArmoryFeets
];
};
private static readonly InventoryType[] RightSideGearInventoryTypes =
[
{
InventoryType.ArmoryEar,
InventoryType.ArmoryNeck,
InventoryType.ArmoryWrist,
InventoryType.ArmoryRings
];
};
private static readonly IReadOnlyList<uint> NoGearsetItems = new List<uint>();
private readonly Configuration _configuration;
private readonly ItemCache _itemCache;
private readonly IListManager _listManager;
private readonly IPluginLog _pluginLog;
public InventoryUtils(Configuration configuration, ItemCache itemCache, IListManager listManager, IPluginLog pluginLog)
public InventoryUtils(Configuration configuration, ItemCache itemCache, IPluginLog pluginLog)
{
_configuration = configuration;
_itemCache = itemCache;
_listManager = listManager;
_pluginLog = pluginLog;
}
@ -69,41 +60,36 @@ internal sealed class InventoryUtils
if (_configuration.Armoury.DiscardFromArmouryChest)
{
var gearsetItems = GetAllGearsetItems();
toDiscard.AddRange(GetArmouryItemsToDiscard(_configuration.Armoury.CheckMainHandOffHand, inventoryManager,
MainHandOffHandInventoryTypes, itemCounts, gearsetItems));
toDiscard.AddRange(GetArmouryItemsToDiscard(_configuration.Armoury.CheckLeftSideGear, inventoryManager,
LeftSideGearInventoryTypes, itemCounts, gearsetItems));
toDiscard.AddRange(GetArmouryItemsToDiscard(_configuration.Armoury.CheckRightSideGear, inventoryManager,
RightSideGearInventoryTypes, itemCounts, gearsetItems));
if (_configuration.Armoury.CheckLeftSideGear)
{
foreach (InventoryType inventoryType in LeftSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts,
gearsetItems));
}
if (_configuration.Armoury.CheckRightSideGear)
{
foreach (InventoryType inventoryType in RightSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts,
gearsetItems));
}
}
return toDiscard
.Where(x => itemCounts[x.InventoryItem->ItemId] < _configuration.IgnoreItemCountWhenAbove)
.Where(x => itemCounts[x.InventoryItem->ItemID] < _configuration.IgnoreItemCountWhenAbove)
.ToList();
}
private unsafe ReadOnlyCollection<ItemWrapper> GetArmouryItemsToDiscard(bool condition, InventoryManager* inventoryManager,
InventoryType[] inventoryTypes, Dictionary<uint, uint> itemCounts, List<uint>? gearsetItems)
{
List<ItemWrapper> items = new();
if (condition)
{
foreach (InventoryType inventoryType in inventoryTypes)
items.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems));
}
return items.AsReadOnly();
}
public unsafe InventoryItem* GetNextItemToDiscard(ItemFilter? itemFilter)
{
List<ItemWrapper> allItemsToDiscard = GetAllItemsToDiscard();
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;
}
private unsafe ReadOnlyCollection<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager,
private unsafe IReadOnlyList<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager,
InventoryType inventoryType, Dictionary<uint, uint> itemCounts,
IReadOnlyList<uint>? gearsetItems)
{
@ -113,36 +99,32 @@ internal sealed class InventoryUtils
for (int i = 0; i < container->Size; ++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))
itemCounts[item->ItemId] = itemCount + item->Quantity;
if (itemCounts.TryGetValue(item->ItemID, out uint itemCount))
itemCounts[item->ItemID] = itemCount + item->Quantity;
else
itemCounts[item->ItemId] = item->Quantity;
itemCounts[item->ItemID] = item->Quantity;
if (_listManager.IsBlacklisted(item->ItemId))
if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID))
continue;
if (!_itemCache.TryGetItem(item->ItemId, out ItemCache.CachedItemInfo? itemInfo) ||
!itemInfo.CanBeDiscarded(_listManager))
if (!_itemCache.TryGetItem(item->ItemID, out ItemCache.CachedItemInfo? itemInfo) || !itemInfo.CanBeDiscarded())
continue; // no info, who knows what that item is
// skip gear if we're unable to load gearsets or it is used in a gearset
if (itemInfo.EquipSlotCategory > 0 && (gearsetItems == null || gearsetItems.Contains(item->ItemId)))
if (itemInfo.EquipSlotCategory > 0 && (gearsetItems == null || gearsetItems.Contains(item->ItemID)))
continue;
if (itemInfo is { EquipSlotCategory: > 0, CanBeBoughtFromCalamitySalvager: false } &&
itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel)
continue;
if (_configuration.IgnoreItemWithSignature && item->CrafterContentId != 0)
continue;
//PluginLog.Verbose($"{i} → {item->ItemID}");
if (_configuration.DiscardingItems.Contains(item->ItemId))
if (_configuration.DiscardingItems.Contains(item->ItemID))
{
_pluginLog.Verbose(
$"Found item {item->ItemId} to discard in inventory {inventoryType} in slot {i}");
_pluginLog.Information(
$"Found item {item->ItemID} to discard in inventory {inventoryType} in slot {i}");
toDiscard.Add(new ItemWrapper { InventoryItem = item });
}
}
@ -152,7 +134,7 @@ internal sealed class InventoryUtils
}
}
return toDiscard.AsReadOnly();
return toDiscard;
}
private unsafe List<uint>? GetAllGearsetItems()
@ -169,23 +151,23 @@ internal sealed class InventoryUtils
{
var gearsetItems = new[]
{
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.MainHand),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.OffHand),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Head),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Body),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Hands),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Legs),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Feet),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Ears),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Neck),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.Wrists),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.RingLeft),
gearset->GetItem(RaptureGearsetModule.GearsetItemIndex.RingRight),
gearset->MainHand,
gearset->OffHand,
gearset->Head,
gearset->Body,
gearset->Hands,
gearset->Legs,
gearset->Feet,
gearset->Ears,
gearset->Neck,
gearset->Wrists,
gearset->RingRight,
gearset->RingLeft,
};
foreach (var gearsetItem in gearsetItems)
{
if (gearsetItem.ItemId != 0)
allGearsetItems.Add(gearsetItem.ItemId);
if (gearsetItem.ItemID != 0)
allGearsetItems.Add(gearsetItem.ItemID);
}
}
}
@ -195,8 +177,8 @@ internal sealed class InventoryUtils
public unsafe void Discard(InventoryItem* item)
{
if (_listManager.IsBlacklisted(item->ItemId))
throw new ArgumentException($"Can't discard {item->ItemId}", nameof(item));
if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID))
throw new Exception($"Can't discard {item->ItemID}");
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();
public ItemCache(IDataManager dataManager, ListManager listManager)
public ItemCache(IDataManager dataManager)
{
foreach (var item in dataManager.GetExcelSheet<Item>()!)
{
@ -33,12 +33,6 @@ internal sealed class ItemCache
UiCategoryName = item.ItemUICategory.Value!.Name.ToString(),
EquipSlotCategory = item.EquipSlotCategory.Row,
};
if (item is { Rarity: 3, MateriaSlotCount: 3, RowId: < 33154 or > 33358 })
listManager.AddToInternalBlacklist(item.RowId);
if (item is { ItemSearchCategory.Row: 79, ItemUICategory.Row: >= 101 and <= 104 })
listManager.AddToInternalBlacklist(item.RowId);
}
foreach (var shopItem in dataManager.GetExcelSheet<GilShopItem>()!)
@ -49,7 +43,7 @@ internal sealed class ItemCache
// the item can be discarded already
if (!_items.TryGetValue(shopItem.Item.Row, out CachedItemInfo? cachedItemInfo) ||
cachedItemInfo.CanBeDiscarded(listManager))
cachedItemInfo.CanBeDiscarded())
continue;
if (shopItem.AchievementRequired.Row != 0)
@ -61,33 +55,8 @@ internal sealed class ItemCache
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)
{
return quest.Row > 0 &&
@ -144,15 +113,18 @@ internal sealed class ItemCache
public required string UiCategoryName { get; init; }
public required uint EquipSlotCategory { get; init; }
public bool CanBeDiscarded(IListManager listManager, bool checkConfiguration = true)
public bool CanBeDiscarded()
{
if (listManager.IsBlacklisted(ItemId, checkConfiguration))
if (InternalConfiguration.BlacklistedItems.Contains(ItemId))
return false;
if (UiCategory is UiCategories.Currency or UiCategories.Crystals or UiCategories.Unobtainable)
return false;
if (listManager.IsWhitelisted(ItemId))
if (EquipSlotCategory is 1 or 2 or 13 or 14)
return false;
if (InternalConfiguration.WhitelistedItems.Contains(ItemId))
return true;
return CanBeBoughtFromCalamitySalvager ||

View File

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

View File

@ -8,35 +8,38 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using ECommons;
using ImGuiNET;
using LLib.ImGui;
using LLib;
namespace ARDiscard.Windows;
internal sealed class ConfigWindow : LWindow
internal sealed class ConfigWindow : LImGui.LWindow
{
private readonly IDalamudPluginInterface _pluginInterface;
private const int ResultLimit = 200;
private readonly DalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly ItemCache _itemCache;
private readonly IListManager _listManager;
private readonly IClientState _clientState;
private readonly ICondition _condition;
private readonly DiscardListTab _discardListTab;
private readonly ExcludedListTab _excludedListTab;
private string _itemName = string.Empty;
private List<(uint ItemId, string Name)>? _allItems;
private List<(uint ItemId, string Name)> _searchResults = new();
private List<(uint ItemId, string Name)> _discarding = new();
private List<(uint ItemId, string Name)>? _allItems = null;
private bool _resetKeyboardFocus = true;
public event EventHandler? DiscardNowClicked;
public event EventHandler? ConfigSaved;
public ConfigWindow(IDalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache,
IListManager listManager, IClientState clientState, ICondition condition)
public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache,
IClientState clientState, ICondition condition)
: base("Auto Discard###AutoDiscardConfig")
{
_pluginInterface = pluginInterface;
_configuration = configuration;
_itemCache = itemCache;
_listManager = listManager;
_clientState = clientState;
_condition = condition;
@ -49,11 +52,8 @@ internal sealed class ConfigWindow : LWindow
MaximumSize = new Vector2(9999, 9999),
};
_excludedListTab = new ExcludedListTab(this, itemCache, _configuration.BlacklistedItems, listManager);
_discardListTab = new DiscardListTab(this, itemCache, _configuration.DiscardingItems)
{
ExcludedTab = _excludedListTab,
};
_discarding.AddRange(_configuration.DiscardingItems
.Select(x => (x, itemCache.GetItemName(x))).ToList());
}
public override void Draw()
@ -65,10 +65,7 @@ internal sealed class ConfigWindow : LWindow
Save();
}
ImGui.SameLine(ImGui.GetContentRegionAvail().X +
ImGui.GetStyle().WindowPadding.X -
ImGui.CalcTextSize("Preview Discards").X -
ImGui.GetStyle().ItemSpacing.X);
ImGui.SameLine(ImGui.GetWindowWidth() - 115 * ImGuiHelpers.GlobalScale);
ImGui.BeginDisabled(!_clientState.IsLoggedIn ||
!(_condition[ConditionFlag.NormalConditions] || _condition[ConditionFlag.Mounted]) ||
DiscardNowClicked == null);
@ -87,7 +84,6 @@ internal sealed class ConfigWindow : LWindow
{
DrawDiscardList();
DrawExcludedCharacters();
DrawExcludedItems();
DrawExperimentalSettings();
ImGui.EndTabBar();
@ -98,7 +94,100 @@ internal sealed class ConfigWindow : LWindow
{
if (ImGui.BeginTabItem("Items to Discard"))
{
_discardListTab.Draw();
var ws = ImGui.GetWindowSize();
if (ImGui.BeginChild("Left", new Vector2(Math.Max(10, ws.X / 2), -1), true))
{
if (!string.IsNullOrEmpty(_itemName))
{
if (_searchResults.Count > ResultLimit)
ImGui.Text($"Search ({ResultLimit:N0} out of {_searchResults.Count:N0} matches)");
else
ImGui.Text($"Search ({_searchResults.Count:N0} matches)");
}
else
ImGui.Text("Search");
ImGui.SetNextItemWidth(ws.X / 2 - 20);
if (_resetKeyboardFocus)
{
ImGui.SetKeyboardFocusHere();
_resetKeyboardFocus = false;
}
string previousName = _itemName;
if (ImGui.InputText("", ref _itemName, 256, ImGuiInputTextFlags.EnterReturnsTrue))
{
_resetKeyboardFocus = true;
if (_searchResults.Count > 0)
{
var itemToAdd = _searchResults.FirstOrDefault();
if (_discarding.All(x => x.ItemId != itemToAdd.ItemId))
{
_discarding.Add(itemToAdd);
}
else
{
_discarding.Remove(itemToAdd);
}
Save();
}
}
if (previousName != _itemName)
UpdateResults();
ImGui.Separator();
if (string.IsNullOrEmpty(_itemName))
{
ImGui.Text("Type item name...");
}
foreach (var (id, name) in _searchResults.Take(ResultLimit))
{
bool selected = _discarding.Any(x => x.Item1 == id);
if (ImGui.Selectable(name, selected))
{
if (!selected)
{
_discarding.Add((id, name));
}
else
{
_discarding.Remove((id, name));
}
Save();
}
}
}
ImGui.EndChild();
ImGui.SameLine();
if (ImGui.BeginChild("Right", new Vector2(-1, -1), true, ImGuiWindowFlags.NoSavedSettings))
{
ImGui.Text("Items that will be automatically discarded");
ImGui.Separator();
List<(uint, string)> toRemove = new();
foreach (var (id, name) in _discarding.OrderBy(x => x.Name.ToLower()))
{
if (ImGui.Selectable(name, true))
toRemove.Add((id, name));
}
if (toRemove.Count > 0)
{
foreach (var tr in toRemove)
_discarding.Remove(tr);
Save();
}
}
ImGui.EndChild();
ImGui.EndTabItem();
}
}
@ -180,19 +269,6 @@ internal sealed class ConfigWindow : LWindow
}
}
private void DrawExcludedItems()
{
if (ImGui.BeginTabItem("Excluded Items"))
{
ImGui.Text(
"Items configured here will never be discarded, and have priority over the 'Items to Discard' tab.");
ImGui.Text("Some items (such as Ultimate weapons) can not be un-blacklisted.");
_excludedListTab.Draw();
ImGui.EndTabItem();
}
}
private void DrawExperimentalSettings()
{
if (ImGui.BeginTabItem("Experimental Settings"))
@ -207,14 +283,6 @@ internal sealed class ConfigWindow : LWindow
ImGui.BeginDisabled(!discardFromArmouryChest);
ImGui.Indent(30);
bool mainHandOffHand = _configuration.Armoury.CheckMainHandOffHand;
if (ImGui.Checkbox("Discard when items are found in Main Hand/Off Hand (Weapons and Tools)",
ref mainHandOffHand))
{
_configuration.Armoury.CheckMainHandOffHand = mainHandOffHand;
Save();
}
bool leftSideGear = _configuration.Armoury.CheckLeftSideGear;
if (ImGui.Checkbox("Discard when items are found in Head/Body/Hands/Legs/Feet", ref leftSideGear))
{
@ -234,8 +302,7 @@ internal sealed class ConfigWindow : LWindow
if (ImGui.InputInt("Ignore items >= this ilvl (Armoury Chest only)",
ref maximumItemLevel))
{
_configuration.Armoury.MaximumGearItemLevel =
Math.Max(0, Math.Min(_itemCache.MaxDungeonItemLevel, maximumItemLevel));
_configuration.Armoury.MaximumGearItemLevel = Math.Max(0, Math.Min(625, maximumItemLevel));
Save();
}
@ -274,15 +341,6 @@ internal sealed class ConfigWindow : LWindow
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();
bool groupPreviewByCategory = _configuration.Preview.GroupByCategory;
@ -303,12 +361,26 @@ internal sealed class ConfigWindow : LWindow
}
}
internal List<(uint ItemId, string Name)> EnsureAllItemsLoaded()
private void UpdateResults()
{
if (string.IsNullOrEmpty(_itemName))
_searchResults = new();
else
{
_searchResults = EnsureAllItemsLoaded().Where(x =>
x.Name.Contains(_itemName, StringComparison.CurrentCultureIgnoreCase)
|| (uint.TryParse(_itemName, out uint itemId) && x.ItemId == itemId))
.OrderBy(x => _itemName.EqualsIgnoreCase(x.Name) ? string.Empty : x.Name)
.ToList();
}
}
private List<(uint ItemId, string Name)> EnsureAllItemsLoaded()
{
if (_allItems == null)
{
_allItems = _itemCache.AllItems
.Where(x => x.CanBeDiscarded(_listManager, false))
.Where(x => x.CanBeDiscarded())
.Select(x => (x.ItemId, x.Name.ToString()))
.ToList();
}
@ -316,18 +388,37 @@ internal sealed class ConfigWindow : LWindow
return _allItems;
}
internal void Save()
private void Save()
{
_configuration.DiscardingItems = _discardListTab.ToSavedItems().ToList();
_configuration.BlacklistedItems = _excludedListTab.ToSavedItems().ToList();
_configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList();
_pluginInterface.SavePluginConfig(_configuration);
ConfigSaved?.Invoke(this, EventArgs.Empty);
}
internal bool AddToDiscardList(uint itemId) => _discardListTab.AddToDiscardList(itemId);
internal bool AddToDiscardList(uint itemId)
{
var item = EnsureAllItemsLoaded().SingleOrDefault(x => x.ItemId == itemId);
if (item.ItemId != 0)
{
_discarding.Add(item);
Save();
return true;
}
internal bool RemoveFromDiscardList(uint itemId) => _discardListTab.RemoveFromDiscardList(itemId);
return false;
}
internal bool RemoveFromDiscardList(uint itemId)
{
if (_discarding.RemoveAll(x => x.ItemId == itemId) > 0)
{
Save();
return true;
}
return false;
}
public bool CanItemBeConfigured(uint itemId)
{

View File

@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using ARDiscard.GameData;
namespace ARDiscard.Windows
{
internal sealed class DiscardListTab : ItemListTab
{
public DiscardListTab(ConfigWindow parent, ItemCache itemCache, List<uint> initialItems)
: base(parent, itemCache, initialItems)
{
}
protected override string RightSideLabel => "Items that will be automatically discarded";
internal required ExcludedListTab ExcludedTab { private get; init; }
public IEnumerable<uint> ToSavedItems()
{
SelectedItems.RemoveAll(x => ExcludedTab.IsBlacklistedInConfiguration(x.ItemId));
return SelectedItems.Select(x => x.ItemId);
}
protected override (string, bool) AsLeftSideDisplay(uint itemId, string name)
{
if (ExcludedTab.IsBlacklistedInConfiguration(itemId))
return ($"{name} (Blacklisted/Excluded Item)", false);
return base.AsLeftSideDisplay(itemId, name);
}
internal bool AddToDiscardList(uint itemId)
{
var item = EnsureAllItemsLoaded().SingleOrDefault(x => x.ItemId == itemId);
if (item.ItemId != 0)
{
SelectedItems.Add(item);
Save();
return true;
}
return false;
}
internal bool RemoveFromDiscardList(uint itemId)
{
if (SelectedItems.RemoveAll(x => x.ItemId == itemId) > 0)
{
Save();
return true;
}
return false;
}
}
}

View File

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

View File

@ -1,35 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using ARDiscard.GameData;
namespace ARDiscard.Windows;
internal sealed class ExcludedListTab : ItemListTab
{
private readonly IListManager _listManager;
public ExcludedListTab(ConfigWindow parent, ItemCache itemCache, List<uint> initialItems, IListManager listManager)
: base(parent, itemCache, initialItems)
{
_listManager = listManager;
SelectedItems.AddRange(
listManager.GetInternalBlacklist()
.Select(x => (x, itemCache.GetItemName(x)))
.Where(x => x.Item1 >= 100 && !string.IsNullOrEmpty(x.Item2)));
}
protected override string RightSideLabel => "Items that will never be discarded";
public IEnumerable<uint> ToSavedItems()
{
return SelectedItems
.Select(x => x.ItemId)
.Where(x => !_listManager.IsBlacklisted(x, false));
}
public bool IsBlacklistedInConfiguration(uint itemId)
=> !_listManager.IsBlacklisted(itemId, false) && SelectedItems.Any(x => x.ItemId == itemId);
protected override (string Name, bool Enabled) AsLeftSideDisplay(uint itemId, string name)
=> (name, !_listManager.IsBlacklisted(itemId, false));
}

View File

@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ARDiscard.GameData;
using ImGuiNET;
namespace ARDiscard.Windows;
internal abstract class ItemListTab
{
private const int ResultLimit = 200;
private readonly ConfigWindow _parent;
private List<(uint ItemId, string Name)> _searchResults = new();
private string _itemName = string.Empty;
private bool _resetKeyboardFocus = true;
protected ItemListTab(ConfigWindow parent, ItemCache itemCache, List<uint> initialItems)
{
_parent = parent;
SelectedItems.AddRange(initialItems.Select(x => (x, itemCache.GetItemName(x))).ToList());
}
protected abstract string RightSideLabel { get; }
protected List<(uint ItemId, string Name)> SelectedItems { get; } = new();
public void Draw()
{
var ws = ImGui.GetWindowSize();
if (ImGui.BeginChild("Left", new Vector2(Math.Max(10, ws.X / 2), -1), true))
{
if (!string.IsNullOrEmpty(_itemName))
{
if (_searchResults.Count > ResultLimit)
ImGui.Text($"Search ({ResultLimit:N0} out of {_searchResults.Count:N0} matches)");
else
ImGui.Text($"Search ({_searchResults.Count:N0} matches)");
}
else
ImGui.Text("Search");
ImGui.SetNextItemWidth(ws.X / 2 - 20);
if (_resetKeyboardFocus)
{
ImGui.SetKeyboardFocusHere();
_resetKeyboardFocus = false;
}
string previousName = _itemName;
if (ImGui.InputText("", ref _itemName, 256, ImGuiInputTextFlags.EnterReturnsTrue))
{
_resetKeyboardFocus = true;
if (_searchResults.Count > 0)
{
var itemToAdd = _searchResults.FirstOrDefault();
if (SelectedItems.All(x => x.ItemId != itemToAdd.ItemId))
{
SelectedItems.Add(itemToAdd);
}
else
{
SelectedItems.Remove(itemToAdd);
}
Save();
}
}
if (previousName != _itemName)
UpdateResults();
ImGui.Separator();
if (string.IsNullOrEmpty(_itemName))
{
ImGui.Text("Type item name...");
}
foreach (var (id, name) in _searchResults.Take(ResultLimit))
{
bool selected = SelectedItems.Any(x => x.Item1 == id);
var display = AsLeftSideDisplay(id, name);
if (!display.Enabled)
ImGui.BeginDisabled();
if (ImGui.Selectable($"{display.Name}##Item{id}", selected))
{
if (!selected)
{
SelectedItems.Add((id, name));
}
else
{
SelectedItems.Remove((id, name));
}
Save();
}
if (!display.Enabled)
ImGui.EndDisabled();
}
}
ImGui.EndChild();
ImGui.SameLine();
if (ImGui.BeginChild("Right", new Vector2(-1, -1), true, ImGuiWindowFlags.NoSavedSettings))
{
ImGui.Text(RightSideLabel);
ImGui.Separator();
List<(uint, string)> toRemove = new();
foreach (var (id, name) in SelectedItems.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase))
{
var display = AsLeftSideDisplay(id, name);
if (!display.Enabled)
ImGui.BeginDisabled();
if (ImGui.Selectable($"{display.Name}##Item{id}", true))
toRemove.Add((id, name));
if (!display.Enabled)
ImGui.EndDisabled();
}
if (toRemove.Count > 0)
{
foreach (var tr in toRemove)
SelectedItems.Remove(tr);
Save();
}
}
ImGui.EndChild();
}
protected virtual (string Name, bool Enabled) AsLeftSideDisplay(uint itemId, string name) => (name, true);
protected void Save() => _parent.Save();
private void UpdateResults()
{
if (string.IsNullOrEmpty(_itemName))
_searchResults = new();
else
{
_searchResults = EnsureAllItemsLoaded().Where(x =>
x.Name.Contains(_itemName, StringComparison.CurrentCultureIgnoreCase)
|| (uint.TryParse(_itemName, out uint itemId) && x.ItemId == itemId))
.OrderBy(x => _itemName.Equals(x.Name, StringComparison.OrdinalIgnoreCase) ? string.Empty : x.Name)
.ToList();
}
}
protected List<(uint ItemId, string Name)> EnsureAllItemsLoaded() => _parent.EnsureAllItemsLoaded();
}

View File

@ -1,95 +1,30 @@
{
"version": 1,
"dependencies": {
"net8.0-windows7.0": {
"net7.0-windows7.0": {
"Dalamud.ContextMenu": {
"type": "Direct",
"requested": "[1.3.1, )",
"resolved": "1.3.1",
"contentHash": "ptAxut5PiLnzZ4G/KQdHJVcyklC/BF3otHJ7zYVUPiKBjsOCoF0n/6h2jK7e+8ev2Y1yAY3Wtx2GuXLFQgt9Uw=="
},
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.13, )",
"resolved": "2.1.13",
"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"
}
"requested": "[2.1.12, )",
"resolved": "2.1.12",
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"autoretainerapi": {
"type": "Project",
"dependencies": {
"ECommons": "[2.2.0.2, )"
"ECommons": "[2.1.0, )"
}
},
"ecommons": {
"type": "Project"
},
"llib": {
"type": "Project",
"dependencies": {
"DalamudPackager": "[2.1.13, )"
}
"type": "Project"
}
}
}

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

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

2
LLib

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

View File

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