diff --git a/assets/icons/nfc-waves.png b/assets/icons/nfc-waves.png index 9f301ed..f939a0c 100644 Binary files a/assets/icons/nfc-waves.png and b/assets/icons/nfc-waves.png differ diff --git a/assets/icons/rfid-icon.png b/assets/icons/rfid-icon.png index 9f301ed..a2a1b4e 100644 Binary files a/assets/icons/rfid-icon.png and b/assets/icons/rfid-icon.png differ diff --git a/js/minigames/helpers/chat-helpers.js b/js/minigames/helpers/chat-helpers.js index 4b5c99d..7bb199a 100644 --- a/js/minigames/helpers/chat-helpers.js +++ b/js/minigames/helpers/chat-helpers.js @@ -237,56 +237,75 @@ export function processGameActionTags(tags, ui) { window.eventDispatcher.emit('npc_became_hostile', { npcId }); } case 'clone_keycard': - if (param) { - const [cardName, cardHex] = param.split('|').map(s => s.trim()); - - // Check if player has RFID cloner - const hasCloner = window.inventory.items.some(item => - item && item.scenarioData && - item.scenarioData.type === 'rfid_cloner' - ); - - if (!hasCloner) { - result.message = '⚠️ You need an RFID cloner to clone cards'; - if (ui) ui.showNotification(result.message, 'warning'); - break; - } - - // Generate card data - const cardData = { - name: cardName, - rfid_hex: cardHex, - rfid_facility: parseInt(cardHex.substring(0, 2), 16), - rfid_card_number: parseInt(cardHex.substring(2, 6), 16), - rfid_protocol: 'EM4100', - type: 'keycard', - key_id: `cloned_${cardName.toLowerCase().replace(/\s+/g, '_')}` - }; - - // Set pending conversation return (MINIMAL CONTEXT!) - // Conversation state automatically managed by npcConversationStateManager - window.pendingConversationReturn = { - npcId: window.currentConversationNPCId, - type: window.currentConversationMinigameType || 'person-chat' - }; - - // Start RFID minigame in clone mode - if (window.startRFIDMinigame) { - window.startRFIDMinigame(null, null, { - mode: 'clone', - cardToClone: cardData - }); - - result.success = true; - result.message = `📡 Starting card clone: ${cardName}`; - console.log('🔐 Started RFID clone minigame for:', cardName); - } else { - result.message = '⚠️ RFID minigame not available'; - console.warn('startRFIDMinigame not found'); - } - } else { - result.message = '⚠️ clone_keycard tag missing parameters (name|hex)'; + // Parameter is the card_id to clone + // Look up card data from NPC's rfidCard property + const cardId = param; + + if (!cardId) { + result.message = '⚠️ clone_keycard tag missing card ID parameter'; console.warn(result.message); + break; + } + + // Check if player has RFID cloner + const hasCloner = window.inventory.items.some(item => + item && item.scenarioData && + item.scenarioData.type === 'rfid_cloner' + ); + + if (!hasCloner) { + result.message = '⚠️ You need an RFID cloner to clone cards'; + if (ui) ui.showNotification(result.message, 'warning'); + break; + } + + // Get NPC and their card data + const cloneNpcId = window.currentConversationNPCId; + let cardData = null; + + if (cloneNpcId && window.npcManager) { + const npc = window.npcManager.getNPC(cloneNpcId); + if (npc?.rfidCard && npc.rfidCard.card_id === cardId) { + // Use NPC's rfidCard data + cardData = { + name: npc.rfidCard.name || cardId, + card_id: npc.rfidCard.card_id, + rfid_protocol: npc.rfidCard.rfid_protocol || 'EM4100', + type: 'keycard' + }; + } + } + + // Fallback if NPC card not found + if (!cardData) { + cardData = { + name: cardId, + card_id: cardId, + rfid_protocol: 'EM4100', + type: 'keycard' + }; + } + + // Set pending conversation return (MINIMAL CONTEXT!) + // Conversation state automatically managed by npcConversationStateManager + window.pendingConversationReturn = { + npcId: window.currentConversationNPCId, + type: window.currentConversationMinigameType || 'person-chat' + }; + + // Start RFID minigame in clone mode + if (window.startRFIDMinigame) { + window.startRFIDMinigame(null, null, { + mode: 'clone', + cardToClone: cardData + }); + + result.success = true; + result.message = `📡 Starting card clone: ${cardData.name}`; + console.log('🔐 Started RFID clone minigame for:', cardData.name); + } else { + result.message = '⚠️ RFID minigame not available'; + console.warn('startRFIDMinigame not found'); } break; diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js index 793b23d..40082ee 100644 --- a/js/minigames/person-chat/person-chat-minigame.js +++ b/js/minigames/person-chat/person-chat-minigame.js @@ -337,6 +337,8 @@ export class PersonChatMinigame extends MinigameScene { // This is important because other NPCs may have changed global variables if (this.inkEngine && this.inkEngine.story) { npcConversationStateManager.syncGlobalVariablesToStory(this.inkEngine.story); + // Also sync inventory-based variables on initial load + npcConversationStateManager.syncInventoryVariablesToStory(this.inkEngine.story, this.npc); } @@ -344,7 +346,9 @@ export class PersonChatMinigame extends MinigameScene { // This is critical because Ink evaluates conditionals when continue() is called if (this.inkEngine && this.inkEngine.story) { npcConversationStateManager.syncGlobalVariablesToStory(this.inkEngine.story); - console.log('🔄 Re-synced global variables before showing dialogue'); + // Also sync inventory-based variables (has_keycard, has_rfid_cloner, card protocols, etc.) + npcConversationStateManager.syncInventoryVariablesToStory(this.inkEngine.story, this.npc); + console.log('🔄 Re-synced global and inventory variables before showing dialogue'); } this.isConversationActive = true; diff --git a/js/minigames/rfid/rfid-minigame.js b/js/minigames/rfid/rfid-minigame.js index 9f689c3..8d7d860 100644 --- a/js/minigames/rfid/rfid-minigame.js +++ b/js/minigames/rfid/rfid-minigame.js @@ -40,6 +40,7 @@ export class RFIDMinigame extends MinigameScene { this.availableCards = params.availableCards || []; // For unlock mode this.hasCloner = params.hasCloner || false; // For unlock mode this.cardToClone = params.cardToClone; // For clone mode + this.isLockingAttempt = this.requiredCardIds.length > 0; // True if trying to unlock a specific lock, false if just browsing // Components this.ui = null; @@ -99,11 +100,11 @@ export class RFIDMinigame extends MinigameScene { // Support both card_id (new) and key_id (legacy) const cardId = card.scenarioData?.card_id || card.scenarioData?.key_id || card.key_id; - const isCorrect = this.requiredCardIds.includes(cardId); + const isCorrect = !this.isLockingAttempt || this.requiredCardIds.includes(cardId); if (isCorrect) { this.animations.showTapSuccess(); - this.ui.showSuccess('Access Granted'); + this.ui.showSuccess(this.isLockingAttempt ? 'Access Granted' : 'Card Read'); setTimeout(() => { this.complete(true); @@ -128,14 +129,14 @@ export class RFIDMinigame extends MinigameScene { // Support both card_id (new) and key_id (legacy) const cardId = savedCard.card_id || savedCard.key_id; - const isCorrect = this.requiredCardIds.includes(cardId); + const isCorrect = !this.isLockingAttempt || this.requiredCardIds.includes(cardId); // Check if UID-only emulation (MIFARE DESFire without master key) const protocol = savedCard.rfid_protocol || 'EM4100'; const isUIDOnly = protocol === 'MIFARE_DESFire' && !savedCard.rfid_data?.masterKeyKnown; - // If UID-only and door doesn't accept it, reject - if (isUIDOnly && !this.acceptsUIDOnly) { + // If UID-only and door doesn't accept it, reject (only when attempting to unlock) + if (this.isLockingAttempt && isUIDOnly && !this.acceptsUIDOnly) { this.animations.showEmulationFailure(); this.ui.showError('Reader requires full authentication'); @@ -160,7 +161,7 @@ export class RFIDMinigame extends MinigameScene { if (isCorrect) { this.animations.showEmulationSuccess(); - this.ui.showSuccess('Access Granted'); + this.ui.showSuccess(this.isLockingAttempt ? 'Access Granted' : 'Card Emulated'); // Emit event if (window.eventDispatcher) { @@ -177,7 +178,8 @@ export class RFIDMinigame extends MinigameScene { setTimeout(() => { this.complete(true); }, 2000); - } else { + } else if (this.isLockingAttempt) { + // Only show "Access Denied" when actually trying to unlock a door this.animations.showEmulationFailure(); this.ui.showError('Access Denied'); @@ -192,6 +194,13 @@ export class RFIDMinigame extends MinigameScene { }); } + setTimeout(() => { + this.ui.showSavedCards(); + }, 1500); + } else { + // When just browsing, show card info instead of error + this.ui.showSuccess(`Card Info: ${savedCard.name} (${protocol})`); + setTimeout(() => { this.ui.showSavedCards(); }, 1500); @@ -391,11 +400,11 @@ export function startRFIDMinigame(lockable, type, params) { // Initialize framework if needed if (!window.MinigameFramework.mainGameScene && window.game) { - window.MinigameFramework.init(window.game.scene.scenes[0]); + window.MinigameFramework.init(window.game); } // Start minigame - window.MinigameFramework.startMinigame('rfid', lockable, params); + window.MinigameFramework.startMinigame('rfid', null, params); } /** diff --git a/js/minigames/rfid/rfid-ui.js b/js/minigames/rfid/rfid-ui.js index 4c1393b..3f78b02 100644 --- a/js/minigames/rfid/rfid-ui.js +++ b/js/minigames/rfid/rfid-ui.js @@ -32,10 +32,11 @@ export class RFIDUIRenderer { // Create Flipper Zero frame const flipper = this.createFlipperFrame(); + // Append to container first so screen element is in the DOM + this.container.appendChild(flipper); + // Show main menu this.showMainMenu('unlock'); - - this.container.appendChild(flipper); } /** @@ -47,14 +48,15 @@ export class RFIDUIRenderer { // Create Flipper Zero frame const flipper = this.createFlipperFrame(); + // Append to container first so screen element is in the DOM + this.container.appendChild(flipper); + // Auto-start reading if card provided if (this.minigame.params.cardToClone) { this.showReadingScreen(); } else { this.showMainMenu('clone'); } - - this.container.appendChild(flipper); } /** @@ -230,7 +232,7 @@ export class RFIDUIRenderer { const cardItem = document.createElement('div'); cardItem.className = 'flipper-menu-item'; cardItem.textContent = `> ${card.name}`; - cardItem.addEventListener('click', () => this.showEmulationScreen(card)); + cardItem.addEventListener('click', () => this.showCardDetails(card)); cardList.appendChild(cardItem); }); @@ -245,6 +247,70 @@ export class RFIDUIRenderer { screen.appendChild(back); } + /** + * Show card details with Emulate button + * @param {Object} card - Card to display + */ + showCardDetails(card) { + const screen = this.getScreen(); + screen.innerHTML = ''; + + const displayData = this.dataManager.getCardDisplayData(card); + + // Breadcrumb + const breadcrumb = document.createElement('div'); + breadcrumb.className = 'flipper-breadcrumb'; + breadcrumb.textContent = 'RFID > Saved > Details'; + screen.appendChild(breadcrumb); + + // Card icon + const icon = document.createElement('div'); + icon.className = 'rfid-emulate-icon'; + icon.textContent = '🔑'; + screen.appendChild(icon); + + // Protocol with color indicator + const protocolDiv = document.createElement('div'); + protocolDiv.className = 'flipper-info'; + protocolDiv.style.borderLeft = `4px solid ${displayData.color}`; + protocolDiv.style.paddingLeft = '8px'; + protocolDiv.innerHTML = `${displayData.icon} ${displayData.protocolName}`; + screen.appendChild(protocolDiv); + + // Card name + const name = document.createElement('div'); + name.className = 'flipper-card-name'; + name.textContent = card.name || 'Card'; + screen.appendChild(name); + + // Card data fields + const data = document.createElement('div'); + data.className = 'flipper-card-data'; + + // Show first 3 fields (most relevant for emulation) + displayData.fields.slice(0, 3).forEach(field => { + const fieldDiv = document.createElement('div'); + fieldDiv.innerHTML = `${field.label}: ${field.value}`; + data.appendChild(fieldDiv); + }); + + screen.appendChild(data); + + // Emulate button + const emulateBtn = document.createElement('div'); + emulateBtn.className = 'flipper-menu-item'; + emulateBtn.textContent = '> Emulate'; + emulateBtn.addEventListener('click', () => this.showEmulationScreen(card)); + screen.appendChild(emulateBtn); + + // Back button + const back = document.createElement('div'); + back.className = 'flipper-button-back'; + back.textContent = '← Back'; + back.addEventListener('click', () => this.showSavedCards()); + screen.appendChild(back); + } + /** * Show emulation screen (supports all protocols) * @param {Object} card - Card to emulate @@ -259,7 +325,7 @@ export class RFIDUIRenderer { // Breadcrumb const breadcrumb = document.createElement('div'); breadcrumb.className = 'flipper-breadcrumb'; - breadcrumb.textContent = 'RFID > Saved > Emulate'; + breadcrumb.textContent = 'RFID > Saved > Emulating'; screen.appendChild(breadcrumb); // Emulation icon diff --git a/js/systems/doors.js b/js/systems/doors.js index fa6e516..c015300 100644 --- a/js/systems/doors.js +++ b/js/systems/doors.js @@ -221,11 +221,19 @@ export function createDoorSpritesForRoom(roomId, position) { // }); // console.log(`Door depth: ${doorSprite.depth} (roomDepth: ${doorY}, between tiles and sprites)`); - // Get lock properties from the destination room (the room you're trying to enter) + // Get lock properties from either the door object or the destination room + // First check if this door has explicit lock properties in the scenario + const doorDefinition = roomData.doors?.find(d => + d.connectedRoom === connectedRoom && d.direction === direction + ); + + // Lock properties can come from the door definition or the connected room + const lockProps = doorDefinition || {}; const connectedRoomData = gameScenario.rooms[connectedRoom]; // Check for both keyPins (camelCase) and key_pins (snake_case) in the room data - const keyPinsArray = connectedRoomData?.keyPins || connectedRoomData?.key_pins; + const keyPinsArray = lockProps.keyPins || lockProps.key_pins || + connectedRoomData?.keyPins || connectedRoomData?.key_pins; // DEBUG: Log what we're finding if (connectedRoomData?.locked) { @@ -247,11 +255,11 @@ export function createDoorSpritesForRoom(roomId, position) { worldX: doorX, worldY: doorY, open: false, - locked: connectedRoomData?.locked || false, - lockType: connectedRoomData?.lockType || null, - requires: connectedRoomData?.requires || null, + locked: lockProps.locked !== undefined ? lockProps.locked : (connectedRoomData?.locked || false), + lockType: lockProps.lockType || connectedRoomData?.lockType || null, + requires: lockProps.requires || connectedRoomData?.requires || null, keyPins: keyPinsArray, // Include keyPins from scenario (supports both cases) - difficulty: connectedRoomData?.difficulty // Include difficulty from scenario + difficulty: lockProps.difficulty || connectedRoomData?.difficulty // Include difficulty from scenario }; // Debug door properties diff --git a/js/systems/interactions.js b/js/systems/interactions.js index 6626dd9..38018a1 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -609,6 +609,23 @@ export function handleObjectInteraction(sprite) { } // If it's not in inventory, let it fall through to the takeable logic below } + + // Handle the RFID Cloner (Flipper Zero) - only open minigame if it's already in inventory + if (sprite.scenarioData.type === "rfid_cloner") { + // Check if this is an inventory item (clicked from inventory) + const isInventoryItem = sprite.objectId && sprite.objectId.startsWith('inventory_'); + + if (isInventoryItem && window.startRFIDMinigame) { + console.log('Starting RFID minigame from inventory (unlock mode)'); + window.startRFIDMinigame(null, null, { + mode: 'unlock', + availableCards: [], + hasCloner: true + }); + return; + } + // If it's not in inventory, let it fall through to the takeable logic below + } // Handle the Lockpick Set - pick it up if takeable, or use it if in inventory if (sprite.scenarioData.type === "lockpick" || sprite.scenarioData.type === "lockpickset") { diff --git a/js/systems/npc-conversation-state.js b/js/systems/npc-conversation-state.js index 4b9c610..d6ffe9d 100644 --- a/js/systems/npc-conversation-state.js +++ b/js/systems/npc-conversation-state.js @@ -330,6 +330,78 @@ class NPCConversationStateManager { } }); } + + /** + * Sync inventory-based variables to story (items player has, tools available, etc.) + * This checks what the player has in inventory and sets corresponding Ink variables. + * Only sets variables that are declared in the story to avoid StoryException errors. + * @param {Object} story - Ink story object + * @param {Object} npc - NPC data (may have rfidCard property) + */ + syncInventoryVariablesToStory(story, npc = null) { + if (!story || !window.inventory?.items) return; + + // Helper to safely set a variable only if it exists in the story + const safeSetVariable = (varName, value) => { + try { + if (story.variablesState[varName] !== undefined) { + story.variablesState[varName] = value; + return true; + } + } catch (error) { + // Variable doesn't exist in this story, skip it + } + return false; + }; + + try { + // Check for RFID cloner in inventory + const hasRFIDCloner = window.inventory.items.some(item => + item?.scenarioData?.type === 'rfid_cloner' + ); + if (safeSetVariable('has_rfid_cloner', hasRFIDCloner)) { + console.log(`📱 Synced has_rfid_cloner = ${hasRFIDCloner}`); + } + + // Check for keycards/items in inventory + const hasItems = window.inventory.items.length > 0; + if (safeSetVariable('has_keycard', hasItems)) { + console.log(`🔑 Synced has_keycard = ${hasItems}`); + } + + // If NPC has RFID card info, sync card protocol details + if (npc?.rfidCard) { + if (safeSetVariable('card_protocol', npc.rfidCard.rfid_protocol || '')) { + console.log(`📡 Synced card_protocol = ${npc.rfidCard.rfid_protocol}`); + } + if (safeSetVariable('card_name', npc.rfidCard.name || '')) { + console.log(`📡 Synced card_name = ${npc.rfidCard.name}`); + } + if (safeSetVariable('card_card_id', npc.rfidCard.card_id || '')) { + console.log(`📡 Synced card_card_id = ${npc.rfidCard.card_id}`); + } + + // Set protocol-specific flags + const isInstantClone = npc.rfidCard.rfid_protocol === 'EM4100' || + npc.rfidCard.rfid_protocol === 'MIFARE_Classic_Weak_Defaults'; + if (safeSetVariable('card_instant_clone', isInstantClone)) { + console.log(`⚡ Synced card_instant_clone = ${isInstantClone}`); + } + + const needsAttack = npc.rfidCard.rfid_protocol === 'MIFARE_Classic_Custom_Keys'; + if (safeSetVariable('card_needs_attack', needsAttack)) { + console.log(`🔓 Synced card_needs_attack = ${needsAttack}`); + } + + const isUIDOnly = npc.rfidCard.rfid_protocol === 'MIFARE_DESFire'; + if (safeSetVariable('card_uid_only', isUIDOnly)) { + console.log(`🛡️ Synced card_uid_only = ${isUIDOnly}`); + } + } + } catch (error) { + console.warn(`⚠️ Error syncing inventory variables to story:`, error); + } + } } // Create global instance diff --git a/scenarios/ink/rfid-guard-custom.json b/scenarios/ink/rfid-guard-custom.json new file mode 100644 index 0000000..afc713f --- /dev/null +++ b/scenarios/ink/rfid-guard-custom.json @@ -0,0 +1 @@ +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["#","^speaker:npc","/#","^Hey. I'm in charge of corporate security.","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^This badge uses MIFARE Classic with custom encryption keys.","\n","^Much more secure than those old EM4100 cards.","\n",{"->":"start.9"},null]}],"nop","\n",{"->":"hub"},null],"hub":[["ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Ask about badge security","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.4"},{"c-0":["\n",{"->":"ask_security"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},{"VAR?":"has_rfid_cloner"},"&&",{"VAR?":"card_needs_attack"},"&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Try to scan their badge","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.14"},{"c-0":["\n","#","^speaker:player","/#","^You try to scan, but it's encrypted...","\n",{"->":"needs_attack"},null]}]}],"nop","\n","ev","str","^Chat about security","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Leave","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"chat_security"},null],"c-1":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^Stay safe.","\n",{"->":"hub"},null]}],null],"ask_security":[["#","^speaker:npc","/#","^This badge? It's a MIFARE Classic 1K with custom encryption keys.","\n","^Much better than the factory defaults some companies use.","\n","^Can't just clone these with a quick scan. The crypto is... well, it's broken technically, but it takes time to crack.","\n","ev","str","^How long to crack?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Interesting...","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","#","^speaker:npc","/#","^With the right tools? Maybe 30 seconds using a Darkside attack.","\n","^But most people don't have those tools.","\n",{"->":"hub"},null],"c-1":["\n",{"->":"hub"},null]}],null],"chat_security":["#","^speaker:npc","/#","^Corporate security is no joke. We take access control seriously.","\n","^All our badges use custom keys. Random generation, changed quarterly.","\n","^The CEO even has a DESFire card - that's military-grade encryption.","\n",{"->":"hub"},null],"needs_attack":[["#","^speaker:npc","/#","^What are you doing?","\n","#","^speaker:player","/#","^Oh, just... checking my phone!","\n","#","^speaker:npc","/#","^That looked like you were trying to scan my badge.","\n","^You'd need to run a proper attack to get this one. Can't just quick-clone it.","\n","ev","str","^Play it cool","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Tell the truth","/str","/ev",{"*":".^.c-1","flg":20},{"c-0":["\n","#","^speaker:player","/#","^Sorry, my device sometimes picks up NFC signals by accident.","\n","#","^speaker:npc","/#","^Uh huh. Sure.","\n",{"->":"hub"},{"#f":5}],"c-1":["\n","#","^speaker:player","/#","^You're right - I was trying to clone it. But it's encrypted.","\n","#","^speaker:npc","/#","^Yeah, that's the point of custom keys.","\n","^You'd need to be close for about 30 seconds to run a Darkside attack.","\n","^Good luck with that while I'm watching!","\n",{"->":"hub"},{"#f":5}]}],null],"global decl":["ev",false,{"VAR=":"has_keycard"},false,{"VAR=":"has_rfid_cloner"},"str","^","/str",{"VAR=":"card_protocol"},"str","^","/str",{"VAR=":"card_name"},"str","^","/str",{"VAR=":"card_card_id"},false,{"VAR=":"card_needs_attack"},"str","^","/str",{"VAR=":"card_uid"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/ink/rfid-guard-low.json b/scenarios/ink/rfid-guard-low.json new file mode 100644 index 0000000..c505e05 --- /dev/null +++ b/scenarios/ink/rfid-guard-low.json @@ -0,0 +1 @@ +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["#","^speaker:npc","/#","^Hi! I work security here at the building.","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^This badge on my belt? Just a basic EM4100 card. Nothing fancy.","\n",{"->":"start.9"},null]}],"nop","\n",{"->":"hub"},null],"hub":[["ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Ask about the badge","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.4"},{"c-0":["\n",{"->":"ask_badge"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},{"VAR?":"has_rfid_cloner"},"&&",{"VAR?":"card_instant_clone"},"&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Casually scan their badge","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.14"},{"c-0":["^ ","#","^clone_keycard:","ev",{"VAR?":"card_card_id"},"out","/ev","/#","\n","#","^speaker:player","/#","^You position your Flipper Zero near their badge while chatting...","\n","#","^speaker:npc","/#","^...and that's when I realized I'd left my lunch at home!","\n",{"->":"cloned"},null]}]}],"nop","\n","ev","str","^Chat about the job","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Leave","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"chat_job"},null],"c-1":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^See you around!","\n",{"->":"hub"},null]}],null],"ask_badge":["#","^speaker:npc","/#","^Oh, this old thing? Yeah, it's one of those 125kHz proximity cards.","\n","^Pretty basic technology. I just wave it at the reader and it opens.","\n","^No PIN or anything - just the card itself.","\n",{"->":"hub"},null],"chat_job":["#","^speaker:npc","/#","^The job's not bad. Mostly just sitting at the desk and checking people in.","\n","^I get to read a lot during my shifts. The night shift especially is pretty quiet.","\n",{"->":"hub"},null],"cloned":[["#","^speaker:player","/#","^[You've successfully cloned the ","ev",{"VAR?":"card_name"},"out","/ev","^!]","\n","#","^speaker:npc","/#","^Anyway, I should probably get back to my post.","\n","ev","str","^Thanks for chatting","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^No problem! Have a good day!","\n",{"->":"hub"},null]}],null],"global decl":["ev",false,{"VAR=":"has_keycard"},false,{"VAR=":"has_rfid_cloner"},"str","^","/str",{"VAR=":"card_protocol"},"str","^","/str",{"VAR=":"card_name"},"str","^","/str",{"VAR=":"card_card_id"},false,{"VAR=":"card_instant_clone"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/ink/rfid-security-guard-fixed.json b/scenarios/ink/rfid-security-guard-fixed.json new file mode 100644 index 0000000..c24b74b --- /dev/null +++ b/scenarios/ink/rfid-security-guard-fixed.json @@ -0,0 +1 @@ +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"conversation_count"},1,"+",{"VAR=":"conversation_count","re":true},"/ev","#","^speaker:npc","/#","^Hey there. I'm the security guard for this facility.","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^I've got the master keycard that opens the secure room.","\n","ev",{"VAR?":"card_protocol"},"str","^EM4100","/str","==","/ev",[{"->":".^.b","c":true},{"b":["\n","^It's a basic EM4100 card - nothing fancy.","\n",{"->":".^.^.^.11"},null]}],"nop","\n","ev",{"VAR?":"card_security"},"str","^medium","/str","==","/ev",[{"->":".^.b","c":true},{"b":["\n","^This one's got proper encryption. Corporate security.","\n",{"->":".^.^.^.21"},null]}],"nop","\n",{"->":"start.15"},null]}],"nop","\n",{"->":"hub"},null],"hub":[["ev",{"VAR?":"has_keycard"},{"VAR?":"card_instant_clone"},"!","&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Ask about the keycard security","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.7"},{"c-0":["\n",{"->":"ask_security"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Ask about the keycard","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.13"},{"c-0":["\n",{"->":"ask_keycard"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},{"VAR?":"has_rfid_cloner"},"&&",{"VAR?":"card_instant_clone"},"&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Subtly scan their badge","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.23"},{"c-0":["^ ","#","^clone_keycard:","ev",{"VAR?":"card_card_id"},"out","/ev","/#","\n","#","^speaker:player","/#","^You casually position your Flipper Zero near their badge...","\n",{"->":"cloned_success"},null]}]}],"nop","\n","ev",{"VAR?":"has_keycard"},{"VAR?":"has_rfid_cloner"},"&&",{"VAR?":"card_needs_attack"},"&&","/ev",[{"->":".^.b","c":true},{"b":["\n","ev","str","^Scan badge (requires attack)","/str","/ev",{"*":".^.c-0","flg":4},{"->":"hub.0.33"},{"c-0":["\n","#","^speaker:player","/#","^You try to scan their badge, but it's encrypted.","\n","#","^speaker:player","/#","^You'll need to run a Darkside attack - this will take about 30 seconds.","\n",{"->":"needs_attack"},null]}]}],"nop","\n","ev","str","^Just browsing","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^Alright, let me know if you need anything.","\n",{"->":"hub"},null]}],null],"ask_security":["#","^speaker:npc","/#","ev",{"VAR?":"card_security"},"str","^low","/str","==","/ev",[{"->":".^.b","c":true},{"b":["\n","^Honestly? It's just a basic proximity card. Nothing special.","\n","^The company's been meaning to upgrade for years...","\n",{"->":".^.^.^.12"},null]}],[{"->":".^.b"},{"b":["\n","^This card uses ","ev",{"VAR?":"card_protocol"},"out","/ev","^ with custom encryption.","\n","^Pretty secure stuff. Can't just clone these easily.","\n",{"->":".^.^.^.12"},null]}],"nop","\n",{"->":"hub"},null],"ask_keycard":[["#","^speaker:npc","/#","^This keycard? Yeah, it's the master access card. Opens everything in the building.","\n","^I can't just hand it to you though - security policy and all that.","\n","ev","str","^Offer to buy it","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Ask if you can borrow it","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Back","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","#","^speaker:npc","/#","^Ha! Nice try, but I can't sell company property. I'd lose my job.","\n",{"->":"hub"},null],"c-1":["\n","#","^speaker:npc","/#","^Sorry, no can do. This thing never leaves my person.","\n",{"->":"hub"},null],"c-2":["\n",{"->":"hub"},null]}],null],"cloned_success":[["#","^speaker:npc","/#","^...So anyway, that's why I love working nights. Much quieter, you know?","\n","^The pay's better too. Plus I get to catch up on my podcasts.","\n","ev","str","^Thanks for the chat!","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Any other secure areas?","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","#","^speaker:npc","/#","^No problem! Stay safe out there.","\n",{"->":"hub"},null],"c-1":["\n","#","^speaker:npc","/#","^Well, there's the CEO's office, but that's on a different floor entirely.","\n","^This master card works for most areas on this level though.","\n",{"->":"hub"},null]}],null],"needs_attack":["#","^speaker:npc","/#","^Hey, what are you doing with that device?","\n","#","^speaker:player","/#","^Oh, just... checking the time!","\n","#","^speaker:npc","/#","^That didn't look like checking the time...","\n","^You'll need to be more subtle. Or find a way to get the card when they're not looking.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"has_keycard"},false,{"VAR=":"has_rfid_cloner"},0,{"VAR=":"conversation_count"},"str","^","/str",{"VAR=":"card_protocol"},"str","^","/str",{"VAR=":"card_name"},"str","^","/str",{"VAR=":"card_card_id"},"str","^","/str",{"VAR=":"card_security"},false,{"VAR=":"card_instant_clone"},false,{"VAR=":"card_needs_attack"},false,{"VAR=":"card_uid_only"},"str","^","/str",{"VAR=":"card_uid"},"str","^","/str",{"VAR=":"card_hex"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/test-rfid-multiprotocol.json b/scenarios/test-rfid-multiprotocol.json index 99ea3a7..0cfef55 100644 --- a/scenarios/test-rfid-multiprotocol.json +++ b/scenarios/test-rfid-multiprotocol.json @@ -1,8 +1,9 @@ { "name": "RFID Multi-Protocol Test", "description": "Comprehensive test for all 4 RFID protocols with attacks", - "scenario_brief": "Test all RFID protocols: EM4100, MIFARE Classic (weak/custom), and DESFire", + "scenario_brief": "Test all RFID protocols in sequence: EM4100 (instant), MIFARE weak (instant), MIFARE custom (attack), and DESFire (UID-only)", "startRoom": "lobby", + "endGoal": "Successfully access all four security levels to test each RFID protocol", "globalVariables": {}, "player": { "id": "player", @@ -23,14 +24,12 @@ "name": "Test Lobby", "type": "room_reception", "connections": { - "north": "low_security", - "east": "medium_security", - "south": "high_security" + "north": ["em4100_room", "mifare_weak_room"] }, "npcs": [ { "id": "guard_low", - "displayName": "Guard (Low Security)", + "displayName": "Guard (EM4100)", "npcType": "person", "position": { "x": 3, "y": 3 }, "spriteSheet": "hacker-red", @@ -40,77 +39,29 @@ }, "storyPath": "scenarios/ink/rfid-guard-low.json", "currentKnot": "start", - "itemsHeld": [ - { - "type": "keycard", - "card_id": "employee_badge", - "rfid_protocol": "EM4100", - "name": "Employee Badge" - } - ] + "rfidCard": { + "card_id": "employee_badge", + "rfid_protocol": "EM4100", + "name": "Employee Badge" + } }, { "id": "guard_weak", - "displayName": "Guard (Weak MIFARE)", - "npcType": "person", - "position": { "x": 6, "y": 3 }, - "spriteSheet": "hacker-blue", - "spriteConfig": { - "idleFrameStart": 20, - "idleFrameEnd": 23 - }, - "storyPath": "scenarios/ink/rfid-guard-weak.json", - "currentKnot": "start", - "itemsHeld": [ - { - "type": "keycard", - "card_id": "hotel_keycard", - "rfid_protocol": "MIFARE_Classic_Weak_Defaults", - "name": "Hotel Keycard" - } - ] - }, - { - "id": "guard_custom", - "displayName": "Guard (Custom Keys)", + "displayName": "Guard (MIFARE Weak)", "npcType": "person", "position": { "x": 9, "y": 3 }, - "spriteSheet": "hacker-green", + "spriteSheet": "hacker-red", "spriteConfig": { "idleFrameStart": 20, "idleFrameEnd": 23 }, - "storyPath": "scenarios/ink/rfid-guard-custom.json", + "storyPath": "scenarios/ink/rfid-guard-low.json", "currentKnot": "start", - "itemsHeld": [ - { - "type": "keycard", - "card_id": "corporate_badge", - "rfid_protocol": "MIFARE_Classic_Custom_Keys", - "name": "Corporate Badge" - } - ] - }, - { - "id": "guard_high", - "displayName": "Guard (DESFire)", - "npcType": "person", - "position": { "x": 12, "y": 3 }, - "spriteSheet": "hacker-yellow", - "spriteConfig": { - "idleFrameStart": 20, - "idleFrameEnd": 23 - }, - "storyPath": "scenarios/ink/rfid-guard-desfire.json", - "currentKnot": "start", - "itemsHeld": [ - { - "type": "keycard", - "card_id": "executive_card", - "rfid_protocol": "MIFARE_DESFire", - "name": "Executive Card" - } - ] + "rfidCard": { + "card_id": "hotel_keycard", + "rfid_protocol": "MIFARE_Classic_Weak_Defaults", + "name": "Hotel Keycard" + } } ], "objects": [ @@ -122,16 +73,25 @@ "takeable": true, "readable": true, "note_title": "RFID Protocol Security Levels", - "note_content": "🟥 LOW SECURITY (Instant Clone):\n • EM4100 (125kHz) - Old tech, no encryption\n • MIFARE Classic (Default Keys) - Factory defaults\n\n🟦 MEDIUM SECURITY (Requires Attack):\n • MIFARE Classic (Custom Keys) - 30 sec Darkside attack\n\n🟩 HIGH SECURITY (UID Only):\n • MIFARE DESFire - Strong encryption, can't crack\n • Only UID emulation works on weak readers", - "observations": "Guide to the 4 RFID protocols" + "note_content": "🟥 LEVEL 1 (Instant Clone):\n • EM4100 (125kHz) - Old tech, no encryption\n • Guard at left has this card on their belt\n • Quick and easy to exploit with Flipper Zero\n\n🟦 LEVEL 2 (Weak Defaults):\n • MIFARE Classic (Default Keys) - Uses factory defaults\n • Guard in middle has this card\n • Still instant to crack despite encryption\n\n🟨 LEVEL 3 (Custom Encryption):\n • MIFARE Classic (Custom Keys) - Requires 30 sec attack\n • Located in Medium Security Room (north from Level 2)\n • Requires Darkside attack\n\n🟩 LEVEL 4 (Strong Encryption):\n • MIFARE DESFire - Can't crack, UID only\n • Located in High Security Room (north from Level 3)\n • Best protection available", + "observations": "Guide to the 4 RFID protocol security levels" + }, + { + "type": "rfid_cloner", + "name": "Flipper Zero", + "saved_cards": [], + "x": 300, + "y": 200, + "takeable": true, + "observations": "A portable multi-tool for RFID scanning and emulation. Essential for this test." } ], "doors": [ { "roomId": "lobby", - "connectedRoom": "low_security", + "connectedRoom": "em4100_room", "direction": "north", - "x": 300, + "x": 200, "y": 100, "locked": true, "lockType": "rfid", @@ -139,32 +99,22 @@ }, { "roomId": "lobby", - "connectedRoom": "medium_security", - "direction": "east", - "x": 600, - "y": 300, + "connectedRoom": "mifare_weak_room", + "direction": "north", + "x": 400, + "y": 100, "locked": true, "lockType": "rfid", "requires": ["hotel_keycard"] - }, - { - "roomId": "lobby", - "connectedRoom": "high_security", - "direction": "south", - "x": 300, - "y": 500, - "locked": true, - "lockType": "rfid", - "requires": ["corporate_badge", "executive_card"], - "acceptsUIDOnly": false } ] }, - "low_security": { - "name": "Low Security Room (EM4100)", + "em4100_room": { + "name": "Level 1: EM4100 (Instant)", "type": "room_office", "connections": { - "south": "lobby" + "south": "lobby", + "north": "mifare_custom_room" }, "npcs": [], "objects": [ @@ -175,27 +125,36 @@ "y": 300, "takeable": true, "readable": true, - "note_title": "✓ EM4100 Test Passed", - "note_content": "You successfully cloned an EM4100 card!\n\nThis protocol:\n• 125kHz frequency\n• No encryption\n• Instant clone\n• Used in: Old hotels, parking garages", + "note_title": "✓ Level 1 Passed: EM4100", + "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": "low_security", + "roomId": "em4100_room", "connectedRoom": "lobby", "direction": "south", - "x": 300, + "x": 200, "y": 500, "locked": false + }, + { + "roomId": "em4100_room", + "connectedRoom": "mifare_custom_room", + "direction": "north", + "x": 300, + "y": 100, + "locked": false } ] }, - "medium_security": { - "name": "Medium Security Room (Weak MIFARE)", + "mifare_weak_room": { + "name": "Level 2: MIFARE Weak (Instant)", "type": "room_office", "connections": { - "west": "lobby" + "south": "lobby", + "north": "mifare_custom_room" }, "npcs": [], "objects": [ @@ -206,40 +165,141 @@ "y": 300, "takeable": true, "readable": true, - "note_title": "✓ MIFARE Weak Defaults Test Passed", - "note_content": "You cloned a MIFARE Classic with default keys!\n\nThis protocol:\n• 13.56MHz NFC\n• Encrypted but uses FFFFFFFFFFFF keys\n• Dictionary attack succeeds instantly (~95%)\n• Used in: Cheap hotels, old transit cards", + "note_title": "✓ Level 2 Passed: MIFARE Weak Defaults", + "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": "medium_security", + "roomId": "mifare_weak_room", "connectedRoom": "lobby", - "direction": "west", - "x": 100, - "y": 300, + "direction": "south", + "x": 400, + "y": 500, + "locked": false + }, + { + "roomId": "mifare_weak_room", + "connectedRoom": "mifare_custom_room", + "direction": "north", + "x": 300, + "y": 100, "locked": false } ] }, - "high_security": { - "name": "High Security Room (Custom MIFARE / DESFire)", - "type": "room_server", + "mifare_custom_room": { + "name": "Level 3: MIFARE Custom (Attack)", + "type": "room_office", "connections": { - "north": "lobby" + "south": ["em4100_room", "mifare_weak_room"], + "north": "desfire_room" }, - "npcs": [], + "npcs": [ + { + "id": "guard_custom", + "displayName": "Guard (Custom Keys)", + "npcType": "person", + "position": { "x": 6, "y": 4 }, + "spriteSheet": "hacker-green", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/rfid-guard-custom.json", + "currentKnot": "start", + "rfidCard": { + "card_id": "corporate_badge", + "rfid_protocol": "MIFARE_Classic_Custom_Keys", + "name": "Corporate Badge" + } + } + ], "objects": [ { "type": "notes", - "name": "Success - High Security", + "name": "Success - MIFARE Custom", "x": 300, "y": 300, "takeable": true, "readable": true, - "note_title": "✓ High Security Protocols Test Passed", - "note_content": "You accessed high security!\n\nMIFARE Classic (Custom Keys):\n• Requires 30-second Darkside attack\n• Used in: Corporate badges, banks\n\nMIFARE DESFire:\n• Can't be cracked - UID only\n• Only works on poorly-configured readers\n• Used in: Government, military", - "observations": "High security test passed" + "note_title": "✓ Level 3 Passed: MIFARE Custom Keys", + "note_content": "You cracked MIFARE Classic with custom keys!\n\nThis protocol:\n• 13.56MHz NFC frequency\n• Uses custom encryption (not factory defaults)\n• Requires Darkside attack (~30 seconds)\n• Used in: Corporate badges, banks\n\nProceed north to test MIFARE DESFire (strongest protection).", + "observations": "MIFARE custom keys attack passed" + }, + { + "type": "keycard", + "name": "Test Corporate Badge", + "card_id": "corporate_badge_physical", + "rfid_protocol": "MIFARE_Classic_Custom_Keys", + "x": 350, + "y": 250, + "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": { + "name": "Level 4: MIFARE DESFire (UID-Only)", + "type": "room_servers", + "connections": { + "south": "mifare_custom_room" + }, + "npcs": [ + { + "id": "guard_high", + "displayName": "Guard (DESFire)", + "npcType": "person", + "position": { "x": 6, "y": 4 }, + "spriteSheet": "hacker-yellow", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/rfid-security-guard-fixed.json", + "currentKnot": "start", + "rfidCard": { + "card_id": "executive_card", + "rfid_protocol": "MIFARE_DESFire", + "name": "Executive Card" + } + } + ], + "objects": [ + { + "type": "notes", + "name": "Success - All Levels Passed", + "x": 300, + "y": 300, + "takeable": true, + "readable": true, + "note_title": "✓ Level 4 & All Tests Complete!", + "note_content": "You've successfully tested all RFID protocols!\n\nMIFARE DESFire (Strongest):\n• 13.56MHz NFC frequency\n• Military-grade AES encryption\n• Can't be cracked - only UID emulation works\n• Requires weakly-configured reader (poor UID validation)\n• Used in: Government, military, high-security\n\nSUMMARY:\n✓ Level 1: EM4100 - Instant clone\n✓ Level 2: MIFARE Weak - Dictionary attack\n✓ Level 3: MIFARE Custom - Darkside attack (30sec)\n✓ Level 4: DESFire - UID emulation only\n\nAll RFID protocols tested successfully!", + "observations": "All protocol tests passed!", + "isEndGoal": true + }, + { + "type": "keycard", + "name": "Executive Card (DESFire)", + "card_id": "executive_card_physical", + "rfid_protocol": "MIFARE_DESFire", + "x": 350, + "y": 250, + "takeable": false, + "observations": "An executive's DESFire card - strongest encryption, UID-only emulation possible" }, { "type": "keycard", @@ -247,18 +307,18 @@ "rfid_protocol": "EM4100", "name": "Master Override Card", "x": 350, - "y": 300, + "y": 350, "takeable": true, - "observations": "A master override card that opens all doors" + "observations": "A master override card - universal access" } ], "doors": [ { - "roomId": "high_security", - "connectedRoom": "lobby", - "direction": "north", + "roomId": "desfire_room", + "connectedRoom": "mifare_custom_room", + "direction": "south", "x": 300, - "y": 100, + "y": 500, "locked": false } ] diff --git a/scenarios/test-rfid.json b/scenarios/test-rfid.json index 7501021..a633a8f 100644 --- a/scenarios/test-rfid.json +++ b/scenarios/test-rfid.json @@ -1,8 +1,9 @@ { "name": "RFID System Test", "description": "Test scenario for RFID keycard lock system", - "scenario_brief": "Test scenario for RFID keycard lock system with Flipper Zero", + "scenario_brief": "Test scenario for RFID keycard lock system with Flipper Zero. Clone the guard's badge to access the secure server room.", "startRoom": "test_lobby", + "endGoal": "Successfully clone the security guard's master keycard and unlock the secure room", "globalVariables": {}, "player": { "id": "player", @@ -11,13 +12,20 @@ "startX": 200, "startY": 200 }, - "startItemsInInventory": [], + "startItemsInInventory": [ + { + "type": "rfid_cloner", + "name": "Flipper Zero", + "saved_cards": [], + "observations": "A portable multi-tool for pentesters. Can read and emulate RFID cards." + } + ], "rooms": { "test_lobby": { "name": "Test Lobby", "type": "room_reception", "connections": { - "east": "test_secure" + "north": "test_secure" }, "npcs": [ { @@ -30,46 +38,16 @@ "idleFrameStart": 20, "idleFrameEnd": 23 }, - "storyPath": "scenarios/ink/rfid-security-guard.json", + "storyPath": "scenarios/ink/rfid-security-guard-fixed.json", "currentKnot": "start", - "itemsHeld": [ - { - "type": "keycard", - "name": "Master Keycard", - "rfid_hex": "FF4A7B9C21", - "rfid_facility": 255, - "rfid_card_number": 18811, - "rfid_protocol": "EM4100", - "key_id": "master_keycard", - "takeable": false, - "observations": "The security guard's master keycard. It has universal access." - } - ] + "rfidCard": { + "card_id": "master_keycard", + "rfid_protocol": "EM4100", + "name": "Master Keycard" + } } ], "objects": [ - { - "type": "keycard", - "name": "Employee Badge", - "rfid_hex": "01AB34CD56", - "rfid_facility": 1, - "rfid_card_number": 43981, - "rfid_protocol": "EM4100", - "key_id": "employee_badge", - "x": 300, - "y": 250, - "takeable": true, - "observations": "A standard employee access badge. Won't open high-security areas." - }, - { - "type": "rfid_cloner", - "name": "Flipper Zero", - "saved_cards": [], - "x": 350, - "y": 250, - "takeable": true, - "observations": "A portable multi-tool for pentesters. Can read and emulate RFID cards." - }, { "type": "notes", "name": "Security Notice", @@ -78,7 +56,7 @@ "takeable": true, "readable": true, "note_title": "RFID Access Control", - "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\nIf you need to clone badges, use the Flipper Zero in the lab. Tap physical badges or emulate saved ones to unlock doors.", + "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" } ], @@ -86,20 +64,20 @@ { "roomId": "test_lobby", "connectedRoom": "test_secure", - "direction": "east", - "x": 600, - "y": 300, + "direction": "north", + "x": 300, + "y": 100, "locked": true, "lockType": "rfid", - "requires": "master_keycard" + "requires": ["master_keycard"] } ] }, "test_secure": { - "name": "Secure Room", - "type": "room_office", + "name": "Secure Server Room", + "type": "room_servers", "connections": { - "west": "test_lobby" + "south": "test_lobby" }, "npcs": [], "objects": [ @@ -112,7 +90,8 @@ "readable": true, "note_title": "RFID Test Passed!", "note_content": "Congratulations! You successfully:\n\n1. ✓ Found the Flipper Zero\n2. ✓ Cloned the Security Guard's master keycard\n3. ✓ Emulated the cloned card to unlock the door\n\nThe RFID system is working perfectly!", - "observations": "A congratulations message" + "observations": "A congratulations message", + "isEndGoal": true }, { "type": "keycard", @@ -121,7 +100,7 @@ "rfid_facility": 255, "rfid_card_number": 43597, "rfid_protocol": "EM4100", - "key_id": "ceo_keycard", + "card_id": "ceo_keycard", "x": 350, "y": 300, "takeable": true, @@ -132,9 +111,9 @@ { "roomId": "test_secure", "connectedRoom": "test_lobby", - "direction": "west", - "x": 100, - "y": 300, + "direction": "south", + "x": 300, + "y": 500, "locked": false } ]