A shop menu¶
A shop run entirely from the server, with no client script: the server opens a layout, fills in the items, and charges for what the player picks.

clientFiles/ui/shop.rml
<rml>
<head>
<link type="text/rcss" href="/sagl/ui/theme/base.rcss"/>
<style>
body { position: absolute; left: 50%; top: 18%; width: 440dp; margin-left: -220dp; }
.item.picked { color: #ffffff; background-color: #323c7fc0; }
#total { color: #36682c; font-family: pricedown; font-size: 30dp; }
</style>
</head>
<body class="panel">
<h1 id="title">Shop</h1>
<span class="subtitle" id="greeting"></span>
<div class="list" id="items"></div>
<form>
<div>Total: <span id="total">$0</span></div>
<input type="submit" class="button" value="Buy"/>
<button id="cancel">Cancel</button>
</form>
<span class="status" id="status"></span>
</body>
</rml>
shop.ts
import { Server, Weapon, type Player, type UiDocument } from "@sagl/server";
const items = [
{ id: "pistol", name: "Pistol", price: 200, give: (p: Player) => p.giveWeapon(Weapon.Pistol, 60) },
{ id: "armour", name: "Body armour", price: 300, give: (p: Player) => (p.armour = 100) },
{ id: "parachute", name: "Parachute", price: 50, give: (p: Player) => p.giveWeapon(Weapon.Parachute, 1) },
];
export function shop(server: Server) {
const counter = server.createZone({ center: { x: 296, y: -38, z: 1001.5 }, radius: 2 });
const open = new Map<Player, UiDocument>();
server.on("zoneEnter", (player, zone) => {
if (zone === counter && !open.get(player)?.open) open.set(player, openShop(player));
});
// Walking away closes it, so a purchase can only come from the counter.
server.on("zoneLeave", (player, zone) => {
if (zone === counter) open.get(player)?.close();
});
}
function openShop(player: Player): UiDocument {
let picked = items[0];
const shop = player.ui.open("ui/shop.rml")
.setText("title", "Ammu-Nation")
.setText("greeting", `Welcome, ${player.name}`)
.setHtml("items", items.map((i) => `<div class="item" id="${i.id}">${i.name} - $${i.price}</div>`).join(""))
.setClass(picked.id, "picked")
.setText("total", `$${picked.price}`);
shop.on("click", (e) => {
const item = items.find((i) => i.id === e.id);
if (!item) return;
shop.setClass(picked.id, "picked", false).setClass(item.id, "picked").setText("total", `$${item.price}`);
picked = item;
});
shop.on("submit", () => {
// Events come from the player's game: the server decides what they can buy.
if (player.money < picked.price) return void shop.setText("status", "You can't afford that.");
player.money -= picked.price;
picked.give(player);
shop.setText("status", `Bought: ${picked.name}`);
});
shop.on("click", "cancel", () => shop.close());
return shop;
}