mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: hero stat
This commit is contained in:
1 parent
9717f153cd
commit
ebb7618f2d
10 files changed
+238
-43
No files matched your search
@@ -69,7 +69,7 @@ export type RatingReward = {
|
||||
roman_level: string;
|
||||
user_string: string;
|
||||
image_url: string;
|
||||
preview_url: string;
|
||||
preview_image_url: string;
|
||||
is_premium: boolean;
|
||||
is_collectible: boolean;
|
||||
};
|
||||
|
||||
+192
-15
@@ -1,4 +1,5 @@
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
|
||||
import CommandWrapper from '../components/CommandWrapper';
|
||||
import { DeltaCaret } from '../components/DeltaCaret';
|
||||
import NoData from '../components/NoData';
|
||||
@@ -11,6 +12,7 @@ import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
|
||||
import { normalizeLeagueIcon } from '../core/blitz/normalizeLeagueIcon';
|
||||
import { emblemURL } from '../core/blitzkrieg/emblemURL';
|
||||
import { getArchivedRatingLeaderboard } from '../core/blitzkrieg/getArchivedRatingLeaderboard';
|
||||
import { webpToPng } from '../core/blitzkrieg/iconPng';
|
||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||
@@ -20,6 +22,14 @@ import { BkrlFormat } from '../core/streams/bkrl';
|
||||
import { CommandRegistry } from '../events/interactionCreate';
|
||||
import { theme } from '../stitches.config';
|
||||
|
||||
const LEAGUE_ACCENT = [
|
||||
'_purple',
|
||||
'_sand',
|
||||
'_amber',
|
||||
'_mauve',
|
||||
'_brown',
|
||||
] as const;
|
||||
|
||||
export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
resolve({
|
||||
command:
|
||||
@@ -47,7 +57,11 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
const accountInfo = await getAccountInfo(region, id, [
|
||||
'statistics.rating',
|
||||
]);
|
||||
const statsA = leaderboard.entries.find((entry) => entry.id === id);
|
||||
const statsIndex = leaderboard.entries.findIndex(
|
||||
(entry) => entry.id === id,
|
||||
);
|
||||
const statsA = leaderboard.entries[statsIndex];
|
||||
const numberA = statsIndex + 1;
|
||||
const statsB1 = accountInfo.statistics.rating!;
|
||||
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
|
||||
const league = getLeagueFromScore(statsB2.score);
|
||||
@@ -65,33 +79,59 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
);
|
||||
}
|
||||
|
||||
const reward = ratingInfo.rewards.find(
|
||||
(reward) =>
|
||||
statsB2.number >= reward.from_position &&
|
||||
statsB2.number <= reward.to_position,
|
||||
);
|
||||
const positionDelta = statsB2.number - numberA;
|
||||
const delta = deltaBkrlBlitzStats(
|
||||
statsA,
|
||||
accountInfo.statistics.rating!,
|
||||
statsB2,
|
||||
);
|
||||
const leagueTheme = LEAGUE_ACCENT[league.index];
|
||||
|
||||
return (
|
||||
<CommandWrapper>
|
||||
<TitleBar
|
||||
title={accountInfo.nickname}
|
||||
image={clanImage}
|
||||
description={t`bot.commands.rating_session.body.subtitle`}
|
||||
description={`${t`bot.commands.rating_session.body.subtitle`} • ${new Date().toLocaleDateString(
|
||||
interaction.locale,
|
||||
)}`}
|
||||
/>
|
||||
|
||||
{delta.battles === 0 && (
|
||||
<NoData type="battles_in_season" locale={interaction.locale} />
|
||||
)}
|
||||
{delta.battles > 0 && (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
padding: 16,
|
||||
backgroundColor: theme.colors.appBackground2_purple,
|
||||
backgroundColor: theme.colors[`appBackground2${leagueTheme}`],
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${theme.colors.componentInteractive_purple}`,
|
||||
border: `1px solid ${theme.colors[`componentInteractive${leagueTheme}`]}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{reward === undefined && (
|
||||
<Glow
|
||||
endOpacity={3 / 4}
|
||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||
style={{
|
||||
transform: 'translateX(-16px) rotate(90deg)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
@@ -121,12 +161,12 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors.textHighContrast_purple,
|
||||
color: theme.colors[`textHighContrast${leagueTheme}`],
|
||||
fontSize: 24,
|
||||
fontWeight: 900,
|
||||
}}
|
||||
@@ -150,17 +190,154 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
|
||||
<div
|
||||
style={{
|
||||
color: theme.colors.textLowContrast_purple,
|
||||
fontSize: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
#{statsB2.number.toLocaleString(interaction.locale)}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors[`textLowContrast${leagueTheme}`],
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
#{statsB2.number.toLocaleString(interaction.locale)}
|
||||
</span>
|
||||
{positionDelta !== 0 && (
|
||||
<>
|
||||
<DeltaCaret delta={positionDelta} />
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
positionDelta > 0
|
||||
? theme.colors.textLowContrast_green
|
||||
: theme.colors.textLowContrast_red,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{positionDelta.toLocaleString(interaction.locale)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{reward && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors[`textHighContrast${leagueTheme}`],
|
||||
fontSize: 16,
|
||||
maxWidth: 136,
|
||||
textAlign: 'right',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{reward.type === 'vehicle'
|
||||
? reward.vehicle.user_string
|
||||
: reward.stuff.title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: theme.colors[`textLowContrast${leagueTheme}`],
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
Reward
|
||||
{reward.type === 'stuff' ? ` x${reward.count}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
style={{
|
||||
width: 64,
|
||||
height: 64,
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
src={await webpToPng(
|
||||
reward.type === 'vehicle'
|
||||
? reward.vehicle.preview_image_url
|
||||
: reward.stuff.image_url,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{reward === undefined && (
|
||||
<Glow
|
||||
endOpacity={3 / 4}
|
||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||
style={{
|
||||
transform: 'translateX(16px) rotate(-90deg)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* separated because fragment middle man invalidates relative parent: https://github.com/vercel/satori/issues/605 */}
|
||||
{reward && (
|
||||
<Glow
|
||||
direction="reverse"
|
||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -40,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{reward && (
|
||||
<Glow
|
||||
color={theme.colors[`solidBackground${leagueTheme}`]}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: -40,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{delta.battles === 0 && (
|
||||
<NoData type="battles_in_period" locale={interaction.locale} />
|
||||
)}
|
||||
</CommandWrapper>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import resolveTankId from '../core/blitz/resolveTankId';
|
||||
import { asset } from '../core/blitzkrieg/asset';
|
||||
import { buildTechTreeLine } from '../core/blitzkrieg/buildTechTreeLine';
|
||||
import { emblemURL } from '../core/blitzkrieg/emblemURL';
|
||||
import { iconPng } from '../core/blitzkrieg/iconPng';
|
||||
import { webpToPng } from '../core/blitzkrieg/iconPng';
|
||||
import { resolveAncestry } from '../core/blitzkrieg/resolveAncestry';
|
||||
import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions';
|
||||
import {
|
||||
@@ -276,7 +276,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||
src={await webpToPng(asset('icons/currencies/xp.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
@@ -301,7 +301,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||
src={await webpToPng(asset('icons/currencies/silver.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
@@ -326,7 +326,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||
src={await webpToPng(asset('icons/currencies/silver.webp'))}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
@@ -452,7 +452,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||
src={await webpToPng(
|
||||
asset('icons/currencies/xp.webp'),
|
||||
)}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
@@ -479,7 +481,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(
|
||||
src={await webpToPng(
|
||||
asset('icons/currencies/free-xp.webp'),
|
||||
)}
|
||||
width={16}
|
||||
@@ -506,7 +508,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(
|
||||
src={await webpToPng(
|
||||
asset('icons/currencies/silver.webp'),
|
||||
)}
|
||||
width={16}
|
||||
@@ -534,7 +536,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={await iconPng(
|
||||
src={await webpToPng(
|
||||
asset('icons/currencies/silver.webp'),
|
||||
)}
|
||||
width={16}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
export interface GlowProps {
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
export interface GlowProps extends ComponentProps<'div'> {
|
||||
color: string;
|
||||
direction?: 'default' | 'reverse';
|
||||
rotation?: number;
|
||||
startOpacity?: number;
|
||||
endOpacity?: number;
|
||||
}
|
||||
|
||||
const width = 16;
|
||||
@@ -10,9 +14,17 @@ export function Glow({
|
||||
color,
|
||||
direction = 'default',
|
||||
rotation = 0,
|
||||
style,
|
||||
startOpacity = 0,
|
||||
endOpacity = 1,
|
||||
...props
|
||||
}: GlowProps) {
|
||||
const color1 = color;
|
||||
const color2 = `${color}00`;
|
||||
const color1 = `${color}${Math.round(endOpacity * 255)
|
||||
.toString(16)
|
||||
.padStart(2, '0')}`;
|
||||
const color2 = `${color}${Math.round(startOpacity * 255)
|
||||
.toString(16)
|
||||
.padStart(2, '0')}`;
|
||||
const background = `linear-gradient(${
|
||||
direction === 'default' ? 0 : 180
|
||||
}deg, ${color1} 0%, ${color2} 100%)`;
|
||||
@@ -24,7 +36,9 @@ export function Glow({
|
||||
alignItems: direction === 'default' ? 'flex-end' : 'flex-start',
|
||||
justifyContent: 'center',
|
||||
transform: `rotate(${rotation}deg)`,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<div style={{ height: 54, width, background }} />
|
||||
<div style={{ height: 72, width, background }} />
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { amber, brown, purple, sandDark, slate } from '@radix-ui/colors';
|
||||
|
||||
export const LEAGUE_COLORS = [
|
||||
purple.purple9,
|
||||
sandDark.sand11,
|
||||
amber.amber10,
|
||||
slate.slate9,
|
||||
brown.brown9,
|
||||
];
|
||||
@@ -5,8 +5,11 @@ interface DeltaCaretProps {
|
||||
export function DeltaCaret({ delta }: DeltaCaretProps) {
|
||||
return (
|
||||
<img
|
||||
width={16}
|
||||
height={16}
|
||||
style={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
src={
|
||||
delta > 0
|
||||
? 'https://i.imgur.com/gjI4a5f.png'
|
||||
|
||||
@@ -2,7 +2,9 @@ import sharp from 'sharp';
|
||||
|
||||
const cache: Record<string, Buffer> = {};
|
||||
|
||||
export async function iconPng(url: string) {
|
||||
export async function webpToPng(url: string) {
|
||||
if (!url.endsWith('.webp')) return url;
|
||||
|
||||
if (!cache[url]) {
|
||||
const imageResponse = await fetch(url);
|
||||
const imageBufferWEBP = await imageResponse.arrayBuffer();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { iconPng } from './iconPng';
|
||||
import { webpToPng } from './iconPng';
|
||||
import { tankIcon } from './tankIcon';
|
||||
|
||||
export function tankIconPng(id: number) {
|
||||
return iconPng(tankIcon(id));
|
||||
return webpToPng(tankIcon(id));
|
||||
}
|
||||
+2
-2
@@ -546,12 +546,12 @@
|
||||
"description": "Rating player's statistics",
|
||||
|
||||
"body": {
|
||||
"subtitle": "Today's rating statistics"
|
||||
"subtitle": "Rating session"
|
||||
},
|
||||
|
||||
"errors": {
|
||||
"no_participation": "# %s hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
|
||||
"unrecorded_stats": "# Statistics unavailable\n\nRatings statistics are currently unavailable. If there is an on-going season, statistics should be available withing 24 hours."
|
||||
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ import {
|
||||
amberDark,
|
||||
amberDarkA,
|
||||
blueDark,
|
||||
brownDark,
|
||||
greenDark,
|
||||
mauveDark,
|
||||
orangeDark,
|
||||
purpleDark,
|
||||
redDark,
|
||||
sandDark,
|
||||
slate,
|
||||
slateDark,
|
||||
slateDarkA,
|
||||
@@ -30,6 +33,9 @@ export const theme = {
|
||||
...createColors(tealDark, 'teal'),
|
||||
...createColors(greenDark, 'green'),
|
||||
...createColors(redDark, 'red'),
|
||||
...createColors(brownDark, 'brown'),
|
||||
...createColors(mauveDark, 'mauve'),
|
||||
...createColors(sandDark, 'sand'),
|
||||
},
|
||||
|
||||
borderStyles: {
|
||||
|
||||
Reference in new issue
Block a user