Skip to content

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.

The shop menu

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;
}