Fisobs adds a parser and registry for custom global::AbstractPhysicalObject types. The pinned source is Dual-Iron/fisobs at commit 975f532b02431e0ef0b7f5c28033220153eba2a6, whose project declares version 5.1.0. Fisobs is an optional dependency. Source inspection establishes its API at that commit, but runtime compatibility with Rain World build 22785462 has not been tested.

The game parser global::SaveState.AbstractPhysicalObjectFromString(global::World,System.String) recognizes built-in types through an explicit chain. For another type, its fallback can preserve the abstract type token, position, and ID in a plain AbstractPhysicalObject. It does not parse custom fields or provide custom realization.

FisobRegistry hooks that parser. When the saved type belongs to a registered Fisob, the registry builds EntitySaveData and calls the fisob’s Parse method.

flowchart TD
    A[Register Fisob] --> B[Load saved type]
    B --> C[Parse custom data]
    C --> D[Abstract object]
    D -->|Realize| E[PhysicalObject]

Define a stable type and parser

Create the ExtEnum type once and keep its string unchanged after release. Registration must happen before a save containing that type is parsed.

sealed class MarkerFisob : Fisob
{
    public static readonly AbstractPhysicalObject.AbstractObjectType MarkerType =
        new AbstractPhysicalObject.AbstractObjectType("MyMod_ReedMarker", true);
 
    public MarkerFisob() : base(MarkerType) { }
 
    public override AbstractPhysicalObject Parse(
        World world, EntitySaveData data, SandboxUnlock? unlock)
    {
        bool lit = data.CustomData == "lit";
        return new AbstractMarker(world, data.Pos, data.ID, lit);
    }
}

EntitySaveData supplies the registered type, EntityID, WorldCoordinate, custom data string, and unrecognized attributes. Treat an empty custom field as a valid older record. If the format grows, include a version token or accept both old and new forms. Validate numbers and enum values before constructing the object because Fisobs catches a parser exception and returns null for that record.

The example parser accepts only "lit". Every other value, including an empty string, becomes false. A real parser with several fields should define how missing, malformed, and future fields behave.

Serialize and realize the abstract subtype

The matching abstract subtype owns persistent fields and supplies its physical object:

sealed class AbstractMarker : AbstractPhysicalObject
{
    private readonly bool lit;
 
    public AbstractMarker(World world, WorldCoordinate pos, EntityID id, bool lit)
        : base(world, MarkerFisob.MarkerType, null, pos, id)
    {
        this.lit = lit;
    }
 
    public override void Realize()
    {
        if (realizedObject == null)
            realizedObject = new Rock(this, world);
    }
 
    public override string ToString()
    {
        return this.SaveToString(lit ? "lit" : "");
    }
}

This narrow shape reuses global::Rock as the realized body. It covers type registration, parsing, persistence, and realization. It does not define custom physics, graphics, properties, an icon, sandbox placement, or an item selection interface.

Register the fisob from the plugin before world or save creation:

Content.Register(new MarkerFisob());

Declare the Fisobs plugin dependency as io.github.dual.fisobs in the plugin metadata and declare the matching mod dependency in the package metadata. The dependency string appears in the pinned Fisobs Plugin.cs. Packaging and discovery still need a game test.

Example scope

These excerpts describe the pinned Fisobs API. They are not a complete plugin and have not been compiled against the installed build 22785462 references or run in Rain World. They require the Fisobs.Core, Fisobs.Items, and Fisobs.Sandbox namespaces and a compatible Fisobs reference.

Preserve both object layers

Spawn through the abstract object. Fisobs Ext.Spawn adds the object to its abstract room and then calls RealizeInRoom(). That order matches Abstract and realized objects. Adding only the PhysicalObject to a realized room loses the abstract record needed for room unloading and save serialization.

Runtime checks should cover registration before save loading, spawn membership in both room layers, room exit and return, save and restart, changed custom data, and an old record with empty custom data. These checks were not run for this page.

Sources

  • global::SaveState.AbstractPhysicalObjectFromString(global::World,System.String) and global::AbstractPhysicalObject.Realize() in Assembly-CSharp.dll, SHA-256 B6BE1D4E18CE219D21091B51564CB6A11C1E4106B41DE903EB8E58849CB16FDB. See Runtime reference.
  • Fisob and FisobRegistry at commit 975f532b02431e0ef0b7f5c28033220153eba2a6.
  • EntitySaveData and Ext at the same commit.