diff --git a/assets/mini-games/desktop-wallpaper.png b/assets/mini-games/desktop-wallpaper.png new file mode 100644 index 0000000..a3f589e Binary files /dev/null and b/assets/mini-games/desktop-wallpaper.png differ 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
+
+
+ + + +