mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: start of static armor mode
This commit is contained in:
1 parent
3d0c87579b
commit
ee58b3543e
5 files changed
+149
-93
No files matched your search
@@ -2,6 +2,7 @@ import { Canvas } from '@react-three/fiber';
|
|||||||
import { Suspense, use, useEffect, useRef } from 'react';
|
import { Suspense, use, useEffect, useRef } from 'react';
|
||||||
import { Armor } from '../../../../../../components/Armor';
|
import { Armor } from '../../../../../../components/Armor';
|
||||||
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
|
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
|
||||||
|
import { StaticArmor } from '../../../../../../components/StaticArmor';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||||
@@ -9,6 +10,7 @@ import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
|||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||||
import * as Duel from '../../../../../../stores/duel';
|
import * as Duel from '../../../../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
|
||||||
|
import * as TankopediaPersistent from '../../../../../../stores/tankopediaPersistent';
|
||||||
import { Controls } from '../Control';
|
import { Controls } from '../Control';
|
||||||
import { Lighting } from '../Lighting';
|
import { Lighting } from '../Lighting';
|
||||||
import { SceneProps } from '../SceneProps';
|
import { SceneProps } from '../SceneProps';
|
||||||
@@ -26,6 +28,7 @@ export function TankSandbox() {
|
|||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
tankModelDefinition.turrets[protagonist.turret.id];
|
||||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||||
|
const armorMode = TankopediaPersistent.use((state) => state.armorMode);
|
||||||
|
|
||||||
function handlePointerDown() {
|
function handlePointerDown() {
|
||||||
window.addEventListener('pointermove', handlePointerMove);
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
@@ -139,7 +142,8 @@ export function TankSandbox() {
|
|||||||
|
|
||||||
{/* idk why the shot display doesn't work without suspense here lol */}
|
{/* idk why the shot display doesn't work without suspense here lol */}
|
||||||
<Suspense fallback={<ModelLoader />}>
|
<Suspense fallback={<ModelLoader />}>
|
||||||
<Armor />
|
{armorMode === 'blitz' && <Armor />}
|
||||||
|
{armorMode === 'static' && <StaticArmor />}
|
||||||
<Lighting />
|
<Lighting />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
|
|||||||
@@ -485,7 +485,9 @@ export function Options() {
|
|||||||
mr="1"
|
mr="1"
|
||||||
>
|
>
|
||||||
<Switch size="1" checked={mode === 'armor'} />
|
<Switch size="1" checked={mode === 'armor'} />
|
||||||
Armor
|
<Text color="gray" size="2">
|
||||||
|
Armor
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||||
import * as Duel from '../../../../../../stores/duel';
|
import * as Duel from '../../../../../../stores/duel';
|
||||||
|
import * as TankopediaPersistent from '../../../../../../stores/tankopediaPersistent';
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
|
|
||||||
export function RotationInputs() {
|
export function RotationInputs() {
|
||||||
@@ -26,6 +27,9 @@ export function RotationInputs() {
|
|||||||
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
|
const mode = TankopediaPersistent.use((state) => state.mode);
|
||||||
|
const armorMode = TankopediaPersistent.use((state) => state.armorMode);
|
||||||
|
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||||
@@ -57,8 +61,9 @@ export function RotationInputs() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
className={isFullScreen ? undefined : styles.container}
|
className={
|
||||||
gap="2"
|
isFullScreen || mode === 'armor' ? undefined : styles.container
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
width: 128 + 32,
|
width: 128 + 32,
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
@@ -67,106 +72,145 @@ export function RotationInputs() {
|
|||||||
top: 16,
|
top: 16,
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
}}
|
}}
|
||||||
|
direction="column"
|
||||||
|
align="center"
|
||||||
|
gap="2"
|
||||||
>
|
>
|
||||||
<TextField.Root
|
<Flex gap="2">
|
||||||
size="1"
|
<TextField.Root
|
||||||
radius="full"
|
size="1"
|
||||||
style={{
|
radius="full"
|
||||||
flex: 1,
|
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
const value = Number(yawInput.current!.value);
|
|
||||||
if (isNaN(value)) {
|
|
||||||
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
|
||||||
protagonist.pitch,
|
|
||||||
degToRad(value),
|
|
||||||
gunModelDefinition.pitch,
|
|
||||||
turretModelDefinition.yaw,
|
|
||||||
hasImprovedVerticalStabilizer,
|
|
||||||
);
|
|
||||||
modelTransformEvent.emit({ pitch, yaw });
|
|
||||||
mutateDuel((state) => {
|
|
||||||
state.protagonist.pitch = pitch;
|
|
||||||
state.protagonist.yaw = yaw;
|
|
||||||
});
|
|
||||||
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
yawInput.current?.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={() => yawInput.current?.focus()}
|
|
||||||
ref={yawInput}
|
|
||||||
>
|
|
||||||
<TextField.Slot
|
|
||||||
style={{
|
style={{
|
||||||
userSelect: 'none',
|
flex: 1,
|
||||||
|
textAlign: 'right',
|
||||||
}}
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
const value = Number(yawInput.current!.value);
|
||||||
|
if (isNaN(value)) {
|
||||||
|
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
protagonist.pitch,
|
||||||
|
degToRad(value),
|
||||||
|
gunModelDefinition.pitch,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
|
mutateDuel((state) => {
|
||||||
|
state.protagonist.pitch = pitch;
|
||||||
|
state.protagonist.yaw = yaw;
|
||||||
|
});
|
||||||
|
yawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
yawInput.current?.blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={() => yawInput.current?.focus()}
|
||||||
|
ref={yawInput}
|
||||||
>
|
>
|
||||||
<WidthIcon />
|
<TextField.Slot
|
||||||
</TextField.Slot>
|
style={{
|
||||||
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
userSelect: 'none',
|
||||||
</TextField.Root>
|
}}
|
||||||
|
>
|
||||||
|
<WidthIcon />
|
||||||
|
</TextField.Slot>
|
||||||
|
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
||||||
|
</TextField.Root>
|
||||||
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
size="1"
|
size="1"
|
||||||
radius="full"
|
radius="full"
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const value = Number(pitchInput.current!.value);
|
const value = Number(pitchInput.current!.value);
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
|
pitchInput.current!.value = (
|
||||||
|
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||||
|
).toFixed(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
degToRad(-value + initialGunPitch),
|
||||||
|
protagonist.yaw,
|
||||||
|
gunModelDefinition.pitch,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
|
mutateDuel((state) => {
|
||||||
|
state.protagonist.pitch = pitch;
|
||||||
|
state.protagonist.yaw = yaw;
|
||||||
|
});
|
||||||
pitchInput.current!.value = (
|
pitchInput.current!.value = (
|
||||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||||
).toFixed(1);
|
).toFixed(1);
|
||||||
return;
|
}}
|
||||||
}
|
onKeyDown={(event) => {
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
if (event.key === 'Enter') {
|
||||||
degToRad(-value + initialGunPitch),
|
pitchInput.current?.blur();
|
||||||
protagonist.yaw,
|
}
|
||||||
gunModelDefinition.pitch,
|
}}
|
||||||
turretModelDefinition.yaw,
|
onFocus={() => pitchInput.current?.focus()}
|
||||||
hasImprovedVerticalStabilizer,
|
ref={pitchInput}
|
||||||
);
|
>
|
||||||
modelTransformEvent.emit({ pitch, yaw });
|
<TextField.Slot
|
||||||
mutateDuel((state) => {
|
style={{
|
||||||
state.protagonist.pitch = pitch;
|
userSelect: 'none',
|
||||||
state.protagonist.yaw = yaw;
|
}}
|
||||||
});
|
>
|
||||||
pitchInput.current!.value = (
|
<HeightIcon />
|
||||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
</TextField.Slot>
|
||||||
).toFixed(1);
|
<TextField.Slot
|
||||||
}}
|
style={{
|
||||||
onKeyDown={(event) => {
|
userSelect: 'none',
|
||||||
if (event.key === 'Enter') {
|
}}
|
||||||
pitchInput.current?.blur();
|
>
|
||||||
}
|
°
|
||||||
}}
|
</TextField.Slot>
|
||||||
onFocus={() => pitchInput.current?.focus()}
|
</TextField.Root>
|
||||||
ref={pitchInput}
|
</Flex>
|
||||||
>
|
|
||||||
<TextField.Slot
|
{/* <Flex gap="2">
|
||||||
|
<Box
|
||||||
|
width="var(--space-6)"
|
||||||
|
height="var(--space-4)"
|
||||||
style={{
|
style={{
|
||||||
userSelect: 'none',
|
borderRadius: 'var(--radius-1)',
|
||||||
|
background: 'linear-gradient(90deg, #FF2020, #FF8080)',
|
||||||
|
opacity: armorMode === 'blitz' ? 1 : 0.5,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopediaPersistent((draft) => {
|
||||||
|
draft.armorMode = 'blitz';
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
width="var(--space-6)"
|
||||||
|
height="var(--space-4)"
|
||||||
|
style={{
|
||||||
|
borderRadius: 'var(--radius-1)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
opacity: armorMode === 'static' ? 1 : 0.5,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopediaPersistent((draft) => {
|
||||||
|
draft.armorMode = 'static';
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HeightIcon />
|
<Box flexGrow="1" style={{ backgroundColor: '#8000FF' }} />
|
||||||
</TextField.Slot>
|
<Box flexGrow="1" style={{ backgroundColor: '#FF0080' }} />
|
||||||
<TextField.Slot
|
</Flex>
|
||||||
style={{
|
</Flex> */}
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
°
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function StaticArmor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ export type TankopediaSortBy = keyof typeof SORT_NAMES;
|
|||||||
export type TankopediaSortDirection = 'ascending' | 'descending';
|
export type TankopediaSortDirection = 'ascending' | 'descending';
|
||||||
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
|
||||||
export type TankopediaMode = 'model' | 'armor';
|
export type TankopediaMode = 'model' | 'armor';
|
||||||
|
export type TankopediaArmorMode = 'blitz' | 'static';
|
||||||
|
|
||||||
export interface TankopediaPersistent {
|
export interface TankopediaPersistent {
|
||||||
model: {
|
model: {
|
||||||
@@ -39,6 +40,7 @@ export interface TankopediaPersistent {
|
|||||||
page: number;
|
page: number;
|
||||||
};
|
};
|
||||||
mode: TankopediaMode;
|
mode: TankopediaMode;
|
||||||
|
armorMode: TankopediaArmorMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const { Provider, use, useMutation, useStore } = createNextSafeStore(
|
export const { Provider, use, useMutation, useStore } = createNextSafeStore(
|
||||||
@@ -70,6 +72,7 @@ export const { Provider, use, useMutation, useStore } = createNextSafeStore(
|
|||||||
page: 0,
|
page: 0,
|
||||||
},
|
},
|
||||||
mode: 'model',
|
mode: 'model',
|
||||||
|
armorMode: 'blitz',
|
||||||
})),
|
})),
|
||||||
{ name: 'tankopedia', merge: (a, b) => merge(b, a) },
|
{ name: 'tankopedia', merge: (a, b) => merge(b, a) },
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in new issue
Block a user