Skip to content

Custom enemies

Custom enemies are a clone-and-patch kind: you start from a vanilla enemy, change its stats/assets, and register it as a new definition. Experimental

Author one

The enemy kind is experimental, so the mod must opt in with Mod(experimental=True) (lint enforces this for non-confirmed kinds).

build.py
from rsmm import sdk
with sdk.Mod("MyEnemies", experimental=True) as mod:
mod.enemy(
"Frost_Goblin",
base="Goblins/Goblin_Warrior", # vanilla donor to clone
name="Frost Goblin",
# **fields override stat-block values on the clone
)

Equivalent declarative form:

mods/MyEnemies/manifest.toml
[mod]
id = "MyEnemies"
experimental = true
[[content]]
kind = "enemy"
id = "Frost_Goblin"
base = "Goblins/Goblin_Warrior"
name = "Frost Goblin"

Mod.boss(...) takes the same shape. Bosses are not a separate class — they are ordinary enemies tagged into the boss flag-list and gated by the boss-timer controller.

Run ./rsmm apply to cook and register the definition.

How enemies spawn

Spawning is selector-driven, not a direct “place enemy X” call. The chain:

flowchart LR
    E["oCDtEnemyDefinition<br/>(one stat block)"] --> T["oCDtEnemyTribeDefinition<br/>(group / faction)"]
    T --> S["Camp spawn selector<br/>(flag-list, tag-filtered)"]
    C["oCDtEnemyCampTierDefinition<br/>(camp difficulty tier)"] --> S
    S --> R["runtime spawn in a camp"]

An enemy reaches a run by being a member of a tribe, which a camp selector picks via tags/flags at the right difficulty tier. So registering a definition is necessary but not sufficient — something must select it.

Class map

The full camp/enemy machinery, for when you need the internals:

ClassUIDLibraryPurpose
oCDtEnemyDefinition0x176debb70x1414118c0one enemy stat block
oCDtEnemyTribeDefinition0x141411200tribe = group / faction
oCDtEnemyCampTierDefinition0x176e18f80x141411560difficulty tier of one camp
oCDtEnemyCampDifficultyDefinition0x141411710global difficulty curve
oCDtEnemyCampEntitySelectorToSpawnEntityCpntSettings0x16b7d175per-camp spawn selector
oCDtEnemyCampEntitySelectorToSpawnTribeEntrySettings0x16b81d80one tribe entry inside a camp
oCDtEnemyFlagListEntitySelectorToSpawnEntityCpntSettings0x17019bf9tag-filtered enemy selector

oCDtEnemyDefinition is fully registered (size 0x350, factory FUN_14030a190). The runtime spawn primitive itself — the vftable[3] call that constructs an entity at a position — is documented in Spawning.

Going deeper