Back to Black Site
SOURCE / PINNED RELEASE

Made of little things.

Black Site

Release
666bc7c74ebd…
Author-recorded commit
05e95af79676…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391

Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/game-keyboard.ts
import { KEYS_DOMAIN, keys, type Action, type Subscription } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';

const actions: (Action & { code: string })[] = [
  { id: 'blacksite.primary', label: 'Equip primary weapon', defaultKey: '1', code: 'Digit1' },
  { id: 'blacksite.secondary', label: 'Equip secondary weapon', defaultKey: '2', code: 'Digit2' },
  { id: 'blacksite.throwable', label: 'Equip throwable', defaultKey: '3', code: 'Digit3' },
  { id: 'blacksite.medical', label: 'Equip medical item', defaultKey: '4', code: 'Digit4' },
  { id: 'blacksite.reload', label: 'Reload', defaultKey: 'r', code: 'KeyR' },
  { id: 'blacksite.knife', label: 'Knife attack', defaultKey: 'q', code: 'KeyQ' },
  { id: 'blacksite.interact', label: 'Interact', defaultKey: 'f', code: 'KeyF' },
  { id: 'blacksite.inventory', label: 'Toggle inventory', defaultKey: 'i', code: 'KeyI' },
  { id: 'blacksite.pause', label: 'Pause or resume', defaultKey: 'p', code: 'KeyP' },
];

function editing(event: KeyboardEvent): boolean {
  return event.isComposing || (event.target instanceof Element &&
    Boolean(event.target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])')));
}

/** Runtime owns forwarding and remapping; this adapter owns gameplay state. */
export function installGameKeyboard(
  held: (code: string, down: boolean) => void,
  invoke: (code: string) => void,
  reset: () => void,
): () => void {
  const registered = new Set<string>();
  const subscriptions = new Map<string, Subscription>();
  const attempted = new Set<string>();
  let closed = false;
  let currentEvent: KeyboardEvent | undefined;
  // Runs before the runtime's document capture listener. Its onAction callback
  // has no event argument, so retain this event until the next task (browsers run microtasks between event listeners).
  const capture = (event: KeyboardEvent) => {
    currentEvent = event;
    window.setTimeout(() => { if (currentEvent === event) currentEvent = undefined; }, 0);
  };
  const down = (event: KeyboardEvent) => {
    if (event.defaultPrevented || editing(event) || event.ctrlKey || event.altKey || event.metaKey) return;
    held(event.code, true);
    if (event.repeat) return;
    if (event.code === 'Escape' || actions.some(action => action.code === event.code && !registered.has(action.id))) {
      invoke(event.code);
    }
  };
  const up = (event: KeyboardEvent) => held(event.code, false);
  const hidden = () => { if (document.hidden) reset(); };
  window.addEventListener('keydown', capture, true);
  window.addEventListener('keydown', down);
  window.addEventListener('keyup', up);
  window.addEventListener('blur', reset);
  document.addEventListener('visibilitychange', hidden);

  const release = (id: string) => {
    subscriptions.get(id)?.close();
    subscriptions.delete(id);
    registered.delete(id);
    try { keys.unregisterAction(id); } catch { /* Runtime may already be gone. */ }
  };
  if (runtimeHasDomain(KEYS_DOMAIN)) {
    for (const { code, ...action } of actions) {
      void (async () => {
        try {
          // Subscribe before registration so an immediate binding push is usable.
          // Some runtimes capture on window before our listener; defer until
          // all capture listeners have seen the current event.
          subscriptions.set(action.id, keys.onAction(action.id, () => queueMicrotask(() => {
            if (closed || (currentEvent && (currentEvent.repeat || editing(currentEvent)))) return;
            if (currentEvent) held(currentEvent.code, false); // Clear only a consumed rebound key.
            invoke(code);
          })));
          attempted.add(action.id);
          await keys.registerAction(action);
          if (closed) release(action.id);
          else registered.add(action.id);
        } catch (error) {
          release(action.id);
          console.warn(`Keyboard action ${action.id} unavailable; using local shortcut.`, error);
        }
      })();
    }
  }
  const close = () => {
    if (closed) return;
    closed = true;
    window.removeEventListener('keydown', capture, true);
    window.removeEventListener('keydown', down);
    window.removeEventListener('keyup', up);
    window.removeEventListener('blur', reset);
    document.removeEventListener('visibilitychange', hidden);
    window.removeEventListener('pagehide', close);
    for (const id of attempted) release(id);
    reset();
  };
  window.addEventListener('pagehide', close);
  return close;
}