Inventory extensions in CMAPI 1.1
CMAPI 1.1 provides an opt-in inventory virtualization layer for mods. The framework does not register slot rules, change inventory capacity, or stack items by default. A separately installed mod must register an IInventorySlotProvider before any layout changes.
Expanded Stacks is the SourceNine Labs companion implementation. It uses this API but remains an independent mod with its own manifest, configuration, version, and support boundary.
Design contract
Planet Crafter inventories and saves continue to contain individual native world objects. A provider changes only how those objects consume and display slots:
- every physical item keeps its native world-object ID;
- CMAPI adds no inventory serialization or mod-owned save record;
- a logical slot contains items that return the same provider-owned
SlotKey; Capacitycontrols how many matching physical items that logical slot can represent;- returning
nullpreserves one-item-per-slot behavior for that item; - the provider never receives a mutable Planet Crafter object.
The runtime applies the logical layout to item-aware additions, general fullness checks, inventory UI, logistics demand capacity, disintegrator output, and CMAPI's public item-grant paths. UI projection exists only during inventory display refresh; native transfers, networking, and save data remain physical.
Registration
Inventory extensions are capability-gated. A mod must check IsAvailable before registration:
using System;
using CMAPI;
public sealed class MaterialSlots : IInventorySlotProvider
{
public InventorySlotRule? GetSlotRule(
InventoryContext inventory,
InventoryItemDescriptor item)
{
if (inventory.Kind != InventoryKind.PlayerBackpack &&
inventory.Kind != InventoryKind.Container)
{
return null;
}
if (item.IsLocked || item.HasCustomState ||
item.UsableType != "Null" || item.EquipableType != "Null")
{
return null;
}
return new InventorySlotRule(
"Example.MaterialSlots:" + item.ItemId,
100
);
}
}
public sealed class MaterialSlotsMod : Mod
{
private IDisposable? registration;
public override void Entry(IModHelper helper)
{
if (!helper.InventoryExtensions.IsAvailable)
{
Monitor.Warning(
"Inventory extensions are unavailable on this game build."
);
return;
}
registration = helper.InventoryExtensions.RegisterSlotProvider(
new MaterialSlots()
);
}
}Only one provider can own the session layout. A second registration throws an InvalidOperationException because two independent grouping policies cannot be reconciled safely. Registration is owner-scoped and is removed if mod startup fails or the owner is unloaded.
Context and item metadata
InventoryContext supplies the native inventory ID and capacity, physical item count, detected inventory kind, and local network role. Providers can use it to exclude equipment or DNA inventories and to apply separate backpack/container policies.
InventoryItemDescriptor contains stable CMAPI-owned metadata:
- stable item/group ID and physical world-object ID;
- item category, subcategory, usable type, and equipable type names;
- locked state;
HasCustomStateplus an opaqueStateKey.
Items with custom state should normally return null. Text labels, linked inventories, secondary inventories, panels, growth, unit generation, settings, genetic traits, hunger, pet time, count vectors, linked objects, and stored energy make otherwise identical item IDs unsafe to merge into one visual slot. The opaque state key may be used for equality inside one API version, but its format is not a persistence contract.
Capacity and display behavior
A provider rule is evaluated for existing items and prospective additions. CMAPI enforces a maximum capacity of 1,000,000 items per logical slot. Returning different capacities for the same slot key in one layout is a provider error; the affected mutation is rejected and an attributed error is logged.
When a logical slot represents more than one item, CMAPI displays one native representative block with a quantity badge. A normal click transfers the native representative item. The next item becomes the representative after the game's normal refresh. No synthetic item is created.
GetLayout and GetLoadedLayouts expose counts without mutable game objects. They are suitable for status commands and configuration validation, not for editing inventory contents.
Save and removal safety
Virtualization can allow a physical item count greater than the inventory's native capacity. The save is still native, but opening it without the provider may make overflow items inaccessible until the provider returns or the count is reduced.
Before disabling a provider, shrinking capacity, changing exclusions, or uninstalling its mod:
- call
GetSafetyReport()or runinventory_safety; - reduce every reported inventory to its native physical capacity;
- apply the provider change;
- reopen the inventories and save once under native capacity.
CMAPI reports unsafe provider removal but never deletes, moves, or rewrites the overflow items. Provider mods should reject unsafe configuration changes before unregistering, as Expanded Stacks does.
Multiplayer policy
InventoryContext.NetworkMode reports whether the local process is single player, host, client, or dedicated server. CMAPI 1.1 cannot verify the remote mod list or configuration. Every peer must therefore run the same provider version and the same layout-affecting settings.
CMAPI logs a warning whenever a provider is registered in a networked session. Provider authors should document this requirement, test host and joining-client transfers, and fail conservatively when local authority is insufficient.
Compatibility behavior
The CMAPI.InventoryExtensions feature ID is advertised only when the tested member-level seam is available. If a game update removes a required hook, CMAPI installs no inventory patches, IsAvailable returns false, and native inventory behavior continues. gameinfo lists the missing members.
See FULL-GAME-COMPATIBILITY.md for the tested Planet Crafter build and TESTING.md for the release matrix.