// BOOKQUOY — controller support (PS5 DualSense over Bluetooth, or anything // exposing the standard Gamepad mapping). Zero new input paths: the pad // synthesizes the SAME KeyboardEvents the keyboard produces, so every trick, // variant, and menu action flows through the one proven handler in main.js. // // left stick / dpad steer · up = push + up-variants · down = brake/varials // R2 push // CROSS ollie / bunnyhop / hop off a grind (Space) // SQUARE kickflip | barspin (J) // CIRCLE heelflip | tailwhip (K) // TRIANGLE pop shove | x-up (L) // R1 tre flip · L1 fs shove · L2 hardflip // OPTIONS start / run it back (Enter) · CREATE reset (R) const DEAD = 0.3; const BTN = { 0: 'Space', 1: 'KeyK', 2: 'KeyJ', 3: 'KeyL', 4: 'KeyF', 5: 'KeyT', 6: 'KeyH', 8: 'KeyR', 9: 'Enter', 12: 'ArrowUp', 13: 'ArrowDown', 14: 'ArrowLeft', 15: 'ArrowRight' }; export class Pad { constructor(onConnect) { this.held = new Set(); this.index = null; addEventListener('gamepadconnected', e => { this.index = e.gamepad.index; if (onConnect) onConnect(e.gamepad.id); }); addEventListener('gamepaddisconnected', e => { if (e.gamepad.index === this.index) this.index = null; this._sync(new Set()); // release everything, no stuck keys }); } _gp() { const pads = navigator.getGamepads ? navigator.getGamepads() : []; if (this.index !== null && pads[this.index]) return pads[this.index]; for (const p of pads) if (p) { this.index = p.index; return p; } return null; } poll() { const gp = this._gp(); if (!gp) return; const want = new Set(); const ax = gp.axes[0] || 0, ay = gp.axes[1] || 0; if (ax < -DEAD) want.add('ArrowLeft'); if (ax > DEAD) want.add('ArrowRight'); if (ay < -DEAD) want.add('ArrowUp'); if (ay > DEAD) want.add('ArrowDown'); if ((gp.buttons[7]?.value || 0) > 0.15) want.add('KeyW'); // R2 push for (const [i, code] of Object.entries(BTN)) { const b = gp.buttons[+i]; if (b && (b.pressed || b.value > 0.5)) want.add(code); } this._sync(want); } _sync(want) { for (const code of want) if (!this.held.has(code)) { this.held.add(code); dispatchEvent(new KeyboardEvent('keydown', { code })); } for (const code of [...this.held]) if (!want.has(code)) { this.held.delete(code); dispatchEvent(new KeyboardEvent('keyup', { code })); } } rumble(strong = 0.8, weak = 0.4, ms = 180) { const gp = this._gp(); gp?.vibrationActuator?.playEffect?.('dual-rumble', { duration: ms, strongMagnitude: strong, weakMagnitude: weak }); } }