mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: migrate all
This commit is contained in:
1 parent
5594c0ec3b
commit
320a764d45
38 files changed
+624
-592
No files matched your search
@@ -193,9 +193,9 @@ export async function renderBreakdown(
|
|||||||
title={
|
title={
|
||||||
tankDefinition
|
tankDefinition
|
||||||
? unwrap(tankDefinition.name)
|
? unwrap(tankDefinition.name)
|
||||||
: literals(strings.bot.commands.breakdown.body.unknown_tank, [
|
: literals(strings.bot.commands.breakdown.body.unknown_tank, {
|
||||||
`${id}`,
|
id,
|
||||||
])
|
})
|
||||||
}
|
}
|
||||||
stats={[
|
stats={[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,31 +8,31 @@ import {
|
|||||||
TANK_ICONS,
|
TANK_ICONS,
|
||||||
TankType,
|
TankType,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { escapeMarkdown, Locale } from 'discord.js';
|
import { escapeMarkdown, Locale } from "discord.js";
|
||||||
import { CommandWrapper } from '../components/CommandWrapper';
|
import { CommandWrapper } from "../components/CommandWrapper";
|
||||||
import { TitleBar } from '../components/TitleBar';
|
import { TitleBar } from "../components/TitleBar";
|
||||||
import { resolveTankId } from '../core/blitz/resolveTankId';
|
import { resolveTankId } from "../core/blitz/resolveTankId";
|
||||||
import { buildTechTreeLine } from '../core/blitzkit/buildTechTreeLine';
|
import { buildTechTreeLine } from "../core/blitzkit/buildTechTreeLine";
|
||||||
import { iconPng } from '../core/blitzkit/iconPng';
|
import { iconPng } from "../core/blitzkit/iconPng";
|
||||||
import { tankDefinitions } from '../core/blitzkit/nonBlockingPromises';
|
import { tankDefinitions } from "../core/blitzkit/nonBlockingPromises";
|
||||||
import { resolveAncestry } from '../core/blitzkit/resolveAncestry';
|
import { resolveAncestry } from "../core/blitzkit/resolveAncestry";
|
||||||
import { tankIconPng } from '../core/blitzkit/tankIconPng';
|
import { tankIconPng } from "../core/blitzkit/tankIconPng";
|
||||||
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
||||||
import { autocompleteTanks } from '../core/discord/autocompleteTanks';
|
import { autocompleteTanks } from "../core/discord/autocompleteTanks";
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
||||||
import { localizationObject } from '../core/discord/localizationObject';
|
import { localizationObject } from "../core/discord/localizationObject";
|
||||||
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
import { resolvePlayerFromCommand } from "../core/discord/resolvePlayerFromCommand";
|
||||||
import { translator } from '../core/localization/translator';
|
import { translator } from "../core/localization/translator";
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from "../events/interactionCreate";
|
||||||
import { theme } from '../stitches.config';
|
import { theme } from "../stitches.config";
|
||||||
|
|
||||||
export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand('research')
|
command: createLocalizedCommand("research")
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(strings.bot.commands.research.options.target_tank.name)
|
.setName(strings.bot.commands.research.options.target_tank.name)
|
||||||
@@ -41,20 +41,20 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.target_tank.name,
|
strings.bot.commands.research.options.target_tank.name,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.research.options.target_tank.description,
|
strings.bot.commands.research.options.target_tank.description
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.target_tank.description,
|
strings.bot.commands.research.options.target_tank.description
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true)
|
||||||
.setRequired(true),
|
.setRequired(true)
|
||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
@@ -64,20 +64,20 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.starting_tank.name,
|
strings.bot.commands.research.options.starting_tank.name,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.research.options.starting_tank.description,
|
strings.bot.commands.research.options.starting_tank.description
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.starting_tank.description,
|
strings.bot.commands.research.options.starting_tank.description
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true)
|
||||||
.setRequired(false),
|
.setRequired(false)
|
||||||
)
|
)
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
@@ -85,11 +85,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { strings, unwrap } = translator(interaction.locale);
|
const { strings, unwrap } = translator(interaction.locale);
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
const targetTankId = await resolveTankId(
|
const targetTankId = await resolveTankId(
|
||||||
interaction.options.getString('target-tank', true),
|
interaction.options.getString("target-tank", true),
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
const startingTankRaw = interaction.options.getString('starting-tank');
|
const startingTankRaw = interaction.options.getString("starting-tank");
|
||||||
const targetTank = awaitedTankDefinitions.tanks[targetTankId];
|
const targetTank = awaitedTankDefinitions.tanks[targetTankId];
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
let startingTankId: number;
|
let startingTankId: number;
|
||||||
@@ -99,46 +99,45 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
startingTankId = await resolveTankId(
|
startingTankId = await resolveTankId(
|
||||||
startingTankRaw,
|
startingTankRaw,
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
true,
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetTankId === startingTankId) {
|
if (targetTankId === startingTankId) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.start_end_equal,
|
strings.bot.commands.research.errors.start_end_equal,
|
||||||
[escapeMarkdown(unwrap(targetTank.name))],
|
{ tank: escapeMarkdown(unwrap(targetTank.name)) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const startingTank = awaitedTankDefinitions.tanks[startingTankId];
|
const startingTank = awaitedTankDefinitions.tanks[startingTankId];
|
||||||
|
|
||||||
if (startingTank.tier > targetTank.tier) {
|
if (startingTank.tier > targetTank.tier) {
|
||||||
return literals(strings.bot.commands.research.errors.unordered, [
|
return literals(strings.bot.commands.research.errors.unordered, {
|
||||||
escapeMarkdown(unwrap(startingTank.name)),
|
tank_1: escapeMarkdown(unwrap(startingTank.name)),
|
||||||
escapeMarkdown(unwrap(targetTank.name)),
|
tank_2: escapeMarkdown(unwrap(targetTank.name)),
|
||||||
escapeMarkdown(unwrap(targetTank.name)),
|
});
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetTankAncestry.includes(startingTankId)) {
|
if (!targetTankAncestry.includes(startingTankId)) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.tanks_not_on_line,
|
strings.bot.commands.research.errors.tanks_not_on_line,
|
||||||
[
|
{
|
||||||
escapeMarkdown(unwrap(targetTank.name)),
|
tank_1: escapeMarkdown(unwrap(targetTank.name)),
|
||||||
escapeMarkdown(unwrap(startingTank.name)),
|
tank_2: escapeMarkdown(unwrap(startingTank.name)),
|
||||||
],
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (targetTank.type !== TankType.RESEARCHABLE) {
|
if (targetTank.type !== TankType.RESEARCHABLE) {
|
||||||
return literals(strings.bot.commands.research.errors.non_tech_tree, [
|
return literals(strings.bot.commands.research.errors.non_tech_tree, {
|
||||||
escapeMarkdown(unwrap(targetTank.name)),
|
tank: escapeMarkdown(unwrap(targetTank.name)),
|
||||||
]);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetTank.tier === 1) {
|
if (targetTank.tier === 1) {
|
||||||
return literals(strings.bot.commands.research.errors.tier_1, [
|
return literals(strings.bot.commands.research.errors.tier_1, {
|
||||||
escapeMarkdown(unwrap(targetTank.name)),
|
tank: escapeMarkdown(unwrap(targetTank.name)),
|
||||||
]);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankStats = await getTankStats(region, id);
|
const tankStats = await getTankStats(region, id);
|
||||||
@@ -150,18 +149,18 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.already_researched,
|
strings.bot.commands.research.errors.already_researched,
|
||||||
[escapeMarkdown(unwrap(targetTank.name))],
|
{ tank: escapeMarkdown(unwrap(targetTank.name)) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAncestor = targetTankAncestry.find(
|
const foundAncestor = targetTankAncestry.find(
|
||||||
(id) =>
|
(id) =>
|
||||||
awaitedTankDefinitions.tanks[id].tier === 1 ||
|
awaitedTankDefinitions.tanks[id].tier === 1 ||
|
||||||
tankStats.some(({ tank_id }) => tank_id === id),
|
tankStats.some(({ tank_id }) => tank_id === id)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundAncestor === undefined) {
|
if (foundAncestor === undefined) {
|
||||||
throw new Error('No first owned tech tree found.');
|
throw new Error("No first owned tech tree found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
startingTankId = foundAncestor;
|
startingTankId = foundAncestor;
|
||||||
@@ -169,12 +168,12 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
const techTreeLine = await buildTechTreeLine(
|
const techTreeLine = await buildTechTreeLine(
|
||||||
startingTankId,
|
startingTankId,
|
||||||
targetTankId,
|
targetTankId
|
||||||
);
|
);
|
||||||
|
|
||||||
const line = [...techTreeLine, startingTankId].reverse();
|
const line = [...techTreeLine, startingTankId].reverse();
|
||||||
const { nickname } = await getAccountInfo(region, id);
|
const { nickname } = await getAccountInfo(region, id);
|
||||||
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
const clan = (await getClanAccountInfo(region, id, ["clan"]))?.clan;
|
||||||
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
|
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
|
||||||
const costs = await Promise.all(
|
const costs = await Promise.all(
|
||||||
line.map(async (id) => {
|
line.map(async (id) => {
|
||||||
@@ -187,18 +186,18 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tank.turrets.forEach((turret) => {
|
tank.turrets.forEach((turret) => {
|
||||||
if (
|
if (
|
||||||
turret.research_cost !== undefined &&
|
turret.research_cost !== undefined &&
|
||||||
typeof turret.research_cost.research_cost_type!.value === 'number'
|
typeof turret.research_cost.research_cost_type!.value === "number"
|
||||||
) {
|
) {
|
||||||
turretXps.set(
|
turretXps.set(
|
||||||
turret.id,
|
turret.id,
|
||||||
turret.research_cost.research_cost_type!.value,
|
turret.research_cost.research_cost_type!.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
turret.guns.forEach((gun) => {
|
turret.guns.forEach((gun) => {
|
||||||
if (
|
if (
|
||||||
gun.research_cost !== undefined &&
|
gun.research_cost !== undefined &&
|
||||||
typeof gun.research_cost.research_cost_type!.value === 'number'
|
typeof gun.research_cost.research_cost_type!.value === "number"
|
||||||
) {
|
) {
|
||||||
gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
|
gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
|
||||||
}
|
}
|
||||||
@@ -208,11 +207,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tank.engines.forEach((engine) => {
|
tank.engines.forEach((engine) => {
|
||||||
if (
|
if (
|
||||||
engine.research_cost !== undefined &&
|
engine.research_cost !== undefined &&
|
||||||
typeof engine.research_cost.research_cost_type!.value === 'number'
|
typeof engine.research_cost.research_cost_type!.value === "number"
|
||||||
) {
|
) {
|
||||||
engineXps.set(
|
engineXps.set(
|
||||||
engine.id,
|
engine.id,
|
||||||
engine.research_cost.research_cost_type!.value,
|
engine.research_cost.research_cost_type!.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -220,11 +219,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tank.tracks.forEach((track) => {
|
tank.tracks.forEach((track) => {
|
||||||
if (
|
if (
|
||||||
track.research_cost !== undefined &&
|
track.research_cost !== undefined &&
|
||||||
typeof track.research_cost.research_cost_type!.value === 'number'
|
typeof track.research_cost.research_cost_type!.value === "number"
|
||||||
) {
|
) {
|
||||||
trackXps.set(
|
trackXps.set(
|
||||||
track.id,
|
track.id,
|
||||||
track.research_cost.research_cost_type!.value,
|
track.research_cost.research_cost_type!.value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -249,10 +248,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
purchase: tank.price.value,
|
purchase: tank.price.value,
|
||||||
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
||||||
(a, b) => a + 3 * b,
|
(a, b) => a + 3 * b,
|
||||||
0,
|
0
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const image = (
|
const image = (
|
||||||
@@ -267,14 +266,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: theme.colors.appBackground2,
|
backgroundColor: theme.colors.appBackground2,
|
||||||
border: theme.borderStyles.subtle,
|
border: theme.borderStyles.subtle,
|
||||||
@@ -285,10 +284,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
position: 'relative',
|
position: "relative",
|
||||||
right: 24,
|
right: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -304,23 +303,23 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
gap: 8,
|
gap: 8,
|
||||||
padding: '16px 0',
|
padding: "16px 0",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="XP icon"
|
alt="XP icon"
|
||||||
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
src={await iconPng(asset("icons/currencies/xp.webp"))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -330,7 +329,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Experience:{' '}
|
Experience:{" "}
|
||||||
{costs
|
{costs
|
||||||
.reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0)
|
.reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0)
|
||||||
.toLocaleString(interaction.locale)}
|
.toLocaleString(interaction.locale)}
|
||||||
@@ -339,14 +338,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
src={await iconPng(asset("icons/currencies/silver.webp"))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -356,7 +355,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Purchase:{' '}
|
Purchase:{" "}
|
||||||
{costs
|
{costs
|
||||||
.reduce((a, b) => a + b.purchase, 0)
|
.reduce((a, b) => a + b.purchase, 0)
|
||||||
.toLocaleString(interaction.locale)}
|
.toLocaleString(interaction.locale)}
|
||||||
@@ -365,14 +364,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
src={await iconPng(asset("icons/currencies/silver.webp"))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -382,7 +381,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Equipment:{' '}
|
Equipment:{" "}
|
||||||
{costs
|
{costs
|
||||||
.reduce((a, b) => a + b.equipment, 0)
|
.reduce((a, b) => a + b.equipment, 0)
|
||||||
.toLocaleString(interaction.locale)}
|
.toLocaleString(interaction.locale)}
|
||||||
@@ -399,7 +398,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: theme.colors.appBackground2,
|
backgroundColor: theme.colors.appBackground2,
|
||||||
border: theme.borderStyles.subtle,
|
border: theme.borderStyles.subtle,
|
||||||
@@ -408,9 +407,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: 48,
|
width: 48,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -426,9 +425,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
position: 'relative',
|
position: "relative",
|
||||||
right: '5%',
|
right: "5%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -437,23 +436,23 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
style={{
|
style={{
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
objectFit: 'contain',
|
objectFit: "contain",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
left: '25%',
|
left: "25%",
|
||||||
top: 0,
|
top: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
left: '50%',
|
left: "50%",
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -476,23 +475,23 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
gap: 8,
|
gap: 8,
|
||||||
padding: '16px 0',
|
padding: "16px 0",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="XP icon"
|
alt="XP icon"
|
||||||
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
src={await iconPng(asset("icons/currencies/xp.webp"))}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
/>
|
/>
|
||||||
@@ -504,11 +503,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.research,
|
strings.bot.commands.research.body.research,
|
||||||
[
|
{
|
||||||
`${costs[index].research?.toLocaleString(
|
cost: costs[index].research?.toLocaleString(
|
||||||
interaction.locale,
|
interaction.locale
|
||||||
)}`,
|
),
|
||||||
],
|
}
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,15 +515,15 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
{costs[index].upgrades > 0 && (
|
{costs[index].upgrades > 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Free XP icon"
|
alt="Free XP icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset('icons/currencies/free-xp.webp'),
|
asset("icons/currencies/free-xp.webp")
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -537,7 +536,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.upgrades,
|
strings.bot.commands.research.body.upgrades,
|
||||||
[costs[index].upgrades.toLocaleString()],
|
{ cost: costs[index].upgrades.toLocaleString() }
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -545,15 +544,15 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset('icons/currencies/silver.webp'),
|
asset("icons/currencies/silver.webp")
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -566,26 +565,26 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.purchase,
|
strings.bot.commands.research.body.purchase,
|
||||||
[
|
{
|
||||||
costs[index].purchase.toLocaleString(
|
cost: costs[index].purchase.toLocaleString(
|
||||||
interaction.locale,
|
interaction.locale
|
||||||
),
|
),
|
||||||
],
|
}
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset('icons/currencies/silver.webp'),
|
asset("icons/currencies/silver.webp")
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -598,18 +597,18 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.equipment,
|
strings.bot.commands.research.body.equipment,
|
||||||
[
|
{
|
||||||
costs[index].equipment.toLocaleString(
|
cost: costs[index].equipment.toLocaleString(
|
||||||
interaction.locale,
|
interaction.locale
|
||||||
),
|
),
|
||||||
],
|
}
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}),
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CommandWrapper>
|
</CommandWrapper>
|
||||||
@@ -621,7 +620,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
autocomplete(interaction) {
|
autocomplete(interaction) {
|
||||||
autocompleteTanks(interaction, true, ['target-tank', 'starting-tank']);
|
autocompleteTanks(interaction, true, ["target-tank", "starting-tank"]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
import { ClanList, fetchBlitz, Region } from '@blitzkit/core';
|
import { ClanList, fetchBlitz, Region } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Locale } from 'discord.js';
|
import { Locale } from "discord.js";
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from "markdown-escape";
|
||||||
import { addClanChoices } from '../core/discord/addClanChoices';
|
import { addClanChoices } from "../core/discord/addClanChoices";
|
||||||
import { addRegionChoices } from '../core/discord/addRegionChoices';
|
import { addRegionChoices } from "../core/discord/addRegionChoices";
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
||||||
import { embedInfo } from '../core/discord/embedInfo';
|
import { embedInfo } from "../core/discord/embedInfo";
|
||||||
import { localizationObject } from '../core/discord/localizationObject';
|
import { localizationObject } from "../core/discord/localizationObject";
|
||||||
import { translator } from '../core/localization/translator';
|
import { translator } from "../core/localization/translator";
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from "../events/interactionCreate";
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 25;
|
const DEFAULT_LIMIT = 25;
|
||||||
|
|
||||||
export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand('search-clans')
|
command: createLocalizedCommand("search-clans")
|
||||||
.addStringOption(addRegionChoices)
|
.addStringOption(addRegionChoices)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
addClanChoices(option).setAutocomplete(false),
|
addClanChoices(option).setAutocomplete(false)
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) => {
|
.addIntegerOption((option) => {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
@@ -28,21 +28,21 @@ export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) => strings.bot.commands.search_clans.options.limit.name,
|
(strings) => strings.bot.commands.search_clans.options.limit.name,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
literals(
|
literals(
|
||||||
strings.bot.commands.search_clans.options.limit.description,
|
strings.bot.commands.search_clans.options.limit.description,
|
||||||
[`${DEFAULT_LIMIT}`],
|
{ limit: DEFAULT_LIMIT }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.search_clans.options.limit.description,
|
strings.bot.commands.search_clans.options.limit.description,
|
||||||
[`${DEFAULT_LIMIT}`],
|
{ limit: DEFAULT_LIMIT }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setMinValue(1)
|
.setMinValue(1)
|
||||||
.setMaxValue(100);
|
.setMaxValue(100);
|
||||||
@@ -50,24 +50,24 @@ export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const region = interaction.options.getString('region') as Region;
|
const region = interaction.options.getString("region") as Region;
|
||||||
const clan = interaction.options.getString('clan')!;
|
const clan = interaction.options.getString("clan")!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
const limit = interaction.options.getInteger("limit") ?? DEFAULT_LIMIT;
|
||||||
const clanList = await fetchBlitz<ClanList>(region, 'clans/list', {
|
const clanList = await fetchBlitz<ClanList>(region, "clans/list", {
|
||||||
search: clan,
|
search: clan,
|
||||||
limit,
|
limit,
|
||||||
});
|
});
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
literals(strings.bot.commands.search_clans.body.title, [
|
literals(strings.bot.commands.search_clans.body.title, {
|
||||||
markdownEscape(clan),
|
search: markdownEscape(clan),
|
||||||
strings.common.regions.normal[region],
|
region: strings.common.regions.normal[region],
|
||||||
]),
|
}),
|
||||||
clanList.length === 0
|
clanList.length === 0
|
||||||
? strings.bot.commands.search_clans.body.no_results
|
? strings.bot.commands.search_clans.body.no_results
|
||||||
: `\`\`\`\n${clanList
|
: `\`\`\`\n${clanList
|
||||||
.map((clan) => `${clan.name} [${clan.tag}]`)
|
.map((clan) => `${clan.name} [${clan.tag}]`)
|
||||||
.join('\n')}\n\`\`\``,
|
.join("\n")}\n\`\`\``
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ import {
|
|||||||
fetchBlitz,
|
fetchBlitz,
|
||||||
Region,
|
Region,
|
||||||
usernamePattern,
|
usernamePattern,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Locale } from 'discord.js';
|
import { Locale } from "discord.js";
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from "markdown-escape";
|
||||||
import { addRegionChoices } from '../core/discord/addRegionChoices';
|
import { addRegionChoices } from "../core/discord/addRegionChoices";
|
||||||
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
import { addUsernameChoices } from "../core/discord/addUsernameChoices";
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
|
||||||
import { embedInfo } from '../core/discord/embedInfo';
|
import { embedInfo } from "../core/discord/embedInfo";
|
||||||
import { localizationObject } from '../core/discord/localizationObject';
|
import { localizationObject } from "../core/discord/localizationObject";
|
||||||
import { translator } from '../core/localization/translator';
|
import { translator } from "../core/localization/translator";
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from "../events/interactionCreate";
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 25;
|
const DEFAULT_LIMIT = 25;
|
||||||
|
|
||||||
@@ -21,10 +21,10 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand('search-players')
|
command: createLocalizedCommand("search-players")
|
||||||
.addStringOption(addRegionChoices)
|
.addStringOption(addRegionChoices)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
addUsernameChoices(option).setAutocomplete(false).setRequired(true),
|
addUsernameChoices(option).setAutocomplete(false).setRequired(true)
|
||||||
)
|
)
|
||||||
.addIntegerOption((option) =>
|
.addIntegerOption((option) =>
|
||||||
option
|
option
|
||||||
@@ -34,49 +34,49 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.search_players.options.limit.name,
|
strings.bot.commands.search_players.options.limit.name,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
literals(
|
literals(
|
||||||
strings.bot.commands.search_players.options.limit.description,
|
strings.bot.commands.search_players.options.limit.description,
|
||||||
[`${DEFAULT_LIMIT}`],
|
{ limit: DEFAULT_LIMIT }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.search_players.options.limit.description,
|
strings.bot.commands.search_players.options.limit.description,
|
||||||
[`${DEFAULT_LIMIT}`],
|
{ limit: DEFAULT_LIMIT }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setMinValue(1)
|
.setMinValue(1)
|
||||||
.setMaxValue(100),
|
.setMaxValue(100)
|
||||||
),
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const region = interaction.options.getString('region') as Region;
|
const region = interaction.options.getString("region") as Region;
|
||||||
const name = interaction.options.getString('username')!;
|
const name = interaction.options.getString("username")!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? 25;
|
const limit = interaction.options.getInteger("limit") ?? 25;
|
||||||
const trimmedSearch = name.trim();
|
const trimmedSearch = name.trim();
|
||||||
const players = usernamePattern.test(trimmedSearch)
|
const players = usernamePattern.test(trimmedSearch)
|
||||||
? await fetchBlitz<AccountList>(region, 'account/list', {
|
? await fetchBlitz<AccountList>(region, "account/list", {
|
||||||
search: trimmedSearch,
|
search: trimmedSearch,
|
||||||
limit,
|
limit,
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
literals(strings.bot.commands.search_players.body.title, [
|
literals(strings.bot.commands.search_players.body.title, {
|
||||||
markdownEscape(trimmedSearch),
|
search: markdownEscape(trimmedSearch),
|
||||||
strings.common.regions.normal[region],
|
region: strings.common.regions.normal[region],
|
||||||
]),
|
}),
|
||||||
`\`\`\`${
|
`\`\`\`${
|
||||||
players.length === 0
|
players.length === 0
|
||||||
? strings.bot.commands.search_players.body.no_results
|
? strings.bot.commands.search_players.body.no_results
|
||||||
: players.map((player) => player.nickname).join('\n')
|
: players.map((player) => player.nickname).join("\n")
|
||||||
}\`\`\``,
|
}\`\`\``
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import { SEARCH_KEYS } from '@blitzkit/core';
|
import { SEARCH_KEYS } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Locale } from 'discord.js';
|
import { Locale } from "discord.js";
|
||||||
import { go } from 'fuzzysort';
|
import { go } from "fuzzysort";
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from "markdown-escape";
|
||||||
import { tankDefinitions, tankNames } from '../blitzkit/nonBlockingPromises';
|
import { tankDefinitions, tankNames } from "../blitzkit/nonBlockingPromises";
|
||||||
import { UserError } from '../blitzkit/userError';
|
import { UserError } from "../blitzkit/userError";
|
||||||
import { tankNamesTechTreeOnly } from '../discord/autocompleteTanks';
|
import { tankNamesTechTreeOnly } from "../discord/autocompleteTanks";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
|
|
||||||
export async function resolveTankId(
|
export async function resolveTankId(
|
||||||
tank: string | number,
|
tank: string | number,
|
||||||
locale: Locale,
|
locale: Locale,
|
||||||
techTreeOnly = false,
|
techTreeOnly = false
|
||||||
) {
|
) {
|
||||||
const { strings } = translator(locale);
|
const { strings } = translator(locale);
|
||||||
const number = typeof tank === 'string' ? Number(tank) : tank;
|
const number = typeof tank === "string" ? Number(tank) : tank;
|
||||||
|
|
||||||
if (Number.isNaN(number)) {
|
if (Number.isNaN(number)) {
|
||||||
const searchResult = go(
|
const searchResult = go(
|
||||||
`${tank}`,
|
`${tank}`,
|
||||||
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
|
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
|
||||||
{ keys: SEARCH_KEYS, limit: 1 },
|
{ keys: SEARCH_KEYS, limit: 1 }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (searchResult.length === 0) {
|
if (searchResult.length === 0) {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
literals(strings.bot.common.errors.tank_name_not_found, [
|
literals(strings.bot.common.errors.tank_name_not_found, {
|
||||||
markdownEscape(`${tank}`),
|
search: markdownEscape(`${tank}`),
|
||||||
]),
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return searchResult[0].obj.id;
|
return searchResult[0].obj.id;
|
||||||
@@ -39,7 +39,9 @@ export async function resolveTankId(
|
|||||||
return number;
|
return number;
|
||||||
} else {
|
} else {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
literals(strings.bot.common.errors.tank_id_not_found, [`${number}`]),
|
literals(strings.bot.common.errors.tank_id_not_found, {
|
||||||
|
search: number,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from '@blitzkit/core';
|
import { fetchTankDefinitions, TIER_ROMAN_NUMERALS } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Locale } from 'discord.js';
|
import { Locale } from "discord.js";
|
||||||
import { StatFilters } from '../blitzstars/filterStats';
|
import { StatFilters } from "../blitzstars/filterStats";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
|
|
||||||
export async function filtersToDescription(
|
export async function filtersToDescription(
|
||||||
{ nation, tier, type, class: tankClass, tank }: StatFilters,
|
{ nation, tier, type, class: tankClass, tank }: StatFilters,
|
||||||
locale: Locale,
|
locale: Locale
|
||||||
) {
|
) {
|
||||||
const { strings, bkLocale } = translator(locale);
|
const { strings, bkLocale } = translator(locale);
|
||||||
const awaitedTankDefinitions = await fetchTankDefinitions();
|
const awaitedTankDefinitions = await fetchTankDefinitions();
|
||||||
@@ -18,11 +18,13 @@ export async function filtersToDescription(
|
|||||||
info.push((strings.common.nations as Record<string, string>)[nation]);
|
info.push((strings.common.nations as Record<string, string>)[nation]);
|
||||||
if (tier) {
|
if (tier) {
|
||||||
info.push(
|
info.push(
|
||||||
literals(strings.bot.common.filters.tier, [TIER_ROMAN_NUMERALS[tier]]),
|
literals(strings.bot.common.filters.tier, {
|
||||||
|
tier: TIER_ROMAN_NUMERALS[tier],
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (tankClass) info.push(strings.common.tank_class_short[tankClass]);
|
if (tankClass) info.push(strings.common.tank_class_short[tankClass]);
|
||||||
if (type) info.push(strings.common.tree_type[type]);
|
if (type) info.push(strings.common.tree_type[type]);
|
||||||
|
|
||||||
return info.length === 0 ? strings.bot.common.filters.none : info.join(', ');
|
return info.length === 0 ? strings.bot.common.filters.none : info.join(", ");
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { numberToRomanNumeral } from '@blitzkit/core';
|
import { numberToRomanNumeral } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import {
|
import {
|
||||||
APIApplicationCommandOptionChoice,
|
APIApplicationCommandOptionChoice,
|
||||||
Locale,
|
Locale,
|
||||||
SlashCommandStringOption,
|
SlashCommandStringOption,
|
||||||
} from 'discord.js';
|
} from "discord.js";
|
||||||
import { range } from 'lodash-es';
|
import { range } from "lodash-es";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
import { localizationObject } from './localizationObject';
|
import { localizationObject } from "./localizationObject";
|
||||||
|
|
||||||
export function addTierChoices(option: SlashCommandStringOption) {
|
export function addTierChoices(option: SlashCommandStringOption) {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
@@ -18,30 +18,30 @@ export function addTierChoices(option: SlashCommandStringOption) {
|
|||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) => strings.bot.common.options.tier.name,
|
(strings) => strings.bot.common.options.tier.name,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setDescription(strings.bot.common.options.tier.description)
|
.setDescription(strings.bot.common.options.tier.description)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) => strings.bot.common.options.tier.description,
|
(strings) => strings.bot.common.options.tier.description
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
.setChoices(
|
.setChoices(
|
||||||
...range(10, 0).map(
|
...range(10, 0).map(
|
||||||
(tier) =>
|
(tier) =>
|
||||||
({
|
({
|
||||||
name: literals(strings.bot.common.options.tier.choices.tier, [
|
name: literals(strings.bot.common.options.tier.choices.tier, {
|
||||||
`${tier}`,
|
tier_1: tier,
|
||||||
numberToRomanNumeral(tier),
|
tier_2: numberToRomanNumeral(tier),
|
||||||
]),
|
}),
|
||||||
value: `${tier}`,
|
value: `${tier}`,
|
||||||
name_localizations: localizationObject(
|
name_localizations: localizationObject(
|
||||||
(strings) => strings.bot.common.options.tier.choices.tier,
|
(strings) => strings.bot.common.options.tier.choices.tier,
|
||||||
[`${tier}`, numberToRomanNumeral(tier)],
|
{ tier_1: tier, tier_2: numberToRomanNumeral(tier) }
|
||||||
),
|
|
||||||
}) satisfies APIApplicationCommandOptionChoice,
|
|
||||||
),
|
),
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.setRequired(true);
|
.setRequired(true);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Locale } from 'discord.js';
|
import { Locale } from "discord.js";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
import { PeriodSize } from './addPeriodSubCommands';
|
import { PeriodSize } from "./addPeriodSubCommands";
|
||||||
|
|
||||||
export function getPeriodOptionName(period: PeriodSize, locale: Locale) {
|
export function getPeriodOptionName(period: PeriodSize, locale: Locale) {
|
||||||
const { strings } = translator(locale);
|
const { strings } = translator(locale);
|
||||||
if (period === 'career') return strings.bot.common.periods.career;
|
if (period === "career") return strings.bot.common.periods.career;
|
||||||
if (period === 'today') return strings.bot.common.periods.today;
|
if (period === "today") return strings.bot.common.periods.today;
|
||||||
return literals(strings.bot.common.periods.days, [period]);
|
return literals(strings.bot.common.periods.days, { days: period });
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,35 @@
|
|||||||
import { Region } from '@blitzkit/core';
|
import { Region } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { ChatInputCommandInteraction } from 'discord.js';
|
import { ChatInputCommandInteraction } from "discord.js";
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from "markdown-escape";
|
||||||
import { searchClansAcrossRegionsBotWrapper } from '../blitz/searchClansAcrossRegionsBotWrapper';
|
import { searchClansAcrossRegionsBotWrapper } from "../blitz/searchClansAcrossRegionsBotWrapper";
|
||||||
import { UserError } from '../blitzkit/userError';
|
import { UserError } from "../blitzkit/userError";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
|
|
||||||
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
|
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
|
||||||
|
|
||||||
export async function resolveClanFromCommand(
|
export async function resolveClanFromCommand(
|
||||||
interaction: ChatInputCommandInteraction,
|
interaction: ChatInputCommandInteraction
|
||||||
) {
|
) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const clan = interaction.options.getString('clan', true);
|
const clan = interaction.options.getString("clan", true);
|
||||||
|
|
||||||
if (serverAndIdPattern.test(clan)) {
|
if (serverAndIdPattern.test(clan)) {
|
||||||
const [server, accountId] = clan.split('/');
|
const [server, accountId] = clan.split("/");
|
||||||
return { region: server as Region, id: Number(accountId) };
|
return { region: server as Region, id: Number(accountId) };
|
||||||
} else {
|
} else {
|
||||||
const accounts = await searchClansAcrossRegionsBotWrapper(
|
const accounts = await searchClansAcrossRegionsBotWrapper(
|
||||||
clan,
|
clan,
|
||||||
interaction.locale,
|
interaction.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
if (accounts[0]) {
|
if (accounts[0]) {
|
||||||
return { region: accounts[0].region, id: accounts[0].clan_id };
|
return { region: accounts[0].region, id: accounts[0].clan_id };
|
||||||
} else {
|
} else {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
literals(strings.bot.common.errors.clan_not_found, [
|
literals(strings.bot.common.errors.clan_not_found, {
|
||||||
markdownEscape(clan),
|
search: markdownEscape(clan),
|
||||||
]),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { getTimeDaysAgo, Region } from '@blitzkit/core';
|
import { getTimeDaysAgo, Region } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
import { CacheType, ChatInputCommandInteraction } from "discord.js";
|
||||||
import { getPeriodNow } from '../blitzkit/getPeriodNow';
|
import { getPeriodNow } from "../blitzkit/getPeriodNow";
|
||||||
import { getPeriodStart } from '../blitzkit/getPeriodStart';
|
import { getPeriodStart } from "../blitzkit/getPeriodStart";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
import { PeriodType } from './addPeriodSubCommands';
|
import { PeriodType } from "./addPeriodSubCommands";
|
||||||
import { getPeriodOptionName } from './getPeriodOptionName';
|
import { getPeriodOptionName } from "./getPeriodOptionName";
|
||||||
|
|
||||||
export interface ResolvedPeriod {
|
export interface ResolvedPeriod {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -16,7 +16,7 @@ export interface ResolvedPeriod {
|
|||||||
export function resolvePeriodFromCommand(
|
export function resolvePeriodFromCommand(
|
||||||
region: Region,
|
region: Region,
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
forcedPeriod?: PeriodType,
|
forcedPeriod?: PeriodType
|
||||||
) {
|
) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
let name: string;
|
let name: string;
|
||||||
@@ -26,16 +26,16 @@ export function resolvePeriodFromCommand(
|
|||||||
? forcedPeriod
|
? forcedPeriod
|
||||||
: (interaction.options.getSubcommand() as PeriodType);
|
: (interaction.options.getSubcommand() as PeriodType);
|
||||||
|
|
||||||
if (periodSubcommand === 'custom') {
|
if (periodSubcommand === "custom") {
|
||||||
const startOption = interaction.options.getInteger('start', true);
|
const startOption = interaction.options.getInteger("start", true);
|
||||||
const endOption = interaction.options.getInteger('end', true);
|
const endOption = interaction.options.getInteger("end", true);
|
||||||
const startDaysAgoMin = Math.min(startOption, endOption);
|
const startDaysAgoMin = Math.min(startOption, endOption);
|
||||||
const endDaysAgoMax = Math.max(startOption, endOption);
|
const endDaysAgoMax = Math.max(startOption, endOption);
|
||||||
|
|
||||||
name = literals(strings.bot.common.periods.custom, [
|
name = literals(strings.bot.common.periods.custom, {
|
||||||
`${startDaysAgoMin}`,
|
day_1: startDaysAgoMin,
|
||||||
`${endDaysAgoMax}`,
|
day_2: endDaysAgoMax,
|
||||||
]);
|
});
|
||||||
start = getTimeDaysAgo(region, endDaysAgoMax);
|
start = getTimeDaysAgo(region, endDaysAgoMax);
|
||||||
end = getTimeDaysAgo(region, startDaysAgoMin);
|
end = getTimeDaysAgo(region, startDaysAgoMin);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { idToRegion, Region } from '@blitzkit/core';
|
import { idToRegion, Region } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
import { CacheType, ChatInputCommandInteraction } from "discord.js";
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from "markdown-escape";
|
||||||
import { searchPlayersAcrossRegionsBotWrapper } from '../../blitz/searchPlayersAcrossRegionsBotWrapper';
|
import { searchPlayersAcrossRegionsBotWrapper } from "../../blitz/searchPlayersAcrossRegionsBotWrapper";
|
||||||
import { getBlitzFromDiscord } from '../../blitzkit/getBlitzFromDiscord';
|
import { getBlitzFromDiscord } from "../../blitzkit/getBlitzFromDiscord";
|
||||||
import { UserError } from '../../blitzkit/userError';
|
import { UserError } from "../../blitzkit/userError";
|
||||||
import { translator } from '../../localization/translator';
|
import { translator } from "../../localization/translator";
|
||||||
|
|
||||||
export interface ResolvedPlayer {
|
export interface ResolvedPlayer {
|
||||||
region: Region;
|
region: Region;
|
||||||
@@ -13,10 +13,10 @@ export interface ResolvedPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resolvePlayerFromCommand(
|
export async function resolvePlayerFromCommand(
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>
|
||||||
) {
|
) {
|
||||||
const { strings } = translator(interaction.locale);
|
const { strings } = translator(interaction.locale);
|
||||||
const commandUsername = interaction.options.getString('username');
|
const commandUsername = interaction.options.getString("username");
|
||||||
|
|
||||||
if (commandUsername !== null) {
|
if (commandUsername !== null) {
|
||||||
const id = parseInt(commandUsername);
|
const id = parseInt(commandUsername);
|
||||||
@@ -27,7 +27,7 @@ export async function resolvePlayerFromCommand(
|
|||||||
} else {
|
} else {
|
||||||
const accounts = await searchPlayersAcrossRegionsBotWrapper(
|
const accounts = await searchPlayersAcrossRegionsBotWrapper(
|
||||||
commandUsername,
|
commandUsername,
|
||||||
interaction.locale,
|
interaction.locale
|
||||||
);
|
);
|
||||||
|
|
||||||
if (accounts[0]) {
|
if (accounts[0]) {
|
||||||
@@ -37,9 +37,9 @@ export async function resolvePlayerFromCommand(
|
|||||||
} satisfies ResolvedPlayer;
|
} satisfies ResolvedPlayer;
|
||||||
} else {
|
} else {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
literals(strings.bot.common.errors.player_not_found, [
|
literals(strings.bot.common.errors.player_not_found, {
|
||||||
markdownEscape(commandUsername),
|
search: markdownEscape(commandUsername),
|
||||||
]),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function assertSecret(secret?: string) {
|
export function assertSecret<Type>(secret?: Type) {
|
||||||
if (secret === undefined) throw new Error('Missing secret');
|
if (secret === undefined) throw new Error("Missing secret");
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,19 @@
|
|||||||
|
import { assertSecret } from "@blitzkit/core";
|
||||||
|
|
||||||
|
const dev = assertSecret(import.meta.env.DEV);
|
||||||
|
|
||||||
export function literals(
|
export function literals(
|
||||||
string: string,
|
string: string,
|
||||||
literals?: Record<string, string | number>
|
literals?: Record<string, string | number | undefined>
|
||||||
) {
|
) {
|
||||||
for (const key in literals) {
|
for (const key in literals) {
|
||||||
string = string.replaceAll(`{${key}}`, `${literals[key]}`);
|
const mutated = string.replaceAll(`{${key}}`, `${literals[key]}`);
|
||||||
|
|
||||||
|
if (dev && mutated === string) {
|
||||||
|
console.warn(`Could not find literal {${key}} in string "${string}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
string = mutated;
|
||||||
}
|
}
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
|
|||||||
@@ -844,7 +844,7 @@
|
|||||||
"other_projects": {
|
"other_projects": {
|
||||||
"title": "Other cool projects"
|
"title": "Other cool projects"
|
||||||
},
|
},
|
||||||
"help_translate": "Please help translate {name} to {language_2}!",
|
"help_translate": "Please help translate {name} to {language}!",
|
||||||
"seo_description": "Tools for everything World of Tanks Blitz"
|
"seo_description": "Tools for everything World of Tanks Blitz"
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1293,7 +1293,7 @@
|
|||||||
"no_results": "No players found"
|
"no_results": "No players found"
|
||||||
},
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
"title": "Tracking {count}",
|
"title": "Tracking {name}",
|
||||||
"subtitle": "Since {time}",
|
"subtitle": "Since {time}",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"change": "Change"
|
"change": "Change"
|
||||||
|
|||||||
@@ -53,31 +53,37 @@ function LayerEntry({
|
|||||||
<td>
|
<td>
|
||||||
{layer.type === null && (
|
{layer.type === null && (
|
||||||
<Text size="2" color={shotStatusColor}>
|
<Text size="2" color={shotStatusColor}>
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, {
|
||||||
Math.round(layer.distance * 1000).toLocaleString(locale),
|
value: Math.round(layer.distance * 1000).toLocaleString(locale),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{layer.type === ArmorType.External && (
|
{layer.type === ArmorType.External && (
|
||||||
<Text size="2" color={shotStatusColor}>
|
<Text size="2" color={shotStatusColor}>
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, {
|
||||||
Math.round(layer.thickness).toLocaleString(locale),
|
value: Math.round(layer.thickness).toLocaleString(locale),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{(layer.type === ArmorType.Primary ||
|
{(layer.type === ArmorType.Primary ||
|
||||||
layer.type === ArmorType.Spaced) && (
|
layer.type === ArmorType.Spaced) && (
|
||||||
<Text size="2" color={shotStatusColor}>
|
<Text size="2" color={shotStatusColor}>
|
||||||
{literals(
|
{angle === undefined
|
||||||
angle === undefined
|
? literals(strings.common.units.mm, {
|
||||||
? strings.common.units.mm
|
value: Math.round(layer.thicknessAngled).toLocaleString(
|
||||||
: strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
locale
|
||||||
|
),
|
||||||
|
})
|
||||||
|
: literals(
|
||||||
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||||
.stats.thickness_and_angle,
|
.stats.thickness_and_angle,
|
||||||
[
|
{
|
||||||
Math.round(layer.thicknessAngled).toLocaleString(locale),
|
thickness: Math.round(layer.thicknessAngled).toLocaleString(
|
||||||
radToDeg(angle ?? 0).toFixed(0),
|
locale
|
||||||
]
|
),
|
||||||
|
angle: radToDeg(angle ?? 0).toFixed(0),
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -89,7 +95,7 @@ function LayerEntry({
|
|||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
||||||
.ricochet_loss,
|
.ricochet_loss,
|
||||||
[Math.max(-100, -50 * layer.distance).toFixed(0)]
|
{ loss: Math.max(-100, -50 * layer.distance).toFixed(0) }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -100,7 +106,7 @@ function LayerEntry({
|
|||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
||||||
.nominal,
|
.nominal,
|
||||||
[Math.round(layer.thickness).toLocaleString(locale)]
|
{ thickness: Math.round(layer.thickness).toLocaleString(locale) }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -132,7 +138,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
|||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
||||||
shot.in.status
|
shot.in.status
|
||||||
],
|
],
|
||||||
[Math.round(shot.damage).toLocaleString(locale)]
|
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -202,7 +208,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
|||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||||
.status[shot.out.status],
|
.status[shot.out.status],
|
||||||
[Math.round(shot.damage).toLocaleString(locale)]
|
{ damage: Math.round(shot.damage).toLocaleString(locale) }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
|
|||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.embed.configuration.export.generate
|
strings.website.tools.embed.configuration.export.generate
|
||||||
.success.info,
|
.success.info,
|
||||||
[`${copyUser?.nickname}`]
|
{ name: copyUser?.nickname }
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export function GalleryList({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
{skeleton ? (
|
{skeleton ? (
|
||||||
<InlineSkeleton width="7rem" />
|
<InlineSkeleton width="7rem" />
|
||||||
) : (
|
) : (
|
||||||
literals(strings.website.tools.gallery.search.results, [
|
literals(strings.website.tools.gallery.search.results, {
|
||||||
filtered.length.toLocaleString(locale),
|
count: filtered.length.toLocaleString(locale),
|
||||||
])
|
})
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -139,10 +139,10 @@ export function Guesser() {
|
|||||||
|
|
||||||
<Flex justify="center" style={{ userSelect: "none" }}>
|
<Flex justify="center" style={{ userSelect: "none" }}>
|
||||||
<Text>
|
<Text>
|
||||||
{literals(strings.website.tools.guess.stats, [
|
{literals(strings.website.tools.guess.stats, {
|
||||||
`${correctGuesses}`,
|
correct: correctGuesses,
|
||||||
`${totalGuesses}`,
|
total: totalGuesses,
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { Code, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Code, Flex, Heading, Text } from "@radix-ui/themes";
|
||||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
import { resolveBranchName } from "../core/blitzkit/branches";
|
||||||
import { getStrings } from '../core/i18n/getStrings';
|
import { getStrings } from "../core/i18n/getStrings";
|
||||||
import { literals } from '@blitzkit/i18n/src/literals';
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
|
|
||||||
const { locale } = Astro.params;
|
const { locale } = Astro.params;
|
||||||
const branchName = resolveBranchName(locale);
|
const branchName = resolveBranchName(locale);
|
||||||
@@ -45,7 +45,7 @@ const strings = getStrings(locale);
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#home-hero[data-variant='pride'] {
|
#home-hero[data-variant="pride"] {
|
||||||
--g-1-dark: var(--crimson-3);
|
--g-1-dark: var(--crimson-3);
|
||||||
--g-1-light: var(--crimson-7);
|
--g-1-light: var(--crimson-7);
|
||||||
--g-2-dark: var(--orange-3);
|
--g-2-dark: var(--orange-3);
|
||||||
@@ -58,7 +58,7 @@ const strings = getStrings(locale);
|
|||||||
--g-7-light: var(--purple-7);
|
--g-7-light: var(--purple-7);
|
||||||
}
|
}
|
||||||
|
|
||||||
#home-hero[data-variant='spooky'] {
|
#home-hero[data-variant="spooky"] {
|
||||||
--g-1-dark: var(--orange-3);
|
--g-1-dark: var(--orange-3);
|
||||||
--g-1-light: var(--orange-7);
|
--g-1-light: var(--orange-7);
|
||||||
--g-2-dark: var(--tomato-3);
|
--g-2-dark: var(--tomato-3);
|
||||||
@@ -73,23 +73,23 @@ const strings = getStrings(locale);
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { clamp, times } from 'lodash-es';
|
import { clamp, times } from "lodash-es";
|
||||||
|
|
||||||
const n = 5;
|
const n = 5;
|
||||||
const homeHero = document.getElementById('home-hero');
|
const homeHero = document.getElementById("home-hero");
|
||||||
|
|
||||||
if (homeHero) {
|
if (homeHero) {
|
||||||
switch (new Date().getMonth()) {
|
switch (new Date().getMonth()) {
|
||||||
case 5:
|
case 5:
|
||||||
homeHero.dataset.variant = 'pride';
|
homeHero.dataset.variant = "pride";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 9:
|
case 9:
|
||||||
homeHero.dataset.variant = 'spooky';
|
homeHero.dataset.variant = "spooky";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('pointermove', (event) => {
|
window.addEventListener("pointermove", (event) => {
|
||||||
const x = event.clientX / window.innerWidth;
|
const x = event.clientX / window.innerWidth;
|
||||||
const c = clamp(
|
const c = clamp(
|
||||||
(1 -
|
(1 -
|
||||||
@@ -97,7 +97,7 @@ const strings = getStrings(locale);
|
|||||||
window.innerHeight) **
|
window.innerHeight) **
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
1,
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
// https://www.desmos.com/calculator/ndz2poxk32
|
// https://www.desmos.com/calculator/ndz2poxk32
|
||||||
@@ -128,13 +128,13 @@ const strings = getStrings(locale);
|
|||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Flex direction="column" align="center" gap="1">
|
<Flex direction="column" align="center" gap="1">
|
||||||
<Heading size={{ initial: '8', xs: '9' }} weight="bold" align="center">
|
<Heading size={{ initial: "8", xs: "9" }} weight="bold" align="center">
|
||||||
<Flex align="center" gap={{ initial: '2', xs: '4' }}>
|
<Flex align="center" gap={{ initial: "2", xs: "4" }}>
|
||||||
BlitzKit
|
BlitzKit
|
||||||
{
|
{
|
||||||
isBranchNamed && (
|
isBranchNamed && (
|
||||||
<Code
|
<Code
|
||||||
size={{ initial: '7', xs: '8' }}
|
size={{ initial: "7", xs: "8" }}
|
||||||
color="gray"
|
color="gray"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
@@ -146,7 +146,9 @@ const strings = getStrings(locale);
|
|||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Text color="gray" align="center" size="4"
|
<Text color="gray" align="center" size="4"
|
||||||
>{literals(strings.website.home.hero.credit, ['TrèsAbhi'])}</Text
|
>{
|
||||||
|
literals(strings.website.home.hero.credit, { name: "TrèsAbhi" })
|
||||||
|
}</Text
|
||||||
>
|
>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function Content() {
|
|||||||
{strings.website.home.hot_tanks.title}
|
{strings.website.home.hot_tanks.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text color="gray" align="center" mb="5" size="2">
|
<Text color="gray" align="center" mb="5" size="2">
|
||||||
{literals(strings.website.home.hot_tanks.subtitle, ["7"])}
|
{literals(strings.website.home.hot_tanks.subtitle, { days: 7 })}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex justify="center" gap="4" wrap="wrap">
|
<Flex justify="center" gap="4" wrap="wrap">
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { formatCompact } from '@blitzkit/core';
|
import { formatCompact } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n/src/literals';
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
import { InfoCircledIcon } from "@radix-ui/react-icons";
|
||||||
import { Callout, Flex } from '@radix-ui/themes';
|
import { Callout, Flex } from "@radix-ui/themes";
|
||||||
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
import { awaitableAverageDefinitions } from "../../core/awaitables/averageDefinitions";
|
||||||
import { awaitableDiscoveredIdsDefinitions } from '../../core/awaitables/discoveredIdsDefinitions';
|
import { awaitableDiscoveredIdsDefinitions } from "../../core/awaitables/discoveredIdsDefinitions";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from "../../types/maybeSkeletonComponentProps";
|
||||||
import { InlineSkeleton } from '../InlineSkeleton';
|
import { InlineSkeleton } from "../InlineSkeleton";
|
||||||
|
|
||||||
const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
||||||
awaitableDiscoveredIdsDefinitions,
|
awaitableDiscoveredIdsDefinitions,
|
||||||
@@ -17,22 +17,22 @@ export function PerformanceInfo({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const { strings, locale } = useLocale();
|
const { strings, locale } = useLocale();
|
||||||
const samples = formatCompact(
|
const samples = formatCompact(
|
||||||
locale,
|
locale,
|
||||||
Math.round(discoveredIdsDefinitions.count),
|
Math.round(discoveredIdsDefinitions.count)
|
||||||
);
|
);
|
||||||
const minutesAgo = Math.floor(
|
const minutesAgo = Math.floor(
|
||||||
(Date.now() - averageDefinitions.time) / (1000 * 60),
|
(Date.now() - averageDefinitions.time) / (1000 * 60)
|
||||||
);
|
);
|
||||||
const hoursAgo = Math.floor(minutesAgo / 60);
|
const hoursAgo = Math.floor(minutesAgo / 60);
|
||||||
const timeAgo = literals(
|
const timeAgo = literals(
|
||||||
strings.website.tools.performance.estimation[
|
strings.website.tools.performance.estimation[
|
||||||
hoursAgo === 0 ? 'minutes' : 'hours'
|
hoursAgo === 0 ? "minutes" : "hours"
|
||||||
],
|
],
|
||||||
[`${hoursAgo === 0 ? minutesAgo : hoursAgo}`],
|
{ count: hoursAgo === 0 ? minutesAgo : hoursAgo }
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="center">
|
<Flex justify="center">
|
||||||
<Callout.Root style={{ width: 'fit-content' }}>
|
<Callout.Root style={{ width: "fit-content" }}>
|
||||||
<Callout.Icon>
|
<Callout.Icon>
|
||||||
<InfoCircledIcon />
|
<InfoCircledIcon />
|
||||||
</Callout.Icon>
|
</Callout.Icon>
|
||||||
@@ -40,10 +40,10 @@ export function PerformanceInfo({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
{skeleton && <InlineSkeleton width="9rem" />}
|
{skeleton && <InlineSkeleton width="9rem" />}
|
||||||
|
|
||||||
{!skeleton &&
|
{!skeleton &&
|
||||||
literals(strings.website.tools.performance.estimation.body, [
|
literals(strings.website.tools.performance.estimation.body, {
|
||||||
samples,
|
count: samples,
|
||||||
timeAgo,
|
age: timeAgo,
|
||||||
])}
|
})}
|
||||||
</Callout.Text>
|
</Callout.Text>
|
||||||
</Callout.Root>
|
</Callout.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -431,9 +431,9 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
{tanksFiltered.length === 1
|
{tanksFiltered.length === 1
|
||||||
? strings.website.common.tank_search.count_singular
|
? strings.website.common.tank_search.count_singular
|
||||||
: literals(strings.website.common.tank_search.count_plural, [
|
: literals(strings.website.common.tank_search.count_plural, {
|
||||||
`${tanksFiltered.length.toLocaleString(locale)}`,
|
count: tanksFiltered.length.toLocaleString(locale),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{onSelectAll && (
|
{onSelectAll && (
|
||||||
@@ -460,9 +460,11 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
|
|
||||||
{tankopediaSort.by !== "meta.none" && (
|
{tankopediaSort.by !== "meta.none" && (
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
{literals(strings.website.common.tank_search.sorting_by, [
|
{literals(strings.website.common.tank_search.sorting_by, {
|
||||||
strings.website.common.tank_search.sort[tankopediaSort.by],
|
name: strings.website.common.tank_search.sort[
|
||||||
])}
|
tankopediaSort.by
|
||||||
|
],
|
||||||
|
})}
|
||||||
{SORT_UNITS[tankopediaSort.by] === undefined
|
{SORT_UNITS[tankopediaSort.by] === undefined
|
||||||
? ""
|
? ""
|
||||||
: ` (${SORT_UNITS[tankopediaSort.by]})`}
|
: ` (${SORT_UNITS[tankopediaSort.by]})`}
|
||||||
|
|||||||
+30
-28
@@ -1,16 +1,16 @@
|
|||||||
import type { AssaultRanges } from '@blitzkit/core';
|
import type { AssaultRanges } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||||
import { clamp } from 'three/src/math/MathUtils.js';
|
import { clamp } from "three/src/math/MathUtils.js";
|
||||||
import {
|
import {
|
||||||
fakeLogSquishyHigh,
|
fakeLogSquishyHigh,
|
||||||
fakeLogSquishyLow,
|
fakeLogSquishyLow,
|
||||||
} from '../../../../../../../core/blitzkit/fakeLog';
|
} from "../../../../../../../core/blitzkit/fakeLog";
|
||||||
import type { TankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
|
import type { TankCharacteristics } from "../../../../../../../core/blitzkit/tankCharacteristics";
|
||||||
import { Var } from '../../../../../../../core/radix/var';
|
import { Var } from "../../../../../../../core/radix/var";
|
||||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||||
import { VisualizerCard } from '../VisualizerCard';
|
import { VisualizerCard } from "../VisualizerCard";
|
||||||
import { AssaultCursor } from './components/Cursor';
|
import { AssaultCursor } from "./components/Cursor";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
ranges: AssaultRanges;
|
ranges: AssaultRanges;
|
||||||
@@ -48,27 +48,29 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
|||||||
p="3"
|
p="3"
|
||||||
pt="8"
|
pt="8"
|
||||||
pr="5"
|
pr="5"
|
||||||
style={{ backgroundColor: Var('gray-1') }}
|
style={{ backgroundColor: Var("gray-1") }}
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
>
|
>
|
||||||
<Flex flexGrow="1">
|
<Flex flexGrow="1">
|
||||||
<Box position="relative" width={Var('space-8')}>
|
<Box position="relative" width={Var("space-8")}>
|
||||||
{ranges.map((range, index) => (
|
{ranges.map((range, index) => (
|
||||||
<Text
|
<Text
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
bottom: `${fakeLogSquishyLow(range.factor / maxFactor) * 100}%`,
|
bottom: `${fakeLogSquishyLow(range.factor / maxFactor) * 100}%`,
|
||||||
transform: 'translateY(50%)',
|
transform: "translateY(50%)",
|
||||||
}}
|
}}
|
||||||
color="gray"
|
color="gray"
|
||||||
size="1"
|
size="1"
|
||||||
wrap="nowrap"
|
wrap="nowrap"
|
||||||
>
|
>
|
||||||
{literals(strings.common.units.hp, [
|
{literals(strings.common.units.hp, {
|
||||||
`${Math.round(stats.damageWithoutAssault * range.factor)}`,
|
value: Math.round(
|
||||||
])}
|
stats.damageWithoutAssault * range.factor
|
||||||
|
),
|
||||||
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -77,8 +79,8 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
|||||||
{ranges.map((range, index) => {
|
{ranges.map((range, index) => {
|
||||||
const mix =
|
const mix =
|
||||||
clamp(2 * (range.factor / maxFactor - 1) + 1, 0, 1) * 100;
|
clamp(2 * (range.factor / maxFactor - 1) + 1, 0, 1) * 100;
|
||||||
const color0 = `color-mix(in hsl, ${Var('tomato-9')}, ${Var('jade-9')} ${mix}%)`;
|
const color0 = `color-mix(in hsl, ${Var("tomato-9")}, ${Var("jade-9")} ${mix}%)`;
|
||||||
const color1 = `color-mix(in hsl, ${Var('tomato-7')}, ${Var('jade-7')} ${mix}%)`;
|
const color1 = `color-mix(in hsl, ${Var("tomato-7")}, ${Var("jade-7")} ${mix}%)`;
|
||||||
const last = ranges[index - 1];
|
const last = ranges[index - 1];
|
||||||
const lastFakeDistance = last?.fakeDistance ?? 0;
|
const lastFakeDistance = last?.fakeDistance ?? 0;
|
||||||
const fakeWidth = range.fakeDistance - lastFakeDistance;
|
const fakeWidth = range.fakeDistance - lastFakeDistance;
|
||||||
@@ -105,8 +107,8 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
|||||||
width="100%"
|
width="100%"
|
||||||
height="1pt"
|
height="1pt"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateY(50%)',
|
transform: "translateY(50%)",
|
||||||
backgroundColor: Var('gray-11'),
|
backgroundColor: Var("gray-11"),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -118,7 +120,7 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex ml="8" height={Var('space-7')}>
|
<Flex ml="8" height={Var("space-7")}>
|
||||||
{ranges.map((range, index) => {
|
{ranges.map((range, index) => {
|
||||||
const last = ranges[index - 1];
|
const last = ranges[index - 1];
|
||||||
const lastFakeDistance = last?.fakeDistance ?? 0;
|
const lastFakeDistance = last?.fakeDistance ?? 0;
|
||||||
@@ -137,15 +139,15 @@ export function AssaultRangesVisualizer({ ranges: _ranges, stats }: Props) {
|
|||||||
color="gray"
|
color="gray"
|
||||||
mt="2"
|
mt="2"
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
right: 0,
|
right: 0,
|
||||||
transform: 'translateX(50%)',
|
transform: "translateX(50%)",
|
||||||
writingMode: 'vertical-rl',
|
writingMode: "vertical-rl",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{literals(strings.common.units.m, [
|
{literals(strings.common.units.m, {
|
||||||
`${Math.round(range.distance)}`,
|
value: Math.round(range.distance),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
+5
-4
@@ -38,16 +38,17 @@ export function CompareOptions(props: FlexProps) {
|
|||||||
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Class}`}>
|
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Class}`}>
|
||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.tankopedia.characteristics.compare.class,
|
strings.website.tools.tankopedia.characteristics.compare.class,
|
||||||
[
|
{
|
||||||
TIER_ROMAN_NUMERALS[tank.tier],
|
tier: TIER_ROMAN_NUMERALS[tank.tier],
|
||||||
|
class:
|
||||||
strings.common.tank_class_short_plural_lowercase[tank.class],
|
strings.common.tank_class_short_plural_lowercase[tank.class],
|
||||||
]
|
}
|
||||||
)}
|
)}
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Tier}`}>
|
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Tier}`}>
|
||||||
{literals(
|
{literals(
|
||||||
strings.website.tools.tankopedia.characteristics.compare.tier,
|
strings.website.tools.tankopedia.characteristics.compare.tier,
|
||||||
[TIER_ROMAN_NUMERALS[tank.tier]]
|
{ tier: TIER_ROMAN_NUMERALS[tank.tier] }
|
||||||
)}
|
)}
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.All}`}>
|
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.All}`}>
|
||||||
|
|||||||
+17
-17
@@ -1,15 +1,15 @@
|
|||||||
import { CREW_MEMBER_NAMES, CrewType } from '@blitzkit/core';
|
import { CREW_MEMBER_NAMES, CrewType } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { AccessibilityIcon, InfoCircledIcon } from '@radix-ui/react-icons';
|
import { AccessibilityIcon, InfoCircledIcon } from "@radix-ui/react-icons";
|
||||||
import { Flex, Heading, IconButton, Popover, Text } from '@radix-ui/themes';
|
import { Flex, Heading, IconButton, Popover, Text } from "@radix-ui/themes";
|
||||||
import { Fragment } from 'react/jsx-runtime';
|
import { Fragment } from "react/jsx-runtime";
|
||||||
import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions';
|
import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions";
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from "../../../../../../hooks/useEquipment";
|
||||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
import { InfoWithDelta } from './InfoWithDelta';
|
import { InfoWithDelta } from "./InfoWithDelta";
|
||||||
import { StatsTableWrapper } from './StatsTableWrapper';
|
import { StatsTableWrapper } from "./StatsTableWrapper";
|
||||||
import type { StatsAcceptorProps } from './TraverseVisualizer';
|
import type { StatsAcceptorProps } from "./TraverseVisualizer";
|
||||||
|
|
||||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
|||||||
provisionDefinitions.provisions[provision].crew
|
provisionDefinitions.provisions[provision].crew
|
||||||
? total + provisionDefinitions.provisions[provision].crew! / 100
|
? total + provisionDefinitions.provisions[provision].crew! / 100
|
||||||
: total,
|
: total,
|
||||||
0,
|
0
|
||||||
) + (hasImprovedVentilation ? 0.08 : 0);
|
) + (hasImprovedVentilation ? 0.08 : 0);
|
||||||
const commanderMastery = 1 + provisionCrewBonus;
|
const commanderMastery = 1 + provisionCrewBonus;
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
|||||||
]
|
]
|
||||||
}${
|
}${
|
||||||
member.count > 1
|
member.count > 1
|
||||||
? ` ${literals(strings.common.units.x, [`${member.count}`])}`
|
? ` ${literals(strings.common.units.x, { value: member.count })}`
|
||||||
: ''
|
: ""
|
||||||
}`}
|
}`}
|
||||||
decimals={0}
|
decimals={0}
|
||||||
noRanking
|
noRanking
|
||||||
@@ -93,9 +93,9 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
|||||||
? strings.website.tools.tankopedia.crew[
|
? strings.website.tools.tankopedia.crew[
|
||||||
CREW_MEMBER_NAMES[sub]
|
CREW_MEMBER_NAMES[sub]
|
||||||
]
|
]
|
||||||
: sub,
|
: sub
|
||||||
)
|
)
|
||||||
.join(', ')}
|
.join(", ")}
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -119,7 +119,7 @@ export function Firepower({
|
|||||||
name={literals(
|
name={literals(
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
strings.website.tools.tankopedia.characteristics.values
|
||||||
.shell_index,
|
.shell_index,
|
||||||
[`${index + 1}`]
|
{ index: index + 1 }
|
||||||
)}
|
)}
|
||||||
decimals={2}
|
decimals={2}
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
@@ -191,7 +191,7 @@ export function Firepower({
|
|||||||
name={literals(
|
name={literals(
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
strings.website.tools.tankopedia.characteristics.values
|
||||||
.dispersion_at_distance,
|
.dispersion_at_distance,
|
||||||
["100"]
|
{ distance: 100 }
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
|
|||||||
+12
-10
@@ -359,31 +359,33 @@ export function GunFlexibilityVisualizer({
|
|||||||
|
|
||||||
<VisualizerCornerStat
|
<VisualizerCornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.flexibility.max}
|
label={strings.website.tools.tankopedia.visualizers.flexibility.max}
|
||||||
value={literals(strings.common.units.deg, [
|
value={literals(strings.common.units.deg, {
|
||||||
radToDeg(maxPitch).toFixed(0),
|
value: radToDeg(maxPitch).toFixed(0),
|
||||||
])}
|
})}
|
||||||
side="bottom-left"
|
side="bottom-left"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VisualizerCornerStat
|
<VisualizerCornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.flexibility.min}
|
label={strings.website.tools.tankopedia.visualizers.flexibility.min}
|
||||||
value={literals(strings.common.units.deg, [
|
value={literals(strings.common.units.deg, {
|
||||||
radToDeg(minPitch).toFixed(0),
|
value: radToDeg(minPitch).toFixed(0),
|
||||||
])}
|
})}
|
||||||
side="bottom-right"
|
side="bottom-right"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VisualizerCornerStat
|
<VisualizerCornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.flexibility.pitch}
|
label={strings.website.tools.tankopedia.visualizers.flexibility.pitch}
|
||||||
value={literals(strings.common.units.deg, [
|
value={literals(strings.common.units.deg, {
|
||||||
radToDeg(pitch).toFixed(0),
|
value: radToDeg(pitch).toFixed(0),
|
||||||
])}
|
})}
|
||||||
side="top-left"
|
side="top-left"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VisualizerCornerStat
|
<VisualizerCornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.flexibility.yaw}
|
label={strings.website.tools.tankopedia.visualizers.flexibility.yaw}
|
||||||
value={literals(strings.common.units.deg, [radToDeg(yaw).toFixed(0)])}
|
value={literals(strings.common.units.deg, {
|
||||||
|
value: radToDeg(yaw).toFixed(0),
|
||||||
|
})}
|
||||||
side="top-right"
|
side="top-right"
|
||||||
/>
|
/>
|
||||||
</VisualizerCard>
|
</VisualizerCard>
|
||||||
|
|||||||
+57
-55
@@ -1,15 +1,15 @@
|
|||||||
import { asset, isExplosive } from '@blitzkit/core';
|
import { asset, isExplosive } from "@blitzkit/core";
|
||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
import { Box, Flex, Text } from "@radix-ui/themes";
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from "react";
|
||||||
import { clamp, degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
import { clamp, degToRad, radToDeg } from "three/src/math/MathUtils.js";
|
||||||
import { Var } from '../../../../../../../core/radix/var';
|
import { Var } from "../../../../../../../core/radix/var";
|
||||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||||
import { Duel } from '../../../../../../../stores/duel';
|
import { Duel } from "../../../../../../../stores/duel";
|
||||||
import type { StatsAcceptorProps } from '../TraverseVisualizer';
|
import type { StatsAcceptorProps } from "../TraverseVisualizer";
|
||||||
import { VisualizerCard } from '../VisualizerCard';
|
import { VisualizerCard } from "../VisualizerCard";
|
||||||
import { VisualizerCornerStat } from '../VisualizerCornerStat';
|
import { VisualizerCornerStat } from "../VisualizerCornerStat";
|
||||||
import { ShellSwitcher } from './components/ShellSwitcher';
|
import { ShellSwitcher } from "./components/ShellSwitcher";
|
||||||
|
|
||||||
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
||||||
const shell = Duel.use((state) => state.protagonist.shell);
|
const shell = Duel.use((state) => state.protagonist.shell);
|
||||||
@@ -30,7 +30,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
<Flex direction="column" mb="6" align="center">
|
<Flex direction="column" mb="6" align="center">
|
||||||
<VisualizerCard
|
<VisualizerCard
|
||||||
mb="0"
|
mb="0"
|
||||||
style={{ width: '100%' }}
|
style={{ width: "100%" }}
|
||||||
ref={container}
|
ref={container}
|
||||||
onPointerMove={(event) => {
|
onPointerMove={(event) => {
|
||||||
if (!container.current || explodes) return;
|
if (!container.current || explodes) return;
|
||||||
@@ -50,7 +50,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
top="0"
|
top="0"
|
||||||
left="0"
|
left="0"
|
||||||
style={{
|
style={{
|
||||||
background: `linear-gradient(to right, transparent, ${Var('gray-a4')})`,
|
background: `linear-gradient(to right, transparent, ${Var("gray-a4")})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@@ -73,20 +73,20 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
background: `conic-gradient(
|
background: `conic-gradient(
|
||||||
at 0 50%,
|
at 0 50%,
|
||||||
|
|
||||||
${Var('ruby-a2')},
|
${Var("ruby-a2")},
|
||||||
${Var('ruby-a1')} ${Math.PI / 2 - ricochet}rad,
|
${Var("ruby-a1")} ${Math.PI / 2 - ricochet}rad,
|
||||||
|
|
||||||
${Var('jade-a2')} ${Math.PI / 2 - ricochet}rad,
|
${Var("jade-a2")} ${Math.PI / 2 - ricochet}rad,
|
||||||
${Var('jade-a4')} ${Math.PI / 2 - normalization}rad,
|
${Var("jade-a4")} ${Math.PI / 2 - normalization}rad,
|
||||||
|
|
||||||
${Var('cyan-a5')} ${Math.PI / 2 - normalization}rad,
|
${Var("cyan-a5")} ${Math.PI / 2 - normalization}rad,
|
||||||
${Var('cyan-a3')} ${Math.PI / 2 + normalization}rad,
|
${Var("cyan-a3")} ${Math.PI / 2 + normalization}rad,
|
||||||
|
|
||||||
${Var('jade-a2')} ${Math.PI / 2 + normalization}rad,
|
${Var("jade-a2")} ${Math.PI / 2 + normalization}rad,
|
||||||
${Var('jade-a4')} ${Math.PI / 2 + ricochet}rad,
|
${Var("jade-a4")} ${Math.PI / 2 + ricochet}rad,
|
||||||
|
|
||||||
${Var('ruby-a1')} ${Math.PI / 2 + ricochet}rad,
|
${Var("ruby-a1")} ${Math.PI / 2 + ricochet}rad,
|
||||||
${Var('ruby-a2')} 180deg
|
${Var("ruby-a2")} 180deg
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -97,7 +97,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
top="50%"
|
top="50%"
|
||||||
height="18rem"
|
height="18rem"
|
||||||
style={{
|
style={{
|
||||||
transformOrigin: 'top center',
|
transformOrigin: "top center",
|
||||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -107,8 +107,8 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
height="100%"
|
height="100%"
|
||||||
bottom="100%"
|
bottom="100%"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
backgroundColor: Var('gray-a8'),
|
backgroundColor: Var("gray-a8"),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
@@ -116,22 +116,22 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
width="3px"
|
width="3px"
|
||||||
height="100%"
|
height="100%"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a4')})`,
|
background: `linear-gradient(${Var("gray-a11")}, ${Var("gray-a4")})`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={asset(`icons/shells/${shell.icon}.webp`)}
|
src={asset(`icons/shells/${shell.icon}.webp`)}
|
||||||
style={{
|
style={{
|
||||||
objectFit: 'contain',
|
objectFit: "contain",
|
||||||
transform: 'translate(-50%, -50%) rotate(-45deg)',
|
transform: "translate(-50%, -50%) rotate(-45deg)",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
width: '2rem',
|
width: "2rem",
|
||||||
height: '2rem',
|
height: "2rem",
|
||||||
top: '6rem',
|
top: "6rem",
|
||||||
left: 0,
|
left: 0,
|
||||||
filter: 'drop-shadow(0px 0px 0.25rem black)',
|
filter: "drop-shadow(0px 0px 0.25rem black)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -142,7 +142,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
top="50%"
|
top="50%"
|
||||||
height="16rem"
|
height="16rem"
|
||||||
style={{
|
style={{
|
||||||
transformOrigin: 'top center',
|
transformOrigin: "top center",
|
||||||
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -152,17 +152,17 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
height="100%"
|
height="100%"
|
||||||
top="-100%"
|
top="-100%"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
background: `linear-gradient(${Var('gray-a4')}, ${Var('gray-a11')})`,
|
background: `linear-gradient(${Var("gray-a4")}, ${Var("gray-a11")})`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<VisualizerCornerStat
|
<VisualizerCornerStat
|
||||||
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||||
value={literals(strings.common.units.deg, [
|
value={literals(strings.common.units.deg, {
|
||||||
radToDeg(Math.abs(angle)).toFixed(0),
|
value: radToDeg(Math.abs(angle)).toFixed(0),
|
||||||
])}
|
})}
|
||||||
side="top-left"
|
side="top-left"
|
||||||
/>
|
/>
|
||||||
<VisualizerCornerStat
|
<VisualizerCornerStat
|
||||||
@@ -171,10 +171,12 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
}
|
}
|
||||||
value={
|
value={
|
||||||
doesRicochet
|
doesRicochet
|
||||||
? '-'
|
? "-"
|
||||||
: literals(strings.common.units.deg, [
|
: literals(strings.common.units.deg, {
|
||||||
radToDeg(Math.min(normalization, Math.abs(angle))).toFixed(0),
|
value: radToDeg(
|
||||||
])
|
Math.min(normalization, Math.abs(angle))
|
||||||
|
).toFixed(0),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
side="top-right"
|
side="top-right"
|
||||||
/>
|
/>
|
||||||
@@ -185,10 +187,10 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
}
|
}
|
||||||
value={
|
value={
|
||||||
doesRicochet
|
doesRicochet
|
||||||
? '-'
|
? "-"
|
||||||
: literals(strings.common.units.deg, [
|
: literals(strings.common.units.deg, {
|
||||||
radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
value: radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||||
])
|
})
|
||||||
}
|
}
|
||||||
side="bottom-left"
|
side="bottom-left"
|
||||||
/>
|
/>
|
||||||
@@ -196,10 +198,10 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||||
value={
|
value={
|
||||||
doesRicochet
|
doesRicochet
|
||||||
? '-'
|
? "-"
|
||||||
: literals(strings.common.units.percentage, [
|
: literals(strings.common.units.percentage, {
|
||||||
armorPercentage.toFixed(0),
|
value: armorPercentage.toFixed(0),
|
||||||
])
|
})
|
||||||
}
|
}
|
||||||
side="bottom-right"
|
side="bottom-right"
|
||||||
/>
|
/>
|
||||||
@@ -215,7 +217,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
top="0"
|
top="0"
|
||||||
left="0"
|
left="0"
|
||||||
style={{
|
style={{
|
||||||
background: `radial-gradient(${Var('black-a7')}, ${Var('black-a11')})`,
|
background: `radial-gradient(${Var("black-a7")}, ${Var("black-a11")})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text color="gray" align="center">
|
<Text color="gray" align="center">
|
||||||
|
|||||||
+34
-34
@@ -1,20 +1,20 @@
|
|||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { Checkbox, Code, Flex, Tabs, Text } from '@radix-ui/themes';
|
import { Checkbox, Code, Flex, Tabs, Text } from "@radix-ui/themes";
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { TankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
|
import type { TankCharacteristics } from "../../../../../../../core/blitzkit/tankCharacteristics";
|
||||||
import { useLocale } from '../../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||||
import { VisualizerCard } from '../VisualizerCard';
|
import { VisualizerCard } from "../VisualizerCard";
|
||||||
|
|
||||||
export interface StatsAcceptorProps {
|
export interface StatsAcceptorProps {
|
||||||
stats: TankCharacteristics;
|
stats: TankCharacteristics;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Terrain = 'Hard' | 'Medium' | 'Soft';
|
type Terrain = "Hard" | "Medium" | "Soft";
|
||||||
|
|
||||||
export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
||||||
const hull = useRef<HTMLDivElement>(null);
|
const hull = useRef<HTMLDivElement>(null);
|
||||||
const turret = useRef<HTMLImageElement>(null);
|
const turret = useRef<HTMLImageElement>(null);
|
||||||
const [terrain, setTerrain] = useState<Terrain>('Hard');
|
const [terrain, setTerrain] = useState<Terrain>("Hard");
|
||||||
const [rotateHull, setRotateHull] = useState(true);
|
const [rotateHull, setRotateHull] = useState(true);
|
||||||
const [rotateTurret, setRotateTurret] = useState(true);
|
const [rotateTurret, setRotateTurret] = useState(true);
|
||||||
const { strings } = useLocale();
|
const { strings } = useLocale();
|
||||||
@@ -63,10 +63,10 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
<div
|
<div
|
||||||
ref={hull}
|
ref={hull}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: '50%',
|
top: "50%",
|
||||||
left: '50%',
|
left: "50%",
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: "translate(-50%, -50%)",
|
||||||
animationDuration: `${
|
animationDuration: `${
|
||||||
rotateHull ? 360 / stats[`hullTraverse${terrain}Terrain`] : 0
|
rotateHull ? 360 / stats[`hullTraverse${terrain}Terrain`] : 0
|
||||||
}s`,
|
}s`,
|
||||||
@@ -75,12 +75,12 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
<img
|
<img
|
||||||
draggable="false"
|
draggable="false"
|
||||||
style={{
|
style={{
|
||||||
width: isTurretless ? '22rem' : '12rem',
|
width: isTurretless ? "22rem" : "12rem",
|
||||||
height: isTurretless ? '22rem' : '12rem',
|
height: isTurretless ? "22rem" : "12rem",
|
||||||
objectFit: 'contain',
|
objectFit: "contain",
|
||||||
filter: 'drop-shadow(0px 0px 4px black)',
|
filter: "drop-shadow(0px 0px 4px black)",
|
||||||
}}
|
}}
|
||||||
src={`/assets/images/tankopedia/visualizers/hull-traverse/hull${isTurretless ? '-only' : ''}.png`}
|
src={`/assets/images/tankopedia/visualizers/hull-traverse/hull${isTurretless ? "-only" : ""}.png`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isTurretless && (
|
{!isTurretless && (
|
||||||
@@ -88,14 +88,14 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
draggable="false"
|
draggable="false"
|
||||||
ref={turret}
|
ref={turret}
|
||||||
style={{
|
style={{
|
||||||
width: '27rem',
|
width: "27rem",
|
||||||
height: '27rem',
|
height: "27rem",
|
||||||
objectFit: 'contain',
|
objectFit: "contain",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
top: '50%',
|
top: "50%",
|
||||||
left: '50%',
|
left: "50%",
|
||||||
filter: 'drop-shadow(0px 0px 4px black)',
|
filter: "drop-shadow(0px 0px 4px black)",
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: "translate(-50%, -50%)",
|
||||||
animationDuration: `${
|
animationDuration: `${
|
||||||
rotateTurret ? 360 / stats.turretTraverseSpeed : 0
|
rotateTurret ? 360 / stats.turretTraverseSpeed : 0
|
||||||
}s`,
|
}s`,
|
||||||
@@ -110,7 +110,7 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
bottom="0"
|
bottom="0"
|
||||||
left="50%"
|
left="50%"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
@@ -137,16 +137,16 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
left="50%"
|
left="50%"
|
||||||
bottom="7"
|
bottom="7"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{strings.website.tools.tankopedia.visualizers.rotator.effective}
|
{strings.website.tools.tankopedia.visualizers.rotator.effective}
|
||||||
</Text>
|
</Text>
|
||||||
<Code size="1" variant="ghost">
|
<Code size="1" variant="ghost">
|
||||||
{literals(strings.common.units.deg_s, [
|
{literals(strings.common.units.deg_s, {
|
||||||
`${(turretSpeedDeg + hullSpeedDeg).toFixed(1)}`,
|
value: (turretSpeedDeg + hullSpeedDeg).toFixed(1),
|
||||||
])}
|
})}
|
||||||
</Code>
|
</Code>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -160,15 +160,15 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
gap="5"
|
gap="5"
|
||||||
justify="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateX(-50%)',
|
transform: "translateX(-50%)",
|
||||||
userSelect: 'none',
|
userSelect: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
gap="2"
|
gap="2"
|
||||||
onClick={() => setRotateHull((state) => !state)}
|
onClick={() => setRotateHull((state) => !state)}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<Checkbox variant="classic" checked={rotateHull} />
|
<Checkbox variant="classic" checked={rotateHull} />
|
||||||
<Text size="2">
|
<Text size="2">
|
||||||
@@ -180,7 +180,7 @@ export function TraverseVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
align="center"
|
align="center"
|
||||||
gap="2"
|
gap="2"
|
||||||
onClick={() => setRotateTurret((state) => !state)}
|
onClick={() => setRotateTurret((state) => !state)}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<Checkbox variant="classic" checked={rotateTurret} />
|
<Checkbox variant="classic" checked={rotateTurret} />
|
||||||
<Text size="2">
|
<Text size="2">
|
||||||
|
|||||||
+28
-28
@@ -1,20 +1,20 @@
|
|||||||
import { literals } from '@blitzkit/i18n';
|
import { literals } from "@blitzkit/i18n";
|
||||||
import { EyeOpenIcon } from '@radix-ui/react-icons';
|
import { EyeOpenIcon } from "@radix-ui/react-icons";
|
||||||
import { Box, Card, Code, Flex, Slider, Tabs, Text } from '@radix-ui/themes';
|
import { Box, Card, Code, Flex, Slider, Tabs, Text } from "@radix-ui/themes";
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from "react";
|
||||||
import { Var } from '../../../../../../core/radix/var';
|
import { Var } from "../../../../../../core/radix/var";
|
||||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||||
import type { StatsAcceptorProps } from './TraverseVisualizer';
|
import type { StatsAcceptorProps } from "./TraverseVisualizer";
|
||||||
|
|
||||||
const maps = [
|
const maps = [
|
||||||
{
|
{
|
||||||
id: 'ghost-factory',
|
id: "ghost-factory",
|
||||||
string: 'ghost_factory',
|
string: "ghost_factory",
|
||||||
size: 512,
|
size: 512,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dead-rail',
|
id: "dead-rail",
|
||||||
string: 'dead_rail',
|
string: "dead_rail",
|
||||||
size: 512,
|
size: 512,
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
@@ -32,7 +32,7 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={{ aspectRatio: '1 / 1', touchAction: 'none' }}
|
style={{ aspectRatio: "1 / 1", touchAction: "none" }}
|
||||||
mb="6"
|
mb="6"
|
||||||
variant="classic"
|
variant="classic"
|
||||||
>
|
>
|
||||||
@@ -61,8 +61,8 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(/assets/images/tankopedia/visualizers/range/${map.id}.png)`,
|
backgroundImage: `url(/assets/images/tankopedia/visualizers/range/${map.id}.png)`,
|
||||||
filter: `grayscale(0.5) brightness(0.8) contrast(1.2)`,
|
filter: `grayscale(0.5) brightness(0.8) contrast(1.2)`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: "cover",
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: "center",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -75,16 +75,18 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: '1 / 1',
|
aspectRatio: "1 / 1",
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: "translate(-50%, -50%)",
|
||||||
background: `radial-gradient(${Var('black-a6')}, ${Var('gray-a12')})`,
|
background: `radial-gradient(${Var("black-a6")}, ${Var("gray-a12")})`,
|
||||||
borderRadius: '50%',
|
borderRadius: "50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ userSelect: 'none' }}>
|
<Text style={{ userSelect: "none" }}>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
<EyeOpenIcon width="1.25rem" height="1.25rem" />
|
<EyeOpenIcon width="1.25rem" height="1.25rem" />
|
||||||
{literals(strings.common.units.m, [resolvedViewRange.toFixed(0)])}
|
{literals(strings.common.units.m, {
|
||||||
|
value: resolvedViewRange.toFixed(0),
|
||||||
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -100,8 +102,8 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
gap="3"
|
gap="3"
|
||||||
align="center"
|
align="center"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: Var('black-a7'),
|
backgroundColor: Var("black-a7"),
|
||||||
backdropFilter: 'blur(4rem)',
|
backdropFilter: "blur(4rem)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text size="2">
|
<Text size="2">
|
||||||
@@ -120,11 +122,9 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
|
|
||||||
<Code size="1" color="gray" variant="ghost">
|
<Code size="1" color="gray" variant="ghost">
|
||||||
<Flex justify="center" width="2em">
|
<Flex justify="center" width="2em">
|
||||||
{literals(
|
{literals(strings.common.units.percentage, {
|
||||||
strings.website.tools.tankopedia.visualizers.view_range
|
percentage: `${Math.round(camouflage * 100)}`.padStart(3, " "),
|
||||||
.percentage,
|
})}
|
||||||
[`${Math.round(camouflage * 100)}`.padStart(3, ' ')],
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Code>
|
</Code>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -136,7 +136,7 @@ export function ViewRangeVisualizer({ stats }: StatsAcceptorProps) {
|
|||||||
left="0"
|
left="0"
|
||||||
width="100%"
|
width="100%"
|
||||||
style={{
|
style={{
|
||||||
background: `radial-gradient(at 50% 100%, ${Var('gray-1')}, transparent 70%)`,
|
background: `radial-gradient(at 50% 100%, ${Var("gray-1")}, transparent 70%)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
|
|||||||
+8
-8
@@ -151,7 +151,7 @@ export function CustomShellButton() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField.Slot side="right">
|
<TextField.Slot side="right">
|
||||||
{literals(strings.common.units.mm, [""])}
|
{literals(strings.common.units.mm, { value: "" })}
|
||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -188,7 +188,7 @@ export function CustomShellButton() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField.Slot side="right">
|
<TextField.Slot side="right">
|
||||||
{literals(strings.common.units.mm, [""])}
|
{literals(strings.common.units.mm, { value: "" })}
|
||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -212,9 +212,9 @@ export function CustomShellButton() {
|
|||||||
minWidth="8rem"
|
minWidth="8rem"
|
||||||
>
|
>
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
{literals(strings.common.units.deg, [
|
{literals(strings.common.units.deg, {
|
||||||
(customShell.normalization ?? 0).toFixed(0),
|
value: (customShell.normalization ?? 0).toFixed(0),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Slider
|
<Slider
|
||||||
variant="classic"
|
variant="classic"
|
||||||
@@ -243,9 +243,9 @@ export function CustomShellButton() {
|
|||||||
|
|
||||||
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
{literals(strings.common.units.deg, [
|
{literals(strings.common.units.deg, {
|
||||||
(customShell.ricochet ?? 90).toFixed(0),
|
value: (customShell.ricochet ?? 90).toFixed(0),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Slider
|
<Slider
|
||||||
variant="classic"
|
variant="classic"
|
||||||
|
|||||||
+8
-8
@@ -59,15 +59,15 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
>
|
>
|
||||||
<Flex direction="column" align="end" justify="between">
|
<Flex direction="column" align="end" justify="between">
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, {
|
||||||
(thicknessRange.value * 1.5).toFixed(0),
|
value: (thicknessRange.value * 1.5).toFixed(0),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="1">
|
<Text size="1">
|
||||||
{strings.website.tools.tankopedia.sandbox.static.primary}
|
{strings.website.tools.tankopedia.sandbox.static.primary}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{literals(strings.common.units.mm, ["0"])}
|
{literals(strings.common.units.mm, { value: 0 })}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -100,15 +100,15 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
|
|||||||
>
|
>
|
||||||
<Flex direction="column" align="end" justify="between">
|
<Flex direction="column" align="end" justify="between">
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, {
|
||||||
thicknessRange.value.toFixed(0),
|
value: thicknessRange.value.toFixed(0),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="1">
|
<Text size="1">
|
||||||
{strings.website.tools.tankopedia.sandbox.static.spaced}
|
{strings.website.tools.tankopedia.sandbox.static.spaced}
|
||||||
</Text>
|
</Text>
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{literals(strings.common.units.mm, ["0"])}
|
{literals(strings.common.units.mm, { value: 0 })}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
>
|
>
|
||||||
{!hasCustomShell && (
|
{!hasCustomShell && (
|
||||||
<Text color="gray" size={{ initial: "1", sm: "2" }}>
|
<Text color="gray" size={{ initial: "1", sm: "2" }}>
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, {
|
||||||
(
|
value: (
|
||||||
resolvePenetrationCoefficient(
|
resolvePenetrationCoefficient(
|
||||||
hasCalibratedShells,
|
hasCalibratedShells,
|
||||||
antagonistShell.type
|
antagonistShell.type
|
||||||
) * antagonistShell.penetration.near
|
) * antagonistShell.penetration.near
|
||||||
).toFixed(0),
|
).toFixed(0),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -156,9 +156,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
||||||
secondaryDiscriminator={
|
secondaryDiscriminator={
|
||||||
<Text style={{ fontSize: "0.75em" }}>
|
<Text style={{ fontSize: "0.75em" }}>
|
||||||
{literals(strings.common.units.mm, [
|
{literals(strings.common.units.mm, {
|
||||||
gun.shells[0].caliber.toFixed(0),
|
value: gun.shells[0].caliber.toFixed(0),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
? strings.website.tools.tankopedia.tech_tree.battle
|
? strings.website.tools.tankopedia.tech_tree.battle
|
||||||
: literals(
|
: literals(
|
||||||
strings.website.tools.tankopedia.tech_tree.battles,
|
strings.website.tools.tankopedia.tech_tree.battles,
|
||||||
[`${games}`]
|
{ count: games }
|
||||||
))}
|
))}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
pl="1"
|
pl="1"
|
||||||
>
|
>
|
||||||
<Text size="2">
|
<Text size="2">
|
||||||
{literals(strings.common.units.x, [`${multiplier}`])}
|
{literals(strings.common.units.x, { value: multiplier })}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
@@ -218,10 +218,10 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
<CaretLeftIcon />
|
<CaretLeftIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Text>
|
<Text>
|
||||||
{literals(strings.website.tools.tankopedia.tech_tree.route, [
|
{literals(strings.website.tools.tankopedia.tech_tree.route, {
|
||||||
`${lineIndex + 1}`,
|
index: lineIndex + 1,
|
||||||
`${lines.length}`,
|
count: lines.length,
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="2"
|
size="2"
|
||||||
|
|||||||
@@ -88,12 +88,13 @@ export const getStaticPaths = (() => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
underline="always"
|
underline="always"
|
||||||
>
|
>
|
||||||
{literals(strings.website.home.help_translate, [
|
{literals(strings.website.home.help_translate, {
|
||||||
"BlitzKit",
|
name: "BlitzKit",
|
||||||
|
language:
|
||||||
strings.common.locales[
|
strings.common.locales[
|
||||||
locale as keyof typeof strings.common.locales
|
locale as keyof typeof strings.common.locales
|
||||||
],
|
],
|
||||||
])}
|
})}
|
||||||
</Link>
|
</Link>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -277,14 +277,14 @@ function Content() {
|
|||||||
>
|
>
|
||||||
<Flex mt="2" mb="2" direction="column" gap="2">
|
<Flex mt="2" mb="2" direction="column" gap="2">
|
||||||
<Heading size="5">
|
<Heading size="5">
|
||||||
{literals(strings.website.tools.session.controls.title, [
|
{literals(strings.website.tools.session.controls.title, {
|
||||||
`${accountInfo?.nickname}`,
|
name: accountInfo?.nickname,
|
||||||
])}
|
})}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text color="gray">
|
<Text color="gray">
|
||||||
{literals(strings.website.tools.session.controls.subtitle, [
|
{literals(strings.website.tools.session.controls.subtitle, {
|
||||||
new Date(session.player.since).toLocaleString(locale),
|
time: new Date(session.player.since).toLocaleString(locale),
|
||||||
])}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -49,15 +49,16 @@ const tankModelDefinitions = await fetchModelDefinitions();
|
|||||||
const tank = tankDefinitions.tanks[id];
|
const tank = tankDefinitions.tanks[id];
|
||||||
const tankModelDefinition = tankModelDefinitions.models[tank.id];
|
const tankModelDefinition = tankModelDefinitions.models[tank.id];
|
||||||
const unwrap = unwrapper(locale);
|
const unwrap = unwrapper(locale);
|
||||||
const title = literals(strings.website.tools.tankopedia.tank_seo_title, [
|
const title = literals(strings.website.tools.tankopedia.tank_seo_title, {
|
||||||
unwrap(tank.name),
|
name: unwrap(tank.name),
|
||||||
TIER_ROMAN_NUMERALS[tank.tier],
|
tier: TIER_ROMAN_NUMERALS[tank.tier],
|
||||||
|
nationality:
|
||||||
strings.common.nations_adjectives[
|
strings.common.nations_adjectives[
|
||||||
tank.nation as keyof typeof strings.common.nations_adjectives
|
tank.nation as keyof typeof strings.common.nations_adjectives
|
||||||
],
|
],
|
||||||
strings.common.tank_class_short[tank.class],
|
class: strings.common.tank_class_short[tank.class],
|
||||||
"BlitzKit",
|
website: "BlitzKit",
|
||||||
]);
|
});
|
||||||
const url = import.meta.env.SITE + Astro.url.pathname;
|
const url = import.meta.env.SITE + Astro.url.pathname;
|
||||||
|
|
||||||
const icon = asset(`icons/tanks/big/${id}.webp`);
|
const icon = asset(`icons/tanks/big/${id}.webp`);
|
||||||
@@ -90,14 +91,14 @@ const characteristics = tankCharacteristics(
|
|||||||
);
|
);
|
||||||
const description = literals(
|
const description = literals(
|
||||||
strings.website.tools.tankopedia.tank_seo_description,
|
strings.website.tools.tankopedia.tank_seo_description,
|
||||||
[
|
{
|
||||||
unwrap(tank.name),
|
name: unwrap(tank.name),
|
||||||
Math.round(characteristics.dpm).toLocaleString(locale),
|
dpm: Math.round(characteristics.dpm).toLocaleString(locale),
|
||||||
Math.round(characteristics.damage).toLocaleString(locale),
|
alpha: Math.round(characteristics.damage).toLocaleString(locale),
|
||||||
Math.round(characteristics.penetration).toLocaleString(locale),
|
penetration: Math.round(characteristics.penetration).toLocaleString(locale),
|
||||||
Math.round(characteristics.speedForwards).toLocaleString(locale),
|
speed: Math.round(characteristics.speedForwards).toLocaleString(locale),
|
||||||
Math.round(characteristics.health).toLocaleString(locale),
|
health: Math.round(characteristics.health).toLocaleString(locale),
|
||||||
]
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface TankGuide {
|
export interface TankGuide {
|
||||||
|
|||||||
Reference in new issue
Block a user