Surfaces
What it is
Walls, floors and ceilings in a player’s room can be covered with wallpaper, and each wallpaper comes in one or more looks (skins). This API lets your mod list those surfaces, see which wallpaper is on each one, put a wallpaper on, strip one off, and get told when either happens. It does not cover the plain paint or material of a surface that has no wallpaper, the scraping the player does to take wallpaper off, or the wallpaper items themselves.
When to use it
- Redecorate a room as part of a quest or a gift.
- Reset a surface to bare when a tenant moves out.
- React when the player puts up or strips wallpaper.
Quick example
This mod writes a log line each time wallpaper is put on or taken off a surface. Once a save is loaded, it also logs how many surfaces already have wallpaper.
using ObenseuerCore;
[ModInfo("example.wallpaperwatch", "Wallpaper Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class WallpaperWatch : FrameworkMod
{
protected override void OnLoad()
{
Surfaces.Changed += args =>
{
if (args.WallpaperItemId == 0) Log.Info("Wallpaper removed from " + args.Id);
else Log.Info("Wallpaper " + args.WallpaperItemId + " (skin " + args.SkinIndex + ") put on " + args.Id);
};
Events.WhenReady(() =>
{
int covered = 0;
foreach (SurfaceInfo surface in Surfaces.All())
if (surface.WallpaperItemId != 0) covered++;
Log.Info(covered + " of " + Surfaces.All().Count + " surfaces have wallpaper.");
});
}
}
Changed is an event: the framework calls your code each time it happens. A WallpaperItemId of 0 means the surface has no wallpaper. Events.WhenReady waits until a save is loaded before it runs your code. See Using events.
Common tasks
Read what is on one surface. Take the id from Surfaces.All(). Get gives null when no surface has that id.
string id = "some id from Surfaces.All()";
SurfaceInfo surface = Surfaces.Get(id);
if (surface != null) Log.Info("Wallpaper " + surface.WallpaperItemId + ", skin " + surface.SkinIndex);
Put White Wallpaper (item number 19470) on a surface in its first skin. The call gives false when the game would not allow it.
bool put = Surfaces.SetWallpaper(id, 19470, 0);
Log.Info(put ? "The wallpaper is on." : "The game would not put that wallpaper there.");
Strip the wallpaper off a surface.
bool stripped = Surfaces.Remove(id);
Log.Info(stripped ? "The surface is bare." : "Could not strip the wallpaper.");
Check whether the change event works.
if (!Surfaces.IsAvailable) Log.Warn("Wallpaper events are off.");
Reference
Text in brackets at the end of a note is extra detail for experienced modders, and you can skip it on a first read.
Surfaces the surfaces in the scene you are in
This is the class to start from. All() and Get(id) read the surfaces, SetWallpaper puts a wallpaper on one, Remove strips it, and Changed tells you when either happens.
public static class Surfaces
{
public static List<SurfaceInfo> All();
public static SurfaceInfo Get(string id);
public static bool SetWallpaper(string id, int itemId, int skinIndex);
public static bool Remove(string id);
public static event Action<SurfaceArgs> Changed;
public static bool IsAvailable { get; }
}
| Method | Returns | What it does |
|---|---|---|
All() | List<SurfaceInfo> | Every surface in the scene you are in. |
Get(id) | SurfaceInfo | The surface with this id. It is null when no surface has it. |
SetWallpaper(id, itemId, skinIndex) | bool | Puts a wallpaper on the surface in that look. It is true when the surface now shows it. |
Remove(id) | bool | Strips the wallpaper. It is true when the surface has none afterwards. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the Changed event could not be set up. |
| Event | Gives you | When it fires |
|---|---|---|
Changed | SurfaceArgs | A surface’s wallpaper or skin has just changed. |
SurfaceInfo a snapshot of one surface
All() and Get(id) give you one of these. WallpaperItemId is 0 when there is no wallpaper, and SkinIndex is the look that is showing. (It is built from the game’s FurnitureSurfaceArea.)
public sealed class SurfaceInfo
{
public string Id { get; }
public int WallpaperItemId { get; }
public int SkinIndex { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | The surface’s id: its save id (see the glossary), or its place in the scene if it has none. |
WallpaperItemId | int | The item number of the wallpaper on it. 0 means no wallpaper. |
SkinIndex | int | Which look of that wallpaper is showing, counting from 0. |
SurfaceArgs what the Changed event tells you
The Changed event gives you one of these, with the wallpaper and look now on the surface. WallpaperItemId is 0 when the wallpaper was removed.
public sealed class SurfaceArgs
{
public string Id { get; }
public int WallpaperItemId { get; }
public int SkinIndex { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | The same id as SurfaceInfo.Id. |
WallpaperItemId | int | The wallpaper now on the surface. 0 means none. |
SkinIndex | int | The look now showing. |
Things to know
All()andGet(id)read the surface as it is right now. ASurfaceInfois a snapshot, so ask again for fresh values.All()only lists surfaces that are switched on in the scene you are in. (The id is the object’s save GUID, or the scene path when it has none. The surfaces are the game’sFurnitureSurfaceAreaobjects.)SetWallpaper(id, itemId, skinIndex)puts that wallpaper on the surface in that look. It returnstruewhen the surface now shows that item and look. If the surface already shows them, nothing is done and you still gettrue.SetWallpapergivesfalsewhen the game would not allow it. That is the case for an unknown surface id, an item number that is not a wallpaper, a look the wallpaper does not have, a wallpaper that is not allowed on that kind of surface (for example a wall wallpaper on a floor), and while a save is loading or saving. (The surface rule is the game’sFurnitureSurfaceArea.AllowedToPlace, which checks the wallpaper’s surface area flags and the surface’s allowed surface types. Before it calls the game, the framework loads the wallpaper’s look textures (LoadVariants, the same as the game’s own placing code does). That step has not been tested in a running game.)- A look number that is below 0 or past the wallpaper’s last look is refused. The game would save such a number and then fail with it, so the framework does not let it through. (A negative index throws when the surface updates, and again on every load. An index past the last look shows no material, and the surface keeps its old one.)
Remove(id)strips the wallpaper and brings back the surface’s normal look. It does not give the player an item and does not use anything up. It returnstruewhen the surface has no wallpaper afterwards, which is also the case if it had none to begin with. (It calls the game’sRemoveWallpaper, which puts back the default material and clears the saved wallpaper.)Removedoes not do what the player does when scraping. The player’s scraping wears down the scraper and can destroy it.Removeskips all of that. (It does not run the game’sTryRemoveWallpaper, which costs scraper integrity.)Changedfires for every change, whether your mod, the player or the game made it. It fires only when the wallpaper item or the look really changes, so putting the same wallpaper on again raises nothing.Changeddoes not fire when a save loads. The game puts the saved wallpaper back before it applies it, so the framework sees no change. If your handler should ignore changes made while a save loads, checkSaveData.Busy(true while a save is loading or being written). (It is raised after the game’sSetWallpaperandRemoveWallpaper. On a load, the game restoressavedItemIdandsavedSkinIndexbefore it callsSetWallpaper, so the check before the call already sees the same values.)IsAvailableisfalsewhen the change event could not be set up. ThenChangednever fires. See “If something fails to set up” below.
What you can’t do (yet)
- You can’t change the paint or material of a surface that has no wallpaper. Only the wallpaper and its look are covered.
- You can’t make the player’s scraping happen or change what it costs.
Removeis the only way to strip a surface here. - You can’t create or edit wallpaper items here. This page only puts an existing wallpaper item on a surface. The items are part of the game’s item data.
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.surfaces.log. Source files: src/Api/Surfaces.cs and src/Api/SurfacesPatches.cs.
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, not from a background thread you started. The game only works safely on one thread, the main thread (see the glossary). This goes for the code that handles
Changedtoo. - It is safe to call any of it at any time, even on the main menu or with no save loaded.
All()gives an empty list,Getgivesnull, andSetWallpaperandRemovegivefalse. Nothing throws. Anullor empty id simply finds nothing. SetWallpaperandRemovedo nothing while a save is loading or saving. They returnfalse.- Your
Changedhandler runs inside the game’s own call, right after the change. If your handler 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 wallpaper changes. If a game update breaks one of them, the framework removes all the changes of the feature group that depends on it, turns the group off, and writes error lines to the group’s log file in BepInEx\logs. The rest of the framework keeps working. The name you will see in the log is the feature group obenseuer.moddingframework.surfaces.
- If the change event cannot be set up,
Changedstops andIsAvailableisfalse. (The framework needs the two private fieldssavedItemIdandsavedSkinIndexonFurnitureSurfaceArea, and it changes two game methods:SetWallpaper(ItemWallpaperData, int, int)andRemoveWallpaper(). If a field or a method is missing, the feature group is turned off.IsAvailableisfalsethen.)