Skip to content

Player Inventory Management

Player inventory manipulation is one of the most common operations in RocketMod plugin development. Nearly every server-side plugin — moderation tools, admin kits, spawn menus, economy systems, quest rewards, loot distributions — interacts with a player's inventory at some point. This article covers the complete RocketMod API surface for reading, writing, and managing player inventories, including item addition, removal, inspection, and the event hooks that fire when inventory state changes.

The patterns documented here are drawn from production inventory-management plugins used on 57 Studios™ Horizon Life RP servers. They have been validated against Unturned 3.x with RocketMod 4.x.

RocketMod plugin giving an item to a player inventory

Prerequisites

  • A working Unturned dedicated server with RocketMod installed. See RocketMod and OpenMod Plugin Basics for setup.
  • Visual Studio 2022 with .NET Framework 4.7.2 targeting.
  • Familiarity with UnturnedPlayer, IRocketCommand, and the RocketMod event model.
  • Basic understanding of Unturned's item ID system (numeric asset IDs mapped in item .dat files).

What you'll learn

  • How to add items to a player's inventory using GiveItem.
  • How to remove items from a player's inventory by type, position, or quantity.
  • How to clear a player's entire inventory.
  • How to iterate over inventory contents and inspect item metadata.
  • How to detect inventory changes through RocketMod events.
  • How to spawn items on the ground at a player's position.
  • How to handle edge cases: full inventories, stack limits, quality, durability.
  • How to implement a permission-gated admin kit command.

The UnturnedPlayer inventory API

RocketMod exposes a player's inventory through properties and methods on the UnturnedPlayer object. The primary entry points are GiveItem, RemoveItem, and Player.inventory (which exposes the underlying SDG PlayerInventory instance).

Adding items to inventory

The most common operation is adding an item to a player's inventory. RocketMod provides the GiveItem method in two overloads:

csharp
// Overload 1: By item GUID string
public void GiveItem(string itemGuid, byte amount)

// Overload 2: By item ID with quality and durability
public void GiveItem(ushort id, byte amount, byte quality, byte durability, byte[] state)

The first overload takes the item's GUID string as the first parameter. This is a 32-character hexadecimal string (with hyphens) that identifies the item asset uniquely. For example, the GUID for a "Dragonfang" weapon might be "3a8f7e21-bc44-4d91-9f06-5c2a1b3d8e70". This GUID is defined in the item's asset file and is consistent across all Unturned installations.

csharp
UnturnedPlayer player = UnturnedPlayer.FromName("Lloyd");

// Give 5 units of an item by GUID
player.GiveItem("3a8f7e21-bc44-4d91-9f06-5c2a1b3d8e70", 5);

The second overload uses the numeric item ID (a ushort) and includes quality, durability, and state data. This overload is used for items that need specific quality values (weapons with attachments, food with conditional states).

csharp
// Give 1 item with full quality and no state data
player.GiveItem(1078, 1, 100, 100, new byte[0]);

Both overloads return booltrue if the item was successfully added, false if it was not (typically because the inventory is full or the item ID is invalid).

Item state and quality

Unturned items carry state data that affects their behavior:

FieldTypePurpose
idushortNumeric item asset ID
amountbyteStack size (1–255 for stackable items)
qualitybyteCondition/durability (0–100)
durabilitybyteEffective durability after repair (0–100)
statebyte[]Binary state data (weapon attachments, food calories, etc.)

The state byte array is item-type-specific. For weapons, it encodes the sight, magazine, barrel, grip, and tactical attachment IDs. For food items, it encodes remaining calories. Passing an empty array is valid for items that do not use state data.

Removing items

RocketMod provides RemoveItem to remove items from inventory by position:

csharp
public void RemoveItem(byte page, byte index)

The inventory is organized into pages (sometimes called "columns"):

PageContentCapacity
0Items (storage slots)36 slots (6 rows × 6 columns)
1Clothing (wearables)7 slots (hat, shirt, pants, mask, backpack, vest, glasses)
2Hotbar6 slots
3Storage (currently open container)Variable (depends on container type)
4Area (ground items nearby)50 slots
csharp
// Remove the item in the first slot of page 0 (items page)
player.RemoveItem(0, 0);

To remove a specific type of item, you must iterate the inventory, find the item by its ID, and remove it by position:

csharp
public static int RemoveItemById(UnturnedPlayer player, ushort itemId)
{
    int removedCount = 0;

    for (byte page = 0; page < 5; page++)
    {
        for (byte index = 0; index < player.Inventory.getItemCount(page); index++)
        {
            ItemJar jar = player.Inventory.getItem(page, index);
            if (jar != null && jar.item.id == itemId)
            {
                player.RemoveItem(page, index);
                removedCount++;
                // Re-count items on this page because indices shifted
                index--;
            }
        }
    }

    return removedCount;
}

Clearing inventory

To clear a player's entire inventory, iterate all pages and remove every item:

csharp
public static void ClearInventory(UnturnedPlayer player)
{
    for (byte page = 0; page < 5; page++)
    {
        byte count = player.Inventory.getItemCount(page);
        for (byte index = 0; index < count; index++)
        {
            player.RemoveItem(page, 0); // Always remove index 0; items shift down
        }
    }
}

Note the removal pattern: always remove index 0 and let items shift down, rather than removing by the original index. This avoids index-out-of-range errors.

Inventory events

RocketMod fires the following inventory-related events through UnturnedPlayerEvents:

EventFires whenParameter notes
OnPlayerInventoryAddedAn item is added to inventoryContains item, page, index, amount
OnPlayerInventoryRemovedAn item is removed from inventoryContains item, page, index, amount
OnPlayerInventoryResizedInventory capacity changes (container opened/closed)Contains new capacity per page

These events receive an UnturnedPlayer instance and event-specific arguments. They fire synchronously during the inventory operation.

csharp
UnturnedPlayerEvents.OnPlayerInventoryAdded += (player, inventoryItem) =>
{
    Logger.Log($"{player.CharacterName} received item {inventoryItem.Item.id} ({inventoryItem.Amount}x)");
};

Implementing a /kit command

The following example implements a permission-gated kit command that gives a predefined set of items to the calling player. The kit contents are defined in the plugin's configuration class.

csharp
using Rocket.API;
using Rocket.Unturned.Chat;
using Rocket.Unturned.Player;
using System.Collections.Generic;

namespace MyAdminSuite.Commands
{
    public class KitCommand : IRocketCommand
    {
        public string Name => "kit";
        public string Help => "Claim a predefined starter kit.";
        public string Syntax => "/kit";
        public List<string> Aliases => new List<string>();
        public List<string> Permissions => new List<string> { "myadminsuite.kit" };
        public AllowedCaller AllowedCaller => AllowedCaller.Player;

        public void Execute(IRocketPlayer caller, string[] command)
        {
            UnturnedPlayer player = (UnturnedPlayer)caller;

            KitConfiguration kit = AdminSuitePlugin.Instance.Configuration.Instance.StarterKit;

            foreach (KitItem item in kit.Items)
            {
                bool success = player.GiveItem(item.ItemGuid, item.Amount);
                if (!success)
                {
                    UnturnedChat.Say(caller, $"Could not give {item.Amount}x of item {item.ItemGuid}. Inventory may be full.", UnityEngine.Color.yellow);
                }
            }

            UnturnedChat.Say(caller, "Kit claimed!", UnityEngine.Color.green);
        }
    }
}

KitConfiguration.cs:

csharp
using Rocket.API;
using System.Collections.Generic;

namespace MyAdminSuite
{
    public class KitConfiguration : IRocketPluginConfiguration
    {
        public string KitName;
        public List<KitItem> Items;

        public void LoadDefaults()
        {
            KitName = "Starter";
            Items = new List<KitItem>
            {
                new KitItem { ItemGuid = "3a8f7e21-bc44-4d91-9f06-5c2a1b3d8e70", Amount = 1 },
                new KitItem { ItemGuid = "b44d91-9f06-5c2a", Amount = 5 },
            };
        }
    }

    public class KitItem
    {
        public string ItemGuid;
        public byte Amount;
    }
}

The kit configuration defines items by their GUID string rather than their numeric ID. This is the preferred approach for RocketMod kit plugins because GUIDs are human-readable in configuration and survive asset reorganization. The GiveItem method resolves the GUID to the correct item asset at runtime.

Cooldown enforcement

Kits should have a per-player cooldown to prevent repeated claiming. The same CooldownTracker pattern from the god/vanish article applies:

csharp
if (AdminSuitePlugin.Instance.KitCooldown.IsOnCooldown(caller.Id))
{
    UnturnedChat.Say(caller, "Kit is on cooldown.", UnityEngine.Color.red);
    return;
}
AdminSuitePlugin.Instance.KitCooldown.SetUsed(caller.Id);

Spawning items on the ground

Sometimes you want to drop items at the player's position rather than adding them directly to the inventory (full inventory fallback, loot distribution, event rewards). RocketMod provides ItemManager.dropItem for this:

csharp
using SDG.Unturned;

public static void DropItemAtPlayer(UnturnedPlayer player, ushort itemId, byte amount)
{
    ItemAsset asset = Assets.find(EAssetType.ITEM, itemId) as ItemAsset;
    if (asset == null) return;

    Item item = new Item(itemId, amount);
    ItemManager.dropItem(item, player.Position, true, true, true);
}

The three boolean parameters control:

  1. Dedicated server flag — always true on a server.
  2. Broadcast flagtrue to make the item visible to all players; false for client-side only.
  3. Spawn effect flagtrue to play the item spawn effect (blue sparkle).

Implementing a /clearinventory command

The following command clears the calling player's inventory with a confirmation requirement to prevent accidental use:

csharp
using Rocket.API;
using Rocket.Unturned.Chat;
using Rocket.Unturned.Player;
using System.Collections.Generic;

namespace MyAdminSuite.Commands
{
    public class ClearInventoryCommand : IRocketCommand
    {
        public string Name => "clearinventory";
        public string Help => "Clear your entire inventory.";
        public string Syntax => "/clearinventory";
        public List<string> Aliases => new List<string> { "clearinv" };
        public List<string> Permissions => new List<string> { "myadminsuite.clearinventory" };
        public AllowedCaller AllowedCaller => AllowedCaller.Player;

        public void Execute(IRocketPlayer caller, string[] command)
        {
            UnturnedPlayer player = (UnturnedPlayer)caller;
            ClearInventory(player);

            int totalClear = CountInventory(player);
            UnturnedChat.Say(caller, $"Inventory cleared ({totalClear} items removed).", UnityEngine.Color.green);
            Logger.Log($"{caller.DisplayName} cleared their inventory ({totalClear} items).");
        }

        private void ClearInventory(UnturnedPlayer player)
        {
            for (byte page = 0; page < 5; page++)
            {
                byte count = player.Inventory.getItemCount(page);
                for (byte index = 0; index < count; index++)
                {
                    player.RemoveItem(page, 0);
                }
            }
        }

        private int CountInventory(UnturnedPlayer player)
        {
            int total = 0;
            for (byte page = 0; page < 5; page++)
            {
                for (byte index = 0; index < player.Inventory.getItemCount(page); index++)
                {
                    ItemJar jar = player.Inventory.getItem(page, index);
                    if (jar != null) total++;
                }
            }
            return total;
        }
    }
}

Item validation and safeguards

Preventing item duplication

Item duplication is a critical server stability concern. The most common source of RocketMod-based duplication is calling GiveItem twice due to double event firing. The event guard pattern from the plugin basics article applies here:

csharp
private bool _isGivingItem = false;

public void SafeGiveItem(UnturnedPlayer player, string itemGuid, byte amount)
{
    if (_isGivingItem) return;
    _isGivingItem = true;
    try
    {
        player.GiveItem(itemGuid, amount);
    }
    finally
    {
        _isGivingItem = false;
    }
}

Item ID validation

When accepting item IDs from user input (e.g., a /give command), validate that the ID or GUID maps to a valid item asset before calling GiveItem. An invalid ID will silently fail — the method returns false but does not throw.

csharp
public static bool IsValidItemAsset(string itemGuid)
{
    ItemAsset asset = Assets.find(EAssetType.ITEM, itemGuid) as ItemAsset;
    return asset != null;
}

Full inventory detection

Check inventory capacity before attempting to give items:

csharp
public static bool HasFreeSlot(UnturnedPlayer player)
{
    for (byte page = 0; page < 5; page++)
    {
        byte count = player.Inventory.getItemCount(page);
        byte capacity = player.Inventory.getCapacity(page);
        if (count < capacity) return true;
    }
    return false;
}

Common errors and diagnostics

SymptomCauseResolution
GiveItem returns false for a known valid itemNumeric ID passed to the GUID overload, or GUID passed to the numeric overloadVerify you are using the correct overload — GUID strings for GiveItem(string, byte), numeric IDs for GiveItem(ushort, byte, byte, byte, byte[])
Item appears in inventory but has zero qualityQuality parameter omitted in the numeric overload callThe numeric overload requires an explicit quality value; use GiveItem(ushort, byte, byte, byte, byte[]) with quality=100
IndexOutOfRangeException when removing itemsIterating forward while removing items shifts indicesAlways remove from index 0 in a loop, or iterate backward
NullReferenceException accessing Player.InventoryPlayer object is stale (disconnected)Check player.IsConnected before accessing inventory properties
Item spawns but no one can pick it updedicated parameter set to false in ItemManager.dropItemAlways pass true for the dedicated server flag
Kit items not awarded on first server loadKit configuration not loaded before kit command callEnsure Configuration.Instance is accessed after plugin load completes
Silent failure when passing a GUID without hyphensGUID string does not parse correctlyThe GUID must include hyphens in the standard 8-4-4-4-12 format

Frequently asked questions

Can I give an item to a player who is not connected?

No. GiveItem operates on a live UnturnedPlayer instance. For offline players, you would need to manipulate the player's save file directly, which is outside the RocketMod API scope.

What happens if I give a stackable item and the inventory is full?

GiveItem first attempts to merge the new item with an existing partial stack. If no partial stack exists and the inventory has no empty slots, the method returns false. The item is not lost; the calling code is responsible for handling the failure (e.g., dropping the item on the ground).

How do I give a weapon with specific attachments?

Use the numeric overload GiveItem(ushort id, byte amount, byte quality, byte durability, byte[] state) and encode the attachment IDs in the state byte array. The state format for weapons is documented in Unturned's ItemWeaponAsset class. Each attachment slot (sight, magazine, barrel, grip, tactical) occupies a specific offset in the state array.

Does RemoveItem drop the item on the ground or destroy it?

RemoveItem destroys the item. It does not spawn the item on the ground. To drop an item from inventory to the ground, use ItemManager.dropItem with the item's data before calling RemoveItem.

How do I check how many of a specific item a player has?

Iterate over the player's inventory pages and count matching item.id values:

csharp
public static int CountItem(UnturnedPlayer player, ushort itemId)
{
    int count = 0;
    for (byte page = 0; page < 5; page++)
    {
        for (byte index = 0; index < player.Inventory.getItemCount(page); index++)
        {
            ItemJar jar = player.Inventory.getItem(page, index);
            if (jar != null && jar.item.id == itemId)
            {
                count += jar.item.amount;
            }
        }
    }
    return count;
}

Can I prevent a player from dropping a specific item?

RocketMod does not have a built-in drop-blocking event. You can intercept the drop by subscribing to the appropriate player update packet, but this is an advanced technique that requires packet-level access through RocketMod's event system or a separate packet-interception library.

How do I give an item and play an effect at the same time?

Chain the GiveItem call with a TriggerEffect call. The item is added synchronously, so the effect plays after the item is confirmed in the inventory:

csharp
public static void GiveItemWithEffect(UnturnedPlayer player, ushort itemId, byte amount, string effectGuid)
{
    bool success = player.GiveItem(itemId, amount, 100, 100, new byte[0]);
    if (success)
    {
        player.TriggerEffect(effectGuid);
        UnturnedChat.Say(player, $"Received item {itemId}.", Color.green);
    }
}

This pattern is used for loot rewards, quest completions, and shop purchases.

How do I check if a player's inventory contains a specific item without iterating all pages?

RocketMod does not provide a HasItem convenience method. The standard approach is to iterate over inventory pages and check each ItemJar.item.id. For performance-sensitive code, maintain a plugin-side dictionary that tracks item counts and is updated in response to OnPlayerInventoryAdded and OnPlayerInventoryRemoved events. This avoids per-check iteration at the cost of keeping the dictionary in sync.

How do I restore a player's inventory after a rollback?

If you need to restore a player's inventory to a previous state, maintain a snapshot queue in your plugin. Before any inventory-modifying operation, take a snapshot of the player's current inventory (iterate all pages and serialize item data to a list). Store the snapshot in a dictionary keyed by player Steam64 ID with a timestamp. When a rollback is requested, clear the player's inventory and re-add all items from the snapshot using GiveItem.

How do I give an item that exists in a Workshop mod that is not loaded on the server?

You cannot. The item asset must be loaded on the server for GiveItem to work. Verify that the Workshop mod containing the item is in the server's WorkshopDownloadConfig.json. If the item ID is valid but the asset is not loaded, GiveItem returns false with no error message.

Can I use GiveItem with item IDs above ushort.MaxValue?

