Mods and modules
A mod is a folder of Luau files and the models, sounds and images they name. The server runs it. Every player’s game runs its client scripts, which it downloads from the server as it joins.
The game’s own modes are a mod too: the stock mod. Every mod sits on top of it, so yours holds only what differs.
A mod’s folder
Section titled “A mod’s folder”content/mods/mymod/ main.server.luau runs first modules/ killstreak/ killstreak.server.luau the server's half of a feature killstreak.client.luau what every player's game runs of it gametypes/ koth/ koth.server.luau a game mode maps/ yard/ yard.server.luau code for one map, in any mode yard.client.luau what every player's game runs on that map weapons/ ak47.weapon.luau a weapon's numbersA server runs it with server --mod content/mods/mymod, as running a server says.
--mod also takes a single file, mymod.server.luau. A mod of one file has the stock mod’s models, sounds and images, but none of its code. Its weapons go in a weapons/ folder beside it.
A file’s name says where it runs:
| Name ends in | Runs on |
|---|---|
.server.luau |
the server |
.client.luau |
every player’s game |
.shared.luau |
both: code the two sides share, such as a jump pad’s push |
.weapon.luau |
both: a weapon’s numbers, as a table |
Any other .luau file fails the load, naming it. So does a misspelt side, such as ranks.sever.luau.
The server never runs a client file. A player’s game never sees a server file. So keep secrets, prices and rules on the server: anything a client sends, the server checks.
Modules
Section titled “Modules”A module is one feature in a folder of modules/: its server file, its client file, and the files and images it uses. Adding a module touches nothing else. Only the mod’s root has modules/.
main.server.luau runs first: your mod’s, or else the stock mod’s. It declares and listens as a module does, and its handlers run before the modules’. Modules load in the order of their names, after it. Then the map’s code loads, then the game mode’s last.
Each module has its own globals, and reads through to those of main.server.luau. Modules share only what they declare: their state and their events. A helper file one module requires, such as modules/teams/teams_lib.server.luau, is shared by every module that requires it. It runs once a load, with the globals of main.server.luau.
A game mode is a folder of gametypes/ with its <name>.server.luau, and runs only while it is played. A mod with no gametypes/, its own or the stock mod’s, plays any game mode its map lists. A module, a game mode and a map with code never share a name: the load fails.
Layers
Section titled “Layers”A mod has three layers: its root, the game mode being played (gametypes/<name>/) and the map being played (maps/<name>/). A file in a closer layer wins: the map’s copy, then the game mode’s, then the root’s. In each layer your mod’s copy wins over stock’s.
So weapons/ak47.weapon.luau in your mod changes stock’s AK-47 everywhere, and the same file in maps/yard/ changes it on yard alone. The server prints each file a mod replaces as a match starts. A model’s or an effect’s textures are found through the layers too, so a map may replace the texture of a stock model.
Files and paths
Section titled “Files and paths”A mod names each file by its path, without the extension. The call says the kind: p:give("weapons/awp") names a weapon, play_sound("sounds/horn") a sound.
- A path is lowercase letters, digits and
_, between slashes. - Each part is 64 bytes at most, the whole path 255. No part is a name Windows keeps for a device, such as
conorcom1. - A path is relative to its layer, never to the file that names it.
- Stock’s own sounds, models and images have no folder:
ak47_fire,zombie_punch,kill_skull.
Every file a mod names is checked when it is first named. A name of no file fails at that call. A mod names 4096 files of each kind at most. Name the files a module uses at its top with preload, so players load them as they join rather than during play.
A material, which a shape is drawn with, is a file <name>.material.json with its images beside it, as stock’s wood.material.json:
{ "color": "wood_color.jpg", "normal": "wood_normal.jpg", "roughness": 0.8 }color and normal are its colour image and normal map, each optional. tint multiplies the colour, as linear [r, g, b, a], white if left out. roughness is 1 and metallic 0 if left out. Stock’s wood is there for any mod’s shapes.
How often it runs
Section titled “How often it runs”The server runs the mod 20 times a second. Every handler, timer and command runs between two of those ticks. A call that takes over 50 ms stops the mod until the next match.
A mod runs in a sandbox:
- It reads no files but its own, and starts no programs.
- It reaches the network only through its calls: HTTP, SQL and WebSockets.
- Luau’s libraries are read-only.
math.randomis seeded from the clock. - It has 64 MB of memory.
- A call gives 4096 orders at most.
- 1024 NPCs, models, looping sounds and looping effects are alive at once at most.
Each error is printed once, cut to 512 bytes, not every tick.
A player’s game gives its client scripts 32 MB in all, and 4 ms of each frame, 2 ms each. A script that runs over its time is stopped, and called no more until its file changes. Any other error ends that call alone.
While you work
Section titled “While you work”Start the server with --reload. It checks the mod every second. An edit takes effect at once, and the match goes on. If you changed what a module declares, such as a new player state, the match starts again instead, and the server says why. So does a client script added or removed.
- A reload in place keeps the mod’s globals, so keep a table as
hits = hits or {}. A function you deleted stays defined until the next match. - An edit that fails to load changes nothing: the mod as it was runs on.
- A mod stopped for running over 50 ms runs again at your next edit.
- Weapon files, client scripts, models and sounds reach the players as they change, without reloading the mod.
The simple form
Section titled “The simple form”A mod of one file, or main.server.luau, may also define two functions:
on_tick(world)runs every tick.world.timeis the mod’s clock.world.playerslists every player as a table, then every NPC.on_event(kind, ...)hears the engine’s events by name:"joined","leaving","fired","hit","died","chat","fell", and"command"for a command declared without its handler.
on_event runs before the modules hear an event. Returning false keeps it from them; returning a table puts its fields over the event’s. Modules hear the same events with on, which is what the rest of this site uses.