Tenants
Status: experimental.
What it is
In the game, the player can own a tenement (an apartment block) and rent its flats out to NPCs. A tenant pays rent, can cause trouble, and the building can be upgraded. This API lets a mod move an NPC into a flat, set off a tenant event (such as a noisy night), open the game’s rent screen for an NPC, read the rent the tenement earns, and read the upgrade and clean levels of the building.
Evicting a tenant and the reports about tenant events are on the Housing page.
When to use it
- Move an NPC into a flat when a quest step is done.
- Make a tenant do something, such as make noise, to test or to tell a story.
- Open the game’s rent screen so the player can pick a flat for an NPC.
- Show how much rent the tenement earns, or how far a flat has been cleaned or upgraded.
Quick example
This mod, once a save is loaded, logs the tenement’s rent income and how many NPCs could be rented a flat right now. Nothing is changed.
using ObenseuerCore;
[ModInfo("example.tenantwatch", "Tenant Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class TenantWatch : FrameworkMod
{
protected override void OnLoad()
{
Events.WhenReady(() =>
{
RentPayment income = Tenants.TotalIncome();
Log.Info(income == null ? "The tenement is not loaded" : "Rent income: " + income.Oc + " OC + " + income.Rm + " RM");
int candidates = 0;
foreach (NpcInfo npc in Npcs.All())
if (Tenants.CanRent(npc.Id)) candidates++;
Log.Info(candidates + " NPCs could rent a flat");
});
}
}
TotalIncome gives null when the tenement is not loaded. The income has two amounts, Oc (Open Sewer Coins) and Rm (Real Money), see OC and RM. WhenReady runs your code once a save is loaded (see Using events).
Common tasks
Move an NPC into flat number 3 straight away, without the deposit, the notification or the fade. Rent returns true when the NPC is now the flat’s tenant.
int npcId = 0;
if (Tenants.CanRent(npcId) && Tenants.Rent(Mod.Id, npcId, 3, 0f, true))
Log.Info("Moved in");
Make a tenant make noise. The event always fires when the call is accepted.
bool ran = Tenants.TriggerEvent(Mod.Id, npcId, TenementEventKind.MakeNoise);
Open the game’s rent screen for an NPC, so the player picks the flat.
bool opened = Tenants.OpenRentScreen(Mod.Id, npcId);
Read how clean and how upgraded a flat is. Flat numbers are the same as on the Housing page.
int clear = Tenants.FurnitureClearPercent(3);
int water = Tenants.ApartmentUpgrade(3, TenementController.ApartmentUpgradeCategory.Water);
int roof = Tenants.GeneralUpgrade(TenementController.GeneralUpgradeCategory.Roof);
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.
The calls that change the game take your mod’s id as modId (see Mod id). The other calls only read.
Tenants
An NPC is given by its id from the NPC API (NpcInfo.Id). A flat is given by its number, the same number Housing.Get uses. Money amounts are a RentPayment, which has Oc and Rm and is described on the Housing page.
Tenants move tenants, trigger their events and read the tenement
Use it to rent an apartment to a resident NPC, trigger a tenant event and read the rent income.
public static class Tenants
{
public static bool CanRent(int npcId);
public static bool Rent(string modId, int npcId, int apartmentId, float moveInHours, bool silent);
public static bool TriggerEvent(string modId, int npcId, TenementEventKind kind);
public static bool OpenRentScreen(string modId, int npcId);
public static RentPayment TotalIncome();
public static RentPayment RawIncome();
public static int GeneralUpgrade(TenementController.GeneralUpgradeCategory category);
public static int ApartmentUpgrade(int apartmentId, TenementController.ApartmentUpgradeCategory category);
public static int FurnitureClearPercent(int apartmentId);
}
| Method | Returns | What it does |
|---|---|---|
CanRent(npcId) | bool | true when the NPC is not a tenant yet and could be one. |
Rent(modId, npcId, apartmentId, moveInHours, silent) | bool | Moves an NPC into a flat. true when it is now the tenant. |
TriggerEvent(modId, npcId, kind) | bool | Makes a tenant event happen. true when an event ran. |
OpenRentScreen(modId, npcId) | bool | Opens the rent screen for an NPC. true when it opened. |
TotalIncome() | RentPayment | The rent the tenement earns. null when the tenement is not loaded. |
RawIncome() | RentPayment | The same rent, before the game divides it into payments. null when the tenement is not loaded. |
GeneralUpgrade(category) | int | The upgrade level of the building. -1 when the tenement is not loaded. |
ApartmentUpgrade(apartmentId, category) | int | The upgrade level of one flat. -1 when it is unknown. |
FurnitureClearPercent(apartmentId) | int | How much of a flat has been cleared of furniture. -1 when it is unknown. |
Things to know
CanRent(npcId)istruewhen the NPC is not a tenant yet and has a tenant record. An NPC who is listed as a relative of a tenant counts as already rented, as it does in the game.Rentmoves the NPC into the flat the game’s own way. It follows the game’s rules, so it refuses (returnsfalse) when:- the flat is the player’s own flat (number 1);
- the flat already has a resident, even one that has furniture only;
- the NPC is already a tenant;
- the tenement is not loaded, or its rent screen is open;
- the flat does not meet what the NPC needs, such as size, electricity, water and heating. (It calls
TenementController.RentApartment. The requirements check isTenementResident.CheckRequirements.)
moveInHoursis how many hours pass before the tenant moves in, from 0 to 8760 (a year). A value outside that range makesRentreturnfalseand writes a line to the log once.silentdecides whether the game takes a deposit and shows the move. Withsilentset totrue, the game takes no deposit and pays none, shows no notification, and does not fade the screen. The NPC that is already in the scene stays visible. Withsilentset tofalse, the game takes the deposit and plays its normal fade. The framework never charges or pays anything itself. (Withsilentfalse, the deposit goes throughMoney.AddMoneyand the fade isRentFade. A rented resident is only hidden by the fade path, so a silent rent leaves the NPC visible, the same as the game’s own silent rent.)TriggerEventmakes the first matching tenant event happen, and the game’s random roll is skipped. It picks the first event of that kind on the tenant’s record that the game says can happen now. The event sends its mail, sets its dialogue values and runs the scene reaction for that tenant (a relay or a spawner), if that scene is loaded.Housing.TenementEventStartedfires for it just like for a random event. (It callsTenementEvent.OnTriggerfor the first event of that type whoseCanTrigger()holds.)TriggerEventreturnsfalsewhen no event ran. That happens when the NPC is not a tenant, the tenant has not moved in yet, the flat has furniture only, the tenant has no event of that kind, or the game’s tenement event controller is not loaded. An event kind that is not inTenementEventKindalso givesfalse.OpenRentScreenopens the game’s rent screen for the NPC, and the player picks the flat. The framework does not rent anything itself. (It callsTenementResident.StartRent, which marks the NPC as the open resident and opens the game menu on the tenement page.)OpenRentScreenreturnsfalsewhen the screen can’t be opened right now. That happens when the NPC is already a tenant or a relative of one, the tenement is not loaded, a rent or contractor screen is already open, a menu is open or the game is paused, input is blocked, the inventory is turned off (the game needs its keys to open the tenement page), the game has switched its menu off (while lockpicking, for example), the player is not holding the task item the game requires for renting, building mode is open, or a dialogue is on.TotalIncome()andRawIncome()give the rent the tenement earns.RawIncomeis the full amount before the game divides it into payments. Both givenullwhen the tenement is not loaded.GeneralUpgrade,ApartmentUpgradeandFurnitureClearPercentgive-1when the tenement or the flat is not loaded.ApartmentUpgradeandFurnitureClearPercenttake the flat number, asHousing.Getdoes.- The calls that change the game return
falsewhile a save is loading or being written. These areRent,TriggerEventandOpenRentScreen.
What you can’t do (yet)
- You can’t close the rent screen from your mod. The player closes it with the menu key, and the framework has no call for it.
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.tenants.log.
Everything not listed in this reference is internal and may change without notice. Items marked experimental may change too.
When you can call it
- Call everything from your mod’s normal code, not from a background thread you started. The game and Unity only work safely on one thread, 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 the tenement or the flat is not loaded, the reads give
-1ornull, and the calls that change the game givefalse.
What to check for and what comes back
- Nothing here throws. A
modIdthat is not valid (it must be 1 to 64 letters, digits,.,_or-) makes a change call returnfalse, and one line goes to the log. - If the game itself fails while the framework works, you get that plain default instead of an error. The first failure of each member is written to the log as an error.
If something fails to set up
This API does not change any of the game’s methods, so there is no feature group to turn off. It has nothing to fail at start.