/** * The page. The first paint is the match as it stands right now — score, * round, phase, scoreboard — server-rendered HTML, so somebody arriving with * a cold cache or no JavaScript still sees a live match. The client script * then takes the same state over a socket and draws it. */ import { AGENTS } from "../abilities.ts"; import type { Result } from "../db.ts"; import type { MatchState } from "../state.ts"; import { WEAPONS, weaponIds } from "../weapons.ts"; export const escape = (s: string): string => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c] ?? c); const phaseWord = (state: MatchState): string => state.phase === "buy" ? "buy phase" : state.phase === "live" ? state.spike.planted ? `spike down · ${Math.ceil(state.spike.fuse)}s` : "live" : state.phase === "end" ? "round over" : "match over"; export function gamePage(opts: { state: MatchState; script: string; name: string; env: string; commit: string; ssh: string | undefined; results: Result[]; }): string { const { state } = opts; const board = [...state.players].sort((a, b) => b.kills - a.kills); const row = (side: "atk" | "def"): string => board .filter((p) => p.side === side) .map( (p) => `
The round. Attackers carry the spike to site A or B and plant it; defenders stop them, or defuse it once it is down. Plant and it runs a 45s fuse. First team to 13 rounds wins, and the sides swap at 12.
The teams. You are red or blue; each round one team attacks and the other defends. Team mates carry a name tag over their head, enemies never do.
Controls. WASD move · click the view to capture the mouse and look, Esc releases · click to fire · R reload · hold F to plant, defuse or pick up · Tab holds the big map open · V swaps to the top-down view · B opens the buy · H brings this card back.
Abilities. C and Q are bought in the buy phase and last the round, E is free and comes back on a cooldown, and X is the ultimate, paid for in ult points you earn by playing.
Telling people apart. Your five are always listed under the view with their health — struck through when they are down — and the last few kills scroll in the top right. In the world an enemy wears a red chevron over their head and a hard dark outline, a team mate wears their name and a health pip, and anybody lying flat on the floor is dead. On the map your team is always shown (crossed out when down) and enemies appear only while you can see them.
On a phone the view stays top-down: the left thumb walks, the right thumb aims and fires, and the buttons under the map buy and cast.
| player | agent | k | d |
|---|
| player | agent | k | d |
|---|