Features

Differences

For most use cases, RMML is an otherwise invisible middleman that sits between your mod’s code and Rusted Moss. However, there are a things to keep in mind when using RMML.

Game Objects, global.rmml_current_mod, and self.mod_name

Because of boring technical reasons, RMML can’t just make a omod_controller_my_mod Game Object and attach your code to it. Instead, it uses self.mod_name as a key into a struct pairing your mod’s name with a Catspeak function call.

RMML tracks the currently running mod with global.rmml_current_mod, which should be compared against when using with or instance_find.

with omod_instance {
  if self.mod_name != global.rmml_current_mod {
    continue
  }
  ...
}

You can exploit this to create instances for other mods or use sub-mods.

instance_create_depth(0,0, 0, omod_instance, { mod_name: "your_mod" })

controller

Rusted Moss Mod Loader creates a controller Instance for each mod that is loaded. This lets you use alarm Events and Instance variables normally. However, you can’t use instance_find or with directly, and need to check for the mod name as demonstrated above.

player

In INI modding, omod_player Events don’t set self to the Player Instance. RMML sets self to the current omod_player Instance automatically. Additionally, the Player Instance is sandboxed like other Instances; if you want to use the Player, you need to set self.mod_name on the Instance.

with oplayer {
  instance_change(omod_player, false)
  self.mod_name = global.rmml_current_mod
}

You also can’t have two mods installed that try to modify the Player, since only one mod’s Event code can run. This is a solvable problem, but goes against my design goals with RMML since it would be too invasive. You’d need to support three types of Player mods: mods that runs before event_inherited(), mods that replace the normal inheritance, and mods that run after event_inherited(). And make sure the Player instance is properly converted into an omod_player in all cases.

Mod File Support

RMML doesn’t just support .md files. It can interpret .ini, .meow, and index.csv files. All mods loaded by RMML receive its main benefits, unless otherwise limited.

Internally, all mods (except for .meow scripts, which run immediately) are eventually inserted into global.rmml_map.

.ini

The original .ini syntax. This is the only mod syntax that Rusted Moss supports directly. Support for .ini mods is only included for legacy purposes, I highly recommend not using it.

[object_list]
controller = enabled
instance = enabled

[controller_events]
room_start = "i = 0; while ( i < instance_number(oenemy_flame_sniper) ) { c = instance_find(oenemy_flame_sniper,i); instance_create_depth(c.x,c.y,30,omod_instance); i += 1; }"

[instance_events]
create = "self.parent = instance_nearest(self.x,self.y,oenemy_flame_sniper);"
draw = "if ( instance_exists(self.parent)) { draw_sprite_ext( spuck_bunny_ears, 0, self.parent.x, self.parent.y-8, 1, 1, 0, c_white, 1 ) }"
step = "if ( instance_exists(self.parent) ) { if ( self.parent.legs == smaya_legs_run )  { self.parent.vsp = self.parent.vsp -4; } }"

.md

The classic RMML syntax, supporting all features. This is recommended for most mods, as it simplifies development and distribution. In short, it’s a simplified version of Markdown, with each code fence interpreted as Catspeak code and H1 and H2 headers used to specify Game Object and Event names.

a game object header
# controller

an event header
## room_start

a code fence, interpreted as Catspeak
```
-- some code
instance_create_depth(0,0, 0, omod_instance)
```

another event
## draw_gui_end
```
...
```

a second game object
# instance
...

