Added VP coins to Dominion
This commit is contained in:
parent
eca8013fd4
commit
fcc061e43d
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
|
@ -110,6 +110,8 @@ export default class DominionGame extends Phaser.Scene {
|
|||
this._liveDiscardPile = null;
|
||||
this._liveDiscardBadge = null;
|
||||
this._liveDiscardCountText = null;
|
||||
this.tokenSprites = [];
|
||||
this.lastVpTokens = { 0: 0, 1: 0, 2: 0, 3: 0 };
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
@ -825,6 +827,133 @@ export default class DominionGame extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
// ── Floating token sprites (Prosperity expansion) ─────────────────────────────
|
||||
// Spawns a token sprite above the player's phase dial with random position
|
||||
// and rotation when VP tokens or gold are earned.
|
||||
|
||||
// Phase dial anchor positions (mirrors buildPhaseDials).
|
||||
_dialPos(seat) {
|
||||
if (seat === 0) return { x: 168, y: 748 };
|
||||
const s = this.oppSlot(seat - 1);
|
||||
const r = 26;
|
||||
return { x: s.x + s.r + 14 + r, y: s.y };
|
||||
}
|
||||
|
||||
spawnTokenSprite(seat, tokenType) {
|
||||
// tokenType: 'vp1', 'vp5', or 'gold'
|
||||
const spriteMap = { vp1: 0, vp5: 1, gold: 2 };
|
||||
const frameIndex = spriteMap[tokenType];
|
||||
if (frameIndex === undefined || !this.textures.exists('dominion-tokens')) return;
|
||||
|
||||
const dial = this._dialPos(seat);
|
||||
// Random position in a ~200×250 area above the dial
|
||||
const offsetX = Phaser.Math.Between(-100, 100);
|
||||
const offsetY = Phaser.Math.Between(-400, -200);
|
||||
const x = dial.x + offsetX;
|
||||
const y = dial.y + offsetY;
|
||||
// Clamp to visible area
|
||||
const clampedX = Phaser.Math.Clamp(x, 75, GAME_WIDTH - 75);
|
||||
const clampedY = Phaser.Math.Clamp(y, 50, GAME_HEIGHT - 50);
|
||||
|
||||
const angle = Phaser.Math.Between(-35, 35);
|
||||
const targetScale = 80 / 150; // 80px final size from 150px source
|
||||
|
||||
const sprite = this.add.sprite(clampedX, clampedY, 'dominion-tokens', frameIndex)
|
||||
.setDisplaySize(80, 80)
|
||||
.setAngle(angle)
|
||||
.setDepth(D.popup)
|
||||
.setAlpha(0)
|
||||
.setScale(1.5)
|
||||
.setData('tokenType', tokenType);
|
||||
|
||||
this.tokenSprites.push(sprite);
|
||||
|
||||
// Animate from larger size down to 80×80 — token stays for the rest of the game
|
||||
this.tweens.add({
|
||||
targets: sprite,
|
||||
alpha: 0.92,
|
||||
scaleX: targetScale,
|
||||
scaleY: targetScale,
|
||||
duration: 1200,
|
||||
ease: 'Back.easeOut',
|
||||
});
|
||||
}
|
||||
|
||||
// Count 1-VP tokens currently visible in the placement area (above human player's dial)
|
||||
_countOneVpTokens() {
|
||||
const dial = this._dialPos(0);
|
||||
let count = 0;
|
||||
for (const s of this.tokenSprites) {
|
||||
if (s.getData('tokenType') === 'vp1' &&
|
||||
s.active && s.alpha > 0.1 &&
|
||||
s.y < dial.y - 200) { // in the area above the dial
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Remove all 1-VP tokens from the placement area with a fade-out
|
||||
_removeOneVpTokens() {
|
||||
for (const s of this.tokenSprites) {
|
||||
if (s.getData('tokenType') === 'vp1' && s.active && s.alpha > 0.1 && s.y < this._dialPos(0).y - 200) {
|
||||
this.tweens.add({
|
||||
targets: s,
|
||||
alpha: 0,
|
||||
duration: 400,
|
||||
ease: 'Sine.In',
|
||||
onComplete: () => {
|
||||
const idx = this.tokenSprites.indexOf(s);
|
||||
if (idx > -1) this.tokenSprites.splice(idx, 1);
|
||||
s.destroy();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_checkTokenGains(prevState, newState) {
|
||||
const prevVp = this.lastVpTokens;
|
||||
const newVp = {};
|
||||
newState.players.forEach((p, seat) => { newVp[seat] = p.vpTokens ?? 0; });
|
||||
|
||||
// Only show token animations for the human player (seat 0)
|
||||
const humanSeat = 0;
|
||||
const diff = newVp[humanSeat] - (prevVp[humanSeat] ?? 0);
|
||||
if (diff > 0) {
|
||||
// Keep replacing 1-VP tokens with 5-VP tokens until we can't anymore
|
||||
let remaining = diff;
|
||||
while (remaining > 0) {
|
||||
const currentOneVp = this._countOneVpTokens();
|
||||
if (currentOneVp > 0 && currentOneVp < 5 && currentOneVp + remaining >= 5) {
|
||||
// Remove all 1-VP tokens and spawn one 5-VP token
|
||||
this._removeOneVpTokens();
|
||||
this.spawnTokenSprite(humanSeat, 'vp5');
|
||||
remaining -= (5 - currentOneVp);
|
||||
} else if (remaining >= 5) {
|
||||
// No 1-VP tokens on screen, but we have 5+ to spend — spawn a 5-VP token
|
||||
this.spawnTokenSprite(humanSeat, 'vp5');
|
||||
remaining -= 5;
|
||||
} else {
|
||||
// Just spawn 1-VP tokens
|
||||
for (let i = 0; i < remaining; i++) {
|
||||
this.spawnTokenSprite(humanSeat, 'vp1');
|
||||
}
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lastVpTokens = newVp;
|
||||
|
||||
// Detect gold gains from log events (human player only)
|
||||
const newLog = newState.log.slice(prevState.log.length);
|
||||
for (const evt of newLog) {
|
||||
if (evt.kind === 'gain' && evt.id === 'gold' && evt.seat === humanSeat) {
|
||||
this.spawnTokenSprite(humanSeat, 'gold');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateControls() {
|
||||
const gs = this.gs;
|
||||
const humanTurn = gs.turn === 0 && !gs.pending && !this.gameOver && !this._animating;
|
||||
|
|
@ -1164,6 +1293,7 @@ export default class DominionGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
this.gs = s;
|
||||
this._checkTokenGains(prev, s);
|
||||
this.clearPrompt();
|
||||
this.render();
|
||||
this.scheduleAdvance(10);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ export default class PreloadScene extends Phaser.Scene {
|
|||
// Prosperity expansion art (frame order documented in expansions/prosperity.js).
|
||||
// Optional — same procedural fallback applies when the sheet is absent.
|
||||
this.load.spritesheet('dominion-prosperity', '/assets/images/dominion-prosperity.png', { frameWidth: 270, frameHeight: 390 });
|
||||
// Prosperity token sprites (1 VP, 5 VP, Gold) — 150×150 each.
|
||||
this.load.spritesheet('dominion-tokens', '/assets/images/dominion-tokens.png', { frameWidth: 150, frameHeight: 150 });
|
||||
this.load.spritesheet('ttr-cards', '/assets/images/tickettoride-cards.png', { frameWidth: 270, frameHeight: 390 });
|
||||
this.load.spritesheet('tab-icons', '/assets/images/tab-icons.png', { frameWidth: 128, frameHeight: 128 });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue