Open Road MP

User interfaces

Menus, shops, forms and HUD panels - built in the gamemode, shown in the player's game.

A gamemode can show players its own windows: a menu, a shop, a form, a scoreboard. It builds them from building blocks (ui.*) and shows them with a UiView. The player's game draws them with the same components as the Open Road MP menu - they look like part of the game.

const welcome = new UiView({ title: "Welcome!" })
welcome.add(
  ui.text("Have a good trip on our server."),
  ui.button({ label: "Let's go", close: true }),
)

mp.events.on("playerJoin", (player) => welcome.show(player))
Preview - as it looks in the game

The server only sends a description of the window - no code of yours runs in the player's game. Clicks and inputs come back to your gamemode as function calls.

The previews on this page are real: the docs run each example with the gamemode host and draw what it shows with the game's own renderer. Click around - instead of your gamemode, the preview tells you what your handler would get.

Notices

For a short message there is no need for a window: mp.ui.notify shows a notice at the top of the HUD, in the style of the game's own messages.

mp.ui.notify(player, { title: "Checkpoint 3/10", text: "+30 s" })
mp.ui.notify(mp.players.all(), { title: "Server restart in 5 minutes", kind: "warning", seconds: 10 })
Field
titleat most 120 characters
textoptional, at most 300 characters
kind"info" (default), "warning" or "alert" - colour and icon
secondshow long it stays, default 5; 0 = until the player leaves the world

Windows and HUD panels

new UiView({ title: "Shop", size: "lg" })                     // a window (default)
new UiView({ mode: "hud", position: "top-right", title: "Race" }) // a panel over the game
  • A window appears in the middle of the screen. The player gets the mouse, and the game gets no input until the window is closed: with its X, with Escape, or with a button that has close: true. closable: false keeps the window open until you close it with hide().
  • A HUD panel sits at the edge of the screen and does not take the mouse - for scoreboards, timers, race positions.

Building blocks

KindBlocks
Layoutui.stack (one below the other), ui.row (side by side), ui.grid (columns), ui.card, ui.tabs, ui.accordion, ui.separator
Textui.heading, ui.text, ui.badge, ui.alert
Dataui.table, ui.progress, ui.spinner
Inputui.button, ui.input, ui.textarea, ui.select, ui.checkbox, ui.switch, ui.radio, ui.slider, ui.logoPicker

Containers take their children after the options. Text can be passed directly, and false, null or undefined are skipped - handy for conditions:

const onSale = true
const buy = (player: OpenRoadMP.Player) => mp.log(`${player.name} buys a Scania S`)

const offer = new UiView({ title: "Truck dealer", size: "sm" })
offer.add(
  ui.card({ title: "Scania S", footer: [ui.button({ label: "Buy", onClick: buy })] },
    "Our best seller",
    onSale && ui.badge("-20 %", { variant: "destructive" }),
  ),
)
Preview - as it looks in the game

All blocks with their options: API reference → ui and UiView.

A complete example: a shop

Tabs, a grid of cards, a table, inputs - put together the way a real shop would be:

const trucks = [
  { name: "Scania S", price: 148000, power: "770 hp" },
  { name: "Volvo FH16", price: 139000, power: "750 hp" },
  { name: "DAF XG+", price: 131000, power: "530 hp" },
  { name: "MAN TGX", price: 127000, power: "640 hp" },
]

function buyTruck(player: OpenRoadMP.Player, truck: string) {
  mp.chat.send(player, `You bought a ${truck}.`)
}