Game Objects are specified using a single H1 header (ie # controller), with all following code and H2 headers applied to that Game Object. H2 headers (ie ## room_start) are Event names and affect the code block immediately following it. Event code for an Event goes between sets of three backticks ``` at the start of a line and is interpreted as Catspeak code. These code fences can include line breaks, comments, and both types of quotes.

All other text in a .md file is ignored. If you need explicit comments (such as for commenting out code fences), you can use HTML comments (<!-- Comment -->).

If you’re using my VS Code highlighter, you’ll want to add catspeak, sp, or meow after the three backticks (ie ````catspeak) to specify the language.

index.csv

A file named index.csv inside a folder in the mods/rmml directory can also be interpreted as a mod. This is a csv, without a header. Each row is in the following order.

IndexExampleWhat
0controllerA Game Object name
1createAn Event name
2filename.meow OR "do { show_message(""hello"") }"Either a relative path from mods/rmml/{MOD_NAME} or a single line of code (starting with do {)
3sub_modAn optional sub-mod name. Don’t include this to use your main mod.

Here’s what that might look like, with an example folder layout.

{OBJECT},{EVENT},{FILENAME_OR_CODE},{?SUB_MOD?}
controller,create,controller_create.meow
basic,step_end,my_basic_end.meow,inherited_mod
instance,draw,"do { show_message(""Hello World!"")}"
mods/rmml/my_mod/
├── controller_create.meow
├── index.csv
└── my_basic_end.meow

If your file names contain spaces, you should wrap the entire path in double quotes ". Inline code blocks containing double quotes should be wrapped in double quotes, with double quotes in the code replaced with two double quotes (ie ""string""). Inline code blocks should be kept to a minimum, because otherwise you’re just using an ini mod.

I recommend using index.csv format for large mods. They load much faster (by bypassing RMML’s slow .md parser) and have better developer ergonomics compared to managing a 1000+ line file.

Game Start Script

If you use a .md mod and don’t use a Game Object and Event header, the resulting code is run during the mod loading process, and only once per game start. You can use this to load sprites or initialize global scripts, without needing to check a global or parse extra Catspeak.

no object or event header
```sp
-- this code only runs the first time the mod is loaded
global.my_sprite = sprite_add("mods/rmml/my_mod", 1, false, false, 0, 0)
```

# controller
## draw_gui_end
has headers
```sp
draw_sprite(global.my_sprite, 0, 0, 0)
```

index.csv mods also support the game start script, by using empty Game Object and Event columns.

,,game_start.meow
,,"do { show_message(""Hello World!"")}"

Dev Mode

RMML implements multiple safety features to prevent mods from crashing your game on start up, which would prevent you from accessing RMMM’s UI. This is a list of changes Dev mode currently implements. You can implement your own Dev features in your own mods.

The easiest way to enable dev mode is with a game start script:

Game Start Scripts
.md Modindex.csvINI File
```
global.rmml.dev = true
```

# controller
...

,,"do { global.rmml.dev = true }"
...
Unsupported :)

JaySpeak

JaySpeak is a dirty hack, the pinnacle of my lack of planning. The idea is that Javascript code is similar enough to Catspeak, so why not convert Javascript code (which has powerful developer tools) into Catspeak code?

INI Mod VS RMML Mod
JaySpeakCatspeak
// comment
let x = function(a, b, c) {
  return a || b && type(c) == "string"
}
-- comment
let x = fun(a, b, c) {
  return a or b and typeof(c) == "string"
}

In practice, this sucks. RMML reads JaySpeak blocks ( ```js) and does simple string replacement on the resulting code. This leads to all sorts of weird, difficult to debug errors as parts of your code are silently converted into illegal function calls and misnamed variables. It’s also slow, since the entire code fence must be placed into a string for replacement.

If you still want to use it (for some reason), you can add js as the .md code fence language.

# controller
## create
```js
// comment
let x = function(a, b, c) {
    return a || b && type(c) == "string"
}
```

I will not be adding JaySpeak support to index.csv mods. Implementing a Typescript -> Javascript -> Catspeak pipeline is an exercise left to the reader.

Finding Mod Files

modlist.txt entries are treated as partial paths, which are added to mods/rmml. The partial path also determines the mod’s internal mod name, used by global.rmml_current_mod and self.mod_name.

Name on modlist.txtFull PathInternal Mod Name
my_mod.mdmods/rmml/my_mod.mdmy_mod
my_mod.inimods/rmml/my_mod.inimy_mod
my_mod.meowmods/rmml/my_mod.meowmy_mod
path/to/mod.mdmods/rmml/path/to/mod.mdpath/to/mod

Instead of a file, you can specify a folder. If you add my_mod to modlist.txt, RMML will look for the following files, in order. In all cases, the internal mod name will be the name of the folder (my_mod).

Source File Partial PathFull Path
my_mod.mdmods/rmml/my_mod/my_mod.md
index.mdmods/rmml/my_mod/index.md
index.csvmods/rmml/my_mod/index.csv

If you want RMMM to properly find your mod, it should either be a single file (.md, .ini, or .meow) or one of the three folder options. Additionally, if your mod doesn’t fit in a single file (because of external sprite assets, or split source files, or other external data) all of your mod’s files should be placed in your main folder.

Here’s an example layout, using index.csv to split code into multiple files.

mods/
├── meta_info.ini
├── modlist.txt
└── rmml/
   ├── my_mod/
   ├── index.csv
   ├── sprites/
   ├── ally.png
   └── enemy.png
   └── src/
      ├── controller_create.meow
      ├── instance_ally_draw.meow
      └── instance_enemy_draw.meow
   ├── rmml.meow
   └── rmmm.md
controller,create,src/controller_create.meow
instance,draw,src/instance_ally_draw.meow,my_mod_ally
instance,draw,src/instance_enemy_draw.meow,my_mod_enemy

Sub-Mods

By default, all modded code you write is associated with your mod’s internal name. .md and index.csv mods support registering Event code with other mods instead of the standard mod.

# controller
## room_start
```
instance_create_depth(0,0, 0, omod_instance)
instance_create_depth(0,0, 0, omod_instance, { mod_name: "my_mod_a" })
instance_create_depth(0,0, 0, omod_instance, { mod_name: "my_mod_b" })
```

# instance
## create
```
show_message("Default Object!")
```

# instance my_mod_a
## create
```
show_message("Object A!")
```

# instance my_mod_b
## create
```
show_message("Object B!")
```

The text after the Game Object header (# instance) is interpreted as a sub-mod, and supports all of the standard RMML features. Internally, sub-mods are their own mod, featuring their own mod name (ie my_mod_a and my_mod_b). This means you can set mod_name to that mod’s name and run its code, which lets you emulate creating new Game Objects.

Note that these sub-mods have no relation to the file they’re in. Using global.rmml.unload on my_mod won’t unload my_mod_a or my_mod_b. While you can specify sub-mod controllers, RMML won’t make a controller with that name automatically. I also recommend adding a common prefix to your mods, to prevent overriding someone else’s mod (unless this is desired).

Publishing to the Database

The rm-mod-database, which RMMM uses to store mods, isn’t just for me. If you have a mod you want added, you can either submit a Pull Request updating manifest.json or contact me (Harlem512) on Discord in the # modding-discusson or DMs and I’ll do the hard part. I do have a few submission requirements, however.

  1. Host on a Permanent Link. Because I don’t want to distribute malware, all links in manifest.json must be permanent (enough) or managed by me. I will basically only accept raw Git-based links (Github, Gitlab, BitBucket, etc) pointing to a blob, since these cannot be changed unless the site itself goes down. Additionally, it must be a raw link; clicking it should immediately download the file without user input. If you need help, I can store your mod in the repo directly (crediting you, of course).
  2. Include a proper name/description/author/version. manifest.json stores what your mod is named and its description. These fields should be filled out, and should describe your mod. Descriptions also support Scribble formatting, except for color.
  3. Use the mod list standards. Your entire mod must either be a single .md or .ini file, or a folder containing one of the folder-based options. All file references must accept that the mod will be placed in the mods/rmml folder. If you need help converting a .ini mod or packing everything into a single directory, I can work with that.
  4. Don’t cause permanent damage. Your mod should be able to be uninstalled without preventing Rusted Moss or another mod from working (unless it’s a dependency). The only exception is save files: save files changed while your mod is installed are free game, but your impact should be minimized. If you need to clean up your resources, you can use an uninstall script to run code when your mod is uninstalled.
  5. Have common human decency. Don’t make anything offensive, please. I am One Person doing this in their free time. If something is delayed or broken, please don’t lash out at me or the official devs.

Uninstall Script

If you’re looking to publish to the RMMM and are messing with the file system, you might want to clean up your mod’s files. Most mods can put all of their files in their mod’s folder, but you might need to mess with Rusted Moss’s files more directly. In these cases, you can use an uninstall script, which runs after your mod is uninstalled.

The code inside a file named uninstall.meow in your mod’s folder will be executed when your mod is uninstalled.

mods/rmml/my_mod/
├── data.json
├── my_mod.md
└── uninstall.meow

This feature doesn’t come with all RMMM installations, so if you’re relying on this behavior (such as to replace Fern_Custom), you’ll want to check the current RMMM version using >.

if global.rmmm_version > 1 {
  -- behavior exists, continue
}