Merge pull request #37 from cliffe/main

.
This commit is contained in:
Damian Idzinski
2025-03-09 19:01:02 +00:00
committed by GitHub
3 changed files with 683 additions and 116 deletions

View File

@@ -0,0 +1,399 @@
{
"scenario_brief": "You are a security specialist tasked with investigating a high-security research facility after reports of unauthorized access. Your mission is to use biometric tools to identify the intruder, secure sensitive research data, and recover the stolen prototype before it leaves the facility.",
"endGoal": "Recover the stolen Project Sentinel prototype from the intruder's hidden exit route and secure all compromised data.",
"startRoom": "reception",
"rooms": {
"reception": {
"type": "room_reception",
"connections": {
"north": "office1"
},
"objects": [
{
"type": "phone",
"name": "Reception Phone",
"takeable": false,
"readable": true,
"text": "Voicemail: 'Security alert: Unauthorized access detected in the biometrics lab. All personnel must verify identity at security checkpoints. Server room PIN changed to 5923. Security lockdown initiated. - Security Team'",
"observations": "The reception phone's message light is blinking with an urgent message"
},
{
"type": "notes",
"name": "Security Log",
"takeable": true,
"readable": true,
"text": "Unusual access patterns detected:\n- Lab 1: 23:45 PM\n- Biometrics Lab: 01:30 AM\n- Server Room: 02:15 AM\n- Loading Dock: 03:05 AM\n- Director's Office: 03:22 AM",
"observations": "A concerning security log from last night"
},
{
"type": "fingerprint_kit",
"name": "Fingerprint Kit",
"takeable": true,
"inInventory": true,
"observations": "A professional kit for collecting fingerprint samples"
},
{
"type": "pc",
"name": "Reception Computer",
"takeable": false,
"hasFingerprint": true,
"fingerprintOwner": "receptionist",
"fingerprintQuality": 0.8,
"observations": "The reception computer shows a security alert screen. There might be fingerprints on the keyboard."
},
{
"type": "lockpick",
"name": "Lockpick",
"takeable": true,
"inInventory": true,
"observations": "A tool for picking locks"
},
{
"type": "workstation",
"name": "Crypto Analysis Station",
"takeable": true,
"inInventory": true,
"observations": "A powerful workstation for cryptographic analysis"
},
{
"type": "notes",
"name": "Facility Map",
"takeable": true,
"readable": true,
"text": "Facility Layout:\n- Reception (Main Entrance)\n- Main Office (North of Reception)\n- Administrative Office (North of Main Office)\n- Research Wing (North of Main Office)\n- Director's Office (North of Administrative Office)\n- Server Room (North of Research Wing)\n- Storage Closet (North of Director's Office)",
"observations": "A map of the facility showing all major areas"
}
]
},
"office1": {
"type": "room_office",
"connections": {
"north": ["office2", "office3"],
"south": "reception"
},
"objects": [
{
"type": "pc",
"name": "Lab Computer",
"takeable": false,
"hasFingerprint": true,
"fingerprintOwner": "researcher",
"fingerprintQuality": 0.9,
"observations": "A research computer with data analysis software running. There might be fingerprints on the keyboard."
},
{
"type": "notes",
"name": "Research Notes",
"takeable": true,
"readable": true,
"text": "Project Sentinel: Biometric security breakthrough. Final test results stored in secure server. Access requires Level 3 clearance or backup key. The prototype scanner is stored in the Director's office.",
"observations": "Important research notes about a biometric security project"
},
{
"type": "tablet",
"name": "Security Tablet",
"takeable": true,
"readable": true,
"text": "Security Alert: Unauthorized access to biometrics lab detected at 01:30 AM. Biometric scanner in server room requires admin fingerprint or emergency override key.",
"observations": "A security tablet showing access logs and alerts"
},
{
"type": "key",
"name": "Biolab Key",
"takeable": true,
"key_id": "ceo_office_key",
"observations": "A backup key for the biometrics lab, kept for emergencies"
},
{
"type": "photo",
"name": "Team Photo",
"takeable": true,
"readable": true,
"text": "Project Sentinel Team:\nDr. Eleanor Chen (Director)\nDr. Marcus Patel (Lead Researcher)\nDr. Wei Zhang (Biometrics Specialist)\nAlex Morgan (Security Consultant)",
"observations": "A framed photo of the Project Sentinel research team"
},
{
"type": "notes",
"name": "Security Guard Schedule",
"takeable": true,
"readable": true,
"text": "Night Shift (00:00-08:00):\n- John Reynolds: Front Entrance\n- Mark Stevens: Lab Wing (ON LEAVE)\n- Sarah Chen: Server Room\n\nNOTE: Due to staffing shortage, server room checks reduced to hourly instead of every 30 minutes.",
"observations": "The security guard rotation schedule for last night"
}
]
},
"office2": {
"type": "room_office",
"connections": {
"north": "ceo",
"south": "office1"
},
"objects": [
{
"type": "pc",
"name": "Biometrics Workstation",
"takeable": false,
"hasFingerprint": true,
"fingerprintOwner": "intruder",
"fingerprintQuality": 0.85,
"observations": "A specialized workstation for biometric research. The screen shows someone was recently using it."
},
{
"type": "notes",
"name": "Access Log",
"takeable": true,
"readable": true,
"text": "Unusual access pattern detected: Admin credentials used during off-hours. Timestamp matches security alert. Safe PIN code: 8741",
"observations": "A log showing suspicious access to the biometrics lab"
},
{
"type": "notes",
"name": "Fingerprint Comparison Report",
"takeable": true,
"readable": true,
"text": "Fingerprint Analysis:\nRecent unauthorized access shows fingerprints matching consultant Alex Morgan (74% confidence).\n\nNOTE: Further analysis needed for confirmation. Check server room terminal for complete database.",
"observations": "A report analyzing fingerprints found on breached equipment"
}
]
},
"office3": {
"type": "room_office",
"connections": {
"north": "server1",
"south": "office1"
},
"objects": [
{
"type": "workstation",
"name": "Fingerprint Analysis Station",
"takeable": false,
"observations": "A specialized workstation for analyzing fingerprint samples"
},
{
"type": "notes",
"name": "Biometric Override Codes",
"takeable": true,
"readable": true,
"text": "Emergency Override Procedure:\n1. Director's Office Biometric Scanner: Code 72958\n2. Loading Dock Security Gate: Code 36714\n\nWARNING: Use only in emergency situations. All uses are logged and reviewed.",
"observations": "A highly sensitive document with emergency override codes"
},
{
"type": "key",
"name": "Server Room Key",
"takeable": true,
"key_id": "briefcase_key",
"observations": "A key to the server room, carelessly left behind by someone"
},
{
"type": "notes",
"name": "Maintenance Log",
"takeable": true,
"readable": true,
"text": "03/07 - HVAC repairs completed\n03/08 - Replaced server room cooling unit\n03/09 - Fixed office lighting circuits\n\nNOTE: Need to repair loading dock camera - currently offline due to power fluctuations.",
"observations": "A maintenance log for the facility"
}
]
},
"ceo": {
"type": "room_ceo",
"connections": {
"north": "closet",
"south": "office2"
},
"locked": true,
"lockType": "key",
"requires": "ceo_office_key",
"difficulty": "medium",
"objects": [
{
"type": "pc",
"name": "Director's Computer",
"takeable": false,
"hasFingerprint": true,
"fingerprintOwner": "director",
"fingerprintQuality": 0.95,
"observations": "The director's high-security computer. Multiple fingerprints visible on the keyboard."
},
{
"type": "phone",
"name": "Director's Phone",
"takeable": false,
"readable": true,
"text": "Last call: Incoming from Security Office at 02:37 AM. Call log shows Security reporting unauthorized access to server room.",
"observations": "The director's phone with call history displayed"
},
{
"type": "cabinet",
"name": "Secure Cabinet",
"takeable": false,
"locked": true,
"lockType": "key",
"requires": "safe_key",
"difficulty": "medium",
"observations": "A high-security cabinet where the prototype would normally be stored",
"contents": [
{
"type": "notes",
"name": "Empty Prototype Case",
"takeable": true,
"readable": true,
"text": "PROJECT SENTINEL PROTOTYPE\nProperty of Biometric Research Division\nAUTHORIZED ACCESS ONLY\n\nCase opened at 03:26 AM - SECURITY ALERT TRIGGERED",
"observations": "An empty case that previously held the prototype device",
"important": true
},
{
"type": "notes",
"name": "Project Investors",
"takeable": true,
"readable": true,
"text": "Project Sentinel Investors:\n- US Department of Defense: $15M\n- Northcrest Security Solutions: $8M\n- Rivera Technologies: $5M\n\nNOTE: Alex Morgan previously worked for Rivera Technologies for 3 years before becoming our consultant. Passed all background checks.",
"observations": "A confidential list of project investors and funding sources"
}
]
},
{
"type": "notes",
"name": "Director's Calendar",
"takeable": true,
"readable": true,
"text": "Today:\n9:00 AM - Staff Briefing\n11:00 AM - DOD Representative Visit\n2:00 PM - Demo of Project Sentinel Prototype\n\nNOTE: Ensure prototype is prepared for demonstration. Security consultant Alex Morgan to assist with setup.",
"observations": "The director's schedule for today"
}
]
},
"closet": {
"type": "room_closet",
"connections": {
"south": "ceo"
},
"locked": true,
"lockType": "pin",
"requires": "72958",
"objects": [
{
"type": "safe",
"name": "Hidden Safe",
"takeable": false,
"locked": true,
"lockType": "biometric",
"requires": "intruder",
"difficulty": "hard",
"observations": "A well-hidden wall safe behind a painting with a fingerprint scanner",
"contents": [
{
"type": "notes",
"name": "Escape Plan",
"takeable": true,
"readable": true,
"text": "4:00 AM - Meet contact at loading dock\n4:15 AM - Transfer prototype and data\n4:30 AM - Leave separately\n\nBackup plan: If compromised, use maintenance tunnel fire exit. Car parked at south lot.",
"observations": "A detailed escape plan with timing information",
"important": true
},
{
"type": "key",
"name": "Safe Key",
"takeable": true,
"key_id": "safe_key",
"observations": "A small key with 'Secure Cabinet' written on it"
}
]
},
{
"type": "notes",
"name": "Scribbled Note",
"takeable": true,
"readable": true,
"text": "A = Meet at dock, 4AM\nN = Bring everything\nM = Getaway car ready\n\nLH will pay other half when delivered.",
"observations": "A hastily scribbled note, partially crumpled"
},
{
"type": "fingerprint_kit",
"name": "Advanced Fingerprint Kit",
"takeable": true,
"observations": "A more advanced fingerprint collection kit with higher resolution scanning"
}
]
},
"server1": {
"type": "room_servers",
"connections": {
"south": "office3"
},
"locked": true,
"lockType": "key",
"requires": "briefcase_key",
"difficulty": "medium",
"objects": [
{
"type": "pc",
"name": "Server Terminal",
"takeable": false,
"hasFingerprint": true,
"fingerprintOwner": "intruder",
"fingerprintQuality": 0.98,
"observations": "The main server terminal controlling access to research data. There are clear fingerprints on the screen."
},
{
"type": "safe",
"name": "Secure Data Safe",
"takeable": false,
"locked": true,
"lockType": "biometric",
"requires": "intruder",
"difficulty": "medium",
"observations": "A secure safe with a fingerprint scanner containing the sensitive research data",
"contents": [
{
"type": "notes",
"name": "Project Sentinel Data",
"takeable": true,
"readable": true,
"text": "Complete research data for Project Sentinel biometric security system. Evidence shows unauthorized copy was made at 02:17 AM by someone using spoofed admin credentials.",
"observations": "The complete research data for the biometric security project",
"important": true
},
{
"type": "notes",
"name": "Security Camera Log",
"takeable": true,
"readable": true,
"text": "Camera footage deleted for the following time periods:\n- Loading Dock: 03:00 AM - 03:30 AM\n- Maintenance Tunnel: 03:10 AM - 03:25 AM\n- Director's Office: 03:20 AM - 03:40 AM\n\nSystem shows credentials used: Alex Morgan, Security Consultant",
"observations": "A report of deleted security camera footage"
}
]
},
{
"type": "suitcase",
"name": "Suspicious Case",
"takeable": false,
"locked": true,
"lockType": "biometric",
"requires": "intruder",
"difficulty": "hard",
"observations": "A suspicious case hidden behind server racks with a fingerprint scanner",
"contents": [
{
"type": "notes",
"name": "Project Sentinel Prototype",
"takeable": true,
"readable": true,
"text": "PROJECT SENTINEL BIOMETRIC SCANNER PROTOTYPE\nSERIAL: PS-001-X\nCLASSIFICATION: TOP SECRET\n\nWARNING: Authorized handling only. Technology contains classified components.",
"observations": "The stolen prototype device, ready to be smuggled out",
"important": true,
"isEndGoal": true
},
{
"type": "notes",
"name": "Buyer Details",
"takeable": true,
"readable": true,
"text": "Buyer: Lazarus Hacking Group\nPayment: $2.5M total, $500K advance paid\nDelivery instructions: Loading dock 4:00 AM, March 10\nContact code name: Nighthawk\n\nDeliverable: Project Sentinel prototype + all research data",
"observations": "Details of the buyer and the transaction",
"important": true
}
]
}
]
}
}
}

