feat: start on final design

This commit is contained in:
TrèsAbhi committed 2024-03-24 22:06:48 -05:00
1 parent 298b89b227
commit afa0cfaeef
21 files changed
+178 -116

No files matched your search

+2 -2
View File
@@ -7,7 +7,7 @@ import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import resolveTankId from '../core/blitz/resolveTankId';
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { emblemURL } from '../core/blitzkrieg/emblemURL';
import getTankHistories from '../core/blitzkrieg/getTankHistories';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getPlayerHistories from '../core/blitzstars/getPlayerHistories';
@@ -41,7 +41,7 @@ async function render(
) {
const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const logo = clan ? emblemIdToURL(clan.emblem_set_id) : undefined;
const logo = clan ? emblemURL(clan.emblem_set_id) : undefined;
const histories = await (type === 'player'
? getPlayerHistories(region, id, {
start,
+2 -2
View File
@@ -6,7 +6,7 @@ import TierWeights from '../components/TierWeights';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { emblemURL } from '../core/blitzkrieg/emblemURL';
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
@@ -40,7 +40,7 @@ async function render(
) {
const { nickname } = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemIdToURL(clan.emblem_set_id) : undefined;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const diffedTankStats = await getStatsInPeriod(
region,
id,
+97 -20
View File
@@ -1,10 +1,15 @@
import markdownEscape from 'markdown-escape';
import AllStatsOverview from '../components/AllStatsOverview';
import CommandWrapper from '../components/CommandWrapper';
import { DeltaCaret } from '../components/DeltaCaret';
import NoData from '../components/NoData';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { getLeagueFromScore } from '../core/blitz/getLeagueFromScore';
import getRatingInfo from '../core/blitz/getRatingInfo';
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 addUsernameChoices from '../core/discord/addUsernameChoices';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
@@ -13,6 +18,7 @@ import { translator } from '../core/localization/translator';
import { deltaBkrlBlitzStats } from '../core/statistics/deltaBkrlBlitzStats';
import { BkrlFormat } from '../core/streams/bkrl';
import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config';
export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
@@ -27,6 +33,8 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
async handler(interaction) {
const { t, translate } = translator(interaction.locale);
const { id, region } = await resolvePlayerFromCommand(interaction);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const leaderboard = await getArchivedRatingLeaderboard(region, 53);
if (leaderboard.format === BkrlFormat.Minimal) {
@@ -35,13 +43,18 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
);
}
const ratingInfo = await getRatingInfo(region);
const accountInfo = await getAccountInfo(region, id, [
'statistics.rating',
]);
const statsA = leaderboard.entries.find((entry) => entry.id === id);
const statsB1 = accountInfo.statistics.rating!;
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
const league = getLeagueFromScore(statsB2.score);
if (ratingInfo.detail) {
throw new Error(`Rating info not available for ${region}`);
}
if (!statsA) {
return t`bot.commands.rating_session.errors.unrecorded_stats`;
}
@@ -52,13 +65,17 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
);
}
const delta = deltaBkrlBlitzStats(statsA, accountInfo.statistics.rating!);
const delta = deltaBkrlBlitzStats(
statsA,
accountInfo.statistics.rating!,
statsB2,
);
return (
<CommandWrapper>
<TitleBar
title={accountInfo.nickname}
// image={clanImage} // TODO: add league icon here
image={clanImage}
description={t`bot.commands.rating_session.body.subtitle`}
/>
@@ -66,24 +83,84 @@ export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
<NoData type="battles_in_season" locale={interaction.locale} />
)}
{delta.battles > 0 && (
<AllStatsOverview
locale={interaction.locale}
stats={{
battles: delta.battles,
damage_dealt: delta.damageDealt,
damage_received: delta.damageReceived,
frags: delta.kills,
hits: delta.hits,
shots: delta.shots,
survived_battles: delta.survived,
wins: delta.wins,
<div
style={{
padding: 16,
backgroundColor: theme.colors.appBackground2_purple,
borderRadius: 4,
border: `1px solid ${theme.colors.componentInteractive_purple}`,
display: 'flex',
}}
supplementaryStats={{
type: 'rating',
score: statsB2.score,
delta: statsB2.score - statsA.score,
}}
/>
>
<div
style={{
display: 'flex',
gap: 8,
justifyContent: 'center',
alignItems: 'center',
}}
>
<img
style={{
width: 64,
height: 64,
objectFit: 'contain',
}}
src={normalizeLeagueIcon(
ratingInfo.leagues[league.index].big_icon,
)}
/>
<div
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span
style={{
color: theme.colors.textHighContrast_purple,
fontSize: 24,
fontWeight: 900,
}}
>
{statsB2.score.toLocaleString(interaction.locale)}
</span>
{delta.score !== 0 && (
<>
<DeltaCaret delta={delta.score} />
<span
style={{
color:
delta.score > 0
? theme.colors.textLowContrast_green
: theme.colors.textLowContrast_red,
fontSize: 16,
}}
>
{delta.score.toLocaleString(interaction.locale)}
</span>
</>
)}
</div>
<span
style={{
color: theme.colors.textLowContrast_purple,
fontSize: 16,
}}
>
#{statsB2.number.toLocaleString(interaction.locale)}
</span>
</div>
</div>
</div>
)}
</CommandWrapper>
);
+2 -2
View File
@@ -8,7 +8,7 @@ import getTankStats from '../core/blitz/getTankStats';
import resolveTankId from '../core/blitz/resolveTankId';
import { asset } from '../core/blitzkrieg/asset';
import { buildTechTreeLine } from '../core/blitzkrieg/buildTechTreeLine';
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { emblemURL } from '../core/blitzkrieg/emblemURL';
import { iconPng } from '../core/blitzkrieg/iconPng';
import { resolveAncestry } from '../core/blitzkrieg/resolveAncestry';
import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions';
@@ -155,7 +155,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
const line = [...techTreeLine, startingTankId].reverse();
const { nickname } = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemIdToURL(clan.emblem_set_id) : undefined;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const costs = await Promise.all(
line.map(async (id) => {
const tank = awaitedTankDefinitions[id];
+2 -2
View File
@@ -5,7 +5,7 @@ import NoData from '../components/NoData';
import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { emblemURL } from '../core/blitzkrieg/emblemURL';
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
@@ -38,7 +38,7 @@ async function render(
) {
const { nickname } = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemIdToURL(clan.emblem_set_id) : undefined;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const diffedTankStats = await getStatsInPeriod(
region,
id,
@@ -1,13 +1,11 @@
import { Locale } from 'discord.js';
import { Percentile } from '../../../../constants/percentiles';
import { SupplementaryStats } from '../../../../core/blitz/getAccountInfo';
import { getLeagueFromScore } from '../../../../core/blitz/getLeagueFromScore';
import { translator } from '../../../../core/localization/translator';
import getWN8Percentile from '../../../../core/statistics/getWN8Percentile';
import { theme } from '../../../../stitches.config';
import { PERCENTILE_COLORS } from '../../../PercentileIndicator/constants';
import { Glow } from './components/Glow';
import { LEAGUE_COLORS } from './constants';
export interface WN8DisplayProps {
stats: SupplementaryStats;
@@ -16,24 +14,12 @@ export interface WN8DisplayProps {
export function HeroStat({ stats, locale }: WN8DisplayProps) {
const { translate } = translator(locale);
let color: string;
let heroStat: string | number;
let subtitle: string | number | undefined;
if (stats.type === 'random') {
const percentile =
stats.WN8 === undefined
? Percentile.VeryBad
: getWN8Percentile(stats.WN8);
color = PERCENTILE_COLORS[percentile];
heroStat = stats.WN8 === undefined ? '--' : stats.WN8.toFixed(0);
subtitle = translate(`common.wn8_percentile.${percentile}`);
} else {
heroStat = stats.score;
const league = getLeagueFromScore(stats.score);
color = LEAGUE_COLORS[league.index];
subtitle = translate(`common.leagues.${league.name}`);
}
const percentile =
stats.WN8 === undefined ? Percentile.VeryBad : getWN8Percentile(stats.WN8);
const color = PERCENTILE_COLORS[percentile];
const heroStat = stats.WN8 === undefined ? '--' : stats.WN8.toFixed(0);
const subtitle = translate(`common.wn8_percentile.${percentile}`);
return (
<div
@@ -43,28 +29,26 @@ export function HeroStat({ stats, locale }: WN8DisplayProps) {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: stats.type === 'rating' ? -20 : -16,
gap: -16,
}}
>
<Glow color={color} />
<div
style={{
width: stats.type === 'rating' ? 112 : 128,
height: stats.type === 'rating' ? 112 : 80,
width: 128,
height: 80,
backgroundColor: color,
borderRadius: stats.type === 'rating' ? 8 : 40,
borderRadius: 40,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: -4,
transform: stats.type === 'rating' ? 'rotate(45deg)' : '',
}}
>
<div
style={{
transform: stats.type === 'rating' ? 'rotate(-45deg)' : '',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
+6 -16
View File
@@ -83,22 +83,12 @@ export default function AllStatsOverview({
flex: 1,
}}
>
{supplementaryStats.type === 'random' && (
<WN8SurroundingStat
label={t`bot.common.hero_stats.tier`}
value={
supplementaryStats.tier
? supplementaryStats.tier.toFixed(2)
: '--'
}
/>
)}
{supplementaryStats.type === 'rating' && (
<WN8SurroundingStat
label={t`bot.common.hero_stats.tier`}
value="idk lol"
/>
)}
<WN8SurroundingStat
label={t`bot.common.hero_stats.tier`}
value={
supplementaryStats.tier ? supplementaryStats.tier.toFixed(2) : '--'
}
/>
<WN8SurroundingStat
label={t`bot.common.hero_stats.survival`}
value={`${(100 * (stats.survived_battles / stats.battles)).toFixed(
+2 -1
View File
@@ -16,7 +16,8 @@ export default function CommandWrapper({ children, fat }: CommandWrapperProps) {
gap: 16,
width: 480,
color: theme.colors.textHighContrast,
background: theme.colors.appBackground1,
position: 'relative',
background: 'url(https://i.imgur.com/zjwyGv1.png)',
}}
>
{children}
+17
View File
@@ -0,0 +1,17 @@
interface DeltaCaretProps {
delta: number;
}
export function DeltaCaret({ delta }: DeltaCaretProps) {
return (
<img
width={16}
height={16}
src={
delta > 0
? 'https://i.imgur.com/gjI4a5f.png'
: 'https://i.imgur.com/lLGZygN.png'
}
/>
);
}
-6
View File
@@ -23,12 +23,6 @@ export default function GenericAllStats({
const killsToDeathRatio =
stats.frags / (stats.battles - stats.survived_battles);
if (supplementaryStats && supplementaryStats.type !== 'random') {
throw new Error(
'Generic all stats does not support random supplementary stats',
);
}
return (
<Root>
<Row>
+1
View File
@@ -15,6 +15,7 @@ export default function TitleBar({ title, image, description }: TitleBarProps) {
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
padding: '8px 0px',
}}
>
<div
+1 -12
View File
@@ -23,22 +23,11 @@ export interface AllStats {
dropped_capture_points: number;
}
export interface SupplementaryStatsRandom {
type: 'random';
export interface SupplementaryStats {
WN8?: number;
tier?: number;
}
export interface SupplementaryStatsRating {
type: 'rating';
score: number;
delta: number;
}
export type SupplementaryStats =
| SupplementaryStatsRandom
| SupplementaryStatsRating;
export interface ClanStats {
spotted: number;
max_frags_tank_id: number;
+3
View File
@@ -0,0 +1,3 @@
export function normalizeLeagueIcon(url: string) {
return url.startsWith('http') ? url : `https:${url}`;
}
@@ -1,3 +1,3 @@
export function emblemIdToURL(emblemId: number) {
export function emblemURL(emblemId: number) {
return `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${emblemId}.png`;
}
+15 -12
View File
@@ -1,18 +1,21 @@
import { RatingPlayer } from '../../commands/ratingLeaderboard';
import { RatingStats } from '../blitz/getAccountInfo';
import { BkrlComprehensive1Entry, BkrlMinimalEntry } from '../streams/bkrl';
import { BkrlSuperset1Entry } from '../streams/bkrl';
export function deltaBkrlBlitzStats(
a: BkrlComprehensive1Entry,
b: RatingStats,
a: BkrlSuperset1Entry,
b1: RatingStats,
b2: RatingPlayer,
) {
return {
battles: b.battles - a.battles,
damageDealt: b.damage_dealt - a.damageDealt,
damageReceived: b.damage_received - a.damageReceived,
hits: b.hits - a.hits,
kills: b.frags - a.kills,
shots: b.shots - a.shots,
survived: b.survived_battles - a.survived,
wins: b.wins - a.wins,
} satisfies Omit<BkrlComprehensive1Entry, keyof BkrlMinimalEntry>;
battles: b1.battles - a.battles,
damageDealt: b1.damage_dealt - a.damageDealt,
damageReceived: b1.damage_received - a.damageReceived,
hits: b1.hits - a.hits,
kills: b1.frags - a.kills,
shots: b1.shots - a.shots,
survived: b1.survived_battles - a.survived,
wins: b1.wins - a.wins,
score: b2.score - a.score,
} satisfies Omit<BkrlSuperset1Entry, 'id'>;
}
+2 -3
View File
@@ -1,5 +1,5 @@
import { TreeType } from '../../components/Tanks';
import { SupplementaryStatsRandom } from '../blitz/getAccountInfo';
import { SupplementaryStats } from '../blitz/getAccountInfo';
import { tankDefinitions } from '../blitzkrieg/tankDefinitions';
import { DiffedTankStats } from '../blitzstars/getStatsInPeriod';
import { tankAverages } from '../blitzstars/tankAverages';
@@ -51,7 +51,6 @@ export async function filterStats(
0,
);
const supplementary = {
type: 'random',
WN8:
filteredOrder.reduce<number>(
(accumulator, id) =>
@@ -70,7 +69,7 @@ export async function filterStats(
: accumulator,
0,
) / battlesOfTanksWithTankDefinition,
} satisfies SupplementaryStatsRandom;
} satisfies SupplementaryStats;
return { stats, supplementary, filteredOrder };
}
+8 -8
View File
@@ -3,7 +3,7 @@ import { ReadStream, WriteStream } from './buffer';
export enum BkrlFormat {
Minimal,
Comprehensive1,
Superset1,
}
export interface BkrlMinimalEntry {
@@ -11,7 +11,7 @@ export interface BkrlMinimalEntry {
score: number;
}
export interface BkrlComprehensive1Entry {
export interface BkrlSuperset1Entry {
id: number;
score: number;
@@ -34,8 +34,8 @@ export type BkrlDiscriminatedEntries =
entries: BkrlMinimalEntry[];
}
| {
format: BkrlFormat.Comprehensive1;
entries: BkrlComprehensive1Entry[];
format: BkrlFormat.Superset1;
entries: BkrlSuperset1Entry[];
};
export class BkrlReadStream extends ReadStream {
@@ -61,9 +61,9 @@ export class BkrlReadStream extends ReadStream {
};
}
case BkrlFormat.Comprehensive1: {
case BkrlFormat.Superset1: {
return {
format: BkrlFormat.Comprehensive1,
format: BkrlFormat.Superset1,
entries: times(
header.count,
() =>
@@ -82,7 +82,7 @@ export class BkrlReadStream extends ReadStream {
hits: this.uint32(),
kills: this.uint32(),
}) satisfies BkrlComprehensive1Entry,
}) satisfies BkrlSuperset1Entry,
),
};
}
@@ -123,7 +123,7 @@ export class BkrlWriteStream extends WriteStream {
break;
}
case BkrlFormat.Comprehensive1: {
case BkrlFormat.Superset1: {
discriminatedEntries.entries.forEach((entry) => {
this.uint32(entry.id);
this.uint16(entry.score);
+4
View File
@@ -2,8 +2,10 @@ import {
amberDark,
amberDarkA,
blueDark,
greenDark,
orangeDark,
purpleDark,
redDark,
slate,
slateDark,
slateDarkA,
@@ -26,6 +28,8 @@ export const theme = {
...createColors(purpleDark, 'purple'),
...createColors(blueDark, 'blue'),
...createColors(tealDark, 'teal'),
...createColors(greenDark, 'green'),
...createColors(redDark, 'red'),
},
borderStyles: {