Armoury discards; Discard (preview) UI

master v2.1
Liza 2023-09-16 13:14:47 +02:00
parent 29ef359871
commit 33630d20cb
Signed by: liza
GPG Key ID: 7199F8D727D55F67
11 changed files with 592 additions and 135 deletions

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<Version>2.0</Version>
<Version>2.1</Version>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -17,7 +17,7 @@
<PropertyGroup>
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
<AutoRetainerLibPath>$(appdata)\XIVLauncher\installedPlugins\AutoRetainer\4.1.1.1\</AutoRetainerLibPath>
<AutoRetainerLibPath>$(appdata)\XIVLauncher\installedPlugins\AutoRetainer\4.1.1.4\</AutoRetainerLibPath>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
@ -72,6 +72,7 @@
</Reference>
</ItemGroup>
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin">
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip"/>
</Target>

View File

@ -1,9 +1,13 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ARDiscard.GameData;
using ARDiscard.Windows;
using AutoRetainerAPI;
using ClickLib.Clicks;
using Dalamud.Data;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.Interface.Windowing;
@ -19,11 +23,13 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
namespace ARDiscard;
[SuppressMessage("ReSharper", "UnusedType.Global")]
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 DalamudPluginInterface _pluginInterface;
private readonly ChatGui _chatGui;
@ -36,26 +42,50 @@ public class AutoDiscardPlogon : IDalamudPlugin
private DateTime _cancelDiscardAfter = DateTime.MaxValue;
public AutoDiscardPlogon(DalamudPluginInterface pluginInterface, CommandManager commandManager, ChatGui chatGui,
DataManager dataManager, ClientState clientState)
DataManager dataManager, ClientState clientState, Condition condition)
{
ItemCache itemCache = new ItemCache(dataManager);
_pluginInterface = pluginInterface;
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
_chatGui = chatGui;
_clientState = clientState;
_commandManager = commandManager;
_commandManager.AddHandler("/discardconfig", new CommandInfo(OpenConfig));
_commandManager.AddHandler("/discardall", new CommandInfo(ProcessCommand));
_inventoryUtils = new(_configuration);
_commandManager.AddHandler("/discardconfig", new CommandInfo(OpenConfig)
{
HelpMessage = "Configures which items to automatically discard",
});
_commandManager.AddHandler("/discardall", new CommandInfo(DiscardAll)
{
HelpMessage = "Discard all configured items now"
});
_commandManager.AddHandler("/discard", new CommandInfo(OpenDiscardWindow)
{
HelpMessage = "Show what will be discarded with your current configuration",
});
_inventoryUtils = new InventoryUtils(_configuration, itemCache);
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
_configWindow = new(_pluginInterface, _configuration, dataManager, clientState);
_discardWindow = new(_inventoryUtils, itemCache, clientState, condition);
_windowSystem.AddWindow(_discardWindow);
_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!.Enqueue(() => DiscardNextItem(PostProcessType.ManuallyStarted, filter));
ECommonsMain.Init(_pluginInterface, this);
_autoRetainerApi = new();
_taskManager = new();
_clientState.Login += _discardWindow.Login;
_clientState.Logout += _discardWindow.Logout;
_autoRetainerApi.OnRetainerPostprocessStep += CheckRetainerPostProcess;
_autoRetainerApi.OnRetainerReadyToPostprocess += DoRetainerPostProcess;
_autoRetainerApi.OnCharacterPostprocessStep += CheckCharacterPostProcess;
@ -80,7 +110,7 @@ public class AutoDiscardPlogon : IDalamudPlugin
{
PluginLog.Information($"Not running post-venture tasks for {name}, disabled for current character");
}
else if (_inventoryUtils.GetNextItemToDiscard() == null)
else if (_inventoryUtils.GetNextItemToDiscard(ItemFilter.None) == null)
{
PluginLog.Information($"Not running post-venture tasks for {name}, no items to discard");
}
@ -96,39 +126,41 @@ public class AutoDiscardPlogon : IDalamudPlugin
private void DoRetainerPostProcess(string retainerName)
{
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Retainer));
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Retainer, ItemFilter.None));
}
private void DoCharacterPostProcess()
{
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Character));
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.Character, ItemFilter.None));
}
private void OpenConfig(string command, string arguments) => OpenConfigUi();
private void OpenConfigUi()
{
_configWindow.IsOpen = true;
_configWindow.IsOpen = !_configWindow.IsOpen;
}
private void ProcessCommand(string command, string arguments)
private void DiscardAll(string command, string arguments)
{
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.FromCommand));
_taskManager.Enqueue(() => DiscardNextItem(PostProcessType.ManuallyStarted, ItemFilter.None));
}
private unsafe void DiscardNextItem(PostProcessType type)
private void OpenDiscardWindow(string command, string arguments)
{
_discardWindow.IsOpen = !_discardWindow.IsOpen;
}
private unsafe void DiscardNextItem(PostProcessType type, ItemFilter? itemFilter)
{
PluginLog.Information($"DiscardNextItem (type = {type})");
InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard();
_discardWindow.Locked = true;
InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard(itemFilter);
if (nextItem == null)
{
PluginLog.Information($"No item to discard found");
if (type == PostProcessType.Retainer)
_autoRetainerApi.FinishRetainerPostProcess();
else if (type == PostProcessType.Character)
_autoRetainerApi.FinishCharacterPostProcess();
else
_chatGui.Print("Done discarding.");
FinishDiscarding(type);
}
else
{
@ -140,11 +172,12 @@ public class AutoDiscardPlogon : IDalamudPlugin
_cancelDiscardAfter = DateTime.Now.AddSeconds(15);
_taskManager.DelayNext(20);
_taskManager.Enqueue(() => ConfirmDiscardItem(type, inventoryType, slot));
_taskManager.Enqueue(() => ConfirmDiscardItem(type, itemFilter, inventoryType, slot));
}
}
private unsafe void ConfirmDiscardItem(PostProcessType type, InventoryType inventoryType, short slot)
private unsafe void ConfirmDiscardItem(PostProcessType type, ItemFilter? itemFilter, InventoryType inventoryType,
short slot)
{
var addon = GetDiscardAddon();
if (addon != null)
@ -154,83 +187,89 @@ public class AutoDiscardPlogon : IDalamudPlugin
ClickSelectYesNo.Using((nint)addon).Yes();
_taskManager.DelayNext(20);
_taskManager.Enqueue(() => ContinueAfterDiscard(type, inventoryType, slot));
_taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot));
}
else
{
InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard();
InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard(itemFilter);
if (nextItem == null)
{
PluginLog.Information("Addon is not visible, but next item is also no longer set");
if (type == PostProcessType.Retainer)
_autoRetainerApi.FinishRetainerPostProcess();
else if (type == PostProcessType.Character)
_autoRetainerApi.FinishCharacterPostProcess();
else
_chatGui.Print("Done discarding.");
FinishDiscarding(type);
}
else if (nextItem->Container == inventoryType && nextItem->Slot == slot)
{
PluginLog.Information(
$"Addon is not (yet) visible, still trying to discard item in slot {slot} in inventory {inventoryType}");
_taskManager.DelayNext(100);
_taskManager.Enqueue(() => ConfirmDiscardItem(type, inventoryType, slot));
_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.DelayNext(100);
_taskManager.Enqueue(() => DiscardNextItem(type));
_taskManager.Enqueue(() => DiscardNextItem(type, itemFilter));
}
}
}
private unsafe void ContinueAfterDiscard(PostProcessType type, InventoryType inventoryType, short slot)
private unsafe void ContinueAfterDiscard(PostProcessType type, ItemFilter? itemFilter, InventoryType inventoryType,
short slot)
{
InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard();
InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard(itemFilter);
if (nextItem == null)
{
PluginLog.Information($"Continuing after discard: no next item (type = {type})");
if (type == PostProcessType.Retainer)
_autoRetainerApi.FinishRetainerPostProcess();
else if (type == PostProcessType.Character)
_autoRetainerApi.FinishCharacterPostProcess();
else
_chatGui.Print("Done discarding.");
FinishDiscarding(type);
}
else if (nextItem->Container == inventoryType && nextItem->Slot == slot)
{
if (_cancelDiscardAfter < DateTime.Now)
{
PluginLog.Information("No longer waiting for plugin to pop up, assume discard failed");
if (type == PostProcessType.Retainer)
_autoRetainerApi.FinishRetainerPostProcess();
else if (type == PostProcessType.Character)
_autoRetainerApi.FinishCharacterPostProcess();
else
_chatGui.PrintError("Discarding probably failed due to an error.");
FinishDiscarding(type, "Discarding probably failed due to an error.");
}
else
{
PluginLog.Information($"ContinueAfterDiscard: Waiting for server response until {_cancelDiscardAfter}");
_taskManager.DelayNext(20);
_taskManager.Enqueue(() => ContinueAfterDiscard(type, inventoryType, slot));
_taskManager.Enqueue(() => ContinueAfterDiscard(type, itemFilter, inventoryType, slot));
}
}
else
{
PluginLog.Information($"ContinueAfterDiscard: Discovered different item to discard");
_taskManager.EnqueueImmediate(() => DiscardNextItem(type));
_taskManager.EnqueueImmediate(() => DiscardNextItem(type, itemFilter));
}
}
private void FinishDiscarding(PostProcessType type, string? error = null)
{
if (type == PostProcessType.Retainer)
_autoRetainerApi.FinishRetainerPostProcess();
else if (type == PostProcessType.Character)
_autoRetainerApi.FinishCharacterPostProcess();
else
{
if (string.IsNullOrEmpty(error))
_chatGui.Print("Done discarding.");
else
_chatGui.PrintError(error);
}
_discardWindow.Locked = false;
_discardWindow.RefreshInventory(true);
}
public void Dispose()
{
_autoRetainerApi.OnRetainerPostprocessStep -= CheckRetainerPostProcess;
_autoRetainerApi.OnRetainerReadyToPostprocess -= DoRetainerPostProcess;
_autoRetainerApi.OnCharacterPostprocessStep -= CheckCharacterPostProcess;
_autoRetainerApi.OnCharacterReadyToPostProcess -= DoCharacterPostProcess;
_clientState.Login -= _discardWindow.Login;
_clientState.Logout -= _discardWindow.Logout;
_autoRetainerApi.Dispose();
ECommonsMain.Dispose();
@ -238,6 +277,7 @@ public class AutoDiscardPlogon : IDalamudPlugin
_inventoryUtils.Dispose();
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
_commandManager.RemoveHandler("/discard");
_commandManager.RemoveHandler("/discardall");
_commandManager.RemoveHandler("/discardconfig");
}
@ -274,6 +314,6 @@ public class AutoDiscardPlogon : IDalamudPlugin
{
Retainer,
Character,
FromCommand,
ManuallyStarted,
}
}

