mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: rotation inputs
This commit is contained in:
1 parent
cebc4392ed
commit
a099df5133
4 files changed
+118
-137
No files matched your search
@@ -88,6 +88,7 @@ export function HeroSection() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
className="tank-sandbox-container"
|
||||||
style={{
|
style={{
|
||||||
flex: 2,
|
flex: 2,
|
||||||
position: isFullScreen ? 'fixed' : 'relative',
|
position: isFullScreen ? 'fixed' : 'relative',
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import mutateTankopediaPersistent, {
|
|||||||
} from '../../../../../../../stores/tankopedia';
|
} from '../../../../../../../stores/tankopedia';
|
||||||
import { TankSearch } from '../../../../components/TankSearch';
|
import { TankSearch } from '../../../../components/TankSearch';
|
||||||
import { ENVIRONMENTS } from '../../Lighting';
|
import { ENVIRONMENTS } from '../../Lighting';
|
||||||
|
import { RotationInputs } from '../../QuickInputs';
|
||||||
|
|
||||||
export function Options() {
|
export function Options() {
|
||||||
const mode = useTankopediaPersistent((state) => state.mode);
|
const mode = useTankopediaPersistent((state) => state.mode);
|
||||||
@@ -73,6 +74,8 @@ export function Options() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<RotationInputs />
|
||||||
|
|
||||||
{mode === 'armor' && (
|
{mode === 'armor' && (
|
||||||
<Flex
|
<Flex
|
||||||
gap="2"
|
gap="2"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const container = style({
|
||||||
|
display: 'none',
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'.tank-sandbox-container:hover &': {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CaretSortIcon } from '@radix-ui/react-icons';
|
||||||
import { Flex, TextField } from '@radix-ui/themes';
|
import { Flex, TextField } from '@radix-ui/themes';
|
||||||
import { use, useEffect, useRef } from 'react';
|
import { use, useEffect, useRef } from 'react';
|
||||||
import { degToRad, radToDeg } from 'three/src/math/MathUtils';
|
import { degToRad, radToDeg } from 'three/src/math/MathUtils';
|
||||||
@@ -5,28 +6,22 @@ import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawL
|
|||||||
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||||
|
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||||
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||||
import { useTankopediaPersistent } from '../../../../../../stores/tankopedia';
|
import * as styles from './index.css';
|
||||||
import { QuickEquipmentButton } from './components/QuickEquipmentButton';
|
|
||||||
|
|
||||||
interface RotationInputsProps {
|
export function RotationInputs() {
|
||||||
isFullScreen?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
|
||||||
const protagonist = useDuel((state) => state.protagonist!);
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
const awaitedModelDefinitions = use(modelDefinitions);
|
const awaitedModelDefinitions = use(modelDefinitions);
|
||||||
const turretYawInput = useRef<HTMLInputElement>(null);
|
const turretYawInput = useRef<HTMLInputElement>(null);
|
||||||
const gunPitchInput = useRef<HTMLInputElement>(null);
|
const gunPitchInput = useRef<HTMLInputElement>(null);
|
||||||
const mode = useTankopediaPersistent((state) => state.mode);
|
|
||||||
const hasCalibratedShells = useEquipment(103, true);
|
|
||||||
const hasEnhancedArmor = useEquipment(110);
|
|
||||||
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
|
||||||
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 initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
|
const isFullScreen = useFullScreen();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||||
@@ -39,147 +34,118 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
justify="center"
|
className={isFullScreen ? undefined : styles.container}
|
||||||
align="center"
|
gap="2"
|
||||||
style={{
|
style={{
|
||||||
width: 'calc(100% - 16px)',
|
width: 128 + 32,
|
||||||
|
pointerEvents: 'auto',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 8,
|
left: '50%',
|
||||||
bottom: isFullScreen ? 80 : 8,
|
top: 16,
|
||||||
pointerEvents: 'none',
|
transform: 'translateX(-50%)',
|
||||||
}}
|
}}
|
||||||
gap="4"
|
|
||||||
>
|
>
|
||||||
<Flex gap="2" style={{ width: 256, pointerEvents: 'auto' }}>
|
<TextField.Root
|
||||||
<TextField.Root
|
size="1"
|
||||||
radius="full"
|
radius="full"
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
const value = Number(turretYawInput.current!.value);
|
const value = Number(turretYawInput.current!.value);
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
turretYawInput.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;
|
|
||||||
});
|
|
||||||
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(
|
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
}}
|
return;
|
||||||
onKeyDown={(event) => {
|
}
|
||||||
if (event.key === 'Enter') {
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
turretYawInput.current?.blur();
|
protagonist.pitch,
|
||||||
}
|
degToRad(value),
|
||||||
}}
|
gunModelDefinition.pitch,
|
||||||
onFocus={() => turretYawInput.current?.focus()}
|
turretModelDefinition.yaw,
|
||||||
ref={turretYawInput}
|
hasImprovedVerticalStabilizer,
|
||||||
>
|
);
|
||||||
<TextField.Slot
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
style={{
|
mutateDuel((state) => {
|
||||||
userSelect: 'none',
|
state.protagonist!.pitch = pitch;
|
||||||
}}
|
state.protagonist!.yaw = yaw;
|
||||||
>
|
});
|
||||||
Yaw
|
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||||
</TextField.Slot>
|
}}
|
||||||
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
onKeyDown={(event) => {
|
||||||
</TextField.Root>
|
if (event.key === 'Enter') {
|
||||||
|
turretYawInput.current?.blur();
|
||||||
<TextField.Root
|
}
|
||||||
radius="full"
|
}}
|
||||||
|
onFocus={() => turretYawInput.current?.focus()}
|
||||||
|
ref={turretYawInput}
|
||||||
|
>
|
||||||
|
<TextField.Slot
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
userSelect: 'none',
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
>
|
||||||
const value = Number(gunPitchInput.current!.value);
|
<CaretSortIcon style={{ transform: 'rotate(90deg)' }} />
|
||||||
if (isNaN(value)) {
|
</TextField.Slot>
|
||||||
gunPitchInput.current!.value = (
|
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
||||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
</TextField.Root>
|
||||||
).toFixed(1);
|
|
||||||
return;
|
<TextField.Root
|
||||||
}
|
size="1"
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
radius="full"
|
||||||
degToRad(-value + initialGunPitch),
|
style={{
|
||||||
protagonist.yaw,
|
flex: 1,
|
||||||
gunModelDefinition.pitch,
|
textAlign: 'right',
|
||||||
turretModelDefinition.yaw,
|
}}
|
||||||
hasImprovedVerticalStabilizer,
|
onBlur={() => {
|
||||||
);
|
const value = Number(gunPitchInput.current!.value);
|
||||||
modelTransformEvent.emit({ pitch, yaw });
|
if (isNaN(value)) {
|
||||||
mutateDuel((state) => {
|
|
||||||
state.protagonist!.pitch = pitch;
|
|
||||||
state.protagonist!.yaw = yaw;
|
|
||||||
});
|
|
||||||
gunPitchInput.current!.value = (
|
gunPitchInput.current!.value = (
|
||||||
-radToDeg(protagonist.pitch) + initialGunPitch
|
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||||
).toFixed(1);
|
).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;
|
||||||
|
});
|
||||||
|
gunPitchInput.current!.value = (
|
||||||
|
-radToDeg(protagonist.pitch) + initialGunPitch
|
||||||
|
).toFixed(1);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
gunPitchInput.current?.blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={() => gunPitchInput.current?.focus()}
|
||||||
|
ref={gunPitchInput}
|
||||||
|
>
|
||||||
|
<TextField.Slot
|
||||||
|
style={{
|
||||||
|
userSelect: 'none',
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
gunPitchInput.current?.blur();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={() => gunPitchInput.current?.focus()}
|
|
||||||
ref={gunPitchInput}
|
|
||||||
>
|
>
|
||||||
<TextField.Slot
|
<CaretSortIcon />
|
||||||
style={{
|
</TextField.Slot>
|
||||||
userSelect: 'none',
|
<TextField.Slot
|
||||||
}}
|
style={{
|
||||||
>
|
userSelect: 'none',
|
||||||
Pitch
|
}}
|
||||||
</TextField.Slot>
|
>
|
||||||
<TextField.Slot
|
°
|
||||||
style={{
|
</TextField.Slot>
|
||||||
userSelect: 'none',
|
</TextField.Root>
|
||||||
}}
|
|
||||||
>
|
|
||||||
°
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{mode === 'armor' && (
|
|
||||||
<Flex gap="4" style={{ pointerEvents: 'auto' }}>
|
|
||||||
<QuickEquipmentButton
|
|
||||||
equipment={103}
|
|
||||||
active={hasCalibratedShells}
|
|
||||||
onClick={() => {
|
|
||||||
mutateDuel((draft) => {
|
|
||||||
draft.antagonist!.equipmentMatrix[0][0] = hasCalibratedShells
|
|
||||||
? 0
|
|
||||||
: 1;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<QuickEquipmentButton
|
|
||||||
equipment={110}
|
|
||||||
active={hasEnhancedArmor}
|
|
||||||
onClick={() => {
|
|
||||||
mutateDuel((draft) => {
|
|
||||||
draft.protagonist!.equipmentMatrix[1][1] = hasEnhancedArmor
|
|
||||||
? 0
|
|
||||||
: -1;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user