Skip to content

CustomItemRegistry

QinShenYu edited this page Jun 10, 2026 · 6 revisions

Namespace: ScavLib.item

Registers custom items into the game's global item registry (Item.GlobalItems) and makes them fully spawnable at runtime by intercepting Utils.Create and cloning a vanilla template Prefab.

The recommended entry point is the chainable CustomItemBuilder. Definition-only registration via RegisterItem / RegisterSimpleItem is preserved for backwards compatibility but marked [Obsolete].

Injection timing is automatic: ScavLib applies a Harmony Postfix to Item.SetupItems(), and any registration issued before the world loads is safely queued and flushed at the correct moment.


Quick Start

using ScavLib.item;
using ScavLib.event_bus;
using ScavLib.event_bus.events;

private void Awake()
{
    CustomItemBuilder.Create("mymod_salve", "MyMod", ItemTemplate.Bandage)
        .Category("medical")
        .Weight(0.3f).Value(15)
        .Tags("medicine", "dressing")
        .DisplayName("Herbal Salve")
        .Description("A pungent paste that soothes burns and shallow cuts.")
        .UsableOnLimb((limb, item) =>
        {
            limb.skinHealAmount += 15f;
            limb.pain           -= 20f;
            item.condition      -= 0.25f;
        })
        .Register(out var err);
}

The call to Register() is safe at Awake() time — registrations made before the world exists are queued and applied when Item.SetupItems() runs.


CustomItemBuilder

CustomItemBuilder is a chainable builder. Every configuration method returns this, so the typical usage is a single fluent statement ending in .Register(out var err).

Creation

The recommended factory takes the item id, owner mod name, and template in one call. The template determines both the cloned vanilla prefab and the ItemInfo subtype (ItemInfo / LiquidItemInfo / BatteryInfo):

CustomItemBuilder.Create(string id, string owner, ItemTemplate template)
CustomItemBuilder.Create(string id, string owner, string vanillaResourceId)
Parameter Description
id Unique item ID. Prefix with your mod name (e.g. "mymod_salve").
owner Mod name recorded in the Owner Ledger. Match ModInfo.Name.
template Which vanilla prefab to clone — see the Template Catalog below.
vanillaResourceId Escape hatch: clone any vanilla resource id directly (e.g. "materialpouch"). Use this when no ItemTemplate enum value matches.

The id is also the key used by Utils.Create(id, ...), GameUtil.SpawnItem(id, ...), and ItemUtil.IsKnownId(id).

Convenience Factories

For the common item families, ScavLib ships single-call convenience factories that pick the right template for you:

CustomItemBuilder.Pistol("mymod_pistol", "MyMod")
    .DisplayName("Custom Pistol")
    .Register(out _);

CustomItemBuilder.Helmet("mymod_helmet", "MyMod")
    .Wearable(VanillaLimb.Head, VanillaWearSlot.Hat, armor: 5f)
    .Register(out _);

The full list of convenience factories:

Family Available factories
Weapons Pistol / Rifle / Shotgun / Magazine
Ammo / Batteries SmallBattery / MediumBattery / LargeBattery
Liquid containers Canteen
Light / utility Flashlight / Backpack / Explosive
Wearable — Head Helmet / BikeHelmet / RiotHelmet / DustMask / Goggles / Balaclava / Scarf
Wearable — Torso Hoodie / Shirt / TorsoArmor / BellyArmor / Belt / Bandolier / FannyPack
Wearable — Limbs Gloves / TacticalGloves / ArmWarmers / LimbWraps / KneePads / Sneakers / Boots / MaterialPouch / LegPouch

Template Catalog

Each ItemTemplate value is cloned from a real vanilla prefab. The clone inherits the prefab's full component chain and ItemInfo; your fluent overrides apply on top. Pick the entry that most closely matches your item's behavior — fields you do not override keep the vanilla template's values (rather than reverting to C# defaults).

Category ItemTemplate Vanilla clone source
Generic SimpleItem geofruit
Generic ScrapMaterial scrapmetal
Medical Bandage bandage
Food FoodSimple nutrientbar
Tools Tool / Cleaver / Knife wrench / machete / flimsyknife
Liquid WaterContainer / Canteen canteen
Liquid CraftingBottle craftingbottle
Guns Pistol / Rifle / Shotgun / MakeshiftRifle pistol / rifle / shotgun / makeshiftrifle
Ammo SmallMagazine / RifleMagazine smallmagazine / riflemagazine
Ammo Round9mm / Round556 / Gauge12 / BoxOf12Gauge 9mmround / 556round / 12gauge / boxof12gauge
Batteries SmallBattery / MediumBattery / LargeBattery smallbattery / mediumbattery / largebattery
Containers SmallPack / Duffelbag / SlingBag / BigPack / LegPouch / FoliageBag matching ids
Wearable — Head Helmet / BikeHelmet bikehelmet
Wearable — Head RiotHelmet riothelmet
Wearable — Head DustMask / Goggles / Balaclava / Scarf matching ids
Wearable — Torso Hoodie hoodie
Wearable — Torso Shirt tornshirt
Wearable — Torso TorsoArmor carapace
Wearable — Torso BellyArmor / Belt / Bandolier / FannyPack matching ids
Wearable — Limbs Gloves latexgloves
Wearable — Limbs TacticalGloves / ArmWarmers / LimbWraps / KneePads matching ids
Wearable — Limbs Sneakers / Boots sneakers
Wearable — Limbs MaterialPouch materialpouch
Light Flashlight / Lantern / MakeshiftHeadlamp matching ids
Explosive Explosive dynamite
Misc Plushie / Watch / MP3Player / GeigerCounter / GrapplingHook / Blueprint / Sleepingbag matching ids
Resources Plastic / Wood / ScrapCube plasticchunk / woodcube / scrapcube

Some ItemTemplate values intentionally share a clone source — Helmet and BikeHelmet both clone bikehelmet, Sneakers and Boots both clone sneakers. They exist as separate enum values for call-site readability; the resulting prefab is identical until your fluent overrides apply.

Core Stats

.Category(string category)
.Weight(float kg)
.Value(int currency)
.Tags(params string[] tags)
.Recognition(int level)
.SlotRotation(float degrees)
.DecayMinutes(float minutes)
.RotSpeed(float speed)

Tags are forwarded so they participate in ItemInfo.HasTag(...) lookups exactly like vanilla items. Tags affecting gameplay behaviour ("cangetwet", "food", etc.) must be declared here — the game reads them in Item.Start().

Behaviour Flags

.Combineable(bool value = true)
.DestroyAtZeroCondition(bool value = true)
.ScaleWeightWithCondition(bool value = true)
.OnlyHoldInHands(bool value = true)
.AutoAttack(bool value = true)
.UsableWithLMB(bool value = true)
.IgnoreDepression(bool value = true)

Action Callbacks

.Usable(Action<Body, Item> onUse, bool replace = false)
.UsableOnLimb(Action<Limb, Item> onUseLimb, bool replace = false)
.OnSpawn(Action<GameObject> onSpawn)

replace: true overwrites the vanilla template's useAction / useLimbAction entirely. The default (replace: false) appends your callback so the vanilla behavior runs first — useful when extending a template like Bandage or Flashlight while keeping its built-in effect.

OnSpawn fires on every spawned instance after the prefab is configured. Multiple OnSpawn calls accumulate; user-supplied hooks always run last so they observe the prefab in its final template-configured state.

Wearable Configuration

For wearable items, use the strongly-typed Wearable chain rather than writing raw slot strings:

.Wearable(VanillaLimb desiredWearLimb, VanillaWearSlot wearSlotId,
          float armor = 0f, float isolation = 0f)
.WearableArmor(float armor)
.WearableIsolation(float isolation)
.WearableHitDurabilityLossMultiplier(float multiplier)
.WearableVisualOffset(int pixels)

The VanillaLimb and VanillaWearSlot enums (in ScavLib.item) make typos fail at compile time. The full slot map:

VanillaWearSlot Matching VanillaLimb Vanilla items in this slot
Hat Head bikehelmet, riothelmet, makeshifthelmet, headlamp, holidayhat
Eyes Head safetyglasses, goggles, autozoomgoggles
Mouth Head dustmask
Blindfold Head blindfold
Balaclava Head balaclava
Neck UpTorso scarf
OuterTorso UpTorso hoodie, striderpelt, carapace
Torso UpTorso tornshirt
TorsoFront DownTorso bellyarmor, traumarig, fannypack, scubadivinggear
Back UpTorso smallpack, duffelbag, slingbag, bigpack, jetpack
Bandolier UpTorso bandolier
Belt DownTorso belt
Arms DownArmF armwarmers
Wraps DownArmF limbwraps
Hands HandF latexgloves, tacticalgloves, climbingclaws
Thigh ThighF legpouch
ThighBack ThighB materialpouch
Knees CrusF kneepads
Feet FootF sneakers, tacticalboots, woodsandals

Component-Level Configuration

Some templates spawn with extra components beyond Item. The builder offers direct chainable setters for the most common ones:

// Battery
.MaxCharge(float capacity)

// Ammo (rounds and magazines)
.AmmoType(GunScript.AmmoType type)
.AmmoMaxRounds(int rounds)

// Container (backpacks, fanny packs, etc.)
.ContainerCapacity(float maxWeight, float maxWeightPerItem)
.ContainerTagRestriction(string requiredTag)
.ContainerItemsVisible(bool visible)
.ContainerEncumberance(float multiplier)

