feat: Improve attacker/blocker declaration by adding client-side creature validation, enforcing correct player priority, and enhancing server-side error logging.

This commit is contained in:
2025-12-22 16:47:38 +01:00
parent 9c72bd7b8c
commit c1e062620e
4 changed files with 29 additions and 5 deletions

View File

@@ -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"), {

View File

@@ -706,6 +706,14 @@ export const GameView: React.FC<GameViewProps> = ({ 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);

View File

@@ -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;
}

View File

@@ -110,7 +110,12 @@ export class GameManager {
engine.castSpell(actorId, action.cardId, action.targets, action.position);
break;
case 'DECLARE_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);