XP Leaderboards

This commit is contained in:
Kalle 2023-07-01 14:43:09 +03:00
parent 4162bb76e2
commit 7d6cd879aa
4 changed files with 176 additions and 6 deletions

View File

@ -1,4 +1,17 @@
import { mainWeaponIds } from "~/modules/in-game-lists";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
export const MATCHES_COUNT_NEEDED_FOR_LEADERBOARD = 7;
export const LEADERBOARD_MAX_SIZE = 250;
export const LEADERBOARD_TYPES = ["USER", "TEAM"] as const;
export const LEADERBOARD_TYPES = [
"USER",
"TEAM",
"XP-ALL",
...(rankedModesShort.map(
(mode) => `XP-MODE-${mode}`
) as `XP-MODE-${(typeof rankedModesShort)[number]}`[]),
...(mainWeaponIds.map(
(id) => `XP-WEAPON-${id}`
) as `XP-WEAPON-${(typeof mainWeaponIds)[number]}`[]),
] as const;

View File

@ -0,0 +1,71 @@
import { sql } from "~/db/sql";
import { LEADERBOARD_MAX_SIZE } from "../leaderboards-constants";
import type { User, XRankPlacement } from "~/db/types";
import type { MainWeaponId } from "~/modules/in-game-lists";
const getStm = (where = "") =>
sql.prepare(/* sql */ `
select
"XRankPlacement"."id" as "entryId",
"XRankPlacement"."playerId",
"XRankPlacement"."weaponSplId",
"XRankPlacement"."name",
"User"."id",
"User"."discordName",
"User"."discordAvatar",
"User"."discordDiscriminator",
"User"."discordId",
"User"."customUrl",
max("XRankPlacement"."power") as "power",
rank () over (
order by "power" desc
) "placementRank"
from "XRankPlacement"
left join "SplatoonPlayer" on "SplatoonPlayer"."id" = "XRankPlacement"."playerId"
left join "User" on "User"."id" = "SplatoonPlayer"."userId"
${where}
group by "XRankPlacement"."playerId"
order by "power" desc
limit ${LEADERBOARD_MAX_SIZE}
`);
const allStm = getStm();
const modeStm = getStm(/* sql */ `
where
"XRankPlacement"."mode" = @mode
`);
const weaponStm = getStm(/* sql */ `
where
"XRankPlacement"."weaponSplId" = @weaponSplId
`);
export interface XPLeaderboardItem {
entryId: number;
power: number;
id: User["id"];
name: XRankPlacement["name"];
playerId: XRankPlacement["playerId"];
discordName: User["discordName"] | null;
discordAvatar: User["discordAvatar"] | null;
discordDiscriminator: User["discordDiscriminator"] | null;
discordId: User["discordId"] | null;
customUrl: User["customUrl"] | null;
placementRank: number;
weaponSplId: MainWeaponId;
}
export function allXPLeaderboard(): XPLeaderboardItem[] {
return allStm.all() as any[];
}
export function modeXPLeaderboard(
mode: XRankPlacement["mode"]
): XPLeaderboardItem[] {
return modeStm.all({ mode }) as any[];
}
export function weaponXPLeaderboard(
weaponSplId: MainWeaponId
): XPLeaderboardItem[] {
return weaponStm.all({ weaponSplId }) as any[];
}

View File

