245 lines
15 KiB
JavaScript
245 lines
15 KiB
JavaScript
import * as Phaser from 'phaser';
|
||
import { GAME_HEIGHT, GAME_WIDTH, COLORS } from '../config.js';
|
||
import { auth } from '../services/auth.js';
|
||
|
||
export default class PreloadScene extends Phaser.Scene {
|
||
constructor() { super('Preload'); }
|
||
|
||
preload() {
|
||
const w = GAME_WIDTH;
|
||
const h = GAME_HEIGHT;
|
||
const barWidth = 600;
|
||
const bg = this.add.rectangle(w / 2, h / 2, barWidth + 8, 28, COLORS.panel)
|
||
.setStrokeStyle(2, COLORS.accent);
|
||
const bar = this.add.rectangle(w / 2 - barWidth / 2, h / 2, 0, 20, COLORS.accent)
|
||
.setOrigin(0, 0.5);
|
||
this.add.text(w / 2, h / 2 - 60, 'Loading…', {
|
||
fontFamily: '"Julius Sans One"',
|
||
fontSize: '32px',
|
||
color: COLORS.textHex,
|
||
}).setOrigin(0.5);
|
||
|
||
this.load.on('progress', (p) => bar.width = barWidth * p);
|
||
this.load.on('complete', () => { bg.destroy(); bar.destroy(); });
|
||
|
||
this.load.spritesheet('opponents', 'assets/images/opponents.png', {
|
||
frameWidth: 300,
|
||
frameHeight: 300,
|
||
});
|
||
// Forbidden Island tiles: 2 cols (dry, flooded) × 24 rows. Row i → dry frame
|
||
// 2i, flooded frame 2i+1 (see IslandData.TILE_FRAME_ROW).
|
||
this.load.spritesheet('forbiddenisland-tiles', 'assets/images/forbiddenisland-tiles.png', {
|
||
frameWidth: 200,
|
||
frameHeight: 200,
|
||
});
|
||
// Forbidden Island Treasure-deck cards: 4 cols × 2 rows of 320×420 cells.
|
||
// Frame order documented in IslandData.CARD_FRAME.
|
||
this.load.spritesheet('forbiddenisland-cards', 'assets/images/forbiddenisland-cards.png', {
|
||
frameWidth: 320,
|
||
frameHeight: 420,
|
||
});
|
||
this.load.spritesheet('cardbacks', 'assets/images/cardbacks.png', {
|
||
frameWidth: 320,
|
||
frameHeight: 420,
|
||
});
|
||
this.load.spritesheet('catan-cards', 'assets/images/catancards.png', {
|
||
frameWidth: 270,
|
||
frameHeight: 390,
|
||
});
|
||
this.load.spritesheet('catan-tiles', 'assets/images/catantiles.png', {
|
||
frameWidth: 312,
|
||
frameHeight: 312,
|
||
});
|
||
this.load.image('risk-board', 'assets/images/risk-board.png');
|
||
this.load.image('catan-robber', 'assets/images/catan-robber.png');
|
||
this.load.image('catan-pirate', 'assets/images/catan-pirate.png');
|
||
this.load.image('bg-menu', 'assets/images/background-menu.png');
|
||
this.load.image('bg-room', 'assets/images/background-room.png');
|
||
this.load.image('bg-casino', 'assets/images/background-casino.png');
|
||
this.load.image('bg-jewelquest-battle', 'assets/images/background-jewelquest.png');
|
||
this.load.image('main-title', 'assets/images/main-title.png');
|
||
this.load.json('playfields', 'data/playfields.json');
|
||
this.load.json('colored-playfields', 'data/colored-playfields.json');
|
||
this.load.json('card-backs', 'data/card-backs.json');
|
||
this.load.json('music', 'data/music.json');
|
||
this.load.json('rushhour', 'data/rushhour.json');
|
||
this.load.json('puddingmonsters', 'data/puddingmonsters.json');
|
||
this.load.json('shift-artwork', 'data/shift-artwork.json');
|
||
this.load.json('slots-artwork', 'data/slots-artwork.json');
|
||
this.load.json('blockfighter', 'data/blockfighter.json');
|
||
this.load.json('jewelquest', 'data/jewelquest.json');
|
||
this.load.json('zuma', 'data/zuma.json');
|
||
this.load.json('dotlink', 'data/dotlink.json');
|
||
this.load.json('katamino', 'data/katamino.json');
|
||
this.load.json('bookwork', 'data/bookwork.json');
|
||
this.load.json('spireclimb-artwork', 'data/spireclimb-artwork.json');
|
||
this.load.json('jumble', 'data/jumble.json');
|
||
this.load.image('spireclimb-act1', 'assets/images/spireclimb-act1.png');
|
||
this.load.image('spireclimb-act2', 'assets/images/spireclimb-act2.png');
|
||
this.load.image('spireclimb-act3', 'assets/images/spireclimb-act3.png');
|
||
|
||
this.load.audio('sfx-water-splash', 'assets/fx/water-splash.mp3');
|
||
this.load.audio('sfx-water-sink', 'assets/fx/water-sink.mp3');
|
||
this.load.audio('sfx-water-raise', 'assets/fx/water-raise.mp3');
|
||
this.load.audio('sfx-water-dry', 'assets/fx/water-dry.mp3');
|
||
this.load.audio('sfx-card-deal', 'assets/fx/card-deal.mp3');
|
||
this.load.audio('sfx-card-deal-8bit', 'assets/fx/8bit-card.mp3');
|
||
this.load.audio('sfx-card-place', 'assets/fx/card-place.mp3');
|
||
this.load.audio('sfx-card-show', 'assets/fx/card-show.mp3');
|
||
this.load.audio('sfx-card-show-8bit', 'assets/fx/8bit-select.mp3');
|
||
this.load.audio('sfx-card-shuffle', 'assets/fx/card-shuffle.mp3');
|
||
this.load.audio('sfx-coins', 'assets/fx/coins.mp3');
|
||
this.load.audio('sfx-purchase', 'assets/fx/purchase.mp3');
|
||
this.load.audio('sfx-casino-blackjack', 'assets/fx/casino-blackjack.mp3');
|
||
this.load.audio('sfx-casino-lose', 'assets/fx/casino-lose.mp3');
|
||
this.load.audio('sfx-casino-lose-8bit', 'assets/fx/8bit-lose.mp3');
|
||
this.load.audio('sfx-casino-win', 'assets/fx/casino-win.mp3');
|
||
this.load.audio('sfx-casino-win-8bit', 'assets/fx/8bit-win.mp3');
|
||
this.load.audio('sfx-chip-bet', 'assets/fx/chip-bet.mp3');
|
||
this.load.audio('sfx-dice-roll', 'assets/fx/dice-roll.mp3');
|
||
this.load.audio('sfx-bingo-balls', 'assets/fx/bingo-balls.mp3');
|
||
this.load.audio('sfx-pencil-write', 'assets/fx/pencil-write.mp3');
|
||
this.load.audio('sfx-piece-click', 'assets/fx/piece-click.mp3');
|
||
this.load.audio('sfx-mastermind-glitch-1', 'assets/fx/mastermind-glitch-01.mp3');
|
||
this.load.audio('sfx-mastermind-glitch-2', 'assets/fx/mastermind-glitch-02.mp3');
|
||
this.load.audio('sfx-mastermind-place', 'assets/fx/mastermind-place.mp3');
|
||
this.load.audio('sfx-mastermind-access-granted', 'assets/fx/mastermind-access-granted.mp3');
|
||
this.load.audio('sfx-mastermind-access-denied', 'assets/fx/mastermind-access-denied.mp3');
|
||
this.load.audio('sfx-mastermind-color', 'assets/fx/mastermind-color.mp3');
|
||
this.load.audio('sfx-mastermind-match', 'assets/fx/mastermind-match.mp3');
|
||
this.load.audio('sfx-mastermind-calculate', 'assets/fx/mastermind-calculate.mp3');
|
||
this.load.audio('sfx-roulette', 'assets/fx/roulette.mp3');
|
||
this.load.audio('sfx-battleship-hit', 'assets/fx/battleship-hit.mp3');
|
||
this.load.audio('sfx-battleship-miss', 'assets/fx/battleship-miss.mp3');
|
||
this.load.audio('sfx-battleship-launch', 'assets/fx/battleship-launch.mp3');
|
||
this.load.audio('sfx-victory-short', 'assets/fx/victory-short.mp3');
|
||
this.load.audio('sfx-sword-hit', 'assets/fx/sword-hit.mp3');
|
||
this.load.audio('sfx-sword-slice', 'assets/fx/sword-slice.mp3');
|
||
this.load.audio('sfx-scifi-launch', 'assets/fx/scifi-launch.mp3');
|
||
this.load.audio('sfx-scifi-explode', 'assets/fx/scifi-explode.mp3');
|
||
this.load.audio('sfx-scifi-riser', 'assets/fx/scifi-riser.mp3');
|
||
this.load.audio('sfx-scifi-reveal', 'assets/fx/scifi-reveal.mp3');
|
||
this.load.audio('sfx-scifi-woosh', 'assets/fx/scifi-woosh.mp3');
|
||
this.load.audio('sfx-scifi-plink', 'assets/fx/scifi-plink.mp3');
|
||
this.load.audio('sfx-scifi-plonk', 'assets/fx/scifi-plonk.mp3');
|
||
this.load.audio('sfx-8bit-select', 'assets/fx/8bit-select.mp3');
|
||
this.load.audio('sfx-8bit-activate', 'assets/fx/8bit-activate.mp3');
|
||
this.load.audio('sfx-8bit-action', 'assets/fx/8bit-action.mp3');
|
||
this.load.audio('sfx-8bit-move', 'assets/fx/8bit-move.mp3');
|
||
this.load.audio('sfx-squish', 'assets/fx/squish.mp3');
|
||
this.load.audio('sfx-squash', 'assets/fx/squash.mp3');
|
||
this.load.audio('sfx-woosh', 'assets/fx/woosh.mp3');
|
||
this.load.spritesheet('jello-items', 'assets/images/jello-items.png', { frameWidth: 132, frameHeight: 132 });
|
||
this.load.audio('sfx-jello-monsters','assets/fx/jello-monsters.mp3');
|
||
this.load.audio('sfx-countdown-tick', 'assets/fx/countdown-01.mp3');
|
||
this.load.audio('sfx-countdown-go', 'assets/fx/countdown-02.mp3');
|
||
this.load.audio('sfx-ui-pick', 'assets/fx/ui-pick.mp3');
|
||
this.load.audio('sfx-ui-activate', 'assets/fx/ui-activate.mp3');
|
||
this.load.audio('sfx-ui-flip', 'assets/fx/ui-flip.mp3');
|
||
this.load.audio('sfx-ui-place', 'assets/fx/ui-place.mp3');
|
||
this.load.audio('sfx-ui-chime', 'assets/fx/ui-chime.mp3');
|
||
this.load.audio('sfx-gem-match-1', 'assets/fx/gem-01.mp3');
|
||
this.load.audio('sfx-gem-match-2', 'assets/fx/gem-02.mp3');
|
||
this.load.audio('sfx-gem-match-4', 'assets/fx/gem-04.mp3');
|
||
this.load.audio('sfx-gem-match-5', 'assets/fx/gem-05.mp3');
|
||
this.load.audio('sfx-gem-chain', 'assets/fx/gem-chain.mp3');
|
||
this.load.audio('sfx-gem-drop', 'assets/fx/gem-drop.mp3');
|
||
this.load.audio('sfx-gem-big-drop','assets/fx/gem-big-drop.mp3');
|
||
this.load.audio('sfx-firework', 'assets/fx/firework.mp3');
|
||
|
||
this.load.spritesheet('catan-special-cards', 'assets/images/catan-special-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||
|
||
// 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.
|
||
this.load.spritesheet('dominion-cards', 'assets/images/dominioncards.png', { frameWidth: 270, frameHeight: 390 });
|
||
// Prosperity expansion art (frame order documented in expansions/prosperity.js).
|
||
// Optional — same procedural fallback applies when the sheet is absent.
|
||
this.load.spritesheet('dominion-prosperity', 'assets/images/dominion-prosperity.png', { frameWidth: 270, frameHeight: 390 });
|
||
// Prosperity token sprites (1 VP, 5 VP, Gold) — 150×150 each.
|
||
this.load.spritesheet('dominion-tokens', 'assets/images/dominion-tokens.png', { frameWidth: 150, frameHeight: 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.
|
||
this.load.spritesheet('splendor-cards', 'assets/images/splendor-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||
// Splendor gems: 6 frames at 64×64. white(0) blue(1) green(2) red(3) black(4) gold(5).
|
||
this.load.spritesheet('splendor-gems', 'assets/images/splendor-gems.png', { frameWidth: 64, frameHeight: 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.
|
||
this.load.spritesheet('azul-tiles', 'assets/images/azul-tiles.png', { frameWidth: 96, frameHeight: 96 });
|
||
this.load.spritesheet('ttr-cards', 'assets/images/tickettoride-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||
this.load.spritesheet('gofish-cards', 'assets/images/gofish-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||
this.load.spritesheet('oldmaid-cards', 'assets/images/oldmaid-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||
this.load.spritesheet('tab-icons', 'assets/images/tab-icons.png', { frameWidth: 128, frameHeight: 128 });
|
||
this.load.spritesheet('game-icons', 'assets/images/game-icons.png', { frameWidth: 44, frameHeight: 44 });
|
||
|
||
// 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.
|
||
this.load.spritesheet('labyrinth-tiles', 'assets/images/labyrinth-tiles.png', { frameWidth: 200, frameHeight: 200 });
|
||
this.load.spritesheet('labyrinth-treasures', 'assets/images/labyrinth-treasures.png', { frameWidth: 100, frameHeight: 100 });
|
||
this.load.spritesheet('labyrinth-cards', 'assets/images/labyrinth-cards.png', { frameWidth: 270, frameHeight: 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.
|
||
this.load.spritesheet('stratego-pieces', 'assets/images/stratego-pieces.png', { frameWidth: 140, frameHeight: 140 });
|
||
// Monopoly pawns: 4 frames (one per seat) at 80×80. Optional — falls back to colored circles.
|
||
this.load.spritesheet('monopoly-pawns', 'assets/images/monopoly-pawns.png', { frameWidth: 80, frameHeight: 80 });
|
||
// Monopoly card art: frame 0 = Chance, frame 1 = Community Chest, at 200×300.
|
||
this.load.spritesheet('monopoly-cards', 'assets/images/monopoly-cards.png', { frameWidth: 200, frameHeight: 300 });
|
||
// Mahjong Match tile-label overlays (128×178 transparent PNGs). The White
|
||
// Dragon has no art — the scene draws its traditional empty frame.
|
||
const mahjongLabels = [
|
||
...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',
|
||
];
|
||
for (const name of mahjongLabels) {
|
||
this.load.image(`mahjong-${name}`, `assets/images/mahjong/${name}.png`);
|
||
}
|
||
|
||
this.load.audio('sfx-monopoly-purchase', 'assets/fx/monopoly-purchase.mp3');
|
||
this.load.audio('sfx-monopoly-expense', 'assets/fx/monopoly-expense.mp3');
|
||
this.load.audio('sfx-monopoly-pay', 'assets/fx/monopoly-pay.mp3');
|
||
this.load.audio('sfx-monopoly-paid', 'assets/fx/monopoly-paid.mp3');
|
||
}
|
||
|
||
async create() {
|
||
// Collect all image assets that need loading from JSON configs
|
||
const pfd = this.cache.json.get('playfields');
|
||
const cbd = this.cache.json.get('card-backs');
|
||
|
||
const shiftArt = this.cache.json.get('shift-artwork');
|
||
const slotsArt = this.cache.json.get('slots-artwork');
|
||
const toLoad = [
|
||
...(pfd?.playfields ?? []).filter((pf) => pf.path && !this.textures.exists(pf.key)),
|
||
...(cbd?.cardBacks ?? []).filter((cb) => cb.path && !this.textures.exists(cb.key)),
|
||
...(shiftArt?.artwork ?? []).filter((a) => a.path && a.key && !this.textures.exists(a.key)),
|
||
...(slotsArt?.artwork ?? []).filter((a) => a.path && a.key && !this.textures.exists(a.key)),
|
||
];
|
||
|
||
// Spire Climb drop-in spritesheets (card art + creature art). Only loaded
|
||
// once the artist sets a `path` in /data/spireclimb-artwork.json; until then
|
||
// the game renders cards/creatures procedurally.
|
||
const scArt = this.cache.json.get('spireclimb-artwork');
|
||
const scSheets = [scArt?.cardSheet, scArt?.creatureSheet, ...Object.values(scArt?.playerSheets || {})]
|
||
.filter((s) => s && s.path && s.key && !this.textures.exists(s.key));
|
||
|
||
if (toLoad.length > 0 || scSheets.length > 0) {
|
||
for (const asset of toLoad) this.load.image(asset.key, asset.path);
|
||
for (const s of scSheets) this.load.spritesheet(s.key, s.path, { frameWidth: s.frameWidth, frameHeight: s.frameHeight });
|
||
await new Promise((resolve) => {
|
||
this.load.once('complete', resolve);
|
||
this.load.start();
|
||
});
|
||
}
|
||
|
||
// Warm the handwritten Scrabble notepad font so its first paint isn't a
|
||
// fallback face. Best effort — never block startup on it.
|
||
try { await Promise.race([document.fonts.load('48px YummyCupcakes'), new Promise(r => setTimeout(r, 1500))]); } catch { /* ignore */ }
|
||
|
||
await auth.refresh();
|
||
this.scene.start('Landing');
|
||
}
|
||
}
|