Skip to main content
This guide builds a trick race: every player’s landed tricks add to their score, and the first to 20 000 points wins. The players’ games report their tricks, and one copy of the plugin, the server copy, keeps the scores and decides who wins. It needs the events.tricks, overlay, session.read, net.session, server and console permissions.

How a game mode is split

A plugin whose manifest says "side": "both" runs twice in a session:
  • The player’s copy runs on every player’s game. reskate.side is "client". It sees the local skater, draws, and sends what happened to the server copy.
  • The server copy runs on the session’s authority. reskate.side is "server". It hears the players’ copies, keeps the state, and answers everyone. Only it may call reskate.server.
Where the server copy runs depends on the session:
  • A lobby: on the lobby’s host, beside the host’s own player copy. The server copies start when the game starts hosting a lobby and stop when it stops. They draw nothing and show no panels. Each has its own id, <folder>@server (reskate.id says so), for its log lines, its storage, its console commands and anything it holds in the game, so it never shares state with the player copy: a server copy stopping leaves the player copy as it was.
  • A dedicated server: on ReSkateServer.exe, from a Plugins folder beside it (see On a dedicated server). The server has no world: there is no skater, camera or HUD there, so game functions say they are unavailable. What a server copy does there is messages, the session’s players, reskate.server and its storage.
A manifest with "side": "client" (the default) runs only as the player’s copy, and "side": "server" only as the server copy.
Nothing a player’s copy says is trusted by the server copy just because it came from the same plugin. Players can change their copy. Check every message in the server copy before you act on it.

1. The package

Make a mod folder in the game’s Mods folder:
reskate-plugin.json asks for both sides and every permission either copy uses:
manifest.json gives the mod its name. Messages between copies go under this name, so every game and the server must have the same one. Without a manifest.json (or without a name in it), the folder’s name is used instead.
See the manifest for every field.

2. Pick the copy

Both copies run the same entry file. Read reskate.side and load the code for this copy:
require("server") loads plugin/server.luau or plugin/server.lua: modules are looked up beside the entry. See require.

3. The player’s copy

The player’s copy does three things: it says hello when it joins, reports each landed trick, and shows the scores the server copy sends back.

Talk to the server copy

reskate.net.send("server", message) sends a message to the server copy only. Other players’ copies never see it. A message is a string (here JSON from reskate.json) of at most 4082 bytes, and a plugin sends at most 16 KB a second. send returns false when there is no session.
The player_joined event names every player that joins, the local player too (player.me). When the local player joins a session, send a hello so the server copy answers with the scores so far. A plugin loaded while the session is already on says hello from its load handler instead.

Report tricks

The trick_landed event (events.tricks) gives the trick’s points. See events for its other fields.

Hear the server copy

The message handler gets who sent the message and its text. from.server is true only for what the server copy said: a player’s copy cannot send a message that arrives with it set. Ignore score messages from anyone else.
reskate.json.decode raises an error on text that is not JSON. Wrap it in pcall whenever the text came from another game.

Show the scores

The draw handler (overlay) draws the table in the top left each tick. The drawing guide covers it in depth.

4. The server copy

The server copy keeps one score for each player and sends the standings to everyone whenever they change.

Keep the state

A player is a table with id (the SteamID64 as a string), name, me, host and server. See session.

Answer everyone, or one player

reskate.net.send("all", message) from the server copy reaches every player’s copy, and each of them sees from.server set. To answer one player, pass the player (or their id) instead of "all". The server copy cannot send to "server": it raises an error, since it is the server.

Check what the players say

The server copy hears messages sent to "server" and those sent to "all". Check the kind and the values before using them. A score no trick can make is a player who changed their copy: reskate.server.kick drops them from the session.
reskate.server.say writes a line in everyone’s chat under the plugin’s name, at most one a second. reskate.server.kick, say, tell and move all return ok, why. They need the server permission and raise an error in a player’s copy, and a plugin makes at most 20 server calls a second. See server.
reskate.server.kick removes a real player from the session. Kick only for something no honest copy can send.

Follow who leaves

The server copy hears player_joined and player_left too (session.read). Drop a player’s score when they leave:

A command for whoever runs the server

A server copy can add console commands like any plugin (console). On a dedicated server they are typed in the server’s console, and on a lobby’s host in the game’s console.

The whole plugin

Try it in a lobby

Host a lobby with the mod in your Mods folder: your game runs both copies. The game’s log says Plugins: hosting a lobby, so server copies run here. when the server copies start. Each player who joins needs the mod too. On the lobby’s host, your own player copy’s messages to "server" stay on your game and reach the server copy there, and what the server copy sends to "all" reaches your player copy as well as everyone else’s. The server copy’s log lines read [Trick_Race@server] ..., and the player copy’s [Trick_Race] ....
The game’s plugin console commands reach the server copies too while your game hosts. plugin list shows them as <folder>@server, and plugin reload Trick_Race@server, plugin off Trick_Race@server and plugin on Trick_Race@server act on the server copy; the plain folder name acts on your player copy. The server copies also reload with their files on their own. A server copy’s console command runs only when no player copy has a command of that name.

On a dedicated server

A dedicated server runs the server copies of the plugins in a Plugins folder beside ReSkateServer.exe (beside its Mods folder). Each plugin is a folder there with its reskate-plugin.json, and its "side" must be "server" or "both":
Copy the whole mod folder, manifest.json included, so the server’s messages go under the same name as the players’ copies. The server checks the folder every 2 s: a new plugin loads, a changed one reloads, and a removed one unloads. Like the game, it refuses a package that holds a program or a script Windows runs. Server copies keep their storage in data/plugins beside the server’s config file. On a dedicated server, reskate.session.active() and reskate.session.is_dedicated() are true, and reskate.session.players() lists the players on the server. None of them is me: the server is not a player. Lines the plugins log and print go to the server’s log.

Console commands on the server

Type these in the server’s console: A plugin’s own command, such as trickrace reset, runs from the server’s console only for a line the server does not know, so a plugin can never hide one of the server’s commands. The server answers Ran by a plugin. when one ran.

Where to go next