feat: some hero code

This commit is contained in:
tresabhi committed 2025-03-20 12:33:36 -05:00
1 parent b856268ee0
commit 46374a7a26
11 files changed
+287 -327

No files matched your search

@@ -1,64 +0,0 @@
import { TankType } 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 { MetaSection } from '../../../../../components/Tankopedia/MetaSection';
import { TechTreeSection } from '../../../../../components/Tankopedia/TechTreeSection';
import { VideoSection } from '../../../../../components/Tankopedia/VideoSection';
import { awaitableModelDefinitions } from '../../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions';
import { awaitableTankDefinitions } from '../../../../../core/awaitables/tankDefinitions';
import {
LocaleProvider,
type LocaleAcceptorProps,
} from '../../../../../hooks/useLocale';
import { App } from '../../../../../stores/app';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import type { MaybeSkeletonComponentProps } from '../../../../../types/maybeSkeletonComponentProps';
type PageProps = MaybeSkeletonComponentProps &
LocaleAcceptorProps & {
id: number;
};
const [tankDefinitions, provisionDefinitions, modelDefinitions] =
await Promise.all([
awaitableTankDefinitions,
awaitableProvisionDefinitions,
awaitableModelDefinitions,
]);
export function Page({ id, skeleton, locale }: PageProps) {
const tank = tankDefinitions.tanks[id];
const model = modelDefinitions.models[id];
return (
<LocaleProvider locale={locale}>
<TankopediaEphemeral.Provider data={model}>
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider
data={{ tank, provisionDefinitions: provisionDefinitions }}
>
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
<HeroSection skeleton={skeleton} />
<CalloutsSection />
<MetaSection />
{tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
<TechTreeSection skeleton={skeleton} />
)}
<CharacteristicsSection />
<GameModeSection />
<VideoSection skeleton={skeleton} />
</PageWrapper>
</Duel.Provider>
</TankopediaPersistent.Provider>
</App.Provider>
</TankopediaEphemeral.Provider>
</LocaleProvider>
);
}
@@ -1,110 +0,0 @@
---
import {
asset,
createDefaultProvisions,
createDefaultSkills,
fetchEquipmentDefinitions,
fetchModelDefinitions,
fetchProvisionDefinitions,
fetchSkillDefinitions,
fetchTankDefinitions,
TIER_ROMAN_NUMERALS,
} from '@blitzkit/core';
import { DEFAULT_LOCALE, unwrapper } from '@blitzkit/i18n';
import { literals } from '@blitzkit/i18n/src/literals';
import type { GetStaticPaths } from 'astro';
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
import { getStrings } from '../../../../../core/i18n/getStrings';
import BaseLayout from '../../../../../layouts/BaseLayout.astro';
import { genericDefaultEquipmentMatrix } from '../../../../../stores/duel/constants';
import { getStaticPaths as parentGetStaticPaths } from '../index.astro';
import { Page } from './_index';
export const getStaticPaths = (async () => {
const tankDefinitions = await fetchTankDefinitions();
const parentPaths = parentGetStaticPaths();
return Object.values(tankDefinitions.tanks)
.map((tank) =>
parentPaths.map((parent) => ({
params: { ...parent.params, id: tank.id },
})),
)
.flat();
}) satisfies GetStaticPaths;
const { id } = Astro.params;
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
const strings = getStrings(locale);
const tankDefinitions = await fetchTankDefinitions();
const skillDefinitions = await fetchSkillDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
const equipmentDefinitions = await fetchEquipmentDefinitions();
const tankModelDefinitions = await fetchModelDefinitions();
const tank = tankDefinitions.tanks[id];
const tankModelDefinition = tankModelDefinitions.models[tank.id];
const name = tank.name_full ?? tank.name;
const unwrap = unwrapper(locale);
const title = literals(strings.website.tools.tanks.tank_seo_title, [
unwrap(name),
TIER_ROMAN_NUMERALS[tank.tier],
strings.common.nations_adjectives[
tank.nation as keyof typeof strings.common.nations_adjectives
],
strings.common.tank_class_short[tank.class],
'BlitzKit',
strings.common.nations[tank.nation as keyof typeof strings.common.nations],
]);
const url = import.meta.env.SITE + Astro.url.pathname;
const icon = asset(`icons/tanks/big/${id}.webp`);
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
const characteristics = tankCharacteristics(
{
tank,
applyDynamicArmor: false,
applyReactiveArmor: false,
applySpallLiner: false,
camouflage: true,
consumables: [],
crewSkills: createDefaultSkills(skillDefinitions),
engine: tank.engines.at(-1)!,
equipmentMatrix: genericDefaultEquipmentMatrix,
turret,
gun,
provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
shell: gun.gun_type!.value.base.shells[0],
stockEngine: tank.engines[0],
stockGun: tank.turrets[0].guns[0],
stockTurret: tank.turrets[0],
stockTrack: tank.tracks[0],
track: tank.tracks.at(-1)!,
},
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
);
const description = literals(strings.website.tools.tanks.tank_seo_description, [
unwrap(name),
Math.round(characteristics.dpm).toLocaleString(locale),
Math.round(characteristics.damage).toLocaleString(locale),
Math.round(characteristics.penetration).toLocaleString(locale),
Math.round(characteristics.speedForwards).toLocaleString(locale),
Math.round(characteristics.health).toLocaleString(locale),
]);
---
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta property="og:image" content={icon} />
<meta property="og:url" content={url} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:card" content="summary" />
<meta name="twitter:image:src" content={icon} />
<BaseLayout>
<Page {locale} id={id} client:only="react">
<Page {locale} id={id} skeleton slot="fallback" />
</Page>
</BaseLayout>
@@ -1,9 +1,8 @@
import { asset } from '@blitzkit/core'; import { Box, Flex, type FlexProps, Progress } from '@radix-ui/themes';
import { Flex, type FlexProps, Progress } from '@radix-ui/themes';
import { useProgress } from '@react-three/drei'; import { useProgress } from '@react-three/drei';
type TankSandboxLoaderProps = Omit<FlexProps, 'id'> & { type TankSandboxLoaderProps = Omit<FlexProps, 'id'> & {
id: number; id: string;
}; };
export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) { export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
@@ -11,9 +10,15 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
return ( return (
<Flex width="100%" height="100%" align="center" justify="center" {...props}> <Flex width="100%" height="100%" align="center" justify="center" {...props}>
<img <Box
src={asset(`icons/tanks/blitzkit/${id}.webp`)} height="100%"
style={{ height: '75%', filter: 'blur(1rem)' }} width="100%"
style={{
filter: 'blur(1rem)',
backgroundImage: `url(/api/tanks/${id}/icons/big.png)`,
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
/> />
<Progress <Progress
size="3" size="3"
@@ -1,77 +1,70 @@
import { TankType } from '@blitzkit/core';
import { Box, Flex, Heading, Text } from '@radix-ui/themes'; import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { times } from 'lodash-es'; import { useRef } from 'react';
import { Suspense, useEffect, useMemo, useRef } from 'react';
import { NAVBAR_HEIGHT } from '../../../constants/navbar'; import { NAVBAR_HEIGHT } from '../../../constants/navbar';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { Var } from '../../../core/radix/var'; import { Var } from '../../../core/radix/var';
import { useFullScreen } from '../../../hooks/useFullScreen'; import { useFullScreen } from '../../../hooks/useFullScreen';
import { useLocale } from '../../../hooks/useLocale'; import { useLocale } from '../../../hooks/useLocale';
import { Duel } from '../../../stores/duel'; import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
import type { ThicknessRange } from '../../Armor/components/StaticArmor';
import { classIcons } from '../../ClassIcon';
import { Options } from './components/Options';
import { TankSandbox } from './components/TankSandbox';
import { TankSandboxLoader } from './components/TankSandboxLoader'; import { TankSandboxLoader } from './components/TankSandboxLoader';
const tankDefinitions = await awaitableTankDefinitions;
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) { export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
const armor = TankopediaEphemeral.use((state) => state.armor);
const { unwrap } = useLocale(); const { unwrap } = useLocale();
const canvas = useRef<HTMLCanvasElement>(null); const canvas = useRef<HTMLCanvasElement>(null);
const isFullScreen = useFullScreen(); const isFullScreen = useFullScreen();
const protagonist = Duel.use((state) => state.protagonist.tank); const protagonist = Duel.use((state) => state.protagonist.tank);
const Icon = classIcons[protagonist.class]; // const Icon = classIcons[protagonist.class];
const treeColor = // const treeColor =
protagonist.type === TankType.COLLECTOR // protagonist.type === TankType.COLLECTOR
? 'blue' // ? 'blue'
: protagonist.type === TankType.PREMIUM // : protagonist.type === TankType.PREMIUM
? 'amber' // ? 'amber'
: undefined; // : undefined;
const thicknessRange = useMemo(() => { // const thicknessRange = useMemo(() => {
const entries = Object.values(tankDefinitions.tanks); // const entries = Object.values(tankDefinitions.tanks);
const filtered = entries.filter( // const filtered = entries.filter(
(thisTank) => thisTank.tier === protagonist.tier, // (thisTank) => thisTank.tier === protagonist.tier,
); // );
const value = // const value =
(filtered.reduce((accumulator, thisTank) => { // (filtered.reduce((accumulator, thisTank) => {
return ( // return (
accumulator + // accumulator +
thisTank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0] // thisTank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
.penetration.near // .penetration.near
); // );
}, 0) / // }, 0) /
filtered.length) * // filtered.length) *
(3 / 4); // (3 / 4);
return { value } satisfies ThicknessRange; // return { value } satisfies ThicknessRange;
}, [protagonist]); // }, [protagonist]);
const duelStore = Duel.useStore(); const thicknessRange = { value: 300 };
const mutateDuel = Duel.useMutation(); // const duelStore = Duel.useStore();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); // const mutateDuel = Duel.useMutation();
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
useEffect(() => { // useEffect(() => {
function handleKeyDown(event: KeyboardEvent) { // function handleKeyDown(event: KeyboardEvent) {
const { shells } = // const { shells } =
duelStore.getState().antagonist.gun.gun_type!.value.base; // duelStore.getState().antagonist.gun.gun_type!.value.base;
times(3, (index) => { // times(3, (index) => {
if (event.key === `${index + 1}` && shells.length > index) { // if (event.key === `${index + 1}` && shells.length > index) {
mutateDuel((draft) => { // mutateDuel((draft) => {
draft.antagonist.shell = shells[index]; // draft.antagonist.shell = shells[index];
}); // });
mutateTankopediaEphemeral((draft) => { // mutateTankopediaEphemeral((draft) => {
draft.customShell = undefined; // draft.customShell = undefined;
}); // });
} // }
}); // });
} // }
window.addEventListener('keydown', handleKeyDown); // window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown); // return () => window.removeEventListener('keydown', handleKeyDown);
}, []); // }, []);
return ( return (
<Flex justify="center" style={{ backgroundColor: Var('color-surface') }}> <Flex justify="center" style={{ backgroundColor: Var('color-surface') }}>
@@ -105,19 +98,19 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
align={{ initial: 'center', md: 'start' }} align={{ initial: 'center', md: 'start' }}
> >
<Flex align="center" gap="3"> <Flex align="center" gap="3">
<Heading {/* <Heading
color={treeColor} color={treeColor}
trim="end" trim="end"
size={{ initial: '7', lg: '6' }} size={{ initial: '7', lg: '6' }}
> >
<Icon width="1em" height="1em" /> <Icon width="1em" height="1em" />
</Heading> </Heading> */}
<Heading <Heading
weight="bold" weight="bold"
size={{ initial: '7', lg: '8' }} size={{ initial: '7', lg: '8' }}
wrap="nowrap" wrap="nowrap"
color={treeColor} // color={treeColor}
> >
{unwrap(protagonist.name)} {unwrap(protagonist.name)}
</Heading> </Heading>
@@ -144,12 +137,12 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
<Box width="100%" height="100%"> <Box width="100%" height="100%">
{skeleton && <TankSandboxLoader id={protagonist.id} />} {skeleton && <TankSandboxLoader id={protagonist.id} />}
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}> {/* <Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} /> <TankSandbox ref={canvas} thicknessRange={thicknessRange} />
</Suspense> </Suspense> */}
</Box> </Box>
<Options canvas={canvas} thicknessRange={thicknessRange} /> {/* <Options canvas={canvas} thicknessRange={thicknessRange} /> */}
</Box> </Box>
</Box> </Box>
</Flex> </Flex>
@@ -1,31 +1,26 @@
import { import { Tank } from '@blitzkit/core';
createDefaultProvisions,
type ProvisionDefinitions,
type TankDefinition,
} from '@blitzkit/core';
import type { DuelMember } from '../../stores/duel'; import type { DuelMember } from '../../stores/duel';
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
export function tankToDuelMember( export function tankToDuelMember(
tank: TankDefinition, tank: Tank,
provisionDefinitions: ProvisionDefinitions, // provisionDefinitions: ProvisionDefinitions,
) { ) {
const turret = tank.turrets.at(-1)!; // const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!; // const gun = turret.guns.at(-1)!;
return { return {
camouflage: true, // camouflage: true,
consumables: [], // consumables: [],
equipmentMatrix: genericDefaultEquipmentMatrix, // equipmentMatrix: genericDefaultEquipmentMatrix,
cooldownBooster: 0, // cooldownBooster: 0,
provisions: createDefaultProvisions(tank, gun, provisionDefinitions), // provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
engine: tank.engines.at(-1)!, // engine: tank.engines.at(-1)!,
gun, // gun,
shell: tank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0], // shell: tank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0],
tank, tank,
track: tank.tracks.at(-1)!, // track: tank.tracks.at(-1)!,
yaw: 0, // yaw: 0,
pitch: 0, // pitch: 0,
turret, // turret,
} satisfies DuelMember; } satisfies DuelMember;
} }
@@ -1,26 +1,49 @@
import { Tank, TankArmor } from '@blitzkit/core'; import { Tank, TankArmor } from '@blitzkit/core';
import { Box } from '@radix-ui/themes';
import { OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { ArmorGroup } from '../../../../components/ArmorGroup';
import { PageWrapper } from '../../../../components/PageWrapper'; import { PageWrapper } from '../../../../components/PageWrapper';
import { HeroSection } from '../../../../components/Tankopedia/HeroSection';
import {
LocaleProvider,
type LocaleAcceptorProps,
} from '../../../../hooks/useLocale';
import { App } from '../../../../stores/app';
import { Duel } from '../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
interface PageProps { type PageProps = MaybeSkeletonComponentProps &
tank: Tank; LocaleAcceptorProps & {
armor: TankArmor; tank: Tank;
} armor: TankArmor;
};
export function Page({ tank, armor }: PageProps) { export function Page({ tank, armor, skeleton, locale }: PageProps) {
return ( return (
<PageWrapper p="0" maxWidth="unset"> <LocaleProvider locale={locale}>
<Box height="100%" width="100%" flexGrow="1" position="relative"> <TankopediaEphemeral.Provider data={armor}>
<Box position="absolute" top="0" left="0" width="100%" height="100%"> <App.Provider>
<Canvas> <TankopediaPersistent.Provider>
<OrbitControls /> <Duel.Provider
<ArmorGroup group="hull" tank={tank} armor={armor} /> data={{
</Canvas> tank,
</Box> // provisionDefinitions,
</Box> }}
</PageWrapper> >
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
<HeroSection skeleton={skeleton} />
{/* <CalloutsSection /> */}
{/* <MetaSection /> */}
{/* {tank.type === TankType.RESEARCHABLE && !tank.deprecated && (
<TechTreeSection skeleton={skeleton} />
)} */}
{/* <CharacteristicsSection /> */}
{/* <GameModeSection /> */}
{/* <VideoSection skeleton={skeleton} /> */}
</PageWrapper>
</Duel.Provider>
</TankopediaPersistent.Provider>
</App.Provider>
</TankopediaEphemeral.Provider>
</LocaleProvider>
); );
} }
@@ -0,0 +1,30 @@
---
import { fetchTank, fetchTankArmor } from '@blitzkit/core';
import type { GetStaticPaths } from 'astro';
import { awaitableTanksFull } from '../../../../core/awaitables/tanksFull';
import BaseLayout from '../../../../layouts/BaseLayout.astro';
import { getStaticPaths as parentGetStaticPaths } from '../index.astro';
import { Page } from './_pre_index';
export const getStaticPaths = (async () => {
const tanksFull = await awaitableTanksFull;
const parentPaths = parentGetStaticPaths();
return tanksFull.tanks
.map((tank) =>
parentPaths.map((parent) => ({
params: { ...parent.params, slug: tank.slug },
props: { id: tank.id },
})),
)
.flat();
}) satisfies GetStaticPaths;
const { id } = Astro.props;
const tank = await fetchTank(id);
const armor = await fetchTankArmor(id);
---
<BaseLayout>
<Page {tank} {armor} client:load />
</BaseLayout>
@@ -0,0 +1,26 @@
import { Tank, TankArmor } from '@blitzkit/core';
import { Box } from '@radix-ui/themes';
import { OrbitControls } from '@react-three/drei';
import { Canvas } from '@react-three/fiber';
import { ArmorGroup } from '../../../../components/ArmorGroup';
import { PageWrapper } from '../../../../components/PageWrapper';
interface PageProps {
tank: Tank;
armor: TankArmor;
}
export function Page({ tank, armor }: PageProps) {
return (
<PageWrapper p="0" maxWidth="unset">
<Box height="100%" width="100%" flexGrow="1" position="relative">
<Box position="absolute" top="0" left="0" width="100%" height="100%">
<Canvas>
<OrbitControls />
<ArmorGroup group="hull" tank={tank} armor={armor} />
</Canvas>
</Box>
</Box>
</PageWrapper>
);
}
@@ -1,5 +1,6 @@
--- ---
import { fetchTank, fetchTankArmor } from '@blitzkit/core'; import { fetchTank, fetchTankArmor } from '@blitzkit/core';
import { DEFAULT_LOCALE } from '@blitzkit/i18n';
import type { GetStaticPaths } from 'astro'; import type { GetStaticPaths } from 'astro';
import { awaitableTanksFull } from '../../../../core/awaitables/tanksFull'; import { awaitableTanksFull } from '../../../../core/awaitables/tanksFull';
import BaseLayout from '../../../../layouts/BaseLayout.astro'; import BaseLayout from '../../../../layouts/BaseLayout.astro';
@@ -20,11 +21,80 @@ export const getStaticPaths = (async () => {
.flat(); .flat();
}) satisfies GetStaticPaths; }) satisfies GetStaticPaths;
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
const { id } = Astro.props; const { id } = Astro.props;
const tank = await fetchTank(id); const tank = await fetchTank(id);
const armor = await fetchTankArmor(id); const armor = await fetchTankArmor(id);
// const strings = getStrings(locale);
// const tankDefinitions = await fetchTankDefinitions();
// const skillDefinitions = await fetchSkillDefinitions();
// const provisionDefinitions = await fetchProvisionDefinitions();
// const equipmentDefinitions = await fetchEquipmentDefinitions();
// const tankModelDefinitions = await fetchModelDefinitions();
// const tank = tankDefinitions.tanks[id];
// const tankModelDefinition = tankModelDefinitions.models[tank.id];
// const name = tank.name_full ?? tank.name;
// const unwrap = unwrapper(locale);
// const title = literals(strings.website.tools.tanks.tank_seo_title, [
// unwrap(name),
// TIER_ROMAN_NUMERALS[tank.tier],
// strings.common.nations_adjectives[
// tank.nation as keyof typeof strings.common.nations_adjectives
// ],
// strings.common.tank_class_short[tank.class],
// 'BlitzKit',
// strings.common.nations[tank.nation as keyof typeof strings.common.nations],
// ]);
// const url = import.meta.env.SITE + Astro.url.pathname;
// const icon = asset(`icons/tanks/big/${id}.webp`);
// const turret = tank.turrets.at(-1)!;
// const gun = turret.guns.at(-1)!;
// const characteristics = tankCharacteristics(
// {
// tank,
// applyDynamicArmor: false,
// applyReactiveArmor: false,
// applySpallLiner: false,
// camouflage: true,
// consumables: [],
// crewSkills: createDefaultSkills(skillDefinitions),
// engine: tank.engines.at(-1)!,
// equipmentMatrix: genericDefaultEquipmentMatrix,
// turret,
// gun,
// provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
// shell: gun.gun_type!.value.base.shells[0],
// stockEngine: tank.engines[0],
// stockGun: tank.turrets[0].guns[0],
// stockTurret: tank.turrets[0],
// stockTrack: tank.tracks[0],
// track: tank.tracks.at(-1)!,
// },
// { equipmentDefinitions, provisionDefinitions, tankModelDefinition },
// );
// const description = literals(strings.website.tools.tanks.tank_seo_description, [
// unwrap(name),
// Math.round(characteristics.dpm).toLocaleString(locale),
// Math.round(characteristics.damage).toLocaleString(locale),
// Math.round(characteristics.penetration).toLocaleString(locale),
// Math.round(characteristics.speedForwards).toLocaleString(locale),
// Math.round(characteristics.health).toLocaleString(locale),
// ]);
--- ---
<!-- <title>{title}</title>
<meta property="og:title" content={title} />
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta property="og:image" content={icon} />
<meta property="og:url" content={url} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:card" content="summary" />
<meta name="twitter:image:src" content={icon} /> -->
<BaseLayout> <BaseLayout>
<Page {tank} {armor} client:load /> <Page {tank} {armor} {locale} client:only="react">
<Page {tank} {armor} {locale} skeleton slot="fallback" />
</Page>
</BaseLayout> </BaseLayout>
+20 -26
View File
@@ -1,12 +1,4 @@
import { import { Tank } from '@blitzkit/core';
type EngineDefinition,
type GunDefinition,
type ProvisionDefinitions,
type ShellDefinition,
type TankDefinition,
type TrackDefinition,
type TurretDefinition,
} from '@blitzkit/core';
import { create } from 'zustand'; import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware'; import { subscribeWithSelector } from 'zustand/middleware';
import { tankToDuelMember } from '../../core/blitzkit/tankToDuelMember'; import { tankToDuelMember } from '../../core/blitzkit/tankToDuelMember';
@@ -25,19 +17,19 @@ export type EquipmentMatrix = [
]; ];
export interface DuelMember { export interface DuelMember {
tank: TankDefinition; tank: Tank;
engine: EngineDefinition; // engine: EngineDefinition;
turret: TurretDefinition; // turret: TurretDefinition;
gun: GunDefinition; // gun: GunDefinition;
shell: ShellDefinition; // shell: ShellDefinition;
track: TrackDefinition; // track: TrackDefinition;
equipmentMatrix: EquipmentMatrix; // equipmentMatrix: EquipmentMatrix;
pitch: number; // pitch: number;
yaw: number; // yaw: number;
consumables: number[]; // consumables: number[];
provisions: number[]; // provisions: number[];
camouflage: boolean; // camouflage: boolean;
cooldownBooster: number; // cooldownBooster: number;
} }
export interface DuelStore { export interface DuelStore {
@@ -48,12 +40,14 @@ export interface DuelStore {
export const Duel = createContextualStore( export const Duel = createContextualStore(
({ ({
tank, tank,
provisionDefinitions, // provisionDefinitions,
}: { }: {
tank: TankDefinition; tank: Tank;
provisionDefinitions: ProvisionDefinitions; // provisionDefinitions: ProvisionDefinitions;
}) => { }) => {
const protagonist = tankToDuelMember(tank, provisionDefinitions); const protagonist = tankToDuelMember(
tank, // provisionDefinitions
);
return create<DuelStore>()( return create<DuelStore>()(
subscribeWithSelector<DuelStore>(() => ({ subscribeWithSelector<DuelStore>(() => ({
@@ -1,7 +1,7 @@
import { import {
createDefaultSkills, createDefaultSkills,
ShellDefinition, ShellDefinition,
type ModelDefinition, TankArmor,
} from '@blitzkit/core'; } from '@blitzkit/core';
import type { Vector3 } from 'three'; import type { Vector3 } from 'three';
import { create } from 'zustand'; import { create } from 'zustand';
@@ -101,7 +101,7 @@ interface TankopediaEphemeral {
skills: Record<string, number>; skills: Record<string, number>;
relativeAgainst: TankopediaRelativeAgainst; relativeAgainst: TankopediaRelativeAgainst;
controlsEnabled: boolean; controlsEnabled: boolean;
model: ModelDefinition; armor: TankArmor;
editStatic: boolean; editStatic: boolean;
highlightArmor?: { highlightArmor?: {
editingPlate: boolean; editingPlate: boolean;
@@ -124,19 +124,17 @@ interface TankopediaEphemeral {
const skillDefinitions = await awaitableSkillDefinitions; const skillDefinitions = await awaitableSkillDefinitions;
export const TankopediaEphemeral = createContextualStore( export const TankopediaEphemeral = createContextualStore((armor: TankArmor) => {
(model: ModelDefinition) => { return create<TankopediaEphemeral>()(
return create<TankopediaEphemeral>()( subscribeWithSelector<TankopediaEphemeral>(() => ({
subscribeWithSelector<TankopediaEphemeral>(() => ({ shootingRangeZoom: ShootingRangeZoom.Arcade,
shootingRangeZoom: ShootingRangeZoom.Arcade, relativeAgainst: TankopediaRelativeAgainst.Class,
relativeAgainst: TankopediaRelativeAgainst.Class, editStatic: false,
editStatic: false, skills: createDefaultSkills(skillDefinitions),
skills: createDefaultSkills(skillDefinitions), armor,
model, controlsEnabled: true,
controlsEnabled: true, xpMultiplier: 1,
xpMultiplier: 1, display: TankopediaDisplay.Model,
display: TankopediaDisplay.Model, })),
})), );
); });
},
);