diff --git a/assets/objects/fingerprint_small.png b/assets/objects/fingerprint_small.png new file mode 100644 index 0000000..93986da Binary files /dev/null and b/assets/objects/fingerprint_small.png differ diff --git a/assets/rooms/room_office2.json b/assets/rooms/room_office2.json index 82efbda..59b1be3 100644 --- a/assets/rooms/room_office2.json +++ b/assets/rooms/room_office2.json @@ -492,7 +492,7 @@ }], "opacity":1, "type":"objectgroup", - "visible":false, + "visible":true, "x":0, "y":0 }, @@ -677,8 +677,8 @@ "type":"", "visible":true, "width":13, - "x":242.5, - "y":207.5 + "x":242.869344413666, + "y":217.102954755309 }, { "gid":227, @@ -701,8 +701,8 @@ "type":"", "visible":true, "width":10, - "x":30.5, - "y":163 + "x":111.386426592798, + "y":165.58541089566 }, { "gid":236, @@ -764,10 +764,22 @@ "width":14, "x":220, "y":67 + }, + { + "gid":367, + "height":30, + "id":150, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":26, + "x":34.2760849492151, + "y":216.662049861496 }], "opacity":1, "type":"objectgroup", - "visible":false, + "visible":true, "x":0, "y":0 }, @@ -783,7 +795,7 @@ "y":0 }], "nextlayerid":11, - "nextobjectid":150, + "nextobjectid":151, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.11.2", @@ -2252,14 +2264,14 @@ { "id":243, "image":"..\/objects\/key.png", - "imageheight":27, - "imagewidth":13 + "imageheight":21, + "imagewidth":12 }, { "id":244, "image":"..\/objects\/lockpick.png", - "imageheight":64, - "imagewidth":64 + "imageheight":30, + "imagewidth":26 }, { "id":245, diff --git a/assets/rooms/room_office2.tmj b/assets/rooms/room_office2.tmj index 4615a9f..25cb786 100644 --- a/assets/rooms/room_office2.tmj +++ b/assets/rooms/room_office2.tmj @@ -55,12 +55,12 @@ { "data":[0, 101, 0, 0, 0, 0, 0, 0, 101, 0, 0, 107, 0, 0, 0, 0, 0, 0, 107, 0, - 418, 0, 0, 0, 0, 0, 0, 0, 0, 418, + 420, 0, 0, 0, 0, 0, 0, 0, 0, 420, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 418, 0, 0, 0, 0, 0, 0, 0, 0, 418, + 420, 0, 0, 0, 0, 0, 0, 0, 0, 420, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":10, @@ -138,7 +138,7 @@ "name":"table_items", "objects":[ { - "gid":189, + "gid":191, "height":14, "id":90, "name":"", @@ -150,7 +150,7 @@ "y":144.666666666667 }, { - "gid":199, + "gid":201, "height":15, "id":105, "name":"", @@ -162,7 +162,7 @@ "y":169 }, { - "gid":336, + "gid":338, "height":12, "id":55, "name":"", @@ -174,7 +174,7 @@ "y":180.666666666667 }, { - "gid":185, + "gid":187, "height":18, "id":88, "name":"", @@ -186,7 +186,7 @@ "y":136.333333333333 }, { - "gid":189, + "gid":191, "height":14, "id":101, "name":"", @@ -198,7 +198,7 @@ "y":122 }, { - "gid":187, + "gid":189, "height":14, "id":45, "name":"", @@ -210,7 +210,7 @@ "y":130.75 }, { - "gid":198, + "gid":200, "height":8, "id":104, "name":"", @@ -222,7 +222,7 @@ "y":121 }, { - "gid":196, + "gid":198, "height":7, "id":103, "name":"", @@ -234,7 +234,7 @@ "y":121 }, { - "gid":191, + "gid":193, "height":18, "id":102, "name":"", @@ -246,7 +246,7 @@ "y":124 }, { - "gid":189, + "gid":191, "height":14, "id":100, "name":"", @@ -259,7 +259,7 @@ }, { - "gid":188, + "gid":190, "height":11, "id":99, "name":"", @@ -271,7 +271,7 @@ "y":171.5 }, { - "gid":201, + "gid":203, "height":15, "id":106, "name":"", @@ -283,7 +283,7 @@ "y":119.5 }, { - "gid":202, + "gid":204, "height":20, "id":107, "name":"", @@ -295,7 +295,7 @@ "y":170.630655586334 }, { - "gid":203, + "gid":205, "height":18, "id":108, "name":"", @@ -307,7 +307,7 @@ "y":174 }, { - "gid":203, + "gid":205, "height":18, "id":109, "name":"", @@ -319,7 +319,7 @@ "y":174 }, { - "gid":205, + "gid":207, "height":11, "id":110, "name":"", @@ -331,7 +331,7 @@ "y":132.5 }, { - "gid":217, + "gid":219, "height":12, "id":111, "name":"", @@ -343,7 +343,7 @@ "y":119 }, { - "gid":221, + "gid":223, "height":16, "id":112, "name":"", @@ -355,7 +355,7 @@ "y":129 }, { - "gid":225, + "gid":227, "height":16, "id":113, "name":"", @@ -367,7 +367,7 @@ "y":183.5 }, { - "gid":334, + "gid":336, "height":12, "id":131, "name":"", @@ -380,7 +380,7 @@ }, { - "gid":333, + "gid":335, "height":18, "id":132, "name":"", @@ -392,7 +392,7 @@ "y":121.5 }, { - "gid":341, + "gid":343, "height":18, "id":133, "name":"", @@ -404,7 +404,7 @@ "y":180.5 }, { - "gid":336, + "gid":338, "height":12, "id":134, "name":"", @@ -416,7 +416,7 @@ "y":133.5 }, { - "gid":337, + "gid":339, "height":14, "id":135, "name":"", @@ -428,7 +428,7 @@ "y":132 }, { - "gid":374, + "gid":376, "height":22, "id":141, "name":"", @@ -440,7 +440,7 @@ "y":132.5 }, { - "gid":372, + "gid":374, "height":30, "id":142, "name":"", @@ -452,7 +452,7 @@ "y":181 }, { - "gid":377, + "gid":379, "height":21, "id":143, "name":"", @@ -475,7 +475,7 @@ "name":"conditional_table_items", "objects":[ { - "gid":218, + "gid":220, "height":11, "id":46, "name":"", @@ -487,7 +487,7 @@ "y":135.75 }, { - "gid":310, + "gid":312, "height":16, "id":54, "name":"", @@ -500,7 +500,7 @@ }], "opacity":1, "type":"objectgroup", - "visible":false, + "visible":true, "x":0, "y":0 }, @@ -510,7 +510,7 @@ "name":"items", "objects":[ { - "gid":122, + "gid":124, "height":16, "id":96, "name":"", @@ -522,7 +522,7 @@ "y":152.5 }, { - "gid":122, + "gid":124, "height":16, "id":97, "name":"", @@ -534,7 +534,7 @@ "y":203 }, { - "gid":342, + "gid":344, "height":52, "id":139, "name":"", @@ -546,7 +546,7 @@ "y":65.2613111726685 }, { - "gid":344, + "gid":346, "height":54, "id":137, "name":"", @@ -558,7 +558,7 @@ "y":68.5 }, { - "gid":345, + "gid":347, "height":50, "id":138, "name":"", @@ -570,7 +570,7 @@ "y":66 }, { - "gid":410, + "gid":412, "height":32, "id":144, "name":"", @@ -582,7 +582,7 @@ "y":216.923361034164 }, { - "gid":409, + "gid":411, "height":32, "id":145, "name":"", @@ -594,7 +594,7 @@ "y":170.755309325946 }, { - "gid":403, + "gid":405, "height":32, "id":146, "name":"", @@ -606,7 +606,7 @@ "y":172.971375807941 }, { - "gid":400, + "gid":402, "height":32, "id":148, "name":"", @@ -618,7 +618,7 @@ "y":79.8965835641736 }, { - "gid":398, + "gid":400, "height":32, "id":149, "name":"", @@ -641,7 +641,7 @@ "name":"conditional_items", "objects":[ { - "gid":131, + "gid":133, "height":21, "id":40, "name":"", @@ -653,7 +653,7 @@ "y":215.5 }, { - "gid":308, + "gid":310, "height":30, "id":62, "name":"", @@ -665,7 +665,7 @@ "y":66 }, { - "gid":307, + "gid":309, "height":33, "id":63, "name":"", @@ -677,7 +677,7 @@ "y":64.5 }, { - "gid":364, + "gid":366, "height":27, "id":75, "name":"", @@ -685,11 +685,11 @@ "type":"", "visible":true, "width":13, - "x":242.5, - "y":207.5 + "x":242.869344413666, + "y":217.102954755309 }, { - "gid":225, + "gid":227, "height":16, "id":48, "name":"", @@ -701,7 +701,7 @@ "y":51.5 }, { - "gid":121, + "gid":123, "height":24, "id":98, "name":"", @@ -709,11 +709,11 @@ "type":"", "visible":true, "width":10, - "x":30.5, - "y":163 + "x":111.386426592798, + "y":165.58541089566 }, { - "gid":234, + "gid":236, "height":20, "id":115, "name":"", @@ -725,7 +725,7 @@ "y":209.5 }, { - "gid":236, + "gid":238, "height":21, "id":116, "name":"", @@ -737,7 +737,7 @@ "y":69 }, { - "gid":240, + "gid":242, "height":21, "id":117, "name":"", @@ -749,7 +749,7 @@ "y":69 }, { - "gid":242, + "gid":244, "height":21, "id":118, "name":"", @@ -762,7 +762,7 @@ }, { - "gid":246, + "gid":248, "height":24, "id":119, "name":"", @@ -772,10 +772,22 @@ "width":14, "x":220, "y":67 + }, + { + "gid":367, + "height":30, + "id":150, + "name":"", + "rotation":0, + "type":"", + "visible":true, + "width":26, + "x":34.2760849492151, + "y":216.662049861496 }], "opacity":1, "type":"objectgroup", - "visible":false, + "visible":true, "x":0, "y":0 }, @@ -791,7 +803,7 @@ "y":0 }], "nextlayerid":11, - "nextobjectid":150, + "nextobjectid":151, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.11.2", @@ -820,7 +832,7 @@ }, { "columns":0, - "firstgid":121, + "firstgid":123, "grid": { "height":1, @@ -2202,14 +2214,14 @@ { "id":243, "image":"..\/objects\/key.png", - "imageheight":27, - "imagewidth":13 + "imageheight":21, + "imagewidth":12 }, { "id":244, "image":"..\/objects\/lockpick.png", - "imageheight":64, - "imagewidth":64 + "imageheight":30, + "imagewidth":26 }, { "id":245, @@ -2531,15 +2543,15 @@ "tilewidth":221 }, { - "firstgid":418, + "firstgid":420, "source":"..\/..\/..\/assets\/rooms\/door_side_sheet_32.tsx" }, { - "firstgid":424, + "firstgid":426, "source":"room14.tsx" }, { - "firstgid":524, + "firstgid":526, "source":"room18.tsx" }], "tilewidth":32, diff --git a/css/biometrics-minigame.css b/css/biometrics-minigame.css new file mode 100644 index 0000000..f05dd9e --- /dev/null +++ b/css/biometrics-minigame.css @@ -0,0 +1,528 @@ +/* Biometrics Minigame Styles */ + +.biometrics-minigame-container { + /* Compact interface similar to Bluetooth scanner */ + position: fixed !important; + top: 5vh !important; + right: 2vw !important; + width: 350px !important; + height: auto !important; + max-height: 60vh !important; + background: linear-gradient(135deg, #2e1a1a 0%, #3e1616 50%, #600f0f 100%) !important; + border: 2px solid #e74c3c !important; + box-shadow: 0 0 20px rgba(231, 76, 60, 0.3), inset 0 0 10px rgba(231, 76, 60, 0.1) !important; + border-radius: 10px !important; + font-family: 'VT323', monospace !important; + color: #e0e0e0 !important; + overflow: hidden !important; + transition: all 0.3s ease !important; +} + +.biometrics-minigame-container.expanded { + width: 450px !important; + max-height: 70vh !important; +} + +.biometrics-minigame-game-container { + width: 100% !important; + height: 100% !important; + max-width: none !important; + background: transparent !important; + border-radius: 0 !important; + box-shadow: none !important; + position: relative !important; + overflow: visible !important; + display: flex !important; + flex-direction: column !important; + padding: 15px !important; + box-sizing: border-box !important; +} + +/* Scanner Header */ +.biometrics-scanner-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding: 10px; + background: rgba(231, 76, 60, 0.1); + border: 1px solid #e74c3c; + border-radius: 6px; + box-shadow: 0 0 10px rgba(231, 76, 60, 0.2); +} + +.biometrics-scanner-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-weight: bold; + color: #e74c3c; + text-shadow: 0 0 5px rgba(231, 76, 60, 0.5); +} + +.samples-count-header { + font-size: 12px; + color: #4caf50; + background: rgba(76, 175, 80, 0.2); + padding: 2px 6px; + border-radius: 3px; + border: 1px solid #4caf50; + margin-left: auto; +} + +.scanner-icon { + height: 24px; + filter: drop-shadow(0 0 3px rgba(231, 76, 60, 0.5)); + image-rendering: pixelated; +} + +.biometrics-scanner-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: #4caf50; +} + +.scanner-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: #4caf50; + box-shadow: 0 0 6px rgba(76, 175, 80, 0.8); + animation: pulse 2s infinite; +} + +.scanner-indicator.active { + background: #4caf50; +} + +.scanner-indicator.inactive { + background: #f44336; + animation: none; +} + +@keyframes pulse { + 0% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } + 100% { opacity: 1; transform: scale(1); } +} + +/* Expand/Collapse Toggle */ +.biometrics-expand-toggle { + position: absolute; + top: 10px; + left: 10px; + width: 24px; + height: 24px; + background: rgba(231, 76, 60, 0.2); + border: 1px solid #e74c3c; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + color: #e74c3c; + transition: all 0.3s ease; + z-index: 10; +} + +.biometrics-expand-toggle:hover { + background: rgba(231, 76, 60, 0.3); + box-shadow: 0 0 8px rgba(231, 76, 60, 0.4); +} + +.biometrics-expand-toggle.expanded { + transform: rotate(180deg); +} + +/* Search Room Button */ +.biometrics-search-room-container { + margin-bottom: 15px; + display: flex; + justify-content: center; + padding: 10px; + background: rgba(231, 76, 60, 0.1); + border-radius: 6px; + box-shadow: 0 0 10px rgba(231, 76, 60, 0.2); +} + +/* Controls */ +.biometrics-scanner-controls { + margin-bottom: 10px; + transition: all 0.3s ease; + max-height: 200px; + overflow: hidden; +} + +.biometrics-minigame-container:not(.expanded) .biometrics-scanner-controls { + max-height: 0; + margin-bottom: 0; + opacity: 0; +} + +.biometrics-search-container { + margin-bottom: 10px; +} + +.biometrics-search-input { + width: 100%; + padding: 8px 12px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid #e74c3c; + border-radius: 6px; + color: #e0e0e0; + font-family: 'VT323', monospace; + font-size: 14px; + box-shadow: 0 0 8px rgba(231, 76, 60, 0.2); + transition: all 0.3s ease; +} + +.biometrics-search-input:focus { + outline: none; + border-color: #4caf50; + box-shadow: 0 0 15px rgba(76, 175, 80, 0.4); + background: rgba(0, 0, 0, 0.5); +} + +.biometrics-search-input::placeholder { + color: #888; +} + +.biometrics-categories { + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-bottom: 10px; +} + +.biometrics-category { + padding: 6px 12px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid #555; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + color: #ccc; + transition: all 0.3s ease; + user-select: none; +} + +.biometrics-category:hover { + background: rgba(231, 76, 60, 0.2); + border-color: #e74c3c; + color: #e74c3c; +} + +.biometrics-category.active { + background: rgba(231, 76, 60, 0.3); + border-color: #e74c3c; + color: #e74c3c; + box-shadow: 0 0 10px rgba(231, 76, 60, 0.3); +} + +/* Action Buttons */ +.biometrics-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.biometrics-action-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background: rgba(231, 76, 60, 0.2); + border: 1px solid #e74c3c; + border-radius: 6px; + color: #e74c3c; + font-family: 'VT323', monospace; + font-size: 14px; + cursor: pointer; + transition: all 0.3s ease; + user-select: none; +} + +.biometrics-action-btn:hover { + background: rgba(231, 76, 60, 0.3); + box-shadow: 0 0 10px rgba(231, 76, 60, 0.4); + transform: translateY(-1px); +} + +.biometrics-action-btn.active { + background: rgba(76, 175, 80, 0.3); + border-color: #4caf50; + color: #4caf50; + box-shadow: 0 0 15px rgba(76, 175, 80, 0.4); +} + +.biometrics-action-btn .btn-icon { + font-size: 16px; +} + +.biometrics-action-btn .btn-text { + font-weight: bold; +} + +/* Samples List */ +.biometrics-samples-list-container { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + transition: all 0.3s ease; + max-height: 300px; + overflow: hidden; +} + +.biometrics-minigame-container:not(.expanded) .biometrics-samples-list-container { + max-height: 0; + opacity: 0; +} + +.biometrics-samples-list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + padding: 8px 12px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid #444; + border-radius: 6px; + font-size: 14px; + font-weight: bold; + color: #e74c3c; +} + +.samples-count { + font-size: 12px; + color: #4caf50; + background: rgba(76, 175, 80, 0.2); + padding: 2px 6px; + border-radius: 3px; + border: 1px solid #4caf50; +} + +.biometrics-samples-list { + flex: 1; + overflow-y: auto; + padding: 8px; + background: rgba(0, 0, 0, 0.1); + border: 1px solid #333; + border-radius: 6px; + max-height: 300px; +} + +.biometrics-samples-list::-webkit-scrollbar { + width: 8px; +} + +.biometrics-samples-list::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.biometrics-samples-list::-webkit-scrollbar-thumb { + background: #e74c3c; + border-radius: 4px; +} + +.biometrics-samples-list::-webkit-scrollbar-thumb:hover { + background: #4caf50; +} + +/* Sample Items */ +.sample-item { + background: rgba(0, 0, 0, 0.3); + border: 1px solid #444; + border-radius: 6px; + margin-bottom: 6px; + padding: 10px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.sample-item:hover { + background: rgba(231, 76, 60, 0.1); + border-color: #e74c3c; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(231, 76, 60, 0.2); +} + +.sample-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + font-size: 14px; +} + +.sample-header strong { + color: #e0e0e0; + font-weight: bold; +} + +.sample-type { + font-size: 12px; + color: #e74c3c; + background: rgba(231, 76, 60, 0.2); + padding: 2px 6px; + border-radius: 3px; + border: 1px solid #e74c3c; +} + +.sample-details { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; +} + +.sample-quality { + font-weight: bold; + padding: 2px 6px; + border-radius: 3px; + border: 1px solid; +} + +.sample-quality.quality-perfect { + color: #4caf50; + background: rgba(76, 175, 80, 0.2); + border-color: #4caf50; +} + +.sample-quality.quality-excellent { + color: #8bc34a; + background: rgba(139, 195, 74, 0.2); + border-color: #8bc34a; +} + +.sample-quality.quality-good { + color: #ffc107; + background: rgba(255, 193, 7, 0.2); + border-color: #ffc107; +} + +.sample-quality.quality-fair { + color: #ff9800; + background: rgba(255, 152, 0, 0.2); + border-color: #ff9800; +} + +.sample-quality.quality-acceptable { + color: #ff5722; + background: rgba(255, 87, 34, 0.2); + border-color: #ff5722; +} + +.sample-quality.quality-poor { + color: #f44336; + background: rgba(244, 67, 54, 0.2); + border-color: #f44336; +} + +.sample-date { + color: #666; + font-style: italic; + font-size: 10px; +} + +/* Instructions */ +.biometrics-scanner-instructions { + margin-top: 10px; + padding: 10px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid #444; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; + color: #ccc; + transition: all 0.3s ease; +} + +.biometrics-minigame-container:not(.expanded) .biometrics-scanner-instructions { + display: none; +} + +.instruction-text { + color: #aaa; +} + +.instruction-text strong { + color: #e74c3c; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .biometrics-minigame-container { + top: 2vh !important; + right: 2vw !important; + left: 2vw !important; + width: 96vw !important; + max-width: 400px !important; + } + + .biometrics-minigame-container.expanded { + width: 96vw !important; + max-width: 500px !important; + } + + .biometrics-scanner-title { + font-size: 14px; + } + + .biometrics-categories { + flex-direction: column; + gap: 5px; + } + + .biometrics-category { + text-align: center; + padding: 4px 8px; + font-size: 11px; + } + + .biometrics-actions { + flex-direction: column; + } + + .biometrics-action-btn { + justify-content: center; + padding: 6px 10px; + font-size: 12px; + } + + .biometrics-expand-toggle { + width: 20px; + height: 20px; + font-size: 10px; + } +} + +/* Animation for new samples */ +@keyframes sampleAppear { + 0% { + opacity: 0; + transform: translateX(-20px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.sample-item.new-sample { + animation: sampleAppear 0.5s ease-out; +} + +/* Hover preservation for smooth updates */ +.sample-item.hover-preserved { + background: rgba(231, 76, 60, 0.1); + border-color: #e74c3c; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(231, 76, 60, 0.2); +} diff --git a/css/bluetooth-scanner.css b/css/bluetooth-scanner.css new file mode 100644 index 0000000..804ab81 --- /dev/null +++ b/css/bluetooth-scanner.css @@ -0,0 +1,462 @@ +/* Bluetooth Scanner Minigame Styles */ + +.bluetooth-scanner-minigame-container { + /* Much smaller, compact interface */ + position: fixed !important; + top: 5vh !important; + right: 2vw !important; + width: 350px !important; + height: auto !important; + max-height: 60vh !important; + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%) !important; + border: 2px solid #00bcd4 !important; + box-shadow: 0 0 20px rgba(0, 188, 212, 0.3), inset 0 0 10px rgba(0, 188, 212, 0.1) !important; + border-radius: 10px !important; + font-family: 'VT323', monospace !important; + color: #e0e0e0 !important; + overflow: hidden !important; + transition: all 0.3s ease !important; +} + +.bluetooth-scanner-minigame-container.expanded { + width: 450px !important; + max-height: 70vh !important; +} + +.bluetooth-scanner-minigame-game-container { + width: 100% !important; + height: 100% !important; + max-width: none !important; + background: transparent !important; + border-radius: 0 !important; + box-shadow: none !important; + position: relative !important; + overflow: visible !important; + display: flex !important; + flex-direction: column !important; + padding: 15px !important; + box-sizing: border-box !important; +} + +/* Scanner Header */ +.bluetooth-scanner-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding: 10px; + background: rgba(0, 188, 212, 0.1); + border: 1px solid #00bcd4; + border-radius: 6px; + box-shadow: 0 0 10px rgba(0, 188, 212, 0.2); +} + +.bluetooth-scanner-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-weight: bold; + color: #00bcd4; + text-shadow: 0 0 5px rgba(0, 188, 212, 0.5); +} + +.scanner-icon { + height: 24px; + filter: drop-shadow(0 0 3px rgba(0, 188, 212, 0.5)); + image-rendering: pixelated; +} + +.bluetooth-scanner-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: #4caf50; +} + +.scanner-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: #4caf50; + box-shadow: 0 0 6px rgba(76, 175, 80, 0.8); + animation: pulse 2s infinite; +} + +.scanner-indicator.active { + background: #4caf50; +} + +.scanner-indicator.inactive { + background: #f44336; + animation: none; +} + +@keyframes pulse { + 0% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } + 100% { opacity: 1; transform: scale(1); } +} + +/* Expand/Collapse Toggle */ +.bluetooth-scanner-expand-toggle { + position: absolute; + top: 10px; + left: 10px; + width: 24px; + height: 24px; + background: rgba(0, 188, 212, 0.2); + border: 1px solid #00bcd4; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + color: #00bcd4; + transition: all 0.3s ease; + z-index: 10; +} + +.bluetooth-scanner-expand-toggle:hover { + background: rgba(0, 188, 212, 0.3); + box-shadow: 0 0 8px rgba(0, 188, 212, 0.4); +} + +.bluetooth-scanner-expand-toggle.expanded { + transform: rotate(180deg); +} + +/* Controls */ +.bluetooth-scanner-controls { + margin-bottom: 10px; + transition: all 0.3s ease; + max-height: 200px; + overflow: hidden; +} + +.bluetooth-scanner-minigame-container:not(.expanded) .bluetooth-scanner-controls { + max-height: 0; + margin-bottom: 0; + opacity: 0; +} + +.bluetooth-search-container { + margin-bottom: 10px; +} + +.bluetooth-search-input { + width: 100%; + padding: 8px 12px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid #00bcd4; + border-radius: 6px; + color: #e0e0e0; + font-family: 'VT323', monospace; + font-size: 14px; + box-shadow: 0 0 8px rgba(0, 188, 212, 0.2); + transition: all 0.3s ease; +} + +.bluetooth-search-input:focus { + outline: none; + border-color: #4caf50; + box-shadow: 0 0 15px rgba(76, 175, 80, 0.4); + background: rgba(0, 0, 0, 0.5); +} + +.bluetooth-search-input::placeholder { + color: #888; +} + +.bluetooth-categories { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.bluetooth-category { + padding: 6px 12px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid #555; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + color: #ccc; + transition: all 0.3s ease; + user-select: none; +} + +.bluetooth-category:hover { + background: rgba(0, 188, 212, 0.2); + border-color: #00bcd4; + color: #00bcd4; +} + +.bluetooth-category.active { + background: rgba(0, 188, 212, 0.3); + border-color: #00bcd4; + color: #00bcd4; + box-shadow: 0 0 10px rgba(0, 188, 212, 0.3); +} + +/* Device List */ +.bluetooth-device-list-container { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + transition: all 0.3s ease; +} + +.bluetooth-scanner-minigame-container:not(.expanded) .bluetooth-device-list-container { + max-height: 200px; +} + +.bluetooth-device-list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + padding: 8px 12px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid #444; + border-radius: 6px; + font-size: 14px; + font-weight: bold; + color: #00bcd4; +} + +.device-count { + font-size: 12px; + color: #4caf50; + background: rgba(76, 175, 80, 0.2); + padding: 2px 6px; + border-radius: 3px; + border: 1px solid #4caf50; +} + +.bluetooth-device-list { + flex: 1; + overflow-y: auto; + padding: 8px; + background: rgba(0, 0, 0, 0.1); + border: 1px solid #333; + border-radius: 6px; + max-height: 300px; +} + +.bluetooth-device-list::-webkit-scrollbar { + width: 8px; +} + +.bluetooth-device-list::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.bluetooth-device-list::-webkit-scrollbar-thumb { + background: #00bcd4; + border-radius: 4px; +} + +.bluetooth-device-list::-webkit-scrollbar-thumb:hover { + background: #4caf50; +} + +/* Device Items */ +.bluetooth-device { + background: rgba(0, 0, 0, 0.3); + border: 1px solid #444; + border-radius: 6px; + margin-bottom: 6px; + padding: 10px; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.bluetooth-device:hover { + background: rgba(0, 188, 212, 0.1); + border-color: #00bcd4; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 188, 212, 0.2); +} + +.bluetooth-device.expanded { + background: rgba(0, 188, 212, 0.15); + border-color: #00bcd4; + box-shadow: 0 0 20px rgba(0, 188, 212, 0.3); +} + +.bluetooth-device-name { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + font-size: 14px; + font-weight: bold; + color: #e0e0e0; +} + +.bluetooth-device-icons { + display: flex; + align-items: center; + gap: 6px; +} + +.bluetooth-device-icon { + font-size: 12px; + opacity: 0.8; +} + +/* Signal Strength Bars */ +.bluetooth-signal-bar-container { + display: flex; + align-items: center; + gap: 2px; +} + +.bluetooth-signal-bars { + display: flex; + align-items: flex-end; + gap: 1px; + height: 16px; +} + +.bluetooth-signal-bar { + width: 3px; + background: #666; + border-radius: 1px; + transition: all 0.3s ease; +} + +.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-bar.active { + background: currentColor; + box-shadow: 0 0 5px currentColor; +} + +.bluetooth-device-details { + font-size: 12px; + color: #aaa; + white-space: pre-line; + margin-bottom: 6px; + line-height: 1.3; + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; +} + +.bluetooth-device.expanded .bluetooth-device-details { + max-height: 200px; +} + +.bluetooth-device-timestamp { + font-size: 10px; + color: #666; + font-style: italic; +} + +/* Instructions */ +.bluetooth-scanner-instructions { + margin-top: 10px; + padding: 10px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid #444; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; + color: #ccc; + transition: all 0.3s ease; +} + +.bluetooth-scanner-minigame-container:not(.expanded) .bluetooth-scanner-instructions { + display: none; +} + +.instruction-text { + color: #aaa; +} + +.instruction-text strong { + color: #00bcd4; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .bluetooth-scanner-minigame-container { + top: 2vh !important; + right: 2vw !important; + left: 2vw !important; + width: 96vw !important; + max-width: 400px !important; + } + + .bluetooth-scanner-minigame-container.expanded { + width: 96vw !important; + max-width: 500px !important; + } + + .bluetooth-scanner-title { + font-size: 14px; + } + + .bluetooth-categories { + flex-direction: column; + gap: 5px; + } + + .bluetooth-category { + text-align: center; + padding: 4px 8px; + font-size: 11px; + } + + .bluetooth-device-name { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + .bluetooth-device-icons { + align-self: flex-end; + } + + .bluetooth-scanner-expand-toggle { + width: 20px; + height: 20px; + font-size: 10px; + } +} + +/* Animation for new devices */ +@keyframes deviceAppear { + 0% { + opacity: 0; + transform: translateX(-20px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.bluetooth-device.new-device { + animation: deviceAppear 0.5s ease-out; +} + +/* Hover preservation for smooth updates */ +.bluetooth-device.hover-preserved { + background: rgba(0, 188, 212, 0.1); + border-color: #00bcd4; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 188, 212, 0.2); +} diff --git a/css/lockpick-set-minigame.css b/css/lockpick-set-minigame.css new file mode 100644 index 0000000..8ff55f5 --- /dev/null +++ b/css/lockpick-set-minigame.css @@ -0,0 +1,251 @@ +/* Lockpick Set Minigame Styles */ + +.lockpick-set-minigame-container { + /* Compact interface similar to other minigames */ + position: fixed !important; + top: 5vh !important; + right: 2vw !important; + width: 350px !important; + height: auto !important; + max-height: 60vh !important; + background: linear-gradient(135deg, #1a2e1a 0%, #163e16 50%, #0f600f 100%) !important; + border: 2px solid #4caf50 !important; + box-shadow: 0 0 20px rgba(76, 175, 80, 0.3), inset 0 0 10px rgba(76, 175, 80, 0.1) !important; + border-radius: 10px !important; + font-family: 'VT323', monospace !important; + color: #e0e0e0 !important; + overflow: hidden !important; + transition: all 0.3s ease !important; +} + +.lockpick-set-minigame-container.expanded { + width: 450px !important; + max-height: 70vh !important; +} + +.lockpick-set-minigame-game-container { + width: 100% !important; + height: 100% !important; + max-width: none !important; + background: transparent !important; + border-radius: 0 !important; + box-shadow: none !important; + position: relative !important; + overflow: visible !important; + display: flex !important; + flex-direction: column !important; + padding: 15px !important; + box-sizing: border-box !important; +} + +/* Lockpick Header */ +.lockpick-set-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + padding: 10px; + background: rgba(76, 175, 80, 0.1); + border: 1px solid #4caf50; + border-radius: 6px; + box-shadow: 0 0 10px rgba(76, 175, 80, 0.2); +} + +.lockpick-set-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-weight: bold; + color: #4caf50; + text-shadow: 0 0 5px rgba(76, 175, 80, 0.5); +} + +.lockpick-icon { + height: 24px; + filter: drop-shadow(0 0 3px rgba(76, 175, 80, 0.5)); + image-rendering: pixelated; +} + +.lockpick-set-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: #4caf50; +} + +.lockpick-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: #4caf50; + box-shadow: 0 0 6px rgba(76, 175, 80, 0.8); + animation: pulse 2s infinite; +} + +.lockpick-indicator.active { + background: #4caf50; +} + +.lockpick-indicator.inactive { + background: #f44336; + animation: none; +} + +@keyframes pulse { + 0% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } + 100% { opacity: 1; transform: scale(1); } +} + +/* Expand/Collapse Toggle */ +.lockpick-expand-toggle { + position: absolute; + top: 10px; + left: 10px; + width: 24px; + height: 24px; + background: rgba(76, 175, 80, 0.2); + border: 1px solid #4caf50; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + color: #4caf50; + transition: all 0.3s ease; + z-index: 10; +} + +.lockpick-expand-toggle:hover { + background: rgba(76, 175, 80, 0.3); + box-shadow: 0 0 8px rgba(76, 175, 80, 0.4); +} + +.lockpick-expand-toggle.expanded { + transform: rotate(180deg); +} + +/* Search Room Button */ +.lockpick-search-room-container { + margin-bottom: 15px; + display: flex; + justify-content: center; + padding: 10px; + background: rgba(76, 175, 80, 0.1); + border-radius: 6px; + box-shadow: 0 0 10px rgba(76, 175, 80, 0.2); +} + +/* Action Buttons */ +.lockpick-action-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background: rgba(76, 175, 80, 0.2); + border: 1px solid #4caf50; + border-radius: 6px; + color: #4caf50; + font-family: 'VT323', monospace; + font-size: 14px; + cursor: pointer; + transition: all 0.3s ease; + user-select: none; +} + +.lockpick-action-btn:hover { + background: rgba(76, 175, 80, 0.3); + box-shadow: 0 0 10px rgba(76, 175, 80, 0.4); + transform: translateY(-1px); +} + +.lockpick-action-btn.active { + background: rgba(255, 193, 7, 0.3); + border-color: #ffc107; + color: #ffc107; + box-shadow: 0 0 15px rgba(255, 193, 7, 0.4); +} + +.lockpick-action-btn .btn-icon { + font-size: 16px; +} + +.lockpick-action-btn .btn-text { + font-weight: bold; +} + +/* Instructions */ +.lockpick-set-instructions { + margin-top: 10px; + padding: 10px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid #444; + border-radius: 6px; + font-size: 12px; + line-height: 1.4; + color: #ccc; + transition: all 0.3s ease; +} + +.lockpick-set-minigame-container:not(.expanded) .lockpick-set-instructions { + display: none; +} + +.instruction-text { + color: #aaa; +} + +.instruction-text strong { + color: #4caf50; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .lockpick-set-minigame-container { + top: 2vh !important; + right: 2vw !important; + left: 2vw !important; + width: 96vw !important; + max-width: 400px !important; + } + + .lockpick-set-minigame-container.expanded { + width: 96vw !important; + max-width: 500px !important; + } + + .lockpick-set-title { + font-size: 14px; + } + + .lockpick-action-btn { + justify-content: center; + padding: 6px 10px; + font-size: 12px; + } + + .lockpick-expand-toggle { + width: 20px; + height: 20px; + font-size: 10px; + } +} + +/* Animation for new highlights */ +@keyframes lockpickAppear { + 0% { + opacity: 0; + transform: translateX(-20px); + } + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.lockpick-indicator.new-highlight { + animation: lockpickAppear 0.5s ease-out; +} diff --git a/index.html b/index.html index cf48c8e..866e6ec 100644 --- a/index.html +++ b/index.html @@ -35,6 +35,9 @@ + + + @@ -50,47 +53,7 @@
- - -
- - -
-
-
Bluetooth Scanner
-
×
-
-
- -
-
-
All
-
Nearby
-
Saved
-
-
-
- - -
-
-
Biometric Samples
-
×
-
-
- -
-
-
All
-
Fingerprints
-
-
+
diff --git a/js/core/game.js b/js/core/game.js index 862ede5..274250b 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -60,7 +60,7 @@ export function preload() { this.load.image('bluetooth_scanner', 'assets/objects/bluetooth_scanner.png'); this.load.image('bluetooth', 'assets/objects/bluetooth.png'); this.load.image('tablet', 'assets/objects/tablet.png'); - this.load.image('fingerprint', 'assets/objects/fingerprint.png'); + this.load.image('fingerprint', 'assets/objects/fingerprint_small.png'); this.load.image('lockpick', 'assets/objects/lockpick.png'); this.load.image('spoofing_kit', 'assets/objects/office-misc-headphones.png'); @@ -524,18 +524,10 @@ export function update() { window.updateSwivelChairRotation(); } - // Check for Bluetooth devices - const currentTime = Date.now(); - if (currentTime - lastBluetoothScan >= 200) { // 200ms interval for more responsive updates - if (window.checkBluetoothDevices) { - window.checkBluetoothDevices(); - } - lastBluetoothScan = currentTime; - } + // Bluetooth device scanning is now handled by the minigame when active } -// Add timing variables at module level -let lastBluetoothScan = 0; +// Bluetooth scanning is now handled by the minigame // Helper functions diff --git a/js/main.js b/js/main.js index 6bd266e..d75a2d6 100644 --- a/js/main.js +++ b/js/main.js @@ -1,8 +1,9 @@ import { GAME_CONFIG } from './utils/constants.js?v=7'; import { preload, create, update } from './core/game.js?v=32'; import { initializeNotifications } from './systems/notifications.js?v=7'; -import { initializeBluetoothPanel } from './systems/bluetooth.js?v=8'; -import { initializeBiometricsPanel } from './systems/biometrics.js?v=22'; +// Bluetooth scanner is now handled as a minigame +// Biometrics is now handled as a minigame +import { startLockpickingMinigame } from './systems/interactions.js?v=23'; import { initializeDebugSystem } from './systems/debug.js?v=7'; import { initializeUI } from './ui/panels.js?v=9'; import { initializeModals } from './ui/modals.js?v=7'; @@ -64,8 +65,11 @@ function initializeGame() { // Initialize all systems initializeNotifications(); - initializeBluetoothPanel(); - initializeBiometricsPanel(); + // Bluetooth scanner and biometrics are now handled as minigames + + // Make lockpicking function available globally + window.startLockpickingMinigame = startLockpickingMinigame; + initializeDebugSystem(); initializeUI(); initializeModals(); diff --git a/js/minigames/biometrics/biometrics-minigame.js b/js/minigames/biometrics/biometrics-minigame.js new file mode 100644 index 0000000..d106638 --- /dev/null +++ b/js/minigames/biometrics/biometrics-minigame.js @@ -0,0 +1,631 @@ +import { MinigameScene } from '../framework/base-minigame.js'; + +// Biometrics Minigame Scene implementation +export class BiometricsMinigame extends MinigameScene { + constructor(container, params) { + // Ensure params is defined before calling parent constructor + params = params || {}; + + // Set default title if not provided + params.title = 'Biometric Scanner'; + + // Enable cancel button for biometrics minigame with custom text + params.showCancel = true; + params.cancelText = 'Close Scanner'; + + super(container, params); + + this.item = params.item; + this.biometricSamples = []; + this.searchingMode = false; + this.highlightedObjects = []; + + // Scanner state management + this.scannerState = { + failedAttempts: {}, + lockoutTimers: {} + }; + + // Constants + this.MAX_FAILED_ATTEMPTS = 3; + this.SCANNER_LOCKOUT_TIME = 30000; // 30 seconds + this.BIOMETRIC_QUALITY_THRESHOLD = 0.7; + } + + init() { + // Call parent init to set up common components + super.init(); + + console.log("Biometrics minigame initializing"); + + // Set container dimensions to be compact like the Bluetooth scanner + this.container.className += ' biometrics-minigame-container'; + + // Clear header content + this.headerElement.innerHTML = ''; + + // Configure game container with scanner background + this.gameContainer.className += ' biometrics-minigame-game-container'; + + // Create scanner interface + this.createScannerInterface(); + + // Initialize biometric samples from global state + this.initializeBiometricSamples(); + } + + createScannerInterface() { + // Create expand/collapse toggle button + const expandToggle = document.createElement('div'); + expandToggle.className = 'biometrics-expand-toggle'; + expandToggle.innerHTML = '▼'; + expandToggle.title = 'Expand/Collapse'; + + // Create scanner header + const scannerHeader = document.createElement('div'); + scannerHeader.className = 'biometrics-scanner-header'; + scannerHeader.innerHTML = ` +
+ Biometric Samples + Biometric Samples + 0 samples +
+
+
+ Ready +
+ `; + + // Create search room button (above samples list) + const searchRoomContainer = document.createElement('div'); + searchRoomContainer.className = 'biometrics-search-room-container'; + searchRoomContainer.innerHTML = ` + + `; + + // Create controls container (for expanded view) + const controlsContainer = document.createElement('div'); + controlsContainer.className = 'biometrics-scanner-controls'; + controlsContainer.innerHTML = ` +
+ +
+
+
All
+
Fingerprints
+
+ `; + + // Create samples list container + const samplesListContainer = document.createElement('div'); + samplesListContainer.className = 'biometrics-samples-list-container'; + samplesListContainer.innerHTML = ` +
+ Collected Samples +
0 samples
+
+
+ `; + + // Create instructions + const instructionsContainer = document.createElement('div'); + instructionsContainer.className = 'biometrics-scanner-instructions'; + instructionsContainer.innerHTML = ` +
+ 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 = '
No samples match your search.
'; + } else if (activeCategory !== 'all') { + biometricsContent.innerHTML = `
No ${activeCategory} samples found.
`; + } else { + biometricsContent.innerHTML = '
No samples collected yet. Use "Search Room" to find fingerprint objects.
'; + } + } else { + filteredSamples.forEach(sample => { + const sampleElement = document.createElement('div'); + sampleElement.className = 'sample-item'; + sampleElement.dataset.id = sample.id || 'unknown'; + + const owner = sample.owner || 'Unknown'; + const type = sample.type || 'fingerprint'; + const quality = sample.quality || 0; + const rating = sample.rating || this.getRatingFromQuality(quality); + const collectedAt = sample.collectedAt || new Date().toISOString(); + + const qualityPercentage = Math.round(quality * 100); + const timestamp = new Date(collectedAt); + const formattedTime = timestamp.toLocaleDateString() + ' ' + timestamp.toLocaleTimeString(); + + sampleElement.innerHTML = ` +
+ ${owner} + ${type} +
+
+ ${rating} (${qualityPercentage}%) + ${formattedTime} +
+ `; + + biometricsContent.appendChild(sampleElement); + }); + } + } + + syncBiometricSamples() { + if (!window.gameState) { + window.gameState = {}; + } + window.gameState.biometricSamples = this.biometricSamples; + } + + // Handle biometric scanner interaction (for unlocking doors, etc.) + handleBiometricScan(scannerId, requiredOwner) { + console.log('Biometric scan requested:', { scannerId, requiredOwner }); + + // Check if scanner is locked out + if (this.scannerState.lockoutTimers[scannerId]) { + const lockoutEnd = this.scannerState.lockoutTimers[scannerId]; + const now = Date.now(); + + if (now < lockoutEnd) { + const remainingTime = Math.ceil((lockoutEnd - now) / 1000); + if (window.gameAlert) { + window.gameAlert(`Scanner locked out. Try again in ${remainingTime} seconds.`, 'error', 'Scanner Locked', 3000); + } + return false; + } else { + // Lockout expired, clear it + delete this.scannerState.lockoutTimers[scannerId]; + delete this.scannerState.failedAttempts[scannerId]; + } + } + + // Check if we have a matching biometric sample + const matchingSample = this.biometricSamples.find(sample => + sample.owner === requiredOwner && sample.quality >= this.BIOMETRIC_QUALITY_THRESHOLD + ); + + if (matchingSample) { + console.log('Biometric scan successful:', matchingSample); + + if (window.gameAlert) { + window.gameAlert(`Biometric scan successful! Authenticated as ${requiredOwner}.`, 'success', 'Scan Successful', 4000); + } + + // Reset failed attempts on success + delete this.scannerState.failedAttempts[scannerId]; + + return true; + } else { + console.log('Biometric scan failed'); + this.handleScannerFailure(scannerId); + return false; + } + } + + handleScannerFailure(scannerId) { + // Initialize failed attempts if not exists + if (!this.scannerState.failedAttempts[scannerId]) { + this.scannerState.failedAttempts[scannerId] = 0; + } + + // Increment failed attempts + this.scannerState.failedAttempts[scannerId]++; + + // Check if we should lockout + if (this.scannerState.failedAttempts[scannerId] >= this.MAX_FAILED_ATTEMPTS) { + this.scannerState.lockoutTimers[scannerId] = Date.now() + this.SCANNER_LOCKOUT_TIME; + if (window.gameAlert) { + window.gameAlert(`Too many failed attempts. Scanner locked for ${this.SCANNER_LOCKOUT_TIME/1000} seconds.`, 'error', 'Scanner Locked', 5000); + } + } else { + const remainingAttempts = this.MAX_FAILED_ATTEMPTS - this.scannerState.failedAttempts[scannerId]; + if (window.gameAlert) { + window.gameAlert(`Scan failed. ${remainingAttempts} attempts remaining before lockout.`, 'warning', 'Scan Failed', 4000); + } + } + } + + start() { + super.start(); + console.log("Biometrics minigame started"); + + // Set up global interaction handler for fingerprint objects + this.setupFingerprintInteractionHandler(); + } + + setupFingerprintInteractionHandler() { + // Store the original interaction handler + this.originalInteractionHandler = window.handleObjectInteraction; + + // Override the interaction handler to handle fingerprint collection + window.handleObjectInteraction = (sprite) => { + // Check if we're in searching mode and this object has fingerprints + if (this.searchingMode && sprite.scenarioData && sprite.scenarioData.hasFingerprint === true) { + + console.log('Collecting fingerprint from object:', sprite); + this.collectFingerprintFromObject(sprite); + return; // Don't call the original handler + } + + // Call the original handler for all other interactions + if (this.originalInteractionHandler) { + this.originalInteractionHandler(sprite); + } + }; + } + + complete(success) { + // Stop searching mode and clear highlights + if (this.searchingMode) { + this.toggleRoomSearching(); + } + + // Sync final state + this.syncBiometricSamples(); + + // Call parent complete with result + super.complete(success, this.gameResult); + } + + cleanup() { + // Restore original interaction handler + if (this.originalInteractionHandler) { + window.handleObjectInteraction = this.originalInteractionHandler; + } + + // Clear highlights + this.clearHighlights(); + + // Call parent cleanup + super.cleanup(); + } +} + +// Function to start the biometrics minigame +export function startBiometricsMinigame(item) { + console.log('Starting biometrics minigame with:', { item }); + + // Make sure the minigame is registered + if (window.MinigameFramework && !window.MinigameFramework.registeredScenes['biometrics']) { + window.MinigameFramework.registerScene('biometrics', BiometricsMinigame); + console.log('Biometrics 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 biometrics minigame with proper parameters + const params = { + title: 'Biometric Scanner', + item: item, + onComplete: (success, result) => { + console.log('Biometrics minigame completed with success:', success); + } + }; + + console.log('Starting biometrics minigame with params:', params); + window.MinigameFramework.startMinigame('biometrics', null, params); +} diff --git a/js/minigames/bluetooth/bluetooth-scanner-minigame.js b/js/minigames/bluetooth/bluetooth-scanner-minigame.js new file mode 100644 index 0000000..09eaa47 --- /dev/null +++ b/js/minigames/bluetooth/bluetooth-scanner-minigame.js @@ -0,0 +1,593 @@ +import { MinigameScene } from '../framework/base-minigame.js'; + +// Bluetooth Scanner Minigame Scene implementation +export class BluetoothScannerMinigame extends MinigameScene { + constructor(container, params) { + // Ensure params is defined before calling parent constructor + params = params || {}; + + // Set default title if not provided + if (!params.title) { + params.title = 'Bluetooth Scanner'; + } + + // Enable cancel button for bluetooth scanner minigame with custom text + params.showCancel = true; + params.cancelText = 'Close Scanner'; + + super(container, params); + + this.item = params.item; + this.bluetoothDevices = []; + this.lastBluetoothPanelUpdate = 0; + this.newBluetoothDevices = 0; + this.scanInterval = null; + + // Constants + this.BLUETOOTH_SCAN_RANGE = 150; // pixels - 2 tiles range for Bluetooth scanning + this.BLUETOOTH_SCAN_INTERVAL = 200; // Scan every 200ms for more responsive updates + this.BLUETOOTH_UPDATE_THROTTLE = 100; // Update UI every 100ms max + } + + init() { + // Call parent init to set up common components + super.init(); + + console.log("Bluetooth scanner minigame initializing"); + + // Set container dimensions to be smaller than full screen + this.container.className += ' bluetooth-scanner-minigame-container'; + + // Clear header content + this.headerElement.innerHTML = ''; + + // Configure game container with scanner background + this.gameContainer.className += ' bluetooth-scanner-minigame-game-container'; + + // Create scanner interface + this.createScannerInterface(); + + // Initialize bluetooth devices from global state + this.initializeBluetoothDevices(); + } + + createScannerInterface() { + // Create expand/collapse toggle button + const expandToggle = document.createElement('div'); + expandToggle.className = 'bluetooth-scanner-expand-toggle'; + expandToggle.innerHTML = '▼'; + expandToggle.title = 'Expand/Collapse'; + + // Create scanner header + const scannerHeader = document.createElement('div'); + scannerHeader.className = 'bluetooth-scanner-header'; + scannerHeader.innerHTML = ` +
+ Bluetooth Scanner + Bluetooth Scanner +
+
+
+ Scanning... +
+ `; + + // Create search and filter controls + const controlsContainer = document.createElement('div'); + controlsContainer.className = 'bluetooth-scanner-controls'; + controlsContainer.innerHTML = ` +
+ +
+
+
All
+
Nearby
+
Saved
+
+ `; + + // Create device list container + const deviceListContainer = document.createElement('div'); + deviceListContainer.className = 'bluetooth-device-list-container'; + deviceListContainer.innerHTML = ` +
+ Detected Devices +
0 devices
+
+
+ `; + + // Create instructions + const instructionsContainer = document.createElement('div'); + instructionsContainer.className = 'bluetooth-scanner-instructions'; + instructionsContainer.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 = '
No devices match your search.
'; + } else if (activeCategory !== 'all') { + bluetoothContent.innerHTML = `
No ${activeCategory} devices found.
`; + } else { + bluetoothContent.innerHTML = '
No devices detected yet. Walk around to find Bluetooth devices.
'; + } + } 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 + this.addEventListener(deviceElement, 'click', (event) => { + deviceElement.classList.toggle('expanded'); + + // Mark as saved when expanded + if (!device.saved && deviceElement.classList.contains('expanded')) { + device.saved = true; + this.updateBluetoothCount(); + this.updateBluetoothPanel(); + this.syncBluetoothDevices(); + } + }); + + bluetoothContent.appendChild(deviceElement); + }); + } + } + + updateBluetoothCount() { + this.newBluetoothDevices = this.bluetoothDevices.filter(device => !device.saved && device.nearby).length; + } + + syncBluetoothDevices() { + if (!window.gameState) { + window.gameState = {}; + } + window.gameState.bluetoothDevices = this.bluetoothDevices; + } + + start() { + super.start(); + console.log("Bluetooth scanner minigame started"); + + // Start scanning + this.startScanning(); + } + + complete(success) { + // Stop scanning when minigame ends + this.stopScanning(); + + // Sync final state + this.syncBluetoothDevices(); + + // Call parent complete with result + super.complete(success, this.gameResult); + } + + cleanup() { + // Stop scanning + this.stopScanning(); + + // Call parent cleanup + super.cleanup(); + } +} + +// Function to start the bluetooth scanner minigame +export function startBluetoothScannerMinigame(item) { + console.log('Starting bluetooth scanner minigame with:', { item }); + + // Make sure the minigame is registered + if (window.MinigameFramework && !window.MinigameFramework.registeredScenes['bluetooth-scanner']) { + window.MinigameFramework.registerScene('bluetooth-scanner', BluetoothScannerMinigame); + console.log('Bluetooth scanner 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 bluetooth scanner minigame with proper parameters + const params = { + title: 'Bluetooth Scanner', + item: item, + onComplete: (success, result) => { + console.log('Bluetooth scanner minigame completed with success:', success); + } + }; + + console.log('Starting bluetooth scanner minigame with params:', params); + window.MinigameFramework.startMinigame('bluetooth-scanner', null, params); +} diff --git a/js/minigames/index.js b/js/minigames/index.js index ae3f617..667cd11 100644 --- a/js/minigames/index.js +++ b/js/minigames/index.js @@ -6,6 +6,9 @@ export { MinigameScene } from './framework/base-minigame.js'; export { LockpickingMinigamePhaser } from './lockpicking/lockpicking-game-phaser.js'; export { DustingMinigame } from './dusting/dusting-game.js'; export { NotesMinigame, startNotesMinigame, showMissionBrief } from './notes/notes-minigame.js'; +export { BluetoothScannerMinigame, startBluetoothScannerMinigame } from './bluetooth/bluetooth-scanner-minigame.js'; +export { BiometricsMinigame, startBiometricsMinigame } from './biometrics/biometrics-minigame.js'; +export { LockpickSetMinigame, startLockpickSetMinigame } from './lockpick/lockpick-set-minigame.js'; // Initialize the global minigame framework for backward compatibility import { MinigameFramework } from './framework/minigame-manager.js'; @@ -20,12 +23,27 @@ import { DustingMinigame } from './dusting/dusting-game.js'; // Import the notes minigame import { NotesMinigame, startNotesMinigame, showMissionBrief } from './notes/notes-minigame.js'; +// Import the bluetooth scanner minigame +import { BluetoothScannerMinigame, startBluetoothScannerMinigame } from './bluetooth/bluetooth-scanner-minigame.js'; + +// Import the biometrics minigame +import { BiometricsMinigame, startBiometricsMinigame } from './biometrics/biometrics-minigame.js'; + +// Import the lockpick set minigame +import { LockpickSetMinigame, startLockpickSetMinigame } from './lockpick/lockpick-set-minigame.js'; + // Register minigames MinigameFramework.registerScene('lockpicking', LockpickingMinigamePhaser); // Use Phaser version as default MinigameFramework.registerScene('lockpicking-phaser', LockpickingMinigamePhaser); // Keep explicit phaser name MinigameFramework.registerScene('dusting', DustingMinigame); MinigameFramework.registerScene('notes', NotesMinigame); +MinigameFramework.registerScene('bluetooth-scanner', BluetoothScannerMinigame); +MinigameFramework.registerScene('biometrics', BiometricsMinigame); +MinigameFramework.registerScene('lockpick-set', LockpickSetMinigame); -// Make notes minigame functions available globally +// Make minigame functions available globally window.startNotesMinigame = startNotesMinigame; -window.showMissionBrief = showMissionBrief; \ No newline at end of file +window.showMissionBrief = showMissionBrief; +window.startBluetoothScannerMinigame = startBluetoothScannerMinigame; +window.startBiometricsMinigame = startBiometricsMinigame; +window.startLockpickSetMinigame = startLockpickSetMinigame; \ No newline at end of file diff --git a/js/minigames/lockpick/lockpick-set-minigame.js b/js/minigames/lockpick/lockpick-set-minigame.js new file mode 100644 index 0000000..db10c67 --- /dev/null +++ b/js/minigames/lockpick/lockpick-set-minigame.js @@ -0,0 +1,325 @@ +import { MinigameScene } from '../framework/base-minigame.js'; + +// Lockpick Set Minigame Scene implementation +export class LockpickSetMinigame extends MinigameScene { + constructor(container, params) { + // Ensure params is defined before calling parent constructor + params = params || {}; + + // Set default title if not provided + params.title = 'Lockpick Set'; + + // Enable cancel button for lockpick minigame with custom text + params.showCancel = true; + params.cancelText = 'Close'; + + super(container, params); + + this.item = params.item; + this.searchingMode = false; + this.highlightedObjects = []; + } + + init() { + // Call parent init to set up common components + super.init(); + + console.log("Lockpick set minigame initializing"); + + // Set container dimensions to be compact + this.container.className += ' lockpick-set-minigame-container'; + + // Clear header content + this.headerElement.innerHTML = ''; + + // Configure game container + this.gameContainer.className += ' lockpick-set-minigame-game-container'; + + // Create lockpick interface + this.createLockpickInterface(); + } + + createLockpickInterface() { + // Create expand/collapse toggle button + const expandToggle = document.createElement('div'); + expandToggle.className = 'lockpick-expand-toggle'; + expandToggle.innerHTML = '▼'; + expandToggle.title = 'Expand/Collapse'; + + // Create search room button (at the top) + const searchRoomContainer = document.createElement('div'); + searchRoomContainer.className = 'lockpick-search-room-container'; + searchRoomContainer.innerHTML = ` + + `; + + // Create lockpick header + const lockpickHeader = document.createElement('div'); + lockpickHeader.className = 'lockpick-set-header'; + lockpickHeader.innerHTML = ` +
+ Lockpick Set + Lockpick Set +
+
+
+ Ready +
+ `; + + // Create instructions + const instructionsContainer = document.createElement('div'); + instructionsContainer.className = 'lockpick-set-instructions'; + instructionsContainer.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 = ` -
-

Collected Samples

-
-

No samples collected yet

-
-
-
-

Scanner Status

-
-

Ready

-
-
- `; - } - - updateBiometricDisplay(); -} - -// Add a biometric sample to the collection -export function addBiometricSample(sample) { - if (!window.gameState.biometricSamples) { - window.gameState.biometricSamples = []; - } - - // Ensure sample has all required properties with proper defaults - const normalizedSample = { - owner: sample.owner || 'Unknown', - type: sample.type || 'fingerprint', - quality: sample.quality || 0, - rating: sample.rating || getRatingFromQuality(sample.quality || 0), - data: sample.data || null, - id: sample.id || generateSampleId(), - collectedAt: new Date().toISOString() - }; - - // Check if sample already exists - const existingSample = window.gameState.biometricSamples.find(s => - s.owner === normalizedSample.owner && s.type === normalizedSample.type - ); - - if (existingSample) { - // Update existing sample with better quality if applicable - if (normalizedSample.quality > existingSample.quality) { - existingSample.quality = normalizedSample.quality; - existingSample.rating = normalizedSample.rating; - existingSample.collectedAt = normalizedSample.collectedAt; - } - } else { - // Add new sample - window.gameState.biometricSamples.push(normalizedSample); - } - - updateBiometricsPanel(); - updateBiometricsCount(); - console.log('Biometric sample added:', normalizedSample); -} - -function updateBiometricDisplay() { - const samplesList = document.getElementById('samples-list'); - const scannerStatus = document.getElementById('scanner-status'); - - if (!samplesList || !scannerStatus) return; - - if (window.gameState.biometricSamples.length === 0) { - samplesList.innerHTML = '

No samples collected yet

'; - } else { - samplesList.innerHTML = window.gameState.biometricSamples.map(sample => { - // Ensure all properties exist with safe defaults - const owner = sample.owner || 'Unknown'; - const type = sample.type || 'fingerprint'; - const quality = sample.quality || 0; - const rating = sample.rating || getRatingFromQuality(quality); - const collectedAt = sample.collectedAt || new Date().toISOString(); - - return ` -
- ${owner} -
- ${type} - ${rating} (${Math.round(quality * 100)}%) -
-
${new Date(collectedAt).toLocaleString()}
-
- `; - }).join(''); - } - - // Update scanner status - scannerStatus.innerHTML = '

Ready

'; -} - -// Helper function to generate rating from quality -function 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'; -} - -// Helper function to generate unique sample ID -function generateSampleId() { - return 'sample_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); -} - -// Handle biometric scanner interaction -export function handleBiometricScan(scannerId, requiredOwner) { - console.log('Biometric scan requested:', { scannerId, requiredOwner }); - - // Check if scanner is locked out - if (window.scannerState.lockoutTimers[scannerId]) { - const lockoutEnd = window.scannerState.lockoutTimers[scannerId]; - const now = Date.now(); - - if (now < lockoutEnd) { - const remainingTime = Math.ceil((lockoutEnd - now) / 1000); - window.gameAlert(`Scanner locked out. Try again in ${remainingTime} seconds.`, 'error', 'Scanner Locked', 3000); - return false; - } else { - // Lockout expired, clear it - delete window.scannerState.lockoutTimers[scannerId]; - delete window.scannerState.failedAttempts[scannerId]; - } - } - - // Check if we have a matching biometric sample - const matchingSample = window.gameState.biometricSamples.find(sample => - sample.owner === requiredOwner && sample.quality >= window.BIOMETRIC_QUALITY_THRESHOLD - ); - - if (matchingSample) { - console.log('Biometric scan successful:', matchingSample); - - // Visual success feedback - const scannerElement = document.querySelector(`[data-scanner-id="${scannerId}"]`); - if (scannerElement) { - scannerElement.style.border = '2px solid #00ff00'; - setTimeout(() => { - scannerElement.style.border = ''; - }, 2000); - } - - window.gameAlert(`Biometric scan successful! Authenticated as ${requiredOwner}.`, 'success', 'Scan Successful', 4000); - - // Reset failed attempts on success - delete window.scannerState.failedAttempts[scannerId]; - - return true; - } else { - console.log('Biometric scan failed'); - handleScannerFailure(scannerId); - return false; - } -} - -function handleScannerFailure(scannerId) { - // Initialize failed attempts if not exists - if (!window.scannerState.failedAttempts[scannerId]) { - window.scannerState.failedAttempts[scannerId] = 0; - } - - // Increment failed attempts - window.scannerState.failedAttempts[scannerId]++; - - // Check if we should lockout - if (window.scannerState.failedAttempts[scannerId] >= window.MAX_FAILED_ATTEMPTS) { - window.scannerState.lockoutTimers[scannerId] = Date.now() + window.SCANNER_LOCKOUT_TIME; - window.gameAlert(`Too many failed attempts. Scanner locked for ${window.SCANNER_LOCKOUT_TIME/1000} seconds.`, 'error', 'Scanner Locked', 5000); - } else { - const remainingAttempts = window.MAX_FAILED_ATTEMPTS - window.scannerState.failedAttempts[scannerId]; - window.gameAlert(`Scan failed. ${remainingAttempts} attempts remaining before lockout.`, 'warning', 'Scan Failed', 4000); - } -} - -// Generate a fingerprint sample with quality assessment -export function 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 = getRatingFromQuality(quality); - - return { - owner: owner || 'Unknown', - type: 'fingerprint', - quality: quality, - rating: rating, - id: generateSampleId(), - collectedFrom: 'evidence' - }; -} - -// Toggle the biometrics panel -export function toggleBiometricsPanel() { - const biometricsPanel = document.getElementById('biometrics-panel'); - if (!biometricsPanel) return; - - const isVisible = biometricsPanel.style.display === 'block'; - biometricsPanel.style.display = isVisible ? 'none' : 'block'; - - // Update panel content when opening - if (!isVisible) { - updateBiometricsPanel(); - } -} - -// Update biometrics panel with current samples -export function updateBiometricsPanel() { - const biometricsContent = document.getElementById('biometrics-content'); - if (!biometricsContent) return; - - const searchTerm = document.getElementById('biometrics-search')?.value?.toLowerCase() || ''; - const activeCategory = document.querySelector('.biometrics-category.active')?.dataset.category || 'all'; - - // Filter samples based on search and category - let filteredSamples = [...(window.gameState.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); - - // Clear current content - biometricsContent.innerHTML = ''; - - // Add samples - if (filteredSamples.length === 0) { - if (searchTerm) { - biometricsContent.innerHTML = '
No samples match your search.
'; - } else if (activeCategory !== 'all') { - biometricsContent.innerHTML = `
No ${activeCategory} samples found.
`; - } else { - biometricsContent.innerHTML = '
No samples collected yet.
'; - } - } else { - filteredSamples.forEach(sample => { - const sampleElement = document.createElement('div'); - sampleElement.className = 'sample-item'; - sampleElement.dataset.id = sample.id || 'unknown'; - - // Ensure all properties exist with safe defaults - const owner = sample.owner || 'Unknown'; - const type = sample.type || 'fingerprint'; - const quality = sample.quality || 0; - const rating = sample.rating || getRatingFromQuality(quality); - const collectedAt = sample.collectedAt || new Date().toISOString(); - - const qualityPercentage = Math.round(quality * 100); - const timestamp = new Date(collectedAt); - const formattedTime = timestamp.toLocaleDateString() + ' ' + timestamp.toLocaleTimeString(); - - sampleElement.innerHTML = ` - ${owner} -
- ${type} - ${rating} (${qualityPercentage}%) -
-
${formattedTime}
- `; - - biometricsContent.appendChild(sampleElement); - }); - } -} - -// Update biometrics count in the toggle button -export function updateBiometricsCount() { - const countElement = document.getElementById('biometrics-count'); - if (countElement && window.gameState?.biometricSamples) { - const count = window.gameState.biometricSamples.length; - countElement.textContent = count; - countElement.style.display = count > 0 ? 'flex' : 'none'; - - // Show the biometrics toggle if we have samples - const biometricsToggle = document.getElementById('biometrics-toggle'); - if (biometricsToggle && count > 0) { - biometricsToggle.style.display = 'block'; - } - } -} - -// Export for global access -window.initializeBiometricsPanel = initializeBiometricsPanel; -window.addBiometricSample = addBiometricSample; -window.handleBiometricScan = handleBiometricScan; -window.generateFingerprintSample = generateFingerprintSample; -window.toggleBiometricsPanel = toggleBiometricsPanel; -window.updateBiometricsPanel = updateBiometricsPanel; -window.updateBiometricsCount = updateBiometricsCount; \ No newline at end of file diff --git a/js/systems/bluetooth.js b/js/systems/bluetooth.js deleted file mode 100644 index b6a75c4..0000000 --- a/js/systems/bluetooth.js +++ /dev/null @@ -1,486 +0,0 @@ -// Bluetooth System -// Handles Bluetooth device scanning and management - -// Bluetooth state management -let bluetoothDevices = []; -let lastBluetoothPanelUpdate = 0; -let newBluetoothDevices = 0; - -// Sync with global game state -function syncBluetoothDevices() { - if (!window.gameState) { - window.gameState = {}; - } - window.gameState.bluetoothDevices = bluetoothDevices; -} - -// Constants -const BLUETOOTH_SCAN_RANGE = 150; // pixels - 2 tiles range for Bluetooth scanning -const BLUETOOTH_SCAN_INTERVAL = 200; // Scan every 200ms for more responsive updates -const BLUETOOTH_UPDATE_THROTTLE = 100; // Update UI every 100ms max - -// Initialize the Bluetooth system -export function initializeBluetoothPanel() { - console.log('Bluetooth system initialized'); - - // Create bluetooth device list - bluetoothDevices = []; - - // Set up bluetooth toggle button handler - const bluetoothToggle = document.getElementById('bluetooth-toggle'); - if (bluetoothToggle) { - bluetoothToggle.addEventListener('click', toggleBluetoothPanel); - } - - // Set up bluetooth close button - const bluetoothClose = document.getElementById('bluetooth-close'); - if (bluetoothClose) { - bluetoothClose.addEventListener('click', toggleBluetoothPanel); - } - - // Set up search functionality - const bluetoothSearch = document.getElementById('bluetooth-search'); - if (bluetoothSearch) { - bluetoothSearch.addEventListener('input', updateBluetoothPanel); - } - - // Set up category filters - const categories = document.querySelectorAll('.bluetooth-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 bluetooth panel - updateBluetoothPanel(); - }); - }); - - // Initialize bluetooth panel - updateBluetoothPanel(); - updateBluetoothCount(); - syncBluetoothDevices(); -} - -// Check for Bluetooth devices -export function checkBluetoothDevices() { - // Find scanner in inventory - const scanner = window.inventory.items.find(item => - item.scenarioData?.type === "bluetooth_scanner" - ); - - if (!scanner) { - return; - } - - // Show the Bluetooth toggle button if it's not already visible - const bluetoothToggle = document.getElementById('bluetooth-toggle'); - if (bluetoothToggle && bluetoothToggle.style.display === 'none') { - bluetoothToggle.style.display = 'flex'; - } - - // 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 <= 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 / 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 = 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 = addBluetoothDevice(deviceName, deviceMac, details, true); - if (newDevice) { - newDevice.signalStrength = signalStrength; - newDevice.signalStrengthPercentage = signalStrengthPercentage; - if (window.gameAlert) { - window.gameAlert(`Bluetooth device detected: ${deviceName} (MAC: ${deviceMac})`, 'info', 'Bluetooth Scanner', 4000); - } - needsUpdate = true; - } - } - } - } - }); - - // Mark devices that weren't detected in this scan as not nearby - bluetoothDevices.forEach(device => { - const deviceKey = `${device.mac}|${device.name}`; - if (device.nearby && !detectedDevices.has(deviceKey)) { - device.nearby = false; - device.lastSeen = new Date(); - needsUpdate = true; - } - }); - - // Force immediate UI update if panel is open and devices changed nearby status - if (needsUpdate) { - const bluetoothPanel = document.getElementById('bluetooth-panel'); - if (bluetoothPanel && bluetoothPanel.style.display === 'block') { - // Force update by resetting throttle timer - lastBluetoothPanelUpdate = 0; - } - } - - // Always update the count and sync devices when there are changes - if (needsUpdate) { - updateBluetoothCount(); - syncBluetoothDevices(); - - // Update the panel UI if it's visible - const bluetoothPanel = document.getElementById('bluetooth-panel'); - if (bluetoothPanel && bluetoothPanel.style.display === 'block') { - const now = Date.now(); - if (now - lastBluetoothPanelUpdate > BLUETOOTH_UPDATE_THROTTLE) { - updateBluetoothPanel(); - lastBluetoothPanelUpdate = now; - } - } - } -} - -// Add a Bluetooth device to the scanner panel -export function addBluetoothDevice(name, mac, details = "", nearby = true) { - // Check if a device with the same MAC + name combination already exists - const deviceExists = bluetoothDevices.some(device => device.mac === mac && device.name === name); - - // If the device already exists, update its nearby status - if (deviceExists) { - const existingDevice = bluetoothDevices.find(device => device.mac === mac && device.name === name); - existingDevice.nearby = nearby; - existingDevice.lastSeen = new Date(); - updateBluetoothPanel(); - 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 - }; - - bluetoothDevices.push(device); - updateBluetoothPanel(); - updateBluetoothCount(); - syncBluetoothDevices(); - - // Show notification for new device - if (window.showNotification) { - window.showNotification(`New Bluetooth device detected: ${name}`, 'info', 'Bluetooth Scanner', 3000); - } - - return device; -} - -// Update the Bluetooth scanner panel with current devices -export function updateBluetoothPanel() { - const bluetoothContent = document.getElementById('bluetooth-content'); - if (!bluetoothContent) return; - - const searchTerm = document.getElementById('bluetooth-search')?.value?.toLowerCase() || ''; - - // 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; - - // 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 = 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 - bluetoothDevices.push(newDevice); - console.log('Added inventory device to bluetoothDevices:', newDevice); - syncBluetoothDevices(); - } else { - // Update existing device - const existingDevice = 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); - syncBluetoothDevices(); - } - } - }); - } - - // Filter devices based on search and category - let filteredDevices = [...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); - }); - - // Clear current content - bluetoothContent.innerHTML = ''; - - // Add devices - if (filteredDevices.length === 0) { - if (searchTerm) { - bluetoothContent.innerHTML = '
No devices match your search.
'; - } else if (activeCategory !== 'all') { - bluetoothContent.innerHTML = `
No ${activeCategory} devices found.
`; - } else { - bluetoothContent.innerHTML = '
No devices detected yet.
'; - } - } 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 }