View File

@@ -47,6 +47,13 @@
"observations": "A device for detecting nearby Bluetooth signals",
"canScanBluetooth": true
},
{
"type": "workstation",
"name": "Crypto Analysis Station",
"takeable": true,
"inInventory": true,
"observations": "A powerful workstation for cryptographic analysis"
},
{
"type": "bluetooth_spoofer",
"name": "Bluetooth Spoofer",

View File

@@ -266,9 +266,7 @@
}
#notes-toggle {
position: fixed;
bottom: 20px;
right: 20px;
position: relative;
width: 60px;
height: 60px;
background-color: #3498db;
@@ -282,6 +280,7 @@
z-index: 1998;
font-size: 28px;
transition: all 0.3s ease;
margin-left: 10px;
}
#notes-toggle:hover {
@@ -585,9 +584,7 @@
}
#bluetooth-toggle {
position: fixed;
bottom: 20px;
right: 90px;
position: relative;
width: 60px;
height: 60px;
background-color: #9b59b6;
@@ -601,6 +598,7 @@
z-index: 1998;
font-size: 28px;
transition: all 0.3s ease;
margin-left: 10px;
}
#bluetooth-toggle:hover {
@@ -855,9 +853,7 @@
}
#biometrics-toggle {
position: fixed;
bottom: 20px;
right: 160px;
position: relative;
width: 60px;
height: 60px;
background-color: #e74c3c;
@@ -871,6 +867,7 @@
z-index: 1998;
font-size: 28px;
transition: all 0.3s ease;
margin-left: 10px;
}
#biometrics-toggle:hover {
@@ -895,6 +892,27 @@
}
/* Rest of existing styles follow */
.biometric-sample-timestamp {
font-size: 11px;
color: #888;
margin-top: 5px;
text-align: right;
}
/* Toggle Buttons Container */
#toggle-buttons-container {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: row-reverse;
z-index: 1998;
}
/* Game container */
#game-container {
position: relative;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/easystarjs@0.4.4/bin/easystar-0.4.4.js"></script>
@@ -923,9 +941,21 @@
</div>
<div id="notes-content"></div>
</div>
<div id="notes-toggle">
<span>📝</span>
<div id="notes-count">0</div>
<!-- Toggle Buttons Container -->
<div id="toggle-buttons-container">
<div id="notes-toggle">
<span>📝</span>
<div id="notes-count">0</div>
</div>
<div id="bluetooth-toggle" style="display: none;">
<span>📡</span>
<div id="bluetooth-count">0</div>
</div>
<div id="biometrics-toggle" style="display: none;">
<span>👆</span>
<div id="biometrics-count">0</div>
</div>
</div>
<!-- Bluetooth Scanner Panel -->
@@ -944,10 +974,6 @@
</div>
<div id="bluetooth-content"></div>
</div>
<div id="bluetooth-toggle" style="display: none;">
<span>📡</span>
<div id="bluetooth-count">0</div>
</div>
<!-- Biometrics Panel -->
<div id="biometrics-panel">
@@ -965,10 +991,6 @@
</div>
<div id="biometrics-content"></div>
</div>
<div id="biometrics-toggle" style="display: none;">
<span>👆</span>
<div id="biometrics-count">0</div>
</div>
<script>
const config = {
@@ -1087,11 +1109,19 @@
// Add a note to the notes panel
function addNote(title, text, important = false) {
// Check if a note with the same title and text already exists
const noteExists = gameNotes.some(note => note.title === title && note.text === text);
const existingNote = gameNotes.find(note => note.title === title && note.text === text);
// If the note already exists, don't add it again
if (noteExists) {
debugLog(`Note "${title}" already exists, not adding duplicate`, 2);
// If the note already exists, don't add it again but mark it as read
if (existingNote) {
debugLog(`Note "${title}" already exists, not adding duplicate`, existingNote, 2);
// Mark as read if it wasn't already
if (!existingNote.read) {
existingNote.read = true;
updateNotesPanel();
updateNotesCount();
}
return null;
}
@@ -1513,82 +1543,21 @@
this.load.image('fingerprint_kit', 'assets/objects/fingerprint_kit.png');
this.load.image('lockpick', 'assets/objects/lockpick.png');
this.load.json('gameScenarioJSON', 'assets/scenarios/ceo_exfil.json');
this.load.json('gameScenarioJSON', 'assets/scenarios/biometric_breach.json');
//this.load.json('gameScenarioJSON', 'assets/scenarios/ceo_exfil.json');
gameScenario = this.cache.json.get('gameScenarioJSON');
}
// creates the workstation
function addCryptoWorkstation() {
// console.log('CyberChef: Adding crypto workstation...');
const workstationData = {
type: "workstation",
name: "Crypto Analysis Station",
observations: "A powerful workstation for cryptographic analysis"
};
function createCryptoWorkstation(objectData) {
// Create the workstation sprite
const workstationSprite = this.add.sprite(0, 0, 'workstation');
workstationSprite.setVisible(false);
workstationSprite.name = "workstation";
workstationSprite.scenarioData = workstationData;
workstationSprite.scenarioData = objectData;
workstationSprite.setInteractive({ useHandCursor: true });
// Override the default handleObjectInteraction for this specific item
workstationSprite.openCryptoWorkstation = function() {
// console.log('CyberChef: Workstation custom interaction triggered');
// Create popup
let popup = document.getElementById('laptop-popup');
if (!popup) {
// console.log('CyberChef: Creating new popup...');
popup = document.createElement('div');
popup.id = 'laptop-popup';
popup.innerHTML = `
<div class="popup-overlay"></div>
<div class="laptop-frame">
<div class="laptop-screen">
<div class="title-bar">
<span>CryptoWorkstation</span>
<button class="close-btn">&times;</button>
</div>
<div id="cyberchef-container"></div>
</div>
</div>
`;
document.body.appendChild(popup);
// Find the CyberChef file
fetch('assets/cyberchef/')
.then(response => response.text())
.then(html => {
// Use regex to find the CyberChef filename
const match = html.match(/CyberChef_v[0-9.]+\.html/);
if (match) {
const cyberchefPath = `assets/cyberchef/${match[0]}`;
// Create and append the iframe with the found path
const iframe = document.createElement('iframe');
iframe.src = cyberchefPath;
iframe.frameBorder = "0";
document.getElementById('cyberchef-container').appendChild(iframe);
} else {
console.error('Could not find CyberChef file');
}
})
.catch(error => {
console.error('Error loading CyberChef:', error);
});
popup.querySelector('.close-btn').addEventListener('click', () => {
popup.style.display = 'none';
});
}
popup.style.display = 'flex';
return true;
};
// Add to inventory directly
addToInventory(workstationSprite);
// console.log('CyberChef: Workstation added to inventory');
return workstationSprite;
}
// creates the game
@@ -1711,9 +1680,12 @@
// Initialize game systems
initializeInventory.call(this);
// Process items marked with inInventory: true in the scenario data
processInitialInventoryItems.call(this);
// Add the workstation to inventory
addCryptoWorkstation.call(this);
// NOTE: Crypto workstation is now handled by processInitialInventoryItems
// based on inInventory flag in scenario data - addCryptoWorkstation.call(this);
// Add this line after processAllDoorCollisions()
setupDoorOverlapChecks.call(this);
@@ -2688,19 +2660,20 @@
// Only log detailed object interactions at debug level 2+
debugLog('OBJECT INTERACTION', {
name: sprite.name,
hasWorkstation: !!sprite.openCryptoWorkstation
type: sprite.scenarioData?.type
}, 2);
if (sprite.openCryptoWorkstation && sprite.openCryptoWorkstation()) {
debugLog('WORKSTATION OPENED', null, 1);
return;
}
if (!sprite || !sprite.scenarioData) {
console.warn('Invalid sprite or missing scenario data');
return;
}
// Handle the Crypto Workstation
if (sprite.scenarioData.type === "workstation") {
openCryptoWorkstation();
return;
}
// Skip range check for inventory items
const isInventoryItem = inventory.items.includes(sprite);
if (!isInventoryItem) {
@@ -2710,8 +2683,12 @@
const distanceSq = dx * dx + dy * dy;
if (distanceSq > INTERACTION_RANGE_SQ) {
// Show notification instead of alert
//gameAlert("Too far away to interact with this object.", 'warning', '', 2000);
// Player is too far away to interact
debugLog('INTERACTION_OUT_OF_RANGE', {
objectName: sprite.name,
distance: Math.sqrt(distanceSq),
maxRange: Math.sqrt(INTERACTION_RANGE_SQ)
}, 2);
return;
}
}
@@ -2797,6 +2774,18 @@
}
}, 1000);
}
} else {
// If the note was a duplicate and it's a note in the room (not inventory),
// still remove it from the room even though we didn't add it to notes again
if (!isInventoryItem && data.type === 'notes' && currentRoom &&
rooms[currentRoom] && rooms[currentRoom].objects &&
rooms[currentRoom].objects[sprite.name]) {
const roomObj = rooms[currentRoom].objects[sprite.name];
roomObj.setVisible(false);
roomObj.active = false;
debugLog(`Note "${data.name}" was a duplicate, but still removed from room`, 2);
}
}
}
}
@@ -2844,6 +2833,9 @@
roomObj.setVisible(false);
roomObj.active = false;
// Also set the internal property to indicate it's been collected
sprite.collected = true;
// Show notification about adding to notes instead of inventory
gameAlert(`Information recorded in your notes.`, 'success', 'Note Recorded', 3000);
}
@@ -2864,10 +2856,16 @@
}
try {
// Check if the item is already in the inventory
const isAlreadyInInventory = inventory.items.some(item => item.name === sprite.name);
// Check if the item is already in the inventory using the unique identifier
const itemIdentifier = createItemIdentifier(sprite.scenarioData);
console.log(`Checking if item ${itemIdentifier} is already in inventory`);
const isAlreadyInInventory = inventory.items.some(item =>
createItemIdentifier(item.scenarioData) === itemIdentifier
);
if (isAlreadyInInventory) {
console.log(`Item ${sprite.name} is already in inventory, not adding again`);
console.log(`Item ${itemIdentifier} is already in inventory, not adding again`);
return false;
}
@@ -2899,11 +2897,6 @@
};
inventorySprite.name = sprite.name;
// Copy over the custom interaction if it exists
if (sprite.openCryptoWorkstation) {
inventorySprite.openCryptoWorkstation = sprite.openCryptoWorkstation;
}
// Set depth higher than container
inventorySprite.setDepth(2003);
@@ -2917,7 +2910,7 @@
}
// Handle other inventory items as before
handleItemInteraction(this, true);
handleObjectInteraction(this);
});
inventorySprite.on('pointerover', function() {
@@ -3009,6 +3002,39 @@
debugLog('INVENTORY INITIALIZED', inventory, 2); // Debug log at level 2
}
// Process all items marked with inInventory: true in the scenario data
function processInitialInventoryItems() {
debugLog('PROCESSING INITIAL INVENTORY ITEMS', null, 2);
// Loop through all rooms in the scenario
Object.entries(gameScenario.rooms).forEach(([roomId, roomData]) => {
if (!roomData.objects) return;
// Check each object in the room
roomData.objects.forEach(objectData => {
// If the object has inInventory: true, add it to the player's inventory
if (objectData.takeable && objectData.inInventory === true) {
debugLog('ADDING INITIAL INVENTORY ITEM', objectData, 2);
// Create a sprite for the item
let sprite;
// Special handling for workstation type
if (objectData.type === "workstation") {
sprite = createCryptoWorkstation.call(this, objectData);
} else {
sprite = createInventorySprite(objectData);
}
if (sprite) {
// Add to inventory
addToInventory(sprite);
}
}
});
});
}
// runs after rooms are fully set up
// checks if doors are overlapping rooms and removes them if they are not
@@ -3211,6 +3237,9 @@
}
// Get lock requirements based on type
// lockRequirements should contain:
// - lockType: 'key', 'pin', 'password', 'bluetooth', or 'biometric'
// - requires: Key ID, PIN code, password, MAC address, or fingerprint owner name
const lockRequirements = type === 'door'
? getLockRequirementsForDoor(lockable)
: getLockRequirementsForItem(lockable);
@@ -3302,6 +3331,60 @@
}
break;
case 'biometric':
debugLog('BIOMETRIC AUTHENTICATION REQUESTED', null, 2);
// Check if player has fingerprint kit
const hasFingerPrintKit = inventory.items.some(item =>
item && item.scenarioData &&
item.scenarioData.type === 'fingerprint_kit'
);
if (!hasFingerPrintKit) {
gameAlert("You need a fingerprint kit to use biometric scanners.", 'warning', 'Missing Equipment', 4000);
return;
}
// Check if player has required fingerprint sample
const requiredFingerprint = lockRequirements.requires;
debugLog('FINGERPRINT REQUIRED', requiredFingerprint, 2);
// Check if player has a valid fingerprint sample
const validSample = gameState.biometricSamples.find(sample =>
sample.type === 'fingerprint' &&
sample.owner === requiredFingerprint &&
sample.quality >= 0.7 // Quality threshold
);
if (validSample) {
debugLog('BIOMETRIC UNLOCK SUCCESS', validSample, 1);
unlockTarget(lockable, type, lockable.layer);
gameAlert(`You successfully used ${validSample.owner}'s fingerprint to unlock the ${type}.`,
'success', 'Biometric Authentication Successful', 5000);
// Play success sound and visual effect
const successEffect = lockable.scene ? lockable.scene.add.circle(
lockable.x,
lockable.y,
32,
0x00ff00,
0.5
) : null;
if (successEffect) {
lockable.scene.tweens.add({
targets: successEffect,
alpha: 0,
scale: 2,
duration: 1000,
onComplete: () => successEffect.destroy()
});
}
} else {
debugLog('BIOMETRIC UNLOCK FAILED', null, 2);
gameAlert(`You don't have the required fingerprint sample.`, 'error', 'Biometric Authentication Failed', 4000);
}
break;
case 'bluetooth':
if (lockable.scenarioData?.locked) {
// Try to spoof the Bluetooth device
@@ -3518,8 +3601,8 @@
if (lockedRooms.length > 0) {
const targetRoom = lockedRooms[0];
const requirements = {
lockType: targetRoom.lockType,
requires: targetRoom.requires
lockType: targetRoom.lockType, // Can be: 'key', 'pin', 'password', 'bluetooth', or 'biometric'
requires: targetRoom.requires // Key ID, PIN code, password, BT MAC address, or fingerprint owner name
};
debugLog('LOCK REQUIREMENTS', requirements, 2);
return requirements;
@@ -3531,8 +3614,8 @@
function getLockRequirementsForItem(item) {
return {
lockType: item.lockType || item.scenarioData?.lockType,
requires: item.requires || item.scenarioData?.requires,
lockType: item.lockType || item.scenarioData?.lockType, // Can be: 'key', 'pin', 'password', 'bluetooth', or 'biometric'
requires: item.requires || item.scenarioData?.requires, // Key ID, PIN code, password, BT MAC address, or fingerprint owner name
isUnlockedButNotCollected: false
};
}
@@ -4651,7 +4734,7 @@
.sort(() => Math.random() - 0.5);
const gameState = {
tensionLevel: 1, // Start with light tension instead of none
tensionLevel: 1, // Start with light tension (1)
pinStates: Array(numPins).fill(0), // 0 = down, 1 = moving, 2 = set
pinPressTime: Array(numPins).fill(0), // Track how long each pin is pressed
currentBindingIndex: 0,
@@ -4676,7 +4759,7 @@
tensionWrench.style.cssText = `
width: 100px;
height: 30px;
background: ${gameState.tensionLevel === 1 ? '#666' : gameState.tensionLevel === 2 ? '#888' : '#aaa'};
background: #666;
border: 2px solid #888;
border-radius: 5px;
cursor: pointer;
@@ -4684,8 +4767,9 @@
text-align: center;
line-height: 30px;
color: white;
transform: rotate(2deg);
`;
tensionWrench.textContent = 'Tension: OFF';
tensionWrench.textContent = 'Tension: LIGHT';
// Function to reset pins
function resetPins(showVisual = true) {
@@ -6380,6 +6464,83 @@
}
}
// Function to initialize the toggle buttons container
function initializeToggleButtons() {
// Set up notes toggle button
const notesToggle = document.getElementById('notes-toggle');
if (notesToggle) {
notesToggle.addEventListener('click', toggleNotesPanel);
}
// Set up bluetooth toggle button
const bluetoothToggle = document.getElementById('bluetooth-toggle');
if (bluetoothToggle) {
bluetoothToggle.addEventListener('click', toggleBluetoothPanel);
}
// Set up biometrics toggle button
const biometricsToggle = document.getElementById('biometrics-toggle');
if (biometricsToggle) {
biometricsToggle.addEventListener('click', toggleBiometricsPanel);
}
}
// Call the initialization function when the game starts
document.addEventListener('DOMContentLoaded', function() {
initializeToggleButtons();
});
// Function to open the crypto workstation
function openCryptoWorkstation() {
debugLog('OPENING CRYPTO WORKSTATION', null, 1);
// Create popup
let popup = document.getElementById('laptop-popup');
if (!popup) {
popup = document.createElement('div');
popup.id = 'laptop-popup';
popup.innerHTML = `
<div class="popup-overlay"></div>
<div class="laptop-frame">
<div class="laptop-screen">
<div class="title-bar">
<span>CryptoWorkstation</span>
<button class="close-btn">&times;</button>
</div>
<div id="cyberchef-container"></div>
</div>
</div>
`;
document.body.appendChild(popup);
// Find the CyberChef file
fetch('assets/cyberchef/')
.then(response => response.text())
.then(html => {
// Use regex to find the CyberChef filename
const match = html.match(/CyberChef_v[0-9.]+\.html/);
if (match) {
const cyberchefPath = `assets/cyberchef/${match[0]}`;
// Create and append the iframe with the found path
const iframe = document.createElement('iframe');
iframe.src = cyberchefPath;
iframe.frameBorder = "0";
document.getElementById('cyberchef-container').appendChild(iframe);
} else {
console.error('Could not find CyberChef file');
}
})
.catch(error => {
console.error('Error loading CyberChef:', error);
});
popup.querySelector('.close-btn').addEventListener('click', () => {
popup.style.display = 'none';
});
}
popup.style.display = 'flex';
}
</script>
</body>
</html>