Conditions
Status: experimental.
What it is
Conditions are the health problems the player can pick up, such as the ones that come from poor hygiene or a high dose of radiation. Each one shows a “New condition!” message when it starts and a “Condition removed!” message when it ends. This API lists the conditions the game knows, and tells you which ones the player has right now. It lets your mod give or take away a condition, and tells you when one starts, stops or changes strength.
When to use it
- Show or log a message when the player gets or loses a condition.
- Check whether the player has a condition before your mod does something.
- Give the player a condition as a penalty, or remove one as a reward.
Quick example
This mod logs every change to a condition. Once a save is loaded, it also logs each condition the game knows and whether the player has it.
using ObenseuerCore;
[ModInfo("example.conditionwatch", "Condition Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class ConditionWatch : FrameworkMod
{
protected override void OnLoad()
{
Conditions.Changed += args => Log.Info(args.Name + " active=" + args.Active + " severity=" + args.Severity);
Events.WhenReady(() =>
{
foreach (ConditionInfo condition in Conditions.All())
Log.Info(condition.Name + " active=" + condition.Active + " severity=" + condition.Severity);
});
}
}
Changed is an event: the framework calls your code each time it happens. WhenReady runs your code once the game is ready. See Using events for more on both. The condition names are the game’s own asset names and are not written down anywhere else, so read them from the log first and then use them in your code.
Common tasks
Check whether the player has a condition. name comes from Conditions.All().
string name = "SomeCondition";
if (Conditions.Has(name)) Log.Info("The player has " + name);
Give the player a condition. Add returns true when the condition started or got stronger.
string name = "SomeCondition";
bool added = Conditions.Add(name);
Log.Info("Added " + name + ": " + added);
Take a condition away. Remove removes one stack, so a condition that has stacked needs one call for each stack. Has tells you whether more are left.
string name = "SomeCondition";
while (Conditions.Has(name) && Conditions.Remove(name)) { }
Reference
This is the full list of what the API gives you. A condition is named by its Name, the game’s internal name for it. That name is the key you pass to Has, Add and Remove.
Conditions list, check, add and remove conditions
Use Has, Add and Remove with the game’s internal name for a condition, which ConditionInfo.Name gives you. Subscribe to Changed to hear each change.
public static class Conditions
{
public static bool IsAvailable { get; }
public static List<ConditionInfo> All();
public static bool Has(string name);
public static bool Add(string name);
public static bool Remove(string name);
public static event Action<ConditionChangedArgs> Changed;
}
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the change event could not be set up. |
| Method | Returns | What it does |
|---|---|---|
All() | List<ConditionInfo> | Every condition the framework knows, active or not. |
Has(name) | bool | true when the player has this condition. |
Add(name) | bool | Gives the player the condition. true when it started or got stronger. |
Remove(name) | bool | Takes away one stack of the condition. true when one stack was removed. |
| Event | Gives you | When it fires |
|---|---|---|
Changed | ConditionChangedArgs | A condition started, stopped or changed strength. |
ConditionInfo one condition and its state
Conditions.All() gives you one of these for each condition the framework knows. Name is the key for Has, Add and Remove, and DisplayName is the text for the player. (It is built from the game’s BaseHealthCondition objects.)
public sealed class ConditionInfo
{
public string Name { get; }
public string DisplayName { get; }
public bool Active { get; }
public float Severity { get; }
}
| Property | Type | What it means |
|---|---|---|
Name | string | The game’s internal name. It is the key for Has, Add and Remove. |
DisplayName | string | The text the player sees in messages. It may be empty or shared, so use it only for showing. |
Active | bool | true when the player has this condition now. |
Severity | float | How strong the condition is now. 0 when the player does not have it. |
ConditionChangedArgs what changed
Conditions.Changed hands you one of these each time a condition starts, stops or changes strength.
public sealed class ConditionChangedArgs
{
public string Name { get; }
public bool Active { get; }
public float Severity { get; }
}
| Property | Type | What it means |
|---|---|---|
Name | string | The game’s internal name of the condition. |
Active | bool | true when the player has the condition after the change. |
Severity | float | How strong it is after the change. 0 when it was removed. |
Things to know
All()lists the conditions the game keeps ready, not every condition that could exist. That is the conditions that come from low hygiene, the ones that come from a high dose of radiation, and any condition the player has right now. Each one is markedActivewhen the player has it, andSeverityis its strength right now. (The list isPlayerStats.hygiene.lowHygieneConditions,PlayerStats.radiation.highDoseConditionsandPlayerStats.conditions.activeConditions.Nameis the Unity object name of the condition asset, andDisplayNameis itsconditionName. No game type or game enum appears in the public types.)Add(name)gives the player the condition, whatever the game’s random chance would have been. The game picks how strong it is, starts it, and shows the “New condition!” message if that condition has messages turned on. If the player already has a condition that can stack,Addadds another stack with a new random strength. (It callsBaseHealthCondition.ApplyCondition(force: true).forceskips the game’s random chance. The message shows when the asset hasnotifyset.)Addreturnsfalseand does nothing when the condition cannot be added. That is when no condition has that name, when no save is loaded, when a save is loading or being written, and when the condition cannot stack and the player does not already have it.Addreturnsfalsefor that last case because the game would show “New condition!” without adding anything, and the framework refuses before a false message can appear. (The checks areFind(name) == null,PlayerStats.instanceor its condition list being null,SaveGate.Busy, andcanStackunset with no active entry.)Remove(name)takes away one stack of the condition. The game shows the “Condition removed!” message if that condition has messages turned on. It returnsfalsewhen the player does not have the condition. (It callsBaseHealthCondition.RemoveCondition()once, which removes one entry from the active list.)- Fungal disease, radiation sickness and mental health are not conditions here. The game treats them as player stats, not conditions. Treatments for them are on the Medical page. (The game has no subclass of
BaseHealthCondition. Fungal disease isPlayerStatsMushrooms.SMVProgression(Stat.Smv), radiation sickness isPlayerStatsRadiation.Dose(Stat.Radiation), and mental health isPlayerStatsMentalHealth.currentStatus(Stat.Mood).) Changedfires after the game starts, stops or re-rolls a condition. It also fires when the game removes a condition by itself because its strength reached zero. Removing one stack of a condition that stays active at the same strength does not fire it. (It is raised afterApplyConditionorRemoveConditionchanges the active state or the strength. It is not worked out at all while nothing is subscribed.)Changeddoes not fire when strength slowly drifts, and not when a new game clears the list. To see the strength right now, callAll(). (The per-tick drift is inUpdateStats.)Changeddoes not wait for a save load to finish. A handler that should ignore changes made while a save is loading can checkSaveData.Busy, which istruewhile a save is loading or being written.- A
Changedhandler that throws an error does not stop the others. The framework logs the error and still runs the other handlers.
Limits
- You can’t create new conditions. The game ships its conditions as fixed assets and a mod cannot add one to the lists, so
Addonly works for conditions the game already has. - A handler for
Changedmay callAddorRemove, but only a few levels deep. If your handler’sAddorRemovecauses anotherChangedthat callsAddorRemoveagain, and this nests more than 8 levels, the deeper call returnsfalseand the log gets one line about it. - Do not use
AddorRemovefrom inside aChangedhandler for a condition the game is removing by itself. The game is in the middle of going through its list of conditions then, and changing the list there makes the game throw an error. (The event can fire from inside the game’s loop over the active list (the auto-removal inUpdateStats).)
Details
Target: Obenseuer 0.5.08 (buildid 25969064), Unity 6000.0.77f1 Mono, BepInEx 6 Unity.Mono (be.788, the tested build) or BepInEx 5 (5.4.23 or later). Namespace: ObenseuerCore. Logs go to BepInEx\logs\obenseuer.moddingframework.conditions.log.
Everything not listed in this reference is internal and may change without notice. Items marked experimental may change too.
The framework changes some of the game’s methods when it starts, so it can tell your mod when a condition changes. This page calls that group of changes a feature group: its name is obenseuer.moddingframework.conditions, the name you will see in the log.
When you can call it
- Call everything from your mod’s normal code (
OnLoad, event handlers,Update), not from a background thread you started. The game and Unity only run safely on the main thread (see the glossary). - It is safe to call any of it at any time, even on the main menu or while a save is loading. If there is nothing to read yet, you get an empty list or
false.AddandRemovealso returnfalsethen.
What to check for and what comes back
Conditions.IsAvailabletells you whetherChangedworks. It isfalsewhen the framework could not set up the event, or when the feature group was turned off.All,Has,AddandRemovekeep working when it isfalse.- A
namethat is null or empty throws anArgumentException. This is the only error these calls throw to you. If the game itself fails while the framework works, you get the plain default (falseor an empty list) and the first failure of each member goes to the log as an error.
If something fails to set up
The framework changes some of the game’s methods when it starts. If a game update breaks one of them, the framework turns off the feature group that depends on it, and writes error lines to the group’s log file in BepInEx\logs. The rest of the framework keeps working.
- If the framework cannot change the game’s condition methods,
Changedstops and the rest keeps working. One error goes to the log, andIsAvailablebecomesfalse. (The methods areBaseHealthCondition.ApplyConditionandRemoveCondition.)