Skip to content

Example mods

Every example below is a complete mod. Copy the files into mods/<id>/, set enabled = true, and run ./rsmm apply. Source lives in docs/ExampleMods/.

Pick a starting point

Hello — smallest Lua mod

The minimum to prove the loader + SDK work: log once when the game is ready and count ticks. No assets, just manifest.toml + init.lua.

mods/ExampleSdkHello/manifest.toml
[mod]
id = "ExampleSdkHello"
name = "Example: SDK hello"
version = "0.2.0"
author = "rsmm-examples"
description = "Smallest possible mod using SDK v3. Logs once on ready + bumps a counter every tick."
enabled = false
sdk_version = ">=3.0,<4"

Magic item — declarative content

A custom magical object, cloned-and-patched from a vanilla rare item. No code — the [[content]] block tells the SDK to cook a new item end-to-end.

mods/ExampleMagicItem/manifest.toml
[mod]
id = "ExampleMagicItem"
name = "Example: Magic Item"
version = "0.3.0"
author = "rsmm-examples"
description = "Declarative custom magical object via [[content]] block."
enabled = false
sdk_version = ">=3.0,<4"
[[content]]
kind = "item"
id = "Iron_Crab_Hide"
base = "Common/Armor_Per_Object"
name = "Iron Crab Hide"
description = "Bonus armor per rare object collected."
rarity = "Common"

The kind = "item" builder resolves the base donor, cooks the new item, and rsmm apply syncs the game-side registration (versiondef + UsedRscCache). You write the manifest; the SDK does the rest. See Authoring mods for every [[content]] kind.

React to in-game events

The loader bridges the game’s named events to the Lua bus, so a mod subscribes by name — enemy_killed, unlock_hero, game_start, level_up_reach, … (one hook re-publishes every named analytics event; browse the list in the R.on contract). Needs the loader DLL. Both event buses are armed by default; the loader skips publishing entirely when no mod has subscribed.

mods/ExampleEvents/init.lua
local R = require "rsmm"
R.on("enemy_killed", function(ev)
R.kv.inc("kills") -- persists across restarts
R.log(("kill #%d"):format(R.kv.get("kills")))
end)
-- Take a whole family at once, and stop listening when you're done.
local h = R.on_match("^gameplay:ABILITY_", function(ev, name)
R.log("ability event:", name)
end)
R.once("run:end", function() R.off(h) end)

Not sure what fires? Let the loader tell you — it records every event it sees with its payload keys:

R.on("ready", function()
R.schedule.after(120, function() R.events.dump() end) -- play, then read _log.txt
end)

These events are observation-grade: they fire after the action and carry the event name + sequence, not a live entity handle.

Call engine functions

data/symbols.json maps semantic names to engine functions and records each one’s cabi, so a mod calls them like methods — no addresses, no signatures. Browse with rsmm symbols list or R.engine.names().

local res = R.engine.fn.Resource_LookupByPath(path, 0, 0, 0)
-- equivalent: R.engine.call("Resource_LookupByPath", path, 0, 0, 0)

Persistent state

R.kv stores per-mod scalars (string/number/boolean) to <mod_dir>/.rsmm_state, reloaded on the next launch. Loaded lazily, flushed automatically on exit, or on demand with R.kv.save().

R.kv.inc("runs_played") -- survives a game restart
R.kv.set("last_hero", "Scarlet")
local n = R.kv.get("runs_played", 0)

Apply any example

  1. Copy the example folder into your mods/ directory.
  2. Set enabled = true in its manifest.toml.
  3. Apply and launch:
    Terminal window
    ./rsmm apply
    ./rsmm run

More examples in the repo

  • ExampleEvents — subscribe to in-game events (enemy_killed, unlock_hero, …) and count them.
  • ExampleEngineCall — call named engine functions via R.engine.fn / R.engine.call.
  • ExampleSeedPin — pin the run RNG seed via the R._internal escape-hatch + per-mod config (R.config.get).
  • ExampleSdkAll — exercises the full SDK surface.
  • ExampleCustomSkill — relabels one of Aladdin’s skills into a custom talent (visible on his level-up cards & Skill Menu). See Custom skills.
  • HyperAggro — a gameplay tweak mod.

Browse them all in docs/ExampleMods/.