feat(paigow): add animated hand-return and modal comparison sequence
- Animate player's split cards back to seat with split layout on submit - Replace showComparisons with step-by-step modal comparison overlay - Modal shows dealer cards, then each player's cards sequentially - Display high/low hand arrows (red=dealer wins, green=player wins) - Show verdict text (WIN/PUSH/LOSE) with sound effects and emotions - Animate cards and result text back to seat between players - Clear drag highlights when hiding hand-set panel - Swap chips/cards depth values in z-ordering constant
This commit is contained in:
parent
2f4a89fd3f
commit
e0b48a6285
|
|
@ -45,7 +45,7 @@ const CHIP_COLORS = { 5: 0xe05c5c, 25: 0x5cb85c, 50: 0x4a90d9, 100: 0x2c2c2c };
|
||||||
const CHIP_AMOUNTS = [5, 25, 50, 100];
|
const CHIP_AMOUNTS = [5, 25, 50, 100];
|
||||||
const RESULT_COLORS = { win: '#5cb85c', lose: '#e05c5c', push: '#8a94a6', foul: '#e05c5c' };
|
const RESULT_COLORS = { win: '#5cb85c', lose: '#e05c5c', push: '#8a94a6', foul: '#e05c5c' };
|
||||||
|
|
||||||
const D = { bg: -1, table: 0, cards: 10, chips: 20, ui: 30, panel: 25, modal: 50 };
|
const D = { bg: -1, table: 0, chips: 10, cards: 20, ui: 30, panel: 25, modal: 50 };
|
||||||
|
|
||||||
// ─── Hand-setting panel geometry ──────────────────────────────────────────────
|
// ─── Hand-setting panel geometry ──────────────────────────────────────────────
|
||||||
const PANEL_H = 390; // panel height
|
const PANEL_H = 390; // panel height
|
||||||
|
|
@ -463,6 +463,7 @@ export default class PaiGowPokerGame extends Phaser.Scene {
|
||||||
|
|
||||||
hideHandSetPanel() {
|
hideHandSetPanel() {
|
||||||
this.panelVisible = false;
|
this.panelVisible = false;
|
||||||
|
this._clearDragHighlights();
|
||||||
for (const o of this.setPanelGroup) o.setVisible(false);
|
for (const o of this.setPanelGroup) o.setVisible(false);
|
||||||
for (const c of this.setCardConts) c.destroy();
|
for (const c of this.setCardConts) c.destroy();
|
||||||
this.setCardConts = [];
|
this.setCardConts = [];
|
||||||
|
|
@ -758,8 +759,50 @@ export default class PaiGowPokerGame extends Phaser.Scene {
|
||||||
const highHand = this.humanHighSlots.map(i => hand[i]);
|
const highHand = this.humanHighSlots.map(i => hand[i]);
|
||||||
const lowHand = this.humanLowSlots.map(i => hand[i]);
|
const lowHand = this.humanLowSlots.map(i => hand[i]);
|
||||||
this.gs = applyHumanSplit(this.gs, highHand, lowHand);
|
this.gs = applyHumanSplit(this.gs, highHand, lowHand);
|
||||||
this.hideHandSetPanel();
|
this.animating = true;
|
||||||
this.startComparingPhase();
|
this._animateHandReturnToSeat(() => {
|
||||||
|
// Clear panel card containers before hideHandSetPanel destroys them
|
||||||
|
for (const c of this.setCardConts) c.destroy();
|
||||||
|
this.setCardConts = [];
|
||||||
|
this.hideHandSetPanel();
|
||||||
|
this.startComparingPhase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_animateHandReturnToSeat(onComplete) {
|
||||||
|
const pos = SEAT_POS[0];
|
||||||
|
// Split display mirrors renderSeat's comparing layout
|
||||||
|
const highCX = pos.x - 130;
|
||||||
|
const lowCX = pos.x + 130;
|
||||||
|
|
||||||
|
const splitTarget = (cx, count, slotIdx) => {
|
||||||
|
const totalW = (count - 1) * CARD_SPREAD_SPLIT;
|
||||||
|
return { x: cx - totalW / 2 + slotIdx * CARD_SPREAD_SPLIT, y: pos.y };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build ordered list: high hand cards first, then low hand
|
||||||
|
const moves = [];
|
||||||
|
this.humanHighSlots.forEach((cardIdx, slotIdx) => {
|
||||||
|
if (cardIdx !== null) moves.push({ cont: this.setCardConts[cardIdx], ...splitTarget(highCX, 5, slotIdx) });
|
||||||
|
});
|
||||||
|
this.humanLowSlots.forEach((cardIdx, slotIdx) => {
|
||||||
|
if (cardIdx !== null) moves.push({ cont: this.setCardConts[cardIdx], ...splitTarget(lowCX, 2, slotIdx) });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!moves.length) { onComplete(); return; }
|
||||||
|
|
||||||
|
let done = 0;
|
||||||
|
moves.forEach(({ cont, x, y }, i) => {
|
||||||
|
cont.setDepth(D.panel + 10);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: cont,
|
||||||
|
x, y,
|
||||||
|
duration: 320,
|
||||||
|
delay: i * 45,
|
||||||
|
ease: 'Power2.Out',
|
||||||
|
onComplete: () => { if (++done === moves.length) onComplete(); },
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Chip balance ──────────────────────────────────────────────────────────
|
// ── Chip balance ──────────────────────────────────────────────────────────
|
||||||
|
|
@ -1184,6 +1227,10 @@ export default class PaiGowPokerGame extends Phaser.Scene {
|
||||||
startComparingPhase() {
|
startComparingPhase() {
|
||||||
this.animating = true;
|
this.animating = true;
|
||||||
|
|
||||||
|
// Advance phase so renderSeat renders everyone (including the human) in split layout,
|
||||||
|
// preserving the animated split the player's cards just landed in.
|
||||||
|
this.gs = { ...this.gs, phase: 'comparing' };
|
||||||
|
|
||||||
// Briefly reveal all AI splits (1.5s), then reveal dealer
|
// Briefly reveal all AI splits (1.5s), then reveal dealer
|
||||||
this.renderAll(); // shows AI split layouts
|
this.renderAll(); // shows AI split layouts
|
||||||
|
|
||||||
|
|
@ -1193,15 +1240,340 @@ export default class PaiGowPokerGame extends Phaser.Scene {
|
||||||
// Resolve round
|
// Resolve round
|
||||||
this.gs = resolveRound(this.gs);
|
this.gs = resolveRound(this.gs);
|
||||||
this.renderAll();
|
this.renderAll();
|
||||||
// Show comparison results seat by seat
|
// Modal comparison sequence, then chip animations
|
||||||
this.showComparisons(() => {
|
this.startModalComparisons(() => {
|
||||||
this.animating = false;
|
this.animateAllChips(() => {
|
||||||
this.showNextRoundPrompt();
|
this.updateServerChips();
|
||||||
|
this.time.delayedCall(800, () => {
|
||||||
|
this.animating = false;
|
||||||
|
this.showNextRoundPrompt();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Modal comparison ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
startModalComparisons(onComplete) {
|
||||||
|
const MODAL_W = 1100;
|
||||||
|
const MODAL_H = 530;
|
||||||
|
const MODAL_X = CX - MODAL_W / 2; // 410
|
||||||
|
const MODAL_TOP = 150;
|
||||||
|
const DEALER_ROW_Y = 262;
|
||||||
|
const PLAYER_ROW_Y = 510;
|
||||||
|
const ARROW_Y = 393;
|
||||||
|
const HIGH_CX = 650;
|
||||||
|
const LOW_CX = 1270;
|
||||||
|
const RESULT_CENTER_Y = MODAL_TOP + Math.round(MODAL_H / 2); // 415
|
||||||
|
|
||||||
|
// slot x for card i in a n-card spread (CARD_SPREAD_SPLIT=10)
|
||||||
|
const slotX = (cx, count, i) => cx - ((count - 1) * CARD_SPREAD_SPLIT) / 2 + i * CARD_SPREAD_SPLIT;
|
||||||
|
|
||||||
|
this._modal = { MODAL_X, MODAL_TOP, MODAL_W, MODAL_H, DEALER_ROW_Y, PLAYER_ROW_Y,
|
||||||
|
ARROW_Y, HIGH_CX, LOW_CX, RESULT_CENTER_Y, slotX };
|
||||||
|
this._buildCompareModal();
|
||||||
|
|
||||||
|
// Fade in frame elements (arrows/rank text/result have their own reveal animations)
|
||||||
|
const frameObjs = this._modal.frameObjects;
|
||||||
|
for (const o of frameObjs) o.setAlpha(0);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: frameObjs, alpha: 1, duration: 250, ease: 'Linear',
|
||||||
|
onComplete: () => {
|
||||||
|
this._animateDealerIntoModal(() => {
|
||||||
|
const players = PLAY_ORDER
|
||||||
|
.map(seat => this.gs.players[seat])
|
||||||
|
.filter(p => p?.active);
|
||||||
|
const processNext = idx => {
|
||||||
|
if (idx >= players.length) {
|
||||||
|
this._destroyModal();
|
||||||
|
onComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isLast = idx === players.length - 1;
|
||||||
|
this._comparePlayerInModal(players[idx], isLast, () => processNext(idx + 1));
|
||||||
|
};
|
||||||
|
processNext(0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildCompareModal() {
|
||||||
|
const { MODAL_X, MODAL_TOP, MODAL_W, MODAL_H, DEALER_ROW_Y, PLAYER_ROW_Y,
|
||||||
|
HIGH_CX, LOW_CX, RESULT_CENTER_Y } = this._modal;
|
||||||
|
const LABEL_STYLE = { fontFamily: '"Julius Sans One"', fontSize: '18px', color: '#d4a017' };
|
||||||
|
const RANK_STYLE = { fontFamily: '"Julius Sans One"', fontSize: '16px', color: '#c0c0c0' };
|
||||||
|
const HEADER_STYLE = { fontFamily: '"Julius Sans One"', fontSize: '14px', color: '#6a7484' };
|
||||||
|
|
||||||
|
const bg = this.add.graphics().setDepth(D.modal);
|
||||||
|
bg.fillStyle(0x0a0f1a, 0.94);
|
||||||
|
bg.fillRoundedRect(MODAL_X, MODAL_TOP, MODAL_W, MODAL_H, 12);
|
||||||
|
bg.lineStyle(2, 0xd4a017, 0.8);
|
||||||
|
bg.strokeRoundedRect(MODAL_X, MODAL_TOP, MODAL_W, MODAL_H, 12);
|
||||||
|
|
||||||
|
const sep = this.add.graphics().setDepth(D.modal + 1);
|
||||||
|
sep.lineStyle(1, 0x3a4454, 0.6);
|
||||||
|
sep.lineBetween(CX, MODAL_TOP + 20, CX, MODAL_TOP + MODAL_H - 20);
|
||||||
|
|
||||||
|
const dealerLabel = this.add.text(CX, MODAL_TOP + 25, 'DEALER', LABEL_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
|
||||||
|
const hiLabel = this.add.text(HIGH_CX, MODAL_TOP + 28, 'HIGH HAND', HEADER_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
const loLabel = this.add.text(LOW_CX, MODAL_TOP + 28, 'LOW HAND', HEADER_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
|
||||||
|
const dealerRankHi = this.add.text(HIGH_CX, DEALER_ROW_Y + CARD_H / 2 + 10, '', RANK_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
const dealerRankLo = this.add.text(LOW_CX, DEALER_ROW_Y + CARD_H / 2 + 10, '', RANK_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
|
||||||
|
const playerLabel = this.add.text(CX, PLAYER_ROW_Y - CARD_H / 2 - 22, '', LABEL_STYLE)
|
||||||
|
.setOrigin(0.5, 1).setDepth(D.modal + 1);
|
||||||
|
const playerRankHi = this.add.text(HIGH_CX, PLAYER_ROW_Y + CARD_H / 2 + 10, '', RANK_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
const playerRankLo = this.add.text(LOW_CX, PLAYER_ROW_Y + CARD_H / 2 + 10, '', RANK_STYLE)
|
||||||
|
.setOrigin(0.5, 0).setDepth(D.modal + 1);
|
||||||
|
|
||||||
|
const arrowHiG = this.add.graphics().setDepth(D.modal + 2).setAlpha(0);
|
||||||
|
const arrowLoG = this.add.graphics().setDepth(D.modal + 2).setAlpha(0);
|
||||||
|
|
||||||
|
// resultTxt lives at modal center and flies to portrait after each player
|
||||||
|
const resultTxt = this.add.text(CX, RESULT_CENTER_Y, '', {
|
||||||
|
fontFamily: '"Julius Sans One"', fontSize: '72px', fontStyle: 'bold', color: '#f5d020',
|
||||||
|
}).setOrigin(0.5).setDepth(D.modal + 3).setAlpha(0).setScale(0);
|
||||||
|
|
||||||
|
Object.assign(this._modal, {
|
||||||
|
bg, sep, dealerLabel, hiLabel, loLabel,
|
||||||
|
dealerRankHi, dealerRankLo, playerLabel, playerRankHi, playerRankLo,
|
||||||
|
arrowHiG, arrowLoG, resultTxt,
|
||||||
|
// frameObjects: modal chrome that hides/shows between players
|
||||||
|
frameObjects: [bg, sep, dealerLabel, hiLabel, loLabel, dealerRankHi, dealerRankLo],
|
||||||
|
// dynamicObjects: per-player state reset each round
|
||||||
|
dynamicObjects: [playerLabel, playerRankHi, playerRankLo, arrowHiG, arrowLoG],
|
||||||
|
// allObjects: everything for final destroy
|
||||||
|
allObjects: [bg, sep, dealerLabel, hiLabel, loLabel,
|
||||||
|
dealerRankHi, dealerRankLo, playerLabel, playerRankHi, playerRankLo,
|
||||||
|
arrowHiG, arrowLoG, resultTxt],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_animateDealerIntoModal(onComplete) {
|
||||||
|
const { DEALER_ROW_Y, HIGH_CX, LOW_CX, slotX } = this._modal;
|
||||||
|
const cards = this.dealerCardGraphics;
|
||||||
|
const dealer = this.gs.dealer;
|
||||||
|
const hiCount = dealer.highHand.length; // 5
|
||||||
|
const loCount = dealer.lowHand.length; // 2
|
||||||
|
|
||||||
|
this._dealerCardOrigins = cards.map(c => ({ x: c.x, y: c.y }));
|
||||||
|
for (const c of cards) c.setDepth(D.modal + 5);
|
||||||
|
|
||||||
|
let done = 0;
|
||||||
|
const total = cards.length;
|
||||||
|
cards.forEach((card, i) => {
|
||||||
|
const isHi = i < hiCount;
|
||||||
|
const idx = isHi ? i : i - hiCount;
|
||||||
|
const tx = isHi ? slotX(HIGH_CX, hiCount, idx) : slotX(LOW_CX, loCount, idx);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: card, x: tx, y: DEALER_ROW_Y,
|
||||||
|
duration: 350, delay: i * 50, ease: 'Power2.Out',
|
||||||
|
onComplete: () => {
|
||||||
|
done++;
|
||||||
|
if (done === total) {
|
||||||
|
const { dealerRankHi, dealerRankLo } = this._modal;
|
||||||
|
dealerRankHi.setText(handName5(dealer.highEval));
|
||||||
|
dealerRankLo.setText(dealer.lowEval?.name ?? '');
|
||||||
|
onComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawModalArrow(g, x, y, dealerWins) {
|
||||||
|
g.clear();
|
||||||
|
if (dealerWins) {
|
||||||
|
g.fillStyle(0xff3a3a, 1); // bright red
|
||||||
|
g.fillTriangle(x, y - 18, x - 14, y + 14, x + 14, y + 14); // apex up
|
||||||
|
} else {
|
||||||
|
g.fillStyle(0x00e676, 1); // bright green
|
||||||
|
g.fillTriangle(x, y + 18, x - 14, y - 14, x + 14, y - 14); // apex down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_comparePlayerInModal(player, isLast, onComplete) {
|
||||||
|
const { PLAYER_ROW_Y, HIGH_CX, LOW_CX, slotX,
|
||||||
|
dealerRankHi, dealerRankLo, playerLabel, playerRankHi, playerRankLo,
|
||||||
|
arrowHiG, arrowLoG, resultTxt, RESULT_CENTER_Y } = this._modal;
|
||||||
|
const dealer = this.gs.dealer;
|
||||||
|
const cards = this.seatCardGraphics[player.seat] ?? [];
|
||||||
|
const hiCount = player.highHand.length; // 5
|
||||||
|
const loCount = player.lowHand.length; // 2
|
||||||
|
|
||||||
|
// Reset per-player UI
|
||||||
|
arrowHiG.setAlpha(0).setScale(1);
|
||||||
|
arrowLoG.setAlpha(0).setScale(1);
|
||||||
|
resultTxt.setAlpha(0).setScale(0).setText('').setPosition(CX, RESULT_CENTER_Y);
|
||||||
|
playerRankHi.setText('').setColor('#c0c0c0').setAlpha(1);
|
||||||
|
playerRankLo.setText('').setColor('#c0c0c0').setAlpha(1);
|
||||||
|
dealerRankHi.setColor('#c0c0c0');
|
||||||
|
dealerRankLo.setColor('#c0c0c0');
|
||||||
|
playerLabel.setText(player.name.toUpperCase()).setColor('#d4a017').setAlpha(1);
|
||||||
|
|
||||||
|
this._playerCardOrigins = cards.map(c => ({ x: c.x, y: c.y }));
|
||||||
|
for (const c of cards) c.setDepth(D.modal + 5);
|
||||||
|
|
||||||
|
let done = 0;
|
||||||
|
const total = cards.length;
|
||||||
|
if (total === 0) {
|
||||||
|
this._playerCardOrigins = [];
|
||||||
|
this._afterCardsIn(player, isLast, onComplete);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cards.forEach((card, i) => {
|
||||||
|
const isHi = i < hiCount;
|
||||||
|
const idx = isHi ? i : i - hiCount;
|
||||||
|
const tx = isHi ? slotX(HIGH_CX, hiCount, idx) : slotX(LOW_CX, loCount, idx);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: card, x: tx, y: PLAYER_ROW_Y,
|
||||||
|
duration: 280, delay: i * 45, ease: 'Power2.Out',
|
||||||
|
onComplete: () => {
|
||||||
|
done++;
|
||||||
|
if (done === total) {
|
||||||
|
playerRankHi.setText(handName5(player.highEval));
|
||||||
|
playerRankLo.setText(player.lowEval?.name ?? '');
|
||||||
|
this._afterCardsIn(player, isLast, onComplete);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_afterCardsIn(player, isLast, onComplete) {
|
||||||
|
const dealer = this.gs.dealer;
|
||||||
|
const { ARROW_Y, HIGH_CX, LOW_CX, RESULT_CENTER_Y, arrowHiG, arrowLoG, resultTxt,
|
||||||
|
dealerRankHi, dealerRankLo, playerRankHi, playerRankLo } = this._modal;
|
||||||
|
|
||||||
|
const hiWinDealer = player.isFoul || compare5Card(player.highEval, dealer.highEval) <= 0;
|
||||||
|
const loWinDealer = player.isFoul || compare2Card(player.lowEval, dealer.lowEval) <= 0;
|
||||||
|
|
||||||
|
// 500ms pause then HIGH arrow
|
||||||
|
this.time.delayedCall(500, () => {
|
||||||
|
this._drawModalArrow(arrowHiG, HIGH_CX, ARROW_Y, hiWinDealer);
|
||||||
|
arrowHiG.setAlpha(0).setScale(0);
|
||||||
|
this.tweens.add({ targets: arrowHiG, alpha: 1, scaleX: 1, scaleY: 1, duration: 200, ease: 'Back.easeOut' });
|
||||||
|
if (hiWinDealer) { dealerRankHi.setColor('#ffe033'); playerRankHi.setColor('#ff3a3a'); }
|
||||||
|
else { dealerRankHi.setColor('#ff3a3a'); playerRankHi.setColor('#ffe033'); }
|
||||||
|
|
||||||
|
// 1000ms pause then LOW arrow + verdict
|
||||||
|
this.time.delayedCall(1000, () => {
|
||||||
|
this._drawModalArrow(arrowLoG, LOW_CX, ARROW_Y, loWinDealer);
|
||||||
|
arrowLoG.setAlpha(0).setScale(0);
|
||||||
|
this.tweens.add({ targets: arrowLoG, alpha: 1, scaleX: 1, scaleY: 1, duration: 200, ease: 'Back.easeOut' });
|
||||||
|
if (loWinDealer) { dealerRankLo.setColor('#ffe033'); playerRankLo.setColor('#ff3a3a'); }
|
||||||
|
else { dealerRankLo.setColor('#ff3a3a'); playerRankLo.setColor('#ffe033'); }
|
||||||
|
|
||||||
|
// 500ms pause after low arrow before verdict appears
|
||||||
|
this.time.delayedCall(500, () => {
|
||||||
|
let verdict, color;
|
||||||
|
if (player.result === 'win') { verdict = 'WIN'; color = '#ffe033'; }
|
||||||
|
else if (player.result === 'push') { verdict = 'PUSH'; color = '#a0aabb'; }
|
||||||
|
else { verdict = 'LOSE'; color = '#ff3a3a'; }
|
||||||
|
|
||||||
|
resultTxt.setText(verdict).setColor(color).setAlpha(0).setScale(0)
|
||||||
|
.setPosition(CX, RESULT_CENTER_Y);
|
||||||
|
this.tweens.add({ targets: resultTxt, alpha: 1, scaleX: 1, scaleY: 1,
|
||||||
|
duration: 250, ease: 'Back.easeOut' });
|
||||||
|
const sfx = player.result === 'win' ? SFX.CASINO_WIN
|
||||||
|
: player.result === 'push' ? SFX.SQUASH
|
||||||
|
: SFX.CASINO_LOSE;
|
||||||
|
playSound(this, sfx);
|
||||||
|
if (player.seat > 0) {
|
||||||
|
const emotion = player.result === 'win' ? 'happy' : 'upset';
|
||||||
|
this.portraits[player.seat]?.playEmotion?.(emotion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 500ms (low arrow) + 1200ms (verdict visible) = 1700ms before fly-back
|
||||||
|
this.time.delayedCall(1700, () => {
|
||||||
|
const cards = this.seatCardGraphics[player.seat] ?? [];
|
||||||
|
const origins = this._playerCardOrigins;
|
||||||
|
const pos = SEAT_POS[player.seat];
|
||||||
|
|
||||||
|
// Fade out modal frame + dynamic UI + dealer cards together (resultTxt stays)
|
||||||
|
const fadeTargets = [
|
||||||
|
...this._modal.frameObjects,
|
||||||
|
...this._modal.dynamicObjects,
|
||||||
|
...this.dealerCardGraphics,
|
||||||
|
];
|
||||||
|
this.tweens.add({ targets: fadeTargets, alpha: 0, duration: 250, ease: 'Linear' });
|
||||||
|
|
||||||
|
// Fly player cards back to seat simultaneously
|
||||||
|
let done = 0;
|
||||||
|
const afterFly = () => {
|
||||||
|
// Once cards are home, fly resultTxt to player's portrait
|
||||||
|
const destX = pos.portraitX ?? pos.x;
|
||||||
|
const destY = pos.portraitY ?? pos.y;
|
||||||
|
this.tweens.add({
|
||||||
|
targets: resultTxt, x: destX, y: destY,
|
||||||
|
scaleX: 0.45, scaleY: 0.45,
|
||||||
|
duration: 1500, ease: 'Power2.In',
|
||||||
|
onComplete: () => {
|
||||||
|
this.tweens.add({ targets: resultTxt, alpha: 0, duration: 200, ease: 'Linear',
|
||||||
|
onComplete: () => {
|
||||||
|
if (isLast) {
|
||||||
|
// No need to reopen — _destroyModal follows in processNext
|
||||||
|
onComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reopen modal frame + dealer cards for next player
|
||||||
|
const reshow = [...this._modal.frameObjects, ...this.dealerCardGraphics];
|
||||||
|
for (const o of reshow) o.setAlpha(0);
|
||||||
|
this.tweens.add({ targets: reshow, alpha: 1, duration: 250, ease: 'Linear',
|
||||||
|
onComplete: () => onComplete() });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!cards.length) {
|
||||||
|
afterFly();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cards.forEach((card, i) => {
|
||||||
|
const { x, y } = origins[i] ?? { x: card.x, y: card.y };
|
||||||
|
this.tweens.add({
|
||||||
|
targets: card, x, y, duration: 260, delay: i * 30, ease: 'Power2.In',
|
||||||
|
onComplete: () => {
|
||||||
|
done++;
|
||||||
|
if (done === cards.length) {
|
||||||
|
for (const c of cards) c.setDepth(D.cards);
|
||||||
|
afterFly();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroyModal() {
|
||||||
|
if (!this._modal) return;
|
||||||
|
for (const o of this._modal.allObjects) { if (o?.destroy) o.destroy(); }
|
||||||
|
// Dealer card containers were faded out in the last player's close; destroy them so
|
||||||
|
// renderAll can recreate them cleanly for the next round.
|
||||||
|
for (const c of this.dealerCardGraphics) { if (c?.destroy) c.destroy(); }
|
||||||
|
this.dealerCardGraphics = [];
|
||||||
|
delete this._modal;
|
||||||
|
delete this._dealerCardOrigins;
|
||||||
|
delete this._playerCardOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
showComparisons(onComplete) {
|
showComparisons(onComplete) {
|
||||||
const activePlayers = this.gs.players.filter(p => p.active);
|
const activePlayers = this.gs.players.filter(p => p.active);
|
||||||
let done = 0;
|
let done = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue