js/pad.js synthesizes the same KeyboardEvents the keyboard produces — one input path, zero new handler code. Stick/dpad steer (+variants), R2 push, CROSS ollie, SQUARE/CIRCLE/TRIANGLE flip/heel/shove, R1 tre, L1 fs shove, L2 hardflip, OPTIONS start, CREATE reset. Dual-rumble on bails and bonks. Title screen: dpad cycles discipline, CROSS drops in (Space now works there for keyboards too). Verified with a stubbed DualSense through __step: push 7.6m/s, steer, ollie->kickflip x2 (220). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 lines
2.8 KiB
JavaScript
77 lines
2.8 KiB
JavaScript
// 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 });
|
|
}
|
|
}
|