Rank each player once across T5 and T6

This commit is contained in:
2026-09-28 15:22:11 -04:00
parent 61d4eebfea
commit 30030599d6
6 changed files with 144 additions and 39 deletions
+13 -9
View File
@@ -10,13 +10,13 @@ export function isLeaderboardMetric(value: string): value is LeaderboardMetric {
return metrics.has(value);
}
/** Aggregate persisted per-match records; filtering a player keeps their actual rank. */
/** Rank each GUID once within the selected matches; player search keeps their actual rank. */
export function getLeaderboard(options: LeaderboardOptions = {}): LeaderboardResponse {
const handle = db();
const metric = options.metric && isLeaderboardMetric(options.metric) ? options.metric : 'highestRound';
const limit = Number.isSafeInteger(options.limit) ? Math.min(100, Math.max(1, options.limit!)) : 50;
const offset = Number.isSafeInteger(options.offset) ? Math.min(MAX_LEADERBOARD_OFFSET, Math.max(0, options.offset!)) : 0;
const conditions = ['mp.has_stats = 1', `m.highest_round >= ${MIN_RECORDED_ROUND}`,
const conditions = ["m.game IN ('t5', 't6')", 'mp.has_stats = 1', `m.highest_round >= ${MIN_RECORDED_ROUND}`,
`mp.highest_round >= ${MIN_RECORDED_ROUND}`];
const parameters: string[] = [];
if (options.game) { conditions.push('m.game = ?'); parameters.push(options.game); }
@@ -25,34 +25,38 @@ export function getLeaderboard(options: LeaderboardOptions = {}): LeaderboardRes
JOIN leaderboard_matches m ON m.id = mp.match_id
WHERE ${conditions.join(' AND ')}`;
const aggregated = `WITH totals AS (
SELECT m.game, mp.guid, MAX(mp.highest_round) AS highestRound,
SELECT mp.guid, MAX(mp.highest_round) AS highestRound,
SUM(mp.kills) AS kills, SUM(mp.rounds_survived) AS roundsSurvived,
SUM(mp.headshots) AS headshots, SUM(mp.revives) AS revives, SUM(mp.downs) AS downs,
COUNT(*) AS gamesPlayed, SUM(mp.completed) AS gamesCompleted,
MAX(mp.kills) AS bestKills, MAX(mp.best_score) AS bestScore, MAX(mp.last_seen_at) AS lastSeen
${scoped} GROUP BY m.game, mp.guid
${scoped} GROUP BY mp.guid
), identities AS (
SELECT guid, name, ROW_NUMBER() OVER (PARTITION BY guid ORDER BY last_seen_at DESC, game) AS recency
FROM leaderboard_players WHERE game IN ('t5', 't6')
), ranked AS (
SELECT totals.*, p.name, RANK() OVER (ORDER BY totals.${metric} DESC) AS rank
FROM totals JOIN leaderboard_players p ON p.game = totals.game AND p.guid = totals.guid
FROM totals JOIN identities p ON p.guid = totals.guid AND p.recency = 1
)`;
const query = options.q?.trim() ?? '';
const search = query ? 'WHERE instr(lower(name), lower(?)) > 0 OR instr(lower(guid), lower(?)) > 0' : '';
const searchedParameters = query ? [...parameters, query, query] : parameters;
const entries = handle.prepare(`${aggregated} SELECT * FROM ranked ${search}
ORDER BY ${metric} DESC, name COLLATE NOCASE, name, game, guid LIMIT ? OFFSET ?`)
ORDER BY ${metric} DESC, name COLLATE NOCASE, name, guid LIMIT ? OFFSET ?`)
.all(...searchedParameters, limit, offset) as LeaderboardEntry[];
const { total } = handle.prepare(`${aggregated} SELECT COUNT(*) AS total FROM ranked ${search}`)
.get(...searchedParameters) as { total: number };
const summary = handle.prepare(`SELECT
(SELECT COUNT(*) FROM (SELECT m.game, mp.guid ${scoped} GROUP BY m.game, mp.guid)) AS players,
COUNT(DISTINCT mp.guid) AS players,
COUNT(DISTINCT m.id) AS matches, COALESCE(SUM(mp.kills), 0) AS kills,
COALESCE(SUM(mp.rounds_survived), 0) AS roundsSurvived,
COALESCE(MAX(mp.highest_round), 0) AS highestRound,
MIN(m.started_at) AS firstTrackedAt, MAX(m.last_received_at) AS updatedAt
${scoped}`).get(...parameters, ...parameters) as LeaderboardResponse['summary'];
${scoped}`).get(...parameters) as LeaderboardResponse['summary'];
// Keep qualifying map variants available, even after their server is removed.
const maps = handle.prepare(`SELECT map_key AS key, game, map, map_name AS name
FROM leaderboard_matches WHERE highest_round >= ${MIN_RECORDED_ROUND}${options.game ? ' AND game = ?' : ''}
FROM leaderboard_matches WHERE game IN ('t5', 't6')
AND highest_round >= ${MIN_RECORDED_ROUND}${options.game ? ' AND game = ?' : ''}
GROUP BY game, map_key HAVING id = MAX(id)
ORDER BY game, name COLLATE NOCASE, map_key`)
.all(...(options.game ? [options.game] : [])) as LeaderboardMap[];