From 2230885f12a1fe60866edb9286f354d95ecb21aa Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Tue, 21 Oct 2025 14:43:34 +0100 Subject: [PATCH] Update Room and Scenario Files: Rename an object in the reception room JSON files to "phone" for clarity. Modify the scenario brief in the CEO exfiltration scenario for improved narrative engagement. Enhance CSS styles across various minigames, including adjustments to layout, font sizes, and new sections for item displays in lockpicking and password minigames, improving overall user experience. --- assets/rooms/room_reception2.json | 2 +- assets/rooms/room_reception2.tmj | 2 +- assets/scenarios/ceo_exfil.json | 2 +- css/container-minigame.css | 7 +- css/inventory.css | 2 +- css/lockpicking.css | 70 ++++++++++++++++ css/password-minigame.css | 44 +++++++++++ css/phone.css | 36 ++++++++- js/minigames/container/container-minigame.js | 15 ++-- .../lockpicking/lockpicking-game-phaser.js | 63 +++++++++++++++ js/minigames/password/password-minigame.js | 35 ++++++++ js/minigames/phone/phone-messages-minigame.js | 69 +++++++++------- js/systems/interactions.js | 2 + js/systems/minigame-starters.js | 79 +++++++++++++++++++ scenarios/ceo_exfil.json | 3 +- 15 files changed, 386 insertions(+), 45 deletions(-) diff --git a/assets/rooms/room_reception2.json b/assets/rooms/room_reception2.json index 896bbef..d816d86 100644 --- a/assets/rooms/room_reception2.json +++ b/assets/rooms/room_reception2.json @@ -145,7 +145,7 @@ "gid":230, "height":17, "id":47, - "name":"", + "name":"phone", "rotation":0, "type":"", "visible":true, diff --git a/assets/rooms/room_reception2.tmj b/assets/rooms/room_reception2.tmj index 757bb24..e2f4262 100644 --- a/assets/rooms/room_reception2.tmj +++ b/assets/rooms/room_reception2.tmj @@ -153,7 +153,7 @@ "gid":230, "height":17, "id":47, - "name":"", + "name":"phone", "rotation":0, "type":"", "visible":true, diff --git a/assets/scenarios/ceo_exfil.json b/assets/scenarios/ceo_exfil.json index 03baf51..3efe4b0 100644 --- a/assets/scenarios/ceo_exfil.json +++ b/assets/scenarios/ceo_exfil.json @@ -1,5 +1,5 @@ { - "scenario_brief": "Hi, You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.", + "scenario_brief": "old version. You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.", "startRoom": "reception", "rooms": { "reception": { diff --git a/css/container-minigame.css b/css/container-minigame.css index 484fd0b..3168e3e 100644 --- a/css/container-minigame.css +++ b/css/container-minigame.css @@ -225,7 +225,6 @@ .desktop-taskbar { background: rgba(0, 0, 0, 0.8); - border-top: 2px solid #333; padding: 10px 20px; display: flex; justify-content: space-between; @@ -271,9 +270,6 @@ align-items: center; gap: 20px; padding: 20px; - background: rgba(255, 255, 255, 0.05); - border-radius: 10px; - border: 1px solid rgba(255, 255, 255, 0.1); } .container-image { @@ -374,10 +370,11 @@ image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; transition: transform 0.2s ease; + transform: scale(2); } .container-content-item:hover { - transform: scale(1.1); + transform: scale(2.2); } .container-content-tooltip { diff --git a/css/inventory.css b/css/inventory.css index 7ce7985..55da451 100644 --- a/css/inventory.css +++ b/css/inventory.css @@ -80,7 +80,7 @@ color: white; padding: 4px 8px; border-radius: 4px; - font-size: 14px; + font-size: 18px; white-space: nowrap; pointer-events: none; opacity: 0; diff --git a/css/lockpicking.css b/css/lockpicking.css index f06f91c..ea26f85 100644 --- a/css/lockpicking.css +++ b/css/lockpicking.css @@ -27,3 +27,73 @@ .lockpick-feedback:empty { display: none; } + +/* Lockpicking item section styling */ +.lockpicking-item-section { + display: flex; + align-items: center; + gap: 20px; + padding: 20px; + background: rgba(0, 0, 0, 0.3); + margin-bottom: 20px; +} + +.lockpicking-item-image { + min-width: 80px; + min-height: 80px; + object-fit: contain; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + border: 2px solid rgba(255, 255, 255, 0.3); + background: rgba(0, 0, 0, 0.3); + flex-shrink: 0; +} + +.lockpicking-item-info h4 { + font-family: 'Press Start 2P', monospace; + font-size: 20px; + margin: 0 0 10px 0; + color: #3498db; +} + +.lockpicking-item-info p { + font-size: 16px; + margin: 0; + color: #ecf0f1; + line-height: 1.4; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .lockpicking-item-section { + flex-direction: column; + text-align: center; + gap: 15px; + } + + .lockpicking-item-image { + width: 70px; + height: 70px; + } + + .lockpicking-item-info h4 { + font-size: 16px; + } + + .lockpicking-item-info p { + font-size: 14px; + } +} + +/* Phaser game container styling - prevent margin/padding shifts */ +#phaser-game-container { + margin: 0 !important; + padding: 0 !important; +} + +#phaser-game-container canvas { + margin: 0 !important; + padding: 0 !important; + display: block; +} diff --git a/css/password-minigame.css b/css/password-minigame.css index d19a856..ce617ea 100644 --- a/css/password-minigame.css +++ b/css/password-minigame.css @@ -10,6 +10,38 @@ position: relative; } +.password-image-section { + display: flex; + align-items: center; + gap: 20px; + padding: 20px; +} + +.password-image { + width: 80px; + height: 80px; + object-fit: contain; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + border: 2px solid rgba(255, 255, 255, 0.3); + background: rgba(0, 0, 0, 0.3); +} + +.password-info h4 { + font-family: 'Press Start 2P', monospace; + font-size: 20px; + margin: 0 0 10px 0; + color: #3498db; +} + +.password-info p { + font-size: 20px; + margin: 0; + color: #ecf0f1; + line-height: 1.4; +} + .password-input-container { display: flex; flex-direction: column; @@ -529,4 +561,16 @@ padding: 10px 20px; font-size: 18px; } + + .password-image-section { + flex-direction: column; + align-items: center; + gap: 10px; + padding: 10px; + } + + .password-image { + width: 60px; + height: 60px; + } } diff --git a/css/phone.css b/css/phone.css index 73a6815..e6154e1 100644 --- a/css/phone.css +++ b/css/phone.css @@ -1,4 +1,38 @@ /* Phone Messages Minigame Styles */ + +.phone-image-section { + display: flex; + align-items: center; + gap: 20px; + padding: 20px; + margin-bottom: 20px; +} + +.phone-image { + width: 80px; + height: 80px; + object-fit: contain; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + border: 2px solid rgba(255, 255, 255, 0.3); + background: rgba(0, 0, 0, 0.3); +} + +.phone-info h4 { + font-family: 'Press Start 2P', monospace; + font-size: 20px; + margin: 0 0 10px 0; + color: #3498db; +} + +.phone-info p { + font-size: 20px; + margin: 0; + color: #ecf0f1; + line-height: 1.4; +} + .phone-messages-container { display: flex; flex-direction: column; @@ -7,7 +41,6 @@ max-width: 400px; margin: 0 auto; background: #a0a0ad; - border: 4px solid #333; clip-path: polygon( 0px calc(100% - 10px), 2px calc(100% - 10px), @@ -469,3 +502,4 @@ padding: 5px; font-weight: bold; } + diff --git a/js/minigames/container/container-minigame.js b/js/minigames/container/container-minigame.js index 94e5e93..fe77de1 100644 --- a/js/minigames/container/container-minigame.js +++ b/js/minigames/container/container-minigame.js @@ -92,7 +92,6 @@ export class ContainerMinigame extends MinigameScene {
${this.isTakeable ? '' : ''} -
`; @@ -100,6 +99,15 @@ export class ContainerMinigame extends MinigameScene { createDesktopUI() { this.gameContainer.innerHTML = ` +
+ ${this.containerItem.scenarioData.name} +
+

${this.containerItem.scenarioData.name}

+

${this.containerItem.scenarioData.observations || ''}

+
+
@@ -115,13 +123,8 @@ export class ContainerMinigame extends MinigameScene { ` : ''}
-
- ${this.containerItem.scenarioData.name} - ${this.containerItem.scenarioData.observations || ''} -
${this.isTakeable ? '' : ''} -
diff --git a/js/minigames/lockpicking/lockpicking-game-phaser.js b/js/minigames/lockpicking/lockpicking-game-phaser.js index 54709cf..8c5cd3c 100644 --- a/js/minigames/lockpicking/lockpicking-game-phaser.js +++ b/js/minigames/lockpicking/lockpicking-game-phaser.js @@ -251,9 +251,37 @@ export class LockpickingMinigamePhaser extends MinigameScene {

Apply tension and hold click on pins to lift them to the shear line

`; + // Create the lockable item display section if item info is provided + this.createLockableItemDisplay(); + this.setupPhaserGame(); } + createLockableItemDisplay() { + // Create display for the locked item (door, chest, etc.) + const itemName = this.params?.itemName || this.lockable || 'Locked Item'; + const itemImage = this.params?.itemImage || null; + const itemObservations = this.params?.itemObservations || ''; + + if (!itemImage) return; // Only create if image is provided + + // Create container for the item display + const itemDisplayDiv = document.createElement('div'); + itemDisplayDiv.className = 'lockpicking-item-section'; + itemDisplayDiv.innerHTML = ` + ${itemName} +
+

${itemName}

+

${itemObservations}

+
+ `; + + // Insert before the game container + this.gameContainer.parentElement.insertBefore(itemDisplayDiv, this.gameContainer); + } + setupPhaserGame() { // Create a container for the Phaser game this.gameContainer.innerHTML = ` @@ -3108,6 +3136,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { if (!this.lockState.tensionApplied) { this.updateFeedback("Apply tension first before picking pins"); + this.flashWrenchRed(); } }); @@ -3231,6 +3260,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { if (!this.lockState.tensionApplied) { this.updateFeedback("Apply tension first before picking pins"); + this.flashWrenchRed(); } } } @@ -4418,4 +4448,37 @@ export class LockpickingMinigamePhaser extends MinigameScene { } return array; } + + flashWrenchRed() { + // Flash the tension wrench red to indicate tension is needed + if (!this.wrenchGraphics) return; + + const originalFillStyle = this.lockState.tensionApplied ? 0x00ff00 : 0x888888; + + // Store original state + const originalClear = this.wrenchGraphics.clear.bind(this.wrenchGraphics); + + // Flash red 3 times + for (let i = 0; i < 3; i++) { + this.scene.time.delayedCall(i * 150, () => { + this.wrenchGraphics.clear(); + this.wrenchGraphics.fillStyle(0xff0000); // Red + + // Long vertical arm + this.wrenchGraphics.fillRect(0, -120, 10, 170); + // Short horizontal arm + this.wrenchGraphics.fillRect(0, 40, 37.5, 10); + }); + + this.scene.time.delayedCall(i * 150 + 75, () => { + this.wrenchGraphics.clear(); + this.wrenchGraphics.fillStyle(originalFillStyle); // Back to original color + + // Long vertical arm + this.wrenchGraphics.fillRect(0, -120, 10, 170); + // Short horizontal arm + this.wrenchGraphics.fillRect(0, 40, 37.5, 10); + }); + } + } } \ No newline at end of file diff --git a/js/minigames/password/password-minigame.js b/js/minigames/password/password-minigame.js index 873ed89..10aa9ff 100644 --- a/js/minigames/password/password-minigame.js +++ b/js/minigames/password/password-minigame.js @@ -51,8 +51,43 @@ export class PasswordMinigame extends MinigameScene { setupPasswordInterface() { // Create the password entry interface + // Check if we can get the device image from sprite or params + const getImageData = () => { + // Try to get sprite data from params (from lockable object passed through minigame framework) + const sprite = this.params.sprite || this.params.lockable; + if (sprite && sprite.name && sprite.scenarioData) { + return { + imageFile: sprite.name, + deviceName: sprite.scenarioData.name || sprite.name, + observations: sprite.scenarioData.observations || '' + }; + } + // Fallback to explicit params if provided + if (this.params.deviceImage) { + return { + imageFile: this.params.deviceImage, + deviceName: this.params.deviceName || this.params.title || 'Device', + observations: this.params.observations || '' + }; + } + return null; + }; + + const imageData = getImageData(); + this.gameContainer.innerHTML = `
+ ${imageData ? ` +
+ ${imageData.deviceName} +
+

${imageData.deviceName}

+

${imageData.observations}

+
+
+ ` : ''}
diff --git a/js/minigames/phone/phone-messages-minigame.js b/js/minigames/phone/phone-messages-minigame.js index 3a5311e..884574c 100644 --- a/js/minigames/phone/phone-messages-minigame.js +++ b/js/minigames/phone/phone-messages-minigame.js @@ -328,7 +328,8 @@ export class PhoneMessagesMinigame extends MinigameScene { const notebookBtn = document.createElement('button'); notebookBtn.className = 'minigame-button'; notebookBtn.id = 'minigame-notebook'; - notebookBtn.innerHTML = 'Notebook Add to Notebook'; + notebookBtn.innerHTML = 'Notebook Add to Notebook'; + this.controlsElement.appendChild(notebookBtn); // Change cancel button text to "Close" @@ -347,7 +348,42 @@ export class PhoneMessagesMinigame extends MinigameScene { setupPhoneInterface() { // Create the phone interface + // Check if we can get the device image from sprite or params + const getImageData = () => { + // Try to get sprite data from params (from lockable object passed through minigame framework) + const sprite = this.params.sprite || this.params.lockable; + if (sprite && sprite.name && sprite.scenarioData) { + return { + imageFile: sprite.name, + deviceName: sprite.scenarioData.name || sprite.name, + observations: sprite.scenarioData.observations || '' + }; + } + // Fallback to explicit params if provided + if (this.params.deviceImage) { + return { + imageFile: this.params.deviceImage, + deviceName: this.params.deviceName || this.params.title || 'Device', + observations: this.params.observations || '' + }; + } + return null; + }; + + const imageData = getImageData(); + this.gameContainer.innerHTML = ` + ${imageData ? ` +
+ ${imageData.deviceName} +
+

${imageData.deviceName}

+

${imageData.observations}

+
+
+ ` : ''}
@@ -393,10 +429,7 @@ export class PhoneMessagesMinigame extends MinigameScene {
- -
- -
+ `; // Get references to important elements @@ -406,7 +439,6 @@ export class PhoneMessagesMinigame extends MinigameScene { this.messageTime = document.getElementById('message-time'); this.messageContent = document.getElementById('message-content'); this.messageActions = document.getElementById('message-actions'); - this.phoneObservations = document.getElementById('phone-observations'); // Control buttons this.prevBtn = document.getElementById('prev-btn'); @@ -422,9 +454,7 @@ export class PhoneMessagesMinigame extends MinigameScene { // Populate messages this.populateMessages(); - - // Populate observations - this.populateObservations(); + } populateMessages() { @@ -444,13 +474,11 @@ export class PhoneMessagesMinigame extends MinigameScene { messageElement.className = `message-item ${message.type || 'text'}`; messageElement.dataset.index = index; - const icon = message.type === 'voice' ? '🎤' : '💬'; const preview = message.type === 'voice' ? (message.text ? message.text.substring(0, 50) + '...' : 'Voice message') : (message.text || 'No text content'); messageElement.innerHTML = ` -
${icon}
${message.sender || 'Unknown'}
${preview}
@@ -463,22 +491,7 @@ export class PhoneMessagesMinigame extends MinigameScene { }); } - populateObservations() { - // Get observations from the original object data - const observations = this.params?.observations || this.phoneData?.observations; - - if (observations) { - this.phoneObservations.innerHTML = ` -
-

Observations:

-

${observations}

-
- `; - } else { - this.phoneObservations.innerHTML = ''; - } - } - + setupEventListeners() { // Message list clicks this.addEventListener(this.messagesList, 'click', (event) => { @@ -788,7 +801,7 @@ export class PhoneMessagesMinigame extends MinigameScene { if (message.type === 'voice') { // For voice messages, show audio icon and transcript - content += `🎵 [Audio Message]\n`; + content += `[Audio Message]\n`; content += `Transcript: ${message.voice || message.text || 'No transcript available'}\n\n`; } else { // For text messages, show the content diff --git a/js/systems/interactions.js b/js/systems/interactions.js index 7c77ded..a1479dc 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -292,6 +292,7 @@ export function handleObjectInteraction(sprite) { title: data.name || 'Phone Messages', messages: messages, observations: data.observations, + lockable: sprite, onComplete: (success, result) => { console.log('Phone messages minigame completed:', success, result); } @@ -318,6 +319,7 @@ export function handleObjectInteraction(sprite) { fileContent: data.text, fileType: data.fileType || 'text', observations: data.observations, + lockable: sprite, source: data.source || 'Unknown Source', onComplete: (success, result) => { console.log('Text file minigame completed:', success, result); diff --git a/js/systems/minigame-starters.js b/js/systems/minigame-starters.js index c0b20da..1bec994 100644 --- a/js/systems/minigame-starters.js +++ b/js/systems/minigame-starters.js @@ -35,10 +35,49 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium', window.MinigameFramework.init(scene); } + // Extract item information from lockable object (handles both items and doors) + let itemName, itemImage, itemObservations; + + // Check if this is a door (has doorProperties) or an item + if (lockable?.doorProperties) { + // This is a door - get the connected room name + const connectedRoomId = lockable.doorProperties.connectedRoom; + const currentRoomId = lockable.doorProperties.roomId; + const gameScenario = window.gameScenario; + const connectedRoom = gameScenario?.rooms?.[connectedRoomId]; + const currentRoom = gameScenario?.rooms?.[currentRoomId]; + const isLocked = lockable.doorProperties.locked; + + // Use door_sign if available (player-visible sign on the door) + const doorSignOrName = connectedRoom?.door_sign || connectedRoom?.name; + + // Format item name with locked status + if (doorSignOrName) { + // Has door_sign or room name - show it + itemName = isLocked ? `Locked ${doorSignOrName}` : doorSignOrName; + itemObservations = `Door to ${doorSignOrName}`; + } else { + // No door_sign and undiscovered room - use generic names + itemName = 'Locked door'; + const currentRoomName = currentRoom?.name || currentRoomId; + itemObservations = `A door leading out of ${currentRoomName}`; + } + + itemImage = 'assets/tiles/door.png'; // Use default door image + } else { + // This is a regular item - use scenarioData + itemName = lockable?.scenarioData?.name || lockable?.name || 'Locked Item'; + itemImage = lockable?.name ? `assets/objects/${lockable.name}.png` : null; + itemObservations = lockable?.scenarioData?.observations || ''; + } + // Start the lockpicking minigame (Phaser version) window.MinigameFramework.startMinigame('lockpicking', null, { lockable: lockable, difficulty: difficulty, + itemName: itemName, + itemImage: itemImage, + itemObservations: itemObservations, cancelText: 'Close', onComplete: (success, result) => { if (success) { @@ -158,6 +197,42 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe console.log(`Using default lock configuration for ${lockId}:`, lockConfig); } + // Extract item information from lockable object (handles both items and doors) + let itemName, itemImage, itemObservations; + + // Check if this is a door (has doorProperties) or an item + if (lockable?.doorProperties) { + // This is a door - get the connected room name + const connectedRoomId = lockable.doorProperties.connectedRoom; + const currentRoomId = lockable.doorProperties.roomId; + const gameScenario = window.gameScenario; + const connectedRoom = gameScenario?.rooms?.[connectedRoomId]; + const currentRoom = gameScenario?.rooms?.[currentRoomId]; + const isLocked = lockable.doorProperties.locked; + + // Use door_sign if available (player-visible sign on the door) + const doorSignOrName = connectedRoom?.door_sign || connectedRoom?.name; + + // Format item name with locked status + if (doorSignOrName) { + // Has door_sign or room name - show it + itemName = isLocked ? `${doorSignOrName}` : doorSignOrName; + itemObservations = `Door to ${doorSignOrName}`; + } else { + // No door_sign and undiscovered room - use generic names + itemName = 'Locked door'; + const currentRoomName = currentRoom?.name || currentRoomId; + itemObservations = `A door leading out of ${currentRoomName}`; + } + + itemImage = 'assets/tiles/door.png'; // Use default door image + } else { + // This is a regular item - use scenarioData + itemName = lockable?.scenarioData?.name || lockable?.name || 'Locked Item'; + itemImage = lockable?.name ? `assets/objects/${lockable.name}.png` : null; + itemObservations = lockable?.scenarioData?.observations || ''; + } + // Start the key selection minigame window.MinigameFramework.startMinigame('lockpicking', null, { keyMode: true, @@ -167,6 +242,9 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe pinCount: lockConfig.pinCount, predefinedPinHeights: lockConfig.pinHeights, // Pass the predefined pin heights difficulty: lockConfig.difficulty, + itemName: itemName, + itemImage: itemImage, + itemObservations: itemObservations, cancelText: 'Close', onComplete: (success, result) => { if (success) { @@ -299,6 +377,7 @@ export function startPasswordMinigame(lockable, type, correctPassword, callback, maxAttempts: options.maxAttempts || 3, postitNote: options.postitNote || '', showPostit: options.showPostit || false, + lockable: lockable, onComplete: (success, result) => { if (success) { console.log('PASSWORD MINIGAME SUCCESS'); diff --git a/scenarios/ceo_exfil.json b/scenarios/ceo_exfil.json index ca9fb76..71a2e10 100644 --- a/scenarios/ceo_exfil.json +++ b/scenarios/ceo_exfil.json @@ -1,5 +1,5 @@ { - "scenario_brief": "You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.", + "scenario_brief": "Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.", "startRoom": "reception", "rooms": { "reception": { @@ -95,6 +95,7 @@ "lockType": "key", "requires": "office1_key:40,35,38,32,10", "difficulty": "easy", + "door_sign": "4A Hot Desks", "connections": { "north": ["office2", "office3"],