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.
// 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;
}
