From e9e2ba7fcfb7722f82db005fe3197f9e0ae9b056 Mon Sep 17 00:00:00 2001 From: Damian Idzinski <45018439+Damian-I@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:29:02 +0000 Subject: [PATCH 01/13] Add files via upload --- index.html | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 290608d..6c02819 100644 --- a/index.html +++ b/index.html @@ -112,6 +112,20 @@ background: rgba(0, 0, 0, 0.5); z-index: 999; } + + #bluetooth-scanner-ui { + position: absolute; + top: 10px; + right: 10px; + display: none; + } + + .scanner-display { + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 10px; + font-family: sans-serif; + } @@ -120,7 +134,13 @@
Loading...
- + +
+
+ Bluetooth Scanner Active +
+
+ \ No newline at end of file From d4821b2eba877a5b33e12fe02bf4620aabf11c7d Mon Sep 17 00:00:00 2001 From: Damian Idzinski <45018439+Damian-I@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:29:44 +0000 Subject: [PATCH 02/13] Add files via upload --- assets/scenarios/ceo_exfil.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/scenarios/ceo_exfil.json b/assets/scenarios/ceo_exfil.json index 701d051..005560b 100644 --- a/assets/scenarios/ceo_exfil.json +++ b/assets/scenarios/ceo_exfil.json @@ -38,6 +38,7 @@ "locked": true, "lockType": "bluetooth", "mac": "00:11:22:33:44:55", + "range": 2, "observations": "A locked tablet device that requires Bluetooth pairing" }, { From 469747e642acc906f4f245262f6441951f8a6ff5 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 13:19:58 +0000 Subject: [PATCH 03/13] Re synced with main --- assets/objects/fingerprint_kit.png | Bin 0 -> 5698 bytes assets/rooms/room_office.json | 40 +- assets/scenarios/ceo_exfil.json | 27 +- assets/sounds/lockpick_binding.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_click.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_overtension.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_reset.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_set.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_success.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_tension.mp3 | Bin 0 -> 13824 bytes assets/sounds/lockpick_wrong.mp3 | Bin 0 -> 13824 bytes index.html | 2716 ++++++++++++++++++++++-- 12 files changed, 2606 insertions(+), 177 deletions(-) create mode 100644 assets/objects/fingerprint_kit.png create mode 100644 assets/sounds/lockpick_binding.mp3 create mode 100644 assets/sounds/lockpick_click.mp3 create mode 100644 assets/sounds/lockpick_overtension.mp3 create mode 100644 assets/sounds/lockpick_reset.mp3 create mode 100644 assets/sounds/lockpick_set.mp3 create mode 100644 assets/sounds/lockpick_success.mp3 create mode 100644 assets/sounds/lockpick_tension.mp3 create mode 100644 assets/sounds/lockpick_wrong.mp3 diff --git a/assets/objects/fingerprint_kit.png b/assets/objects/fingerprint_kit.png new file mode 100644 index 0000000000000000000000000000000000000000..db802b0011aa1e5879449ac69057e4144289cbc2 GIT binary patch literal 5698 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 JEpuMC<-Z-#(_{bu literal 0 HcmV?d00001 diff --git a/assets/rooms/room_office.json b/assets/rooms/room_office.json index bad8649..b79f272 100644 --- a/assets/rooms/room_office.json +++ b/assets/rooms/room_office.json @@ -316,7 +316,43 @@ "width":48, "x":100.927703875072, "y":101.732793522267 - }], + }, + { + "height":48, + "id":15, + "name":"fingerprint_kit", + "rotation":0, + "type":"", + "visible":true, + "width":48, + "x":390, + "y":144 + }, + { + "height": 48, + "id": 16, + "name": "spoofing_kit", + "rotation": 0, + "type": "", + "visible": true, + "width": 48, + "x": 340, + "y": 144 + }, + { + "height": 48, + "id": 17, + "name": "lockpick", + "rotation": 0, + "type": "", + "visible": true, + "width": 48, + "x": 330, + "y": 144 + } + + + ], "opacity":1, "type":"objectgroup", "visible":true, @@ -324,7 +360,7 @@ "y":0 }], "nextlayerid":12, - "nextobjectid":15, + "nextobjectid":18, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.11.0", diff --git a/assets/scenarios/ceo_exfil.json b/assets/scenarios/ceo_exfil.json index 005560b..bf5be26 100644 --- a/assets/scenarios/ceo_exfil.json +++ b/assets/scenarios/ceo_exfil.json @@ -38,7 +38,6 @@ "locked": true, "lockType": "bluetooth", "mac": "00:11:22:33:44:55", - "range": 2, "observations": "A locked tablet device that requires Bluetooth pairing" }, { @@ -63,7 +62,10 @@ "name": "Office Computer", "takeable": false, "requires": "password", - "observations": "A computer with a cybersecurity alert on screen" + "hasFingerprint": true, + "fingerprintOwner": "ceo", + "fingerprintQuality": 0.9, + "observations": "A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard." }, { "type": "notes", @@ -72,6 +74,18 @@ "readable": true, "text": "URGENT: Multiple unauthorized access attempts detected from CEO's office IP address", "observations": "A concerning IT department memo" + }, + { + "type": "fingerprint_kit", + "name": "Fingerprint Kit", + "takeable": true, + "observations": "A kit used for collecting fingerprints from surfaces" + }, + { + "type": "spoofing_kit", + "name": "Fingerprint Spoofing Kit", + "takeable": true, + "observations": "A specialized kit containing silicone, gelatin, and other materials for creating artificial fingerprints" } ] }, @@ -127,6 +141,12 @@ "readable": true, "text": "Large data transfers detected to unknown external IPs - All originating from CEO's office", "observations": "Suspicious network activity logs" + }, + { + "type": "lockpick", + "name": "Lock Pick Kit", + "takeable": true, + "observations": "A professional lock picking kit with various picks and tension wrenches" } ] }, @@ -139,6 +159,7 @@ "locked": true, "lockType": "key", "requires": "ceo_office_key", + "difficulty": "easy", "objects": [ { "type": "pc", @@ -153,6 +174,7 @@ "locked": true, "lockType": "key", "requires": "briefcase_key", + "difficulty": "medium", "observations": "An expensive leather briefcase with a sturdy lock", "contents": [ { @@ -198,6 +220,7 @@ "locked": true, "lockType": "key", "requires": "safe_key", + "difficulty": "hard", "observations": "A well-hidden wall safe behind a painting", "contents": [ { diff --git a/assets/sounds/lockpick_binding.mp3 b/assets/sounds/lockpick_binding.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bdf479bafff351d821df2c85334892093b24ff45 GIT binary patch literal 13824 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 @@ -134,13 +398,31 @@
Loading...
- -
-
- Bluetooth Scanner Active + + +
+ + +
+
+
Notes & Information
+
×
+
+ +
+
+
All
+
Important
+
Unread
+
+
- +
+ 📝 +
0
+
+ From 5fdcee5c22267a7b5d0d0943d0db453fef906573 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 13:27:11 +0000 Subject: [PATCH 04/13] Add Bluetooth Spoofer to game assets and reception room - Load Bluetooth Spoofer image asset - Add Bluetooth Spoofer object to reception room JSON - Include Bluetooth Spoofer in game scenario with description and spoofing capability --- assets/rooms/room_reception.json | 13 ++++++++++++- assets/scenarios/ceo_exfil.json | 9 ++++++++- index.html | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/assets/rooms/room_reception.json b/assets/rooms/room_reception.json index eb88f44..265cd2b 100644 --- a/assets/rooms/room_reception.json +++ b/assets/rooms/room_reception.json @@ -244,6 +244,17 @@ "width":48, "x":380, "y":166 + }, + { + "height":48, + "id":13, + "name":"bluetooth_spoofer", + "rotation":0, + "type":"", + "visible":true, + "width":48, + "x":320, + "y":166 } ], "opacity":1, @@ -253,7 +264,7 @@ "y":0 }], "nextlayerid":12, - "nextobjectid":11, + "nextobjectid":14, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.11.0", diff --git a/assets/scenarios/ceo_exfil.json b/assets/scenarios/ceo_exfil.json index bf5be26..2a77d16 100644 --- a/assets/scenarios/ceo_exfil.json +++ b/assets/scenarios/ceo_exfil.json @@ -45,7 +45,14 @@ "name": "Bluetooth Scanner", "takeable": true, "observations": "A device for detecting nearby Bluetooth signals", - "canScanBluetooth": true, + "canScanBluetooth": true + }, + { + "type": "bluetooth_spoofer", + "name": "Bluetooth Spoofer", + "takeable": true, + "observations": "A specialized device that can mimic Bluetooth signals from other devices", + "canSpoofBluetooth": true, "mac": "00:11:22:33:44:55" } ] diff --git a/index.html b/index.html index 93820bb..abfa0b7 100644 --- a/index.html +++ b/index.html @@ -955,6 +955,7 @@ this.load.image('book', 'assets/objects/book.png'); this.load.image('workstation', 'assets/objects/workstation.png'); this.load.image('bluetooth_scanner', 'assets/objects/bluetooth_scanner.png'); + this.load.image('bluetooth_spoofer', 'assets/objects/bluetooth_spoofer.png'); this.load.image('tablet', 'assets/objects/tablet.png'); this.load.image('fingerprint_kit', 'assets/objects/fingerprint_kit.png'); this.load.image('lockpick', 'assets/objects/lockpick.png'); From 16d5abfb1cfc4c9c338ff48961ebec471b272e9f Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 15:00:17 +0000 Subject: [PATCH 05/13] Implement Bluetooth Scanner Panel with Advanced Device Detection and Interaction - Add comprehensive Bluetooth scanner UI with search, filtering, and device tracking - Implement device detection, tracking, and categorization - Create interactive Bluetooth panel with device details and expanded view - Add proximity-based device scanning and notification system - Enhance inventory interaction for Bluetooth scanner and spoofer - Improve device unlocking and spoofing mechanics --- index.html | 576 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 536 insertions(+), 40 deletions(-) diff --git a/index.html b/index.html index abfa0b7..d6ebbd0 100644 --- a/index.html +++ b/index.html @@ -390,6 +390,204 @@ background: rgba(0, 0, 0, 0.5); z-index: 999; } + + /* Bluetooth Scanner Panel */ + #bluetooth-panel { + position: fixed; + bottom: 80px; + right: 90px; + width: 350px; + max-height: 500px; + background-color: rgba(0, 0, 0, 0.9); + color: white; + border-radius: 5px; + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.5); + z-index: 1999; + font-family: Arial, sans-serif; + display: none; + overflow: hidden; + transition: all 0.3s ease; + border: 1px solid #444; + } + + #bluetooth-header { + background-color: #222; + padding: 12px 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #444; + } + + #bluetooth-title { + font-weight: bold; + font-size: 18px; + color: #9b59b6; + } + + #bluetooth-close { + cursor: pointer; + font-size: 18px; + color: #aaa; + transition: color 0.2s; + } + + #bluetooth-close:hover { + color: white; + } + + #bluetooth-search-container { + padding: 10px 15px; + background-color: #333; + border-bottom: 1px solid #444; + } + + #bluetooth-search { + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 3px; + background-color: #222; + color: white; + font-size: 14px; + } + + #bluetooth-search:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(155, 89, 182, 0.5); + } + + #bluetooth-categories { + display: flex; + padding: 5px 15px; + background-color: #2c2c2c; + border-bottom: 1px solid #444; + } + + .bluetooth-category { + padding: 5px 10px; + margin-right: 5px; + cursor: pointer; + border-radius: 3px; + font-size: 12px; + transition: all 0.2s; + } + + .bluetooth-category.active { + background-color: #9b59b6; + color: white; + } + + .bluetooth-category:hover:not(.active) { + background-color: #444; + } + + #bluetooth-content { + padding: 15px; + overflow-y: auto; + max-height: 350px; + } + + .bluetooth-device { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px solid #444; + cursor: pointer; + transition: background-color 0.2s; + padding: 10px; + border-radius: 3px; + } + + .bluetooth-device:hover { + background-color: #333; + } + + .bluetooth-device:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; + } + + .bluetooth-device-name { + font-weight: bold; + margin-bottom: 5px; + font-size: 14px; + color: #9b59b6; + display: flex; + justify-content: space-between; + align-items: center; + } + + .bluetooth-device-icons { + display: flex; + gap: 5px; + } + + .bluetooth-device-icon { + font-size: 12px; + color: #aaa; + } + + .bluetooth-device-details { + font-size: 13px; + line-height: 1.4; + white-space: pre-wrap; + max-height: 80px; + overflow: hidden; + transition: max-height 0.3s; + } + + .bluetooth-device.expanded .bluetooth-device-details { + max-height: 1000px; + } + + .bluetooth-device-timestamp { + font-size: 11px; + color: #888; + margin-top: 5px; + text-align: right; + } + + #bluetooth-toggle { + position: fixed; + bottom: 20px; + right: 90px; + width: 60px; + height: 60px; + background-color: #9b59b6; + color: white; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + z-index: 1998; + font-size: 28px; + transition: all 0.3s ease; + } + + #bluetooth-toggle:hover { + background-color: #8e44ad; + transform: scale(1.1); + } + + #bluetooth-count { + position: absolute; + top: -5px; + right: -5px; + background-color: #e74c3c; + color: white; + border-radius: 50%; + width: 24px; + height: 24px; + font-size: 14px; + display: flex; + justify-content: center; + align-items: center; + font-weight: bold; + display: none; + } @@ -423,6 +621,27 @@
0
+ +
+
+
Bluetooth Scanner
+
×
+
+
+ +
+
+
All
+
Nearby
+
Saved
+
+
+
+ + \ No newline at end of file From e208f36910ca0a6cb868781b43d8729343193eb8 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 15:53:08 +0000 Subject: [PATCH 06/13] Enhance Bluetooth Scanner with Real-Time Signal Strength and Dynamic Device Tracking - Implement dynamic signal strength visualization with color-coded bars - Improve Bluetooth device detection with more responsive scanning interval - Add real-time signal strength tracking and sorting for nearby devices - Optimize device detection logic to handle device appearance and disappearance - Enhance Bluetooth panel UI with detailed signal information --- index.html | 107 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index d6ebbd0..0feacfa 100644 --- a/index.html +++ b/index.html @@ -547,6 +547,36 @@ margin-top: 5px; text-align: right; } + + /* Bluetooth Signal Strength Bar */ + .bluetooth-signal-bar-container { + margin-top: 8px; + width: 100%; + height: 20px; + background-color: #222; + border-radius: 3px; + position: relative; + overflow: hidden; + } + + .bluetooth-signal-bar { + height: 100%; + background-color: #9b59b6; + transition: width 0.5s ease, background-color 0.5s ease; + } + + .bluetooth-signal-text { + position: absolute; + top: 0; + left: 0; + right: 0; + line-height: 20px; + text-align: center; + color: white; + font-size: 11px; + font-weight: bold; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); + } #bluetooth-toggle { position: fixed; @@ -1074,7 +1104,7 @@ // Bluetooth constants const BLUETOOTH_SCAN_RANGE = TILE_SIZE * 2; // 2 tiles range for Bluetooth scanning let lastBluetoothScan = 0; // Track last scan time - const BLUETOOTH_SCAN_INTERVAL = 500; // Scan every 500ms + const BLUETOOTH_SCAN_INTERVAL = 200; // Scan every 200ms for more responsive updates const gameState = { biometricSamples: [], @@ -3222,6 +3252,9 @@ // Find all Bluetooth devices in the current room if (!currentRoom || !rooms[currentRoom] || !rooms[currentRoom].objects) return; + // Keep track of devices detected in this scan + const detectedDevices = new Set(); + Object.values(rooms[currentRoom].objects).forEach(obj => { if (obj.scenarioData?.lockType === "bluetooth") { const distance = Phaser.Math.Distance.Between( @@ -3229,39 +3262,54 @@ obj.x, obj.y ); + const deviceMac = obj.scenarioData?.mac || "Unknown"; + if (distance <= BLUETOOTH_SCAN_RANGE) { + detectedDevices.add(deviceMac); + debugLog('BLUETOOTH DEVICE DETECTED', { deviceName: obj.scenarioData?.name, - deviceMac: obj.scenarioData?.mac, + deviceMac: deviceMac, distance: Math.round(distance), range: BLUETOOTH_SCAN_RANGE }, 2); // Add to Bluetooth scanner panel const deviceName = obj.scenarioData?.name || "Unknown Device"; - const deviceMac = obj.scenarioData?.mac || "Unknown"; - const details = `Type: ${obj.scenarioData?.type || "Unknown"}\nDistance: ${Math.round(distance)} units\nSignal Strength: ${Math.max(0, Math.round(100 - (distance / BLUETOOTH_SCAN_RANGE * 100)))}%`; + const signalStrength = Math.max(0, Math.round(100 - (distance / BLUETOOTH_SCAN_RANGE * 100))); + const details = `Type: ${obj.scenarioData?.type || "Unknown"}\nDistance: ${Math.round(distance)} units\nSignal Strength: ${signalStrength}%`; - addBluetoothDevice(deviceName, deviceMac, details, true); - - // Only show notification for new devices - if (!bluetoothDevices.some(device => device.mac === deviceMac)) { - gameAlert(`Bluetooth device detected: ${deviceName} (MAC: ${deviceMac})`, 'info', 'Bluetooth Scanner', 4000); - } - } else { - // Update device as not nearby if it's in our list - const deviceMac = obj.scenarioData?.mac || "Unknown"; + // Check if device already exists in our list const existingDevice = bluetoothDevices.find(device => device.mac === deviceMac); - if (existingDevice && existingDevice.nearby) { - existingDevice.nearby = false; + if (existingDevice) { + // Update existing device details with real-time data + existingDevice.details = details; existingDevice.lastSeen = new Date(); + existingDevice.nearby = true; + existingDevice.signalStrength = signalStrength; updateBluetoothPanel(); - updateBluetoothCount(); + } else { + // Add as new device if not already in our list + const newDevice = addBluetoothDevice(deviceName, deviceMac, details, true); + if (newDevice) { + newDevice.signalStrength = signalStrength; + gameAlert(`Bluetooth device detected: ${deviceName} (MAC: ${deviceMac})`, 'info', 'Bluetooth Scanner', 4000); + } } } } }); + + // Mark devices that weren't detected in this scan as not nearby + bluetoothDevices.forEach(device => { + if (device.nearby && !detectedDevices.has(device.mac)) { + device.nearby = false; + device.lastSeen = new Date(); + updateBluetoothPanel(); + updateBluetoothCount(); + } + }); } function spoofBluetoothDevice(target) { @@ -5001,11 +5049,17 @@ ); } - // Sort devices with nearby ones first, then by last seen (newest first) + // Sort devices with nearby ones first, then by signal strength (highest first for nearby), then by last seen (newest first) filteredDevices.sort((a, b) => { 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); }); @@ -5032,11 +5086,19 @@ 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) { + const signalStrength = device.signalStrength || 0; deviceContent += `📶`; } if (device.saved) { @@ -5045,6 +5107,17 @@ deviceContent += `
`; deviceContent += `
MAC: ${device.mac}\n${device.details}
`; + + // Add signal strength bar for nearby devices + if (device.nearby && typeof device.signalStrength === 'number') { + const signalColor = getSignalColor(device.signalStrength); + deviceContent += ` +
+
+
${device.signalStrength}% Signal
+
`; + } + deviceContent += `
Last seen: ${formattedDate} ${formattedTime}
`; deviceElement.innerHTML = deviceContent; From bad5a49c43f3e0ff923130f8f92e1b37be55e9a6 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 16:00:25 +0000 Subject: [PATCH 07/13] Added realtime updates, signal strength bar --- index.html | 85 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/index.html b/index.html index 0feacfa..1e31b52 100644 --- a/index.html +++ b/index.html @@ -550,32 +550,38 @@ /* Bluetooth Signal Strength Bar */ .bluetooth-signal-bar-container { - margin-top: 8px; - width: 100%; - height: 20px; - background-color: #222; - border-radius: 3px; - position: relative; - overflow: hidden; + margin: 0; + display: flex; + align-items: flex-end; + height: 16px; } - + + .bluetooth-signal-bars { + display: flex; + align-items: flex-end; + height: 16px; + gap: 1px; + } + .bluetooth-signal-bar { - height: 100%; - background-color: #9b59b6; - transition: width 0.5s ease, background-color 0.5s ease; + width: 3px; + background-color: #444; + border-radius: 1px; + transition: background-color 0.3s ease; } - + + .bluetooth-signal-bar.active { + background-color: currentColor; + } + + .bluetooth-signal-bar:nth-child(1) { height: 3px; } + .bluetooth-signal-bar:nth-child(2) { height: 6px; } + .bluetooth-signal-bar:nth-child(3) { height: 9px; } + .bluetooth-signal-bar:nth-child(4) { height: 12px; } + .bluetooth-signal-bar:nth-child(5) { height: 16px; } + .bluetooth-signal-text { - position: absolute; - top: 0; - left: 0; - right: 0; - line-height: 20px; - text-align: center; - color: white; - font-size: 11px; - font-weight: bold; - text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); + display: none; } #bluetooth-toggle { @@ -5097,29 +5103,34 @@ ${device.name}
`; - if (device.nearby) { - const signalStrength = device.signalStrength || 0; + if (device.nearby && typeof device.signalStrength === 'number') { + const signalColor = getSignalColor(device.signalStrength); + + // Calculate how many bars should be active based on signal strength + const activeBars = Math.ceil(device.signalStrength / 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 += `💾`; } - + deviceContent += `
`; deviceContent += `
MAC: ${device.mac}\n${device.details}
`; - - // Add signal strength bar for nearby devices - if (device.nearby && typeof device.signalStrength === 'number') { - const signalColor = getSignalColor(device.signalStrength); - deviceContent += ` -
-
-
${device.signalStrength}% Signal
-
`; - } - deviceContent += `
Last seen: ${formattedDate} ${formattedTime}
`; - + deviceElement.innerHTML = deviceContent; // Toggle expanded state when clicked From f2b53c608aa79e948f04a5250f6fb711437438a5 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 16:06:45 +0000 Subject: [PATCH 08/13] Add Bluetooth Pairing Button for Nearby Devices --- index.html | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/index.html b/index.html index 1e31b52..a69d909 100644 --- a/index.html +++ b/index.html @@ -624,6 +624,24 @@ font-weight: bold; display: none; } + + /* Bluetooth Pairing Button */ + .bluetooth-pair-button { + display: inline-block; + margin-top: 8px; + padding: 5px 10px; + background-color: #9b59b6; + color: white; + border: none; + border-radius: 3px; + font-size: 12px; + cursor: pointer; + transition: background-color 0.2s; + } + + .bluetooth-pair-button:hover { + background-color: #8e44ad; + } @@ -5129,6 +5147,19 @@ deviceContent += ``; deviceContent += `
MAC: ${device.mac}\n${device.details}
`; + + // Add pairing button only if device is nearby and player has a Bluetooth spoofer + if (device.nearby) { + // Check if player has a Bluetooth spoofer in inventory + const hasSpoofer = inventory.items.some(item => + item.scenarioData?.type === "bluetooth_spoofer" + ); + + if (hasSpoofer) { + deviceContent += ``; + } + } + deviceContent += `
Last seen: ${formattedDate} ${formattedTime}
`; deviceElement.innerHTML = deviceContent; From 438eae022575e952e9c2ddf24aa49db575c91a1b Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 16:16:42 +0000 Subject: [PATCH 09/13] Added basic functionality to pair button --- index.html | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index a69d909..432a58f 100644 --- a/index.html +++ b/index.html @@ -642,6 +642,14 @@ .bluetooth-pair-button:hover { background-color: #8e44ad; } + + .bluetooth-pair-button.paired { + background-color: #27ae60; + } + + .bluetooth-pair-button.paired:hover { + background-color: #219653; + } @@ -5151,12 +5159,24 @@ // Add pairing button only if device is nearby and player has a Bluetooth spoofer if (device.nearby) { // Check if player has a Bluetooth spoofer in inventory - const hasSpoofer = inventory.items.some(item => + const spoofer = inventory.items.find(item => item.scenarioData?.type === "bluetooth_spoofer" ); - if (hasSpoofer) { - deviceContent += ``; + if (spoofer) { + // Check if this device is already paired with (MAC address programmed to spoofer) + const isPaired = spoofer.scenarioData?.mac === device.mac; + const buttonClass = isPaired ? 'bluetooth-pair-button paired' : 'bluetooth-pair-button'; + const buttonText = isPaired ? 'MAC Address Copied' : 'Copy MAC Address'; + + deviceContent += ``; + + // If device is paired, add a hint about using it to unlock + if (isPaired) { + deviceContent += `
+ You can now use this MAC address to unlock matching Bluetooth locks +
`; + } } } @@ -5225,10 +5245,70 @@ }); }); + // Set up global event delegation for the pairing buttons + document.addEventListener('click', function(event) { + if (event.target.classList.contains('bluetooth-pair-button')) { + const mac = event.target.dataset.mac; + attemptPairingWithDevice(mac); + event.stopPropagation(); // Prevent device expanding/collapsing when clicking the button + } + }); + // Initialize Bluetooth count updateBluetoothCount(); } + // Function to handle pairing attempts with Bluetooth devices + function attemptPairingWithDevice(mac) { + // Find the device in our list + const device = bluetoothDevices.find(device => device.mac === mac); + if (!device) { + gameAlert("Device not found.", 'error', 'Pairing Failed', 3000); + return; + } + + // Find spoofer in inventory + const spoofer = inventory.items.find(item => + item.scenarioData?.type === "bluetooth_spoofer" + ); + + if (!spoofer) { + gameAlert("You need a Bluetooth spoofer to pair with this device.", 'warning', 'Spoofer Required', 3000); + return; + } + + // Check if player is close enough to the device (using the proximity from real-time scanning) + if (!device.nearby) { + gameAlert("You need to be closer to the device to pair with it.", 'warning', 'Too Far', 3000); + return; + } + + // Check if this device is already paired with + if (spoofer.scenarioData?.mac === device.mac) { + gameAlert(`This device's MAC address (${device.mac}) is already programmed into your spoofer.`, 'info', 'Already Paired', 3000); + return; + } + + // Show a "working" message + gameAlert(`Copying MAC address: ${device.mac}...`, 'info', 'Programming Spoofer', 3000); + + // Program the spoofer with the target MAC address + spoofer.scenarioData.mac = device.mac; + + // Show success message + setTimeout(() => { + gameAlert(`Successfully programmed spoofer with MAC address: ${device.mac}`, 'success', 'Pairing Complete', 4000); + debugLog('BLUETOOTH SPOOFER PROGRAMMED', { + deviceName: device.name, + deviceMac: device.mac + }, 1); + + // Update the UI to show the pairing was successful + device.paired = true; + updateBluetoothPanel(); + }, 1500); + } + \ No newline at end of file From 9410d17f7e9eed67b8ae3a1de95b72d2344fc7ef Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 16:32:29 +0000 Subject: [PATCH 10/13] Prevent Duplicate Item Pickup in Inventory --- index.html | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 432a58f..61caa3d 100644 --- a/index.html +++ b/index.html @@ -2595,6 +2595,13 @@ } try { + // Check if the item is already in the inventory + const isAlreadyInInventory = inventory.items.some(item => item.name === sprite.name); + if (isAlreadyInInventory) { + console.log(`Item ${sprite.name} is already in inventory, not adding again`); + return false; + } + // Remove from room if it exists if (currentRoom && rooms[currentRoom] && @@ -3032,10 +3039,15 @@ // Allow the item to be picked up even if locked if (type === 'item' && lockable.scenarioData?.takeable) { - addToInventory(lockable); - // Remove from room objects if it exists there - if (currentRoom && rooms[currentRoom].objects) { - delete rooms[currentRoom].objects[lockable.name]; + // Check if the item is already in the inventory before adding it + const isAlreadyInInventory = inventory.items.some(item => item.name === lockable.name); + + if (!isAlreadyInInventory) { + addToInventory(lockable); + // Remove from room objects if it exists there + if (currentRoom && rooms[currentRoom].objects) { + delete rooms[currentRoom].objects[lockable.name]; + } } } return; From a5b426b16e1a0d57fe730b3784021689d6544d4f Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 16:52:05 +0000 Subject: [PATCH 11/13] Fixed flashing bug with ui --- index.html | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 61caa3d..2f36225 100644 --- a/index.html +++ b/index.html @@ -650,6 +650,19 @@ .bluetooth-pair-button.paired:hover { background-color: #219653; } + + /* Add a class to preserve hover state during updates */ + .bluetooth-device.hover-preserved { + background-color: #333; + } + + /* Add a more specific rule to prevent hover state from being lost when hovering over child elements */ + .bluetooth-device:hover .bluetooth-device-name, + .bluetooth-device:hover .bluetooth-device-details, + .bluetooth-device:hover .bluetooth-device-timestamp, + .bluetooth-device:hover .bluetooth-pair-button { + pointer-events: auto; + } @@ -3279,6 +3292,10 @@ gameAlert('You collected the items from the container.', 'success', 'Items Collected', 4000); } + // Add a throttle mechanism for Bluetooth panel updates + let lastBluetoothPanelUpdate = 0; + const BLUETOOTH_UPDATE_THROTTLE = 500; // milliseconds + function checkBluetoothDevices() { // Find scanner in inventory const scanner = inventory.items.find(item => @@ -3298,6 +3315,7 @@ // Keep track of devices detected in this scan const detectedDevices = new Set(); + let needsUpdate = false; Object.values(rooms[currentRoom].objects).forEach(obj => { if (obj.scenarioData?.lockType === "bluetooth") { @@ -3328,17 +3346,23 @@ if (existingDevice) { // Update existing device details with real-time data + const oldSignalStrength = existingDevice.signalStrength; existingDevice.details = details; existingDevice.lastSeen = new Date(); existingDevice.nearby = true; existingDevice.signalStrength = signalStrength; - updateBluetoothPanel(); + + // Only mark for update if signal strength changed significantly + if (Math.abs(oldSignalStrength - signalStrength) > 5) { + needsUpdate = true; + } } else { // Add as new device if not already in our list const newDevice = addBluetoothDevice(deviceName, deviceMac, details, true); if (newDevice) { newDevice.signalStrength = signalStrength; gameAlert(`Bluetooth device detected: ${deviceName} (MAC: ${deviceMac})`, 'info', 'Bluetooth Scanner', 4000); + needsUpdate = true; } } } @@ -3350,10 +3374,17 @@ if (device.nearby && !detectedDevices.has(device.mac)) { device.nearby = false; device.lastSeen = new Date(); - updateBluetoothPanel(); - updateBluetoothCount(); + needsUpdate = true; } }); + + // Only update the panel if needed and not too frequently + const now = Date.now(); + if (needsUpdate && now - lastBluetoothPanelUpdate > BLUETOOTH_UPDATE_THROTTLE) { + updateBluetoothPanel(); + updateBluetoothCount(); + lastBluetoothPanelUpdate = now; + } } function spoofBluetoothDevice(target) { @@ -5074,6 +5105,10 @@ // Get active category const activeCategory = document.querySelector('.bluetooth-category.active')?.dataset.category || 'all'; + // Store the currently hovered device, if any + const hoveredDevice = document.querySelector('.bluetooth-device:hover'); + const hoveredDeviceId = hoveredDevice ? hoveredDevice.dataset.id : null; + // Filter devices based on search and category let filteredDevices = [...bluetoothDevices]; @@ -5125,6 +5160,11 @@ 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(); From 3b3481c66430ece6a8127b5cbf5c0854b5a8c582 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 17:02:26 +0000 Subject: [PATCH 12/13] Implement MAC Address Pairing Minigame for Bluetooth Spoofer --- index.html | 273 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 253 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index 2f36225..fa98a97 100644 --- a/index.html +++ b/index.html @@ -3407,14 +3407,14 @@ debugLog('BLUETOOTH SPOOF ATTEMPT', { deviceName: target.scenarioData?.name, deviceMac: target.scenarioData?.mac, - spooferMac: spoofer.scenarioData?.mac, + spooferMac: spoofer.scenarioData?.macPaired, distance: Math.round(distance) }, 2); // Check if player is within range if (distance <= BLUETOOTH_SCAN_RANGE) { // Check if the spoofer has the correct MAC address - if (spoofer.scenarioData?.mac === target.scenarioData?.mac) { + if (spoofer.scenarioData?.macPaired === target.scenarioData?.mac) { debugLog('BLUETOOTH SPOOF SUCCESS', { deviceName: target.scenarioData?.name, mac: target.scenarioData?.mac @@ -5217,9 +5217,9 @@ if (spoofer) { // Check if this device is already paired with (MAC address programmed to spoofer) - const isPaired = spoofer.scenarioData?.mac === device.mac; + const isPaired = spoofer.scenarioData?.macPaired === device.mac; const buttonClass = isPaired ? 'bluetooth-pair-button paired' : 'bluetooth-pair-button'; - const buttonText = isPaired ? 'MAC Address Copied' : 'Copy MAC Address'; + const buttonText = isPaired ? 'MAC Address Paired' : 'Pair MAC Address'; deviceContent += ``; @@ -5336,29 +5336,262 @@ } // Check if this device is already paired with - if (spoofer.scenarioData?.mac === device.mac) { + if (spoofer.scenarioData?.macPaired === device.mac) { gameAlert(`This device's MAC address (${device.mac}) is already programmed into your spoofer.`, 'info', 'Already Paired', 3000); return; } - // Show a "working" message - gameAlert(`Copying MAC address: ${device.mac}...`, 'info', 'Programming Spoofer', 3000); + // Launch the MAC address pairing minigame + startBluetoothPairingMinigame(device); + } + + // Bluetooth MAC Address Pairing Minigame + function startBluetoothPairingMinigame(device) { + // Create minigame container + const minigameContainer = document.createElement('div'); + minigameContainer.style.cssText = ` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 60%; + height: 60%; + background: rgba(0, 0, 0, 0.9); + border: 1px solid #444; + z-index: 1000; + padding: 20px; + border-radius: 5px; + display: flex; + flex-direction: column; + align-items: center; + `; - // Program the spoofer with the target MAC address - spoofer.scenarioData.mac = device.mac; + // Add instructions + const instructions = document.createElement('div'); + instructions.innerHTML = ` +

MAC Address Spoofing

+

+ Align the signal frequencies to match the target device's MAC address pattern.
+ Drag the sliders to adjust each frequency band until all segments turn green.
+ When all segments are aligned, the MAC address will be successfully paired. +

+ `; + instructions.style.cssText = ` + position: absolute; + top: 10px; + left: 50%; + transform: translateX(-50%); + width: 90%; + `; + + // Create device info display + const deviceInfo = document.createElement('div'); + deviceInfo.innerHTML = ` +
Target Device: ${device.name}
+
MAC Address: ${device.mac}
+ `; + deviceInfo.style.cssText = ` + margin-top: 70px; + text-align: center; + width: 100%; + `; + + // Create signal visualization + const signalVisualization = document.createElement('div'); + signalVisualization.style.cssText = ` + width: 90%; + height: 120px; + background: #111; + border: 1px solid #333; + margin: 20px 0; + position: relative; + overflow: hidden; + `; + + // Create frequency bands (6 for MAC address octets) + const frequencyBands = document.createElement('div'); + frequencyBands.style.cssText = ` + display: flex; + width: 90%; + justify-content: space-between; + margin-bottom: 30px; + `; + + // Create sliders for each frequency band + const sliders = []; + const targetValues = []; + const currentValues = []; + const segments = []; - // Show success message - setTimeout(() => { - gameAlert(`Successfully programmed spoofer with MAC address: ${device.mac}`, 'success', 'Pairing Complete', 4000); - debugLog('BLUETOOTH SPOOFER PROGRAMMED', { - deviceName: device.name, - deviceMac: device.mac - }, 1); + // Parse MAC address to generate target values + const macParts = device.mac.split(':'); + + for (let i = 0; i < 6; i++) { + // Convert hex to decimal for target value (0-100 range) + const hexValue = macParts[i] || '00'; + const decimalValue = parseInt(hexValue, 16); + const targetValue = Math.round((decimalValue / 255) * 100); + targetValues.push(targetValue); - // Update the UI to show the pairing was successful - device.paired = true; - updateBluetoothPanel(); - }, 1500); + // Start with random values + const initialValue = Math.floor(Math.random() * 100); + currentValues.push(initialValue); + + // Create slider container + const sliderContainer = document.createElement('div'); + sliderContainer.style.cssText = ` + display: flex; + flex-direction: column; + align-items: center; + width: 40px; + `; + + // Create slider + const slider = document.createElement('input'); + slider.type = 'range'; + slider.min = 0; + slider.max = 100; + slider.value = initialValue; + slider.style.cssText = ` + width: 120px; + transform: rotate(-90deg); + margin: 50px 0; + `; + + // Create segment in visualization + const segment = document.createElement('div'); + segment.style.cssText = ` + position: absolute; + bottom: 0; + left: ${i * (100/6)}%; + width: ${100/6}%; + height: ${initialValue}%; + background: #cc5500; + transition: height 0.2s, background-color 0.3s; + `; + segments.push(segment); + signalVisualization.appendChild(segment); + + // Create label + const label = document.createElement('div'); + label.textContent = hexValue.toUpperCase(); + label.style.cssText = ` + color: #aaa; + font-size: 12px; + margin-top: 10px; + `; + + // Add event listener to slider + slider.addEventListener('input', (e) => { + const value = parseInt(e.target.value); + currentValues[i] = value; + segment.style.height = `${value}%`; + + // Check if this segment is aligned correctly + const isAligned = Math.abs(value - targetValues[i]) <= 5; + segment.style.backgroundColor = isAligned ? '#00cc00' : '#cc5500'; + + // Check if all segments are aligned + checkCompletion(); + }); + + sliders.push(slider); + sliderContainer.appendChild(slider); + sliderContainer.appendChild(label); + frequencyBands.appendChild(sliderContainer); + } + + // Create status message + const statusMessage = document.createElement('div'); + statusMessage.style.cssText = ` + color: #aaa; + font-size: 14px; + margin-top: 15px; + text-align: center; + `; + statusMessage.textContent = 'Align all frequency bands...'; + + // Create cancel button + const cancelButton = document.createElement('button'); + cancelButton.textContent = 'Cancel'; + cancelButton.style.cssText = ` + background-color: #555; + color: white; + border: none; + border-radius: 3px; + padding: 8px 15px; + margin-top: 20px; + cursor: pointer; + font-size: 14px; + `; + cancelButton.addEventListener('click', () => { + endMinigame(false); + }); + + // Add all elements to container + minigameContainer.appendChild(instructions); + minigameContainer.appendChild(deviceInfo); + minigameContainer.appendChild(signalVisualization); + minigameContainer.appendChild(frequencyBands); + minigameContainer.appendChild(statusMessage); + minigameContainer.appendChild(cancelButton); + + // Add container to document + document.body.appendChild(minigameContainer); + + // Function to check if all segments are aligned + function checkCompletion() { + const allAligned = currentValues.every((value, index) => + Math.abs(value - targetValues[index]) <= 5 + ); + + if (allAligned) { + statusMessage.textContent = 'MAC Address pattern matched!'; + statusMessage.style.color = '#00cc00'; + + // Add success animation + segments.forEach(segment => { + segment.style.backgroundColor = '#00cc00'; + }); + + // End minigame with success after a short delay + setTimeout(() => { + endMinigame(true); + }, 1500); + } + } + + // Function to end minigame + function endMinigame(success) { + // Remove minigame container + document.body.removeChild(minigameContainer); + + // Call callback with result + if (success) { + // Find spoofer in inventory + const spoofer = inventory.items.find(item => + item.scenarioData?.type === "bluetooth_spoofer" + ); + + if (spoofer) { + // Program the spoofer with the target MAC address + spoofer.scenarioData.macPaired = device.mac; + + // Show success message + gameAlert(`Successfully programmed spoofer with MAC address: ${device.mac}`, 'success', 'Pairing Complete', 4000); + debugLog('BLUETOOTH SPOOFER PROGRAMMED', { + deviceName: device.name, + deviceMac: device.mac + }, 1); + + // Update the UI to show the pairing was successful + device.paired = true; + updateBluetoothPanel(); + } + } else { + gameAlert('Pairing canceled.', 'info', 'Pairing Canceled', 3000); + } + } } From be6091a4c92648691ddcb977801275f8fd6da180 Mon Sep 17 00:00:00 2001 From: Damian-I Date: Sat, 8 Mar 2025 21:09:17 +0000 Subject: [PATCH 13/13] Added support for unlocking Bluetooth-locked inventory items --- index.html | 294 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 259 insertions(+), 35 deletions(-) diff --git a/index.html b/index.html index fa98a97..252a6d8 100644 --- a/index.html +++ b/index.html @@ -651,6 +651,24 @@ background-color: #219653; } + /* Bluetooth Unlock Button */ + .bluetooth-unlock-button { + display: inline-block; + margin-top: 8px; + padding: 5px 10px; + background-color: #2980b9; + color: white; + border: none; + border-radius: 3px; + font-size: 12px; + cursor: pointer; + transition: background-color 0.2s; + } + + .bluetooth-unlock-button:hover { + background-color: #3498db; + } + /* Add a class to preserve hover state during updates */ .bluetooth-device.hover-preserved { background-color: #333; @@ -3398,38 +3416,87 @@ return false; } - // Calculate distance between player and target device - const distance = Phaser.Math.Distance.Between( - player.x, player.y, - target.x, target.y - ); + // Check if target is in inventory or in the environment + const isInventoryItem = inventory.items.includes(target); - debugLog('BLUETOOTH SPOOF ATTEMPT', { - deviceName: target.scenarioData?.name, - deviceMac: target.scenarioData?.mac, - spooferMac: spoofer.scenarioData?.macPaired, - distance: Math.round(distance) - }, 2); - - // Check if player is within range - if (distance <= BLUETOOTH_SCAN_RANGE) { - // Check if the spoofer has the correct MAC address - if (spoofer.scenarioData?.macPaired === target.scenarioData?.mac) { - debugLog('BLUETOOTH SPOOF SUCCESS', { - deviceName: target.scenarioData?.name, - mac: target.scenarioData?.mac - }, 1); - - // Unlock the device - target.scenarioData.locked = false; - gameAlert("Bluetooth connection spoofed. Device unlocked.", 'success', 'Spoofing Successful', 4000); - return true; - } else { - gameAlert("Bluetooth spoofer MAC address doesn't match the target device.", 'error', 'Spoofing Failed', 4000); + // If it's an environment object, check distance + if (!isInventoryItem) { + // Calculate distance between player and target device + const distance = Phaser.Math.Distance.Between( + player.x, player.y, + target.x, target.y + ); + + debugLog('BLUETOOTH SPOOF ATTEMPT', { + deviceName: target.scenarioData?.name, + deviceMac: target.scenarioData?.mac, + spooferMac: spoofer.scenarioData?.macPaired, + distance: Math.round(distance), + isInventoryItem: false + }, 2); + + // Check if player is within range + if (distance > BLUETOOTH_SCAN_RANGE) { + gameAlert("Too far from device to establish Bluetooth connection.", 'error', 'Connection Failed', 3000); return false; } } else { - gameAlert("Too far from device to establish Bluetooth connection.", 'error', 'Connection Failed', 3000); + // Log attempt for inventory item + debugLog('BLUETOOTH SPOOF ATTEMPT', { + deviceName: target.scenarioData?.name, + deviceMac: target.scenarioData?.mac, + spooferMac: spoofer.scenarioData?.macPaired, + isInventoryItem: true, + distance: 0 // Inventory items are always at distance 0 + }, 2); + } + + // Normalize MAC addresses for comparison + const targetMac = target.scenarioData?.mac?.toLowerCase() || ""; + const spooferMac = spoofer.scenarioData?.macPaired?.toLowerCase() || ""; + + // Check if the spoofer has the correct MAC address + if (spooferMac && targetMac && spooferMac === targetMac) { + debugLog('BLUETOOTH SPOOF SUCCESS', { + deviceName: target.scenarioData?.name, + mac: target.scenarioData?.mac, + isInventoryItem: isInventoryItem + }, 1); + + // Unlock the device + target.scenarioData.locked = false; + + // If it's an inventory item, update its appearance if needed + if (isInventoryItem) { + // Update the item's texture if it has an unlocked version + if (target.scenarioData?.unlockedTexture) { + target.setTexture(target.scenarioData.unlockedTexture); + } + + // If the item has contents, allow them to be collected + if (target.scenarioData?.contents) { + target.scenarioData.isUnlockedButNotCollected = true; + collectContainerContents(target); + + // Remove the device from the bluetoothDevices array + const deviceIndex = bluetoothDevices.findIndex(device => + device.mac.toLowerCase() === targetMac && device.inInventory + ); + + if (deviceIndex !== -1) { + bluetoothDevices.splice(deviceIndex, 1); + } + } + } + + gameAlert("Bluetooth connection spoofed. Device unlocked.", 'success', 'Spoofing Successful', 4000); + + // Update the Bluetooth panel to reflect the unlocked state + updateBluetoothPanel(); + + return true; + } else { + gameAlert("Bluetooth spoofer MAC address doesn't match the target device.", 'error', 'Spoofing Failed', 4000); return false; } } @@ -5109,6 +5176,53 @@ const hoveredDevice = document.querySelector('.bluetooth-device:hover'); const hoveredDeviceId = hoveredDevice ? hoveredDevice.dataset.id : null; + // Add Bluetooth-locked items from inventory to the main bluetoothDevices array + 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 + const existingDeviceIndex = bluetoothDevices.findIndex(device => + device.mac.toLowerCase() === normalizedMac + ); + + if (existingDeviceIndex === -1) { + // Add as a new device + const deviceName = item.scenarioData?.name || item.name || "Unknown 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: 100, // Max strength for inventory items + inInventory: true // Mark as inventory item + }; + + // Add to the main bluetoothDevices array + bluetoothDevices.push(newDevice); + console.log('Added inventory device to bluetoothDevices:', newDevice); + } else { + // Update existing device + const existingDevice = bluetoothDevices[existingDeviceIndex]; + existingDevice.inInventory = true; + existingDevice.nearby = true; + existingDevice.signalStrength = 100; + existingDevice.lastSeen = new Date(); + existingDevice.details = `Type: ${item.scenarioData?.type || "Unknown"}\nLocation: Inventory\nStatus: Locked`; + console.log('Updated existing device with inventory info:', existingDevice); + } + } + }); + // Filter devices based on search and category let filteredDevices = [...bluetoothDevices]; @@ -5130,6 +5244,12 @@ // Sort devices with nearby ones first, then by signal strength (highest first for nearby), then by last seen (newest first) 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; } @@ -5204,6 +5324,10 @@ if (device.saved) { deviceContent += `💾`; } + + if (device.inInventory) { + deviceContent += `🎒`; + } deviceContent += ``; deviceContent += `
MAC: ${device.mac}\n${device.details}
`; @@ -5228,6 +5352,11 @@ deviceContent += `
You can now use this MAC address to unlock matching Bluetooth locks
`; + + // If this is an inventory item, add an unlock button + if (device.inInventory) { + deviceContent += ``; + } } } } @@ -5237,7 +5366,13 @@ deviceElement.innerHTML = deviceContent; // Toggle expanded state when clicked - deviceElement.addEventListener('click', () => { + deviceElement.addEventListener('click', (event) => { + // Don't toggle if clicking on the pair button + if (event.target.classList.contains('bluetooth-pair-button') || + event.target.closest('.bluetooth-pair-button')) { + return; + } + deviceElement.classList.toggle('expanded'); // Mark as saved when expanded @@ -5301,7 +5436,40 @@ document.addEventListener('click', function(event) { if (event.target.classList.contains('bluetooth-pair-button')) { const mac = event.target.dataset.mac; - attemptPairingWithDevice(mac); + console.log('Attempting to pair with device MAC:', mac); + + // Find the device in our list + const device = bluetoothDevices.find(device => device.mac === mac); + console.log('Found device:', device); + + if (device) { + attemptPairingWithDevice(mac); + } else { + gameAlert("Device not found in Bluetooth devices list.", 'error', 'Pairing Failed', 3000); + } + + event.stopPropagation(); // Prevent device expanding/collapsing when clicking the button + } + + // Handle unlock button clicks + if (event.target.classList.contains('bluetooth-unlock-button')) { + const mac = event.target.dataset.mac; + console.log('Attempting to unlock device MAC:', mac); + + // Find the inventory item with this MAC address + const item = inventory.items.find(item => + item.scenarioData?.mac === mac && + item.scenarioData?.lockType === "bluetooth" && + item.scenarioData?.locked + ); + console.log('Found inventory item:', item); + + if (item) { + unlockInventoryDeviceByMac(mac); + } else { + gameAlert("Device not found in inventory.", 'error', 'Unlock Failed', 3000); + } + event.stopPropagation(); // Prevent device expanding/collapsing when clicking the button } }); @@ -5309,16 +5477,67 @@ // Initialize Bluetooth count updateBluetoothCount(); } + + // Function to unlock a Bluetooth-locked inventory item by MAC address + 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 = 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); + gameAlert("Device not found in inventory.", 'error', 'Unlock Failed', 3000); + return; + } + + console.log('Found inventory item to unlock:', item); + + // Attempt to spoof the device + const success = spoofBluetoothDevice(item); + + // If successful, remove the device from the bluetoothDevices array + if (success) { + // Find the device in the bluetoothDevices array + const deviceIndex = bluetoothDevices.findIndex(device => + device.mac.toLowerCase() === normalizedMac && device.inInventory + ); + + // Remove it if found + if (deviceIndex !== -1) { + console.log('Removing unlocked device from bluetoothDevices array'); + bluetoothDevices.splice(deviceIndex, 1); + + // Update the Bluetooth panel to reflect the changes + updateBluetoothPanel(); + } + } + } // Function to handle pairing attempts with Bluetooth devices function attemptPairingWithDevice(mac) { - // Find the device in our list - const device = bluetoothDevices.find(device => device.mac === mac); + console.log('Attempting to pair with MAC:', mac); + console.log('All Bluetooth devices:', bluetoothDevices); + + // Find the device in our list (case-insensitive comparison) + const normalizedMac = mac.toLowerCase(); + const device = bluetoothDevices.find(device => device.mac.toLowerCase() === normalizedMac); + if (!device) { + console.error('Device not found with MAC:', mac); gameAlert("Device not found.", 'error', 'Pairing Failed', 3000); return; } + console.log('Found device for pairing:', device); + // Find spoofer in inventory const spoofer = inventory.items.find(item => item.scenarioData?.type === "bluetooth_spoofer" @@ -5330,14 +5549,15 @@ } // Check if player is close enough to the device (using the proximity from real-time scanning) - if (!device.nearby) { + // Skip proximity check for inventory items + if (!device.nearby && !device.inInventory) { gameAlert("You need to be closer to the device to pair with it.", 'warning', 'Too Far', 3000); return; } // Check if this device is already paired with - if (spoofer.scenarioData?.macPaired === device.mac) { - gameAlert(`This device's MAC address (${device.mac}) is already programmed into your spoofer.`, 'info', 'Already Paired', 3000); + if (spoofer.scenarioData?.macPaired && spoofer.scenarioData.macPaired.toLowerCase() === normalizedMac) { + gameAlert(`This device's MAC address (${mac}) is already programmed into your spoofer.`, 'info', 'Already Paired', 3000); return; } @@ -5574,6 +5794,8 @@ ); if (spoofer) { + console.log('Programming spoofer with MAC address:', device.mac); + // Program the spoofer with the target MAC address spoofer.scenarioData.macPaired = device.mac; @@ -5586,6 +5808,8 @@ // Update the UI to show the pairing was successful device.paired = true; + + // Update the Bluetooth panel to reflect the pairing updateBluetoothPanel(); } } else {