mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +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>
|
||||
|
||||
<div
|
||||
className="tank-sandbox-container"
|
||||
style={{
|
||||
flex: 2,
|
||||
position: isFullScreen ? 'fixed' : 'relative',
|
||||
|
||||
@@ -36,6 +36,7 @@ import mutateTankopediaPersistent, {
|
||||
} from '../../../../../../../stores/tankopedia';
|
||||
import { TankSearch } from '../../../../components/TankSearch';
|
||||
import { ENVIRONMENTS } from '../../Lighting';
|
||||
import { RotationInputs } from '../../QuickInputs';
|
||||
|
||||
export function Options() {
|
||||
const mode = useTankopediaPersistent((state) => state.mode);
|
||||
@@ -73,6 +74,8 @@ export function Options() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<RotationInputs />
|
||||
|
||||
{mode === 'armor' && (
|
||||
<Flex
|
||||
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 { use, useEffect, useRef } from 'react';
|
||||
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 { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||
import { useEquipment } from '../../../../../../hooks/useEquipment';
|
||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
|
||||
import { useTankopediaPersistent } from '../../../../../../stores/tankopedia';
|
||||
import { QuickEquipmentButton } from './components/QuickEquipmentButton';
|
||||
import * as styles from './index.css';
|
||||
|
||||
interface RotationInputsProps {
|
||||
isFullScreen?: boolean;
|
||||
}
|
||||
|
||||
export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
||||
export function RotationInputs() {
|
||||
const protagonist = useDuel((state) => state.protagonist!);
|
||||
const awaitedModelDefinitions = use(modelDefinitions);
|
||||
const turretYawInput = 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 turretModelDefinition =
|
||||
tankModelDefinition.turrets[protagonist.turret.id];
|
||||
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
|
||||
const initialGunPitch = tankModelDefinition.turretRotation?.pitch ?? 0;
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const isFullScreen = useFullScreen();
|
||||
|
||||
useEffect(() => {
|
||||
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(1);
|
||||
@@ -39,147 +34,118 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
||||
|
||||
return (
|
||||
<Flex
|
||||
justify="center"
|
||||
align="center"
|
||||
className={isFullScreen ? undefined : styles.container}
|
||||
gap="2"
|
||||
style={{
|
||||
width: 'calc(100% - 16px)',
|
||||
width: 128 + 32,
|
||||
pointerEvents: 'auto',
|
||||
position: 'absolute',
|
||||
left: 8,
|
||||
bottom: isFullScreen ? 80 : 8,
|
||||
pointerEvents: 'none',
|
||||
left: '50%',
|
||||
top: 16,
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
gap="4"
|
||||
>
|
||||
<Flex gap="2" style={{ width: 256, pointerEvents: 'auto' }}>
|
||||
<TextField.Root
|
||||
radius="full"
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
}}
|
||||
onBlur={() => {
|
||||
const value = Number(turretYawInput.current!.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;
|
||||
});
|
||||
<TextField.Root
|
||||
size="1"
|
||||
radius="full"
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
}}
|
||||
onBlur={() => {
|
||||
const value = Number(turretYawInput.current!.value);
|
||||
if (isNaN(value)) {
|
||||
turretYawInput.current!.value = radToDeg(protagonist.yaw).toFixed(
|
||||
1,
|
||||
);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
turretYawInput.current?.blur();
|
||||
}
|
||||
}}
|
||||
onFocus={() => turretYawInput.current?.focus()}
|
||||
ref={turretYawInput}
|
||||
>
|
||||
<TextField.Slot
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
Yaw
|
||||
</TextField.Slot>
|
||||
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
||||
</TextField.Root>
|
||||
|
||||
<TextField.Root
|
||||
radius="full"
|
||||
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(1);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
turretYawInput.current?.blur();
|
||||
}
|
||||
}}
|
||||
onFocus={() => turretYawInput.current?.focus()}
|
||||
ref={turretYawInput}
|
||||
>
|
||||
<TextField.Slot
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
onBlur={() => {
|
||||
const value = Number(gunPitchInput.current!.value);
|
||||
if (isNaN(value)) {
|
||||
gunPitchInput.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;
|
||||
});
|
||||
>
|
||||
<CaretSortIcon style={{ transform: 'rotate(90deg)' }} />
|
||||
</TextField.Slot>
|
||||
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
||||
</TextField.Root>
|
||||
|
||||
<TextField.Root
|
||||
size="1"
|
||||
radius="full"
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'right',
|
||||
}}
|
||||
onBlur={() => {
|
||||
const value = Number(gunPitchInput.current!.value);
|
||||
if (isNaN(value)) {
|
||||
gunPitchInput.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;
|
||||
});
|
||||
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
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
Pitch
|
||||
</TextField.Slot>
|
||||
<TextField.Slot
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
°
|
||||
</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>
|
||||
)}
|
||||
<CaretSortIcon />
|
||||
</TextField.Slot>
|
||||
<TextField.Slot
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
°
|
||||
</TextField.Slot>
|
||||
</TextField.Root>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user