-
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, as of 0.7.0, 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;
[Subscribe]
private void OnWorldLoaded(WorldLoadedEvent e)
{
// Already-registered items will spawn fine; this is just for verification.
bool ok = ItemUtil.IsKnownId("mymod_salve");
GameUtil.Log($"mymod_salve ready: {ok}");
}
private void Awake()
{
CustomItemBuilder.Create("mymod_salve")
.FromTemplate(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();
}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().
CustomItemBuilder.Create(string id)| Parameter | Type | Description |
|---|---|---|
id |
string |
Unique item ID. Prefix with your mod name to avoid collisions (e.g. "mymod_salve"). |
The ID is also the key used by Utils.Create(id, ...),
GameUtil.SpawnItem(id, ...), and ItemUtil.IsKnownId(id).
.FromTemplate(ItemTemplate template)
.FromTemplate(string vanillaId)Selects which vanilla item Prefab to clone as the visual / physical base for
your item. The ItemTemplate enum covers the common bases:
ItemTemplate |
Vanilla ID | Use for |
|---|---|---|
Generic |
a small generic loose item | Default fallback. Used when FromTemplate is omitted. |
Bandage |
bandage |
Medical consumables, dressings, salves. |
Pills |
a pill bottle | Anything granular / pillular. |
Bottle |
craftingbottle |
Containers that should also work as a WaterContainerItem. |
Food |
a generic edible | Edible consumables (drink hook + tags). |
Tool |
a small handheld tool | Tools, instruments, repair items. |
The string overload FromTemplate("some_vanilla_id") lets you clone any
existing item by ID — useful when you want an exact copy of a less-common
vanilla item.
.Category(string category) // default: "custom"
.Weight(float kg) // default: 1f
.Value(int currency) // default: 1
.Tags(params string[] tags) // applied via ItemInfo.SetTags(); supports e.g. "medicine", "dressing", "cangetwet"
.Recognition(int level) // wraps new Recognition(level)Tags are forwarded through ItemInfo.SetTags(...) 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().
.Usable(bool value = true) // ItemInfo.usable
.UsableOnLimb(bool value = true) // ItemInfo.usableOnLimb
.Combineable(bool value = true) // ItemInfo.combineable
.DestroyAtZeroCondition(bool value = true) // ItemInfo.destroyAtZeroCondition.UseAction(Action<Item> onUse) // ItemInfo.useAction
.UseLimbAction(Action<Limb, Item> onUseLimb) // ItemInfo.useLimbActionThese map 1:1 to the matching ItemInfo callbacks the game already invokes
from its inventory UI; the builder does not wrap, schedule, or guard them.
.DisplayName(string en) // adds LocaleRegistry "EN" item entry
.DisplayName(string lang, string text) // adds for a specific language
.Description(string en) // adds locale "other" entry under "<id>_desc"
.Description(string lang, string text)These are thin wrappers over LocaleRegistry. For more than
two languages, use LocaleRegistry.AddItems(...) directly. See the
LocaleRegistry page for the full bulk-loading API.
.Register() // commits the item; returns void
.Register(bool overwrite) // overwrite: replace an existing ID instead of failingRegister(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 logs a warning and is ignored. 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, which is why pre-0.7.0 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, and skips the original method (returning the new GameObject). - 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. Unknown IDs are still handled by the
vanilla Resources.Load path, so non-ScavLib custom-item frameworks
continue to function alongside ScavLib without conflict.
The pre-0.7.0 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 ✔
super_bandage ✗ (too generic, may collide)
bandage ✗ (conflicts with a built-in item)
The Owner Ledger records the registering mod name (taken from the
BepInPlugin GUID or, if ModRegistry.Register was called, from
ModInfo.Name). When two mods race for the same ID, scavlib check will
list both owners and flag the conflict.
ItemUtil.IsKnownId("mymod_salve"); // does Item.GlobalItems contain it?
CustomItemRegistry.GetOwner("mymod_salve"); // which mod registered it?
CustomItemRegistry.GetAllRegistered(); // IReadOnlyDictionary<string, string>Run scavlib check in the developer console for a human-readable summary
covering custom items, recipes, liquids, Harmony patches, and commands.
A toggleable healing salve that consumes herbs and bandages, ships English and Chinese names, and is fully spawnable from the dev console:
using ScavLib.item;
using ScavLib.locale;
using UnityEngine;
private void Awake()
{
// Optional: extra languages beyond DisplayName / Description.
LocaleRegistry.AddItem("mymod_salve", "fr-FR", "Onguent aux Herbes");
CustomItemBuilder.Create("mymod_salve")
.FromTemplate(ItemTemplate.Bandage)
.Category("medical")
.Weight(0.3f)
.Value(15)
.Tags("medicine", "dressing", "cangetwet")
.Recognition(3)
.DisplayName("Herbal Salve")
.DisplayName("zh-CN", "草药膏")
.Description("A pungent paste that soothes burns and shallow cuts.")
.Description("zh-CN", "气味刺鼻的药膏,能舒缓烧伤与浅切口")
.UsableOnLimb((limb, item) =>
{
limb.skinHealAmount += 15f;
limb.pain -= 20f;
item.condition -= 0.25f;
})
.DestroyAtZeroCondition(true)
.Register();
}After WorldLoadedEvent, you can spawn one for testing via the dev console
or GameUtil.SpawnItemAt("mymod_salve", PlayerCamera.main.body.transform).