mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
Refactor minigame structure and styles: Update index_new.html to link to the new minigames-framework.css, add new lockpicking-comparison.html and locksmith-forge.html files for enhanced gameplay, and introduce dusting and lockpicking CSS files for improved styling. Update README_design.md for clarity on main.js functionality. Add new test-phaser-lockpicking.html for testing purposes. Enhance Bluetooth system with new functionality in bluetooth.js and interactions.js. Ensure game state management for notes and Bluetooth devices is consistent across the application.
This commit is contained in:
@@ -48,7 +48,7 @@ BreakEscape/
|
||||
│ └── utilities.css # Utility classes and helpers
|
||||
│
|
||||
├── js/ # JavaScript source code
|
||||
│ ├── main.js # Application entry point and initialization
|
||||
│ ├── main.js # Application entry point, init, and game state variables
|
||||
│ │
|
||||
│ ├── core/ # Core game engine components
|
||||
│ │ ├── game.js # Main game scene (preload, create, update)
|
||||
|
||||
182
css/dusting.css
Normal file
182
css/dusting.css
Normal file
@@ -0,0 +1,182 @@
|
||||
/* Dusting Minigame Styles */
|
||||
|
||||
.dusting-container {
|
||||
width: 75% !important;
|
||||
height: 75% !important;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.dusting-game-container {
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
margin: 0 auto 20px auto;
|
||||
background: #1a1a1a;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5) inset;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 2px solid #333;
|
||||
}
|
||||
|
||||
.dusting-grid-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-size: 20px 20px;
|
||||
background-repeat: repeat;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dusting-tools-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dusting-tool-button {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
transition: opacity 0.2s, transform 0.1s;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.dusting-tool-button:hover {
|
||||
opacity: 0.9;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.dusting-tool-button.active {
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.dusting-tool-fine {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.dusting-tool-medium {
|
||||
background-color: #2ecc71;
|
||||
}
|
||||
|
||||
.dusting-tool-wide {
|
||||
background-color: #e67e22;
|
||||
}
|
||||
|
||||
.dusting-particle-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dusting-particle {
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dusting-progress-container {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
color: white;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 14px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dusting-grid-cell {
|
||||
position: absolute;
|
||||
background: #000;
|
||||
border: 1px solid #222;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.dusting-cell-clean {
|
||||
background: black !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dusting-cell-light-dust {
|
||||
background: #444 !important;
|
||||
box-shadow: inset 0 0 3px rgba(255,255,255,0.2) !important;
|
||||
}
|
||||
|
||||
.dusting-cell-fingerprint {
|
||||
background: #0f0 !important;
|
||||
box-shadow: inset 0 0 5px rgba(0,255,0,0.5), 0 0 5px rgba(0,255,0,0.3) !important;
|
||||
}
|
||||
|
||||
.dusting-cell-medium-dust {
|
||||
background: #888 !important;
|
||||
box-shadow: inset 0 0 4px rgba(255,255,255,0.3) !important;
|
||||
}
|
||||
|
||||
.dusting-cell-heavy-dust {
|
||||
background: #ccc !important;
|
||||
box-shadow: inset 0 0 5px rgba(255,255,255,0.5) !important;
|
||||
}
|
||||
|
||||
.dusting-progress-found {
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.dusting-progress-over-dusted {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.dusting-progress-normal {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Dusting Game Success/Failure Messages */
|
||||
.dusting-success-message {
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.dusting-success-quality {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dusting-success-details {
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.dusting-failure-message {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.dusting-failure-subtitle {
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
515
css/lockpicking.css
Normal file
515
css/lockpicking.css
Normal file
@@ -0,0 +1,515 @@
|
||||
/* Lockpicking Minigame Styles */
|
||||
|
||||
/* Override header positioning for lockpicking */
|
||||
.minigame-header {
|
||||
position: relative !important;
|
||||
background: rgba(34, 34, 34, 0.95);
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.minigame-header h3 {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 16px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.minigame-header p {
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lock-visual {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
height: 300px; /* Taller for better visibility */
|
||||
background: #f0e6a6; /* Light yellow/beige background */
|
||||
border-radius: 5px;
|
||||
padding: 25px;
|
||||
position: relative;
|
||||
margin: 20px auto; /* Center and add margins */
|
||||
border: 2px solid #887722;
|
||||
max-width: 800px; /* Reasonable maximum width */
|
||||
width: 90%; /* Responsive width */
|
||||
}
|
||||
|
||||
.pin {
|
||||
width: 40px;
|
||||
height: 200px; /* Taller to match container */
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border-radius: 4px 4px 0 0;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.pin:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.shear-line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #aa8833;
|
||||
bottom: 60px; /* Match driver pin starting position */
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.key-pin {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0px; /* Start at 0px, grows dynamically via JavaScript */
|
||||
background: #dd3333; /* Red for key pins */
|
||||
border-radius: 0 0 0 0;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%); /* Pointed bottom */
|
||||
transition: height 0.1s ease; /* Smooth height animation */
|
||||
}
|
||||
|
||||
.driver-pin {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 40px; /* Smaller height for better proportion */
|
||||
background: #3388dd; /* Blue for driver pins */
|
||||
bottom: 60px; /* Start at shear line level */
|
||||
border-radius: 4px 4px 0 0;
|
||||
transition: bottom 0.1s ease, background-color 0.3s;
|
||||
}
|
||||
|
||||
.spring {
|
||||
position: absolute;
|
||||
bottom: 100px; /* Positioned above driver pin */
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
#cccccc 0px,
|
||||
#cccccc 2px,
|
||||
#999999 2px,
|
||||
#999999 4px
|
||||
);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.pin.binding {
|
||||
box-shadow: 0 0 8px 2px #ffcc00;
|
||||
}
|
||||
|
||||
/* Keep driver pin (blue) above the shear line when set */
|
||||
.pin.set .driver-pin {
|
||||
background: #22aa22; /* Green to indicate set */
|
||||
}
|
||||
|
||||
/* Key pin turns green when set */
|
||||
.pin.set .key-pin {
|
||||
background: #22aa22; /* Green to indicate set */
|
||||
}
|
||||
|
||||
.cylinder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #ddbb77;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border: 2px solid #887722;
|
||||
}
|
||||
|
||||
.cylinder-inner {
|
||||
width: 80%;
|
||||
height: 20px;
|
||||
background: #ccaa66;
|
||||
border-radius: 3px;
|
||||
transform-origin: center;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.cylinder.rotated .cylinder-inner {
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
|
||||
.lockpick-feedback {
|
||||
padding: 15px;
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
min-height: 30px;
|
||||
margin-top: 20px;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tension-control {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
background: #333;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.tension-wrench-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.tension-track {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: #444;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tension-progress {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(to right, #666, #2196F3);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.tension-status {
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.tension-wrench {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
background: #666;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s, background-color 0.3s;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 20px;
|
||||
z-index: 2;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.tension-wrench:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
.tension-wrench.active {
|
||||
background: #2196F3;
|
||||
}
|
||||
|
||||
.wrench-handle {
|
||||
width: 60%;
|
||||
height: 10px;
|
||||
background: #999;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.wrench-tip {
|
||||
width: 20px;
|
||||
height: 30px;
|
||||
background: #999;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
/* General success/failure message styles */
|
||||
.lockpicking-success-message {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.lockpicking-success-subtitle {
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.lockpicking-success-details {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.lockpicking-failure-message {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.lockpicking-failure-subtitle {
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.tension-control {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
background: #333;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.tension-wrench-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.tension-track {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: #444;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tension-progress {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(to right, #666, #2196F3);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
.tension-status {
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.tension-wrench {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
background: #666;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s, background-color 0.3s;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 20px;
|
||||
z-index: 2;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.tension-wrench:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
.tension-wrench.active {
|
||||
background: #2196F3;
|
||||
}
|
||||
|
||||
.wrench-handle {
|
||||
width: 60%;
|
||||
height: 10px;
|
||||
background: #999;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.wrench-tip {
|
||||
width: 20px;
|
||||
height: 30px;
|
||||
background: #999;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.cylinder {
|
||||
height: 20px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.lock-visual {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 160px;
|
||||
background: #f0e6a6; /* Light yellow/beige background */
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
border: 2px solid #887722;
|
||||
}
|
||||
|
||||
.pin {
|
||||
width: 30px;
|
||||
height: 110px;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border-radius: 4px 4px 0 0;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pin:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.shear-line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #aa8833;
|
||||
bottom: 50px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.key-pin {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0px;
|
||||
background: #dd3333; /* Red for key pins */
|
||||
transition: height 0.05s;
|
||||
border-radius: 0 0 0 0;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%); /* Pointed bottom */
|
||||
}
|
||||
|
||||
.driver-pin {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: #3388dd; /* Blue for driver pins */
|
||||
transition: bottom 0.05s;
|
||||
bottom: 50px;
|
||||
border-radius: 0 0 0 0;
|
||||
}
|
||||
|
||||
.spring {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background: linear-gradient(to bottom,
|
||||
#cccccc 0%, #cccccc 20%,
|
||||
#999999 20%, #999999 25%,
|
||||
#cccccc 25%, #cccccc 40%,
|
||||
#999999 40%, #999999 45%,
|
||||
#cccccc 45%, #cccccc 60%,
|
||||
#999999 60%, #999999 65%,
|
||||
#cccccc 65%, #cccccc 80%,
|
||||
#999999 80%, #999999 85%,
|
||||
#cccccc 85%, #cccccc 100%
|
||||
);
|
||||
transition: height 0.05s;
|
||||
}
|
||||
|
||||
.pin.binding {
|
||||
box-shadow: 0 0 8px 2px #ffcc00;
|
||||
}
|
||||
|
||||
.pin.set .driver-pin {
|
||||
bottom: 52px; /* Just above shear line */
|
||||
background: #22aa22; /* Green to indicate set */
|
||||
}
|
||||
|
||||
.pin.set .key-pin {
|
||||
height: 49px; /* Just below shear line */
|
||||
background: #22aa22; /* Green to indicate set */
|
||||
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%);
|
||||
}
|
||||
|
||||
.cylinder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #ddbb77;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border: 2px solid #887722;
|
||||
}
|
||||
|
||||
.cylinder-inner {
|
||||
width: 80%;
|
||||
height: 20px;
|
||||
background: #ccaa66;
|
||||
border-radius: 3px;
|
||||
transform-origin: center;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.cylinder.rotated .cylinder-inner {
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
|
||||
.lockpick-feedback {
|
||||
padding: 15px;
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
min-height: 30px;
|
||||
margin-top: 20px;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Phaser-specific styles */
|
||||
.phaser-game-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 5px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 2px solid #444;
|
||||
}
|
||||
|
||||
.phaser-game-container canvas {
|
||||
border-radius: 5px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
194
css/minigames-framework.css
Normal file
194
css/minigames-framework.css
Normal file
@@ -0,0 +1,194 @@
|
||||
/* Minigame Framework Styles */
|
||||
|
||||
.minigame-container {
|
||||
position: fixed;
|
||||
top: 2vh;
|
||||
left: 2vw;
|
||||
width: 96vw;
|
||||
height: 96vh;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #444;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.minigame-header {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.minigame-header h3 {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 16px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.minigame-header p {
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.minigame-game-container {
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
height: 60%;
|
||||
margin: 20px auto;
|
||||
background: #1a1a1a;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5) inset;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.minigame-message-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.minigame-success-message {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(46, 204, 113, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
z-index: 10001;
|
||||
font-size: 14px;
|
||||
border: 2px solid #27ae60;
|
||||
box-shadow: 0 0 20px rgba(46, 204, 113, 0.5);
|
||||
}
|
||||
|
||||
.minigame-failure-message {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(231, 76, 60, 0.9);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
z-index: 10001;
|
||||
font-size: 14px;
|
||||
border: 2px solid #c0392b;
|
||||
box-shadow: 0 0 20px rgba(231, 76, 60, 0.5);
|
||||
}
|
||||
|
||||
.minigame-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.minigame-button {
|
||||
background: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 16px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.minigame-button:hover {
|
||||
background: #2980b9;
|
||||
}
|
||||
|
||||
.minigame-button:active {
|
||||
background: #21618c;
|
||||
}
|
||||
|
||||
.minigame-progress-container {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: #333;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.minigame-progress-bar {
|
||||
height: 100%;
|
||||
background: #2ecc71;
|
||||
width: 0%;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
/* Minigame disabled state */
|
||||
.minigame-disabled {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* Biometric scanner visual feedback */
|
||||
.biometric-scanner-success {
|
||||
border: 2px solid #00ff00 !important;
|
||||
}
|
||||
|
||||
/* Close button for minigames */
|
||||
.minigame-close-button {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.minigame-close-button:hover {
|
||||
background: #c0392b;
|
||||
}
|
||||
|
||||
.minigame-close-button:active {
|
||||
background: #a93226;
|
||||
}
|
||||
|
||||
/* Progress bar styling for minigames */
|
||||
.minigame-progress-container {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.minigame-progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #2ecc71, #27ae60);
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 5px;
|
||||
}
|
||||
@@ -1,133 +1,5 @@
|
||||
/* Minigames Styles */
|
||||
|
||||
/* Lockpicking Game */
|
||||
.lockpick-container {
|
||||
width: 350px;
|
||||
height: 300px;
|
||||
background: #8A5A3C;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
border: 2px solid #887722;
|
||||
}
|
||||
|
||||
.pin {
|
||||
width: 30px;
|
||||
height: 110px;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border-radius: 4px 4px 0 0;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.pin:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.shear-line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #aa8833;
|
||||
bottom: 50px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.key-pin {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0px;
|
||||
background: #dd3333; /* Red for key pins */
|
||||
transition: height 0.05s;
|
||||
border-radius: 0 0 0 0;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%); /* Pointed bottom */
|
||||
}
|
||||
|
||||
.driver-pin {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: #3388dd; /* Blue for driver pins */
|
||||
transition: bottom 0.05s;
|
||||
bottom: 50px;
|
||||
border-radius: 0 0 0 0;
|
||||
}
|
||||
|
||||
.spring {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
background: linear-gradient(to bottom,
|
||||
#cccccc 0%, #cccccc 20%,
|
||||
#999999 20%, #999999 25%,
|
||||
#cccccc 25%, #cccccc 40%,
|
||||
#999999 40%, #999999 45%,
|
||||
#cccccc 45%, #cccccc 60%,
|
||||
#999999 60%, #999999 65%,
|
||||
#cccccc 65%, #cccccc 80%,
|
||||
#999999 80%, #999999 85%,
|
||||
#cccccc 85%, #cccccc 100%
|
||||
);
|
||||
transition: height 0.05s;
|
||||
}
|
||||
|
||||
.pin.binding {
|
||||
box-shadow: 0 0 8px 2px #ffcc00;
|
||||
}
|
||||
|
||||
.pin.set .driver-pin {
|
||||
bottom: 52px; /* Just above shear line */
|
||||
background: #22aa22; /* Green to indicate set */
|
||||
}
|
||||
|
||||
.pin.set .key-pin {
|
||||
height: 49px; /* Just below shear line */
|
||||
background: #22aa22; /* Green to indicate set */
|
||||
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%);
|
||||
}
|
||||
|
||||
.cylinder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #ddbb77;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
border: 2px solid #887722;
|
||||
}
|
||||
|
||||
.cylinder-inner {
|
||||
width: 80%;
|
||||
height: 20px;
|
||||
background: #ccaa66;
|
||||
border-radius: 3px;
|
||||
transform-origin: center;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.cylinder.rotated .cylinder-inner {
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
|
||||
.lockpick-feedback {
|
||||
padding: 15px;
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
min-height: 30px;
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Minigame Framework Styles */
|
||||
.minigame-container {
|
||||
@@ -254,118 +126,6 @@
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
/* Advanced Lockpicking specific styles */
|
||||
.lock-visual {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
height: 200px;
|
||||
background: #f0e6a6;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
margin: 20px;
|
||||
border: 2px solid #887722;
|
||||
}
|
||||
|
||||
.pin {
|
||||
width: 30px;
|
||||
height: 150px;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border-radius: 4px 4px 0 0;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.pin:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.shear-line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #aa8833;
|
||||
top: 60px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.key-pin {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0px;
|
||||
background: #dd3333;
|
||||
transition: height 0.1s;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.driver-pin {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: #3388dd;
|
||||
transition: bottom 0.1s;
|
||||
bottom: 60px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.spring {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
#cccccc 0px,
|
||||
#cccccc 2px,
|
||||
#999999 2px,
|
||||
#999999 4px
|
||||
);
|
||||
transition: height 0.1s;
|
||||
}
|
||||
|
||||
.pin.binding {
|
||||
box-shadow: 0 0 10px 2px #ffcc00;
|
||||
}
|
||||
|
||||
.pin.set .driver-pin {
|
||||
bottom: 62px;
|
||||
background: #22aa22;
|
||||
}
|
||||
|
||||
.pin.set .key-pin {
|
||||
height: 59px;
|
||||
background: #22aa22;
|
||||
}
|
||||
|
||||
.tension-control {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tension-wrench {
|
||||
width: 60px;
|
||||
height: 20px;
|
||||
background: #888;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.tension-wrench.active {
|
||||
transform: rotate(15deg);
|
||||
background: #ffcc00;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
@@ -373,20 +133,6 @@
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.lockpick-feedback {
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Dusting Minigame */
|
||||
.dusting-container {
|
||||
width: 75% !important;
|
||||
@@ -538,37 +284,6 @@
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Lockpicking Game Success/Failure Messages */
|
||||
.lockpicking-success-message {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.lockpicking-success-subtitle {
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.lockpicking-success-details {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.lockpicking-failure-message {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.lockpicking-failure-subtitle {
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Dusting Game Success/Failure Messages */
|
||||
.dusting-success-message {
|
||||
font-weight: bold;
|
||||
|
||||
114
css/panels.css
114
css/panels.css
@@ -244,6 +244,120 @@
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#bluetooth-content {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.bluetooth-device {
|
||||
background-color: #333;
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bluetooth-device:hover {
|
||||
background-color: #444;
|
||||
border-color: #9b59b6;
|
||||
}
|
||||
|
||||
.bluetooth-device:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bluetooth-device.expanded {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
.bluetooth-device-name {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.bluetooth-device-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.bluetooth-device-icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bluetooth-device-details {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
margin-top: 8px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.bluetooth-device.expanded .bluetooth-device-details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bluetooth-device-timestamp {
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
margin-top: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Bluetooth Signal Strength Bar */
|
||||
.bluetooth-signal-bar-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.bluetooth-signal-bars {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.bluetooth-signal-bar {
|
||||
width: 3px;
|
||||
background-color: #666;
|
||||
border-radius: 1px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bluetooth-signal-bar.active {
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
.bluetooth-signal-bar:nth-child(1) { height: 3px; }
|
||||
.bluetooth-signal-bar:nth-child(2) { height: 6px; }
|
||||
.bluetooth-signal-bar:nth-child(3) { height: 9px; }
|
||||
.bluetooth-signal-bar:nth-child(4) { height: 12px; }
|
||||
.bluetooth-signal-bar:nth-child(5) { height: 16px; }
|
||||
|
||||
.bluetooth-signal-text {
|
||||
font-size: 10px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.bluetooth-device.hover-preserved {
|
||||
background-color: #444;
|
||||
border-color: #9b59b6;
|
||||
}
|
||||
|
||||
.bluetooth-device:hover .bluetooth-device-name,
|
||||
.bluetooth-device:hover .bluetooth-device-details,
|
||||
.bluetooth-device:hover .bluetooth-device-timestamp,
|
||||
.bluetooth-device:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Biometrics Panel */
|
||||
#biometrics-panel {
|
||||
position: fixed;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<link rel="stylesheet" href="css/notifications.css">
|
||||
<link rel="stylesheet" href="css/panels.css">
|
||||
<link rel="stylesheet" href="css/inventory.css">
|
||||
<link rel="stylesheet" href="css/minigames.css">
|
||||
<link rel="stylesheet" href="css/minigames-framework.css">
|
||||
<link rel="stylesheet" href="css/modals.css">
|
||||
|
||||
<!-- External JavaScript libraries -->
|
||||
|
||||
@@ -173,7 +173,7 @@ export function update() {
|
||||
|
||||
// Check for Bluetooth devices
|
||||
const currentTime = Date.now();
|
||||
if (currentTime - lastBluetoothScan >= 2000) { // 2 second interval
|
||||
if (currentTime - lastBluetoothScan >= 200) { // 200ms interval for more responsive updates
|
||||
if (window.checkBluetoothDevices) {
|
||||
window.checkBluetoothDevices();
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ export function initializeRooms(gameInstance) {
|
||||
gameRef = gameInstance;
|
||||
console.log('Initializing rooms');
|
||||
rooms = {};
|
||||
window.rooms = rooms; // Ensure window.rooms references the same object
|
||||
currentRoom = '';
|
||||
currentPlayerRoom = '';
|
||||
window.currentPlayerRoom = '';
|
||||
discoveredRooms = new Set();
|
||||
}
|
||||
|
||||
@@ -316,6 +318,9 @@ export function createRoom(roomId, roomData, position) {
|
||||
objects: {},
|
||||
position
|
||||
};
|
||||
|
||||
// Ensure window.rooms is updated
|
||||
window.rooms = rooms;
|
||||
|
||||
const layers = rooms[roomId].layers;
|
||||
const wallsLayers = rooms[roomId].wallsLayers;
|
||||
@@ -601,8 +606,8 @@ export function updatePlayerRoom() {
|
||||
|
||||
// If we're not overlapping any rooms
|
||||
if (overlappingRooms.length === 0) {
|
||||
console.log('Player not in any room');
|
||||
currentPlayerRoom = null;
|
||||
window.currentPlayerRoom = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -610,6 +615,7 @@ export function updatePlayerRoom() {
|
||||
if (currentPlayerRoom !== overlappingRooms[0]) {
|
||||
console.log(`Player's main room changed to: ${overlappingRooms[0]}`);
|
||||
currentPlayerRoom = overlappingRooms[0];
|
||||
window.currentPlayerRoom = overlappingRooms[0];
|
||||
}
|
||||
|
||||
return currentPlayerRoom;
|
||||
|
||||
@@ -36,6 +36,7 @@ window.gameState = {
|
||||
biometricSamples: [],
|
||||
biometricUnlocks: [],
|
||||
bluetoothDevices: [],
|
||||
notes: [],
|
||||
startTime: null
|
||||
};
|
||||
window.lastBluetoothScan = 0;
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { MinigameScene } from '../framework/base-minigame.js';
|
||||
|
||||
// Load dusting-specific CSS
|
||||
const dustingCSS = document.createElement('link');
|
||||
dustingCSS.rel = 'stylesheet';
|
||||
dustingCSS.href = 'css/dusting.css';
|
||||
dustingCSS.id = 'dusting-css';
|
||||
if (!document.getElementById('dusting-css')) {
|
||||
document.head.appendChild(dustingCSS);
|
||||
}
|
||||
|
||||
// Dusting Minigame Scene implementation
|
||||
export class DustingMinigame extends MinigameScene {
|
||||
constructor(container, params) {
|
||||
|
||||
@@ -12,22 +12,24 @@ export const MinigameFramework = {
|
||||
console.log("MinigameFramework initialized");
|
||||
},
|
||||
|
||||
startMinigame(sceneType, params) {
|
||||
startMinigame(sceneType, container, params) {
|
||||
if (!this.registeredScenes[sceneType]) {
|
||||
console.error(`Minigame scene '${sceneType}' not registered`);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Disable main game input
|
||||
if (this.mainGameScene) {
|
||||
// Disable main game input if we have a main game scene
|
||||
if (this.mainGameScene && this.mainGameScene.input) {
|
||||
this.mainGameScene.input.mouse.enabled = false;
|
||||
this.mainGameScene.input.keyboard.enabled = false;
|
||||
}
|
||||
|
||||
// Create minigame container
|
||||
const container = document.createElement('div');
|
||||
container.className = 'minigame-container';
|
||||
document.body.appendChild(container);
|
||||
// Use provided container or create one
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.className = 'minigame-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Create and start the minigame
|
||||
const MinigameClass = this.registeredScenes[sceneType];
|
||||
@@ -36,26 +38,27 @@ export const MinigameFramework = {
|
||||
this.currentMinigame.start();
|
||||
|
||||
console.log(`Started minigame: ${sceneType}`);
|
||||
return this.currentMinigame;
|
||||
},
|
||||
|
||||
endMinigame(success, result) {
|
||||
if (this.currentMinigame) {
|
||||
this.currentMinigame.cleanup();
|
||||
|
||||
// Remove minigame container
|
||||
// Remove minigame container only if it was auto-created
|
||||
const container = document.querySelector('.minigame-container');
|
||||
if (container) {
|
||||
if (container && !container.hasAttribute('data-external')) {
|
||||
container.remove();
|
||||
}
|
||||
|
||||
// Re-enable main game input
|
||||
if (this.mainGameScene) {
|
||||
// Re-enable main game input if we have a main game scene
|
||||
if (this.mainGameScene && this.mainGameScene.input) {
|
||||
this.mainGameScene.input.mouse.enabled = true;
|
||||
this.mainGameScene.input.keyboard.enabled = true;
|
||||
}
|
||||
|
||||
// Call completion callback
|
||||
if (this.currentMinigame.params.onComplete) {
|
||||
if (this.currentMinigame.params && this.currentMinigame.params.onComplete) {
|
||||
this.currentMinigame.params.onComplete(success, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ export { MinigameScene } from './framework/base-minigame.js';
|
||||
|
||||
// Export minigame implementations
|
||||
export { LockpickingMinigame } from './lockpicking/lockpicking-game.js';
|
||||
export { LockpickingMinigamePhaser } from './lockpicking/lockpicking-game-phaser.js';
|
||||
export { DustingMinigame } from './dusting/dusting-game.js';
|
||||
|
||||
// Initialize the global minigame framework for backward compatibility
|
||||
import { MinigameFramework } from './framework/minigame-manager.js';
|
||||
import { LockpickingMinigame } from './lockpicking/lockpicking-game.js';
|
||||
import { LockpickingMinigamePhaser } from './lockpicking/lockpicking-game-phaser.js';
|
||||
|
||||
// Make the framework available globally
|
||||
window.MinigameFramework = MinigameFramework;
|
||||
@@ -17,5 +19,7 @@ window.MinigameFramework = MinigameFramework;
|
||||
import { DustingMinigame } from './dusting/dusting-game.js';
|
||||
|
||||
// Register minigames
|
||||
MinigameFramework.registerScene('lockpicking', LockpickingMinigame);
|
||||
MinigameFramework.registerScene('lockpicking', LockpickingMinigamePhaser); // Use Phaser version as default
|
||||
MinigameFramework.registerScene('lockpicking-legacy', LockpickingMinigame); // Keep old version for backward compatibility
|
||||
MinigameFramework.registerScene('lockpicking-phaser', LockpickingMinigamePhaser); // Keep explicit phaser name
|
||||
MinigameFramework.registerScene('dusting', DustingMinigame);
|
||||
2079
js/minigames/lockpicking/lockpicking-game-phaser.js
Normal file
2079
js/minigames/lockpicking/lockpicking-game-phaser.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,14 @@
|
||||
import { MinigameScene } from '../framework/base-minigame.js';
|
||||
|
||||
// Load lockpicking-specific CSS
|
||||
const lockpickingCSS = document.createElement('link');
|
||||
lockpickingCSS.rel = 'stylesheet';
|
||||
lockpickingCSS.href = 'css/lockpicking.css';
|
||||
lockpickingCSS.id = 'lockpicking-css';
|
||||
if (!document.getElementById('lockpicking-css')) {
|
||||
document.head.appendChild(lockpickingCSS);
|
||||
}
|
||||
|
||||
// Lockpicking Minigame Scene implementation
|
||||
export class LockpickingMinigame extends MinigameScene {
|
||||
constructor(container, params) {
|
||||
|
||||
@@ -4,6 +4,20 @@
|
||||
// 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() {
|
||||
@@ -45,6 +59,8 @@ export function initializeBluetoothPanel() {
|
||||
|
||||
// Initialize bluetooth panel
|
||||
updateBluetoothPanel();
|
||||
updateBluetoothCount();
|
||||
syncBluetoothDevices();
|
||||
}
|
||||
|
||||
// Check for Bluetooth devices
|
||||
@@ -54,7 +70,9 @@ export function checkBluetoothDevices() {
|
||||
item.scenarioData?.type === "bluetooth_scanner"
|
||||
);
|
||||
|
||||
if (!scanner) return;
|
||||
if (!scanner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the Bluetooth toggle button if it's not already visible
|
||||
const bluetoothToggle = document.getElementById('bluetooth-toggle');
|
||||
@@ -63,11 +81,15 @@ export function checkBluetoothDevices() {
|
||||
}
|
||||
|
||||
// Find all Bluetooth devices in the current room
|
||||
if (!window.currentPlayerRoom || !window.rooms[window.currentPlayerRoom] || !window.rooms[window.currentPlayerRoom].objects) return;
|
||||
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;
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep track of devices detected in this scan
|
||||
const detectedDevices = new Set();
|
||||
@@ -80,36 +102,35 @@ export function checkBluetoothDevices() {
|
||||
);
|
||||
|
||||
const deviceMac = obj.scenarioData?.mac || "Unknown";
|
||||
const BLUETOOTH_SCAN_RANGE = 150; // pixels
|
||||
const deviceName = obj.scenarioData?.name || "Unknown Device";
|
||||
|
||||
if (distance <= BLUETOOTH_SCAN_RANGE) {
|
||||
detectedDevices.add(deviceMac);
|
||||
|
||||
console.log('BLUETOOTH DEVICE DETECTED', {
|
||||
deviceName: obj.scenarioData?.name,
|
||||
deviceMac: deviceMac,
|
||||
distance: Math.round(distance),
|
||||
range: BLUETOOTH_SCAN_RANGE
|
||||
});
|
||||
detectedDevices.add(`${deviceMac}|${deviceName}`); // Use combination for uniqueness
|
||||
|
||||
// Add to Bluetooth scanner panel
|
||||
const deviceName = obj.scenarioData?.name || "Unknown Device";
|
||||
const signalStrength = Math.max(0, Math.round(100 - (distance / BLUETOOTH_SCAN_RANGE * 100)));
|
||||
const details = `Type: ${obj.scenarioData?.type || "Unknown"}\nDistance: ${Math.round(distance)} units\nSignal Strength: ${signalStrength}%`;
|
||||
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
|
||||
const existingDevice = bluetoothDevices.find(device => device.mac === deviceMac);
|
||||
// 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 oldSignalStrength = existingDevice.signalStrength;
|
||||
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;
|
||||
|
||||
// Only mark for update if signal strength changed significantly
|
||||
if (Math.abs(oldSignalStrength - signalStrength) > 5) {
|
||||
// Always update if device came back into range or signal strength changed significantly
|
||||
if (!wasNearby || Math.abs(oldSignalStrengthPercentage - signalStrengthPercentage) > 5) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
} else {
|
||||
@@ -117,7 +138,10 @@ export function checkBluetoothDevices() {
|
||||
const newDevice = addBluetoothDevice(deviceName, deviceMac, details, true);
|
||||
if (newDevice) {
|
||||
newDevice.signalStrength = signalStrength;
|
||||
window.gameAlert(`Bluetooth device detected: ${deviceName} (MAC: ${deviceMac})`, 'info', 'Bluetooth Scanner', 4000);
|
||||
newDevice.signalStrengthPercentage = signalStrengthPercentage;
|
||||
if (window.gameAlert) {
|
||||
window.gameAlert(`Bluetooth device detected: ${deviceName} (MAC: ${deviceMac})`, 'info', 'Bluetooth Scanner', 4000);
|
||||
}
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
@@ -127,54 +151,148 @@ export function checkBluetoothDevices() {
|
||||
|
||||
// Mark devices that weren't detected in this scan as not nearby
|
||||
bluetoothDevices.forEach(device => {
|
||||
if (device.nearby && !detectedDevices.has(device.mac)) {
|
||||
const deviceKey = `${device.mac}|${device.name}`;
|
||||
if (device.nearby && !detectedDevices.has(deviceKey)) {
|
||||
device.nearby = false;
|
||||
device.lastSeen = new Date();
|
||||
needsUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Only update the panel if needed and not too frequently
|
||||
const now = Date.now();
|
||||
if (needsUpdate && now - lastBluetoothPanelUpdate > 1000) { // 1 second throttle
|
||||
updateBluetoothPanel();
|
||||
// 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();
|
||||
lastBluetoothPanelUpdate = now;
|
||||
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 device already exists
|
||||
const existingDevice = bluetoothDevices.find(device => device.mac === mac);
|
||||
if (existingDevice) {
|
||||
// Update existing device
|
||||
existingDevice.details = details;
|
||||
existingDevice.lastSeen = new Date();
|
||||
// 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;
|
||||
return existingDevice;
|
||||
existingDevice.lastSeen = new Date();
|
||||
updateBluetoothPanel();
|
||||
syncBluetoothDevices();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create new device
|
||||
const newDevice = {
|
||||
const device = {
|
||||
id: Date.now(),
|
||||
name: name,
|
||||
mac: mac,
|
||||
details: details,
|
||||
nearby: nearby,
|
||||
saved: false,
|
||||
firstSeen: new Date(),
|
||||
lastSeen: new Date(),
|
||||
signalStrength: 0
|
||||
signalStrength: -100, // Default to weak signal (-100 dBm)
|
||||
signalStrengthPercentage: 0 // Default to 0% for visual display
|
||||
};
|
||||
|
||||
bluetoothDevices.push(newDevice);
|
||||
return newDevice;
|
||||
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];
|
||||
|
||||
@@ -182,23 +300,36 @@ export function updateBluetoothPanel() {
|
||||
if (activeCategory === 'nearby') {
|
||||
filteredDevices = filteredDevices.filter(device => device.nearby);
|
||||
} else if (activeCategory === 'saved') {
|
||||
filteredDevices = filteredDevices.filter(device => !device.nearby);
|
||||
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.mac.toLowerCase().includes(searchTerm) ||
|
||||
device.details.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort devices by signal strength (nearby first, then by signal strength)
|
||||
// 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;
|
||||
}
|
||||
return (b.signalStrength || 0) - (a.signalStrength || 0);
|
||||
|
||||
// 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
|
||||
@@ -207,46 +338,101 @@ export function updateBluetoothPanel() {
|
||||
// Add devices
|
||||
if (filteredDevices.length === 0) {
|
||||
if (searchTerm) {
|
||||
bluetoothContent.innerHTML = '<div class="device-item">No devices match your search.</div>';
|
||||
} else if (activeCategory === 'nearby') {
|
||||
bluetoothContent.innerHTML = '<div class="device-item">No nearby devices found.</div>';
|
||||
} else if (activeCategory === 'saved') {
|
||||
bluetoothContent.innerHTML = '<div class="device-item">No saved devices found.</div>';
|
||||
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="device-item">No devices detected yet.</div>';
|
||||
bluetoothContent.innerHTML = '<div class="bluetooth-device">No devices detected yet.</div>';
|
||||
}
|
||||
} else {
|
||||
filteredDevices.forEach(device => {
|
||||
const deviceElement = document.createElement('div');
|
||||
deviceElement.className = 'device-item';
|
||||
deviceElement.dataset.mac = device.mac;
|
||||
deviceElement.className = 'bluetooth-device';
|
||||
deviceElement.dataset.id = device.id;
|
||||
|
||||
const formattedTime = device.lastSeen ? device.lastSeen.toLocaleString() : 'Unknown';
|
||||
const signalStrength = device.signalStrength || 0;
|
||||
// If this was the hovered device, add the hover class
|
||||
if (hoveredDeviceId && device.id === hoveredDeviceId) {
|
||||
deviceElement.classList.add('hover-preserved');
|
||||
}
|
||||
|
||||
deviceElement.innerHTML = `
|
||||
<div class="device-info">
|
||||
<div class="device-name">${device.name}</div>
|
||||
<div class="device-address">${device.mac}</div>
|
||||
</div>
|
||||
<div class="device-signal">${signalStrength}%</div>
|
||||
<div class="device-status ${device.nearby ? 'nearby' : 'saved'}">
|
||||
${device.nearby ? 'Nearby' : 'Not in range'}
|
||||
</div>
|
||||
`;
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
updateBluetoothCount();
|
||||
}
|
||||
|
||||
// Update the new Bluetooth devices count
|
||||
export function updateBluetoothCount() {
|
||||
const bluetoothCount = document.getElementById('bluetooth-count');
|
||||
if (bluetoothCount) {
|
||||
const nearbyCount = bluetoothDevices.filter(device => device.nearby).length;
|
||||
bluetoothCount.textContent = nearbyCount;
|
||||
newBluetoothDevices = bluetoothDevices.filter(device => !device.saved && device.nearby).length;
|
||||
|
||||
bluetoothCount.textContent = newBluetoothDevices;
|
||||
bluetoothCount.style.display = newBluetoothDevices > 0 ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,16 +443,44 @@ export function toggleBluetoothPanel() {
|
||||
const isVisible = bluetoothPanel.style.display === 'block';
|
||||
bluetoothPanel.style.display = isVisible ? 'none' : 'block';
|
||||
|
||||
// Update panel content when opening
|
||||
// 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.updateBluetoothCount = updateBluetoothCount;
|
||||
window.unlockInventoryDeviceByMac = unlockInventoryDeviceByMac;
|
||||
@@ -861,8 +861,8 @@ function startLockpickingMinigame(lockable, scene, difficulty = 'medium', callba
|
||||
window.MinigameFramework.init(scene);
|
||||
}
|
||||
|
||||
// Start the lockpicking minigame
|
||||
window.MinigameFramework.startMinigame('lockpicking', {
|
||||
// Start the lockpicking minigame (Phaser version)
|
||||
window.MinigameFramework.startMinigame('lockpicking', null, {
|
||||
lockable: lockable,
|
||||
difficulty: difficulty,
|
||||
onComplete: (success, result) => {
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
import { showNotification } from './notifications.js?v=5';
|
||||
import { formatTime } from '../utils/helpers.js?v=16';
|
||||
|
||||
// Game notes array
|
||||
const gameNotes = [];
|
||||
// Initialize game state if not exists
|
||||
if (!window.gameState) {
|
||||
window.gameState = {};
|
||||
}
|
||||
if (!window.gameState.notes) {
|
||||
window.gameState.notes = [];
|
||||
}
|
||||
|
||||
let unreadNotes = 0;
|
||||
|
||||
// Initialize the notes system
|
||||
@@ -26,10 +32,12 @@ export function initializeNotes() {
|
||||
const categories = document.querySelectorAll('.notes-category');
|
||||
categories.forEach(category => {
|
||||
category.addEventListener('click', () => {
|
||||
console.log('NOTES DEBUG: Category clicked:', category.dataset.category);
|
||||
// Remove active class from all categories
|
||||
categories.forEach(c => c.classList.remove('active'));
|
||||
// Add active class to clicked category
|
||||
category.classList.add('active');
|
||||
console.log('NOTES DEBUG: Active category set to:', category.dataset.category);
|
||||
// Update notes panel
|
||||
updateNotesPanel();
|
||||
});
|
||||
@@ -43,8 +51,10 @@ export function initializeNotes() {
|
||||
|
||||
// Add a note to the notes panel
|
||||
export function addNote(title, text, important = false) {
|
||||
console.log('NOTES DEBUG: Adding note', { title, important, textLength: text.length });
|
||||
|
||||
// Check if a note with the same title and text already exists
|
||||
const existingNote = gameNotes.find(note => note.title === title && note.text === text);
|
||||
const existingNote = window.gameState.notes.find(note => note.title === title && note.text === text);
|
||||
|
||||
// If the note already exists, don't add it again but mark it as read
|
||||
if (existingNote) {
|
||||
@@ -69,7 +79,9 @@ export function addNote(title, text, important = false) {
|
||||
important: important
|
||||
};
|
||||
|
||||
gameNotes.push(note);
|
||||
console.log('NOTES DEBUG: Note created', note);
|
||||
|
||||
window.gameState.notes.push(note);
|
||||
updateNotesPanel();
|
||||
updateNotesCount();
|
||||
|
||||
@@ -87,8 +99,18 @@ export function updateNotesPanel() {
|
||||
// Get active category
|
||||
const activeCategory = document.querySelector('.notes-category.active')?.dataset.category || 'all';
|
||||
|
||||
console.log('NOTES DEBUG: Updating panel', {
|
||||
activeCategory,
|
||||
totalNotes: window.gameState.notes.length,
|
||||
notesData: window.gameState.notes.map(note => ({
|
||||
title: note.title,
|
||||
important: note.important,
|
||||
read: note.read
|
||||
}))
|
||||
});
|
||||
|
||||
// Filter notes based on search and category
|
||||
let filteredNotes = [...gameNotes];
|
||||
let filteredNotes = [...window.gameState.notes];
|
||||
|
||||
// Apply category filter
|
||||
if (activeCategory === 'important') {
|
||||
@@ -97,6 +119,16 @@ export function updateNotesPanel() {
|
||||
filteredNotes = filteredNotes.filter(note => !note.read);
|
||||
}
|
||||
|
||||
console.log('NOTES DEBUG: After filtering', {
|
||||
activeCategory,
|
||||
filteredCount: filteredNotes.length,
|
||||
filteredNotes: filteredNotes.map(note => ({
|
||||
title: note.title,
|
||||
important: note.important,
|
||||
read: note.read
|
||||
}))
|
||||
});
|
||||
|
||||
// Apply search filter
|
||||
if (searchTerm) {
|
||||
filteredNotes = filteredNotes.filter(note =>
|
||||
@@ -171,7 +203,7 @@ export function updateNotesPanel() {
|
||||
// Update the unread notes count
|
||||
export function updateNotesCount() {
|
||||
const notesCount = document.getElementById('notes-count');
|
||||
unreadNotes = gameNotes.filter(note => !note.read).length;
|
||||
unreadNotes = window.gameState.notes.filter(note => !note.read).length;
|
||||
|
||||
notesCount.textContent = unreadNotes;
|
||||
notesCount.style.display = unreadNotes > 0 ? 'flex' : 'none';
|
||||
|
||||
228
lockpicking-comparison.html
Normal file
228
lockpicking-comparison.html
Normal file
@@ -0,0 +1,228 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lockpicking Minigame Comparison</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #1a1a1a;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.comparison-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.minigame-section {
|
||||
flex: 1;
|
||||
background: #2a2a2a;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
border: 2px solid #444;
|
||||
}
|
||||
|
||||
.minigame-section h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.minigame-container {
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #00ff00;
|
||||
color: #000;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #00cc00;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #666;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
background: #444;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.info-panel h3 {
|
||||
margin-top: 0;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid #555;
|
||||
}
|
||||
|
||||
.feature-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.feature-list .pro {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.feature-list .con {
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #00ff00;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #ccc;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Lockpicking Minigame Comparison</h1>
|
||||
<p>Compare the original HTML/JS version with the new Phaser.js implementation</p>
|
||||
</div>
|
||||
|
||||
<div class="comparison-container">
|
||||
<div class="minigame-section">
|
||||
<h2>Original HTML/JS Version</h2>
|
||||
<div class="minigame-container" id="original-container">
|
||||
<!-- Original minigame will be loaded here -->
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="startOriginal()">Start Original</button>
|
||||
<button class="btn" onclick="stopOriginal()">Stop</button>
|
||||
</div>
|
||||
<div class="info-panel">
|
||||
<h3>Features</h3>
|
||||
<ul class="feature-list">
|
||||
<li class="pro">✓ Lightweight - no additional dependencies</li>
|
||||
<li class="pro">✓ Simple DOM manipulation</li>
|
||||
<li class="pro">✓ Easy to customize with CSS</li>
|
||||
<li class="con">✗ Limited animation capabilities</li>
|
||||
<li class="con">✗ Basic graphics rendering</li>
|
||||
<li class="con">✗ Manual input handling</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="minigame-section">
|
||||
<h2>Phaser.js Version</h2>
|
||||
<div class="minigame-container" id="phaser-container">
|
||||
<!-- Phaser minigame will be loaded here -->
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button class="btn" onclick="startPhaser()">Start Phaser</button>
|
||||
<button class="btn" onclick="stopPhaser()">Stop</button>
|
||||
</div>
|
||||
<div class="info-panel">
|
||||
<h3>Features</h3>
|
||||
<ul class="feature-list">
|
||||
<li class="pro">✓ Rich graphics and animations</li>
|
||||
<li class="pro">✓ Built-in game engine features</li>
|
||||
<li class="pro">✓ Professional game development tools</li>
|
||||
<li class="con">✗ Larger bundle size</li>
|
||||
<li class="con">✗ More complex setup</li>
|
||||
<li class="con">✗ Learning curve for Phaser API</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Load Phaser.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
|
||||
|
||||
<!-- Load minigame framework -->
|
||||
<script type="module">
|
||||
import { MinigameFramework } from './js/minigames/index.js';
|
||||
|
||||
let originalMinigame = null;
|
||||
let phaserMinigame = null;
|
||||
|
||||
window.startOriginal = function() {
|
||||
if (originalMinigame) {
|
||||
originalMinigame.cleanup();
|
||||
}
|
||||
|
||||
const container = document.getElementById('original-container');
|
||||
container.setAttribute('data-external', 'true');
|
||||
originalMinigame = MinigameFramework.startMinigame('lockpicking-legacy', container, {
|
||||
lockable: 'test-lock',
|
||||
difficulty: 'medium'
|
||||
});
|
||||
};
|
||||
|
||||
window.stopOriginal = function() {
|
||||
if (originalMinigame) {
|
||||
originalMinigame.complete(false);
|
||||
originalMinigame = null;
|
||||
}
|
||||
};
|
||||
|
||||
window.startPhaser = function() {
|
||||
if (phaserMinigame) {
|
||||
phaserMinigame.cleanup();
|
||||
}
|
||||
|
||||
const container = document.getElementById('phaser-container');
|
||||
container.setAttribute('data-external', 'true');
|
||||
phaserMinigame = MinigameFramework.startMinigame('lockpicking', container, {
|
||||
lockable: 'test-lock',
|
||||
difficulty: 'medium'
|
||||
});
|
||||
};
|
||||
|
||||
window.stopPhaser = function() {
|
||||
if (phaserMinigame) {
|
||||
phaserMinigame.complete(false);
|
||||
phaserMinigame = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-start both minigames after a short delay
|
||||
setTimeout(() => {
|
||||
startOriginal();
|
||||
startPhaser();
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
560
locksmith-forge.html
Normal file
560
locksmith-forge.html
Normal file
@@ -0,0 +1,560 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Locksmith Forge - Lockpicking Challenges</title>
|
||||
|
||||
<!-- Google Fonts - Press Start 2P, VT323 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Web Font Loader script to ensure fonts load properly -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
||||
<script>
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: ['Press Start 2P', 'VT323']
|
||||
},
|
||||
active: function() {
|
||||
console.log('Fonts loaded successfully');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'VT323', monospace;
|
||||
background: #333;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.minigame-close-button, #minigame-cancel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 2px solid #444;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.level-display {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
text-shadow: 0 0 10px #00ff00;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stat {
|
||||
background: #333;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #00ff00;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
border: 2px solid #444;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
||||
margin-bottom: 20px;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#gameContainer {
|
||||
width: 100%;
|
||||
background: #1a1a1a;
|
||||
/* border: 1px solid #444; */
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phaser-game-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
input:disabled, select:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.control-group input:disabled + .range-value {
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.controls {
|
||||
background: #333;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #00ff00;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
min-width: 120px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.range-value {
|
||||
min-width: 40px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #00ff00;
|
||||
color: #000;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: bold;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #00cc00;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #666;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #444;
|
||||
min-height: 20px;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 18px;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 10px;
|
||||
border: 1px solid #444;
|
||||
overflow: hidden;
|
||||
margin: 10px 0;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #00ff00, #00cc00);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.achievement {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
background: #ffaa00;
|
||||
color: #000;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
animation: glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from { box-shadow: 0 0 5px #ffaa00; }
|
||||
to { box-shadow: 0 0 20px #ffaa00, 0 0 30px #ffaa00; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="level-display">LEVEL <span id="currentLevel">1</span></div>
|
||||
<div class="stats">
|
||||
<div class="stat">Pins: <span id="pinCount">3</span></div>
|
||||
<div class="stat">Difficulty: <span id="difficulty">Easy</span></div>
|
||||
<div class="stat">Sensitivity: <span id="sensitivity">5</span></div>
|
||||
<div class="stat">Lift Speed: <span id="liftSpeed">1.0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="game-container">
|
||||
<div id="gameContainer"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls" style="display: none;">
|
||||
<!-- Controls hidden - parameters are automatically set by level -->
|
||||
<div class="control-group">
|
||||
<label>Threshold Sensitivity:</label>
|
||||
<input type="range" id="thresholdSensitivity" min="1" max="10" value="5" step="1" disabled>
|
||||
<span class="range-value" id="thresholdSensitivityValue">5</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Highlight Binding Order:</label>
|
||||
<select id="highlightBindingOrder" disabled>
|
||||
<option value="enabled">Enabled</option>
|
||||
<option value="disabled">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Pin Alignment Highlighting:</label>
|
||||
<select id="pinAlignmentHighlighting" disabled>
|
||||
<option value="enabled">Enabled</option>
|
||||
<option value="disabled">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Lift Speed:</label>
|
||||
<input type="range" id="liftSpeedRange" min="0.5" max="3.0" value="1.0" step="0.1" disabled>
|
||||
<span class="range-value" id="liftSpeedValue">1.0</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<button id="startButton">Restart Level</button>
|
||||
<button id="resetButton">Reset Progress</button>
|
||||
<button id="skipButton">Skip Level</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Ready to start Level 1</div>
|
||||
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
|
||||
<div id="achievements"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
|
||||
<script type="module">
|
||||
import { LockpickingMinigamePhaser } from './js/minigames/lockpicking/lockpicking-game-phaser.js';
|
||||
|
||||
class ProgressiveLockpicking {
|
||||
constructor() {
|
||||
this.currentLevel = 1;
|
||||
this.maxLevel = 50;
|
||||
this.successCount = 0;
|
||||
this.failureCount = 0;
|
||||
this.currentGame = null;
|
||||
this.levelConfig = this.generateLevelConfig();
|
||||
|
||||
this.initializeUI();
|
||||
this.bindEvents();
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
generateLevelConfig() {
|
||||
const config = {};
|
||||
|
||||
for (let level = 1; level <= this.maxLevel; level++) {
|
||||
// Base progression
|
||||
let pinCount = Math.min(3 + Math.floor((level - 1) / 5), 8); // 3-8 pins
|
||||
let difficulty = this.getDifficulty(level);
|
||||
let sensitivity = Math.max(1, Math.min(10, 5 + Math.floor((level - 1) / 3))); // 1-10
|
||||
let liftSpeed = Math.max(0.5, Math.min(3.0, 1.0 + (level - 1) * 0.1)); // 0.5-3.0
|
||||
|
||||
// Add some randomness and complexity
|
||||
if (level > 10) {
|
||||
// Randomly disable hints for higher levels
|
||||
const disableHints = Math.random() > 0.7;
|
||||
if (disableHints) {
|
||||
sensitivity = Math.max(1, sensitivity - 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (level > 20) {
|
||||
// Increase difficulty more aggressively
|
||||
liftSpeed = Math.min(3.0, liftSpeed + 0.2);
|
||||
}
|
||||
|
||||
if (level > 30) {
|
||||
// Very challenging levels
|
||||
pinCount = Math.min(8, pinCount + 1);
|
||||
sensitivity = Math.max(1, sensitivity - 1);
|
||||
}
|
||||
|
||||
config[level] = {
|
||||
pinCount,
|
||||
difficulty,
|
||||
sensitivity,
|
||||
liftSpeed: Math.round(liftSpeed * 10) / 10,
|
||||
highlightBindingOrder: level <= 15 ? 'enabled' : (Math.random() > 0.5 ? 'enabled' : 'disabled'),
|
||||
pinAlignmentHighlighting: level <= 10 ? 'enabled' : (Math.random() > 0.6 ? 'enabled' : 'disabled')
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
getDifficulty(level) {
|
||||
if (level <= 5) return 'easy';
|
||||
if (level <= 15) return 'medium';
|
||||
if (level <= 30) return 'difficult';
|
||||
if (level <= 40) return 'hard';
|
||||
return 'expert';
|
||||
}
|
||||
|
||||
initializeUI() {
|
||||
// Initialize range inputs
|
||||
this.updateRangeValue('thresholdSensitivity');
|
||||
this.updateRangeValue('liftSpeedRange');
|
||||
|
||||
// Set initial values from level config
|
||||
this.updateControlsFromLevel();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// Range input events
|
||||
document.getElementById('thresholdSensitivity').addEventListener('input', (e) => {
|
||||
this.updateRangeValue('thresholdSensitivity');
|
||||
});
|
||||
|
||||
document.getElementById('liftSpeedRange').addEventListener('input', (e) => {
|
||||
this.updateRangeValue('liftSpeedRange');
|
||||
});
|
||||
|
||||
// Button events
|
||||
document.getElementById('startButton').addEventListener('click', () => {
|
||||
this.startChallenge();
|
||||
});
|
||||
|
||||
document.getElementById('resetButton').addEventListener('click', () => {
|
||||
this.resetLevel();
|
||||
});
|
||||
|
||||
document.getElementById('skipButton').addEventListener('click', () => {
|
||||
this.skipLevel();
|
||||
});
|
||||
}
|
||||
|
||||
updateRangeValue(id) {
|
||||
const input = document.getElementById(id);
|
||||
const value = input.value;
|
||||
const valueDisplay = document.getElementById(id + 'Value');
|
||||
if (valueDisplay) {
|
||||
valueDisplay.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
updateControlsFromLevel() {
|
||||
const config = this.levelConfig[this.currentLevel];
|
||||
if (!config) return;
|
||||
|
||||
document.getElementById('thresholdSensitivity').value = config.sensitivity;
|
||||
document.getElementById('liftSpeedRange').value = config.liftSpeed;
|
||||
document.getElementById('highlightBindingOrder').value = config.highlightBindingOrder;
|
||||
document.getElementById('pinAlignmentHighlighting').value = config.pinAlignmentHighlighting;
|
||||
|
||||
this.updateRangeValue('thresholdSensitivity');
|
||||
this.updateRangeValue('liftSpeedRange');
|
||||
}
|
||||
|
||||
startChallenge() {
|
||||
if (this.currentGame) {
|
||||
this.currentGame.cleanup();
|
||||
}
|
||||
|
||||
const config = this.levelConfig[this.currentLevel];
|
||||
const params = {
|
||||
pinCount: config.pinCount,
|
||||
difficulty: config.difficulty,
|
||||
thresholdSensitivity: parseInt(document.getElementById('thresholdSensitivity').value),
|
||||
highlightingBindingOrder: document.getElementById('highlightBindingOrder').value,
|
||||
pinAlignmentHighlighting: document.getElementById('pinAlignmentHighlighting').value,
|
||||
liftSpeed: parseFloat(document.getElementById('liftSpeedRange').value),
|
||||
lockable: { id: 'progressive-challenge' },
|
||||
closeButtonText: 'Reset',
|
||||
closeButtonAction: 'reset'
|
||||
};
|
||||
|
||||
this.updateStatus(`Starting Level ${this.currentLevel}...`);
|
||||
|
||||
console.log('Creating minigame with params:', params);
|
||||
console.log('Game container:', document.getElementById('gameContainer'));
|
||||
|
||||
this.currentGame = new LockpickingMinigamePhaser(
|
||||
document.getElementById('gameContainer'),
|
||||
params
|
||||
);
|
||||
|
||||
console.log('Minigame created:', this.currentGame);
|
||||
|
||||
// Initialize the minigame
|
||||
this.currentGame.init();
|
||||
|
||||
console.log('Minigame initialized');
|
||||
|
||||
// Start the minigame
|
||||
this.currentGame.start();
|
||||
|
||||
console.log('Minigame started');
|
||||
|
||||
// Listen for completion by overriding the complete method
|
||||
const originalComplete = this.currentGame.complete.bind(this.currentGame);
|
||||
this.currentGame.complete = (success) => {
|
||||
console.log('Minigame completed with success:', success);
|
||||
this.handleChallengeComplete(success);
|
||||
originalComplete(success);
|
||||
};
|
||||
}
|
||||
|
||||
handleChallengeComplete(success) {
|
||||
if (success) {
|
||||
this.successCount++;
|
||||
this.updateStatus(`Level ${this.currentLevel} completed successfully!`);
|
||||
this.showAchievement(`Level ${this.currentLevel} Complete!`);
|
||||
|
||||
// Progress to next level after a delay
|
||||
setTimeout(() => {
|
||||
this.currentLevel = Math.min(this.currentLevel + 1, this.maxLevel);
|
||||
this.updateDisplay();
|
||||
this.updateControlsFromLevel();
|
||||
this.updateStatus(`Starting Level ${this.currentLevel}...`);
|
||||
|
||||
// Auto-start the next level
|
||||
setTimeout(() => {
|
||||
this.startChallenge();
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
} else {
|
||||
this.failureCount++;
|
||||
this.updateStatus(`Level ${this.currentLevel} failed. Retrying...`);
|
||||
|
||||
// Auto-retry after a delay
|
||||
setTimeout(() => {
|
||||
this.startChallenge();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
this.updateProgress();
|
||||
}
|
||||
|
||||
resetLevel() {
|
||||
this.currentLevel = 1;
|
||||
this.successCount = 0;
|
||||
this.failureCount = 0;
|
||||
this.updateDisplay();
|
||||
this.updateControlsFromLevel();
|
||||
this.updateStatus(`Progress reset. Ready for Level ${this.currentLevel}`);
|
||||
this.updateProgress();
|
||||
|
||||
// Auto-start the first level
|
||||
setTimeout(() => {
|
||||
this.startChallenge();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
skipLevel() {
|
||||
this.currentLevel = Math.min(this.currentLevel + 1, this.maxLevel);
|
||||
this.updateDisplay();
|
||||
this.updateControlsFromLevel();
|
||||
this.updateStatus(`Skipped to Level ${this.currentLevel}`);
|
||||
this.updateProgress();
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
document.getElementById('currentLevel').textContent = this.currentLevel;
|
||||
|
||||
const config = this.levelConfig[this.currentLevel];
|
||||
if (config) {
|
||||
document.getElementById('pinCount').textContent = config.pinCount;
|
||||
document.getElementById('difficulty').textContent = config.difficulty;
|
||||
document.getElementById('sensitivity').textContent = config.sensitivity;
|
||||
document.getElementById('liftSpeed').textContent = config.liftSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(message) {
|
||||
document.getElementById('status').textContent = message;
|
||||
}
|
||||
|
||||
updateProgress() {
|
||||
const progress = (this.currentLevel - 1) / this.maxLevel * 100;
|
||||
document.getElementById('progressFill').style.width = progress + '%';
|
||||
}
|
||||
|
||||
showAchievement(message) {
|
||||
const achievements = document.getElementById('achievements');
|
||||
const achievement = document.createElement('div');
|
||||
achievement.className = 'achievement';
|
||||
achievement.textContent = message;
|
||||
|
||||
achievements.appendChild(achievement);
|
||||
|
||||
// Remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (achievement.parentNode) {
|
||||
achievement.parentNode.removeChild(achievement);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the progressive lockpicking system
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const progressiveSystem = new ProgressiveLockpicking();
|
||||
// Auto-start the first level
|
||||
setTimeout(() => {
|
||||
progressiveSystem.startChallenge();
|
||||
}, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -41,6 +41,7 @@
|
||||
"takeable": true,
|
||||
"locked": true,
|
||||
"lockType": "bluetooth",
|
||||
"requires": "bluetooth",
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"observations": "A locked tablet device that requires Bluetooth pairing"
|
||||
},
|
||||
|
||||
307
test-phaser-lockpicking.html
Normal file
307
test-phaser-lockpicking.html
Normal file
@@ -0,0 +1,307 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Phaser Lockpicking Test</title>
|
||||
|
||||
<!-- Google Fonts - Press Start 2P, VT323 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Web Font Loader script to ensure fonts load properly -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
||||
<script>
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: ['Press Start 2P', 'VT323']
|
||||
},
|
||||
active: function() {
|
||||
console.log('Fonts loaded successfully');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'VT323', monospace;
|
||||
background: #1a1a1a;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
background: #2a2a2a;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-container h1 {
|
||||
text-align: center;
|
||||
color: #00ff00;
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.controls-section {
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.controls-section h2 {
|
||||
color: #00ff00;
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
font-size: 14px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
min-width: 120px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.control-group input, .control-group select {
|
||||
background: #444;
|
||||
border: 1px solid #666;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.control-group input[type="range"] {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.range-value {
|
||||
min-width: 30px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.minigame-container {
|
||||
background: #333;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
min-height: 500px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #00ff00;
|
||||
color: #000;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
font-size: 12px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #00cc00;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #666;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-size: 16px;
|
||||
color: #00ff00;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>Phaser Lockpicking Minigame Test</h1>
|
||||
|
||||
<div class="controls-section">
|
||||
<h2>Game Parameters</h2>
|
||||
<div class="control-group">
|
||||
<label for="pinCount">Pin Count:</label>
|
||||
<input type="range" id="pinCount" min="3" max="8" value="5" step="1">
|
||||
<span class="range-value" id="pinCountValue">5</span>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="difficulty">Difficulty:</label>
|
||||
<select id="difficulty">
|
||||
<option value="easy">Easy</option>
|
||||
<option value="medium" selected>Medium</option>
|
||||
<option value="hard">Hard</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="lockable">Lockable ID:</label>
|
||||
<input type="text" id="lockable" value="test-lock" placeholder="Enter lockable ID">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="thresholdSensitivity">Threshold Sensitivity:</label>
|
||||
<input type="range" id="thresholdSensitivity" min="1" max="10" value="5" step="1">
|
||||
<span class="range-value" id="thresholdSensitivityValue">5</span>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="liftSpeed">Lift Speed:</label>
|
||||
<input type="range" id="liftSpeed" min="0.5" max="5" value="1" step="0.1">
|
||||
<span class="range-value" id="liftSpeedValue">1.0</span>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="highlightBindingOrder">Highlight Binding Order:</label>
|
||||
<select id="highlightBindingOrder">
|
||||
<option value="true" selected>Enabled</option>
|
||||
<option value="false">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label for="highlightPinAlignment">Highlight Pin Alignment:</label>
|
||||
<select id="highlightPinAlignment">
|
||||
<option value="true" selected>Enabled</option>
|
||||
<option value="false">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="minigame-container" id="test-container">
|
||||
<!-- Minigame will be loaded here -->
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" id="startBtn" onclick="startTest()">Start Test</button>
|
||||
<button class="btn" id="stopBtn" onclick="stopTest()" disabled>Stop Test</button>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">Ready to start</div>
|
||||
</div>
|
||||
|
||||
<!-- Load Phaser.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
|
||||
|
||||
<!-- Load minigame framework -->
|
||||
<script type="module">
|
||||
import { MinigameFramework } from './js/minigames/index.js';
|
||||
|
||||
let testMinigame = null;
|
||||
|
||||
// Update range value displays
|
||||
function updateRangeValue(rangeId, valueId) {
|
||||
const range = document.getElementById(rangeId);
|
||||
const value = document.getElementById(valueId);
|
||||
value.textContent = range.value;
|
||||
}
|
||||
|
||||
// Initialize range value displays
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateRangeValue('pinCount', 'pinCountValue');
|
||||
updateRangeValue('thresholdSensitivity', 'thresholdSensitivityValue');
|
||||
updateRangeValue('liftSpeed', 'liftSpeedValue');
|
||||
|
||||
// Add event listeners for range inputs
|
||||
document.getElementById('pinCount').addEventListener('input', function() {
|
||||
updateRangeValue('pinCount', 'pinCountValue');
|
||||
});
|
||||
|
||||
document.getElementById('thresholdSensitivity').addEventListener('input', function() {
|
||||
updateRangeValue('thresholdSensitivity', 'thresholdSensitivityValue');
|
||||
});
|
||||
|
||||
document.getElementById('liftSpeed').addEventListener('input', function() {
|
||||
updateRangeValue('liftSpeed', 'liftSpeedValue');
|
||||
});
|
||||
});
|
||||
|
||||
window.startTest = function() {
|
||||
if (testMinigame) {
|
||||
testMinigame.cleanup();
|
||||
}
|
||||
|
||||
const container = document.getElementById('test-container');
|
||||
container.setAttribute('data-external', 'true');
|
||||
|
||||
// Get parameters from controls
|
||||
const pinCount = parseInt(document.getElementById('pinCount').value);
|
||||
const difficulty = document.getElementById('difficulty').value;
|
||||
const lockable = document.getElementById('lockable').value || 'test-lock';
|
||||
const thresholdSensitivity = parseInt(document.getElementById('thresholdSensitivity').value);
|
||||
const liftSpeed = parseFloat(document.getElementById('liftSpeed').value);
|
||||
const highlightBindingOrder = document.getElementById('highlightBindingOrder').value === 'true';
|
||||
const highlightPinAlignment = document.getElementById('highlightPinAlignment').value === 'true';
|
||||
|
||||
// Update UI
|
||||
document.getElementById('startBtn').disabled = true;
|
||||
document.getElementById('stopBtn').disabled = false;
|
||||
const alignmentText = highlightPinAlignment ? 'with' : 'without';
|
||||
const bindingText = highlightBindingOrder ? 'with' : 'without';
|
||||
document.getElementById('status').textContent = `Starting with ${pinCount} pins, ${difficulty} difficulty, sensitivity ${thresholdSensitivity}, lift speed ${liftSpeed}, ${alignmentText} alignment highlighting, ${bindingText} binding hints...`;
|
||||
|
||||
testMinigame = MinigameFramework.startMinigame('lockpicking', container, {
|
||||
lockable: lockable,
|
||||
difficulty: difficulty,
|
||||
pinCount: pinCount,
|
||||
thresholdSensitivity: thresholdSensitivity,
|
||||
liftSpeed: liftSpeed,
|
||||
highlightBindingOrder: highlightBindingOrder,
|
||||
highlightPinAlignment: highlightPinAlignment,
|
||||
onComplete: (success, result) => {
|
||||
const status = document.getElementById('status');
|
||||
if (success) {
|
||||
status.textContent = `Success! Lock picked in ${result.time || 'unknown'} seconds`;
|
||||
status.style.color = '#00ff00';
|
||||
} else {
|
||||
status.textContent = 'Failed to pick lock';
|
||||
status.style.color = '#ff0000';
|
||||
}
|
||||
|
||||
// Re-enable start button
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.stopTest = function() {
|
||||
if (testMinigame) {
|
||||
testMinigame.complete(false);
|
||||
testMinigame = null;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
document.getElementById('status').textContent = 'Test stopped';
|
||||
document.getElementById('status').style.color = '#ffffff';
|
||||
};
|
||||
|
||||
// Auto-start the test after a delay
|
||||
setTimeout(() => {
|
||||
startTest();
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user