Skip to content

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?) and server.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:

server.clientScripts.useClientFile("scripts/payphone.js");

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.