feat: hero stat

This commit is contained in:
TrèsAbhi committed 2024-03-25 18:00:07 -05:00
1 parent 9717f153cd
commit ebb7618f2d
10 files changed
+238 -43

No files matched your search

+1 -1
View File
@@ -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
View File
@@ -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>
);
+10 -8
View File
@@ -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 -2
View File
@@ -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'
+3 -1
View File
@@ -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();
+2 -2
View File
@@ -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
View File
@@ -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."
}
}
}
+6
View File
@@ -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: {