From ef8e2f294a91a9adf7908c874fb073a2f55d538e Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Sat, 29 Nov 2025 23:43:30 +0000 Subject: [PATCH] Refactor RFID handling and enhance game logic - Updated `GamesController` and `Game` model to include RFID lock types in the filtering logic for 'requires' attributes, ensuring proper handling of biometric, bluetooth, and RFID types. - Improved `RFIDMinigame` to standardize card ID retrieval, supporting both `card_id` and `key_id`. - Enhanced `unlock-system.js` to prioritize physical keycard checks and streamline the unlocking process with detailed logging. - Adjusted scenario JSON files to replace `keyId` with `card_id` for consistency and added new lock requirements for various rooms. --- .../break_escape/games_controller.rb | 9 +- app/models/break_escape/game.rb | 9 +- .../js/minigames/rfid/rfid-minigame.js | 6 +- .../break_escape/js/systems/unlock-system.js | 40 +++++++-- scenarios/secgen_vm_lab/scenario.json.erb | 4 +- .../test-rfid-multiprotocol/scenario.json.erb | 89 ++----------------- scenarios/test-rfid/scenario.json.erb | 25 +----- 7 files changed, 60 insertions(+), 122 deletions(-) diff --git a/app/controllers/break_escape/games_controller.rb b/app/controllers/break_escape/games_controller.rb index cb4f002..ba08907 100644 --- a/app/controllers/break_escape/games_controller.rb +++ b/app/controllers/break_escape/games_controller.rb @@ -494,10 +494,13 @@ module BreakEscape def filter_requires_recursive(obj) case obj when Hash - # Remove 'requires' for exploitable lock types (key/pin/password/rfid) - # Keep it for biometric/bluetooth since they reference collectible items, not answers + # Remove 'requires' for exploitable lock types (key/pin/password) + # Keep it for biometric/bluetooth/rfid since they reference collectible items, not answers + # - biometric: requires fingerprint owner name (e.g., "Mrs Moo") + # - bluetooth: requires device MAC/name (e.g., "00:11:22:33:44:55") + # - rfid: requires card IDs (e.g., ["master_keycard"]) lock_type = obj['lockType'] - if lock_type && !%w[biometric bluetooth].include?(lock_type) + if lock_type && !%w[biometric bluetooth rfid].include?(lock_type) obj.delete('requires') end diff --git a/app/models/break_escape/game.rb b/app/models/break_escape/game.rb index 5b4941b..a348de1 100644 --- a/app/models/break_escape/game.rb +++ b/app/models/break_escape/game.rb @@ -519,10 +519,13 @@ module BreakEscape def filter_requires_and_contents_recursive(obj) case obj when Hash - # Remove 'requires' for exploitable lock types (key/pin/password/rfid) - # Keep it for biometric/bluetooth since they reference collectible items, not answers + # Remove 'requires' for exploitable lock types (key/pin/password) + # Keep it for biometric/bluetooth/rfid since they reference collectible items, not answers + # - biometric: requires fingerprint owner name (e.g., "Mrs Moo") + # - bluetooth: requires device MAC/name (e.g., "00:11:22:33:44:55") + # - rfid: requires card IDs (e.g., ["master_keycard"]) lock_type = obj['lockType'] - if lock_type && !%w[biometric bluetooth].include?(lock_type) + if lock_type && !%w[biometric bluetooth rfid].include?(lock_type) obj.delete('requires') end diff --git a/public/break_escape/js/minigames/rfid/rfid-minigame.js b/public/break_escape/js/minigames/rfid/rfid-minigame.js index b7ecffd..c1bbcc2 100644 --- a/public/break_escape/js/minigames/rfid/rfid-minigame.js +++ b/public/break_escape/js/minigames/rfid/rfid-minigame.js @@ -98,8 +98,8 @@ export class RFIDMinigame extends MinigameScene { handleCardTap(card) { console.log('📡 Card tapped:', card.scenarioData?.name); - // Support both card_id (new) and key_id (legacy) - const cardId = card.scenarioData?.card_id || card.scenarioData?.key_id || card.key_id; + // Get card ID (standard: card_id, legacy: key_id) + const cardId = card.scenarioData?.card_id || card.scenarioData?.key_id; const isCorrect = !this.isLockingAttempt || this.requiredCardIds.includes(cardId); if (isCorrect) { @@ -127,7 +127,7 @@ export class RFIDMinigame extends MinigameScene { handleEmulate(savedCard) { console.log('📡 Emulating card:', savedCard.name); - // Support both card_id (new) and key_id (legacy) + // Get card ID (standard: card_id, legacy: key_id) const cardId = savedCard.card_id || savedCard.key_id; const isCorrect = !this.isLockingAttempt || this.requiredCardIds.includes(cardId); diff --git a/public/break_escape/js/systems/unlock-system.js b/public/break_escape/js/systems/unlock-system.js index 625e307..1662820 100644 --- a/public/break_escape/js/systems/unlock-system.js +++ b/public/break_escape/js/systems/unlock-system.js @@ -392,10 +392,14 @@ export function handleUnlock(lockable, type) { item.scenarioData.type === 'keycard' ); - // Check if any physical card matches - const hasValidCard = keycards.some(card => - requiredCardIds.includes(card.scenarioData.card_id || card.scenarioData.key_id) + // Helper to get card ID (standard: card_id, legacy: key_id) + const getCardId = (cardData) => cardData.card_id || cardData.key_id; + + // Find a matching physical keycard + const matchingKeycard = keycards.find(card => + requiredCardIds.includes(getCardId(card.scenarioData)) ); + const hasValidCard = !!matchingKeycard; // Check for RFID cloner with saved cards const cloner = window.inventory.items.find(item => @@ -408,7 +412,7 @@ export function handleUnlock(lockable, type) { // Check if any saved card matches const hasValidClone = savedCards.some(card => - requiredCardIds.includes(card.card_id || card.key_id) + requiredCardIds.includes(getCardId(card)) ); console.log('RFID CHECK', { @@ -418,16 +422,32 @@ export function handleUnlock(lockable, type) { keycardsCount: keycards.length, savedCardsCount: savedCards.length, hasValidCard, - hasValidClone + hasValidClone, + matchingKeycard: matchingKeycard?.scenarioData?.name }); - if (keycards.length > 0 || savedCards.length > 0) { + // PRIORITY 1: If player has a matching physical keycard, open door directly + if (hasValidCard) { + const cardName = matchingKeycard.scenarioData.name || 'Keycard'; + console.log(`RFID DIRECT UNLOCK with physical keycard: ${cardName}`); + + // Notify server and unlock + notifyServerUnlock(lockable, type, 'rfid').then(serverResponse => { + unlockTarget(lockable, type, lockable.layer, serverResponse); + window.gameAlert(`Door opened with ${cardName}`, 'success', 'Access Granted', 3000); + }).catch(error => { + console.error('RFID unlock failed:', error); + window.gameAlert('Access denied', 'error', 'Error', 3000); + }); + } + // PRIORITY 2: If player has RFID cloner, open minigame to emulate or read cards + else if (hasCloner) { // Start RFID minigame in unlock mode window.startRFIDMinigame(lockable, type, { mode: 'unlock', - requiredCardIds: requiredCardIds, // Pass array + requiredCardIds: requiredCardIds, acceptsUIDOnly: acceptsUIDOnly, - availableCards: keycards, + availableCards: keycards, // Cards they can read in the minigame hasCloner: hasCloner, onComplete: async (success) => { if (success) { @@ -440,7 +460,9 @@ export function handleUnlock(lockable, type) { } } }); - } else { + } + // PRIORITY 3: No valid keycard and no cloner + else { console.log('NO RFID CARDS OR CLONER AVAILABLE'); window.gameAlert('Requires RFID keycard', 'error', 'Access Denied', 4000); } diff --git a/scenarios/secgen_vm_lab/scenario.json.erb b/scenarios/secgen_vm_lab/scenario.json.erb index f8648d2..1d68e6b 100644 --- a/scenarios/secgen_vm_lab/scenario.json.erb +++ b/scenarios/secgen_vm_lab/scenario.json.erb @@ -97,7 +97,7 @@ { "type": "keycard", "name": "Server Room Keycard", - "keyId": "server_keycard", + "card_id": "server_keycard", "takeable": true, "observations": "A keycard with 'SERVER ROOM' printed on it" } @@ -122,7 +122,7 @@ ] }, "server_room": { - "type": "room_server", + "type": "room_servers", "connections": { "south": "flag_room" }, diff --git a/scenarios/test-rfid-multiprotocol/scenario.json.erb b/scenarios/test-rfid-multiprotocol/scenario.json.erb index 6087b6f..8a0dbd3 100644 --- a/scenarios/test-rfid-multiprotocol/scenario.json.erb +++ b/scenarios/test-rfid-multiprotocol/scenario.json.erb @@ -85,28 +85,6 @@ "takeable": true, "observations": "A portable multi-tool for RFID scanning and emulation. Essential for this test." } - ], - "doors": [ - { - "roomId": "lobby", - "connectedRoom": "em4100_room", - "direction": "north", - "x": 200, - "y": 100, - "locked": true, - "lockType": "rfid", - "requires": ["employee_badge"] - }, - { - "roomId": "lobby", - "connectedRoom": "mifare_weak_room", - "direction": "north", - "x": 400, - "y": 100, - "locked": true, - "lockType": "rfid", - "requires": ["hotel_keycard"] - } ] }, "em4100_room": { @@ -116,6 +94,9 @@ "south": "lobby", "north": "mifare_custom_room" }, + "locked": true, + "lockType": "rfid", + "requires": ["employee_badge"], "npcs": [], "objects": [ { @@ -129,24 +110,6 @@ "note_content": "You successfully cloned an EM4100 card!\n\nThis protocol:\n• 125kHz frequency\n• No encryption whatsoever\n• Instant clone - no attack needed\n• Used in: Old hotels, parking garages\n\nProceed north to test MIFARE Classic weak defaults.", "observations": "EM4100 test passed" } - ], - "doors": [ - { - "roomId": "em4100_room", - "connectedRoom": "lobby", - "direction": "south", - "x": 200, - "y": 500, - "locked": false - }, - { - "roomId": "em4100_room", - "connectedRoom": "mifare_custom_room", - "direction": "north", - "x": 300, - "y": 100, - "locked": false - } ] }, "mifare_weak_room": { @@ -156,6 +119,9 @@ "south": "lobby", "north": "mifare_custom_room" }, + "locked": true, + "lockType": "rfid", + "requires": ["hotel_keycard"], "npcs": [], "objects": [ { @@ -169,24 +135,6 @@ "note_content": "You cloned a MIFARE Classic with default keys!\n\nThis protocol:\n• 13.56MHz NFC frequency\n• Uses encryption but keeps factory defaults (FFFFFFFFFFFF)\n• Dictionary attack succeeds instantly (~95% success)\n• Used in: Cheap hotels, old transit cards\n\nProceed north to test MIFARE Custom Keys (requires attack).", "observations": "MIFARE weak defaults test passed" } - ], - "doors": [ - { - "roomId": "mifare_weak_room", - "connectedRoom": "lobby", - "direction": "south", - "x": 400, - "y": 500, - "locked": false - }, - { - "roomId": "mifare_weak_room", - "connectedRoom": "mifare_custom_room", - "direction": "north", - "x": 300, - "y": 100, - "locked": false - } ] }, "mifare_custom_room": { @@ -238,18 +186,6 @@ "takeable": false, "observations": "A guard's corporate badge - can be scanned with RFID cloner" } - ], - "doors": [ - { - "roomId": "mifare_custom_room", - "connectedRoom": "desfire_room", - "direction": "north", - "x": 300, - "y": 100, - "locked": true, - "lockType": "rfid", - "requires": ["corporate_badge"] - } ] }, "desfire_room": { @@ -258,6 +194,9 @@ "connections": { "south": "mifare_custom_room" }, + "locked": true, + "lockType": "rfid", + "requires": ["corporate_badge"], "npcs": [ { "id": "guard_high", @@ -311,16 +250,6 @@ "takeable": true, "observations": "A master override card - universal access" } - ], - "doors": [ - { - "roomId": "desfire_room", - "connectedRoom": "mifare_custom_room", - "direction": "south", - "x": 300, - "y": 500, - "locked": false - } ] } } diff --git a/scenarios/test-rfid/scenario.json.erb b/scenarios/test-rfid/scenario.json.erb index 732baa4..afb7e4b 100644 --- a/scenarios/test-rfid/scenario.json.erb +++ b/scenarios/test-rfid/scenario.json.erb @@ -59,18 +59,6 @@ "note_content": "All high-security areas require RFID badge access. Master keycards (FF prefix) have universal access. Standard employee badges (01 prefix) have limited access.\n\nThe guard in this room has the master keycard. If you have an RFID cloner, you can scan their badge to clone it.", "observations": "Instructions about the RFID security system" } - ], - "doors": [ - { - "roomId": "test_lobby", - "connectedRoom": "test_secure", - "direction": "north", - "x": 300, - "y": 100, - "locked": true, - "lockType": "rfid", - "requires": ["master_keycard"] - } ] }, "test_secure": { @@ -79,6 +67,9 @@ "connections": { "south": "test_lobby" }, + "locked": true, + "lockType": "rfid", + "requires": ["master_keycard"], "npcs": [], "objects": [ { @@ -106,16 +97,6 @@ "takeable": true, "observations": "The CEO's personal keycard. Ultimate access level." } - ], - "doors": [ - { - "roomId": "test_secure", - "connectedRoom": "test_lobby", - "direction": "south", - "x": 300, - "y": 500, - "locked": false - } ] } }