Survival
Survival is how your mod reaches the player’s needs and health. Each need is a number: hunger, thirst, bowel, bladder, tiredness, hygiene, mood, health, bleeding, alcohol, high, radiation and more. You can read each one, set it, slow down or speed up how fast it changes, get told when one crosses a line, change or cancel damage, and react when the player eats, drinks or uses an item. You never touch the game’s objects yourself.
Survival read, set and watch the needs, and handle damage
Use Get and Set to read and change a need, OnCrossed to hear when one crosses a line, and InterceptDamage to change or cancel health damage.
public static partial class Survival
{
public static bool IsAvailable { get; }
public static float Get(Stat stat);
public static bool Set(Stat stat, float value);
public static bool OnCrossed(string modId, Stat stat, float threshold, Action<Stat, float, bool> callback);
public static bool RemoveCrossed(string modId, Action<Stat, float, bool> callback);
public static void RemoveAll(string modId);
public static bool InterceptDamage(string modId, Action<DamageArgs> handler);
public static bool RemoveDamage(string modId, Action<DamageArgs> handler);
public static event Action<DamageTypeNames> PlayerDied;
public static event Action PassedOut;
public static event Action<float, DamageTypeNames> Damaged;
public static event Action<BreakdownArgs> Breakdown;
}
| Method | Returns | What it does |
|---|---|---|
Get(stat) | float | The stat’s value as the game keeps it. 0 when there is no player yet, for example on the main menu. |
Set(stat, value) | bool | Sets the value, kept inside the stat’s range. Returns false when it could not be set. |
OnCrossed(modId, stat, threshold, callback) | bool | Calls you when the value crosses a line. Returns false when not available. |
RemoveCrossed(modId, callback) | bool | Stops a watch that OnCrossed started. Returns true when one was removed. |
RemoveAll(modId) | void | Removes everything your mod set up here: decay changes, item and damage handlers, and watches. |
InterceptDamage(modId, handler) | bool | Runs your code before health damage is applied. Returns false when not available. |
RemoveDamage(modId, handler) | bool | Stops one damage handler. Returns true when it was removed. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | true when all the changes the Survival feature needs are in place. See Details. |
| Event | Gives you | When it fires |
|---|---|---|
PlayerDied | DamageTypeNames, the cause | The player died. |
PassedOut | Nothing | The player fainted. |
Damaged | float, how much, and DamageTypeNames, the cause | The player lost health. |
Breakdown | BreakdownArgs | The game is about to start a mental breakdown step. |
Stat one number about the player
Every call that reads or changes a need takes one of these, such as Survival.Get(Stat.Hunger). Each value is one of the game’s player stats, and the table below gives the game field it reads and writes.
public enum Stat
{
Hunger = 0,
Thirst = 1,
Bowel = 2,
Bladder = 3,
Tiredness = 4,
Hygiene = 5,
Mood = 6,
Health = 7,
Bleeding = 8,
Alcohol = 9,
High = 10,
Smv = 11,
Radiation = 12,
SaunaHeat = 13,
DuckNeed = 14,
AlcoholAddiction = 15,
MushroomAddiction = 16,
SmokingAddiction = 17,
GamblingAddiction = 18,
AlcoholNeed = 19,
MushroomNeed = 20,
SmokingNeed = 21,
GamblingNeed = 22
}
| Value | Meaning |
|---|---|
Hunger | The game’s player stat hungerAndThirst.Hunger. |
Thirst | The game’s player stat hungerAndThirst.Thirst. |
Bowel | The game’s player stat hungerAndThirst.Bowel. |
Bladder | The game’s player stat hungerAndThirst.Bladder. |
Tiredness | The game’s player stat tiredness.currentStatus. |
Hygiene | The game’s player stat hygiene.Hygiene. |
Mood | The game’s player stat mentalHealth.currentStatus. |
Health | The game’s player stat health.currentStatus. |
Bleeding | The game’s player stat health.Bleeding. |
Alcohol | The game’s player stat alcohol.AlcoholInBlood. |
High | The game’s player stat mushrooms.MushroomsHigh. |
Smv | The game’s player stat mushrooms.SMVProgression. |
Radiation | The game’s player stat radiation.Dose. |
SaunaHeat | The game’s player stat sauna.Overheat. |
DuckNeed | The game’s player stat duck.currentStatus. |
AlcoholAddiction | The game’s player stat addictions.AlcoholAddiction. |
MushroomAddiction | The game’s player stat addictions.MushroomAddiction. |
SmokingAddiction | The game’s player stat addictions.SmokingAddiction. |
GamblingAddiction | The game’s player stat addictions.GamblingAddiction. |
AlcoholNeed | The game’s player stat addictions.AlcoholNeed. |
MushroomNeed | The game’s player stat addictions.MushroomNeed. |
SmokingNeed | The game’s player stat addictions.SmokingNeed. |
GamblingNeed | The game’s player stat addictions.GamblingNeed. |
The stats live in the game’s PlayerStats.instance. This table shows the game value each stat reads and writes, and the range Set keeps the value inside.
| Stat | Game field | Range |
|---|---|---|
| Hunger | PlayerStatsHungerAndThirst.Hunger | -100 to 100 |
| Thirst | PlayerStatsHungerAndThirst.Thirst | 0 to 100 |
| Bowel | PlayerStatsHungerAndThirst.Bowel | 0 to 100 |
| Bladder | PlayerStatsHungerAndThirst.Bladder | 0 to 100 |
| Tiredness | PlayerStatsTiredness.currentStatus | 0 to 100 |
| Hygiene | PlayerStatsHygiene.Hygiene | 0 to 100 |
| Mood | PlayerStatsMentalHealth.currentStatus | 0 to 100 |
| Health | PlayerStatsHealth.currentStatus | 0 to 100 |
| Bleeding | PlayerStatsHealth.Bleeding | 0 to 100 |
| Alcohol | PlayerStatsAlcohol.AlcoholInBlood | 0 to 100 (the game itself does not stop it going above 100) |
| High | PlayerStatsMushrooms.MushroomsHigh | 0 to 100 |
| Smv | PlayerStatsMushrooms.SMVProgression | 0 to 100 |
| Radiation | PlayerStatsRadiation.Dose | 0 and up |
| SaunaHeat | PlayerStatsSauna.Overheat | 0 and up |
| DuckNeed | PlayerStatsDuck.currentStatus | 0 to 100 |
| AlcoholAddiction, MushroomAddiction, SmokingAddiction, GamblingAddiction | PlayerStatsAddictions.<name> | 0 to 100 |
| AlcoholNeed, MushroomNeed, SmokingNeed, GamblingNeed | PlayerStatsAddictions.<name> | 0 to 100 |
DamageArgs one health damage step
InterceptDamage hands you one of these before health is lost. Change Amount or set Cancel to change or stop that damage.
public sealed class DamageArgs
{
public DamageTypeNames Reason { get; }
public bool Silent { get; }
public float Original { get; }
public float Amount { get; set; }
public bool Cancel { get; set; }
}
| Property | Type | What it means |
|---|---|---|
Reason | DamageTypeNames | What caused the damage. |
Silent | bool | true when the game shows no message for it. |
Original | float | The amount the game asked for. |
Amount | float | The amount that will be applied. You may change it. |
Cancel | bool | Set to true to stop the damage. |
BreakdownArgs one breakdown step
Breakdown hands you this before the game starts a step of a mental breakdown. Kind says which step it is, and Cancel stops that step.
public sealed class BreakdownArgs
{
public BreakdownKind Kind { get; }
public bool Cancel { get; set; }
}
| Property | Type | What it means |
|---|---|---|
Kind | BreakdownKind | Which breakdown step is starting. |
Cancel | bool | Set to true to stop that step. |
BreakdownKind which breakdown step it is
This is the Kind of a BreakdownArgs. The game has four breakdown steps, and each value names one of them.
public enum BreakdownKind
{
Suicide,
SelfHarm,
Mushrooms,
Alcohol
}
| Value | Meaning |
|---|---|
Suicide | The player tries to end their life. This is the game’s Depression.Suicide step. |
SelfHarm | The player hurts themselves. This is the game’s Depression.HurtYourself step. |
Mushrooms | The player takes mushrooms from their inventory. (Depression.UseRandomMushroomsFromInventory) |
Alcohol | The player drinks alcohol from their inventory. (Depression.DrinkRandomAlcoholFromInventory) |
Survival.Decay change how fast a need changes
Call Decay.Set to make one need change faster or slower, and Decay.Remove to undo it. Decay.Supports tells you which stats can be changed.
public static class Decay
{
public static bool Supports(Stat stat);
public static bool Set(Stat stat, string modId, float multiplier, float offsetPerHour);
public static bool Remove(Stat stat, string modId);
}
| Method | Returns | What it does |
|---|---|---|
Supports(stat) | bool | true when this stat’s speed can be changed. |
Set(stat, modId, multiplier, offsetPerHour) | bool | Sets your mod’s speed change for a stat. |
Remove(stat, modId) | bool | Removes it again. |
Survival.Consumables run your code when the player uses an item
Call Intercept to run your code before an item is used, and set Cancel to stop that use. Consumed tells you after a use has been applied.
public static class Consumables
{
public static void Intercept(string modId, ConsumeHandler handler);
public static bool Remove(string modId);
public static bool Remove(string modId, ConsumeHandler handler);
public static event Action<ConsumedArgs> Consumed;
}
| Method | Returns | What it does |
|---|---|---|
Intercept(modId, handler) | void | Runs before the item is used. You may cancel the use. |
Remove(modId) | bool | Removes all of your mod’s handlers. Returns false when it had none. |
Remove(modId, handler) | bool | Removes one handler. Returns false when it was not registered. |
| Event | Gives you | When it fires |
|---|---|---|
Consumed | ConsumedArgs | After the game applied a use. It does not fire for a cancelled or refused use. |
ConsumeHandler your code for an item use
This is the delegate you pass to Consumables.Intercept. The framework calls it before the game applies an item use, and args is where you cancel it.
public delegate void ConsumeHandler(ItemData itemData, ItemConsumable itemConsumable, ConsumeArgs args);
| Parameter | Type | What it means |
|---|---|---|
itemData | ItemData | The item data of the item being used. |
itemConsumable | ItemConsumable | The consumable action the game is about to run for that item. |
args | ConsumeArgs | Set Cancel to true to stop the use. |
ConsumeArgs set Cancel to stop an item use
Each ConsumeHandler gets one of these for the use. Set Cancel to true to stop the item use.
public class ConsumeArgs
{
public bool Cancel { get; set; }
}
| Property | Type | What it means |
|---|---|---|
Cancel | bool | Set to true to stop the item use. |
ConsumedArgs what Consumed tells you
Consumed hands you this after a use has been applied. ItemId tells you which item was used.
public sealed class ConsumedArgs
{
public int ItemId { get; }
}
| Property | Type | What it means |
|---|---|---|
ItemId | int | The id of the item that was used. |
Putting it together
Halves how fast hunger rises, logs when hunger crosses 80, and logs when the player dies.
using System;
using BepInEx;
using BepInEx.Unity.Mono;
using ObenseuerCore;
public class SlowHunger : BaseUnityPlugin
{
private static readonly CoreLog Log = CoreLog.For("SlowHunger");
private void Awake()
{
Survival.Decay.Set(Stat.Hunger, "example.slowhunger", 0.5f, 0f);
Survival.OnCrossed("example.slowhunger", Stat.Hunger, 80f, (stat, value, rising) =>
Log.Info(stat + " crossed 80, rising " + rising + ", value " + value));
Survival.PlayerDied += reason => Log.Info("player died, reason " + reason);
}
}
| Step | What it does |
|---|---|
Survival.Decay.Set(Stat.Hunger, "example.slowhunger", 0.5f, 0f) | Halves the speed at which hunger changes, with no offset. |
Survival.OnCrossed(..., 80f, ...) | Logs when hunger crosses 80, going up or down. |
Survival.PlayerDied += ... | Logs the cause when the player dies. |
The same example is in the Examples\SlowHunger\ folder of the framework.
Things to know
The values of Stat never change, and new stats are added at the end.
Reading, setting and watching a stat
- Nothing in
Survivalthrows into the game. If a feature the call needs is missing, you get the plain default:0,falseor nothing happens.Survival.IsAvailableistrueonly when every feature group Survival needs is in place (see If something fails to set up). A handler of yours that throws is logged, and the next handler still runs. - Bad arguments still throw, even when
IsAvailableis false. An emptymodIdthrowsArgumentException. Anullcallback or handler throwsArgumentNullException. Getgives you the value exactly as the game keeps it. It does not scale it to a percentage.Setreturnsfalsewhen the value is not a number, there is no player, or the game is loading or saving. Otherwise it keeps your value inside the range in the table. For example, setting Health to 500 sets it to 100.OnCrossedcallscallback(stat, value, rising)when the value moves from belowthresholdto at or above it (risingis true), or from at or above it to below it (risingis false). It returnsfalsewhenIsAvailableis false.- The value is checked once every game second. The first check after a load only sets the starting point and never fires. Stats only change while the player is active, so nothing fires while the game is frozen.
RemoveAll(modId)removes everything your mod set up throughSurvival. That is its speed changes (Decay), its item and damage handlers and itsOnCrossedwatches. It does not remove handlers you added with+=to the plain events (PlayerDied,PassedOut,Damaged,Breakdown,Consumed). Take those off with-=.
Changing how fast a need changes (Decay)
Decay.Setchanges how fast one need changes, and you can undo it. Each mod gets one entry for each stat, and a secondSetfrom the same mod replaces the first.- The speed becomes
base x (all multipliers multiplied together) + (all offsets added together).baseis the game’s own rate for that stat, per hour. A multiplier must be 0 or more, orSetreturnsfalse. - You can change
Hunger,Thirst,Hygiene,AlcoholNeed,MushroomNeed,SmokingNeedandGamblingNeed.Decay.Supports(stat)istrueonly for these. They are the stats where the game has one per-hour rate that it uses to move the stat. - A craving can never be pushed upward by an offset. For
AlcoholNeed,MushroomNeed,SmokingNeedandGamblingNeedthe final speed is never below 0. A negative result becomes 0, so an offset cannot make a craving rise. The game lowers these cravings with no upper limit when the player is not addicted. - For the other stats, a negative total speed is allowed and makes the stat move the other way. Hunger and Thirst normally rise, so a negative total makes them fall. Hygiene normally falls, so a positive total makes it rise. Offsets may be negative, so the total can be negative for those stats.
- The game changes the speed again afterwards, so an offset is not an exact amount per hour. Awake, hunger is multiplied by
(1 - High / 100)and(1 - AlcoholAddiction / 200). While the player sleeps, hunger and thirst are multiplied by 0.33 and hygiene by 0.5. - For the four cravings, one change affects both rising and falling. A craving rises by
Addiction x 0.01 x rate / 60(the other needs use the same form) and falls byrate / 60. A change to the rate changes both. - Hunger and thirst speed also affect the toilet needs. Hunger decay also scales how fast Bowel builds up, and thirst decay scales Bladder.
- Some stats cannot have a speed change.
DuckNeedcannot, because the game only uses its rate to set the level when the player has enough ducks, so it is not a real rate. Addiction levels (AlcoholAddictionand the others),Bowel,Bladder,Health,Mood,Tiredness,RadiationandSaunaHeatcannot either.
Using items (Consumables)
Consumables.Interceptruns your code before the game does anything for an item use, once for each use. If any handler setsargs.Cancel = true, the whole use is skipped. Nothing else happens for that use: no cooldown, no sound, no events, no change to the item, no stat change, no liquid change, no outputs, no item removed, no ammo change and no spawned items. (The framework looks at the use entry point,ItemInfo.UseItem(int, bool)for inventory use (a drink from a liquid container included) andCollectibleItem.InvokeActions()for use in the world.ItemConsumable.Invoke(ItemData, bool)is the fallback for other callers.)- A use the game refuses never reaches your handler. That is a use with no use button, a cooldown, an active trade,
CanUsefalse, or a permission check that says the player may not use it. - All handlers share one
ConsumeArgsfor each use, and a handler that throws is logged while the others still run. The framework never changes the sharedItemConsumable. Consumables.Remove(modId)removes all of your mod’s handlers, andRemove(modId, handler)removes one.Survival.RemoveAllcalls the first.- You cannot make an item stronger or weaker.
Interceptcan only cancel a use. It cannot change the size of an effect.
The Consumed event
Consumables.Consumedis for watching only. It fires once for eachItemConsumable.Invokethe game runs, after the game has applied the effects, used up or removed the item and spawned its extra items.ItemIdis the item’s id in the game’s item list (Item.ID). The payload has no stat changes in it.Consumeddoes not fire when a use was cancelled or refused, or when the game throws, or whenSurvival.IsAvailableis false.- One use can fire
Consumedmore than once. It fires once for every consumable action the game runs. For exampleItemInfo.UseItemruns the child item’s actions and then the item’s own action, andItem.InvokeActionsruns each action of an item. A use forced by depression fires it too. - Unsubscribe from
Consumedwith-=.RemoveAlldoes not remove it, and handlers are kept apart so one that fails does not stop the others. - A use that depression forces is the one case where cancelling works differently. The game calls
ItemConsumable.Invokedirectly, so only the fallback applies. A cancel skips the item’s effects and its removal, so the item stays in the inventory. The game’s mental health code treats the use as handled and skips its own fallback. (ItemInfo.Activatestill runs, andDepressionstill returnstrueafter a cancel.)
Damage and other events
InterceptDamageruns your code before the game takes health from the player. Handlers run in the order they were added.- Your handler gets a
DamageArgs:Originalis the amount the game asked for,Amountis the working amount, andReasonandSilentsay what caused it and whether a message shows. You can changeAmountor setCancel = true. ACancelfrom any handler wins, and the later handlers still run and see it. Damaged(lost, reason)fires when the player really lost health.lostis the amount actually taken, after the health stopped at 0. Bleeding does not fire it, because bleeding raisesBleedingand not health directly. Mushroom and gas protection do not fire it, because they stop the damage before health changes. (It fires whenPlayerStatsHealth.DamagelowerscurrentStatus.)PassedOutfires when the player faints. It does not fire when the call is refused, when normal sleep starts, when the player is arrested, or when the player is already passed out. (It fires whenWaitingController.PassOut(..., Type.Passout)changeswaitingTypetoPassout. Normal sleep (Sleep) setsSleeping, andType.Arrestis the arrest case.)Breakdownfires when the game starts a step of a mental breakdown.BreakdownArgs.Kindsays which one:Suicide,SelfHarm,MushroomsorAlcohol. SetCancel = trueto stop that step. ACancelfrom any handler wins, and later handlers still run and see it.- Handlers do not run while a save loads or saves, and the breakdown step goes ahead then.
PlayerDied,PassedOutandDamagedare plain events.RemoveAllcannot remove your handlers from them. Unsubscribe with-=.
Damage handler limits
Damage handlers do not run in three cases. They do not run while a save loads or saves, for an amount of 0 or less, or from inside another damage handler. RemoveDamage(modId, handler) removes one handler of your mod, and RemoveAll removes them all.
Limits
- A health bar or other display for your own custom stats is not available.
Still to be checked in the game
- The cancel path at the entry points (
ItemInfo.UseItemandCollectibleItem.InvokeActions) has not been tested in a running game.