Moddable Events

Moddable Events

Rusted Moss allows us to write Event code for 21 Events. We get almost everything we’d need, but there are some exceptions, notably higher-index Alarm and User Events. If you want more information about each Event, you can check out the official documentation. If you want technical details on how modded Events operate, here you go.

First are the twelve Events that run each frame, in the following order. The Modding Name is used in mod files ({MODDING_NAME} = ... or ## {MODDING_NAME}).

Modding NameRole
step_beginRuns before Alarm Events.
step
step_end
draw_beginFirst Event that allows drawing to the screen.
draw
draw_end
draw_gui_startFirst Event that uses screen coordinates instead of world coordinates.
draw_gui
draw_gui_endOnly Event that doesn’t have the biome shader. Perfect for GUI elements.

Next are 12 special Events, that only run under certain conditions.

Modding NameDescription
alarm_0, alarm_1, alarm_2Three Alarm events for indexes 0, 1, and 2.
animation_endRuns when an Instance’s Sprite animation ends.
cleanupRuns immediately after an Instance is removed from a Room (for any reason), after the destroy Event runs.
createRuns immediately for an Instance when an Instance is created.
destroyRuns when an Instance is destroyed, typically through instance_destroy(...)

These Events are also available, however have an other_ midfix for global.mod_map.

Modding NameDescription
room_startRuns when a Room is first entered. It happens after the create Event for Instances that are part of the Room.
room_endRuns when a Room is left, such as with room_goto. The current Event finishes, and then this Event is called.
user_0, user_1, user_2Three User Events for indexes 0, 1, and 2.

Alarm Events

Alarm Events run after a specified number of frames, set using alarm_set({INDEX}, {DELAY}). You can use alarm_get({INDEX}) to read an Alarm’s current delay. There are twelve Alarm Events, however we only get to mod three of them. The rest can be detected using clever logic in the step_begin Event.

## step_begin
```
with oplayer {
  -- alarm is zero when it should run
  if alarm_get(9) == 0 {
    show_message("Hello World")
  }
}
```

Since Alarms don’t decrement if there isn’t any Event code, we can’t use arbitrary Alarms, but we can emulate them using code.

## create
```
self.my_alarm = 5
```

## step_begin
```
self.my_alarm -= 1
if self.my_alarm == 0 {
  show_message("Hello World")
}
```

User Events

User Events must be called manually and run immediately.

with otile_switcher {
  event_perform(ev_other, ev_user_11)
}

In normal GameMaker, they’re useful for running code that can be easily modified by a Game Object’s children. Similar to Alarms, there are more User Events (16) than we have moddable access to (3). I am not aware of any way to get around this, which is a limitation.

Execution Order

For games, a lot of logic depends on earlier code within the same frame; simulation code typically needs to happen before rendering, so the graphics aren’t a frame behind Player inputs. Usually you want even more granularity; a bullet killing an enemy in a frame should destroy that enemy before it has a chance to attack in a later part of the frame.

For most use cases, the nine per-frame Events are good enough for ensuring the right code runs before or after other code. If you think you need more than 9, you probably want to rethink how your code is structured. However, modding doesn’t have this luxury.

A lot of the time, you need code to run immediately before or immediately after some Instance’s Event to modify behavior. Maybe you want to intercept draw calls using surface_set_target, or abuse object_index, or you need to re-create an Instance that was destroyed before the game crashes. For all of these cases, we must learn about the order GameMaker chooses Instances to run Events for.

Depth

There are two factors main that GameMaker uses: an Instance’s depth and when it was created. depth is a GameMaker-managed Instance variable used to manipulate draw order, and is just a number.

Before we continue, let’s play a game. Think about each Event, and try to guess how those two would affect each Event.

Order Table

Let’s start with four example objects, created in the following order and with varying depths:

let person = fun(depth, name) {
  instance_create_depth(0,0, depth, omod_instance).name = name
}

person( 0, "Alice")
person(10, "Bobby")
person(10, "Carol")
person( 0, "David")

We can combine this with a simple piece of code we can copy into each Event.

show_message(self.name)

Here’s the results. How’d you do? From what I understand, this execution order is extremely consistent to the point of total reliability. Here is more information about how to abuse this behavior.

EventOrder (left first)Formal
room_start and room_endAlice, David, Bobby, CarolLowest depth, then oldest
All step EventsAlice, Bobby, Carol, DavidOldest to youngest
All draw EventsCarol, Bobby, David, AliceHighest depth, then youngest
with statementDavid, Carol, Bobby, AliceYoungest to oldest

Persistent Objects

The ordering gets weird with persistent Instances.

For with statements, all Instances created in the current Room run (in youngest to oldest order), then all persistent Instances run in the draw Event order (highest depth, then youngest).

# controller

## room_start
```
let person = fun(depth, name) {
  let inst = instance_create_depth(0,0, depth, omod_instance)
  inst.name = name
  return inst
}

if !global.inited {
  global.inited = true
  person( 0, "Alice").persistent = true
  person(10, "Bobby").persistent = true
  person(10, "Carol").persistent = true
  person( 0, "David").persistent = true
}

person(  0, "Erica")
person( 10, "Frank")
person( 10, "Janet")
person(  0, "Harry")

with omod_instance {
  show_message(self.name)
}
```
RoomOrder (left first)Formal
InitialHarry, Janet, Frank, Erica,
David, Carol, Bobby, Alice
Youngest to oldest, ignoring persistent tag
SubsequentHarry, Janet, Frank, Erica
Carol, Bobby, David, Alice
Youngest to oldest non-persistent; then highest depth, then youngest for persistent

Step Events also exhibit similar behavior.

RoomOrder (left first)Formal
InitialAlice, Bobby, Carol, David,
Erica, Frank, Janet, Harry
Oldest to youngest, ignoring persistent tag
SubsequentAlice, David, Bobby, Carol,
Erica, Frank, Janet, Harry
Lowest depth, then oldest for persistent; then oldest to youngest for non-persistent

I am not sure how this pattern continues for other Events, or how Instances created by Rooms fit into this model. If you know, feel free to reach out.