+ Instructions:
+ • Use "Search Room" to highlight objects with fingerprints
+ • Click highlighted objects to collect fingerprint samples
+ • Collected samples can be used to unlock biometric scanners
+ • Higher quality samples have better success rates
+
+ `;
+
+ // Assemble the interface
+ this.gameContainer.appendChild(expandToggle);
+ this.gameContainer.appendChild(searchRoomContainer);
+ this.gameContainer.appendChild(scannerHeader);
+ this.gameContainer.appendChild(controlsContainer);
+ this.gameContainer.appendChild(samplesListContainer);
+ this.gameContainer.appendChild(instructionsContainer);
+
+ // Set up event listeners
+ this.setupEventListeners();
+
+ // Set up expand/collapse functionality
+ this.setupExpandToggle(expandToggle);
+ }
+
+ setupEventListeners() {
+ // Search functionality
+ const biometricsSearch = document.getElementById('biometrics-search');
+ if (biometricsSearch) {
+ this.addEventListener(biometricsSearch, 'input', () => this.updateBiometricsPanel());
+ }
+
+ // Category filters
+ const categories = this.gameContainer.querySelectorAll('.biometrics-category');
+ categories.forEach(category => {
+ this.addEventListener(category, 'click', () => {
+ // Remove active class from all categories
+ categories.forEach(c => c.classList.remove('active'));
+ // Add active class to clicked category
+ category.classList.add('active');
+ // Update biometrics panel
+ this.updateBiometricsPanel();
+ });
+ });
+
+ // Search room button
+ const searchRoomBtn = document.getElementById('search-room-btn');
+ if (searchRoomBtn) {
+ this.addEventListener(searchRoomBtn, 'click', () => this.toggleRoomSearching());
+ }
+ }
+
+ setupExpandToggle(expandToggle) {
+ this.addEventListener(expandToggle, 'click', () => {
+ const isExpanded = this.container.classList.contains('expanded');
+
+ if (isExpanded) {
+ // Collapse
+ this.container.classList.remove('expanded');
+ expandToggle.innerHTML = '▼';
+ expandToggle.title = 'Expand';
+ } else {
+ // Expand
+ this.container.classList.add('expanded');
+ expandToggle.innerHTML = '▲';
+ expandToggle.title = 'Collapse';
+ }
+ });
+ }
+
+ initializeBiometricSamples() {
+ // Initialize from global state if available
+ if (window.gameState && window.gameState.biometricSamples) {
+ this.biometricSamples = [...window.gameState.biometricSamples];
+ } else {
+ this.biometricSamples = [];
+ }
+
+ // Update the panel
+ this.updateBiometricsPanel();
+ }
+
+ toggleRoomSearching() {
+ this.searchingMode = !this.searchingMode;
+ const searchBtn = document.getElementById('search-room-btn');
+
+ if (this.searchingMode) {
+ // Start searching mode
+ searchBtn.classList.add('active');
+ searchBtn.querySelector('.btn-text').textContent = 'Stop Searching';
+ this.highlightFingerprintObjects();
+ console.log('Room searching started');
+ } else {
+ // Stop searching mode
+ searchBtn.classList.remove('active');
+ searchBtn.querySelector('.btn-text').textContent = 'Search Room for Fingerprints';
+ this.clearHighlights();
+ console.log('Room searching stopped');
+ }
+ }
+
+ highlightFingerprintObjects() {
+ // Clear existing highlights
+ this.clearHighlights();
+
+ // Find all objects in the current room that have fingerprints
+ if (!window.currentPlayerRoom || !window.rooms[window.currentPlayerRoom] || !window.rooms[window.currentPlayerRoom].objects) {
+ return;
+ }
+
+ const room = window.rooms[window.currentPlayerRoom];
+ this.highlightedObjects = [];
+
+ Object.values(room.objects).forEach(obj => {
+ if (obj.scenarioData?.hasFingerprint === true) {
+ // Add red highlight effect to the object
+ if (obj.setTint) {
+ obj.setTint(0xff0000); // Red tint for fingerprint objects
+ this.highlightedObjects.push(obj);
+ }
+
+ // Add a visual indicator
+ this.addFingerprintIndicator(obj);
+ }
+ });
+
+ if (this.highlightedObjects.length > 0) {
+ console.log(`Highlighted ${this.highlightedObjects.length} objects with fingerprints`);
+ } else {
+ console.log('No objects with fingerprints found in this room');
+ }
+ }
+
+ addFingerprintIndicator(obj) {
+ // Create a fingerprint image indicator directly over the object
+ if (obj.scene && obj.scene.add) {
+ const indicator = obj.scene.add.image(obj.x, obj.y, 'fingerprint');
+ indicator.setDepth(1000); // High depth to appear on top
+ indicator.setOrigin(-0.25, 0);
+ // indicator.setScale(0.5); // Make it smaller
+ indicator.setTint(0xff0000); // Red tint
+
+ // Add pulsing animation
+ obj.scene.tweens.add({
+ targets: indicator,
+ alpha: { from: 1, to: 0.3 },
+ duration: 1000,
+ yoyo: true,
+ repeat: -1
+ });
+
+ // Store reference for cleanup
+ obj.fingerprintIndicator = indicator;
+ }
+ }
+
+ clearHighlights() {
+ // Remove highlights from all objects
+ this.highlightedObjects.forEach(obj => {
+ if (obj.clearTint) {
+ obj.clearTint();
+ }
+ if (obj.fingerprintIndicator) {
+ obj.fingerprintIndicator.destroy();
+ delete obj.fingerprintIndicator;
+ }
+ });
+ this.highlightedObjects = [];
+ }
+
+ collectFingerprintFromObject(obj) {
+ if (!obj.scenarioData) return;
+
+ // Use the fingerprint owner if specified, otherwise use the object's name
+ const owner = obj.scenarioData.fingerprintOwner || obj.scenarioData.name || obj.scenarioData.owner || 'Unknown';
+
+ // Generate fingerprint sample with quality based on difficulty
+ let quality = obj.scenarioData.fingerprintQuality;
+ if (!quality) {
+ // Generate quality based on difficulty
+ const difficulty = obj.scenarioData.fingerprintDifficulty;
+ if (difficulty === 'easy') {
+ quality = 0.8 + Math.random() * 0.2; // 80-100%
+ } else if (difficulty === 'medium') {
+ quality = 0.6 + Math.random() * 0.3; // 60-90%
+ } else if (difficulty === 'hard') {
+ quality = 0.4 + Math.random() * 0.3; // 40-70%
+ } else {
+ quality = 0.6 + Math.random() * 0.4; // 60-100% default
+ }
+ }
+
+ const sample = this.generateFingerprintSample(owner, quality);
+
+ // Add to collection
+ this.addBiometricSample(sample);
+
+ // Remove highlight from this object
+ if (obj.clearTint) {
+ obj.clearTint();
+ }
+ if (obj.fingerprintIndicator) {
+ obj.fingerprintIndicator.destroy();
+ delete obj.fingerprintIndicator;
+ }
+
+ // Remove from highlighted objects
+ const index = this.highlightedObjects.indexOf(obj);
+ if (index > -1) {
+ this.highlightedObjects.splice(index, 1);
+ }
+
+ // Show success message
+ if (window.gameAlert) {
+ window.gameAlert(`Fingerprint collected from ${owner} (${sample.rating})`, 'success', 'Sample Collected', 3000);
+ }
+
+ console.log('Fingerprint collected:', sample);
+ }
+
+ generateFingerprintSample(owner, quality = null) {
+ // If no quality provided, generate based on random factors
+ if (quality === null) {
+ quality = 0.6 + (Math.random() * 0.4); // 60-100% quality range
+ }
+
+ const rating = this.getRatingFromQuality(quality);
+
+ return {
+ owner: owner || 'Unknown',
+ type: 'fingerprint',
+ quality: quality,
+ rating: rating,
+ id: this.generateSampleId(),
+ collectedAt: new Date().toISOString()
+ };
+ }
+
+ getRatingFromQuality(quality) {
+ const qualityPercentage = Math.round(quality * 100);
+ if (qualityPercentage >= 95) return 'Perfect';
+ if (qualityPercentage >= 85) return 'Excellent';
+ if (qualityPercentage >= 75) return 'Good';
+ if (qualityPercentage >= 60) return 'Fair';
+ if (qualityPercentage >= 40) return 'Acceptable';
+ return 'Poor';
+ }
+
+ generateSampleId() {
+ return 'sample_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
+ }
+
+ addBiometricSample(sample) {
+ // Check if sample already exists
+ const existingSample = this.biometricSamples.find(s =>
+ s.owner === sample.owner && s.type === sample.type
+ );
+
+ if (existingSample) {
+ // Update existing sample with better quality if applicable
+ if (sample.quality > existingSample.quality) {
+ existingSample.quality = sample.quality;
+ existingSample.rating = sample.rating;
+ existingSample.collectedAt = sample.collectedAt;
+ }
+ } else {
+ // Add new sample
+ this.biometricSamples.push(sample);
+ }
+
+ this.updateBiometricsPanel();
+ this.syncBiometricSamples();
+ console.log('Biometric sample added:', sample);
+ }
+
+ updateBiometricsPanel() {
+ const biometricsContent = document.getElementById('biometrics-samples-list');
+ if (!biometricsContent) return;
+
+ const searchTerm = document.getElementById('biometrics-search')?.value?.toLowerCase() || '';
+ const activeCategory = this.gameContainer.querySelector('.biometrics-category.active')?.dataset.category || 'all';
+
+ // Filter samples based on search and category
+ let filteredSamples = [...this.biometricSamples];
+
+ // Apply category filter
+ if (activeCategory === 'fingerprint') {
+ filteredSamples = filteredSamples.filter(sample => sample.type === 'fingerprint');
+ }
+
+ // Apply search filter
+ if (searchTerm) {
+ filteredSamples = filteredSamples.filter(sample =>
+ sample.owner.toLowerCase().includes(searchTerm) ||
+ sample.type.toLowerCase().includes(searchTerm)
+ );
+ }
+
+ // Sort samples by quality (highest first)
+ filteredSamples.sort((a, b) => b.quality - a.quality);
+
+ // Update samples count in both header and list
+ const samplesCount = this.gameContainer.querySelector('.samples-count');
+ const samplesCountHeader = this.gameContainer.querySelector('.samples-count-header');
+ const totalSamples = this.biometricSamples.length;
+
+ if (samplesCount) {
+ samplesCount.textContent = `${filteredSamples.length} sample${filteredSamples.length !== 1 ? 's' : ''}`;
+ }
+
+ if (samplesCountHeader) {
+ samplesCountHeader.textContent = `${totalSamples} sample${totalSamples !== 1 ? 's' : ''}`;
+ }
+
+ // Clear current content
+ biometricsContent.innerHTML = '';
+
+ // Add samples
+ if (filteredSamples.length === 0) {
+ if (searchTerm) {
+ biometricsContent.innerHTML = '
+ Instructions:
+ • Walk around to detect Bluetooth devices
+ • Green signal bars indicate nearby devices
+ • Click devices to save them for later reference
+ • Devices in your inventory are always visible
+
+ `;
+
+ // Assemble the interface
+ this.gameContainer.appendChild(expandToggle);
+ this.gameContainer.appendChild(scannerHeader);
+ this.gameContainer.appendChild(controlsContainer);
+ this.gameContainer.appendChild(deviceListContainer);
+ this.gameContainer.appendChild(instructionsContainer);
+
+ // Set up event listeners
+ this.setupEventListeners();
+
+ // Set up expand/collapse functionality
+ this.setupExpandToggle(expandToggle);
+ }
+
+ setupEventListeners() {
+ // Search functionality
+ const bluetoothSearch = document.getElementById('bluetooth-search');
+ if (bluetoothSearch) {
+ this.addEventListener(bluetoothSearch, 'input', () => this.updateBluetoothPanel());
+ }
+
+ // Category filters
+ const categories = this.gameContainer.querySelectorAll('.bluetooth-category');
+ categories.forEach(category => {
+ this.addEventListener(category, 'click', () => {
+ // Remove active class from all categories
+ categories.forEach(c => c.classList.remove('active'));
+ // Add active class to clicked category
+ category.classList.add('active');
+ // Update bluetooth panel
+ this.updateBluetoothPanel();
+ });
+ });
+ }
+
+ setupExpandToggle(expandToggle) {
+ this.addEventListener(expandToggle, 'click', () => {
+ const isExpanded = this.container.classList.contains('expanded');
+
+ if (isExpanded) {
+ // Collapse
+ this.container.classList.remove('expanded');
+ expandToggle.innerHTML = '▼';
+ expandToggle.title = 'Expand';
+ } else {
+ // Expand
+ this.container.classList.add('expanded');
+ expandToggle.innerHTML = '▲';
+ expandToggle.title = 'Collapse';
+ }
+ });
+ }
+
+ initializeBluetoothDevices() {
+ // Initialize from global state if available
+ if (window.gameState && window.gameState.bluetoothDevices) {
+ this.bluetoothDevices = [...window.gameState.bluetoothDevices];
+ } else {
+ this.bluetoothDevices = [];
+ }
+
+ // Start scanning for devices
+ this.startScanning();
+
+ // Update the panel
+ this.updateBluetoothPanel();
+ }
+
+ startScanning() {
+ // Start the scanning interval
+ this.scanInterval = setInterval(() => {
+ this.checkBluetoothDevices();
+ }, this.BLUETOOTH_SCAN_INTERVAL);
+
+ console.log('Bluetooth scanning started');
+ }
+
+ stopScanning() {
+ if (this.scanInterval) {
+ clearInterval(this.scanInterval);
+ this.scanInterval = null;
+ console.log('Bluetooth scanning stopped');
+ }
+ }
+
+ checkBluetoothDevices() {
+ // Find all Bluetooth devices in the current room
+ if (!window.currentPlayerRoom || !window.rooms[window.currentPlayerRoom] || !window.rooms[window.currentPlayerRoom].objects) {
+ return;
+ }
+
+ const room = window.rooms[window.currentPlayerRoom];
+ const player = window.player;
+ if (!player) {
+ return;
+ }
+
+ // Keep track of devices detected in this scan
+ const detectedDevices = new Set();
+ let needsUpdate = false;
+
+ Object.values(room.objects).forEach(obj => {
+ if (obj.scenarioData?.lockType === "bluetooth") {
+ const distance = Math.sqrt(
+ Math.pow(player.x - obj.x, 2) + Math.pow(player.y - obj.y, 2)
+ );
+
+ const deviceMac = obj.scenarioData?.mac || "Unknown";
+ const deviceName = obj.scenarioData?.name || "Unknown Device";
+
+ if (distance <= this.BLUETOOTH_SCAN_RANGE) {
+ detectedDevices.add(`${deviceMac}|${deviceName}`); // Use combination for uniqueness
+
+ // Add to Bluetooth scanner panel
+ const signalStrengthPercentage = Math.max(0, Math.round(100 - (distance / this.BLUETOOTH_SCAN_RANGE * 100)));
+ // Convert percentage to dBm format (-100 to -30 dBm range)
+ const signalStrength = Math.round(-100 + (signalStrengthPercentage * 0.7)); // -100 to -30 dBm
+ const details = `Type: ${obj.scenarioData?.type || "Unknown"}\nDistance: ${Math.round(distance)} units\nSignal Strength: ${signalStrength}dBm (${signalStrengthPercentage}%)`;
+
+ // Check if device already exists in our list (by MAC + name combination for uniqueness)
+ const existingDevice = this.bluetoothDevices.find(device =>
+ device.mac === deviceMac && device.name === deviceName
+ );
+
+ if (existingDevice) {
+ // Update existing device details with real-time data
+ const wasNearby = existingDevice.nearby;
+ const oldSignalStrengthPercentage = existingDevice.signalStrengthPercentage || 0;
+
+ existingDevice.details = details;
+ existingDevice.lastSeen = new Date();
+ existingDevice.nearby = true;
+ existingDevice.signalStrength = signalStrength;
+ existingDevice.signalStrengthPercentage = signalStrengthPercentage;
+
+ // Always update if device came back into range or signal strength changed significantly
+ if (!wasNearby || Math.abs(oldSignalStrengthPercentage - signalStrengthPercentage) > 5) {
+ needsUpdate = true;
+ }
+ } else {
+ // Add as new device if not already in our list
+ const newDevice = this.addBluetoothDevice(deviceName, deviceMac, details, true);
+ if (newDevice) {
+ newDevice.signalStrength = signalStrength;
+ newDevice.signalStrengthPercentage = signalStrengthPercentage;
+ needsUpdate = true;
+ }
+ }
+ }
+ }
+ });
+
+ // Mark devices that weren't detected in this scan as not nearby
+ this.bluetoothDevices.forEach(device => {
+ const deviceKey = `${device.mac}|${device.name}`;
+ if (device.nearby && !detectedDevices.has(deviceKey)) {
+ device.nearby = false;
+ device.lastSeen = new Date();
+ needsUpdate = true;
+ }
+ });
+
+ // Always update the count and sync devices when there are changes
+ if (needsUpdate) {
+ this.updateBluetoothCount();
+ this.syncBluetoothDevices();
+
+ // Update the panel UI
+ const now = Date.now();
+ if (now - this.lastBluetoothPanelUpdate > this.BLUETOOTH_UPDATE_THROTTLE) {
+ this.updateBluetoothPanel();
+ this.lastBluetoothPanelUpdate = now;
+ }
+ }
+ }
+
+ addBluetoothDevice(name, mac, details = "", nearby = true) {
+ // Check if a device with the same MAC + name combination already exists
+ const deviceExists = this.bluetoothDevices.some(device => device.mac === mac && device.name === name);
+
+ // If the device already exists, update its nearby status
+ if (deviceExists) {
+ const existingDevice = this.bluetoothDevices.find(device => device.mac === mac && device.name === name);
+ existingDevice.nearby = nearby;
+ existingDevice.lastSeen = new Date();
+ this.updateBluetoothPanel();
+ this.syncBluetoothDevices();
+ return null;
+ }
+
+ const device = {
+ id: Date.now(),
+ name: name,
+ mac: mac,
+ details: details,
+ nearby: nearby,
+ saved: false,
+ firstSeen: new Date(),
+ lastSeen: new Date(),
+ signalStrength: -100, // Default to weak signal (-100 dBm)
+ signalStrengthPercentage: 0 // Default to 0% for visual display
+ };
+
+ this.bluetoothDevices.push(device);
+ this.updateBluetoothPanel();
+ this.updateBluetoothCount();
+ this.syncBluetoothDevices();
+
+ return device;
+ }
+
+ updateBluetoothPanel() {
+ const bluetoothContent = document.getElementById('bluetooth-device-list');
+ if (!bluetoothContent) return;
+
+ const searchTerm = document.getElementById('bluetooth-search')?.value?.toLowerCase() || '';
+
+ // Get active category
+ const activeCategory = this.gameContainer.querySelector('.bluetooth-category.active')?.dataset.category || 'all';
+
+ // Store the currently hovered device, if any
+ const hoveredDevice = bluetoothContent.querySelector('.bluetooth-device:hover');
+ const hoveredDeviceId = hoveredDevice ? hoveredDevice.dataset.id : null;
+
+ // Add Bluetooth-locked items from inventory to the main bluetoothDevices array
+ if (window.inventory && window.inventory.items) {
+ window.inventory.items.forEach(item => {
+ if (item.scenarioData?.lockType === "bluetooth" && item.scenarioData?.locked) {
+ // Check if this device is already in our list
+ const deviceMac = item.scenarioData?.mac || "Unknown";
+
+ // Normalize MAC address format (ensure lowercase for comparison)
+ const normalizedMac = deviceMac.toLowerCase();
+
+ // Check if device already exists in our list (by MAC + name combination)
+ const deviceName = item.scenarioData?.name || item.name || "Unknown Device";
+ const existingDeviceIndex = this.bluetoothDevices.findIndex(device =>
+ device.mac.toLowerCase() === normalizedMac && device.name === deviceName
+ );
+
+ if (existingDeviceIndex === -1) {
+ // Add as a new device
+ const details = `Type: ${item.scenarioData?.type || "Unknown"}\nLocation: Inventory\nStatus: Locked`;
+
+ const newDevice = {
+ id: `inv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ name: deviceName,
+ mac: deviceMac,
+ details: details,
+ lastSeen: new Date(),
+ nearby: true, // Always nearby since it's in inventory
+ saved: true, // Auto-save inventory items
+ signalStrength: -30, // Max strength for inventory items (-30 dBm)
+ signalStrengthPercentage: 100, // 100% for visual display
+ inInventory: true // Mark as inventory item
+ };
+
+ // Add to the main bluetoothDevices array
+ this.bluetoothDevices.push(newDevice);
+ console.log('Added inventory device to bluetoothDevices:', newDevice);
+ this.syncBluetoothDevices();
+ } else {
+ // Update existing device
+ const existingDevice = this.bluetoothDevices[existingDeviceIndex];
+ existingDevice.inInventory = true;
+ existingDevice.nearby = true;
+ existingDevice.signalStrength = -30; // -30 dBm for inventory items
+ existingDevice.signalStrengthPercentage = 100; // 100% for visual display
+ existingDevice.lastSeen = new Date();
+ existingDevice.details = `Type: ${item.scenarioData?.type || "Unknown"}\nLocation: Inventory\nStatus: Locked`;
+ console.log('Updated existing device with inventory info:', existingDevice);
+ this.syncBluetoothDevices();
+ }
+ }
+ });
+ }
+
+ // Filter devices based on search and category
+ let filteredDevices = [...this.bluetoothDevices];
+
+ // Apply category filter
+ if (activeCategory === 'nearby') {
+ filteredDevices = filteredDevices.filter(device => device.nearby);
+ } else if (activeCategory === 'saved') {
+ filteredDevices = filteredDevices.filter(device => device.saved);
+ }
+
+ // Apply search filter
+ if (searchTerm) {
+ filteredDevices = filteredDevices.filter(device =>
+ device.name.toLowerCase().includes(searchTerm) ||
+ device.mac.toLowerCase().includes(searchTerm) ||
+ device.details.toLowerCase().includes(searchTerm)
+ );
+ }
+
+ // Sort devices with inventory items first, then nearby ones, then by signal strength
+ filteredDevices.sort((a, b) => {
+ // Inventory items first
+ if (a.inInventory !== b.inInventory) {
+ return a.inInventory ? -1 : 1;
+ }
+
+ // Then nearby items
+ if (a.nearby !== b.nearby) {
+ return a.nearby ? -1 : 1;
+ }
+
+ // For nearby devices, sort by signal strength
+ if (a.nearby && b.nearby && a.signalStrength !== b.signalStrength) {
+ return b.signalStrength - a.signalStrength;
+ }
+
+ return new Date(b.lastSeen) - new Date(a.lastSeen);
+ });
+
+ // Update device count
+ const deviceCount = this.gameContainer.querySelector('.device-count');
+ if (deviceCount) {
+ deviceCount.textContent = `${filteredDevices.length} device${filteredDevices.length !== 1 ? 's' : ''}`;
+ }
+
+ // Clear current content
+ bluetoothContent.innerHTML = '';
+
+ // Add devices
+ if (filteredDevices.length === 0) {
+ if (searchTerm) {
+ bluetoothContent.innerHTML = '
+ Instructions:
+ • Use "Search for Pickable Locks" to highlight locks in the room
+ • Click highlighted locks to attempt lockpicking
+ • Different locks have different difficulty levels
+ • Higher skill and better tools improve success rates
+
+ `;
+
+ // Assemble the interface
+ this.gameContainer.appendChild(expandToggle);
+ this.gameContainer.appendChild(searchRoomContainer);
+ this.gameContainer.appendChild(lockpickHeader);
+ this.gameContainer.appendChild(instructionsContainer);
+
+ // Set up event listeners
+ this.setupEventListeners();
+
+ // Set up expand/collapse functionality
+ this.setupExpandToggle(expandToggle);
+ }
+
+ setupEventListeners() {
+ // Search locks button
+ const searchLocksBtn = document.getElementById('search-locks-btn');
+ if (searchLocksBtn) {
+ this.addEventListener(searchLocksBtn, 'click', () => this.toggleLockSearching());
+ }
+ }
+
+ setupExpandToggle(expandToggle) {
+ this.addEventListener(expandToggle, 'click', () => {
+ const isExpanded = this.container.classList.contains('expanded');
+
+ if (isExpanded) {
+ // Collapse
+ this.container.classList.remove('expanded');
+ expandToggle.innerHTML = '▼';
+ expandToggle.title = 'Expand';
+ } else {
+ // Expand
+ this.container.classList.add('expanded');
+ expandToggle.innerHTML = '▲';
+ expandToggle.title = 'Collapse';
+ }
+ });
+ }
+
+ toggleLockSearching() {
+ this.searchingMode = !this.searchingMode;
+ const searchBtn = document.getElementById('search-locks-btn');
+
+ if (this.searchingMode) {
+ // Start searching mode
+ searchBtn.classList.add('active');
+ searchBtn.querySelector('.btn-text').textContent = 'Stop Searching';
+ this.highlightPickableLocks();
+ console.log('Lock searching started');
+ } else {
+ // Stop searching mode
+ searchBtn.classList.remove('active');
+ searchBtn.querySelector('.btn-text').textContent = 'Search for Pickable Locks';
+ this.clearHighlights();
+ console.log('Lock searching stopped');
+ }
+ }
+
+ highlightPickableLocks() {
+ // Clear existing highlights
+ this.clearHighlights();
+
+ // Find all objects in the current room that have pickable locks
+ if (!window.currentPlayerRoom || !window.rooms[window.currentPlayerRoom]) {
+ return;
+ }
+
+ const room = window.rooms[window.currentPlayerRoom];
+ this.highlightedObjects = [];
+
+ // Check regular objects
+ if (room.objects) {
+ Object.values(room.objects).forEach(obj => {
+ // Check if object has a lock that can be picked (key locks can be picked)
+ if (obj.scenarioData?.lockType === 'key' || obj.scenarioData?.pickable === true) {
+ // Add highlight effect to the object
+ if (obj.setTint) {
+ obj.setTint(0x00ff00); // Green tint for pickable locks
+ this.highlightedObjects.push(obj);
+ }
+
+ // Add a visual indicator
+ this.addLockpickIndicator(obj);
+ }
+ });
+ }
+
+ // Check doors (they're stored separately)
+ if (room.doors) {
+ Object.values(room.doors).forEach(door => {
+ // Check if door has a lock that can be picked
+ if (door.properties?.lockType === 'key' || door.scenarioData?.lockType === 'key') {
+ // Add highlight effect to the door
+ if (door.setTint) {
+ door.setTint(0x00ff00); // Green tint for pickable locks
+ this.highlightedObjects.push(door);
+ }
+
+ // Add a visual indicator
+ this.addLockpickIndicator(door);
+ }
+ });
+ }
+
+ if (this.highlightedObjects.length > 0) {
+ console.log(`Highlighted ${this.highlightedObjects.length} pickable locks:`, this.highlightedObjects.map(obj => ({
+ id: obj.objectId,
+ name: obj.scenarioData?.name,
+ type: obj.scenarioData?.type,
+ lockType: obj.scenarioData?.lockType
+ })));
+ } else {
+ console.log('No pickable locks found in this room');
+ }
+ }
+
+ addLockpickIndicator(obj) {
+ // Create a lockpick image indicator directly over the object
+ if (obj.scene && obj.scene.add) {
+ const indicator = obj.scene.add.image(obj.x, obj.y, 'lockpick');
+ indicator.setDepth(1000); // High depth to appear on top
+ indicator.setOrigin(-0.25, 0);
+ indicator.setTint(0x00ff00); // Green tint
+
+ // Add pulsing animation
+ obj.scene.tweens.add({
+ targets: indicator,
+ alpha: { from: 1, to: 0.3 },
+ duration: 1000,
+ yoyo: true,
+ repeat: -1
+ });
+
+ // Store reference for cleanup
+ obj.lockpickIndicator = indicator;
+ }
+ }
+
+ clearHighlights() {
+ // Remove highlights from all objects
+ this.highlightedObjects.forEach(obj => {
+ if (obj.clearTint) {
+ obj.clearTint();
+ }
+ if (obj.lockpickIndicator) {
+ obj.lockpickIndicator.destroy();
+ delete obj.lockpickIndicator;
+ }
+ });
+ this.highlightedObjects = [];
+ }
+
+ start() {
+ super.start();
+ console.log("Lockpick set minigame started");
+
+ // Override the cancel button to just close without completion logic
+ if (this.cancelButton) {
+ this.cancelButton.onclick = () => {
+ console.log("Closing lockpick set tool");
+ this.complete(true);
+ };
+ }
+
+ // No need to override interaction handler - just highlight objects
+ // The normal interaction system will handle clicking on highlighted objects
+ }
+
+ attemptLockpicking(obj) {
+ // Start the actual lockpicking minigame using the existing system
+ if (window.startLockpickingMinigame) {
+ console.log('Starting lockpicking minigame for object:', obj);
+
+ // Get difficulty from object data
+ const difficulty = obj.scenarioData?.difficulty || obj.scenarioData?.lockDifficulty || 'medium';
+
+ // Use the existing lockpicking system
+ window.startLockpickingMinigame(obj, window.game, difficulty, (success) => {
+ if (success) {
+ console.log('Lockpicking successful');
+ // The existing system should handle unlocking
+ } else {
+ console.log('Lockpicking failed');
+ }
+ });
+ } else {
+ console.error('Lockpicking minigame not available');
+ if (window.gameAlert) {
+ window.gameAlert('Lockpicking minigame not available', 'error', 'Error', 3000);
+ }
+ }
+ }
+
+ complete(success) {
+ // Stop searching mode and clear highlights
+ if (this.searchingMode) {
+ this.toggleLockSearching();
+ }
+
+ // For the lockpick set minigame, we don't need success/failure logic
+ // Just close the minigame without any completion state
+ super.complete(true, null);
+ }
+
+ cleanup() {
+ // Clear highlights
+ this.clearHighlights();
+
+ // Call parent cleanup
+ super.cleanup();
+ }
+}
+
+// Function to start the lockpick set minigame
+export function startLockpickSetMinigame(item) {
+ console.log('Starting lockpick set minigame with:', { item });
+
+ // Make sure the minigame is registered
+ if (window.MinigameFramework && !window.MinigameFramework.registeredScenes['lockpick-set']) {
+ window.MinigameFramework.registerScene('lockpick-set', LockpickSetMinigame);
+ console.log('Lockpick set minigame registered on demand');
+ }
+
+ // Initialize the framework if not already done
+ if (!window.MinigameFramework.mainGameScene && item && item.scene) {
+ window.MinigameFramework.init(item.scene);
+ }
+
+ // Start the lockpick set minigame with proper parameters
+ const params = {
+ title: 'Lockpick Set',
+ item: item,
+ onComplete: (success, result) => {
+ console.log('Lockpick set minigame completed with success:', success);
+ }
+ };
+
+ console.log('Starting lockpick set minigame with params:', params);
+ window.MinigameFramework.startMinigame('lockpick-set', null, params);
+}
diff --git a/js/systems/biometrics.js b/js/systems/biometrics.js
deleted file mode 100644
index a73e3af..0000000
--- a/js/systems/biometrics.js
+++ /dev/null
@@ -1,375 +0,0 @@
-// Biometrics System
-// Handles biometric sample collection and fingerprint scanning
-
-// Initialize the biometrics system
-export function initializeBiometricsPanel() {
- console.log('Biometrics system initialized');
-
- // Set up biometric scanner state
- if (!window.gameState.biometricSamples) {
- window.gameState.biometricSamples = [];
- }
-
- // Scanner state management
- window.scannerState = {
- failedAttempts: {},
- lockoutTimers: {}
- };
-
- // Scanner constants
- window.MAX_FAILED_ATTEMPTS = 3;
- window.SCANNER_LOCKOUT_TIME = 30000; // 30 seconds
- window.BIOMETRIC_QUALITY_THRESHOLD = 0.7;
-
- // Initialize biometric panel UI
- setupBiometricPanel();
-
- // Set up biometrics toggle button
- const biometricsToggle = document.getElementById('biometrics-toggle');
- if (biometricsToggle) {
- biometricsToggle.addEventListener('click', toggleBiometricsPanel);
- }
-
- // Set up biometrics close button
- const biometricsClose = document.getElementById('biometrics-close');
- if (biometricsClose) {
- biometricsClose.addEventListener('click', toggleBiometricsPanel);
- }
-
- // Set up search functionality
- const biometricsSearch = document.getElementById('biometrics-search');
- if (biometricsSearch) {
- biometricsSearch.addEventListener('input', updateBiometricsPanel);
- }
-
- // Set up category filters
- const categories = document.querySelectorAll('.biometrics-category');
- categories.forEach(category => {
- category.addEventListener('click', () => {
- // Remove active class from all categories
- categories.forEach(c => c.classList.remove('active'));
- // Add active class to clicked category
- category.classList.add('active');
- // Update biometrics panel
- updateBiometricsPanel();
- });
- });
-
- // Initialize biometrics count
- updateBiometricsCount();
-}
-
-function setupBiometricPanel() {
- const biometricPanel = document.getElementById('biometrics-panel');
- if (!biometricPanel) {
- console.error('Biometric panel not found');
- return;
- }
-
- // Use existing biometrics content container
- const biometricsContent = document.getElementById('biometrics-content');
- if (biometricsContent) {
- biometricsContent.innerHTML = `
-
';
- }
- } else {
- filteredDevices.forEach(device => {
- const deviceElement = document.createElement('div');
- deviceElement.className = 'bluetooth-device';
- deviceElement.dataset.id = device.id;
-
- // If this was the hovered device, add the hover class
- if (hoveredDeviceId && device.id === hoveredDeviceId) {
- deviceElement.classList.add('hover-preserved');
- }
-
- // Format the timestamp
- const timestamp = new Date(device.lastSeen);
- const formattedDate = timestamp.toLocaleDateString();
- const formattedTime = timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
-
- // Get signal color based on strength
- const getSignalColor = (strength) => {
- if (strength >= 80) return '#00cc00'; // Strong - green
- if (strength >= 50) return '#cccc00'; // Medium - yellow
- return '#cc5500'; // Weak - orange
- };
-
- let deviceContent = `
- ${device.name}
-
`;
-
- if (device.nearby && typeof device.signalStrength === 'number') {
- // Use percentage for visual display
- const signalPercentage = device.signalStrengthPercentage || Math.max(0, Math.round(((device.signalStrength + 100) / 70) * 100));
- const signalColor = getSignalColor(signalPercentage);
-
- // Calculate how many bars should be active based on signal strength percentage
- const activeBars = Math.ceil(signalPercentage / 20); // 0-20% = 1 bar, 21-40% = 2 bars, etc.
-
- deviceContent += `
-
`;
-
- for (let i = 1; i <= 5; i++) {
- const isActive = i <= activeBars;
- deviceContent += ``;
- }
-
- deviceContent += `
`;
- } else if (device.nearby) {
- // Fallback if signal strength not available
- deviceContent += `📶`;
- }
-
- if (device.saved) {
- deviceContent += `💾`;
- }
-
- if (device.inInventory) {
- deviceContent += `🎒`;
- }
-
- deviceContent += `
`;
- deviceContent += `
MAC: ${device.mac}\n${device.details}
`;
- deviceContent += `
Last seen: ${formattedDate} ${formattedTime}
`;
-
- deviceElement.innerHTML = deviceContent;
-
- // Toggle expanded state when clicked
- deviceElement.addEventListener('click', (event) => {
- deviceElement.classList.toggle('expanded');
-
- // Mark as saved when expanded
- if (!device.saved && deviceElement.classList.contains('expanded')) {
- device.saved = true;
- updateBluetoothCount();
- updateBluetoothPanel();
- syncBluetoothDevices();
- }
- });
-
- bluetoothContent.appendChild(deviceElement);
- });
- }
-}
-
-// Update the new Bluetooth devices count
-export function updateBluetoothCount() {
- const bluetoothCount = document.getElementById('bluetooth-count');
- if (bluetoothCount) {
- newBluetoothDevices = bluetoothDevices.filter(device => !device.saved && device.nearby).length;
-
- bluetoothCount.textContent = newBluetoothDevices;
- bluetoothCount.style.display = newBluetoothDevices > 0 ? 'flex' : 'none';
- }
-}
-
-export function toggleBluetoothPanel() {
- const bluetoothPanel = document.getElementById('bluetooth-panel');
- if (!bluetoothPanel) return;
-
- const isVisible = bluetoothPanel.style.display === 'block';
- bluetoothPanel.style.display = isVisible ? 'none' : 'block';
-
- // Always update panel content when opening to show current state
- if (!isVisible) {
- updateBluetoothPanel();
- // Reset the throttle timer so updates happen immediately when panel is open
- lastBluetoothPanelUpdate = 0;
- }
-}
-
-// Function to unlock a Bluetooth-locked inventory item by MAC address
-export function unlockInventoryDeviceByMac(mac) {
- console.log('Attempting to unlock inventory device with MAC:', mac);
-
- // Normalize MAC address for comparison
- const normalizedMac = mac.toLowerCase();
-
- // Find the inventory item with this MAC address
- const item = window.inventory.items.find(item =>
- item.scenarioData?.mac?.toLowerCase() === normalizedMac &&
- item.scenarioData?.lockType === "bluetooth" &&
- item.scenarioData?.locked
- );
-
- if (!item) {
- console.error('Inventory item not found with MAC:', mac);
- if (window.gameAlert) {
- window.gameAlert("Device not found in inventory.", 'error', 'Unlock Failed', 3000);
- }
- return;
- }
-
- console.log('Found inventory item to unlock:', item);
-}
-
-// Export for global access
-window.initializeBluetoothPanel = initializeBluetoothPanel;
-window.checkBluetoothDevices = checkBluetoothDevices;
-window.addBluetoothDevice = addBluetoothDevice;
-window.toggleBluetoothPanel = toggleBluetoothPanel;
-window.updateBluetoothPanel = updateBluetoothPanel;
-window.updateBluetoothCount = updateBluetoothCount;
-window.unlockInventoryDeviceByMac = unlockInventoryDeviceByMac;
\ No newline at end of file
diff --git a/js/systems/interactions.js b/js/systems/interactions.js
index e0f6a24..0cc223c 100644
--- a/js/systems/interactions.js
+++ b/js/systems/interactions.js
@@ -379,6 +379,45 @@ export function handleObjectInteraction(sprite) {
}
}
+ // Handle the Bluetooth Scanner - only open minigame if it's already in inventory
+ if (sprite.scenarioData.type === "bluetooth_scanner") {
+ // Check if this is an inventory item (clicked from inventory)
+ const isInventoryItem = sprite.objectId && sprite.objectId.startsWith('inventory_');
+
+ if (isInventoryItem && window.startBluetoothScannerMinigame) {
+ console.log('Starting bluetooth scanner minigame from inventory');
+ window.startBluetoothScannerMinigame(sprite);
+ return;
+ }
+ // If it's not in inventory, let it fall through to the takeable logic below
+ }
+
+ // Handle the Fingerprint Kit - only open minigame if it's already in inventory
+ if (sprite.scenarioData.type === "fingerprint_kit") {
+ // Check if this is an inventory item (clicked from inventory)
+ const isInventoryItem = sprite.objectId && sprite.objectId.startsWith('inventory_');
+
+ if (isInventoryItem && window.startBiometricsMinigame) {
+ console.log('Starting biometrics minigame from inventory');
+ window.startBiometricsMinigame(sprite);
+ return;
+ }
+ // If it's not in inventory, let it fall through to the takeable logic below
+ }
+
+ // Handle the Lockpick Set - only open minigame if it's already in inventory
+ if (sprite.scenarioData.type === "lockpick" || sprite.scenarioData.type === "lockpickset") {
+ // Check if this is an inventory item (clicked from inventory)
+ const isInventoryItem = sprite.objectId && sprite.objectId.startsWith('inventory_');
+
+ if (isInventoryItem && window.startLockpickSetMinigame) {
+ console.log('Starting lockpick set minigame from inventory');
+ window.startLockpickSetMinigame(sprite);
+ return;
+ }
+ // If it's not in inventory, let it fall through to the takeable logic below
+ }
+
// Handle biometric scanner interaction
if (sprite.scenarioData.biometricType === 'fingerprint') {
handleBiometricScan(sprite);
@@ -577,22 +616,35 @@ function addToInventory(sprite) {
// Show notification
window.gameAlert(`Added ${sprite.scenarioData.name} to inventory`, 'success', 'Item Collected', 3000);
- // If this is the Bluetooth scanner, show the toggle button
- if (sprite.scenarioData.type === "bluetooth_scanner") {
- const bluetoothToggle = document.getElementById('bluetooth-toggle');
- if (bluetoothToggle) {
- bluetoothToggle.style.display = 'flex';
- }
+ // If this is the Bluetooth scanner, automatically open the minigame after adding to inventory
+ if (sprite.scenarioData.type === "bluetooth_scanner" && window.startBluetoothScannerMinigame) {
+ // Small delay to ensure the item is fully added to inventory
+ setTimeout(() => {
+ console.log('Auto-opening bluetooth scanner minigame after adding to inventory');
+ window.startBluetoothScannerMinigame(itemImg);
+ }, 500);
}
- // If this is the fingerprint kit, show the biometrics toggle button
- if (sprite.scenarioData.type === "fingerprint_kit") {
- const biometricsToggle = document.getElementById('biometrics-toggle');
- if (biometricsToggle) {
- biometricsToggle.style.display = 'flex';
- }
+ // If this is the Fingerprint Kit, automatically open the minigame after adding to inventory
+ if (sprite.scenarioData.type === "fingerprint_kit" && window.startBiometricsMinigame) {
+ // Small delay to ensure the item is fully added to inventory
+ setTimeout(() => {
+ console.log('Auto-opening biometrics minigame after adding to inventory');
+ window.startBiometricsMinigame(itemImg);
+ }, 500);
}
+ // If this is the Lockpick Set, automatically open the minigame after adding to inventory
+ if ((sprite.scenarioData.type === "lockpick" || sprite.scenarioData.type === "lockpickset") && window.startLockpickSetMinigame) {
+ // Small delay to ensure the item is fully added to inventory
+ setTimeout(() => {
+ console.log('Auto-opening lockpick set minigame after adding to inventory');
+ window.startLockpickSetMinigame(itemImg);
+ }, 500);
+ }
+
+ // Fingerprint kit is now handled as a minigame when clicked from inventory
+
return true;
} catch (error) {
console.error('Error adding to inventory:', error);
diff --git a/js/systems/inventory.js b/js/systems/inventory.js
index e63d16f..3a31a80 100644
--- a/js/systems/inventory.js
+++ b/js/systems/inventory.js
@@ -62,7 +62,7 @@ function createInventorySprite(itemData) {
// Create a pseudo-sprite object that can be used in inventory
const sprite = {
name: itemData.type,
- objectId: `initial_${itemData.type}_${Date.now()}`,
+ objectId: `inventory_${itemData.type}_${Date.now()}`,
scenarioData: itemData,
setVisible: function(visible) {
// For inventory items, visibility is handled by DOM
@@ -149,22 +149,35 @@ function addToInventory(sprite) {
window.gameAlert(`Added ${sprite.scenarioData.name} to inventory`, 'success', 'Item Collected', 3000);
}
- // If this is the Bluetooth scanner, show the toggle button
- if (sprite.scenarioData.type === "bluetooth_scanner") {
- const bluetoothToggle = document.getElementById('bluetooth-toggle');
- if (bluetoothToggle) {
- bluetoothToggle.style.display = 'flex';
- }
+ // If this is the Bluetooth scanner, automatically open the minigame after adding to inventory
+ if (sprite.scenarioData.type === "bluetooth_scanner" && window.startBluetoothScannerMinigame) {
+ // Small delay to ensure the item is fully added to inventory
+ setTimeout(() => {
+ console.log('Auto-opening bluetooth scanner minigame after adding to inventory');
+ window.startBluetoothScannerMinigame(itemImg);
+ }, 500);
}
- // If this is the fingerprint kit, show the biometrics toggle button
- if (sprite.scenarioData.type === "fingerprint_kit") {
- const biometricsToggle = document.getElementById('biometrics-toggle');
- if (biometricsToggle) {
- biometricsToggle.style.display = 'flex';
- }
+ // If this is the Fingerprint Kit, automatically open the minigame after adding to inventory
+ if (sprite.scenarioData.type === "fingerprint_kit" && window.startBiometricsMinigame) {
+ // Small delay to ensure the item is fully added to inventory
+ setTimeout(() => {
+ console.log('Auto-opening biometrics minigame after adding to inventory');
+ window.startBiometricsMinigame(itemImg);
+ }, 500);
}
+ // If this is the Lockpick Set, automatically open the minigame after adding to inventory
+ if ((sprite.scenarioData.type === "lockpick" || sprite.scenarioData.type === "lockpickset") && window.startLockpickSetMinigame) {
+ // Small delay to ensure the item is fully added to inventory
+ setTimeout(() => {
+ console.log('Auto-opening lockpick set minigame after adding to inventory');
+ window.startLockpickSetMinigame(itemImg);
+ }, 500);
+ }
+
+ // Fingerprint kit is now handled as a minigame when clicked from inventory
+
// Handle crypto workstation - use the proper modal implementation from helpers.js
if (sprite.scenarioData.type === "workstation") {
// Don't override the openCryptoWorkstation function - it's already properly defined in helpers.js
diff --git a/js/ui/panels.js b/js/ui/panels.js
index 4bb9392..fed7ad9 100644
--- a/js/ui/panels.js
+++ b/js/ui/panels.js
@@ -5,7 +5,7 @@
export function initializeUI() {
console.log('UI panels system initialized');
- // Note: Individual systems (notes.js, biometrics.js, bluetooth.js) handle their own panel setup
+ // Note: Individual systems (notes.js, biometrics.js) handle their own panel setup
// This file only provides utility functions for generic panel operations
}