Implement Minigames for Bluetooth Scanner, Biometrics, and Lockpick Set: Transition Bluetooth and biometrics functionalities to dedicated minigames, enhancing user interaction and gameplay experience. Introduce new CSS styles for each minigame and update the main game logic to support these changes. Remove legacy systems for biometrics and Bluetooth management to streamline code and improve maintainability.

This commit is contained in:
Z. Cliffe Schreuders
2025-10-11 02:25:50 +01:00
parent 422b8e5c7b
commit d46fa79718
18 changed files with 3018 additions and 1023 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

View File

@@ -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,

View File

@@ -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,

528
css/biometrics-minigame.css Normal file
View File

@@ -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);
}

462
css/bluetooth-scanner.css Normal file
View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -35,6 +35,9 @@
<link rel="stylesheet" href="css/lockpicking.css">
<link rel="stylesheet" href="css/modals.css">
<link rel="stylesheet" href="css/notes.css">
<link rel="stylesheet" href="css/bluetooth-scanner.css">
<link rel="stylesheet" href="css/biometrics-minigame.css">
<link rel="stylesheet" href="css/lockpick-set-minigame.css">
<!-- External JavaScript libraries -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
@@ -50,47 +53,7 @@
<!-- Toggle Buttons Container -->
<div id="toggle-buttons-container">
<div id="bluetooth-toggle" style="display: none;">
<img src="assets/objects/bluetooth_scanner.png" alt="Bluetooth">
<div id="bluetooth-count">0</div>
</div>
<div id="biometrics-toggle" style="display: none;">
<img src="assets/objects/fingerprint.png" alt="Biometrics">
<div id="biometrics-count">0</div>
</div>
</div>
<!-- Bluetooth Scanner Panel -->
<div id="bluetooth-panel">
<div id="bluetooth-header">
<div id="bluetooth-title">Bluetooth Scanner</div>
<div id="bluetooth-close">×</div>
</div>
<div id="bluetooth-search-container">
<input type="text" id="bluetooth-search" placeholder="Search devices...">
</div>
<div id="bluetooth-categories">
<div class="bluetooth-category active" data-category="all">All</div>
<div class="bluetooth-category" data-category="nearby">Nearby</div>
<div class="bluetooth-category" data-category="saved">Saved</div>
</div>
<div id="bluetooth-content"></div>
</div>
<!-- Biometrics Panel -->
<div id="biometrics-panel">
<div id="biometrics-header">
<div id="biometrics-title">Biometric Samples</div>
<div id="biometrics-close">×</div>
</div>
<div id="biometrics-search-container">
<input type="text" id="biometrics-search" placeholder="Search samples...">
</div>
<div id="biometrics-categories">
<div class="biometrics-category active" data-category="all">All</div>
<div class="biometrics-category" data-category="fingerprint">Fingerprints</div>
</div>
<div id="biometrics-content"></div>
<!-- Biometrics is now handled as a minigame -->
</div>
<!-- Inventory Container -->

View File

@@ -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

View File

@@ -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();

View File

@@ -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 = `
<div class="biometrics-scanner-title">
<img src="assets/objects/fingerprint.png" alt="Biometric Samples" class="scanner-icon">
<span>Biometric Samples</span>
<span class="samples-count-header">0 samples</span>
</div>
<div class="biometrics-scanner-status">
<div class="scanner-indicator active"></div>
<span>Ready</span>
</div>
`;
// Create search room button (above samples list)
const searchRoomContainer = document.createElement('div');
searchRoomContainer.className = 'biometrics-search-room-container';
searchRoomContainer.innerHTML = `
<button id="search-room-btn" class="biometrics-action-btn">
<span class="btn-icon">🔍</span>
<span class="btn-text">Search Room for Fingerprints</span>
</button>
`;
// Create controls container (for expanded view)
const controlsContainer = document.createElement('div');
controlsContainer.className = 'biometrics-scanner-controls';
controlsContainer.innerHTML = `
<div class="biometrics-search-container">
<input type="text" id="biometrics-search" placeholder="Search samples..." class="biometrics-search-input">
</div>
<div class="biometrics-categories">
<div class="biometrics-category active" data-category="all">All</div>
<div class="biometrics-category" data-category="fingerprint">Fingerprints</div>
</div>
`;
// Create samples list container
const samplesListContainer = document.createElement('div');
samplesListContainer.className = 'biometrics-samples-list-container';
samplesListContainer.innerHTML = `
<div class="biometrics-samples-list-header">
<span>Collected Samples</span>
<div class="samples-count">0 samples</div>
</div>
<div class="biometrics-samples-list" id="biometrics-samples-list"></div>
`;
// Create instructions
const instructionsContainer = document.createElement('div');
instructionsContainer.className = 'biometrics-scanner-instructions';
instructionsContainer.innerHTML = `
<div class="instruction-text">
<strong>Instructions:</strong><br>
• Use "Search Room" to highlight objects with fingerprints<br>
• Click highlighted objects to collect fingerprint samples<br>
• Collected samples can be used to unlock biometric scanners<br>
• Higher quality samples have better success rates
</div>
`;
// 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 = '<div class="sample-item">No samples match your search.</div>';
} else if (activeCategory !== 'all') {
biometricsContent.innerHTML = `<div class="sample-item">No ${activeCategory} samples found.</div>`;
} else {
biometricsContent.innerHTML = '<div class="sample-item">No samples collected yet. Use "Search Room" to find fingerprint objects.</div>';
}
} 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 = `
<div class="sample-header">
<strong>${owner}</strong>
<span class="sample-type">${type}</span>
</div>
<div class="sample-details">
<span class="sample-quality quality-${rating.toLowerCase()}">${rating} (${qualityPercentage}%)</span>
<span class="sample-date">${formattedTime}</span>
</div>
`;
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);
}

View File

@@ -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 = `
<div class="bluetooth-scanner-title">
<img src="assets/objects/bluetooth_scanner.png" alt="Bluetooth Scanner" class="scanner-icon">
<span>Bluetooth Scanner</span>
</div>
<div class="bluetooth-scanner-status">
<div class="scanner-indicator active"></div>
<span>Scanning...</span>
</div>
`;
// Create search and filter controls
const controlsContainer = document.createElement('div');
controlsContainer.className = 'bluetooth-scanner-controls';
controlsContainer.innerHTML = `
<div class="bluetooth-search-container">
<input type="text" id="bluetooth-search" placeholder="Search devices..." class="bluetooth-search-input">
</div>
<div class="bluetooth-categories">
<div class="bluetooth-category active" data-category="all">All</div>
<div class="bluetooth-category" data-category="nearby">Nearby</div>
<div class="bluetooth-category" data-category="saved">Saved</div>
</div>
`;
// Create device list container
const deviceListContainer = document.createElement('div');
deviceListContainer.className = 'bluetooth-device-list-container';
deviceListContainer.innerHTML = `
<div class="bluetooth-device-list-header">
<span>Detected Devices</span>
<div class="device-count">0 devices</div>
</div>
<div class="bluetooth-device-list" id="bluetooth-device-list"></div>
`;
// Create instructions
const instructionsContainer = document.createElement('div');
instructionsContainer.className = 'bluetooth-scanner-instructions';
instructionsContainer.innerHTML = `
<div class="instruction-text">
<strong>Instructions:</strong><br>
• Walk around to detect Bluetooth devices<br>
• Green signal bars indicate nearby devices<br>
• Click devices to save them for later reference<br>
• Devices in your inventory are always visible
</div>
`;
// 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 = '<div class="bluetooth-device">No devices match your search.</div>';
} else if (activeCategory !== 'all') {
bluetoothContent.innerHTML = `<div class="bluetooth-device">No ${activeCategory} devices found.</div>`;
} else {
bluetoothContent.innerHTML = '<div class="bluetooth-device">No devices detected yet. Walk around to find Bluetooth devices.</div>';
}
} 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 = `<div class="bluetooth-device-name">
<span>${device.name}</span>
<div class="bluetooth-device-icons">`;
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 += `<div class="bluetooth-signal-bar-container">
<div class="bluetooth-signal-bars">`;
for (let i = 1; i <= 5; i++) {
const isActive = i <= activeBars;
deviceContent += `<div class="bluetooth-signal-bar ${isActive ? 'active' : ''}" style="color: ${signalColor};"></div>`;
}
deviceContent += `</div></div>`;
} else if (device.nearby) {
// Fallback if signal strength not available
deviceContent += `<span class="bluetooth-device-icon">📶</span>`;
}
if (device.saved) {
deviceContent += `<span class="bluetooth-device-icon">💾</span>`;
}
if (device.inInventory) {
deviceContent += `<span class="bluetooth-device-icon">🎒</span>`;
}
deviceContent += `</div></div>`;
deviceContent += `<div class="bluetooth-device-details">MAC: ${device.mac}\n${device.details}</div>`;
deviceContent += `<div class="bluetooth-device-timestamp">Last seen: ${formattedDate} ${formattedTime}</div>`;
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);
}

