namespace Aeshnidae.Mule;
///
/// /mule-data [page] - the vault as the Aelrynth plugins read it (RynthInventory's Global
/// Inventory shows it with icons). The same contents /mule list prints, one chat line per
/// page in the CompanionFeed shape, so a player without the plugin loses nothing and the
/// plugin can do nothing a player could not type.
///
/// ~ael1 {"t":"mule","page":1,"pages":3,"count":57,"max":1000,"items":[...]}
///
/// An item is {"o":objectId,"w":wcid,"n":name,"i":icon,"u":underlay,"v":overlay,
/// "s":stack,"m":maxStack,"y":itemType,"e":uiEffects}. Read-only: nothing here moves or
/// loads anything, and a vault still loading answers {"t":"mule","ready":false}, never an
/// empty list (the same rule every /mule verb follows).
///
public static class MuleData
{
// A chat line must stay well inside one network message; ~130 bytes an item.
public const int PerPage = 25;
[CommandHandler("mule-data", AccessLevel.Player, CommandHandlerFlag.RequiresWorld, -1,
"The vault as data, one page a line, for the Aelrynth plugins. /mule list is the human version.",
"/mule-data [page]")]
public static void Handle(Session session, params string[] parameters)
{
var player = session?.Player;
if (player is null)
return;
if (!Mod.Settings.Enabled || !MuleDb.Ready)
{
CompanionFeed.Send(session, new { t = "mule", ready = false, why = "closed" });
return;
}
try
{
var accountId = player.Account?.AccountId ?? 0;
var handle = Vault.Get(accountId, out _);
if (handle is null || !handle.Ready || handle.Fault.Length > 0)
{
CompanionFeed.Send(session, new { t = "mule", ready = false, why = handle?.Fault.Length > 0 ? "fault" : "loading" });
return;
}
var page = parameters is { Length: > 0 } && int.TryParse(parameters[0], out var p) && p > 0 ? p : 1;
// The order /mule list uses: newest first (later links, then later placement).
List all;
lock (Vault.Gate(accountId))
{
all = handle.Links
.Select((link, seq) => (link, seq))
.SelectMany(x => x.link.Inventory.Values.Select(i => (item: i, x.seq, pos: i.PlacementPosition ?? 0)))
.OrderByDescending(x => x.seq)
.ThenByDescending(x => x.pos)
.Select(x => x.item)
.ToList();
}
var pages = Math.Max(1, (all.Count + PerPage - 1) / PerPage);
page = Math.Clamp(page, 1, pages);
var items = all.Skip((page - 1) * PerPage).Take(PerPage).Select(i => new
{
o = i.Guid.Full,
w = i.WeenieClassId,
n = i.Name,
i = i.IconId,
u = i.IconUnderlayId == 0 ? (uint?)null : i.IconUnderlayId,
v = i.IconOverlayId == 0 ? (uint?)null : i.IconOverlayId,
s = i.StackSize is > 1 ? i.StackSize : null,
m = i.MaxStackSize is > 1 ? i.MaxStackSize : null,
y = (uint)(i.ItemType),
e = i.UiEffects is { } fx && (uint)fx != 0 ? (uint?)fx : null,
}).ToList();
CompanionFeed.Send(session, new { t = "mule", ready = true, page, pages, count = all.Count, max = Mod.Settings.MaxItems, items });
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] /mule-data failed for {player.Name}: {ex}", ModManager.LogLevel.Error);
CompanionFeed.Send(session, new { t = "mule", ready = false, why = "error" });
}
}
}