Skip to content

rsmm.sdk.builder

rsmm.sdk.builder

Mod.asset(self, decoded_path: 'str', source: 'str | Path') -> 'None'

Stage a source asset to override the game file at decoded_path.

decoded_path is the plaintext asset path as it appears in data/asset_map.json (forward slashes), e.g. 3D/Characters/Heroes/Melusine/Textures/T_Melusine_ALB.png. The file is copied verbatim into mods/<id>/assets/<decoded_path>; rsmm apply auto-cooks source formats (.glb/.png/.dds/…) into the cooked container and installs them. Pre-cooked inputs pass through.

Mod.boss(self, id: 'str', *, base: 'str', name: 'str | None' = None, **fields)

Register a custom boss cloned from vanilla base.

Mod.enemy(self, id: 'str', *, base: 'str', name: 'str | None' = None, **fields)

Register a custom enemy cloned from vanilla base.

Mod.hero(self, id: 'str', *, base: 'str', name: 'str | None' = None, abilities: 'list | None' = None, **fields)

Register a custom hero cloned from vanilla base.

Mod.item(self, id: 'str', *, base: 'str', name: 'str | None' = None, **fields)

Register a custom item cloned from vanilla base.

Mod.map(self, id: 'str', *, base: 'str', **fields)

Register a custom map/level cloned from vanilla base.

Mod.model(self, decoded_path: 'str', source: 'str | Path', rotate_deg: 'tuple[float, float, float] | None' = None, scale: 'float | None' = None) -> 'None'

Override a mesh asset. Source must be a .glb/.gltf.

A custom mesh is cooked at apply-time by retargeting it onto the game asset’s skeleton (engine.geometry_cook). It is auto-scaled and recentered into the original’s space; orientation defaults to an auto-upright guess (tallest axis -> up). If the mesh comes out turned or upside down, pass rotate_deg=(x, y, z) — a rigid rotation in degrees applied before the fit (e.g. (90, 0, 0) to flip upright, (0, 180, 0) to face the other way).

The auto-fit matches only the tallest axis, so a mesh with a different aspect ratio than the original can come out too big or small. Pass scale= to multiply the auto-fit (e.g. scale=0.5 to halve it).

Mod.skinpack(self, name: 'str', key: 'int', *, ac_id: 'str' = '', al_id: 'str' = '', base_id: 'str' = '') -> 'None'

Register a custom selectable skin-pack slot.

The skin roster is hardcoded in the engine (a fixed 9 oCAdditionalContent “SkinPack” entries); see docs/_re/kinds/skins.md. A new slot can only be added at runtime, so this is realized by the native loader DLL (src/loader/src/hook_skins.cpp), which post-detours the roster builder and appends a standalone entry per def. Defs are staged to mods/<id>/skinpacks.json; the loader aggregates them across enabled mods.

Args: name: display name shown in the skin grid. key: pack key (int) the engine matches against a hero’s pack-id field (entry +0x3c). Must be unique across packs. ac_id: AC content id (entry +0x50), e.g. RW000PSAC000000A. al_id: AL content id (entry +0x60), e.g. RW000PSAL000000A. base_id: base content id (entry +0x70).

Note: registering the slot is verified, but the engine’s skin-grid populate (FUN_1401f0f10) only renders a node as a button when the manager filter (vtable[1]) returns 3. Two derived filter variants exist: the Steam build checks ISteamApps::BIsDLCEnabled via the node key (+0x3c); the local build checks a DLC entitlement file path at node+0x80. A brand-new key is rejected by both gates, so the slot is on the roster list but invisible by default. To prove the button path in-game, build the loader and run with RSMM_SKIN_FORCE_SHOW=1 — it force-pushes the node into the grid and logs the filter verdict (see docs/_re/kinds/skins.md A1/A2). The dlc_path field can be set in skinpacks.json for the local filter variant. Asset resolution for a new slot is likewise unconfirmed; stage the per-skin cooked assets with :meth:asset/:meth:texture/:meth:model, and until the resolver is mapped, prefer reusing an existing slot’s AC/AL id.

Mod.summary(self) -> 'dict'

Snapshot of everything staged so far — print before :meth:commit to see exactly what the mod will write (no disk writes).

Mod.tag(self, tag_id: 'str', members) -> 'None'

Group content into a named, cross-mod-extensible tag (Minecraft #namespace:path tags analog).

members is a ContentRef / id string, or an iterable of them. Calling tag() again with the same id appends (de-duped, order-preserved), so several mods — or several call sites — can grow one tag. Members are deref’d to raw ids, so you can pass the handles returned by :meth:item/:meth:enemy/… directly::

dagger = m.item("RubyDagger", base="Knife")
m.tag("daggers", [dagger, "VanillaKnife"])

Tags are written to mods/<id>/tags.json; downstream tooling/mods aggregate them (same model as skinpacks.json).

Mod.texture(self, decoded_path: 'str', source: 'str | Path') -> 'None'

Override a texture asset. Source must be a .png/.dds/.tga.

Mod.validate(self) -> 'list[str]'

Return human-readable warnings about the current state (does not raise). Empty list = clean. Called automatically by :meth:commit; run it yourself for a fast pre-flight.