Seats
This page uses Vector3, GameObject from Unity. The glossary says what each one is.
What it is
A seat is any chair in the game, toilets included. This API lets your mod list the seats in the scene you are in, see which are taken and by whom, make the player sit down in one or stand up again, and get told when the player or an NPC sits down or stands up. It does not control NPCs: the game’s schedules decide when NPCs sit.
When to use it
- React when the player sits down or stands up, for example to start a card game or a conversation.
- Find a free seat near a spot.
- Seat the player from a script or a cutscene, and stand them up again.
Quick example
This mod writes a log line each time someone sits down or stands up. Once a save is loaded, it also logs how many seats there are in the scene and how many of them are free.
using ObenseuerCore;
[ModInfo("example.seatwatch", "Seat Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class SeatWatch : FrameworkMod
{
protected override void OnLoad()
{
Seats.Sat += seat => Log.Info((seat.ByPlayer ? "The player" : "An NPC") + " sat down at " + seat.Id);
Seats.Left += seat => Log.Info((seat.ByPlayer ? "The player" : "An NPC") + " stood up from " + seat.Id);
Events.WhenReady(() =>
{
int free = 0;
foreach (SeatInfo seat in Seats.All())
if (!seat.IsOccupied) free++;
Log.Info(Seats.All().Count + " seats here, " + free + " free.");
});
}
}
Sat and Left are events: the framework calls your code each time they happen. WhenReady waits until a save is loaded before it runs your code. See Using events for more on both.
Common tasks
Seat the player in the first free seat that is not a toilet. Sit returns true when the player is seated afterwards. It returns false when the game would not let them sit, for example because they are too far away.
foreach (SeatInfo seat in Seats.All())
{
if (seat.IsToilet || seat.IsOccupied) continue;
if (Seats.Sit(seat.Id)) Log.Info("The player is sitting at " + seat.Id);
break;
}
Stand the player up if they are sitting in a particular seat. Get returns null when no seat has that id.
SeatInfo one = Seats.Get("some-seat-id");
if (one != null && one.OccupiedByPlayer) Seats.Leave();
Reference
This is the full list of what the API gives you. Text in brackets at the end of a note is extra detail for experienced modders, and you can skip it on a first read.
Seats list, sit, stand and hear about seats
This is the class to start from. All() and Get(id) find seats, Sit and Leave seat the player or stand them up, and Sat and Left tell you when someone sits down or stands up.
public static class Seats
{
public static List<SeatInfo> All();
public static SeatInfo Get(string id);
public static bool Sit(string id);
public static bool Leave();
public static bool IsAvailable { get; }
public static event Action<SeatArgs> Sat;
public static event Action<SeatArgs> Left;
}
| Method | Returns | What it does |
|---|---|---|
All() | List<SeatInfo> | Every seat in the scene you are in, toilets included. |
Get(id) | SeatInfo | The seat with this id. null when there is none. |
Sit(id) | bool | Makes the player sit in this seat. true when they are seated in it afterwards. |
Leave() | bool | Makes the player stand up. true once they have started to stand. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the Sat and Left events could not be set up. |
| Event | Gives you | When it fires |
|---|---|---|
Sat | SeatArgs | Someone has sat down. |
Left | SeatArgs | Someone has stood up. |
SeatInfo a snapshot of one seat
All() and Get(id) give you one of these. Use IsOccupied and OccupiedByPlayer to find a free seat or the player’s own seat, and pass Id to Sit. (It is built from the game’s InteractableChair, and IsToilet is true for a Toilet.)
public sealed class SeatInfo
{
public string Id { get; }
public bool IsOccupied { get; }
public bool OccupiedByPlayer { get; }
public Vector3 Position { get; }
public bool IsToilet { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | A name for this seat that you can use with Get and Sit. |
IsOccupied | bool | true when an NPC, a piece of furniture or the player holds the seat. |
OccupiedByPlayer | bool | true when the player is sitting in this seat. |
Position | Vector3 | Where the seat is, in world coordinates. |
IsToilet | bool | true when the seat is a toilet. |
SeatArgs what Sat and Left tell you
The Sat and Left events give you one of these. ByPlayer tells you whether the player or an NPC did it.
public sealed class SeatArgs
{
public string Id { get; }
public bool ByPlayer { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | The seat’s id, the same as SeatInfo.Id. |
ByPlayer | bool | true when the player did it. false when an NPC did. |
Things to know
All()lists the seats that are switched on in the scene you are in. Toilets are included. (it lists every activeInteractableChairin the active scene.)Idis a name that identifies one seat. It normally stays the same each time the game runs. A seat the game gave no unique id gets one made from its place in the scene, and that id changes when the scene changes. (it is the save GUID, else the scene path.)Get(id)returnsnullfor an empty or unknown id.Sit(id)makes the player sit the way the game does when they press the use key. If the game would refuse,Sitrefuses quietly and returnsfalse. (the checks areInteractableChair.PlayerIsSitting,SaveData.Busy, theGameControllerstate (menu, shop, pause menu, console, dialogue,keysDisabled,GameIsPaused),PlayerState.IsSleeping,OpenSewerCharacterController.ControlsDisabled,Interactable.IsInteractDisabled,Occupiedand the distance to the camera (InteractObjects.useDistance). After the checks it calls the chair’s publicInteract(), which is virtual, so aToiletkeeps its own lid and bucket checks. The result is read back fromPlayerSittingHere, so a refusal inside the game, for example a camera move still running, also givesfalse.)Leave()starts the player standing up, so they may still look seated right after the call. It returnstrueonce the stand-up has started. The player is no longer seated, andLeftfires, when the camera move ends. (it uses the same busy checks asSit, thenInteract()on the seated chair. It is refused whileDisableStopSitis set (contests and card tables) or while the chair’s camera move is still running. For aToiletwhoseenabledisfalse, the game’sToilet.Useonly shows a warning.)Satfires after the player sits, or after an NPC is seated while you play.ByPlayertells you which. (only the live schedule path raisesSatandLeftfor NPCs.NPCSitandStopNPCSitare driven by the NPC schedules and the tenement spawn.)Leftfires when the player stands up, or when an NPC is told to stand. For an NPC it fires before the game’s short delay that releases the NPC.- The events fire after the game has changed the state. A handler that throws is logged, and the other handlers still run.
Limits
Sit(id)isfalsewhen the seat is not found, when the player is already seated anywhere, and when the game is loading or saving a save. It is alsofalsewhen the player is busy: a menu, shop, pause menu, console or conversation is open, keys are turned off, the game is paused, or the player is asleep. And it isfalsewhen the player cannot move, for example on a ladder or while picking a lock, when the seat is switched off for use or already taken, and when the player’s camera is more than 1.5 metres from the seat. A toilet still shows its own warnings, such as a missing lid or bucket, because they come from the game’s own use code. (the camera check usesInteractObjects.useDistance. The result also covers a refusal inside the game, as described above.)Leave()returnsfalsewhen the player is not seated, when the game is busy in the ways listed forSit, or when the game has locked them in. A card table or a contest locks the player into the chair, and a toilet that is switched off only shows a warning instead of standing the player up.Satdoes not fire for NPCs that the game places in a seat while it loads or sets up a scene, and it does not fire for the sit that a save restores.- The events never fire while a save is loading.
What you can’t do (yet)
- You can’t set whether a seat counts as occupied. The game sets that flag again on every physics step from a trigger on the furniture, so a change from your mod would be overwritten at once. (
InteractableChair.ChangeOccupiedis written every physics frame byChairOccupiedTrigger.) - You can’t put an NPC in a seat or take one out. The NPC schedules and the tenement spawn do that.
- You can’t stop the player from standing up. The card game and contest code does that for itself. (
DisableStopSitis not wrapped.) - You can’t change where the camera or the player goes when sitting. (
cameraPosition,upChairOffsetanddownChairOffsetare not wrapped.) - You can’t flush, empty the bucket, or use paper or the urinal from here. (the
Toiletmembers are not wrapped.)
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.seats.log.
Everything not listed in this reference is internal and may change without notice.
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 only works safely on one thread, the main thread (see the glossary). - It is safe to call any of it at any time. With no save loaded, or on the main menu,
All()gives an empty list,Getgivesnull, andSitandLeavegivefalse. - Nothing throws an error to you. A null or empty
idfinds nothing. - Your event handlers run inside the game’s own call, one at a time. If one of your handlers throws an error, the framework logs it and still runs the other handlers.
If something fails to set up
The framework changes some of the game’s methods when it starts, so it can tell your mod when someone sits or stands. If a game update breaks one of them, only the Sat and Left events turn off, and the framework writes error lines and a warning to the log. Its feature group is obenseuer.moddingframework.seats, the name you will see in the log. A feature group is the named set of the framework’s changes for one feature.
Seats.IsAvailableisfalsewhen the events are switched off.All,Get,SitandLeavekeep working. If any one of the changes is missing, none of them is used. (the methods are the privateInteractableChair.Sit(bool, bool),StopSitEnd(),NPCSit(GameObject, bool)andStopNPCSit(GameObject). The framework also needs the privatedisableStopSitandcurrentCoroutinefields.)