Appearance
Example: arena
From the runnable examples. Lint, build and run it from servers/inventery/:
sh
node plot/sdk/lint.mjs docs/developers/examples/arena
node plot/sdk/build.mjs docs/developers/examples/arena
node docs/developers/examples/run.mjs docs/developers/examples/arenaplot.json
json
{
"kind": "runtime",
"namespace": "arena",
"id": "ana.arena",
"version": "1.0.0",
"name": "Arena",
"apiVersion": 1,
"code": "dist/logic.js",
"stashItems": true,
"lobbies": { "size": 2 },
"tickRate": 30,
"rating": "teen",
"tags": ["shooter", "pvp"]
}logic.ts
ts
import { input, lobbies, network, players, scene, time, ui } from "@inventery/host";
import type { PlayerId, Vec3 } from "@inventery/host";
const RANGE = 100, DAMAGE = 5, COOLDOWN = 0.25;
const lastShot = new Map<PlayerId, number>(); // memory is fine for this: losing it on a restart is harmless
export function onPlayerEnter(p: PlayerId) {
ui.chat(p, "Tap to fire.");
const lobby = lobbies.current();
if (lobby.players.length >= lobby.size) {
lobbies.lock(); // a duel: nobody else walks in mid-fight
lobbies.startCountdown({ protectEveryone: true }); // an equal start: nobody can be hit before it ends
}
}
export function onCountdownEnd() {
for (const p of players.list()) players.setCamera(p, { mode: "first" });
}
export function onPlayerLeave() {
if (players.list().length === 0) lobbies.unlock(); // empty again: open for the next pair
}
// Every tick, read this lobby's inputs. The room is the referee.
export function tickLobby() {
for (const ev of input.read()) {
if (ev.action !== "fire" || !ev.aimRay) continue;
const now = time.now();
if (now - (lastShot.get(ev.player) ?? -1) < COOLDOWN) continue; // the server decides the fire rate
lastShot.set(ev.player, now);
// Lag compensation: check the shot against where everyone was when the shooter saw them.
// The ray starts inside the shooter's own body, so it ignores the shooter.
const seenAt = ev.renderTime ?? now;
const hit = network.raycastAtTime(seenAt, ev.aimRay, { maxDistance: RANGE, players: true, blocks: true, ignore: [ev.player] });
if (hit?.player) players.damage(hit.player, DAMAGE, { by: ev.player, cause: "shot" });
// The tracer is in the scene, so onlookers outside see the fight too.
const end: Vec3 = hit?.point ?? along(ev.aimRay.origin, ev.aimRay.dir, RANGE);
scene.create({
Transform: { pos: ev.aimRay.origin },
Emitter: { particles: "inventery:spark", burst: 6, to: end, color: "#ffe08a" },
Lifetime: { seconds: 0.2 },
});
}
}
function along(o: Vec3, d: Vec3, t: number): Vec3 {
return [o[0] + d[0] * t, o[1] + d[1] * t, o[2] + d[2] * t];
}