Compare commits
3 Commits
41466e14f3
...
33bbeff79b
| Author | SHA1 | Date |
|---|---|---|
|
|
33bbeff79b | |
|
|
ca38548700 | |
|
|
0536a261f6 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 MiB After Width: | Height: | Size: 4.5 MiB |
|
|
@ -100,10 +100,26 @@ export default class DominionGame extends Phaser.Scene {
|
|||
this._suppressTurnUi = false;
|
||||
this._handFxGraphics = [];
|
||||
this._handFxTweens = [];
|
||||
this._supplyFxEmitters = [];
|
||||
this._deckAnimDisplay = null;
|
||||
this._discardAnimDisplay = null;
|
||||
this._liveDeckPile = null;
|
||||
this._liveDeckBadge = null;
|
||||
this._liveDeckCountText = null;
|
||||
this._liveDiscardPile = null;
|
||||
this._liveDiscardBadge = null;
|
||||
this._liveDiscardCountText = null;
|
||||
}
|
||||
|
||||
create() {
|
||||
new MusicPlayer(this, this.cache.json.get('music').tracks);
|
||||
if (!this.textures.exists('dominion-sparkle')) {
|
||||
const g = this.add.graphics();
|
||||
g.fillStyle(0xffffff, 1);
|
||||
g.fillCircle(3, 3, 3);
|
||||
g.generateTexture('dominion-sparkle', 6, 6);
|
||||
g.destroy();
|
||||
}
|
||||
this.buildBackground();
|
||||
this.buildPortraits();
|
||||
this.buildTurnArrow();
|
||||
|
|
@ -145,6 +161,8 @@ export default class DominionGame extends Phaser.Scene {
|
|||
};
|
||||
this.gs = preDeal;
|
||||
this.render();
|
||||
this._deckAnimDisplay = preDeal.players[0].deck.length; // 10 before deal
|
||||
this._discardAnimDisplay = 0;
|
||||
this._animDrawCards(p0.hand, initialState);
|
||||
}
|
||||
|
||||
|
|
@ -421,6 +439,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
|
||||
render() {
|
||||
this._clearHandFx();
|
||||
this._clearSupplyFx();
|
||||
if (this.hoverTimer) { this.hoverTimer.remove(); this.hoverTimer = null; }
|
||||
this.hideHover();
|
||||
this.dynamicLayer?.destroy(true);
|
||||
|
|
@ -500,6 +519,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
hit.setInteractive({ useHandCursor: true });
|
||||
hit.on('pointerup', () => this.humanBuy(id));
|
||||
this.markBuyable(face);
|
||||
this._buildSupplyFxItem(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -510,6 +530,27 @@ export default class DominionGame extends Phaser.Scene {
|
|||
face.addAt(glow, 0);
|
||||
}
|
||||
|
||||
_clearSupplyFx() {
|
||||
this._supplyFxEmitters.forEach(e => e.destroy());
|
||||
this._supplyFxEmitters = [];
|
||||
}
|
||||
|
||||
_buildSupplyFxItem(x, y) {
|
||||
const emitter = this.add.particles(x, y, 'dominion-sparkle', {
|
||||
x: { min: -SUPPLY_W / 2 + 4, max: SUPPLY_W / 2 - 4 },
|
||||
y: -SUPPLY_H / 2,
|
||||
speedX: { min: -18, max: 18 },
|
||||
speedY: { min: -70, max: -30 },
|
||||
alpha: { start: 0.9, end: 0 },
|
||||
scale: { start: 0.8, end: 0.15 },
|
||||
lifespan: 900,
|
||||
frequency: 100,
|
||||
tint: [0xffffff, 0xffeebb, 0xd4a017, 0xc8a84b],
|
||||
depth: D.supply + 8,
|
||||
});
|
||||
this._supplyFxEmitters.push(emitter);
|
||||
}
|
||||
|
||||
renderInPlay() {
|
||||
const gs = this.gs;
|
||||
const p = gs.players[gs.turn];
|
||||
|
|
@ -652,39 +693,52 @@ export default class DominionGame extends Phaser.Scene {
|
|||
|
||||
renderCounts() {
|
||||
const gs = this.gs;
|
||||
const displayDeck = this._deckAnimDisplay ?? gs.players[0].deck.length;
|
||||
const displayDiscard = this._discardAnimDisplay ?? gs.players[0].discard.length;
|
||||
|
||||
// Human deck — face-down card pile with count badge
|
||||
const deckPile = this.buildCardFace(HAND_W, HAND_H, null, { faceDown: true });
|
||||
deckPile.setPosition(DECK_PILE_X, DECK_PILE_Y).setDepth(D.hand);
|
||||
deckPile.setVisible(displayDeck > 0);
|
||||
this.dynamicLayer.add(deckPile);
|
||||
const deckCount = gs.players[0].deck.length;
|
||||
const dcBg = this.add.graphics();
|
||||
dcBg.fillStyle(0x000000, 0.72);
|
||||
dcBg.fillCircle(DECK_PILE_X, DECK_PILE_Y, 22);
|
||||
dcBg.lineStyle(2, COLORS.accent, 1);
|
||||
dcBg.strokeCircle(DECK_PILE_X, DECK_PILE_Y, 22);
|
||||
dcBg.setDepth(D.hand + 1);
|
||||
const dcText = this.add.text(DECK_PILE_X, DECK_PILE_Y, `${deckCount}`, {
|
||||
dcBg.setVisible(displayDeck > 0);
|
||||
const dcText = this.add.text(DECK_PILE_X, DECK_PILE_Y, `${displayDeck}`, {
|
||||
fontFamily: 'Righteous', fontSize: '22px', color: COLORS.textHex,
|
||||
}).setOrigin(0.5).setDepth(D.hand + 1);
|
||||
dcText.setVisible(displayDeck > 0);
|
||||
this.dynamicLayer.add(dcBg);
|
||||
this.dynamicLayer.add(dcText);
|
||||
this._liveDeckPile = deckPile;
|
||||
this._liveDeckBadge = dcBg;
|
||||
this._liveDeckCountText = dcText;
|
||||
|
||||
// Human discard pile — face-down card with count badge
|
||||
const discardPile = this.buildCardFace(HAND_W, HAND_H, null, { faceDown: true });
|
||||
discardPile.setPosition(DISCARD_PILE_X, DISCARD_PILE_Y).setDepth(D.hand);
|
||||
discardPile.setVisible(displayDiscard > 0);
|
||||
this.dynamicLayer.add(discardPile);
|
||||
const discardCount = gs.players[0].discard.length;
|
||||
const dpBg = this.add.graphics();
|
||||
dpBg.fillStyle(0x000000, 0.72);
|
||||
dpBg.fillCircle(DISCARD_PILE_X, DISCARD_PILE_Y, 22);
|
||||
dpBg.lineStyle(2, COLORS.accent, 1);
|
||||
dpBg.strokeCircle(DISCARD_PILE_X, DISCARD_PILE_Y, 22);
|
||||
dpBg.setDepth(D.hand + 1);
|
||||
const dpText = this.add.text(DISCARD_PILE_X, DISCARD_PILE_Y, `${discardCount}`, {
|
||||
dpBg.setVisible(displayDiscard > 0);
|
||||
const dpText = this.add.text(DISCARD_PILE_X, DISCARD_PILE_Y, `${displayDiscard}`, {
|
||||
fontFamily: 'Righteous', fontSize: '22px', color: COLORS.textHex,
|
||||
}).setOrigin(0.5).setDepth(D.hand + 1);
|
||||
dpText.setVisible(displayDiscard > 0);
|
||||
this.dynamicLayer.add(dpBg);
|
||||
this.dynamicLayer.add(dpText);
|
||||
this._liveDiscardPile = discardPile;
|
||||
this._liveDiscardBadge = dpBg;
|
||||
this._liveDiscardCountText = dpText;
|
||||
|
||||
this.opponents.forEach((opp, i) => {
|
||||
const seat = i + 1;
|
||||
|
|
@ -803,7 +857,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
}).setOrigin(0.5));
|
||||
|
||||
// Icon row (between title and type line)
|
||||
this.addIconRow(c, def, 0, bandTop + (h / 2 - bandTop) * 0.55, h);
|
||||
this.addIconRow(c, def, 0, bandTop + (h / 2 - bandTop) * 0.55, h, w);
|
||||
|
||||
// Cost coin (top-left)
|
||||
this.addCoinBadge(c, -w / 2 + 14, -h / 2 + 14, def.cost, Math.round(h * 0.075));
|
||||
|
|
@ -818,7 +872,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
return c;
|
||||
}
|
||||
|
||||
addIconRow(container, def, cx, cy, h) {
|
||||
addIconRow(container, def, cx, cy, h, w = Infinity) {
|
||||
const tokens = [];
|
||||
if (def.plus.cards) tokens.push(['cards', def.plus.cards, 0x2f6fb0]);
|
||||
if (def.plus.actions) tokens.push(['action', def.plus.actions, 0x3f9b54]);
|
||||
|
|
@ -827,9 +881,31 @@ export default class DominionGame extends Phaser.Scene {
|
|||
if (def.coin !== undefined) tokens.push(['$', def.coin, 0xd4a017]); // treasure value
|
||||
if (tokens.length === 0) return;
|
||||
|
||||
const fs = Phaser.Math.Clamp(Math.round(h * 0.06), 8, 13);
|
||||
const tw = fs * 4.2;
|
||||
const gap = 4;
|
||||
const fs0 = Phaser.Math.Clamp(Math.round(h * 0.06), 8, 13);
|
||||
let tw = fs0 * 4.2;
|
||||
let fs = fs0;
|
||||
const gap = 4;
|
||||
const maxW = w - 8;
|
||||
|
||||
const nominalTotalW = tokens.length * tw + (tokens.length - 1) * gap;
|
||||
if (nominalTotalW > maxW) {
|
||||
tw = (maxW - (tokens.length - 1) * gap) / tokens.length;
|
||||
fs = Math.floor(tw / 4.2);
|
||||
if (fs < 7) {
|
||||
const barGap = 2;
|
||||
const barW = (maxW - (tokens.length - 1) * barGap) / tokens.length;
|
||||
const barH = Math.max(3, Math.round(h * 0.025));
|
||||
let bx = cx - maxW / 2 + barW / 2;
|
||||
for (const [, , color] of tokens) {
|
||||
const bar = this.add.rectangle(bx, cy, barW, barH, color, 0.9);
|
||||
bar.setStrokeStyle(0.5, 0x000000, 0.3);
|
||||
container.add(bar);
|
||||
bx += barW + barGap;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const totalW = tokens.length * tw + (tokens.length - 1) * gap;
|
||||
let x = cx - totalW / 2 + tw / 2;
|
||||
for (const [kind, val, color] of tokens) {
|
||||
|
|
@ -986,7 +1062,13 @@ export default class DominionGame extends Phaser.Scene {
|
|||
this._suppressTurnUi = true;
|
||||
}
|
||||
const draws = (drawnCards.length > 0 && deckChanged) ? drawnCards : [];
|
||||
this._animDiscardThenDraw(allDiscarded, draws, s);
|
||||
const prevMe = prev.players[0];
|
||||
const hadShuffle = newLog.some(e => e.kind === 'shuffle' && e.seat === 0);
|
||||
this._deckAnimDisplay = prevMe.deck.length;
|
||||
this._discardAnimDisplay = prevMe.discard.length;
|
||||
this._animDiscardThenDraw(allDiscarded, draws, s, {
|
||||
hadShuffle, oldDeckCount: prevMe.deck.length, oldDiscardCount: prevMe.discard.length,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1003,7 +1085,13 @@ export default class DominionGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
if (drawnCards.length > 0 && deckChanged) {
|
||||
this._animDrawCards(drawnCards, s);
|
||||
const prevMe2 = prev.players[0];
|
||||
const hadShuffle2 = newLog.some(e => e.kind === 'shuffle' && e.seat === 0);
|
||||
this._deckAnimDisplay = prevMe2.deck.length;
|
||||
this._discardAnimDisplay = prevMe2.discard.length;
|
||||
this._animDrawCards(drawnCards, s, {
|
||||
hadShuffle: hadShuffle2, oldDeckCount: prevMe2.deck.length, oldDiscardCount: prevMe2.discard.length,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1103,12 +1191,13 @@ export default class DominionGame extends Phaser.Scene {
|
|||
|
||||
if (gs.phase === 'buy') {
|
||||
if (gs.players[seat].hand.some((c) => isType(c.id, 'treasure'))) {
|
||||
playSound(this, SFX.COINS);
|
||||
this.setState(playAllTreasures(gs));
|
||||
return;
|
||||
}
|
||||
const buy = AI.chooseBuy(gs, seat, skill);
|
||||
if (buy) {
|
||||
playSound(this, SFX.CARD_SHOW);
|
||||
playSound(this, SFX.PURCHASE);
|
||||
if (buy === 'province') this.portraits[seat]?.playEmotion?.('happy');
|
||||
this.setState(buyCard(gs, buy));
|
||||
} else {
|
||||
|
|
@ -1128,19 +1217,20 @@ export default class DominionGame extends Phaser.Scene {
|
|||
|
||||
humanPlayTreasure(iid) {
|
||||
if (this.gs.pending || this.gs.turn !== 0 || this._animating) return;
|
||||
playSound(this, SFX.COINS);
|
||||
this.setState(playTreasure(this.gs, iid));
|
||||
}
|
||||
|
||||
humanPlayTreasures() {
|
||||
if (this.gs.pending || this.gs.turn !== 0 || this._animating) return;
|
||||
playSound(this, SFX.CARD_PLACE);
|
||||
playSound(this, SFX.COINS);
|
||||
this.setState(playAllTreasures(this.gs));
|
||||
}
|
||||
|
||||
humanBuy(id) {
|
||||
if (this.gs.pending || this.gs.turn !== 0 || this._animating) return;
|
||||
this._boughtThisTurn = true;
|
||||
playSound(this, SFX.CARD_SHOW);
|
||||
playSound(this, SFX.PURCHASE);
|
||||
this.setState(buyCard(this.gs, id));
|
||||
}
|
||||
|
||||
|
|
@ -1546,6 +1636,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
const fu = this.buildCardFace(PLAY_W, PLAY_H, def);
|
||||
fu.setPosition(src.x, src.y).setScale(OPP_W / PLAY_W, OPP_H / PLAY_H);
|
||||
this.animLayer.add(fu);
|
||||
playSound(this, SFX.CARD_SHOW);
|
||||
this.tweens.add({
|
||||
targets: fu, x: tx, y: ty, scaleX: 1, scaleY: 1,
|
||||
duration: 350, ease: 'Cubic.easeOut',
|
||||
|
|
@ -1588,6 +1679,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
targets: mini, x: tx, y: ty, duration: 320, ease: 'Cubic.easeOut',
|
||||
onComplete: () => {
|
||||
mini.destroy();
|
||||
playSound(this, SFX.CARD_DEAL);
|
||||
if (tgt?.face) tgt.face.setAlpha(1);
|
||||
next();
|
||||
},
|
||||
|
|
@ -1646,22 +1738,24 @@ export default class DominionGame extends Phaser.Scene {
|
|||
this.render();
|
||||
const slot = this.oppSlot(seat - 1);
|
||||
|
||||
const cardName = getCard(cardId).name;
|
||||
const label = this.add.text(slot.x - slot.r - 74, slot.y, `Purchased:\n${cardName}`, {
|
||||
fontFamily: 'Righteous', fontSize: '32px', color: '#FFD700',
|
||||
align: 'right', stroke: '#000000', strokeThickness: 4,
|
||||
}).setOrigin(1, 0.5).setDepth(D.hud + 5).setAlpha(1);
|
||||
this._animPurchaseShowcase(cardId, srcX, srcY, slot.x, slot.y, () => {
|
||||
const cardName = getCard(cardId).name;
|
||||
const label = this.add.text(slot.x - slot.r - 74, slot.y, `Purchased:\n${cardName}`, {
|
||||
fontFamily: 'Righteous', fontSize: '32px', color: '#FFD700',
|
||||
align: 'right', stroke: '#000000', strokeThickness: 4,
|
||||
}).setOrigin(1, 0.5).setDepth(D.hud + 5).setAlpha(1);
|
||||
|
||||
const src = { iid: -1, id: cardId, x: srcX, y: srcY };
|
||||
this._animDiscardCard(src, slot.x, slot.y, () => {
|
||||
this.tweens.add({
|
||||
targets: label, alpha: 0, duration: 600, delay: 400, ease: 'Sine.easeIn',
|
||||
onComplete: () => label.destroy(),
|
||||
});
|
||||
this._animating = false;
|
||||
if (this._pendingAnimState) { const s = this._pendingAnimState; this._pendingAnimState = null; this.setState(s); }
|
||||
else this.scheduleAdvance(10);
|
||||
}, { flyDuration: 1000, foldDuration: 500 });
|
||||
const src = { iid: -1, id: cardId, x: slot.x, y: slot.y };
|
||||
this._animDiscardCard(src, slot.x, slot.y, () => {
|
||||
this.tweens.add({
|
||||
targets: label, alpha: 0, duration: 600, delay: 400, ease: 'Sine.easeIn',
|
||||
onComplete: () => label.destroy(),
|
||||
});
|
||||
this._animating = false;
|
||||
if (this._pendingAnimState) { const s = this._pendingAnimState; this._pendingAnimState = null; this.setState(s); }
|
||||
else this.scheduleAdvance(10);
|
||||
}, { flyDuration: 1, foldDuration: 500 });
|
||||
});
|
||||
}
|
||||
|
||||
_chainHumanGain(gainEvt, newState) {
|
||||
|
|
@ -1682,7 +1776,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
|
||||
// ── Draw animations ───────────────────────────────────────────────────────────
|
||||
|
||||
_animDiscardThenDraw(discardedCards, drawnCards, newState) {
|
||||
_animDiscardThenDraw(discardedCards, drawnCards, newState, shuffleInfo = {}) {
|
||||
this._animating = true;
|
||||
|
||||
// Capture positions + face sprite refs BEFORE any render wipes the arrays.
|
||||
|
|
@ -1700,6 +1794,14 @@ export default class DominionGame extends Phaser.Scene {
|
|||
|
||||
let idx = 0;
|
||||
const animateNext = () => {
|
||||
// Each call after the first means the previous card just landed in the discard pile.
|
||||
if (idx > 0) {
|
||||
this._discardAnimDisplay = (this._discardAnimDisplay ?? 0) + 1;
|
||||
if (this._liveDiscardCountText?.active)
|
||||
this._liveDiscardCountText.setText(`${this._discardAnimDisplay}`);
|
||||
if (this._liveDiscardPile?.active) this._liveDiscardPile.setVisible(true);
|
||||
if (this._liveDiscardBadge?.active) this._liveDiscardBadge.setVisible(true);
|
||||
}
|
||||
if (idx >= sources.length) {
|
||||
// All cards have landed. Now render the intermediate/final state.
|
||||
const p0 = newState.players[0];
|
||||
|
|
@ -1716,8 +1818,13 @@ export default class DominionGame extends Phaser.Scene {
|
|||
this.render();
|
||||
|
||||
if (drawnCards.length > 0) {
|
||||
this._animDrawCards(drawnCards, newState);
|
||||
this._animDrawCards(drawnCards, newState, {
|
||||
...shuffleInfo,
|
||||
oldDiscardCount: this._discardAnimDisplay ?? 0,
|
||||
});
|
||||
} else {
|
||||
this._deckAnimDisplay = null;
|
||||
this._discardAnimDisplay = null;
|
||||
this._suppressTurnUi = false;
|
||||
this.updateTurnUi();
|
||||
this._animating = false;
|
||||
|
|
@ -1753,6 +1860,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
const fdCard = this.buildCardFace(HAND_W, HAND_H, null, { faceDown: true });
|
||||
fdCard.setPosition(tx, ty).setScale(0, 1);
|
||||
this.animLayer.add(fdCard);
|
||||
playSound(this, SFX.CARD_SHOW);
|
||||
this.tweens.add({
|
||||
targets: fdCard, scaleX: 1, duration: foldDuration, ease: 'Sine.easeOut',
|
||||
onComplete: () => { fdCard.destroy(); onComplete(); },
|
||||
|
|
@ -1763,23 +1871,92 @@ export default class DominionGame extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
_animPurchaseShowcase(cardId, srcX, srcY, tx, ty, onComplete) {
|
||||
const cy = GAME_HEIGHT / 2;
|
||||
const SHOW_W = 270, SHOW_H = 390;
|
||||
const def = getCard(cardId);
|
||||
|
||||
const overlay = this.add.rectangle(CX, cy, GAME_WIDTH, GAME_HEIGHT, 0x000000, 0)
|
||||
.setDepth(D.modal);
|
||||
this.tweens.add({ targets: overlay, alpha: 0.65, duration: 250 });
|
||||
|
||||
const card = this.buildCardFace(SHOW_W, SHOW_H, def);
|
||||
card.setPosition(srcX, srcY).setScale(SUPPLY_W / SHOW_W).setDepth(D.modal + 1);
|
||||
|
||||
this.tweens.add({
|
||||
targets: card, x: CX, y: cy, scaleX: 1, scaleY: 1,
|
||||
duration: 350, ease: 'Back.easeOut',
|
||||
onComplete: () => {
|
||||
// Comet border effect around the full-size card
|
||||
const gfx = this.add.graphics().setDepth(D.modal + 2);
|
||||
const perim = 2 * (SHOW_W + SHOW_H);
|
||||
const TAIL_PX = 130;
|
||||
const SEGMENTS = 24;
|
||||
const COMETS = 3;
|
||||
const COMET_COLORS = [0xffd700, 0xffffff, 0xff9900];
|
||||
const left = CX - SHOW_W / 2, top = cy - SHOW_H / 2;
|
||||
const tracker = { t: 0 };
|
||||
const borderTween = this.tweens.add({
|
||||
targets: tracker, t: { from: 0, to: 1 },
|
||||
duration: 1400, repeat: -1, ease: 'Linear',
|
||||
onUpdate: () => {
|
||||
gfx.clear();
|
||||
for (let c = 0; c < COMETS; c++) {
|
||||
const headDist = ((tracker.t + c / COMETS) % 1) * perim;
|
||||
const cometColor = COMET_COLORS[c];
|
||||
for (let seg = 0; seg < SEGMENTS; seg++) {
|
||||
const frac = seg / SEGMENTS;
|
||||
const d1 = ((headDist - (1 - frac) * TAIL_PX + perim * 100) % perim);
|
||||
const d2 = ((headDist - (1 - (seg + 1) / SEGMENTS) * TAIL_PX + perim * 100) % perim);
|
||||
const [x1, y1] = perimPoint(d1, SHOW_W, SHOW_H, left, top);
|
||||
const [x2, y2] = perimPoint(d2, SHOW_W, SHOW_H, left, top);
|
||||
const alpha = 0.1 + frac * 0.9;
|
||||
const lineColor = frac > 0.85 ? 0xffffff : cometColor;
|
||||
const lineWidth = frac > 0.85 ? 5 : 2.5;
|
||||
gfx.lineStyle(lineWidth, lineColor, alpha);
|
||||
gfx.beginPath();
|
||||
gfx.moveTo(x1, y1);
|
||||
gfx.lineTo(x2, y2);
|
||||
gfx.strokePath();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.time.delayedCall(1500, () => {
|
||||
borderTween.stop();
|
||||
gfx.destroy();
|
||||
this.tweens.add({ targets: overlay, alpha: 0, duration: 300 });
|
||||
this.tweens.add({
|
||||
targets: card,
|
||||
x: tx, y: ty, scaleX: HAND_W / SHOW_W, scaleY: HAND_H / SHOW_H,
|
||||
duration: 400, ease: 'Cubic.easeIn',
|
||||
onComplete: () => { card.destroy(); overlay.destroy(); onComplete(); },
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_animGainCard(gainedCard, srcX, srcY, dest, newState) {
|
||||
this._animating = true;
|
||||
this.gs = newState;
|
||||
this.clearPrompt();
|
||||
this.render();
|
||||
|
||||
const tx = dest === 'deck' ? DECK_PILE_X : DISCARD_PILE_X;
|
||||
const ty = dest === 'deck' ? DECK_PILE_Y : DISCARD_PILE_Y;
|
||||
const src = { iid: gainedCard.iid, id: gainedCard.id, x: srcX, y: srcY };
|
||||
const tx = dest === 'deck' ? DECK_PILE_X : DISCARD_PILE_X;
|
||||
const ty = dest === 'deck' ? DECK_PILE_Y : DISCARD_PILE_Y;
|
||||
|
||||
this._animDiscardCard(src, tx, ty, () => {
|
||||
this._animating = false;
|
||||
if (this._pendingAnimState) {
|
||||
const s = this._pendingAnimState; this._pendingAnimState = null; this.setState(s);
|
||||
} else {
|
||||
this.scheduleAdvance(10);
|
||||
}
|
||||
this._animPurchaseShowcase(gainedCard.id, srcX, srcY, tx, ty, () => {
|
||||
const src = { iid: gainedCard.iid, id: gainedCard.id, x: tx, y: ty };
|
||||
this._animDiscardCard(src, tx, ty, () => {
|
||||
this._animating = false;
|
||||
if (this._pendingAnimState) {
|
||||
const s = this._pendingAnimState; this._pendingAnimState = null; this.setState(s);
|
||||
} else {
|
||||
this.scheduleAdvance(10);
|
||||
}
|
||||
}, { flyDuration: 1, foldDuration: 250 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1908,23 +2085,69 @@ export default class DominionGame extends Phaser.Scene {
|
|||
this._dragDropLabel = null;
|
||||
}
|
||||
|
||||
_animDrawCards(drawnCards, newState) {
|
||||
_animDrawCards(drawnCards, newState, shuffleInfo = {}) {
|
||||
const { hadShuffle = false, oldDeckCount = 0 } = shuffleInfo;
|
||||
|
||||
this._animating = true;
|
||||
this._animatingIids = new Set(drawnCards.map(c => c.iid));
|
||||
this.gs = newState;
|
||||
this.clearPrompt();
|
||||
this.render();
|
||||
this.render(); // uses _deckAnimDisplay / _discardAnimDisplay overrides
|
||||
|
||||
const updateDeckDisplay = () => {
|
||||
if (this._liveDeckCountText?.active)
|
||||
this._liveDeckCountText.setText(`${this._deckAnimDisplay}`);
|
||||
const empty = this._deckAnimDisplay <= 0;
|
||||
if (this._liveDeckPile?.active) this._liveDeckPile.setVisible(!empty);
|
||||
if (this._liveDeckBadge?.active) this._liveDeckBadge.setVisible(!empty);
|
||||
};
|
||||
|
||||
const doShuffle = (callback) => {
|
||||
this.time.delayedCall(400, () => {
|
||||
playSound(this, SFX.CARD_SHUFFLE);
|
||||
const startVal = this._discardAnimDisplay ?? 0;
|
||||
const counter = { v: startVal };
|
||||
this.tweens.add({
|
||||
targets: counter, v: 0, duration: 600, ease: 'Cubic.Out',
|
||||
onUpdate: () => {
|
||||
this._discardAnimDisplay = Math.round(counter.v);
|
||||
if (this._liveDiscardCountText?.active)
|
||||
this._liveDiscardCountText.setText(`${this._discardAnimDisplay}`);
|
||||
},
|
||||
onComplete: () => {
|
||||
const postDeck = (drawnCards.length - drawnSoFar) + newState.players[0].deck.length;
|
||||
this._deckAnimDisplay = postDeck;
|
||||
updateDeckDisplay();
|
||||
callback();
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let idx = 0;
|
||||
let drawnSoFar = 0;
|
||||
let shuffleDone = false;
|
||||
|
||||
const animateNext = () => {
|
||||
if (hadShuffle && !shuffleDone && drawnSoFar === oldDeckCount) {
|
||||
shuffleDone = true;
|
||||
doShuffle(animateNext);
|
||||
return;
|
||||
}
|
||||
if (idx >= drawnCards.length) { this._finishDrawAnim(); return; }
|
||||
const card = drawnCards[idx++];
|
||||
const card = drawnCards[idx++];
|
||||
const sprite = this.handSprites.find(s => s.iid === card.iid);
|
||||
const tx = sprite?.x ?? CX;
|
||||
const tx = sprite?.x ?? CX;
|
||||
this._animateOneCard(card, tx, 968, () => {
|
||||
drawnSoFar++;
|
||||
playSound(this, SFX.CARD_DEAL);
|
||||
const s2 = this.handSprites.find(s => s.iid === card.iid);
|
||||
if (s2) s2.face.setAlpha(1);
|
||||
this._animatingIids.delete(card.iid);
|
||||
|
||||
this._deckAnimDisplay = (this._deckAnimDisplay ?? 1) - 1;
|
||||
updateDeckDisplay();
|
||||
|
||||
animateNext();
|
||||
});
|
||||
};
|
||||
|
|
@ -1945,6 +2168,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
const fuCard = this.buildCardFace(HAND_W, HAND_H, def);
|
||||
fuCard.setPosition(DECK_PILE_X, DECK_PILE_Y).setScale(0, 1);
|
||||
this.animLayer.add(fuCard);
|
||||
playSound(this, SFX.CARD_SHOW);
|
||||
|
||||
this.tweens.add({
|
||||
targets: fuCard, scaleX: 1, duration: 250, ease: 'Sine.easeOut',
|
||||
|
|
@ -1960,6 +2184,8 @@ export default class DominionGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
_finishDrawAnim() {
|
||||
this._deckAnimDisplay = null;
|
||||
this._discardAnimDisplay = null;
|
||||
this.handSprites.forEach(s => s.face.setAlpha(1));
|
||||
this._animatingIids.clear();
|
||||
this._animating = false;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ export default class PreloadScene extends Phaser.Scene {
|
|||
this.load.audio('sfx-card-place', '/assets/fx/card-place.mp3');
|
||||
this.load.audio('sfx-card-show', '/assets/fx/card-show.mp3');
|
||||
this.load.audio('sfx-card-shuffle', '/assets/fx/card-shuffle.mp3');
|
||||
this.load.audio('sfx-coins', '/assets/fx/coins.mp3');
|
||||
this.load.audio('sfx-purchase', '/assets/fx/purchase.mp3');
|
||||
this.load.audio('sfx-casino-blackjack', '/assets/fx/casino-blackjack.mp3');
|
||||
this.load.audio('sfx-casino-lose', '/assets/fx/casino-lose.mp3');
|
||||
this.load.audio('sfx-casino-win', '/assets/fx/casino-win.mp3');
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export const SFX = {
|
|||
CARD_PLACE: 'sfx-card-place',
|
||||
CARD_SHOW: 'sfx-card-show',
|
||||
CARD_SHUFFLE: 'sfx-card-shuffle',
|
||||
COINS: 'sfx-coins',
|
||||
PURCHASE: 'sfx-purchase',
|
||||
CASINO_BLACKJACK: 'sfx-casino-blackjack',
|
||||
CASINO_LOSE: 'sfx-casino-lose',
|
||||
CASINO_WIN: 'sfx-casino-win',
|
||||
|
|
|
|||
Loading…
Reference in New Issue