No. The GiveItem numeric overload uses ushort (16-bit unsigned integer, max value 65535). Unturned item IDs do not exceed this range. If you try to pass a value above 65535, the compiler will reject the code. If you have a value from a string source, validate it with ushort.TryParse() before calling GiveItem.

How do I handle the state byte array for medical items?

Medical items (medkits, bandages, antibiotics) use the state byte array to encode their remaining heal amount. The first byte of the state array is the heal value (0–100). When giving medical items with partial heal remaining, set state[0] to the desired heal percentage. For full heal, pass 100 as the quality parameter and an empty state array — the game defaults to full heal.

What happens to item durability when using GiveItem with the GUID overload?

The GUID overload GiveItem(string, byte) spawns items with default quality (100) and default durability (100). If you need items with specific durability values, use the numeric overload GiveItem(ushort, byte, byte, byte, byte[]) and pass explicit quality and durability values. This is important for item-repair systems where item condition matters.

How do I prevent item duplication when using inventory events?

Item duplication via inventory events happens when your plugin calls GiveItem inside an OnPlayerInventoryAdded or OnPlayerInventoryRemoved handler, which can trigger the event again recursively. Use a re-entrancy guard flag:

csharp
private static bool _isHandlingInventoryEvent = false;

UnturnedPlayerEvents.OnPlayerInventoryAdded += (player, item) =>
{
    if (_isHandlingInventoryEvent) return;
    _isHandlingInventoryEvent = true;
    try
    {
        // Your inventory event logic here
    }
    finally
    {
        _isHandlingInventoryEvent = false;
    }
};

This prevents the event handler from re-entering when your own GiveItem call fires the same event.

Can I give items to all players at once?

Iterate over all connected players and call GiveItem on each:

csharp
public static void GiveItemToAll(ushort itemId, byte amount)
{
    foreach (SteamPlayer client in Provider.clients)
    {
        UnturnedPlayer player = UnturnedPlayer.FromSteamPlayer(client);
        player.GiveItem(itemId, amount, 100, 100, new byte[0]);
    }
}

This pattern is used for server-wide give events, holiday rewards, and compensation distributions.

Inventory event-driven auto-sort

The following pattern automatically sorts a player's inventory when a new item is added. This keeps the inventory organized without manual player intervention:

csharp
public static class InventoryAutoSort
{
    private static readonly HashSet<ulong> _pendingSort = new HashSet<ulong>();

    public static void Initialize()
    {
        UnturnedPlayerEvents.OnPlayerInventoryAdded += (player, item) =>
        {
            ScheduleSort(player);
        };
    }

    private static void ScheduleSort(UnturnedPlayer player)
    {
        ulong id = player.CSteamID.m_SteamID;
        if (_pendingSort.Add(id))
        {
            AdminSuitePlugin.Instance.StartCoroutine(DelayedSort(player));
        }
    }

    private static IEnumerator DelayedSort(UnturnedPlayer player)
    {
        yield return new WaitForSeconds(0.5f);
        ulong id = player.CSteamID.m_SteamID;
        _pendingSort.Remove(id);
        SortInventory(player);
    }

    public static void SortInventory(UnturnedPlayer player)
    {
        // Read all items from page 0 (items page)
        List<ItemData> items = new List<ItemData>();
        byte count = player.Inventory.getItemCount(0);
        for (byte i = 0; i < count; i++)
        {
            ItemJar jar = player.Inventory.getItem(0, i);
            if (jar != null)
            {
                items.Add(new ItemData
                {
                    Id = jar.item.id,
                    Amount = jar.item.amount,
                    Quality = jar.item.quality,
                    State = jar.item.state
                });
            }
        }

        // Clear the page
        for (byte i = 0; i < count; i++)
            player.RemoveItem(0, 0);

        // Sort by item ID
        items.Sort((a, b) => a.Id.CompareTo(b.Id));

        // Re-add in sorted order
        foreach (ItemData item in items)
        {
            player.GiveItem(item.Id, item.Amount, item.Quality, 100, item.State);
        }
    }

    private class ItemData
    {
        public ushort Id;
        public byte Amount;
        public byte Quality;
        public byte[] State;
    }
}

Cross-references

Document history

VersionDateAuthorNotes
1.02025-07-2757 StudiosInitial publication. GiveItem, RemoveItem, ClearInventory API reference, kit implementation, inventory events, safeguards, and diagnostics.