const shop = new UiView({ title: "Truck shop", description: "Everything for the road", size: "lg" })
shop.add(
  ui.tabs({
    tabs: [
      {
        title: "Trucks",
        content: [
          ui.grid({ columns: 2 },
            ...trucks.map((t) =>
              ui.card({ title: t.name, description: t.power,
                footer: [ui.button({ label: `Buy for € ${t.price.toLocaleString("en")}`, onClick: (p) => buyTruck(p, t.name) })] }),
            ),
          ),
        ],
      },
      {
        title: "Prices",
        content: [
          ui.table({ columns: ["Truck", "Power", "Price"],
            rows: trucks.map((t) => [t.name, t.power, `€ ${t.price.toLocaleString("en")}`]) }),
        ],
      },
      {
        title: "Order",
        content: [
          ui.select({ id: "truck", label: "Truck", options: trucks.map((t) => t.name), value: trucks[0].name }),
          ui.radio({ id: "colour", label: "Colour", options: ["White", "Black", "Red"], value: "White" }),
          ui.slider({ id: "tank", label: "Fuel tank (l)", min: 400, max: 1400, step: 100, value: 800 }),
          ui.switch({ id: "delivery", label: "Deliver to my garage", checked: true }),
          ui.textarea({ id: "note", label: "Note for the dealer", placeholder: "Anything else?" }),
          ui.button({ label: "Order", close: true,
            onClick: (player, values) => buyTruck(player, String(values.truck)) }),
        ],
      },
    ],
  }),
)

mp.commands.add("shop", "open the truck shop", (player) => shop.show(player))
Preview - as it looks in the game

And a HUD panel - no mouse, at the edge of the screen:

const delivery = new UiView({ mode: "hud", position: "top-left", title: "Delivery" })
delivery.add(
  ui.text("Apples · Düsseldorf → Hamburg", { tone: "muted", size: "sm" }),
  ui.progress({ label: "Distance", value: 62 }),
  ui.row({ justify: "between" },
    ui.badge("€ 12,000"),
    ui.text("1 h 20 min left", { size: "sm" }),
  ),
)
Preview - as it looks in the game

Clicks and inputs

Buttons have onClick, selects, checkboxes, switches, radios and sliders onChange, a text field onSubmit (Enter). The function runs in your gamemode and gets the player and the current values of all inputs of the window, by their id:

const plateForm = new UiView({ title: "Licence plate" })
plateForm.add(
  ui.input({ id: "plate", label: "Text", placeholder: "OR MP 42", maxLength: 12 }),
  ui.select({ id: "country", label: "Design", options: ["germany", "france", "poland"], value: "germany" }),
  ui.button({
    label: "Apply",
    close: true,
    onClick: (player, values) => {
      mp.players.setLicensePlate(player, String(values.plate), String(values.country))
    },
  }),
)

mp.commands.add("plate", "pick your licence plate", (player) => plateForm.show(player))
Preview - as it looks in the game

Values are text for input (a number with type: "number"), textarea, select and radio, true/false for checkbox and switch, and a number for slider and logoPicker.

ui.logoPicker shows the game's 16 company logos as pictures - the player clicks one. Its value is the logo number (0-15), ready for mp.companies.create({ name, logo }) or company.logo:

ui.logoPicker({ id: "logo", label: "Logo", value: company.logo, onChange: (_p, values) => (company.logo = Number(values.logo)) })

One view, many players

A view can be open for many players at once - the handlers get the player who clicked. For content that differs per player (their money, their truck), build a view for that player when it is opened:

mp.commands.add("account", "your account", (player) => {
  new UiView({ title: "Account" })
    .add(
      ui.text(`Truck: ${player.truck}`),
      ui.progress({ label: "Engine wear", value: player.damage.engine * 100 }),
    )
    .show(player)
})
Preview - as it looks in the game

Changing what is shown

Change the blocks or options and call update() - everyone who has the view open sees the new version. hide(player) closes it for one player, hide() for everyone.

const race = new UiView({ mode: "hud", title: "Race", position: "top-right" })
race.show(mp.players.all())

function showStandings(order: OpenRoadMP.Player[]) {
  race.set(ui.table({ columns: ["#", "Driver"], rows: order.map((p, i) => [String(i + 1), p.name]) }))
  race.update()
}
Preview - as it looks in the game

onClose tells you when a player closed a window (or left the server):

shop.onClose((player) => mp.log(`${player.name} closed the shop`))

Limits

  • A view may be at most 32 KB as JSON (a few hundred blocks). For long lists, use pages or tabs.
  • Images and icons from the internet are not possible - a server cannot load anything into the player's game.
  • The example gamemode freeroam has a complete window: /garage (a truck for every brand in tabs).

On this page