Client, server, or both¶
Almost everything a gamemode does to the world has two ways in: from the
server (server.*, player.*), and from client scripts (sagl.*), which
run on every player's game. Sounds, effects, objects, labels, pickups,
interactions, animations, the HUD and UI: both. Where each part of a
feature runs is yours to choose.
- All on the server. The simplest to write: one program, everything in one place, and the server knows everything that happens. The cost is traffic: everything a player sees, the server sends them.
- All on the client. A gag, decoration, anything nobody needs to trust:
a client script does it on each player's game, and the server only hands
the script out. Each game can see the world (
sagl.world) to decide for itself, e.g. that someone else is already at a payphone. - Split. The client does what's local and frequent (sounds, animations,
prompts), and asks the server (
sagl.emit) for what must be shared or trusted. The server checks the request and answers. It has helpers for that:player.isNear(are they really there?) andserver.claims(who got there first?).
What players own in their own game (where they are, what they see and hear) is theirs to change in a client script. What the server owns (money, weapons, health, vehicles, the server's peds and objects) only the server changes: a client script that wants them changed asks.
The payphone, both ways¶
The freeroam example's payphone rings, can be answered, and plays a caller.
examples/freeroam/payphone-server.ts does it from the server: the server
rings it for everyone (a sound message per ring), hears the answer through
an interaction, and plays the animations and the caller on that player.
examples/freeroam/client-files/scripts/payphone.js does it all on each
player's game, and is what freeroam uses: every game rings it at the same
moments (by the clock), shows the prompt, and answers it with no server
involved; a game that sees another player at the phone stops ringing. The
server's part is one line:
If answering it gave a reward, the script would ask
(sagl.emit("payphone", { answer: true })), and the server would check
player.isNear(PHONE, 2) and server.claims.take("payphone", player)
before paying.