mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-07 05:42:28 -05:00
XP Leaderboards
This commit is contained in:
parent
4162bb76e2
commit
7d6cd879aa
|
|
@ -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;
|
||||
|
|
|
|||
71
app/features/leaderboards/queries/XPLeaderboard.server.ts
Normal file
71
app/features/leaderboards/queries/XPLeaderboard.server.ts
Normal 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[];
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,5 +187,6 @@
|
|||
"no": "No",
|
||||
|
||||
"leaderboard.type.USER": "User",
|
||||
"leaderboard.type.TEAM": "Team"
|
||||
"leaderboard.type.TEAM": "Team",
|
||||
"leaderboard.type.XP-ALL": "All"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user