564 lines
18 KiB
JavaScript
564 lines
18 KiB
JavaScript
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;
|
||
|
||
// Center x for bottom-bar messages; resting y for main line and sub line
|
||
const MSG_X = 700;
|
||
const MSG_Y = BOTTOM_BAR_Y + 38; // 828 — main message line
|
||
const SUB_Y = BOTTOM_BAR_Y + 78; // 868 — sub-message line
|
||
|
||
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._wordObjects = [];
|
||
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);
|
||
|
||
// Static text used for initial state, game-over, and insufficient-funds messages
|
||
this.messageText = this.add.text(MSG_X, MSG_Y, 'Press SPIN or SPACE to test your Righteousness against Temptation', {
|
||
fontSize: '26px',
|
||
fontFamily: 'Georgia, serif',
|
||
color: '#e8d8b0',
|
||
align: 'center',
|
||
wordWrap: { width: 1100 }
|
||
}).setOrigin(0.5, 0.5);
|
||
|
||
// Sub-message static text (hidden by default; used for special messages)
|
||
this.redeemText = this.add.text(MSG_X, SUB_Y, '$50 per spin. Tithe 40% of winnings to THE LORD. Losses go towards your SIN. Win if THE LORD reaches $2000. Lose if SIN reaches $2000 or you run out of money.', {
|
||
fontSize: '18px',
|
||
fontFamily: 'Georgia, serif',
|
||
color: '#ff9944',
|
||
align: 'center'
|
||
}).setOrigin(0.5, 0.5);
|
||
}
|
||
|
||
_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 ──────────────────────────────────────────────
|
||
|
||
/** Kill and destroy every per-word text object from the last animation. */
|
||
_clearWordObjects() {
|
||
this._wordObjects.forEach(t => {
|
||
this.tweens.killTweensOf(t);
|
||
if (t.active) t.destroy();
|
||
});
|
||
this._wordObjects = [];
|
||
}
|
||
|
||
/**
|
||
* Spawn a row of word texts that individually animate in from a large starting size.
|
||
*
|
||
* @param {string[]} words - Words to render.
|
||
* @param {number} centerX - Horizontal center of the row.
|
||
* @param {number} finalY - Vertical center of the row at rest.
|
||
* @param {number} fontSize - Final font size in px.
|
||
* @param {string} color - CSS colour string.
|
||
* @param {object} styleExtras - Extra Phaser text style properties.
|
||
* @param {number} fromScale - Starting scale (>1 = starts huge, <1 = starts tiny).
|
||
* @param {number} fromY - Y offset from finalY at start (negative = above).
|
||
* @param {number} msPerWord - Delay increment (ms) between successive words.
|
||
* @param {number} baseDelay - Global delay (ms) before the first word fires.
|
||
* @returns {Phaser.GameObjects.Text[]}
|
||
*/
|
||
_spawnRowOfWords(words, centerX, finalY, fontSize, color, styleExtras, fromScale, fromY, msPerWord, baseDelay) {
|
||
const GAP = Math.round(fontSize * 0.38);
|
||
|
||
// Create off-screen first so .width is readable for layout.
|
||
// setOrigin(0.5, 0.5) so that setPosition targets the word's centre,
|
||
// matching the centre-based layout math below and ensuring the scale
|
||
// animation shrinks/grows from the middle of each word.
|
||
const objs = words.map(w =>
|
||
this.add.text(-4000, -4000, w, {
|
||
fontSize: `${fontSize}px`,
|
||
fontFamily: 'Georgia, serif',
|
||
...styleExtras,
|
||
}).setOrigin(0.5, 0.5)
|
||
);
|
||
|
||
const totalW = objs.reduce((s, t) => s + t.width, 0) + GAP * Math.max(0, words.length - 1);
|
||
let x = centerX - totalW / 2;
|
||
|
||
objs.forEach((t, i) => {
|
||
const fx = x + t.width / 2;
|
||
x += t.width + GAP;
|
||
|
||
t.setColor(color)
|
||
.setPosition(fx, finalY + fromY)
|
||
.setScale(fromScale)
|
||
.setAlpha(0);
|
||
|
||
this.tweens.add({
|
||
targets: t,
|
||
scale: 1,
|
||
alpha: 1,
|
||
y: finalY,
|
||
duration: 280,
|
||
delay: baseDelay + msPerWord * i,
|
||
ease: 'Back.easeOut',
|
||
easeParams: [2.5],
|
||
});
|
||
});
|
||
|
||
return objs;
|
||
}
|
||
|
||
/** Restore the bottom bar to a clean neutral state (clears word objects). */
|
||
_resetMessageTexts() {
|
||
this._clearWordObjects();
|
||
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 — each word of the main line crashes down from above;
|
||
* sub-line words rise up from below, then pulse.
|
||
*/
|
||
_showLossMessage(main, sub) {
|
||
this._clearWordObjects();
|
||
this.tweens.killTweensOf(this.messageText);
|
||
this.tweens.killTweensOf(this.redeemText);
|
||
this.messageText.setText('').setAlpha(0);
|
||
this.redeemText.setAlpha(0);
|
||
|
||
// Dark red flash across the bar
|
||
const flash = this.add.rectangle(800, MSG_Y, 1600, BOTTOM_BAR_HEIGHT, 0x990000).setAlpha(0.55);
|
||
this.tweens.add({ targets: flash, alpha: 0, duration: 350, onComplete: () => flash.destroy() });
|
||
|
||
const mainWords = main.split(/\s+/).filter(Boolean);
|
||
const subWords = sub.split(/\s+/).filter(Boolean);
|
||
|
||
const msMain = 82;
|
||
const msSub = 75;
|
||
const subStart = mainWords.length * msMain + 180;
|
||
|
||
// Main: each word crashes down from above, starting at 4.5× scale
|
||
const mainObjs = this._spawnRowOfWords(
|
||
mainWords, MSG_X, MSG_Y,
|
||
34, '#ff3333', {},
|
||
4.5, -42,
|
||
msMain, 0
|
||
);
|
||
|
||
// Sub: each word rises from below, starting at 3× scale
|
||
const subObjs = this._spawnRowOfWords(
|
||
subWords, MSG_X, SUB_Y,
|
||
22, '#ff9944', {},
|
||
3.0, 28,
|
||
msSub, subStart
|
||
);
|
||
|
||
this._wordObjects = [...mainObjs, ...subObjs];
|
||
|
||
// After sub finishes, pulse all sub words indefinitely
|
||
const pulseAt = subStart + subWords.length * msSub + 300;
|
||
this.time.delayedCall(pulseAt, () => {
|
||
subObjs.forEach(t => {
|
||
if (!t.active) return;
|
||
this.tweens.add({
|
||
targets: t,
|
||
alpha: { from: 1, to: 0.3 },
|
||
duration: 850,
|
||
yoyo: true,
|
||
repeat: -1,
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Divine Descent — each word of the main line drops from above in gold;
|
||
* sub-line words bloom up from a tiny point.
|
||
*/
|
||
_showWinMessage(main, sub) {
|
||
this._clearWordObjects();
|
||
this.tweens.killTweensOf(this.messageText);
|
||
this.tweens.killTweensOf(this.redeemText);
|
||
this.messageText.setText('').setAlpha(0);
|
||
this.redeemText.setAlpha(0);
|
||
|
||
// 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(),
|
||
});
|
||
}
|
||
|
||
const mainWords = main.split(/\s+/).filter(Boolean);
|
||
const subWords = sub.split(/\s+/).filter(Boolean);
|
||
|
||
const msMain = 100;
|
||
const msSub = 88;
|
||
const subStart = mainWords.length * msMain + 200;
|
||
|
||
// Main: each word descends from above, starting at 3.8× scale, with golden stroke
|
||
const mainObjs = this._spawnRowOfWords(
|
||
mainWords, MSG_X, MSG_Y,
|
||
34, '#ffd700',
|
||
{ stroke: '#5a3000', strokeThickness: 2 },
|
||
3.8, -50,
|
||
msMain, 0
|
||
);
|
||
|
||
// Sub: each word blooms up from a tiny point (fromScale 0.2 → 1)
|
||
const subObjs = this._spawnRowOfWords(
|
||
subWords, MSG_X, SUB_Y,
|
||
22, '#c8a87e', {},
|
||
0.2, 0,
|
||
msSub, subStart
|
||
);
|
||
|
||
this._wordObjects = [...mainObjs, ...subObjs];
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────────
|
||
|
||
_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 }; }
|
||
}
|