mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 11:17:05 +02:00
feat: some hero code
This commit is contained in:
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>
|
||||
+11
-6
@@ -1,9 +1,8 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { Flex, type FlexProps, Progress } from '@radix-ui/themes';
|
||||
import { Box, Flex, type FlexProps, Progress } from '@radix-ui/themes';
|
||||
import { useProgress } from '@react-three/drei';
|
||||
|
||||
type TankSandboxLoaderProps = Omit<FlexProps, 'id'> & {
|
||||
id: number;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
|
||||
@@ -11,9 +10,15 @@ export function TankSandboxLoader({ id, ...props }: TankSandboxLoaderProps) {
|
||||
|
||||
return (
|
||||
<Flex width="100%" height="100%" align="center" justify="center" {...props}>
|
||||
<img
|
||||
src={asset(`icons/tanks/blitzkit/${id}.webp`)}
|
||||
style={{ height: '75%', filter: 'blur(1rem)' }}
|
||||
<Box
|
||||
height="100%"
|
||||
width="100%"
|
||||
style={{
|
||||
filter: 'blur(1rem)',
|
||||
backgroundImage: `url(/api/tanks/${id}/icons/big.png)`,
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
/>
|
||||
<Progress
|
||||
size="3"
|
||||
|
||||
@@ -1,77 +1,70 @@
|
||||
import { TankType } from '@blitzkit/core';
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
||||
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||
import { Var } from '../../../core/radix/var';
|
||||
import { useFullScreen } from '../../../hooks/useFullScreen';
|
||||
import { useLocale } from '../../../hooks/useLocale';
|
||||
import { Duel } from '../../../stores/duel';
|
||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||
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';
|
||||
|
||||
const tankDefinitions = await awaitableTankDefinitions;
|
||||
|
||||
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const armor = TankopediaEphemeral.use((state) => state.armor);
|
||||
const { unwrap } = useLocale();
|
||||
const canvas = useRef<HTMLCanvasElement>(null);
|
||||
const isFullScreen = useFullScreen();
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const Icon = classIcons[protagonist.class];
|
||||
const treeColor =
|
||||
protagonist.type === TankType.COLLECTOR
|
||||
? 'blue'
|
||||
: protagonist.type === TankType.PREMIUM
|
||||
? 'amber'
|
||||
: undefined;
|
||||
const thicknessRange = useMemo(() => {
|
||||
const entries = Object.values(tankDefinitions.tanks);
|
||||
const filtered = entries.filter(
|
||||
(thisTank) => thisTank.tier === protagonist.tier,
|
||||
);
|
||||
const value =
|
||||
(filtered.reduce((accumulator, thisTank) => {
|
||||
return (
|
||||
accumulator +
|
||||
thisTank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
|
||||
.penetration.near
|
||||
);
|
||||
}, 0) /
|
||||
filtered.length) *
|
||||
(3 / 4);
|
||||
// const Icon = classIcons[protagonist.class];
|
||||
// const treeColor =
|
||||
// protagonist.type === TankType.COLLECTOR
|
||||
// ? 'blue'
|
||||
// : protagonist.type === TankType.PREMIUM
|
||||
// ? 'amber'
|
||||
// : undefined;
|
||||
// const thicknessRange = useMemo(() => {
|
||||
// const entries = Object.values(tankDefinitions.tanks);
|
||||
// const filtered = entries.filter(
|
||||
// (thisTank) => thisTank.tier === protagonist.tier,
|
||||
// );
|
||||
// const value =
|
||||
// (filtered.reduce((accumulator, thisTank) => {
|
||||
// return (
|
||||
// accumulator +
|
||||
// thisTank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0]
|
||||
// .penetration.near
|
||||
// );
|
||||
// }, 0) /
|
||||
// filtered.length) *
|
||||
// (3 / 4);
|
||||
|
||||
return { value } satisfies ThicknessRange;
|
||||
}, [protagonist]);
|
||||
const duelStore = Duel.useStore();
|
||||
const mutateDuel = Duel.useMutation();
|
||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
// return { value } satisfies ThicknessRange;
|
||||
// }, [protagonist]);
|
||||
const thicknessRange = { value: 300 };
|
||||
// const duelStore = Duel.useStore();
|
||||
// const mutateDuel = Duel.useMutation();
|
||||
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
const { shells } =
|
||||
duelStore.getState().antagonist.gun.gun_type!.value.base;
|
||||
// useEffect(() => {
|
||||
// function handleKeyDown(event: KeyboardEvent) {
|
||||
// const { shells } =
|
||||
// duelStore.getState().antagonist.gun.gun_type!.value.base;
|
||||
|
||||
times(3, (index) => {
|
||||
if (event.key === `${index + 1}` && shells.length > index) {
|
||||
mutateDuel((draft) => {
|
||||
draft.antagonist.shell = shells[index];
|
||||
});
|
||||
mutateTankopediaEphemeral((draft) => {
|
||||
draft.customShell = undefined;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// times(3, (index) => {
|
||||
// if (event.key === `${index + 1}` && shells.length > index) {
|
||||
// mutateDuel((draft) => {
|
||||
// draft.antagonist.shell = shells[index];
|
||||
// });
|
||||
// mutateTankopediaEphemeral((draft) => {
|
||||
// draft.customShell = undefined;
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
// window.addEventListener('keydown', handleKeyDown);
|
||||
// return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<Flex justify="center" style={{ backgroundColor: Var('color-surface') }}>
|
||||
@@ -105,19 +98,19 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
align={{ initial: 'center', md: 'start' }}
|
||||
>
|
||||
<Flex align="center" gap="3">
|
||||
<Heading
|
||||
{/* <Heading
|
||||
color={treeColor}
|
||||
trim="end"
|
||||
size={{ initial: '7', lg: '6' }}
|
||||
>
|
||||
<Icon width="1em" height="1em" />
|
||||
</Heading>
|
||||
</Heading> */}
|
||||
|
||||
<Heading
|
||||
weight="bold"
|
||||
size={{ initial: '7', lg: '8' }}
|
||||
wrap="nowrap"
|
||||
color={treeColor}
|
||||
// color={treeColor}
|
||||
>
|
||||
{unwrap(protagonist.name)}
|
||||
</Heading>
|
||||
@@ -144,12 +137,12 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
<Box width="100%" height="100%">
|
||||
{skeleton && <TankSandboxLoader id={protagonist.id} />}
|
||||
|
||||
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
|
||||
{/* <Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
|
||||
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
||||
</Suspense>
|
||||
</Suspense> */}
|
||||
</Box>
|
||||
|
||||
<Options canvas={canvas} thicknessRange={thicknessRange} />
|
||||
{/* <Options canvas={canvas} thicknessRange={thicknessRange} /> */}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
import {
|
||||
createDefaultProvisions,
|
||||
type ProvisionDefinitions,
|
||||
type TankDefinition,
|
||||
} from '@blitzkit/core';
|
||||
import { Tank } from '@blitzkit/core';
|
||||
import type { DuelMember } from '../../stores/duel';
|
||||
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
|
||||
|
||||
export function tankToDuelMember(
|
||||
tank: TankDefinition,
|
||||
provisionDefinitions: ProvisionDefinitions,
|
||||
tank: Tank,
|
||||
// provisionDefinitions: ProvisionDefinitions,
|
||||
) {
|
||||
const turret = tank.turrets.at(-1)!;
|
||||
const gun = turret.guns.at(-1)!;
|
||||
// const turret = tank.turrets.at(-1)!;
|
||||
// const gun = turret.guns.at(-1)!;
|
||||
|
||||
return {
|
||||
camouflage: true,
|
||||
consumables: [],
|
||||
equipmentMatrix: genericDefaultEquipmentMatrix,
|
||||
cooldownBooster: 0,
|
||||
provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
|
||||
engine: tank.engines.at(-1)!,
|
||||
gun,
|
||||
shell: tank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0],
|
||||
// camouflage: true,
|
||||
// consumables: [],
|
||||
// equipmentMatrix: genericDefaultEquipmentMatrix,
|
||||
// cooldownBooster: 0,
|
||||
// provisions: createDefaultProvisions(tank, gun, provisionDefinitions),
|
||||
// engine: tank.engines.at(-1)!,
|
||||
// gun,
|
||||
// shell: tank.turrets.at(-1)!.guns.at(-1)!.gun_type!.value.base.shells[0],
|
||||
tank,
|
||||
track: tank.tracks.at(-1)!,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
turret,
|
||||
// track: tank.tracks.at(-1)!,
|
||||
// yaw: 0,
|
||||
// pitch: 0,
|
||||
// turret,
|
||||
} satisfies DuelMember;
|
||||
}
|
||||
@@ -1,26 +1,49 @@
|
||||
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 { 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 {
|
||||
tank: Tank;
|
||||
armor: TankArmor;
|
||||
}
|
||||
type PageProps = MaybeSkeletonComponentProps &
|
||||
LocaleAcceptorProps & {
|
||||
tank: Tank;
|
||||
armor: TankArmor;
|
||||
};
|
||||
|
||||
export function Page({ tank, armor }: PageProps) {
|
||||
export function Page({ tank, armor, skeleton, locale }: 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>
|
||||
<LocaleProvider locale={locale}>
|
||||
<TankopediaEphemeral.Provider data={armor}>
|
||||
<App.Provider>
|
||||
<TankopediaPersistent.Provider>
|
||||
<Duel.Provider
|
||||
data={{
|
||||
tank,
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
@@ -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 { DEFAULT_LOCALE } from '@blitzkit/i18n';
|
||||
import type { GetStaticPaths } from 'astro';
|
||||
import { awaitableTanksFull } from '../../../../core/awaitables/tanksFull';
|
||||
import BaseLayout from '../../../../layouts/BaseLayout.astro';
|
||||
@@ -20,11 +21,80 @@ export const getStaticPaths = (async () => {
|
||||
.flat();
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||
const { id } = Astro.props;
|
||||
const tank = await fetchTank(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>
|
||||
<Page {tank} {armor} client:load />
|
||||
<Page {tank} {armor} {locale} client:only="react">
|
||||
<Page {tank} {armor} {locale} skeleton slot="fallback" />
|
||||
</Page>
|
||||
</BaseLayout>
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
type EngineDefinition,
|
||||
type GunDefinition,
|
||||
type ProvisionDefinitions,
|
||||
type ShellDefinition,
|
||||
type TankDefinition,
|
||||
type TrackDefinition,
|
||||
type TurretDefinition,
|
||||
} from '@blitzkit/core';
|
||||
import { Tank } from '@blitzkit/core';
|
||||
import { create } from 'zustand';
|
||||
import { subscribeWithSelector } from 'zustand/middleware';
|
||||
import { tankToDuelMember } from '../../core/blitzkit/tankToDuelMember';
|
||||
@@ -25,19 +17,19 @@ export type EquipmentMatrix = [
|
||||
];
|
||||
|
||||
export interface DuelMember {
|
||||
tank: TankDefinition;
|
||||
engine: EngineDefinition;
|
||||
turret: TurretDefinition;
|
||||
gun: GunDefinition;
|
||||
shell: ShellDefinition;
|
||||
track: TrackDefinition;
|
||||
equipmentMatrix: EquipmentMatrix;
|
||||
pitch: number;
|
||||
yaw: number;
|
||||
consumables: number[];
|
||||
provisions: number[];
|
||||
camouflage: boolean;
|
||||
cooldownBooster: number;
|
||||
tank: Tank;
|
||||
// engine: EngineDefinition;
|
||||
// turret: TurretDefinition;
|
||||
// gun: GunDefinition;
|
||||
// shell: ShellDefinition;
|
||||
// track: TrackDefinition;
|
||||
// equipmentMatrix: EquipmentMatrix;
|
||||
// pitch: number;
|
||||
// yaw: number;
|
||||
// consumables: number[];
|
||||
// provisions: number[];
|
||||
// camouflage: boolean;
|
||||
// cooldownBooster: number;
|
||||
}
|
||||
|
||||
export interface DuelStore {
|
||||
@@ -48,12 +40,14 @@ export interface DuelStore {
|
||||
export const Duel = createContextualStore(
|
||||
({
|
||||
tank,
|
||||
provisionDefinitions,
|
||||
// provisionDefinitions,
|
||||
}: {
|
||||
tank: TankDefinition;
|
||||
provisionDefinitions: ProvisionDefinitions;
|
||||
tank: Tank;
|
||||
// provisionDefinitions: ProvisionDefinitions;
|
||||
}) => {
|
||||
const protagonist = tankToDuelMember(tank, provisionDefinitions);
|
||||
const protagonist = tankToDuelMember(
|
||||
tank, // provisionDefinitions
|
||||
);
|
||||
|
||||
return create<DuelStore>()(
|
||||
subscribeWithSelector<DuelStore>(() => ({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createDefaultSkills,
|
||||
ShellDefinition,
|
||||
type ModelDefinition,
|
||||
TankArmor,
|
||||
} from '@blitzkit/core';
|
||||
import type { Vector3 } from 'three';
|
||||
import { create } from 'zustand';
|
||||
@@ -101,7 +101,7 @@ interface TankopediaEphemeral {
|
||||
skills: Record<string, number>;
|
||||
relativeAgainst: TankopediaRelativeAgainst;
|
||||
controlsEnabled: boolean;
|
||||
model: ModelDefinition;
|
||||
armor: TankArmor;
|
||||
editStatic: boolean;
|
||||
highlightArmor?: {
|
||||
editingPlate: boolean;
|
||||
@@ -124,19 +124,17 @@ interface TankopediaEphemeral {
|
||||
|
||||
const skillDefinitions = await awaitableSkillDefinitions;
|
||||
|
||||
export const TankopediaEphemeral = createContextualStore(
|
||||
(model: ModelDefinition) => {
|
||||
return create<TankopediaEphemeral>()(
|
||||
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
||||
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||
editStatic: false,
|
||||
skills: createDefaultSkills(skillDefinitions),
|
||||
model,
|
||||
controlsEnabled: true,
|
||||
xpMultiplier: 1,
|
||||
display: TankopediaDisplay.Model,
|
||||
})),
|
||||
);
|
||||
},
|
||||
);
|
||||
export const TankopediaEphemeral = createContextualStore((armor: TankArmor) => {
|
||||
return create<TankopediaEphemeral>()(
|
||||
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
||||
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||
editStatic: false,
|
||||
skills: createDefaultSkills(skillDefinitions),
|
||||
armor,
|
||||
controlsEnabled: true,
|
||||
xpMultiplier: 1,
|
||||
display: TankopediaDisplay.Model,
|
||||
})),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user