From e3ed198059f93be46de7e1c54221e6b30d86070c Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Tue, 14 Oct 2025 01:52:15 +0100 Subject: [PATCH] Add Password Minigame: Introduce a new minigame for password entry, featuring customizable hints, keyboard input, and attempt tracking. Implement CSS styles for the minigame interface and integrate it into the existing framework. Update index.html to include the new CSS file and register the minigame in the minigame manager. Add test page for functionality and ensure compatibility with existing game mechanics. --- assets/mini-games/desktop-wallpaper.png | Bin 0 -> 27109 bytes css/container-minigame.css | 135 +++++++ css/minigames.css | 382 ++++++++++++++++++ index.html | 1 + js/minigames/container/container-minigame.js | 153 +++++++- js/minigames/index.js | 5 + js/minigames/password/password-minigame.js | 357 +++++++++++++++++ js/systems/minigame-starters.js | 50 +++ js/systems/unlock-system.js | 37 +- password-minigame-example.json | 157 ++++++++ scenarios/ceo_exfil.json | 25 +- test-auto-desktop.html | 383 +++++++++++++++++++ test-container-desktop.html | 348 +++++++++++++++++ test-container-simple.html | 189 +++++++++ test-password-minigame.html | 290 ++++++++++++++ verify-css.html | 208 ++++++++++ 16 files changed, 2689 insertions(+), 31 deletions(-) create mode 100644 assets/mini-games/desktop-wallpaper.png create mode 100644 js/minigames/password/password-minigame.js create mode 100644 password-minigame-example.json create mode 100644 test-auto-desktop.html create mode 100644 test-container-desktop.html create mode 100644 test-container-simple.html create mode 100644 test-password-minigame.html create mode 100644 verify-css.html diff --git a/assets/mini-games/desktop-wallpaper.png b/assets/mini-games/desktop-wallpaper.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f589e975941142852dfb10f67b47392af3aca4 GIT binary patch literal 27109 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 N002ovPDHLkV1l0tGhP4y literal 0 HcmV?d00001 diff --git a/css/container-minigame.css b/css/container-minigame.css index 6dae6c1..af8a541 100644 --- a/css/container-minigame.css +++ b/css/container-minigame.css @@ -8,6 +8,141 @@ gap: 20px; } +/* Desktop Mode Styles */ +.container-minigame.desktop-mode { + padding: 0; + gap: 0; + background: #000; +} + +.desktop-background { + flex: 1; + position: relative; + background: #000; + overflow: hidden; +} + +.desktop-wallpaper { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: url('../assets/mini-games/desktop-wallpaper.png'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + opacity: 0.8; +} + +.desktop-wallpaper::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.2); +} + +.desktop-icons { + position: relative; + z-index: 2; + width: 100%; + height: 100%; + padding: 20px; +} + +.desktop-icon { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + width: 80px; + cursor: pointer; + transition: all 0.2s ease; +} + +.desktop-icon:hover { + transform: scale(1.1); +} + +.desktop-icon-image { + width: 48px; + height: 48px; + object-fit: contain; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + background: rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 4px; + border: 2px solid transparent; +} + +.desktop-icon:hover .desktop-icon-image { + border-color: #00ff00; + background: rgba(0, 255, 0, 0.1); +} + +.desktop-icon-label { + font-family: 'Press Start 2P', monospace; + font-size: 8px; + color: white; + text-align: center; + margin-top: 4px; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8); + word-wrap: break-word; + max-width: 80px; +} + +.desktop-taskbar { + background: rgba(0, 0, 0, 0.8); + border-top: 2px solid #333; + padding: 10px 20px; + display: flex; + justify-content: space-between; + align-items: center; + min-height: 50px; +} + +.desktop-info { + display: flex; + flex-direction: column; + gap: 2px; +} + +.desktop-title { + font-family: 'Press Start 2P', monospace; + font-size: 10px; + color: #00ff00; +} + +.desktop-subtitle { + font-family: 'VT323', monospace; + font-size: 14px; + color: #ccc; +} + +.desktop-actions { + display: flex; + gap: 10px; +} + +.empty-desktop { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-family: 'Press Start 2P', monospace; + font-size: 12px; + color: #666; + text-align: center; +} + .container-image-section { display: flex; align-items: center; diff --git a/css/minigames.css b/css/minigames.css index ed6ba0f..da49885 100644 --- a/css/minigames.css +++ b/css/minigames.css @@ -190,3 +190,385 @@ transition: width 0.3s ease; border-radius: 5px; } + +/* Password Minigame Specific Styles */ +.password-minigame-area { + display: flex; + flex-direction: column; + height: 100%; + padding: 20px; + gap: 15px; + background: #1a1a1a; + position: relative; +} + +.password-input-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.monitor-bezel { + background: #2a2a2a; + border: 8px solid #1a1a1a; + border-radius: 15px; + padding: 20px; + box-shadow: + inset 0 0 20px rgba(0, 0, 0, 0.5), + 0 0 30px rgba(0, 0, 0, 0.8); + position: relative; + background-image: url('../assets/mini-games/desktop-wallpaper.png'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; +} + +.monitor-bezel::before { + content: ''; + position: absolute; + top: -4px; + left: -4px; + right: -4px; + bottom: -4px; + background: linear-gradient(45deg, #444, #666, #444); + border-radius: 19px; + z-index: -1; +} + +.monitor-bezel::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); + border-radius: 7px; + z-index: 1; +} + +.monitor-screen { + border: 2px solid #333; + border-radius: 8px; + padding: 15px; + min-height: 120px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + z-index: 2; +} + +.monitor-screen::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(0, 255, 0, 0.1), rgba(0, 255, 255, 0.1)); + border-radius: 6px; + z-index: 1; +} + +.monitor-screen > * { + position: relative; + z-index: 2; +} + +.password-input-container label { + font-size: 12px; + color: #00ff00; + margin-bottom: 5px; +} + +.password-field-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.password-field { + width: 100%; + padding: 12px 45px 12px 12px; + background: #1a1a1a; + border: 2px solid #00ff00; + border-radius: 5px; + color: white; + font-family: 'Press Start 2P', monospace; + font-size: 10px; + outline: none; + transition: border-color 0.3s ease; +} + +.password-field:focus { + border-color: #00ffff; + box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); +} + +.password-field::placeholder { + color: #666; +} + +.toggle-password-btn { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #00ff00; + cursor: pointer; + font-size: 16px; + padding: 5px; + border-radius: 3px; + transition: background-color 0.3s ease; +} + +.toggle-password-btn:hover { + background: rgba(0, 255, 0, 0.1); +} + +.password-hint-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.hint-btn { + background: #f39c12; + color: white; + border: none; + padding: 8px 16px; + border-radius: 5px; + cursor: pointer; + font-family: 'Press Start 2P', monospace; + font-size: 8px; + transition: background 0.3s ease; + align-self: flex-start; +} + +.hint-btn:hover { + background: #e67e22; +} + +.password-hint { + background: rgba(243, 156, 18, 0.1); + border: 1px solid #f39c12; + border-radius: 5px; + padding: 10px; + font-size: 10px; + color: #f39c12; +} + +.postit-note { + background: #ffff88; + border: 1px solid #ddd; + border-radius: 3px; + padding: 15px; + margin: 10px 0; + box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); + position: relative; + transform: rotate(-2deg); + font-family: 'Press Start 2P', monospace; + font-size: 8px; + color: #333; + max-width: 200px; + word-wrap: break-word; +} + +.postit-note::before { + content: ''; + position: absolute; + top: -1px; + right: -1px; + width: 0; + height: 0; + border-left: 15px solid transparent; + border-top: 15px solid #f0f0f0; +} + +.postit-note::after { + content: ''; + position: absolute; + top: 5px; + right: 5px; + width: 8px; + height: 8px; + background: #ff6b6b; + border-radius: 50%; + box-shadow: 0 0 0 1px #fff, 0 0 0 2px #ff6b6b; +} + +.onscreen-keyboard { + display: flex; + flex-direction: column; + gap: 5px; + background: #2a2a2a; + border: 2px solid #444; + border-radius: 8px; + padding: 10px; + margin: 10px 0; +} + +.keyboard-row { + display: flex; + justify-content: center; + gap: 3px; + flex-wrap: wrap; +} + +.key { + background: #444; + color: white; + border: 1px solid #666; + border-radius: 4px; + padding: 8px 12px; + cursor: pointer; + font-family: 'Press Start 2P', monospace; + font-size: 8px; + min-width: 35px; + text-align: center; + transition: all 0.2s ease; + user-select: none; +} + +.key:hover { + background: #555; + border-color: #00ff00; +} + +.key:active { + background: #00ff00; + color: black; + transform: scale(0.95); +} + +.key-backspace { + background: #e74c3c; + min-width: 60px; +} + +.key-backspace:hover { + background: #c0392b; +} + +.key-space { + background: #3498db; + min-width: 100px; +} + +.key-space:hover { + background: #2980b9; +} + +.key-special { + background: #9b59b6; + min-width: 80px; +} + +.key-special:hover { + background: #8e44ad; +} + +.password-actions { + display: flex; + justify-content: center; + gap: 15px; + margin-top: 10px; +} + +.submit-btn { + background: #2ecc71; + color: white; + border: none; + padding: 12px 24px; + border-radius: 5px; + cursor: pointer; + font-family: 'Press Start 2P', monospace; + font-size: 10px; + transition: background 0.3s ease; +} + +.submit-btn:hover { + background: #27ae60; +} + +.submit-btn:active { + background: #229954; +} + +.cancel-btn { + background: #e74c3c; + color: white; + border: none; + padding: 12px 24px; + border-radius: 5px; + cursor: pointer; + font-family: 'Press Start 2P', monospace; + font-size: 10px; + transition: background 0.3s ease; +} + +.cancel-btn:hover { + background: #c0392b; +} + +.cancel-btn:active { + background: #a93226; +} + +.attempts-counter { + text-align: center; + font-size: 10px; + color: #f39c12; + background: rgba(243, 156, 18, 0.1); + border: 1px solid #f39c12; + border-radius: 5px; + padding: 8px; + margin-top: 10px; +} + +.attempts-counter span { + color: #e74c3c; + font-weight: bold; +} + +/* Responsive design for smaller screens */ +@media (max-width: 768px) { + .onscreen-keyboard { + padding: 5px; + } + + .key { + padding: 6px 8px; + font-size: 7px; + min-width: 30px; + } + + .key-backspace { + min-width: 50px; + } + + .key-space { + min-width: 80px; + } + + .key-special { + min-width: 60px; + } + + .password-field { + font-size: 9px; + padding: 10px 40px 10px 10px; + } + + .submit-btn, .cancel-btn { + padding: 10px 20px; + font-size: 9px; + } +} diff --git a/index.html b/index.html index 1db810c..0795eb2 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,7 @@ + diff --git a/js/minigames/container/container-minigame.js b/js/minigames/container/container-minigame.js index debacc0..a0bcf0e 100644 --- a/js/minigames/container/container-minigame.js +++ b/js/minigames/container/container-minigame.js @@ -8,6 +8,27 @@ export class ContainerMinigame extends MinigameScene { this.containerItem = params.containerItem; this.contents = params.contents || []; this.isTakeable = params.isTakeable || false; + + // Auto-detect desktop mode for PC/tablet containers + this.desktopMode = params.desktopMode || this.shouldUseDesktopMode(); + } + + shouldUseDesktopMode() { + // Check if the container is a PC, tablet, or computer-related device + const containerName = this.containerItem?.scenarioData?.name?.toLowerCase() || ''; + const containerType = this.containerItem?.scenarioData?.type?.toLowerCase() || ''; + const containerImage = this.containerItem?.name?.toLowerCase() || ''; + + // Keywords that indicate desktop/computer devices + const desktopKeywords = [ + 'computer', 'pc', 'laptop', 'desktop', 'terminal', 'workstation', + 'tablet', 'ipad', 'surface', 'monitor', 'screen', 'display', + 'server', 'mainframe', 'console', 'kiosk', 'smartboard' + ]; + + // Check if any keyword matches + const allText = `${containerName} ${containerType} ${containerImage}`.toLowerCase(); + return desktopKeywords.some(keyword => allText.includes(keyword)); } init() { @@ -27,6 +48,20 @@ export class ContainerMinigame extends MinigameScene { } createContainerUI() { + if (this.desktopMode) { + this.createDesktopUI(); + } else { + this.createStandardUI(); + } + + // Populate contents + this.populateContents(); + + // Set up event listeners + this.setupEventListeners(); + } + + createStandardUI() { this.gameContainer.innerHTML = `
@@ -52,15 +87,41 @@ export class ContainerMinigame extends MinigameScene {
`; - - // Populate contents - this.populateContents(); - - // Set up event listeners - this.setupEventListeners(); + } + + createDesktopUI() { + this.gameContainer.innerHTML = ` +
+
+
+
+ +
+
+ +
+
+ ${this.containerItem.scenarioData.name} + ${this.containerItem.scenarioData.observations || ''} +
+
+ ${this.isTakeable ? '' : ''} + +
+
+
+ `; } populateContents() { + if (this.desktopMode) { + this.populateDesktopIcons(); + } else { + this.populateStandardContents(); + } + } + + populateStandardContents() { const contentsGrid = document.getElementById('container-contents-grid'); if (!contentsGrid) return; @@ -107,6 +168,57 @@ export class ContainerMinigame extends MinigameScene { }); } + populateDesktopIcons() { + const desktopIcons = document.getElementById('desktop-icons'); + if (!desktopIcons) return; + + if (this.contents.length === 0) { + desktopIcons.innerHTML = '
Desktop is empty
'; + return; + } + + this.contents.forEach((item, index) => { + const icon = document.createElement('div'); + icon.className = 'desktop-icon'; + + const iconImg = document.createElement('img'); + iconImg.className = 'desktop-icon-image'; + iconImg.src = `assets/objects/${item.type}.png`; + iconImg.alt = item.name; + + const iconLabel = document.createElement('div'); + iconLabel.className = 'desktop-icon-label'; + iconLabel.textContent = item.name; + + // Add item data + iconImg.scenarioData = item; + iconImg.name = item.type; + iconImg.objectId = `desktop_${index}`; + + // Add click handler for taking items + if (item.takeable) { + icon.style.cursor = 'pointer'; + + // Special handling for notes - trigger notes minigame instead of taking + if (item.type === 'notes' && item.readable && item.text) { + icon.addEventListener('click', () => this.handleNotesItem(item, iconImg)); + } else { + icon.addEventListener('click', () => this.takeItem(item, iconImg)); + } + } + + // Position icon randomly on desktop + const x = Math.random() * 70 + 10; // 10% to 80% of width + const y = Math.random() * 60 + 10; // 10% to 70% of height + icon.style.left = `${x}%`; + icon.style.top = `${y}%`; + + icon.appendChild(iconImg); + icon.appendChild(iconLabel); + desktopIcons.appendChild(icon); + }); + } + setupEventListeners() { // Take container button const takeContainerBtn = document.getElementById('take-container-btn'); @@ -254,8 +366,13 @@ export class ContainerMinigame extends MinigameScene { } // Function to start the container minigame -export function startContainerMinigame(containerItem, contents, isTakeable = false) { - console.log('Starting container minigame', { containerItem, contents, isTakeable }); +export function startContainerMinigame(containerItem, contents, isTakeable = false, desktopMode = null) { + // Auto-detect desktop mode if not explicitly set + if (desktopMode === null) { + desktopMode = shouldUseDesktopModeForContainer(containerItem); + } + + console.log('Starting container minigame', { containerItem, contents, isTakeable, desktopMode }); // Initialize the minigame framework if not already done if (!window.MinigameFramework) { @@ -273,6 +390,7 @@ export function startContainerMinigame(containerItem, contents, isTakeable = fal containerItem: containerItem, contents: contents, isTakeable: isTakeable, + desktopMode: desktopMode, cancelText: 'Close', showCancel: true, onComplete: (success, result) => { @@ -281,6 +399,25 @@ export function startContainerMinigame(containerItem, contents, isTakeable = fal }); } +// Helper function to determine if a container should use desktop mode +function shouldUseDesktopModeForContainer(containerItem) { + // Check if the container is a PC, tablet, or computer-related device + const containerName = containerItem?.scenarioData?.name?.toLowerCase() || ''; + const containerType = containerItem?.scenarioData?.type?.toLowerCase() || ''; + const containerImage = containerItem?.name?.toLowerCase() || ''; + + // Keywords that indicate desktop/computer devices + const desktopKeywords = [ + 'computer', 'pc', 'laptop', 'desktop', 'terminal', 'workstation', + 'tablet', 'ipad', 'surface', 'monitor', 'screen', 'display', + 'server', 'mainframe', 'console', 'kiosk', 'smartboard' + ]; + + // Check if any keyword matches + const allText = `${containerName} ${containerType} ${containerImage}`.toLowerCase(); + return desktopKeywords.some(keyword => allText.includes(keyword)); +} + // Function to return to container after notes minigame export function returnToContainerAfterNotes() { console.log('Returning to container after notes minigame'); diff --git a/js/minigames/index.js b/js/minigames/index.js index 1cc84ba..50f7020 100644 --- a/js/minigames/index.js +++ b/js/minigames/index.js @@ -12,6 +12,7 @@ export { LockpickSetMinigame, startLockpickSetMinigame } from './lockpick/lockpi export { ContainerMinigame, startContainerMinigame, returnToContainerAfterNotes } from './container/container-minigame.js'; export { PhoneMessagesMinigame, returnToPhoneAfterNotes } from './phone/phone-messages-minigame.js'; export { PinMinigame, startPinMinigame } from './pin/pin-minigame.js'; +export { PasswordMinigame } from './password/password-minigame.js'; // Initialize the global minigame framework for backward compatibility import { MinigameFramework } from './framework/minigame-manager.js'; @@ -61,6 +62,9 @@ import { PhoneMessagesMinigame, returnToPhoneAfterNotes } from './phone/phone-me // Import the PIN minigame import { PinMinigame, startPinMinigame } from './pin/pin-minigame.js'; +// Import the password minigame +import { PasswordMinigame } from './password/password-minigame.js'; + // Register minigames MinigameFramework.registerScene('lockpicking', LockpickingMinigamePhaser); // Use Phaser version as default MinigameFramework.registerScene('lockpicking-phaser', LockpickingMinigamePhaser); // Keep explicit phaser name @@ -72,6 +76,7 @@ MinigameFramework.registerScene('lockpick-set', LockpickSetMinigame); MinigameFramework.registerScene('container', ContainerMinigame); MinigameFramework.registerScene('phone-messages', PhoneMessagesMinigame); MinigameFramework.registerScene('pin', PinMinigame); +MinigameFramework.registerScene('password', PasswordMinigame); // Make minigame functions available globally window.startNotesMinigame = startNotesMinigame; diff --git a/js/minigames/password/password-minigame.js b/js/minigames/password/password-minigame.js new file mode 100644 index 0000000..efd8fbb --- /dev/null +++ b/js/minigames/password/password-minigame.js @@ -0,0 +1,357 @@ +import { MinigameScene } from '../framework/base-minigame.js'; + +export class PasswordMinigame extends MinigameScene { + constructor(container, params) { + super(container, params); + + // Initialize password-specific state + this.gameData = { + password: params.password || '', + passwordHint: params.passwordHint || '', + showHint: params.showHint || false, + showKeyboard: params.showKeyboard || false, + maxAttempts: params.maxAttempts || 3, + attempts: 0, + showPassword: false, + postitNote: params.postitNote || '', + showPostit: params.showPostit || false + }; + + // Store the correct password for validation + this.correctPassword = params.password || ''; + } + + init() { + // Call parent init to set up basic UI structure + super.init(); + + // Customize the header + this.headerElement.innerHTML = ` +

${this.params.title || 'Password Entry'}

+

Enter the correct password to proceed

+ `; + + // Set up the password interface + this.setupPasswordInterface(); + + // Set up event listeners + this.setupEventListeners(); + } + + setupPasswordInterface() { + // Create the password entry interface + this.gameContainer.innerHTML = ` +
+ ${this.gameData.showPostit && this.gameData.postitNote ? ` +
+ ${this.gameData.postitNote} +
+ ` : ''} + +
+
+
+ +
+ + +
+
+ + ${this.gameData.showHint ? ` +
+ + +
+ ` : ''} +
+
+ + ${this.gameData.showKeyboard ? ` +
+
+ + + + + + + + + + + +
+
+ + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + +
+
+ + +
+
+ ` : ''} + +
+ + +
+ +
+ Attempts: ${this.gameData.attempts}/${this.gameData.maxAttempts} +
+
+ `; + + // Get references to important elements + this.passwordField = document.getElementById('password-field'); + this.togglePasswordBtn = document.getElementById('toggle-password'); + this.submitBtn = document.getElementById('submit-password'); + this.cancelBtn = document.getElementById('cancel-password'); + this.attemptsDisplay = document.getElementById('attempts-display'); + + // Focus the password field + if (this.passwordField) { + this.passwordField.focus(); + } + } + + setupEventListeners() { + // Password field events + if (this.passwordField) { + this.addEventListener(this.passwordField, 'keydown', (event) => { + this.handleKeyPress(event); + }); + + this.addEventListener(this.passwordField, 'input', (event) => { + this.handlePasswordInput(event); + }); + } + + // Toggle password visibility + if (this.togglePasswordBtn) { + this.addEventListener(this.togglePasswordBtn, 'click', () => { + this.togglePasswordVisibility(); + }); + } + + // Submit button + if (this.submitBtn) { + this.addEventListener(this.submitBtn, 'click', () => { + this.submitPassword(); + }); + } + + // Cancel button + if (this.cancelBtn) { + this.addEventListener(this.cancelBtn, 'click', () => { + this.cancelPassword(); + }); + } + + // Hint button + const hintBtn = document.getElementById('show-hint'); + if (hintBtn) { + this.addEventListener(hintBtn, 'click', () => { + this.toggleHint(); + }); + } + + // Onscreen keyboard + const keyboard = document.getElementById('onscreen-keyboard'); + if (keyboard) { + this.addEventListener(keyboard, 'click', (event) => { + this.handleKeyboardClick(event); + }); + } + } + + start() { + // Call parent start + super.start(); + + console.log("Password minigame started"); + } + + handleKeyPress(event) { + if (!this.gameState.isActive) return; + + switch(event.key) { + case 'Enter': + event.preventDefault(); + this.submitPassword(); + break; + case 'Escape': + event.preventDefault(); + this.cancelPassword(); + break; + } + } + + handlePasswordInput(event) { + // Update the internal password state + this.gameData.password = event.target.value; + } + + togglePasswordVisibility() { + this.gameData.showPassword = !this.gameData.showPassword; + + // Update input type + this.passwordField.type = this.gameData.showPassword ? 'text' : 'password'; + + // Update button icon + this.togglePasswordBtn.textContent = this.gameData.showPassword ? '👁️' : '👁️‍🗨️'; + } + + toggleHint() { + const hintElement = document.getElementById('password-hint'); + const hintBtn = document.getElementById('show-hint'); + + if (hintElement && hintBtn) { + if (hintElement.style.display === 'none') { + hintElement.style.display = 'block'; + hintBtn.textContent = 'Hide Hint'; + } else { + hintElement.style.display = 'none'; + hintBtn.textContent = 'Show Hint'; + } + } + } + + handleKeyboardClick(event) { + if (!this.gameState.isActive) return; + + const key = event.target; + if (!key.classList.contains('key')) return; + + const keyValue = key.dataset.key; + + if (keyValue === 'Enter') { + this.submitPassword(); + } else if (keyValue === 'Escape') { + this.cancelPassword(); + } else if (keyValue === 'Backspace') { + this.passwordField.value = this.passwordField.value.slice(0, -1); + this.gameData.password = this.passwordField.value; + } else if (keyValue === ' ') { + this.passwordField.value += ' '; + this.gameData.password = this.passwordField.value; + } else if (keyValue && keyValue.length === 1) { + this.passwordField.value += keyValue; + this.gameData.password = this.passwordField.value; + } + + // Keep focus on password field + this.passwordField.focus(); + } + + submitPassword() { + if (!this.gameState.isActive) return; + + const enteredPassword = this.passwordField.value.trim(); + + if (!enteredPassword) { + this.showFailure("Please enter a password", false, 2000); + return; + } + + this.gameData.attempts++; + this.attemptsDisplay.textContent = this.gameData.attempts; + + if (enteredPassword === this.correctPassword) { + this.passwordCorrect(); + } else { + this.passwordIncorrect(); + } + } + + passwordCorrect() { + this.cleanup(); + this.showSuccess("Password accepted! Access granted.", true, 3000); + + // Set game result for the callback + this.gameResult = { + success: true, + password: this.gameData.password, + attempts: this.gameData.attempts + }; + } + + passwordIncorrect() { + if (this.gameData.attempts >= this.gameData.maxAttempts) { + this.passwordFailed(); + } else { + this.showFailure(`Incorrect password. ${this.gameData.maxAttempts - this.gameData.attempts} attempts remaining.`, false, 3000); + + // Clear the password field + this.passwordField.value = ''; + this.gameData.password = ''; + this.passwordField.focus(); + } + } + + passwordFailed() { + this.cleanup(); + this.showFailure("Maximum attempts exceeded. Access denied.", true, 3000); + + this.gameResult = { + success: false, + reason: 'max_attempts_exceeded', + attempts: this.gameData.attempts + }; + } + + cancelPassword() { + this.cleanup(); + this.showFailure("Password entry cancelled.", true, 2000); + + this.gameResult = { + success: false, + reason: 'cancelled', + attempts: this.gameData.attempts + }; + } + + cleanup() { + // Call parent cleanup (handles event listeners) + super.cleanup(); + } +} diff --git a/js/systems/minigame-starters.js b/js/systems/minigame-starters.js index 65b9065..c0b20da 100644 --- a/js/systems/minigame-starters.js +++ b/js/systems/minigame-starters.js @@ -264,8 +264,58 @@ export function startPinMinigame(lockable, type, correctPin, callback) { }); } +export function startPasswordMinigame(lockable, type, correctPassword, callback, options = {}) { + console.log('Starting password minigame for', type, 'with password:', correctPassword); + + // Initialize the minigame framework if not already done + if (!window.MinigameFramework) { + console.error('MinigameFramework not available'); + // Fallback to simple prompt + const passwordInput = prompt(`Enter password:`); + if (passwordInput === correctPassword) { + console.log('PASSWORD SUCCESS (fallback)'); + window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000); + callback(true); + } else if (passwordInput !== null) { + console.log('PASSWORD FAIL (fallback)'); + window.gameAlert("Incorrect password.", 'error', 'Password Rejected', 3000); + callback(false); + } + return; + } + + // Use the advanced minigame framework + if (!window.MinigameFramework.mainGameScene) { + window.MinigameFramework.init(window.game); + } + + // Start the password minigame + window.MinigameFramework.startMinigame('password', null, { + title: `Enter password for ${type}`, + password: correctPassword, + passwordHint: options.passwordHint || '', + showHint: options.showHint || false, + showKeyboard: options.showKeyboard || false, + maxAttempts: options.maxAttempts || 3, + postitNote: options.postitNote || '', + showPostit: options.showPostit || false, + onComplete: (success, result) => { + if (success) { + console.log('PASSWORD MINIGAME SUCCESS'); + window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000); + callback(true); + } else { + console.log('PASSWORD MINIGAME FAILED'); + window.gameAlert("Failed to enter correct password.", 'error', 'Password Rejected', 3000); + callback(false); + } + } + }); +} + // Export for global access window.startLockpickingMinigame = startLockpickingMinigame; window.startKeySelectionMinigame = startKeySelectionMinigame; window.startPinMinigame = startPinMinigame; +window.startPasswordMinigame = startPasswordMinigame; diff --git a/js/systems/unlock-system.js b/js/systems/unlock-system.js index e3089a3..2a8e375 100644 --- a/js/systems/unlock-system.js +++ b/js/systems/unlock-system.js @@ -10,7 +10,7 @@ import { DOOR_ALIGN_OVERLAP } from '../utils/constants.js'; import { rooms } from '../core/rooms.js'; import { unlockDoor } from './doors.js'; -import { startLockpickingMinigame, startKeySelectionMinigame, startPinMinigame } from './minigame-starters.js'; +import { startLockpickingMinigame, startKeySelectionMinigame, startPinMinigame, startPasswordMinigame } from './minigame-starters.js'; // Helper function to check if two rectangles overlap function boundsOverlap(rect1, rect2) { @@ -106,29 +106,22 @@ export function handleUnlock(lockable, type) { case 'password': console.log('PASSWORD REQUESTED'); - if (window.showPasswordModal) { - window.showPasswordModal(function(passwordInput) { - if (passwordInput === lockRequirements.requires) { - unlockTarget(lockable, type, lockable.layer); - console.log('PASSWORD SUCCESS'); - window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000); - } else if (passwordInput !== null) { - console.log('PASSWORD FAIL'); - window.gameAlert("Incorrect password.", 'error', 'Password Rejected', 3000); - } - }); - } else { - // Fallback to prompt - const passwordInput = prompt(`Enter password:`); - if (passwordInput === lockRequirements.requires) { + + // Get password options from the lockable object + const passwordOptions = { + passwordHint: lockable.passwordHint || lockable.scenarioData?.passwordHint || '', + showHint: lockable.showHint || lockable.scenarioData?.showHint || false, + showKeyboard: lockable.showKeyboard || lockable.scenarioData?.showKeyboard || false, + maxAttempts: lockable.maxAttempts || lockable.scenarioData?.maxAttempts || 3, + postitNote: lockable.postitNote || lockable.scenarioData?.postitNote || '', + showPostit: lockable.showPostit || lockable.scenarioData?.showPostit || false + }; + + startPasswordMinigame(lockable, type, lockRequirements.requires, (success) => { + if (success) { unlockTarget(lockable, type, lockable.layer); - console.log('PASSWORD SUCCESS'); - window.gameAlert(`Correct password! The ${type} is now unlocked.`, 'success', 'Password Accepted', 4000); - } else if (passwordInput !== null) { - console.log('PASSWORD FAIL'); - window.gameAlert("Incorrect password.", 'error', 'Password Rejected', 3000); } - } + }, passwordOptions); break; case 'biometric': diff --git a/password-minigame-example.json b/password-minigame-example.json new file mode 100644 index 0000000..f38e988 --- /dev/null +++ b/password-minigame-example.json @@ -0,0 +1,157 @@ +{ + "name": "Password Minigame Example", + "description": "Example scenario showing how to use the password minigame", + "rooms": { + "office": { + "id": "office", + "name": "Office", + "image": "room_office.png", + "map": "room_office.json", + "position": { "x": 0, "y": 0 }, + "connections": [] + } + }, + "objects": [ + { + "id": "secure_door", + "name": "Secure Door", + "type": "door", + "room": "office", + "x": 400, + "y": 300, + "image": "door.png", + "lockType": "password", + "requires": "admin123", + "passwordHint": "The default administrator password", + "showHint": true, + "showKeyboard": false, + "maxAttempts": 3, + "locked": true, + "observations": "A secure door with a password keypad." + }, + { + "id": "computer_terminal", + "name": "Computer Terminal", + "type": "computer", + "room": "office", + "x": 200, + "y": 200, + "image": "computer.png", + "lockType": "password", + "requires": "cyber2024", + "passwordHint": "Current year with cyber prefix", + "showHint": true, + "showKeyboard": true, + "maxAttempts": 5, + "postitNote": "Password: cyber2024", + "showPostit": true, + "locked": true, + "observations": "A computer terminal requiring password access." + }, + { + "id": "safe_box", + "name": "Safe Box", + "type": "container", + "room": "office", + "x": 600, + "y": 400, + "image": "safe.png", + "lockType": "password", + "requires": "treasure", + "showHint": false, + "showKeyboard": true, + "maxAttempts": 2, + "locked": true, + "contents": [ + { + "id": "secret_document", + "name": "Secret Document", + "type": "document", + "image": "document.png", + "takeable": true, + "observations": "A classified document containing sensitive information." + } + ], + "observations": "A heavy safe box with a digital keypad." + }, + { + "id": "office_computer", + "name": "Office Computer", + "type": "container", + "room": "office", + "x": 300, + "y": 300, + "image": "computer.png", + "lockType": "password", + "requires": "desktop123", + "showHint": false, + "showKeyboard": false, + "maxAttempts": 3, + "locked": true, + "contents": [ + { + "id": "project_files", + "name": "Project Files", + "type": "document", + "takeable": true, + "observations": "Important project documentation." + }, + { + "id": "meeting_notes", + "name": "Meeting Notes", + "type": "notes", + "takeable": true, + "readable": true, + "text": "Meeting notes about the upcoming project deadline..." + }, + { + "id": "encryption_key", + "name": "Encryption Key", + "type": "key", + "takeable": true, + "observations": "A digital encryption key file." + } + ], + "observations": "An office computer with desktop access. Desktop mode will be automatically enabled." + }, + { + "id": "tablet_device", + "name": "Executive Tablet", + "type": "container", + "room": "office", + "x": 500, + "y": 200, + "image": "tablet.png", + "lockType": "password", + "requires": "tablet2024", + "showHint": false, + "showKeyboard": true, + "maxAttempts": 3, + "locked": true, + "contents": [ + { + "id": "executive_notes", + "name": "Executive Notes", + "type": "notes", + "takeable": true, + "readable": true, + "text": "Confidential executive meeting notes..." + }, + { + "id": "financial_data", + "name": "Financial Data", + "type": "document", + "takeable": true, + "observations": "Sensitive financial information." + } + ], + "observations": "An executive tablet device. Desktop mode will be automatically enabled." + } + ], + "victoryConditions": [ + { + "type": "collect_item", + "itemId": "secret_document" + } + ] +} diff --git a/scenarios/ceo_exfil.json b/scenarios/ceo_exfil.json index 22517ca..3deeea9 100644 --- a/scenarios/ceo_exfil.json +++ b/scenarios/ceo_exfil.json @@ -30,8 +30,31 @@ "type": "pc", "name": "Reception Computer", "takeable": false, + "lockType": "password", + "passwordHint": "Optional hint text", + "showHint": true, + "showKeyboard": true, + "maxAttempts": 3, + "locked": true, "requires": "password", - "observations": "The reception's computer, currently locked" + "observations": "The reception's computer, currently locked", + "contents": [ + { + "type": "notes", + "name": "Private Note", + "takeable": true, + "readable": true, + "text": "Closet keypad code: 7391 - Must move evidence to safe before audit", + "observations": "A hastily written note on expensive paper" + }, + { + "type": "key", + "name": "Safe Key", + "takeable": true, + "key_id": "safe_key:52,29,44,37", + "observations": "A heavy-duty safe key hidden behind server equipment" + } + ] }, { "type": "tablet", diff --git a/test-auto-desktop.html b/test-auto-desktop.html new file mode 100644 index 0000000..bf9a83a --- /dev/null +++ b/test-auto-desktop.html @@ -0,0 +1,383 @@ + + + + + + Auto Desktop Mode Test + + + + + + + + + + +
+

Auto Desktop Mode Detection Test

+

Test automatic desktop mode detection for PC, tablet, and computer containers.

+ +
+ Auto-Detection Keywords: computer, pc, laptop, desktop, terminal, workstation, tablet, ipad, surface, monitor, screen, display, server, mainframe, console, kiosk, smartboard +
+ + +
+

Computer Container (Auto Desktop)

+
+ Test container with "computer" in the name - should automatically enable desktop mode. +
+ +
+ + +
+

Tablet Container (Auto Desktop)

+
+ Test container with "tablet" in the name - should automatically enable desktop mode. +
+ +
+ + +
+

PC Container (Auto Desktop)

+
+ Test container with "pc" in the name - should automatically enable desktop mode. +
+ +
+ + +
+

Regular Container (No Auto Desktop)

+
+ Test container with "safe" in the name - should NOT automatically enable desktop mode. +
+ +
+ + +
+

Manual Override Test

+
+ Test container with "safe" but manually force desktop mode - should enable desktop mode. +
+ +
+ + +
+

Test Results

+
Click a test button to see results here...
+
+
+ + + + + + + + diff --git a/test-container-desktop.html b/test-container-desktop.html new file mode 100644 index 0000000..503c8e2 --- /dev/null +++ b/test-container-desktop.html @@ -0,0 +1,348 @@ + + + + + + Container Desktop Mode Test + + + + + + + + + + +
+

Container Desktop Mode Test Suite

+

Test the container minigame in desktop mode with desktop wallpaper and icons.

+ + +
+

Standard Container Test

+
+ Test the standard container minigame (non-desktop mode) with various items. +
+ +
+ + +
+

Desktop Container Test

+
+ Test the container minigame in desktop mode with desktop wallpaper and scattered icons. +
+ +
+ + +
+

Desktop with Notes Test

+
+ Test desktop mode with notes that should trigger the notes minigame when clicked. +
+ +
+ + +
+

Empty Desktop Test

+
+ Test desktop mode with no items to see the empty desktop message. +
+ +
+ + +
+

Test Results

+
Click a test button to see results here...
+
+
+ + + + + + + + diff --git a/test-container-simple.html b/test-container-simple.html new file mode 100644 index 0000000..c307bf3 --- /dev/null +++ b/test-container-simple.html @@ -0,0 +1,189 @@ + + + + + + Simple Container Desktop Test + + + + + + + + + + +
+

Simple Container Desktop Mode Test

+

Test the container desktop mode CSS and layout without the full framework.

+ + +
+

Standard Container Layout

+
+ Standard container minigame layout (non-desktop mode). +
+
+
+
+ Container +
+

Test Container

+

A standard container for testing

+
+
+ +
+

Contents

+
+
+ Key +
Test Key
+
+
+ Document +
Test Document
+
+
+
+
+
+
+ + +
+

Desktop Container Layout

+
+ Container minigame in desktop mode with desktop wallpaper and scattered icons. +
+
+
+
+
+
+
+ Document +
Secret Files
+
+
+ Key +
Encryption Key
+
+
+ Notes +
Meeting Notes
+
+
+ Folder +
Backup Files
+
+
+
+ +
+
+ Office Computer + Desktop with scattered files +
+
+ + +
+
+
+
+
+ + +
+

Empty Desktop Layout

+
+ Desktop mode with no items to show the empty desktop message. +
+
+
+
+
+
+
Desktop is empty
+
+
+ +
+
+ Empty Desktop + No files found +
+
+ +
+
+
+
+
+
+ + diff --git a/test-password-minigame.html b/test-password-minigame.html new file mode 100644 index 0000000..3af199b --- /dev/null +++ b/test-password-minigame.html @@ -0,0 +1,290 @@ + + + + + + Password Minigame Test + + + + + + + + + +
+

Password Minigame Test Suite

+

Test the new password minigame with various configurations and options.

+ + +
+

Basic Password Test

+
+ Test basic password entry with show/hide functionality. Password: "secret123" +
+ +
+ + +
+

Password with Hint (Desktop Background)

+
+ Test password entry with hint button inside monitor bezel with desktop wallpaper. Password: "admin"
+ Hint: "The default administrator password" +
+ +
+ + +
+

Password with Onscreen Keyboard

+
+ Test password entry with onscreen QWERTY keyboard. Password: "keyboard" +
+ +
+ + +
+

Full Featured Password

+
+ Test password with all features: hint, keyboard, and custom attempts. Password: "fulltest"
+ Hint: "A complete test of all features" +
+ +
+ + +
+

Password with Post-it Note

+
+ Test password with post-it note on desktop. Password: "postit"
+ Post-it: "Password is written on the sticky note!" +
+ +
+ + +
+

Wrong Password Test

+
+ Test entering wrong passwords to trigger failure scenarios. Try: "wrong", "incorrect", "fail" +
+ +
+ + +
+

Test Results

+
Click a test button to see results here...
+
+
+ + + + + diff --git a/verify-css.html b/verify-css.html new file mode 100644 index 0000000..35dd20b --- /dev/null +++ b/verify-css.html @@ -0,0 +1,208 @@ + + + + + + CSS Verification + + + + + + + + + +
+

CSS Verification Test

+

This page verifies that the password minigame CSS is properly loaded and applied.

+ +
+

Password Field with Monitor Bezel Test

+
+
+
+
+ +
+ + +
+
+
+ +
+
+
+
+
CSS NOT LOADED
+
+ +
+

Hint System Test

+
+ + +
+
CSS NOT LOADED
+
+ +
+

Onscreen Keyboard Test

+
+
+ + + + +
+
+
CSS NOT LOADED
+
+ +
+

Action Buttons Test

+
+ + +
+
CSS NOT LOADED
+
+ +
+

Attempts Counter Test

+
+ Attempts: 0/3 +
+
CSS NOT LOADED
+
+ +
+

Overall Status

+
VERIFICATION IN PROGRESS
+
+
+ + + +