mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
fix: update all
This commit is contained in:
1 parent
bdea9fdab3
commit
e2f50871ec
86 files changed
+1939
-1537
No files matched your search
@@ -43,7 +43,7 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
tankStats.map(async (tankData) => ({
|
tankStats.map(async (tankData) => ({
|
||||||
tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!,
|
tankDefinitions: (await tankDefinitions).tanks[tankData.tank_id]!,
|
||||||
id: tankData.tank_id,
|
id: tankData.tank_id,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
).filter((tank) => tank.tankDefinitions?.tier === tier);
|
||||||
const groupedTanks: Record<string, TankDefinition[]> = {};
|
const groupedTanks: Record<string, TankDefinition[]> = {};
|
||||||
@@ -69,16 +69,16 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
.filter((tank) => tank.tier === tier)
|
.filter((tank) => tank.tier === tier)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class)
|
TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class),
|
||||||
)
|
)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
||||||
awaitedGameDefinitions.nations.indexOf(b.nation)
|
awaitedGameDefinitions.nations.indexOf(b.nation),
|
||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
(tank) =>
|
(tank) =>
|
||||||
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tanks/${tank.slug}>)`
|
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name!))}](<https://blitzkit.app/tanks/${tank.slug}>)`,
|
||||||
);
|
);
|
||||||
const lines = [title, ...tankList];
|
const lines = [title, ...tankList];
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ async function playerListing(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name)}
|
alt={unwrap(tank.name!)}
|
||||||
src={await iconPng(tankIcon(tank.id))}
|
src={await iconPng(tankIcon(tank.id))}
|
||||||
style={{
|
style={{
|
||||||
width: 32,
|
width: 32,
|
||||||
@@ -317,7 +317,7 @@ async function playerListing(
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{unwrap(tank.name)}
|
{unwrap(tank.name!)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -35,49 +35,49 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
command: createLocalizedCommand("research")
|
command: createLocalizedCommand("research")
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(strings.bot.commands.research.options.target_tank.name)
|
.setName(strings.bot.commands.research.options.target_tank.name!)
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.target_tank.name,
|
strings.bot.commands.research.options.target_tank.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.research.options.target_tank.description
|
strings.bot.commands.research.options.target_tank.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.target_tank.description
|
strings.bot.commands.research.options.target_tank.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true)
|
||||||
.setRequired(true)
|
.setRequired(true),
|
||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(strings.bot.commands.research.options.starting_tank.name)
|
.setName(strings.bot.commands.research.options.starting_tank.name!)
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.starting_tank.name,
|
strings.bot.commands.research.options.starting_tank.name,
|
||||||
undefined,
|
undefined,
|
||||||
true
|
true,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
strings.bot.commands.research.options.starting_tank.description
|
strings.bot.commands.research.options.starting_tank.description,
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) =>
|
(strings) =>
|
||||||
strings.bot.commands.research.options.starting_tank.description
|
strings.bot.commands.research.options.starting_tank.description,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true)
|
||||||
.setRequired(false)
|
.setRequired(false),
|
||||||
)
|
)
|
||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const targetTankId = await resolveTankId(
|
const targetTankId = await resolveTankId(
|
||||||
interaction.options.getString("target-tank", true),
|
interaction.options.getString("target-tank", true),
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
const startingTankRaw = interaction.options.getString("starting-tank");
|
const startingTankRaw = interaction.options.getString("starting-tank");
|
||||||
const targetTank = awaitedTankDefinitions.tanks[targetTankId];
|
const targetTank = awaitedTankDefinitions.tanks[targetTankId];
|
||||||
@@ -99,13 +99,13 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
startingTankId = await resolveTankId(
|
startingTankId = await resolveTankId(
|
||||||
startingTankRaw,
|
startingTankRaw,
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (targetTankId === startingTankId) {
|
if (targetTankId === startingTankId) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.start_end_equal,
|
strings.bot.commands.research.errors.start_end_equal,
|
||||||
{ tank: escapeMarkdown(unwrap(targetTank.name)) }
|
{ tank: escapeMarkdown(unwrap(targettank.name!)) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +113,8 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
if (startingTank.tier > targetTank.tier) {
|
if (startingTank.tier > targetTank.tier) {
|
||||||
return literals(strings.bot.commands.research.errors.unordered, {
|
return literals(strings.bot.commands.research.errors.unordered, {
|
||||||
tank_1: escapeMarkdown(unwrap(startingTank.name)),
|
tank_1: escapeMarkdown(unwrap(startingtank.name!)),
|
||||||
tank_2: escapeMarkdown(unwrap(targetTank.name)),
|
tank_2: escapeMarkdown(unwrap(targettank.name!)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,21 +122,21 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.tanks_not_on_line,
|
strings.bot.commands.research.errors.tanks_not_on_line,
|
||||||
{
|
{
|
||||||
tank_1: escapeMarkdown(unwrap(targetTank.name)),
|
tank_1: escapeMarkdown(unwrap(targettank.name!)),
|
||||||
tank_2: escapeMarkdown(unwrap(startingTank.name)),
|
tank_2: escapeMarkdown(unwrap(startingtank.name!)),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (targetTank.type !== TankType.RESEARCHABLE) {
|
if (targetTank.type !== TankType.TANK_TYPE_RESEARCHABLE) {
|
||||||
return literals(strings.bot.commands.research.errors.non_tech_tree, {
|
return literals(strings.bot.commands.research.errors.non_tech_tree, {
|
||||||
tank: escapeMarkdown(unwrap(targetTank.name)),
|
tank: escapeMarkdown(unwrap(targettank.name!)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetTank.tier === 1) {
|
if (targetTank.tier === 1) {
|
||||||
return literals(strings.bot.commands.research.errors.tier_1, {
|
return literals(strings.bot.commands.research.errors.tier_1, {
|
||||||
tank: escapeMarkdown(unwrap(targetTank.name)),
|
tank: escapeMarkdown(unwrap(targettank.name!)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,14 +149,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
||||||
return literals(
|
return literals(
|
||||||
strings.bot.commands.research.errors.already_researched,
|
strings.bot.commands.research.errors.already_researched,
|
||||||
{ tank: escapeMarkdown(unwrap(targetTank.name)) }
|
{ tank: escapeMarkdown(unwrap(targettank.name!)) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundAncestor = targetTankAncestry.find(
|
const foundAncestor = targetTankAncestry.find(
|
||||||
(id) =>
|
(id) =>
|
||||||
awaitedTankDefinitions.tanks[id].tier === 1 ||
|
awaitedTankDefinitions.tanks[id].tier === 1 ||
|
||||||
tankStats.some(({ tank_id }) => tank_id === id)
|
tankStats.some(({ tank_id }) => tank_id === id),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (foundAncestor === undefined) {
|
if (foundAncestor === undefined) {
|
||||||
@@ -168,7 +168,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
const techTreeLine = await buildTechTreeLine(
|
const techTreeLine = await buildTechTreeLine(
|
||||||
startingTankId,
|
startingTankId,
|
||||||
targetTankId
|
targetTankId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const line = [...techTreeLine, startingTankId].reverse();
|
const line = [...techTreeLine, startingTankId].reverse();
|
||||||
@@ -190,7 +190,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
) {
|
) {
|
||||||
turretXps.set(
|
turretXps.set(
|
||||||
turret.id,
|
turret.id,
|
||||||
turret.research_cost.research_cost_type!.value
|
turret.research_cost.research_cost_type!.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
) {
|
) {
|
||||||
engineXps.set(
|
engineXps.set(
|
||||||
engine.id,
|
engine.id,
|
||||||
engine.research_cost.research_cost_type!.value
|
engine.research_cost.research_cost_type!.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -223,7 +223,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
) {
|
) {
|
||||||
trackXps.set(
|
trackXps.set(
|
||||||
track.id,
|
track.id,
|
||||||
track.research_cost.research_cost_type!.value
|
track.research_cost.research_cost_type!.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -245,13 +245,13 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
return {
|
return {
|
||||||
research: tank.research_cost!.research_cost_type!.value as number,
|
research: tank.research_cost!.research_cost_type!.value as number,
|
||||||
upgrades: moduleXp,
|
upgrades: moduleXp,
|
||||||
purchase: tank.price.value,
|
purchase: tank.price!.value,
|
||||||
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
||||||
(a, b) => a + 3 * b,
|
(a, b) => a + 3 * b,
|
||||||
0
|
0,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const image = (
|
const image = (
|
||||||
@@ -456,7 +456,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name)}
|
alt={unwrap(tank.name!)}
|
||||||
src={TANK_ICONS[tank.class]}
|
src={TANK_ICONS[tank.class]}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -467,7 +467,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{unwrap(tank.name)}
|
{unwrap(tank.name!)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,9 +505,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
strings.bot.commands.research.body.research,
|
strings.bot.commands.research.body.research,
|
||||||
{
|
{
|
||||||
cost: costs[index].research?.toLocaleString(
|
cost: costs[index].research?.toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -523,7 +523,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<img
|
<img
|
||||||
alt="Free XP icon"
|
alt="Free XP icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset("icons/currencies/free-xp.webp")
|
asset("icons/currencies/free-xp.webp"),
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -536,7 +536,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
{literals(
|
{literals(
|
||||||
strings.bot.commands.research.body.upgrades,
|
strings.bot.commands.research.body.upgrades,
|
||||||
{ cost: costs[index].upgrades.toLocaleString() }
|
{ cost: costs[index].upgrades.toLocaleString() },
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -552,7 +552,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset("icons/currencies/silver.webp")
|
asset("icons/currencies/silver.webp"),
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -567,9 +567,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
strings.bot.commands.research.body.purchase,
|
strings.bot.commands.research.body.purchase,
|
||||||
{
|
{
|
||||||
cost: costs[index].purchase.toLocaleString(
|
cost: costs[index].purchase.toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -584,7 +584,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
<img
|
<img
|
||||||
alt="Silver icon"
|
alt="Silver icon"
|
||||||
src={await iconPng(
|
src={await iconPng(
|
||||||
asset("icons/currencies/silver.webp")
|
asset("icons/currencies/silver.webp"),
|
||||||
)}
|
)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
@@ -599,16 +599,16 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
strings.bot.commands.research.body.equipment,
|
strings.bot.commands.research.body.equipment,
|
||||||
{
|
{
|
||||||
cost: costs[index].equipment.toLocaleString(
|
cost: costs[index].equipment.toLocaleString(
|
||||||
interaction.locale
|
interaction.locale,
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
}),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CommandWrapper>
|
</CommandWrapper>
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import {
|
|||||||
TankClass,
|
TankClass,
|
||||||
TankType,
|
TankType,
|
||||||
TREE_TYPE_ICONS,
|
TREE_TYPE_ICONS,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { createColors } from 'bepaint';
|
import { createColors } from "bepaint";
|
||||||
import {
|
import {
|
||||||
type AccentColor,
|
type AccentColor,
|
||||||
type GrayColor,
|
type GrayColor,
|
||||||
PALETTES,
|
PALETTES,
|
||||||
} from '../../../core/radix/radixColors';
|
} from "../../../core/radix/radixColors";
|
||||||
import { RowStat } from './RowStat';
|
import { RowStat } from "./RowStat";
|
||||||
|
|
||||||
export interface RowStatItem {
|
export interface RowStatItem {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -21,7 +21,7 @@ export interface RowStatItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RowProps {
|
interface RowProps {
|
||||||
displayType?: 'tank' | 'summary';
|
displayType?: "tank" | "summary";
|
||||||
naked?: boolean;
|
naked?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
minimized?: boolean;
|
minimized?: boolean;
|
||||||
@@ -32,7 +32,7 @@ interface RowProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Row({
|
export function Row({
|
||||||
color = 'slate',
|
color = "slate",
|
||||||
stats,
|
stats,
|
||||||
title,
|
title,
|
||||||
minimized,
|
minimized,
|
||||||
@@ -49,20 +49,20 @@ export function Row({
|
|||||||
className="session-tracker-row"
|
className="session-tracker-row"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: naked ? 600 : Infinity,
|
maxWidth: naked ? 600 : Infinity,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: naked
|
backgroundColor: naked
|
||||||
? `${theme.appBackground2}c0`
|
? `${theme.appBackground2}c0`
|
||||||
: theme.appBackground2,
|
: theme.appBackground2,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
padding: 8,
|
padding: 8,
|
||||||
gap: 4,
|
gap: 4,
|
||||||
backgroundColor: naked
|
backgroundColor: naked
|
||||||
@@ -70,10 +70,14 @@ export function Row({
|
|||||||
: theme.componentInteractive,
|
: theme.componentInteractive,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{displayType === 'tank' && tankClass !== undefined && (
|
{displayType === "tank" && tankClass !== undefined && (
|
||||||
<img
|
<img
|
||||||
alt={TankClass[tankClass]}
|
alt={TankClass[tankClass]}
|
||||||
src={TREE_TYPE_ICONS[type ?? TankType.RESEARCHABLE][tankClass]}
|
src={
|
||||||
|
TREE_TYPE_ICONS[type ?? TankType.TANK_TYPE_RESEARCHABLE][
|
||||||
|
tankClass
|
||||||
|
]
|
||||||
|
}
|
||||||
style={{ width: 16, height: 16 }}
|
style={{ width: 16, height: 16 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -81,10 +85,10 @@ export function Row({
|
|||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
color:
|
color:
|
||||||
displayType === 'tank'
|
displayType === "tank"
|
||||||
? type === TankType.COLLECTOR
|
? type === TankType.TANK_TYPE_COLLECTOR
|
||||||
? theme.textLowContrast_blue
|
? theme.textLowContrast_blue
|
||||||
: type === TankType.PREMIUM
|
: type === TankType.TANK_TYPE_PREMIUM
|
||||||
? theme.textLowContrast_amber
|
? theme.textLowContrast_amber
|
||||||
: theme.textHighContrast
|
: theme.textHighContrast
|
||||||
: theme.textLowContrast,
|
: theme.textLowContrast,
|
||||||
@@ -98,7 +102,7 @@ export function Row({
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
}}
|
}}
|
||||||
@@ -109,8 +113,8 @@ export function Row({
|
|||||||
<RowStat
|
<RowStat
|
||||||
minimized={minimized}
|
minimized={minimized}
|
||||||
key={index}
|
key={index}
|
||||||
name={`${row.title} • ${row.career ?? '--'}`}
|
name={`${row.title} • ${row.career ?? "--"}`}
|
||||||
value={row.current ?? '--'}
|
value={row.current ?? "--"}
|
||||||
delta={row.delta}
|
delta={row.delta}
|
||||||
percentile={row.percentile}
|
percentile={row.percentile}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TankClass, TankType, TREE_TYPE_ICONS } from '@blitzkit/core';
|
import { TankClass, TankType, TREE_TYPE_ICONS } from "@blitzkit/core";
|
||||||
import { theme } from '../../../../stitches.config';
|
import { theme } from "../../../../stitches.config";
|
||||||
|
|
||||||
export interface ItemProps {
|
export interface ItemProps {
|
||||||
tankClass?: TankClass;
|
tankClass?: TankClass;
|
||||||
@@ -8,9 +8,9 @@ export interface ItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TREE_TYPE_COLOR = {
|
const TREE_TYPE_COLOR = {
|
||||||
[TankType.RESEARCHABLE]: '',
|
[TankType.TANK_TYPE_RESEARCHABLE]: "",
|
||||||
[TankType.PREMIUM]: '_amber',
|
[TankType.TANK_TYPE_PREMIUM]: "_amber",
|
||||||
[TankType.COLLECTOR]: '_blue',
|
[TankType.TANK_TYPE_COLLECTOR]: "_blue",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export function Item({
|
export function Item({
|
||||||
@@ -21,12 +21,12 @@ export function Item({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
height: 32,
|
height: 32,
|
||||||
paddingLeft: 8,
|
paddingLeft: 8,
|
||||||
paddingRight: 8,
|
paddingRight: 8,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colors[`componentInteractive${TREE_TYPE_COLOR[treeType]}`],
|
theme.colors[`componentInteractive${TREE_TYPE_COLOR[treeType]}`],
|
||||||
@@ -34,12 +34,12 @@ export function Item({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'flex-start',
|
justifyContent: "flex-start",
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tankType && (
|
{tankType && (
|
||||||
@@ -52,7 +52,7 @@ export function Item({
|
|||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: "nowrap",
|
||||||
color: theme.colors[`textLowContrast${TREE_TYPE_COLOR[treeType]}`],
|
color: theme.colors[`textLowContrast${TREE_TYPE_COLOR[treeType]}`],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ import { translator } from "../localization/translator";
|
|||||||
|
|
||||||
export async function filtersToDescription(
|
export async function filtersToDescription(
|
||||||
{ nation, tier, type, class: tankClass, tank }: StatFilters,
|
{ nation, tier, type, class: tankClass, tank }: StatFilters,
|
||||||
locale: Locale
|
locale: Locale,
|
||||||
) {
|
) {
|
||||||
const { strings, bkLocale } = translator(locale);
|
const { strings, bkLocale } = translator(locale);
|
||||||
const awaitedTankDefinitions = await fetchTankDefinitions();
|
const awaitedTankDefinitions = await fetchTankDefinitions();
|
||||||
const info: string[] = [];
|
const info: string[] = [];
|
||||||
|
|
||||||
// TODO: somehow localize tank names?
|
// TODO: somehow localize tank names?
|
||||||
if (tank) return awaitedTankDefinitions.tanks[tank].name.locales[bkLocale];
|
if (tank) return awaitedTankDefinitions.tanks[tank].name!.locales[bkLocale];
|
||||||
if (nation)
|
if (nation)
|
||||||
info.push((strings.common.nations as Record<string, string>)[nation]);
|
info.push((strings.common.nations as Record<string, string>)[nation]);
|
||||||
if (tier) {
|
if (tier) {
|
||||||
info.push(
|
info.push(
|
||||||
literals(strings.bot.common.filters.tier, {
|
literals(strings.bot.common.filters.tier, {
|
||||||
tier: TIER_ROMAN_NUMERALS[tier],
|
tier: TIER_ROMAN_NUMERALS[tier],
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (tankClass) info.push(strings.common.tank_class_short[tankClass]);
|
if (tankClass) info.push(strings.common.tank_class_short[tankClass]);
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ export async function filterStats(
|
|||||||
(filters.tier === undefined || entry.tier === filters.tier) &&
|
(filters.tier === undefined || entry.tier === filters.tier) &&
|
||||||
(filters.class === undefined || entry.class === filters.class) &&
|
(filters.class === undefined || entry.class === filters.class) &&
|
||||||
(filters.type === undefined ||
|
(filters.type === undefined ||
|
||||||
(filters.type === TankType.COLLECTOR &&
|
(filters.type === TankType.TANK_TYPE_COLLECTOR &&
|
||||||
entry.type === TankType.COLLECTOR) ||
|
entry.type === TankType.TANK_TYPE_COLLECTOR) ||
|
||||||
(filters.type === TankType.PREMIUM &&
|
(filters.type === TankType.TANK_TYPE_PREMIUM &&
|
||||||
entry.type === TankType.PREMIUM) ||
|
entry.type === TankType.TANK_TYPE_PREMIUM) ||
|
||||||
(filters.type === TankType.RESEARCHABLE &&
|
(filters.type === TankType.TANK_TYPE_RESEARCHABLE &&
|
||||||
entry.type === TankType.RESEARCHABLE)) &&
|
entry.type === TankType.TANK_TYPE_RESEARCHABLE)) &&
|
||||||
(filters.tank === undefined || entry.id === filters.tank)
|
(filters.tank === undefined || entry.id === filters.tank)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Locale, SlashCommandStringOption } from 'discord.js';
|
import { Locale, SlashCommandStringOption } from "discord.js";
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from "../localization/translator";
|
||||||
import { localizationObject } from './localizationObject';
|
import { localizationObject } from "./localizationObject";
|
||||||
|
|
||||||
export function addTankChoices(option: SlashCommandStringOption) {
|
export function addTankChoices(option: SlashCommandStringOption) {
|
||||||
const { strings } = translator(Locale.EnglishUS);
|
const { strings } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
return option
|
return option
|
||||||
.setName(strings.bot.common.options.tank.name)
|
.setName(strings.bot.common.options.tank.name!)
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject(
|
localizationObject(
|
||||||
(strings) => strings.bot.common.options.tank.name,
|
(strings) => strings.bot.common.options.tank.name,
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import {
|
|||||||
} from "./autocompleteClan/constants";
|
} from "./autocompleteClan/constants";
|
||||||
|
|
||||||
export const tankNamesTechTreeOnly = fetchTankNames().then((names) =>
|
export const tankNamesTechTreeOnly = fetchTankNames().then((names) =>
|
||||||
names.filter((tank) => tank.treeType === TankType.RESEARCHABLE)
|
names.filter((tank) => tank.treeType === TankType.TANK_TYPE_RESEARCHABLE),
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function autocompleteTanks(
|
export async function autocompleteTanks(
|
||||||
interaction: AutocompleteInteraction<CacheType>,
|
interaction: AutocompleteInteraction<CacheType>,
|
||||||
techTreeOnly = false,
|
techTreeOnly = false,
|
||||||
fields = ["tank"]
|
fields = ["tank"],
|
||||||
) {
|
) {
|
||||||
const { unwrap } = translator(interaction.locale);
|
const { unwrap } = translator(interaction.locale);
|
||||||
const focusedOption = interaction.options.getFocused(true);
|
const focusedOption = interaction.options.getFocused(true);
|
||||||
@@ -31,7 +31,7 @@ export async function autocompleteTanks(
|
|||||||
go(
|
go(
|
||||||
focusedOption.value,
|
focusedOption.value,
|
||||||
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
|
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
|
||||||
{ keys: SEARCH_KEYS, limit: 10 }
|
{ keys: SEARCH_KEYS, limit: 10 },
|
||||||
).map(async (item) => {
|
).map(async (item) => {
|
||||||
let name = unwrap(item.obj.searchableName);
|
let name = unwrap(item.obj.searchableName);
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export async function autocompleteTanks(
|
|||||||
0,
|
0,
|
||||||
unwrap(item.obj.searchableName).length -
|
unwrap(item.obj.searchableName).length -
|
||||||
overSize -
|
overSize -
|
||||||
OVERFLOW_SUFFIX.length
|
OVERFLOW_SUFFIX.length,
|
||||||
)}${OVERFLOW_SUFFIX}`;
|
)}${OVERFLOW_SUFFIX}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ export async function autocompleteTanks(
|
|||||||
name,
|
name,
|
||||||
value: `${item.obj.id}`,
|
value: `${item.obj.id}`,
|
||||||
} satisfies ApplicationCommandOptionChoiceData<string>;
|
} satisfies ApplicationCommandOptionChoiceData<string>;
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
: []
|
: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { ShellDefinition, ShellType } from "../protos";
|
import { ShellDefinition, ShellType } from "../protos";
|
||||||
|
|
||||||
export function canSplash(type: ShellDefinition["type"]): type is ShellType.HE {
|
export function canSplash(
|
||||||
return type === ShellType.HE;
|
type: ShellDefinition["type"],
|
||||||
|
): type is ShellType.SHELL_TYPE_HE {
|
||||||
|
return type === ShellType.SHELL_TYPE_HE;
|
||||||
}
|
}
|
||||||
@@ -2,22 +2,22 @@ import { TankClass, TankType } from "../protos";
|
|||||||
import type { BlitzStats } from "../statistics";
|
import type { BlitzStats } from "../statistics";
|
||||||
|
|
||||||
export const TANK_TYPES = [
|
export const TANK_TYPES = [
|
||||||
TankType.RESEARCHABLE,
|
TankType.TANK_TYPE_RESEARCHABLE,
|
||||||
TankType.PREMIUM,
|
TankType.TANK_TYPE_PREMIUM,
|
||||||
TankType.COLLECTOR,
|
TankType.TANK_TYPE_COLLECTOR,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TANK_TYPE_COMMAND_NAMES = {
|
export const TANK_TYPE_COMMAND_NAMES = {
|
||||||
[TankType.RESEARCHABLE]: "researchable",
|
[TankType.TANK_TYPE_RESEARCHABLE]: "researchable",
|
||||||
[TankType.PREMIUM]: "premium",
|
[TankType.TANK_TYPE_PREMIUM]: "premium",
|
||||||
[TankType.COLLECTOR]: "collector",
|
[TankType.TANK_TYPE_COLLECTOR]: "collector",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TANK_CLASSES = [
|
export const TANK_CLASSES = [
|
||||||
TankClass.LIGHT,
|
TankClass.TANK_CLASS_LIGHT,
|
||||||
TankClass.MEDIUM,
|
TankClass.TANK_CLASS_MEDIUM,
|
||||||
TankClass.HEAVY,
|
TankClass.TANK_CLASS_HEAVY,
|
||||||
TankClass.TANK_DESTROYER,
|
TankClass.TANK_CLASS_TANK_DESTROYER,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const emptyAllStats: BlitzStats = {
|
export const emptyAllStats: BlitzStats = {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
export function checkConsumableProvisionInclusivity(
|
export function checkConsumableProvisionInclusivity(
|
||||||
consumableProvision: Consumable | Provision,
|
consumableProvision: Consumable | Provision,
|
||||||
tank: TankDefinition,
|
tank: TankDefinition,
|
||||||
gun: GunDefinition
|
gun: GunDefinition,
|
||||||
) {
|
) {
|
||||||
const included = consumableProvision.include?.every((rule) => {
|
const included = consumableProvision.include?.every((rule) => {
|
||||||
switch (rule.filter_type!.$case) {
|
switch (rule.filter_type!.$case) {
|
||||||
@@ -46,7 +46,7 @@ export function checkConsumableProvisionInclusivity(
|
|||||||
case "categories":
|
case "categories":
|
||||||
return rule.filter_type!.value.categories.some((category) => {
|
return rule.filter_type!.value.categories.some((category) => {
|
||||||
switch (category) {
|
switch (category) {
|
||||||
case ConsumableTankCategoryFilterCategory.CLIP:
|
case ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP:
|
||||||
return gun.gun_type!.$case !== "regular";
|
return gun.gun_type!.$case !== "regular";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ShellDefinition, ShellType } from "../protos";
|
import { ShellDefinition, ShellType } from "../protos";
|
||||||
|
|
||||||
export function isExplosive(
|
export function isExplosive(
|
||||||
type: ShellDefinition["type"]
|
type: ShellDefinition["type"],
|
||||||
): type is ShellType.HEAT | ShellType.HE {
|
): type is ShellType.SHELL_TYPE_HEAT | ShellType.SHELL_TYPE_HE {
|
||||||
return type === ShellType.HEAT || type === ShellType.HE;
|
return type === ShellType.SHELL_TYPE_HEAT || type === ShellType.SHELL_TYPE_HE;
|
||||||
}
|
}
|
||||||
@@ -2,27 +2,27 @@ import { TANK_CLASSES } from "../blitz/constants";
|
|||||||
import { GameDefinitions, TankDefinition, TankType } from "../protos";
|
import { GameDefinitions, TankDefinition, TankType } from "../protos";
|
||||||
|
|
||||||
const treeTypeOrder = [
|
const treeTypeOrder = [
|
||||||
TankType.RESEARCHABLE,
|
TankType.TANK_TYPE_RESEARCHABLE,
|
||||||
TankType.PREMIUM,
|
TankType.TANK_TYPE_PREMIUM,
|
||||||
TankType.COLLECTOR,
|
TankType.TANK_TYPE_COLLECTOR,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function metaSortTank(
|
export function metaSortTank(
|
||||||
tanks: TankDefinition[],
|
tanks: TankDefinition[],
|
||||||
gameDefinitions: GameDefinitions
|
gameDefinitions: GameDefinitions,
|
||||||
) {
|
) {
|
||||||
return tanks
|
return tanks
|
||||||
.sort((a, b) => b.tier - a.tier)
|
.sort((a, b) => b.tier - a.tier)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) => treeTypeOrder.indexOf(b.type) - treeTypeOrder.indexOf(a.type)
|
(a, b) => treeTypeOrder.indexOf(b.type) - treeTypeOrder.indexOf(a.type),
|
||||||
)
|
)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) => TANK_CLASSES.indexOf(b.class) - TANK_CLASSES.indexOf(a.class)
|
(a, b) => TANK_CLASSES.indexOf(b.class) - TANK_CLASSES.indexOf(a.class),
|
||||||
)
|
)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
gameDefinitions.nations.indexOf(b.nation) -
|
gameDefinitions.nations.indexOf(b.nation) -
|
||||||
gameDefinitions.nations.indexOf(a.nation)
|
gameDefinitions.nations.indexOf(a.nation),
|
||||||
)
|
)
|
||||||
.sort((a, b) => a.tier - b.tier);
|
.sort((a, b) => a.tier - b.tier);
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ import { BoundingBox, Vector3 } from "../protos";
|
|||||||
|
|
||||||
export function normalizeBoundingBox(boundingBox: BoundingBox) {
|
export function normalizeBoundingBox(boundingBox: BoundingBox) {
|
||||||
return {
|
return {
|
||||||
x: boundingBox.max.x - boundingBox.min.x,
|
x: boundingBox.max!.x - boundingBox.min!.x,
|
||||||
y: boundingBox.max.y - boundingBox.min.y,
|
y: boundingBox.max!.y - boundingBox.min!.y,
|
||||||
z: boundingBox.max.z - boundingBox.min.z,
|
z: boundingBox.max!.z - boundingBox.min!.z,
|
||||||
} satisfies Vector3;
|
} satisfies Vector3;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ShellType } from '../protos';
|
import { ShellType } from "../protos";
|
||||||
|
|
||||||
export function resolvePenetrationCoefficient(
|
export function resolvePenetrationCoefficient(
|
||||||
calibratedShells: boolean,
|
calibratedShells: boolean,
|
||||||
@@ -6,11 +6,11 @@ export function resolvePenetrationCoefficient(
|
|||||||
) {
|
) {
|
||||||
if (!calibratedShells) return 1;
|
if (!calibratedShells) return 1;
|
||||||
|
|
||||||
return type === ShellType.AP
|
return type === ShellType.SHELL_TYPE_AP
|
||||||
? 1.08
|
? 1.08
|
||||||
: type === ShellType.APCR
|
: type === ShellType.SHELL_TYPE_APCR
|
||||||
? 1.05
|
? 1.05
|
||||||
: type === ShellType.HEAT
|
: type === ShellType.SHELL_TYPE_HEAT
|
||||||
? 1.13
|
? 1.13
|
||||||
: 1.08;
|
: 1.08;
|
||||||
}
|
}
|
||||||
@@ -1,74 +1,74 @@
|
|||||||
import { times } from 'lodash-es';
|
import { times } from "lodash-es";
|
||||||
import { CrewType, ShellType, TankClass, TankType } from '../../protos';
|
import { CrewType, ShellType, TankClass, TankType } from "../../protos";
|
||||||
|
|
||||||
export const SHELL_NAMES: Record<ShellType, string> = {
|
export const SHELL_NAMES: Record<ShellType, string> = {
|
||||||
[ShellType.AP]: 'AP',
|
[ShellType.SHELL_TYPE_AP]: "AP",
|
||||||
[ShellType.APCR]: 'APCR',
|
[ShellType.SHELL_TYPE_APCR]: "APCR",
|
||||||
[ShellType.HEAT]: 'HEAT',
|
[ShellType.SHELL_TYPE_HEAT]: "HEAT",
|
||||||
[ShellType.HE]: 'HE',
|
[ShellType.SHELL_TYPE_HE]: "HE",
|
||||||
};
|
};
|
||||||
export const CREW_MEMBER_NAMES = {
|
export const CREW_MEMBER_NAMES = {
|
||||||
[CrewType.COMMANDER]: 'commander',
|
[CrewType.CREW_TYPE_COMMANDER]: "commander",
|
||||||
[CrewType.DRIVER]: 'driver',
|
[CrewType.CREW_TYPE_DRIVER]: "driver",
|
||||||
[CrewType.GUNNER]: 'gunner',
|
[CrewType.CREW_TYPE_GUNNER]: "gunner",
|
||||||
[CrewType.LOADER]: 'loader',
|
[CrewType.CREW_TYPE_LOADER]: "loader",
|
||||||
[CrewType.RADIOMAN]: 'radioman',
|
[CrewType.CREW_TYPE_RADIOMAN]: "radioman",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use svg
|
* @deprecated use svg
|
||||||
*/
|
*/
|
||||||
export const TANK_ICONS: Record<TankClass, string> = {
|
export const TANK_ICONS: Record<TankClass, string> = {
|
||||||
[TankClass.TANK_DESTROYER]: 'https://i.imgur.com/BIHSEH0.png',
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/BIHSEH0.png",
|
||||||
[TankClass.LIGHT]: 'https://i.imgur.com/CSNha5V.png',
|
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/CSNha5V.png",
|
||||||
[TankClass.MEDIUM]: 'https://i.imgur.com/wvf3ltm.png',
|
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/wvf3ltm.png",
|
||||||
[TankClass.HEAVY]: 'https://i.imgur.com/ECeqlZa.png',
|
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/ECeqlZa.png",
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @deprecated use svg
|
* @deprecated use svg
|
||||||
*/
|
*/
|
||||||
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
|
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
|
||||||
[TankClass.TANK_DESTROYER]: 'https://i.imgur.com/TCu3EdR.png',
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/TCu3EdR.png",
|
||||||
[TankClass.LIGHT]: 'https://i.imgur.com/zdkpTRb.png',
|
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/zdkpTRb.png",
|
||||||
[TankClass.MEDIUM]: 'https://i.imgur.com/3z7eHX6.png',
|
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/3z7eHX6.png",
|
||||||
[TankClass.HEAVY]: 'https://i.imgur.com/P3vbmyA.png',
|
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/P3vbmyA.png",
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* @deprecated use svg
|
* @deprecated use svg
|
||||||
*/
|
*/
|
||||||
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
|
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
|
||||||
[TankClass.TANK_DESTROYER]: 'https://i.imgur.com/WTjeirB.png',
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/WTjeirB.png",
|
||||||
[TankClass.LIGHT]: 'https://i.imgur.com/EwhtKkU.png',
|
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/EwhtKkU.png",
|
||||||
[TankClass.MEDIUM]: 'https://i.imgur.com/u8YDMBh.png',
|
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/u8YDMBh.png",
|
||||||
[TankClass.HEAVY]: 'https://i.imgur.com/8xRf3nc.png',
|
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png",
|
||||||
};
|
};
|
||||||
export const TIERS = times(10, (index) => index + 1);
|
export const TIERS = times(10, (index) => index + 1);
|
||||||
export const TIER_ROMAN_NUMERALS: Record<number, string> = {
|
export const TIER_ROMAN_NUMERALS: Record<number, string> = {
|
||||||
1: 'I',
|
1: "I",
|
||||||
2: 'II',
|
2: "II",
|
||||||
3: 'III',
|
3: "III",
|
||||||
4: 'IV',
|
4: "IV",
|
||||||
5: 'V',
|
5: "V",
|
||||||
6: 'VI',
|
6: "VI",
|
||||||
7: 'VII',
|
7: "VII",
|
||||||
8: 'VIII',
|
8: "VIII",
|
||||||
9: 'IX',
|
9: "IX",
|
||||||
10: 'X',
|
10: "X",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const flags: Record<string, string> = {
|
export const flags: Record<string, string> = {
|
||||||
ussr: '<:ussr:1218421042033197197>',
|
ussr: "<:ussr:1218421042033197197>",
|
||||||
germany: '🇩🇪',
|
germany: "🇩🇪",
|
||||||
usa: '🇺🇸',
|
usa: "🇺🇸",
|
||||||
china: '🇨🇳',
|
china: "🇨🇳",
|
||||||
uk: '🇬🇧',
|
uk: "🇬🇧",
|
||||||
france: '🇫🇷',
|
france: "🇫🇷",
|
||||||
japan: '🇯🇵',
|
japan: "🇯🇵",
|
||||||
european: '🇪🇺',
|
european: "🇪🇺",
|
||||||
other: '<:other:1218421572243558482>',
|
other: "<:other:1218421572243558482>",
|
||||||
};
|
};
|
||||||
export const TREE_TYPE_ICONS: Record<TankType, Record<TankClass, string>> = {
|
export const TREE_TYPE_ICONS: Record<TankType, Record<TankClass, string>> = {
|
||||||
[TankType.RESEARCHABLE]: TANK_ICONS,
|
[TankType.TANK_TYPE_RESEARCHABLE]: TANK_ICONS,
|
||||||
[TankType.PREMIUM]: TANK_ICONS_PREMIUM,
|
[TankType.TANK_TYPE_PREMIUM]: TANK_ICONS_PREMIUM,
|
||||||
[TankType.COLLECTOR]: TANK_ICONS_COLLECTOR,
|
[TankType.TANK_TYPE_COLLECTOR]: TANK_ICONS_COLLECTOR,
|
||||||
};
|
};
|
||||||
@@ -15,7 +15,7 @@ export async function fetchTankNames() {
|
|||||||
tankDefinitionsArray.map(async (tank) => {
|
tankDefinitionsArray.map(async (tank) => {
|
||||||
const searchableNameDeburr: I18nString = { locales: {} };
|
const searchableNameDeburr: I18nString = { locales: {} };
|
||||||
|
|
||||||
Object.entries(tank.name).forEach(([key, value]) => {
|
Object.entries(tank.name!).forEach(([key, value]) => {
|
||||||
searchableNameDeburr.locales[key] = deburr(value);
|
searchableNameDeburr.locales[key] = deburr(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@ export async function fetchTankNames() {
|
|||||||
?.map((id) =>
|
?.map((id) =>
|
||||||
locales.supported.map(
|
locales.supported.map(
|
||||||
({ locale }) =>
|
({ locale }) =>
|
||||||
camouflageDefinitions.camouflages[id]?.name.locales[locale]
|
camouflageDefinitions.camouflages[id]?.name!.locales[locale],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.flat()
|
.flat()
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -37,7 +37,7 @@ export async function fetchTankNames() {
|
|||||||
.join(" "),
|
.join(" "),
|
||||||
treeType: tank.type,
|
treeType: tank.type,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { BoundingBox } from "../protos";
|
|||||||
export function unionBoundingBox(a: BoundingBox, b: BoundingBox) {
|
export function unionBoundingBox(a: BoundingBox, b: BoundingBox) {
|
||||||
return {
|
return {
|
||||||
min: {
|
min: {
|
||||||
x: Math.min(a.min.x, b.min.x),
|
x: Math.min(a.min!.x, b.min!.x),
|
||||||
y: Math.min(a.min.y, b.min.y),
|
y: Math.min(a.min!.y, b.min!.y),
|
||||||
z: Math.min(a.min.z, b.min.z),
|
z: Math.min(a.min!.z, b.min!.z),
|
||||||
},
|
},
|
||||||
max: {
|
max: {
|
||||||
x: Math.max(a.max.x, b.max.x),
|
x: Math.max(a.max!.x, b.max!.x),
|
||||||
y: Math.max(a.max.y, b.max.y),
|
y: Math.max(a.max!.y, b.max!.y),
|
||||||
z: Math.max(a.max.z, b.max.z),
|
z: Math.max(a.max!.z, b.max!.z),
|
||||||
},
|
},
|
||||||
} satisfies BoundingBox;
|
} satisfies BoundingBox;
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AverageDefinitions,
|
AverageDefinitions,
|
||||||
|
AverageDefinitionsAllStats,
|
||||||
AverageDefinitionsEntry,
|
AverageDefinitionsEntry,
|
||||||
type AverageDefinitionsManifest,
|
type AverageDefinitionsManifest,
|
||||||
type IndividualTankStats,
|
type IndividualTankStats,
|
||||||
@@ -30,9 +31,6 @@ type OptionalSecondLevel<T> = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type AverageDefinitionsEntrySubPartial =
|
|
||||||
OptionalSecondLevel<AverageDefinitionsEntry>;
|
|
||||||
|
|
||||||
const MINUTE = 60 * 1000;
|
const MINUTE = 60 * 1000;
|
||||||
const HOUR = 60 * MINUTE;
|
const HOUR = 60 * MINUTE;
|
||||||
const DAY = 24 * HOUR;
|
const DAY = 24 * HOUR;
|
||||||
@@ -47,15 +45,15 @@ const preDiscoveredIds = await fetchPreDiscoveredIds();
|
|||||||
const playerIds: Record<Region, number[][]> = {
|
const playerIds: Record<Region, number[][]> = {
|
||||||
asia: chunk(
|
asia: chunk(
|
||||||
preDiscoveredIds.filter((id) => idToRegion(id) === "asia"),
|
preDiscoveredIds.filter((id) => idToRegion(id) === "asia"),
|
||||||
PLAYER_IDS_PER_CALL
|
PLAYER_IDS_PER_CALL,
|
||||||
),
|
),
|
||||||
com: chunk(
|
com: chunk(
|
||||||
preDiscoveredIds.filter((id) => idToRegion(id) === "com"),
|
preDiscoveredIds.filter((id) => idToRegion(id) === "com"),
|
||||||
PLAYER_IDS_PER_CALL
|
PLAYER_IDS_PER_CALL,
|
||||||
),
|
),
|
||||||
eu: chunk(
|
eu: chunk(
|
||||||
preDiscoveredIds.filter((id) => idToRegion(id) === "eu"),
|
preDiscoveredIds.filter((id) => idToRegion(id) === "eu"),
|
||||||
PLAYER_IDS_PER_CALL
|
PLAYER_IDS_PER_CALL,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let regionIndex = 0;
|
let regionIndex = 0;
|
||||||
@@ -71,7 +69,7 @@ times(THREADS, async () => {
|
|||||||
const regionIds = playerIds[region];
|
const regionIds = playerIds[region];
|
||||||
const [ids] = regionIds.splice(
|
const [ids] = regionIds.splice(
|
||||||
Math.floor(Math.random() * regionIds.length),
|
Math.floor(Math.random() * regionIds.length),
|
||||||
1
|
1,
|
||||||
);
|
);
|
||||||
const accountInfo = await getAccountInfo(region, ids, undefined, {
|
const accountInfo = await getAccountInfo(region, ids, undefined, {
|
||||||
fields: "last_battle_time,statistics.all.battles",
|
fields: "last_battle_time,statistics.all.battles",
|
||||||
@@ -104,8 +102,8 @@ times(THREADS, async () => {
|
|||||||
filteredIds.map((id) =>
|
filteredIds.map((id) =>
|
||||||
getTankStats(region, id, {
|
getTankStats(region, id, {
|
||||||
fields: "last_battle_time,battle_life_time,all,tank_id",
|
fields: "last_battle_time,battle_life_time,all,tank_id",
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
players.forEach((tanks) => {
|
players.forEach((tanks) => {
|
||||||
@@ -114,7 +112,7 @@ times(THREADS, async () => {
|
|||||||
|
|
||||||
if (!tankIds.includes(tank.tank_id)) {
|
if (!tankIds.includes(tank.tank_id)) {
|
||||||
console.log(
|
console.log(
|
||||||
`Found new tank: ${tank.tank_id} (${tankIds.length + 1})`
|
`Found new tank: ${tank.tank_id} (${tankIds.length + 1})`,
|
||||||
);
|
);
|
||||||
tankIds.push(tank.tank_id);
|
tankIds.push(tank.tank_id);
|
||||||
tanksSorted[tank.tank_id] = [];
|
tanksSorted[tank.tank_id] = [];
|
||||||
@@ -140,7 +138,7 @@ async function postWork() {
|
|||||||
console.log(
|
console.log(
|
||||||
`Generating statistics based on ${samples.d_120.toLocaleString()} players (${samples.total.toLocaleString()} checked in total) and ${
|
`Generating statistics based on ${samples.d_120.toLocaleString()} players (${samples.total.toLocaleString()} checked in total) and ${
|
||||||
tankIds.length
|
tankIds.length
|
||||||
} tanks...`
|
} tanks...`,
|
||||||
);
|
);
|
||||||
|
|
||||||
using uploader = new AssetUploader("averages");
|
using uploader = new AssetUploader("averages");
|
||||||
@@ -152,7 +150,12 @@ async function postWork() {
|
|||||||
// (tank) => Date.now() - tank.last_battle_time * 1000 <= MAX_ACTIVITY_TIME,
|
// (tank) => Date.now() - tank.last_battle_time * 1000 <= MAX_ACTIVITY_TIME,
|
||||||
// );
|
// );
|
||||||
const tanks = tanksSortedEntry;
|
const tanks = tanksSortedEntry;
|
||||||
const entry: AverageDefinitionsEntrySubPartial = {
|
const entry: {
|
||||||
|
mu: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
|
||||||
|
sigma: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
|
||||||
|
r: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
|
||||||
|
samples: Samples;
|
||||||
|
} = {
|
||||||
mu: {},
|
mu: {},
|
||||||
sigma: {},
|
sigma: {},
|
||||||
r: {},
|
r: {},
|
||||||
@@ -162,13 +165,13 @@ async function postWork() {
|
|||||||
tanks.forEach((tank) => {
|
tanks.forEach((tank) => {
|
||||||
const timeSinceLastActivity = Date.now() - tank.last_battle_time * 1000;
|
const timeSinceLastActivity = Date.now() - tank.last_battle_time * 1000;
|
||||||
|
|
||||||
entry.samples.total++;
|
entry.samples!.total++;
|
||||||
if (timeSinceLastActivity <= 120 * DAY) entry.samples.d_120++;
|
if (timeSinceLastActivity <= 120 * DAY) entry.samples!.d_120++;
|
||||||
if (timeSinceLastActivity <= 90 * DAY) entry.samples.d_90++;
|
if (timeSinceLastActivity <= 90 * DAY) entry.samples!.d_90++;
|
||||||
if (timeSinceLastActivity <= 60 * DAY) entry.samples.d_60++;
|
if (timeSinceLastActivity <= 60 * DAY) entry.samples!.d_60++;
|
||||||
if (timeSinceLastActivity <= 30 * DAY) entry.samples.d_30++;
|
if (timeSinceLastActivity <= 30 * DAY) entry.samples!.d_30++;
|
||||||
if (timeSinceLastActivity <= 7 * DAY) entry.samples.d_7++;
|
if (timeSinceLastActivity <= 7 * DAY) entry.samples!.d_7++;
|
||||||
if (timeSinceLastActivity <= 1 * DAY) entry.samples.d_1++;
|
if (timeSinceLastActivity <= 1 * DAY) entry.samples!.d_1++;
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataWY = tanks.map((tank) => ({
|
const dataWY = tanks.map((tank) => ({
|
||||||
@@ -191,7 +194,7 @@ async function postWork() {
|
|||||||
function sum(slicer: (data: DataPoint) => number) {
|
function sum(slicer: (data: DataPoint) => number) {
|
||||||
return data.reduce(
|
return data.reduce(
|
||||||
(accumulator, data) => accumulator + slicer(data),
|
(accumulator, data) => accumulator + slicer(data),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,16 +81,16 @@ const blitzTankFilterDefinitionCategoryToBlitzkit: Record<
|
|||||||
BlitzTankFilterDefinitionCategory,
|
BlitzTankFilterDefinitionCategory,
|
||||||
ConsumableTankCategoryFilterCategory
|
ConsumableTankCategoryFilterCategory
|
||||||
> = {
|
> = {
|
||||||
clip: ConsumableTankCategoryFilterCategory.CLIP,
|
clip: ConsumableTankCategoryFilterCategory.CONSUMABLE_TANK_CATEGORY_FILTER_CATEGORY_CLIP,
|
||||||
};
|
};
|
||||||
function vector3TupleToBlitzkit(tuple: Vector3Tuple) {
|
function vector3TupleToBlitzkit(tuple: Vector3Tuple) {
|
||||||
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
||||||
}
|
}
|
||||||
const blitzTankClassToBlitzkit: Record<BlitzTankClass, TankClass> = {
|
const blitzTankClassToBlitzkit: Record<BlitzTankClass, TankClass> = {
|
||||||
lightTank: TankClass.LIGHT,
|
lightTank: TankClass.TANK_CLASS_LIGHT,
|
||||||
"AT-SPG": TankClass.TANK_DESTROYER,
|
"AT-SPG": TankClass.TANK_CLASS_TANK_DESTROYER,
|
||||||
heavyTank: TankClass.HEAVY,
|
heavyTank: TankClass.TANK_CLASS_HEAVY,
|
||||||
mediumTank: TankClass.MEDIUM,
|
mediumTank: TankClass.TANK_CLASS_MEDIUM,
|
||||||
};
|
};
|
||||||
export interface BlitzStrings {
|
export interface BlitzStrings {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@@ -149,18 +149,18 @@ type BlitzModuleType = {
|
|||||||
type UnlocksListing = BlitzModuleType | undefined;
|
type UnlocksListing = BlitzModuleType | undefined;
|
||||||
type BlitzCrewType = "commander" | "radioman" | "gunner" | "driver" | "loader";
|
type BlitzCrewType = "commander" | "radioman" | "gunner" | "driver" | "loader";
|
||||||
const blitzCrewTypeToBlitzkit: Record<BlitzCrewType, CrewType> = {
|
const blitzCrewTypeToBlitzkit: Record<BlitzCrewType, CrewType> = {
|
||||||
commander: CrewType.COMMANDER,
|
commander: CrewType.CREW_TYPE_COMMANDER,
|
||||||
driver: CrewType.DRIVER,
|
driver: CrewType.CREW_TYPE_DRIVER,
|
||||||
gunner: CrewType.GUNNER,
|
gunner: CrewType.CREW_TYPE_GUNNER,
|
||||||
loader: CrewType.LOADER,
|
loader: CrewType.CREW_TYPE_LOADER,
|
||||||
radioman: CrewType.RADIOMAN,
|
radioman: CrewType.CREW_TYPE_RADIOMAN,
|
||||||
};
|
};
|
||||||
const blitzkitCrewTypeToBlitz: Record<CrewType, BlitzCrewType> = {
|
const blitzkitCrewTypeToBlitz: Record<CrewType, BlitzCrewType> = {
|
||||||
[CrewType.COMMANDER]: "commander",
|
[CrewType.CREW_TYPE_COMMANDER]: "commander",
|
||||||
[CrewType.DRIVER]: "driver",
|
[CrewType.CREW_TYPE_DRIVER]: "driver",
|
||||||
[CrewType.GUNNER]: "gunner",
|
[CrewType.CREW_TYPE_GUNNER]: "gunner",
|
||||||
[CrewType.LOADER]: "loader",
|
[CrewType.CREW_TYPE_LOADER]: "loader",
|
||||||
[CrewType.RADIOMAN]: "radioman",
|
[CrewType.CREW_TYPE_RADIOMAN]: "radioman",
|
||||||
};
|
};
|
||||||
interface VehicleDefinitions {
|
interface VehicleDefinitions {
|
||||||
invisibility: {
|
invisibility: {
|
||||||
@@ -298,7 +298,7 @@ interface VehicleDefinitions {
|
|||||||
type: "default";
|
type: "default";
|
||||||
enabled: false;
|
enabled: false;
|
||||||
factorsModifiers: "";
|
factorsModifiers: "";
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -517,18 +517,18 @@ type CombatRolesYaml = Record<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const blitzShellKindToBlitzkit: Record<ShellKind, ShellType> = {
|
const blitzShellKindToBlitzkit: Record<ShellKind, ShellType> = {
|
||||||
ARMOR_PIERCING: ShellType.AP,
|
ARMOR_PIERCING: ShellType.SHELL_TYPE_AP,
|
||||||
ARMOR_PIERCING_CR: ShellType.APCR,
|
ARMOR_PIERCING_CR: ShellType.SHELL_TYPE_APCR,
|
||||||
HIGH_EXPLOSIVE: ShellType.HE,
|
HIGH_EXPLOSIVE: ShellType.SHELL_TYPE_HE,
|
||||||
HOLLOW_CHARGE: ShellType.HEAT,
|
HOLLOW_CHARGE: ShellType.SHELL_TYPE_HEAT,
|
||||||
};
|
};
|
||||||
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/;
|
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/;
|
||||||
const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = {
|
const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = {
|
||||||
chassis: ModuleType.TRACKS,
|
chassis: ModuleType.MODULE_TYPE_TRACKS,
|
||||||
engine: ModuleType.ENGINE,
|
engine: ModuleType.MODULE_TYPE_ENGINE,
|
||||||
gun: ModuleType.GUN,
|
gun: ModuleType.MODULE_TYPE_GUN,
|
||||||
turret: ModuleType.TURRET,
|
turret: ModuleType.MODULE_TYPE_TURRET,
|
||||||
vehicle: ModuleType.VEHICLE,
|
vehicle: ModuleType.MODULE_TYPE_VEHICLE,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function definitions() {
|
export async function definitions() {
|
||||||
@@ -555,10 +555,10 @@ export async function definitions() {
|
|||||||
const provisionDefinitions: ProvisionDefinitions = { provisions: {} };
|
const provisionDefinitions: ProvisionDefinitions = { provisions: {} };
|
||||||
const skillDefinitions: SkillDefinitions = {
|
const skillDefinitions: SkillDefinitions = {
|
||||||
classes: {
|
classes: {
|
||||||
[TankClass.LIGHT]: { skills: [] },
|
[TankClass.TANK_CLASS_LIGHT]: { skills: [] },
|
||||||
[TankClass.MEDIUM]: { skills: [] },
|
[TankClass.TANK_CLASS_MEDIUM]: { skills: [] },
|
||||||
[TankClass.HEAVY]: { skills: [] },
|
[TankClass.TANK_CLASS_HEAVY]: { skills: [] },
|
||||||
[TankClass.TANK_DESTROYER]: { skills: [] },
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: { skills: [] },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const nations = vfs
|
const nations = vfs
|
||||||
@@ -566,7 +566,7 @@ export async function definitions() {
|
|||||||
.filter((nation) => nation !== "common");
|
.filter((nation) => nation !== "common");
|
||||||
const tankStringIdMap: Record<string, number> = {};
|
const tankStringIdMap: Record<string, number> = {};
|
||||||
const optionalDevices = await vfs.xml<{ root: OptionalDevices }>(
|
const optionalDevices = await vfs.xml<{ root: OptionalDevices }>(
|
||||||
"Data/XML/item_defs/vehicles/common/optional_devices.xml"
|
"Data/XML/item_defs/vehicles/common/optional_devices.xml",
|
||||||
);
|
);
|
||||||
const stringsI18n: Record<string, Record<string, string>> = {};
|
const stringsI18n: Record<string, Record<string, string>> = {};
|
||||||
|
|
||||||
@@ -574,12 +574,12 @@ export async function definitions() {
|
|||||||
locales.supported.map(async ({ locale }) => {
|
locales.supported.map(async ({ locale }) => {
|
||||||
const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale];
|
const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale];
|
||||||
const cache = await fetch(
|
const cache = await fetch(
|
||||||
`https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`
|
`https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`,
|
||||||
)
|
)
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((string) => parseYaml(string) as BlitzStrings);
|
.then((string) => parseYaml(string) as BlitzStrings);
|
||||||
const preInstalled = await vfs.yaml<BlitzStrings>(
|
const preInstalled = await vfs.yaml<BlitzStrings>(
|
||||||
`Data/Strings/${blitzLocale}.yaml`
|
`Data/Strings/${blitzLocale}.yaml`,
|
||||||
);
|
);
|
||||||
|
|
||||||
stringsI18n[locale] = {
|
stringsI18n[locale] = {
|
||||||
@@ -588,7 +588,7 @@ export async function definitions() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.log(`Loaded strings for ${locale}`);
|
console.log(`Loaded strings for ${locale}`);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
function getString(name: string) {
|
function getString(name: string) {
|
||||||
@@ -627,9 +627,9 @@ export async function definitions() {
|
|||||||
consumables,
|
consumables,
|
||||||
(
|
(
|
||||||
await vfs.xml<{ root: ConsumablesCommon }>(
|
await vfs.xml<{ root: ConsumablesCommon }>(
|
||||||
`Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`
|
`Data/XML/item_defs/vehicles/common/consumables/${match[1]}.xml`,
|
||||||
)
|
)
|
||||||
).root
|
).root,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,39 +642,39 @@ export async function definitions() {
|
|||||||
provisions,
|
provisions,
|
||||||
(
|
(
|
||||||
await vfs.xml<{ root: ConsumablesCommon }>(
|
await vfs.xml<{ root: ConsumablesCommon }>(
|
||||||
`Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`
|
`Data/XML/item_defs/vehicles/common/provisions/${match[1]}.xml`,
|
||||||
)
|
)
|
||||||
).root
|
).root,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatar = await vfs.xml<{ root: Avatar }>(
|
const avatar = await vfs.xml<{ root: Avatar }>(
|
||||||
`Data/XML/item_defs/tankmen/avatar.xml`
|
`Data/XML/item_defs/tankmen/avatar.xml`,
|
||||||
);
|
);
|
||||||
const maps = await vfs.yaml<Maps>(`Data/maps.yaml`);
|
const maps = await vfs.yaml<Maps>(`Data/maps.yaml`);
|
||||||
const tankXps = new Map<number, ResearchCost>();
|
const tankXps = new Map<number, ResearchCost>();
|
||||||
const camouflagesXml = await vfs.xml<{ root: CamouflagesXML }>(
|
const camouflagesXml = await vfs.xml<{ root: CamouflagesXML }>(
|
||||||
`Data/XML/item_defs/vehicles/common/camouflages.xml`
|
`Data/XML/item_defs/vehicles/common/camouflages.xml`,
|
||||||
);
|
);
|
||||||
const camouflagesYaml = await vfs.yaml<CamouflagesYaml>(
|
const camouflagesYaml = await vfs.yaml<CamouflagesYaml>(
|
||||||
`Data/camouflages.yaml`
|
`Data/camouflages.yaml`,
|
||||||
);
|
);
|
||||||
const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages);
|
const camouflagesXmlEntries = Object.entries(camouflagesXml.root.camouflages);
|
||||||
const squadBattleTypeStyles = await vfs.yaml<SquadBattleTypeStyles>(
|
const squadBattleTypeStyles = await vfs.yaml<SquadBattleTypeStyles>(
|
||||||
`Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`
|
`Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`,
|
||||||
);
|
);
|
||||||
const gameTypeSelectorStyles = await vfs.yaml<SquadBattleTypeStyles>(
|
const gameTypeSelectorStyles = await vfs.yaml<SquadBattleTypeStyles>(
|
||||||
`Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`
|
`Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`,
|
||||||
);
|
);
|
||||||
const gameModeNativeNames: Record<string, number> = {};
|
const gameModeNativeNames: Record<string, number> = {};
|
||||||
const combatRoles = await vfs.yaml<CombatRolesYaml>(
|
const combatRoles = await vfs.yaml<CombatRolesYaml>(
|
||||||
`Data/XML/item_defs/vehicles/common/combat_roles.yaml`
|
`Data/XML/item_defs/vehicles/common/combat_roles.yaml`,
|
||||||
);
|
);
|
||||||
const consumableNativeNames: Record<string, number> = {};
|
const consumableNativeNames: Record<string, number> = {};
|
||||||
const provisionNativeNames: Record<string, number> = {};
|
const provisionNativeNames: Record<string, number> = {};
|
||||||
|
|
||||||
for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||||
/"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g
|
/"(\d+)" -> "(battleType\/([a-zA-Z]+))"/g,
|
||||||
)) {
|
)) {
|
||||||
const id = Number(match[1]);
|
const id = Number(match[1]);
|
||||||
const name = getString(match[2]);
|
const name = getString(match[2]);
|
||||||
@@ -686,7 +686,7 @@ export async function definitions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||||
/eGameMode\.([a-zA-Z]+) -> "~res:\/Gfx\/UI\/Hangar\/GameTypes\/battle-type_([^"]+)"/g
|
/eGameMode\.([a-zA-Z]+) -> "~res:\/Gfx\/UI\/Hangar\/GameTypes\/battle-type_([^"]+)"/g,
|
||||||
)) {
|
)) {
|
||||||
Object.entries(gameModeNativeNames).forEach(([key, value]) => {
|
Object.entries(gameModeNativeNames).forEach(([key, value]) => {
|
||||||
if (key.toLowerCase() === match[2].toLowerCase()) {
|
if (key.toLowerCase() === match[2].toLowerCase()) {
|
||||||
@@ -720,7 +720,7 @@ export async function definitions() {
|
|||||||
|
|
||||||
for (const nation of nations) {
|
for (const nation of nations) {
|
||||||
const tankList = await vfs.xml<{ root: VehicleDefinitionList }>(
|
const tankList = await vfs.xml<{ root: VehicleDefinitionList }>(
|
||||||
`Data/XML/item_defs/vehicles/${nation}/list.xml`
|
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const tankKey in tankList.root) {
|
for (const tankKey in tankList.root) {
|
||||||
@@ -761,7 +761,7 @@ export async function definitions() {
|
|||||||
console.warn(
|
console.warn(
|
||||||
`Multiple tanks share slug ${slug}: ${requesters
|
`Multiple tanks share slug ${slug}: ${requesters
|
||||||
.map(({ key }) => key)
|
.map(({ key }) => key)
|
||||||
.join(", ")}`
|
.join(", ")}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (requesters.length !== 2) {
|
if (requesters.length !== 2) {
|
||||||
@@ -791,7 +791,7 @@ export async function definitions() {
|
|||||||
|
|
||||||
if (canonical === undefined) {
|
if (canonical === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Two tutorial bots share the same slug? The world is truly broken."
|
"Two tutorial bots share the same slug? The world is truly broken.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,7 +804,7 @@ export async function definitions() {
|
|||||||
|
|
||||||
for (const nation of nations) {
|
for (const nation of nations) {
|
||||||
const tankList = await vfs.xml<{ root: VehicleDefinitionList }>(
|
const tankList = await vfs.xml<{ root: VehicleDefinitionList }>(
|
||||||
`Data/XML/item_defs/vehicles/${nation}/list.xml`
|
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||||
);
|
);
|
||||||
const turretList = await vfs.xml<{
|
const turretList = await vfs.xml<{
|
||||||
root: TurretDefinitionsList;
|
root: TurretDefinitionsList;
|
||||||
@@ -857,7 +857,7 @@ export async function definitions() {
|
|||||||
: Number(item.cost.split(":")[1]),
|
: Number(item.cost.split(":")[1]),
|
||||||
},
|
},
|
||||||
} satisfies Unlock;
|
} satisfies Unlock;
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.flat();
|
.flat();
|
||||||
}
|
}
|
||||||
@@ -872,10 +872,10 @@ export async function definitions() {
|
|||||||
const tank = tankList.root[tankKey];
|
const tank = tankList.root[tankKey];
|
||||||
let tankPrice: TankPrice;
|
let tankPrice: TankPrice;
|
||||||
const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>(
|
const tankDefinition = await vfs.xml<{ root: VehicleDefinitions }>(
|
||||||
`Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`
|
`Data/XML/item_defs/vehicles/${nation}/${tankKey}.xml`,
|
||||||
);
|
);
|
||||||
const tankParameters = await vfs.yaml<TankParameters>(
|
const tankParameters = await vfs.yaml<TankParameters>(
|
||||||
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`
|
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
||||||
);
|
);
|
||||||
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
|
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
|
||||||
.split(" ")
|
.split(" ")
|
||||||
@@ -890,23 +890,23 @@ export async function definitions() {
|
|||||||
|
|
||||||
if (slug === undefined) {
|
if (slug === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not find slug for ${nation}/${tankKey} (${tankId})`
|
`Could not find slug for ${nation}/${tankKey} (${tankId})`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tank.sellPrice) {
|
if (tank.sellPrice) {
|
||||||
tankPrice = {
|
tankPrice = {
|
||||||
type: TankPriceType.GOLD,
|
type: TankPriceType.TANK_PRICE_TYPE_GOLD,
|
||||||
value: tank.sellPrice["#text"] * 2,
|
value: tank.sellPrice["#text"] * 2,
|
||||||
};
|
};
|
||||||
} else if (typeof tank.price === "number") {
|
} else if (typeof tank.price === "number") {
|
||||||
tankPrice = {
|
tankPrice = {
|
||||||
type: TankPriceType.CREDITS,
|
type: TankPriceType.TANK_PRICE_TYPE_CREDITS,
|
||||||
value: tank.price,
|
value: tank.price,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
tankPrice = {
|
tankPrice = {
|
||||||
type: TankPriceType.CREDITS,
|
type: TankPriceType.TANK_PRICE_TYPE_CREDITS,
|
||||||
value: tank.price["#text"] * 400,
|
value: tank.price["#text"] * 400,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -938,7 +938,7 @@ export async function definitions() {
|
|||||||
Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => {
|
Object.entries(tankDefinition.root.crew).forEach(([crewKey, value]) => {
|
||||||
let entry: Crew;
|
let entry: Crew;
|
||||||
const index = crew.findIndex(
|
const index = crew.findIndex(
|
||||||
({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey
|
({ type }) => blitzkitCrewTypeToBlitz[type] === crewKey,
|
||||||
);
|
);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
if (crewKey === "#text") return;
|
if (crewKey === "#text") return;
|
||||||
@@ -992,6 +992,7 @@ export async function definitions() {
|
|||||||
ancestors: [],
|
ancestors: [],
|
||||||
successors: [],
|
successors: [],
|
||||||
id: tankId,
|
id: tankId,
|
||||||
|
dev_name: tankKey,
|
||||||
roles: {},
|
roles: {},
|
||||||
camouflages: camouflages,
|
camouflages: camouflages,
|
||||||
fixed_camouflage: fixedCamouflage,
|
fixed_camouflage: fixedCamouflage,
|
||||||
@@ -1011,10 +1012,10 @@ export async function definitions() {
|
|||||||
slug,
|
slug,
|
||||||
nation,
|
nation,
|
||||||
type: tankTags.includes("collectible")
|
type: tankTags.includes("collectible")
|
||||||
? TankType.COLLECTOR
|
? TankType.TANK_TYPE_COLLECTOR
|
||||||
: (typeof tank.price === "number" ? false : "gold" in tank.price)
|
: (typeof tank.price === "number" ? false : "gold" in tank.price)
|
||||||
? TankType.PREMIUM
|
? TankType.TANK_TYPE_PREMIUM
|
||||||
: TankType.RESEARCHABLE,
|
: TankType.TANK_TYPE_RESEARCHABLE,
|
||||||
tier: tank.level,
|
tier: tank.level,
|
||||||
class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass],
|
class: blitzTankClassToBlitzkit[tankTags[0] as BlitzTankClass],
|
||||||
testing: tankTags.includes("testTank"),
|
testing: tankTags.includes("testTank"),
|
||||||
@@ -1022,7 +1023,7 @@ export async function definitions() {
|
|||||||
price: tankPrice,
|
price: tankPrice,
|
||||||
camouflage_still: tankDefinition.root.invisibility.still,
|
camouflage_still: tankDefinition.root.invisibility.still,
|
||||||
camouflage_moving: tankDefinition.root.invisibility.moving,
|
camouflage_moving: tankDefinition.root.invisibility.moving,
|
||||||
camouflage_onFire: tankDefinition.root.invisibility.firePenalty,
|
camouflage_on_fire: tankDefinition.root.invisibility.firePenalty,
|
||||||
turrets: [],
|
turrets: [],
|
||||||
engines: [],
|
engines: [],
|
||||||
tracks: [],
|
tracks: [],
|
||||||
@@ -1031,12 +1032,12 @@ export async function definitions() {
|
|||||||
if (tank.combatRole) {
|
if (tank.combatRole) {
|
||||||
Object.entries(tank.combatRole).forEach(([gameMode, role]) => {
|
Object.entries(tank.combatRole).forEach(([gameMode, role]) => {
|
||||||
const id = Object.entries(gameModeNativeNames).find(
|
const id = Object.entries(gameModeNativeNames).find(
|
||||||
([key]) => key.toLowerCase() === gameMode.toLowerCase()
|
([key]) => key.toLowerCase() === gameMode.toLowerCase(),
|
||||||
)?.[1];
|
)?.[1];
|
||||||
|
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown game mode in tank ${tankKey}: ${gameMode}`
|
`Unknown game mode in tank ${tankKey}: ${gameMode}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1118,7 +1119,7 @@ export async function definitions() {
|
|||||||
(turretKey, turretIndex) => {
|
(turretKey, turretIndex) => {
|
||||||
const turret = tankDefinition.root.turrets0[turretKey];
|
const turret = tankDefinition.root.turrets0[turretKey];
|
||||||
const turretModel = Number(
|
const turretModel = Number(
|
||||||
parsePath(turret.models.undamaged).name.split("_")[1]
|
parsePath(turret.models.undamaged).name.split("_")[1],
|
||||||
);
|
);
|
||||||
const turretId = toUniqueId(nation, turretList.root.ids[turretKey]);
|
const turretId = toUniqueId(nation, turretList.root.ids[turretKey]);
|
||||||
const turretYaw = (
|
const turretYaw = (
|
||||||
@@ -1186,12 +1187,12 @@ export async function definitions() {
|
|||||||
min: vector3TupleToBlitzkit(
|
min: vector3TupleToBlitzkit(
|
||||||
tankParameters.collision[
|
tankParameters.collision[
|
||||||
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
|
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
|
||||||
].bbox.min
|
].bbox.min,
|
||||||
),
|
),
|
||||||
max: vector3TupleToBlitzkit(
|
max: vector3TupleToBlitzkit(
|
||||||
tankParameters.collision[
|
tankParameters.collision[
|
||||||
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
|
parsePath(turret.hitTester.collisionModel).name.toLowerCase()
|
||||||
].bbox.max
|
].bbox.max,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1209,7 +1210,7 @@ export async function definitions() {
|
|||||||
.split(" ")
|
.split(" ")
|
||||||
.map(Number) as [number, number];
|
.map(Number) as [number, number];
|
||||||
const gunModel = Number(
|
const gunModel = Number(
|
||||||
parsePath(gun.models.undamaged).name.split("_")[1]
|
parsePath(gun.models.undamaged).name.split("_")[1],
|
||||||
);
|
);
|
||||||
const gunName = getString(gunListEntry.userString);
|
const gunName = getString(gunListEntry.userString);
|
||||||
const gunType =
|
const gunType =
|
||||||
@@ -1332,24 +1333,24 @@ export async function definitions() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
: gunType === "autoReloader"
|
: gunType === "autoReloader"
|
||||||
? {
|
? {
|
||||||
$case: "auto_reloader",
|
$case: "auto_reloader",
|
||||||
value: {
|
value: {
|
||||||
intra_clip: 60 / gun.clip!.rate,
|
intra_clip: 60 / gun.clip!.rate,
|
||||||
shell_count: gunClipCount,
|
shell_count: gunClipCount,
|
||||||
shell_reloads: gun
|
shell_reloads: gun
|
||||||
.pumpGunReloadTimes!.split(" ")
|
.pumpGunReloadTimes!.split(" ")
|
||||||
.map(Number),
|
.map(Number),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
$case: "auto_loader",
|
||||||
|
value: {
|
||||||
|
intra_clip: 60 / gun.clip!.rate,
|
||||||
|
clip_reload: gun.reloadTime,
|
||||||
|
shell_count: gunClipCount,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
: {
|
|
||||||
$case: "auto_loader",
|
|
||||||
value: {
|
|
||||||
intra_clip: 60 / gun.clip!.rate,
|
|
||||||
clip_reload: gun.reloadTime,
|
|
||||||
shell_count: gunClipCount,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} satisfies GunDefinition);
|
} satisfies GunDefinition);
|
||||||
|
|
||||||
modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = {
|
modelDefinitions.models[tankId].turrets[turretId].guns[gunId] = {
|
||||||
@@ -1360,8 +1361,8 @@ export async function definitions() {
|
|||||||
gun.armor.gun === undefined
|
gun.armor.gun === undefined
|
||||||
? 0
|
? 0
|
||||||
: typeof gun.armor.gun === "number"
|
: typeof gun.armor.gun === "number"
|
||||||
? gun.armor.gun
|
? gun.armor.gun
|
||||||
: gun.armor.gun["#text"],
|
: gun.armor.gun["#text"],
|
||||||
pitch: {
|
pitch: {
|
||||||
min: gunPitch[0],
|
min: gunPitch[0],
|
||||||
max: gunPitch[1],
|
max: gunPitch[1],
|
||||||
@@ -1408,7 +1409,7 @@ export async function definitions() {
|
|||||||
type: blitzShellKindToBlitzkit[shell.kind],
|
type: blitzShellKindToBlitzkit[shell.kind],
|
||||||
explosion_radius:
|
explosion_radius:
|
||||||
shell.kind === "HIGH_EXPLOSIVE"
|
shell.kind === "HIGH_EXPLOSIVE"
|
||||||
? shell.explosionRadius ?? 0
|
? (shell.explosionRadius ?? 0)
|
||||||
: undefined,
|
: undefined,
|
||||||
icon: shell.icon,
|
icon: shell.icon,
|
||||||
penetration: {
|
penetration: {
|
||||||
@@ -1419,7 +1420,7 @@ export async function definitions() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
totalUnlocks.forEach((unlocks) => {
|
totalUnlocks.forEach((unlocks) => {
|
||||||
@@ -1447,7 +1448,7 @@ export async function definitions() {
|
|||||||
case "gun": {
|
case "gun": {
|
||||||
gunXps.set(
|
gunXps.set(
|
||||||
toUniqueId(nation, gunList.root.ids[vehicle["#text"]]),
|
toUniqueId(nation, gunList.root.ids[vehicle["#text"]]),
|
||||||
parseResearchCost(vehicle.cost)
|
parseResearchCost(vehicle.cost),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1455,7 +1456,7 @@ export async function definitions() {
|
|||||||
case "turret": {
|
case "turret": {
|
||||||
turretXps.set(
|
turretXps.set(
|
||||||
toUniqueId(nation, turretList.root.ids[vehicle["#text"]]),
|
toUniqueId(nation, turretList.root.ids[vehicle["#text"]]),
|
||||||
parseResearchCost(vehicle.cost)
|
parseResearchCost(vehicle.cost),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1463,7 +1464,7 @@ export async function definitions() {
|
|||||||
case "engine": {
|
case "engine": {
|
||||||
engineXps.set(
|
engineXps.set(
|
||||||
toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]),
|
toUniqueId(nation, enginesList.root.ids[vehicle["#text"]]),
|
||||||
parseResearchCost(vehicle.cost)
|
parseResearchCost(vehicle.cost),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1471,7 +1472,7 @@ export async function definitions() {
|
|||||||
case "chassis": {
|
case "chassis": {
|
||||||
trackXps.set(
|
trackXps.set(
|
||||||
toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]),
|
toUniqueId(nation, chassisList.root.ids[vehicle["#text"]]),
|
||||||
parseResearchCost(vehicle.cost)
|
parseResearchCost(vehicle.cost),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1518,7 +1519,7 @@ export async function definitions() {
|
|||||||
name: getString(optionalDeviceEntry.userString),
|
name: getString(optionalDeviceEntry.userString),
|
||||||
description: getString(optionalDeviceEntry.description),
|
description: getString(optionalDeviceEntry.description),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.entries(optionalDeviceSlots.root.presets).forEach(
|
Object.entries(optionalDeviceSlots.root.presets).forEach(
|
||||||
@@ -1537,7 +1538,7 @@ export async function definitions() {
|
|||||||
})
|
})
|
||||||
.flat(),
|
.flat(),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.entries(consumables).forEach(([key, consumable]) => {
|
Object.entries(consumables).forEach(([key, consumable]) => {
|
||||||
@@ -1620,7 +1621,7 @@ export async function definitions() {
|
|||||||
(item) =>
|
(item) =>
|
||||||
blitzTankFilterDefinitionCategoryToBlitzkit[
|
blitzTankFilterDefinitionCategoryToBlitzkit[
|
||||||
item as BlitzTankFilterDefinitionCategory
|
item as BlitzTankFilterDefinitionCategory
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1718,7 +1719,7 @@ export async function definitions() {
|
|||||||
(item) =>
|
(item) =>
|
||||||
blitzTankFilterDefinitionCategoryToBlitzkit[
|
blitzTankFilterDefinitionCategoryToBlitzkit[
|
||||||
item as BlitzTankFilterDefinitionCategory
|
item as BlitzTankFilterDefinitionCategory
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1749,11 +1750,11 @@ export async function definitions() {
|
|||||||
value.default_abilities.forEach((ability) => {
|
value.default_abilities.forEach((ability) => {
|
||||||
if (ability in consumableNativeNames) {
|
if (ability in consumableNativeNames) {
|
||||||
gameDefinitions.roles[value.id].consumables.push(
|
gameDefinitions.roles[value.id].consumables.push(
|
||||||
consumableNativeNames[ability]
|
consumableNativeNames[ability],
|
||||||
);
|
);
|
||||||
} else if (ability in provisionNativeNames) {
|
} else if (ability in provisionNativeNames) {
|
||||||
gameDefinitions.roles[value.id].provisions.push(
|
gameDefinitions.roles[value.id].provisions.push(
|
||||||
provisionNativeNames[ability]
|
provisionNativeNames[ability],
|
||||||
);
|
);
|
||||||
} else throw new Error(`Unknown ability ${ability}`);
|
} else throw new Error(`Unknown ability ${ability}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export async function glossary() {
|
|||||||
const glossary = await fetchGlossary(supported.blitz ?? supported.locale);
|
const glossary = await fetchGlossary(supported.blitz ?? supported.locale);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Found ${Object.keys(glossary).length} things for ${supported.locale}`
|
`Found ${Object.keys(glossary).length} things for ${supported.locale}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const key in glossary) {
|
for (const key in glossary) {
|
||||||
@@ -30,7 +30,7 @@ export async function glossary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key in avatars) {
|
if (key in avatars) {
|
||||||
avatars[key].avatar.name.locales[supported.locale] =
|
avatars[key].avatar.name!.locales[supported.locale] =
|
||||||
glossaryEntry.title;
|
glossaryEntry.title;
|
||||||
} else {
|
} else {
|
||||||
const extension = extname(glossaryEntry.image_url);
|
const extension = extname(glossaryEntry.image_url);
|
||||||
@@ -45,7 +45,7 @@ export async function glossary() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const gallery: Gallery = { avatars: [] };
|
const gallery: Gallery = { avatars: [] };
|
||||||
@@ -53,12 +53,12 @@ export async function glossary() {
|
|||||||
|
|
||||||
const bar = new ProgressBar(
|
const bar = new ProgressBar(
|
||||||
`Fetching avatars of ${totalCount} things :bar`,
|
`Fetching avatars of ${totalCount} things :bar`,
|
||||||
totalCount
|
totalCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(avatars).map(async ([key, { url, avatar }]) => {
|
Object.entries(avatars).map(async ([key, { url, avatar }]) => {
|
||||||
if (!(locales.default in avatars[key].avatar.name.locales)) {
|
if (!(locales.default in avatars[key].avatar.name!.locales)) {
|
||||||
console.warn(`Avatar ${key} has no ${locales.default} name`);
|
console.warn(`Avatar ${key} has no ${locales.default} name`);
|
||||||
bar.tick();
|
bar.tick();
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export async function glossary() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
bar.tick();
|
bar.tick();
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await uploader.add({
|
await uploader.add({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { crc32 } from 'crc';
|
import { crc32 } from "crc";
|
||||||
import { compressBlock } from 'lz4js';
|
import { compressBlock } from "lz4js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thanks Maddoxkkm! Modernized heavily.
|
* Thanks Maddoxkkm! Modernized heavily.
|
||||||
@@ -14,7 +14,7 @@ export function writeDVPL(buffer: Buffer) {
|
|||||||
destination,
|
destination,
|
||||||
0,
|
0,
|
||||||
source.length,
|
source.length,
|
||||||
0,
|
[0],
|
||||||
);
|
);
|
||||||
let output = Buffer.from(destination);
|
let output = Buffer.from(destination);
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ function toDVPLFooter(
|
|||||||
result.writeUInt32LE(compressedSize, 4);
|
result.writeUInt32LE(compressedSize, 4);
|
||||||
result.writeUInt32LE(crc32, 8);
|
result.writeUInt32LE(crc32, 8);
|
||||||
result.writeUInt32LE(type, 12);
|
result.writeUInt32LE(type, 12);
|
||||||
result.write('DVPL', 16, 4, 'utf8');
|
result.write("DVPL", 16, 4, "utf8");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ export class SteamVFS extends AbstractVFS {
|
|||||||
private username: string,
|
private username: string,
|
||||||
private password: string,
|
private password: string,
|
||||||
private app: number,
|
private app: number,
|
||||||
private depot: number
|
private depot: number,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -57,13 +57,14 @@ export class SteamVFS extends AbstractVFS {
|
|||||||
this.steam.getManifest(
|
this.steam.getManifest(
|
||||||
this.app,
|
this.app,
|
||||||
this.depot,
|
this.depot,
|
||||||
|
// @ts-expect-error
|
||||||
productInfo.apps[this.app].appinfo.depots[this.depot].manifests.public
|
productInfo.apps[this.app].appinfo.depots[this.depot].manifests.public
|
||||||
.gid,
|
.gid,
|
||||||
"public",
|
"public",
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
(_, response) => {
|
(_, response) => {
|
||||||
resolve(response);
|
resolve(response);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ if (newTanks.length > 0) {
|
|||||||
notes += "## New Tanks\n\n";
|
notes += "## New Tanks\n\n";
|
||||||
|
|
||||||
for (const tank of newTanks) {
|
for (const tank of newTanks) {
|
||||||
notes += `### [${tank.name.locales.en}](${domain}/tanks/${tank.slug})\n`;
|
notes += `### [${tank.name!.locales.en}](${domain}/tanks/${tank.slug})\n`;
|
||||||
notes += `})\n\n`;
|
notes += `})\n\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,49 +96,49 @@ if (changedTanks.length > 0) {
|
|||||||
const previewTankModel = previewModelDefinitions.models[preview.id];
|
const previewTankModel = previewModelDefinitions.models[preview.id];
|
||||||
|
|
||||||
const currentConsumables = Object.values(
|
const currentConsumables = Object.values(
|
||||||
currentConsumableDefinitions.consumables
|
currentConsumableDefinitions.consumables,
|
||||||
).filter((consumable) =>
|
).filter((consumable) =>
|
||||||
checkConsumableProvisionInclusivity(consumable, current, currentGun)
|
checkConsumableProvisionInclusivity(consumable, current, currentGun),
|
||||||
);
|
);
|
||||||
const previewConsumables = Object.values(
|
const previewConsumables = Object.values(
|
||||||
previewConsumableDefinitions.consumables
|
previewConsumableDefinitions.consumables,
|
||||||
).filter((consumable) =>
|
).filter((consumable) =>
|
||||||
checkConsumableProvisionInclusivity(consumable, preview, previewGun)
|
checkConsumableProvisionInclusivity(consumable, preview, previewGun),
|
||||||
);
|
);
|
||||||
const addedConsumables = previewConsumables.filter(
|
const addedConsumables = previewConsumables.filter(
|
||||||
(previewConsumable) =>
|
(previewConsumable) =>
|
||||||
!currentConsumables.some(
|
!currentConsumables.some(
|
||||||
(currentConsumable) => currentConsumable.id === previewConsumable.id
|
(currentConsumable) => currentConsumable.id === previewConsumable.id,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const removedConsumables = currentConsumables.filter(
|
const removedConsumables = currentConsumables.filter(
|
||||||
(currentConsumable) =>
|
(currentConsumable) =>
|
||||||
!previewConsumables.some(
|
!previewConsumables.some(
|
||||||
(previewConsumable) => previewConsumable.id === currentConsumable.id
|
(previewConsumable) => previewConsumable.id === currentConsumable.id,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentProvisions = availableProvisions(
|
const currentProvisions = availableProvisions(
|
||||||
current,
|
current,
|
||||||
currentGun,
|
currentGun,
|
||||||
currentProvisionDefinitions
|
currentProvisionDefinitions,
|
||||||
);
|
);
|
||||||
const previewProvisions = availableProvisions(
|
const previewProvisions = availableProvisions(
|
||||||
preview,
|
preview,
|
||||||
previewGun,
|
previewGun,
|
||||||
previewProvisionDefinitions
|
previewProvisionDefinitions,
|
||||||
);
|
);
|
||||||
const addedProvisions = previewProvisions.filter(
|
const addedProvisions = previewProvisions.filter(
|
||||||
(previewProvision) =>
|
(previewProvision) =>
|
||||||
!currentProvisions.some(
|
!currentProvisions.some(
|
||||||
(currentProvision) => currentProvision.id === previewProvision.id
|
(currentProvision) => currentProvision.id === previewProvision.id,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const removedProvisions = currentProvisions.filter(
|
const removedProvisions = currentProvisions.filter(
|
||||||
(currentProvision) =>
|
(currentProvision) =>
|
||||||
!previewProvisions.some(
|
!previewProvisions.some(
|
||||||
(previewProvision) => previewProvision.id === currentProvision.id
|
(previewProvision) => previewProvision.id === currentProvision.id,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentEquipment = currentEquipmentDefinitions.presets[
|
const currentEquipment = currentEquipmentDefinitions.presets[
|
||||||
@@ -155,16 +155,16 @@ if (changedTanks.length > 0) {
|
|||||||
.filter(
|
.filter(
|
||||||
(previewEquipment) =>
|
(previewEquipment) =>
|
||||||
!currentEquipment.some(
|
!currentEquipment.some(
|
||||||
(currentEquipment) => currentEquipment === previewEquipment
|
(currentEquipment) => currentEquipment === previewEquipment,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.map((id) => previewEquipmentDefinitions.equipments[id]);
|
.map((id) => previewEquipmentDefinitions.equipments[id]);
|
||||||
const removedEquipment = currentEquipment
|
const removedEquipment = currentEquipment
|
||||||
.filter(
|
.filter(
|
||||||
(currentEquipment) =>
|
(currentEquipment) =>
|
||||||
!previewEquipment.some(
|
!previewEquipment.some(
|
||||||
(previewEquipment) => previewEquipment === currentEquipment
|
(previewEquipment) => previewEquipment === currentEquipment,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
.map((id) => currentEquipmentDefinitions.equipments[id]);
|
.map((id) => currentEquipmentDefinitions.equipments[id]);
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ if (changedTanks.length > 0) {
|
|||||||
provisions: createDefaultProvisions(
|
provisions: createDefaultProvisions(
|
||||||
current,
|
current,
|
||||||
currentGun,
|
currentGun,
|
||||||
currentProvisionDefinitions
|
currentProvisionDefinitions,
|
||||||
),
|
),
|
||||||
equipmentMatrix: genericDefaultEquipmentMatrix,
|
equipmentMatrix: genericDefaultEquipmentMatrix,
|
||||||
stockEngine: current.engines[0],
|
stockEngine: current.engines[0],
|
||||||
@@ -198,7 +198,7 @@ if (changedTanks.length > 0) {
|
|||||||
equipmentDefinitions: currentEquipmentDefinitions,
|
equipmentDefinitions: currentEquipmentDefinitions,
|
||||||
provisionDefinitions: currentProvisionDefinitions,
|
provisionDefinitions: currentProvisionDefinitions,
|
||||||
tankModelDefinition: currentTankModel,
|
tankModelDefinition: currentTankModel,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const previewCharacteristics = tankCharacteristics(
|
const previewCharacteristics = tankCharacteristics(
|
||||||
{
|
{
|
||||||
@@ -216,7 +216,7 @@ if (changedTanks.length > 0) {
|
|||||||
provisions: createDefaultProvisions(
|
provisions: createDefaultProvisions(
|
||||||
preview,
|
preview,
|
||||||
previewGun,
|
previewGun,
|
||||||
previewProvisionDefinitions
|
previewProvisionDefinitions,
|
||||||
),
|
),
|
||||||
equipmentMatrix: genericDefaultEquipmentMatrix,
|
equipmentMatrix: genericDefaultEquipmentMatrix,
|
||||||
stockEngine: preview.engines[0],
|
stockEngine: preview.engines[0],
|
||||||
@@ -230,7 +230,7 @@ if (changedTanks.length > 0) {
|
|||||||
equipmentDefinitions: previewEquipmentDefinitions,
|
equipmentDefinitions: previewEquipmentDefinitions,
|
||||||
provisionDefinitions: previewProvisionDefinitions,
|
provisionDefinitions: previewProvisionDefinitions,
|
||||||
tankModelDefinition: previewTankModel,
|
tankModelDefinition: previewTankModel,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const changedCharacteristics: string[] = [];
|
const changedCharacteristics: string[] = [];
|
||||||
|
|
||||||
@@ -244,17 +244,17 @@ if (changedTanks.length > 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notes += `### [${preview.name.locales.en}](${domain}/tanks/${preview.slug})\n`;
|
notes += `### [${preview.name!.locales.en}](${domain}/tanks/${preview.slug})\n`;
|
||||||
notes += `})\n\n`;
|
notes += `})\n\n`;
|
||||||
|
|
||||||
if (addedConsumables.length + removedConsumables.length > 0) {
|
if (addedConsumables.length + removedConsumables.length > 0) {
|
||||||
notes += `Consumables: `;
|
notes += `Consumables: `;
|
||||||
notes += [
|
notes += [
|
||||||
...addedConsumables.map(
|
...addedConsumables.map(
|
||||||
(consumable) => `🟢 ${consumable.name.locales.en}`
|
(consumable) => `🟢 ${consumable.name!.locales.en}`,
|
||||||
),
|
),
|
||||||
...removedConsumables.map(
|
...removedConsumables.map(
|
||||||
(consumable) => `🔴 ${consumable.name.locales.en}`
|
(consumable) => `🔴 ${consumable.name!.locales.en}`,
|
||||||
),
|
),
|
||||||
].join(", ");
|
].join(", ");
|
||||||
notes += `\n`;
|
notes += `\n`;
|
||||||
@@ -264,10 +264,10 @@ if (changedTanks.length > 0) {
|
|||||||
notes += `Provisions: `;
|
notes += `Provisions: `;
|
||||||
notes += [
|
notes += [
|
||||||
...addedProvisions.map(
|
...addedProvisions.map(
|
||||||
(provision) => `🟢 ${provision.name.locales.en}`
|
(provision) => `🟢 ${provision.name!.locales.en}`,
|
||||||
),
|
),
|
||||||
...removedProvisions.map(
|
...removedProvisions.map(
|
||||||
(provision) => `🔴 ${provision.name.locales.en}`
|
(provision) => `🔴 ${provision.name!.locales.en}`,
|
||||||
),
|
),
|
||||||
].join(", ");
|
].join(", ");
|
||||||
notes += `\n`;
|
notes += `\n`;
|
||||||
@@ -276,9 +276,11 @@ if (changedTanks.length > 0) {
|
|||||||
if (addedEquipment.length + removedEquipment.length > 0) {
|
if (addedEquipment.length + removedEquipment.length > 0) {
|
||||||
notes += `-Equipment: `;
|
notes += `-Equipment: `;
|
||||||
notes += [
|
notes += [
|
||||||
...addedEquipment.map((equipment) => `🟢 ${equipment.name.locales.en}`),
|
...addedEquipment.map(
|
||||||
|
(equipment) => `🟢 ${equipment.name!.locales.en}`,
|
||||||
|
),
|
||||||
...removedEquipment.map(
|
...removedEquipment.map(
|
||||||
(equipment) => `🔴 ${equipment.name.locales.en}`
|
(equipment) => `🔴 ${equipment.name!.locales.en}`,
|
||||||
),
|
),
|
||||||
].join(", ");
|
].join(", ");
|
||||||
notes += `\n`;
|
notes += `\n`;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ for (const tank of tanksSanitized) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const q = `World of Tanks Blitz ${tank.name.locales[locales.default]}`;
|
const q = `World of Tanks Blitz ${tank.name!.locales[locales.default]}`;
|
||||||
|
|
||||||
const results = await youtube.search.list({
|
const results = await youtube.search.list({
|
||||||
part: ["snippet"],
|
part: ["snippet"],
|
||||||
@@ -74,7 +74,7 @@ for (const tank of tanksSanitized) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${++done} done; found ${items.length} for ${tank.name.locales.en}`,
|
`${++done} done; found ${items.length} for ${tank.name!.locales.en}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000 / 5));
|
await new Promise((resolve) => setTimeout(resolve, 1000 / 5));
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { ReloadIcon } from "@radix-ui/react-icons";
|
import { ReloadIcon } from "@radix-ui/react-icons";
|
||||||
import {
|
import { Card, Flex, IconButton, Text, TextField } from "@radix-ui/themes";
|
||||||
Card,
|
|
||||||
Flex,
|
|
||||||
IconButton,
|
|
||||||
Text,
|
|
||||||
TextField,
|
|
||||||
} from "@radix-ui/themes";
|
|
||||||
import { Html } from "@react-three/drei";
|
import { Html } from "@react-three/drei";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { radToDeg } from "three/src/math/MathUtils.js";
|
import { radToDeg } from "three/src/math/MathUtils.js";
|
||||||
@@ -107,12 +101,12 @@ export function ArmorPlateDisplay() {
|
|||||||
const track = tank.tracks[protagonist.track.id];
|
const track = tank.tracks[protagonist.track.id];
|
||||||
|
|
||||||
if (highlightArmor.name.startsWith("hull_")) {
|
if (highlightArmor.name.startsWith("hull_")) {
|
||||||
tank.armor.thickness[index] = thickness;
|
tank.armor!.thickness[index] = thickness;
|
||||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||||
turret.armor.thickness[index] = thickness;
|
turret.armor!.thickness[index] = thickness;
|
||||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||||
if (highlightArmor.name.includes("_armor_")) {
|
if (highlightArmor.name.includes("_armor_")) {
|
||||||
gun.armor.thickness[index] = thickness;
|
gun.armor!.thickness[index] = thickness;
|
||||||
} else {
|
} else {
|
||||||
gun.thickness = thickness;
|
gun.thickness = thickness;
|
||||||
}
|
}
|
||||||
@@ -162,15 +156,15 @@ export function ArmorPlateDisplay() {
|
|||||||
initialTank.tracks[protagonist.track.id];
|
initialTank.tracks[protagonist.track.id];
|
||||||
|
|
||||||
if (highlightArmor.name.startsWith("hull_")) {
|
if (highlightArmor.name.startsWith("hull_")) {
|
||||||
tank.armor.thickness[index] =
|
tank.armor!.thickness[index] =
|
||||||
initialTank.armor.thickness[index];
|
initialTank.armor!.thickness[index];
|
||||||
} else if (highlightArmor.name.startsWith("turret_")) {
|
} else if (highlightArmor.name.startsWith("turret_")) {
|
||||||
turret.armor.thickness[index] =
|
turret.armor!.thickness[index] =
|
||||||
initialTurret.armor.thickness[index];
|
initialTurret.armor!.thickness[index];
|
||||||
} else if (highlightArmor.name.startsWith("gun_")) {
|
} else if (highlightArmor.name.startsWith("gun_")) {
|
||||||
if (highlightArmor.name.includes("_armor_")) {
|
if (highlightArmor.name.includes("_armor_")) {
|
||||||
gun.armor.thickness[index] =
|
gun.armor!.thickness[index] =
|
||||||
initialGun.armor.thickness[index];
|
initialGun.armor!.thickness[index];
|
||||||
} else {
|
} else {
|
||||||
gun.thickness = initialGun.thickness;
|
gun.thickness = initialGun.thickness;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||||
const isDynamicArmorActive = Duel.use((state) =>
|
const isDynamicArmorActive = Duel.use((state) =>
|
||||||
state.protagonist.consumables.includes(73)
|
state.protagonist.consumables.includes(73),
|
||||||
);
|
);
|
||||||
|
|
||||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||||
@@ -41,8 +41,8 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
const isVisible = isHull;
|
const isVisible = isHull;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
tankModelDefinition.armor,
|
tankModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -69,13 +69,13 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
const isCurrentTurret = node.name.startsWith(
|
const isCurrentTurret = node.name.startsWith(
|
||||||
`turret_${turretModelDefinition.model_id
|
`turret_${turretModelDefinition.model_id
|
||||||
.toString()
|
.toString()
|
||||||
.padStart(2, "0")}`
|
.padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentTurret;
|
const isVisible = isCurrentTurret;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
turretModelDefinition.armor,
|
turretModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -103,13 +103,13 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
<group ref={gunContainer}>
|
<group ref={gunContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentGun = node.name.startsWith(
|
const isCurrentGun = node.name.startsWith(
|
||||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentGun;
|
const isVisible = isCurrentGun;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
gunModelDefinition.armor,
|
gunModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
+15
-15
@@ -88,10 +88,10 @@ export function PrimaryArmorSceneComponent({
|
|||||||
|
|
||||||
material.uniforms.caliber.value = shell.caliber;
|
material.uniforms.caliber.value = shell.caliber;
|
||||||
material.uniforms.ricochet.value = degToRad(
|
material.uniforms.ricochet.value = degToRad(
|
||||||
isExplosive(shell.type) ? 90 : shell.ricochet!
|
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||||
);
|
);
|
||||||
material.uniforms.normalization.value = degToRad(
|
material.uniforms.normalization.value = degToRad(
|
||||||
shell.normalization ?? 0
|
shell.normalization ?? 0,
|
||||||
);
|
);
|
||||||
material.uniforms.isExplosive.value = isExplosive(shell.type);
|
material.uniforms.isExplosive.value = isExplosive(shell.type);
|
||||||
material.uniforms.canSplash.value = canSplash(shell.type);
|
material.uniforms.canSplash.value = canSplash(shell.type);
|
||||||
@@ -100,11 +100,11 @@ export function PrimaryArmorSceneComponent({
|
|||||||
|
|
||||||
handleProtagonistEquipmentChange(
|
handleProtagonistEquipmentChange(
|
||||||
Duel.state.protagonist.equipmentMatrix,
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
handleAntagonistEquipmentChange(
|
handleAntagonistEquipmentChange(
|
||||||
Duel.state.antagonist.equipmentMatrix,
|
Duel.state.antagonist.equipmentMatrix,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
@@ -124,12 +124,12 @@ export function PrimaryArmorSceneComponent({
|
|||||||
}
|
}
|
||||||
function handleProtagonistEquipmentChange(
|
function handleProtagonistEquipmentChange(
|
||||||
equipment: EquipmentMatrix,
|
equipment: EquipmentMatrix,
|
||||||
noInvalidate = false
|
noInvalidate = false,
|
||||||
) {
|
) {
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment,
|
||||||
);
|
);
|
||||||
material.uniforms.thickness.value = hasEnhancedArmor
|
material.uniforms.thickness.value = hasEnhancedArmor
|
||||||
? thickness * 1.03
|
? thickness * 1.03
|
||||||
@@ -139,16 +139,16 @@ export function PrimaryArmorSceneComponent({
|
|||||||
}
|
}
|
||||||
function handleAntagonistEquipmentChange(
|
function handleAntagonistEquipmentChange(
|
||||||
equipment: EquipmentMatrix,
|
equipment: EquipmentMatrix,
|
||||||
noInvalidate = false
|
noInvalidate = false,
|
||||||
) {
|
) {
|
||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell =
|
const shell =
|
||||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
const penetration = shell.penetration.near;
|
const penetration = shell.penetration!.near;
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
Duel.state.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment,
|
||||||
);
|
);
|
||||||
|
|
||||||
material.uniforms.penetration.value =
|
material.uniforms.penetration.value =
|
||||||
@@ -165,7 +165,7 @@ export function PrimaryArmorSceneComponent({
|
|||||||
handleShellChange();
|
handleShellChange();
|
||||||
handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration);
|
handleGreenPenetrationChange(TankopediaPersistent.state.greenPenetration);
|
||||||
handleAdvancedHighlightingChange(
|
handleAdvancedHighlightingChange(
|
||||||
TankopediaPersistent.state.advancedHighlighting
|
TankopediaPersistent.state.advancedHighlighting,
|
||||||
);
|
);
|
||||||
handleOpaqueChange(TankopediaPersistent.state.opaque);
|
handleOpaqueChange(TankopediaPersistent.state.opaque);
|
||||||
handleWireframeChange(TankopediaPersistent.state.wireframe);
|
handleWireframeChange(TankopediaPersistent.state.wireframe);
|
||||||
@@ -179,20 +179,20 @@ export function PrimaryArmorSceneComponent({
|
|||||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||||
TankopediaPersistent.on(
|
TankopediaPersistent.on(
|
||||||
(state) => state.greenPenetration,
|
(state) => state.greenPenetration,
|
||||||
handleGreenPenetrationChange
|
handleGreenPenetrationChange,
|
||||||
),
|
),
|
||||||
TankopediaPersistent.on(
|
TankopediaPersistent.on(
|
||||||
(state) => state.advancedHighlighting,
|
(state) => state.advancedHighlighting,
|
||||||
handleAdvancedHighlightingChange
|
handleAdvancedHighlightingChange,
|
||||||
),
|
),
|
||||||
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
|
TankopediaPersistent.on((state) => state.opaque, handleOpaqueChange),
|
||||||
Duel.on(
|
Duel.on(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
(equipment) => handleProtagonistEquipmentChange(equipment)
|
(equipment) => handleProtagonistEquipmentChange(equipment),
|
||||||
),
|
),
|
||||||
Duel.on(
|
Duel.on(
|
||||||
(state) => state.antagonist.equipmentMatrix,
|
(state) => state.antagonist.equipmentMatrix,
|
||||||
(equipment) => handleAntagonistEquipmentChange(equipment)
|
(equipment) => handleAntagonistEquipmentChange(equipment),
|
||||||
),
|
),
|
||||||
() => transitionEvent.off(handleTransitionEvent),
|
() => transitionEvent.off(handleTransitionEvent),
|
||||||
];
|
];
|
||||||
@@ -204,7 +204,7 @@ export function PrimaryArmorSceneComponent({
|
|||||||
|
|
||||||
useFrame(({ gl, camera }) => {
|
useFrame(({ gl, camera }) => {
|
||||||
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
|
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
|
||||||
gl.getPixelRatio()
|
gl.getPixelRatio(),
|
||||||
);
|
);
|
||||||
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
|
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
|
||||||
material.uniforms.spacedArmorDepth.value =
|
material.uniforms.spacedArmorDepth.value =
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||||
const maskOrigin =
|
const maskOrigin =
|
||||||
gunModelDefinition.mask === undefined
|
gunModelDefinition.mask === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||||
const isDynamicArmorActive = Duel.use((state) =>
|
const isDynamicArmorActive = Duel.use((state) =>
|
||||||
state.protagonist.consumables.includes(73)
|
state.protagonist.consumables.includes(73),
|
||||||
);
|
);
|
||||||
|
|
||||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||||
@@ -58,8 +58,8 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
const isVisible = isHull;
|
const isVisible = isHull;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
tankModelDefinition.armor,
|
tankModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -104,13 +104,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
<group ref={turretContainer}>
|
<group ref={turretContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentTurret = node.name.startsWith(
|
const isCurrentTurret = node.name.startsWith(
|
||||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
|
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentTurret;
|
const isVisible = isCurrentTurret;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
turretModelDefinition.armor,
|
turretModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -139,13 +139,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
<group ref={gunContainer}>
|
<group ref={gunContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentGun = node.name.startsWith(
|
const isCurrentGun = node.name.startsWith(
|
||||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentGun;
|
const isVisible = isCurrentGun;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
gunModelDefinition.armor,
|
gunModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
+6
-6
@@ -50,13 +50,13 @@ export function SpacedArmorSubExternal({
|
|||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell =
|
const shell =
|
||||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
material.uniforms.penetration.value = shell.penetration.near;
|
material.uniforms.penetration.value = shell.penetration!.near;
|
||||||
}
|
}
|
||||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment,
|
||||||
);
|
);
|
||||||
material.uniforms.thickness.value = hasEnhancedArmor
|
material.uniforms.thickness.value = hasEnhancedArmor
|
||||||
? thickness * 1.03
|
? thickness * 1.03
|
||||||
@@ -66,11 +66,11 @@ export function SpacedArmorSubExternal({
|
|||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell =
|
const shell =
|
||||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
const penetration = shell.penetration.near;
|
const penetration = shell.penetration!.near;
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
Duel.state.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment,
|
||||||
);
|
);
|
||||||
|
|
||||||
material.uniforms.penetration.value =
|
material.uniforms.penetration.value =
|
||||||
@@ -87,11 +87,11 @@ export function SpacedArmorSubExternal({
|
|||||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||||
Duel.on(
|
Duel.on(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
handleProtagonistEquipmentChange
|
handleProtagonistEquipmentChange,
|
||||||
),
|
),
|
||||||
Duel.on(
|
Duel.on(
|
||||||
(state) => state.antagonist.equipmentMatrix,
|
(state) => state.antagonist.equipmentMatrix,
|
||||||
handleAntagonistEquipmentChange
|
handleAntagonistEquipmentChange,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -53,20 +53,20 @@ export function SpacedArmorSubSpaced({
|
|||||||
const shell =
|
const shell =
|
||||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
|
|
||||||
material.uniforms.penetration.value = shell.penetration.near;
|
material.uniforms.penetration.value = shell.penetration!.near;
|
||||||
material.uniforms.caliber.value = shell.caliber;
|
material.uniforms.caliber.value = shell.caliber;
|
||||||
material.uniforms.ricochet.value = degToRad(
|
material.uniforms.ricochet.value = degToRad(
|
||||||
isExplosive(shell.type) ? 90 : shell.ricochet!
|
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||||
);
|
);
|
||||||
material.uniforms.normalization.value = degToRad(
|
material.uniforms.normalization.value = degToRad(
|
||||||
shell.normalization ?? 0
|
shell.normalization ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
function handleProtagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment,
|
||||||
);
|
);
|
||||||
material.uniforms.thickness.value = hasEnhancedArmor
|
material.uniforms.thickness.value = hasEnhancedArmor
|
||||||
? thickness * 1.03
|
? thickness * 1.03
|
||||||
@@ -76,11 +76,11 @@ export function SpacedArmorSubSpaced({
|
|||||||
const tankopediaEphemeral = Tankopedia.state;
|
const tankopediaEphemeral = Tankopedia.state;
|
||||||
const shell =
|
const shell =
|
||||||
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell;
|
||||||
const penetration = shell.penetration.near;
|
const penetration = shell.penetration!.near;
|
||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
Duel.state.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
equipment
|
equipment,
|
||||||
);
|
);
|
||||||
|
|
||||||
material.uniforms.penetration.value =
|
material.uniforms.penetration.value =
|
||||||
@@ -97,11 +97,11 @@ export function SpacedArmorSubSpaced({
|
|||||||
Tankopedia.on((state) => state.customShell, handleShellChange),
|
Tankopedia.on((state) => state.customShell, handleShellChange),
|
||||||
Duel.on(
|
Duel.on(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
handleProtagonistEquipmentChange
|
handleProtagonistEquipmentChange,
|
||||||
),
|
),
|
||||||
Duel.on(
|
Duel.on(
|
||||||
(state) => state.antagonist.equipmentMatrix,
|
(state) => state.antagonist.equipmentMatrix,
|
||||||
handleAntagonistEquipmentChange
|
handleAntagonistEquipmentChange,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+30
-24
@@ -83,16 +83,18 @@ export function SpacedArmorSceneComponent({
|
|||||||
point: Vector3,
|
point: Vector3,
|
||||||
intersections: Intersection[],
|
intersections: Intersection[],
|
||||||
allowRicochet: boolean,
|
allowRicochet: boolean,
|
||||||
remainingPenetrationInput?: number
|
remainingPenetrationInput?: number,
|
||||||
) => {
|
) => {
|
||||||
const { customShell } = Tankopedia.state;
|
const { customShell } = Tankopedia.state;
|
||||||
const shell = customShell ?? Duel.state.antagonist.shell;
|
const shell = customShell ?? Duel.state.antagonist.shell;
|
||||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||||
const shot: Shot = {
|
const shot: Shot = {
|
||||||
splashRadius:
|
splashRadius:
|
||||||
shell.type === ShellType.HE ? shell.explosion_radius : undefined,
|
shell.type === ShellType.SHELL_TYPE_HE
|
||||||
|
? shell.explosion_radius
|
||||||
|
: undefined,
|
||||||
damage: -1,
|
damage: -1,
|
||||||
containsGaps: shell.type === ShellType.HEAT,
|
containsGaps: shell.type === ShellType.SHELL_TYPE_HEAT,
|
||||||
in: {
|
in: {
|
||||||
surfaceNormal: cameraNormal,
|
surfaceNormal: cameraNormal,
|
||||||
layers: [],
|
layers: [],
|
||||||
@@ -103,21 +105,21 @@ export function SpacedArmorSceneComponent({
|
|||||||
const hasCalibratedShells = hasEquipment(
|
const hasCalibratedShells = hasEquipment(
|
||||||
103,
|
103,
|
||||||
Duel.state.antagonist.tank.equipment_preset,
|
Duel.state.antagonist.tank.equipment_preset,
|
||||||
Duel.state.antagonist.equipmentMatrix
|
Duel.state.antagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasEnhancedArmor = hasEquipment(
|
const hasEnhancedArmor = hasEquipment(
|
||||||
110,
|
110,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const penetration =
|
const penetration =
|
||||||
shell.penetration.near *
|
shell.penetration!.near *
|
||||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||||
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
||||||
const filteredIntersections = intersections.filter(
|
const filteredIntersections = intersections.filter(
|
||||||
(intersection) =>
|
(intersection) =>
|
||||||
"type" in intersection.object.userData &&
|
"type" in intersection.object.userData &&
|
||||||
!discardClippingPlane(intersection.object, intersection.point)
|
!discardClippingPlane(intersection.object, intersection.point),
|
||||||
) as unknown as Intersection<Object3D & { userData: ArmorUserData }>[];
|
) as unknown as Intersection<Object3D & { userData: ArmorUserData }>[];
|
||||||
const encounteredExternalModuleVariants: ExternalModuleVariant[] = [];
|
const encounteredExternalModuleVariants: ExternalModuleVariant[] = [];
|
||||||
const noDuplicateIntersections: typeof filteredIntersections = [];
|
const noDuplicateIntersections: typeof filteredIntersections = [];
|
||||||
@@ -126,12 +128,12 @@ export function SpacedArmorSceneComponent({
|
|||||||
if (intersection.object.userData.type === ArmorType.External) {
|
if (intersection.object.userData.type === ArmorType.External) {
|
||||||
if (
|
if (
|
||||||
!encounteredExternalModuleVariants.includes(
|
!encounteredExternalModuleVariants.includes(
|
||||||
intersection.object.userData.variant
|
intersection.object.userData.variant,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
noDuplicateIntersections.push(intersection);
|
noDuplicateIntersections.push(intersection);
|
||||||
encounteredExternalModuleVariants.push(
|
encounteredExternalModuleVariants.push(
|
||||||
intersection.object.userData.variant
|
intersection.object.userData.variant,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -159,10 +161,10 @@ export function SpacedArmorSceneComponent({
|
|||||||
const surfaceNormal = intersection
|
const surfaceNormal = intersection
|
||||||
.normal!.clone()
|
.normal!.clone()
|
||||||
.applyQuaternion(
|
.applyQuaternion(
|
||||||
intersection.object.getWorldQuaternion(new Quaternion())
|
intersection.object.getWorldQuaternion(new Quaternion()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shell.type === ShellType.HEAT && index > 0) {
|
if (shell.type === ShellType.SHELL_TYPE_HEAT && index > 0) {
|
||||||
const lastLayer = shot.in.layers.at(-1) as ShotLayerBase;
|
const lastLayer = shot.in.layers.at(-1) as ShotLayerBase;
|
||||||
const distance = lastLayer.point.distanceTo(intersection.point);
|
const distance = lastLayer.point.distanceTo(intersection.point);
|
||||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||||
@@ -181,7 +183,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
const thickness = layer.thickness * thicknessCoefficient;
|
const thickness = layer.thickness * thicknessCoefficient;
|
||||||
remainingPenetration -= thickness;
|
remainingPenetration -= thickness;
|
||||||
const blocked =
|
const blocked =
|
||||||
remainingPenetration <= 0 || shell.type === ShellType.HE;
|
remainingPenetration <= 0 || shell.type === ShellType.SHELL_TYPE_HE;
|
||||||
|
|
||||||
shot.in.layers.push({
|
shot.in.layers.push({
|
||||||
type: ArmorType.External,
|
type: ArmorType.External,
|
||||||
@@ -190,7 +192,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
point: intersection.point,
|
point: intersection.point,
|
||||||
surfaceNormal,
|
surfaceNormal,
|
||||||
status:
|
status:
|
||||||
shell.type === ShellType.HE
|
shell.type === ShellType.SHELL_TYPE_HE
|
||||||
? "blocked"
|
? "blocked"
|
||||||
: blocked
|
: blocked
|
||||||
? "blocked"
|
? "blocked"
|
||||||
@@ -200,7 +202,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
} else {
|
} else {
|
||||||
const thickness = layer.thickness * thicknessCoefficient;
|
const thickness = layer.thickness * thicknessCoefficient;
|
||||||
const ricochet = degToRad(
|
const ricochet = degToRad(
|
||||||
isExplosive(shell.type) ? 90 : shell.ricochet!
|
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||||
);
|
);
|
||||||
const angle = surfaceNormal.angleTo(cameraNormal);
|
const angle = surfaceNormal.angleTo(cameraNormal);
|
||||||
|
|
||||||
@@ -241,7 +243,8 @@ export function SpacedArmorSceneComponent({
|
|||||||
point: intersection.point,
|
point: intersection.point,
|
||||||
surfaceNormal: intersection.face!.normal,
|
surfaceNormal: intersection.face!.normal,
|
||||||
status:
|
status:
|
||||||
shell.type === ShellType.HE && layer.type !== ArmorType.Primary
|
shell.type === ShellType.SHELL_TYPE_HE &&
|
||||||
|
layer.type !== ArmorType.Primary
|
||||||
? "blocked"
|
? "blocked"
|
||||||
: blocked
|
: blocked
|
||||||
? "blocked"
|
? "blocked"
|
||||||
@@ -251,7 +254,10 @@ export function SpacedArmorSceneComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shell.type !== ShellType.HE && remainingPenetration <= 0) {
|
if (
|
||||||
|
shell.type !== ShellType.SHELL_TYPE_HE &&
|
||||||
|
remainingPenetration <= 0
|
||||||
|
) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +272,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
if (!lastLayer || !firstLayer) {
|
if (!lastLayer || !firstLayer) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
if (shell.type === ShellType.HE) {
|
if (shell.type === ShellType.SHELL_TYPE_HE) {
|
||||||
const totalSpacedArmorThickness = shot.in.layers.reduce(
|
const totalSpacedArmorThickness = shot.in.layers.reduce(
|
||||||
(acc, layer) => {
|
(acc, layer) => {
|
||||||
if (layer.type === null || layer.type === ArmorType.Primary) {
|
if (layer.type === null || layer.type === ArmorType.Primary) {
|
||||||
@@ -279,10 +285,10 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
return acc + layer.thicknessAngled;
|
return acc + layer.thicknessAngled;
|
||||||
},
|
},
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
const distanceFromSpacedArmor = firstLayer.point.distanceTo(
|
const distanceFromSpacedArmor = firstLayer.point.distanceTo(
|
||||||
lastLayer.point
|
lastLayer.point,
|
||||||
);
|
);
|
||||||
const finalDamage = Math.max(
|
const finalDamage = Math.max(
|
||||||
0,
|
0,
|
||||||
@@ -291,7 +297,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
(1 - distanceFromSpacedArmor / shell.explosion_radius!) -
|
(1 - distanceFromSpacedArmor / shell.explosion_radius!) -
|
||||||
1.1 *
|
1.1 *
|
||||||
(lastLayer.thicknessAngled +
|
(lastLayer.thicknessAngled +
|
||||||
Math.min(penetration, totalSpacedArmorThickness))
|
Math.min(penetration, totalSpacedArmorThickness)),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,20 +334,20 @@ export function SpacedArmorSceneComponent({
|
|||||||
.sub(
|
.sub(
|
||||||
lastLayer.surfaceNormal
|
lastLayer.surfaceNormal
|
||||||
.clone()
|
.clone()
|
||||||
.multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal))
|
.multiplyScalar(2 * shellNormal.dot(lastLayer.surfaceNormal)),
|
||||||
);
|
);
|
||||||
|
|
||||||
caster.set(lastLayer.point, ricochetNormal);
|
caster.set(lastLayer.point, ricochetNormal);
|
||||||
|
|
||||||
const ricochetIntersections = caster.intersectObjects(
|
const ricochetIntersections = caster.intersectObjects(
|
||||||
props.scene.children,
|
props.scene.children,
|
||||||
true
|
true,
|
||||||
);
|
);
|
||||||
const outShot = shoot(
|
const outShot = shoot(
|
||||||
lastLayer.point,
|
lastLayer.point,
|
||||||
ricochetIntersections,
|
ricochetIntersections,
|
||||||
false,
|
false,
|
||||||
remainingPenetration
|
remainingPenetration,
|
||||||
);
|
);
|
||||||
|
|
||||||
shot.in.status = "ricochet";
|
shot.in.status = "ricochet";
|
||||||
@@ -363,7 +369,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
return shot;
|
return shot;
|
||||||
},
|
},
|
||||||
[camera]
|
[camera],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
|
|
||||||
const maskOrigin =
|
const maskOrigin =
|
||||||
@@ -53,16 +53,16 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
? undefined
|
? undefined
|
||||||
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||||
const showPrimaryArmor = TankopediaPersistent.use(
|
const showPrimaryArmor = TankopediaPersistent.use(
|
||||||
(state) => state.showPrimaryArmor
|
(state) => state.showPrimaryArmor,
|
||||||
);
|
);
|
||||||
const showSpacedArmor = TankopediaPersistent.use(
|
const showSpacedArmor = TankopediaPersistent.use(
|
||||||
(state) => state.showSpacedArmor
|
(state) => state.showSpacedArmor,
|
||||||
);
|
);
|
||||||
const showExternalModules = TankopediaPersistent.use(
|
const showExternalModules = TankopediaPersistent.use(
|
||||||
(state) => state.showExternalModules
|
(state) => state.showExternalModules,
|
||||||
);
|
);
|
||||||
const isDynamicArmorActive = Duel.use((state) =>
|
const isDynamicArmorActive = Duel.use((state) =>
|
||||||
state.protagonist.consumables.includes(73)
|
state.protagonist.consumables.includes(73),
|
||||||
);
|
);
|
||||||
|
|
||||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||||
@@ -75,8 +75,8 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const isVisible = isHull;
|
const isVisible = isHull;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
tankModelDefinition.armor,
|
tankModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -127,13 +127,13 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
<group ref={turretContainer}>
|
<group ref={turretContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentTurret = node.name.startsWith(
|
const isCurrentTurret = node.name.startsWith(
|
||||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
|
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentTurret;
|
const isVisible = isCurrentTurret;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
turretModelDefinition.armor,
|
turretModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -168,12 +168,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
@@ -184,10 +184,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
modelTransformEvent.last!.pitch,
|
modelTransformEvent.last!.pitch,
|
||||||
modelTransformEvent.last!.yaw +
|
modelTransformEvent.last!.yaw +
|
||||||
delta.x * (Math.PI / boundingRect.width),
|
delta.x * (Math.PI / boundingRect.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
}
|
}
|
||||||
@@ -216,13 +216,13 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
<group ref={gunContainer}>
|
<group ref={gunContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentGun = node.name.startsWith(
|
const isCurrentGun = node.name.startsWith(
|
||||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentGun;
|
const isVisible = isCurrentGun;
|
||||||
const armorId = nameToArmorId(node.name);
|
const armorId = nameToArmorId(node.name);
|
||||||
const { spaced, thickness } = resolveArmor(
|
const { spaced, thickness } = resolveArmor(
|
||||||
gunModelDefinition.armor,
|
gunModelDefinition.armor!,
|
||||||
armorId
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -256,12 +256,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
delta.set(event.clientX, event.clientY).sub(position);
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
@@ -272,10 +272,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
delta.y * (Math.PI / boundingRect.height),
|
delta.y * (Math.PI / boundingRect.height),
|
||||||
modelTransformEvent.last!.yaw +
|
modelTransformEvent.last!.yaw +
|
||||||
delta.x * (Math.PI / boundingRect.width),
|
delta.x * (Math.PI / boundingRect.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
}
|
}
|
||||||
@@ -332,12 +332,12 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
delta.set(event.clientX, event.clientY).sub(position);
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
@@ -348,10 +348,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
delta.y * (Math.PI / boundingRect.height),
|
delta.y * (Math.PI / boundingRect.height),
|
||||||
modelTransformEvent.last!.yaw +
|
modelTransformEvent.last!.yaw +
|
||||||
delta.x * (Math.PI / boundingRect.width),
|
delta.x * (Math.PI / boundingRect.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { TankClass } from '@blitzkit/core';
|
import { TankClass } from "@blitzkit/core";
|
||||||
import type { ComponentProps, ReactNode } from 'react';
|
import type { ComponentProps, ReactNode } from "react";
|
||||||
import { ClassHeavy } from './components/ClassHeavy';
|
import { ClassHeavy } from "./components/ClassHeavy";
|
||||||
import { ClassLight } from './components/ClassLight';
|
import { ClassLight } from "./components/ClassLight";
|
||||||
import { ClassMedium } from './components/ClassMedium';
|
import { ClassMedium } from "./components/ClassMedium";
|
||||||
import { ClassTankDestroyer } from './components/ClassTankDestroyer';
|
import { ClassTankDestroyer } from "./components/ClassTankDestroyer";
|
||||||
|
|
||||||
export const classIcons: Record<
|
export const classIcons: Record<
|
||||||
TankClass,
|
TankClass,
|
||||||
(props: ComponentProps<'svg'>) => ReactNode
|
(props: ComponentProps<"svg">) => ReactNode
|
||||||
> = {
|
> = {
|
||||||
[TankClass.TANK_DESTROYER]: ClassTankDestroyer,
|
[TankClass.TANK_CLASS_TANK_DESTROYER]: ClassTankDestroyer,
|
||||||
[TankClass.HEAVY]: ClassHeavy,
|
[TankClass.TANK_CLASS_HEAVY]: ClassHeavy,
|
||||||
[TankClass.LIGHT]: ClassLight,
|
[TankClass.TANK_CLASS_LIGHT]: ClassLight,
|
||||||
[TankClass.MEDIUM]: ClassMedium,
|
[TankClass.TANK_CLASS_MEDIUM]: ClassMedium,
|
||||||
};
|
};
|
||||||
@@ -15,7 +15,7 @@ interface BodyProps {
|
|||||||
export function Body({ stats }: BodyProps) {
|
export function Body({ stats }: BodyProps) {
|
||||||
const members = CompareEphemeral.use((state) => state.members);
|
const members = CompareEphemeral.use((state) => state.members);
|
||||||
const hasNonRegularGun = members.some(
|
const hasNonRegularGun = members.some(
|
||||||
({ gun }) => gun.gun_type!.$case !== "regular"
|
({ gun }) => gun.gun_type!.$case !== "regular",
|
||||||
);
|
);
|
||||||
const { unwrap, strings } = useLocale();
|
const { unwrap, strings } = useLocale();
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export function Body({ stats }: BodyProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name)}
|
alt={unwrap(thisShell.name!)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
<img
|
<img
|
||||||
alt={unwrap(
|
alt={unwrap(
|
||||||
provisionDefinitions.provisions[provision]
|
provisionDefinitions.provisions[provision]
|
||||||
.name,
|
.name!,
|
||||||
)}
|
)}
|
||||||
key={provision}
|
key={provision}
|
||||||
src={asset(
|
src={asset(
|
||||||
@@ -501,7 +501,7 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
alt={unwrap(
|
alt={unwrap(
|
||||||
consumableDefinitions.consumables[
|
consumableDefinitions.consumables[
|
||||||
consumable
|
consumable
|
||||||
].name,
|
].name!,
|
||||||
)}
|
)}
|
||||||
key={consumable}
|
key={consumable}
|
||||||
src={asset(
|
src={asset(
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export function TankCard({ index, tank }: TankCardProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name)}
|
alt={unwrap(tank.name!)}
|
||||||
src={tankIcon(tank.id)}
|
src={tankIcon(tank.id)}
|
||||||
width={64}
|
width={64}
|
||||||
height={64}
|
height={64}
|
||||||
@@ -137,7 +137,7 @@ export function TankCard({ index, tank }: TankCardProps) {
|
|||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{unwrap(tank.name)}
|
{unwrap(tank.name!)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const fakeCompositeStats = compositeStats(
|
|||||||
wins: 30,
|
wins: 30,
|
||||||
xp: 1230 * 50,
|
xp: 1230 * 50,
|
||||||
},
|
},
|
||||||
averageDefinitions.averages[7297].mu
|
averageDefinitions.averages[7297].mu!,
|
||||||
);
|
);
|
||||||
|
|
||||||
export function BreakdownPreview() {
|
export function BreakdownPreview() {
|
||||||
@@ -88,9 +88,9 @@ export function BreakdownRenderer() {
|
|||||||
const diff = useMemo(
|
const diff = useMemo(
|
||||||
() =>
|
() =>
|
||||||
deltaTankStats(tankStatsA, tankStatsB).sort(
|
deltaTankStats(tankStatsA, tankStatsB).sort(
|
||||||
(a, b) => b.last_battle_time - a.last_battle_time
|
(a, b) => b.last_battle_time - a.last_battle_time,
|
||||||
),
|
),
|
||||||
[tankStatsB]
|
[tankStatsB],
|
||||||
);
|
);
|
||||||
const compositeStats = diff
|
const compositeStats = diff
|
||||||
.map((diff) => {
|
.map((diff) => {
|
||||||
@@ -100,14 +100,14 @@ export function BreakdownRenderer() {
|
|||||||
|
|
||||||
const compositeStats = calculateCompositeStats(
|
const compositeStats = calculateCompositeStats(
|
||||||
{ battle_life_time: diff.battle_life_time, ...diff.all },
|
{ battle_life_time: diff.battle_life_time, ...diff.all },
|
||||||
average.mu
|
average.mu!,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { id: diff.tank_id, compositeStats };
|
return { id: diff.tank_id, compositeStats };
|
||||||
})
|
})
|
||||||
.filter((stats) => stats !== null);
|
.filter((stats) => stats !== null);
|
||||||
const sum = sumCompositeStats(
|
const sum = sumCompositeStats(
|
||||||
compositeStats.map(({ compositeStats }) => compositeStats)
|
compositeStats.map(({ compositeStats }) => compositeStats),
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{
|
|||||||
|
|
||||||
export function GalleryCard(props: GalleryCardProps) {
|
export function GalleryCard(props: GalleryCardProps) {
|
||||||
const src = asset(
|
const src = asset(
|
||||||
`gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`
|
`gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`,
|
||||||
);
|
);
|
||||||
const card = useRef<HTMLDivElement>(null!);
|
const card = useRef<HTMLDivElement>(null!);
|
||||||
const { unwrap } = useLocale();
|
const { unwrap } = useLocale();
|
||||||
@@ -54,11 +54,11 @@ export function GalleryCard(props: GalleryCardProps) {
|
|||||||
objectFit: "cover",
|
objectFit: "cover",
|
||||||
}}
|
}}
|
||||||
src={src}
|
src={src}
|
||||||
alt={unwrap(props.avatar.name)}
|
alt={unwrap(props.avatar.name!)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text size="1" align="center" style={{ maxWidth: "100%" }}>
|
<Text size="1" align="center" style={{ maxWidth: "100%" }}>
|
||||||
{unwrap(props.avatar.name)}
|
{unwrap(props.avatar.name!)}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -90,12 +90,12 @@ export function GalleryCard(props: GalleryCardProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex px="4" justify="center" mt="3">
|
<Flex px="4" justify="center" mt="3">
|
||||||
<img src={src} alt={unwrap(props.avatar.name)} />
|
<img src={src} alt={unwrap(props.avatar.name!)} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex justify="center" mt="4">
|
<Flex justify="center" mt="4">
|
||||||
<Dialog.Title align="center" style={{ maxWidth: "10rem" }}>
|
<Dialog.Title align="center" style={{ maxWidth: "10rem" }}>
|
||||||
{unwrap(props.avatar.name)}
|
{unwrap(props.avatar.name!)}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ export function Guesser() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setResults(
|
setResults(
|
||||||
searchResults.map((result) => tankDefinitions.tanks[result.obj.id])
|
searchResults.map((result) => tankDefinitions.tanks[result.obj.id]),
|
||||||
);
|
);
|
||||||
}, 500),
|
}, 500),
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -126,11 +126,11 @@ export function Guesser() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
|
|
||||||
input.current.value = unwrap(result.name);
|
input.current.value = unwrap(result.name!);
|
||||||
setSelected(result);
|
setSelected(result);
|
||||||
setResults(null);
|
setResults(null);
|
||||||
}}
|
}}
|
||||||
text={unwrap(result.name)}
|
text={unwrap(result.name!)}
|
||||||
prefix={
|
prefix={
|
||||||
<img
|
<img
|
||||||
style={{ width: "1em", height: "1em" }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
@@ -321,7 +321,7 @@ export function Guesser() {
|
|||||||
return tiers.includes(tank.tier);
|
return tiers.includes(tank.tier);
|
||||||
});
|
});
|
||||||
const id = Number(
|
const id = Number(
|
||||||
filteredIds[Math.floor(Math.random() * filteredIds.length)]
|
filteredIds[Math.floor(Math.random() * filteredIds.length)],
|
||||||
);
|
);
|
||||||
const tank = tankDefinitions.tanks[id];
|
const tank = tankDefinitions.tanks[id];
|
||||||
|
|
||||||
|
|||||||
@@ -118,16 +118,16 @@ export function Model() {
|
|||||||
ref={turretGroup}
|
ref={turretGroup}
|
||||||
rotation={[0, 0, modelTransformEvent.last!.yaw]}
|
rotation={[0, 0, modelTransformEvent.last!.yaw]}
|
||||||
position={[
|
position={[
|
||||||
tankModel.turret_origin.x + trackModel.origin.x,
|
tankModel.turret_origin!.x + trackModel.origin!.x,
|
||||||
tankModel.turret_origin.z + trackModel.origin.z,
|
tankModel.turret_origin!.z + trackModel.origin!.z,
|
||||||
tankModel.turret_origin.y + trackModel.origin.y,
|
tankModel.turret_origin!.y + trackModel.origin!.y,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<group
|
<group
|
||||||
position={[
|
position={[
|
||||||
-turretTankModel.turret_origin.x - turretTrackModel.origin.x,
|
-turretTankModel.turret_origin!.x - turretTrackModel.origin!.x,
|
||||||
-turretTankModel.turret_origin.z - turretTrackModel.origin.z,
|
-turretTankModel.turret_origin!.z - turretTrackModel.origin!.z,
|
||||||
-turretTankModel.turret_origin.y - turretTrackModel.origin.y,
|
-turretTankModel.turret_origin!.y - turretTrackModel.origin!.y,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{turretNodes.map((node) => {
|
{turretNodes.map((node) => {
|
||||||
@@ -160,28 +160,28 @@ export function Model() {
|
|||||||
ref={gunGroup}
|
ref={gunGroup}
|
||||||
rotation={[modelTransformEvent.last!.pitch, 0, 0]}
|
rotation={[modelTransformEvent.last!.pitch, 0, 0]}
|
||||||
position={[
|
position={[
|
||||||
turretTankModel.turret_origin.x +
|
turretTankModel.turret_origin!.x +
|
||||||
turretTrackModel.origin.x +
|
turretTrackModel.origin!.x +
|
||||||
turretModel.gun_origin.x,
|
turretModel.gun_origin!.x,
|
||||||
turretTankModel.turret_origin.z +
|
turretTankModel.turret_origin!.z +
|
||||||
turretTrackModel.origin.z +
|
turretTrackModel.origin!.z +
|
||||||
turretModel.gun_origin.z,
|
turretModel.gun_origin!.z,
|
||||||
turretTankModel.turret_origin.y +
|
turretTankModel.turret_origin!.y +
|
||||||
turretTrackModel.origin.y +
|
turretTrackModel.origin!.y +
|
||||||
turretModel.gun_origin.y,
|
turretModel.gun_origin!.y,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<group
|
<group
|
||||||
position={[
|
position={[
|
||||||
-turretTankModel.turret_origin.x -
|
-turretTankModel.turret_origin!.x -
|
||||||
turretTrackModel.origin.x -
|
turretTrackModel.origin!.x -
|
||||||
turretModel.gun_origin.x,
|
turretModel.gun_origin!.x,
|
||||||
-turretTankModel.turret_origin.z -
|
-turretTankModel.turret_origin!.z -
|
||||||
turretTrackModel.origin.z -
|
turretTrackModel.origin!.z -
|
||||||
turretModel.gun_origin.z,
|
turretModel.gun_origin!.z,
|
||||||
-turretTankModel.turret_origin.y -
|
-turretTankModel.turret_origin!.y -
|
||||||
turretTrackModel.origin.y -
|
turretTrackModel.origin!.y -
|
||||||
turretModel.gun_origin.y,
|
turretModel.gun_origin!.y,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{turretNodes.map((node) => {
|
{turretNodes.map((node) => {
|
||||||
@@ -216,15 +216,15 @@ export function Model() {
|
|||||||
|
|
||||||
<group
|
<group
|
||||||
position={[
|
position={[
|
||||||
-gunTankModel.turret_origin.x -
|
-gunTankModel.turret_origin!.x -
|
||||||
gunTrackModel.origin.x -
|
gunTrackModel.origin!.x -
|
||||||
gunTurretModel.gun_origin.x,
|
gunTurretModel.gun_origin!.x,
|
||||||
-gunTankModel.turret_origin.z -
|
-gunTankModel.turret_origin!.z -
|
||||||
gunTrackModel.origin.z -
|
gunTrackModel.origin!.z -
|
||||||
gunTurretModel.gun_origin.z,
|
gunTurretModel.gun_origin!.z,
|
||||||
-gunTankModel.turret_origin.y -
|
-gunTankModel.turret_origin!.y -
|
||||||
gunTrackModel.origin.y -
|
gunTrackModel.origin!.y -
|
||||||
gunTurretModel.gun_origin.y,
|
gunTurretModel.gun_origin!.y,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{gunNodes.map((node) => {
|
{gunNodes.map((node) => {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
import { asset } from "@blitzkit/core";
|
||||||
import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
|
import { ClockIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||||
import { Flex, Text } from '@radix-ui/themes';
|
import { Flex, Text } from "@radix-ui/themes";
|
||||||
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
|
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
|
||||||
import { useDelta } from '../../hooks/useDelta';
|
import { useDelta } from "../../hooks/useDelta";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||||
|
|
||||||
interface ConsumableButtonProps extends TankComponentButtonProps {
|
interface ConsumableButtonProps extends TankComponentButtonProps {
|
||||||
consumable: number;
|
consumable: number;
|
||||||
@@ -27,8 +27,8 @@ export function ConsumableButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
style={{ width: '6rem' }}
|
style={{ width: "6rem" }}
|
||||||
tooltip={unwrap(consumableDefinitions.consumables[consumable].name)}
|
tooltip={unwrap(consumableDefinitions.consumables[consumable].name!)}
|
||||||
icon={asset(`icons/consumables/${consumable}.webp`)}
|
icon={asset(`icons/consumables/${consumable}.webp`)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -39,9 +39,9 @@ export function ConsumableButton({
|
|||||||
size="1"
|
size="1"
|
||||||
color={
|
color={
|
||||||
durationDelta > 0
|
durationDelta > 0
|
||||||
? 'green'
|
? "green"
|
||||||
: durationDelta < 0
|
: durationDelta < 0
|
||||||
? 'tomato'
|
? "tomato"
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -56,9 +56,9 @@ export function ConsumableButton({
|
|||||||
size="1"
|
size="1"
|
||||||
color={
|
color={
|
||||||
cooldownDelta < 0
|
cooldownDelta < 0
|
||||||
? 'green'
|
? "green"
|
||||||
: cooldownDelta > 0
|
: cooldownDelta > 0
|
||||||
? 'tomato'
|
? "tomato"
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
import { asset } from "@blitzkit/core";
|
||||||
import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions';
|
import { awaitableEquipmentDefinitions } from "../../core/awaitables/equipmentDefinitions";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||||
|
|
||||||
interface EquipmentButtonProps extends TankComponentButtonProps {
|
interface EquipmentButtonProps extends TankComponentButtonProps {
|
||||||
equipment: number;
|
equipment: number;
|
||||||
@@ -15,7 +15,7 @@ export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
tooltip={unwrap(equipmentDefinitions.equipments[equipment].name)}
|
tooltip={unwrap(equipmentDefinitions.equipments[equipment].name!)}
|
||||||
icon={asset(`icons/equipment/${equipment}.webp`)}
|
icon={asset(`icons/equipment/${equipment}.webp`)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { asset } from '@blitzkit/core';
|
import { asset } from "@blitzkit/core";
|
||||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
|
||||||
import { useLocale } from '../../hooks/useLocale';
|
import { useLocale } from "../../hooks/useLocale";
|
||||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
import { GenericTankComponentButton } from "./GenericTankComponentButton";
|
||||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from "./TankComponentButton";
|
||||||
|
|
||||||
interface ProvisionButtonProps extends TankComponentButtonProps {
|
interface ProvisionButtonProps extends TankComponentButtonProps {
|
||||||
provision: number;
|
provision: number;
|
||||||
@@ -15,7 +15,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GenericTankComponentButton
|
<GenericTankComponentButton
|
||||||
tooltip={unwrap(provisionDefinitions.provisions[provision].name)}
|
tooltip={unwrap(provisionDefinitions.provisions[provision].name!)}
|
||||||
icon={asset(`icons/provisions/${provision}.webp`)}
|
icon={asset(`icons/provisions/${provision}.webp`)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import {
|
|||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
type TrackDefinition,
|
type TrackDefinition,
|
||||||
type TurretDefinition,
|
type TurretDefinition,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { DropdownMenu, Flex } from '@radix-ui/themes';
|
import { DropdownMenu, Flex } from "@radix-ui/themes";
|
||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
import { useLocale } from '../hooks/useLocale';
|
import { useLocale } from "../hooks/useLocale";
|
||||||
import { ModuleButton } from './ModuleButtons/ModuleButton';
|
import { ModuleButton } from "./ModuleButtons/ModuleButton";
|
||||||
|
|
||||||
interface ModuleManagerProps {
|
interface ModuleManagerProps {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
@@ -72,8 +72,8 @@ export function ModuleManager({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
module="turret"
|
module="turret"
|
||||||
discriminator={TIER_ROMAN_NUMERALS[turret.tier]}
|
discriminator={TIER_ROMAN_NUMERALS[turret.tier]}
|
||||||
/>{' '}
|
/>{" "}
|
||||||
{unwrap(turret.name)}
|
{unwrap(turret.name!)}
|
||||||
</DropdownMenu.RadioItem>
|
</DropdownMenu.RadioItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -112,8 +112,8 @@ export function ModuleManager({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
module="gun"
|
module="gun"
|
||||||
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
|
||||||
/>{' '}
|
/>{" "}
|
||||||
{unwrap(gun.name)}
|
{unwrap(gun.name!)}
|
||||||
</DropdownMenu.RadioItem>
|
</DropdownMenu.RadioItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -151,8 +151,8 @@ export function ModuleManager({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
module="engine"
|
module="engine"
|
||||||
discriminator={TIER_ROMAN_NUMERALS[engine.tier]}
|
discriminator={TIER_ROMAN_NUMERALS[engine.tier]}
|
||||||
/>{' '}
|
/>{" "}
|
||||||
{unwrap(engine.name)}
|
{unwrap(engine.name!)}
|
||||||
</DropdownMenu.RadioItem>
|
</DropdownMenu.RadioItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -190,8 +190,8 @@ export function ModuleManager({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
module="chassis"
|
module="chassis"
|
||||||
discriminator={TIER_ROMAN_NUMERALS[track.tier]}
|
discriminator={TIER_ROMAN_NUMERALS[track.tier]}
|
||||||
/>{' '}
|
/>{" "}
|
||||||
{unwrap(track.name)}
|
{unwrap(track.name!)}
|
||||||
</DropdownMenu.RadioItem>
|
</DropdownMenu.RadioItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
const averages = averageDefinitions.averages[tank.id];
|
const averages = averageDefinitions.averages[tank.id];
|
||||||
const ratio = useAveragesExclusionRatio();
|
const ratio = useAveragesExclusionRatio();
|
||||||
const playerCountPeriod = Performance.use(
|
const playerCountPeriod = Performance.use(
|
||||||
(state) => state.playerCountPeriod
|
(state) => state.playerCountPeriod,
|
||||||
);
|
);
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
|
|
||||||
@@ -29,107 +29,109 @@ export const TankRow = memo<TankRowProps>(
|
|||||||
<TankRowHeaderCell tank={tank} />
|
<TankRowHeaderCell tank={tank} />
|
||||||
|
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.wins / averages.mu.battles}
|
value={averages.mu!.wins / averages.mu!.battles}
|
||||||
truth={average.winrate}
|
truth={average.winrate}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{((averages.mu.wins / averages.mu.battles) * 100).toFixed(1)}%
|
{((averages.mu!.wins / averages.mu!.battles) * 100).toFixed(1)}%
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{formatCompact(
|
{formatCompact(
|
||||||
locale,
|
locale,
|
||||||
Math.round(ratio * averages.samples[playerCountPeriod])
|
Math.round(ratio * averages.samples![playerCountPeriod]),
|
||||||
)}
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.damage_dealt / averages.mu.battles}
|
value={averages.mu!.damage_dealt / averages.mu!.battles}
|
||||||
truth={average.damage}
|
truth={average.damage}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{Math.round(
|
{Math.round(
|
||||||
averages.mu.damage_dealt / averages.mu.battles
|
averages.mu!.damage_dealt / averages.mu!.battles,
|
||||||
).toLocaleString(locale)}
|
).toLocaleString(locale)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.survived_battles / averages.mu.battles}
|
value={averages.mu!.survived_battles / averages.mu!.battles}
|
||||||
truth={average.survival}
|
truth={average.survival}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{Math.round(
|
{Math.round(
|
||||||
(averages.mu.survived_battles / averages.mu.battles) * 100
|
(averages.mu!.survived_battles / averages.mu!.battles) * 100,
|
||||||
)}
|
)}
|
||||||
%
|
%
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.xp / averages.mu.battles}
|
value={averages.mu!.xp / averages.mu!.battles}
|
||||||
truth={average.xp}
|
truth={average.xp}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{Math.round(averages.mu.xp / averages.mu.battles).toLocaleString(
|
{Math.round(averages.mu!.xp / averages.mu!.battles).toLocaleString(
|
||||||
locale
|
locale,
|
||||||
)}
|
)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.frags / averages.mu.battles}
|
value={averages.mu!.frags / averages.mu!.battles}
|
||||||
truth={average.kills}
|
truth={average.kills}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{(averages.mu.frags / averages.mu.battles).toFixed(2)}
|
{(averages.mu!.frags / averages.mu!.battles).toFixed(2)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.spotted / averages.mu.battles}
|
value={averages.mu!.spotted / averages.mu!.battles}
|
||||||
truth={average.spots}
|
truth={average.spots}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{(averages.mu.spotted / averages.mu.battles).toFixed(2)}
|
{(averages.mu!.spotted / averages.mu!.battles).toFixed(2)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.hits / averages.mu.shots}
|
value={averages.mu!.hits / averages.mu!.shots}
|
||||||
truth={average.accuracy}
|
truth={average.accuracy}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{Math.round((averages.mu.hits / averages.mu.shots) * 100)}%
|
{Math.round((averages.mu!.hits / averages.mu!.shots) * 100)}%
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.shots / averages.mu.battles}
|
value={averages.mu!.shots / averages.mu!.battles}
|
||||||
truth={average.shots}
|
truth={average.shots}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{(averages.mu.shots / averages.mu.battles).toFixed(1)}
|
{(averages.mu!.shots / averages.mu!.battles).toFixed(1)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.hits / averages.mu.battles}
|
value={averages.mu!.hits / averages.mu!.battles}
|
||||||
truth={average.hits}
|
truth={average.hits}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{(averages.mu.hits / averages.mu.battles).toFixed(1)}
|
{(averages.mu!.hits / averages.mu!.battles).toFixed(1)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.damage_dealt / averages.mu.damage_received}
|
value={averages.mu!.damage_dealt / averages.mu!.damage_received}
|
||||||
truth={average.damageRatio}
|
truth={average.damageRatio}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{(averages.mu.damage_dealt / averages.mu.damage_received).toFixed(2)}
|
{(averages.mu!.damage_dealt / averages.mu!.damage_received).toFixed(
|
||||||
|
2,
|
||||||
|
)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.damage_received / averages.mu.battles}
|
value={averages.mu!.damage_received / averages.mu!.battles}
|
||||||
truth={average.damageReceived}
|
truth={average.damageReceived}
|
||||||
direction={CompareCellDirection.LOWER_IS_BETTER}
|
direction={CompareCellDirection.LOWER_IS_BETTER}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{Math.round(
|
{Math.round(
|
||||||
averages.mu.damage_received / averages.mu.battles
|
averages.mu!.damage_received / averages.mu!.battles,
|
||||||
).toLocaleString(locale)}
|
).toLocaleString(locale)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
<CompareCell
|
<CompareCell
|
||||||
value={averages.mu.capture_points / averages.mu.battles}
|
value={averages.mu!.capture_points / averages.mu!.battles}
|
||||||
truth={average.capturePoints}
|
truth={average.capturePoints}
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
{(averages.mu.capture_points / averages.mu.battles).toFixed(2)}
|
{(averages.mu!.capture_points / averages.mu!.battles).toFixed(2)}
|
||||||
</CompareCell>
|
</CompareCell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prev, next) => prev.tank.id === next.tank.id
|
(prev, next) => prev.tank.id === next.tank.id,
|
||||||
);
|
);
|
||||||
@@ -49,10 +49,10 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const filteredTanks = usePromise(
|
const filteredTanks = usePromise(
|
||||||
() =>
|
() =>
|
||||||
filterTanks(filters, tankList).then((tanks) =>
|
filterTanks(filters, tankList).then((tanks) =>
|
||||||
tanks.filter(({ id }) => id in averageDefinitions.averages)
|
tanks.filter(({ id }) => id in averageDefinitions.averages),
|
||||||
),
|
),
|
||||||
// react-promise-suspense has awful type annotations
|
// react-promise-suspense has awful type annotations
|
||||||
[filters] as any
|
[filters] as any,
|
||||||
);
|
);
|
||||||
const tanksMapped = useMemo(
|
const tanksMapped = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -60,7 +60,7 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
id,
|
id,
|
||||||
...averageDefinitions.averages[id],
|
...averageDefinitions.averages[id],
|
||||||
})),
|
})),
|
||||||
[filters]
|
[filters],
|
||||||
);
|
);
|
||||||
const tanksSorted = useMemo(() => {
|
const tanksSorted = useMemo(() => {
|
||||||
const { playerCountPeriod } = Performance.state;
|
const { playerCountPeriod } = Performance.state;
|
||||||
@@ -69,83 +69,85 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
case "accuracy":
|
case "accuracy":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots)
|
sort.direction *
|
||||||
|
(a.mu!.hits / a.mu!.shots - b.mu!.hits / b.mu!.shots),
|
||||||
);
|
);
|
||||||
case "capture_points":
|
case "capture_points":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.capture_points / a.mu.battles -
|
(a.mu!.capture_points / a.mu!.battles -
|
||||||
b.mu.capture_points / b.mu.battles)
|
b.mu!.capture_points / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "damage":
|
case "damage":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.damage_dealt / a.mu.battles -
|
(a.mu!.damage_dealt / a.mu!.battles -
|
||||||
b.mu.damage_dealt / b.mu.battles)
|
b.mu!.damage_dealt / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "damage_ratio":
|
case "damage_ratio":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.damage_dealt / a.mu.damage_received -
|
(a.mu!.damage_dealt / a.mu!.damage_received -
|
||||||
b.mu.damage_dealt / b.mu.damage_received)
|
b.mu!.damage_dealt / b.mu!.damage_received),
|
||||||
);
|
);
|
||||||
case "damage_taken":
|
case "damage_taken":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.damage_received / a.mu.battles -
|
(a.mu!.damage_received / a.mu!.battles -
|
||||||
b.mu.damage_received / b.mu.battles)
|
b.mu!.damage_received / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "hits":
|
case "hits":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles)
|
(a.mu!.hits / a.mu!.battles - b.mu!.hits / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "kills":
|
case "kills":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles)
|
(a.mu!.frags / a.mu!.battles - b.mu!.frags / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "shots":
|
case "shots":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles)
|
(a.mu!.shots / a.mu!.battles - b.mu!.shots / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "players":
|
case "players":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.samples[playerCountPeriod] - b.samples[playerCountPeriod])
|
(a.samples![playerCountPeriod] - b.samples![playerCountPeriod]),
|
||||||
);
|
);
|
||||||
case "spots":
|
case "spots":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles)
|
(a.mu!.spotted / a.mu!.battles - b.mu!.spotted / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "survival":
|
case "survival":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.survived_battles / a.mu.battles -
|
(a.mu!.survived_battles / a.mu!.battles -
|
||||||
b.mu.survived_battles / b.mu.battles)
|
b.mu!.survived_battles / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "winrate":
|
case "winrate":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction *
|
sort.direction *
|
||||||
(a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles)
|
(a.mu!.wins / a.mu!.battles - b.mu!.wins / b.mu!.battles),
|
||||||
);
|
);
|
||||||
case "xp":
|
case "xp":
|
||||||
return tanksMapped.sort(
|
return tanksMapped.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles)
|
sort.direction *
|
||||||
|
(a.mu!.xp / a.mu!.battles - b.mu!.xp / b.mu!.battles),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [filters, sort]);
|
}, [filters, sort]);
|
||||||
@@ -161,11 +163,11 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const sum = useCallback(
|
const sum = useCallback(
|
||||||
(slice: (tank: BlitzkitStats) => number) => {
|
(slice: (tank: BlitzkitStats) => number) => {
|
||||||
return tanksSorted.reduce(
|
return tanksSorted.reduce(
|
||||||
(acc, tank) => acc + tank.samples[playerCountPeriod] * slice(tank.mu),
|
(acc, tank) => acc + tank.samples![playerCountPeriod] * slice(tank.mu!),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[tanksSorted]
|
[tanksSorted],
|
||||||
);
|
);
|
||||||
|
|
||||||
const battles = sum(({ battles }) => battles);
|
const battles = sum(({ battles }) => battles);
|
||||||
@@ -209,7 +211,7 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
{formatCompact(
|
{formatCompact(
|
||||||
locale,
|
locale,
|
||||||
ratio * averageDefinitions.samples[playerCountPeriod]
|
ratio * averageDefinitions.samples![playerCountPeriod],
|
||||||
)}
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell align="center">
|
<Table.Cell align="center">
|
||||||
@@ -255,12 +257,12 @@ export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
key={index}
|
key={index}
|
||||||
onIntersection={() => {
|
onIntersection={() => {
|
||||||
setLoadedRows((state) =>
|
setLoadedRows((state) =>
|
||||||
Math.min(state + 2, tanksSorted.length)
|
Math.min(state + 2, tanksSorted.length),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
style,
|
style,
|
||||||
...props
|
...props
|
||||||
}: TankCardProps,
|
}: TankCardProps,
|
||||||
ref
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { unwrap, locale } = useLocale();
|
const { unwrap, locale } = useLocale();
|
||||||
const provideLink = !noLink && onSelect === undefined;
|
const provideLink = !noLink && onSelect === undefined;
|
||||||
@@ -46,13 +46,13 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
tabIndex={onSelect ? 0 : undefined}
|
tabIndex={onSelect ? 0 : undefined}
|
||||||
size="2"
|
size="2"
|
||||||
color={
|
color={
|
||||||
tank.type === TankType.COLLECTOR
|
tank.type === TankType.TANK_TYPE_COLLECTOR
|
||||||
? "blue"
|
? "blue"
|
||||||
: tank.type === TankType.PREMIUM
|
: tank.type === TankType.TANK_TYPE_PREMIUM
|
||||||
? "amber"
|
? "amber"
|
||||||
: "gray"
|
: "gray"
|
||||||
}
|
}
|
||||||
highContrast={tank.type === TankType.RESEARCHABLE}
|
highContrast={tank.type === TankType.TANK_TYPE_RESEARCHABLE}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSelect?.(tank);
|
onSelect?.(tank);
|
||||||
TankopediaPersistent.mutate((draft) => {
|
TankopediaPersistent.mutate((draft) => {
|
||||||
@@ -79,7 +79,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name)}
|
alt={unwrap(tank.name!)}
|
||||||
src={asset(`icons/tanks/big/${tank.id}.webp`)}
|
src={asset(`icons/tanks/big/${tank.id}.webp`)}
|
||||||
className="image"
|
className="image"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
@@ -95,7 +95,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
>
|
>
|
||||||
<Icon className="class-icon" />
|
<Icon className="class-icon" />
|
||||||
<Text align="center" className="name">
|
<Text align="center" className="name">
|
||||||
{unwrap(tank.name)}
|
{unwrap(tank.name!)}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -115,5 +115,5 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
@@ -19,13 +19,13 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
|
|||||||
<LinkI18n
|
<LinkI18n
|
||||||
locale={locale}
|
locale={locale}
|
||||||
color={
|
color={
|
||||||
tank.type === TankType.COLLECTOR
|
tank.type === TankType.TANK_TYPE_COLLECTOR
|
||||||
? "blue"
|
? "blue"
|
||||||
: tank.type === TankType.PREMIUM
|
: tank.type === TankType.TANK_TYPE_PREMIUM
|
||||||
? "amber"
|
? "amber"
|
||||||
: "gray"
|
: "gray"
|
||||||
}
|
}
|
||||||
highContrast={tank.type === TankType.RESEARCHABLE}
|
highContrast={tank.type === TankType.TANK_TYPE_RESEARCHABLE}
|
||||||
underline="hover"
|
underline="hover"
|
||||||
wrap="nowrap"
|
wrap="nowrap"
|
||||||
href={`/tanks/${tank.slug}`}
|
href={`/tanks/${tank.slug}`}
|
||||||
@@ -55,7 +55,7 @@ export function TankRowHeaderCell({ tank }: TankRowHeaderCellProps) {
|
|||||||
<ExperimentIcon style={{ width: "1em", height: "1em" }} />
|
<ExperimentIcon style={{ width: "1em", height: "1em" }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{unwrap(tank.name)}
|
{unwrap(tank.name!)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</StickyRowHeaderCell>
|
</StickyRowHeaderCell>
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
|||||||
case "fire.reload":
|
case "fire.reload":
|
||||||
return resolveReload(gun).toFixed(2);
|
return resolveReload(gun).toFixed(2);
|
||||||
case "fire.standardPenetration":
|
case "fire.standardPenetration":
|
||||||
return shell0?.penetration.near.toFixed(0);
|
return shell0?.penetration!.near.toFixed(0);
|
||||||
case "fire.premiumPenetration":
|
case "fire.premiumPenetration":
|
||||||
return shell1 ? shell1?.penetration.near.toFixed(0) : "--";
|
return shell1 ? shell1?.penetration!.near.toFixed(0) : "--";
|
||||||
case "fire.tertiaryPenetration":
|
case "fire.tertiaryPenetration":
|
||||||
return shell1 ? shell2?.penetration.near.toFixed(0) : "--";
|
return shell1 ? shell2?.penetration!.near.toFixed(0) : "--";
|
||||||
case "fire.shellVelocity":
|
case "fire.shellVelocity":
|
||||||
return shell0.velocity.toFixed(0);
|
return shell0.velocity.toFixed(0);
|
||||||
case "fire.shellCapacity":
|
case "fire.shellCapacity":
|
||||||
@@ -66,12 +66,12 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
|||||||
return shell0.range.toFixed(0);
|
return shell0.range.toFixed(0);
|
||||||
case "fire.gunDepression":
|
case "fire.gunDepression":
|
||||||
return (
|
return (
|
||||||
gunModelDefinition.pitch.max +
|
gunModelDefinition.pitch!.max +
|
||||||
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
|
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
case "fire.gunElevation":
|
case "fire.gunElevation":
|
||||||
return (
|
return (
|
||||||
-gunModelDefinition.pitch.min -
|
-gunModelDefinition.pitch!.min -
|
||||||
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
|
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
case "maneuverability.forwardsSpeed":
|
case "maneuverability.forwardsSpeed":
|
||||||
@@ -113,8 +113,8 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
|||||||
case "survivability.volume": {
|
case "survivability.volume": {
|
||||||
const dimensions = normalizeBoundingBox(
|
const dimensions = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
tankModelDefinition.bounding_box,
|
tankModelDefinition.bounding_box!,
|
||||||
turretModelDefinition.bounding_box,
|
turretModelDefinition.bounding_box!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -124,8 +124,8 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
|||||||
case "survivability.length": {
|
case "survivability.length": {
|
||||||
const bounds = normalizeBoundingBox(
|
const bounds = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
tankModelDefinition.bounding_box,
|
tankModelDefinition.bounding_box!,
|
||||||
turretModelDefinition.bounding_box,
|
turretModelDefinition.bounding_box!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -85,11 +85,11 @@ for (const gameModeIdString in gameModeRoleSets) {
|
|||||||
|
|
||||||
outerLoop: for (const id of role.consumables) {
|
outerLoop: for (const id of role.consumables) {
|
||||||
const name =
|
const name =
|
||||||
consumableDefinitions.consumables[id].name.locales[locales.default];
|
consumableDefinitions.consumables[id].name!.locales[locales.default];
|
||||||
|
|
||||||
for (const otherConsumable of consumables.values()) {
|
for (const otherConsumable of consumables.values()) {
|
||||||
const otherName =
|
const otherName =
|
||||||
consumableDefinitions.consumables[otherConsumable].name.locales[
|
consumableDefinitions.consumables[otherConsumable].name!.locales[
|
||||||
locales.default
|
locales.default
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -102,11 +102,11 @@ for (const gameModeIdString in gameModeRoleSets) {
|
|||||||
|
|
||||||
outerLoop: for (const id of role.provisions) {
|
outerLoop: for (const id of role.provisions) {
|
||||||
const name =
|
const name =
|
||||||
provisionDefinitions.provisions[id].name.locales[locales.default];
|
provisionDefinitions.provisions[id].name!.locales[locales.default];
|
||||||
|
|
||||||
for (const otherProvision of provisions.values()) {
|
for (const otherProvision of provisions.values()) {
|
||||||
const otherName =
|
const otherName =
|
||||||
provisionDefinitions.provisions[otherProvision].name.locales[
|
provisionDefinitions.provisions[otherProvision].name!.locales[
|
||||||
locales.default
|
locales.default
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ const consumablesArray = Object.values(consumableDefinitions.consumables)
|
|||||||
.filter(
|
.filter(
|
||||||
(consumable) =>
|
(consumable) =>
|
||||||
!consumable.game_mode_exclusive &&
|
!consumable.game_mode_exclusive &&
|
||||||
consumable.name.locales[locales.default],
|
consumable.name!.locales[locales.default],
|
||||||
)
|
)
|
||||||
.map((consumable) => consumable.id);
|
.map((consumable) => consumable.id);
|
||||||
const provisionsArray = Array.from(
|
const provisionsArray = Array.from(
|
||||||
@@ -137,11 +137,11 @@ const provisionsArray = Array.from(
|
|||||||
.filter(
|
.filter(
|
||||||
(provision) =>
|
(provision) =>
|
||||||
!provision.game_mode_exclusive &&
|
!provision.game_mode_exclusive &&
|
||||||
provision.name.locales[locales.default],
|
provision.name!.locales[locales.default],
|
||||||
)
|
)
|
||||||
.reduce((uniqueMap, consumable) => {
|
.reduce((uniqueMap, consumable) => {
|
||||||
if (!uniqueMap.has(consumable.name.locales[locales.default])) {
|
if (!uniqueMap.has(consumable.name!.locales[locales.default])) {
|
||||||
uniqueMap.set(consumable.name.locales[locales.default], consumable.id);
|
uniqueMap.set(consumable.name!.locales[locales.default], consumable.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueMap;
|
return uniqueMap;
|
||||||
@@ -149,10 +149,10 @@ const provisionsArray = Array.from(
|
|||||||
.values(),
|
.values(),
|
||||||
);
|
);
|
||||||
const shellTypeIcons: Record<ShellType, string> = {
|
const shellTypeIcons: Record<ShellType, string> = {
|
||||||
[ShellType.AP]: "ap",
|
[ShellType.SHELL_TYPE_AP]: "ap",
|
||||||
[ShellType.APCR]: "ap_cr",
|
[ShellType.SHELL_TYPE_APCR]: "ap_cr",
|
||||||
[ShellType.HE]: "he",
|
[ShellType.SHELL_TYPE_HE]: "he",
|
||||||
[ShellType.HEAT]: "hc",
|
[ShellType.SHELL_TYPE_HEAT]: "hc",
|
||||||
};
|
};
|
||||||
|
|
||||||
const GUN_TYPE_ICONS: Record<
|
const GUN_TYPE_ICONS: Record<
|
||||||
@@ -165,9 +165,9 @@ const GUN_TYPE_ICONS: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TANK_TYPE_COLORS: Record<TankType, string> = {
|
const TANK_TYPE_COLORS: Record<TankType, string> = {
|
||||||
[TankType.RESEARCHABLE]: "gray",
|
[TankType.TANK_TYPE_RESEARCHABLE]: "gray",
|
||||||
[TankType.PREMIUM]: "amber",
|
[TankType.TANK_TYPE_PREMIUM]: "amber",
|
||||||
[TankType.COLLECTOR]: "blue",
|
[TankType.TANK_TYPE_COLLECTOR]: "blue",
|
||||||
};
|
};
|
||||||
|
|
||||||
const GUN_TYPES = Object.keys(
|
const GUN_TYPES = Object.keys(
|
||||||
@@ -566,12 +566,12 @@ function TypeFilter() {
|
|||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
<Button color="gray" variant="surface">
|
<Button color="gray" variant="surface">
|
||||||
<Flex gap="1">
|
<Flex gap="1">
|
||||||
{types.map((tankType, index) => {
|
{types.map((tankType) => {
|
||||||
return (
|
return (
|
||||||
<ResearchedIcon
|
<ResearchedIcon
|
||||||
style={{
|
style={{
|
||||||
color: `var(--${TANK_TYPE_COLORS[tankType]}-${
|
color: `var(--${TANK_TYPE_COLORS[tankType]}-${
|
||||||
tankType === TankType.RESEARCHABLE ? "12" : "11"
|
tankType === TankType.TANK_TYPE_RESEARCHABLE ? "12" : "11"
|
||||||
})`,
|
})`,
|
||||||
filter: "drop-shadow(0 0 var(--space-1) var(--black-a11))",
|
filter: "drop-shadow(0 0 var(--space-1) var(--black-a11))",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
@@ -609,7 +609,7 @@ function TypeFilter() {
|
|||||||
<ResearchedIcon
|
<ResearchedIcon
|
||||||
style={{
|
style={{
|
||||||
color: `var(--${TANK_TYPE_COLORS[tankType]}-${
|
color: `var(--${TANK_TYPE_COLORS[tankType]}-${
|
||||||
tankType === TankType.RESEARCHABLE ? "12" : "11"
|
tankType === TankType.TANK_TYPE_RESEARCHABLE ? "12" : "11"
|
||||||
})`,
|
})`,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
width: "1.25em",
|
width: "1.25em",
|
||||||
@@ -941,7 +941,7 @@ function ConsumablesFilter() {
|
|||||||
src={asset(`icons/consumables/${consumable}.webp`)}
|
src={asset(`icons/consumables/${consumable}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(consumableDefinition.name)}
|
{unwrap(consumableDefinition.name!)}
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -1035,7 +1035,7 @@ function ProvisionsFilter() {
|
|||||||
src={asset(`icons/provisions/${provision}.webp`)}
|
src={asset(`icons/provisions/${provision}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(provisionDefinition.name)}
|
{unwrap(provisionDefinition.name!)}
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -1125,9 +1125,9 @@ function GameModeAbilitiesFilter() {
|
|||||||
<DropdownMenu.Label>
|
<DropdownMenu.Label>
|
||||||
{hasBoth
|
{hasBoth
|
||||||
? literals(strings.website.common.tank_search.active, {
|
? literals(strings.website.common.tank_search.active, {
|
||||||
name: unwrap(gameMode.name),
|
name: unwrap(gameMode.name!),
|
||||||
})
|
})
|
||||||
: unwrap(gameMode.name)}
|
: unwrap(gameMode.name!)}
|
||||||
</DropdownMenu.Label>
|
</DropdownMenu.Label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1164,7 +1164,7 @@ function GameModeAbilitiesFilter() {
|
|||||||
src={asset(`icons/consumables/${consumable}.webp`)}
|
src={asset(`icons/consumables/${consumable}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(abilityDefinition.name)}
|
{unwrap(abilityDefinition.name!)}
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1174,9 +1174,9 @@ function GameModeAbilitiesFilter() {
|
|||||||
<DropdownMenu.Label>
|
<DropdownMenu.Label>
|
||||||
{hasBoth
|
{hasBoth
|
||||||
? literals(strings.website.common.tank_search.passive, {
|
? literals(strings.website.common.tank_search.passive, {
|
||||||
name: unwrap(gameMode.name),
|
name: unwrap(gameMode.name!),
|
||||||
})
|
})
|
||||||
: unwrap(gameMode.name)}
|
: unwrap(gameMode.name!)}
|
||||||
</DropdownMenu.Label>
|
</DropdownMenu.Label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1213,7 +1213,7 @@ function GameModeAbilitiesFilter() {
|
|||||||
src={asset(`icons/provisions/${provision}.webp`)}
|
src={asset(`icons/provisions/${provision}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unwrap(powerDefinition.name)}
|
{unwrap(powerDefinition.name!)}
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function QuickLink({ topResult }: QuickLinkProps) {
|
|||||||
<TextField.Slot>
|
<TextField.Slot>
|
||||||
<LinkI18n locale={locale} href={`/tanks/${topResult.slug}`}>
|
<LinkI18n locale={locale} href={`/tanks/${topResult.slug}`}>
|
||||||
<Button variant="ghost">
|
<Button variant="ghost">
|
||||||
{unwrap(topResult.name)} <CaretRightIcon />
|
{unwrap(topResult.name!)} <CaretRightIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
return filterTanks(
|
return filterTanks(
|
||||||
tankFilters,
|
tankFilters,
|
||||||
awaitedTanksDefinitionsArray,
|
awaitedTanksDefinitionsArray,
|
||||||
wargaming?.id
|
wargaming?.id,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
@@ -75,12 +75,12 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
.go(tankFilters.search, tankNames, {
|
.go(tankFilters.search, tankNames, {
|
||||||
keys: SEARCH_KEYS,
|
keys: SEARCH_KEYS,
|
||||||
})
|
})
|
||||||
.map((result) => tankDefinitions.tanks[result.obj.id])
|
.map((result) => tankDefinitions.tanks[result.obj.id]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// react-promise-suspense has awful type annotations
|
// react-promise-suspense has awful type annotations
|
||||||
[tankFilters] as any
|
[tankFilters] as any,
|
||||||
);
|
);
|
||||||
const sorted = useMemo(() => {
|
const sorted = useMemo(() => {
|
||||||
if (tankFilters.search === null) {
|
if (tankFilters.search === null) {
|
||||||
@@ -104,19 +104,19 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
case "survivability.viewRange":
|
case "survivability.viewRange":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range
|
a.turrets.at(-1)!.view_range - b.turrets.at(-1)!.view_range,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "survivability.camouflageStill":
|
case "survivability.camouflageStill":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) => a.camouflage_still - b.camouflage_still
|
(a, b) => a.camouflage_still - b.camouflage_still,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "survivability.camouflageMoving":
|
case "survivability.camouflageMoving":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) => a.camouflage_moving - b.camouflage_moving
|
(a, b) => a.camouflage_moving - b.camouflage_moving,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
a.camouflage_still *
|
a.camouflage_still *
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.camouflage_loss -
|
a.turrets.at(-1)!.guns.at(-1)!.camouflage_loss -
|
||||||
b.camouflage_still *
|
b.camouflage_still *
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss
|
b.turrets.at(-1)!.guns.at(-1)!.camouflage_loss,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -140,15 +140,15 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||||
const aSize = normalizeBoundingBox(
|
const aSize = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
aTankModelDefinition.bounding_box,
|
aTankModelDefinition.bounding_box!,
|
||||||
aTurretModelDefinition.bounding_box
|
aTurretModelDefinition.bounding_box!,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const bSize = normalizeBoundingBox(
|
const bSize = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
bTankModelDefinition.bounding_box,
|
bTankModelDefinition.bounding_box!,
|
||||||
bTurretModelDefinition.bounding_box
|
bTurretModelDefinition.bounding_box!,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const aVolume = aSize.x * aSize.y * aSize.z;
|
const aVolume = aSize.x * aSize.y * aSize.z;
|
||||||
const bVolume = bSize.x * bSize.y * bSize.z;
|
const bVolume = bSize.x * bSize.y * bSize.z;
|
||||||
@@ -167,15 +167,15 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
bTankModelDefinition.turrets[b.turrets.at(-1)!.id];
|
||||||
const aSize = normalizeBoundingBox(
|
const aSize = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
aTankModelDefinition.bounding_box,
|
aTankModelDefinition.bounding_box!,
|
||||||
aTurretModelDefinition.bounding_box
|
aTurretModelDefinition.bounding_box!,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const bSize = normalizeBoundingBox(
|
const bSize = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
bTankModelDefinition.bounding_box,
|
bTankModelDefinition.bounding_box!,
|
||||||
bTurretModelDefinition.bounding_box
|
bTurretModelDefinition.bounding_box!,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const aLength = Math.max(aSize.x, aSize.y, aSize.z);
|
const aLength = Math.max(aSize.x, aSize.y, aSize.z);
|
||||||
const bLength = Math.max(bSize.x, bSize.y, bSize.z);
|
const bLength = Math.max(bSize.x, bSize.y, bSize.z);
|
||||||
@@ -189,12 +189,12 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
(a, b) =>
|
(a, b) =>
|
||||||
resolveDpm(
|
resolveDpm(
|
||||||
a.turrets.at(-1)!.guns.at(-1)!,
|
a.turrets.at(-1)!.guns.at(-1)!,
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0]
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||||
) -
|
) -
|
||||||
resolveDpm(
|
resolveDpm(
|
||||||
b.turrets.at(-1)!.guns.at(-1)!,
|
b.turrets.at(-1)!.guns.at(-1)!,
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0]
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0],
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -204,15 +204,15 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
||||||
? resolveDpm(
|
? resolveDpm(
|
||||||
a.turrets.at(-1)!.guns.at(-1)!,
|
a.turrets.at(-1)!.guns.at(-1)!,
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
a.turrets.at(-1)!.guns.at(-1)!.shells[1],
|
||||||
)
|
)
|
||||||
: 0) -
|
: 0) -
|
||||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
||||||
? resolveDpm(
|
? resolveDpm(
|
||||||
b.turrets.at(-1)!.guns.at(-1)!,
|
b.turrets.at(-1)!.guns.at(-1)!,
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[1]
|
b.turrets.at(-1)!.guns.at(-1)!.shells[1],
|
||||||
)
|
)
|
||||||
: 0)
|
: 0),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
resolveReload(a.turrets.at(-1)!.guns.at(-1)!) -
|
resolveReload(a.turrets.at(-1)!.guns.at(-1)!) -
|
||||||
resolveReload(b.turrets.at(-1)!.guns.at(-1)!)
|
resolveReload(b.turrets.at(-1)!.guns.at(-1)!),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -228,35 +228,35 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber -
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0].caliber,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "fire.standardPenetration":
|
case "fire.standardPenetration":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near -
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "fire.premiumPenetration":
|
case "fire.premiumPenetration":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ??
|
(a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration!.near ??
|
||||||
0) -
|
0) -
|
||||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration.near ??
|
(b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration!.near ??
|
||||||
0)
|
0),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "fire.tertiaryPenetration":
|
case "fire.tertiaryPenetration":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
(a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ??
|
(a.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration!.near ??
|
||||||
0) -
|
0) -
|
||||||
(b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration.near ??
|
(b.turrets.at(-1)!.guns.at(-1)!.shells[2]?.penetration!.near ??
|
||||||
0)
|
0),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage -
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0].armor_damage,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage -
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0].module_damage,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity -
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0].velocity,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shells[0].range -
|
a.turrets.at(-1)!.guns.at(-1)!.shells[0].range -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shells[0].range
|
b.turrets.at(-1)!.guns.at(-1)!.shells[0].range,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.shell_capacity -
|
a.turrets.at(-1)!.guns.at(-1)!.shell_capacity -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.shell_capacity
|
b.turrets.at(-1)!.guns.at(-1)!.shell_capacity,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.aim_time -
|
a.turrets.at(-1)!.guns.at(-1)!.aim_time -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.aim_time
|
b.turrets.at(-1)!.guns.at(-1)!.aim_time,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.turrets.at(-1)!.guns.at(-1)!.dispersion_base -
|
a.turrets.at(-1)!.guns.at(-1)!.dispersion_base -
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.dispersion_base
|
b.turrets.at(-1)!.guns.at(-1)!.dispersion_base,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.tracks.at(-1)!.dispersion_move -
|
a.tracks.at(-1)!.dispersion_move -
|
||||||
b.tracks.at(-1)!.dispersion_move
|
b.tracks.at(-1)!.dispersion_move,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -328,13 +328,13 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
|
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
|
||||||
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.max +
|
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch!.max +
|
||||||
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
|
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
|
||||||
0) -
|
0) -
|
||||||
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
|
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
|
||||||
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.max -
|
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch!.max -
|
||||||
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
|
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
|
||||||
0)
|
0),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -342,31 +342,31 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
|
modelDefinitions.models[b.id].turrets[b.turrets.at(-1)!.id]
|
||||||
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch.min +
|
.guns[b.turrets.at(-1)!.guns.at(-1)!.id].pitch!.min +
|
||||||
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
|
(modelDefinitions.models[b.id].initial_turret_rotation?.pitch ??
|
||||||
0) -
|
0) -
|
||||||
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
|
modelDefinitions.models[a.id].turrets[a.turrets.at(-1)!.id]
|
||||||
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch.min -
|
.guns[a.turrets.at(-1)!.guns.at(-1)!.id].pitch!.min -
|
||||||
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
|
(modelDefinitions.models[a.id].initial_turret_rotation?.pitch ??
|
||||||
0)
|
0),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "maneuverability.forwardsSpeed":
|
case "maneuverability.forwardsSpeed":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) => a.speed_forwards - b.speed_forwards
|
(a, b) => a.speed_forwards - b.speed_forwards,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "maneuverability.backwardsSpeed":
|
case "maneuverability.backwardsSpeed":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) => a.speed_backwards - b.speed_backwards
|
(a, b) => a.speed_backwards - b.speed_backwards,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "maneuverability.power":
|
case "maneuverability.power":
|
||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power
|
(a, b) => a.engines.at(-1)!.power - b.engines.at(-1)!.power,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -384,7 +384,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
b.engines.at(-1)!.weight +
|
b.engines.at(-1)!.weight +
|
||||||
b.tracks.at(-1)!.weight +
|
b.tracks.at(-1)!.weight +
|
||||||
b.turrets.at(-1)!.weight +
|
b.turrets.at(-1)!.weight +
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.weight)
|
b.turrets.at(-1)!.guns.at(-1)!.weight),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -400,7 +400,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
b.engines.at(-1)!.weight +
|
b.engines.at(-1)!.weight +
|
||||||
b.tracks.at(-1)!.weight +
|
b.tracks.at(-1)!.weight +
|
||||||
b.turrets.at(-1)!.weight +
|
b.turrets.at(-1)!.weight +
|
||||||
b.turrets.at(-1)!.guns.at(-1)!.weight)
|
b.turrets.at(-1)!.guns.at(-1)!.weight),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
sorted = filtered.sort(
|
sorted = filtered.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a.tracks.at(-1)!.traverse_speed -
|
a.tracks.at(-1)!.traverse_speed -
|
||||||
b.tracks.at(-1)!.traverse_speed
|
b.tracks.at(-1)!.traverse_speed,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -520,12 +520,12 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
key={index}
|
key={index}
|
||||||
onIntersection={() => {
|
onIntersection={() => {
|
||||||
setLoadedCards((state) =>
|
setLoadedCards((state) =>
|
||||||
Math.min(state + 2, sorted.length)
|
Math.min(state + 2, sorted.length),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
)}
|
)}
|
||||||
</TankCardWrapper>
|
</TankCardWrapper>
|
||||||
)}
|
)}
|
||||||
@@ -538,15 +538,15 @@ export const TankSearch = memo<TankSearchProps>(
|
|||||||
<TankCardWrapper>
|
<TankCardWrapper>
|
||||||
{times(
|
{times(
|
||||||
Math.round(
|
Math.round(
|
||||||
skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random()
|
skeleton ? DEFAULT_LOADED_CARDS : 10 + 10 * Math.random(),
|
||||||
),
|
),
|
||||||
(index) => (
|
(index) => (
|
||||||
<SkeletonTankCard key={index} />
|
<SkeletonTankCard key={index} />
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
</TankCardWrapper>
|
</TankCardWrapper>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
+3
-3
@@ -24,7 +24,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
|||||||
provisionDefinitions.provisions[provision].crew
|
provisionDefinitions.provisions[provision].crew
|
||||||
? total + provisionDefinitions.provisions[provision].crew! / 100
|
? total + provisionDefinitions.provisions[provision].crew! / 100
|
||||||
: total,
|
: total,
|
||||||
0
|
0,
|
||||||
) + (hasImprovedVentilation ? 0.08 : 0);
|
) + (hasImprovedVentilation ? 0.08 : 0);
|
||||||
const commanderMastery = 1 + provisionCrewBonus;
|
const commanderMastery = 1 + provisionCrewBonus;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
|||||||
decimals={0}
|
decimals={0}
|
||||||
noRanking
|
noRanking
|
||||||
value={() =>
|
value={() =>
|
||||||
(member.type === CrewType.COMMANDER
|
(member.type === CrewType.CREW_TYPE_COMMANDER
|
||||||
? commanderMastery
|
? commanderMastery
|
||||||
: commanderMastery * 1.1) * 100
|
: commanderMastery * 1.1) * 100
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ export function Crew({ stats }: StatsAcceptorProps) {
|
|||||||
? strings.website.tools.tankopedia.crew[
|
? strings.website.tools.tankopedia.crew[
|
||||||
CREW_MEMBER_NAMES[sub]
|
CREW_MEMBER_NAMES[sub]
|
||||||
]
|
]
|
||||||
: sub
|
: sub,
|
||||||
)
|
)
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
+6
-6
@@ -38,7 +38,7 @@ export function Firepower({
|
|||||||
const selected = thisShell.id === shell.id;
|
const selected = thisShell.id === shell.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
<Tooltip content={unwrap(thisShell.name!)} key={thisShell.id}>
|
||||||
<IconButton
|
<IconButton
|
||||||
color={selected ? undefined : "gray"}
|
color={selected ? undefined : "gray"}
|
||||||
variant={selected ? "solid" : "soft"}
|
variant={selected ? "solid" : "soft"}
|
||||||
@@ -59,7 +59,7 @@ export function Firepower({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name)}
|
alt={unwrap(thisShell.name!)}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
@@ -119,7 +119,7 @@ export function Firepower({
|
|||||||
name={literals(
|
name={literals(
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
strings.website.tools.tankopedia.characteristics.values
|
||||||
.shell_index,
|
.shell_index,
|
||||||
{ index: index + 1 }
|
{ index: index + 1 },
|
||||||
)}
|
)}
|
||||||
decimals={2}
|
decimals={2}
|
||||||
deltaType="lowerIsBetter"
|
deltaType="lowerIsBetter"
|
||||||
@@ -191,7 +191,7 @@ export function Firepower({
|
|||||||
name={literals(
|
name={literals(
|
||||||
strings.website.tools.tankopedia.characteristics.values
|
strings.website.tools.tankopedia.characteristics.values
|
||||||
.dispersion_at_distance,
|
.dispersion_at_distance,
|
||||||
{ distance: 100 }
|
{ distance: 100 },
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
@@ -249,7 +249,7 @@ export function Firepower({
|
|||||||
/>
|
/>
|
||||||
<InfoWithDelta value="gunDepression" stats={stats} decimals={1} indent />
|
<InfoWithDelta value="gunDepression" stats={stats} decimals={1} indent />
|
||||||
<InfoWithDelta stats={stats} decimals={1} indent value="gunElevation" />
|
<InfoWithDelta stats={stats} decimals={1} indent value="gunElevation" />
|
||||||
{gunModelDefinition.pitch.front && (
|
{gunModelDefinition.pitch!.front && (
|
||||||
<>
|
<>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
stats={stats}
|
stats={stats}
|
||||||
@@ -265,7 +265,7 @@ export function Firepower({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{gunModelDefinition.pitch.back && (
|
{gunModelDefinition.pitch!.back && (
|
||||||
<>
|
<>
|
||||||
<InfoWithDelta
|
<InfoWithDelta
|
||||||
stats={stats}
|
stats={stats}
|
||||||
|
|||||||
+9
-9
@@ -1,13 +1,13 @@
|
|||||||
import type { QuicklimeEvent } from 'quicklime';
|
import type { QuicklimeEvent } from "quicklime";
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from "react";
|
||||||
import type { Group } from 'three';
|
import type { Group } from "three";
|
||||||
import { awaitableModelDefinitions } from '../../../../../../../../core/awaitables/modelDefinitions';
|
import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions";
|
||||||
import {
|
import {
|
||||||
modelTransformEvent,
|
modelTransformEvent,
|
||||||
type ModelTransformEventData,
|
type ModelTransformEventData,
|
||||||
} from '../../../../../../../../core/blitzkit/modelTransform';
|
} from "../../../../../../../../core/blitzkit/modelTransform";
|
||||||
import { Duel } from '../../../../../../../../stores/duel';
|
import { Duel } from "../../../../../../../../stores/duel";
|
||||||
import { ModelChunk } from './ModelChunk';
|
import { ModelChunk } from "./ModelChunk";
|
||||||
|
|
||||||
const modelDefinitions = await awaitableModelDefinitions;
|
const modelDefinitions = await awaitableModelDefinitions;
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ export function CanvasContent() {
|
|||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<group ref={hullWrapper} position={[0, 0, turretModel.gun_origin.z]}>
|
<group ref={hullWrapper} position={[0, 0, turretModel.gun_origin!.z]}>
|
||||||
<group position={[0, 0, -turretModel.gun_origin.z]}>
|
<group position={[0, 0, -turretModel.gun_origin!.z]}>
|
||||||
<ModelChunk only="body" />
|
<ModelChunk only="body" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
+20
-20
@@ -1,13 +1,13 @@
|
|||||||
import { mauveDark } from '@radix-ui/colors';
|
import { mauveDark } from "@radix-ui/colors";
|
||||||
import { LineBasicMaterial, MeshBasicMaterial } from 'three';
|
import { LineBasicMaterial, MeshBasicMaterial } from "three";
|
||||||
import { awaitableModelDefinitions } from '../../../../../../../../core/awaitables/modelDefinitions';
|
import { awaitableModelDefinitions } from "../../../../../../../../core/awaitables/modelDefinitions";
|
||||||
import { jsxTree } from '../../../../../../../../core/blitzkit/jsxTree';
|
import { jsxTree } from "../../../../../../../../core/blitzkit/jsxTree";
|
||||||
import { useModel } from '../../../../../../../../hooks/useModel';
|
import { useModel } from "../../../../../../../../hooks/useModel";
|
||||||
import { Duel } from '../../../../../../../../stores/duel';
|
import { Duel } from "../../../../../../../../stores/duel";
|
||||||
|
|
||||||
const modelDefinitions = await awaitableModelDefinitions;
|
const modelDefinitions = await awaitableModelDefinitions;
|
||||||
|
|
||||||
const surfaceMaterial: Record<Props['only'], MeshBasicMaterial> = {
|
const surfaceMaterial: Record<Props["only"], MeshBasicMaterial> = {
|
||||||
gun: new MeshBasicMaterial({
|
gun: new MeshBasicMaterial({
|
||||||
color: mauveDark.mauve8,
|
color: mauveDark.mauve8,
|
||||||
toneMapped: false,
|
toneMapped: false,
|
||||||
@@ -23,7 +23,7 @@ const surfaceMaterial: Record<Props['only'], MeshBasicMaterial> = {
|
|||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
const outlineMaterial: Record<Props['only'], LineBasicMaterial> = {
|
const outlineMaterial: Record<Props["only"], LineBasicMaterial> = {
|
||||||
gun: new LineBasicMaterial({
|
gun: new LineBasicMaterial({
|
||||||
color: mauveDark.mauve11,
|
color: mauveDark.mauve11,
|
||||||
toneMapped: false,
|
toneMapped: false,
|
||||||
@@ -41,7 +41,7 @@ const outlineMaterial: Record<Props['only'], LineBasicMaterial> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
only: 'gun' | 'turret' | 'body';
|
only: "gun" | "turret" | "body";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ModelChunk({ only }: Props) {
|
export function ModelChunk({ only }: Props) {
|
||||||
@@ -53,8 +53,8 @@ export function ModelChunk({ only }: Props) {
|
|||||||
const turretModel = tankModel.turrets[turret.id];
|
const turretModel = tankModel.turrets[turret.id];
|
||||||
const gunModel = turretModel.guns[gun.id];
|
const gunModel = turretModel.guns[gun.id];
|
||||||
|
|
||||||
const originZ = tankModel.turret_origin.z + turretModel.gun_origin.z;
|
const originZ = tankModel.turret_origin!.z + turretModel.gun_origin!.z;
|
||||||
const originY = tankModel.turret_origin.y + turretModel.gun_origin.y;
|
const originY = tankModel.turret_origin!.y + turretModel.gun_origin!.y;
|
||||||
|
|
||||||
const { gltf } = useModel(tank.id);
|
const { gltf } = useModel(tank.id);
|
||||||
const nodes = Object.values(gltf.nodes);
|
const nodes = Object.values(gltf.nodes);
|
||||||
@@ -64,25 +64,25 @@ export function ModelChunk({ only }: Props) {
|
|||||||
{nodes.map((node) => {
|
{nodes.map((node) => {
|
||||||
const isCurrentMantlet =
|
const isCurrentMantlet =
|
||||||
node.name ===
|
node.name ===
|
||||||
`gun_${gunModel.model_id.toString().padStart(2, '0')}_mask`;
|
`gun_${gunModel.model_id.toString().padStart(2, "0")}_mask`;
|
||||||
const isCurrentGun =
|
const isCurrentGun =
|
||||||
node.name === `gun_${gunModel.model_id.toString().padStart(2, '0')}`;
|
node.name === `gun_${gunModel.model_id.toString().padStart(2, "0")}`;
|
||||||
const isGun = isCurrentGun || isCurrentMantlet;
|
const isGun = isCurrentGun || isCurrentMantlet;
|
||||||
|
|
||||||
const isCurrentTurret =
|
const isCurrentTurret =
|
||||||
node.name ===
|
node.name ===
|
||||||
`turret_${turretModel.model_id.toString().padStart(2, '0')}`;
|
`turret_${turretModel.model_id.toString().padStart(2, "0")}`;
|
||||||
const isTurret = isCurrentTurret;
|
const isTurret = isCurrentTurret;
|
||||||
|
|
||||||
const isHull = node.name === 'hull';
|
const isHull = node.name === "hull";
|
||||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
const isWheel = node.name.startsWith("chassis_wheel_");
|
||||||
const isTrack = node.name.startsWith('chassis_track_');
|
const isTrack = node.name.startsWith("chassis_track_");
|
||||||
const isBody = isHull || isWheel || isTrack;
|
const isBody = isHull || isWheel || isTrack;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(only === 'gun' && !isGun) ||
|
(only === "gun" && !isGun) ||
|
||||||
(only === 'turret' && !isTurret) ||
|
(only === "turret" && !isTurret) ||
|
||||||
(only === 'body' && !isBody)
|
(only === "body" && !isBody)
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -51,21 +51,21 @@ export function GunFlexibilityVisualizer({
|
|||||||
const [yaw, setYaw] = useState(0);
|
const [yaw, setYaw] = useState(0);
|
||||||
const [pitch, setPitch] = useState(0);
|
const [pitch, setPitch] = useState(0);
|
||||||
const [minPitch, setMinPitch] = useState(
|
const [minPitch, setMinPitch] = useState(
|
||||||
-degToRad(gunModel.pitch.front?.max ?? gunModel.pitch.max)
|
-degToRad(gunModel.pitch!.front?.max ?? gunModel.pitch!.max),
|
||||||
);
|
);
|
||||||
const [maxPitch, setMaxPitch] = useState(
|
const [maxPitch, setMaxPitch] = useState(
|
||||||
-degToRad(gunModel.pitch.front?.min ?? gunModel.pitch.min)
|
-degToRad(gunModel.pitch!.front?.min ?? gunModel.pitch!.min),
|
||||||
);
|
);
|
||||||
|
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
const highlighter = useRef<HTMLDivElement>(null);
|
const highlighter = useRef<HTMLDivElement>(null);
|
||||||
const pointer = useRef<HTMLDivElement>(null);
|
const pointer = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const p = gunModel.pitch;
|
const p = gunModel.pitch!;
|
||||||
const y = turretModel.yaw;
|
const y = turretModel.yaw;
|
||||||
const f = p.front;
|
const f = p.front;
|
||||||
const b = p.back;
|
const b = p.back;
|
||||||
const t = gunModel.pitch.transition ?? DEFAULT_PITCH_TRANSITION;
|
const t = gunModel.pitch!.transition ?? DEFAULT_PITCH_TRANSITION;
|
||||||
|
|
||||||
let d = "";
|
let d = "";
|
||||||
|
|
||||||
@@ -223,12 +223,12 @@ export function GunFlexibilityVisualizer({
|
|||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
|
|
||||||
const min = applyPitchYawLimits(
|
const min = applyPitchYawLimits(
|
||||||
@@ -237,7 +237,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
p,
|
p,
|
||||||
y,
|
y,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
const max = applyPitchYawLimits(
|
const max = applyPitchYawLimits(
|
||||||
Math.PI,
|
Math.PI,
|
||||||
@@ -245,7 +245,7 @@ export function GunFlexibilityVisualizer({
|
|||||||
p,
|
p,
|
||||||
y,
|
y,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
|
|
||||||
yaw = min[1];
|
yaw = min[1];
|
||||||
|
|||||||
+2
-2
@@ -15,7 +15,7 @@ export function ShellSwitcher() {
|
|||||||
const selected = thisShell.id === shell.id;
|
const selected = thisShell.id === shell.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={unwrap(thisShell.name)} key={thisShell.id}>
|
<Tooltip content={unwrap(thisShell.name!)} key={thisShell.id}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="3"
|
size="3"
|
||||||
color={selected ? undefined : "gray"}
|
color={selected ? undefined : "gray"}
|
||||||
@@ -36,7 +36,7 @@ export function ShellSwitcher() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name)}
|
alt={unwrap(thisShell.name!)}
|
||||||
style={{ width: "1.125rem", height: "1.125rem" }}
|
style={{ width: "1.125rem", height: "1.125rem" }}
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+6
-6
@@ -26,13 +26,13 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||||
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
||||||
const assaultDistance = Duel.use(
|
const assaultDistance = Duel.use(
|
||||||
(state) => state.protagonist.assaultDistance
|
(state) => state.protagonist.assaultDistance,
|
||||||
);
|
);
|
||||||
const equipmentMatrix = Duel.use(
|
const equipmentMatrix = Duel.use(
|
||||||
(state) => state.protagonist.equipmentMatrix
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const { tank, turret, gun, engine, track, shell } = Duel.use(
|
const { tank, turret, gun, engine, track, shell } = Duel.use(
|
||||||
(state) => state.protagonist
|
(state) => state.protagonist,
|
||||||
);
|
);
|
||||||
const stockEngine = tank.engines[0];
|
const stockEngine = tank.engines[0];
|
||||||
const stockTrack = tank.tracks[0];
|
const stockTrack = tank.tracks[0];
|
||||||
@@ -67,7 +67,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
tankModelDefinition,
|
tankModelDefinition,
|
||||||
equipmentDefinitions: equipmentDefinitions,
|
equipmentDefinitions: equipmentDefinitions,
|
||||||
provisionDefinitions: provisionDefinitions,
|
provisionDefinitions: provisionDefinitions,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
@@ -77,10 +77,10 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
modelTransformEvent.last!.pitch,
|
modelTransformEvent.last!.pitch,
|
||||||
modelTransformEvent.last!.yaw,
|
modelTransformEvent.last!.yaw,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
|
|||||||
+46
-46
@@ -43,11 +43,11 @@ interface ModuleWithUnlocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const moduleTypeTypeIconName: Record<ModuleType, string> = {
|
const moduleTypeTypeIconName: Record<ModuleType, string> = {
|
||||||
[ModuleType.VEHICLE]: "",
|
[ModuleType.MODULE_TYPE_VEHICLE]: "",
|
||||||
[ModuleType.ENGINE]: "engine",
|
[ModuleType.MODULE_TYPE_ENGINE]: "engine",
|
||||||
[ModuleType.TRACKS]: "chassis",
|
[ModuleType.MODULE_TYPE_TRACKS]: "chassis",
|
||||||
[ModuleType.TURRET]: "turret",
|
[ModuleType.MODULE_TYPE_TURRET]: "turret",
|
||||||
[ModuleType.GUN]: "gun",
|
[ModuleType.MODULE_TYPE_GUN]: "gun",
|
||||||
};
|
};
|
||||||
|
|
||||||
function ModuleButton({
|
function ModuleButton({
|
||||||
@@ -63,7 +63,7 @@ function ModuleButton({
|
|||||||
top: boolean;
|
top: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}) {
|
}) {
|
||||||
const isTank = unlock.type === ModuleType.VEHICLE;
|
const isTank = unlock.type === ModuleType.MODULE_TYPE_VEHICLE;
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
@@ -91,7 +91,7 @@ function ModuleButton({
|
|||||||
{TIER_ROMAN_NUMERALS[tier]}
|
{TIER_ROMAN_NUMERALS[tier]}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{unlock.cost.value > 0 && (
|
{unlock.cost!.value > 0 && (
|
||||||
<Flex
|
<Flex
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -103,12 +103,12 @@ function ModuleButton({
|
|||||||
gap={isTank ? "1" : undefined}
|
gap={isTank ? "1" : undefined}
|
||||||
>
|
>
|
||||||
<Text size="1" color="gray">
|
<Text size="1" color="gray">
|
||||||
{formatCompact(locale, unlock.cost.value)}
|
{formatCompact(locale, unlock.cost!.value)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
alt={unlock.cost.type}
|
alt={unlock.cost!.type}
|
||||||
src={asset(`icons/currencies/${unlock.cost.type}.webp`)}
|
src={asset(`icons/currencies/${unlock.cost!.type}.webp`)}
|
||||||
style={{
|
style={{
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
@@ -171,16 +171,16 @@ export function Modules() {
|
|||||||
|
|
||||||
function setByUnlock(unlock: Unlock) {
|
function setByUnlock(unlock: Unlock) {
|
||||||
Duel.mutate((draft) => {
|
Duel.mutate((draft) => {
|
||||||
if (unlock.type === ModuleType.TURRET) {
|
if (unlock.type === ModuleType.MODULE_TYPE_TURRET) {
|
||||||
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
|
draft.protagonist.turret = draft.protagonist.tank.turrets.find(
|
||||||
(turret) => turret.id === unlock.id
|
(turret) => turret.id === unlock.id,
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
// Check if current gun exists in new turret
|
// Check if current gun exists in new turret
|
||||||
const gunInNewTurret = draft.protagonist.turret.guns.find(
|
const gunInNewTurret = draft.protagonist.turret.guns.find(
|
||||||
(gun) => gun.id === draft.protagonist.gun.id
|
(gun) => gun.id === draft.protagonist.gun.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (gunInNewTurret) {
|
if (gunInNewTurret) {
|
||||||
// Gun exists - get it FROM this turret
|
// Gun exists - get it FROM this turret
|
||||||
draft.protagonist.gun = gunInNewTurret;
|
draft.protagonist.gun = gunInNewTurret;
|
||||||
@@ -190,9 +190,9 @@ export function Modules() {
|
|||||||
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
draft.protagonist.gun = draft.protagonist.turret.guns.at(-1)!;
|
||||||
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
draft.protagonist.shell = draft.protagonist.gun.shells[0];
|
||||||
}
|
}
|
||||||
} else if (unlock.type === ModuleType.GUN) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_GUN) {
|
||||||
const gunInTurret = draft.protagonist.turret.guns.find(
|
const gunInTurret = draft.protagonist.turret.guns.find(
|
||||||
(gun) => gun.id === unlock.id
|
(gun) => gun.id === unlock.id,
|
||||||
);
|
);
|
||||||
if (gunInTurret) {
|
if (gunInTurret) {
|
||||||
draft.protagonist.gun = gunInTurret;
|
draft.protagonist.gun = gunInTurret;
|
||||||
@@ -200,23 +200,23 @@ export function Modules() {
|
|||||||
} else {
|
} else {
|
||||||
// TODO: warn somehow?
|
// TODO: warn somehow?
|
||||||
const suitableTurret = draft.protagonist.tank.turrets.find((turret) =>
|
const suitableTurret = draft.protagonist.tank.turrets.find((turret) =>
|
||||||
turret.guns.some((gun) => gun.id === unlock.id)
|
turret.guns.some((gun) => gun.id === unlock.id),
|
||||||
)!;
|
)!;
|
||||||
const gunInSuitableTurret = suitableTurret.guns.find(
|
const gunInSuitableTurret = suitableTurret.guns.find(
|
||||||
(gun) => gun.id === unlock.id
|
(gun) => gun.id === unlock.id,
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
draft.protagonist.turret = suitableTurret;
|
draft.protagonist.turret = suitableTurret;
|
||||||
draft.protagonist.gun = gunInSuitableTurret;
|
draft.protagonist.gun = gunInSuitableTurret;
|
||||||
draft.protagonist.shell = gunInSuitableTurret.shells[0];
|
draft.protagonist.shell = gunInSuitableTurret.shells[0];
|
||||||
}
|
}
|
||||||
} else if (unlock.type === ModuleType.ENGINE) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) {
|
||||||
draft.protagonist.engine = draft.protagonist.tank.engines.find(
|
draft.protagonist.engine = draft.protagonist.tank.engines.find(
|
||||||
(engine) => engine.id === unlock.id
|
(engine) => engine.id === unlock.id,
|
||||||
)!;
|
)!;
|
||||||
} else if (unlock.type === ModuleType.TRACKS) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) {
|
||||||
draft.protagonist.track = draft.protagonist.tank.tracks.find(
|
draft.protagonist.track = draft.protagonist.tank.tracks.find(
|
||||||
(track) => track.id === unlock.id
|
(track) => track.id === unlock.id,
|
||||||
)!;
|
)!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -231,19 +231,19 @@ export function Modules() {
|
|||||||
const central = !first && !last;
|
const central = !first && !last;
|
||||||
let module: ModuleDefinition | TankDefinition | undefined = undefined;
|
let module: ModuleDefinition | TankDefinition | undefined = undefined;
|
||||||
|
|
||||||
if (unlock.type === ModuleType.TRACKS) {
|
if (unlock.type === ModuleType.MODULE_TYPE_TRACKS) {
|
||||||
module = tank.tracks.find((track) => track.id === unlock.id)!;
|
module = tank.tracks.find((track) => track.id === unlock.id)!;
|
||||||
} else if (unlock.type === ModuleType.ENGINE) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_ENGINE) {
|
||||||
module = tank.engines.find((engine) => engine.id === unlock.id)!;
|
module = tank.engines.find((engine) => engine.id === unlock.id)!;
|
||||||
} else if (unlock.type === ModuleType.TURRET) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_TURRET) {
|
||||||
module = tank.turrets.find((turret) => turret.id === unlock.id)!;
|
module = tank.turrets.find((turret) => turret.id === unlock.id)!;
|
||||||
} else if (unlock.type === ModuleType.GUN) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_GUN) {
|
||||||
module = tank.turrets
|
module = tank.turrets
|
||||||
.find((turret) =>
|
.find((turret) =>
|
||||||
turret.guns.some((gun) => gun.id === unlock.id)
|
turret.guns.some((gun) => gun.id === unlock.id),
|
||||||
)!
|
)!
|
||||||
.guns.find((gun) => gun.id === unlock.id)!;
|
.guns.find((gun) => gun.id === unlock.id)!;
|
||||||
} else if (unlock.type === ModuleType.VEHICLE) {
|
} else if (unlock.type === ModuleType.MODULE_TYPE_VEHICLE) {
|
||||||
module = tankDefinitions.tanks[unlock.id];
|
module = tankDefinitions.tanks[unlock.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ export function Modules() {
|
|||||||
gap="2"
|
gap="2"
|
||||||
position="relative"
|
position="relative"
|
||||||
>
|
>
|
||||||
{unlock.cost.value > 0 && (
|
{unlock.cost!.value > 0 && (
|
||||||
<>
|
<>
|
||||||
{unlocks.length > 1 && (
|
{unlocks.length > 1 && (
|
||||||
<div
|
<div
|
||||||
@@ -302,24 +302,24 @@ export function Modules() {
|
|||||||
unlock={unlock}
|
unlock={unlock}
|
||||||
tier={extractModule(module).tier}
|
tier={extractModule(module).tier}
|
||||||
selected={
|
selected={
|
||||||
(unlock.type === ModuleType.TURRET
|
(unlock.type === ModuleType.MODULE_TYPE_TURRET
|
||||||
? turret.id
|
? turret.id
|
||||||
: unlock.type === ModuleType.GUN
|
: unlock.type === ModuleType.MODULE_TYPE_GUN
|
||||||
? gun.id
|
? gun.id
|
||||||
: unlock.type === ModuleType.ENGINE
|
: unlock.type === ModuleType.MODULE_TYPE_ENGINE
|
||||||
? engine.id
|
? engine.id
|
||||||
: unlock.type === ModuleType.TRACKS
|
: unlock.type === ModuleType.MODULE_TYPE_TRACKS
|
||||||
? track.id
|
? track.id
|
||||||
: -1) === unlock.id
|
: -1) === unlock.id
|
||||||
}
|
}
|
||||||
top={
|
top={
|
||||||
unlock.type === ModuleType.TURRET
|
unlock.type === ModuleType.MODULE_TYPE_TURRET
|
||||||
? extractModule(module).id == topTurret.id
|
? extractModule(module).id == topTurret.id
|
||||||
: unlock.type === ModuleType.GUN
|
: unlock.type === ModuleType.MODULE_TYPE_GUN
|
||||||
? extractModule(module).id === topGun.id
|
? extractModule(module).id === topGun.id
|
||||||
: unlock.type === ModuleType.ENGINE
|
: unlock.type === ModuleType.MODULE_TYPE_ENGINE
|
||||||
? extractModule(module).id === topEngine.id
|
? extractModule(module).id === topEngine.id
|
||||||
: unlock.type === ModuleType.TRACKS
|
: unlock.type === ModuleType.MODULE_TYPE_TRACKS
|
||||||
? extractModule(module).id === topTrack.id
|
? extractModule(module).id === topTrack.id
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@ export function Modules() {
|
|||||||
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
|
{(extractModule(module) as ModuleWithUnlocks).unlocks &&
|
||||||
tree(
|
tree(
|
||||||
type,
|
type,
|
||||||
(extractModule(module) as ModuleWithUnlocks).unlocks!
|
(extractModule(module) as ModuleWithUnlocks).unlocks!,
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
@@ -397,32 +397,32 @@ export function Modules() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex gap="2" wrap="wrap" gapY="6">
|
<Flex gap="2" wrap="wrap" gapY="6">
|
||||||
{tree(ModuleType.ENGINE, [
|
{tree(ModuleType.MODULE_TYPE_ENGINE, [
|
||||||
{
|
{
|
||||||
cost: { type: "xp", value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: engine0.id,
|
id: engine0.id,
|
||||||
type: ModuleType.ENGINE,
|
type: ModuleType.MODULE_TYPE_ENGINE,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
{tree(ModuleType.TRACKS, [
|
{tree(ModuleType.MODULE_TYPE_TRACKS, [
|
||||||
{
|
{
|
||||||
cost: { type: "xp", value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: track0.id,
|
id: track0.id,
|
||||||
type: ModuleType.TRACKS,
|
type: ModuleType.MODULE_TYPE_TRACKS,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
{tree(ModuleType.TURRET, [
|
{tree(ModuleType.MODULE_TYPE_TURRET, [
|
||||||
{
|
{
|
||||||
cost: { type: "xp", value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: turret0.id,
|
id: turret0.id,
|
||||||
type: ModuleType.TURRET,
|
type: ModuleType.MODULE_TYPE_TURRET,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
{tree(ModuleType.GUN, [
|
{tree(ModuleType.MODULE_TYPE_GUN, [
|
||||||
{
|
{
|
||||||
cost: { type: "xp", value: 0 },
|
cost: { type: "xp", value: 0 },
|
||||||
id: gun0.id,
|
id: gun0.id,
|
||||||
type: ModuleType.GUN,
|
type: ModuleType.MODULE_TYPE_GUN,
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function GameModeSection() {
|
|||||||
style={{
|
style={{
|
||||||
borderRadius: "var(--radius-3)",
|
borderRadius: "var(--radius-3)",
|
||||||
background: `url(${asset(
|
background: `url(${asset(
|
||||||
`icons/game_mode_banners/${id}.webp`
|
`icons/game_mode_banners/${id}.webp`,
|
||||||
)})`,
|
)})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
@@ -51,7 +51,7 @@ export function GameModeSection() {
|
|||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
style={{
|
style={{
|
||||||
background: `url(${asset(
|
background: `url(${asset(
|
||||||
`icons/game_mode_banners/${id}.webp`
|
`icons/game_mode_banners/${id}.webp`,
|
||||||
)})`,
|
)})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
@@ -71,7 +71,7 @@ export function GameModeSection() {
|
|||||||
justify="center"
|
justify="center"
|
||||||
>
|
>
|
||||||
<Text size="4" weight="bold">
|
<Text size="4" weight="bold">
|
||||||
{unwrap(gameMode.name)}
|
{unwrap(gameMode.name!)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex direction="column">
|
<Flex direction="column">
|
||||||
@@ -88,9 +88,9 @@ export function GameModeSection() {
|
|||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={asset(`icons/provisions/${id}.webp`)}
|
src={asset(`icons/provisions/${id}.webp`)}
|
||||||
alt={unwrap(provisions.name)}
|
alt={unwrap(provisions.name!)}
|
||||||
/>
|
/>
|
||||||
{unwrap(provisions.name)}
|
{unwrap(provisions.name!)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
@@ -109,9 +109,9 @@ export function GameModeSection() {
|
|||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
src={asset(`icons/consumables/${id}.webp`)}
|
src={asset(`icons/consumables/${id}.webp`)}
|
||||||
alt={unwrap(consumable.name)}
|
alt={unwrap(consumable.name!)}
|
||||||
/>
|
/>
|
||||||
{unwrap(consumable.name)}
|
{unwrap(consumable.name!)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|||||||
+15
-15
@@ -68,29 +68,29 @@ export function CustomShellButton() {
|
|||||||
const modified = { ...customShell, type };
|
const modified = { ...customShell, type };
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ShellType.AP: {
|
case ShellType.SHELL_TYPE_AP: {
|
||||||
modified.normalization = 5;
|
modified.normalization = 5;
|
||||||
modified.ricochet = 70;
|
modified.ricochet = 70;
|
||||||
modified.explosion_radius = undefined;
|
modified.explosion_radius = undefined;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ShellType.APCR: {
|
case ShellType.SHELL_TYPE_APCR: {
|
||||||
modified.normalization = 2;
|
modified.normalization = 2;
|
||||||
modified.ricochet = 70;
|
modified.ricochet = 70;
|
||||||
modified.explosion_radius = undefined;
|
modified.explosion_radius = undefined;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ShellType.HEAT: {
|
case ShellType.SHELL_TYPE_HEAT: {
|
||||||
modified.normalization = undefined;
|
modified.normalization = undefined;
|
||||||
modified.ricochet = undefined;
|
modified.ricochet = undefined;
|
||||||
modified.explosion_radius = undefined;
|
modified.explosion_radius = undefined;
|
||||||
}
|
}
|
||||||
case ShellType.HE: {
|
case ShellType.SHELL_TYPE_HE: {
|
||||||
modified.normalization = undefined;
|
modified.normalization = undefined;
|
||||||
modified.ricochet = undefined;
|
modified.ricochet = undefined;
|
||||||
modified.explosion_radius =
|
modified.explosion_radius =
|
||||||
gun.shells.find(
|
gun.shells.find(
|
||||||
(shell) => shell.type === ShellType.HE
|
(shell) => shell.type === ShellType.SHELL_TYPE_HE,
|
||||||
)?.explosion_radius ?? 1;
|
)?.explosion_radius ?? 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -104,16 +104,16 @@ export function CustomShellButton() {
|
|||||||
<Select.Trigger variant="classic" />
|
<Select.Trigger variant="classic" />
|
||||||
|
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
<Select.Item value={`${ShellType.AP}`}>
|
<Select.Item value={`${ShellType.SHELL_TYPE_AP}`}>
|
||||||
{strings.common.shells.ap}
|
{strings.common.shells.ap}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
<Select.Item value={`${ShellType.APCR}`}>
|
<Select.Item value={`${ShellType.SHELL_TYPE_APCR}`}>
|
||||||
{strings.common.shells.ap_cr}
|
{strings.common.shells.ap_cr}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
<Select.Item value={`${ShellType.HEAT}`}>
|
<Select.Item value={`${ShellType.SHELL_TYPE_HEAT}`}>
|
||||||
{strings.common.shells.hc}
|
{strings.common.shells.hc}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
<Select.Item value={`${ShellType.HE}`}>
|
<Select.Item value={`${ShellType.SHELL_TYPE_HE}`}>
|
||||||
{strings.common.shells.he}
|
{strings.common.shells.he}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
@@ -166,11 +166,11 @@ export function CustomShellButton() {
|
|||||||
<TextField.Root
|
<TextField.Root
|
||||||
variant="classic"
|
variant="classic"
|
||||||
style={{ maxWidth: "6rem" }}
|
style={{ maxWidth: "6rem" }}
|
||||||
defaultValue={customShell.penetration.near}
|
defaultValue={customShell.penetration!.near}
|
||||||
type="number"
|
type="number"
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
draft.customShell!.penetration.near =
|
draft.customShell!.penetration!.near =
|
||||||
event.target.valueAsNumber;
|
event.target.valueAsNumber;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -182,7 +182,7 @@ export function CustomShellButton() {
|
|||||||
(event.target as HTMLInputElement).blur();
|
(event.target as HTMLInputElement).blur();
|
||||||
} else if (event.key === "Escape") {
|
} else if (event.key === "Escape") {
|
||||||
(event.target as HTMLInputElement).valueAsNumber =
|
(event.target as HTMLInputElement).valueAsNumber =
|
||||||
customShell.penetration.near;
|
customShell.penetration!.near;
|
||||||
(event.target as HTMLInputElement).blur();
|
(event.target as HTMLInputElement).blur();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -193,8 +193,8 @@ export function CustomShellButton() {
|
|||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{(customShell.type === ShellType.AP ||
|
{(customShell.type === ShellType.SHELL_TYPE_AP ||
|
||||||
customShell.type === ShellType.APCR) && (
|
customShell.type === ShellType.SHELL_TYPE_APCR) && (
|
||||||
<>
|
<>
|
||||||
<Flex align="center" justify="between" gap="4" py="1">
|
<Flex align="center" justify="between" gap="4" py="1">
|
||||||
<Text>
|
<Text>
|
||||||
@@ -264,7 +264,7 @@ export function CustomShellButton() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{customShell.type === ShellType.HE && (
|
{customShell.type === ShellType.SHELL_TYPE_HE && (
|
||||||
<Flex align="center" justify="between" gap="4">
|
<Flex align="center" justify="between" gap="4">
|
||||||
<Text>Explosion radius</Text>
|
<Text>Explosion radius</Text>
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
|
|||||||
+7
-7
@@ -38,7 +38,7 @@ export function QuickInputs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleTransformEvent(
|
function handleTransformEvent(
|
||||||
event: QuicklimeEvent<ModelTransformEventData>
|
event: QuicklimeEvent<ModelTransformEventData>,
|
||||||
) {
|
) {
|
||||||
syncInputValues(event.data);
|
syncInputValues(event.data);
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ export function QuickInputs() {
|
|||||||
|
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
yawInput.current!.value = radToDeg(
|
yawInput.current!.value = radToDeg(
|
||||||
modelTransformEvent.last!.yaw
|
modelTransformEvent.last!.yaw,
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -85,15 +85,15 @@ export function QuickInputs() {
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
modelTransformEvent.last!.pitch,
|
modelTransformEvent.last!.pitch,
|
||||||
degToRad(value),
|
degToRad(value),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
yawInput.current!.value = radToDeg(
|
yawInput.current!.value = radToDeg(
|
||||||
modelTransformEvent.last!.yaw
|
modelTransformEvent.last!.yaw,
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
@@ -129,10 +129,10 @@ export function QuickInputs() {
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
degToRad(value + initialGunPitch),
|
degToRad(value + initialGunPitch),
|
||||||
modelTransformEvent.last!.yaw,
|
modelTransformEvent.last!.yaw,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
pitchInput.current!.value = (
|
pitchInput.current!.value = (
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ type OptionsProps = MaybeSkeletonComponentProps & {
|
|||||||
|
|
||||||
export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||||
const hasCustomShell = Tankopedia.use(
|
const hasCustomShell = Tankopedia.use(
|
||||||
(state) => state.customShell !== undefined
|
(state) => state.customShell !== undefined,
|
||||||
);
|
);
|
||||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const advancedHighlighting = TankopediaPersistent.use(
|
const advancedHighlighting = TankopediaPersistent.use(
|
||||||
(state) => state.advancedHighlighting
|
(state) => state.advancedHighlighting,
|
||||||
);
|
);
|
||||||
const fullScreenAvailable = useFullscreenAvailability(true);
|
const fullScreenAvailable = useFullscreenAvailability(true);
|
||||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||||
@@ -102,8 +102,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
value: (
|
value: (
|
||||||
resolvePenetrationCoefficient(
|
resolvePenetrationCoefficient(
|
||||||
hasCalibratedShells,
|
hasCalibratedShells,
|
||||||
antagonistShell.type
|
antagonistShell.type,
|
||||||
) * antagonistShell.penetration.near
|
) * antagonistShell.penetration!.near
|
||||||
).toFixed(0),
|
).toFixed(0),
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -203,7 +203,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(thisShell.name)}
|
alt={unwrap(thisShell.name!)}
|
||||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||||
style={{
|
style={{
|
||||||
width: "50%",
|
width: "50%",
|
||||||
@@ -456,7 +456,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
|||||||
<Dialog.Trigger>
|
<Dialog.Trigger>
|
||||||
<Button variant="solid" highContrast>
|
<Button variant="solid" highContrast>
|
||||||
<Crosshair1Icon />
|
<Crosshair1Icon />
|
||||||
{unwrap(antagonistTank.name)}
|
{unwrap(antagonistTank.name!)}
|
||||||
<SmallTankIcon id={antagonistTank.id} size={16} />
|
<SmallTankIcon id={antagonistTank.id} size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ export function Title() {
|
|||||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||||
const revealed = Tankopedia.use((state) => state.revealed);
|
const revealed = Tankopedia.use((state) => state.revealed);
|
||||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||||
const name = unwrap(protagonist.name);
|
const name = unwrap(protagonist.name!);
|
||||||
const fontSize = revealed
|
const fontSize = revealed
|
||||||
? disturbed
|
? disturbed
|
||||||
? "1.5rem"
|
? "1.5rem"
|
||||||
|
|||||||
+14
-14
@@ -53,24 +53,24 @@ export function Controls({
|
|||||||
const protagonistGunModelDefinition =
|
const protagonistGunModelDefinition =
|
||||||
protagonistTurretModelDefinition.guns[protagonistGun.id];
|
protagonistTurretModelDefinition.guns[protagonistGun.id];
|
||||||
const protagonistHullOrigin = new Vector3(
|
const protagonistHullOrigin = new Vector3(
|
||||||
protagonistTrackModelDefinition.origin.x,
|
protagonistTrackModelDefinition.origin!.x,
|
||||||
protagonistTrackModelDefinition.origin.y,
|
protagonistTrackModelDefinition.origin!.y,
|
||||||
-protagonistTrackModelDefinition.origin.z,
|
-protagonistTrackModelDefinition.origin!.z,
|
||||||
);
|
);
|
||||||
const protagonistTurretOrigin = new Vector3(
|
const protagonistTurretOrigin = new Vector3(
|
||||||
protagonistModelDefinition.turret_origin.x,
|
protagonistModelDefinition.turret_origin!.x,
|
||||||
protagonistModelDefinition.turret_origin.y,
|
protagonistModelDefinition.turret_origin!.y,
|
||||||
-protagonistModelDefinition.turret_origin.z,
|
-protagonistModelDefinition.turret_origin!.z,
|
||||||
);
|
);
|
||||||
const protagonistGunOrigin = new Vector3(
|
const protagonistGunOrigin = new Vector3(
|
||||||
protagonistTurretModelDefinition.gun_origin.x,
|
protagonistTurretModelDefinition.gun_origin!.x,
|
||||||
protagonistTurretModelDefinition.gun_origin.y,
|
protagonistTurretModelDefinition.gun_origin!.y,
|
||||||
-protagonistTurretModelDefinition.gun_origin.z,
|
-protagonistTurretModelDefinition.gun_origin!.z,
|
||||||
);
|
);
|
||||||
const antagonistGunHeight =
|
const antagonistGunHeight =
|
||||||
protagonistTrackModelDefinition.origin.y +
|
protagonistTrackModelDefinition.origin!.y +
|
||||||
antagonistModelDefinition.turret_origin.y +
|
antagonistModelDefinition.turret_origin!.y +
|
||||||
antagonistTurretModelDefinition.gun_origin.y;
|
antagonistTurretModelDefinition.gun_origin!.y;
|
||||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||||
const doAutoRotate = autoRotate && !disturbed;
|
const doAutoRotate = autoRotate && !disturbed;
|
||||||
const gunHeight =
|
const gunHeight =
|
||||||
@@ -119,7 +119,7 @@ export function Controls({
|
|||||||
const [pitch] = applyPitchYawLimits(
|
const [pitch] = applyPitchYawLimits(
|
||||||
-Infinity,
|
-Infinity,
|
||||||
0,
|
0,
|
||||||
protagonistGunModelDefinition.pitch,
|
protagonistGunModelDefinition.pitch!,
|
||||||
protagonistTurretModelDefinition.yaw,
|
protagonistTurretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer,
|
hasDownImprovedVerticalStabilizer,
|
||||||
@@ -152,7 +152,7 @@ export function Controls({
|
|||||||
const [pitch] = applyPitchYawLimits(
|
const [pitch] = applyPitchYawLimits(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
protagonistGunModelDefinition.pitch,
|
protagonistGunModelDefinition.pitch!,
|
||||||
protagonistTurretModelDefinition.yaw,
|
protagonistTurretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer,
|
hasDownImprovedVerticalStabilizer,
|
||||||
|
|||||||
+7
-7
@@ -1,9 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from "react";
|
||||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
import { degToRad } from "three/src/math/MathUtils.js";
|
||||||
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
|
import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions";
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
|
|
||||||
const modelDefinitions = await awaitableModelDefinitions;
|
const modelDefinitions = await awaitableModelDefinitions;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export function InitialAligner() {
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
degToRad(8),
|
degToRad(8),
|
||||||
degToRad(25),
|
degToRad(25),
|
||||||
gunModel.pitch,
|
gunModel.pitch!,
|
||||||
turretModel.yaw,
|
turretModel.yaw,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -134,12 +134,12 @@ export function TankModel() {
|
|||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
@@ -150,10 +150,10 @@ export function TankModel() {
|
|||||||
modelTransformEvent.last!.pitch,
|
modelTransformEvent.last!.pitch,
|
||||||
modelTransformEvent.last!.yaw +
|
modelTransformEvent.last!.yaw +
|
||||||
delta.x * (Math.PI / boundingRect.width),
|
delta.x * (Math.PI / boundingRect.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
}
|
}
|
||||||
@@ -217,12 +217,12 @@ export function TankModel() {
|
|||||||
const hasImprovedVerticalStabilizer = hasEquipment(
|
const hasImprovedVerticalStabilizer = hasEquipment(
|
||||||
122,
|
122,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
const hasDownImprovedVerticalStabilizer = hasEquipment(
|
||||||
124,
|
124,
|
||||||
Duel.state.protagonist.tank.equipment_preset,
|
Duel.state.protagonist.tank.equipment_preset,
|
||||||
Duel.state.protagonist.equipmentMatrix
|
Duel.state.protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
const boundingRect = canvas.getBoundingClientRect();
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
delta.set(event.clientX, event.clientY).sub(position);
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
@@ -231,10 +231,10 @@ export function TankModel() {
|
|||||||
[pitch, yaw] = applyPitchYawLimits(
|
[pitch, yaw] = applyPitchYawLimits(
|
||||||
pitch - delta.y * (Math.PI / boundingRect.height),
|
pitch - delta.y * (Math.PI / boundingRect.height),
|
||||||
yaw + delta.x * (Math.PI / boundingRect.width),
|
yaw + delta.x * (Math.PI / boundingRect.width),
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer
|
hasDownImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -37,9 +37,9 @@ export function TransitionSkeleton() {
|
|||||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||||
const wrapper = useRef<Group>(null);
|
const wrapper = useRef<Group>(null);
|
||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||||
|
|
||||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export function TransitionSkeleton() {
|
|||||||
<group ref={turretContainer}>
|
<group ref={turretContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentTurret = node.name.startsWith(
|
const isCurrentTurret = node.name.startsWith(
|
||||||
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`
|
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentTurret;
|
const isVisible = isCurrentTurret;
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
@@ -102,7 +102,7 @@ export function TransitionSkeleton() {
|
|||||||
<group ref={gunContainer}>
|
<group ref={gunContainer}>
|
||||||
{armorNodes.map((node) => {
|
{armorNodes.map((node) => {
|
||||||
const isCurrentGun = node.name.startsWith(
|
const isCurrentGun = node.name.startsWith(
|
||||||
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`
|
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`,
|
||||||
);
|
);
|
||||||
const isVisible = isCurrentGun;
|
const isVisible = isCurrentGun;
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -73,7 +73,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
-Infinity,
|
-Infinity,
|
||||||
0,
|
0,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
hasDownImprovedVerticalStabilizer,
|
hasDownImprovedVerticalStabilizer,
|
||||||
@@ -88,7 +88,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
Infinity,
|
Infinity,
|
||||||
0,
|
0,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
@@ -102,7 +102,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
@@ -124,7 +124,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const [pitch, yaw] = applyPitchYawLimits(
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
modelTransformEvent.last!.pitch,
|
modelTransformEvent.last!.pitch,
|
||||||
modelTransformEvent.last!.yaw,
|
modelTransformEvent.last!.yaw,
|
||||||
gunModelDefinition.pitch,
|
gunModelDefinition.pitch!,
|
||||||
turretModelDefinition.yaw,
|
turretModelDefinition.yaw,
|
||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const thicknessRange = useMemo(() => {
|
const thicknessRange = useMemo(() => {
|
||||||
const entries = Object.values(tankDefinitions.tanks);
|
const entries = Object.values(tankDefinitions.tanks);
|
||||||
const filtered = entries.filter(
|
const filtered = entries.filter(
|
||||||
(thisTank) => thisTank.tier === protagonist.tier
|
(thisTank) => thisTank.tier === protagonist.tier,
|
||||||
);
|
);
|
||||||
const value =
|
const value =
|
||||||
(filtered.reduce((accumulator, thisTank) => {
|
(filtered.reduce((accumulator, thisTank) => {
|
||||||
return (
|
return (
|
||||||
accumulator +
|
accumulator +
|
||||||
thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration.near
|
thisTank.turrets.at(-1)!.guns.at(-1)!.shells[0].penetration!.near
|
||||||
);
|
);
|
||||||
}, 0) /
|
}, 0) /
|
||||||
filtered.length) *
|
filtered.length) *
|
||||||
@@ -99,11 +99,11 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
isFullScreen
|
isFullScreen
|
||||||
? "100vh"
|
? "100vh"
|
||||||
: disturbed
|
: disturbed
|
||||||
? "calc(100svh - 8rem)"
|
? "calc(100svh - 8rem)"
|
||||||
: {
|
: {
|
||||||
initial: "28rem",
|
initial: "28rem",
|
||||||
md: "calc(100svh - 14rem)",
|
md: "calc(100svh - 14rem)",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maxWidth={isFullScreen ? undefined : "120rem"}
|
maxWidth={isFullScreen ? undefined : "120rem"}
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
|
|||||||
+3
-3
@@ -36,7 +36,7 @@ export function VoteCaster() {
|
|||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
|
|
||||||
<Dialog.Content maxWidth="23rem">
|
<Dialog.Content maxWidth="23rem">
|
||||||
<Dialog.Title>Voting for {unwrap(tank.name)}</Dialog.Title>
|
<Dialog.Title>Voting for {unwrap(tank.name!)}</Dialog.Title>
|
||||||
|
|
||||||
<Flex direction="column" mt="4">
|
<Flex direction="column" mt="4">
|
||||||
<Flex align="center" justify="between">
|
<Flex align="center" justify="between">
|
||||||
@@ -118,10 +118,10 @@ export function VoteCaster() {
|
|||||||
survivability === null
|
survivability === null
|
||||||
? ""
|
? ""
|
||||||
: `survivability=${survivability}&`
|
: `survivability=${survivability}&`
|
||||||
}player=${wargaming.id}&token=${wargaming.token}`
|
}player=${wargaming.id}&token=${wargaming.token}`,
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
(response) => response.json() as Promise<BlitzkitResponse>
|
(response) => response.json() as Promise<BlitzkitResponse>,
|
||||||
)
|
)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
setShowDialogue(false);
|
setShowDialogue(false);
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import {
|
|||||||
TankPriceType,
|
TankPriceType,
|
||||||
TankType,
|
TankType,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import { Box, Flex } from '@radix-ui/themes';
|
import { Box, Flex } from "@radix-ui/themes";
|
||||||
import { Var } from '../../../core/radix/var';
|
import { Var } from "../../../core/radix/var";
|
||||||
import { useLocale } from '../../../hooks/useLocale';
|
import { useLocale } from "../../../hooks/useLocale";
|
||||||
import { App } from '../../../stores/app';
|
import { App } from "../../../stores/app";
|
||||||
import { Duel } from '../../../stores/duel';
|
import { Duel } from "../../../stores/duel";
|
||||||
import { classIcons } from '../../ClassIcon';
|
import { classIcons } from "../../ClassIcon";
|
||||||
import { CopyableCode } from '../../CopyableCode';
|
import { CopyableCode } from "../../CopyableCode";
|
||||||
import { Listing } from './components/Listing';
|
import { Listing } from "./components/Listing";
|
||||||
|
|
||||||
const NATIONAL_BANNER_POSITION_OVERRIDES: Record<string, string> = {
|
const NATIONAL_BANNER_POSITION_OVERRIDES: Record<string, string> = {
|
||||||
germany: '50% 35%',
|
germany: "50% 35%",
|
||||||
france: '50% 35%',
|
france: "50% 35%",
|
||||||
other: '50% 35%',
|
other: "50% 35%",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MetaSection() {
|
export function MetaSection() {
|
||||||
@@ -30,10 +30,10 @@ export function MetaSection() {
|
|||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
background: `url(/assets/images/national-wallpapers/${protagonist.nation}.jpg)`,
|
background: `url(/assets/images/national-wallpapers/${protagonist.nation}.jpg)`,
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundSize: 'cover',
|
backgroundSize: "cover",
|
||||||
backgroundPosition:
|
backgroundPosition:
|
||||||
NATIONAL_BANNER_POSITION_OVERRIDES[protagonist.nation] ?? 'center',
|
NATIONAL_BANNER_POSITION_OVERRIDES[protagonist.nation] ?? "center",
|
||||||
}}
|
}}
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
maxWidth="120rem"
|
maxWidth="120rem"
|
||||||
@@ -41,9 +41,9 @@ export function MetaSection() {
|
|||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
background: `url(${asset(`flags/scratched/${protagonist.nation}.webp`)})`,
|
background: `url(${asset(`flags/scratched/${protagonist.nation}.webp`)})`,
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundSize: 'min(53rem, 80vw)',
|
backgroundSize: "min(53rem, 80vw)",
|
||||||
backgroundPosition: '-8rem 50%',
|
backgroundPosition: "-8rem 50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -51,19 +51,19 @@ export function MetaSection() {
|
|||||||
align="center"
|
align="center"
|
||||||
py="6rem"
|
py="6rem"
|
||||||
style={{
|
style={{
|
||||||
background: `linear-gradient(${Var('black-a8')}, ${Var('black-a8')}, ${Var('gray-1')})`,
|
background: `linear-gradient(${Var("black-a8")}, ${Var("black-a8")}, ${Var("gray-1")})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex direction="column" align="center" gap="6">
|
<Flex direction="column" align="center" gap="6">
|
||||||
<Flex
|
<Flex
|
||||||
gap={{ initial: '2', sm: '8' }}
|
gap={{ initial: "2", sm: "8" }}
|
||||||
direction={{ initial: 'column', sm: 'row' }}
|
direction={{ initial: "column", sm: "row" }}
|
||||||
>
|
>
|
||||||
<Flex direction="column" width="100%" gap="2">
|
<Flex direction="column" width="100%" gap="2">
|
||||||
<Listing label={strings.website.tools.tankopedia.meta.nation}>
|
<Listing label={strings.website.tools.tankopedia.meta.nation}>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
<img
|
<img
|
||||||
style={{ width: '1em', height: '1em' }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt={protagonist.nation}
|
alt={protagonist.nation}
|
||||||
src={asset(`flags/circle/${protagonist.nation}.webp`)}
|
src={asset(`flags/circle/${protagonist.nation}.webp`)}
|
||||||
/>
|
/>
|
||||||
@@ -86,10 +86,10 @@ export function MetaSection() {
|
|||||||
<Listing
|
<Listing
|
||||||
label={strings.website.tools.tankopedia.meta.type}
|
label={strings.website.tools.tankopedia.meta.type}
|
||||||
color={
|
color={
|
||||||
protagonist.type === TankType.COLLECTOR
|
protagonist.type === TankType.TANK_TYPE_COLLECTOR
|
||||||
? 'blue'
|
? "blue"
|
||||||
: protagonist.type === TankType.PREMIUM
|
: protagonist.type === TankType.TANK_TYPE_PREMIUM
|
||||||
? 'amber'
|
? "amber"
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -117,7 +117,7 @@ export function MetaSection() {
|
|||||||
label={strings.website.tools.tankopedia.meta.dev_name}
|
label={strings.website.tools.tankopedia.meta.dev_name}
|
||||||
>
|
>
|
||||||
<CopyableCode
|
<CopyableCode
|
||||||
copy={devName}
|
copy={protagonist.dev_name}
|
||||||
variant="soft"
|
variant="soft"
|
||||||
highContrast
|
highContrast
|
||||||
color="gray"
|
color="gray"
|
||||||
@@ -126,37 +126,38 @@ export function MetaSection() {
|
|||||||
</CopyableCode>
|
</CopyableCode>
|
||||||
</Listing>
|
</Listing>
|
||||||
)}
|
)}
|
||||||
{protagonist.type === TankType.PREMIUM && (
|
{protagonist.type === TankType.TANK_TYPE_PREMIUM && (
|
||||||
<Listing
|
<Listing
|
||||||
label={strings.website.tools.tankopedia.meta.purchase}
|
label={strings.website.tools.tankopedia.meta.purchase}
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
{protagonist.price.value / 400}
|
{protagonist.price!.value / 400}
|
||||||
<img
|
<img
|
||||||
style={{ width: '1em', height: '1em' }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt="gold"
|
alt="gold"
|
||||||
src={asset('icons/currencies/gold.webp')}
|
src={asset("icons/currencies/gold.webp")}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Listing>
|
</Listing>
|
||||||
)}
|
)}
|
||||||
<Listing
|
<Listing
|
||||||
label={
|
label={
|
||||||
protagonist.type === TankType.RESEARCHABLE
|
protagonist.type === TankType.TANK_TYPE_RESEARCHABLE
|
||||||
? strings.website.tools.tankopedia.meta.purchase
|
? strings.website.tools.tankopedia.meta.purchase
|
||||||
: strings.website.tools.tankopedia.meta.restoration
|
: strings.website.tools.tankopedia.meta.restoration
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
{protagonist.price.value.toLocaleString(locale)}
|
{protagonist.price!.value.toLocaleString(locale)}
|
||||||
<img
|
<img
|
||||||
style={{ width: '1em', height: '1em' }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt={TankPriceType[protagonist.price.type]}
|
alt={TankPriceType[protagonist.price!.type]}
|
||||||
src={asset(
|
src={asset(
|
||||||
`icons/currencies/${
|
`icons/currencies/${
|
||||||
protagonist.price.type === TankPriceType.GOLD
|
protagonist.price!.type ===
|
||||||
? 'gold'
|
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||||
: 'silver'
|
? "gold"
|
||||||
|
: "silver"
|
||||||
}.webp`,
|
}.webp`,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -164,15 +165,16 @@ export function MetaSection() {
|
|||||||
</Listing>
|
</Listing>
|
||||||
<Listing label={strings.website.tools.tankopedia.meta.sale}>
|
<Listing label={strings.website.tools.tankopedia.meta.sale}>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
{(protagonist.price.value / 2).toLocaleString(locale)}
|
{(protagonist.price!.value / 2).toLocaleString(locale)}
|
||||||
<img
|
<img
|
||||||
style={{ width: '1em', height: '1em' }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt={TankPriceType[protagonist.price.type]}
|
alt={TankPriceType[protagonist.price!.type]}
|
||||||
src={asset(
|
src={asset(
|
||||||
`icons/currencies/${
|
`icons/currencies/${
|
||||||
protagonist.price.type === TankPriceType.GOLD
|
protagonist.price!.type ===
|
||||||
? 'gold'
|
TankPriceType.TANK_PRICE_TYPE_GOLD
|
||||||
: 'silver'
|
? "gold"
|
||||||
|
: "silver"
|
||||||
}.webp`,
|
}.webp`,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -188,9 +190,9 @@ export function MetaSection() {
|
|||||||
.value as number
|
.value as number
|
||||||
).toLocaleString(locale)}
|
).toLocaleString(locale)}
|
||||||
<img
|
<img
|
||||||
style={{ width: '1em', height: '1em' }}
|
style={{ width: "1em", height: "1em" }}
|
||||||
alt="xp"
|
alt="xp"
|
||||||
src={asset('icons/currencies/xp.webp')}
|
src={asset("icons/currencies/xp.webp")}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Listing>
|
</Listing>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
? 0
|
? 0
|
||||||
: (tank.research_cost.research_cost_type!.value as number) /
|
: (tank.research_cost.research_cost_type!.value as number) /
|
||||||
xpMultiplier),
|
xpMultiplier),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
const averages = averageDefinitions.averages[tank.id]?.mu;
|
const averages = averageDefinitions.averages[tank.id]?.mu;
|
||||||
const averageXp = averages ? averages.xp / averages.battles : undefined;
|
const averageXp = averages ? averages.xp / averages.battles : undefined;
|
||||||
@@ -78,7 +78,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={unwrap(tank.name)}
|
alt={unwrap(tank.name!)}
|
||||||
src={asset(`icons/tanks/big/${id}.webp`)}
|
src={asset(`icons/tanks/big/${id}.webp`)}
|
||||||
width={64}
|
width={64}
|
||||||
height={64}
|
height={64}
|
||||||
@@ -92,7 +92,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{TIER_ROMAN_NUMERALS[tank.tier]}
|
{TIER_ROMAN_NUMERALS[tank.tier]}
|
||||||
</Text>
|
</Text>
|
||||||
<Text wrap="nowrap">{unwrap(tank.name)}</Text>
|
<Text wrap="nowrap">{unwrap(tank.name!)}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap="2" align="center">
|
<Flex gap="2" align="center">
|
||||||
<Text color="gray" size="1" wrap="nowrap">
|
<Text color="gray" size="1" wrap="nowrap">
|
||||||
@@ -122,7 +122,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
objectPosition: "center",
|
objectPosition: "center",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{formatCompact(locale, tank.price.value)}
|
{formatCompact(locale, tank.price!.value)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -135,7 +135,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
|||||||
? strings.website.tools.tankopedia.tech_tree.battle
|
? strings.website.tools.tankopedia.tech_tree.battle
|
||||||
: literals(
|
: literals(
|
||||||
strings.website.tools.tankopedia.tech_tree.battles,
|
strings.website.tools.tankopedia.tech_tree.battles,
|
||||||
{ count: games }
|
{ count: games },
|
||||||
))}
|
))}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,14 +26,18 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
function extend(line: Line): Line[] {
|
function extend(line: Line): Line[] {
|
||||||
const root = tankDefinitions.tanks[line.at(-1)!];
|
const root = tankDefinitions.tanks[line.at(-1)!];
|
||||||
|
|
||||||
if (root.ancestors === undefined || root.ancestors.length === 0 || root.tier === 1) {
|
if (
|
||||||
|
root.ancestors === undefined ||
|
||||||
|
root.ancestors.length === 0 ||
|
||||||
|
root.tier === 1
|
||||||
|
) {
|
||||||
return [line];
|
return [line];
|
||||||
} else {
|
} else {
|
||||||
if (root.ancestors.length === 1 || root.tier === 2) {
|
if (root.ancestors.length === 1 || root.tier === 2) {
|
||||||
line.push(
|
line.push(
|
||||||
root.ancestors.find(
|
root.ancestors.find(
|
||||||
(ancestor) => !tankDefinitions.tanks[ancestor].deprecated
|
(ancestor) => !tankDefinitions.tanks[ancestor].deprecated,
|
||||||
) ?? root.ancestors[0]
|
) ?? root.ancestors[0],
|
||||||
);
|
);
|
||||||
return extend(line);
|
return extend(line);
|
||||||
} else {
|
} else {
|
||||||
@@ -52,7 +56,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const [lineIndex, setLineIndex] = useState(0);
|
const [lineIndex, setLineIndex] = useState(0);
|
||||||
const line = useMemo(
|
const line = useMemo(
|
||||||
() => (lines.length === 0 ? [master.id] : [...lines[lineIndex]].reverse()),
|
() => (lines.length === 0 ? [master.id] : [...lines[lineIndex]].reverse()),
|
||||||
[master, lineIndex]
|
[master, lineIndex],
|
||||||
);
|
);
|
||||||
const totalXp = line.reduce(
|
const totalXp = line.reduce(
|
||||||
(xp, id) =>
|
(xp, id) =>
|
||||||
@@ -62,11 +66,11 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
? 0
|
? 0
|
||||||
: (tankDefinitions.tanks[id].research_cost.research_cost_type!
|
: (tankDefinitions.tanks[id].research_cost.research_cost_type!
|
||||||
.value as number)),
|
.value as number)),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
const totalCredits = line.reduce(
|
const totalCredits = line.reduce(
|
||||||
(credits, id) => credits + tankDefinitions.tanks[id].price.value,
|
(credits, id) => credits + tankDefinitions.tanks[id].price!.value,
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -75,7 +79,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
master.type !== TankType.RESEARCHABLE ||
|
master.type !== TankType.TANK_TYPE_RESEARCHABLE ||
|
||||||
master.ancestors === undefined ||
|
master.ancestors === undefined ||
|
||||||
master.ancestors.length === 0
|
master.ancestors.length === 0
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ import {
|
|||||||
TankType,
|
TankType,
|
||||||
type CompositeStats,
|
type CompositeStats,
|
||||||
type CompositeStatsKey,
|
type CompositeStatsKey,
|
||||||
} from '@blitzkit/core';
|
} from "@blitzkit/core";
|
||||||
import strings from '@blitzkit/i18n/strings/en.json';
|
import strings from "@blitzkit/i18n/strings/en.json";
|
||||||
import { amberDark, blueDark } from '@radix-ui/colors';
|
import { amberDark, blueDark } from "@radix-ui/colors";
|
||||||
import { Flex, Text, type FlexProps } from '@radix-ui/themes';
|
import { Flex, Text, type FlexProps } from "@radix-ui/themes";
|
||||||
import { times } from 'lodash-es';
|
import { times } from "lodash-es";
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from "react";
|
||||||
import { breakdownConfig } from '../constants/embeds';
|
import { breakdownConfig } from "../constants/embeds";
|
||||||
import { toRadiusVar } from '../core/radix/utils';
|
import { toRadiusVar } from "../core/radix/utils";
|
||||||
import { useLocale } from '../hooks/useLocale';
|
import { useLocale } from "../hooks/useLocale";
|
||||||
import { useEmbedStateCurry } from '../stores/embedState/utilities';
|
import { useEmbedStateCurry } from "../stores/embedState/utilities";
|
||||||
import { ClassHeavy } from './ClassIcon/components/ClassHeavy';
|
import { ClassHeavy } from "./ClassIcon/components/ClassHeavy";
|
||||||
import { ClassLight } from './ClassIcon/components/ClassLight';
|
import { ClassLight } from "./ClassIcon/components/ClassLight";
|
||||||
import { ClassMedium } from './ClassIcon/components/ClassMedium';
|
import { ClassMedium } from "./ClassIcon/components/ClassMedium";
|
||||||
import { ClassTankDestroyer } from './ClassIcon/components/ClassTankDestroyer';
|
import { ClassTankDestroyer } from "./ClassIcon/components/ClassTankDestroyer";
|
||||||
|
|
||||||
type BreakdownEmbedWrapperProps = FlexProps & {
|
type BreakdownEmbedWrapperProps = FlexProps & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -33,7 +33,7 @@ export function BreakdownEmbedWrapper({
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
gap={useEmbedState('listGap')}
|
gap={useEmbedState("listGap")}
|
||||||
height="100%"
|
height="100%"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -60,65 +60,65 @@ export function BreakdownEmbedCard({
|
|||||||
flexShrink="0"
|
flexShrink="0"
|
||||||
direction="column"
|
direction="column"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: toRadiusVar(useEmbedState('cardRadius')),
|
borderRadius: toRadiusVar(useEmbedState("cardRadius")),
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
backgroundColor: useEmbedState('cardBodyBackgroundColor'),
|
backgroundColor: useEmbedState("cardBodyBackgroundColor"),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
justify="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: useEmbedState('cardHeaderBackgroundColor'),
|
backgroundColor: useEmbedState("cardHeaderBackgroundColor"),
|
||||||
}}
|
}}
|
||||||
p={useEmbedState('cardHeaderPadding')}
|
p={useEmbedState("cardHeaderPadding")}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
{...{
|
{...{
|
||||||
...useRichText('cardTitle'),
|
...useRichText("cardTitle"),
|
||||||
style:
|
style:
|
||||||
tank === null
|
tank === null
|
||||||
? undefined
|
? undefined
|
||||||
: tank === undefined ||
|
: tank === undefined ||
|
||||||
tank.type === TankType.RESEARCHABLE ||
|
tank.type === TankType.TANK_TYPE_RESEARCHABLE ||
|
||||||
!useEmbedState('cardTitleTypeColor')
|
!useEmbedState("cardTitleTypeColor")
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
color:
|
color:
|
||||||
tank.type === TankType.PREMIUM
|
tank.type === TankType.TANK_TYPE_PREMIUM
|
||||||
? amberDark.amber11
|
? amberDark.amber11
|
||||||
: blueDark.blue11,
|
: blueDark.blue11,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="1">
|
<Flex align="center" gap="1">
|
||||||
{useEmbedState('cardTitleClassIcon') &&
|
{useEmbedState("cardTitleClassIcon") &&
|
||||||
tank !== null &&
|
tank !== null &&
|
||||||
tank !== undefined && (
|
tank !== undefined && (
|
||||||
<>
|
<>
|
||||||
{tank.class === TankClass.LIGHT && (
|
{tank.class === TankClass.TANK_CLASS_LIGHT && (
|
||||||
<ClassLight width="1em" height="1em" />
|
<ClassLight width="1em" height="1em" />
|
||||||
)}
|
)}
|
||||||
{tank.class === TankClass.MEDIUM && (
|
{tank.class === TankClass.TANK_CLASS_MEDIUM && (
|
||||||
<ClassMedium width="1em" height="1em" />
|
<ClassMedium width="1em" height="1em" />
|
||||||
)}
|
)}
|
||||||
{tank.class === TankClass.HEAVY && (
|
{tank.class === TankClass.TANK_CLASS_HEAVY && (
|
||||||
<ClassHeavy width="1em" height="1em" />
|
<ClassHeavy width="1em" height="1em" />
|
||||||
)}
|
)}
|
||||||
{tank.class === TankClass.TANK_DESTROYER && (
|
{tank.class === TankClass.TANK_CLASS_TANK_DESTROYER && (
|
||||||
<ClassTankDestroyer width="1em" height="1em" />
|
<ClassTankDestroyer width="1em" height="1em" />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}{' '}
|
)}{" "}
|
||||||
{tank === null
|
{tank === null
|
||||||
? 'Total'
|
? "Total"
|
||||||
: tank === undefined
|
: tank === undefined
|
||||||
? `Unknown tank`
|
? `Unknown tank`
|
||||||
: unwrap(tank.name)}
|
: unwrap(tank.name!)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex p={useEmbedState('cardBodyPadding')}>
|
<Flex p={useEmbedState("cardBodyPadding")}>
|
||||||
{times(4, (index) => {
|
{times(4, (index) => {
|
||||||
const column = (index + 1) as 1 | 2 | 3 | 4;
|
const column = (index + 1) as 1 | 2 | 3 | 4;
|
||||||
const statKey = useEmbedState(`column${column}`) as CompositeStatsKey;
|
const statKey = useEmbedState(`column${column}`) as CompositeStatsKey;
|
||||||
@@ -129,13 +129,13 @@ export function BreakdownEmbedCard({
|
|||||||
key={column}
|
key={column}
|
||||||
direction="column"
|
direction="column"
|
||||||
align="center"
|
align="center"
|
||||||
gap={useEmbedState('columnGap')}
|
gap={useEmbedState("columnGap")}
|
||||||
flexGrow="1"
|
flexGrow="1"
|
||||||
>
|
>
|
||||||
<Text {...useRichText('columnValue')}>
|
<Text {...useRichText("columnValue")}>
|
||||||
{formatCompositeStat(composite[statKey], statKey, composite)}
|
{formatCompositeStat(composite[statKey], statKey, composite)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text {...useRichText('columnLabel')}>
|
<Text {...useRichText("columnLabel")}>
|
||||||
{customLabel.length === 0
|
{customLabel.length === 0
|
||||||
? strings.common.composite_stats[statKey]
|
? strings.common.composite_stats[statKey]
|
||||||
: customLabel}
|
: customLabel}
|
||||||
|
|||||||
@@ -143,22 +143,28 @@ export function tankCharacteristics(
|
|||||||
const commanderMastery = 1 + provisionCrewBonus;
|
const commanderMastery = 1 + provisionCrewBonus;
|
||||||
const loaderMastery =
|
const loaderMastery =
|
||||||
commanderMastery *
|
commanderMastery *
|
||||||
(tank.crew.some(({ type }) => type === CrewType.LOADER) ? 1.1 : 1.05);
|
(tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_LOADER)
|
||||||
|
? 1.1
|
||||||
|
: 1.05);
|
||||||
const gunnerMastery =
|
const gunnerMastery =
|
||||||
commanderMastery *
|
commanderMastery *
|
||||||
(tank.crew.some(({ type }) => type === CrewType.GUNNER) ? 1.1 : 1.05);
|
(tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_GUNNER)
|
||||||
|
? 1.1
|
||||||
|
: 1.05);
|
||||||
const driverMastery =
|
const driverMastery =
|
||||||
commanderMastery *
|
commanderMastery *
|
||||||
(tank.crew.some(({ type }) => type === CrewType.DRIVER) ? 1.1 : 1.05);
|
(tank.crew.some(({ type }) => type === CrewType.CREW_TYPE_DRIVER)
|
||||||
|
? 1.1
|
||||||
|
: 1.05);
|
||||||
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
|
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
|
||||||
const burstShells = gun.burst?.count ?? 1;
|
const burstShells = gun.burst?.count ?? 1;
|
||||||
const burstInterShell = gun.burst?.interval;
|
const burstInterShell = gun.burst?.interval;
|
||||||
const armorDamageCoefficient =
|
const armorDamageCoefficient =
|
||||||
coefficient([hasTungsten, 0.15]) *
|
coefficient([hasTungsten, 0.15]) *
|
||||||
coefficient(
|
coefficient(
|
||||||
[applyReactiveArmor && shell.type !== ShellType.HE, -0.27],
|
[applyReactiveArmor && shell.type !== ShellType.SHELL_TYPE_HE, -0.27],
|
||||||
[applyDynamicArmor, -0.1],
|
[applyDynamicArmor, -0.1],
|
||||||
[applySpallLiner && shell.type === ShellType.HE, -0.2],
|
[applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, -0.2],
|
||||||
);
|
);
|
||||||
const assaultDamageCoefficient =
|
const assaultDamageCoefficient =
|
||||||
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
|
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
|
||||||
@@ -215,7 +221,8 @@ export function tankCharacteristics(
|
|||||||
const enginePowerCoefficient = coefficient(
|
const enginePowerCoefficient = coefficient(
|
||||||
[
|
[
|
||||||
hasEngineAccelerator,
|
hasEngineAccelerator,
|
||||||
tank.class === TankClass.HEAVY || tank.class === TankClass.TANK_DESTROYER
|
tank.class === TankClass.TANK_CLASS_HEAVY ||
|
||||||
|
tank.class === TankClass.TANK_CLASS_TANK_DESTROYER
|
||||||
? 0.05
|
? 0.05
|
||||||
: 0.04,
|
: 0.04,
|
||||||
],
|
],
|
||||||
@@ -250,11 +257,11 @@ export function tankCharacteristics(
|
|||||||
const viewRangeCoefficient =
|
const viewRangeCoefficient =
|
||||||
coefficient([
|
coefficient([
|
||||||
hasImprovedOptics,
|
hasImprovedOptics,
|
||||||
tank.class === TankClass.HEAVY
|
tank.class === TankClass.TANK_CLASS_HEAVY
|
||||||
? 0.06
|
? 0.06
|
||||||
: tank.class === TankClass.MEDIUM
|
: tank.class === TankClass.TANK_CLASS_MEDIUM
|
||||||
? 0.08
|
? 0.08
|
||||||
: tank.class === TankClass.LIGHT
|
: tank.class === TankClass.TANK_CLASS_LIGHT
|
||||||
? 0.11
|
? 0.11
|
||||||
: 0.04,
|
: 0.04,
|
||||||
]) * coefficient([true, progressiveStat(commanderMastery)]);
|
]) * coefficient([true, progressiveStat(commanderMastery)]);
|
||||||
@@ -275,20 +282,20 @@ export function tankCharacteristics(
|
|||||||
true,
|
true,
|
||||||
coefficient([
|
coefficient([
|
||||||
camouflage,
|
camouflage,
|
||||||
tank.class === TankClass.TANK_DESTROYER
|
tank.class === TankClass.TANK_CLASS_TANK_DESTROYER
|
||||||
? 0.04
|
? 0.04
|
||||||
: tank.class === TankClass.HEAVY
|
: tank.class === TankClass.TANK_CLASS_HEAVY
|
||||||
? 0.03
|
? 0.03
|
||||||
: 0.02,
|
: 0.02,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
hasCamouflageNet,
|
hasCamouflageNet,
|
||||||
tank.class === TankClass.HEAVY
|
tank.class === TankClass.TANK_CLASS_HEAVY
|
||||||
? 0.02
|
? 0.02
|
||||||
: tank.class === TankClass.MEDIUM
|
: tank.class === TankClass.TANK_CLASS_MEDIUM
|
||||||
? 0.04
|
? 0.04
|
||||||
: tank.class === TankClass.LIGHT
|
: tank.class === TankClass.TANK_CLASS_LIGHT
|
||||||
? 0.07
|
? 0.07
|
||||||
: 0.1,
|
: 0.1,
|
||||||
],
|
],
|
||||||
@@ -299,9 +306,9 @@ export function tankCharacteristics(
|
|||||||
coefficient(
|
coefficient(
|
||||||
[
|
[
|
||||||
camouflage,
|
camouflage,
|
||||||
tank.class === TankClass.TANK_DESTROYER
|
tank.class === TankClass.TANK_CLASS_TANK_DESTROYER
|
||||||
? 0.04
|
? 0.04
|
||||||
: tank.class === TankClass.HEAVY
|
: tank.class === TankClass.TANK_CLASS_HEAVY
|
||||||
? 0.03
|
? 0.03
|
||||||
: 0.02,
|
: 0.02,
|
||||||
],
|
],
|
||||||
@@ -310,11 +317,11 @@ export function tankCharacteristics(
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
hasCamouflageNet,
|
hasCamouflageNet,
|
||||||
tank.class === TankClass.HEAVY
|
tank.class === TankClass.TANK_CLASS_HEAVY
|
||||||
? 0.04
|
? 0.04
|
||||||
: tank.class === TankClass.MEDIUM
|
: tank.class === TankClass.TANK_CLASS_MEDIUM
|
||||||
? 0.08
|
? 0.08
|
||||||
: tank.class === TankClass.LIGHT
|
: tank.class === TankClass.TANK_CLASS_LIGHT
|
||||||
? 0.14
|
? 0.14
|
||||||
: 0.2,
|
: 0.2,
|
||||||
],
|
],
|
||||||
@@ -322,8 +329,8 @@ export function tankCharacteristics(
|
|||||||
|
|
||||||
const size = normalizeBoundingBox(
|
const size = normalizeBoundingBox(
|
||||||
unionBoundingBox(
|
unionBoundingBox(
|
||||||
tankModelDefinition.bounding_box,
|
tankModelDefinition.bounding_box!,
|
||||||
turretModelDefinition.bounding_box,
|
turretModelDefinition.bounding_box!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const weightKg =
|
const weightKg =
|
||||||
@@ -408,7 +415,7 @@ export function tankCharacteristics(
|
|||||||
? gun.gun_type!.value.reload
|
? gun.gun_type!.value.reload
|
||||||
: gun.gun_type!.value.clip_reload) * reloadCoefficient;
|
: gun.gun_type!.value.clip_reload) * reloadCoefficient;
|
||||||
const caliber = shell.caliber;
|
const caliber = shell.caliber;
|
||||||
const penetration = shell.penetration.near * penetrationCoefficient;
|
const penetration = shell.penetration!.near * penetrationCoefficient;
|
||||||
const clipDamage =
|
const clipDamage =
|
||||||
gun.gun_type!.$case === "regular"
|
gun.gun_type!.$case === "regular"
|
||||||
? undefined
|
? undefined
|
||||||
@@ -428,21 +435,21 @@ export function tankCharacteristics(
|
|||||||
const dispersionGunDamaged =
|
const dispersionGunDamaged =
|
||||||
gun.dispersion_damaged * dispersionDamagedCoefficient;
|
gun.dispersion_damaged * dispersionDamagedCoefficient;
|
||||||
const gunDepression =
|
const gunDepression =
|
||||||
gunModelDefinition.pitch.max +
|
gunModelDefinition.pitch!.max +
|
||||||
gunDefaultPitch +
|
gunDefaultPitch +
|
||||||
(hasImprovedVerticalStabilizer ? 3 : 0) +
|
(hasImprovedVerticalStabilizer ? 3 : 0) +
|
||||||
(hasDownImprovedVerticalStabilizer ? 3 : 0);
|
(hasDownImprovedVerticalStabilizer ? 3 : 0);
|
||||||
const gunElevation =
|
const gunElevation =
|
||||||
-gunModelDefinition.pitch.min -
|
-gunModelDefinition.pitch!.min -
|
||||||
gunDefaultPitch +
|
gunDefaultPitch +
|
||||||
(hasImprovedVerticalStabilizer ? 4 : 0);
|
(hasImprovedVerticalStabilizer ? 4 : 0);
|
||||||
const gunFrontalDepression = gunModelDefinition.pitch.front?.max;
|
const gunFrontalDepression = gunModelDefinition.pitch!.front?.max;
|
||||||
const gunFrontalElevation = gunModelDefinition.pitch.front
|
const gunFrontalElevation = gunModelDefinition.pitch!.front
|
||||||
? -gunModelDefinition.pitch.front.min
|
? -gunModelDefinition.pitch!.front.min
|
||||||
: undefined;
|
: undefined;
|
||||||
const gunRearDepression = gunModelDefinition.pitch.back?.max;
|
const gunRearDepression = gunModelDefinition.pitch!.back?.max;
|
||||||
const gunRearElevation = gunModelDefinition.pitch.back
|
const gunRearElevation = gunModelDefinition.pitch!.back
|
||||||
? -gunModelDefinition.pitch.back.min
|
? -gunModelDefinition.pitch!.back.min
|
||||||
: undefined;
|
: undefined;
|
||||||
const azimuthLeft = turretModelDefinition.yaw
|
const azimuthLeft = turretModelDefinition.yaw
|
||||||
? -turretModelDefinition.yaw.min
|
? -turretModelDefinition.yaw.min
|
||||||
@@ -496,18 +503,20 @@ export function tankCharacteristics(
|
|||||||
const viewRange = turret.view_range * viewRangeCoefficient;
|
const viewRange = turret.view_range * viewRangeCoefficient;
|
||||||
const camouflageStill = tank.camouflage_still * camouflageCoefficientStill;
|
const camouflageStill = tank.camouflage_still * camouflageCoefficientStill;
|
||||||
const camouflageMoving =
|
const camouflageMoving =
|
||||||
tank.class === TankClass.LIGHT
|
tank.class === TankClass.TANK_CLASS_LIGHT
|
||||||
? tank.camouflage_still * camouflageCoefficientStill
|
? tank.camouflage_still * camouflageCoefficientStill
|
||||||
: tank.camouflage_moving * camouflageCoefficientMoving;
|
: tank.camouflage_moving * camouflageCoefficientMoving;
|
||||||
const camouflageShootingStill =
|
const camouflageShootingStill =
|
||||||
tank.camouflage_still * gun.camouflage_loss * camouflageCoefficientStill;
|
tank.camouflage_still * gun.camouflage_loss * camouflageCoefficientStill;
|
||||||
const camouflageShootingMoving =
|
const camouflageShootingMoving =
|
||||||
(tank.class === TankClass.LIGHT
|
(tank.class === TankClass.TANK_CLASS_LIGHT
|
||||||
? tank.camouflage_still * camouflageCoefficientStill
|
? tank.camouflage_still * camouflageCoefficientStill
|
||||||
: tank.camouflage_moving * camouflageCoefficientMoving) *
|
: tank.camouflage_moving * camouflageCoefficientMoving) *
|
||||||
gun.camouflage_loss;
|
gun.camouflage_loss;
|
||||||
const camouflageCaughtOnFire =
|
const camouflageCaughtOnFire =
|
||||||
tank.camouflage_onFire * tank.camouflage_still * camouflageCoefficientStill;
|
tank.camouflage_on_fire *
|
||||||
|
tank.camouflage_still *
|
||||||
|
camouflageCoefficientStill;
|
||||||
const width = size.z;
|
const width = size.z;
|
||||||
const height = size.x;
|
const height = size.x;
|
||||||
const length = size.y;
|
const length = size.y;
|
||||||
@@ -518,7 +527,7 @@ export function tankCharacteristics(
|
|||||||
const shellRange = shell.range;
|
const shellRange = shell.range;
|
||||||
const shellCapacity = gun.shell_capacity;
|
const shellCapacity = gun.shell_capacity;
|
||||||
const gunType = gun.gun_type!.$case;
|
const gunType = gun.gun_type!.$case;
|
||||||
const penetrationAt250m = shell.penetration.far;
|
const penetrationAt250m = shell.penetration!.far;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crewCount,
|
crewCount,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { awaitableAverageDefinitions } from '../core/awaitables/averageDefinitions';
|
import { awaitableAverageDefinitions } from "../core/awaitables/averageDefinitions";
|
||||||
import { awaitableDiscoveredIdsDefinitions } from '../core/awaitables/discoveredIdsDefinitions';
|
import { awaitableDiscoveredIdsDefinitions } from "../core/awaitables/discoveredIdsDefinitions";
|
||||||
|
|
||||||
const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
||||||
awaitableDiscoveredIdsDefinitions,
|
awaitableDiscoveredIdsDefinitions,
|
||||||
@@ -7,5 +7,5 @@ const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export function useAveragesExclusionRatio() {
|
export function useAveragesExclusionRatio() {
|
||||||
return discoveredIdsDefinitions.count / averageDefinitions.samples.total;
|
return discoveredIdsDefinitions.count / averageDefinitions.samples!.total;
|
||||||
}
|
}
|
||||||
@@ -21,23 +21,23 @@ export function useTankTransform(
|
|||||||
track: TrackDefinition,
|
track: TrackDefinition,
|
||||||
turret: TurretDefinition,
|
turret: TurretDefinition,
|
||||||
turretContainer: RefObject<Group>,
|
turretContainer: RefObject<Group>,
|
||||||
gunContainer: RefObject<Group>
|
gunContainer: RefObject<Group>,
|
||||||
) {
|
) {
|
||||||
const tankModelDefinition = useTankModelDefinition();
|
const tankModelDefinition = useTankModelDefinition();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
const trackModelDefinition = tankModelDefinition.tracks[track.id];
|
||||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin!);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turret_origin!);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gun_origin!);
|
||||||
const turretPosition = new Vector3();
|
const turretPosition = new Vector3();
|
||||||
const turretRotation = new Euler();
|
const turretRotation = new Euler();
|
||||||
const gunPosition = new Vector3();
|
const gunPosition = new Vector3();
|
||||||
const gunRotation = new Euler();
|
const gunRotation = new Euler();
|
||||||
|
|
||||||
function handleModelTransform(
|
function handleModelTransform(
|
||||||
event: QuicklimeEvent<ModelTransformEventData>
|
event: QuicklimeEvent<ModelTransformEventData>,
|
||||||
) {
|
) {
|
||||||
handleModelTransformInner(event.data);
|
handleModelTransformInner(event.data);
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ export function useTankTransform(
|
|||||||
|
|
||||||
if (tankModelDefinition.initial_turret_rotation) {
|
if (tankModelDefinition.initial_turret_rotation) {
|
||||||
const initialPitch = -degToRad(
|
const initialPitch = -degToRad(
|
||||||
tankModelDefinition.initial_turret_rotation.pitch
|
tankModelDefinition.initial_turret_rotation.pitch,
|
||||||
);
|
);
|
||||||
const initialYaw = -degToRad(
|
const initialYaw = -degToRad(
|
||||||
tankModelDefinition.initial_turret_rotation.yaw
|
tankModelDefinition.initial_turret_rotation.yaw,
|
||||||
);
|
);
|
||||||
const initialRoll = -degToRad(
|
const initialRoll = -degToRad(
|
||||||
tankModelDefinition.initial_turret_rotation.roll
|
tankModelDefinition.initial_turret_rotation.roll,
|
||||||
);
|
);
|
||||||
|
|
||||||
turretPosition
|
turretPosition
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
const tank = Guess.use((state) => state.tank);
|
const tank = Guess.use((state) => state.tank);
|
||||||
const guessState = Guess.use((state) => state.guessState);
|
const guessState = Guess.use((state) => state.guessState);
|
||||||
const isRevealed = guessState !== GuessState.NotGuessed;
|
const isRevealed = guessState !== GuessState.NotGuessed;
|
||||||
const name = unwrap(tank.name);
|
const name = unwrap(tank.name!);
|
||||||
const fontSize = `min(48vh, ${55 / name.length}vw)`;
|
const fontSize = `min(48vh, ${55 / name.length}vw)`;
|
||||||
const transitionDuration = isRevealed ? "2s" : undefined;
|
const transitionDuration = isRevealed ? "2s" : undefined;
|
||||||
const tiers = Guess.use((state) => state.tiers);
|
const tiers = Guess.use((state) => state.tiers);
|
||||||
@@ -77,7 +77,7 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
if (filteredIds.length === 0) return;
|
if (filteredIds.length === 0) return;
|
||||||
|
|
||||||
const id = Number(
|
const id = Number(
|
||||||
filteredIds[Math.floor(Math.random() * filteredIds.length)]
|
filteredIds[Math.floor(Math.random() * filteredIds.length)],
|
||||||
);
|
);
|
||||||
const newTank = tankDefinitions.tanks[id];
|
const newTank = tankDefinitions.tanks[id];
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ function Content() {
|
|||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const session = Session.use();
|
const session = Session.use();
|
||||||
const [tankStatsB, setTankStatsB] = useState<IndividualTankStats[] | null>(
|
const [tankStatsB, setTankStatsB] = useState<IndividualTankStats[] | null>(
|
||||||
null
|
null,
|
||||||
);
|
);
|
||||||
const [accountInfo, setAccountInfo] = useState<
|
const [accountInfo, setAccountInfo] = useState<
|
||||||
IndividualAccountInfo | null | undefined
|
IndividualAccountInfo | null | undefined
|
||||||
@@ -81,7 +81,7 @@ function Content() {
|
|||||||
if (!id) return setAccountInfo(null);
|
if (!id) return setAccountInfo(null);
|
||||||
|
|
||||||
setAccountInfo(
|
setAccountInfo(
|
||||||
session.tracking ? await getAccountInfo(idToRegion(id), id) : null
|
session.tracking ? await getAccountInfo(idToRegion(id), id) : null,
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
}, [session.tracking && session.player.id]);
|
}, [session.tracking && session.player.id]);
|
||||||
@@ -95,20 +95,20 @@ function Content() {
|
|||||||
const average = averageDefinitions.averages[tank.id];
|
const average = averageDefinitions.averages[tank.id];
|
||||||
const composite = compositeStats(
|
const composite = compositeStats(
|
||||||
{ ...entry.all, battle_life_time: entry.battle_life_time },
|
{ ...entry.all, battle_life_time: entry.battle_life_time },
|
||||||
average?.mu
|
average?.mu!,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { tank, composite };
|
return { tank, composite };
|
||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
[session.tracking && session.player, tankStatsB]
|
[session.tracking && session.player, tankStatsB],
|
||||||
);
|
);
|
||||||
const total = useMemo(
|
const total = useMemo(
|
||||||
() =>
|
() =>
|
||||||
delta
|
delta
|
||||||
? sumCompositeStats(delta.map(({ composite }) => composite))
|
? sumCompositeStats(delta.map(({ composite }) => composite))
|
||||||
: undefined,
|
: undefined,
|
||||||
[delta]
|
[delta],
|
||||||
);
|
);
|
||||||
const search = debounce(async () => {
|
const search = debounce(async () => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
@@ -150,7 +150,7 @@ function Content() {
|
|||||||
|
|
||||||
const tankStats = await getTankStats(
|
const tankStats = await getTankStats(
|
||||||
idToRegion(session.player.id),
|
idToRegion(session.player.id),
|
||||||
session.player.id
|
session.player.id,
|
||||||
);
|
);
|
||||||
setTankStatsB(tankStats);
|
setTankStatsB(tankStats);
|
||||||
}
|
}
|
||||||
@@ -294,7 +294,7 @@ function Content() {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const stats = await getTankStats(
|
const stats = await getTankStats(
|
||||||
session.player.region,
|
session.player.region,
|
||||||
session.player.id
|
session.player.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (stats === null) {
|
if (stats === null) {
|
||||||
@@ -365,7 +365,7 @@ function Content() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
Session.mutate((draft) => {
|
Session.mutate((draft) => {
|
||||||
(draft as SessionTracking).columns.unshift(
|
(draft as SessionTracking).columns.unshift(
|
||||||
key
|
key,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -402,7 +402,8 @@ function Content() {
|
|||||||
{compositeStatsKeys
|
{compositeStatsKeys
|
||||||
.filter(
|
.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
!session.columns.includes(item) || item === column
|
!session.columns.includes(item) ||
|
||||||
|
item === column,
|
||||||
)
|
)
|
||||||
.map((key) => (
|
.map((key) => (
|
||||||
<Select.Item key={key} value={key}>
|
<Select.Item key={key} value={key}>
|
||||||
@@ -452,7 +453,7 @@ function Content() {
|
|||||||
{formatCompositeStat(
|
{formatCompositeStat(
|
||||||
composite[column],
|
composite[column],
|
||||||
column,
|
column,
|
||||||
composite
|
composite,
|
||||||
)}
|
)}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function Page({ id, skeleton, locale, guide }: PageProps) {
|
|||||||
<HeroSection skeleton={skeleton} />
|
<HeroSection skeleton={skeleton} />
|
||||||
<MetaSection />
|
<MetaSection />
|
||||||
<CalloutsSection />
|
<CalloutsSection />
|
||||||
{tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
|
{tank.type === TankType.TANK_TYPE_RESEARCHABLE && !tank.deprecated && (
|
||||||
<TechTreeSection skeleton={skeleton} />
|
<TechTreeSection skeleton={skeleton} />
|
||||||
)}
|
)}
|
||||||
<CharacteristicsSection skeleton={skeleton} />
|
<CharacteristicsSection skeleton={skeleton} />
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const tank = tankDefinitions.tanks[id];
|
|||||||
const tankModelDefinition = tankModelDefinitions.models[tank.id];
|
const tankModelDefinition = tankModelDefinitions.models[tank.id];
|
||||||
const unwrap = unwrapper(locale);
|
const unwrap = unwrapper(locale);
|
||||||
const title = literals(strings.website.tools.tankopedia.tank_seo_title, {
|
const title = literals(strings.website.tools.tankopedia.tank_seo_title, {
|
||||||
name: unwrap(tank.name),
|
name: unwrap(tank.name!),
|
||||||
tier: TIER_ROMAN_NUMERALS[tank.tier],
|
tier: TIER_ROMAN_NUMERALS[tank.tier],
|
||||||
nationality:
|
nationality:
|
||||||
strings.common.nations_adjectives[
|
strings.common.nations_adjectives[
|
||||||
@@ -92,7 +92,7 @@ const characteristics = tankCharacteristics(
|
|||||||
const description = literals(
|
const description = literals(
|
||||||
strings.website.tools.tankopedia.tank_seo_description,
|
strings.website.tools.tankopedia.tank_seo_description,
|
||||||
{
|
{
|
||||||
name: unwrap(tank.name),
|
name: unwrap(tank.name!),
|
||||||
dpm: Math.round(characteristics.dpm).toLocaleString(locale),
|
dpm: Math.round(characteristics.dpm).toLocaleString(locale),
|
||||||
alpha: Math.round(characteristics.damage).toLocaleString(locale),
|
alpha: Math.round(characteristics.damage).toLocaleString(locale),
|
||||||
penetration: Math.round(characteristics.penetration).toLocaleString(locale),
|
penetration: Math.round(characteristics.penetration).toLocaleString(locale),
|
||||||
|
|||||||
Reference in new issue
Block a user