// ── `/top [stat] [server] [alltime]` — the leaderboard's top ten (D126) ───── // // Public at every setting, as on the web: the leaderboard's NAMES are public, and // only `lastSeen` sits behind presence (a tally refreshes it every minute a player // is on, so it is the Online tab by another name). This answer never carries a // `lastSeen`, and never a Steam id — `events.leaderboard` is asked with // `presence: false`, so the field is not there to leak. const c = require('./common') const events = require('../model/events/events.model') /** Ten rows: a summary a person reads, not a table they scroll (§32.4 reading 2). */ const LIMIT = 10 // `stat`'s choices are fixed, so they are a `choices` list rather than free // text. The value is the leaderboard's own sort key. const STATS = { kills: { sort: 'kills', label: 'kills', value: (r) => r.kills }, deaths: { sort: 'deaths', label: 'deaths', value: (r) => r.deaths }, npckills: { sort: 'npcKills', label: 'NPC kills', value: (r) => r.npcKills }, playtime: { sort: 'playtime', label: 'playtime', value: (r) => r.playtimeSec, format: hours }, } function hours(sec) { const h = Math.floor(sec / 3600) const m = Math.floor((sec % 3600) / 60) return h ? `${h}h ${m}m` : `${m}m` } async function handler({ options }) { const now = Date.now() const stat = STATS[(options && options.stat) || 'kills'] || STATS.kills // A leaderboard is one server's. With several and none named the command asks // rather than choosing one, privately — the question is for the caller. const picked = c.pickServer(await c.listServers(now), options && options.server) const refusal = c.pickRefusal(picked, { needOne: true }) if (refusal) return refusal const { server } = picked // The current wipe unless asked for all-time. A server that has not reported // a wipe yet has nothing to scope to, and all-time is the honest answer. const allTime = Boolean(options && options.alltime) || !server.wipeId const rows = await events.leaderboard({ serverId: server.id, wipeId: allTime ? null : server.wipeId, sort: stat.sort, limit: LIMIT, presence: false, }) const scope = allTime ? 'all time' : 'this wipe' const title = `${server.name} — top ${stat.label}, ${scope}` if (!rows.length) return { title, url: c.serverUrl(server), text: `Nobody is on the board for ${scope} yet.` } const format = stat.format || String return { title, url: c.serverUrl(server), text: rows.map((r, i) => `${i + 1}. ${r.name || 'Unknown player'} — ${format(stat.value(r))}`).join('\n'), } } module.exports = { name: 'top', description: 'The top ten players on a Rust server by kills, deaths, NPC kills or playtime', options: [ { name: 'stat', type: 'string', description: 'What to rank by (kills when left out)', required: false, choices: [ { name: 'Kills', value: 'kills' }, { name: 'Deaths', value: 'deaths' }, { name: 'NPC kills', value: 'npckills' }, { name: 'Playtime', value: 'playtime' }, ], }, { name: 'server', type: 'string', description: 'Server name or id (needed when there are several)', required: false }, { name: 'alltime', type: 'boolean', description: 'Every wipe rather than the current one', required: false }, ], access: 'everyone', handler, }