Refactor Lockpicking Minigame: Update button functionality for mode switching between key and lockpicking modes. Introduce new icons for keyway, password, and pin in the inventory. Enhance interaction logic for locked objects and doors, including visual indicators. Clean up CSS styles for improved layout and user experience. Remove unused assets and streamline code for better performance.

This commit is contained in:
Z. Cliffe Schreuders
2025-10-22 00:10:26 +01:00
parent 2230885f12
commit 48b1ad3bbf
13 changed files with 427 additions and 115 deletions

BIN
assets/icons/keyway.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

BIN
assets/icons/password.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

BIN
assets/icons/pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

BIN
assets/objects/pc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 B

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -72,7 +72,7 @@
<div class="laptop-screen">
<div class="title-bar">
<span>Crypto Workstation</span>
<button class="close-btn" onclick="closeLaptop()">×</button>
<button class="minigame-close-button" onclick="closeLaptop()">×</button>
</div>
<div id="cyberchef-container">
<iframe id="cyberchef-frame" src=""></iframe>

View File

@@ -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

View File

@@ -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 {
</div>
`;
// 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 = '<img src="assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> 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 = '<img src="assets/objects/key.png" alt="Key" class="icon-large"> 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 = '<img src="assets/objects/lockpick.png" alt="Lockpick" class="icon-large"> 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");
}
}
}

View File

@@ -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,

View File

@@ -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');

View File

@@ -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;