mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: cooler header
This commit is contained in:
1 parent
f0295079cc
commit
9d916a588d
10 files changed
+99
-38
No files matched your search
+7
-1
@@ -64,6 +64,7 @@ export function InfoWithDelta({
|
|||||||
: (stats[props.value] as number);
|
: (stats[props.value] as number);
|
||||||
const delta = useDelta(uhWhatDoICallThisVariable);
|
const delta = useDelta(uhWhatDoICallThisVariable);
|
||||||
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const model = modelDefinitions.models[protagonistTank.id];
|
||||||
const others = useMemo(() => {
|
const others = useMemo(() => {
|
||||||
const defaultSkills = createDefaultSkills(skillDefinitions);
|
const defaultSkills = createDefaultSkills(skillDefinitions);
|
||||||
|
|
||||||
@@ -78,7 +79,12 @@ export function InfoWithDelta({
|
|||||||
relativeAgainst === TankopediaRelativeAgainst.All,
|
relativeAgainst === TankopediaRelativeAgainst.All,
|
||||||
)
|
)
|
||||||
.map((tank) => {
|
.map((tank) => {
|
||||||
const member = tankToDuelMember(tank, provisionDefinitions);
|
const thisTankModel = modelDefinitions.models[tank.id];
|
||||||
|
const member = tankToDuelMember(
|
||||||
|
tank,
|
||||||
|
thisTankModel,
|
||||||
|
provisionDefinitions,
|
||||||
|
);
|
||||||
|
|
||||||
return tankCharacteristics(
|
return tankCharacteristics(
|
||||||
{
|
{
|
||||||
|
|||||||
+15
-15
@@ -1,7 +1,7 @@
|
|||||||
import { OrbitControls } from '@react-three/drei';
|
import { OrbitControls } from '@react-three/drei';
|
||||||
import { invalidate, useThree } from '@react-three/fiber';
|
import { invalidate, useThree } from '@react-three/fiber';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { PerspectiveCamera, Vector3 } from 'three';
|
import { PerspectiveCamera, Vector3 } from 'three';
|
||||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
@@ -81,16 +81,14 @@ export function Controls({ naked }: ControlsProps) {
|
|||||||
protagonistTrackModelDefinition.origin.y +
|
protagonistTrackModelDefinition.origin.y +
|
||||||
antagonistModelDefinition.turret_origin.y +
|
antagonistModelDefinition.turret_origin.y +
|
||||||
antagonistTurretModelDefinition.gun_origin.y;
|
antagonistTurretModelDefinition.gun_origin.y;
|
||||||
const [autoRotate, setAutoRotate] = useState(
|
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||||
display !== TankopediaDisplay.ShootingRange && !naked,
|
|
||||||
);
|
|
||||||
const gunHeight =
|
const gunHeight =
|
||||||
protagonistHullOrigin.y +
|
protagonistHullOrigin.y +
|
||||||
protagonistTurretOrigin.y +
|
protagonistTurretOrigin.y +
|
||||||
protagonistGunOrigin.y;
|
protagonistGunOrigin.y;
|
||||||
const inspectModeInitialPosition = [
|
const inspectModeInitialPosition = [
|
||||||
-8,
|
-8,
|
||||||
gunHeight + (naked ? 10 : 4),
|
gunHeight + (naked ? 10 : 2),
|
||||||
-13,
|
-13,
|
||||||
] as const;
|
] as const;
|
||||||
const protagonistGunOriginOnlyY = new Vector3(
|
const protagonistGunOriginOnlyY = new Vector3(
|
||||||
@@ -217,10 +215,6 @@ export function Controls({ naked }: ControlsProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isInitialIteration = true;
|
let isInitialIteration = true;
|
||||||
|
|
||||||
function handleDisturbance() {
|
|
||||||
setAutoRotate(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
|
if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
|
||||||
|
|
||||||
@@ -254,8 +248,14 @@ export function Controls({ naked }: ControlsProps) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
poseEvent.on(handleDisturbance);
|
function disturb() {
|
||||||
canvas.addEventListener('pointerdown', handleDisturbance);
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.disturbed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
poseEvent.on(disturb);
|
||||||
|
canvas.addEventListener('pointerdown', disturb);
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
canvas.addEventListener('wheel', handleWheel);
|
canvas.addEventListener('wheel', handleWheel);
|
||||||
document.body.addEventListener('scroll', handleScroll);
|
document.body.addEventListener('scroll', handleScroll);
|
||||||
@@ -319,7 +319,7 @@ export function Controls({ naked }: ControlsProps) {
|
|||||||
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
|
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
|
||||||
(state) => state.display === TankopediaDisplay.ShootingRange,
|
(state) => state.display === TankopediaDisplay.ShootingRange,
|
||||||
() => {
|
() => {
|
||||||
handleDisturbance();
|
disturb();
|
||||||
updateCamera();
|
updateCamera();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -330,8 +330,8 @@ export function Controls({ naked }: ControlsProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
canvas.removeEventListener('pointerdown', handleDisturbance);
|
canvas.removeEventListener('pointerdown', disturb);
|
||||||
poseEvent.off(handleDisturbance);
|
poseEvent.off(disturb);
|
||||||
unsubscribeDisplay();
|
unsubscribeDisplay();
|
||||||
unsubscribeShootingRangeZoom();
|
unsubscribeShootingRangeZoom();
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
@@ -347,7 +347,7 @@ export function Controls({ naked }: ControlsProps) {
|
|||||||
ref={orbitControls}
|
ref={orbitControls}
|
||||||
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
||||||
enableDamping={false}
|
enableDamping={false}
|
||||||
autoRotate={autoRotate}
|
autoRotate={!disturbed}
|
||||||
autoRotateSpeed={1 / 4}
|
autoRotateSpeed={1 / 4}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { TankSandboxLoader } from './components/TankSandboxLoader';
|
|||||||
const tankDefinitions = await awaitableTankDefinitions;
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
|
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||||
const { unwrap } = useLocale();
|
const { unwrap } = useLocale();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
@@ -73,10 +74,13 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="center" style={{ backgroundColor: 'black' }}>
|
<Flex justify="center">
|
||||||
<Flex
|
<Flex
|
||||||
direction={{ initial: 'column', md: 'row' }}
|
direction={{ initial: 'column', md: 'row' }}
|
||||||
style={{ zIndex: isFullScreen ? 2 : undefined }}
|
style={{
|
||||||
|
zIndex: isFullScreen ? 2 : undefined,
|
||||||
|
backgroundColor: 'black',
|
||||||
|
}}
|
||||||
height={
|
height={
|
||||||
isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)`
|
isFullScreen ? '100vh' : `calc(100vh - ${NAVBAR_HEIGHT}px - 8rem)`
|
||||||
}
|
}
|
||||||
@@ -94,22 +98,33 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
left={{ initial: '0', md: '5', lg: '9' }}
|
left={{ initial: '0', md: '5', lg: '9' }}
|
||||||
py="4"
|
py="4"
|
||||||
top={{ initial: '8', md: '50%' }}
|
top={{ initial: '8', md: '50%' }}
|
||||||
style={{ transform: 'translateY(-50%)' }}
|
style={{ transform: 'translateY(-50%)', transitionDuration: '200ms' }}
|
||||||
direction="column"
|
direction="column"
|
||||||
align={{ initial: 'center', md: 'start' }}
|
align={{ initial: 'center', md: 'start' }}
|
||||||
|
gap={disturbed ? '0' : '2'}
|
||||||
>
|
>
|
||||||
<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={
|
||||||
|
disturbed
|
||||||
|
? { initial: '7', lg: '8' }
|
||||||
|
: { initial: '8', lg: '9' }
|
||||||
|
}
|
||||||
|
style={{ transitionDuration: '200ms' }}
|
||||||
>
|
>
|
||||||
<Icon width="1em" height="1em" />
|
<Icon width="1em" height="1em" />
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Heading
|
<Heading
|
||||||
weight="bold"
|
weight="bold"
|
||||||
size={{ initial: '7', lg: '8' }}
|
size={
|
||||||
|
disturbed
|
||||||
|
? { initial: '7', lg: '8' }
|
||||||
|
: { initial: '8', lg: '9' }
|
||||||
|
}
|
||||||
|
style={{ transitionDuration: '200ms' }}
|
||||||
wrap="nowrap"
|
wrap="nowrap"
|
||||||
color={treeColor}
|
color={treeColor}
|
||||||
>
|
>
|
||||||
@@ -117,14 +132,21 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
</Heading>
|
</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Text
|
<Box ml="3">
|
||||||
color="gray"
|
<Text
|
||||||
size={{ initial: '3', lg: '4' }}
|
color="gray"
|
||||||
weight="light"
|
size={{ initial: '3', lg: disturbed ? '4' : '5' }}
|
||||||
ml={{ initial: '0', md: '7', lg: '7' }}
|
weight="light"
|
||||||
>
|
ml={{
|
||||||
BlitzKit Tankopedia
|
initial: '0',
|
||||||
</Text>
|
md: '8',
|
||||||
|
lg: disturbed ? 'var(--font-size-8)' : 'var(--font-size-9)',
|
||||||
|
}}
|
||||||
|
style={{ transitionDuration: '200ms' }}
|
||||||
|
>
|
||||||
|
BlitzKit Tankopedia
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
@@ -134,8 +156,14 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
flexShrink="0"
|
flexShrink="0"
|
||||||
position="relative"
|
position="relative"
|
||||||
>
|
>
|
||||||
<Box position="absolute" width="100%" height="100%">
|
<Box position="absolute" width="100%" height="100%" overflow="hidden">
|
||||||
<Box width="100%" height="100%">
|
<Box
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
position="relative"
|
||||||
|
left={disturbed ? '0' : '12.5%'}
|
||||||
|
style={{ transitionDuration: '200ms' }}
|
||||||
|
>
|
||||||
{skeleton && <TankSandboxLoader id={protagonist.id} />}
|
{skeleton && <TankSandboxLoader id={protagonist.id} />}
|
||||||
|
|
||||||
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
|
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ export function MetaSection() {
|
|||||||
<TankSearch
|
<TankSearch
|
||||||
compact
|
compact
|
||||||
onSelect={(tank) => {
|
onSelect={(tank) => {
|
||||||
|
const model = modelDefinitions.models[tank.id];
|
||||||
|
|
||||||
setShowSwapDialog(false);
|
setShowSwapDialog(false);
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.model = modelDefinitions.models[tank.id];
|
draft.model = modelDefinitions.models[tank.id];
|
||||||
@@ -102,6 +104,7 @@ export function MetaSection() {
|
|||||||
mutateDuel((draft) => {
|
mutateDuel((draft) => {
|
||||||
draft.protagonist = tankToDuelMember(
|
draft.protagonist = tankToDuelMember(
|
||||||
tank,
|
tank,
|
||||||
|
model,
|
||||||
provisionDefinitions,
|
provisionDefinitions,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import type { ProvisionDefinitions, TankDefinition } from '@blitzkit/core';
|
import type {
|
||||||
|
ModelDefinition,
|
||||||
|
ProvisionDefinitions,
|
||||||
|
TankDefinition,
|
||||||
|
} from '@blitzkit/core';
|
||||||
import { uniqueId } from 'lodash-es';
|
import { uniqueId } from 'lodash-es';
|
||||||
import type { CompareMember } from '../../stores/compareEphemeral';
|
import type { CompareMember } from '../../stores/compareEphemeral';
|
||||||
import { tankToDuelMember } from './tankToDuelMember';
|
import { tankToDuelMember } from './tankToDuelMember';
|
||||||
|
|
||||||
export function tankToCompareMember(
|
export function tankToCompareMember(
|
||||||
tank: TankDefinition,
|
tank: TankDefinition,
|
||||||
|
model: ModelDefinition,
|
||||||
provisionDefinitions: ProvisionDefinitions,
|
provisionDefinitions: ProvisionDefinitions,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...tankToDuelMember(tank, provisionDefinitions),
|
...tankToDuelMember(tank, model, provisionDefinitions),
|
||||||
key: uniqueId(),
|
key: uniqueId(),
|
||||||
} satisfies CompareMember;
|
} satisfies CompareMember;
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,30 @@
|
|||||||
import {
|
import {
|
||||||
createDefaultProvisions,
|
createDefaultProvisions,
|
||||||
|
ModelDefinition,
|
||||||
type ProvisionDefinitions,
|
type ProvisionDefinitions,
|
||||||
type TankDefinition,
|
type TankDefinition,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
import type { DuelMember } from '../../stores/duel';
|
import type { DuelMember } from '../../stores/duel';
|
||||||
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
|
import { genericDefaultEquipmentMatrix } from '../../stores/duel/constants';
|
||||||
|
import { applyPitchYawLimits } from '../blitz/applyPitchYawLimits';
|
||||||
|
|
||||||
export function tankToDuelMember(
|
export function tankToDuelMember(
|
||||||
tank: TankDefinition,
|
tank: TankDefinition,
|
||||||
|
model: ModelDefinition,
|
||||||
provisionDefinitions: ProvisionDefinitions,
|
provisionDefinitions: ProvisionDefinitions,
|
||||||
) {
|
) {
|
||||||
const turret = tank.turrets.at(-1)!;
|
const turret = tank.turrets.at(-1)!;
|
||||||
|
const turretModel = model.turrets[turret.id];
|
||||||
const gun = turret.guns.at(-1)!;
|
const gun = turret.guns.at(-1)!;
|
||||||
|
const gunModel = turretModel.guns[gun.gun_type!.value.base.id];
|
||||||
|
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
degToRad(8),
|
||||||
|
degToRad(25),
|
||||||
|
gunModel.pitch,
|
||||||
|
turretModel.yaw,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
camouflage: true,
|
camouflage: true,
|
||||||
@@ -24,8 +37,8 @@ export function tankToDuelMember(
|
|||||||
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,
|
||||||
pitch: 0,
|
pitch,
|
||||||
turret,
|
turret,
|
||||||
} satisfies DuelMember;
|
} satisfies DuelMember;
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ export function Page({ id, skeleton, locale }: PageProps) {
|
|||||||
<App.Provider>
|
<App.Provider>
|
||||||
<TankopediaPersistent.Provider>
|
<TankopediaPersistent.Provider>
|
||||||
<Duel.Provider
|
<Duel.Provider
|
||||||
data={{ tank, provisionDefinitions: provisionDefinitions }}
|
data={{ tank, model, provisionDefinitions: provisionDefinitions }}
|
||||||
>
|
>
|
||||||
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
|
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
|
||||||
<HeroSection skeleton={skeleton} />
|
<HeroSection skeleton={skeleton} />
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function Page({ id }: PageProps) {
|
|||||||
<LocaleProvider locale="en">
|
<LocaleProvider locale="en">
|
||||||
<App.Provider>
|
<App.Provider>
|
||||||
<TankopediaPersistent.Provider>
|
<TankopediaPersistent.Provider>
|
||||||
<Duel.Provider data={{ provisionDefinitions, tank }}>
|
<Duel.Provider data={{ provisionDefinitions, model, tank }}>
|
||||||
<TankopediaEphemeral.Provider data={model}>
|
<TankopediaEphemeral.Provider data={model}>
|
||||||
<Content />
|
<Content />
|
||||||
</TankopediaEphemeral.Provider>
|
</TankopediaEphemeral.Provider>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ModelDefinition,
|
||||||
type EngineDefinition,
|
type EngineDefinition,
|
||||||
type GunDefinition,
|
type GunDefinition,
|
||||||
type ProvisionDefinitions,
|
type ProvisionDefinitions,
|
||||||
@@ -48,16 +49,19 @@ export interface DuelStore {
|
|||||||
export const Duel = createContextualStore(
|
export const Duel = createContextualStore(
|
||||||
({
|
({
|
||||||
tank,
|
tank,
|
||||||
|
model,
|
||||||
provisionDefinitions,
|
provisionDefinitions,
|
||||||
}: {
|
}: {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
|
model: ModelDefinition;
|
||||||
provisionDefinitions: ProvisionDefinitions;
|
provisionDefinitions: ProvisionDefinitions;
|
||||||
}) => {
|
}) => {
|
||||||
const protagonist = tankToDuelMember(tank, provisionDefinitions);
|
const protagonist = tankToDuelMember(tank, model, provisionDefinitions);
|
||||||
|
|
||||||
return create<DuelStore>()(
|
return create<DuelStore>()(
|
||||||
subscribeWithSelector<DuelStore>(() => ({
|
subscribeWithSelector<DuelStore>(() => ({
|
||||||
protagonist,
|
protagonist,
|
||||||
|
model,
|
||||||
antagonist: protagonist,
|
antagonist: protagonist,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export const SHOOTING_RANGE_ZOOM_COEFFICIENTS: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface TankopediaEphemeral {
|
interface TankopediaEphemeral {
|
||||||
|
disturbed: boolean;
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
shootingRangeZoom: ShootingRangeZoom;
|
shootingRangeZoom: ShootingRangeZoom;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
@@ -128,6 +129,7 @@ export const TankopediaEphemeral = createContextualStore(
|
|||||||
(model: ModelDefinition) => {
|
(model: ModelDefinition) => {
|
||||||
return create<TankopediaEphemeral>()(
|
return create<TankopediaEphemeral>()(
|
||||||
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
subscribeWithSelector<TankopediaEphemeral>(() => ({
|
||||||
|
disturbed: false,
|
||||||
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
shootingRangeZoom: ShootingRangeZoom.Arcade,
|
||||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||||
editStatic: false,
|
editStatic: false,
|
||||||
|
|||||||
Reference in new issue
Block a user