Show map rankings for player personal records
This commit is contained in:
@@ -39,6 +39,7 @@ export interface PlayerMapRecord extends PlayerStats {
|
||||
name: string;
|
||||
lastSeen: string;
|
||||
records: PersonalBestRecords;
|
||||
ranks: Record<PersonalBestMetric, number>;
|
||||
}
|
||||
export interface PlayerMatch {
|
||||
id: number;
|
||||
|
||||
@@ -36,10 +36,31 @@ export function getPlayerProfile(guid: string, requestedOffset = 0,
|
||||
const mapStats = handle.prepare(`SELECT m.game, m.map_key AS key, m.map_name AS name,
|
||||
MAX(mp.last_seen_at) AS lastSeen, ${totals} ${scope}
|
||||
GROUP BY m.game, m.map_key ORDER BY highestRound DESC, kills DESC, name COLLATE NOCASE, m.game, key`)
|
||||
.all(guid) as Array<Omit<PlayerMapRecord, 'records'>>;
|
||||
.all(guid) as Array<Omit<PlayerMapRecord, 'records' | 'ranks'>>;
|
||||
const [records, ...mapRecords] = getPersonalBestRecords(handle, [{ guid, ...stats },
|
||||
...mapStats.map(map => ({ guid, ...map, map: map.key }))]);
|
||||
const maps = mapStats.map((map, index) => ({ ...map, records: mapRecords[index] }));
|
||||
// Rank all three bests in one pass over this player's maps. Filter the GUID
|
||||
// only after ranking competitors so ties and skipped ranks match the leaderboard.
|
||||
const mapRanks = handle.prepare(`WITH totals AS (
|
||||
SELECT requested.key AS requestId, mp.guid,
|
||||
MAX(mp.highest_round) AS highestRound, MAX(mp.kills) AS bestKills, MAX(mp.best_score) AS bestScore
|
||||
FROM json_each(?) requested
|
||||
JOIN leaderboard_matches m ON m.game = json_extract(requested.value, '$.game')
|
||||
AND m.map_key = json_extract(requested.value, '$.key')
|
||||
JOIN leaderboard_match_players mp ON mp.match_id = m.id
|
||||
WHERE m.game IN ('t5', 't6') AND m.status = 'completed' AND mp.has_stats = 1
|
||||
AND m.highest_round >= ${MIN_RECORDED_ROUND} AND mp.highest_round >= ${MIN_RECORDED_ROUND}
|
||||
AND EXISTS (SELECT 1 FROM leaderboard_players p WHERE p.guid = mp.guid AND p.game IN ('t5', 't6'))
|
||||
GROUP BY requested.key, mp.guid
|
||||
), ranked AS (
|
||||
SELECT requestId, guid,
|
||||
RANK() OVER (PARTITION BY requestId ORDER BY highestRound DESC) AS highestRound,
|
||||
RANK() OVER (PARTITION BY requestId ORDER BY bestKills DESC) AS bestKills,
|
||||
RANK() OVER (PARTITION BY requestId ORDER BY bestScore DESC) AS bestScore
|
||||
FROM totals
|
||||
) SELECT highestRound, bestKills, bestScore FROM ranked WHERE guid = ? ORDER BY requestId`)
|
||||
.all(JSON.stringify(mapStats.map(({ game, key }) => ({ game, key }))), guid) as PlayerMapRecord['ranks'][];
|
||||
const maps = mapStats.map((map, index) => ({ ...map, records: mapRecords[index], ranks: mapRanks[index] }));
|
||||
const matches = handle.prepare(`SELECT m.id, m.game, m.server, m.server_deleted_at, m.map_name AS mapName,
|
||||
m.started_at AS startedAt, mp.last_seen_at AS lastSeen, m.status, mp.completed,
|
||||
mp.highest_round AS highestRound, mp.rounds_survived AS roundsSurvived,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{#each maps as map (`${map.game}:${map.key}`)}
|
||||
<article>
|
||||
<header><h3><a href={`/leaderboard?${new URLSearchParams({ game: map.game, map: map.key, metric })}`}>{map.name}</a></h3><span>{map.game === 't5' ? 'Black Ops I' : 'Black Ops II'} · {number.format(map.gamesPlayed)} {map.gamesPlayed === 1 ? 'game' : 'games'}</span></header>
|
||||
<dl>{#each records as record}<div><dt>{record.label}</dt><dd><a href={contextHref(`/matches/${map.records[record.key].matchId}`, returnTo ?? "/leaderboard")} aria-label={`${map.name} ${record.label}: ${number.format(map[record.key])}, Match #${map.records[record.key].matchId}`} title={map.records[record.key].endedAt ? $timeFormat.dateTime(map.records[record.key].endedAt!) : 'Completed match'}>{number.format(map[record.key])}</a></dd></div>{/each}</dl>
|
||||
<dl>{#each records as record}<div><dt>{record.label}</dt><dd><a href={contextHref(`/matches/${map.records[record.key].matchId}`, returnTo ?? "/leaderboard")} aria-label={`${map.name} ${record.label}: ${number.format(map[record.key])}, Match #${map.records[record.key].matchId}`} title={map.records[record.key].endedAt ? $timeFormat.dateTime(map.records[record.key].endedAt!) : 'Completed match'}>{number.format(map[record.key])}</a><small aria-label={`${map.name} ${record.label} rank ${map.ranks[record.key]}`}>Rank #{number.format(map.ranks[record.key])}</small></dd></div>{/each}</dl>
|
||||
<p>{number.format(map.kills)} total kills · {number.format(map.roundsSurvived)} observed rounds</p>
|
||||
</article>
|
||||
{/each}
|
||||
@@ -23,5 +23,6 @@
|
||||
dl { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); margin: var(--space-3) 0; }
|
||||
dd { font-family: var(--font-numeric); font-size: var(--text-lg); font-variant-numeric: tabular-nums; margin-top: var(--space-1); }
|
||||
dd a { display: inline-flex; align-items: center; min-height: 44px; }
|
||||
dd small { display: block; color: var(--text-secondary); font-family: var(--font-body); font-size: var(--text-xs); }
|
||||
a { color: var(--accent-primary-hover); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user