mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01: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 { 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>
|
||||||
@@ -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,
|
})),
|
||||||
})),
|
);
|
||||||
);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
Reference in new issue
Block a user