diff --git a/src/app/App.tsx b/src/app/App.tsx index f9a7d689..fcdb47ba 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -190,8 +190,8 @@ import { DEFAULT_SOUND_EMITTER_MAX_DISTANCE, DEFAULT_TELEPORT_TARGET_YAW_DEGREES, PLAYER_START_COLLIDER_MODES, + PLAYER_START_GAMEPAD_CAMERA_LOOK_BINDINGS, PLAYER_START_GAMEPAD_BINDINGS, - PLAYER_START_KEYBOARD_BINDING_CODES, PLAYER_START_MOVEMENT_ACTIONS, PLAYER_START_NAVIGATION_MODES, DEFAULT_SPOT_LIGHT_ANGLE_DEGREES, @@ -218,6 +218,7 @@ import { normalizeEntityName, normalizeYawDegrees, normalizeInteractablePrompt, + type PlayerStartGamepadCameraLookBinding, type PlayerStartColliderMode, type PlayerStartGamepadBinding, type PlayerStartInputBindings, @@ -360,15 +361,15 @@ function getPlayerStartMovementActionLabel( function formatPlayerStartKeyboardBindingLabel( code: PlayerStartKeyboardBindingCode ): string { + if (/^Key[A-Z]$/.test(code)) { + return code.slice(3); + } + + if (/^Digit[0-9]$/.test(code)) { + return code.slice(5); + } + switch (code) { - case "KeyW": - return "W"; - case "KeyA": - return "A"; - case "KeyS": - return "S"; - case "KeyD": - return "D"; case "ArrowUp": return "Arrow Up"; case "ArrowLeft": @@ -377,14 +378,18 @@ function formatPlayerStartKeyboardBindingLabel( return "Arrow Down"; case "ArrowRight": return "Arrow Right"; - case "KeyI": - return "I"; - case "KeyJ": - return "J"; - case "KeyK": - return "K"; - case "KeyL": - return "L"; + case "Space": + return "Space"; + case "Tab": + return "Tab"; + case "Enter": + return "Enter"; + case "Backspace": + return "Backspace"; + case "Escape": + return "Escape"; + default: + return code.replace(/([a-z0-9])([A-Z])/g, "$1 $2"); } } @@ -411,6 +416,15 @@ function formatPlayerStartGamepadBindingLabel( } } +function formatPlayerStartGamepadCameraLookBindingLabel( + binding: PlayerStartGamepadCameraLookBinding +): string { + switch (binding) { + case "rightStick": + return "Right Stick"; + } +} + const STARTER_MATERIAL_ORDER = new Map( STARTER_MATERIAL_LIBRARY.map((material, index) => [material.id, index]) ); @@ -1446,6 +1460,8 @@ export function App({ store, initialStatusMessage }: AppProps) { ); const [playerStartInputBindingsDraft, setPlayerStartInputBindingsDraft] = useState(createPlayerStartInputBindings()); + const [playerStartKeyboardCaptureAction, setPlayerStartKeyboardCaptureAction] = + useState(null); const [soundEmitterAudioAssetIdDraft, setSoundEmitterAudioAssetIdDraft] = useState(DEFAULT_SOUND_EMITTER_AUDIO_ASSET_ID ?? ""); const [soundEmitterVolumeDraft, setSoundEmitterVolumeDraft] = useState( @@ -1739,6 +1755,52 @@ export function App({ store, initialStatusMessage }: AppProps) { whiteboxSnapStep ); + useEffect(() => { + setPlayerStartKeyboardCaptureAction(null); + }, [selectedPlayerStart?.id]); + + useEffect(() => { + if (playerStartKeyboardCaptureAction === null) { + return; + } + + const handleWindowKeyCapture = (event: globalThis.KeyboardEvent) => { + event.preventDefault(); + event.stopPropagation(); + + if (event.repeat) { + return; + } + + if (event.code === "Escape") { + setPlayerStartKeyboardCaptureAction(null); + setStatusMessage("Cancelled Player Start key capture."); + return; + } + + const capturedCode = event.code.trim(); + + if (capturedCode.length === 0) { + return; + } + + handlePlayerStartKeyboardBindingChange( + playerStartKeyboardCaptureAction, + capturedCode + ); + setPlayerStartKeyboardCaptureAction(null); + setStatusMessage( + `Bound ${getPlayerStartMovementActionLabel(playerStartKeyboardCaptureAction)} to ${formatPlayerStartKeyboardBindingLabel(capturedCode)}.` + ); + }; + + window.addEventListener("keydown", handleWindowKeyCapture, true); + + return () => { + window.removeEventListener("keydown", handleWindowKeyCapture, true); + }; + }, [playerStartKeyboardCaptureAction]); + useEffect(() => { setSceneNameDraft(editorState.document.name); }, [editorState.document.name]); @@ -4017,6 +4079,25 @@ export function App({ store, initialStatusMessage }: AppProps) { ); }; + const handlePlayerStartGamepadCameraLookBindingChange = ( + nextBinding: PlayerStartGamepadCameraLookBinding + ) => { + const nextBindings = createPlayerStartInputBindings({ + keyboard: playerStartInputBindingsDraft.keyboard, + gamepad: { + ...playerStartInputBindingsDraft.gamepad, + cameraLook: nextBinding + } + }); + + setPlayerStartInputBindingsDraft(nextBindings); + scheduleDraftCommit(() => + applyPlayerStartChange({ + inputBindings: nextBindings + }) + ); + }; + const applySceneEntryChange = () => { if (selectedSceneEntry === null) { setStatusMessage("Select a Scene Entry before editing it."); @@ -10108,24 +10189,25 @@ export function App({ store, initialStatusMessage }: AppProps) { {getPlayerStartMovementActionLabel(action)} Key - + {playerStartKeyboardCaptureAction === action + ? "Press Any Key..." + : formatPlayerStartKeyboardBindingLabel( + playerStartInputBindingsDraft.keyboard[ + action + ] + )} +