mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: complete tankoepdia page
This commit is contained in:
1 parent
2b5ffb2b84
commit
a54ba78dbf
39 files changed
+790
-161
No files matched your search
@@ -65,7 +65,7 @@ export const averageDefinitionsArray = averageDefinitions.then((data) =>
|
||||
({
|
||||
id: Number(key),
|
||||
...(value as AverageDefinitionsEntry),
|
||||
} satisfies AverageDefinitionsEntryWithId),
|
||||
}) satisfies AverageDefinitionsEntryWithId,
|
||||
)
|
||||
.filter((tank) => tank.mu.battles > 0),
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
export * from './assertSecret';
|
||||
export * from './asset';
|
||||
export * from './availableProvisions';
|
||||
// export * from './averageDefinitions';
|
||||
export * from './averageDefinitions';
|
||||
// export * from './camouflageDefinitions';
|
||||
// export * from './checkConsumableProvisionInclusivity';
|
||||
// export * from './coefficient';
|
||||
// export * from './consumableDefinitions';
|
||||
export * from './coefficient';
|
||||
export * from './consumableDefinitions';
|
||||
// export * from './context';
|
||||
// export * from './createDefaultSkills';
|
||||
// export * from './degressiveStat';
|
||||
export * from './createDefaultSkills';
|
||||
export * from './degressiveStat';
|
||||
// export * from './discoveredIdDefinitions';
|
||||
// export * from './emblemURL';
|
||||
export * from './equipmentDefinitions';
|
||||
@@ -29,13 +29,13 @@ export * from './normalizeBoundingBox';
|
||||
// export * from './parseBkni';
|
||||
// export * from './patientFetch';
|
||||
// export * from './patientFetchJSON';
|
||||
// export * from './progressiveStat';
|
||||
export * from './progressiveStat';
|
||||
export * from './provisionDefinitions';
|
||||
export * from './resolveDpm';
|
||||
export * from './resolvePenetrationCoefficient';
|
||||
// export * from './reviews';
|
||||
export * from './reviews';
|
||||
export * from './skillDefinitions';
|
||||
// export * from './sum';
|
||||
export * from './sum';
|
||||
export * from './superCompress';
|
||||
export * from './superDecompress';
|
||||
export * from './tankDefinitions';
|
||||
@@ -44,6 +44,6 @@ export * from './tankNames';
|
||||
export * from './unionBoundingBox';
|
||||
export * from './uniqueGuns';
|
||||
// export * from './unwrapBlitzkitResponse';
|
||||
// export * from './votes';
|
||||
export * from './votes';
|
||||
// export * from './withCORSProxy';
|
||||
// export * from './youtubers';
|
||||
export * from './youtubers';
|
||||
@@ -1,6 +1,6 @@
|
||||
import protobuf from 'protobufjs';
|
||||
import average_definitions from '../../protos/average_definitions.proto';
|
||||
import reviews from '../../protos/reviews.proto';
|
||||
import average_definitions from '../../protos/average_definitions.proto?raw';
|
||||
import reviews from '../../protos/reviews.proto?raw';
|
||||
|
||||
export type ProtoSource = keyof typeof protos;
|
||||
|
||||
|
||||
+13
-14
@@ -20,24 +20,23 @@ import {
|
||||
Tooltip,
|
||||
} from '@radix-ui/themes';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { Fragment, use, useEffect, useRef, useState } from 'react';
|
||||
import { lerp } from 'three/src/math/MathUtils';
|
||||
import { Ad, AdType } from '../../../../../../components/Ad';
|
||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { tankCharacteristics } from '../../../../../../core/blitzkit/tankCharacteristics';
|
||||
import { useAdExempt } from '../../../../../../hooks/useAdExempt';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
||||
import * as Duel from '../../../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
|
||||
import { Info } from './components/Info';
|
||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { Ad, AdType } from '../../../../../../../../website/src/components/Ad';
|
||||
import { InfoWithDelta } from '../../../../../../../../website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/InfoWithDelta';
|
||||
import { applyPitchYawLimits } from '../../../../../../../../website/src/core/blitz/applyPitchYawLimits';
|
||||
import { useAdExempt } from '../../../../../../../../website/src/hooks/useAdExempt';
|
||||
import { useEquipment } from '../../../../../../../../website/src/hooks/useEquipment';
|
||||
import { useTankModelDefinition } from '../../../../../../../../website/src/hooks/useTankModelDefinition';
|
||||
import { Duel } from '../../../../../../../../website/src/stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../../../../../../website/src/stores/tankopediaEphemeral';
|
||||
import { Info } from '../../../../performance/components/Info';
|
||||
|
||||
const awaitedEquipmentDefinitions = await equipmentDefinitions;
|
||||
const awaitedProvisionDefinitions = await provisionDefinitions;
|
||||
|
||||
export function Characteristics() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const exempt = useAdExempt();
|
||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
||||
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
|
||||
const penetrationDistanceInput = useRef<HTMLInputElement>(null);
|
||||
const hasImprovedVentilation = useEquipment(102);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { PageWrapper } from '../../../../../components/PageWrapper';
|
||||
import { useAdExempt } from '../../../../../hooks/useAdExempt';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
||||
import { VideoSection } from './VideoSection';
|
||||
import { VideoSection } from '../../../../../../../website/src/components/Tankopedia/VideoSection';
|
||||
|
||||
export function Content() {
|
||||
const exempt = useAdExempt();
|
||||
|
||||
@@ -15,7 +15,7 @@ import { PageWrapper } from '../../../../components/PageWrapper';
|
||||
import { useAdExempt } from '../../../../hooks/useAdExempt';
|
||||
import * as Duel from '../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../stores/tankopediaEphemeral';
|
||||
import { VideoSection } from './components/VideoSection';
|
||||
import { VideoSection } from '../../../../../../website/src/components/Tankopedia/VideoSection';
|
||||
|
||||
export default function Page() {
|
||||
const exempt = useAdExempt();
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
"../website/src/components/Tankopedia/HistorySection.tsx",
|
||||
"../website/src/components/Tankopedia/CalloutsSection.tsx",
|
||||
"../website/src/components/Tankopedia/ShotDisplaySection.tsx"
|
||||
]
|
||||
, "../website/src/components/Tankopedia/CharacteristicsSection/components/ConfigurationChildWrapper.tsx", "../website/src/components/Tankopedia/VideoSection.tsx" ]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConsumableEntry } from '@blitzkit/core';
|
||||
import type { ConsumableEntry } from '@blitzkit/core/src/blitzkit/consumableDefinitions';
|
||||
import { Flex, type FlexProps } from '@radix-ui/themes';
|
||||
import { ConsumableButton } from './ModuleButtons/ConsumableButton';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EquipmentPreset } from '@blitzkit/core';
|
||||
import type { EquipmentPreset } from '@blitzkit/core';
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { EquipmentMatrix } from '../../../website-legacy/src/stores/duel';
|
||||
import type { EquipmentMatrix } from '../stores/duel';
|
||||
import { EquipmentButton } from './ModuleButtons/EquipmentButton';
|
||||
|
||||
interface EquipmentManagerProps {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { asset, consumableDefinitions } from '@blitzkit/core';
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { consumableDefinitions } from '@blitzkit/core/src/blitzkit/consumableDefinitions';
|
||||
import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { useDelta } from '../../../../website-legacy/src/hooks/useDelta';
|
||||
import { useDelta } from '../../hooks/useDelta';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import { TankComponentButtonProps } from './TankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
|
||||
interface ConsumableButtonProps extends TankComponentButtonProps {
|
||||
consumable: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { asset, equipmentDefinitions } from '@blitzkit/core';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import { TankComponentButtonProps } from './TankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
|
||||
interface EquipmentButtonProps extends TankComponentButtonProps {
|
||||
equipment: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { asset, provisionDefinitions } from '@blitzkit/core';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import { TankComponentButtonProps } from './TankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
|
||||
interface ProvisionButtonProps extends TankComponentButtonProps {
|
||||
provision: number;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MinusCircledIcon } from '@radix-ui/react-icons';
|
||||
import { Callout, Flex } from '@radix-ui/themes';
|
||||
import { ExperimentIcon } from '../../../../../components/ExperimentIcon';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import { Duel } from '../../stores/duel';
|
||||
import { ExperimentIcon } from '../ExperimentIcon';
|
||||
|
||||
export function CalloutsSection() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
|
||||
+3
-11
@@ -1,6 +1,6 @@
|
||||
import { CaretDownIcon, CaretUpIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { ReactNode } from 'react';
|
||||
import { Flex, Separator, Text } from '@radix-ui/themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface InfoProps {
|
||||
name: ReactNode;
|
||||
@@ -41,15 +41,7 @@ export function Info({
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{children !== undefined && (
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 1,
|
||||
backgroundColor: 'red',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{children !== undefined && <Separator style={{ flex: 1 }} />}
|
||||
|
||||
{children !== undefined && (
|
||||
<Flex align="center" gap="1">
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { useDelta } from '../../../../../../../hooks/useDelta';
|
||||
import { Info, InfoProps } from './Info';
|
||||
import { useDelta } from '../../../../../../hooks/useDelta';
|
||||
import { type InfoProps, Info } from './Info';
|
||||
|
||||
interface InfoWithDeltaProps extends InfoProps {
|
||||
children: number;
|
||||
+632
@@ -0,0 +1,632 @@
|
||||
import {
|
||||
asset,
|
||||
coefficient,
|
||||
CREW_MEMBER_NAMES,
|
||||
equipmentDefinitions,
|
||||
GUN_TYPE_NAMES,
|
||||
isExplosive,
|
||||
provisionDefinitions,
|
||||
resolvePenetrationCoefficient,
|
||||
} from '@blitzkit/core';
|
||||
import { AccessibilityIcon, InfoCircledIcon } from '@radix-ui/react-icons';
|
||||
import {
|
||||
Flex,
|
||||
Heading,
|
||||
IconButton,
|
||||
Popover,
|
||||
Slider,
|
||||
Text,
|
||||
TextField,
|
||||
Tooltip,
|
||||
} from '@radix-ui/themes';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Fragment } from 'react/jsx-runtime';
|
||||
import { lerp } from 'three/src/math/MathUtils.js';
|
||||
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
|
||||
import { useAdExempt } from '../../../../../hooks/useAdExempt';
|
||||
import { useEquipment } from '../../../../../hooks/useEquipment';
|
||||
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
|
||||
import { Duel } from '../../../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
||||
import { Ad, AdType } from '../../../../Ad';
|
||||
import { Info } from './components/Info';
|
||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||
|
||||
const awaitedEquipmentDefinitions = await equipmentDefinitions;
|
||||
const awaitedProvisionDefinitions = await provisionDefinitions;
|
||||
|
||||
export function Characteristics() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const exempt = useAdExempt();
|
||||
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
|
||||
const penetrationDistanceInput = useRef<HTMLInputElement>(null);
|
||||
const hasImprovedVentilation = useEquipment(102);
|
||||
const crewMastery = Duel.use((state) => state.protagonist.crewMastery);
|
||||
const [penetrationDistance, setPenetrationDistance] = useState(250);
|
||||
const setPenetrationDistanceDebounced = debounce((value: number) => {
|
||||
setPenetrationDistance(value);
|
||||
}, 500);
|
||||
|
||||
const provisions = Duel.use((state) => state.protagonist.provisions);
|
||||
const provisionCrewBonus =
|
||||
provisions.reduce(
|
||||
(total, provision) =>
|
||||
awaitedProvisionDefinitions[provision].crew
|
||||
? total + awaitedProvisionDefinitions[provision].crew! / 100
|
||||
: total,
|
||||
0,
|
||||
) + (hasImprovedVentilation ? 0.08 : 0);
|
||||
const commanderMastery = crewMastery + provisionCrewBonus;
|
||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
||||
const equipmentMatrix = Duel.use(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
);
|
||||
const { tank, turret, gun, engine, track, shell } = Duel.use(
|
||||
(state) => state.protagonist,
|
||||
);
|
||||
const stockEngine = tank.engines[0];
|
||||
const stockTrack = tank.tracks[0];
|
||||
const stockTurret = tank.turrets[0];
|
||||
const stockGun = stockTurret.guns[0];
|
||||
const tankModelDefinition = useTankModelDefinition();
|
||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||
const stats = tankCharacteristics(
|
||||
{
|
||||
tank,
|
||||
camouflage,
|
||||
consumables,
|
||||
crewMastery,
|
||||
crewSkills,
|
||||
engine,
|
||||
equipmentMatrix,
|
||||
gun,
|
||||
provisions,
|
||||
shell,
|
||||
turret,
|
||||
track,
|
||||
stockEngine,
|
||||
stockGun,
|
||||
stockTrack,
|
||||
stockTurret,
|
||||
applyReactiveArmor: false,
|
||||
applyDynamicArmor: false,
|
||||
applySpallLiner: false,
|
||||
},
|
||||
{
|
||||
tankModelDefinition,
|
||||
equipmentDefinitions: awaitedEquipmentDefinitions,
|
||||
provisionDefinitions: awaitedProvisionDefinitions,
|
||||
},
|
||||
);
|
||||
|
||||
const hasSupercharger = useEquipment(107);
|
||||
const hasCalibratedShells = useEquipment(103);
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const penetrationCoefficient = coefficient([
|
||||
hasCalibratedShells,
|
||||
resolvePenetrationCoefficient(true, shell.type) - 1,
|
||||
]);
|
||||
const penetrationLossOverDistanceCoefficient = coefficient([
|
||||
hasSupercharger,
|
||||
-0.6,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (penetrationDistanceInput.current) {
|
||||
penetrationDistanceInput.current.value = `${penetrationDistance}`;
|
||||
}
|
||||
}, [penetrationDistance]);
|
||||
useEffect(() => {
|
||||
mutateDuel((draft) => {
|
||||
[draft.protagonist.pitch, draft.protagonist.yaw] = applyPitchYawLimits(
|
||||
draft.protagonist.pitch,
|
||||
draft.protagonist.yaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
hasImprovedVerticalStabilizer,
|
||||
);
|
||||
});
|
||||
}, [hasImprovedVerticalStabilizer]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
gap="8"
|
||||
direction={{
|
||||
initial: 'column',
|
||||
md: 'row',
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap="8" style={{ flex: 1 }}>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex align="center" gap="4">
|
||||
<Heading size="5">Firepower</Heading>
|
||||
|
||||
<Flex>
|
||||
{gun.shells.map((thisShell, shellIndex) => (
|
||||
<Tooltip content={thisShell.name} key={thisShell.id}>
|
||||
<IconButton
|
||||
color={thisShell.id === shell.id ? undefined : 'gray'}
|
||||
variant="soft"
|
||||
style={{
|
||||
borderTopLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
borderBottomLeftRadius: shellIndex === 0 ? undefined : 0,
|
||||
borderTopRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||
borderBottomRightRadius:
|
||||
shellIndex === gun.shells.length - 1 ? undefined : 0,
|
||||
marginLeft: shellIndex === 0 ? 0 : -1,
|
||||
}}
|
||||
onClick={() => {
|
||||
mutateDuel((draft) => {
|
||||
draft.protagonist.shell = thisShell;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={thisShell.name}
|
||||
width={16}
|
||||
height={16}
|
||||
src={asset(`icons/shells/${thisShell.icon}.webp`)}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
|
||||
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
|
||||
{stats.dpm}
|
||||
</InfoWithDelta>
|
||||
{gun.type === 'autoReloader' && (
|
||||
<InfoWithDelta
|
||||
decimals={0}
|
||||
indent
|
||||
name="Effective at 60s"
|
||||
unit="hp / min"
|
||||
>
|
||||
{stats.dpmEffective!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
{gun.type !== 'regular' && (
|
||||
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
|
||||
)}
|
||||
{gun.type === 'autoReloader' ? (
|
||||
<>
|
||||
<Info indent name="Most optimal shell index">
|
||||
{stats.mostOptimalShellIndex! + 1}
|
||||
</Info>
|
||||
<Info name="Shell reloads" unit="s" />
|
||||
{stats.shellReloads!.map((reload, index) => (
|
||||
<InfoWithDelta
|
||||
key={index}
|
||||
indent
|
||||
name={`Shell ${index + 1}`}
|
||||
decimals={2}
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{reload}
|
||||
</InfoWithDelta>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<InfoWithDelta
|
||||
decimals={2}
|
||||
name="Reload"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.shellReload!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
{(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
|
||||
<InfoWithDelta
|
||||
indent
|
||||
decimals={2}
|
||||
name="Intra-clip"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.intraClip!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
|
||||
{stats.penetration}
|
||||
</InfoWithDelta>
|
||||
{typeof shell.penetration !== 'number' && (
|
||||
<>
|
||||
<InfoWithDelta
|
||||
indent
|
||||
decimals={0}
|
||||
name={`At ${penetrationDistance}m`}
|
||||
>
|
||||
{lerp(
|
||||
shell.penetration[0],
|
||||
shell.penetration[1],
|
||||
(penetrationDistance *
|
||||
penetrationLossOverDistanceCoefficient) /
|
||||
500,
|
||||
) * penetrationCoefficient}
|
||||
</InfoWithDelta>
|
||||
<Flex align="center" gap="2" style={{ paddingLeft: 24 }}>
|
||||
<Text color="gray">Distance</Text>
|
||||
<Slider
|
||||
key={penetrationDistance}
|
||||
min={0}
|
||||
max={500}
|
||||
style={{ flex: 1 }}
|
||||
defaultValue={[penetrationDistance]}
|
||||
onValueChange={([value]) => {
|
||||
setPenetrationDistanceDebounced(value);
|
||||
|
||||
if (!penetrationDistanceInput.current) return;
|
||||
|
||||
penetrationDistanceInput.current.value = value.toString();
|
||||
}}
|
||||
/>
|
||||
<TextField.Root
|
||||
style={{ width: 64 }}
|
||||
ref={penetrationDistanceInput}
|
||||
defaultValue={penetrationDistance}
|
||||
onBlur={() => {
|
||||
setPenetrationDistance(
|
||||
Math.min(
|
||||
parseInt(penetrationDistanceInput.current!.value),
|
||||
500,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TextField.Slot side="right">m</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<InfoWithDelta name="Damage" unit="hp" decimals={0}>
|
||||
{stats.damage}
|
||||
</InfoWithDelta>
|
||||
{gun.type !== 'regular' && (
|
||||
<InfoWithDelta name="Clipping potential" indent>
|
||||
{stats.clipDamage!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
||||
{stats.moduleDamage}
|
||||
</InfoWithDelta>
|
||||
{isExplosive(shell.type) && (
|
||||
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
|
||||
{stats.explosionRadius!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
|
||||
{stats.caliber}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Normalization" decimals={0} unit="°">
|
||||
{stats.shellNormalization}
|
||||
</InfoWithDelta>
|
||||
{!isExplosive(shell.type) && (
|
||||
<InfoWithDelta
|
||||
name="Ricochet"
|
||||
decimals={0}
|
||||
deltaType="lowerIsBetter"
|
||||
unit="°"
|
||||
>
|
||||
{stats.shellRicochet!}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
||||
{stats.shellVelocity}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={2}
|
||||
name="Aim time"
|
||||
unit="s"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.aimTime}
|
||||
</InfoWithDelta>
|
||||
<Info name="Dispersion at 100m" />
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
indent
|
||||
name="Still"
|
||||
unit="m"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersion}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
prefix="+"
|
||||
decimals={3}
|
||||
indent
|
||||
name="Moving"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionMoving}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+"
|
||||
indent
|
||||
name="Hull traversing"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionHullTraversing}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+"
|
||||
indent
|
||||
name="Turret traversing"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionTurretTraversing}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
prefix="+"
|
||||
indent
|
||||
name="After shooting"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionShooting}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={1}
|
||||
prefix="x "
|
||||
indent
|
||||
name="Gun damaged"
|
||||
unit="scalar"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.dispersionGunDamaged}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={3}
|
||||
indent
|
||||
name="Maximum"
|
||||
unit="m"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.maxDispersion}
|
||||
</InfoWithDelta>
|
||||
<Info name="Gun flexibility" unit="°" />
|
||||
<InfoWithDelta decimals={1} indent name="Depression">
|
||||
{stats.gunDepression}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Elevation">
|
||||
{stats.gunElevation}
|
||||
</InfoWithDelta>
|
||||
{gunModelDefinition.pitch.front && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Frontal depression">
|
||||
{stats.gunFrontalDepression!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Frontal elevation">
|
||||
{stats.gunFrontalElevation!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
{gunModelDefinition.pitch.back && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Rear depression">
|
||||
{stats.gunRearDepression!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Rear elevation">
|
||||
{stats.gunRearElevation!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
{turretModelDefinition.yaw && (
|
||||
<>
|
||||
<InfoWithDelta decimals={1} indent name="Azimuth left">
|
||||
{stats.azimuthLeft!}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="Azimuth right">
|
||||
{stats.azimuthRight!}
|
||||
</InfoWithDelta>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex direction="column" gap="2">
|
||||
<Flex gap="2" align="center">
|
||||
<Heading size="5">Crew training</Heading>
|
||||
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<IconButton variant="ghost">
|
||||
<InfoCircledIcon />
|
||||
</IconButton>
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content>
|
||||
<Flex gap="1" align="center">
|
||||
<AccessibilityIcon />
|
||||
<Text>
|
||||
represents the other roles a crew member can take.
|
||||
</Text>
|
||||
</Flex>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</Flex>
|
||||
|
||||
{tank.crew.map((member) => {
|
||||
const count = member.count ?? 1;
|
||||
|
||||
return (
|
||||
<Fragment key={member.type}>
|
||||
<InfoWithDelta
|
||||
key={`${member.type}-root`}
|
||||
name={`${CREW_MEMBER_NAMES[member.type]}${
|
||||
count > 1 ? ` x ${count}` : ''
|
||||
}`}
|
||||
unit="%"
|
||||
decimals={0}
|
||||
>
|
||||
{(member.type === 'commander'
|
||||
? commanderMastery
|
||||
: commanderMastery * 1.1) * 100}
|
||||
</InfoWithDelta>
|
||||
|
||||
{member.substitute && (
|
||||
<InfoWithDelta
|
||||
key={`${member.type}-substitute`}
|
||||
decimals={0}
|
||||
unit="%"
|
||||
indent
|
||||
name={
|
||||
<>
|
||||
<Flex align="center" gap="1" display="inline-flex">
|
||||
<AccessibilityIcon />
|
||||
{member.substitute
|
||||
.map((sub, index) =>
|
||||
index === 0 ? CREW_MEMBER_NAMES[sub] : sub,
|
||||
)
|
||||
.join(', ')}
|
||||
</Flex>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{commanderMastery * 1.05 * 100}
|
||||
</InfoWithDelta>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{!exempt && (
|
||||
<Flex
|
||||
justify="center"
|
||||
display={{
|
||||
initial: 'flex',
|
||||
xs: 'none',
|
||||
}}
|
||||
>
|
||||
<Ad type={AdType.MediumRectangleHorizontalPurple} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Flex direction="column" gap="8" style={{ flex: 1 }}>
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Maneuverability</Heading>
|
||||
<Info name="Speed" unit="kph" />
|
||||
<InfoWithDelta decimals={0} indent name="Forwards">
|
||||
{stats.speedForwards}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} indent name="Backwards">
|
||||
{stats.speedBackwards}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} name="Engine power" unit="hp">
|
||||
{stats.enginePower}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
decimals={1}
|
||||
name="Weight"
|
||||
unit="tn"
|
||||
deltaType="lowerIsBetter"
|
||||
>
|
||||
{stats.weight}
|
||||
</InfoWithDelta>
|
||||
<Info name="Terrain coefficients" />
|
||||
<InfoWithDelta decimals={0} unit="%" indent name="Hard">
|
||||
{stats.hardTerrainCoefficient}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} unit="%" indent name="Medium">
|
||||
{stats.mediumTerrainCoefficient}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={0} unit="%" indent name="Soft">
|
||||
{stats.softTerrainCoefficient}
|
||||
</InfoWithDelta>
|
||||
<Info name="Raw terrain coefficients" />
|
||||
<InfoWithDelta decimals={2} unit="%" indent name="Hard">
|
||||
{stats.hardTerrainCoefficientRaw}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={2} unit="%" indent name="Medium">
|
||||
{stats.mediumTerrainCoefficientRaw}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={2} unit="%" indent name="Soft">
|
||||
{stats.softTerrainCoefficientRaw}
|
||||
</InfoWithDelta>
|
||||
<Info name="Power to weight ratio" unit="hp/tn" />
|
||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||
{stats.powerToWeightRatioHardTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||
{stats.powerToWeightRatioMediumTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||
{stats.powerToWeightRatioSoftTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="Turret traverse speed" unit="°/s" decimals={1}>
|
||||
{stats.turretTraverseSpeed}
|
||||
</InfoWithDelta>
|
||||
<Info name="Hull traverse speed" unit="°/s" />
|
||||
<InfoWithDelta decimals={1} indent name="On hard terrain">
|
||||
{stats.hullTraverseHardTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On medium terrain">
|
||||
{stats.hullTraverseMediumTerrain}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta decimals={1} indent name="On soft terrain">
|
||||
{stats.hullTraverseSoftTerrain}
|
||||
</InfoWithDelta>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading size="5">Survivability</Heading>
|
||||
<InfoWithDelta name="Health" unit="hp" decimals={0}>
|
||||
{stats.health}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta
|
||||
name="Fire chance"
|
||||
unit="%"
|
||||
deltaType="lowerIsBetter"
|
||||
decimals={0}
|
||||
>
|
||||
{stats.fireChance * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta name="View range" unit="m" decimals={0}>
|
||||
{stats.viewRange}
|
||||
</InfoWithDelta>
|
||||
<Info name="Camouflage" unit="%" />
|
||||
<InfoWithDelta indent name="Still" decimals={0}>
|
||||
{stats.camouflageStill * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Moving" decimals={0}>
|
||||
{stats.camouflageMoving * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Shooting still" decimals={0}>
|
||||
{stats.camouflageShootingStill * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Shooting moving" decimals={0}>
|
||||
{stats.camouflageShootingMoving * 100}
|
||||
</InfoWithDelta>
|
||||
<InfoWithDelta indent name="Caught on fire" decimals={0}>
|
||||
{stats.camouflageCaughtOnFire * 100}
|
||||
</InfoWithDelta>
|
||||
<Info name="Width" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.width}
|
||||
</Info>
|
||||
<Info name="Height" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.height}
|
||||
</Info>
|
||||
<Info name="Length" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.length}
|
||||
</Info>
|
||||
<Info name="Volume" unit="m" decimals={0} deltaType="lowerIsBetter">
|
||||
{stats.volume}
|
||||
</Info>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { Flex, FlexProps } from '@radix-ui/themes';
|
||||
import { ReactNode } from 'react';
|
||||
import { type FlexProps, Flex } from '@radix-ui/themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type ConfigurationChildWrapperProps = FlexProps & {
|
||||
children: ReactNode;
|
||||
+8
-9
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
checkConsumableProvisionInclusivity,
|
||||
consumableDefinitions,
|
||||
} from '@blitzkit/core';
|
||||
import {} from '@blitzkit/core';
|
||||
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
||||
import { consumableDefinitions } from '@blitzkit/core/src/blitzkit/consumableDefinitions';
|
||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -11,16 +10,16 @@ import {
|
||||
Popover,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { ConsumablesManager } from '../../../../../../../components/ConsumablesManager';
|
||||
import { useEquipment } from '../../../../../../../hooks/useEquipment';
|
||||
import * as Duel from '../../../../../../../stores/duel';
|
||||
import { useEquipment } from '../../../../hooks/useEquipment';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { ConsumablesManager } from '../../../ConsumablesManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
|
||||
const awaitedConsumableDefinitions = await consumableDefinitions;
|
||||
|
||||
export function Consumables() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const awaitedConsumableDefinitions = use(consumableDefinitions);
|
||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||
const consumablesList = Object.values(awaitedConsumableDefinitions).filter(
|
||||
(consumable) =>
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
import { equipmentDefinitions } from '@blitzkit/core';
|
||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { EquipmentManager } from '../../../../../../../components/EquipmentManager';
|
||||
import * as Duel from '../../../../../../../stores/duel';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { EquipmentManager } from '../../../EquipmentManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
|
||||
const awaitedEquipmentDefinitions = await equipmentDefinitions;
|
||||
|
||||
export function Equipment() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const protagonist = Duel.use((state) => state.protagonist);
|
||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||
const equipmentPreset =
|
||||
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
|
||||
const equipmentMatrix = Duel.use(
|
||||
+2
-2
@@ -2,8 +2,8 @@ import { asset } from '@blitzkit/core';
|
||||
import { Flex, Heading, Slider, TextField } from '@radix-ui/themes';
|
||||
import { clamp, debounce } from 'lodash-es';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton';
|
||||
import * as Duel from '../../../../../../../stores/duel';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { GenericTankComponentButton } from '../../../ModuleButtons/GenericTankComponentButton';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
|
||||
export function Miscellaneous() {
|
||||
+20
-20
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
asset,
|
||||
formatCompact,
|
||||
ModuleDefinition,
|
||||
ModuleType,
|
||||
TankDefinition,
|
||||
tankDefinitions,
|
||||
tankIcon,
|
||||
Tier,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
Unlock,
|
||||
type ModuleDefinition,
|
||||
type ModuleType,
|
||||
type TankDefinition,
|
||||
type Tier,
|
||||
type Unlock,
|
||||
} from '@blitzkit/core';
|
||||
import {
|
||||
Button,
|
||||
@@ -19,9 +19,8 @@ import {
|
||||
Link,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import * as Duel from '../../../../../../../stores/duel';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
|
||||
function ModuleButton({
|
||||
tier,
|
||||
@@ -112,9 +111,10 @@ function ModuleButton({
|
||||
);
|
||||
}
|
||||
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
|
||||
export function Modules() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const awaitedTankDefinitions = use(tankDefinitions);
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const hasUpgrades =
|
||||
tank.turrets.length > 1 ||
|
||||
@@ -264,23 +264,23 @@ export function Modules() {
|
||||
(unlock.type === 'turret'
|
||||
? turret.id
|
||||
: unlock.type === 'gun'
|
||||
? gun.id
|
||||
: unlock.type === 'engine'
|
||||
? engine.id
|
||||
: unlock.type === 'chassis'
|
||||
? track.id
|
||||
: -1) === unlock.id
|
||||
? gun.id
|
||||
: unlock.type === 'engine'
|
||||
? engine.id
|
||||
: unlock.type === 'chassis'
|
||||
? track.id
|
||||
: -1) === unlock.id
|
||||
}
|
||||
top={
|
||||
unlock.type === 'turret'
|
||||
? module.id == topTurret.id
|
||||
: unlock.type === 'gun'
|
||||
? module.id === topGun.id
|
||||
: unlock.type === 'engine'
|
||||
? module.id === topEngine.id
|
||||
: unlock.type === 'chassis'
|
||||
? module.id === topTrack.id
|
||||
: false
|
||||
? module.id === topGun.id
|
||||
: unlock.type === 'engine'
|
||||
? module.id === topEngine.id
|
||||
: unlock.type === 'chassis'
|
||||
? module.id === topTrack.id
|
||||
: false
|
||||
}
|
||||
onClick={() => setByUnlock(unlock)}
|
||||
/>
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
import { availableProvisions, provisionDefinitions } from '@blitzkit/core';
|
||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { ProvisionsManager } from '../../../../../../../components/ProvisionsManager';
|
||||
import * as Duel from '../../../../../../../stores/duel';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { ProvisionsManager } from '../../../ProvisionsManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
|
||||
const awaitedProvisionDefinitions = await provisionDefinitions;
|
||||
|
||||
export function Provisions() {
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const { tank, gun } = Duel.use((state) => state.protagonist);
|
||||
const provisions = Duel.use((state) => state.protagonist.provisions);
|
||||
const awaitedProvisionDefinitions = use(provisionDefinitions);
|
||||
const provisionsList = availableProvisions(
|
||||
tank,
|
||||
gun,
|
||||
+5
-5
@@ -9,13 +9,13 @@ import {
|
||||
Popover,
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { CrewSkillManager } from '../../../../../../../../components/CrewSkillManager';
|
||||
import * as TankopediaEphemeral from '../../../../../../../../stores/tankopediaEphemeral';
|
||||
import { ConfigurationChildWrapper } from '../ConfigurationChildWrapper';
|
||||
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
||||
import { CrewSkillManager } from '../../../CrewSkillManager';
|
||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||
|
||||
const awaitedSkillDefinitions = await skillDefinitions;
|
||||
|
||||
export function Skills() {
|
||||
const awaitedSkillDefinitions = use(skillDefinitions);
|
||||
const skillLevels = TankopediaEphemeral.use((state) => state.skills);
|
||||
const mutateTankopediaTemporary = TankopediaEphemeral.useMutation();
|
||||
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
import { Flex, Heading } from '@radix-ui/themes';
|
||||
import { Characteristics } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics';
|
||||
import { Consumables } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics/components/Consumables';
|
||||
import { Equipment } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics/components/Equipment';
|
||||
import { Miscellaneous } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics/components/Miscellaneous';
|
||||
import { Modules } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics/components/Modules';
|
||||
import { Provisions } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics/components/Provisions';
|
||||
import { Skills } from '../../../../website-legacy/src/app/tools/tankopedia/[id]/components/Characteristics/components/Skills';
|
||||
import { Characteristics } from './components/Characteristics';
|
||||
import { Consumables } from './components/Consumables';
|
||||
import { Equipment } from './components/Equipment';
|
||||
import { Miscellaneous } from './components/Miscellaneous';
|
||||
import { Modules } from './components/Modules';
|
||||
import { Provisions } from './components/Provisions';
|
||||
import { Skills } from './components/Skills';
|
||||
|
||||
export function CharacteristicsSection() {
|
||||
return (
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
provisionDefinitions,
|
||||
} from '@blitzkit/core';
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import * as Duel from '../../../../../../stores/duel';
|
||||
import { Duel } from '../../stores/duel';
|
||||
|
||||
const awaitedGameDefinitions = await gameDefinitions;
|
||||
const awaitedConsumableDefinitions = await consumableDefinitions;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Heading, Text } from '@radix-ui/themes';
|
||||
import { PageWrapper } from '../../../../../components/PageWrapper';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import { Duel } from '../../stores/duel';
|
||||
import { PageWrapper } from '../PageWrapper';
|
||||
|
||||
export function HistorySection() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, Text, TextProps } from '@radix-ui/themes';
|
||||
import { Flex, Text, type TextProps } from '@radix-ui/themes';
|
||||
|
||||
type ListingProps = TextProps & {
|
||||
label: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StarsInt } from '@blitzkit/core';
|
||||
import { Flex, Text, TextProps } from '@radix-ui/themes';
|
||||
import type { StarsInt } from '@blitzkit/core';
|
||||
import { type TextProps, Flex, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { PartialStar } from './components/PartialStar';
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export function MetaSection() {
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{assertSecret(process.env.PUBLIC_PROMOTE_OPENTEST) === 'true' && (
|
||||
{assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === 'true' && (
|
||||
<Link
|
||||
href={`https://opentest.blitzkit.app/tools/tankopedia/${protagonist.id}`}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { ShotDisplayCard } from '../../../../../components/Armor/components/ShotDisplayCard';
|
||||
import * as TankEphemeral from '../../../../../stores/tankopediaEphemeral';
|
||||
import { TankopediaEphemeral } from '../../stores/tankopediaEphemeral';
|
||||
import { ShotDisplayCard } from '../Armor/components/ShotDisplayCard';
|
||||
|
||||
export function ShotDisplaySection() {
|
||||
const shot = TankEphemeral.use((state) => state.shot);
|
||||
const shot = TankopediaEphemeral.use((state) => state.shot);
|
||||
|
||||
if (!shot) return null;
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import {
|
||||
tankDefinitions,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
} from '@blitzkit/core';
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import Link from 'next/link';
|
||||
import * as TankopediaEphemeral from '../../../../../../../../stores/tankopediaEphemeral';
|
||||
import { Box, Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
||||
|
||||
interface NodeProps {
|
||||
id: number;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { asset, imgur, tankDefinitions } from '@blitzkit/core';
|
||||
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 * as Duel from '../../../../../../../stores/duel';
|
||||
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
|
||||
import { Duel } from '../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||
import { Arrow } from './components/Arrow';
|
||||
import { Node } from './components/Node';
|
||||
|
||||
@@ -168,6 +168,7 @@ export function TechTreeSection() {
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
{index > 0 && <Arrow key={`${id}-arrow`} />}
|
||||
|
||||
<Node
|
||||
key={`${id}-node`}
|
||||
id={id}
|
||||
|
||||
+7
-8
@@ -1,15 +1,14 @@
|
||||
import { reviews, youtubers } from '@blitzkit/core';
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { use } from 'react';
|
||||
import { Link } from '../../../../../components/Link';
|
||||
import { PageWrapper } from '../../../../../components/PageWrapper';
|
||||
import { VerifiedIcon } from '../../../../../components/VerifiedIcon';
|
||||
import { Var } from '../../../../../core/radix/var';
|
||||
import * as Duel from '../../../../../stores/duel';
|
||||
import { Box, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import { Duel } from '../../stores/duel';
|
||||
import { PageWrapper } from '../PageWrapper';
|
||||
import { VerifiedIcon } from '../VerifiedIcon';
|
||||
|
||||
const awaitedReviews = await reviews;
|
||||
|
||||
export function VideoSection() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const awaitedReviews = use(reviews);
|
||||
const videos = awaitedReviews.reviews[tank.id]?.videos;
|
||||
|
||||
if (!videos) return null;
|
||||
@@ -1,25 +1,25 @@
|
||||
import {
|
||||
coefficient,
|
||||
degressiveStat,
|
||||
EngineDefinition,
|
||||
EquipmentDefinitions,
|
||||
GunDefinition,
|
||||
isExplosive,
|
||||
ModelDefinition,
|
||||
normalizeBoundingBox,
|
||||
progressiveStat,
|
||||
ProvisionDefinitions,
|
||||
resolveDpm,
|
||||
resolveNearPenetration,
|
||||
resolvePenetrationCoefficient,
|
||||
ShellDefinition,
|
||||
sum,
|
||||
TankDefinition,
|
||||
TrackDefinition,
|
||||
TurretDefinition,
|
||||
unionBoundingBox,
|
||||
type EngineDefinition,
|
||||
type EquipmentDefinitions,
|
||||
type GunDefinition,
|
||||
type ModelDefinition,
|
||||
type ProvisionDefinitions,
|
||||
type ShellDefinition,
|
||||
type TankDefinition,
|
||||
type TrackDefinition,
|
||||
type TurretDefinition,
|
||||
} from '@blitzkit/core';
|
||||
import { EquipmentMatrix } from '../../stores/duel';
|
||||
import { coefficient } from '@blitzkit/core/src/blitzkit/coefficient';
|
||||
import type { EquipmentMatrix } from '../../stores/duel';
|
||||
|
||||
export type TankCharacteristics = ReturnType<typeof tankCharacteristics>;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { $patreonLogin } from '../stores/patreonLogin';
|
||||
|
||||
let cache: Record<string, boolean | Promise<boolean>> = {};
|
||||
@@ -8,7 +8,7 @@ export function useAdExempt() {
|
||||
const patreon = useStore($patreonLogin);
|
||||
const [exempt, setExempt] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!patreon.token) return setExempt(false);
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useFullscreenAvailability() {
|
||||
const [available, setAvailable] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
setAvailable(document.fullscreenEnabled);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type RefObject, useLayoutEffect, useRef } from 'react';
|
||||
import { type RefObject, useEffect, useRef } from 'react';
|
||||
|
||||
interface UseIntersectionOptions extends IntersectionObserverInit {
|
||||
disabled: boolean;
|
||||
@@ -11,7 +11,7 @@ export function useIntersection(
|
||||
) {
|
||||
const observer = useRef<IntersectionObserver>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
if (options?.disabled || !ref.current) return;
|
||||
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
|
||||
@@ -4,7 +4,15 @@ import {
|
||||
tankDefinitions,
|
||||
} from '@blitzkit/core';
|
||||
import { PageWrapper } from '../../../../components/PageWrapper';
|
||||
import { CalloutsSection } from '../../../../components/Tankopedia/CalloutsSection';
|
||||
import { CharacteristicsSection } from '../../../../components/Tankopedia/CharacteristicsSection';
|
||||
import { GameModeSection } from '../../../../components/Tankopedia/GameModeSection';
|
||||
import { HeroSection } from '../../../../components/Tankopedia/HeroSection';
|
||||
import { HistorySection } from '../../../../components/Tankopedia/HistorySection';
|
||||
import { MetaSection } from '../../../../components/Tankopedia/MetaSection';
|
||||
import { ShotDisplaySection } from '../../../../components/Tankopedia/ShotDisplaySection';
|
||||
import { TechTreeSection } from '../../../../components/Tankopedia/TechTreeSection';
|
||||
import { VideoSection } from '../../../../components/Tankopedia/VideoSection';
|
||||
import { App } from '../../../../stores/app';
|
||||
import { Duel } from '../../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
||||
@@ -31,17 +39,17 @@ export function Page({ id }: PageProps) {
|
||||
>
|
||||
<PageWrapper p="0" noMaxWidth color="purple" size={1600} gap="9">
|
||||
<HeroSection />
|
||||
{/* <ShotDisplaySection />
|
||||
<MetaSection />
|
||||
<TechTreeSection />
|
||||
{!exempt && <AdMidSectionResponsive />}
|
||||
<CalloutsSection />
|
||||
<CharacteristicsSection />
|
||||
{!exempt && <AdMidSectionResponsive />}
|
||||
<GameModeSection />
|
||||
<VideoSection />
|
||||
<HistorySection />
|
||||
{!exempt && <AdMidSectionResponsive mb="6" />} */}
|
||||
<ShotDisplaySection />
|
||||
<CalloutsSection />
|
||||
<MetaSection />
|
||||
<TechTreeSection />
|
||||
{/* {!exempt && <AdMidSectionResponsive />} */}
|
||||
<CharacteristicsSection />
|
||||
{/* {!exempt && <AdMidSectionResponsive />} */}
|
||||
<GameModeSection />
|
||||
<VideoSection />
|
||||
<HistorySection />
|
||||
{/* {!exempt && <AdMidSectionResponsive mb="6" />} */}
|
||||
</PageWrapper>
|
||||
</Duel.Provider>
|
||||
</TankopediaPersistent.Provider>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { TextProps } from '@radix-ui/themes';
|
||||
import { EmbedConfig } from '.';
|
||||
import * as EmbedState from './';
|
||||
import { EmbedConfigItemType, EmbedItemType } from './constants';
|
||||
import type { TextProps } from '@radix-ui/themes';
|
||||
import type { EmbedConfig } from '.';
|
||||
import type { EmbedConfigItemType, EmbedItemType } from './constants';
|
||||
|
||||
export function useEmbedStateCurry<Config extends EmbedConfig>() {
|
||||
function useState<Key extends keyof Config>(key: Key) {
|
||||
|
||||
Reference in new issue
Block a user