Dropbox Queue commands

This commit is contained in:
Liza 2024-05-05 20:10:34 +02:00
parent ccfb53df2e
commit 0fe6450ab1
Signed by: liza
GPG Key ID: 7199F8D727D55F67
7 changed files with 285 additions and 2 deletions

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "ECommons"] [submodule "ECommons"]
path = ECommons path = ECommons
url = https://github.com/NightmareXIV/ECommons.git url = https://github.com/NightmareXIV/ECommons.git
[submodule "LLib"]
path = LLib
url = https://git.carvel.li/liza/LLib.git

View File

@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ECommons", "ECommons\ECommo
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoRetainerAPI", "AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj", "{FD2D72FE-F7AE-43AF-8453-66A830AA38D7}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoRetainerAPI", "AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj", "{FD2D72FE-F7AE-43AF-8453-66A830AA38D7}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LLib", "LLib\LLib.csproj", "{FAB49680-A224-4668-A758-7A563F26FAFE}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -24,5 +26,9 @@ Global
{FD2D72FE-F7AE-43AF-8453-66A830AA38D7}.Debug|Any CPU.Build.0 = Debug|x64 {FD2D72FE-F7AE-43AF-8453-66A830AA38D7}.Debug|Any CPU.Build.0 = Debug|x64
{FD2D72FE-F7AE-43AF-8453-66A830AA38D7}.Release|Any CPU.ActiveCfg = Release|x64 {FD2D72FE-F7AE-43AF-8453-66A830AA38D7}.Release|Any CPU.ActiveCfg = Release|x64
{FD2D72FE-F7AE-43AF-8453-66A830AA38D7}.Release|Any CPU.Build.0 = Release|x64 {FD2D72FE-F7AE-43AF-8453-66A830AA38D7}.Release|Any CPU.Build.0 = Release|x64
{FAB49680-A224-4668-A758-7A563F26FAFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FAB49680-A224-4668-A758-7A563F26FAFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FAB49680-A224-4668-A758-7A563F26FAFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FAB49680-A224-4668-A758-7A563F26FAFE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -0,0 +1,263 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Dalamud.Game.Command;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using LLib;
namespace KitchenSink.Commands
{
internal sealed class DropboxQueue : IDisposable
{
private static readonly InventoryType[] DefaultInventoryTypes =
[
InventoryType.Inventory1,
InventoryType.Inventory2,
InventoryType.Inventory3,
InventoryType.Inventory4,
InventoryType.Crystals,
InventoryType.Currency,
];
private readonly ICommandManager _commandManager;
private readonly IChatGui _chatGui;
private readonly DropboxApi _dropboxApi;
public DropboxQueue(DalamudReflector reflector, ICommandManager commandManager, IChatGui chatGui,
IPluginLog pluginLog)
{
_commandManager = commandManager;
_chatGui = chatGui;
_dropboxApi = new DropboxApi(reflector, pluginLog);
_commandManager.AddHandler("/dbq", new CommandInfo(Queue)
{
HelpMessage =
$"Queue items to be imported into dropbox{Environment.NewLine}\t/dbq item1:qty1 item2:qty2 [...] - queues items for the next trade (use * as quantity for 'all'){Environment.NewLine}\t/dbq clear - remove all items in the queue{Environment.NewLine}\t/dbq crystals - show the command to fill all shards & crystals to 9999"
});
}
private void Queue(string command, string arguments)
{
if (arguments == "crystals")
{
ShowCrystals();
}
else if (arguments == "clear")
{
_dropboxApi.ClearQueue();
}
else
{
var parsedItems = arguments.Split(' ')
.Select(x => x.Split(':'))
.Select(x =>
{
if (x.Length != 2)
return ($"Unable to parse {string.Join(" ", x)}.", null);
if (!uint.TryParse(x[0], out uint itemId))
return ($"Unable to parse item id {x[0]}.", null);
int needed;
if (x[1] == "*")
needed = int.MaxValue;
else if (!int.TryParse(x[1], out needed))
return ($"Unable to parse quantity {x[1]}.", null);
return (string.Empty, new NeededItem(itemId, needed));
}).ToList();
if (parsedItems.Count == 0)
return;
var problematic = parsedItems.Where(x => !string.IsNullOrEmpty(x.Item1)).Select(x => x.Item1).ToList();
if (problematic.Count == 1)
{
_chatGui.PrintError($"dbq: {problematic.First()}");
}
else if (problematic.Count >= 2)
{
_chatGui.PrintError("dbq: Multiple errors occured:");
foreach (string problem in problematic)
_chatGui.PrintError($" - {problem}");
}
else
{
Dictionary<uint, ItemCount> allItems = GetItemCounts();
foreach (var neededItem in parsedItems.Select(x => x.Item2).Cast<NeededItem>())
{
if (!allItems.TryGetValue(neededItem.ItemId, out ItemCount? itemCount))
continue;
int normalQualityQuantity = Math.Min(neededItem.Needed, itemCount.NormalQualityQuantity);
int highQualityQuantity = Math.Min(neededItem.Needed - normalQualityQuantity,
itemCount.HighQualityQuantity);
if (normalQualityQuantity > 0)
_dropboxApi.EnqueueItem(neededItem.ItemId, false, normalQualityQuantity);
if (highQualityQuantity > 0)
_dropboxApi.EnqueueItem(neededItem.ItemId, true, highQualityQuantity);
if (normalQualityQuantity > 0 || highQualityQuantity > 0)
{
allItems[neededItem.ItemId] =
new ItemCount(
itemCount.NormalQualityQuantity - normalQualityQuantity,
itemCount.HighQualityQuantity - highQualityQuantity);
}
}
}
}
}
private unsafe Dictionary<uint, ItemCount> GetItemCounts()
{
Dictionary<uint, ItemCount> allItems = new();
InventoryManager* inventoryManager = InventoryManager.Instance();
if (inventoryManager == null)
return allItems;
foreach (var type in DefaultInventoryTypes)
{
InventoryContainer* container = inventoryManager->GetInventoryContainer(type);
for (int i = 0; i < container->Size; ++i)
{
var item = container->GetInventorySlot(i);
if (item != null && item->ItemID != 0)
{
if (item->Spiritbond > 0)
continue;
if (!allItems.TryGetValue(item->ItemID, out ItemCount? itemCount))
itemCount = new(0, 0);
if (item->Flags.HasFlag(InventoryItem.ItemFlags.HQ))
itemCount = itemCount with
{
HighQualityQuantity = itemCount.HighQualityQuantity + (int)item->Quantity
};
else
itemCount = itemCount with
{
NormalQualityQuantity = itemCount.NormalQualityQuantity + (int)item->Quantity
};
allItems[item->ItemID] = itemCount;
}
}
}
return allItems;
}
private unsafe void ShowCrystals()
{
InventoryManager* inventoryManger = InventoryManager.Instance();
if (inventoryManger == null)
return;
var missingCrystals = Enumerable.Range(2, 12).Select(itemId => (uint)itemId)
.Select(itemId => new NeededItem(itemId,
9999 - inventoryManger->GetItemCountInContainer(itemId, InventoryType.Crystals)))
.Where(x => x.Needed > 0)
.ToList();
if (missingCrystals.Count == 0)
{
_chatGui.Print("No crystals need to be filled");
return;
}
_chatGui.Print($"Command: /dbq {string.Join(" ", missingCrystals)}");
}
public void Dispose()
{
_commandManager.RemoveHandler("/dbq");
}
private sealed record NeededItem(uint ItemId, int Needed)
{
public override string ToString() => $"{ItemId}:{Needed}";
}
private sealed record ItemCount(int NormalQualityQuantity, int HighQualityQuantity);
private sealed class DropboxApi
{
private readonly DalamudReflector _reflector;
private readonly IPluginLog _pluginLog;
public DropboxApi(DalamudReflector reflector, IPluginLog pluginLog)
{
_reflector = reflector;
_pluginLog = pluginLog;
}
[SuppressMessage("Performance", "CA2000", Justification = "Should not dispose other plugin")]
public void EnqueueItem(uint itemId, bool hq, int quantity)
{
_pluginLog.Verbose($"Preparing to queue {itemId}, {hq}, {quantity}");
if (quantity < 0)
return;
if (!TryGetItemQuantities(out IDalamudPlugin? dropboxPlugin, out IDictionary? itemQuantities))
throw new InvalidOperationException("Could not retrieve item quantities");
uint[] tradeableItemIds = (uint[])dropboxPlugin.GetType()
.GetField("TradeableItems", BindingFlags.Public | BindingFlags.Instance)!
.GetValue(dropboxPlugin)!;
if (!tradeableItemIds.Contains(itemId))
{
_pluginLog.Warning($"Item {itemId} is untradable");
return;
}
var itemDescriptorType = itemQuantities.GetType().GetGenericArguments()[0];
var itemDescriptor = Activator.CreateInstance(itemDescriptorType, args: [itemId, hq])!;
var queuedQuantity = itemQuantities[itemDescriptor];
_pluginLog.Verbose($"Retrieved quantity: {queuedQuantity}");
if (queuedQuantity == null)
return;
var boxType = queuedQuantity.GetType();
var valueField = boxType.GetField("Value", BindingFlags.Public | BindingFlags.Instance)!;
_pluginLog.Information($"Adding {itemDescriptor} to queue");
valueField.SetValue(queuedQuantity, quantity);
}
public void ClearQueue()
{
if (!TryGetItemQuantities(out IDalamudPlugin? _, out IDictionary? itemQuantities))
throw new InvalidOperationException("Could not retrieve item quantities");
itemQuantities.Clear();
}
private bool TryGetItemQuantities([NotNullWhen(true)] out IDalamudPlugin? dropboxPlugin,
[NotNullWhen(true)] out IDictionary? itemQuantities)
{
if (!_reflector.TryGetDalamudPlugin("Dropbox", out dropboxPlugin))
{
itemQuantities = null;
return false;
}
var itemQueueUiType = dropboxPlugin.GetType().Assembly.GetType("Dropbox.ItemQueueUI")!;
itemQuantities =
(IDictionary?)itemQueueUiType.GetField("ItemQuantities", BindingFlags.Public | BindingFlags.Static)!
.GetValue(null);
return itemQuantities != null;
}
}
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<Version>0.2</Version> <Version>0.3</Version>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -56,6 +56,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj"/> <ProjectReference Include="..\AutoRetainerAPI\AutoRetainerAPI\AutoRetainerAPI.csproj"/>
<ProjectReference Include="..\ECommons\ECommons\ECommons.csproj"/> <ProjectReference Include="..\ECommons\ECommons\ECommons.csproj"/>
<ProjectReference Include="..\LLib\LLib.csproj" />
</ItemGroup> </ItemGroup>
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin"> <Target Name="RenameLatestZip" AfterTargets="PackagePlugin">

View File

@ -4,6 +4,7 @@ using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ECommons; using ECommons;
using KitchenSink.Commands; using KitchenSink.Commands;
using LLib;
namespace KitchenSink; namespace KitchenSink;
@ -13,19 +14,24 @@ internal sealed class KitchenSinkPlugin : IDalamudPlugin
{ {
private readonly AutoRetainerApi _autoRetainerApi; private readonly AutoRetainerApi _autoRetainerApi;
private readonly CharacterSwitch _characterSwitch; private readonly CharacterSwitch _characterSwitch;
private readonly DropboxQueue _dropboxQueue;
public KitchenSinkPlugin(DalamudPluginInterface pluginInterface, ICommandManager commandManager, public KitchenSinkPlugin(DalamudPluginInterface pluginInterface, ICommandManager commandManager,
IClientState clientState, IChatGui chatGui, INotificationManager notificationManager, IDtrBar dtrBar, IClientState clientState, IChatGui chatGui, INotificationManager notificationManager, IDtrBar dtrBar,
ICondition condition, IPluginLog pluginLog) ICondition condition, IPluginLog pluginLog, IFramework framework)
{ {
DalamudReflector reflector = new DalamudReflector(pluginInterface, framework, pluginLog);
ECommonsMain.Init(pluginInterface, this); ECommonsMain.Init(pluginInterface, this);
_autoRetainerApi = new AutoRetainerApi(); _autoRetainerApi = new AutoRetainerApi();
_characterSwitch = new CharacterSwitch(_autoRetainerApi, commandManager, clientState, chatGui, _characterSwitch = new CharacterSwitch(_autoRetainerApi, commandManager, clientState, chatGui,
notificationManager, dtrBar, condition, pluginLog); notificationManager, dtrBar, condition, pluginLog);
_dropboxQueue = new DropboxQueue(reflector, commandManager, chatGui, pluginLog);
} }
public void Dispose() public void Dispose()
{ {
_dropboxQueue.Dispose();
_characterSwitch.Dispose(); _characterSwitch.Dispose();
_autoRetainerApi.Dispose(); _autoRetainerApi.Dispose();
ECommonsMain.Dispose(); ECommonsMain.Dispose();

View File

@ -16,6 +16,9 @@
}, },
"ecommons": { "ecommons": {
"type": "Project" "type": "Project"
},
"llib": {
"type": "Project"
} }
} }
} }

1
LLib Submodule

@ -0,0 +1 @@
Subproject commit 3792244261a9f5426a7916f5a6dd1966238ba84a