SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 372375d18e3f…
- Author-recorded commit
- 6fbf09f88a0a…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 76e4eb30c67a6f45…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Director keyPressed() emulation. The Lingo uses Mac key codes for the
// arrows (123 left, 124 right, 125 down, 126 up) and Escape (53), and
// characters for the rest (" ", "s", "1").
export type DirectorKey = 123 | 124 | 125 | 126 | 53 | ' ' | 's' | '1';
const CODE_TO_KEY: Record<string, DirectorKey> = {
ArrowLeft: 123,
ArrowRight: 124,
ArrowDown: 125,
ArrowUp: 126,
KeyA: 123,
KeyD: 124,
KeyS: 125,
KeyW: 126,
Escape: 53,
Space: ' ',
KeyM: 's', // sound toggle; "s" is taken by WASD steering
Digit1: '1',
};
export class Input {
private readonly held = new Set<DirectorKey>();
/** Virtual (touch/gamepad) sources merged in each poll. */
private readonly virtual = new Set<DirectorKey>();
/** Short pulses from shell-triggered NAP-KEYS actions. */
private readonly actions = new Set<DirectorKey>();
private readonly actionTimers = new Map<DirectorKey, ReturnType<typeof setTimeout>>();
keyPressed(key: DirectorKey): boolean {
return this.held.has(key) || this.virtual.has(key) || this.actions.has(key);
}
setVirtual(key: DirectorKey, down: boolean): void {
if (down) this.virtual.add(key);
else this.virtual.delete(key);
}
/** Turn a shell-triggered action into a brief control pulse. Key repeat extends it. */
pulseAction(key: DirectorKey, durationMs = 160): void {
const previous = this.actionTimers.get(key);
if (previous !== undefined) clearTimeout(previous);
this.actions.add(key);
this.actionTimers.set(
key,
setTimeout(() => {
this.actions.delete(key);
this.actionTimers.delete(key);
}, durationMs),
);
}
clear(): void {
this.held.clear();
this.virtual.clear();
this.actions.clear();
for (const timer of this.actionTimers.values()) clearTimeout(timer);
this.actionTimers.clear();
}
attach(target: Window): () => void {
const down = (e: KeyboardEvent) => {
// Typing in a text field (the pack link) is not driving.
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
const key = CODE_TO_KEY[e.code];
if (key === undefined) return;
this.held.add(key);
if (e.code.startsWith('Arrow') || e.code === 'Space') e.preventDefault();
};
const up = (e: KeyboardEvent) => {
const key = CODE_TO_KEY[e.code];
if (key !== undefined) this.held.delete(key);
};
const blur = () => this.held.clear();
target.addEventListener('keydown', down);
target.addEventListener('keyup', up);
target.addEventListener('blur', blur);
return () => {
target.removeEventListener('keydown', down);
target.removeEventListener('keyup', up);
target.removeEventListener('blur', blur);
};
}
}
