import { GameState } from '../state/GameState.js'; const TOP_BAR_HEIGHT = 100; const BOTTOM_BAR_HEIGHT = 110; const BOTTOM_BAR_Y = 900 - BOTTOM_BAR_HEIGHT; // Box centers (x) for the three top fund displays const BOX_WIDTH = 1600 / 3; const PLAYER_BOX_X = BOX_WIDTH * 0 + BOX_WIDTH / 2; const LORD_BOX_X = BOX_WIDTH * 1 + BOX_WIDTH / 2; const SIN_BOX_X = BOX_WIDTH * 2 + BOX_WIDTH / 2; const BOX_CENTER_Y = TOP_BAR_HEIGHT / 2; // Resting positions for the two bottom-bar text objects const MSG_X = 700; const MSG_Y = BOTTOM_BAR_Y + BOTTOM_BAR_HEIGHT / 2; // 845 const SUB_Y = BOTTOM_BAR_Y + BOTTOM_BAR_HEIGHT / 2 + 30; // 875 const pick = arr => arr[Phaser.Math.Between(0, arr.length - 1)]; // ── Message pools ───────────────────────────────────────────────────────────── const LOSS_MESSAGES = [ 'Thou Hath Sinned.', 'The Darkness Consumes Thee.', 'Sin Claims Its Tithe.', 'The Devil Takes His Due.', 'Wretched Soul!', 'Thy Coin Belongs to Evil.', "Hell's Coffers Grow Richer.", 'Condemned by Fortune.', 'Behold Thy Folly!', 'The Serpent Strikes Again!', 'Damnation Draws Near.', 'Thy Faith Was Wanting.', 'The Wicked Path Claims Thee.', 'Evil Rejoices This Hour!', 'Another Soul Slips Away.', 'The Pit Calls Thy Name.', 'Darkness Prevails.', "Sin's Ledger Grows.", 'The Fallen Laugh at Thee.', 'Thy Virtue Wavers.', 'The Shadow Feeds.', "Perdition's Price Is Paid.", 'The Infernal Reels Mock Thee.', 'Evil Creeps Ever Closer.', 'The Unholy Slots Claim Thee.', 'Darkness Demands Its Toll.', 'The Abyss Grows Richer.', 'Thy Coins Serve the Beast.', 'Sin Savors Thy Defeat.', 'The Hellfire Burns Brighter.', ]; const LOSS_SUBS = [ 'Redeem Yourself!', 'Pray Harder!', 'Repent and Spin Again.', 'Thy Soul Hangs in the Balance.', 'Only Grace Can Save Thee Now.', 'Kneel Before the Righteous Reels.', 'The Lord Awaits Thy Return.', 'Seek Salvation in the Next Spin.', 'Sin Grows Bold — Shall Ye Falter?', 'Heaven Watches and Weeps.', 'Rise, Faithful Servant!', 'Cast Out the Darkness!', 'Penance Must Be Paid.', 'Confess and Spin Anew.', 'Thy Redemption Awaits.', 'Do Not Yield to Evil!', "The Lord's Mercy Is Boundless.", 'Turn from the Wicked Path!', 'Fight Back with Faith!', 'Let the Light Guide Thy Hand.', 'A Holy Victory Is Within Reach.', 'Fortify Thy Spirit!', 'The Divine Is Patient with Thee.', 'Evil Shall Not Have the Last Word.', 'Courage, Faithful One!', 'One More Spin for Glory.', 'Grace Favors the Persistent.', 'The Pious Press On.', 'Thy Virtue Must Endure.', 'Arise and Challenge the Darkness!', ]; const WIN_MESSAGES = [ '✝ Blessed Are the Righteous! ✝', 'The Lord Smiles Upon Thee!', 'Holy Fortune Shines Bright!', "Heaven's Reward Is Thine!", 'Grace Has Found Thee!', 'Divine Providence Strikes!', 'The Faithful Are Rewarded!', 'Thy Virtue Bears Fruit!', "God's Glory Fills Thy Coffers!", 'Righteousness Prevails!', 'The Angels Rejoice!', 'Sacred Fortune Blesses Thee!', 'The Holy Reels Align!', 'Light Conquers the Darkness!', 'Heaven Opens Its Gates!', "The Lord's Hand Guides Thee!", 'Faith Moves Mountains and Reels!', 'Hallelujah — A Holy Match!', 'Thy Piety Is Rewarded!', 'The Divine Plan Unfolds!', 'Blessed Be This Spin!', 'The Righteous Shall Inherit!', 'Favor of the Almighty!', 'A Miracle Upon the Reels!', "Heaven's Treasury Opens!", 'Sin Retreats in Shame!', 'The Virtuous Triumph!', 'Glory to the Highest!', 'The Light Pierces the Darkness!', "God's Grace Rewards Thee!", ]; const WIN_SUBS = [ 'Press On, Faithful One!', 'The Lord Provides — Spin Again!', 'Grace Continues to Flow.', 'Heaven Cheers Thy Victory!', 'Righteousness Is Its Own Reward.', 'The Holy Spirit Guides Thy Hand.', 'May the Divine Light Shine On.', 'Sin Cowers Before Thy Faith.', 'The Righteous Walk in Fortune.', 'Let Virtue Lead Every Spin.', 'The Angels Record Thy Victory.', 'Keep the Faith — Keep Spinning!', 'Thy Pious Heart Is Noticed.', 'The Lord Multiplies Thy Blessings.', 'Walk Boldly in the Light.', 'Evil Cannot Touch the Faithful.', 'Another Spin for His Glory!', 'The Righteous Path Pays Off.', "Heaven's Ledger Grows in Thy Favor.", 'Consecrated Luck Is Upon Thee.', 'The Devout Are Never Forgotten.', 'Thy Coin Returns Manifold.', 'Spin Again — Heaven Is Watching.', "The Lord's Grace Has No Limit.", 'Persist and Multiply in Faith!', 'Sin Is Losing Ground.', 'The Holy Reels Await Once More.', 'Blessed Is the Hand That Spins.', 'Fortune Favors the Faithful.', 'The Kingdom Grows with Each Spin.', ]; // ───────────────────────────────────────────────────────────────────────────── export default class UIScene extends Phaser.Scene { constructor() { super({ key: 'UIScene' }); } create() { this._spinDisabled = false; this._gameOver = false; this._buildTopBar(); this._buildBottomBar(); this._buildSpinButton(); this._bindEvents(); this._updateFundDisplays(); } _buildTopBar() { const g = this.add.graphics(); // Background g.fillStyle(0x12082a, 1); g.fillRect(0, 0, 1600, TOP_BAR_HEIGHT); g.lineStyle(2, 0xffd700, 0.8); g.strokeRect(0, 0, 1600, TOP_BAR_HEIGHT); // Dividers g.lineStyle(1, 0xffd700, 0.3); g.beginPath(); g.moveTo(BOX_WIDTH, 8); g.lineTo(BOX_WIDTH, TOP_BAR_HEIGHT - 8); g.strokePath(); g.beginPath(); g.moveTo(BOX_WIDTH * 2, 8); g.lineTo(BOX_WIDTH * 2, TOP_BAR_HEIGHT - 8); g.strokePath(); // Box labels const labelStyle = { fontSize: '13px', fontFamily: 'Georgia, serif', color: '#c8a87e', alpha: 0.8 }; this.add.text(PLAYER_BOX_X, 14, 'YOUR FUNDS', labelStyle).setOrigin(0.5, 0); this.add.text(LORD_BOX_X, 14, 'THE LORD', labelStyle).setOrigin(0.5, 0); this.add.text(SIN_BOX_X, 14, 'SIN', labelStyle).setOrigin(0.5, 0); // Fund value texts const valueStyle = { fontSize: '28px', fontFamily: 'Georgia, serif', color: '#ffd700', stroke: '#2a0a4e', strokeThickness: 3 }; this.playerText = this.add.text(PLAYER_BOX_X, 55, '$1000', valueStyle).setOrigin(0.5, 0.5); this.lordText = this.add.text(LORD_BOX_X, 55, '$0', valueStyle).setOrigin(0.5, 0.5); this.sinText = this.add.text(SIN_BOX_X, 55, '$0', { ...valueStyle, color: '#ff4444' }).setOrigin(0.5, 0.5); } _buildBottomBar() { const g = this.add.graphics(); g.fillStyle(0x12082a, 1); g.fillRect(0, BOTTOM_BAR_Y, 1600, BOTTOM_BAR_HEIGHT); g.lineStyle(2, 0xffd700, 0.8); g.strokeRect(0, BOTTOM_BAR_Y, 1600, BOTTOM_BAR_HEIGHT); this.messageText = this.add.text(MSG_X, MSG_Y, 'Press SPIN or SPACE to begin', { fontSize: '22px', fontFamily: 'Georgia, serif', color: '#e8d8b0', align: 'center', wordWrap: { width: 1100 } }).setOrigin(0.5, 0.5); // Secondary sub-message (hidden by default) this.redeemText = this.add.text(MSG_X, SUB_Y, '', { fontSize: '16px', fontFamily: 'Georgia, serif', color: '#ff9944', align: 'center' }).setOrigin(0.5, 0.5).setAlpha(0); } _buildSpinButton() { const btnX = 1420; const btnY = BOTTOM_BAR_Y + BOTTOM_BAR_HEIGHT / 2; const btnW = 140; const btnH = 60; this.spinBtnGfx = this.add.graphics(); this._drawSpinBtn(false); this.spinBtnHitArea = this.add.zone(btnX, btnY, btnW, btnH) .setInteractive({ useHandCursor: true }); this.spinBtnLabel = this.add.text(btnX, btnY, 'SPIN', { fontSize: '26px', fontFamily: 'Georgia, serif', color: '#1a0a2e', fontStyle: 'bold' }).setOrigin(0.5, 0.5); this.spinBtnHitArea.on('pointerdown', () => { this.game.events.emit('spin'); }); this.spinBtnHitArea.on('pointerover', () => { if (!this._spinDisabled) this._drawSpinBtn(true); }); this.spinBtnHitArea.on('pointerout', () => { if (!this._spinDisabled) this._drawSpinBtn(false); }); this._btnX = btnX; this._btnY = btnY; this._btnW = btnW; this._btnH = btnH; } _drawSpinBtn(hover) { const btnX = 1420; const btnY = BOTTOM_BAR_Y + BOTTOM_BAR_HEIGHT / 2; const btnW = 140; const btnH = 60; this.spinBtnGfx.clear(); if (this._spinDisabled) { this.spinBtnGfx.fillStyle(0x3a3a3a, 1); this.spinBtnGfx.fillRoundedRect(btnX - btnW / 2, btnY - btnH / 2, btnW, btnH, 12); this.spinBtnGfx.lineStyle(3, 0x555555, 1); this.spinBtnGfx.strokeRoundedRect(btnX - btnW / 2, btnY - btnH / 2, btnW, btnH, 12); } else { this.spinBtnGfx.fillStyle(hover ? 0xffe066 : 0xffd700, 1); this.spinBtnGfx.fillRoundedRect(btnX - btnW / 2, btnY - btnH / 2, btnW, btnH, 12); this.spinBtnGfx.lineStyle(3, hover ? 0xffa500 : 0xc8a000, 1); this.spinBtnGfx.strokeRoundedRect(btnX - btnW / 2, btnY - btnH / 2, btnW, btnH, 12); } } _setSpinDisabled(disabled) { this._spinDisabled = disabled; this._drawSpinBtn(false); this.spinBtnLabel.setColor(disabled ? '#555555' : '#1a0a2e'); } _bindEvents() { this.game.events.on('win', () => { this._updateFundDisplays(); this._showWinMessage(pick(WIN_MESSAGES), pick(WIN_SUBS)); }, this); this.game.events.on('loss', () => { this._updateFundDisplays(); this._showLossMessage(pick(LOSS_MESSAGES), pick(LOSS_SUBS)); }, this); this.game.events.on('spinning-started', () => { this._setSpinDisabled(true); }, this); this.game.events.on('spin-complete', () => { if (!this._gameOver) this._setSpinDisabled(false); }, this); this.game.events.on('funds-updated', () => { this._updateFundDisplays(); }, this); this.game.events.on('insufficient-funds', () => { this._resetMessageTexts(); 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 }) => { this._gameOver = true; this._setSpinDisabled(true); this._resetMessageTexts(); 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); } // ── Animated message display ────────────────────────────────────────────── /** Restore both text objects to their neutral resting state. */ _resetMessageTexts() { this.tweens.killTweensOf(this.messageText); this.tweens.killTweensOf(this.redeemText); this.messageText.setPosition(MSG_X, MSG_Y).setAlpha(1).setScale(1); this.redeemText.setPosition(MSG_X, SUB_Y).setAlpha(0).setScale(1); } /** * Infernal Slam — main text crashes in from the left, sub-text rises from hell. */ _showLossMessage(main, sub) { this.tweens.killTweensOf(this.messageText); this.tweens.killTweensOf(this.redeemText); // Dark red flash across the bar const flash = this.add.rectangle(800, MSG_Y, 1600, BOTTOM_BAR_HEIGHT, 0x990000) .setAlpha(0.5); this.tweens.add({ targets: flash, alpha: 0, duration: 350, onComplete: () => flash.destroy(), }); // Main text — slam in from the left with overshoot this.messageText .setText(main) .setColor('#ff3333') .setPosition(-320, MSG_Y) .setAlpha(0) .setScale(1.15); this.tweens.add({ targets: this.messageText, x: MSG_X, alpha: 1, scale: 1, duration: 480, ease: 'Back.easeOut', easeParams: [4], onComplete: () => { // Sharp horizontal tremble on arrival this.tweens.add({ targets: this.messageText, x: { from: MSG_X - 6, to: MSG_X + 6 }, duration: 40, yoyo: true, repeat: 5, ease: 'Sine.easeInOut', }); }, }); // Sub-text — rises up from below the canvas this.redeemText .setText(sub) .setColor('#ff9944') .setPosition(MSG_X, 920) .setAlpha(0) .setScale(1); this.tweens.add({ targets: this.redeemText, y: SUB_Y, alpha: 1, duration: 420, delay: 380, ease: 'Cubic.easeOut', onComplete: () => { this.tweens.add({ targets: this.redeemText, alpha: { from: 1, to: 0.3 }, duration: 850, yoyo: true, repeat: -1, }); }, }); } /** * Divine Descent — main text drops from the heavens, sub-text blooms into being. */ _showWinMessage(main, sub) { this.tweens.killTweensOf(this.messageText); this.tweens.killTweensOf(this.redeemText); // Golden flash across the bar const flash = this.add.rectangle(800, MSG_Y, 1600, BOTTOM_BAR_HEIGHT, 0xffd700) .setAlpha(0.4); this.tweens.add({ targets: flash, alpha: 0, duration: 500, onComplete: () => flash.destroy(), }); // Floating ✦ sparkles scatter across the bar for (let i = 0; i < 7; i++) { const sp = this.add.text( Phaser.Math.Between(100, 1300), MSG_Y + Phaser.Math.Between(-20, 20), ['✦', '✝', '★', '✧'][Math.floor(Math.random() * 4)], { fontSize: `${Phaser.Math.Between(12, 26)}px`, color: '#ffd700' } ).setOrigin(0.5).setAlpha(0); this.tweens.add({ targets: sp, y: sp.y - Phaser.Math.Between(25, 55), alpha: { from: 1, to: 0 }, scale: 1.4, delay: i * 55, duration: 650, onComplete: () => sp.destroy(), }); } // Main text — descends from just above the bar, scales down to normal this.messageText .setText(main) .setColor('#ffd700') .setPosition(MSG_X, BOTTOM_BAR_Y - 28) .setAlpha(0) .setScale(1.35); this.tweens.add({ targets: this.messageText, y: MSG_Y, alpha: 1, scale: 1, duration: 460, ease: 'Back.easeOut', easeParams: [2.5], }); // Sub-text — blooms up from a tiny point at its resting position this.redeemText .setText(sub) .setColor('#c8a87e') .setPosition(MSG_X, SUB_Y) .setAlpha(0) .setScale(0.15); this.tweens.add({ targets: this.redeemText, scale: 1, alpha: 1, duration: 380, delay: 280, ease: 'Back.easeOut', easeParams: [3], }); } // ───────────────────────────────────────────────────────────────────────── _updateFundDisplays() { this.playerText.setText(`$${GameState.playerFunds}`); this.lordText.setText(`$${GameState.lordFunds}`); this.sinText.setText(`$${GameState.sinTotal}`); // Flash update on change [this.playerText, this.lordText, this.sinText].forEach(t => { this.tweens.add({ targets: t, scaleX: { from: 1.15, to: 1 }, scaleY: { from: 1.15, to: 1 }, duration: 200, ease: 'Bounce.easeOut' }); }); } // Called by GameScene to position animations toward the right box getPlayerBoxCenter() { return { x: PLAYER_BOX_X, y: BOX_CENTER_Y }; } getLordBoxCenter() { return { x: LORD_BOX_X, y: BOX_CENTER_Y }; } getSinBoxCenter() { return { x: SIN_BOX_X, y: BOX_CENTER_Y }; } }