-
Notifications
You must be signed in to change notification settings - Fork 0
CustomLiquidBuilder
Namespace: ScavLib.liquid
Registers custom liquid types into the game's Liquids.Registry dictionary.
Custom liquids participate fully in the existing systems: they can be poured
into WaterContainerItems, used as RecipeBuilder ingredients or results,
drunk, and (optionally) injected.
Registration is safe at Awake() time — entries are queued and applied
during the same Harmony Postfix that flushes custom items and recipes.
using ScavLib.liquid;
using UnityEngine;
private void Awake()
{
CustomLiquidBuilder.Create("mymod_tonic")
.DisplayName("Bitter Tonic")
.Color(new Color(0.35f, 0.55f, 0.20f))
.ValuePerLiter(8f)
.Quality("medicine", 0.4f)
.OnDrink((ml, body) =>
{
body.painShock -= 10f * (ml / 100f);
body.sicknessAmount = Mathf.Max(0f, body.sicknessAmount - ml * 0.05f);
})
.HealthUsable()
.OnHealthUse((ml, limb) =>
{
limb.skinHealAmount += 5f * (ml / 50f);
})
.Register();
}A chainable builder. Each configuration method returns this; commit with
.Register().
CustomLiquidBuilder.Create(string liquidId)| Parameter | Type | Description |
|---|---|---|
liquidId |
string |
Unique liquid ID. Prefix with your mod name (e.g. "mymod_tonic"). This is the key used by Liquids.Registry, WaterContainerItem.AddLiquid(id, ml), and RecipeBuilder.IngredientLiquid(id, ml). |
.DisplayName(string en)
.DisplayName(string lang, string text)
.Color(Color color) // LiquidType.color
.LocaleFromItem(bool value = true) // LiquidType.localeFromItemDisplayName is a thin wrapper over LocaleRegistry; call
it once per language, or use LocaleRegistry.AddItems(...) for bulk
loading.
When LocaleFromItem(true) is set, the game pulls the display string from
whichever item is acting as the container instead of the liquid's own
locale entry, matching the vanilla localeFromItem flag.
.ValuePerLiter(float value) // LiquidType.valuePerLiter.Quality(string qualityId, float amount)
.Quality(CraftingQuality quality)Adds an entry to LiquidType.qualities. Qualities are how the game
identifies a liquid for crafting-by-quality matches — e.g. tagging a
custom liquid with "disinfectant" (amount 0.4f) makes it eligible for
any recipe declared via IngredientLiquidByQuality("disinfectant", ...).
Quality amounts are interpreted per liter. At craft time the game calls
LiquidType.GetScaledQualities(ml) to scale them down to the actually-
consumed volume.
.OnDrink(LiquidType.OnDrink callback)
// void (float ml, Body body)
.HealthUsable(bool value = true)
.OnHealthUse(LiquidType.OnHealthUse callback)
// void (float ml, Limb limb)Delegate signatures are exactly the game's:
public delegate void OnDrink(float ml, Body body);
public delegate void OnHealthUse(float ml, Limb limb);The ml parameter is the volume actually consumed in this invocation, not
the liquid's total amount in the container — multiplying status effects by
ml / 100f is the typical pattern.
HealthUsable(true) must be called for OnHealthUse to be reachable; the
game checks LiquidType.healthUsable before exposing the limb-treatment
UI for the liquid.
.Injectable(bool value = true) // LiquidType.injectable
.InjectionSickness(float multiplier) // LiquidType.injectionSickness; default 1fInjectable liquids can be administered through syringes / IVs. The
injectionSickness multiplier scales the sickness penalty applied when
the liquid is injected, mirroring the vanilla field of the same name.
.Register()
.Register(bool overwrite)Register(overwrite: true) replaces an existing Liquids.Registry entry
with the same ID. Duplicate registration without overwrite logs a
warning and is ignored.
A custom antiseptic that doubles as a "disinfectant" quality for crafting
and inflicts mild sickness when injected:
using ScavLib.liquid;
using UnityEngine;
private void Awake()
{
CustomLiquidBuilder.Create("mymod_antiseptic")
.DisplayName("Antiseptic Solution")
.DisplayName("zh-CN", "消毒液")
.Color(new Color(0.85f, 0.95f, 0.95f))
.ValuePerLiter(12f)
.Quality("disinfectant", 0.6f)
.HealthUsable()
.OnHealthUse((ml, limb) =>
{
limb.skinDirtyness = Mathf.Max(0f, limb.skinDirtyness - ml * 0.02f);
limb.infectionAmount = Mathf.Max(0f, limb.infectionAmount - ml * 0.1f);
})
.OnDrink((ml, body) =>
{
// Drinking antiseptic is a bad idea.
body.sicknessAmount += ml * 0.2f;
body.venomCurrent += ml * 0.05f;
})
.Injectable()
.InjectionSickness(2f)
.Register();
}Pair this with a RecipeBuilder.IngredientLiquidByQuality("disinfectant", 30f)
and the new liquid is immediately accepted everywhere vanilla disinfectants
are.
CustomLiquidBuilder.GetOwner("mymod_tonic");
CustomLiquidBuilder.GetAllRegistered(); // IReadOnlyDictionary<string, string>scavlib check lists every ScavLib-registered liquid alongside its owner
mod and flags duplicate IDs.
- ScavLib does not ship a custom liquid container Prefab. To pre-fill
a bottle with your liquid at world-load time, spawn a vanilla
craftingbottle(or anyWaterContainerItem) and callwaterContainer.AddLiquid("mymod_tonic", ml). - The vanilla
LiquidTypestruct exposes no "color blend" hook; the registeredColoris used wholesale for the in-container fluid render. - Custom liquid IDs survive language switches.
Locale.ChangeLanguage()reloads the active scene, butLiquids.Registryis a static dictionary and ScavLib re-applies its queue on the very nextRecipes.SetUpRecipespost-load.