commit c15f511f2bafd8e282ea1fcf2e0c73dac36b1fe0 Author: Liza Carvelli Date: Sat Sep 9 18:10:45 2023 +0200 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05dc549 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +*.user diff --git a/ARDiscard.sln b/ARDiscard.sln new file mode 100644 index 0000000..4fc5e8b --- /dev/null +++ b/ARDiscard.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARDiscard", "ARDiscard\ARDiscard.csproj", "{A9B4C542-1C83-4F46-81E7-5BAFCA109767}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A9B4C542-1C83-4F46-81E7-5BAFCA109767}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9B4C542-1C83-4F46-81E7-5BAFCA109767}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9B4C542-1C83-4F46-81E7-5BAFCA109767}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9B4C542-1C83-4F46-81E7-5BAFCA109767}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/ARDiscard/.gitignore b/ARDiscard/.gitignore new file mode 100644 index 0000000..958518b --- /dev/null +++ b/ARDiscard/.gitignore @@ -0,0 +1,3 @@ +/dist +/obj +/bin diff --git a/ARDiscard/ARDiscard.csproj b/ARDiscard/ARDiscard.csproj new file mode 100644 index 0000000..3c3bc31 --- /dev/null +++ b/ARDiscard/ARDiscard.csproj @@ -0,0 +1,77 @@ + + + net7.0-windows + 1.0 + 11.0 + enable + true + false + false + dist + true + portable + $(SolutionDir)=X:\ + true + portable + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + + + + $(DALAMUD_HOME)/ + + + + + + + + + $(DalamudLibPath)Dalamud.dll + false + + + $(DalamudLibPath)ImGui.NET.dll + false + + + $(DalamudLibPath)ImGuiScene.dll + false + + + $(DalamudLibPath)Lumina.dll + false + + + $(DalamudLibPath)Lumina.Excel.dll + false + + + $(DalamudLibPath)Newtonsoft.Json.dll + false + + + $(DalamudLibPath)FFXIVClientStructs.dll + false + + + $(DalamudLibPath)FFXIVClientStructs.dll + false + + + $(appdata)\XIVLauncher\installedPlugins\AutoRetainer\4.1.0.6\AutoRetainerAPI.dll + + + $(appdata)\XIVLauncher\installedPlugins\AutoRetainer\4.1.0.6\ECommons.dll + + + $(appdata)\XIVLauncher\installedPlugins\AutoRetainer\4.1.0.6\ClickLib.dll + + + + + + + diff --git a/ARDiscard/ARDiscard.json b/ARDiscard/ARDiscard.json new file mode 100644 index 0000000..3926d77 --- /dev/null +++ b/ARDiscard/ARDiscard.json @@ -0,0 +1,7 @@ +{ + "Name": "Discard after AutoRetainer", + "Author": "Liza Carvelli", + "Punchline": "", + "Description": "", + "RepoUrl": "https://git.carvel.li/liza/ARDiscard" +} diff --git a/ARDiscard/AutoDiscardPlogon.cs b/ARDiscard/AutoDiscardPlogon.cs new file mode 100644 index 0000000..b80bae8 --- /dev/null +++ b/ARDiscard/AutoDiscardPlogon.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using AutoRetainerAPI; +using ClickLib.Clicks; +using Dalamud.Data; +using Dalamud.Game.Command; +using Dalamud.Game.Gui; +using Dalamud.Interface.Windowing; +using Dalamud.Logging; +using Dalamud.Memory; +using Dalamud.Plugin; +using ECommons; +using ECommons.Automation; +using ECommons.DalamudServices; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace ARDiscard; + +public class AutoDiscardPlogon : IDalamudPlugin +{ + private readonly WindowSystem _windowSystem = new(nameof(AutoDiscardPlogon)); + private readonly Configuration _configuration; + private readonly ConfigWindow _configWindow; + + private readonly DalamudPluginInterface _pluginInterface; + private readonly ChatGui _chatGui; + private readonly CommandManager _commandManager; + private readonly InventoryUtils _inventoryUtils; + private readonly AutoRetainerApi _autoRetainerApi; + private readonly TaskManager _taskManager; + + public AutoDiscardPlogon(DalamudPluginInterface pluginInterface, CommandManager commandManager, ChatGui chatGui, + DataManager dataManager) + { + _pluginInterface = pluginInterface; + _configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration(); + _chatGui = chatGui; + _commandManager = commandManager; + _commandManager.AddHandler("/discardconfig", new CommandInfo(OpenConfig)); + _commandManager.AddHandler("/discardall", new CommandInfo(ProcessCommand)); + _inventoryUtils = new(_configuration); + + _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; + _pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi; + _configWindow = new(_pluginInterface, _configuration, dataManager); + _configWindow.IsOpen = true; + _windowSystem.AddWindow(_configWindow); + + ECommonsMain.Init(_pluginInterface, this); + _autoRetainerApi = new(); + _taskManager = new(); + + _autoRetainerApi.OnRetainerReadyToPostprocess += DoPostProcess; + _autoRetainerApi.OnRetainerPostprocessStep += CheckPostProcess; + } + + public string Name => "Discard after AutoRetainer"; + + private unsafe void CheckPostProcess(string retainerName) + { + if (_inventoryUtils.GetNextItemToDiscard() != null) + _autoRetainerApi.RequestPostprocess(); + } + + private void DoPostProcess(string retainerName) + { + _taskManager.Enqueue(() => DiscardNextItem(true)); + } + + private void OpenConfig(string command, string arguments) => OpenConfigUi(); + + private void OpenConfigUi() + { + _configWindow.IsOpen = true; + } + + private void ProcessCommand(string command, string arguments) + { + _taskManager.Enqueue(() => DiscardNextItem(false)); + } + + private unsafe void DiscardNextItem(bool finishRetainerAction) + { + InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard(); + if (nextItem == null) + { + if (finishRetainerAction) + _autoRetainerApi.FinishPostProcess(); + else + _chatGui.Print("Done discarding."); + return; + } + + var (inventoryType, slot) = (nextItem->Container, nextItem->Slot); + + //_chatGui.Print($"Discarding {nextItem->ItemID}, {nextItem->Container}, {nextItem->Slot}."); + _inventoryUtils.Discard(nextItem); + + + _taskManager.DelayNext(5); + _taskManager.Enqueue(ConfirmDiscardItem); + _taskManager.DelayNext(2000); + _taskManager.Enqueue(() => ContinueAfterDiscard(finishRetainerAction, inventoryType, slot)); + } + + private unsafe void ConfirmDiscardItem() + { + var addon = GetDiscardAddon(); + if (addon != null) + { + ((AddonSelectYesno*)addon)->YesButton->AtkComponentBase.SetEnabledState(true); + ClickSelectYesNo.Using((nint)addon).Yes(); + } + } + + private unsafe void ContinueAfterDiscard(bool finishRetainerAction, InventoryType inventoryType, short slot) + { + InventoryItem* nextItem = _inventoryUtils.GetNextItemToDiscard(); + if (nextItem == null) + { + if (finishRetainerAction) + _autoRetainerApi.FinishPostProcess(); + else + _chatGui.Print("Done discarding."); + return; + } + + if (nextItem->Container == inventoryType && nextItem->Slot == slot) + { + _taskManager.DelayNext(100); + _taskManager.Enqueue(() => ContinueAfterDiscard(finishRetainerAction, inventoryType, slot)); + } + else + { + _taskManager.EnqueueImmediate(() => DiscardNextItem(finishRetainerAction)); + } + } + + public void Dispose() + { + _autoRetainerApi.Dispose(); + ECommonsMain.Dispose(); + + _inventoryUtils.Dispose(); + _pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi; + _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; + _commandManager.RemoveHandler("/discardall"); + _commandManager.RemoveHandler("/discardconfig"); + } + + private static unsafe AtkUnitBase* GetDiscardAddon() + { + for (int i = 1; i < 100; i++) + { + try + { + var addon = (AtkUnitBase*)Svc.GameGui.GetAddonByName("SelectYesno", i); + if (addon == null) return null; + if (addon->IsVisible) + { + var textNode = addon->UldManager.NodeList[15]->GetAsAtkTextNode(); + var text = MemoryHelper.ReadSeString(&textNode->NodeText).ExtractText(); + PluginLog.Information($"TEt → {text}"); + if (text.StartsWith("Discard")) + { + return addon; + } + } + } + catch (Exception) + { + return null; + } + } + + return null; + } +} diff --git a/ARDiscard/ConfigWindow.cs b/ARDiscard/ConfigWindow.cs new file mode 100644 index 0000000..f38e1ab --- /dev/null +++ b/ARDiscard/ConfigWindow.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Data; +using Dalamud.Interface.Windowing; +using Dalamud.Plugin; +using ECommons; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; + +namespace ARDiscard; + +public class ConfigWindow : Window +{ + private readonly DalamudPluginInterface _pluginInterface; + private readonly Configuration _configuration; + private readonly DataManager _dataManager; + private string _itemName = string.Empty; + + private List<(uint ItemId, string Name)> _searchResults = new(); + private List<(uint ItemId, string Name)> _discarding = new(); + + public ConfigWindow(DalamudPluginInterface pluginInterface, Configuration configuration, DataManager dataManager) + : base("Auto Discard###AutoDiscardConfig") + { + _pluginInterface = pluginInterface; + _configuration = configuration; + _dataManager = dataManager; + + Size = new Vector2(600, 400); + SizeCondition = ImGuiCond.FirstUseEver; + + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(600, 400), + MaximumSize = new Vector2(9999, 9999), + }; + + _discarding = _configuration.DiscardingItems + .Select(x => (x, dataManager.GetExcelSheet()?.GetRow(x)?.Name?.ToString() ?? x.ToString())).ToList(); + } + + public override void Draw() + { + var ws = ImGui.GetWindowSize(); + if (ImGui.BeginChild("Left", new Vector2(Math.Max(10, ws.X / 2), -1), true)) + { + ImGui.Text("Search"); + ImGui.SetNextItemWidth(ws.X / 2 - 20); + if (ImGui.InputText("", ref _itemName, 256)) + UpdateResults(); + + ImGui.Separator(); + + if (_searchResults.Count == 0) + { + ImGui.Text("Type item name..."); + } + + foreach (var (id, name) in _searchResults) + { + bool selected = _discarding.Any(x => x.Item1 == id); + if (ImGui.Selectable(name, selected)) + { + if (!selected) + { + _discarding.Add((id, name)); + } + else + { + _discarding.Remove((id, name)); + } + + _configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList(); + _pluginInterface.SavePluginConfig(_configuration); + } + } + } + + 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); + + _configuration.DiscardingItems = _discarding.Select(x => x.ItemId).ToList(); + _pluginInterface.SavePluginConfig(_configuration); + } + } + + ImGui.EndChild(); + } + + private void UpdateResults() + { + if (string.IsNullOrEmpty(_itemName)) + _searchResults = new(); + else + { + _searchResults = _dataManager.GetExcelSheet() + .Where(x => x.RowId != 0) + .Where(x => !x.IsUnique && !x.IsUntradable) + .Where(x => x.ItemUICategory?.Value?.Name?.ToString() != "Currency") + .Where(x => !string.IsNullOrEmpty(x.Name) && + x.Name.ToString().Contains(_itemName, StringComparison.CurrentCultureIgnoreCase)) + .Select(x => (x.RowId, x.Name.ToString())) + .ToList(); + } + } +} diff --git a/ARDiscard/Configuration.cs b/ARDiscard/Configuration.cs new file mode 100644 index 0000000..99aaf10 --- /dev/null +++ b/ARDiscard/Configuration.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Dalamud.Configuration; + +namespace ARDiscard; + +public class Configuration : IPluginConfiguration +{ + public int Version { get; set; } = 1; + public List DiscardingItems = new(); +} diff --git a/ARDiscard/InventoryUtils.cs b/ARDiscard/InventoryUtils.cs new file mode 100644 index 0000000..9aae53a --- /dev/null +++ b/ARDiscard/InventoryUtils.cs @@ -0,0 +1,59 @@ +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); + for (int i = 0; i < container->Size; ++i) + { + var item = container->GetInventorySlot(i); + if (item != null) + { + if (_configuration.DiscardingItems.Contains(item->ItemID)) + { + return item; + } + } + } + } + + return null; + } + + public unsafe void Discard(InventoryItem* item) + { + _discardItem(AgentInventoryContext.Instance(), item, item->Container, item->Slot, 0); + } +} diff --git a/ARDiscard/packages.lock.json b/ARDiscard/packages.lock.json new file mode 100644 index 0000000..467f0f2 --- /dev/null +++ b/ARDiscard/packages.lock.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "dependencies": { + "net7.0-windows7.0": { + "DalamudPackager": { + "type": "Direct", + "requested": "[2.1.11, )", + "resolved": "2.1.11", + "contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw==" + } + } + } +} \ No newline at end of file