diff --git a/public/assets/fx/8bit-action.mp3 b/public/assets/fx/8bit-action.mp3 new file mode 100644 index 0000000..0c383ec Binary files /dev/null and b/public/assets/fx/8bit-action.mp3 differ diff --git a/public/assets/fx/8bit-activate.mp3 b/public/assets/fx/8bit-activate.mp3 new file mode 100644 index 0000000..e10da6f Binary files /dev/null and b/public/assets/fx/8bit-activate.mp3 differ diff --git a/public/assets/fx/8bit-move.mp3 b/public/assets/fx/8bit-move.mp3 new file mode 100644 index 0000000..884988b Binary files /dev/null and b/public/assets/fx/8bit-move.mp3 differ diff --git a/public/src/games/battleship/BattleshipGame.js b/public/src/games/battleship/BattleshipGame.js index 9d7ebe3..a326cb5 100644 --- a/public/src/games/battleship/BattleshipGame.js +++ b/public/src/games/battleship/BattleshipGame.js @@ -216,7 +216,7 @@ export default class BattleshipGame extends Phaser.Scene { // Player — bottom-right margin. this.plrX = RX + GRID + (GAME_WIDTH - (RX + GRID)) / 2; this.plrY = 740; - createPlayerPortrait(this, this.plrX, this.plrY, r, DEPTH.ui, 'Battleship'); + createPlayerPortrait(this, this.plrX, this.plrY, r, DEPTH.ui, 'BattleshipGame'); this.add.text(this.plrX, this.plrY - r - 12, auth.user?.username ?? 'You', { fontFamily: '"Julius Sans One"', fontSize: '20px', color: COLORS.textHex, wordWrap: { width: 240 }, align: 'center', @@ -373,11 +373,7 @@ export default class BattleshipGame extends Phaser.Scene { const w = piece.horizontal ? piece.spec.len * CELL : CELL; const h = piece.horizontal ? CELL : piece.spec.len * CELL; piece.container.setSize(w, h); - // Our hull graphics are top-left anchored (local 0,0 → w,h), but Phaser's - // input normalizes the hit test by a Container's displayOrigin, which is - // hardcoded to (width/2, height/2). Offset the hit area by the same amount - // so the clickable zone lines up with the visible ship instead of sitting - // up-and-left of it. + piece.container.removeInteractive(); piece.container.setInteractive( new Phaser.Geom.Rectangle(w / 2, h / 2, w, h), Phaser.Geom.Rectangle.Contains); this.input.setDraggable(piece.container); @@ -423,6 +419,8 @@ export default class BattleshipGame extends Phaser.Scene { } onShipDragStart(piece) { + if (this.gs.phase !== 'placement') return; + this.tweens.killTweensOf(piece.container); this.activeShip = piece; if (piece.placed) { piece.placed = false; this.syncFleetFromPieces(); this.updateReadyButton(); } piece.container.setScale(1).setDepth(DEPTH.missile); @@ -745,10 +743,26 @@ export default class BattleshipGame extends Phaser.Scene { if (res.result === 'sunk') this.onShipSunk(res.ship, firedByPlayer); if (this.opponentPortrait) this.playOppEmotion(firedByPlayer ? 'upset' : 'happy'); } - this.renderMarkers(); + this.drawImpactMarker(firedByPlayer, res); this.refreshFleetPanels(); } + drawImpactMarker(firedByPlayer, res) { + const g = firedByPlayer ? this.enemyMarkers : this.yourMarkers; + const ox = firedByPlayer ? EX : RX; + const x = ox + res.c * CELL + CELL / 2; + const y = BY + res.r * CELL + CELL / 2; + if (res.result === 'miss') { + g.fillStyle(0x0a1a22, 0.5); g.fillCircle(x, y, 9); + g.fillStyle(C.miss, 0.95); g.fillCircle(x, y, 7); + g.lineStyle(1, 0x88a6b2, 0.8); g.strokeCircle(x, y, 7); + } else { + g.fillStyle(C.hit, 0.25); g.fillCircle(x, y, 16); + g.fillStyle(C.hit, 1); g.fillCircle(x, y, 10); + g.fillStyle(C.hitGlow, 1); g.fillCircle(x, y, 5); + } + } + splash(x, y) { const ripple = this.add.graphics().setDepth(DEPTH.fx); const prox = { p: 0 }; diff --git a/public/src/games/risk/RiskGame.js b/public/src/games/risk/RiskGame.js index 4229792..4d508c9 100644 --- a/public/src/games/risk/RiskGame.js +++ b/public/src/games/risk/RiskGame.js @@ -389,21 +389,27 @@ export default class RiskGame extends Phaser.Scene { if (s.phase === 'attack') { if (s.owner[id] === seat && s.armies[id] >= 2 && ADJ[id].some((t) => s.owner[t] !== seat)) { + playSound(this, SFX.EIGHTBIT_SELECT); this.selFrom = id; this.render(); return; } - if (this.selFrom != null && canAttack(s, seat, this.selFrom, id)) { this.doHumanAttack(this.selFrom, id); return; } + if (this.selFrom != null && canAttack(s, seat, this.selFrom, id)) { + playSound(this, SFX.EIGHTBIT_ACTIVATE); + this.doHumanAttack(this.selFrom, id); return; + } this.selFrom = null; this.render(); return; } if (s.phase === 'fortify') { if (this.selFrom == null) { if (s.owner[id] === seat && s.armies[id] >= 2 && connectedOwned(s, seat, id).length > 0) { + playSound(this, SFX.EIGHTBIT_SELECT); this.selFrom = id; this.render(); } return; } if (id === this.selFrom) { this.selFrom = null; this.render(); return; } if (s.owner[id] === seat && connectedOwned(s, seat, this.selFrom).includes(id)) { + playSound(this, SFX.EIGHTBIT_ACTION); const from = this.selFrom, to = id; this.openMoveModal(1, s.armies[from] - 1, s.armies[from] - 1, async (n) => { this.selFrom = null; @@ -937,8 +943,9 @@ export default class RiskGame extends Phaser.Scene { const progress = { t: 0 }; this.tweens.add({ targets: progress, t: 1, duration: 1000, ease: 'Sine.easeInOut', - onUpdate: () => drawArc(progress.t), - onComplete: () => { drawArc(1); resolve(); }, + onStart: () => playSound(this, SFX.BATTLESHIP_LAUNCH), + onUpdate: () => drawArc(progress.t), + onComplete: () => { drawArc(1); playSound(this, SFX.BATTLESHIP_HIT); resolve(); }, }); }); @@ -986,7 +993,8 @@ export default class RiskGame extends Phaser.Scene { const progress = { t: 0 }; this.tweens.add({ targets: progress, t: 1, duration: 1000, ease: 'Sine.easeInOut', - onUpdate: () => drawLine(progress.t), + onStart: () => playSound(this, SFX.EIGHTBIT_MOVE), + onUpdate: () => drawLine(progress.t), onComplete: () => { drawLine(1); resolve(); }, }); }); diff --git a/public/src/scenes/PreloadScene.js b/public/src/scenes/PreloadScene.js index 6c9f2a3..3c9616a 100644 --- a/public/src/scenes/PreloadScene.js +++ b/public/src/scenes/PreloadScene.js @@ -115,6 +115,10 @@ export default class PreloadScene extends Phaser.Scene { this.load.audio('sfx-scifi-woosh', '/assets/fx/scifi-woosh.mp3'); this.load.audio('sfx-scifi-plink', '/assets/fx/scifi-plink.mp3'); this.load.audio('sfx-scifi-plonk', '/assets/fx/scifi-plonk.mp3'); + this.load.audio('sfx-8bit-select', '/assets/fx/8bit-select.mp3'); + this.load.audio('sfx-8bit-activate', '/assets/fx/8bit-activate.mp3'); + this.load.audio('sfx-8bit-action', '/assets/fx/8bit-action.mp3'); + this.load.audio('sfx-8bit-move', '/assets/fx/8bit-move.mp3'); this.load.spritesheet('catan-special-cards', '/assets/images/catan-special-cards.png', { frameWidth: 270, frameHeight: 390 }); diff --git a/public/src/ui/Sounds.js b/public/src/ui/Sounds.js index 500a94d..0c8a56a 100644 --- a/public/src/ui/Sounds.js +++ b/public/src/ui/Sounds.js @@ -40,6 +40,10 @@ export const SFX = { MONOPOLY_EXPENSE: 'sfx-monopoly-expense', MONOPAY: 'sfx-monopoly-pay', MONOPOLY_PAID: 'sfx-monopoly-paid', + EIGHTBIT_SELECT: 'sfx-8bit-select', + EIGHTBIT_ACTIVATE: 'sfx-8bit-activate', + EIGHTBIT_ACTION: 'sfx-8bit-action', + EIGHTBIT_MOVE: 'sfx-8bit-move', }; export function playSound(scene, key) {