diff --git a/js/minigames/lockpicking/key-operations.js b/js/minigames/lockpicking/key-operations.js index cbfa1c5..07419fe 100644 --- a/js/minigames/lockpicking/key-operations.js +++ b/js/minigames/lockpicking/key-operations.js @@ -256,10 +256,15 @@ export class KeyOperations { // Create a visual representation of a key for the selection UI by building the actual key and scaling it down const keyContainer = this.parent.scene.add.container(0, 0); - // Save the original key data before temporarily changing it + // Save the original key data and pin count before temporarily changing them const originalKeyData = this.parent.keyData; + const originalPinCount = this.parent.pinCount; - // Temporarily set the key data to create the key + // Temporarily set the key data and pin count to create this specific key + this.parent.keyData = keyData; + this.parent.pinCount = keyData.pinCount || 5; + + // Create the key with this specific key data this.createKey(); // Get the key group and scale it down @@ -295,8 +300,9 @@ export class KeyOperations { keyContainer.add(keyGroup); } - // Restore the original key data + // Restore the original key data and pin count this.parent.keyData = originalKeyData; + this.parent.pinCount = originalPinCount; return keyContainer; } diff --git a/js/minigames/lockpicking/key-selection.js b/js/minigames/lockpicking/key-selection.js index 5505f46..eda7628 100644 --- a/js/minigames/lockpicking/key-selection.js +++ b/js/minigames/lockpicking/key-selection.js @@ -44,8 +44,8 @@ export class KeySelection { // Generate a random key with the specified number of pins const cuts = []; for (let i = 0; i < pinCount; i++) { - // Generate random cut depth between 20-80 (avoiding extremes) - cuts.push(Math.floor(Math.random() * 60) + 20); + // Generate random cut depth between 25-65 (middle range for realistic key variation) + cuts.push(Math.floor(Math.random() * 40) + 25); } return { id: `random_key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, diff --git a/js/minigames/lockpicking/lockpicking-game-phaser.js b/js/minigames/lockpicking/lockpicking-game-phaser.js index 1c720dc..07c34e6 100644 --- a/js/minigames/lockpicking/lockpicking-game-phaser.js +++ b/js/minigames/lockpicking/lockpicking-game-phaser.js @@ -505,8 +505,9 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Create a UI for selecting between multiple keys // keys: array of key objects with id, cuts, and optional name properties // correctKeyId: ID of the correct key (if null, uses index 0 as fallback) + // Shows 3 keys at a time with navigation buttons for more than 3 keys - // Find the correct key index + // Find the correct key index in the original array let correctKeyIndex = 0; if (correctKeyId) { correctKeyIndex = keys.findIndex(key => key.id === correctKeyId); @@ -530,6 +531,23 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Reset pins to their original positions before showing key selection this.lockConfig.resetPinsToOriginalPositions(); + // Layout constants + const keyWidth = 140; + const keyHeight = 80; + const spacing = 20; + const padding = 20; + const labelHeight = 30; // Space for key label below each key + const keysPerPage = 3; // Always show 3 keys at a time + const buttonWidth = 30; + const buttonHeight = 30; + const buttonSpacing = 10; // Space between button and keys + + // Calculate container dimensions (always 3 keys wide + buttons on sides with minimal spacing) + // For 3 keys: [button] padding [key1] spacing [key2] spacing [key3] padding [button] + const keysWidth = (keysPerPage - 1) * (keyWidth + spacing) + keyWidth; // 3 keys with spacing between them + const containerWidth = keysWidth + (keys.length > keysPerPage ? buttonWidth * 2 + buttonSpacing * 2 + padding * 2 : padding * 2); + const containerHeight = keyHeight + labelHeight + spacing + padding * 2 + 50; // +50 for title + // Create container for key selection - positioned in the middle but below pins const keySelectionContainer = this.scene.add.container(0, 230); keySelectionContainer.setDepth(1000); // High z-index to appear above everything @@ -537,13 +555,14 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Add background const background = this.scene.add.graphics(); background.fillStyle(0x000000, 0.8); - background.fillRect(0, 0, 700, 180); + background.fillRect(0, 0, containerWidth, containerHeight); background.lineStyle(2, 0xffffff); - background.strokeRect(0, 0, 600, 170); + background.strokeRect(0, 0, containerWidth - 1, containerHeight - 1); keySelectionContainer.add(background); // Add title - const title = this.scene.add.text(300, 15, 'Select the correct key', { + const titleX = containerWidth / 2; + const title = this.scene.add.text(titleX, 15, 'Select the correct key', { fontSize: '24px', fill: '#ffffff', fontFamily: 'VT323', @@ -551,41 +570,171 @@ export class LockpickingMinigamePhaser extends MinigameScene { title.setOrigin(0.5, 0); keySelectionContainer.add(title); - // Create key options - const keyWidth = 140; - const keyHeight = 80; - const spacing = 20; - const startX = 50; - const startY = 50; + // Track current page + let currentPage = 0; + const totalPages = Math.ceil(keys.length / keysPerPage); - keys.forEach((keyData, index) => { - const keyX = startX + index * (keyWidth + spacing); - const keyY = startY; + // Create navigation buttons if more than 3 keys + let prevButton = null; + let nextButton = null; + let prevText = null; + let nextText = null; + let pageIndicator = null; + let itemsToRemoveNext = null; // Track items for cleanup + + // Create a function to render the current page of keys + const renderKeyPage = () => { + // Remove any existing key visuals and labels from the previous page + const itemsToRemove = []; + keySelectionContainer.list.forEach(item => { + if (item !== background && item !== title && item !== prevButton && item !== nextButton && item !== pageIndicator && item !== prevText && item !== nextText) { + itemsToRemove.push(item); + } + }); + itemsToRemove.forEach(item => item.destroy()); - // Create key visual representation - const keyVisual = this.keyOps.createKeyVisual(keyData, keyWidth, keyHeight); - keyVisual.setPosition(keyX, keyY); - keySelectionContainer.add(keyVisual); + // Calculate which keys to show on this page + const startIndex = currentPage * keysPerPage; + const endIndex = Math.min(startIndex + keysPerPage, keys.length); + const pageKeys = keys.slice(startIndex, endIndex); - // Make key clickable - keyVisual.setInteractive(new Phaser.Geom.Rectangle(0, 0, keyWidth, keyHeight), Phaser.Geom.Rectangle.Contains); - keyVisual.on('pointerdown', () => { - // Close the popup - keySelectionContainer.destroy(); - // Trigger key selection and insertion - this.keyOps.selectKey(index, correctKeyIndex, keyData); + // Display keys for this page + // Position: [button] buttonSpacing [keys] buttonSpacing [button] + const keysStartX = (keys.length > keysPerPage ? buttonWidth + buttonSpacing : padding); + const startX = keysStartX + padding / 2; + const startY = 50; + + pageKeys.forEach((keyData, pageIndex) => { + const actualIndex = startIndex + pageIndex; + const keyX = startX + pageIndex * (keyWidth + spacing); + const keyY = startY; + + // Create key visual representation + const keyVisual = this.keyOps.createKeyVisual(keyData, keyWidth, keyHeight); + keyVisual.setPosition(keyX, keyY); + keySelectionContainer.add(keyVisual); + + // Make key clickable + keyVisual.setInteractive(new Phaser.Geom.Rectangle(0, 0, keyWidth, keyHeight), Phaser.Geom.Rectangle.Contains); + keyVisual.on('pointerdown', () => { + // Close the popup + keySelectionContainer.destroy(); + // Trigger key selection and insertion + this.keyOps.selectKey(actualIndex, correctKeyIndex, keyData); + }); + + // Add key label (use name if available, otherwise use number) + const keyName = keyData.name || `Key ${actualIndex + 1}`; + const keyLabel = this.scene.add.text(keyX + keyWidth/2, keyY + keyHeight + 5, keyName, { + fontSize: '16px', + fill: '#ffffff', + fontFamily: 'VT323' + }); + keyLabel.setOrigin(0.5, 0); + keySelectionContainer.add(keyLabel); }); - // Add key label (use name if available, otherwise use number) - const keyName = keyData.name || `Key ${index + 1}`; - const keyLabel = this.scene.add.text(keyX + keyWidth/2, keyY + keyHeight + 5, keyName, { - fontSize: '16px', + // Update page indicator + if (pageIndicator) { + pageIndicator.setText(`${currentPage + 1}/${totalPages}`); + } + + // Update button visibility + if (prevButton) { + if (currentPage > 0) { + prevButton.setVisible(true); + prevText.setVisible(true); + } else { + prevButton.setVisible(false); + prevText.setVisible(false); + } + } + + if (nextButton) { + if (currentPage < totalPages - 1) { + nextButton.setVisible(true); + nextText.setVisible(true); + } else { + nextButton.setVisible(false); + nextText.setVisible(false); + } + } + }; + + if (keys.length > keysPerPage) { + // Position buttons on the sides of the keys, vertically centered + const keysAreaCenterY = 50 + (keyHeight + labelHeight) / 2; + + // Previous button (left side) + prevButton = this.scene.add.graphics(); + prevButton.fillStyle(0x444444); + prevButton.fillRect(0, 0, buttonWidth, buttonHeight); + prevButton.lineStyle(2, 0xffffff); + prevButton.strokeRect(0, 0, buttonWidth, buttonHeight); + prevButton.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonWidth, buttonHeight), Phaser.Geom.Rectangle.Contains); + prevButton.on('pointerdown', () => { + if (currentPage > 0) { + currentPage--; + renderKeyPage(); + } + }); + prevButton.setPosition(padding / 2, keysAreaCenterY - buttonHeight / 2); + prevButton.setDepth(1001); + prevButton.setVisible(false); // Initially hidden + keySelectionContainer.add(prevButton); + + // Previous button text + prevText = this.scene.add.text(padding / 2 + buttonWidth / 2, keysAreaCenterY, '‹', { + fontSize: '20px', fill: '#ffffff', fontFamily: 'VT323' }); - keyLabel.setOrigin(0.5, 0); - keySelectionContainer.add(keyLabel); - }); + prevText.setOrigin(0.5, 0.5); + prevText.setDepth(1002); + prevText.setVisible(false); // Initially hidden + keySelectionContainer.add(prevText); + + // Next button (right side) + nextButton = this.scene.add.graphics(); + nextButton.fillStyle(0x444444); + nextButton.fillRect(0, 0, buttonWidth, buttonHeight); + nextButton.lineStyle(2, 0xffffff); + nextButton.strokeRect(0, 0, buttonWidth, buttonHeight); + nextButton.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonWidth, buttonHeight), Phaser.Geom.Rectangle.Contains); + nextButton.on('pointerdown', () => { + if (currentPage < totalPages - 1) { + currentPage++; + renderKeyPage(); + } + }); + nextButton.setPosition(containerWidth - padding / 2 - buttonWidth, keysAreaCenterY - buttonHeight / 2); + nextButton.setDepth(1001); + nextButton.setVisible(false); // Initially hidden + keySelectionContainer.add(nextButton); + + // Next button text + nextText = this.scene.add.text(containerWidth - padding / 2 - buttonWidth / 2, keysAreaCenterY, '›', { + fontSize: '20px', + fill: '#ffffff', + fontFamily: 'VT323' + }); + nextText.setOrigin(0.5, 0.5); + nextText.setDepth(1002); + nextText.setVisible(false); // Initially hidden + keySelectionContainer.add(nextText); + + // Page indicator - centered below all keys + pageIndicator = this.scene.add.text(containerWidth / 2, containerHeight - 20, `1/${totalPages}`, { + fontSize: '12px', + fill: '#888888', + fontFamily: 'VT323' + }); + pageIndicator.setOrigin(0.5, 0.5); + keySelectionContainer.add(pageIndicator); + } + + // Render the first page + renderKeyPage(); this.keySelectionContainer = keySelectionContainer; } diff --git a/key-demo.html b/key-demo.html index b778c17..b1dbfbf 100644 --- a/key-demo.html +++ b/key-demo.html @@ -254,6 +254,9 @@
Key 3 (Partial)
Key 4 (Random)
Key 5 (Deep)
+
Key 6 (Shallow)
+
Key 7 (Mixed)
+
Key 8 (Reverse)
@@ -293,28 +296,43 @@ this.keyPresets = { key1: { name: "Key 1 (Correct)", - cuts: [25, 50, 75, 30, 60], // This will be overridden by auto-generation + cuts: [35, 50, 65, 40, 55], // This will be overridden by auto-generation description: "This key is auto-generated to match the actual pin heights" }, key2: { name: "Key 2 (Wrong)", - cuts: [10, 20, 30, 40, 50], + cuts: [25, 30, 35, 40, 45], description: "This key has cuts that don't match the pins" }, key3: { name: "Key 3 (Partial)", - cuts: [25, 50, 75, 40, 60], + cuts: [35, 50, 65, 45, 55], description: "This key has some correct cuts but not all" }, key4: { name: "Key 4 (Random)", - cuts: [15, 45, 80, 35, 55], + cuts: [28, 48, 62, 38, 52], description: "Random cut pattern - might work by chance" }, key5: { name: "Key 5 (Deep)", - cuts: [80, 90, 95, 85, 88], + cuts: [60, 63, 65, 62, 61], description: "Very deep cuts - probably too deep" + }, + key6: { + name: "Key 6 (Shallow)", + cuts: [25, 26, 27, 28, 29], + description: "Very shallow cuts - not deep enough" + }, + key7: { + name: "Key 7 (Mixed)", + cuts: [40, 52, 60, 35, 58], + description: "Similar cuts but slightly off in each position" + }, + key8: { + name: "Key 8 (Reverse)", + cuts: [55, 40, 65, 50, 35], + description: "Cuts in reverse order - interesting pattern" } }; @@ -388,7 +406,7 @@ // Generate new random keys for selection this.currentGame.keySelectionMode = true; // Mark that we're in key selection mode - this.currentGame.createKeysForChallenge('correct_key'); + this.currentGame.keySelection.createKeysForChallenge('correct_key'); // Update feedback document.getElementById('feedback').textContent = @@ -484,7 +502,16 @@ // If in key mode, automatically show key selection if (this.currentMode === 'key') { setTimeout(() => { - this.currentGame.startWithKeySelection(); + // Prepare all preset keys for selection + const allKeys = Object.values(this.keyPresets).map((preset, index) => ({ + id: `key_${index}`, + cuts: preset.cuts, + name: preset.name, + pinCount: preset.cuts.length + })); + + // Start with key selection showing all 8 keys + this.currentGame.startWithKeySelection(allKeys, 'key_0'); }, 500); // Small delay to ensure game is fully initialized } } catch (error) {