@ -12,6 +12,7 @@ import {
LEADERBOARDS_PAGE,
navIconUrl,
teamPage,
topSearchPlayerPage,
userPage,
userSubmittedImage,
} from "~/utils/urls";
@ -29,6 +30,19 @@ import React from "react";
import { LEADERBOARD_TYPES } from "../leaderboards-constants";
import { useTranslation } from "~/hooks/useTranslation";
import { i18next } from "~/modules/i18n";
import {
type XPLeaderboardItem,
allXPLeaderboard,
modeXPLeaderboard,
weaponXPLeaderboard,
} from "../queries/XPLeaderboard.server";
import { WeaponImage } from "~/components/Image";
import {
weaponCategories,
type MainWeaponId,
type RankedModeShort,
} from "~/modules/in-game-lists";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
export const handle: SendouRouteHandle = {
i18n: ["vods"],
@ -73,12 +87,20 @@ export const loader = async ({ request }: LoaderArgs) => {
return {
userLeaderboard: type === "USER" ? userSPLeaderboard() : null,
teamLeaderboard: type === "TEAM" ? teamSPLeaderboard() : null,
xpLeaderboard:
type === "XP-ALL"
? allXPLeaderboard()
: type.startsWith("XP-MODE")
? modeXPLeaderboard(type.split("-")[2] as RankedModeShort)
: type.startsWith("XP-WEAPON")
? weaponXPLeaderboard(Number(type.split("-")[2]) as MainWeaponId)
: null,
title: makeTitle(t("pages.leaderboards")),
};
};
export default function LeaderboardsPage() {
const { t } = useTranslation(["common"]);
const { t } = useTranslation(["common", "game-misc", "weapons"]);
const [searchParams, setSearchParams] = useSearchParams();
const data = useLoaderData<typeof loader>();
@ -92,14 +114,42 @@ export default function LeaderboardsPage() {
}
>
<optgroup label="SP">
{LEADERBOARD_TYPES.map((type) => {
{LEADERBOARD_TYPES.filter((type) => !type.includes("XP")).map(
(type) => {
return (
<option key={type} value={type}>
{t(`common:leaderboard.type.${type as "USER" | "TEAM"}`)}
</option>
);
}
)}
</optgroup>
<optgroup label="XP">
<option value="XP-ALL">{t(`common:leaderboard.type.XP-ALL`)}</option>
{rankedModesShort.map((mode) => {
return (
<option key={type} value={type}>
{t(`common:leaderboard.type.${type}`)}
<option key={mode} value={`XP-MODE-${mode}`}>
{t(`game-misc:MODE_LONG_${mode}`)}
</option>
);
})}
</optgroup>
{weaponCategories.map((category) => {
return (
<optgroup
key={category.name}
label={`XP (${t(`common:weapon.category.${category.name}`)})`}
>
{category.weaponIds.map((weaponId) => {
return (
<option key={weaponId} value={`XP-WEAPON-${weaponId}`}>
{t(`weapons:MAIN_${weaponId}`)}
</option>
);
})}
</optgroup>
);
})}
</select>
{data.userLeaderboard ? (
<PlayersTable entries={data.userLeaderboard} />
@ -107,6 +157,7 @@ export default function LeaderboardsPage() {
{data.teamLeaderboard ? (
<TeamTable entries={data.teamLeaderboard} />
) : null}
{data.xpLeaderboard ? <XPTable entries={data.xpLeaderboard} /> : null}
</Main>
);
}
@ -178,3 +229,37 @@ function TeamTable({ entries }: { entries: TeamSPLeaderboardItem[] }) {
</div>
);
}
function XPTable({ entries }: { entries: XPLeaderboardItem[] }) {
return (
<div className="placements__table">
{entries.map((entry) => {
return (
<Link
to={topSearchPlayerPage(entry.playerId)}
key={entry.entryId}
className="placements__table__row"
>
<div className="placements__table__inner-row">
<div className="placements__table__rank">
{entry.placementRank}
</div>
{entry.discordId ? (
<Avatar size="xxs" user={entry as any} />
) : null}
<WeaponImage
className="placements__table__weapon"
variant="build"
weaponSplId={entry.weaponSplId}
width={32}
height={32}
/>
<div>{entry.name}</div>
<div className="placements__table__power">{entry.power}</div>
</div>
</Link>
);
})}
</div>
);
}

View File

@ -187,5 +187,6 @@
"no": "No",
"leaderboard.type.USER": "User",
"leaderboard.type.TEAM": "Team"
"leaderboard.type.TEAM": "Team",
"leaderboard.type.XP-ALL": "All"
}