diff --git a/assets/icons/keyway.png b/assets/icons/keyway.png new file mode 100644 index 0000000..e84da80 Binary files /dev/null and b/assets/icons/keyway.png differ diff --git a/assets/icons/password.png b/assets/icons/password.png new file mode 100644 index 0000000..19668b5 Binary files /dev/null and b/assets/icons/password.png differ diff --git a/assets/icons/pin.png b/assets/icons/pin.png new file mode 100644 index 0000000..224517e Binary files /dev/null and b/assets/icons/pin.png differ diff --git a/assets/objects/pc.png b/assets/objects/pc.png new file mode 100644 index 0000000..e583626 Binary files /dev/null and b/assets/objects/pc.png differ diff --git a/assets/objects/pc13.png b/assets/objects/pc13.png deleted file mode 100644 index 8a3cd80..0000000 Binary files a/assets/objects/pc13.png and /dev/null differ diff --git a/css/container-minigame.css b/css/container-minigame.css index 3168e3e..b93b2d1 100644 --- a/css/container-minigame.css +++ b/css/container-minigame.css @@ -319,11 +319,9 @@ gap: 10px; padding: 15px; background: rgba(0, 0, 0, 0.3); - border-radius: 10px; - border: 1px solid rgba(255, 255, 255, 0.1); + border: 2px solid rgba(255, 255, 255, 0.1); min-height: 120px; max-height: 200px; - overflow-y: auto; } .container-contents-grid::-webkit-scrollbar { diff --git a/css/main.css b/css/main.css index 1b90390..0896212 100644 --- a/css/main.css +++ b/css/main.css @@ -47,7 +47,6 @@ body { background: rgba(0, 0, 0, 0.95); z-index: 2000; pointer-events: auto; - border: 4px solid #444; clip-path: polygon( 0px calc(100% - 10px), 2px calc(100% - 10px), @@ -91,48 +90,8 @@ body { .laptop-frame { background: transparent; - border: 2px solid #444; - clip-path: polygon( - 0px calc(100% - 10px), - 2px calc(100% - 10px), - 2px calc(100% - 6px), - 4px calc(100% - 6px), - 4px calc(100% - 4px), - 6px calc(100% - 4px), - 6px calc(100% - 2px), - 10px calc(100% - 2px), - 10px 100%, - calc(100% - 10px) 100%, - calc(100% - 10px) calc(100% - 2px), - calc(100% - 6px) calc(100% - 2px), - calc(100% - 6px) calc(100% - 4px), - calc(100% - 4px) calc(100% - 4px), - calc(100% - 4px) calc(100% - 6px), - calc(100% - 2px) calc(100% - 6px), - calc(100% - 2px) calc(100% - 10px), - 100% calc(100% - 10px), - 100% 10px, - calc(100% - 2px) 10px, - calc(100% - 2px) 6px, - calc(100% - 4px) 6px, - calc(100% - 4px) 4px, - calc(100% - 6px) 4px, - calc(100% - 6px) 2px, - calc(100% - 10px) 2px, - calc(100% - 10px) 0px, - 10px 0px, - 10px 2px, - 6px 2px, - 6px 4px, - 4px 4px, - 4px 6px, - 2px 6px, - 2px 10px, - 0px 10px - ); - padding: 20px; width: 100%; - height: calc(100% - 40px); + height: calc(100%); position: relative; } @@ -140,45 +99,8 @@ body { width: 100%; height: 100%; background: #1a1a1a; - border: 2px solid #333; - clip-path: polygon( - 0px calc(100% - 10px), - 2px calc(100% - 10px), - 2px calc(100% - 6px), - 4px calc(100% - 6px), - 4px calc(100% - 4px), - 6px calc(100% - 4px), - 6px calc(100% - 2px), - 10px calc(100% - 2px), - 10px 100%, - calc(100% - 10px) 100%, - calc(100% - 10px) calc(100% - 2px), - calc(100% - 6px) calc(100% - 2px), - calc(100% - 6px) calc(100% - 4px), - calc(100% - 4px) calc(100% - 4px), - calc(100% - 4px) calc(100% - 6px), - calc(100% - 2px) calc(100% - 6px), - calc(100% - 2px) calc(100% - 10px), - 100% calc(100% - 10px), - 100% 10px, - calc(100% - 2px) 10px, - calc(100% - 2px) 6px, - calc(100% - 4px) 6px, - calc(100% - 4px) 4px, - calc(100% - 6px) 4px, - calc(100% - 6px) 2px, - calc(100% - 10px) 2px, - calc(100% - 10px) 0px, - 10px 0px, - 10px 2px, - 6px 2px, - 6px 4px, - 4px 4px, - 4px 6px, - 2px 6px, - 2px 10px, - 0px 10px - ); + /* border: 2px solid #333; */ + display: flex; flex-direction: column; overflow: hidden; @@ -194,6 +116,7 @@ body { border-bottom: 1px solid #444; font-family: 'VT323', monospace; font-size: 18px; + min-height: 25px; } .title-bar .close-btn { diff --git a/index.html b/index.html index b837468..fa60435 100644 --- a/index.html +++ b/index.html @@ -72,7 +72,7 @@
Crypto Workstation - +
diff --git a/js/core/game.js b/js/core/game.js index f2f6936..3635623 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -63,6 +63,9 @@ export function preload() { this.load.image('fingerprint', 'assets/objects/fingerprint_small.png'); this.load.image('lockpick', 'assets/objects/lockpick.png'); this.load.image('spoofing_kit', 'assets/objects/office-misc-headphones.png'); + this.load.image('keyway', 'assets/icons/keyway.png'); + this.load.image('password', 'assets/icons/password.png'); + this.load.image('pin', 'assets/icons/pin.png'); // Load new object sprites from Tiled map tileset // These are the key objects that appear in the new room_reception2.json diff --git a/js/minigames/lockpicking/lockpicking-game-phaser.js b/js/minigames/lockpicking/lockpicking-game-phaser.js index 8c5cd3c..5615609 100644 --- a/js/minigames/lockpicking/lockpicking-game-phaser.js +++ b/js/minigames/lockpicking/lockpicking-game-phaser.js @@ -59,6 +59,13 @@ export class LockpickingMinigamePhaser extends MinigameScene { this.skipStartingKey = params.skipStartingKey || false; // Skip creating initial key if true this.keySelectionMode = false; // Track if we're in key selection mode + // Mode switching settings + this.canSwitchToPickMode = params.canSwitchToPickMode || false; // Allow switching from key to pick mode + this.inventoryKeys = params.inventoryKeys || null; // Stored for mode switching + this.requirefKeyId = params.requiredKeyId || null; // Track required key ID + this.canSwitchToKeyMode = params.canSwitchToKeyMode || false; // Allow switching from lockpick to key mode + this.availableKeys = params.availableKeys || null; // Keys available for mode switching + // Sound effects this.sounds = {}; @@ -74,7 +81,9 @@ export class LockpickingMinigamePhaser extends MinigameScene { thresholdSensitivity: this.thresholdSensitivity, highlightBindingOrder: this.highlightBindingOrder, highlightPinAlignment: this.highlightPinAlignment, - liftSpeed: this.liftSpeed + liftSpeed: this.liftSpeed, + canSwitchToPickMode: this.canSwitchToPickMode, + canSwitchToKeyMode: this.canSwitchToKeyMode }); this.pins = []; @@ -278,6 +287,44 @@ export class LockpickingMinigamePhaser extends MinigameScene {
`; + // Add mode switch button if applicable + if (this.canSwitchToPickMode && this.keyMode) { + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = ` + display: flex; + gap: 10px; + margin-top: 10px; + justify-content: center; + `; + + const switchModeBtn = document.createElement('button'); + switchModeBtn.className = 'minigame-button'; + switchModeBtn.id = 'lockpicking-switch-mode-btn'; + switchModeBtn.innerHTML = 'Lockpick Switch to Lockpicking'; + switchModeBtn.onclick = () => this.switchToPickMode(); + + buttonContainer.appendChild(switchModeBtn); + itemDisplayDiv.appendChild(buttonContainer); + } else if (this.canSwitchToKeyMode && !this.keyMode) { + // Show switch to key mode button when in lockpicking mode + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = ` + display: flex; + gap: 10px; + margin-top: 10px; + justify-content: center; + `; + + const switchModeBtn = document.createElement('button'); + switchModeBtn.className = 'minigame-button'; + switchModeBtn.id = 'lockpicking-switch-to-keys-btn'; + switchModeBtn.innerHTML = 'Key Switch to Key Mode'; + switchModeBtn.onclick = () => this.switchToKeyMode(); + + buttonContainer.appendChild(switchModeBtn); + itemDisplayDiv.appendChild(buttonContainer); + } + // Insert before the game container this.gameContainer.parentElement.insertBefore(itemDisplayDiv, this.gameContainer); } @@ -4481,4 +4528,143 @@ export class LockpickingMinigamePhaser extends MinigameScene { }); } } + + switchToPickMode() { + // Switch from key selection mode to lockpicking mode + console.log('Switching from key mode to lockpicking mode'); + + // Hide the mode switch button + const switchBtn = document.getElementById('lockpicking-switch-mode-btn'); + if (switchBtn) { + switchBtn.style.display = 'none'; + } + + // Exit key mode + this.keyMode = false; + this.keySelectionMode = false; + + // Clean up key selection UI if visible + if (this.keySelectionContainer) { + this.keySelectionContainer.destroy(); + this.keySelectionContainer = null; + } + + // Clean up any key visuals + if (this.keyGroup) { + this.keyGroup.destroy(); + this.keyGroup = null; + } + if (this.keyClickZone) { + this.keyClickZone.destroy(); + this.keyClickZone = null; + } + + // Show lockpicking tools + if (this.tensionWrench) { + this.tensionWrench.setVisible(true); + } + if (this.hookGroup) { + this.hookGroup.setVisible(true); + } + if (this.wrenchText) { + this.wrenchText.setVisible(true); + } + if (this.hookPickLabel) { + this.hookPickLabel.setVisible(true); + } + + // Reset pins to original positions + this.resetPinsToOriginalPositions(); + + // Update feedback + this.updateFeedback("Lockpicking mode - Apply tension first, then lift pins in binding order"); + } + + showLockpickingTools() { + // Show tension wrench and hook pick in lockpicking mode + if (this.tensionWrench) { + this.tensionWrench.setVisible(true); + } + if (this.hookGroup) { + this.hookGroup.setVisible(true); + } + + // Show labels + if (this.wrenchText) { + this.wrenchText.setVisible(true); + } + if (this.hookPickLabel) { + this.hookPickLabel.setVisible(true); + } + } + + switchToKeyMode() { + // Switch from lockpicking mode to key selection mode + console.log('Switching from lockpicking mode to key mode'); + + // Hide the mode switch button + const switchBtn = document.getElementById('lockpicking-switch-to-keys-btn'); + if (switchBtn) { + switchBtn.style.display = 'none'; + } + + // Enter key mode + this.keyMode = true; + this.keySelectionMode = true; + + // Hide lockpicking tools + if (this.tensionWrench) { + this.tensionWrench.setVisible(false); + } + if (this.hookGroup) { + this.hookGroup.setVisible(false); + } + if (this.wrenchText) { + this.wrenchText.setVisible(false); + } + if (this.hookPickLabel) { + this.hookPickLabel.setVisible(false); + } + + // Reset pins to original positions + this.resetPinsToOriginalPositions(); + + // Add mode switch back button (can switch back to lockpicking if available) + if (this.canSwitchToPickMode) { + const itemDisplayDiv = document.querySelector('.lockpicking-item-section'); + if (itemDisplayDiv) { + // Remove any existing button container + const existingButtonContainer = itemDisplayDiv.querySelector('div[style*="margin-top"]'); + if (existingButtonContainer) { + existingButtonContainer.remove(); + } + + // Add new button container + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = ` + display: flex; + gap: 10px; + margin-top: 10px; + justify-content: center; + `; + + const switchModeBtn = document.createElement('button'); + switchModeBtn.className = 'minigame-button'; + switchModeBtn.id = 'lockpicking-switch-mode-btn'; + switchModeBtn.innerHTML = 'Lockpick Switch to Lockpicking'; + switchModeBtn.onclick = () => this.switchToPickMode(); + + buttonContainer.appendChild(switchModeBtn); + itemDisplayDiv.appendChild(buttonContainer); + } + } + + // Show key selection UI with available keys + if (this.availableKeys && this.availableKeys.length > 0) { + this.createKeySelectionUI(this.availableKeys, this.requiredKeyId); + this.updateFeedback("Select a key to use"); + } else { + this.updateFeedback("No keys available"); + } + } } \ No newline at end of file diff --git a/js/systems/interactions.js b/js/systems/interactions.js index a1479dc..8c589fc 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -55,6 +55,11 @@ export function checkObjectInteractions() { if (obj.isHighlighted) { obj.isHighlighted = false; obj.clearTint(); + // Clean up interaction sprite if exists + if (obj.interactionIndicator) { + obj.interactionIndicator.destroy(); + delete obj.interactionIndicator; + } } return; } @@ -69,6 +74,11 @@ export function checkObjectInteractions() { if (obj.isHighlighted) { obj.isHighlighted = false; obj.clearTint(); + // Clean up interaction sprite if exists + if (obj.interactionIndicator) { + obj.interactionIndicator.destroy(); + delete obj.interactionIndicator; + } } return; } @@ -82,15 +92,167 @@ export function checkObjectInteractions() { if (!obj.isHighlighted) { obj.isHighlighted = true; obj.setTint(0x4da6ff); // Blue tint for interactable objects + // Add interaction indicator sprite + addInteractionIndicator(obj); } } else if (obj.isHighlighted) { obj.isHighlighted = false; obj.clearTint(); + // Clean up interaction sprite if exists + if (obj.interactionIndicator) { + obj.interactionIndicator.destroy(); + delete obj.interactionIndicator; + } } }); + + // Also check door sprites + if (room.doorSprites) { + Object.values(room.doorSprites).forEach(door => { + // Skip inactive or non-locked doors + if (!door.active || !door.doorProperties || !door.doorProperties.locked) { + // Clear highlight if door was previously highlighted + if (door.isHighlighted) { + door.isHighlighted = false; + door.clearTint(); + // Clean up interaction sprite if exists + if (door.interactionIndicator) { + door.interactionIndicator.destroy(); + delete door.interactionIndicator; + } + } + return; + } + + // Skip doors outside viewport for performance (if viewport bounds available) + if (viewBounds && ( + door.x < viewBounds.left || + door.x > viewBounds.right || + door.y < viewBounds.top || + door.y > viewBounds.bottom)) { + // Clear highlight if door is outside viewport + if (door.isHighlighted) { + door.isHighlighted = false; + door.clearTint(); + // Clean up interaction sprite if exists + if (door.interactionIndicator) { + door.interactionIndicator.destroy(); + delete door.interactionIndicator; + } + } + return; + } + + // Use squared distance for performance + const dx = px - door.x; + const dy = py - door.y; + const distanceSq = dx * dx + dy * dy; + + if (distanceSq <= INTERACTION_RANGE_SQ) { + if (!door.isHighlighted) { + door.isHighlighted = true; + door.setTint(0x4da6ff); // Blue tint for locked doors + // Add interaction indicator sprite for doors + addInteractionIndicator(door); + } + } else if (door.isHighlighted) { + door.isHighlighted = false; + door.clearTint(); + // Clean up interaction sprite if exists + if (door.interactionIndicator) { + door.interactionIndicator.destroy(); + delete door.interactionIndicator; + } + } + }); + } }); } +function getInteractionSpriteKey(obj) { + // Determine which sprite to show based on the object's interaction type + + // Check for doors first (they may not have scenarioData) + if (obj.doorProperties) { + if (obj.doorProperties.locked) { + // Check door lock type + const lockType = obj.doorProperties.lockType; + if (lockType === 'password') return 'password'; + if (lockType === 'pin') return 'pin'; + return 'keyway'; // Default to keyway for key locks or unknown types + } + return null; // Unlocked doors don't need overlay + } + + if (!obj || !obj.scenarioData) { + return null; + } + + const data = obj.scenarioData; + + // Check for locked containers and items + if (data.locked === true) { + // Check specific lock type + const lockType = data.lockType; + if (lockType === 'password') return 'password'; + if (lockType === 'pin') return 'pin'; + if (lockType === 'biometric') return 'fingerprint'; + // Default to keyway for key locks or unknown types + return 'keyway'; + } + + // Check for containers with contents (even if not locked yet) + if (data.contents) { + return 'keyway'; + } + + // Check for fingerprint collection + if (data.hasFingerprint === true) { + return 'fingerprint'; + } + + return null; +} + +function addInteractionIndicator(obj) { + // Only add indicator if we have a game instance and the object has a scene + if (!gameRef || !obj.scene || !obj.scene.add) { + return; + } + + const spriteKey = getInteractionSpriteKey(obj); + if (!spriteKey) return; + + // Create indicator sprite centered over the object + try { + // Get the center of the parent sprite, accounting for its origin + const center = obj.getCenter(); + + // Position indicator above the object (accounting for parent's display height) + const indicatorX = center.x; + const indicatorY = center.y; // Position above with 10px offset + + const indicator = obj.scene.add.image(indicatorX, indicatorY, spriteKey); + indicator.setDepth(999); // High depth to appear on top + indicator.setOrigin(0.5, 0.5); // Center the sprite + // indicator.setScale(0.5); // Scale down to be less intrusive + + // Add pulsing animation + obj.scene.tweens.add({ + targets: indicator, + alpha: { from: 1, to: 0.5 }, + duration: 800, + yoyo: true, + repeat: -1 + }); + + // Store reference for cleanup + obj.interactionIndicator = indicator; + } catch (error) { + console.warn('Failed to add interaction indicator:', error); + } +} + export function handleObjectInteraction(sprite) { console.log('OBJECT INTERACTION', { name: sprite.name, diff --git a/js/systems/minigame-starters.js b/js/systems/minigame-starters.js index 1bec994..27a6a9e 100644 --- a/js/systems/minigame-starters.js +++ b/js/systems/minigame-starters.js @@ -79,6 +79,44 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium', itemImage: itemImage, itemObservations: itemObservations, cancelText: 'Close', + canSwitchToKeyMode: window.inventory.items.some(item => + item && item.scenarioData && + item.scenarioData.type === 'key' + ), + availableKeys: (() => { + // Collect all available keys for mode switching + const keys = []; + + // Individual keys + const individualKeys = window.inventory.items.filter(item => + item && item.scenarioData && + item.scenarioData.type === 'key' + ); + individualKeys.forEach(key => { + keys.push({ + id: key.scenarioData.key_id, + name: key.scenarioData.name, + cuts: key.scenarioData.cuts || [] + }); + }); + + // Keys from key ring + const keyRingItem = window.inventory.items.find(item => + item && item.scenarioData && + item.scenarioData.type === 'key_ring' + ); + if (keyRingItem && keyRingItem.scenarioData.allKeys) { + keyRingItem.scenarioData.allKeys.forEach(keyData => { + keys.push({ + id: keyData.key_id, + name: keyData.name, + cuts: keyData.cuts || [] + }); + }); + } + + return keys.length > 0 ? keys : null; + })(), onComplete: (success, result) => { if (success) { console.log('LOCKPICK SUCCESS'); @@ -246,6 +284,12 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe itemImage: itemImage, itemObservations: itemObservations, cancelText: 'Close', + canSwitchToPickMode: window.inventory.items.some(item => + item && item.scenarioData && + item.scenarioData.type === 'lockpick' + ), + inventoryKeys: keysToShow, + requiredKeyId: requiredKeyId, onComplete: (success, result) => { if (success) { console.log('KEY SELECTION SUCCESS'); diff --git a/js/systems/unlock-system.js b/js/systems/unlock-system.js index 45fb293..4f104be 100644 --- a/js/systems/unlock-system.js +++ b/js/systems/unlock-system.js @@ -82,39 +82,35 @@ export function handleUnlock(lockable, type) { playerKeys = playerKeys.concat(keyRingKeys); } + // Check for lockpick kit + const hasLockpick = window.inventory.items.some(item => + item && item.scenarioData && + item.scenarioData.type === 'lockpick' + ); + if (playerKeys.length > 0) { - // Show key selection interface + // Keys take priority - go straight to key selection + console.log('KEYS AVAILABLE - STARTING KEY SELECTION'); startKeySelectionMinigame(lockable, type, playerKeys, requiredKey, unlockTarget); - } else { - // Check for lockpick kit - const hasLockpick = window.inventory.items.some(item => - item && item.scenarioData && - item.scenarioData.type === 'lockpick' - ); + } else if (hasLockpick) { + // Only lockpick available - launch lockpicking minigame directly + console.log('LOCKPICK AVAILABLE - STARTING LOCKPICKING MINIGAME'); + let difficulty = lockable.scenarioData?.difficulty || lockable.properties?.difficulty || 'medium'; - if (hasLockpick) { - console.log('LOCKPICK AVAILABLE'); - if (confirm("Would you like to attempt picking this lock?")) { - let difficulty = lockable.scenarioData?.difficulty || lockable.properties?.difficulty || 'medium'; - - console.log('STARTING LOCKPICK MINIGAME', { difficulty }); - startLockpickingMinigame(lockable, window.game, difficulty, (success) => { - if (success) { - // Small delay to ensure minigame cleanup completes - setTimeout(() => { - unlockTarget(lockable, type, lockable.layer); - window.gameAlert(`Successfully picked the lock!`, 'success', 'Lock Picked', 4000); - }, 100); - } else { - console.log('LOCKPICK FAILED'); - window.gameAlert('Failed to pick the lock. Try again.', 'error', 'Pick Failed', 3000); - } - }); + startLockpickingMinigame(lockable, window.game, difficulty, (success) => { + if (success) { + setTimeout(() => { + unlockTarget(lockable, type, lockable.layer); + window.gameAlert(`Successfully picked the lock!`, 'success', 'Lock Picked', 4000); + }, 100); + } else { + console.log('LOCKPICK FAILED'); + window.gameAlert('Failed to pick the lock. Try again.', 'error', 'Pick Failed', 3000); } - } else { - console.log('NO KEYS OR LOCKPICK AVAILABLE'); - window.gameAlert(`Requires key: ${requiredKey}`, 'error', 'Locked', 4000); - } + }); + } else { + console.log('NO KEYS OR LOCKPICK AVAILABLE'); + window.gameAlert(`Requires key: ${requiredKey}`, 'error', 'Locked', 4000); } break;