Example Mod

What

For more context, I recommend reading the Quickstart article first. We’re going to take a more in-depth look at sniper_bnuy.ini, Rusted Moss’s example mod.

sniper_bnuy.ini

[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; } }"

This a mod file, and contains the Catspeak code that runs. Let’s break it down.

Object List

[object_list]
controller = enabled
instance = enabled

This section tells Rusted Moss what Game Objects to run code for. In this case, controller and instance Game Objects are enabled.

Object Events

[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; }"

This is where we define the Catspeak code that runs on each Event. The header ([controller_events]) tells us what Game Object to run code for, and the key (room_start) tells us the Event. Inside the " quotes, as the value for room_start, is our Catspeak code. It’s not very readable, so I’ll add some line breaks for now and friendly comments.

i = 0
-- For each `oenemy_flame_sniper` in the current room ...
while ( i < instance_number(oenemy_flame_sniper) ) {
  -- ... find that Instance ...
  c = instance_find(oenemy_flame_sniper, i);
  -- ... and make a new `omod_instance` Instance at its position.
  instance_create_depth(c.x, c.y, 30, omod_instance);
  i += 1;
}

Pretty simple, we’re making an omod_instance Instance for each oenemy_flame_sniper in the Room, when we first enter a Room. There’s a few other notes for this code.

  1. Line Breaks: Because of boring technical reasons, we cannot add line breaks to our Catspeak source code. There are ways around it, but for now we’ll need to remove all of the line breaks. For clarity, I’ll be including line breaks in code examples.
  2. Gamemaker Functions: instance_number, instance_find, and instance_create_depth are all Gamemaker functions. Rusted Moss conveniently exposes every Gamemaker function for us, which is cool. However, because of boring technical reasons, some of these functions just don’t work.
  3. oenemy_flame_sniper: This is one of Rusted Moss’s Game Objects, specifically the enemies found in certain Living Quarters rooms. Similar to Gamemaker functions, Rusted Moss exposes every Game Object as well. Undertale Mod Tool lists every Game Object for our convenience.
  4. room_start: This Event runs when we first enter a Room. A single omod_controller Instance is created for us when the game starts, so we don’t need to do anything else for this event to run.
  5. omod_instance: This is a special Game Object, since we get to write code for it! Let’s see what it’s code looks like:

omod_instance Code

[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; } }"

Since the game makes an omod_controller for us, we don’t need to create a controller Instance. However, instance Game Objects must be created by our code (in this example, the create event creates our Instances). There are also three events this time, so let’s break it down.

-- Create event, add an Instance variable for the closest `oenemy_flame_sniper`
self.parent = instance_nearest(self.x, self.y, oenemy_flame_sniper);
-- Draw event, draw bunny ears on the head of each "parent" Instance
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 event, if the parent is "running", make it "jump"
if ( instance_exists(self.parent) ) {
  if ( self.parent.legs == smaya_legs_run ) {
    self.parent.vsp = self.parent.vsp - 4;
  }
}

Each omod_instance Instance is responsible for managing a single oenemy_flame_sniper Instance, drawing its ears and jumping. There are a few things to note here.

  1. self.parent: This is an Instance variable on self, which is the currently scoped Instance. In most cases, this will be the moddable Instance you’e writing code for, however you can use the with statement to change the current scope.
  2. legs and vsp: These are custom Instance variables on oenemy_flame_sniper Instances. To find out how what they do, you’ll want to investigate the source code using Undertale Mod Tool.
  3. spuck_bunny_ears: This is a Sprite, all of which are exposed for modding. Because of boring technical reasons, our ability to modify Sprites is slightly limited, but there are hundreds of existing Sprites we can use.
  4. create, draw, and step Events: Unlike controller, instance Game Object need to be created first (since there is no Instance for the Event to run on). create runs immediately after creation (halting execution). draw and step both happen each frame, with draw triggering after step.