Compare commits

..

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

23 changed files with 385 additions and 1748 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.2</Version>
<TargetFramework>net7.0-windows</TargetFramework>
<Version>4.5</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://plugins.carvel.li/icons/ARDiscard.png"
}

View File

@ -11,7 +11,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 +20,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,23 +35,18 @@ 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() ?? new Configuration();
@ -73,38 +68,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);
_contextMenuIntegration = new(_pluginInterface, _chatGui, itemCache, _configuration, _configWindow, _gameGui);
_autoDiscardIpc = new(_pluginInterface, _configuration);
_clientState.Login += _discardWindow.Login;
@ -158,13 +143,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));
}
@ -177,13 +160,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;
}
@ -204,11 +184,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));
}
}
@ -219,11 +199,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
@ -238,14 +218,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));
}
}
@ -269,16 +249,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));
}
}
@ -316,7 +296,6 @@ public sealed class AutoDiscardPlogon : IDalamudPlugin
_iconCache.Dispose();
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
_pluginInterface.UiBuilder.OpenMainUi -= OpenDiscardUi;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_commandManager.RemoveHandler("/discard");
_commandManager.RemoveHandler("/discardall");
@ -336,7 +315,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

@ -9,14 +9,12 @@ internal sealed class Configuration : IPluginConfiguration
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,17 @@ 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 CheckMainHandOffHand { 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 OnlyWhenConfigIsOpen { get; set; } = false;
}
public sealed class PreviewConfiguration

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,65 +14,61 @@ 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)
if (args.ParentAddonName is not ("Inventory" or "InventoryExpansion" or "InventoryLarge" or "ArmouryBoard"))
return;
var item = targetInventory.TargetItem.Value;
if (!_configWindow.CanItemBeConfigured(item.ItemId))
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);
if (_configuration.DiscardingItems.Contains(args.ItemId))
args.AddCustomItem(_removeInventoryItem);
else if (_itemCache.TryGetItem(args.ItemId, out ItemCache.CachedItemInfo? cachedItemInfo) &&
cachedItemInfo.CanBeDiscarded())
args.AddCustomItem(_addInventoryItem);
}
else
private void OpenGameObjectContextMenu(GameObjectContextMenuOpenArgs args)
{
if (args.AddonName is not "ChatLog")
if (!IsEnabled())
return;
if (args.ParentAddonName is not "ChatLog")
return;
uint itemId = (uint)_gameGui.HoveredItem;
@ -83,31 +79,13 @@ internal sealed class ContextMenuIntegration : IDisposable
itemId -= 500_000;
if (_configuration.DiscardingItems.Contains(itemId))
{
args.AddMenuItem(new MenuItem
{
Prefix = _removeInventoryItem.Prefix,
PrefixColor = _removeInventoryItem.PrefixColor,
Name = _removeInventoryItem.Name,
OnClicked = _ => RemoveFromDiscardList(itemId),
});
}
args.AddCustomItem(new GameObjectContextMenuItem(_removeItemPayload, _ => 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),
});
}
}
cachedItemInfo.CanBeDiscarded())
args.AddCustomItem(new GameObjectContextMenuItem(_addItemPayload, _ => AddToDiscardList(itemId)));
}
private void AddToDiscardList(IMenuItemClickedArgs args) =>
AddToDiscardList(((MenuTargetInventory)args.Target).TargetItem!.Value.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

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

View File

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

View File

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

View File

@ -1,22 +1,20 @@
using System.Collections.Generic;
using System.Collections.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
@ -28,13 +26,16 @@ internal sealed class ListManager : IListManager
38951, // TOP token
}
.Concat(Enumerable.Range(1, 99).Select(x => (uint)x))
.ToHashSet();
.ToList()
.AsReadOnly();
public static readonly IList<uint> UltimateWeapons = new List<uint>();
/// <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
@ -189,7 +190,7 @@ 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
@ -210,14 +211,6 @@ internal sealed class ListManager : IListManager
#endregion
#region Eureka Lootboxes
21831, // Sharlayan Cabinet
21832, // Sharlayan Wardrobe
22306, // Eurekan Potion
#endregion
#region Unapproved Firmament materials
32005, // Switch
@ -267,56 +260,5 @@ internal sealed class ListManager : IListManager
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,46 @@ 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
];
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;
}
@ -78,11 +75,11 @@ internal sealed class InventoryUtils
}
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,
private unsafe IEnumerable<ItemWrapper> GetArmouryItemsToDiscard(bool condition, InventoryManager* inventoryManager,
InventoryType[] inventoryTypes, Dictionary<uint, uint> itemCounts, List<uint>? gearsetItems)
{
List<ItemWrapper> items = new();
@ -92,18 +89,18 @@ internal sealed class InventoryUtils
items.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, itemCounts, gearsetItems));
}
return items.AsReadOnly();
return items;
}
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 +110,34 @@ 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) ||
InternalConfiguration.UltimateWeapons.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 +147,7 @@ internal sealed class InventoryUtils
}
}
return toDiscard.AsReadOnly();
return toDiscard;
}
private unsafe List<uint>? GetAllGearsetItems()
@ -169,23 +164,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 +190,9 @@ 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) ||
InternalConfiguration.UltimateWeapons.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>()!)
{
@ -35,10 +35,9 @@ internal sealed class ItemCache
};
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);
{
InternalConfiguration.UltimateWeapons.Add(item.RowId);
}
}
foreach (var shopItem in dataManager.GetExcelSheet<GilShopItem>()!)
@ -49,7 +48,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,24 +60,7 @@ internal sealed class ItemCache
cachedItemInfo.CanBeBoughtFromCalamitySalvager = true;
}
// only look at msq + regional side quests
foreach (var quest in dataManager.GetExcelSheet<Quest>()!.Where(x => x.JournalGenre.Value?.JournalCategory.Value?.JournalSection.Row is 0 or 1 or 3))
{
foreach (var itemId in quest.ItemReward.Where(x => x > 0))
{
var item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId);
if (item is { Rarity: 1, ItemAction.Row: 388 } && item.RowId != 38809 && item.RowId != 29679)
listManager.AddToInternalWhitelist(item.RowId);
}
}
MaxDungeonItemLevel = _items.Values.Where(x => x.Rarity == 2)
.Select(x => (int)x.ILvl)
.Max();
}
public int MaxDungeonItemLevel { get; }
private bool CanDiscardItemsFromQuest(LazyRow<Quest> quest)
{
@ -136,15 +118,15 @@ 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) || InternalConfiguration.UltimateWeapons.Contains(ItemId))
return false;
if (UiCategory is UiCategories.Currency or UiCategories.Crystals or UiCategories.Unobtainable)
return false;
if (listManager.IsWhitelisted(ItemId))
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"))
@ -208,8 +284,7 @@ internal sealed class ConfigWindow : LWindow
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))
if (ImGui.Checkbox("Discard when items are found in Main Hand/Off Hand (Weapons and Tools)", ref mainHandOffHand))
{
_configuration.Armoury.CheckMainHandOffHand = mainHandOffHand;
Save();
@ -234,8 +309,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 +348,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 +368,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 +395,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,14 +115,12 @@ internal sealed class DiscardWindow : LWindow
{
if (_configuration.Preview.ShowIcons)
{
ISharedImmediateTexture icon = _iconCache.GetIcon(displayedItem.IconId);
if (icon.TryGetWrap(out IDalamudTextureWrap? wrap, out _))
IDalamudTextureWrap? icon = _iconCache.GetIcon(displayedItem.IconId);
if (icon != null)
{
ImGui.Image(wrap.ImGuiHandle, new Vector2(23, 23));
ImGui.Image(icon.ImGuiHandle, new Vector2(23, 23));
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
wrap.Dispose();
}
}
@ -158,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
@ -172,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 a63c8e7154e272374ffa03d5c801736d4229e38a
Subproject commit 7cb54772e3a4a60ad02520e898d1ed0e82b2a751

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

2
LLib

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

View File

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