diff --git a/src/client/dev-dist/sw.js b/src/client/dev-dist/sw.js index c0dee49..6ddc826 100644 --- a/src/client/dev-dist/sw.js +++ b/src/client/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-5a5d9309'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.vopjl6fp8f" + "revision": "0.inrr5fp7a9" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/src/client/src/modules/game/GameView.tsx b/src/client/src/modules/game/GameView.tsx index 045e792..a56ea14 100644 --- a/src/client/src/modules/game/GameView.tsx +++ b/src/client/src/modules/game/GameView.tsx @@ -706,6 +706,14 @@ export const GameView: React.FC = ({ gameState, currentPlayerId } onDragStart={() => { }} onClick={(id) => { if (gameState.step === 'declare_attackers') { + // Validate Creature Type + const types = card.types || []; + const typeLine = card.typeLine || ''; + if (!types.includes('Creature') && !typeLine.includes('Creature')) { + // Optional: Shake effect or visual feedback that it's invalid + return; + } + const newSet = new Set(proposedAttackers); if (newSet.has(id)) newSet.delete(id); else newSet.add(id); diff --git a/src/server/game/RulesEngine.ts b/src/server/game/RulesEngine.ts index 405ba35..1beaadd 100644 --- a/src/server/game/RulesEngine.ts +++ b/src/server/game/RulesEngine.ts @@ -471,14 +471,25 @@ export class RulesEngine { // 4. Combat Steps requiring declaration (Pause for External Action) if (step === 'declare_attackers') { // WAITING for declareAttackers() from Client - // Do NOT reset priority yet. - // TODO: Maybe set a timeout or auto-skip if no creatures? + // 508.1. Active Player gets priority to declare attackers. + // Unlike other steps where AP gets priority to cast spells, here the "Action" determines the flow. + // But technically, the AP *must* act. So we ensure they have priority. + if (this.state.priorityPlayerId !== activePlayerId) { + this.resetPriority(activePlayerId); + } return; } if (step === 'declare_blockers') { // WAITING for declareBlockers() from Client (Defending Player) - // Do NOT reset priority yet. + // 509.1. Defending Player gets priority to declare blockers. + // In 1v1, this is the non-active player. + const defendingPlayerId = this.state.turnOrder.find(id => id !== activePlayerId); + if (defendingPlayerId) { + if (this.state.priorityPlayerId !== defendingPlayerId) { + this.resetPriority(defendingPlayerId); + } + } return; } diff --git a/src/server/managers/GameManager.ts b/src/server/managers/GameManager.ts index 24c2c54..ce08809 100644 --- a/src/server/managers/GameManager.ts +++ b/src/server/managers/GameManager.ts @@ -110,7 +110,12 @@ export class GameManager { engine.castSpell(actorId, action.cardId, action.targets, action.position); break; case 'DECLARE_ATTACKERS': - engine.declareAttackers(actorId, action.attackers); + try { + engine.declareAttackers(actorId, action.attackers); + } catch (err: any) { + console.error(`[DeclareAttackers Error] Actor: ${actorId}, Active: ${game.activePlayerId}, Priority: ${game.priorityPlayerId}, Step: ${game.step}`); + throw err; // Re-throw to catch block below + } break; case 'DECLARE_BLOCKERS': engine.declareBlockers(actorId, action.blockers);