These accumulate into an internal hook list that runs at spawn time, ensuring components are configured before any user OnSpawn callback observes the GameObject.

i18n Shortcuts

.DisplayName(string en)
.DisplayName(IDictionary<string, string> byLanguage)
.Description(string en)
.Description(IDictionary<string, string> byLanguage)

These are thin wrappers over LocaleRegistry. For bulk or runtime-driven translations, use LocaleRegistry.AddItems(...) directly.

Registration

.Register()
.Register(out string error)
.Register(bool overwrite, out string error)

Register(overwrite: true, ...) replaces an existing entry in Item.GlobalItems and rebinds the clone template. Useful for rebalancing a vanilla item or for two mods that mutually consent to one overriding the other.

If Register() is called twice with the same id without overwrite: true, the second call returns false with a non-empty error and the original registration stands. The Owner Ledger records the first registration's owner; conflicts surface in scavlib check.


How Spawning Works

Vanilla Utils.Create is a thin wrapper around Resources.Load:

public static GameObject Create(string id, Vector2 pos, float rot)
    => Object.Instantiate(Resources.Load(id), pos, Quaternion.Euler(0,0,rot)) as GameObject;

public static GameObject Create(string id, Transform trans)
    => Object.Instantiate(Resources.Load(id), trans) as GameObject;

For an unknown id this returns null, so without intervention custom items could not be spawned — there is no Prefab in Resources/ to load.

ScavLib applies a Priority.Low Harmony Prefix to both overloads. The Prefix runs before the original method and:

  1. Looks the id up in the CustomItemRegistry template map.
  2. If found, instantiates the recorded clone template, re-stamps its Item.id to the custom id, applies the registered ItemInfo, runs all accumulated OnSpawn hooks, and skips the original method.
  3. If not found, falls through to the original method untouched.

The Priority.Low weighting matters for cross-mod compatibility: other frameworks that patch Utils.Create get to run first, and ScavLib only intervenes for ids it actually owns.


Manual Registration (Legacy / Obsolete)

The pre-builder entry points are retained for source compatibility:

[Obsolete("Use CustomItemBuilder.Create(...).Register() instead.")]
public static void RegisterItem(string id, ItemInfo info, bool overwrite = false);

[Obsolete("Use CustomItemBuilder.Create(...).Register() instead.")]
public static void RegisterSimpleItem(string id, string category = "custom",
    float weight = 1f, int value = 1, string tags = "");

Both still register the ItemInfo into Item.GlobalItems. They do not bind a clone template, so calling Utils.Create("legacy_id", ...) on items registered this way will still return null. To make a legacy registration spawnable, re-declare it through CustomItemBuilder.


ID Naming Convention

Prefix your item ids with a short mod identifier:

mymod_salve       OK
super_bandage     too generic — may collide
bandage           conflicts with a built-in item

The Owner Ledger records the registering mod name. When two mods race for the same id, scavlib check lists both owners and flags the conflict.


Inspecting Registered Items

ItemUtil.IsKnownId("mymod_salve");
CustomItemRegistry.GetOwner("mymod_salve");
CustomItemRegistry.GetAllRegistered();

Run scavlib check in the developer console for a human-readable summary covering custom items, recipes, liquids, Harmony patches, and commands.


Worked Example — Wearable Helmet

using ScavLib.item;

private void Awake()
{
    CustomItemBuilder.Helmet("mymod_helmet", "MyMod")
        .DisplayName(new Dictionary<string, string>
        {
            { "EN",    "Reinforced Helmet" },
            { "zh-CN", "强化头盔" }
        })
        .Category("custom")
        .Weight(1.2f).Value(40)
        .Recognition(4)
        .Wearable(
            desiredWearLimb: VanillaLimb.Head,
            wearSlotId:      VanillaWearSlot.Hat,
            armor:           5f,
            isolation:       0.2f)
        .WearableHitDurabilityLossMultiplier(0.5f)
        .Register(out _);
}

The resulting item drops into the player's hat slot, applies armor and cold isolation, and survives hits at twice the normal durability rate.


Worked Example — Themed Backpack

using ScavLib.item;

private void Awake()
{
    CustomItemBuilder.Backpack("mymod_medbag", "MyMod")
        .DisplayName("Medical Pack")
        .Category("custom")
        .Weight(0.6f).Value(35)
        .Recognition(3)
        .ContainerCapacity(maxWeight: 8f, maxWeightPerItem: 3f)
        .ContainerTagRestriction("medical")
        .ContainerEncumberance(0.8f)
        .Register(out _);
}

Only items tagged "medical" will fit, the encumbrance penalty is reduced, and total payload is capped at 8 kg.

Clone this wiki locally