Armoury discards; Discard (preview) UI
This commit is contained in:
parent
29ef359871
commit
9d255fea92
@ -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>
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
11
ARDiscard/GameData/InternalConfiguration.cs
Normal file
11
ARDiscard/GameData/InternalConfiguration.cs
Normal 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();
|
||||
}
|
187
ARDiscard/GameData/InventoryUtils.cs
Normal file
187
ARDiscard/GameData/InventoryUtils.cs
Normal 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; }
|
||||
}
|
||||
}
|
54
ARDiscard/GameData/ItemCache.cs
Normal file
54
ARDiscard/GameData/ItemCache.cs
Normal 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; }
|
||||
}
|
||||
}
|
10
ARDiscard/GameData/ItemFilter.cs
Normal file
10
ARDiscard/GameData/ItemFilter.cs
Normal 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; }
|
||||
}
|
8
ARDiscard/GameData/UiCategories.cs
Normal file
8
ARDiscard/GameData/UiCategories.cs
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
145
ARDiscard/Windows/DiscardWindow.cs
Normal file
145
ARDiscard/Windows/DiscardWindow.cs
Normal 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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user