diff --git a/js/minigames/lockpicking/lockpicking-game-phaser.js b/js/minigames/lockpicking/lockpicking-game-phaser.js index d764191..30b95a9 100644 --- a/js/minigames/lockpicking/lockpicking-game-phaser.js +++ b/js/minigames/lockpicking/lockpicking-game-phaser.js @@ -130,7 +130,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Create a container for the Phaser game this.gameContainer.innerHTML = `
-
Ready to pick
+
`; this.feedback = this.gameContainer.querySelector('.lockpick-feedback'); @@ -256,13 +256,14 @@ export class LockpickingMinigamePhaser extends MinigameScene { this.tensionWrench.add(this.wrenchGraphics); - // Make it interactive - larger hit area to include horizontal arm - // Covers vertical arm, horizontal arm, and handle - this.tensionWrench.setInteractive(new Phaser.Geom.Rectangle(-12.5, -138.75, 60, 176.25), Phaser.Geom.Rectangle.Contains); + // Make it interactive - extended hit area to match pin click zones (down to keyway bottom) + // Covers vertical arm, horizontal arm, handle, and extends down to bottom of keyway + this.tensionWrench.setInteractive(new Phaser.Geom.Rectangle(-12.5, -138.75, 60, 268.75), Phaser.Geom.Rectangle.Contains); // Add text const wrenchText = this.scene.add.text(-10, 58, 'Tension Wrench', { - fontSize: '14px', + fontSize: '18px', + fontFamily: 'VT323', fill: '#00ff00', fontWeight: 'bold' }); @@ -280,7 +281,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Play tension sound if (this.sounds.tension) { this.sounds.tension.play(); - navigator.vibrate([200]); + navigator.vibrate([50]); } if (this.lockState.tensionApplied) { @@ -473,7 +474,8 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Add hook pick label const hookPickLabel = this.scene.add.text(-10, 85, 'Hook Pick', { - fontSize: '14px', + fontSize: '18px', + fontFamily: 'VT323', fill: '#00ff00', fontWeight: 'bold' }); @@ -885,7 +887,8 @@ export class LockpickingMinigamePhaser extends MinigameScene { if (i === 0) { // Spring label const springLabel = this.scene.add.text(pinX, pinY - 140, 'Spring', { - fontSize: '14px', + fontSize: '18px', + fontFamily: 'VT323', fill: '#00ff00', fontWeight: 'bold' }); @@ -893,8 +896,10 @@ export class LockpickingMinigamePhaser extends MinigameScene { springLabel.setDepth(100); // Bring to front // Driver pin label - positioned below the shear line - const driverPinLabel = this.scene.add.text(pinX, pinY - 35, 'Driver Pin', { - fontSize: '14px', + const driverPinX = 100 + margin + 1 * pinSpacing; // Pin index 1 (2nd pin) + const driverPinLabel = this.scene.add.text(driverPinX, pinY - 35, 'Driver Pin', { + fontSize: '18px', + fontFamily: 'VT323', fill: '#00ff00', fontWeight: 'bold' }); @@ -902,8 +907,10 @@ export class LockpickingMinigamePhaser extends MinigameScene { driverPinLabel.setDepth(100); // Bring to front // Key pin label - positioned at the middle of the key pin - const keyPinLabel = this.scene.add.text(pinX, pinY - 50 + driverPinLength + (keyPinLength / 2), 'Key Pin', { - fontSize: '14px', + const keyPinX = 100 + margin + 2 * pinSpacing; // Pin index 2 (3rd pin) + const keyPinLabel = this.scene.add.text(keyPinX, pinY - 50 + driverPinLength + (keyPinLength / 2), 'Key Pin', { + fontSize: '18px', + fontFamily: 'VT323', fill: '#00ff00', fontWeight: 'bold' }); @@ -953,7 +960,8 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Add pin number const pinText = this.scene.add.text(0, 40, (i + 1).toString(), { - fontSize: '14px', + fontSize: '18px', + fontFamily: 'VT323', fill: '#ffffff', fontWeight: 'bold' }); @@ -983,7 +991,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Play click sound if (this.sounds.click) { this.sounds.click.play(); - navigator.vibrate(200); + navigator.vibrate(50); } // Hide labels on first pin click @@ -1045,7 +1053,8 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Add shear line label const shearLineText = this.scene.add.text(503, 145, 'SHEAR LINE', { - fontSize: '14px', + fontSize: '12px', + fontFamily: 'VT323', fill: '#00ff00', fontWeight: 'bold' }); @@ -1075,6 +1084,187 @@ export class LockpickingMinigamePhaser extends MinigameScene { this.returnHookToStart(); } }); + + // Add keyboard bindings + this.scene.input.keyboard.on('keydown', (event) => { + const key = event.key; + + // Pin number keys (1-8) + if (key >= '1' && key <= '8') { + const pinIndex = parseInt(key) - 1; // Convert 1-8 to 0-7 + + // Check if pin exists + if (pinIndex < this.pinCount) { + const pin = this.pins[pinIndex]; + if (pin) { + // Simulate pin click + this.lockState.currentPin = pin; + this.gameState.mouseDown = true; + + // Play click sound + if (this.sounds.click) { + this.sounds.click.play(); + navigator.vibrate(50); + } + + // Hide labels on first pin click + if (!this.pinClicked) { + this.pinClicked = true; + if (this.wrenchText) { + this.wrenchText.setVisible(false); + } + if (this.shearLineText) { + this.shearLineText.setVisible(false); + } + if (this.hookPickLabel) { + this.hookPickLabel.setVisible(false); + } + if (this.springLabel) { + this.springLabel.setVisible(false); + } + if (this.driverPinLabel) { + this.driverPinLabel.setVisible(false); + } + if (this.keyPinLabel) { + this.keyPinLabel.setVisible(false); + } + + // Hide all pin numbers + this.pins.forEach(pin => { + if (pin.pinText) { + pin.pinText.setVisible(false); + } + }); + } + + if (!this.lockState.tensionApplied) { + this.updateFeedback("Apply tension first before picking pins"); + } + } + } + } + + // SPACE key for tension wrench toggle + if (key === ' ') { + event.preventDefault(); // Prevent page scroll + + // Simulate tension wrench click + this.lockState.tensionApplied = !this.lockState.tensionApplied; + + // Play tension sound + if (this.sounds.tension) { + this.sounds.tension.play(); + navigator.vibrate([200]); + } + + if (this.lockState.tensionApplied) { + this.wrenchGraphics.clear(); + this.wrenchGraphics.fillStyle(0x00ff00); + + // Long vertical arm (left side of L) - same dimensions as inactive + this.wrenchGraphics.fillRect(0, -120, 10, 170); + + // Short horizontal arm (bottom of L) extending into keyway - same dimensions as inactive + this.wrenchGraphics.fillRect(0, 40, 37.5, 10); + + this.updateFeedback("Tension applied. Only the binding pin can be set - others will fall back down."); + } else { + this.wrenchGraphics.clear(); + this.wrenchGraphics.fillStyle(0x888888); + + // Long vertical arm (left side of L) - same dimensions as active + this.wrenchGraphics.fillRect(0, -120, 10, 170); + + // Short horizontal arm (bottom of L) extending into keyway - same dimensions as active + this.wrenchGraphics.fillRect(0, 40, 37.5, 10); + + this.updateFeedback("Tension released. All pins will fall back down."); + + // Play reset sound + if (this.sounds.reset) { + this.sounds.reset.play(); + } + + // Reset ALL pins when tension is released (including set and overpicked ones) + this.pins.forEach(pin => { + pin.isSet = false; + pin.isOverpicked = false; + pin.currentHeight = 0; + pin.keyPinHeight = 0; // Reset key pin height + pin.driverPinHeight = 0; // Reset driver pin height + pin.overpickingTimer = null; // Reset overpicking timer + + // Reset visual + pin.keyPin.clear(); + pin.keyPin.fillStyle(0xdd3333); + + // Draw rectangular part of key pin + pin.keyPin.fillRect(-12, -50 + pin.driverPinLength, 24, pin.keyPinLength - 8); + + // Draw triangular bottom in pixel art style + pin.keyPin.fillRect(-12, -50 + pin.driverPinLength + pin.keyPinLength - 8, 24, 2); + pin.keyPin.fillRect(-10, -50 + pin.driverPinLength + pin.keyPinLength - 6, 20, 2); + pin.keyPin.fillRect(-8, -50 + pin.driverPinLength + pin.keyPinLength - 4, 16, 2); + pin.keyPin.fillRect(-6, -50 + pin.driverPinLength + pin.keyPinLength - 2, 12, 2); + + pin.driverPin.clear(); + pin.driverPin.fillStyle(0x3388dd); + pin.driverPin.fillRect(-12, -50, 24, pin.driverPinLength); + + // Reset spring to original position + pin.spring.clear(); + pin.spring.fillStyle(0x666666); + const springTop = -130; // Fixed spring top + const springBottom = -50; // Driver pin top when not lifted + const springHeight = springBottom - springTop; + + // Calculate total spring space and distribute segments evenly + const totalSpringSpace = springHeight; + const segmentSpacing = totalSpringSpace / 11; // 11 gaps between 12 segments + + for (let s = 0; s < 12; s++) { + const segmentHeight = 4; + const segmentY = springTop + (s * segmentSpacing); + pin.spring.fillRect(-12, segmentY, 24, segmentHeight); + } + + // Hide all highlights + if (pin.shearHighlight) pin.shearHighlight.setVisible(false); + if (pin.setHighlight) pin.setHighlight.setVisible(false); + if (pin.bindingHighlight) pin.bindingHighlight.setVisible(false); + if (pin.overpickedHighlight) pin.overpickedHighlight.setVisible(false); + if (pin.failureHighlight) pin.failureHighlight.setVisible(false); + }); + + // Reset lock state + this.lockState.pinsSet = 0; + } + + this.updateBindingPins(); + } + }); + + // Add keyboard release handler for pin keys + this.scene.input.keyboard.on('keyup', (event) => { + const key = event.key; + + // Pin number keys (1-8) + if (key >= '1' && key <= '8') { + const pinIndex = parseInt(key) - 1; // Convert 1-8 to 0-7 + + // Check if pin exists and is currently being held + if (pinIndex < this.pinCount && this.lockState.currentPin && this.lockState.currentPin.index === pinIndex) { + this.checkPinSet(this.lockState.currentPin); + this.lockState.currentPin = null; + this.gameState.mouseDown = false; + + // Return hook to resting position + if (this.hookPickGraphics && this.hookConfig) { + this.returnHookToStart(); + } + } + } + }); } update() { @@ -1345,7 +1535,20 @@ export class LockpickingMinigamePhaser extends MinigameScene { pin.shearHighlight.fillRect(-22.5, -110, 45, 140); pin.container.addAt(pin.shearHighlight, 0); // Add at beginning to appear behind pins } + + // Check if highlight is transitioning from hidden to visible + const wasHidden = !pin.shearHighlight.visible; pin.shearHighlight.setVisible(true); + + // Play feedback when highlight first appears + if (wasHidden) { + if (this.sounds.click) { + this.sounds.click.play(); + } + if (typeof navigator !== 'undefined' && navigator.vibrate) { + navigator.vibrate(100); + } + } } else { if (pin.shearHighlight) { pin.shearHighlight.setVisible(false); @@ -1592,7 +1795,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Play set sound if (this.sounds.set) { this.sounds.set.play(); - navigator.vibrate([200,100,200]); + navigator.vibrate(500); } this.updateFeedback(`Pin ${pin.index + 1} set! (${this.lockState.pinsSet}/${this.pinCount})`); @@ -1821,7 +2024,7 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Play success sound if (this.sounds.success) { this.sounds.success.play(); - navigator.vibrate([200,100,200,100,200]); + navigator.vibrate(500); } this.updateFeedback("Lock picked successfully!"); @@ -2045,7 +2248,6 @@ export class LockpickingMinigamePhaser extends MinigameScene { // Show success message immediately but delay the game completion const successHTML = `
Lock picked successfully!
-
All pins set at the shear line
`; // this.showSuccess(successHTML, false, 2000); diff --git a/locksmith-forge.html b/locksmith-forge.html index 04938dd..cd7096c 100644 --- a/locksmith-forge.html +++ b/locksmith-forge.html @@ -236,16 +236,20 @@ max-width: 90%; z-index: 10; } + + +
LEVEL 1
Pins: 3
-
Hints: Enabled
Sensitivity: 5
Lift Speed: 1.0
+
Binding Order: Enabled
+
Pin Alignment: Enabled
@@ -355,39 +359,69 @@ const config = {}; for (let level = 1; level <= this.maxLevel; level++) { - // Base progression - let pinCount = Math.min(3 + Math.floor((level - 1) / 5), 8); // 3-8 pins - let difficulty = this.getDifficulty(level); - let sensitivity = Math.max(1, Math.min(8, 1 + Math.floor((level - 1) / 5.57))); // 1-8, reaches 8 at level 40 - let liftSpeed = Math.max(0.5, Math.min(3.0, 0.6 + (level - 1) * 0.03)); // 0.5-3.0 (starts much slower, progresses very gradually) + // Add base progression across 10-level blocks + const blockNumber = Math.floor((level - 1) / 10) + 1; + // Determine position within the current 10-level block (1-10) + const positionInBlock = ((level - 1) % 10) + 1; - // Add some randomness and complexity - if (level > 10) { - // Randomly disable hints for higher levels - const disableHints = Math.random() > 0.7; - if (disableHints) { - sensitivity = Math.max(1, sensitivity - 2); - } + + // Each set of 10 levels starts with 3 pins + 1 for each block of 10 levels + let pinCount = Math.min(8, blockNumber + 2); // updated below + console.log('pinCount', pinCount); + console.log('blockNumber', blockNumber); + + // Alternate between increasing speed and sensitivity within each 10-level block + let sensitivity = 1; + let liftSpeed = 0.6; + + if (positionInBlock <= 5) { + // First 5 levels: increase sensitivity + sensitivity = 1 + Math.floor((positionInBlock - 1) / 2); + } else { + // Last 5 levels: increase speed + const speedLevel = positionInBlock - 5; + liftSpeed = 0.6 + (speedLevel * 0.1); + } + + // Add base progression across 10-level blocks + sensitivity += blockNumber; + liftSpeed += (blockNumber * 0.2); + + // every 3rd, 5th level, increase pin count + if (positionInBlock >= 5) { + pinCount = Math.min(8, pinCount + 2); // max 8 pins + } else if (positionInBlock >= 3) { + pinCount = Math.min(8, pinCount + 1); // max 8 pins } - if (level > 20) { - // Increase difficulty more aggressively - liftSpeed = Math.min(3.0, liftSpeed + 0.1); - } + // Ensure values stay within bounds + sensitivity = Math.max(1, Math.min(8, sensitivity)); + liftSpeed = Math.max(0.5, Math.min(3.0, liftSpeed)); - if (level > 30) { - // Very challenging levels - pinCount = Math.min(8, pinCount + 1); - sensitivity = Math.max(1, sensitivity - 1); + // Hint settings based on position in 10-level block + let highlightBindingOrder = 'enabled'; + let pinAlignmentHighlighting = 'enabled'; + + // Last 3 levels of each 10-level block remove hints progressively + if (positionInBlock === 8) { + // 8th level: remove binding order highlighting + highlightBindingOrder = 'disabled'; + } else if (positionInBlock === 9) { + // 9th level: remove pin alignment highlighting + pinAlignmentHighlighting = 'disabled'; + } else if (positionInBlock === 10) { + // 10th level: remove both + highlightBindingOrder = 'disabled'; + pinAlignmentHighlighting = 'disabled'; } config[level] = { pinCount, - difficulty, + difficulty: this.getDifficulty(level), sensitivity, - liftSpeed: Math.round(liftSpeed * 10) / 10, - highlightBindingOrder: (level % 10 === 0) ? 'disabled' : (level <= 15 ? 'enabled' : (Math.random() > 0.5 ? 'enabled' : 'disabled')), - pinAlignmentHighlighting: (level % 10 === 0) ? 'disabled' : (level <= 10 ? 'enabled' : (Math.random() > 0.6 ? 'enabled' : 'disabled')) + liftSpeed: parseFloat(liftSpeed.toFixed(2)), + highlightBindingOrder, + pinAlignmentHighlighting }; } @@ -517,23 +551,23 @@ // Check for milestone levels const milestoneMessages = { 1: { - status: `🎯 Great start! Level 1 complete! You're on your way to becoming a lockpicking master! 🎯`, + status: `🎯 Great start! 🎯`, achievement: `🌟 First Steps - Level 1 Complete! 🌟` }, 10: { - status: `🔥 Excellent progress! Level 10 conquered! You're getting the hang of this! 🔥`, + status: `🔥 Excellent progress! 🔥`, achievement: `⚡ Rising Star - Level 10 Complete! ⚡` }, 20: { - status: `💪 Impressive! Level 20 mastered! Your skills are really developing! 💪`, + status: `💪 Impressive! 💪`, achievement: `🏅 Skillful Picker - Level 20 Complete! 🏅` }, 30: { - status: `🚀 Outstanding! Level 30 achieved! You're becoming a true expert! 🚀`, + status: `🚀 Outstanding! 🚀`, achievement: `🎖️ Expert Level - Level 30 Complete! 🎖️` }, 40: { - status: `⚔️ Phenomenal! Level 40 conquered! You're almost at the ultimate challenge! ⚔️`, + status: `⚔️ Phenomenal! ⚔️`, achievement: `🏆 Elite Picker - Level 40 Complete! 🏆` } }; @@ -606,20 +640,84 @@ const config = this.levelConfig[this.currentLevel]; if (config) { + // Update values document.getElementById('pinCount').textContent = config.pinCount; - - // Show hints status based on whether visual highlighting is enabled - const hintsEnabled = config.highlightBindingOrder === 'enabled' || config.pinAlignmentHighlighting === 'enabled'; - document.getElementById('hints').textContent = hintsEnabled ? 'Enabled' : 'Disabled'; - + document.getElementById('bindingHints').textContent = config.highlightBindingOrder === 'enabled' ? 'Visible' : 'Hidden'; + document.getElementById('alignmentHints').textContent = config.pinAlignmentHighlighting === 'enabled' ? 'Visible' : 'Hidden'; document.getElementById('sensitivity').textContent = config.sensitivity; document.getElementById('liftSpeed').textContent = config.liftSpeed; + + // Apply highlighting based on level position + this.highlightBasedOnLevel(config); } // Update progress bar to reflect current level this.updateProgress(); } + highlightBasedOnLevel(config) { + // Determine position within the current 10-level block (1-10) + const positionInBlock = ((this.currentLevel - 1) % 10) + 1; + const blockNumber = Math.floor((this.currentLevel - 1) / 10); + + // Get all stat elements + const statElements = document.querySelectorAll('.stat'); + + statElements.forEach(stat => { + const span = stat.querySelector('span'); + if (!span) return; + + const statType = span.id; + let shouldHighlight = false; + + // Determine what should be highlighted based on level position + switch (statType) { + case 'pinCount': + // Highlight on levels 3 and 5 (when pin count increases) + shouldHighlight = (positionInBlock === 3 || positionInBlock === 5); + break; + + case 'sensitivity': + // Highlight on levels 1-5 (sensitivity focus phase) + shouldHighlight = (positionInBlock <= 5); + break; + + case 'liftSpeed': + // Highlight on levels 6-10 (speed focus phase) + shouldHighlight = (positionInBlock >= 6); + break; + + case 'bindingHints': + // Highlight when binding order hints are enabled + shouldHighlight = (config.highlightBindingOrder === 'disabled'); + break; + + case 'alignmentHints': + // Highlight when pin alignment hints are enabled + shouldHighlight = (config.pinAlignmentHighlighting === 'disabled'); + break; + + default: + shouldHighlight = false; + } + + if (shouldHighlight) { + // Highlight the value + stat.style.backgroundColor = '#2a4a2a'; + stat.style.color = '#00ff00'; + stat.style.fontWeight = 'bold'; + stat.style.transition = 'all 0.3s ease'; + stat.style.opacity = '1'; + } else { + // Show normally (slightly faded) + stat.style.backgroundColor = ''; + stat.style.color = ''; + stat.style.fontWeight = ''; + stat.style.opacity = '0.7'; + } + }); + } + updateStatus(message) { const feedbackElement = document.querySelector('.lockpick-feedback'); if (feedbackElement) {