mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: localize tankopedia sandbox
This commit is contained in:
1 parent
ade6ec8af1
commit
e0c897025e
6 files changed
+398
-208
No files matched your search
@@ -6,6 +6,20 @@
|
||||
},
|
||||
|
||||
"common": {
|
||||
"units_suffix": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
"deg": "%s1°",
|
||||
"hp": "%s1hp"
|
||||
},
|
||||
|
||||
"shells": {
|
||||
"ap": "AP",
|
||||
"apcr": "APCR",
|
||||
"heat": "HEAT",
|
||||
"he": "HE"
|
||||
},
|
||||
|
||||
"player_count_period": {
|
||||
"total": "Total",
|
||||
"d_120": "Past 120 days",
|
||||
@@ -812,7 +826,82 @@
|
||||
"description": "Blitz tank encyclopedia",
|
||||
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz",
|
||||
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health."
|
||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
||||
"sandbox": {
|
||||
"poses": {
|
||||
"hull_down": "Hull down",
|
||||
"face_hug": "Face hug",
|
||||
"default": "Default"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"armor": "Armor",
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
},
|
||||
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
"copy": "Copy"
|
||||
},
|
||||
|
||||
"model": {
|
||||
"name": "Model"
|
||||
},
|
||||
"dynamic": {
|
||||
"name": "Dynamic armor",
|
||||
|
||||
"custom_shell": {
|
||||
"type": "Type",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
"normalization": "Normalization",
|
||||
"ricochet": "Ricochet",
|
||||
"explosion_radius": "Explosion radius",
|
||||
"exit": "Exit",
|
||||
"reset": "Reset"
|
||||
},
|
||||
|
||||
"advanced": "Advanced highlighting",
|
||||
"shooter": "Shooter:",
|
||||
|
||||
"shot_card": {
|
||||
"element": {
|
||||
"primary": "Primary",
|
||||
"spaced": "Spaced",
|
||||
"external": "External",
|
||||
"gap": "Gap",
|
||||
"gun": "Gun",
|
||||
"track": "Tracks"
|
||||
},
|
||||
"status": {
|
||||
"penetration": "%s1hp penetration",
|
||||
"blocked": "Blocked",
|
||||
"ricochet": "Ricochet",
|
||||
"splash": "%s1hp splash"
|
||||
},
|
||||
"stats": {
|
||||
"thickness_and_angle": "%s1mm %s2°",
|
||||
"nominal": "%s1mm nominal",
|
||||
"ricochet_loss": "%s1% penetration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"static": {
|
||||
"name": "Static armor",
|
||||
"primary": "Primary",
|
||||
"spaced": "Spaced",
|
||||
"external": "External",
|
||||
"edit": {
|
||||
"label": "Edit",
|
||||
"reset": "Reset"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"compare": {
|
||||
"name": "Compare",
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Path,
|
||||
Quaternion,
|
||||
} from 'three';
|
||||
import { LocaleProvider, useLocale } from '../../../hooks/useLocale';
|
||||
import {
|
||||
TankopediaEphemeral,
|
||||
type ShotLayerNonExternal,
|
||||
@@ -45,6 +46,7 @@ export function ShotDisplay() {
|
||||
const splashRadiusWrapper = useRef<Group>(null);
|
||||
const splashRadiusMaterial = useRef<MeshBasicMaterial>(null);
|
||||
let animationStartTime: number | null = null;
|
||||
const { locale } = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
if (shot?.splashRadius !== undefined) {
|
||||
@@ -180,7 +182,9 @@ export function ShotDisplay() {
|
||||
mt={tracerGoingUp ? '9' : '0'}
|
||||
pt={tracerGoingUp ? '4' : '0'}
|
||||
>
|
||||
<ShotDisplayCard shot={shot} />
|
||||
<LocaleProvider locale={locale}>
|
||||
<ShotDisplayCard shot={shot} />
|
||||
</LocaleProvider>
|
||||
</Box>
|
||||
</Html>
|
||||
|
||||
|
||||
@@ -8,34 +8,18 @@ import {
|
||||
} from '@radix-ui/themes';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { radToDeg } from 'three/src/math/MathUtils.js';
|
||||
import { literals } from '../../../core/i18n/literals';
|
||||
import { useLocale } from '../../../hooks/useLocale';
|
||||
import type {
|
||||
Shot,
|
||||
ShotLayer,
|
||||
ShotStatus,
|
||||
} from '../../../stores/tankopediaEphemeral';
|
||||
import type { Shot, ShotLayer } from '../../../stores/tankopediaEphemeral';
|
||||
import { shotStatusColors } from './ShotDisplay';
|
||||
import { ArmorType } from './SpacedArmorScene';
|
||||
import type { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
||||
|
||||
export const layerTypeNames: Record<ArmorType | 'null', string> = {
|
||||
[ArmorType.Primary]: 'Primary',
|
||||
[ArmorType.Spaced]: 'Spaced',
|
||||
[ArmorType.External]: 'External',
|
||||
null: 'Gap',
|
||||
};
|
||||
|
||||
const externalLayerNames: Record<ExternalModuleVariant, string> = {
|
||||
gun: 'Gun',
|
||||
track: 'Tracks',
|
||||
};
|
||||
|
||||
const shotStatusNames: Record<ShotStatus, string> = {
|
||||
blocked: 'Blocked',
|
||||
ricochet: 'Ricochet',
|
||||
penetration: 'penetration',
|
||||
splash: 'splash',
|
||||
};
|
||||
export const layerTypeNames = {
|
||||
[ArmorType.Primary]: 'primary',
|
||||
[ArmorType.Spaced]: 'spaced',
|
||||
[ArmorType.External]: 'external',
|
||||
null: 'gap',
|
||||
} as const;
|
||||
|
||||
function LayerEntry({
|
||||
layerIndex,
|
||||
@@ -50,7 +34,7 @@ function LayerEntry({
|
||||
layer: ShotLayer;
|
||||
angle?: number;
|
||||
}) {
|
||||
const { locale } = useLocale();
|
||||
const { locale, strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Flex align="center" gap="2">
|
||||
@@ -64,20 +48,26 @@ function LayerEntry({
|
||||
{layer.type === null && (
|
||||
<>
|
||||
<Text size="2" color={shotStatusColor} style={{ width: 64 }}>
|
||||
{Math.round(layer.distance * 1000).toLocaleString(locale)}
|
||||
<Text size="1">mm</Text>
|
||||
{literals(strings.common.units_suffix.mm, [
|
||||
Math.round(layer.distance * 1000).toLocaleString(locale),
|
||||
])}
|
||||
</Text>
|
||||
|
||||
<Text size="2" color="gray">
|
||||
{Math.max(-100, Math.round(-50 * layer.distance))}% penetration
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
||||
.ricochet_loss,
|
||||
[Math.max(-100, -50 * layer.distance).toFixed(0)],
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{layer.type === ArmorType.External && (
|
||||
<Text size="2" color={shotStatusColor} style={{ width: 64 }}>
|
||||
{Math.round(layer.thickness).toLocaleString(locale)}
|
||||
<Text size="1">mm</Text>
|
||||
{literals(strings.common.units_suffix.mm, [
|
||||
Math.round(layer.thickness).toLocaleString(locale),
|
||||
])}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -85,15 +75,24 @@ function LayerEntry({
|
||||
layer.type === ArmorType.Spaced) && (
|
||||
<>
|
||||
<Text size="2" color={shotStatusColor} style={{ width: 80 }}>
|
||||
{Math.round(layer.thicknessAngled).toLocaleString(locale)}
|
||||
<Text size="1">{`mm${
|
||||
angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°`
|
||||
}`}</Text>
|
||||
{literals(
|
||||
angle === undefined
|
||||
? strings.common.units_suffix.mm
|
||||
: strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
.stats.thickness_and_angle,
|
||||
[
|
||||
Math.round(layer.thicknessAngled).toLocaleString(locale),
|
||||
radToDeg(angle ?? 0).toFixed(0),
|
||||
],
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text size="2" color="gray">
|
||||
{Math.round(layer.thickness).toLocaleString(locale)}
|
||||
<Text size="1">mm</Text> nominal
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats
|
||||
.nominal,
|
||||
[Math.round(layer.thickness).toLocaleString(locale)],
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
@@ -106,7 +105,7 @@ interface ShotDisplayCardProps extends CardProps {
|
||||
}
|
||||
|
||||
export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||
const { locale } = useLocale();
|
||||
const { locale, strings } = useLocale();
|
||||
|
||||
if (!shot) return null;
|
||||
|
||||
@@ -120,21 +119,23 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex direction="column" gap="1">
|
||||
<Text color={inTitleColor} weight="bold">
|
||||
{shot.in.status !== 'blocked' && shot.in.status !== 'ricochet' && (
|
||||
<>
|
||||
{Math.round(shot.damage).toLocaleString(locale)}
|
||||
<Text size="1">hp</Text>
|
||||
</>
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[
|
||||
shot.in.status
|
||||
],
|
||||
[Math.round(shot.damage).toLocaleString(locale)],
|
||||
)}
|
||||
{` ${shotStatusNames[shot.in.status]} `}
|
||||
</Text>
|
||||
|
||||
<Flex direction="column">
|
||||
{shot.in.layers.map((layer, index) => {
|
||||
const layerName =
|
||||
layer.type === ArmorType.External
|
||||
? externalLayerNames[layer.variant]
|
||||
: layerTypeNames[`${layer.type}`];
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
.element[
|
||||
layer.type === ArmorType.External
|
||||
? layer.variant
|
||||
: layerTypeNames[`${layer.type}`]
|
||||
];
|
||||
const layerIndex =
|
||||
layer.type === null
|
||||
? undefined
|
||||
@@ -184,22 +185,22 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
|
||||
</Inset>
|
||||
|
||||
<Text color={outTitleColor} weight="bold">
|
||||
{shot.out.status !== 'blocked' &&
|
||||
shot.out.status !== 'ricochet' && (
|
||||
<>
|
||||
{Math.round(shot.damage).toLocaleString(locale)}
|
||||
<Text size="1">hp</Text>
|
||||
</>
|
||||
)}
|
||||
{` ${shotStatusNames[shot.out.status]} `}
|
||||
{literals(
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
.status[shot.out.status],
|
||||
[Math.round(shot.damage).toLocaleString(locale)],
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Flex direction="column">
|
||||
{shot.out.layers.map((layer, index) => {
|
||||
const layerName =
|
||||
layer.type === ArmorType.External
|
||||
? externalLayerNames[layer.variant]
|
||||
: layerTypeNames[`${layer.type}`];
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.shot_card
|
||||
.element[
|
||||
layer.type === ArmorType.External
|
||||
? layer.variant
|
||||
: layerTypeNames[`${layer.type}`]
|
||||
];
|
||||
const shiftedIndex = index + shot.out!.layers.length;
|
||||
const layerIndex =
|
||||
layer.type === null
|
||||
|
||||
+65
-17
@@ -11,6 +11,8 @@ import {
|
||||
} from '@radix-ui/themes';
|
||||
import { throttle } from 'lodash-es';
|
||||
import { type ChangeEvent } from 'react';
|
||||
import { literals } from '../../../../../../core/i18n/literals';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||
|
||||
@@ -19,6 +21,7 @@ export function CustomShellButton() {
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Popover.Root
|
||||
@@ -49,13 +52,16 @@ export function CustomShellButton() {
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
{/* <Popover.Title>Custom shell</Popover.Title> */}
|
||||
|
||||
{customShell && (
|
||||
<>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>Type</Text>
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.type
|
||||
}
|
||||
</Text>
|
||||
<Select.Root
|
||||
value={`${customShell.type}`}
|
||||
onValueChange={(value) => {
|
||||
@@ -99,16 +105,29 @@ export function CustomShellButton() {
|
||||
<Select.Trigger />
|
||||
|
||||
<Select.Content>
|
||||
<Select.Item value={`${ShellType.AP}`}>AP</Select.Item>
|
||||
<Select.Item value={`${ShellType.APCR}`}>APCR</Select.Item>
|
||||
<Select.Item value={`${ShellType.HEAT}`}>HEAT</Select.Item>
|
||||
<Select.Item value={`${ShellType.HE}`}>HE</Select.Item>
|
||||
<Select.Item value={`${ShellType.AP}`}>
|
||||
{strings.common.shells.ap}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.APCR}`}>
|
||||
{strings.common.shells.apcr}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.HEAT}`}>
|
||||
{strings.common.shells.heat}
|
||||
</Select.Item>
|
||||
<Select.Item value={`${ShellType.HE}`}>
|
||||
{strings.common.shells.he}
|
||||
</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>Caliber</Text>
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.caliber
|
||||
}
|
||||
</Text>
|
||||
<TextField.Root
|
||||
style={{ maxWidth: '6rem' }}
|
||||
defaultValue={customShell.caliber}
|
||||
@@ -131,12 +150,19 @@ export function CustomShellButton() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">mm</TextField.Slot>
|
||||
<TextField.Slot side="right">
|
||||
{literals(strings.common.units_suffix.mm, [''])}
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" justify="between" gap="4">
|
||||
<Text>Penetration</Text>
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.penetration
|
||||
}
|
||||
</Text>
|
||||
<TextField.Root
|
||||
style={{ maxWidth: '6rem' }}
|
||||
defaultValue={customShell.penetration.near}
|
||||
@@ -160,7 +186,9 @@ export function CustomShellButton() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">mm</TextField.Slot>
|
||||
<TextField.Slot side="right">
|
||||
{literals(strings.common.units_suffix.mm, [''])}
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
|
||||
@@ -168,7 +196,12 @@ export function CustomShellButton() {
|
||||
customShell.type === ShellType.APCR) && (
|
||||
<>
|
||||
<Flex align="center" justify="between" gap="4" py="1">
|
||||
<Text>Normalization</Text>
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.normalization
|
||||
}
|
||||
</Text>
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
@@ -178,7 +211,9 @@ export function CustomShellButton() {
|
||||
minWidth="8rem"
|
||||
>
|
||||
<Text color="gray">
|
||||
{Math.round(customShell.normalization ?? 0)}°
|
||||
{literals(strings.common.units_suffix.deg, [
|
||||
(customShell.normalization ?? 0).toFixed(0),
|
||||
])}
|
||||
</Text>
|
||||
<Slider
|
||||
min={0}
|
||||
@@ -197,11 +232,18 @@ export function CustomShellButton() {
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" justify="between" gap="4" py="1">
|
||||
<Text>Ricochet</Text>
|
||||
<Text>
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.ricochet
|
||||
}
|
||||
</Text>
|
||||
|
||||
<Flex align="center" gap="2" flexGrow="1" maxWidth="15rem">
|
||||
<Text color="gray">
|
||||
{Math.round(customShell.ricochet ?? 90)}°
|
||||
{literals(strings.common.units_suffix.deg, [
|
||||
(customShell.ricochet ?? 90).toFixed(0),
|
||||
])}
|
||||
</Text>
|
||||
<Slider
|
||||
min={0}
|
||||
@@ -254,7 +296,10 @@ export function CustomShellButton() {
|
||||
<Flex justify="end" mt="6" gap="2">
|
||||
<Popover.Close>
|
||||
<Button color="red" variant="outline">
|
||||
Exit
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic
|
||||
.custom_shell.exit
|
||||
}
|
||||
</Button>
|
||||
</Popover.Close>
|
||||
<Button
|
||||
@@ -265,7 +310,10 @@ export function CustomShellButton() {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.dynamic.custom_shell
|
||||
.reset
|
||||
}
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
+125
-104
@@ -1,6 +1,8 @@
|
||||
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
||||
import { Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
||||
import { Suspense } from 'react';
|
||||
import { literals } from '../../../../../../core/i18n/literals';
|
||||
import { useLocale } from '../../../../../../hooks/useLocale';
|
||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
|
||||
@@ -24,6 +26,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
|
||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -33,112 +36,144 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
||||
mr="4"
|
||||
style={{ transform: 'translateY(-50%)', userSelect: 'none' }}
|
||||
direction="column"
|
||||
gap="2"
|
||||
gap="5"
|
||||
align="end"
|
||||
>
|
||||
<Flex
|
||||
height="64px"
|
||||
gap="2"
|
||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{(thicknessRange.value * 1.5).toFixed(0)}
|
||||
</Text>
|
||||
<Flex gap="1">
|
||||
<Flex direction="column" align="end" gap="3">
|
||||
<Flex
|
||||
height="64px"
|
||||
gap="2"
|
||||
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.showPrimaryArmor = !draft.showPrimaryArmor;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units_suffix.mm, [
|
||||
(thicknessRange.value * 1.5).toFixed(0),
|
||||
])}
|
||||
</Text>
|
||||
<Text size="1">
|
||||
{strings.website.tools.tankopedia.sandbox.static.primary}
|
||||
</Text>
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units_suffix.mm, ['0'])}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
align="center"
|
||||
width="1rem"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(rgb(64, 0, 0), rgb(255, 0, 0) 33%, rgb(255, 255, 0) 66%, rgb(0, 255, 0))`,
|
||||
color: 'black',
|
||||
}}
|
||||
>
|
||||
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||
<Text size="1">Primary</Text>
|
||||
</Flex>
|
||||
<Text color="gray" size="1">
|
||||
0
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
width="16px"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(rgb(64, 0, 0), rgb(255, 0, 0) 33%, rgb(255, 255, 0) 66%, rgb(0, 255, 0))`,
|
||||
<Flex
|
||||
height="64px"
|
||||
gap="2"
|
||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.showSpacedArmor = !draft.showSpacedArmor;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units_suffix.mm, [
|
||||
thicknessRange.value.toFixed(0),
|
||||
])}
|
||||
</Text>
|
||||
<Text size="1">
|
||||
{strings.website.tools.tankopedia.sandbox.static.spaced}
|
||||
</Text>
|
||||
<Text color="gray" size="1">
|
||||
{literals(strings.common.units_suffix.mm, ['0'])}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
height="64px"
|
||||
gap="2"
|
||||
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.showSpacedArmor = !draft.showSpacedArmor;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text color="gray" size="1">
|
||||
{thicknessRange.value.toFixed(0)}
|
||||
</Text>
|
||||
<Flex gap="1">
|
||||
<Flex
|
||||
align="center"
|
||||
width="1rem"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
|
||||
}}
|
||||
>
|
||||
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||
<Text size="1">Spaced</Text>
|
||||
</Flex>
|
||||
<Text color="gray" size="1">
|
||||
0
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
width="16px"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.showExternalModules = !draft.showExternalModules;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Text size="1">
|
||||
{strings.website.tools.tankopedia.sandbox.static.external}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaPersistent((draft) => {
|
||||
draft.showExternalModules = !draft.showExternalModules;
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" align="end" justify="between">
|
||||
<Flex gap="1">
|
||||
<Flex
|
||||
justify="center"
|
||||
width="1rem"
|
||||
height="1rem"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#c0c0c0',
|
||||
color: 'black',
|
||||
}}
|
||||
>
|
||||
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
|
||||
<Text size="1">External</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" align="end" gap="2">
|
||||
<Flex align="center" gap="2">
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.editStatic = !draft.editStatic;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text size="1">
|
||||
{strings.website.tools.tankopedia.sandbox.static.edit.label}
|
||||
</Text>
|
||||
<Checkbox size="2" checked={editStatic} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box
|
||||
width="16px"
|
||||
height="16px"
|
||||
style={{
|
||||
borderRadius: 'var(--radius-full)',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#c0c0c0',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="2" mt="2">
|
||||
<Button
|
||||
color="red"
|
||||
size="1"
|
||||
@@ -151,22 +186,8 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
||||
}}
|
||||
>
|
||||
<ReloadIcon />
|
||||
Reset
|
||||
{strings.website.tools.tankopedia.sandbox.static.edit.reset}
|
||||
</Button>
|
||||
|
||||
<Flex
|
||||
gap="2"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.editStatic = !draft.editStatic;
|
||||
draft.highlightArmor = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Text size="1">Edit</Text>
|
||||
<Checkbox size="2" checked={editStatic} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Suspense>
|
||||
|
||||
+55
-28
@@ -30,9 +30,11 @@ import {
|
||||
} from '@radix-ui/themes';
|
||||
import { Suspense, useState, type RefObject } from 'react';
|
||||
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
||||
import { literals } from '../../../../../core/i18n/literals';
|
||||
import { useEquipment } from '../../../../../hooks/useEquipment';
|
||||
import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
||||
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
|
||||
import { useLocale } from '../../../../../hooks/useLocale';
|
||||
import { App } from '../../../../../stores/app';
|
||||
import { Duel } from '../../../../../stores/duel';
|
||||
import {
|
||||
@@ -87,6 +89,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -124,13 +127,14 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
>
|
||||
{!hasCustomShell && (
|
||||
<Text color="gray" size={{ initial: '1', sm: '2' }}>
|
||||
{(
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
antagonistShell.type,
|
||||
) * antagonistShell.penetration.near
|
||||
).toFixed(0)}
|
||||
mm
|
||||
{literals(strings.common.units_suffix.mm, [
|
||||
(
|
||||
resolvePenetrationCoefficient(
|
||||
hasCalibratedShells,
|
||||
antagonistShell.type,
|
||||
) * antagonistShell.penetration.near
|
||||
).toFixed(0),
|
||||
])}
|
||||
</Text>
|
||||
)}
|
||||
{antagonistUniqueGuns.length > 1 && (
|
||||
@@ -198,10 +202,11 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
}
|
||||
secondaryDiscriminator={
|
||||
<Text style={{ fontSize: '0.75em' }}>
|
||||
{Math.round(
|
||||
gun.gun_type!.value.base.shells[0].caliber,
|
||||
)}
|
||||
mm
|
||||
{literals(strings.common.units_suffix.mm, [
|
||||
gun.gun_type!.value.base.shells[0].caliber.toFixed(
|
||||
0,
|
||||
),
|
||||
])}
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
@@ -341,13 +346,13 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
>
|
||||
<Checkbox checked={advancedHighlighting} />
|
||||
<Text color="gray" size="2">
|
||||
Advanced highlighting
|
||||
{strings.website.tools.tankopedia.sandbox.dynamic.advanced}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="2">
|
||||
<Text color="gray" size="2">
|
||||
Shooter:
|
||||
{strings.website.tools.tankopedia.sandbox.dynamic.shooter}
|
||||
</Text>
|
||||
<Dialog.Root
|
||||
open={antagonistSelectorOpen}
|
||||
@@ -403,21 +408,27 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
}}
|
||||
>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.Model}`}>
|
||||
<Tooltip content="Model">
|
||||
<Tooltip
|
||||
content={strings.website.tools.tankopedia.sandbox.model.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('jAdYf0m')} style={{ height: '1.25em' }} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.DynamicArmor}`}>
|
||||
<Tooltip content="Dynamic armor">
|
||||
<Tooltip
|
||||
content={strings.website.tools.tankopedia.sandbox.dynamic.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('oe4Cq0g')} style={{ height: '1.25em' }} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</SegmentedControl.Item>
|
||||
<SegmentedControl.Item value={`${TankopediaDisplay.StaticArmor}`}>
|
||||
<Tooltip content="Static armor">
|
||||
<Tooltip
|
||||
content={strings.website.tools.tankopedia.sandbox.static.name}
|
||||
>
|
||||
<Flex height="100%" align="center">
|
||||
<img src={imgur('VQ4uDno')} style={{ height: '1.25em' }} />
|
||||
</Flex>
|
||||
@@ -441,15 +452,15 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Item onClick={() => poseEvent.emit(Pose.HullDown)}>
|
||||
Hull down
|
||||
{strings.website.tools.tankopedia.sandbox.poses.hull_down}
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item onClick={() => poseEvent.emit(Pose.FaceHug)}>
|
||||
Face hug
|
||||
{strings.website.tools.tankopedia.sandbox.poses.face_hug}
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item onClick={() => poseEvent.emit(Pose.Default)}>
|
||||
Default
|
||||
{strings.website.tools.tankopedia.sandbox.poses.default}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
@@ -464,7 +475,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
<DropdownMenu.Content>
|
||||
{display === TankopediaDisplay.DynamicArmor && (
|
||||
<>
|
||||
<DropdownMenu.Label>Armor</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>
|
||||
{strings.website.tools.tankopedia.sandbox.settings.armor}
|
||||
</DropdownMenu.Label>
|
||||
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={greenPenetration}
|
||||
@@ -474,7 +487,10 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Green penetration
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.settings
|
||||
.green_penetration
|
||||
}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
|
||||
<DropdownMenu.CheckboxItem
|
||||
@@ -485,7 +501,10 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Hide tank model under armor
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.settings
|
||||
.hide_model_under_armor
|
||||
}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
|
||||
<DropdownMenu.CheckboxItem
|
||||
@@ -496,7 +515,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Opaque
|
||||
{strings.website.tools.tankopedia.sandbox.settings.opaque}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
|
||||
{developerMode && (
|
||||
@@ -508,13 +527,18 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<b>DEV:</b> Wireframe
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.settings
|
||||
.dev_wireframe
|
||||
}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenu.Label>Environment</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>
|
||||
{strings.website.tools.tankopedia.sandbox.settings.environment}
|
||||
</DropdownMenu.Label>
|
||||
|
||||
<DropdownMenu.CheckboxItem
|
||||
checked={showGrid}
|
||||
@@ -524,7 +548,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Show grid
|
||||
{strings.website.tools.tankopedia.sandbox.settings.show_grid}
|
||||
</DropdownMenu.CheckboxItem>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
@@ -557,7 +581,10 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
}}
|
||||
>
|
||||
<DownloadIcon />
|
||||
Download
|
||||
{
|
||||
strings.website.tools.tankopedia.sandbox.screenshot
|
||||
.download
|
||||
}
|
||||
</Button>
|
||||
</Popover.Close>
|
||||
<Popover.Close>
|
||||
@@ -576,7 +603,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
||||
}}
|
||||
>
|
||||
<CopyIcon />
|
||||
Copy
|
||||
{strings.website.tools.tankopedia.sandbox.screenshot.copy}
|
||||
</Button>
|
||||
</Popover.Close>
|
||||
</Flex>
|
||||
|
||||
Reference in new issue
Block a user