feat(splendor): add noble claiming animations, token interactions, and UI polish
- Implement cinematic noble claiming animation with fireworks and sound, shrinking to panel thumbnails - Add hover ring highlight and gem sound effects for token selection zones - Render claimed noble thumbnails in player panels with dynamic positioning - Trigger happy emotion on player portraits when purchasing point cards - Relocate Leave button to bottom-right and remove unused playIntro flag - Exclude Splendor from card back selection in OpponentSelectScene
This commit is contained in:
parent
aa6fce0f6c
commit
25ccdb5da9
|
|
@ -24,6 +24,7 @@ const MARKET_X = 80; // left edge of decks
|
||||||
const CARDS_X = MARKET_X + DECK_W + 24; // first face-up card
|
const CARDS_X = MARKET_X + DECK_W + 24; // first face-up card
|
||||||
const TIER_Y = { 3: 168, 2: 168 + CH + 26, 1: 168 + (CH + 26) * 2 };
|
const TIER_Y = { 3: 168, 2: 168 + CH + 26, 1: 168 + (CH + 26) * 2 };
|
||||||
const NOBLE = 96, NOBLE_Y = 56;
|
const NOBLE = 96, NOBLE_Y = 56;
|
||||||
|
const NOBLE_THUMB = 34, NOBLE_THUMB_GAP = 4; // square noble thumbnails in player panels
|
||||||
|
|
||||||
const BANK_X = 830, BANK_Y0 = 176, BANK_STEP = 88, TOKEN_R = 36;
|
const BANK_X = 830, BANK_Y0 = 176, BANK_STEP = 88, TOKEN_R = 36;
|
||||||
|
|
||||||
|
|
@ -65,6 +66,8 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
this.animatingBuy = null; // { seat, cardId } suppresses thumbnail during animation
|
this.animatingBuy = null; // { seat, cardId } suppresses thumbnail during animation
|
||||||
this.boughtCards = {}; // seat → card[] — persists purchased cards for display
|
this.boughtCards = {}; // seat → card[] — persists purchased cards for display
|
||||||
this._thumbHoverTimer = null; // pending 500 ms intent timer for thumbnail preview
|
this._thumbHoverTimer = null; // pending 500 ms intent timer for thumbnail preview
|
||||||
|
this.animatingNoble = null; // { seat, nobleId } suppresses noble thumbnail during animation
|
||||||
|
this._pendingNoble = null; // { seat, noble, srcX, srcY } queued noble animation
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
|
@ -109,7 +112,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
this.portraits[idx] = createPlayerPortrait(this, px, py, portraitR, DEPTH.ui + 1, 'SplendorGame');
|
this.portraits[idx] = createPlayerPortrait(this, px, py, portraitR, DEPTH.ui + 1, 'SplendorGame');
|
||||||
} else {
|
} else {
|
||||||
const opp = this.opponents[idx - 1];
|
const opp = this.opponents[idx - 1];
|
||||||
this.portraits[idx] = createOpponentPortrait(this, opp, px, py, portraitR, DEPTH.ui + 1, { playIntro: false });
|
this.portraits[idx] = createOpponentPortrait(this, opp, px, py, portraitR, DEPTH.ui + 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +132,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
fontFamily: 'Righteous', fontSize: '40px', color: COLORS.textHex,
|
fontFamily: 'Righteous', fontSize: '40px', color: COLORS.textHex,
|
||||||
}).setOrigin(0.5, 0).setDepth(DEPTH.ui);
|
}).setOrigin(0.5, 0).setDepth(DEPTH.ui);
|
||||||
|
|
||||||
new Button(this, GAME_WIDTH - 96, 40, 'Leave', () => this.scene.start('GameMenu'),
|
new Button(this, GAME_WIDTH - 96, GAME_HEIGHT - 36, 'Leave', () => this.scene.start('GameMenu'),
|
||||||
{ variant: 'ghost', width: 140, height: 42, fontSize: 18 }).setDepth(DEPTH.ui);
|
{ variant: 'ghost', width: 140, height: 42, fontSize: 18 }).setDepth(DEPTH.ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,6 +183,42 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wraps applyAction and detects if a noble was earned this turn.
|
||||||
|
_applyAction(action) {
|
||||||
|
const prevNobles = this.gs.nobles.slice();
|
||||||
|
const prevCounts = this.gs.players.map((p) => p.nobles.length);
|
||||||
|
const next = applyAction(this.gs, action);
|
||||||
|
this.gs = next;
|
||||||
|
for (let seat = 0; seat < this.gs.players.length; seat++) {
|
||||||
|
if (this.gs.players[seat].nobles.length > prevCounts[seat]) {
|
||||||
|
const claimed = prevNobles.find((pn) => !this.gs.nobles.some((n) => n.id === pn.id));
|
||||||
|
if (claimed) {
|
||||||
|
const si = prevNobles.indexOf(claimed);
|
||||||
|
this._pendingNoble = {
|
||||||
|
seat,
|
||||||
|
noble: claimed,
|
||||||
|
srcX: MARKET_X + si * (NOBLE + 14) + NOBLE / 2,
|
||||||
|
srcY: NOBLE_Y + NOBLE / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nobleThumbPos(seat, nobleIdx) {
|
||||||
|
const n = this.gs.players.length;
|
||||||
|
const gap = 16;
|
||||||
|
const h = Math.min(220, Math.floor((GAME_HEIGHT - 90 - gap * (n - 1)) / n));
|
||||||
|
const panelY = 76 + seat * (h + gap);
|
||||||
|
const totalCY = panelY + 70 + 57;
|
||||||
|
const nobleCount = this.gs.players[seat].nobles.length;
|
||||||
|
const groupW = nobleCount * NOBLE_THUMB + Math.max(0, nobleCount - 1) * NOBLE_THUMB_GAP;
|
||||||
|
const tx = PANEL_X + PANEL_W - 16 - groupW + nobleIdx * (NOBLE_THUMB + NOBLE_THUMB_GAP);
|
||||||
|
const ty = totalCY + 14 + 3;
|
||||||
|
return { cx: tx + NOBLE_THUMB / 2, cy: ty + NOBLE_THUMB / 2 };
|
||||||
|
}
|
||||||
|
|
||||||
boughtCardThumbPos(seat, idx) {
|
boughtCardThumbPos(seat, idx) {
|
||||||
const n = this.gs.players.length;
|
const n = this.gs.players.length;
|
||||||
const gap = 16;
|
const gap = 16;
|
||||||
|
|
@ -435,8 +474,13 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
}).setOrigin(1, 0).setDepth(DEPTH.card + 3));
|
}).setOrigin(1, 0).setDepth(DEPTH.card + 3));
|
||||||
}
|
}
|
||||||
if (this.isHumanTurn() && color !== GOLD && n > 0) {
|
if (this.isHumanTurn() && color !== GOLD && n > 0) {
|
||||||
|
const ring = this.reg(this.add.graphics().setDepth(DEPTH.card + 3).setAlpha(0));
|
||||||
|
ring.lineStyle(4, 0xffffff, 1).strokeCircle(cx, cy, TOKEN_R + 7);
|
||||||
|
|
||||||
const zone = this.reg(this.add.zone(cx, cy, TOKEN_R * 2, TOKEN_R * 2)
|
const zone = this.reg(this.add.zone(cx, cy, TOKEN_R * 2, TOKEN_R * 2)
|
||||||
.setInteractive({ useHandCursor: true }).setDepth(DEPTH.card + 3));
|
.setInteractive({ useHandCursor: true }).setDepth(DEPTH.card + 4));
|
||||||
|
zone.on('pointerover', () => ring.setAlpha(1));
|
||||||
|
zone.on('pointerout', () => ring.setAlpha(0));
|
||||||
zone.on('pointerdown', () => this.onTokenClick(color));
|
zone.on('pointerdown', () => this.onTokenClick(color));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -502,7 +546,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
// divider + total row (card bonuses + tokens)
|
// divider + total row (card bonuses + tokens)
|
||||||
const ccy = y + 70;
|
const ccy = y + 70;
|
||||||
const divY = ccy + 38;
|
const divY = ccy + 38;
|
||||||
const totalCY = ccy + 53;
|
const totalCY = ccy + 57;
|
||||||
const divG = this.reg(this.add.graphics().setDepth(DEPTH.ui + 1));
|
const divG = this.reg(this.add.graphics().setDepth(DEPTH.ui + 1));
|
||||||
divG.lineStyle(1, COLORS.accent, 0.35)
|
divG.lineStyle(1, COLORS.accent, 0.35)
|
||||||
.lineBetween(gemStartX - totalR - 2, divY, gemStartX + (order.length - 1) * gemSpacing + totalR + 2, divY);
|
.lineBetween(gemStartX - totalR - 2, divY, gemStartX + (order.length - 1) * gemSpacing + totalR + 2, divY);
|
||||||
|
|
@ -518,6 +562,29 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
}).setOrigin(0.5).setDepth(DEPTH.ui + 2));
|
}).setOrigin(0.5).setDepth(DEPTH.ui + 2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// noble thumbnails — right-aligned below the total circles row
|
||||||
|
if (p.nobles.length > 0) {
|
||||||
|
const nobleCount = p.nobles.length;
|
||||||
|
const nobleGroupW = nobleCount * NOBLE_THUMB + (nobleCount - 1) * NOBLE_THUMB_GAP;
|
||||||
|
const nobleGroupLeft = x + w - 16 - nobleGroupW;
|
||||||
|
const nTY = totalCY + 14 + 3;
|
||||||
|
p.nobles.forEach((noble, ni) => {
|
||||||
|
if (this.animatingNoble?.seat === idx && this.animatingNoble?.nobleId === noble.id) return;
|
||||||
|
const nTX = nobleGroupLeft + ni * (NOBLE_THUMB + NOBLE_THUMB_GAP);
|
||||||
|
const ng = this.reg(this.add.graphics().setDepth(DEPTH.ui + 1));
|
||||||
|
ng.fillStyle(0x000000, 0.32).fillRoundedRect(nTX + 2, nTY + 2, NOBLE_THUMB, NOBLE_THUMB, 4);
|
||||||
|
ng.fillStyle(0x2b2620, 1).fillRoundedRect(nTX, nTY, NOBLE_THUMB, NOBLE_THUMB, 4);
|
||||||
|
if (this.hasArt) {
|
||||||
|
this.regMaskedImg(nTX, nTY, NOBLE_THUMB, NOBLE_THUMB, 'splendor-cards', nobleFrame(noble), DEPTH.ui + 2, 4);
|
||||||
|
}
|
||||||
|
this.reg(this.add.graphics().setDepth(DEPTH.ui + 3))
|
||||||
|
.lineStyle(1.5, COLORS.accent, 0.85).strokeRoundedRect(nTX, nTY, NOBLE_THUMB, NOBLE_THUMB, 4);
|
||||||
|
this.reg(this.add.text(nTX + 3, nTY + 1, '3', {
|
||||||
|
fontFamily: 'Righteous', fontSize: '10px', color: COLORS.goldHex,
|
||||||
|
}).setDepth(DEPTH.ui + 4));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// bought card thumbnails (right of portrait, left of gems, wrapping into rows)
|
// bought card thumbnails (right of portrait, left of gems, wrapping into rows)
|
||||||
(this.boughtCards[idx] ?? []).forEach((bc, bi) => {
|
(this.boughtCards[idx] ?? []).forEach((bc, bi) => {
|
||||||
if (this.animatingBuy?.seat === idx && this.animatingBuy?.cardId === bc.id) return;
|
if (this.animatingBuy?.seat === idx && this.animatingBuy?.cardId === bc.id) return;
|
||||||
|
|
@ -714,7 +781,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
: action.colors;
|
: action.colors;
|
||||||
|
|
||||||
const preTokens = { ...this.gs.players[seat].tokens };
|
const preTokens = { ...this.gs.players[seat].tokens };
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard') {
|
if (this.gs.phase === 'discard') {
|
||||||
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
||||||
|
|
@ -740,6 +807,12 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
duration: 380,
|
duration: 380,
|
||||||
delay: i * 90,
|
delay: i * 90,
|
||||||
ease: 'Cubic.easeOut',
|
ease: 'Cubic.easeOut',
|
||||||
|
onStart: () => {
|
||||||
|
try {
|
||||||
|
const a = new Audio(`/assets/fx/gem-0${(i % 2) + 1}.mp3`);
|
||||||
|
a.volume = 0.7; a.play();
|
||||||
|
} catch { /* */ }
|
||||||
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
gem.destroy();
|
gem.destroy();
|
||||||
if (--pending === 0) {
|
if (--pending === 0) {
|
||||||
|
|
@ -767,10 +840,80 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
displayWidth: 32, displayHeight: 32,
|
displayWidth: 32, displayHeight: 32,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
ease: 'Cubic.easeOut',
|
ease: 'Cubic.easeOut',
|
||||||
|
onStart: () => {
|
||||||
|
try {
|
||||||
|
const a = new Audio(`/assets/fx/gem-0${(destIdx % 2) + 1}.mp3`);
|
||||||
|
a.volume = 0.7; a.play();
|
||||||
|
} catch { /* */ }
|
||||||
|
},
|
||||||
onComplete: () => gem.destroy(),
|
onComplete: () => gem.destroy(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── noble animation ──────────────────────────────────────────────────────────
|
||||||
|
animNoble(seat, noble, srcX, srcY, onDone) {
|
||||||
|
const DISP = 270;
|
||||||
|
const cx = GAME_WIDTH / 2, cy = GAME_HEIGHT / 2;
|
||||||
|
|
||||||
|
for (const p of this.portraits) p?.hide();
|
||||||
|
|
||||||
|
const overlay = this.add.rectangle(cx, cy, GAME_WIDTH, GAME_HEIGHT, 0x000000)
|
||||||
|
.setAlpha(0).setDepth(DEPTH.popup);
|
||||||
|
this.tweens.add({ targets: overlay, alpha: 0.52, duration: 280 });
|
||||||
|
|
||||||
|
const container = this.add.container(srcX, srcY).setDepth(DEPTH.popup + 1);
|
||||||
|
const bg = this.add.graphics();
|
||||||
|
bg.fillStyle(0x2b2620, 1).fillRoundedRect(-NOBLE / 2, -NOBLE / 2, NOBLE, NOBLE, 10);
|
||||||
|
container.add(bg);
|
||||||
|
if (this.hasArt) {
|
||||||
|
container.add(
|
||||||
|
this.add.image(0, 0, 'splendor-cards', nobleFrame(noble)).setDisplaySize(NOBLE, NOBLE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const border = this.add.graphics();
|
||||||
|
border.lineStyle(2, COLORS.accent, 0.9).strokeRoundedRect(-NOBLE / 2, -NOBLE / 2, NOBLE, NOBLE, 10);
|
||||||
|
container.add(border);
|
||||||
|
container.add(this.add.text(-NOBLE / 2 + 8, -NOBLE / 2 + 4, '3', {
|
||||||
|
fontFamily: 'Righteous', fontSize: '22px', color: COLORS.goldHex,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: container,
|
||||||
|
x: cx, y: cy,
|
||||||
|
scale: DISP / NOBLE,
|
||||||
|
duration: 420,
|
||||||
|
ease: 'Cubic.easeOut',
|
||||||
|
onComplete: () => {
|
||||||
|
this._playReserveFireworks(cx, cy, 'white');
|
||||||
|
try { const a = new Audio('/assets/fx/firework.mp3'); a.volume = 0.7; a.play(); } catch { /* */ }
|
||||||
|
|
||||||
|
this.time.delayedCall(1200, () => {
|
||||||
|
const ni = this.gs.players[seat].nobles.findIndex((n) => n.id === noble.id);
|
||||||
|
const { cx: thumbCX, cy: thumbCY } = this.nobleThumbPos(seat, ni >= 0 ? ni : 0);
|
||||||
|
|
||||||
|
this.tweens.add({ targets: overlay, alpha: 0, duration: 300,
|
||||||
|
onComplete: () => { try { overlay.destroy(); } catch { /* */ } } });
|
||||||
|
try { const a = new Audio('/assets/fx/ui-attach.mp3'); a.volume = 0.8; a.play(); } catch { /* */ }
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: container,
|
||||||
|
x: thumbCX, y: thumbCY,
|
||||||
|
scale: NOBLE_THUMB / NOBLE,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 360,
|
||||||
|
ease: 'Cubic.easeIn',
|
||||||
|
onComplete: () => {
|
||||||
|
for (const p of this.portraits) p?.show();
|
||||||
|
container.removeAll(true);
|
||||||
|
container.destroy();
|
||||||
|
onDone();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Builds a card container centred at (x,y) in world space with full overlay art.
|
// Builds a card container centred at (x,y) in world space with full overlay art.
|
||||||
// All draw coords are in container-local space (0,0 = card centre).
|
// All draw coords are in container-local space (0,0 = card centre).
|
||||||
_buildCardContainer(x, y, card) {
|
_buildCardContainer(x, y, card) {
|
||||||
|
|
@ -1043,7 +1186,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
|
|
||||||
// Save token state before and after applying the action
|
// Save token state before and after applying the action
|
||||||
const preTokens = { ...this.gs.players[this.humanSeat].tokens };
|
const preTokens = { ...this.gs.players[this.humanSeat].tokens };
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
const postTokens = this.gs.players[this.humanSeat].tokens;
|
const postTokens = this.gs.players[this.humanSeat].tokens;
|
||||||
|
|
||||||
|
|
@ -1073,6 +1216,12 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
duration: 380,
|
duration: 380,
|
||||||
delay: i * 90,
|
delay: i * 90,
|
||||||
ease: 'Cubic.easeOut',
|
ease: 'Cubic.easeOut',
|
||||||
|
onStart: () => {
|
||||||
|
try {
|
||||||
|
const a = new Audio(`/assets/fx/gem-0${(i % 2) + 1}.mp3`);
|
||||||
|
a.volume = 0.7; a.play();
|
||||||
|
} catch { /* */ }
|
||||||
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
gem.destroy();
|
gem.destroy();
|
||||||
if (--pending === 0) {
|
if (--pending === 0) {
|
||||||
|
|
@ -1139,7 +1288,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
srcX = PANEL_X + 150 + ri * 116 + 52; srcY = panelY + h - 40 + 15;
|
srcX = PANEL_X + 150 + ri * 116 + 52; srcY = panelY + h - 40 + 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard' && this.gs.current === seat) { this.render(); return; }
|
if (this.gs.phase === 'discard' && this.gs.current === seat) { this.render(); return; }
|
||||||
|
|
||||||
|
|
@ -1165,7 +1314,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
const srcX = srcPos?.x ?? GAME_WIDTH / 2;
|
const srcX = srcPos?.x ?? GAME_WIDTH / 2;
|
||||||
const srcY = srcPos?.y ?? GAME_HEIGHT / 2;
|
const srcY = srcPos?.y ?? GAME_HEIGHT / 2;
|
||||||
|
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard' && this.gs.current === this.humanSeat) {
|
if (this.gs.phase === 'discard' && this.gs.current === this.humanSeat) {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -1187,7 +1336,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard' && this.gs.current === this.humanSeat) {
|
if (this.gs.phase === 'discard' && this.gs.current === this.humanSeat) {
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -1199,6 +1348,20 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
|
|
||||||
// ── turn loop ───────────────────────────────────────────────────────────────
|
// ── turn loop ───────────────────────────────────────────────────────────────
|
||||||
advance() {
|
advance() {
|
||||||
|
if (this._pendingNoble) {
|
||||||
|
const { seat, noble, srcX, srcY } = this._pendingNoble;
|
||||||
|
this._pendingNoble = null;
|
||||||
|
this.animatingNoble = { seat, nobleId: noble.id };
|
||||||
|
this.busy = true;
|
||||||
|
this.render();
|
||||||
|
this.animNoble(seat, noble, srcX, srcY, () => {
|
||||||
|
this.animatingNoble = null;
|
||||||
|
this.busy = false;
|
||||||
|
this.render();
|
||||||
|
this.advance();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isGameOver(this.gs)) { this.render(); this.onGameOver(); return; }
|
if (isGameOver(this.gs)) { this.render(); this.onGameOver(); return; }
|
||||||
if (this.gs.phase === 'discard') {
|
if (this.gs.phase === 'discard') {
|
||||||
// Only the human ever parks here interactively; AI auto-discards inline.
|
// Only the human ever parks here interactively; AI auto-discards inline.
|
||||||
|
|
@ -1235,7 +1398,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
const srcX = srcPos?.x ?? GAME_WIDTH / 2;
|
const srcX = srcPos?.x ?? GAME_WIDTH / 2;
|
||||||
const srcY = srcPos?.y ?? GAME_HEIGHT / 2;
|
const srcY = srcPos?.y ?? GAME_HEIGHT / 2;
|
||||||
|
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard') {
|
if (this.gs.phase === 'discard') {
|
||||||
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
||||||
|
|
@ -1269,12 +1432,13 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
srcX = PANEL_X + 150 + ri * 116 + 52; srcY = panelY + h - 40 + 15;
|
srcX = PANEL_X + 150 + ri * 116 + 52; srcY = panelY + h - 40 + 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gs = applyAction(this.gs, action);
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard') {
|
if (this.gs.phase === 'discard') {
|
||||||
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (card.points > 0) this.portraits[seat]?.playEmotion('happy');
|
||||||
if (!this.boughtCards[seat]) this.boughtCards[seat] = [];
|
if (!this.boughtCards[seat]) this.boughtCards[seat] = [];
|
||||||
this.boughtCards[seat].push(card);
|
this.boughtCards[seat].push(card);
|
||||||
this.animatingBuy = { seat, cardId: card.id };
|
this.animatingBuy = { seat, cardId: card.id };
|
||||||
|
|
@ -1289,7 +1453,11 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gs = applyAction(this.gs, action);
|
if (action.type === 'buy') {
|
||||||
|
const card = this.findCard(action.cardId);
|
||||||
|
if (card?.points > 0) this.portraits[seat]?.playEmotion('happy');
|
||||||
|
}
|
||||||
|
this._applyAction(action);
|
||||||
this.playActionSound(action);
|
this.playActionSound(action);
|
||||||
if (this.gs.phase === 'discard') {
|
if (this.gs.phase === 'discard') {
|
||||||
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
this.gs = applyDiscard(this.gs, defaultDiscards(this.gs));
|
||||||
|
|
@ -1304,7 +1472,7 @@ export default class SplendorGame extends Phaser.Scene {
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
if (action.type === 'buy') playSound(this, SFX.PURCHASE);
|
if (action.type === 'buy') playSound(this, SFX.PURCHASE);
|
||||||
else if (action.type === 'reserve' || action.type === 'reserveDeck') playSound(this, SFX.CARD_DEAL);
|
else if (action.type === 'reserve' || action.type === 'reserveDeck') playSound(this, SFX.CARD_DEAL);
|
||||||
else if (action.type === 'take2' || action.type === 'take3') playSound(this, SFX.COINS);
|
// gem sounds played per-gem in animAITake / confirmTake
|
||||||
}
|
}
|
||||||
|
|
||||||
showTurnBanner(text) {
|
showTurnBanner(text) {
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
'selectedPlayfield', 'playfieldTiles', (pf) => this.selectPlayfield(pf));
|
'selectedPlayfield', 'playfieldTiles', (pf) => this.selectPlayfield(pf));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.gameDef.cardGame) {
|
if (this.gameDef.cardGame && this.gameDef.slug !== 'splendor') {
|
||||||
this.buildOptionSection('Card Back', 798, this.cache.json.get('card-backs')?.cardBacks ?? [],
|
this.buildOptionSection('Card Back', 798, this.cache.json.get('card-backs')?.cardBacks ?? [],
|
||||||
'selectedCardBack', 'cardBackTiles', (cb) => this.selectCardBack(cb),
|
'selectedCardBack', 'cardBackTiles', (cb) => this.selectCardBack(cb),
|
||||||
CARD_TILE_W, CARD_TILE_H, CARD_TILE_GAP);
|
CARD_TILE_W, CARD_TILE_H, CARD_TILE_GAP);
|
||||||
|
|
@ -150,7 +150,7 @@ export default class OpponentSelectScene extends Phaser.Scene {
|
||||||
const pfd = this.cache.json.get('playfields') ?? {};
|
const pfd = this.cache.json.get('playfields') ?? {};
|
||||||
this.applyDefault('playfields', pfd.default, 'selectedPlayfield', 'playfieldTiles');
|
this.applyDefault('playfields', pfd.default, 'selectedPlayfield', 'playfieldTiles');
|
||||||
}
|
}
|
||||||
if (this.gameDef.cardGame) {
|
if (this.gameDef.cardGame && this.gameDef.slug !== 'splendor') {
|
||||||
const cardBacks = this.cache.json.get('card-backs')?.cardBacks ?? [];
|
const cardBacks = this.cache.json.get('card-backs')?.cardBacks ?? [];
|
||||||
if (cardBacks.length > 0) {
|
if (cardBacks.length > 0) {
|
||||||
const randomCb = cardBacks[Math.floor(Math.random() * cardBacks.length)];
|
const randomCb = cardBacks[Math.floor(Math.random() * cardBacks.length)];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue