diff --git a/public/assets/images/shift/fall-bike.png b/public/assets/images/shift/fall-bike.png new file mode 100644 index 0000000..c3e7580 Binary files /dev/null and b/public/assets/images/shift/fall-bike.png differ diff --git a/public/assets/images/shift/norway-fishing.png b/public/assets/images/shift/norway-fishing.png new file mode 100644 index 0000000..b3453f4 Binary files /dev/null and b/public/assets/images/shift/norway-fishing.png differ diff --git a/public/assets/images/shift/puppy-space.png b/public/assets/images/shift/puppy-space.png new file mode 100644 index 0000000..074e5d7 Binary files /dev/null and b/public/assets/images/shift/puppy-space.png differ diff --git a/public/assets/images/shift/red-dress.png b/public/assets/images/shift/red-dress.png new file mode 100644 index 0000000..32be3ca Binary files /dev/null and b/public/assets/images/shift/red-dress.png differ diff --git a/public/assets/images/shift/warrior.png b/public/assets/images/shift/warrior.png new file mode 100644 index 0000000..56b03fa Binary files /dev/null and b/public/assets/images/shift/warrior.png differ diff --git a/public/data/shift-artwork.json b/public/data/shift-artwork.json index a81a234..b7f515c 100644 --- a/public/data/shift-artwork.json +++ b/public/data/shift-artwork.json @@ -17,6 +17,36 @@ "name": "Underwater Dolphin Adventure", "key": "dolphin-underwater", "path": "/assets/images/shift/dolphin-underwater.png" + }, + { + "id": "fall-bike", + "name": "Biking in the Fall", + "key": "fall-bike", + "path": "/assets/images/shift/fall-bike.png" + }, + { + "id": "norway-fishing", + "name": "Fishing Village in Norway", + "key": "norway-fishing", + "path": "/assets/images/shift/norway-fishing.png" + }, + { + "id": "puppy-space", + "name": "Room with a View", + "key": "puppy-space", + "path": "/assets/images/shift/puppy-space.png" + }, + { + "id": "red-dress", + "name": "Red Dress in Paris", + "key": "red-dress", + "path": "/assets/images/shift/red-dress.png" + }, + { + "id": "warrior", + "name": "Fierce Warrior", + "key": "warrior", + "path": "/assets/images/shift/warrior.png" } ] } \ No newline at end of file diff --git a/public/src/games/bejeweled/BejeweledGame.js b/public/src/games/bejeweled/BejeweledGame.js index 1bf50e4..6b7e490 100644 --- a/public/src/games/bejeweled/BejeweledGame.js +++ b/public/src/games/bejeweled/BejeweledGame.js @@ -820,6 +820,13 @@ export default class BejeweledGame extends Phaser.Scene { this.time.delayedCall(i * 90, () => this.playEvent(e)); }); + // Apply total time bonus after all events in the phase. + const totalTimeBonus = phase.events.reduce((sum, e) => sum + (e.timeBonus ?? 0), 0); + if (totalTimeBonus > 0) { + this.timeLeft = Math.min(this.timeLeft + totalTimeBonus, BLITZ_SECONDS); + this.redrawTimer(); + } + // Clear matched gems with a burst. if (phase.cleared.length) playSound(this, SFX.MASTERMIND_MATCH); const sparse = phase.cleared.length > 14; @@ -899,7 +906,7 @@ export default class BejeweledGame extends Phaser.Scene { this.multText.setText(`×${e.multiplier}`).setAlpha(1); this.tweens.add({ targets: this.multText, scale: 1.5, duration: 160, yoyo: true, ease: 'Quad.easeOut' }); this.showBanner(`MULTIPLIER ×${e.multiplier}!`, '#6cc1ff'); - return; + if (e.timeBonus > 0) this.time.delayedCall(300, () => this.timeBonusBanner(e.timeBonus)); } const { x, y } = this.cellXY(e.c, e.r); if (e.type === 'flame') { @@ -919,6 +926,7 @@ export default class BejeweledGame extends Phaser.Scene { }).setDepth(D.fx); this.time.delayedCall(60, () => em.stop()); this.time.delayedCall(800, () => em.destroy()); + if (e.timeBonus > 0) this.time.delayedCall(500, () => this.timeBonusBanner(e.timeBonus)); } else if (e.type === 'star') { playSound(this, SFX.SCIFI_LAUNCH); this.cameras.main.shake(90, 0.003); @@ -935,6 +943,7 @@ export default class BejeweledGame extends Phaser.Scene { }).setDepth(D.fx); this.time.delayedCall(60, () => em.stop()); this.time.delayedCall(700, () => em.destroy()); + if (e.timeBonus > 0) this.time.delayedCall(400, () => this.timeBonusBanner(e.timeBonus)); } else if (e.type === 'hyper') { playSound(this, SFX.SCIFI_REVEAL); this.cameras.main.shake(170, 0.006); @@ -967,6 +976,7 @@ export default class BejeweledGame extends Phaser.Scene { }).setDepth(D.fx); this.time.delayedCall(80, () => em.stop()); this.time.delayedCall(900, () => em.destroy()); + if (e.timeBonus > 0) this.time.delayedCall(600, () => this.timeBonusBanner(e.timeBonus)); } } @@ -1007,6 +1017,17 @@ export default class BejeweledGame extends Phaser.Scene { }); } + timeBonusBanner(seconds) { + const cx = GAME_WIDTH / 2; + const cy = BOARD_Y + BOARD_W / 2 - 40 - 75; + const t = this.add.text(cx, cy, `+${seconds} Seconds Added!`, { + fontFamily: 'Righteous', fontSize: '52px', color: '#5eff8a', + stroke: '#0a2210', strokeThickness: 8, + }).setOrigin(0.5).setDepth(D.banner).setScale(0.3).setAlpha(0); + this.tweens.add({ targets: t, scale: 1, alpha: 1, duration: 220, ease: 'Back.easeOut' }); + this.tweens.add({ targets: t, alpha: 0, delay: 950, duration: 300, onComplete: () => t.destroy() }); + } + comboCallout(cascade) { const idx = Math.min(cascade, COMBO_WORDS.length - 1); const word = cascade >= 7 ? 'UNBELIEVABLE!' : COMBO_WORDS[idx]; diff --git a/public/src/games/bejeweled/BejeweledLogic.js b/public/src/games/bejeweled/BejeweledLogic.js index 2aed562..9733e8c 100644 --- a/public/src/games/bejeweled/BejeweledLogic.js +++ b/public/src/games/bejeweled/BejeweledLogic.js @@ -168,10 +168,10 @@ function expandClears(board, seedKeys, rng, hyperOverrides = new Map()) { const { c, r } = unkey(k); const sp = board[r][c].special; if (sp === SPECIAL.FLAME) { - events.push({ type: 'flame', c, r }); + events.push({ type: 'flame', c, r, timeBonus: TIME_BONUS.flame }); for (let dr = -1; dr <= 1; dr++) for (let dc = -1; dc <= 1; dc++) add(c + dc, r + dr); } else if (sp === SPECIAL.STAR) { - events.push({ type: 'star', c, r }); + events.push({ type: 'star', c, r, timeBonus: TIME_BONUS.star }); for (let i = 0; i < COLS; i++) add(i, r); for (let i = 0; i < ROWS; i++) add(c, i); } else if (sp === SPECIAL.HYPER) { @@ -190,7 +190,7 @@ function expandClears(board, seedKeys, rng, hyperOverrides = new Map()) { if (board[rr][cc]?.color === color) { cells.push([cc, rr]); add(cc, rr); } } } - events.push({ type: 'hyper', c, r, color, cells }); + events.push({ type: 'hyper', c, r, color, cells, timeBonus: TIME_BONUS.hyper }); } } return { keys, events }; @@ -226,6 +226,7 @@ function collapse(board, rng) { const GEM_POINTS = 30; const EVENT_BONUS = { flame: 100, star: 200, hyper: 400 }; +const TIME_BONUS = { flame: 10, star: 10, hyper: 15, mult: 5 }; // Resolve one or more cascade phases. opts.preClear seeds phase 1 directly // (hyper swaps, Last Hurrah); afterwards phases come from runs on the board. @@ -273,17 +274,21 @@ function runCascades(state, rng, opts = {}) { // Multiplier gems consumed this phase raise the global multiplier. let eventBonus = 0; + let timeBonus = 0; const cleared = []; for (const k of keys) { const { c, r } = unkey(k); const gem = board[r][c]; if (gem.special === SPECIAL.MULT && state.multiplier < MAX_MULTIPLIER) { state.multiplier++; - events.push({ type: 'mult', c, r, multiplier: state.multiplier }); + events.push({ type: 'mult', c, r, multiplier: state.multiplier, timeBonus: TIME_BONUS.mult }); } if (!spawnKeys.has(k)) cleared.push({ c, r, color: gem.color, special: gem.special }); } - for (const e of events) eventBonus += EVENT_BONUS[e.type] ?? 0; + for (const e of events) { + eventBonus += EVENT_BONUS[e.type] ?? 0; + timeBonus += TIME_BONUS[e.type] ?? 0; + } const points = Math.round((keys.size * GEM_POINTS + runBonus + eventBonus) * cascade * state.multiplier); @@ -308,7 +313,7 @@ function runCascades(state, rng, opts = {}) { phases.push({ cascade, points, multiplier: state.multiplier, - cleared, spawns: placedSpawns, events, falls, refills, + cleared, spawns: placedSpawns, events, falls, refills, timeBonus, }); swapKeys = []; }