Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Bill3621/CustomItems/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Before custom items can be used in-game, they must be registered with the CustomItems framework. Registration assigns unique IDs, validates item names, and makes items available for spawning and event handling.

Registration methods

The CustomItems static class provides four core methods for managing item registration:

Register

public static void Register(CustomItem item)
Registers a single custom item instance. What it does:
  1. Validates the item name is unique
  2. Assigns a unique ID
  3. Adds to internal dictionaries (_itemsById and _itemsByName)
  4. Calls OnRegistered() on the item
Implementation:
CustomItems.cs:26-38
public static void Register(CustomItem item)
{
    if (_itemsByName.ContainsKey(item.Name))
        throw new InvalidOperationException($"Item '{item.Name}' already registered.");

    item.Id = GetNextId();
    _itemsById[item.Id] = item;
    _itemsByName[item.Name] = item;

    item.OnRegistered();

    Log.Debug($"Registered item '{item.Name}' with ID {item.Id} and type {item.Type}.");
}
Example usage:
var healingSyringe = new HealingSyringe();
CustomItems.Register(healingSyringe);
Attempting to register an item with a duplicate name will throw an InvalidOperationException.

RegisterAll

public static List<CustomItem> RegisterAll()
Automatically discovers and registers all CustomItem subclasses in the calling assembly. What it does:
  1. Uses reflection to find all non-abstract CustomItem subclasses
  2. Creates instances using Activator.CreateInstance()
  3. Registers each instance via Register()
  4. Returns a list of successfully registered items
Implementation:
CustomItems.cs:40-64
public static List<CustomItem> RegisterAll()
{
    var itemTypes = Assembly.GetCallingAssembly().GetTypes().Where(t => !t.IsAbstract && typeof(CustomItem).IsAssignableFrom(t));
    var createdItems = new List<CustomItem>();

    foreach (var type in itemTypes)
    {
        if (Activator.CreateInstance(type) is not CustomItem item)
            continue;

        try
        {
            Register(item);
            createdItems.Add(item);
        }
        catch (Exception ex)
        {
            Log.Warn($"Failed to register item '{item.Name}': {ex.Message}");
        }
    }

    Log.Debug($"Registered {createdItems.Count} items from assembly '{Assembly.GetCallingAssembly().GetName().Name}'.");

    return createdItems;
}
Example usage:
CustomItemsPlugin.cs:56
if (Config.EnableExampleItems) API.CustomItems.RegisterAll();
RegisterAll() only registers items from the calling assembly. If you have items in a different assembly/plugin, call RegisterAll() from that assembly or use Register() manually.

Unregistration methods

Unregister

public static void Unregister(CustomItem item)
Unregisters a single custom item. What it does:
  1. Validates the item is registered
  2. Removes from internal dictionaries
  3. Removes all active instances (by serial number) from CurrentItems
  4. Calls OnUnregistered() on the item
Implementation:
CustomItems.cs:68-83
public static void Unregister(CustomItem item)
{
    if (!_itemsByName.ContainsKey(item.Name))
        throw new InvalidOperationException($"Item '{item.Name}' not registered.");
    _itemsById.Remove(item.Id);
    _itemsByName.Remove(item.Name);

    foreach (var serial in CurrentItems.Where(kvp => kvp.Value == item).Select(kvp => kvp.Key).ToList())
    {
        CurrentItems.Remove(serial);
    }

    item.OnUnregistered();

    Log.Debug($"Unregistered item '{item.Name}' with ID {item.Id}.");
}
Example usage:
CustomItems.Unregister(myCustomItem);

UnregisterAll

public static List<CustomItem> UnregisterAll()
Unregisters all items from the calling assembly. What it does:
  1. Finds all registered items from the calling assembly
  2. Unregisters each via Unregister()
  3. Returns a list of unregistered items
Implementation:
CustomItems.cs:85-98
public static List<CustomItem> UnregisterAll()
{
    var callingAssembly = Assembly.GetCallingAssembly();

    var itemsToRemove = _itemsByName.Values
        .Where(item => item.GetType().Assembly == callingAssembly)
        .ToList();

    foreach (var item in itemsToRemove) Unregister(item);

    Log.Debug($"Unregistered {itemsToRemove.Count} items from assembly '{callingAssembly.GetName().Name}'.");

    return itemsToRemove;
}
Example usage:
CustomItemsPlugin.cs:63
if (Config.EnableExampleItems) API.CustomItems.UnregisterAll();

Plugin lifecycle integration

Typically, you register items in your plugin’s Enable() method and unregister in Disable():
public class MyPlugin : Plugin
{
    private List<CustomItem> _registeredItems;
    
    public override void Enable()
    {
        // Option 1: Register all items in this assembly
        _registeredItems = CustomItems.RegisterAll();
        
        // Option 2: Register specific items
        CustomItems.Register(new MyCustomItem());
    }
    
    public override void Disable()
    {
        // Unregister all items from this assembly
        CustomItems.UnregisterAll();
        
        // Or unregister specific items
        foreach (var item in _registeredItems)
        {
            CustomItems.Unregister(item);
        }
    }
}

Current items tracking

The framework maintains a dictionary of active item instances:
CustomItems.cs:23
public static readonly Dictionary<ushort, CustomItem> CurrentItems = [];
Key points:
  • Maps item serial numbers to CustomItem instances
  • Populated when items are spawned or given to players
  • Cleared at the start of each round
  • Used by event handlers to route events to the correct custom item
Cleared on round start:
EventHandler.cs:10-12
public override void OnServerWaitingForPlayers()
{
    API.CustomItems.CurrentItems.Clear();
    // ...
}

Lookup methods

Once registered, items can be looked up by ID or name:

GetById

CustomItems.cs:112-114
public static CustomItem GetById(ushort id)
{
    return _itemsById.TryGetValue(id, out CustomItem item) ? item : null;
}

GetIdByName

CustomItems.cs:102-109
public static ushort GetIdByName(string name)
{
    if (_itemsByName.TryGetValue(name, out CustomItem item))
    {
        return item.Id;
    }
    throw new KeyNotFoundException($"Item with name '{name}' not found.");
}
Example usage:
// Get item by ID
var item = CustomItems.GetById(0);

// Get ID by name
var empId = CustomItems.GetIdByName("EMP Grenade");

Spawning and giving items

After registration, items can be spawned or given to players:

TrySpawn

public static bool TrySpawn(ushort id, Vector3 position, out Pickup pickup)
Spawns a custom item at a world position.
CustomItems.cs:118-133
public static bool TrySpawn(ushort id, Vector3 position, out Pickup pickup)
{
    if (!_itemsById.TryGetValue(id, out CustomItem item))
    {
        pickup = null;
        return false;
    }

    pickup = Pickup.Create(item.Type, position);
    pickup.Weight = item.Weight;
    if (pickup == null) return false;
    CurrentItems.Add(pickup.Serial, (CustomItem)Activator.CreateInstance(item.GetType()));
    NetworkServer.Spawn(pickup.GameObject);
    Log.Debug($"Spawned item '{item.Name}' ({id}) at {position} with serial {pickup.Serial}.");
    return true;
}
Example:
if (CustomItems.TrySpawn(0, new Vector3(0, 1, 0), out var pickup))
{
    Log.Info($"Spawned item at {pickup.Position}");
}

TryGive

public static bool TryGive(ushort id, Player player, out Item item, ItemAddReason addReason = ItemAddReason.Undefined)
Gives a custom item to a player’s inventory.
CustomItems.cs:135-147
public static bool TryGive(ushort id, Player player, out Item item, ItemAddReason addReason = ItemAddReason.Undefined)
{
    if (!_itemsById.TryGetValue(id, out CustomItem cItem))
    {
        item = null;
        return false;
    }
    item = player.AddItem(cItem.Type, addReason);
    if (item == null) return false;
    CurrentItems.Add(item.Serial, (CustomItem)Activator.CreateInstance(cItem.GetType()));
    Log.Debug($"Gave item '{cItem.Name}' ({id}) to '{player.Nickname}' with serial {item.Serial}.");
    return true;
}
Example:
if (CustomItems.TryGive(empGrenadeId, player, out var item))
{
    player.SendHint("You received an EMP Grenade!");
}
Both TrySpawn() and TryGive() create new instances of your custom item class (via Activator.CreateInstance()). This means each spawned/given item has its own state.

Registration best practices

For most plugins, RegisterAll() is the simplest approach:
public override void Enable()
{
    CustomItems.RegisterAll();
}

public override void Disable()
{
    CustomItems.UnregisterAll();
}
Use Register() when you need:
  • Conditional registration based on config
  • Specific registration order
  • Items from multiple assemblies
public override void Enable()
{
    if (Config.EnableEMPGrenades)
        CustomItems.Register(new EMPGrenade());
    
    if (Config.EnableHealingItems)
        CustomItems.Register(new HealingSyringe());
}
Failing to unregister can cause:
  • Memory leaks
  • Duplicate registration errors on reload
  • Events firing for disabled plugins
public override void Disable()
{
    CustomItems.UnregisterAll();
    // or
    foreach (var item in myItems)
        CustomItems.Unregister(item);
}
RegisterAll() catches exceptions, but Register() throws:
try
{
    CustomItems.Register(new MyItem());
}
catch (InvalidOperationException ex)
{
    Log.Error($"Failed to register item: {ex.Message}");
}

Next steps