fertig-classic-games/src/data/assetManifest.js

164 lines
8.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Per-game asset manifest for lazy loading.
//
// Assets listed here are NOT loaded by PreloadScene at startup. Instead,
// GameRoomScene loads a game's entry (via services/assetLoader.js) the first
// time the user enters that game; already-loaded keys are skipped, so repeat
// visits and cross-game shared assets (e.g. the mahjong tiles) are free.
//
// Descriptor shape:
// { type: 'image'|'spritesheet'|'audio'|'json', key, path, frameWidth?, frameHeight? }
//
// A slug's entry is an array of descriptors, a resolver `(scene) => [descriptors]`,
// or a mixed array of both. Resolvers exist for the drop-in artwork sheets whose
// paths live in the (always-preloaded) data/*-artwork.json manifests — a null
// `path` there means "not painted yet" and the game renders procedurally.
// Mahjong tile-label overlays (128×178 transparent PNGs), shared by the
// traditional Mahjong game and Mahjong Match solitaire. The White Dragon has
// no art — the scenes draw its traditional empty frame.
const MAHJONG_TILES = [
...Array.from({ length: 9 }, (_, i) => `bamboo${i + 1}`),
...Array.from({ length: 9 }, (_, i) => `circle${i + 1}`),
...Array.from({ length: 15 }, (_, i) => `pinyin${i + 1}`),
'orchid', 'peony', 'chrysanthemum', 'lotus',
'spring', 'summer', 'fall', 'winter',
].map((name) => ({ type: 'image', key: `mahjong-${name}`, path: `assets/images/mahjong/${name}.png` }));
// Drop-in spritesheets declared in a cached artwork JSON. Only entries with a
// `path` set are returned (until then the game renders procedurally) — the
// same contract PreloadScene used when it loaded these at startup.
function sheetsFrom(scene, jsonKey, fields) {
const art = scene.cache.json.get(jsonKey);
if (!art) return [];
const sheets = fields.flatMap((f) => {
const v = art[f];
return v && typeof v === 'object' && !('key' in v) ? Object.values(v) : [v];
});
return sheets
.filter((s) => s && s.key && s.path)
.map((s) => ({ type: 'spritesheet', key: s.key, path: s.path, frameWidth: s.frameWidth, frameHeight: s.frameHeight }));
}
// Drop-in standalone images declared in a cached artwork JSON's `artwork` array.
function imagesFrom(scene, jsonKey) {
const art = scene.cache.json.get(jsonKey);
return (art?.artwork ?? [])
.filter((a) => a && a.key && a.path)
.map((a) => ({ type: 'image', key: a.key, path: a.path }));
}
const sheet = (key, path, frameWidth, frameHeight) => ({ type: 'spritesheet', key, path, frameWidth, frameHeight });
const image = (key, path) => ({ type: 'image', key, path });
export const MANIFEST = {
forbiddenisland: [
// Tiles: 2 cols (dry, flooded) × 24 rows. Row i → dry frame 2i, flooded
// frame 2i+1 (see IslandData.TILE_FRAME_ROW).
sheet('forbiddenisland-tiles', 'assets/images/forbiddenisland-tiles.png', 200, 200),
// Treasure-deck cards: 4 cols × 2 rows of 320×420 cells. Frame order
// documented in IslandData.CARD_FRAME.
sheet('forbiddenisland-cards', 'assets/images/forbiddenisland-cards.png', 320, 420),
],
catan: [
sheet('catan-cards', 'assets/images/catancards.png', 270, 390),
sheet('catan-tiles', 'assets/images/catantiles.png', 312, 312),
sheet('catan-special-cards', 'assets/images/catan-special-cards.png', 270, 390),
image('catan-robber', 'assets/images/catan-robber.png'),
image('catan-pirate', 'assets/images/catan-pirate.png'),
],
risk: [image('risk-board', 'assets/images/risk-board.png')],
jewelquest: [image('bg-jewelquest-battle', 'assets/images/background-jewelquest.png')],
dominion: [
// Dominion card art. One 270×390 cell per card (art fills the top ~60%;
// the title/icon band is drawn at runtime). Optional — the scene falls
// back to procedural placeholders when the sheet is absent.
sheet('dominion-cards', 'assets/images/dominioncards.png', 270, 390),
// Prosperity expansion art (frame order documented in expansions/prosperity.js).
// Optional — same procedural fallback applies when the sheet is absent.
sheet('dominion-prosperity', 'assets/images/dominion-prosperity.png', 270, 390),
// Prosperity token sprites (1 VP, 5 VP, Gold) — 150×150 each.
sheet('dominion-tokens', 'assets/images/dominion-tokens.png', 150, 150),
],
splendor: [
// 28 frames at 270×390. 0-14 = tier×bonus backgrounds, 15-24 = nobles,
// 25-27 = deck backs. Frame order documented in SplendorData.js.
// Optional — vectors are drawn when absent.
sheet('splendor-cards', 'assets/images/splendor-cards.png', 270, 390),
// Gems: 6 frames at 64×64. white(0) blue(1) green(2) red(3) black(4) gold(5).
sheet('splendor-gems', 'assets/images/splendor-gems.png', 64, 64),
],
// Azul tiles: 6 frames at 96×96. blue(0) yellow(1) red(2) black(3) white(4)
// first-player(5). Frame order documented in AzulData.js. A placeholder
// sheet ships; vectors draw when absent.
azul: [sheet('azul-tiles', 'assets/images/azul-tiles.png', 96, 96)],
tickettoride: [sheet('ttr-cards', 'assets/images/tickettoride-cards.png', 270, 390)],
gofish: [sheet('gofish-cards', 'assets/images/gofish-cards.png', 270, 390)],
oldmaid: [sheet('oldmaid-cards', 'assets/images/oldmaid-cards.png', 270, 390)],
labyrinth: [
// Tile backgrounds: frame 0 = movable, 1 = fixed (corridors are drawn in
// code). Treasure overlays & cards share the treasure index as their
// frame. All optional — the scene draws vector fallbacks when absent.
sheet('labyrinth-tiles', 'assets/images/labyrinth-tiles.png', 200, 200),
sheet('labyrinth-treasures', 'assets/images/labyrinth-treasures.png', 100, 100),
sheet('labyrinth-cards', 'assets/images/labyrinth-cards.png', 270, 390),
],
// Stratego unit art: 12 transparent frames (0=Flag, 1..10=rank, 11=Bomb),
// 6 cols × 2 rows. Optional — the scene draws vector glyphs when absent.
stratego: [sheet('stratego-pieces', 'assets/images/stratego-pieces.png', 140, 140)],
monopoly: [
// Pawns: 4 frames (one per seat) at 80×80. Optional — falls back to colored circles.
sheet('monopoly-pawns', 'assets/images/monopoly-pawns.png', 80, 80),
// Card art: frame 0 = Chance, frame 1 = Community Chest, at 200×300.
sheet('monopoly-cards', 'assets/images/monopoly-cards.png', 200, 300),
],
puddingmonsters: [sheet('jello-items', 'assets/images/jello-items.png', 132, 132)],
mahjong: MAHJONG_TILES,
mahjongmatch: MAHJONG_TILES,
spireclimb: [
image('spireclimb-act1', 'assets/images/spireclimb-act1.png'),
image('spireclimb-act2', 'assets/images/spireclimb-act2.png'),
image('spireclimb-act3', 'assets/images/spireclimb-act3.png'),
(scene) => sheetsFrom(scene, 'spireclimb-artwork', ['cardSheet', 'creatureSheet', 'playerSheets']),
],
shift: (scene) => imagesFrom(scene, 'shift-artwork'),
slots: (scene) => imagesFrom(scene, 'slots-artwork'),
dungeonboss: (scene) => sheetsFrom(scene, 'dungeonboss-artwork',
['roomSheet', 'heroSheet', 'spellSheet', 'bossSheet', 'deckBackSheet', 'iconSheet']),
swdbg: (scene) => sheetsFrom(scene, 'swdbg-artwork', ['cardSheet', 'baseSheet']),
balatro: (scene) => sheetsFrom(scene, 'balatro-artwork',
['jokerSheet', 'tarotSheet', 'planetSheet', 'spectralSheet', 'voucherSheet', 'packSheet', 'deckBackSheet', 'bossIconSheet']),
// Optional drop-in art — spec in assets/gamedata/peggle/sprites.md. Missing
// files 404 harmlessly and the game renders procedurally: circles for
// pegs/ball, gradient starfield for the board background.
// Ship rotation sheets are described by each ship's `sprite` blocks in
// data/star-control-ships.json (written by tools/fetchStarControlAssets.js).
// Ships without a sheet render as procedural vector chevrons, so a missing
// file is harmless and new JSON ships need no manifest edit.
starcontrol: (scene) => {
const cfg = scene.cache.json.get('star-control-ships');
return Object.values(cfg?.ships ?? {})
.flatMap((s) => [s.sprite, s.projectileSprite, ...(s.forms ?? []).map((f) => f.sprite)])
.filter((sp) => sp?.sheet && sp.frameWidth > 0 && sp.frameHeight > 0)
.map((sp) => sheet(sp.sheet, `assets/images/starcontrol/${sp.sheet}.png`, sp.frameWidth, sp.frameHeight));
},
peggle: [
sheet('peggle-sprites', 'assets/images/peggle-sprites.png', 64, 64),
// One board background per master, named by opponent id
// (assets/images/peggle/<masterId>.png, 1200×900).
(scene) => {
const levels = scene.cache.json.get('peggle-levels')?.levels ?? [];
const ids = [...new Set(levels.map((l) => l.masterId).filter(Boolean))];
return ids.map((id) => image(`peggle-bg-${id}`, `assets/images/peggle/${id}.png`));
},
],
};
// Returns the full (normalized) descriptor list for a slug — including assets
// that may already be loaded. Callers filter with the loader's cache checks.
export function resolveGameAssets(scene, slug) {
const entry = MANIFEST[slug];
if (!entry) return [];
const parts = typeof entry === 'function' ? entry(scene) : entry;
return parts.flatMap((p) => (typeof p === 'function' ? p(scene) : [p]));
}