View File

@ -3,7 +3,7 @@ using Dalamud.Configuration;
namespace ARDiscard;
public class Configuration : IPluginConfiguration
public sealed class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 1;
public bool RunAfterVenture { get; set; }
@ -11,10 +11,20 @@ public class Configuration : IPluginConfiguration
public List<uint> DiscardingItems { get; set; } = new();
public List<CharacterInfo> ExcludedCharacters { get; set; } = new();
public class CharacterInfo
public ArmouryConfiguration Armoury { get; set; } = new();
public sealed class CharacterInfo
{
public ulong LocalContentId { get; set; }
public string CachedPlayerName { get; set; }
public string CachedWorldName { get; set; }
}
public sealed class ArmouryConfiguration
{
public bool DiscardFromArmouryChest { get; set; } = false;
public bool CheckLeftSideGear { get; set; } = false;
public bool CheckRightSideGear { get; set; } = false;
public int MaximumGearItemLevel { get; set; } = 45;
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace ARDiscard.GameData;
public static class InternalConfiguration
{
public static IReadOnlyList<uint> BlacklistedItems = new List<uint>
{
2820, // red onion helm
}.AsReadOnly();
}

View File

@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Data;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.GeneratedSheets;
namespace ARDiscard.GameData;
public class InventoryUtils : IDisposable
{
private static readonly InventoryType[] DefaultInventoryTypes =
{
InventoryType.Inventory1,
InventoryType.Inventory2,
InventoryType.Inventory3,
InventoryType.Inventory4
};
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.ArmoryHands,
InventoryType.ArmoryRings
};
private readonly Configuration _configuration;
private readonly ItemCache _itemCache;
private unsafe delegate void DiscardItemDelegate(AgentInventoryContext* inventoryManager, InventoryItem* itemSlot,
InventoryType inventory, int slot, uint addonId, int position = -1);
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 85 C0 74 ?? 0F B7 48")]
private DiscardItemDelegate _discardItem = null!;
public InventoryUtils(Configuration configuration, ItemCache itemCache)
{
_configuration = configuration;
_itemCache = itemCache;
SignatureHelper.Initialise(this);
}
public void Dispose()
{
}
public unsafe List<ItemWrapper> GetAllItemsToDiscard()
{
List<ItemWrapper> toDiscard = new List<ItemWrapper>();
InventoryManager* inventoryManager = InventoryManager.Instance();
foreach (InventoryType inventoryType in DefaultInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, false));
if (_configuration.Armoury.DiscardFromArmouryChest)
{
if (_configuration.Armoury.CheckLeftSideGear)
{
foreach (InventoryType inventoryType in LeftSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, true));
}
if (_configuration.Armoury.CheckRightSideGear)
{
foreach (InventoryType inventoryType in RightSideGearInventoryTypes)
toDiscard.AddRange(GetItemsToDiscard(inventoryManager, inventoryType, true));
}
}
return toDiscard;
}
public unsafe InventoryItem* GetNextItemToDiscard(ItemFilter? itemFilter)
{
List<ItemWrapper> allItemsToDiscard = GetAllItemsToDiscard();
ItemWrapper? toDiscard = allItemsToDiscard.FirstOrDefault(x =>
itemFilter == null || itemFilter.ItemIds.Contains(x.InventoryItem->ItemID));
return toDiscard != null ? toDiscard.InventoryItem : null;
}
private unsafe IReadOnlyList<ItemWrapper> GetItemsToDiscard(InventoryManager* inventoryManager,
InventoryType inventoryType, bool doGearChecks)
{
List<ItemWrapper> toDiscard = new List<ItemWrapper>();
InventoryContainer* container = inventoryManager->GetInventoryContainer(inventoryType);
//PluginLog.Verbose($"Checking {inventoryType}, {container->Size}");
for (int i = 0; i < container->Size; ++i)
{
var item = container->GetInventorySlot(i);
if (item != null)
{
if (InternalConfiguration.BlacklistedItems.Contains(item->ItemID))
continue;
if (doGearChecks)
{
if (IsItemPartOfGearset(item->ItemID))
continue;
ItemCache.CachedItemInfo? itemInfo = _itemCache.GetItem(item->ItemID);
if (itemInfo == null)
continue; // no info, who knows what that item is
if (itemInfo.ILvl >= _configuration.Armoury.MaximumGearItemLevel)
continue;
}
//PluginLog.Verbose($"{i} → {item->ItemID}");
if (_configuration.DiscardingItems.Contains(item->ItemID))
{
PluginLog.Information(
$"Found item {item->ItemID} to discard in inventory {inventoryType} in slot {i}");
toDiscard.Add(new ItemWrapper { InventoryItem = item });
}
}
else
{
//PluginLog.Verbose($"{i} → none");
}
}
return toDiscard;
}
private unsafe bool IsItemPartOfGearset(uint searchForItemId)
{
var gearsetModule = RaptureGearsetModule.Instance();
if (gearsetModule == null)
return true; // can't check gearsets, pretend everything is part of one
for (int i = 0; i < 100; ++i)
{
var gearset = gearsetModule->GetGearset(i);
if (gearset != null && gearset->Flags.HasFlag(RaptureGearsetModule.GearsetFlag.Exists))
{
var gearsetItems = new[]
{
gearset->MainHand,
gearset->OffHand,
gearset->Head,
gearset->Body,
gearset->Hands,
gearset->Legs,
gearset->Feet,
gearset->Ears,
gearset->Neck,
gearset->Wrists,
gearset->RingRight,
gearset->RightLeft, // why is this called RightLeft
};
foreach (var gearsetItem in gearsetItems)
{
if (gearsetItem.ItemID == searchForItemId)
return true;
}
}
}
return false;
}
public unsafe void Discard(InventoryItem* item)
{
_discardItem(AgentInventoryContext.Instance(), item, item->Container, item->Slot, 0);
}
public sealed unsafe class ItemWrapper
{
public required InventoryItem* InventoryItem { get; init; }
}
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
namespace ARDiscard.GameData;
public sealed class ItemCache
{
private readonly Dictionary<uint, CachedItemInfo> _items = new();
public ItemCache(DataManager dataManager)
{
foreach (var item in dataManager.GetExcelSheet<Item>()!)
{
if (item.RowId == 0)
continue;
_items[item.RowId] = new CachedItemInfo
{
ItemId = item.RowId,
Name = item.Name.ToString(),
ILvl = item.LevelItem.Row,
Rarity = item.Rarity,
IsUnique = item.IsUnique,
IsUntradable = item.IsUntradable,
Level = item.LevelEquip,
UiCategory = item.ItemUICategory.Row,
};
}
}
public IEnumerable<CachedItemInfo> AllItems => _items.Values;
public CachedItemInfo? GetItem(uint itemId) => _items[itemId];
public string GetItemName(uint itemId)
{
if (_items.TryGetValue(itemId, out var item))
return item.Name;
return string.Empty;
}
public sealed class CachedItemInfo
{
public required uint ItemId { get; init; }
public required string Name { get; init; }
public required uint ILvl { get; init; }
public required uint Level { get; init; }
public required byte Rarity { get; init; }
public required bool IsUnique { get; init; }
public required bool IsUntradable { get; init; }
public required uint UiCategory { get; init; }
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace ARDiscard.GameData;
public class ItemFilter
{
public static ItemFilter? None = null;
public required List<uint> ItemIds { get; init; }
}

View File

@ -0,0 +1,8 @@
namespace ARDiscard.GameData;
public static class UiCategories
{
public const uint Unobtainable = 39;
public const uint Crystals = 59;
public const uint Currency = 100;
}

View File

@ -1,67 +0,0 @@
using System;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace ARDiscard;
public class InventoryUtils : IDisposable
{
private readonly Configuration _configuration;
private static readonly InventoryType[] InventoryTypes =
{ InventoryType.Inventory1, InventoryType.Inventory2, InventoryType.Inventory3, InventoryType.Inventory4 };
private unsafe delegate void DiscardItemDelegate(AgentInventoryContext* inventoryManager, InventoryItem* itemSlot,
InventoryType inventory, int slot, uint addonId, int position = -1);
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 85 C0 74 ?? 0F B7 48")]
private DiscardItemDelegate _discardItem = null!;
public InventoryUtils(Configuration configuration)
{
_configuration = configuration;
SignatureHelper.Initialise(this);
}
public void Dispose()
{
}
public unsafe InventoryItem* GetNextItemToDiscard()
{
InventoryManager* inventoryManager = InventoryManager.Instance();
foreach (InventoryType inventoryType in InventoryTypes)
{
InventoryContainer* container = inventoryManager->GetInventoryContainer(inventoryType);
//PluginLog.Verbose($"Checking {inventoryType}, {container->Size}");
for (int i = 0; i < container->Size; ++i)
{
var item = container->GetInventorySlot(i);
if (item != null)
{
//PluginLog.Verbose($"{i} → {item->ItemID}");
if (_configuration.DiscardingItems.Contains(item->ItemID))
{
PluginLog.Information(
$"Found item {item->ItemID} to discard in inventory {inventoryType} in slot {i}");
return item;
}
}
else
{
//PluginLog.Verbose($"{i} → none");
}
}
}
return null;
}
public unsafe void Discard(InventoryItem* item)
{
_discardItem(AgentInventoryContext.Instance(), item, item->Container, item->Slot, 0);
}
}

View File

@ -2,23 +2,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Data;
using ARDiscard.GameData;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using ECommons;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Condition = Dalamud.Game.ClientState.Conditions.Condition;
namespace ARDiscard;
namespace ARDiscard.Windows;
public class ConfigWindow : Window
public sealed class ConfigWindow : Window
{
private readonly DalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly DataManager _dataManager;
private readonly ItemCache _itemCache;
private readonly ClientState _clientState;
private readonly Condition _condition;
private string _itemName = string.Empty;
private List<(uint ItemId, string Name)> _searchResults = new();
@ -26,14 +29,18 @@ public class ConfigWindow : Window
private List<(uint ItemId, string Name)>? _allItems = null;
private bool _resetKeyboardFocus = true;
public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, DataManager dataManager,
ClientState clientState)
public event EventHandler? DiscardNowClicked;
public event EventHandler? ConfigSaved;
public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, ItemCache itemCache,
ClientState clientState, Condition condition)
: base("Auto Discard###AutoDiscardConfig")
{
_pluginInterface = pluginInterface;
_configuration = configuration;
_dataManager = dataManager;
_itemCache = itemCache;
_clientState = clientState;
_condition = condition;
Size = new Vector2(600, 400);
SizeCondition = ImGuiCond.FirstUseEver;
@ -45,7 +52,7 @@ public class ConfigWindow : Window
};
_discarding.AddRange(_configuration.DiscardingItems
.Select(x => (x, dataManager.GetExcelSheet<Item>()?.GetRow(x)?.Name?.ToString() ?? x.ToString())).ToList());
.Select(x => (x, itemCache.GetItemName(x))).ToList());
}
public override void Draw()
@ -57,6 +64,13 @@ public class ConfigWindow : Window
Save();
}
ImGui.SameLine(ImGui.GetWindowWidth() - 115 * ImGuiHelpers.GlobalScale);
ImGui.BeginDisabled(!_clientState.IsLoggedIn || !_condition[ConditionFlag.NormalConditions] ||
DiscardNowClicked == null);
if (ImGui.Button("Preview Discards"))
DiscardNowClicked!.Invoke(this, EventArgs.Empty);
ImGui.EndDisabled();
bool runBeforeLogout = _configuration.RunBeforeLogout;
if (ImGui.Checkbox("[Global] Run before logging out in Multi-Mode", ref runBeforeLogout))
{
@ -68,6 +82,7 @@ public class ConfigWindow : Window
{
DrawDiscardList();
DrawExcludedCharacters();
DrawExperimentalSettings();
ImGui.EndTabBar();
}
@ -170,7 +185,7 @@ public class ConfigWindow : Window
{
if (ImGui.BeginTabItem("Excluded Characters"))
{
if (_clientState.IsLoggedIn && _clientState.LocalContentId > 0)
if (_clientState is { IsLoggedIn: true, LocalContentId: > 0 })
{
string worldName = _clientState.LocalPlayer?.HomeWorld.GameData?.Name ?? "??";
ImGui.TextWrapped(
@ -243,6 +258,49 @@ public class ConfigWindow : Window
}
}
private void DrawExperimentalSettings()
{
if (ImGui.BeginTabItem("Experimental Settings"))
{
bool discardFromArmouryChest = _configuration.Armoury.DiscardFromArmouryChest;
if (ImGui.Checkbox("Discard items from Armoury Chest", ref discardFromArmouryChest))
{
_configuration.Armoury.DiscardFromArmouryChest = discardFromArmouryChest;
Save();
}
ImGui.BeginDisabled(!discardFromArmouryChest);
ImGui.Indent(30);
bool leftSideGear = _configuration.Armoury.CheckLeftSideGear;
if (ImGui.Checkbox("Discard when items are found in Head/Body/Hands/Legs/Feet", ref leftSideGear))
{
_configuration.Armoury.CheckLeftSideGear = leftSideGear;
Save();
}
bool rightSideGear = _configuration.Armoury.CheckRightSideGear;
if (ImGui.Checkbox("Discard when items are found in Accessories", ref rightSideGear))
{
_configuration.Armoury.CheckRightSideGear = rightSideGear;
Save();
}
ImGui.SetNextItemWidth(ImGuiHelpers.GlobalScale * 100);
int maximumItemLevel = _configuration.Armoury.MaximumGearItemLevel;
if (ImGui.InputInt("Ignore items >= this ilvl (Armoury Chest only)",
ref maximumItemLevel))
{
_configuration.Armoury.MaximumGearItemLevel = Math.Max(0, Math.Min(625, maximumItemLevel));
Save();
}
ImGui.Unindent(30);
ImGui.EndDisabled();
ImGui.EndTabItem();
}
}
private void UpdateResults()
{
if (string.IsNullOrEmpty(_itemName))
@ -251,13 +309,11 @@ public class ConfigWindow : Window
{
if (_allItems == null)
{
_allItems = _dataManager.GetExcelSheet<Item>()!
.Where(x => x.RowId != 0)
_allItems = _itemCache.AllItems
.Where(x => !x.IsUnique && !x.IsUntradable)
.Where(x => x.ItemUICategory?.Value?.Name?.ToString() != "Currency" &&
x.ItemUICategory?.Value?.Name?.ToString() != "Crystal")
.Where(x => !string.IsNullOrEmpty(x.Name.ToString()))
.Select(x => (x.RowId, x.Name.ToString()))
.Where(x => x.UiCategory != UiCategories.Currency && x.UiCategory != UiCategories.Crystals &&
x.UiCategory != UiCategories.Unobtainable)
.Select(x => (x.ItemId, x.Name.ToString()))
.ToList();
}
@ -272,5 +328,7 @@ public class ConfigWindow : Window
{
_configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList();
_pluginInterface.SavePluginConfig(_configuration);
ConfigSaved?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ARDiscard.GameData;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface;
using Dalamud.Interface.Windowing;
using FFXIVClientStructs.FFXIV.Common.Math;
using ImGuiNET;
namespace ARDiscard.Windows;
public sealed class DiscardWindow : Window
{
private readonly InventoryUtils _inventoryUtils;
private readonly ItemCache _itemCache;
private readonly ClientState _clientState;
private readonly Condition _condition;
private List<SelectableItem> _displayedItems = new();
public event EventHandler? OpenConfigurationClicked;
public event EventHandler<ItemFilter>? DiscardAllClicked;
public DiscardWindow(InventoryUtils inventoryUtils, ItemCache itemCache, ClientState clientState,
Condition condition)
: base("Discard Items")
{
_inventoryUtils = inventoryUtils;
_itemCache = itemCache;
_clientState = clientState;
_condition = condition;
Size = new Vector2(600, 400);
SizeCondition = ImGuiCond.FirstUseEver;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(600, 400),
MaximumSize = new Vector2(9999, 9999),
};
}
public bool Locked { get; set; } = false;
public override void Draw()
{
ImGui.Text("With your current configuration, the following items would be discarded:");
ImGui.BeginDisabled(Locked);
if (ImGui.BeginChild("Right", new Vector2(-1, -30), true, ImGuiWindowFlags.NoSavedSettings))
{
if (!_clientState.IsLoggedIn)
{
ImGui.Text("Not logged in.");
}
else if (_displayedItems.Count == 0)
{
ImGui.Text("No items to discard.");
}
else
{
foreach (var displayedItem in _displayedItems)
{
if (ImGui.Selectable(displayedItem.ToString(), displayedItem.Selected))
displayedItem.Selected = !displayedItem.Selected;
}
}
}
ImGui.EndDisabled();
ImGui.EndChild();
ImGui.BeginDisabled(OpenConfigurationClicked == null);
if (ImGui.Button("Open Configuration"))
OpenConfigurationClicked!.Invoke(this, EventArgs.Empty);
ImGui.EndDisabled();
ImGui.SameLine(ImGui.GetWindowWidth() - 160 * ImGuiHelpers.GlobalScale);
ImGui.BeginDisabled(Locked ||
!_clientState.IsLoggedIn ||
!_condition[ConditionFlag.NormalConditions] ||
_displayedItems.Count(x => x.Selected) == 0 ||
DiscardAllClicked == null);
if (ImGui.Button("Discard all selected items"))
{
DiscardAllClicked!.Invoke(this, new ItemFilter
{
ItemIds = _displayedItems.Where(x => x.Selected).Select(x => x.ItemId).ToList()
});
}
ImGui.EndDisabled();
}
public override void OnOpen() => RefreshInventory(false);
public override void OnClose() => _displayedItems.Clear();
public unsafe void RefreshInventory(bool keepSelected)
{
if (!IsOpen)
return;
List<uint> notSelected = new();
if (keepSelected)
{
notSelected.AddRange(_displayedItems
.Where(x => !x.Selected)
.Select(x => x.ItemId));
}
_displayedItems = _inventoryUtils.GetAllItemsToDiscard()
.GroupBy(x => x.InventoryItem->ItemID)
.Select(x => new SelectableItem
{
ItemId = x.Key,
Name = _itemCache.GetItemName(x.Key),
Quantity = x.Sum(y => y.InventoryItem->Quantity),
Selected = !notSelected.Contains(x.Key),
})
.ToList();
}
private sealed class SelectableItem
{
public required uint ItemId { get; init; }
public required string Name { get; init; }
public required long Quantity { get; init; }
public bool Selected { get; set; } = true;
public override string ToString()
{
if (Quantity > 1)
return $"{Name} ({Quantity}x)";
return Name;
}
}
public void Login(object? sender, EventArgs e) => RefreshInventory(false);
public void Logout(object? sender, EventArgs e) => _displayedItems.Clear();
}