Add Virtue Slots game with Christian theme, including slot machine mechanics, vial-based economy system, and win/loss animations
This commit is contained in:
parent
2b6943c319
commit
2f518e5165
|
|
@ -0,0 +1,148 @@
|
|||
# Virtue Slots
|
||||
|
||||
A browser-based slot machine game with a Christian religious theme, built with Phaser 3 and vanilla JavaScript. May Fortune Favor the Faithful.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **10 holy symbols** — from Dove to Holy Grail, each with unique payout values scaled by holiness
|
||||
- **Animated reels** — smooth spin, decelerate, and snap-to-symbol with sprite overlays
|
||||
- **Win sequence** — "MATCH: 3×" banner punches in with elastic animation, sparkles, and shaking; followed by arcing coin animations splitting 60% to the player and 40% to The Lord
|
||||
- **Loss sequence** — a devil carries the spent money up to the Sin box with a wobble animation
|
||||
- **Split labels** — on a win, animated callouts show "YOUR WINNINGS 60%" and "THE LORD'S TITHE 40%" so the player always understands where the money goes
|
||||
- **The Reckoning** — two filling vials on the right track cumulative funds for "The Lord" and "Sin"; whichever reaches $2,000 first wins; vials shake progressively harder as they approach the limit
|
||||
- **Living background** — stars drift, twinkle, and pulse independently for an ambient celestial feel
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Thing | Choice |
|
||||
|---|---|
|
||||
| Game engine | [Phaser 3](https://phaser.io/) via CDN |
|
||||
| Language | Vanilla JavaScript — ES6 modules |
|
||||
| Bundler | **None** — files are served directly |
|
||||
| Canvas | 1600 × 900, FIT + CENTER scale mode |
|
||||
|
||||
---
|
||||
|
||||
## Running Locally
|
||||
|
||||
Browsers block ES6 module imports from `file://`, so serve over HTTP:
|
||||
|
||||
```bash
|
||||
# Python (built-in)
|
||||
python3 -m http.server 8080
|
||||
|
||||
# Node (npx)
|
||||
npx serve .
|
||||
```
|
||||
|
||||
Then open **http://localhost:8080** in any modern browser.
|
||||
|
||||
---
|
||||
|
||||
## Controls
|
||||
|
||||
| Action | Input |
|
||||
|---|---|
|
||||
| Spin | Click the **SPIN** button |
|
||||
| Spin | Press **Space Bar** |
|
||||
|
||||
---
|
||||
|
||||
## Game Economy
|
||||
|
||||
| Setting | Value |
|
||||
|---|---|
|
||||
| Starting funds | $1,000 |
|
||||
| Cost per spin | $50 |
|
||||
| Win rate | ~1 in 15 spins |
|
||||
| Win split | 60% to player · 40% to The Lord |
|
||||
| Loss result | $50 added to the Sin total |
|
||||
|
||||
---
|
||||
|
||||
## Symbols & Payouts
|
||||
|
||||
Symbols are ordered by holiness. A three-of-a-kind match pays the symbol's full payout value.
|
||||
|
||||
| Symbol | Holiness | Payout |
|
||||
|---|---|---|
|
||||
| Dove | 1 | $200 |
|
||||
| Joel Osteen | 2 | $300 |
|
||||
| Holy Bible | 3 | $400 |
|
||||
| Lamb of God | 4 | $500 |
|
||||
| Crown of Thorns | 5 | $600 |
|
||||
| Halo | 6 | $700 |
|
||||
| The Cross | 7 | $900 |
|
||||
| Jesus on Cross | 8 | $1,100 |
|
||||
| Baby Jesus | 9 | $1,300 |
|
||||
| Holy Grail | 10 | $1,500 |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
index.html Loads Phaser 3 (CDN) and main.js as type="module"
|
||||
main.js Phaser.Game config — registers all three scenes
|
||||
|
||||
state/
|
||||
GameState.js Singleton: playerFunds, lordFunds, sinTotal, spinning flag
|
||||
|
||||
utils/
|
||||
RNG.js shouldWin() (~1/15), pickResults() for reel targets
|
||||
|
||||
objects/
|
||||
Symbol.js SYMBOLS array — id, label, holiness, payout, color
|
||||
Reel.js Single reel: virtual infinite strip, pooled cells, sprite overlay
|
||||
SlotMachine.js Orchestrates 3 reels; manages spin timing and result callback
|
||||
WinAnimation.js Match banner → gold flash → arcing coin animation → split labels
|
||||
LossAnimation.js Devil graphic rises to the Sin box
|
||||
MatchBanner.js Full-screen "MATCH: 3× {label}" overlay with particle burst
|
||||
VialDisplay.js Filling vial with shake escalation and winner detection
|
||||
|
||||
scenes/
|
||||
BootScene.js Preloads assets (symbol spritesheet), then starts game
|
||||
GameScene.js Background, stars, slot machine, vials, input handling
|
||||
UIScene.js Overlay HUD: fund boxes (top), message + SPIN button (bottom)
|
||||
|
||||
assets/
|
||||
symbol_sprites.png 200×100 px spritesheet — 10 frames, one per symbol in order
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scene Communication
|
||||
|
||||
Scenes communicate exclusively through the global Phaser event bus (`this.game.events`):
|
||||
|
||||
| Emitter | Event | Payload |
|
||||
|---|---|---|
|
||||
| UIScene | `spin` | — |
|
||||
| GameScene | `win` | `{ playerGain, lordGain, symbol }` |
|
||||
| GameScene | `loss` | `{ sinAdded }` |
|
||||
| GameScene | `funds-updated` | — |
|
||||
| GameScene | `spin-complete` | — |
|
||||
| GameScene | `insufficient-funds` | — |
|
||||
| VialDisplay | `vial-winner` | `{ winner }` |
|
||||
|
||||
---
|
||||
|
||||
## Sprite Sheet
|
||||
|
||||
`assets/symbol_sprites.png` must be a horizontal sprite sheet:
|
||||
|
||||
- **Frame size:** 200 × 100 px
|
||||
- **Frame count:** 10 (one per symbol)
|
||||
- **Frame order:** must match the `SYMBOLS` array order in `objects/Symbol.js` (Dove at index 0, Holy Grail at index 9)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Do whatever you want with it. Probably don't build a real gambling site.
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
// Symbols ordered by holiness ascending (index 0 = least holy)
|
||||
export const SYMBOLS = [
|
||||
{ id: 'dove', label: 'Dove', holiness: 1, payout: 100, color: 0xd0e8ff },
|
||||
{ id: 'joel', label: 'Joel Osteen', holiness: 2, payout: 150, color: 0xa8d8ea },
|
||||
{ id: 'bible', label: 'Holy Bible', holiness: 3, payout: 200, color: 0x7ec8e3 },
|
||||
{ id: 'lamb', label: 'Lamb of God', holiness: 4, payout: 250, color: 0x88d498 },
|
||||
{ id: 'crown_thorns', label: 'Crown of Thorns', holiness: 5, payout: 350, color: 0xc8a87e },
|
||||
{ id: 'halo', label: 'Halo', holiness: 6, payout: 450, color: 0xf0d060 },
|
||||
{ id: 'cross', label: 'The Cross', holiness: 7, payout: 600, color: 0xf0a830 },
|
||||
{ id: 'jesus_cross', label: 'Jesus on Cross', holiness: 8, payout: 800, color: 0xe88020 },
|
||||
{ id: 'baby_jesus', label: 'Baby Jesus', holiness: 9, payout: 1000, color: 0xff9944 },
|
||||
{ id: 'dove', label: 'Dove', holiness: 1, payout: 200, color: 0xd0e8ff },
|
||||
{ id: 'joel', label: 'Joel Osteen', holiness: 2, payout: 300, color: 0xa8d8ea },
|
||||
{ id: 'bible', label: 'Holy Bible', holiness: 3, payout: 400, color: 0x7ec8e3 },
|
||||
{ id: 'lamb', label: 'Lamb of God', holiness: 4, payout: 500, color: 0x88d498 },
|
||||
{ id: 'crown_thorns', label: 'Crown of Thorns', holiness: 5, payout: 600, color: 0xc8a87e },
|
||||
{ id: 'halo', label: 'Halo', holiness: 6, payout: 700, color: 0xf0d060 },
|
||||
{ id: 'cross', label: 'The Cross', holiness: 7, payout: 900, color: 0xf0a830 },
|
||||
{ id: 'jesus_cross', label: 'Jesus on Cross', holiness: 8, payout: 1100, color: 0xe88020 },
|
||||
{ id: 'baby_jesus', label: 'Baby Jesus', holiness: 9, payout: 1300, color: 0xff9944 },
|
||||
{ id: 'holy_grail', label: 'Holy Grail', holiness: 10, payout: 1500, color: 0xffd700 },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,299 @@
|
|||
export class VialDisplay {
|
||||
constructor(scene, x, y, label, fillColor, borderColor) {
|
||||
this.scene = scene;
|
||||
this.baseX = x;
|
||||
this.baseY = y;
|
||||
this.label = label;
|
||||
this.fillColor = fillColor;
|
||||
this.borderColor = borderColor;
|
||||
|
||||
this.VIAL_W = 62;
|
||||
this.VIAL_H = 370;
|
||||
this.MAX = 2000;
|
||||
this.currentAmount = 0;
|
||||
this._shakeTween = null;
|
||||
this._shakeApplied = 0; // last threshold at which shake was started
|
||||
this._won = false;
|
||||
|
||||
this._build();
|
||||
}
|
||||
|
||||
_build() {
|
||||
const { scene, baseX, baseY, VIAL_W, VIAL_H, fillColor, borderColor } = this;
|
||||
const R = VIAL_W / 2; // radius for rounded top cap
|
||||
|
||||
this.container = scene.add.container(baseX, baseY);
|
||||
|
||||
// Dark glass interior
|
||||
const glassBg = scene.add.graphics();
|
||||
glassBg.fillStyle(0x060214, 0.95);
|
||||
glassBg.fillRoundedRect(-VIAL_W / 2, 0, VIAL_W, VIAL_H, { tl: R, tr: R, bl: 6, br: 6 });
|
||||
this.container.add(glassBg);
|
||||
|
||||
// Fill graphic — redrawn dynamically
|
||||
this.fillGfx = scene.add.graphics();
|
||||
this.container.add(this.fillGfx);
|
||||
|
||||
// Glass border rendered on top of fill so it stays crisp
|
||||
const border = scene.add.graphics();
|
||||
border.lineStyle(3, borderColor, 0.9);
|
||||
border.strokeRoundedRect(-VIAL_W / 2, 0, VIAL_W, VIAL_H, { tl: R, tr: R, bl: 6, br: 6 });
|
||||
// Outer glow ring
|
||||
border.lineStyle(10, borderColor, 0.18);
|
||||
border.strokeRoundedRect(-VIAL_W / 2 - 5, -5, VIAL_W + 10, VIAL_H + 10, { tl: R + 5, tr: R + 5, bl: 10, br: 10 });
|
||||
// Left-side glass highlight
|
||||
border.lineStyle(2, 0xffffff, 0.22);
|
||||
border.beginPath();
|
||||
border.moveTo(-VIAL_W / 2 + 6, R + 6);
|
||||
border.lineTo(-VIAL_W / 2 + 6, VIAL_H - 12);
|
||||
border.strokePath();
|
||||
this.container.add(border);
|
||||
|
||||
// Tick marks at $500, $1000, $1500, $2000
|
||||
const ticks = scene.add.graphics();
|
||||
[500, 1000, 1500, 2000].forEach(amt => {
|
||||
const ty = VIAL_H - (amt / this.MAX) * VIAL_H;
|
||||
ticks.lineStyle(amt === 2000 ? 2 : 1, borderColor, amt === 2000 ? 0.6 : 0.25);
|
||||
ticks.beginPath();
|
||||
ticks.moveTo(-VIAL_W / 2 + 4, ty);
|
||||
ticks.lineTo( VIAL_W / 2 - 4, ty);
|
||||
ticks.strokePath();
|
||||
|
||||
const lbl = scene.add.text(VIAL_W / 2 + 7, ty, `$${amt}`, {
|
||||
fontSize: amt === 2000 ? '12px' : '10px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: amt === 2000 ? '#ffd700' : '#4a5a6a',
|
||||
}).setOrigin(0, 0.5);
|
||||
this.container.add(lbl);
|
||||
});
|
||||
this.container.add(ticks);
|
||||
|
||||
// Floating amount text shown just above the fill surface
|
||||
this.amtText = scene.add.text(0, VIAL_H - 6, '$0', {
|
||||
fontSize: '11px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontStyle: 'bold',
|
||||
color: '#ffffff',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 2,
|
||||
}).setOrigin(0.5, 1).setAlpha(0);
|
||||
this.container.add(this.amtText);
|
||||
|
||||
// Vial label below
|
||||
this.container.add(
|
||||
scene.add.text(0, VIAL_H + 14, this.label, {
|
||||
fontSize: '15px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: '#c8a87e',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 2,
|
||||
}).setOrigin(0.5, 0)
|
||||
);
|
||||
|
||||
// Goal text
|
||||
this.container.add(
|
||||
scene.add.text(0, VIAL_H + 36, 'GOAL: $2,000', {
|
||||
fontSize: '10px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
color: '#3a4a5a',
|
||||
}).setOrigin(0.5, 0)
|
||||
);
|
||||
}
|
||||
|
||||
_drawFill(amount) {
|
||||
const { VIAL_W, VIAL_H, MAX, fillColor } = this;
|
||||
const capped = Math.min(amount, MAX);
|
||||
const fillH = (capped / MAX) * VIAL_H;
|
||||
|
||||
this.fillGfx.clear();
|
||||
if (fillH < 2) {
|
||||
this.amtText.setAlpha(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const fillY = VIAL_H - fillH;
|
||||
const R = VIAL_W / 2;
|
||||
|
||||
// Main fill body
|
||||
this.fillGfx.fillStyle(fillColor, 0.88);
|
||||
if (fillY < R) {
|
||||
// Near the rounded top — match the cap shape
|
||||
this.fillGfx.fillRoundedRect(-VIAL_W / 2 + 3, fillY, VIAL_W - 6, fillH, { tl: R - 3, tr: R - 3, bl: 4, br: 4 });
|
||||
} else {
|
||||
this.fillGfx.fillRoundedRect(-VIAL_W / 2 + 3, fillY, VIAL_W - 6, fillH, 4);
|
||||
}
|
||||
|
||||
// Bright liquid surface shimmer
|
||||
this.fillGfx.fillStyle(0xffffff, 0.38);
|
||||
this.fillGfx.fillRoundedRect(-VIAL_W / 2 + 5, fillY, VIAL_W - 10, 5, 3);
|
||||
|
||||
// Reposition floating amount label just above the surface
|
||||
this.amtText.setY(Math.max(fillY - 4, 4));
|
||||
this.amtText.setAlpha(1);
|
||||
this.amtText.setText(`$${Math.round(capped)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate mini coins flying from world position (fromX, fromY) into the vial,
|
||||
* then animate the fill rising to targetAmount.
|
||||
*/
|
||||
animateUpdate(targetAmount, fromX, fromY, onComplete) {
|
||||
const scene = this.scene;
|
||||
const toAmount = Math.min(targetAmount, this.MAX);
|
||||
|
||||
if (toAmount <= this.currentAmount || this._won) {
|
||||
if (onComplete) onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
const aimX = this.baseX;
|
||||
const aimY = this.baseY + this.VIAL_H * 0.78;
|
||||
const numCoins = Phaser.Math.Between(5, 8);
|
||||
let landed = 0;
|
||||
|
||||
for (let i = 0; i < numCoins; i++) {
|
||||
const coin = scene.add.graphics();
|
||||
coin.fillStyle(this.fillColor, 1);
|
||||
coin.fillCircle(0, 0, 7);
|
||||
coin.lineStyle(1.5, 0xffffff, 0.5);
|
||||
coin.strokeCircle(0, 0, 7);
|
||||
coin.setPosition(fromX + Phaser.Math.Between(-14, 14), fromY);
|
||||
coin.setScale(0.1).setAlpha(0);
|
||||
|
||||
scene.tweens.add({
|
||||
targets: coin,
|
||||
alpha: 1,
|
||||
scale: 1,
|
||||
duration: 140,
|
||||
delay: i * 80,
|
||||
ease: 'Back.easeOut',
|
||||
onComplete: () => {
|
||||
scene.tweens.add({
|
||||
targets: coin,
|
||||
x: aimX + Phaser.Math.Between(-6, 6),
|
||||
y: aimY,
|
||||
scale: 0.3,
|
||||
alpha: 0.7,
|
||||
duration: 480 + i * 50,
|
||||
ease: 'Cubic.easeIn',
|
||||
onComplete: () => {
|
||||
coin.destroy();
|
||||
if (++landed === numCoins) this._animateFill(toAmount, onComplete);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_animateFill(toAmount, onComplete) {
|
||||
const scene = this.scene;
|
||||
const proxy = { value: this.currentAmount };
|
||||
|
||||
scene.tweens.add({
|
||||
targets: proxy,
|
||||
value: toAmount,
|
||||
duration: 1100,
|
||||
ease: 'Cubic.easeOut',
|
||||
onUpdate: () => {
|
||||
this.currentAmount = proxy.value;
|
||||
this._drawFill(proxy.value);
|
||||
// Start shaking the first time the fill crosses $1500
|
||||
if (proxy.value >= 1500 && this._shakeApplied < 1500) {
|
||||
this._shakeApplied = 1500;
|
||||
this._applyShake(1500);
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
this.currentAmount = toAmount;
|
||||
this._drawFill(toAmount);
|
||||
// Update shake to final intensity
|
||||
if (toAmount >= 1500) this._applyShake(toAmount);
|
||||
if (toAmount >= this.MAX && !this._won) {
|
||||
this._won = true;
|
||||
this._celebrateWin();
|
||||
}
|
||||
if (onComplete) onComplete();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_applyShake(amount) {
|
||||
if (amount < 1500) return;
|
||||
|
||||
if (this._shakeTween) {
|
||||
this._shakeTween.stop();
|
||||
this._shakeTween = null;
|
||||
this.container.setX(this.baseX);
|
||||
}
|
||||
|
||||
const pct = Math.min((amount - 1500) / 500, 1);
|
||||
const intensity = 3 + pct * 15; // 3px at $1500 → 18px at $2000
|
||||
const speed = Math.max(22, 100 - pct * 82); // 100ms → 18ms (faster = more frantic)
|
||||
|
||||
this._shakeTween = this.scene.tweens.add({
|
||||
targets: this.container,
|
||||
x: { from: this.baseX - intensity, to: this.baseX + intensity },
|
||||
duration: speed,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut',
|
||||
});
|
||||
}
|
||||
|
||||
_celebrateWin() {
|
||||
const scene = this.scene;
|
||||
|
||||
// Stop shaking and snap back to position
|
||||
if (this._shakeTween) {
|
||||
this._shakeTween.stop();
|
||||
this._shakeTween = null;
|
||||
}
|
||||
this.container.setX(this.baseX);
|
||||
|
||||
// Rapid flash on the fill
|
||||
scene.tweens.add({
|
||||
targets: this.fillGfx,
|
||||
alpha: { from: 1, to: 0.1 },
|
||||
duration: 90,
|
||||
yoyo: true,
|
||||
repeat: 7,
|
||||
});
|
||||
|
||||
// Expanding burst ring from vial center
|
||||
const burst = scene.add.graphics();
|
||||
burst.lineStyle(6, this.borderColor, 1);
|
||||
burst.strokeCircle(0, 0, 20);
|
||||
burst.setPosition(this.baseX, this.baseY + this.VIAL_H / 2);
|
||||
scene.tweens.add({
|
||||
targets: burst,
|
||||
scaleX: 6, scaleY: 6,
|
||||
alpha: 0,
|
||||
duration: 700,
|
||||
ease: 'Cubic.easeOut',
|
||||
onComplete: () => burst.destroy(),
|
||||
});
|
||||
|
||||
// Winner badge punches in above the vial
|
||||
const badge = scene.add.text(this.baseX, this.baseY - 22, '✦ FULL ✦', {
|
||||
fontSize: '16px',
|
||||
fontFamily: 'Georgia, serif',
|
||||
fontStyle: 'bold',
|
||||
color: '#ffd700',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 3,
|
||||
shadow: { offsetX: 0, offsetY: 0, color: '#ffd700', blur: 14, fill: true },
|
||||
}).setOrigin(0.5, 0.5).setAlpha(0).setScale(0.3);
|
||||
|
||||
scene.tweens.add({
|
||||
targets: badge,
|
||||
alpha: 1,
|
||||
scale: 1,
|
||||
y: this.baseY - 40,
|
||||
duration: 500,
|
||||
ease: 'Back.easeOut',
|
||||
});
|
||||
|
||||
scene.game.events.emit('vial-winner', { winner: this.label });
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { GameState } from '../state/GameState.js';
|
|||
import { SlotMachine } from '../objects/SlotMachine.js';
|
||||
import { WinAnimation } from '../objects/WinAnimation.js';
|
||||
import { LossAnimation } from '../objects/LossAnimation.js';
|
||||
import { VialDisplay } from '../objects/VialDisplay.js';
|
||||
|
||||
export default class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
|
|
@ -87,6 +88,30 @@ export default class GameScene extends Phaser.Scene {
|
|||
this.winAnim = new WinAnimation();
|
||||
this.lossAnim = new LossAnimation();
|
||||
|
||||
// ── Right section: The Reckoning vials ──────────────────────────────────
|
||||
const sectionBg = this.add.graphics();
|
||||
sectionBg.fillStyle(0x0c0620, 0.65);
|
||||
sectionBg.fillRoundedRect(1157, 138, 434, 498, 14);
|
||||
sectionBg.lineStyle(1, 0xffd700, 0.3);
|
||||
sectionBg.strokeRoundedRect(1157, 138, 434, 498, 14);
|
||||
|
||||
this.add.text(1374, 157, 'THE RECKONING', {
|
||||
fontSize: '13px', fontFamily: 'Georgia, serif',
|
||||
color: '#c8a87e', letterSpacing: 5,
|
||||
}).setOrigin(0.5, 0.5);
|
||||
|
||||
this.add.text(1374, 175, 'First to $2,000 wins', {
|
||||
fontSize: '10px', fontFamily: 'Georgia, serif', color: '#4a5a6a',
|
||||
}).setOrigin(0.5, 0.5);
|
||||
|
||||
this.add.text(1374, 378, 'VS', {
|
||||
fontSize: '20px', fontFamily: 'Georgia, serif', fontStyle: 'bold',
|
||||
color: '#2a1a4a', stroke: '#000000', strokeThickness: 3,
|
||||
}).setOrigin(0.5, 0.5);
|
||||
|
||||
this.lordVial = new VialDisplay(this, 1268, 190, 'The Lord', 0xffd700, 0xc8a87e);
|
||||
this.sinVial = new VialDisplay(this, 1478, 190, 'Sin', 0xff4444, 0xff6666);
|
||||
|
||||
// Keyboard: Space to spin
|
||||
this.input.keyboard.on('keydown-SPACE', () => this._triggerSpin());
|
||||
|
||||
|
|
@ -132,8 +157,10 @@ export default class GameScene extends Phaser.Scene {
|
|||
lordBox,
|
||||
symbols[0],
|
||||
() => {
|
||||
GameState.spinning = false;
|
||||
this.game.events.emit('spin-complete');
|
||||
this.lordVial.animateUpdate(GameState.lordFunds, lordBox.x, 115, () => {
|
||||
GameState.spinning = false;
|
||||
this.game.events.emit('spin-complete');
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
|
@ -151,8 +178,10 @@ export default class GameScene extends Phaser.Scene {
|
|||
this.slotMachine.getCenterY(),
|
||||
sinBox,
|
||||
() => {
|
||||
GameState.spinning = false;
|
||||
this.game.events.emit('spin-complete');
|
||||
this.sinVial.animateUpdate(GameState.sinTotal, sinBox.x, 115, () => {
|
||||
GameState.spinning = false;
|
||||
this.game.events.emit('spin-complete');
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,6 +179,17 @@ export default class UIScene extends Phaser.Scene {
|
|||
this.messageText.setText('Insufficient funds to spin! You have been consumed by Sin.');
|
||||
this.messageText.setColor('#ff4444');
|
||||
}, this);
|
||||
|
||||
this.game.events.on('vial-winner', ({ winner }) => {
|
||||
const isLord = winner.toLowerCase().includes('lord');
|
||||
this.messageText.setText(
|
||||
isLord
|
||||
? '✝ The Lord Has Triumphed! ✝\nHis cup runneth over — glory be!'
|
||||
: '☠ Sin Has Prevailed! ☠\nYou have been consumed by darkness.'
|
||||
);
|
||||
this.messageText.setColor(isLord ? '#ffd700' : '#ff4444');
|
||||
this.redeemText.setAlpha(0);
|
||||
}, this);
|
||||
}
|
||||
|
||||
_updateFundDisplays() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue