Skip to content

Creating Your First Mod

Ruin0x11 edited this page Apr 7, 2020 · 1 revision

This page will walk you through how to create a new mod and some of the fundamental modding capabilities available for use.

This page will assume you're using VS Code as your editor.

Folder Structure

Mods are located under src/mod in the code repository. Each folder inside it is named after a mod's identifier and contains these files:

  • mod.lua: The mod's manifest. Contains its ID, version, and requirements.
  • init.lua: Optional. When the mod is loaded at game startup this file will be executed automatically.

Note that besides mod.lua and init.lua there is currently no other requirements enforced on how your mod is structured. However there are some conventions we strongly recommend following to encourage source code discoverability:

├── mod.lua                # mod manifest
├── init.lua               # startup script
├── api/                   # public API exposed by this mod
│   ├── PublicClass.lua   # UpperCamelCase
│   ├── IInterface.lua
├── data/                  # data definitions
│   ├── init.lua          # loads everything in data/
│   ├── chara.lua         # definitions for base.chara
│   ├── <...>
├── init/                  # code run by init.lua on startup
├── internal/              # code private to this mod
│   ├── private_class.lua # lowercase_underscore
├── graphic/               # tile/character/item/asset images
├── locale/                # translation files
│   ├── en/               # language of the translation
│   └── jp/
└── sound                  # sounds and music

Here are the details of mod.lua and init.lua.

mod.lua

The contents of mod.lua should look like this:

return {
  id = "mod_id",
  version = "0.1.0",
  dependencies = {
    base = ">= 0.1.0"
  }
}

mod.lua should return a table with the following fields:

id is the mod's identifier. Mod identifiers must be all lowercase and valid Lua identifiers. That is, it must start with either a letter or underscore and only contain letters, numbers, or underscores.

version is the mod's version. For now it should use the semver format, but the exact version format hasn't been decided on yet.

dependencies lists the mod's dependencies. It is a table where each key is a.mod identifier and each value is a.version specifier. The version specifier is currently unimplemented so this table is only used for running the init.lua script of each mod in the correct order. If you use content from another mod be sure to add its identifier here to prevent errors.

init.lua

This file is optional. Before the game is loaded this file will be executed at startup in an order dependent on each mod's dependencies - this file will only be executed once all dependent mods' init.lua have finished executing.

A simple example

Let's create a new mod from scratch. First start the game and reach the title screen, then press F3 to start the game in "quickstart" mode. This will bring you straight to a blank map.

![](/2020-03-06 02_55_02-OpenNefia.png)

Next, without closing the game, create a new folder under src/mod/ named example_mod. Create a file named mod.lua inside this folder and put the following inside it:

return {
  id = "example_mod",
  version = "0.1.0",
  dependencies = {
    elona = ">= 0.1.0"
  }
}

Here we add a dependency on elona so we can access its assets.

Next create a file named init.lua and put the following in it:

local Log = require ("api.Log")

Log.info("Hello from %s!", _MOD_NAME)

Next, hotload this file using your editor. If you're using VS Code, the palette command is named "OpenNefia: Hotload this file".

If everything goes well, this should be printed in the console and the in-game log:

![](/2020-03-05 02_16_56-OpenNefia2.png)

We just loaded a new mod at runtime, without any restarting needed. _MOD_NAME is a global provided in every mod's environment containing the identifier of the mod as a string.

As an aside, you can can do all the setup necessary to create a new mod next time by calling the palette command OpenNefia: Create new mod.

Next, let's define a new character type. The different fields required on each data type can be hard to remember all at once. To help with this, the game exposes some functionality in its debug server to generate new templates for you to fill out. (This is the same server which handles code hotloading.)

Let's generate this template this two ways, first to help you understand what can be accomplished using the in-game REPL, and then using the editor integration which makes this more convenient. First, open the REPL by pressing ` (grave) in the game window.

![](/2020-03-05 02_27_55-OpenNefia.png)

The data table exposes a method called make_template, which takes a string identifying a data type, such as base.chara, and an identifier for the _id field. it returns a string containing the Lua code used for adding a new entry of that type, with the required fields provided for you to modify. The idea is that if you run this code unmodified, you should get no errors and be able to immediately start using the definition in-game.

Try this out by calling it in the REPL. Like in mods, the data table is also automatically provided for you in the REPL's environment.

> data:make_template("base.chara")

![](/2020-03-05 13_14_40-OpenNefia2.png)

You should see some Lua code printed out on the console. This is the code you should paste into your mod's init.lua to add a new character. So how do you take this code from the REPL and put it in the file? As it turns out, there is a function provided by the Env API at api.Env, set_clipboard_text(), that handles copying text to the clipboard. Try this out:

> Env.set_clipboard_text("Scut!")

Verify this works by pasting the clipboard somewhere.

