-
Notifications
You must be signed in to change notification settings - Fork 0
CustomItemRegistry
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.
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 is a chainable builder. Every configuration method
returns this, so the typical usage is a single fluent statement ending in
.Register(out var err).
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).
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
|
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
ItemTemplatevalues intentionally share a clone source —HelmetandBikeHelmetboth clonebikehelmet,SneakersandBootsboth clonesneakers. They exist as separate enum values for call-site readability; the resulting prefab is identical until your fluent overrides apply.
.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().
.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).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.
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
|
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.
.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.
.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.
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:
- Looks the id up in the
CustomItemRegistrytemplate map. - If found, instantiates the recorded clone template, re-stamps its
Item.idto the custom id, applies the registeredItemInfo, runs all accumulatedOnSpawnhooks, and skips the original method. - 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.
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.
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.
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.
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.
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.