import * as Phaser from 'phaser'; import { GAME_HEIGHT, GAME_WIDTH, COLORS } from '../config.js'; import { auth } from '../services/auth.js'; import { getGame } from '../data/gamesRegistry.js'; import { stopMenuMusic, configureMainMenuTrack } from '../ui/MenuMusic.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(); }); // NOTE: game-specific art lives in src/data/assetManifest.js and is // lazy-loaded by GameRoomScene the first time a game is entered. Only // shared assets (menus, opponents, card backs, SFX, JSON) load here. this.load.spritesheet('opponents', 'assets/images/opponents.png', { frameWidth: 300, frameHeight: 300, }); this.load.spritesheet('cardbacks', 'assets/images/cardbacks.png', { frameWidth: 320, frameHeight: 420, }); 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('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('nintendo-music', 'data/nintendo-music.json'); this.load.json('arcadedark-music', 'data/arcadedark-music.json'); this.load.json('hacker-music', 'data/hacker-music.json'); this.load.json('adventure-music', 'data/adventure-music.json'); this.load.json('chinese-music', 'data/chinese-music.json'); this.load.json('advancewars-artwork', 'data/advancewars-artwork.json'); this.load.json('tetrisattack-artwork', 'data/tetrisattack-artwork.json'); this.load.json('totalannihilation-artwork', 'data/totalannihilation-artwork.json'); this.load.json('rushhour', 'data/rushhour.json'); this.load.json('bloxorz', 'data/bloxorz.json'); this.load.json('puddingmonsters', 'data/puddingmonsters.json'); this.load.json('gootower-levels', 'assets/gamedata/gootower/levels.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('dungeonboss-artwork', 'data/dungeonboss-artwork.json'); this.load.json('swdbg-cards', 'data/swdbg-cards.json'); this.load.json('swdbg-artwork', 'data/swdbg-artwork.json'); this.load.json('jumble', 'data/jumble.json'); this.load.json('balatro-artwork', 'data/balatro-artwork.json'); this.load.json('civilization-artwork', 'data/civilization-artwork.json'); this.load.json('peggle-levels', 'assets/gamedata/peggle/levels.json'); this.load.json('superkart-racers', 'data/superkart-racers.json'); this.load.json('superkart-rules', 'data/superkart-rules.json'); this.load.json('superkart-artwork', 'data/superkart-artwork.json'); this.load.json('colorado-defense-cities', 'data/colorado-defense-cities.json'); this.load.json('star-control-ships', 'data/star-control-ships.json'); this.load.audio('sfx-engine-start', 'assets/fx/engine-start.mp3'); this.load.audio('sfx-engine-heavy', 'assets/fx/engine-heavy.mp3'); this.load.audio('sfx-engine-fast', 'assets/fx/engine-fast.mp3'); this.load.audio('sfx-engine-medium', 'assets/fx/engine-medium.mp3'); this.load.audio('sfx-engine-rev', 'assets/fx/engine-rev.mp3'); this.load.audio('sfx-kart-coin', 'assets/fx/kart-coin.mp3'); this.load.audio('sfx-kart-flatten', 'assets/fx/kart-flatten.mp3'); this.load.audio('sfx-kart-thump', 'assets/fx/kart-thump.mp3'); this.load.audio('sfx-kart-shrink', 'assets/fx/kart-shrink.mp3'); this.load.audio('sfx-kart-spin', 'assets/fx/kart-spin.mp3'); this.load.audio('sfx-kart-hit', 'assets/fx/kart-hit.mp3'); this.load.audio('sfx-kart-shell', 'assets/fx/kart-shell.mp3'); this.load.audio('sfx-kart-star', 'assets/fx/kart-star.mp3'); this.load.audio('sfx-kart-spinner', 'assets/fx/kart-spinner.mp3'); 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-energy-hum', 'assets/fx/energy-hum.mp3'); this.load.audio('sfx-laser-zap', 'assets/fx/laser-zap.mp3'); this.load.audio('sfx-rewind', 'assets/fx/rewind.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-click', 'assets/fx/click.mp3'); this.load.audio('sfx-8bit-jump', 'assets/fx/8bit-jump.mp3'); this.load.audio('sfx-8bit-explode', 'assets/fx/8bit-explode.mp3'); this.load.audio('sfx-8bit-explode2', 'assets/fx/8bit-explode2.mp3'); this.load.audio('sfx-8bit-count', 'assets/fx/8bit-count.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.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.audio('sfx-sw-light-1', 'assets/fx/sw-light-1.mp3'); this.load.audio('sfx-sw-light-2', 'assets/fx/sw-light-2.mp3'); this.load.audio('sfx-sw-light-3', 'assets/fx/sw-light-3.mp3'); this.load.audio('sfx-sw-dark-1', 'assets/fx/sw-dark-1.mp3'); this.load.audio('sfx-sw-dark-2', 'assets/fx/sw-dark-2.mp3'); this.load.audio('sfx-sw-dark-3', 'assets/fx/sw-dark-3.mp3'); 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 }); 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() { // Playfields and card backs are shared across games and shown as // thumbnails in OpponentSelect, so they load at startup. Per-game drop-in // artwork (shift/slots/spireclimb/dungeonboss/swdbg/balatro) is handled by // data/assetManifest.js and lazy-loaded on game entry instead. const musicData = this.cache.json.get('music'); const menuTrack = musicData?.soundtracks?.main_menu?.[0]?.file; if (menuTrack) configureMainMenuTrack(menuTrack); const pfd = this.cache.json.get('playfields'); const cbd = this.cache.json.get('card-backs'); const toLoad = [ ...(pfd?.playfields ?? []).filter((pf) => pf.path && !this.textures.exists(pf.key)), ...(cbd?.cardBacks ?? []).filter((cb) => cb.path && !this.textures.exists(cb.key)), ]; if (toLoad.length > 0) { for (const asset of toLoad) this.load.image(asset.key, asset.path); await new Promise((resolve) => { this.load.once('complete', resolve); this.load.start(); }); } // Warm the handwritten Scrabble notepad font and Balatro's pixel font so // their first paint isn't a fallback face. Best effort — never block // startup on them. try { await Promise.race([Promise.all([document.fonts.load('48px YummyCupcakes'), document.fonts.load('48px m6x11')]), new Promise(r => setTimeout(r, 1500))]); } catch { /* ignore */ } await auth.refresh(); // Secret level-editor entrance: index.html?peggle-editor=1 boots straight // into the Peggle editor instead of the landing page. const params = new URLSearchParams(window.location.search); if (params.has('peggle-editor')) { window.history.replaceState(null, '', window.location.pathname + window.location.hash); this.scene.start('PeggleEditor'); return; } // Same trick for the Super Kart track editor: ?superkart-editor=1. if (params.has('superkart-editor')) { window.history.replaceState(null, '', window.location.pathname + window.location.hash); this.scene.start('SuperKartEditor'); return; } // ...and the Goo Tower level editor: ?gootower-editor=1. if (params.has('gootower-editor')) { window.history.replaceState(null, '', window.location.pathname + window.location.hash); this.scene.start('GooTowerEditor'); return; } // Deep link: index.html?game= jumps straight to the same place a // main-menu click would (GameMenuScene.openGame's branching logic). const deepLinkGame = getGame(params.get('game')); if (deepLinkGame) { window.history.replaceState(null, '', window.location.pathname + window.location.hash); if (deepLinkGame.maxOpponents === 0) { stopMenuMusic(); this.scene.start('GameRoom', { game: deepLinkGame, opponents: [] }); } else { this.scene.start('OpponentSelect', { game: deepLinkGame }); } return; } this.scene.start('Landing'); } }