Welcome traveler! This is the documentation for the Elona foobar mod API.
Before proceeding, please note that the API will almost certainly undergo substantial changes before it is stabilized. If you write anything with the API, expect that it will break sometime in the future, until all the serious design/implementation issues and bugs have been worked out.
You can run a script at startup by adding a parameter to your config.json. Copy the life.lua example into your user/script folder, and add this line to your config.json:
"startup_script": "life.lua"
Starting the game will place you in a script testing map, isolated from your other saves. There you can see the script in action. The life.lua script will run Conway's Game of Life by setting tiles in the map. Let's break down how it works.
local Map = ELONA.require("core.Map")
local Enums = ELONA.require("core.Enums")
local Event = ELONA.require("core.Event")
local Rand = ELONA.require("core.Rand")
At the top, we have some defines. All modules related to the core API can be obtained by using require.
local function create_life_grid()
local grid = {}
for i = 1, Map.width() do
grid[i] = {}
for j = 1, Map.height() do
grid[i][j] = Rand.rnd(2)
end
end
return grid
end
This function will return a new grid for our life simulation. We will use this for setting up the life grid inside our global storage table if it doesn't already exist.
local function evolve(cell)
-- ...
end
This function runs the life simulation. We won't go into the details, but if you're interested you can see this article.
local function run_life()
if not Map.is_overworld() then
local grid = Store.map.grid
if grid == nil then
Store.map.grid = create_life_grid()
grid = Store.map.grid
end
for y = 1, Map.width() do
for x = 1, Map.height() do
local tile
if Store.map.grid[x][y] == 1 and not Map.is_blocked(x, y) then
tile = Map.generate_tile("Wall")
else
tile = Map.generate_tile("Room")
end
Map.set_tile(x, y, tile)
Map.set_tile_memory(x, y, tile)
end
end
Store.map.grid = evolve(grid)
end
end
Here is the main part of the script. This function updates the game map based on the data we stored. Let's go into detail what it does.
if not Map.is_overworld() then
-- ...
end
First we need to check if we're traveling in the overworld, where setting tiles on the map wouldn't make sense.
local grid = Store.map.grid
if grid == nil then
Store.map.grid = create_life_grid()
grid = Store.map.grid
end
The table Store is for keeping any data we need inside our script.
- Store.map holds data that is only relevant to the current map. When the map is changed, it is cleared.
- Store.global holds data that persists throughout the entire game session.
In this case, we store the state of our life grid in .map. This state will be preserved between subsequent calls torun_life` and will be cleaned up when the map is exited. We also check if the store doesn't have our grid and create it if so.
for y = 1, Map.width() do
for x = 1, Map.height() do
-- ...
end
end
Next we iterate over every x-y pair in the map. Since there is only ever one map loaded at a time, we can call Map.width and Map.height without passing in anything.
local tile
if Store.map.grid[x][y] == 1 and not Map.is_blocked(x, y) then
tile = Map.generate_tile(Enums.TileKind.Wall)
else
tile = Map.generate_tile(Enums.TileKind.Room)
end
Map.set_tile(x, y, tile)
Map.set_memory(x, y, tile)
Here is where we make use of the Map module to modify the map. You can read the documentation for Map.is_blocked, Map.generate_tile and Map.set_tile elsewhere in the docs. Essentially, if the simulation reports a cell with value 1, we set that square to a wall tile, else to a floor tile. We also make sure to set the player's memory of that tile so they can see it even if it's out of field of view.
We also use the enum type TileKind here. Some functions take enums to denote one of several different states an object can be in, like the curse state of an object (Blessed, None, Cursed, or Doomed). These will typically be found inside the Enum module. You also can pass the name of the enum itself without using the Enums table (like Map.generate_tile("Wall")), to avoid having to require Enums all the time.
Store.map.grid = evolve(grid)
Lastly, we call our logic function to update the state of our life grid.
Event.register("core.map_initialized", run_life)
Event.register("core.all_turns_finished", run_life)
Finally, this is the most critical part of the script: hooking it up so it can be ran in response to in-game events. The Event.register function will set up a function you provide to be called when an in-game event is fired. In this case, we want to run our function run_life when the map is first loaded and every time all characters in the map have finished moving. There are various kinds of events you can listen for, and they are listed in the documentation for core.event
That's all for this tutorial. Go forth and find game-breaking bugs! (and preferably report them to us)