Open Road MP

Your first gamemode

Create a gamemode, start it on your server and set up your editor.

You need a running server (see Running a server) and Node.js 22.18 or newer.

1. Create the folder

A gamemode is a folder in gamemodes/ with an index.ts. The easiest start is a copy of the example:

gamemodes/
  freeroam/        the example that comes with the server
  mymode/          your copy
    index.ts       entry point - the server starts this file
    tsconfig.json  editor settings (type checking)
    package.json   only for type checking

Or start empty with just an index.ts:

gamemodes/mymode/index.ts
mp.log("mymode loaded")

// Where everyone starts - every gamemode needs this (step 3)
mp.events.on("gamemodeStarted", () => {
  mp.world.setSpawn({ city: "berlin", vehicle: "scania_r_2016" })
})

mp.events.on("playerJoin", (player) => {
  mp.chat.send(player, `Hello ${player.name}, welcome to ${mp.server.name}!`)
})

2. Turn it on

List it in server.cfg. Several gamemodes can run at the same time:

server.cfg
gamemodes = [mymode]
# or together with the example:
gamemodes = [freeroam, mymode]

Start the server. Its log shows that the gamemode was loaded:

[mymode] mymode loaded

3. Set the standard spawn

Every session on an Open Road MP server is a fresh career: the player's single-player save is not loaded, so they arrive without a truck, without a garage and without money. Your gamemode decides where they start and what they drive - set a standard spawn with mp.world.setSpawn. Without one, players start wherever the game puts them, which may be off the road.

gamemodes/mymode/index.ts
mp.events.on("gamemodeStarted", () => {
  mp.world.setSpawn({ city: "berlin", vehicle: "scania_r_2016" })
})

mp.events.on("playerJoin", (player) => {
  mp.players.setMoney(player, 5000)   // a fresh career has no money
})

What the player sees:

  1. They join and the world loads. The game shows its desktop (and, the first time, asks for a home city).
  2. They press Drive and get the vehicle of the spawn.
  3. Open Road MP puts them at the spawn, ready to go.

Set it in gamemodeStarted: by then mp.world knows all cities and depots, and the spawn is checked right away (an unknown city throws an error into your log). The spawn takes one of three forms, each with an optional vehicle:

SpawnWhere
{ city: "berlin" }a random depot of the city - players start at different depots
{ company: "tradeaux", city: "dusseldorf" }that depot, everyone at the same spot
{ position: { x, y, z }, heading: 90 }an exact point on the map, heading in degrees (0 = north)
  • City and company ids come from the game data: mp.world.cities() and mp.world.companies({ city }) list them. The live map shows positions.
  • vehicle is the truck players get with Drive: a name like players.setVehicle takes ("scania_r_2016", "daf_2021" ...). Without it, the server's fallback_vehicle from server.cfg is used (mp.players.setFallbackVehicle changes it per player).
  • When it applies: on playerReady, before your own playerReady handlers. If the player has no truck yet, Open Road MP keeps the spot and moves them there as soon as they have one.
  • Change it any time with another mp.world.setSpawn(...); it is used for everyone who joins from then on. mp.world.setSpawn(null) removes it.
  • Different spawns per player (e.g. by team): leave the standard spawn out and teleport in your own playerReady handler with mp.players.teleportToCity / teleportToCompany / teleport - it waits for the truck the same way.

The example gamemode freeroam sets its spawn from spawnCity and spawnTruck in its settings.ts.

4. Change and reload

Changes to your code take effect after a restart of the gamemode - the server keeps running, players stay connected. Type in the server console:

gamemode restart mymode

Before every start the server type-checks the gamemode against the mp API (TypeScript, strict). With errors it does not start it and lists them in the log:

[mymode] index.ts(12,14): TS2551: Property 'setMony' does not exist on type 'PlayersApi'. Did you mean 'setMoney'?
Gamemode mymode not started: 1 type check error. Fix, then run "gamemode restart mymode".

So a typo shows up at once, not when a player first runs into it. Fix it and restart.

5. Editor support

Open the gamemode folder in VS Code (or any editor with TypeScript support). With the tsconfig.json from the example, the editor knows the whole mp API: it completes names, shows the descriptions and marks mistakes.

It is the same check the server runs. To check all files at once from the command line:

cd gamemodes/mymode
npm install
npm run check

TypeScript in Node.js

Node.js runs your .ts files directly by removing the types. That works for almost all TypeScript, with a few rules:

  • Import your own files with the extension: import { formatEuro } from "./util.ts".
  • Not allowed: enum, namespace with content and parameter properties in constructors (constructor(private x: number)). The tsconfig.json of the example (erasableSyntaxOnly) marks them.
  • Files are ES modules: import/export, top-level await works.
  • Everything Node.js offers is available: setTimeout/setInterval, fetch, node:fs ...
// A message every 15 minutes
setInterval(() => {
  mp.chat.broadcast("Drive safely - and take a break now and then.")
}, 15 * 60 * 1000)

Next: Events - reacting to what happens on the server.

On this page