Notice that we did not have to first write Env = require ("api.Env") in the REPL first. This is because all the APIs provided with the base game are automatically required for you when the REPL first starts. On the other hand this does not apply to anything contained in mods, so you'll have to require them yourself before using them in the REPL. Don't fret, though: the VS Code extension has a command which will do exactly this for the currently opened file, called OpenNefia: Require this file in REPL, bound to Ctrl+Shift+E by default. Try it out:

![](/2020-03-06 02_32_27-Greenshot2.png)

Back to where we were before: Using Env.set_clipboard_text, we can copy the template created by data:make_template() with the REPL. Another useful feature in the REPL is the storage of the most recent results in the variables _1, _2, _3, and so on, with _1 being the most recently returned value. Thus, you can do the following:

> data:make_template("base.chara")
> Env.set_clipboard_text(_1)

This is the same as:

> Env.set_clipboard_text(data:make_template("base.chara"))

Now paste the template into your mod's init.lua. Change the _id field to something more descriptive, like my_chara. Note that the _id field must be a string holding a valid lowercase Lua identifier, so no uppercase letters or spaces. By now the definition should look like this:

data:add {
    _type = "base.chara",
    _id = "my_chara",
    level = 1,
    faction = "base.enemy",
    race = "elona.slime",
    class = "elona.predator",
    image = "elona.chip_race_slime",
    rarity = 0,
    coefficient = 0,
}

To save effort when creating new definitions, this system of inserting templates is provided as a feature of the debug server. You can have your editor automatically insert templates of a given type by running the palette command OpenNefia: Insert template, bound to Ctrl+Shift++T by default. It will query for a type and ID and insert the template generated by the game at the cursor. Let's try this by creating a new base.chip entry to set the character's image to.

![](/2020-03-05 17_13_17.png)

![](/2020-03-05 17_15_06.png)

The template will automatically put documentation inline by default. For this example, let's use a pre-made image. First create the folder mod/example_mod/graphic. Then, download the sprite below and place it in mod/example_mod/graphic/programming_putit.png.

Next, update the image field of the template and the character.

data:add {
  _type = "base.chip",
  _id = "programming_putit",
  image = "mod/example_mod/graphic/programming_putit.png"
}

data:add {
    _type = "base.chara",
    _id = "my_chara",
    level = 1,
    faction = "base.enemy",
    race = "elona.slime",
    class = "elona.predator",
    image = "example_mod.programming_putit",
    rarity = 0,
    coefficient = 0,
}

The character's image field should be example_mod.programming_putit, based on the _id of the template. Finally, hotload the file again, then create the character:

> p = Chara.player()
> Chara.create("example_mod.my_chara", p.x, p.y)

![](/2020-03-06 02_36_51-OpenNefia.png)

You should see your character blink into existence near the player.

The nice thing about this system is that you can create and modify data definitions at runtime to try new things without needing to restart the game. The hope is that when this becomes stable enough it will become possible to write up a new mod from scratch without any significant hitches requiring a restart.

The hotloading system is smart enough to detect when you're trying to add new data and will instead do an in-place update of the existing definition if it has the same _id field. This lets you change the fields of a definition and see the changes reflected when you reinstantiate the object.

Let's try this by changing the color of our character to green. Add a new field to the character's table (not the chip's) named color like so:

  color = {175, 255, 175},

Then, hotload the file and create another character of the same type. You can use the up and down arrows in the REPL to navigate the input history. If you type something in the REPL prompt before doing so, you can do a substring search against every history entry. Try this by typing Chara. and pressing the up arrow.

![](/2020-03-05 19_20_43-OpenNefia.png)

To quit out of this mode, press Escape. Using this feature, find the call to Chara.create you made earlier and press Enter.

![](/2020-03-05 19_30_41-OpenNefia.png)

As you can see, the new changes have been reflected. You might be wondering why the existing character we created earlier did not change color the moment we hotloaded init.lua. The reason is that all the values of the definition are copied by value into newly instantiated objects. The data definition is merely a prototype that is used as the basis for copying, instead of being shared across every object instance.

However, you can access the original definition used to create an object instance, using the field proto: (TODO: standardize terminology)

> Chara.player().proto

To assist with modding, it's recommended not to rely on this field except when an object is being instantiated, so that mods can override the prototype's value with values set on each individual object.

So how are we supposed to figure out which fields have any relevance to the definition we're adding, like color? The solution is to use help(). This is a function for getting a help string for things like data types, modules, and functions (TODO: implement this). Try this in the REPL.

> help("base.chara")

This lists the available fields for use in the prototype. Alternatively, if you use the palette command to insert templates with the editor, all available fields and their documentation will be inserted for you by default.

You can also view documentation like this using the editor extension. Call the palette action OpenNefia: Search documentation to see a list of all the available documentation.

For the curious, the actual implementation of help() is in api.Doc as Doc.help(). The help strings for each individual field can be found in internal/data/schemas.lua.

Next Steps

Proceed to The Data System.