View File

@@ -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;
window.showMissionBrief = showMissionBrief;
window.startBluetoothScannerMinigame = startBluetoothScannerMinigame;
window.startBiometricsMinigame = startBiometricsMinigame;
window.startLockpickSetMinigame = startLockpickSetMinigame;

View File

@@ -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 = `
<button id="search-locks-btn" class="lockpick-action-btn">
<span class="btn-icon">🔍</span>
<span class="btn-text">Search for Pickable Locks</span>
</button>
`;
// Create lockpick header
const lockpickHeader = document.createElement('div');
lockpickHeader.className = 'lockpick-set-header';
lockpickHeader.innerHTML = `
<div class="lockpick-set-title">
<img src="assets/objects/lockpick.png" alt="Lockpick Set" class="lockpick-icon">
<span>Lockpick Set</span>
</div>
<div class="lockpick-set-status">
<div class="lockpick-indicator active"></div>
<span>Ready</span>
</div>
`;
// Create instructions
const instructionsContainer = document.createElement('div');
instructionsContainer.className = 'lockpick-set-instructions';
instructionsContainer.innerHTML = `
<div class="instruction-text">
<strong>Instructions:</strong><br>
• Use "Search for Pickable Locks" to highlight locks in the room<br>
• Click highlighted locks to attempt lockpicking<br>
• Different locks have different difficulty levels<br>
• Higher skill and better tools improve success rates
</div>
`;
// 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);
}

View File

@@ -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 = `
<div class="panel-section">
<h4>Collected Samples</h4>
<div id="samples-list">
<p>No samples collected yet</p>
</div>
</div>
<div class="panel-section">
<h4>Scanner Status</h4>
<div id="scanner-status">
<p>Ready</p>
</div>
</div>
`;
}
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 = '<p>No samples collected yet</p>';
} 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 `
<div class="sample-item">
<strong>${owner}</strong>
<div class="sample-details">
<span class="sample-type">${type}</span>
<span class="sample-quality quality-${rating.toLowerCase()}">${rating} (${Math.round(quality * 100)}%)</span>
</div>
<div class="sample-date">${new Date(collectedAt).toLocaleString()}</div>
</div>
`;
}).join('');
}
// Update scanner status
scannerStatus.innerHTML = '<p>Ready</p>';
}
// 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 = '<div class="sample-item">No samples match your search.</div>';
} else if (activeCategory !== 'all') {
biometricsContent.innerHTML = `<div class="sample-item">No ${activeCategory} samples found.</div>`;
} else {
biometricsContent.innerHTML = '<div class="sample-item">No samples collected yet.</div>';
}
} 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 = `
<strong>${owner}</strong>
<div class="sample-details">
<span class="sample-type">${type}</span>
<span class="sample-quality quality-${rating.toLowerCase()}">${rating} (${qualityPercentage}%)</span>
</div>
<div class="sample-date">${formattedTime}</div>
`;
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;

View File

@@ -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 = '<div class="bluetooth-device">No devices match your search.</div>';
} else if (activeCategory !== 'all') {
bluetoothContent.innerHTML = `<div class="bluetooth-device">No ${activeCategory} devices found.</div>`;
} else {
bluetoothContent.innerHTML = '<div class="bluetooth-device">No devices detected yet.</div>';
}
} 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 = `<div class="bluetooth-device-name">
<span>${device.name}</span>
<div class="bluetooth-device-icons">`;
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 += `<div class="bluetooth-signal-bar-container">
<div class="bluetooth-signal-bars">`;
for (let i = 1; i <= 5; i++) {
const isActive = i <= activeBars;
deviceContent += `<div class="bluetooth-signal-bar ${isActive ? 'active' : ''}" style="color: ${signalColor};"></div>`;
}
deviceContent += `</div></div>`;
} else if (device.nearby) {
// Fallback if signal strength not available
deviceContent += `<span class="bluetooth-device-icon">📶</span>`;
}
if (device.saved) {
deviceContent += `<span class="bluetooth-device-icon">💾</span>`;
}
if (device.inInventory) {
deviceContent += `<span class="bluetooth-device-icon">🎒</span>`;
}
deviceContent += `</div></div>`;
deviceContent += `<div class="bluetooth-device-details">MAC: ${device.mac}\n${device.details}</div>`;
deviceContent += `<div class="bluetooth-device-timestamp">Last seen: ${formattedDate} ${formattedTime}</div>`;
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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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
}