Back to Browser Side Quest
SOURCE / PINNED RELEASE

Made of little things.

Browser Side Quest

Release
b9d42f5768e4…
Author-recorded commit
c267690145fa…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/browser-side-quest

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

src/client/assets.ts
// All game assets are imported through Vite so the single-file build inlines them.
// Never fetch them at runtime: the napplet sandbox has no ambient network authority.
import type { ServerMapData } from '../sim/map.js';
import clientMap from '../assets/maps/world_client.json';
import serverMap from '../assets/maps/world_server.json';

const imageUrls = import.meta.glob<string>('../assets/img/*.png', {
  eager: true,
  query: '?url',
  import: 'default',
});

const spriteJson = import.meta.glob<SpriteData>('../assets/sprites/*.json', {
  eager: true,
  import: 'default',
});

// `?inline` always yields a base64 data: URL (dev and build), so the Web Audio
// engine can decode the bytes in-process; the napplet CSP blocks fetch(data:).
const soundUrls = import.meta.glob<string>('../assets/sounds/*.mp3', {
  eager: true,
  query: '?inline',
  import: 'default',
});

// Area music (CC BY 3.0) from the community fork browserquest/BrowserQuest,
// re-encoded to 64 kbps; credits are on the About parchment and in docs/brief.md.
const musicUrls = import.meta.glob<string>('../assets/music/*.mp3', {
  eager: true,
  query: '?url',
  import: 'default',
});

export interface SpriteData {
  id: string;
  width: number;
  height: number;
  animations: Record<string, { length: number; row: number }>;
  offset_x?: number;
  offset_y?: number;
}

export interface ClientMapData {
  width: number;
  height: number;
  tilesize: number;
  data: (number | number[])[];
  collisions: number[];
  blocking?: number[];
  plateau?: number[];
  high: number[];
  animated: Record<string, { l: number; d?: number }>;
  doors: { x: number; y: number; p: number; tcx: number; tcy: number; to: string; tx: number; ty: number }[];
  checkpoints: { id: number; x: number; y: number; w: number; h: number }[];
  musicAreas?: { id: string; x: number; y: number; w: number; h: number }[];
}

const byBaseName = <T>(record: Record<string, T>, ext: string): Record<string, T> => {
  const out: Record<string, T> = {};
  for (const [path, value] of Object.entries(record)) {
    const name = path.slice(path.lastIndexOf('/') + 1, -ext.length);
    out[name] = value;
  }
  return out;
};

export const images = byBaseName(imageUrls, '.png');
export const sprites = byBaseName(spriteJson, '.json');
export const sounds = byBaseName(soundUrls, '.mp3');
export const music = byBaseName(musicUrls, '.mp3');
export const clientMapData = clientMap as unknown as ClientMapData;
export const serverMapData = serverMap as unknown as ServerMapData;

export function imageUrl(name: string): string {
  const url = images[name];
  if (!url) throw new Error(`Missing image asset: ${name}`);
  return url;
}