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