From fa14a0c17f0dc379c1f88bc2d12239c0399a1843 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Thu, 30 Apr 2026 00:14:57 +0200 Subject: [PATCH] Feature: Implement support for climb action bindings --- src/app/App.tsx | 2 ++ src/document/migrate-scene-document.ts | 10 +++++++++ src/document/scene-document-validation.ts | 24 ++++++++++++++++++++++ src/entities/entity-instances.ts | 17 +++++++++++++++ src/runtime-three/player-input-bindings.ts | 16 +++++++++++++++ 5 files changed, 69 insertions(+) diff --git a/src/app/App.tsx b/src/app/App.tsx index 740a8c58..49bb23d8 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -673,6 +673,8 @@ function getPlayerStartInputActionLabel( return "Sprint"; case "crouch": return "Crouch"; + case "climb": + return "Climb"; case "interact": return "Interact"; case "clearTarget": diff --git a/src/document/migrate-scene-document.ts b/src/document/migrate-scene-document.ts index 6a01f7dc..71f154cb 100644 --- a/src/document/migrate-scene-document.ts +++ b/src/document/migrate-scene-document.ts @@ -1826,6 +1826,11 @@ function readPlayerStartInputBindings(value: unknown, label: string) { `${label}.keyboard.crouch`, DEFAULT_PLAYER_START_KEYBOARD_BINDINGS.crouch ), + climb: readPlayerStartKeyboardBindingCode( + keyboard?.climb, + `${label}.keyboard.climb`, + DEFAULT_PLAYER_START_KEYBOARD_BINDINGS.climb + ), interact: readPlayerStartKeyboardBindingCode( keyboard?.interact, `${label}.keyboard.interact`, @@ -1878,6 +1883,11 @@ function readPlayerStartInputBindings(value: unknown, label: string) { `${label}.gamepad.crouch`, DEFAULT_PLAYER_START_GAMEPAD_BINDINGS.crouch ), + climb: readPlayerStartGamepadActionBinding( + gamepad?.climb, + `${label}.gamepad.climb`, + DEFAULT_PLAYER_START_GAMEPAD_BINDINGS.climb + ), interact: readPlayerStartGamepadActionBinding( gamepad?.interact, `${label}.gamepad.interact`, diff --git a/src/document/scene-document-validation.ts b/src/document/scene-document-validation.ts index 320c1559..359dcf26 100644 --- a/src/document/scene-document-validation.ts +++ b/src/document/scene-document-validation.ts @@ -3296,6 +3296,17 @@ function validatePlayerStartEntity( ); } + if (!isPlayerStartKeyboardBindingCode(entity.inputBindings?.keyboard.climb)) { + diagnostics.push( + createDiagnostic( + "error", + "invalid-player-start-climb-keyboard-binding", + "Player Start climb keyboard binding must be a supported key code.", + `${path}.inputBindings.keyboard.climb` + ) + ); + } + if ( !isPlayerStartKeyboardBindingCode(entity.inputBindings?.keyboard.interact) ) { @@ -3433,6 +3444,19 @@ function validatePlayerStartEntity( ); } + if ( + !isPlayerStartGamepadActionBinding(entity.inputBindings?.gamepad.climb) + ) { + diagnostics.push( + createDiagnostic( + "error", + "invalid-player-start-climb-gamepad-binding", + "Player Start climb gamepad binding must be a supported standard-gamepad action input.", + `${path}.inputBindings.gamepad.climb` + ) + ); + } + if ( !isPlayerStartGamepadActionBinding(entity.inputBindings?.gamepad.interact) ) { diff --git a/src/entities/entity-instances.ts b/src/entities/entity-instances.ts index 0ed5fc7f..2e066869 100644 --- a/src/entities/entity-instances.ts +++ b/src/entities/entity-instances.ts @@ -1159,6 +1159,7 @@ export function clonePlayerStartInputBindings( jump: bindings.keyboard.jump, sprint: bindings.keyboard.sprint, crouch: bindings.keyboard.crouch, + climb: bindings.keyboard.climb, interact: bindings.keyboard.interact, clearTarget: bindings.keyboard.clearTarget, pauseTime: bindings.keyboard.pauseTime @@ -1171,6 +1172,7 @@ export function clonePlayerStartInputBindings( jump: bindings.gamepad.jump, sprint: bindings.gamepad.sprint, crouch: bindings.gamepad.crouch, + climb: bindings.gamepad.climb, interact: bindings.gamepad.interact, clearTarget: bindings.gamepad.clearTarget, pauseTime: bindings.gamepad.pauseTime, @@ -1203,6 +1205,9 @@ export function createPlayerStartInputBindings( crouch: overrides.keyboard?.crouch ?? DEFAULT_PLAYER_START_KEYBOARD_BINDINGS.crouch, + climb: + overrides.keyboard?.climb ?? + DEFAULT_PLAYER_START_KEYBOARD_BINDINGS.climb, interact: overrides.keyboard?.interact ?? DEFAULT_PLAYER_START_KEYBOARD_BINDINGS.interact, @@ -1231,6 +1236,8 @@ export function createPlayerStartInputBindings( overrides.gamepad?.sprint ?? DEFAULT_PLAYER_START_GAMEPAD_BINDINGS.sprint, crouch: overrides.gamepad?.crouch ?? DEFAULT_PLAYER_START_GAMEPAD_BINDINGS.crouch, + climb: + overrides.gamepad?.climb ?? DEFAULT_PLAYER_START_GAMEPAD_BINDINGS.climb, interact: overrides.gamepad?.interact ?? DEFAULT_PLAYER_START_GAMEPAD_BINDINGS.interact, @@ -1281,6 +1288,10 @@ export function createPlayerStartInputBindings( throw new Error("Player Start crouch keyboard binding must be supported."); } + if (!isPlayerStartKeyboardBindingCode(keyboard.climb)) { + throw new Error("Player Start climb keyboard binding must be supported."); + } + if (!isPlayerStartKeyboardBindingCode(keyboard.interact)) { throw new Error( "Player Start interact keyboard binding must be supported." @@ -1333,6 +1344,10 @@ export function createPlayerStartInputBindings( throw new Error("Player Start crouch gamepad binding must be supported."); } + if (!isPlayerStartGamepadActionBinding(gamepad.climb)) { + throw new Error("Player Start climb gamepad binding must be supported."); + } + if (!isPlayerStartGamepadActionBinding(gamepad.interact)) { throw new Error("Player Start interact gamepad binding must be supported."); } @@ -1587,6 +1602,7 @@ export function arePlayerStartInputBindingsEqual( left.keyboard.jump === right.keyboard.jump && left.keyboard.sprint === right.keyboard.sprint && left.keyboard.crouch === right.keyboard.crouch && + left.keyboard.climb === right.keyboard.climb && left.keyboard.interact === right.keyboard.interact && left.keyboard.clearTarget === right.keyboard.clearTarget && left.keyboard.pauseTime === right.keyboard.pauseTime && @@ -1597,6 +1613,7 @@ export function arePlayerStartInputBindingsEqual( left.gamepad.jump === right.gamepad.jump && left.gamepad.sprint === right.gamepad.sprint && left.gamepad.crouch === right.gamepad.crouch && + left.gamepad.climb === right.gamepad.climb && left.gamepad.interact === right.gamepad.interact && left.gamepad.clearTarget === right.gamepad.clearTarget && left.gamepad.pauseTime === right.gamepad.pauseTime && diff --git a/src/runtime-three/player-input-bindings.ts b/src/runtime-three/player-input-bindings.ts index 44cfd3f3..50b27b60 100644 --- a/src/runtime-three/player-input-bindings.ts +++ b/src/runtime-three/player-input-bindings.ts @@ -18,6 +18,7 @@ export interface PlayerStartActionInputState extends PlayerStartMovementActionSt jump: number; sprint: number; crouch: number; + climb: number; interact: number; clearTarget: number; pauseTime: number; @@ -316,6 +317,10 @@ export function resolvePlayerStartActionInputs( pressedKeys.has(bindings.keyboard.crouch) ? 1 : 0, readGamepadActionBindingStrength(gamepads, bindings.gamepad.crouch) ), + climb: Math.max( + pressedKeys.has(bindings.keyboard.climb) ? 1 : 0, + readGamepadActionBindingStrength(gamepads, bindings.gamepad.climb) + ), interact: Math.max( pressedKeys.has(bindings.keyboard.interact) ? 1 : 0, readGamepadActionBindingStrength(gamepads, bindings.gamepad.interact) @@ -355,6 +360,17 @@ export function resolvePlayerStartInteractInput( .interact; } +export function resolvePlayerStartClimbInput( + pressedKeys: ReadonlySet, + bindings: PlayerStartInputBindings, + gamepads: + | ArrayLike + | null + | undefined = getAvailableGamepads() +): number { + return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads).climb; +} + export function resolvePlayerStartClearTargetInput( pressedKeys: ReadonlySet, bindings: PlayerStartInputBindings,