Back to Obsidian Breaker
SOURCE / PINNED RELEASE

Made of little things.

Obsidian Breaker

Release
44c589c52043…
Author-recorded commit
9ee5b61ce1c9…
License
LICENSE
Author’s source reference
nostr://npub1dmmh0futyszl4445mv4xrygkgsgj8vpu79yx3qyy9qxmz4pemtzqlqmc87/wss%3A%2F%2Fgit.napplet.soy%2F/n-de5dc6a02e7

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

src/napplet-settings.ts
import { config } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';
import settingsSchema from '../config.schema.json';

export type Difficulty = 'chill' | 'standard' | 'ruthless';
export type AimPace = 'slow' | 'steady' | 'quick';

export interface GameSettings {
  difficulty: Difficulty;
  aimPace: AimPace;
  showGuide: boolean;
  screenShake: boolean;
}

export const DEFAULT_SETTINGS: GameSettings = {
  difficulty: 'standard',
  aimPace: 'steady',
  showGuide: true,
  screenShake: true,
};

type SettingsListener = (settings: GameSettings) => void;

const listeners = new Set<SettingsListener>();
let current: GameSettings = { ...DEFAULT_SETTINGS };

function normalize(values: Record<string, unknown>): GameSettings {
  const difficulty =
    values.difficulty === 'chill' || values.difficulty === 'ruthless'
      ? values.difficulty
      : 'standard';
  const aimPace =
    values.aimPace === 'slow' || values.aimPace === 'quick' ? values.aimPace : 'steady';
  return {
    difficulty,
    aimPace,
    showGuide: typeof values.showGuide === 'boolean' ? values.showGuide : DEFAULT_SETTINGS.showGuide,
    screenShake:
      typeof values.screenShake === 'boolean' ? values.screenShake : DEFAULT_SETTINGS.screenShake,
  };
}

export function gameSettings(): GameSettings {
  return current;
}

export function onGameSettings(listener: SettingsListener): () => void {
  listeners.add(listener);
  listener(current);
  return () => {
    listeners.delete(listener);
  };
}

// Config is optional here: the game ships with complete local defaults.
if (runtimeHasDomain('config')) {
  void (async () => {
    // Some hosts consume the embedded schema; others need standard registration.
    // Do not replace a schema that the host already supplied.
    if (!config.schema) {
      await config.registerSchema(settingsSchema as Parameters<typeof config.registerSchema>[0]);
    }
    const subscription = config.subscribe((values) => {
      current = normalize(values);
      for (const listener of listeners) listener(current);
    });
    window.addEventListener('pagehide', () => subscription.close(), { once: true });
  })().catch(() => {
    // Optional operations may be unavailable even when a host exposes the domain.
    // Keep the local defaults so the game still runs.
  });
}