feat: renaming

This commit is contained in:
TrèsAbhi committed 2025-09-01 22:56:39 -05:00
1 parent 8b527f2333
commit 96482d4268
38 files changed
+931 -943

No files matched your search

@@ -1,17 +1,15 @@
import { TIER_ROMAN_NUMERALS } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n/src/literals';
import { Flex, SegmentedControl, Text, type FlexProps } from '@radix-ui/themes';
import { useLocale } from '../../../../../../hooks/useLocale';
import { Duel } from '../../../../../../stores/duel';
import { TIER_ROMAN_NUMERALS } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
import { Flex, SegmentedControl, Text, type FlexProps } from "@radix-ui/themes";
import { useLocale } from "../../../../../../hooks/useLocale";
import { Duel } from "../../../../../../stores/duel";
import {
TankopediaEphemeral,
Tankopedia,
TankopediaRelativeAgainst,
} from '../../../../../../stores/tankopediaEphemeral';
} from "../../../../../../stores/tankopedia";
export function CompareOptions(props: FlexProps) {
const relativeAgainst = TankopediaEphemeral.use(
(state) => state.relativeAgainst,
);
const relativeAgainst = Tankopedia.use((state) => state.relativeAgainst);
const tank = Duel.use((state) => state.protagonist.tank);
const { strings } = useLocale();
@@ -20,7 +18,7 @@ export function CompareOptions(props: FlexProps) {
align="center"
gap="2"
justify="center"
direction={{ initial: 'column', sm: 'row' }}
direction={{ initial: "column", sm: "row" }}
{...props}
>
<Text color="gray">
@@ -32,7 +30,7 @@ export function CompareOptions(props: FlexProps) {
size="1"
value={`${relativeAgainst}`}
onValueChange={(value) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.relativeAgainst = Number(value) as TankopediaRelativeAgainst;
});
}}
@@ -43,13 +41,13 @@ export function CompareOptions(props: FlexProps) {
[
TIER_ROMAN_NUMERALS[tank.tier],
strings.common.tank_class_short_plural_lowercase[tank.class],
],
]
)}
</SegmentedControl.Item>
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.Tier}`}>
{literals(
strings.website.tools.tankopedia.characteristics.compare.tier,
[TIER_ROMAN_NUMERALS[tank.tier]],
[TIER_ROMAN_NUMERALS[tank.tier]]
)}
</SegmentedControl.Item>
<SegmentedControl.Item value={`${TankopediaRelativeAgainst.All}`}>
@@ -1,10 +1,10 @@
import { CaretDownIcon, CaretUpIcon } from '@radix-ui/react-icons';
import { Box, Flex, Text } from '@radix-ui/themes';
import fuzzysort from 'fuzzysort';
import { Quicklime } from 'quicklime';
import { useEffect, useRef, type ReactNode } from 'react';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { Mark } from '../../../../../Mark';
import { CaretDownIcon, CaretUpIcon } from "@radix-ui/react-icons";
import { Box, Flex, Text } from "@radix-ui/themes";
import fuzzysort from "fuzzysort";
import { Quicklime } from "quicklime";
import { useEffect, useRef, type ReactNode } from "react";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { Mark } from "../../../../../Mark";
export const highlightedRows = new Set<HTMLDivElement>();
export const highlightedRowsUpdate = new Quicklime();
@@ -16,7 +16,7 @@ export interface InfoProps {
delta?: number;
decimals?: number;
prefix?: string;
deltaType?: 'higherIsBetter' | 'lowerIsBetter';
deltaType?: "higherIsBetter" | "lowerIsBetter";
}
export function Info({
@@ -26,15 +26,15 @@ export function Info({
delta,
decimals,
prefix,
deltaType = 'higherIsBetter',
deltaType = "higherIsBetter",
}: InfoProps) {
const statSearch = TankopediaEphemeral.use((state) => state.statSearch);
const statSearch = Tankopedia.use((state) => state.statSearch);
const container = useRef<HTMLDivElement>(null);
let label = name;
let highlighted = false;
if (statSearch !== undefined && typeof name === 'string') {
if (statSearch !== undefined && typeof name === "string") {
const result = fuzzysort.single(statSearch, name);
if (result) {
@@ -62,7 +62,7 @@ export function Info({
return (
<Flex
align="center"
pl={indent ? '2' : '0'}
pl={indent ? "2" : "0"}
width="100%"
gap="4"
ref={container}
@@ -77,9 +77,9 @@ export function Info({
<>
<Text
color={
delta * (deltaType === 'lowerIsBetter' ? -1 : 1) > 0
? 'green'
: 'tomato'
delta * (deltaType === "lowerIsBetter" ? -1 : 1) > 0
? "green"
: "tomato"
}
>
{decimals !== undefined
@@ -88,9 +88,9 @@ export function Info({
</Text>
<Text
color={
delta * (deltaType === 'lowerIsBetter' ? -1 : 1) > 0
? 'green'
: 'tomato'
delta * (deltaType === "lowerIsBetter" ? -1 : 1) > 0
? "green"
: "tomato"
}
>
{delta > 0 ? <CaretUpIcon /> : <CaretDownIcon />}
@@ -105,7 +105,7 @@ export function Info({
</Text>
)}
{decimals !== undefined && typeof children === 'number'
{decimals !== undefined && typeof children === "number"
? children.toFixed(decimals)
: children}
</Text>
@@ -1,27 +1,27 @@
import { createDefaultSkills } from '@blitzkit/core';
import { Flex, Progress, Text } from '@radix-ui/themes';
import { clamp } from 'lodash-es';
import { memo, useMemo, type ComponentProps, type ReactNode } from 'react';
import { awaitableEquipmentDefinitions } from '../../../../../../core/awaitables/equipmentDefinitions';
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions';
import { awaitableSkillDefinitions } from '../../../../../../core/awaitables/skillDefinitions';
import { awaitableTankDefinitions } from '../../../../../../core/awaitables/tankDefinitions';
import { createDefaultSkills } from "@blitzkit/core";
import { Flex, Progress, Text } from "@radix-ui/themes";
import { clamp } from "lodash-es";
import { memo, useMemo, type ComponentProps, type ReactNode } from "react";
import { awaitableEquipmentDefinitions } from "../../../../../../core/awaitables/equipmentDefinitions";
import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions";
import { awaitableProvisionDefinitions } from "../../../../../../core/awaitables/provisionDefinitions";
import { awaitableSkillDefinitions } from "../../../../../../core/awaitables/skillDefinitions";
import { awaitableTankDefinitions } from "../../../../../../core/awaitables/tankDefinitions";
import {
tankCharacteristics,
type TankCharacteristics,
} from '../../../../../../core/blitzkit/tankCharacteristics';
import { tankToDuelMember } from '../../../../../../core/blitzkit/tankToDuelMember';
import { useDelta } from '../../../../../../hooks/useDelta';
import { useLocale } from '../../../../../../hooks/useLocale';
import { Duel } from '../../../../../../stores/duel';
} from "../../../../../../core/blitzkit/tankCharacteristics";
import { tankToDuelMember } from "../../../../../../core/blitzkit/tankToDuelMember";
import { useDelta } from "../../../../../../hooks/useDelta";
import { useLocale } from "../../../../../../hooks/useLocale";
import { Duel } from "../../../../../../stores/duel";
import {
TankopediaEphemeral,
Tankopedia,
TankopediaRelativeAgainst,
} from '../../../../../../stores/tankopediaEphemeral';
import { Info, type InfoProps } from './Info';
} from "../../../../../../stores/tankopedia";
import { Info, type InfoProps } from "./Info";
type InfoWithDeltaProps = Omit<InfoProps, 'name'> & {
type InfoWithDeltaProps = Omit<InfoProps, "name"> & {
stats: TankCharacteristics;
noRanking?: boolean;
} & (
@@ -51,20 +51,18 @@ const [
export const InfoWithDelta = memo<InfoWithDeltaProps>(
({ stats, indent, noRanking, deltaType, ...props }) => {
const { strings } = useLocale();
const relativeAgainst = TankopediaEphemeral.use(
(state) => state.relativeAgainst,
);
const relativeAgainst = Tankopedia.use((state) => state.relativeAgainst);
const uhWhatDoICallThisVariable =
typeof props.value === 'function'
typeof props.value === "function"
? props.value(stats)!
: (stats[props.value] as number);
const delta = useDelta(uhWhatDoICallThisVariable);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
const shellIndex = Duel.use((state) =>
state.protagonist.gun.shells.indexOf(state.protagonist.shell),
state.protagonist.gun.shells.indexOf(state.protagonist.shell)
);
const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix,
(state) => state.protagonist.equipmentMatrix
);
const others = useMemo(() => {
const defaultSkills = createDefaultSkills(skillDefinitions);
@@ -77,7 +75,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
tank.class === protagonistTank.class) ||
(relativeAgainst === TankopediaRelativeAgainst.Tier &&
tank.tier === protagonistTank.tier) ||
relativeAgainst === TankopediaRelativeAgainst.All,
relativeAgainst === TankopediaRelativeAgainst.All
)
.map((tank) => {
const member = tankToDuelMember(tank, provisionDefinitions);
@@ -112,14 +110,14 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
equipmentDefinitions,
provisionDefinitions,
tankModelDefinition: modelDefinitions.models[tank.id],
},
}
);
})
.filter((tank) => {
if (tank === undefined) return false;
const othersValue =
typeof props.value === 'function'
typeof props.value === "function"
? props.value(tank)!
: (tank[props.value] as number);
@@ -128,22 +126,22 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
}, [relativeAgainst, shellIndex, equipmentMatrix]);
const betterTanks = others.filter((tank) => {
const othersValue =
typeof props.value === 'function'
typeof props.value === "function"
? props.value(tank)!
: (tank[props.value] as number);
if (othersValue === undefined) return false;
return deltaType === 'lowerIsBetter'
return deltaType === "lowerIsBetter"
? othersValue < uhWhatDoICallThisVariable
: othersValue > uhWhatDoICallThisVariable;
});
const goodness = (others.length - betterTanks.length) / others.length;
let color: ComponentProps<typeof Progress>['color'];
let color: ComponentProps<typeof Progress>["color"];
if (goodness <= 0.25) color = 'red';
else if (goodness <= 0.5) color = 'orange';
else if (goodness <= 0.75) color = 'yellow';
else color = 'green';
if (goodness <= 0.25) color = "red";
else if (goodness <= 0.5) color = "orange";
else if (goodness <= 0.75) color = "yellow";
else color = "green";
return (
<Flex direction="column">
@@ -151,7 +149,7 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
deltaType={deltaType}
indent={indent}
name={
'name' in props
"name" in props
? props.name
: strings.website.tools.tankopedia.characteristics.values[
props.value
@@ -164,17 +162,17 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
</Info>
{!noRanking && (
<Flex pl={indent ? '2' : '0'} align="center" gap="2">
<Flex pl={indent ? "2" : "0"} align="center" gap="2">
<Progress
variant="soft"
size="1"
value={goodness * 100}
color={color}
style={{ height: '0.125rem', opacity: 0.5 }}
style={{ height: "0.125rem", opacity: 0.5 }}
/>
<Text color="gray" size="1">
{clamp(betterTanks.length + 1, 1, others.length)} /{' '}
{clamp(betterTanks.length + 1, 1, others.length)} /{" "}
{others.length}
</Text>
</Flex>
@@ -184,6 +182,6 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
},
(a, b) =>
(typeof a.value === 'function' ? a.value(a.stats) : a.stats[a.value]) ===
(typeof b.value === 'function' ? b.value(b.stats) : b.stats[b.value]),
(typeof a.value === "function" ? a.value(a.stats) : a.stats[a.value]) ===
(typeof b.value === "function" ? b.value(b.stats) : b.stats[b.value])
);
@@ -1,10 +1,10 @@
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Flex, Spinner, TextField, type FlexProps } from '@radix-ui/themes';
import { debounce } from 'lodash-es';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useLocale } from '../../../../../../hooks/useLocale';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { highlightedRows, highlightedRowsUpdate } from './Info';
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
import { Flex, Spinner, TextField, type FlexProps } from "@radix-ui/themes";
import { debounce } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react";
import { useLocale } from "../../../../../../hooks/useLocale";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { highlightedRows, highlightedRowsUpdate } from "./Info";
export function StatSearch(props: FlexProps) {
const { strings } = useLocale();
@@ -19,7 +19,7 @@ export function StatSearch(props: FlexProps) {
setSearching(true);
} else {
setSearching(false);
count.current.textContent = '';
count.current.textContent = "";
}
requestUpdate();
@@ -30,21 +30,21 @@ export function StatSearch(props: FlexProps) {
if (!input.current || !count.current) return;
if (input.current.value.length === 0) {
count.current.textContent = '';
count.current.textContent = "";
setSearching(false);
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.statSearch = undefined;
});
return;
}
setSearching(false);
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.statSearch = input.current!.value;
});
}, 500),
[],
[]
);
useEffect(() => {
@@ -52,7 +52,7 @@ export function StatSearch(props: FlexProps) {
if (!count.current || !input.current) return;
count.current.textContent =
input.current.value.length === 0 ? '' : `0 / ${highlightedRows.size}`;
input.current.value.length === 0 ? "" : `0 / ${highlightedRows.size}`;
};
highlightedRowsUpdate.on(handleHighlightedRowsUpdate);
@@ -68,23 +68,23 @@ export function StatSearch(props: FlexProps) {
ref={input}
placeholder={strings.website.tools.tankopedia.search}
variant="classic"
style={{ width: '100%' }}
style={{ width: "100%" }}
onChange={search}
onKeyDown={(event) => {
if (!count.current) return;
if (event.key === 'Enter') {
if (event.key === "Enter") {
const newIndex = (index + 1) % highlightedRows.size;
count.current.textContent = `${newIndex + 1} / ${highlightedRows.size}`;
const array = [...highlightedRows];
array.forEach((element) => element.classList.remove('focused'));
array.forEach((element) => element.classList.remove("focused"));
if (array[newIndex]) {
array[newIndex].classList.add('focused');
array[newIndex].classList.add("focused");
array[newIndex].scrollIntoView({
block: 'center',
inline: 'center',
block: "center",
inline: "center",
});
}
@@ -1,19 +1,19 @@
import { Flex } from '@radix-ui/themes';
import { useEffect } from 'react';
import { awaitableEquipmentDefinitions } from '../../../../../core/awaitables/equipmentDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelTransformEvent } from '../../../../../core/blitzkit/modelTransform';
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
import { useEquipment } from '../../../../../hooks/useEquipment';
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../../../types/maybeSkeletonComponentProps';
import { Crew } from './components/Crew';
import { Firepower } from './components/Firepower';
import { Maneuverability } from './components/Maneuverability';
import { Survivability } from './components/Survivability';
import { Flex } from "@radix-ui/themes";
import { useEffect } from "react";
import { awaitableEquipmentDefinitions } from "../../../../../core/awaitables/equipmentDefinitions";
import { awaitableProvisionDefinitions } from "../../../../../core/awaitables/provisionDefinitions";
import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits";
import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform";
import { tankCharacteristics } from "../../../../../core/blitzkit/tankCharacteristics";
import { useEquipment } from "../../../../../hooks/useEquipment";
import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition";
import { Duel } from "../../../../../stores/duel";
import { Tankopedia } from "../../../../../stores/tankopedia";
import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps";
import { Crew } from "./components/Crew";
import { Firepower } from "./components/Firepower";
import { Maneuverability } from "./components/Maneuverability";
import { Survivability } from "./components/Survivability";
const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
awaitableEquipmentDefinitions,
@@ -21,18 +21,18 @@ const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
]);
export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const crewSkills = Tankopedia.use((state) => state.skills);
const provisions = Duel.use((state) => state.protagonist.provisions);
const consumables = Duel.use((state) => state.protagonist.consumables);
const camouflage = Duel.use((state) => state.protagonist.camouflage);
const assaultDistance = Duel.use(
(state) => state.protagonist.assaultDistance,
(state) => state.protagonist.assaultDistance
);
const equipmentMatrix = Duel.use(
(state) => state.protagonist.equipmentMatrix,
(state) => state.protagonist.equipmentMatrix
);
const { tank, turret, gun, engine, track, shell } = Duel.use(
(state) => state.protagonist,
(state) => state.protagonist
);
const stockEngine = tank.engines[0];
const stockTrack = tank.tracks[0];
@@ -67,7 +67,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
tankModelDefinition,
equipmentDefinitions: equipmentDefinitions,
provisionDefinitions: provisionDefinitions,
},
}
);
const hasImprovedVerticalStabilizer = useEquipment(122);
@@ -80,7 +80,7 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -89,25 +89,25 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) {
return (
<Flex
gap="8"
direction={{ initial: 'column', md: 'row' }}
align={{ initial: 'center', sm: 'start' }}
width={{ initial: '100%', sm: 'auto' }}
direction={{ initial: "column", md: "row" }}
align={{ initial: "center", sm: "start" }}
width={{ initial: "100%", sm: "auto" }}
>
<Firepower skeleton={skeleton} stats={stats} />
<Flex
gap="8"
direction={{ initial: 'column', lg: 'row' }}
align={{ initial: 'center', sm: 'start' }}
width={{ initial: '100%', sm: 'auto' }}
direction={{ initial: "column", lg: "row" }}
align={{ initial: "center", sm: "start" }}
width={{ initial: "100%", sm: "auto" }}
>
<Maneuverability stats={stats} />
<Flex
gap="8"
direction="column"
align={{ initial: 'center', sm: 'start' }}
width={{ initial: '100%', sm: 'auto' }}
align={{ initial: "center", sm: "start" }}
width={{ initial: "100%", sm: "auto" }}
>
<Survivability stats={stats} />
<Crew stats={stats} />
@@ -1,4 +1,4 @@
import { InfoCircledIcon } from '@radix-ui/react-icons';
import { InfoCircledIcon } from "@radix-ui/react-icons";
import {
Button,
Flex,
@@ -6,14 +6,14 @@ import {
IconButton,
Popover,
Text,
} from '@radix-ui/themes';
import { useLocale } from '../../../../hooks/useLocale';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import { CrewSkillManager } from '../../../CrewSkillManager';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
} from "@radix-ui/themes";
import { useLocale } from "../../../../hooks/useLocale";
import { Tankopedia } from "../../../../stores/tankopedia";
import { CrewSkillManager } from "../../../CrewSkillManager";
import { ConfigurationChildWrapper } from "./ConfigurationChildWrapper";
export function Skills() {
const skillLevels = TankopediaEphemeral.use((state) => state.skills);
const skillLevels = Tankopedia.use((state) => state.skills);
const { strings } = useLocale();
return (
@@ -45,7 +45,7 @@ export function Skills() {
variant="ghost"
color="red"
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
Object.keys(draft.skills).forEach((skill) => {
draft.skills[skill] = 0;
});
@@ -57,7 +57,7 @@ export function Skills() {
<Button
variant="ghost"
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
Object.keys(draft.skills).forEach((skill) => {
draft.skills[skill] = 7;
});
@@ -71,7 +71,7 @@ export function Skills() {
<CrewSkillManager
skillLevels={skillLevels}
onChange={(skills) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.skills = skills;
});
}}
@@ -1,5 +1,5 @@
import { ShellType } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n/src/literals';
import { ShellType } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
import {
Button,
Flex,
@@ -9,15 +9,15 @@ import {
Slider,
Text,
TextField,
} from '@radix-ui/themes';
import { throttle } from 'lodash-es';
import { type ChangeEvent } from 'react';
import { useLocale } from '../../../../../../hooks/useLocale';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
} from "@radix-ui/themes";
import { throttle } from "lodash-es";
import { type ChangeEvent } from "react";
import { useLocale } from "../../../../../../hooks/useLocale";
import { Duel } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
export function CustomShellButton() {
const customShell = TankopediaEphemeral.use((state) => state.customShell);
const customShell = Tankopedia.use((state) => state.customShell);
const gun = Duel.use((state) => state.protagonist.gun);
const antagonistShell = Duel.use((state) => state.antagonist.shell);
const { strings } = useLocale();
@@ -27,24 +27,24 @@ export function CustomShellButton() {
onOpenChange={(open) => {
if (!open || customShell) return;
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell = antagonistShell;
});
}}
>
<Popover.Trigger>
<IconButton
color={customShell ? undefined : 'gray'}
color={customShell ? undefined : "gray"}
variant="soft"
size={{ initial: '2', sm: '3' }}
size={{ initial: "2", sm: "3" }}
radius="none"
>
<img
alt="custom shell"
src="/assets/images/icons/custom-shell.png"
style={{
width: '50%',
height: '50%',
width: "50%",
height: "50%",
}}
/>
</IconButton>
@@ -90,13 +90,13 @@ export function CustomShellButton() {
modified.ricochet = undefined;
modified.explosion_radius =
gun.shells.find(
(shell) => shell.type === ShellType.HE,
(shell) => shell.type === ShellType.HE
)?.explosion_radius ?? 1;
break;
}
}
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell = modified;
});
}}
@@ -129,11 +129,11 @@ export function CustomShellButton() {
</Text>
<TextField.Root
variant="classic"
style={{ maxWidth: '6rem' }}
style={{ maxWidth: "6rem" }}
defaultValue={customShell.caliber}
type="number"
onChange={(event) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell!.caliber = event.target.valueAsNumber;
});
}}
@@ -141,9 +141,9 @@ export function CustomShellButton() {
onKeyDown={(event) => {
event.stopPropagation();
if (event.key === 'Enter') {
if (event.key === "Enter") {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
} else if (event.key === "Escape") {
(event.target as HTMLInputElement).valueAsNumber =
customShell.caliber;
(event.target as HTMLInputElement).blur();
@@ -151,7 +151,7 @@ export function CustomShellButton() {
}}
>
<TextField.Slot side="right">
{literals(strings.common.units.mm, [''])}
{literals(strings.common.units.mm, [""])}
</TextField.Slot>
</TextField.Root>
</Flex>
@@ -165,11 +165,11 @@ export function CustomShellButton() {
</Text>
<TextField.Root
variant="classic"
style={{ maxWidth: '6rem' }}
style={{ maxWidth: "6rem" }}
defaultValue={customShell.penetration.near}
type="number"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell!.penetration.near =
event.target.valueAsNumber;
});
@@ -178,9 +178,9 @@ export function CustomShellButton() {
onKeyDown={(event) => {
event.stopPropagation();
if (event.key === 'Enter') {
if (event.key === "Enter") {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
} else if (event.key === "Escape") {
(event.target as HTMLInputElement).valueAsNumber =
customShell.penetration.near;
(event.target as HTMLInputElement).blur();
@@ -188,7 +188,7 @@ export function CustomShellButton() {
}}
>
<TextField.Slot side="right">
{literals(strings.common.units.mm, [''])}
{literals(strings.common.units.mm, [""])}
</TextField.Slot>
</TextField.Root>
</Flex>
@@ -223,9 +223,9 @@ export function CustomShellButton() {
step={Number.EPSILON}
defaultValue={[customShell.normalization ?? 0]}
onValueChange={([value]) => {
console.log('asd');
console.log("asd");
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell!.normalization = value;
});
}}
@@ -254,7 +254,7 @@ export function CustomShellButton() {
step={Number.EPSILON}
defaultValue={[customShell.ricochet ?? 90]}
onValueChange={throttle(([value]) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell!.ricochet = value;
});
}, 1000)}
@@ -269,11 +269,11 @@ export function CustomShellButton() {
<Text>Explosion radius</Text>
<TextField.Root
variant="classic"
style={{ maxWidth: '6rem' }}
style={{ maxWidth: "6rem" }}
defaultValue={customShell.explosion_radius ?? 0}
type="number"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell!.explosion_radius =
event.target.valueAsNumber;
});
@@ -282,9 +282,9 @@ export function CustomShellButton() {
onKeyDown={(event) => {
event.stopPropagation();
if (event.key === 'Enter') {
if (event.key === "Enter") {
(event.target as HTMLInputElement).blur();
} else if (event.key === 'Escape') {
} else if (event.key === "Escape") {
(event.target as HTMLInputElement).valueAsNumber =
customShell.explosion_radius ?? 1;
(event.target as HTMLInputElement).blur();
@@ -309,7 +309,7 @@ export function CustomShellButton() {
<Button
color="red"
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell = antagonistShell;
});
}}
@@ -1,14 +1,14 @@
import { asset } from '@blitzkit/core';
import { IconButton } from '@radix-ui/themes';
import { useArmor } from '../../../../../../hooks/useArmor';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { asset } from "@blitzkit/core";
import { IconButton } from "@radix-ui/themes";
import { useArmor } from "../../../../../../hooks/useArmor";
import { Duel } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
export function DynamicArmorSwitcher() {
const tank = Duel.use((state) => state.protagonist.tank);
const mutateDuel = Duel.useMutation();
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
state.protagonist.consumables.includes(73)
);
const { hasDynamicArmor } = useArmor(tank.id);
@@ -16,9 +16,9 @@ export function DynamicArmorSwitcher() {
return (
<IconButton
color={isDynamicArmorActive ? undefined : 'gray'}
color={isDynamicArmorActive ? undefined : "gray"}
variant="soft"
size={{ initial: '2', sm: '3' }}
size={{ initial: "2", sm: "3" }}
onClick={() => {
mutateDuel((draft) => {
if (draft.protagonist.consumables.includes(73)) {
@@ -32,17 +32,17 @@ export function DynamicArmorSwitcher() {
}
}
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt="Dynamic Armor"
src={asset('icons/consumables/73.webp')}
src={asset("icons/consumables/73.webp")}
style={{
width: '50%',
height: '50%',
width: "50%",
height: "50%",
}}
/>
</IconButton>
@@ -1,44 +1,44 @@
import { literals } from '@blitzkit/i18n/src/literals';
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
import { Button, Checkbox, Flex, Text } from '@radix-ui/themes';
import { Suspense } from 'react';
import { useLocale } from '../../../../../../hooks/useLocale';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
import type { MaybeSkeletonComponentProps } from '../../../../../../types/maybeSkeletonComponentProps';
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
import { DynamicArmorSwitcher } from './DynamicArmorSwitcher';
import { literals } from "@blitzkit/i18n/src/literals";
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from "@radix-ui/react-icons";
import { Button, Checkbox, Flex, Text } from "@radix-ui/themes";
import { Suspense } from "react";
import { useLocale } from "../../../../../../hooks/useLocale";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps";
import type { ThicknessRange } from "../../../../../Armor/components/StaticArmor";
import { DynamicArmorSwitcher } from "./DynamicArmorSwitcher";
type ThicknessesProps = MaybeSkeletonComponentProps & {
thicknessRange: ThicknessRange;
};
export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
const display = TankopediaEphemeral.use((state) => state.display);
const display = Tankopedia.use((state) => state.display);
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
(state) => state.showExternalModules
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.showSpacedArmor,
(state) => state.showSpacedArmor
);
const showPrimaryArmor = TankopediaPersistent.use(
(state) => state.showPrimaryArmor,
(state) => state.showPrimaryArmor
);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
const editStatic = Tankopedia.use((state) => state.editStatic);
const { strings } = useLocale();
return (
<Flex
position="absolute"
right={display === TankopediaDisplay.StaticArmor ? '0' : '-6rem'}
right={display === TankopediaDisplay.StaticArmor ? "0" : "-6rem"}
top="50%"
mr="4"
style={{
transform: 'translateY(-50%)',
userSelect: 'none',
transitionDuration: '200ms',
transform: "translateY(-50%)",
userSelect: "none",
transitionDuration: "200ms",
}}
direction="column"
gap="5"
@@ -48,12 +48,12 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
<Flex
height="64px"
gap="2"
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: "pointer" }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.showPrimaryArmor = !draft.showPrimaryArmor;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}}
@@ -68,7 +68,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
{strings.website.tools.tankopedia.sandbox.static.primary}
</Text>
<Text color="gray" size="1">
{literals(strings.common.units.mm, ['0'])}
{literals(strings.common.units.mm, ["0"])}
</Text>
</Flex>
@@ -76,10 +76,10 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
align="center"
width="1rem"
style={{
borderRadius: 'var(--radius-full)',
overflow: 'hidden',
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',
color: "black",
}}
>
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
@@ -89,12 +89,12 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
<Flex
height="64px"
gap="2"
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: "pointer" }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.showSpacedArmor = !draft.showSpacedArmor;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}}
@@ -109,7 +109,7 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
{strings.website.tools.tankopedia.sandbox.static.spaced}
</Text>
<Text color="gray" size="1">
{literals(strings.common.units.mm, ['0'])}
{literals(strings.common.units.mm, ["0"])}
</Text>
</Flex>
@@ -117,8 +117,8 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
align="center"
width="1rem"
style={{
borderRadius: 'var(--radius-full)',
overflow: 'hidden',
borderRadius: "var(--radius-full)",
overflow: "hidden",
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
}}
>
@@ -128,12 +128,12 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
<Flex
gap="2"
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: "pointer" }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.showExternalModules = !draft.showExternalModules;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}}
@@ -149,10 +149,10 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
width="1rem"
height="1rem"
style={{
borderRadius: 'var(--radius-full)',
overflow: 'hidden',
backgroundColor: '#c0c0c0',
color: 'black',
borderRadius: "var(--radius-full)",
overflow: "hidden",
backgroundColor: "#c0c0c0",
color: "black",
}}
>
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
@@ -164,9 +164,9 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
<Flex align="center" gap="2">
<Flex
gap="2"
style={{ cursor: 'pointer' }}
style={{ cursor: "pointer" }}
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.editStatic = !draft.editStatic;
draft.highlightArmor = undefined;
});
@@ -184,9 +184,9 @@ export function Thicknesses({ thicknessRange, skeleton }: ThicknessesProps) {
size="1"
variant="ghost"
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.editStatic = false;
draft.model = TankopediaEphemeral.initial.model;
draft.model = Tankopedia.initial.model;
});
}}
>
@@ -3,8 +3,8 @@ import {
resolvePenetrationCoefficient,
TIER_ROMAN_NUMERALS,
uniqueGuns,
} from '@blitzkit/core';
import { literals } from '@blitzkit/i18n/src/literals';
} from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
import {
CameraIcon,
CopyIcon,
@@ -12,7 +12,7 @@ import {
EnterFullScreenIcon,
ExitFullScreenIcon,
EyeOpenIcon,
} from '@radix-ui/react-icons';
} from "@radix-ui/react-icons";
import {
Button,
Checkbox,
@@ -25,27 +25,27 @@ import {
Tabs,
Text,
Tooltip,
} from '@radix-ui/themes';
import { Suspense, useState, type RefObject } from 'react';
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
import { useEquipment } from '../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../hooks/useFullScreen';
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
import { useLocale } from '../../../../../hooks/useLocale';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
import type { MaybeSkeletonComponentProps } from '../../../../../types/maybeSkeletonComponentProps';
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
import { ModuleButton } from '../../../../ModuleButtons/ModuleButton';
import { SmallTankIcon } from '../../../../SmallTankIcon';
import { TankSearch } from '../../../../TankSearch';
import { screenshotReadyEvent } from '../TankSandbox/components/SceneProps';
import { CustomShellButton } from './components/CustomShellButton';
import { DynamicArmorSwitcher } from './components/DynamicArmorSwitcher';
import { QuickInputs } from './components/QuickInputs';
import { Thicknesses } from './components/Thicknesses';
} from "@radix-ui/themes";
import { Suspense, useState, type RefObject } from "react";
import { Pose, poseEvent } from "../../../../../core/blitzkit/pose";
import { useEquipment } from "../../../../../hooks/useEquipment";
import { useFullScreen } from "../../../../../hooks/useFullScreen";
import { useFullscreenAvailability } from "../../../../../hooks/useFullscreenAvailability";
import { useLocale } from "../../../../../hooks/useLocale";
import { Duel } from "../../../../../stores/duel";
import { Tankopedia } from "../../../../../stores/tankopedia";
import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent";
import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants";
import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps";
import type { ThicknessRange } from "../../../../Armor/components/StaticArmor";
import { ModuleButton } from "../../../../ModuleButtons/ModuleButton";
import { SmallTankIcon } from "../../../../SmallTankIcon";
import { TankSearch } from "../../../../TankSearch";
import { screenshotReadyEvent } from "../TankSandbox/components/SceneProps";
import { CustomShellButton } from "./components/CustomShellButton";
import { DynamicArmorSwitcher } from "./components/DynamicArmorSwitcher";
import { QuickInputs } from "./components/QuickInputs";
import { Thicknesses } from "./components/Thicknesses";
type OptionsProps = MaybeSkeletonComponentProps & {
thicknessRange: ThicknessRange;
@@ -53,13 +53,13 @@ type OptionsProps = MaybeSkeletonComponentProps & {
};
export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
const hasCustomShell = TankopediaEphemeral.use(
(state) => state.customShell !== undefined,
const hasCustomShell = Tankopedia.use(
(state) => state.customShell !== undefined
);
const display = TankopediaEphemeral.use((state) => state.display);
const display = Tankopedia.use((state) => state.display);
const isFullScreen = useFullScreen();
const advancedHighlighting = TankopediaPersistent.use(
(state) => state.advancedHighlighting,
(state) => state.advancedHighlighting
);
const fullScreenAvailable = useFullscreenAvailability(true);
const protagonistTank = Duel.use((state) => state.protagonist.tank);
@@ -68,14 +68,14 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
const antagonistTank = Duel.use((state) => state.antagonist.tank);
const hasCalibratedShells = useEquipment(103, true);
const [tab, setTab] = useState('search');
const [tab, setTab] = useState("search");
const mutateDuel = Duel.useMutation();
const hasEnhancedArmor = useEquipment(110);
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const { strings, unwrap } = useLocale();
const revealed = TankopediaEphemeral.use((state) => state.revealed);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const revealed = Tankopedia.use((state) => state.revealed);
const disturbed = Tankopedia.use((state) => state.disturbed);
return (
<>
@@ -87,21 +87,21 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
gap="2"
direction="column"
top="50%"
right={display === TankopediaDisplay.DynamicArmor ? '3' : '-4rem'}
right={display === TankopediaDisplay.DynamicArmor ? "3" : "-4rem"}
style={{
position: 'absolute',
transform: 'translateY(-50%)',
transitionDuration: '200ms',
position: "absolute",
transform: "translateY(-50%)",
transitionDuration: "200ms",
}}
align="end"
>
{!hasCustomShell && (
<Text color="gray" size={{ initial: '1', sm: '2' }}>
<Text color="gray" size={{ initial: "1", sm: "2" }}>
{literals(strings.common.units.mm, [
(
resolvePenetrationCoefficient(
hasCalibratedShells,
antagonistShell.type,
antagonistShell.type
) * antagonistShell.penetration.near
).toFixed(0),
])}
@@ -115,14 +115,14 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
radius="full"
variant="soft"
color="gray"
size={{ initial: '2', sm: '3' }}
style={{ position: 'relative' }}
size={{ initial: "2", sm: "3" }}
style={{ position: "relative" }}
>
<Text
size="1"
color="gray"
style={{
position: 'absolute',
position: "absolute",
right: 0,
bottom: 0,
}}
@@ -134,11 +134,11 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<img
alt="Antagonist Gun"
src={asset('icons/modules/gun.webp')}
src={asset("icons/modules/gun.webp")}
style={{
width: '65%',
height: '65%',
objectFit: 'contain',
width: "65%",
height: "65%",
objectFit: "contain",
}}
/>
</IconButton>
@@ -160,7 +160,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
selected={gun.id === antagonistGun.id}
discriminator={TIER_ROMAN_NUMERALS[gun.tier]}
secondaryDiscriminator={
<Text style={{ fontSize: '0.75em' }}>
<Text style={{ fontSize: "0.75em" }}>
{literals(strings.common.units.mm, [
gun.shells[0].caliber.toFixed(0),
])}
@@ -176,7 +176,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<Flex
direction="column"
style={{
borderRadius: 'var(--radius-full)',
borderRadius: "var(--radius-full)",
}}
overflow="hidden"
>
@@ -185,17 +185,17 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
color={
thisShell.id === antagonistShell.id && !hasCustomShell
? undefined
: 'gray'
: "gray"
}
variant="soft"
key={thisShell.id}
size={{ initial: '2', sm: '3' }}
size={{ initial: "2", sm: "3" }}
radius="none"
onClick={() => {
mutateDuel((draft) => {
draft.antagonist.shell = thisShell;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.customShell = undefined;
});
@@ -205,8 +205,8 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
alt={unwrap(thisShell.name)}
src={asset(`icons/shells/${thisShell.icon}.webp`)}
style={{
width: '50%',
height: '50%',
width: "50%",
height: "50%",
}}
/>
</IconButton>
@@ -218,15 +218,15 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<Flex
direction="column"
style={{
pointerEvents: 'auto',
borderRadius: 'var(--radius-full)',
pointerEvents: "auto",
borderRadius: "var(--radius-full)",
}}
overflow="hidden"
>
<IconButton
color={hasCalibratedShells ? undefined : 'gray'}
color={hasCalibratedShells ? undefined : "gray"}
variant="soft"
size={{ initial: '2', sm: '3' }}
size={{ initial: "2", sm: "3" }}
radius="none"
onClick={() => {
mutateDuel((draft) => {
@@ -234,24 +234,24 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
? 0
: 1;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt="Calibrated Shells"
src={asset('icons/equipment/103.webp')}
src={asset("icons/equipment/103.webp")}
style={{
width: '50%',
height: '50%',
width: "50%",
height: "50%",
}}
/>
</IconButton>
<IconButton
color={hasEnhancedArmor ? undefined : 'gray'}
color={hasEnhancedArmor ? undefined : "gray"}
variant="soft"
size={{ initial: '2', sm: '3' }}
size={{ initial: "2", sm: "3" }}
radius="none"
onClick={() => {
mutateDuel((draft) => {
@@ -259,17 +259,17 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
? 0
: -1;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
});
}}
>
<img
alt="Enhanced Armor"
src={asset('icons/equipment/110.webp')}
src={asset("icons/equipment/110.webp")}
style={{
width: '50%',
height: '50%',
width: "50%",
height: "50%",
}}
/>
</IconButton>
@@ -286,22 +286,22 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
direction="column"
align="center"
position="absolute"
bottom={revealed ? '4' : '-100%'}
bottom={revealed ? "4" : "-100%"}
left="50%"
style={{ transform: 'translateX(-50%)', transitionDuration: '200ms' }}
style={{ transform: "translateX(-50%)", transitionDuration: "200ms" }}
>
<Flex
direction="column"
align="center"
style={{ transitionDuration: '200ms' }}
style={{ transitionDuration: "200ms" }}
position="relative"
bottom={display === TankopediaDisplay.DynamicArmor ? '0' : '-7rem'}
bottom={display === TankopediaDisplay.DynamicArmor ? "0" : "-7rem"}
>
<Flex
align="center"
gap="2"
mb="1"
style={{ cursor: 'pointer' }}
style={{ cursor: "pointer" }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.advancedHighlighting = !draft.advancedHighlighting;
@@ -338,7 +338,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<Tabs.Root
value={tab}
onValueChange={setTab}
style={{ position: 'relative' }}
style={{ position: "relative" }}
>
<TankSearch
compact
@@ -366,7 +366,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
variant="classic"
value={`${disturbed ? display : -1}`}
onValueChange={(value) => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.disturbed = true;
draft.display = Number(value);
});
@@ -379,7 +379,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<Flex height="100%" align="center">
<img
src="/assets/images/icons/tankopedia-model.png"
style={{ height: '1.25em' }}
style={{ height: "1.25em" }}
/>
</Flex>
</Tooltip>
@@ -391,7 +391,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<Flex height="100%" align="center">
<img
src="/assets/images/icons/tankopedia-dynamic-armor.png"
style={{ height: '1.25em' }}
style={{ height: "1.25em" }}
/>
</Flex>
</Tooltip>
@@ -403,7 +403,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<Flex height="100%" align="center">
<img
src="/assets/images/icons/tankopedia-static-armor.png"
style={{ height: '1.25em' }}
style={{ height: "1.25em" }}
/>
</Flex>
</Tooltip>
@@ -457,15 +457,15 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
requestAnimationFrame(() => {
if (!canvas.current) return;
const anchor = document.createElement('a');
const anchor = document.createElement("a");
anchor.setAttribute(
'download',
`${protagonistTank.name}.png`,
"download",
`${protagonistTank.name}.png`
);
anchor.setAttribute(
'href',
canvas.current.toDataURL('image/png'),
"href",
canvas.current.toDataURL("image/png")
);
anchor.click();
@@ -493,7 +493,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
if (!blob) return;
navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob }),
new ClipboardItem({ "image/png": blob }),
]);
});
@@ -1,10 +1,10 @@
import { Box } from '@radix-ui/themes';
import { Var } from '../../../../../core/radix/var';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import './index.css';
import { Box } from "@radix-ui/themes";
import { Var } from "../../../../../core/radix/var";
import { Tankopedia } from "../../../../../stores/tankopedia";
import "./index.css";
export function ScrollHint() {
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const disturbed = Tankopedia.use((state) => state.disturbed);
if (disturbed) return null;
@@ -17,9 +17,9 @@ export function ScrollHint() {
height="2rem"
width="1rem"
style={{
borderRadius: '0.5rem',
outline: `2px solid ${Var('gray-11')}`,
transform: 'translateX(-50%)',
borderRadius: "0.5rem",
outline: `2px solid ${Var("gray-11")}`,
transform: "translateX(-50%)",
}}
>
<Box
@@ -28,8 +28,8 @@ export function ScrollHint() {
height="0.75rem"
m="0.125rem"
style={{
borderRadius: '50%',
backgroundColor: Var('gray-12'),
borderRadius: "50%",
backgroundColor: Var("gray-12"),
}}
/>
</Box>
@@ -1,17 +1,17 @@
import { Flex, Heading } from '@radix-ui/themes';
import { useLocale } from '../../../../../../hooks/useLocale';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { Tracker } from './components/Tracker';
import { Flex, Heading } from "@radix-ui/themes";
import { useLocale } from "../../../../../../hooks/useLocale";
import { Duel } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { Tracker } from "./components/Tracker";
export function Title() {
const { unwrap } = useLocale();
const protagonist = Duel.use((state) => state.protagonist.tank);
const revealed = TankopediaEphemeral.use((state) => state.revealed);
const revealed = Tankopedia.use((state) => state.revealed);
const name = unwrap(protagonist.name);
const fontSize = revealed
? revealed
? '2rem'
? "2rem"
: `min(16vh, ${125 / name.length}vw)`
: `min(12vh, ${75 / name.length}vw)`;
@@ -20,7 +20,7 @@ export function Title() {
onPointerDown={(event) => {
event.preventDefault();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.revealed = true;
});
}}
@@ -28,27 +28,27 @@ export function Title() {
position="absolute"
align="center"
justify="center"
top={revealed ? '6rem' : '50%'}
top={revealed ? "6rem" : "50%"}
left="50%"
width="100%"
height={revealed ? fontSize : '100%'}
height={revealed ? fontSize : "100%"}
style={{
pointerEvents: revealed ? 'none' : undefined,
transitionDuration: '1s',
transform: 'translate(-50%, -50%)',
pointerEvents: revealed ? "none" : undefined,
transitionDuration: "1s",
transform: "translate(-50%, -50%)",
}}
>
<Heading
style={{
fontWeight: 900,
userSelect: 'none',
pointerEvents: 'none',
userSelect: "none",
pointerEvents: "none",
fontSize,
whiteSpace: 'nowrap',
whiteSpace: "nowrap",
opacity: revealed ? 1 : 0.5,
letterSpacing: revealed || !revealed ? 0 : '-0.03em',
letterSpacing: revealed || !revealed ? 0 : "-0.03em",
transition: `
letter-spacing 1.5s ${revealed ? '' : 'cubic-bezier(0.81, -2, 0.68, 1)'},
letter-spacing 1.5s ${revealed ? "" : "cubic-bezier(0.81, -2, 0.68, 1)"},
font-size 1s,
-webkit-text-stroke 2s,
opacity 1s
@@ -1,15 +1,15 @@
import { I_HAT, J_HAT } from '@blitzkit/core';
import { OrbitControls } from '@react-three/drei';
import { invalidate, useFrame, useThree } from '@react-three/fiber';
import { useEffect, useRef } from 'react';
import { PerspectiveCamera, Vector3 } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { I_HAT, J_HAT } from "@blitzkit/core";
import { OrbitControls } from "@react-three/drei";
import { invalidate, useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react";
import { PerspectiveCamera, Vector3 } from "three";
import { OrbitControls as OrbitControlsClass } from "three-stdlib";
import { awaitableModelDefinitions } from "../../../../../../core/awaitables/modelDefinitions";
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
import { Pose, poseEvent } from "../../../../../../core/blitzkit/pose";
import { Duel } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
const poseDistances: Record<Pose, number> = {
[Pose.HullDown]: 15,
@@ -56,23 +56,23 @@ export function Controls({
const protagonistHullOrigin = new Vector3(
protagonistTrackModelDefinition.origin.x,
protagonistTrackModelDefinition.origin.y,
-protagonistTrackModelDefinition.origin.z,
-protagonistTrackModelDefinition.origin.z
);
const protagonistTurretOrigin = new Vector3(
protagonistModelDefinition.turret_origin.x,
protagonistModelDefinition.turret_origin.y,
-protagonistModelDefinition.turret_origin.z,
-protagonistModelDefinition.turret_origin.z
);
const protagonistGunOrigin = new Vector3(
protagonistTurretModelDefinition.gun_origin.x,
protagonistTurretModelDefinition.gun_origin.y,
-protagonistTurretModelDefinition.gun_origin.z,
-protagonistTurretModelDefinition.gun_origin.z
);
const antagonistGunHeight =
protagonistTrackModelDefinition.origin.y +
antagonistModelDefinition.turret_origin.y +
antagonistTurretModelDefinition.gun_origin.y;
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const disturbed = Tankopedia.use((state) => state.disturbed);
const doAutoRotate = autoRotate && !disturbed;
const gunHeight =
protagonistHullOrigin.y +
@@ -81,7 +81,7 @@ export function Controls({
const inspectModeInitialPosition = new Vector3(
-8,
naked ? gunHeight + 10 : 2,
-13,
-13
);
const t0 = useRef(Date.now() / 1000);
@@ -100,11 +100,11 @@ export function Controls({
});
useEffect(() => {
const unsubscribeTankopediaEphemeral = TankopediaEphemeral.on(
const unsubscribeTankopediaEphemeral = Tankopedia.on(
(state) => state.controlsEnabled,
(enabled) => {
if (orbitControls.current) orbitControls.current.enabled = enabled;
},
}
);
function handlePoseEvent(event: Pose) {
@@ -112,12 +112,12 @@ export function Controls({
const hasImprovedVerticalStabilizer = hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
switch (event) {
@@ -128,7 +128,7 @@ export function Controls({
protagonistGunModelDefinition.pitch,
protagonistTurretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
camera.position
@@ -140,14 +140,14 @@ export function Controls({
new Vector3(
0,
poseDistances[event] * Math.sin(pitch),
poseDistances[event] * -Math.cos(pitch),
),
poseDistances[event] * -Math.cos(pitch)
)
);
camera.lookAt(
protagonistHullOrigin
.clone()
.add(protagonistTurretOrigin)
.add(protagonistGunOrigin),
.add(protagonistGunOrigin)
);
orbitControls.current?.target.set(0, antagonistGunHeight, 0);
@@ -161,7 +161,7 @@ export function Controls({
protagonistGunModelDefinition.pitch,
protagonistTurretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
camera.position
@@ -173,8 +173,8 @@ export function Controls({
new Vector3(
0,
poseDistances[event] * Math.sin(pitch),
poseDistances[event] * -Math.cos(pitch),
),
poseDistances[event] * -Math.cos(pitch)
)
);
orbitControls.current?.target
.set(0, 0, 0)
@@ -185,8 +185,8 @@ export function Controls({
new Vector3(
0,
0.5 * poseDistances[event] * Math.sin(pitch),
0.5 * poseDistances[event] * -Math.cos(pitch),
),
0.5 * poseDistances[event] * -Math.cos(pitch)
)
);
break;
@@ -220,15 +220,15 @@ export function Controls({
}
function disturb() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.disturbed = true;
});
}
poseEvent.on(disturb);
canvas.addEventListener('pointerdown', disturb);
canvas.addEventListener('wheel', handleWheel);
document.body.addEventListener('scroll', handleScroll);
canvas.addEventListener("pointerdown", disturb);
canvas.addEventListener("wheel", handleWheel);
document.body.addEventListener("scroll", handleScroll);
function updateCamera() {
if (!orbitControls.current) return;
@@ -239,7 +239,7 @@ export function Controls({
orbitControls.current.target.set(
0,
gunHeight * (naked ? 1 / 4 : 3 / 4),
0,
0
);
orbitControls.current.enablePan = true;
orbitControls.current.enableZoom = true;
@@ -251,10 +251,10 @@ export function Controls({
updateCamera();
return () => {
canvas.removeEventListener('pointerdown', disturb);
canvas.removeEventListener("pointerdown", disturb);
poseEvent.off(disturb);
canvas.removeEventListener('wheel', handleWheel);
document.body.removeEventListener('scroll', handleScroll);
canvas.removeEventListener("wheel", handleWheel);
document.body.removeEventListener("scroll", handleScroll);
};
}, []);
@@ -266,7 +266,7 @@ export function Controls({
enableZoom={zoomable}
zoomSpeed={zoomable ? undefined : 0}
ref={orbitControls}
enabled={TankopediaEphemeral.state.controlsEnabled}
enabled={Tankopedia.state.controlsEnabled}
enableDamping={false}
autoRotateSpeed={1 / 4}
/>
@@ -1,14 +1,14 @@
import { invalidate, useThree } from '@react-three/fiber';
import { useEffect } from 'react';
import type { Fog } from 'three';
import { fogAnimationTime, fogFar0, fogFar1, forNear0, forNear1 } from '..';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { invalidate, useThree } from "@react-three/fiber";
import { useEffect } from "react";
import type { Fog } from "three";
import { fogAnimationTime, fogFar0, fogFar1, forNear0, forNear1 } from "..";
import { Tankopedia } from "../../../../../../stores/tankopedia";
export function InitialFogReveal() {
const fog = useThree((state) => state.scene.fog as Fog | null);
useEffect(() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.revealed = true;
});
@@ -1,17 +1,17 @@
import { invalidate, useThree, type ThreeEvent } from '@react-three/fiber';
import { useRef } from 'react';
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
import { useModel } from '../../../../../../hooks/useModel';
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
import { useTankTransform } from '../../../../../../hooks/useTankTransform';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
import { ModelTankWrapper } from '../../../../../Armor/components/ModelTankWrapper';
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
import { useRef } from "react";
import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three";
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
import { useModel } from "../../../../../../hooks/useModel";
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
import { useTankTransform } from "../../../../../../hooks/useTankTransform";
import { Duel } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
export function TankModel() {
const duelStore = Duel.useStore();
@@ -34,9 +34,9 @@ export function TankModel() {
return (
<ModelTankWrapper ref={hullContainer}>
{nodes.map((node) => {
const isHull = node.name === 'hull';
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isHull = node.name === "hull";
const isWheel = node.name.startsWith("chassis_wheel_");
const isTrack = node.name.startsWith("chassis_track_");
const isVisible = isHull || isWheel || isTrack;
const position = new Vector2();
const delta = new Vector2();
@@ -59,19 +59,16 @@ export function TankModel() {
}
function onPointerDown(event: ThreeEvent<PointerEvent>) {
if (
isTrack &&
TankopediaEphemeral.state.display === TankopediaDisplay.Model
) {
if (isTrack && Tankopedia.state.display === TankopediaDisplay.Model) {
position.set(event.clientX, event.clientY);
event.stopPropagation();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = false;
});
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
} else {
event.stopPropagation();
}
@@ -85,12 +82,12 @@ export function TankModel() {
translateTexture(deltaX + deltaY);
}
function handlePointerUp() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
return jsxTree(node, {
@@ -110,10 +107,10 @@ export function TankModel() {
<group ref={turretContainer}>
{nodes.map((node) => {
const isTurret = node.name.startsWith('turret_');
const isTurret = node.name.startsWith("turret_");
const isCurrentTurret =
node.name ===
`turret_${turretModelDefinition.model_id.toString().padStart(2, '0')}`;
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentTurret;
const position = new Vector2();
const delta = new Vector2();
@@ -127,27 +124,27 @@ export function TankModel() {
position.set(event.clientX, event.clientY);
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = false;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const boundingRect = canvas.getBoundingClientRect();
@@ -161,16 +158,16 @@ export function TankModel() {
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
}
function handlePointerUp() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
return jsxTree(node, {
@@ -194,10 +191,10 @@ export function TankModel() {
node.name ===
`gun_${gunModelDefinition.model_id
.toString()
.padStart(2, '0')}_mask`;
.padStart(2, "0")}_mask`;
const isCurrentGun =
node.name ===
`gun_${gunModelDefinition.model_id.toString().padStart(2, '0')}`;
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentGun || isCurrentMantlet;
const position = new Vector2();
const delta = new Vector2();
@@ -210,10 +207,10 @@ export function TankModel() {
function onPointerDown(event: ThreeEvent<PointerEvent>) {
event.stopPropagation();
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = false;
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
@@ -222,20 +219,20 @@ export function TankModel() {
pitch = modelTransformEvent.last!.pitch;
yaw = modelTransformEvent.last!.yaw;
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = hasEquipment(
122,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
duel.protagonist.tank.equipment_preset,
duel.protagonist.equipmentMatrix,
duel.protagonist.equipmentMatrix
);
const boundingRect = canvas.getBoundingClientRect();
delta.set(event.clientX, event.clientY).sub(position);
@@ -247,16 +244,16 @@ export function TankModel() {
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
}
function handlePointerUp() {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.controlsEnabled = true;
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
return jsxTree(node, {
@@ -1,14 +1,14 @@
import { useRef } from 'react';
import { Group, ShaderMaterial, UniformsLib, UniformsUtils } from 'three';
import { jsxTree } from '../../../../../../../core/blitzkit/jsxTree';
import { useModel } from '../../../../../../../hooks/useModel';
import { useTankModelDefinition } from '../../../../../../../hooks/useTankModelDefinition';
import { useTankTransform } from '../../../../../../../hooks/useTankTransform';
import { Duel } from '../../../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../../../stores/tankopediaEphemeral';
import { ModelTankWrapper } from '../../../../../../Armor/components/ModelTankWrapper';
import fragmentShader from './shaders/fragment.glsl?raw';
import vertexShader from './shaders/vertex.glsl?raw';
import { useRef } from "react";
import { Group, ShaderMaterial, UniformsLib, UniformsUtils } from "three";
import { jsxTree } from "../../../../../../../core/blitzkit/jsxTree";
import { useModel } from "../../../../../../../hooks/useModel";
import { useTankModelDefinition } from "../../../../../../../hooks/useTankModelDefinition";
import { useTankTransform } from "../../../../../../../hooks/useTankTransform";
import { Duel } from "../../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../../stores/tankopedia";
import { ModelTankWrapper } from "../../../../../../Armor/components/ModelTankWrapper";
import fragmentShader from "./shaders/fragment.glsl?raw";
import vertexShader from "./shaders/vertex.glsl?raw";
const skeletonMaterial = new ShaderMaterial({
vertexShader,
@@ -33,16 +33,16 @@ export function TransitionSkeleton() {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const { gltf } = useModel(protagonist.tank.id);
const nodes = Object.values(gltf.nodes);
const revealed = TankopediaEphemeral.use((state) => state.revealed);
const revealed = Tankopedia.use((state) => state.revealed);
useTankTransform(track, turret, turretContainer, gunContainer);
return (
<ModelTankWrapper ref={hullContainer} visible={revealed}>
{nodes.map((node) => {
const isHull = node.name === 'hull';
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isHull = node.name === "hull";
const isWheel = node.name.startsWith("chassis_wheel_");
const isTrack = node.name.startsWith("chassis_track_");
const isVisible = isHull || isWheel || isTrack;
if (!isVisible) return null;
@@ -58,7 +58,7 @@ export function TransitionSkeleton() {
{nodes.map((node) => {
const isCurrentTurret =
node.name ===
`turret_${turretModelDefinition.model_id.toString().padStart(2, '0')}`;
`turret_${turretModelDefinition.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentTurret;
if (!isVisible) return null;
@@ -76,10 +76,10 @@ export function TransitionSkeleton() {
node.name ===
`gun_${gunModelDefinition.model_id
.toString()
.padStart(2, '0')}_mask`;
.padStart(2, "0")}_mask`;
const isCurrentGun =
node.name ===
`gun_${gunModelDefinition.model_id.toString().padStart(2, '0')}`;
`gun_${gunModelDefinition.model_id.toString().padStart(2, "0")}`;
const isVisible = isCurrentGun || isCurrentMantlet;
if (!isVisible) return null;
@@ -1,5 +1,5 @@
import { BLITZKIT_TANK_ICON_SIZE } from '@blitzkit/core';
import { invalidate } from '@react-three/fiber';
import { BLITZKIT_TANK_ICON_SIZE } from "@blitzkit/core";
import { invalidate } from "@react-three/fiber";
import {
forwardRef,
Suspense,
@@ -7,33 +7,33 @@ import {
useImperativeHandle,
useRef,
useState,
} from 'react';
import { Fog } from 'three';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { modelTransformEvent } from '../../../../../core/blitzkit/modelTransform';
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
import { useEquipment } from '../../../../../hooks/useEquipment';
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
import { Armor } from '../../../../Armor';
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
import { ShotDisplay } from '../../../../Armor/components/ShotDisplay';
} from "react";
import { Fog } from "three";
import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits";
import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform";
import { Pose, poseEvent } from "../../../../../core/blitzkit/pose";
import { useEquipment } from "../../../../../hooks/useEquipment";
import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition";
import { Duel } from "../../../../../stores/duel";
import { Tankopedia } from "../../../../../stores/tankopedia";
import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent";
import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants";
import { Armor } from "../../../../Armor";
import { ArmorPlateDisplay } from "../../../../Armor/components/ArmorPlateDisplay";
import { ShotDisplay } from "../../../../Armor/components/ShotDisplay";
import {
StaticArmor,
type ThicknessRange,
} from '../../../../Armor/components/StaticArmor';
import { SmartCanvas } from '../../../../SmartCanvas';
import { AutoClear } from './components/AutoClear';
import { Controls } from './components/Control';
import { InitialAligner } from './components/InitialAligner';
import { InitialFogReveal } from './components/InitialFogReveal';
import { Lighting } from './components/Lighting';
import { SceneProps } from './components/SceneProps';
import { TankModel } from './components/TankModel';
import { TransitionSkeleton } from './components/TransitionSkeleton';
} from "../../../../Armor/components/StaticArmor";
import { SmartCanvas } from "../../../../SmartCanvas";
import { AutoClear } from "./components/AutoClear";
import { Controls } from "./components/Control";
import { InitialAligner } from "./components/InitialAligner";
import { InitialFogReveal } from "./components/InitialFogReveal";
import { Lighting } from "./components/Lighting";
import { SceneProps } from "./components/SceneProps";
import { TankModel } from "./components/TankModel";
import { TransitionSkeleton } from "./components/TransitionSkeleton";
interface TankSandboxProps {
thicknessRange: ThicknessRange;
@@ -48,7 +48,7 @@ export const fogAnimationTime = 2.5;
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => {
const fog = useRef(new Fog('black', forNear1, fogFar1));
const fog = useRef(new Fog("black", forNear1, fogFar1));
const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122);
const hasDownImprovedVerticalStabilizer = useEquipment(124);
@@ -58,10 +58,10 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const turretModelDefinition =
tankModelDefinition.turrets[protagonistTurret.id];
const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id];
const rawDisplay = TankopediaEphemeral.use((state) => state.display);
const rawDisplay = Tankopedia.use((state) => state.display);
const [display, setDisplay] = useState(rawDisplay);
const hideTankModelUnderArmor = TankopediaPersistent.use(
(state) => state.hideTankModelUnderArmor,
(state) => state.hideTankModelUnderArmor
);
useEffect(() => {
@@ -95,8 +95,8 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
useImperativeHandle(ref, () => canvas.current!, []);
function handlePointerDown() {
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
function handlePointerMove(event: PointerEvent) {
event.preventDefault();
@@ -104,8 +104,8 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
function handlePointerUp(event: PointerEvent) {
event.preventDefault();
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}
useEffect(() => {
@@ -118,7 +118,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -132,7 +132,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -146,7 +146,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -168,7 +168,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
modelTransformEvent.last!.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasImprovedVerticalStabilizer
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -176,13 +176,13 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
useEffect(() => {
if (display !== TankopediaDisplay.DynamicArmor) {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
});
}
if (display !== TankopediaDisplay.StaticArmor) {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.highlightArmor = undefined;
});
}
@@ -200,16 +200,16 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
shadows="soft"
onPointerDown={handlePointerDown}
onPointerMissed={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
}}
style={{
userSelect: 'none',
width: naked ? BLITZKIT_TANK_ICON_SIZE.width : '100%',
height: naked ? BLITZKIT_TANK_ICON_SIZE.height : '100%',
outline: naked ? '1rem red solid' : undefined,
userSelect: "none",
width: naked ? BLITZKIT_TANK_ICON_SIZE.width : "100%",
height: naked ? BLITZKIT_TANK_ICON_SIZE.height : "100%",
outline: naked ? "1rem red solid" : undefined,
}}
>
{!naked && <SceneProps />}
@@ -236,5 +236,5 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
<InitialAligner />
</SmartCanvas>
);
},
}
);
@@ -1,28 +1,28 @@
import { Box, Flex } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { Suspense, useEffect, useMemo, useRef } from 'react';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { useFullScreen } from '../../../hooks/useFullScreen';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
import { Options } from './components/Options';
import { TankSandbox } from './components/TankSandbox';
import { Title } from './components/TankSandbox/Title';
import { Box, Flex } from "@radix-ui/themes";
import { times } from "lodash-es";
import { Suspense, useEffect, useMemo, useRef } from "react";
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
import { useFullScreen } from "../../../hooks/useFullScreen";
import { Duel } from "../../../stores/duel";
import { Tankopedia } from "../../../stores/tankopedia";
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
import type { ThicknessRange } from "../../Armor/components/StaticArmor";
import { Options } from "./components/Options";
import { TankSandbox } from "./components/TankSandbox";
import { Title } from "./components/TankSandbox/Title";
const tankDefinitions = await awaitableTankDefinitions;
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
const revealed = TankopediaEphemeral.use((state) => state.revealed);
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
const revealed = Tankopedia.use((state) => state.revealed);
const disturbed = Tankopedia.use((state) => state.disturbed);
const canvas = useRef<HTMLCanvasElement>(null);
const isFullScreen = useFullScreen();
const protagonist = Duel.use((state) => state.protagonist.tank);
const thicknessRange = useMemo(() => {
const entries = Object.values(tankDefinitions.tanks);
const filtered = entries.filter(
(thisTank) => thisTank.tier === protagonist.tier,
(thisTank) => thisTank.tier === protagonist.tier
);
const value =
(filtered.reduce((accumulator, thisTank) => {
@@ -41,7 +41,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
useEffect(() => {
if (disturbed) {
document.body.classList.remove('no-navbar');
document.body.classList.remove("no-navbar");
}
}, [disturbed]);
@@ -54,15 +54,15 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
mutateDuel((draft) => {
draft.antagonist.shell = shells[index];
});
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.customShell = undefined;
});
}
});
}
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
return (
@@ -70,7 +70,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
overflow="hidden"
justify="center"
style={{
backgroundColor: 'black',
backgroundColor: "black",
}}
position="relative"
>
@@ -82,36 +82,36 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
left="0"
style={{
opacity: disturbed ? 0 : 1,
background: 'black',
transitionDuration: '1s',
background: "black",
transitionDuration: "1s",
}}
/>
{/* <ScrollHint /> */}
<Flex
direction={{ initial: 'column-reverse', md: 'row' }}
direction={{ initial: "column-reverse", md: "row" }}
style={{
zIndex: isFullScreen ? 2 : undefined,
transitionDuration: '1s',
background: isFullScreen ? 'black' : undefined,
transitionDuration: "1s",
background: isFullScreen ? "black" : undefined,
}}
height={
isFullScreen
? '100vh'
? "100vh"
: disturbed
? 'calc(100svh - 6rem)'
? "calc(100svh - 6rem)"
: {
initial: '28rem',
md: 'calc(100svh - 12rem)',
initial: "28rem",
md: "calc(100svh - 12rem)",
}
}
maxWidth={isFullScreen ? undefined : '120rem'}
maxWidth={isFullScreen ? undefined : "120rem"}
flexGrow="1"
width={isFullScreen ? '100vw' : undefined}
position={isFullScreen ? 'fixed' : 'relative'}
top={isFullScreen ? '0' : undefined}
left={isFullScreen ? '0' : undefined}
width={isFullScreen ? "100vw" : undefined}
position={isFullScreen ? "fixed" : "relative"}
top={isFullScreen ? "0" : undefined}
left={isFullScreen ? "0" : undefined}
>
<Box
className="tank-sandbox-container"
@@ -126,14 +126,14 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
height="100%"
position="relative"
style={{
transitionDuration: '1s',
transitionDuration: "1s",
}}
>
<Box
style={{
width: '100%',
height: '100%',
transitionDuration: '2s',
width: "100%",
height: "100%",
transitionDuration: "2s",
opacity: revealed ? 1 : 0,
filter: disturbed
? undefined
@@ -1,12 +1,12 @@
import { asset, formatCompact, TIER_ROMAN_NUMERALS } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n/src/literals';
import { Box, Flex, Skeleton, Text } from '@radix-ui/themes';
import { awaitableAverageDefinitions } from '../../../../core/awaitables/averageDefinitions';
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
import { useLocale } from '../../../../hooks/useLocale';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
import { LinkI18n } from '../../../LinkI18n';
import { asset, formatCompact, TIER_ROMAN_NUMERALS } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
import { Box, Flex, Skeleton, Text } from "@radix-ui/themes";
import { awaitableAverageDefinitions } from "../../../../core/awaitables/averageDefinitions";
import { awaitableTankDefinitions } from "../../../../core/awaitables/tankDefinitions";
import { useLocale } from "../../../../hooks/useLocale";
import { Tankopedia } from "../../../../stores/tankopedia";
import type { MaybeSkeletonComponentProps } from "../../../../types/maybeSkeletonComponentProps";
import { LinkI18n } from "../../../LinkI18n";
type NodeProps = MaybeSkeletonComponentProps & {
id: number;
@@ -21,7 +21,7 @@ const [tankDefinitions, averageDefinitions] = await Promise.all([
export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
const { locale, strings, unwrap } = useLocale();
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
const xpMultiplier = Tankopedia.use((state) => state.xpMultiplier);
const tank = tankDefinitions.tanks[id];
const nextTanks = nextIds?.map((id) => tankDefinitions.tanks[id]);
const thisTankXp =
@@ -36,7 +36,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
? 0
: (tank.research_cost.research_cost_type!.value as number) /
xpMultiplier),
0,
0
);
const averages = averageDefinitions.averages[tank.id]?.mu;
const averageXp = averages ? averages.xp / averages.battles : undefined;
@@ -49,32 +49,32 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
href={`/tanks/${tankDefinitions.tanks[id].slug}`}
key={id}
style={{
textDecoration: 'none',
color: 'inherit',
position: 'relative',
textDecoration: "none",
color: "inherit",
position: "relative",
}}
>
<Box
mx={highlight ? '2' : '0'}
mx={highlight ? "2" : "0"}
style={{
background: highlight
? `url(${asset(`flags/fade_small/${tank.nation}.webp`)})`
: undefined,
backgroundPosition: 'center',
backgroundSize: 'cover',
boxShadow: highlight ? 'var(--shadow-4)' : undefined,
borderRadius: 'var(--radius-4)',
overflow: 'hidden',
backgroundPosition: "center",
backgroundSize: "cover",
boxShadow: highlight ? "var(--shadow-4)" : undefined,
borderRadius: "var(--radius-4)",
overflow: "hidden",
}}
>
<Flex
direction="column"
align="center"
py="4"
px={highlight ? '4' : '2'}
px={highlight ? "4" : "2"}
style={{
backdropFilter: highlight ? 'brightness(0.25)' : undefined,
WebkitBackdropFilter: highlight ? 'brightness(0.25)' : undefined,
backdropFilter: highlight ? "brightness(0.25)" : undefined,
WebkitBackdropFilter: highlight ? "brightness(0.25)" : undefined,
}}
>
<img
@@ -83,7 +83,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
width={64}
height={64}
style={{
objectFit: 'contain',
objectFit: "contain",
}}
/>
@@ -99,12 +99,12 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
<Flex gap="1" align="center">
<img
alt="XP"
src={asset('icons/currencies/xp.webp')}
src={asset("icons/currencies/xp.webp")}
style={{
width: '1em',
height: '1em',
objectFit: 'contain',
objectPosition: 'center',
width: "1em",
height: "1em",
objectFit: "contain",
objectPosition: "center",
}}
/>
{formatCompact(locale, thisTankXp)}
@@ -114,12 +114,12 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
<Flex gap="1" align="center">
<img
alt="Silver"
src={asset('icons/currencies/silver.webp')}
src={asset("icons/currencies/silver.webp")}
style={{
width: '1em',
height: '1em',
objectFit: 'contain',
objectPosition: 'center',
width: "1em",
height: "1em",
objectFit: "contain",
objectPosition: "center",
}}
/>
{formatCompact(locale, tank.price.value)}
@@ -135,7 +135,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
? strings.website.tools.tankopedia.tech_tree.battle
: literals(
strings.website.tools.tankopedia.tech_tree.battles,
[`${games}`],
[`${games}`]
))}
</Text>
)}
@@ -1,15 +1,15 @@
import { asset, TankType } from '@blitzkit/core';
import { literals } from '@blitzkit/i18n/src/literals';
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from '@radix-ui/react-icons';
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { useLocale } from '../../../hooks/useLocale';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
import { Arrow } from './components/Arrow';
import { Node } from './components/Node';
import { asset, TankType } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n/src/literals";
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from "@radix-ui/react-icons";
import { Flex, Heading, IconButton, ScrollArea, Text } from "@radix-ui/themes";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { awaitableTankDefinitions } from "../../../core/awaitables/tankDefinitions";
import { useLocale } from "../../../hooks/useLocale";
import { Duel } from "../../../stores/duel";
import { Tankopedia } from "../../../stores/tankopedia";
import type { MaybeSkeletonComponentProps } from "../../../types/maybeSkeletonComponentProps";
import { Arrow } from "./components/Arrow";
import { Node } from "./components/Node";
type Line = number[];
@@ -20,7 +20,7 @@ export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10];
export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
const { locale, strings } = useLocale();
const master = Duel.use((state) => state.protagonist.tank);
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
const xpMultiplier = Tankopedia.use((state) => state.xpMultiplier);
const container = useRef<HTMLDivElement>(null);
const lines = useMemo(() => {
function extend(line: Line): Line[] {
@@ -32,8 +32,8 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
if (root.ancestors.length === 1 || root.tier === 2) {
line.push(
root.ancestors.find(
(ancestor) => !tankDefinitions.tanks[ancestor].deprecated,
) ?? root.ancestors[0],
(ancestor) => !tankDefinitions.tanks[ancestor].deprecated
) ?? root.ancestors[0]
);
return extend(line);
} else {
@@ -52,7 +52,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
const [lineIndex, setLineIndex] = useState(0);
const line = useMemo(
() => (lines.length === 0 ? [master.id] : [...lines[lineIndex]].reverse()),
[master, lineIndex],
[master, lineIndex]
);
const totalXp = line.reduce(
(xp, id) =>
@@ -62,11 +62,11 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
? 0
: (tankDefinitions.tanks[id].research_cost.research_cost_type!
.value as number)),
0,
0
);
const totalCredits = line.reduce(
(credits, id) => credits + tankDefinitions.tanks[id].price.value,
0,
0
);
useEffect(() => {
@@ -87,7 +87,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
direction="column"
align="center"
gap="0"
style={{ backgroundColor: 'var(--color-surface)' }}
style={{ backgroundColor: "var(--color-surface)" }}
py="6"
>
<Flex direction="column" align="center">
@@ -101,12 +101,12 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
<Flex gap="1" align="center">
<img
alt="XP"
src={asset('icons/currencies/xp.webp')}
src={asset("icons/currencies/xp.webp")}
style={{
width: '1em',
height: '1em',
objectFit: 'contain',
objectPosition: 'center',
width: "1em",
height: "1em",
objectFit: "contain",
objectPosition: "center",
}}
/>
{Math.round(totalXp / xpMultiplier).toLocaleString(locale)}
@@ -117,12 +117,12 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
<Flex gap="1" align="center">
<img
alt="Silver"
src={asset('icons/currencies/silver.webp')}
src={asset("icons/currencies/silver.webp")}
style={{
width: '1em',
height: '1em',
objectFit: 'contain',
objectPosition: 'center',
width: "1em",
height: "1em",
objectFit: "contain",
objectPosition: "center",
}}
/>
{totalCredits.toLocaleString(locale)}
@@ -139,15 +139,15 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
<Flex
key={multiplier}
style={{
background: 'url(/assets/images/icons/xp-multiplier.png)',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
background: "url(/assets/images/icons/xp-multiplier.png)",
backgroundSize: "contain",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
opacity: selected ? 1 : 0.5,
cursor: 'pointer',
cursor: "pointer",
}}
onClick={() => {
TankopediaEphemeral.mutate((draft) => {
Tankopedia.mutate((draft) => {
draft.xpMultiplier = multiplier;
});
}}