Skip to content

Source Structure

Ruin0x11 edited this page Apr 7, 2020 · 2 revisions

This page is a personal brain-dump of the most important files in the source, to assist in getting familiar with how the system works. Essentially I tried to list out the files I end up visiting the most frequently when doing development work.

Everything listed below is contained within the top level src/ folder.

Internal code

boot.lua: This file contains the very first code that is run, before the LÖVE window is created. This file hooks the global require function to make it compatible with hotloading. It is also where the restricted set of globals allowed to be used everywhere is set up.

main.lua: Main LÖVE entry point; handles things like running the draw/update coroutines, polling the debug server for input and pausing execution if an error is caught.

repl.lua: Code run to start the game in headless mode.

repl_startup.lua: This file is executed in the REPL's own environment when it is first created. Any globals created here will be local to the REPL's scope.

api/: The public API provided with the base game. Mods are able to require any file in this folder.

ext/: Extensions to the Lua standard library tables. For example, ext/table.lua adds functions to the table library.

game/field_layer.lua: The UI layer which draws the main tilemap.

game/field_logic.lua: The main game loop when a map is being displayed.

internal/: Code used internally. Not accessable within mods.

thirdparty/: Third-party libraries. Currently accessible from within mods, but this should probably be changed.

mod/: Mod folders. Each folder in this directory contains a separate mod, with the name of the folder being the mod's ID. See the modding tutorial for more details.

internal/config.lua: The game config. This currently follows no schema, but in the future it should be coded to support types and help text.

internal/data.lua: The data table, where all game data definitions are stored.

internal/global/: Contains global data and singletons separated into their own code chunks.

internal/layer/: The code for drawing tiles/objects/shadows on the map.

internal/i18n/: The code for the localization system.

internal/env.lua: Handles code hotloading and setting up the environment inside mods.

Data definitions

internal/data/events.lua: Defines the base set of events built in to the game (the ones prefixed with base.).

mod/elona_sys/data/event.lua/mod/elona/data/event.lua: More event definitions.

internal/data/schemas.lua: Defines the built-in data types and documentation for them.

mod/elona/data/: Contains the data definitions for Elona. chara.lua contains character data, item.lua contains items, and so on.

mod/elona/data/ai.lua: Contains a port of 1.22's AI to be somewhat more modular.

mod/elona_sys/events.lua/mod/elona/events.lua: These files implement a large amount of the game logic implemented with event callbacks. They should be better organized later.

mod/elona_sys/map_loader/: A map generator which loads maps from 1.22 and converts them to the format OpenNefia understands.

mod/elona/dungeon_template/: A map generator which generates random dungeons based on 1.22's algorithms, and allows for adding new algorithms.

Important APIs

api/Chara.lua/api/Item.lua/api/Feat.lua: Functions for modifying most of the different kinds of game objects.

api/Map.lua: Functions for modifying maps.

api/Event.lua: Interface to the event system. Used substantially wherever game logic is implemented.

api/Rand.lua: Randomness.

api/chara/IChara.lua/api/item/IItem.lua/api/feat/IFeat.lua: Interfaces containing the methods invokable on game objects with colon syntax (:method()). These are associated with the data types in internal/data/schemas.lua - objects backed by a definition of base.chara receive the IChara interface, and so on.

api/Draw.lua: Wrapper around LÖVE's graphics drawing API.

api/InstancedMap.lua: The class for instantiated maps.

api/Gui.lua: Functions for writing to the message window, updating the screen, binding keys to the main game screen, and playing sounds/music.

api/I18N.lua: Text localization.

api/Log.lua: Logging API.

api/gui/: Contains classes for the in-game UI.

api/gui/hud/: Contains the individual parts of the HUD drawn over the map.

api/gui/menu/ReplLayer.lua: The code for the REPL, which is accessed by pressing `.

The most annoying parts of the code

  1. Figuring out where everything goes. If it has something to do with game behavior it usually goes in mod/elona/events.lua but sometimes mod/elona_sys/events.lua if the code has no dependencies on anything in elona. Remembering this is hard.

  2. Remembering where everything is. Global search (rg) is useful but it feels like a band-aid at times. The documentation feature is supposed to help this by providing line numbers, but LuaJIT's line numbering is spotty if the code is inside a massive call to data:add_multi or something. We might have to fall back to source parsing to fill in the gap, which is rather heavyweight.

  3. Having random dependencies on mod code inside the base API. The problem is these dependencies are unforeseen (like calculating maximum HP or something based on a stat that's defined in elona) and can only really be solved by moving the code into an event handler, which exacerbates point #1.

  4. Anything having to do with map_template or dungeon_template. These are basically a rewrite of Elona's map/dungeon generation code to be more generic by using an interface to map_generator and allowing mods to insert a custom generation function. The generators will handle things like connecting the stairs between levels, refreshing the level at intervals (farms, fruit trees, etc.) and generating dungeon traps. In practice anything that generates a map is going to go through map_template due to the multitude of required variables that must be set for new maps (NOTE: is this true? we define fallbacks already in InstancedMap.lua). It might be easier to declare map_template as the "blessed" way of creating maps to avoid all the confusion.

  5. Determining what to keep as a public API versus defining locally. Systems like Emacs just throw everything defined with defun or similar into a massive global namespace. This is useful for discoverability but awful for backwards compatibility if anyone can just rely on arbitrary functions intended to be internal. The way to fix broken things in Emacs specifically is to copy the code from the offending package and stick it in the user's init file, then rewrite it to be bug-free. On the other hand making API exposure opt-in means people would be less inclined to publish potentially useful functions, meaning other mods will have to copy the code themselves. This is probably a social rather than a technical issue, if it even is an issue at all. And since Lua has the local keyword there isn't really anything that can be done about it anyway. Plus, if things break it is at least possible to dig into the source and patch things manually, since nothing is compiled.

  6. Things not hotloading properly when they should. This seems to be less of an issue than it used to be but still crops up occasionally.

  7. Things not capable of being hotloaded due to local state not being preserved. This is a sad fact of life.

  8. An annoying lack of documentation. The in-game toolset is more or less there but there other things I end up focusing on, and it breaks all the time. Also, since things are changing so rapidly, insisting on writing documentation which will end up going out of date shortly after becomes an unnecessary timesink.

  9. The documentation system is based on ldoc, which has a dependency on the entirety of penlight. I wish this weren't so.