mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
Add key demo HTML and enhance lockpicking minigame: Introduce key-demo.html for testing new key mode functionality, featuring UI elements and game mechanics. Update lockpicking-game-phaser.js to support key mode, including key data generation, insertion logic, and collision detection for improved gameplay experience. Ensure clear documentation and maintainability in the codebase.
This commit is contained in:
@@ -29,6 +29,12 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
this.closeButtonText = params.closeButtonText || '×';
|
||||
this.closeButtonAction = params.closeButtonAction || 'close';
|
||||
|
||||
// Key mode settings
|
||||
this.keyMode = params.keyMode || false;
|
||||
this.keyData = params.keyData || null; // Key data with cuts/ridges
|
||||
this.keyInsertionProgress = 0; // 0 = not inserted, 1 = fully inserted
|
||||
this.keyInserting = false;
|
||||
|
||||
// Sound effects
|
||||
this.sounds = {};
|
||||
|
||||
@@ -170,8 +176,17 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
self.createPins();
|
||||
self.createHookPick();
|
||||
self.createShearLine();
|
||||
|
||||
// Create key if in key mode
|
||||
if (self.keyMode) {
|
||||
self.createKey();
|
||||
self.hideLockpickingTools();
|
||||
self.updateFeedback("Click the key to insert it into the lock");
|
||||
} else {
|
||||
self.updateFeedback("Apply tension first, then lift pins in binding order - only the binding pin can be set");
|
||||
}
|
||||
|
||||
self.setupInputHandlers();
|
||||
self.updateFeedback("Apply tension first, then lift pins in binding order - only the binding pin can be set");
|
||||
console.log('Phaser scene setup complete');
|
||||
}
|
||||
|
||||
@@ -247,12 +262,12 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
this.cylinderGraphics.lineStyle(1, 0x8b4513); // Darker bronze border
|
||||
this.cylinderGraphics.strokeRect(100, 155, 400, 180);
|
||||
|
||||
// Create keyway - space where key would enter (from halfway up key pins)
|
||||
// Create keyway - space where key would enter (moved higher to align with shear line)
|
||||
this.keywayGraphics = this.scene.add.graphics();
|
||||
this.keywayGraphics.fillStyle(0x2a2a2a); // Dark gray for keyway
|
||||
this.keywayGraphics.fillRect(100, 200, 400, 90); // From left edge (x=100), 2/3 height (90 instead of 135)
|
||||
this.keywayGraphics.fillRect(100, 170, 400, 120); // Moved higher (y=170) and increased height (120)
|
||||
this.keywayGraphics.lineStyle(1, 0x1a1a1a); // Darker border
|
||||
this.keywayGraphics.strokeRect(100, 200, 400, 90);
|
||||
this.keywayGraphics.strokeRect(100, 170, 400, 120);
|
||||
}
|
||||
|
||||
createTensionWrench() {
|
||||
@@ -546,6 +561,675 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
console.log('Hook config initialized - targetPin:', this.hookConfig.targetPin, 'pinCount:', this.pinCount);
|
||||
}
|
||||
|
||||
generateKeyDataFromPins() {
|
||||
// Generate key cuts based on actual pin heights
|
||||
// Calculate cut depths so that when key is inserted, pins align at shear line
|
||||
const cuts = [];
|
||||
const shearLineY = -45; // Shear line position (in pin container coordinates)
|
||||
const keyBladeHeight = 110; // Key blade height (from keyConfig)
|
||||
const pinContainerY = 200; // Pin container Y position (world coordinates)
|
||||
|
||||
for (let i = 0; i < this.pinCount; i++) {
|
||||
const pin = this.pins[i];
|
||||
const keyPinLength = pin.keyPinLength;
|
||||
|
||||
// Simple key cut calculation:
|
||||
// The cut depth should be the key pin length minus the gap from key blade top to shear line
|
||||
|
||||
// Key blade is centered in keyway: keywayStartY + keywayHeight/2 = 170 + 60 = 230
|
||||
// Key blade top is: 230 - keyBladeHeight/2 = 230 - 55 = 175
|
||||
// Shear line is at y=155 in world coordinates (200 - 45)
|
||||
const keyBladeTop_world = 175; // 170 + 60 - 55 (keywayStartY + keywayHeight/2 - keyBladeHeight/2)
|
||||
const shearLine_world = 155; // 200 - 45 (pin container Y - shear line Y)
|
||||
const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; // 175 - 155 = 20
|
||||
|
||||
// Cut depth = key pin length - gap from key blade top to shear line
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine;
|
||||
|
||||
// Clamp to valid range (0 to key blade height)
|
||||
const clampedCutDepth = Math.max(0, Math.min(keyBladeHeight, cutDepth_needed));
|
||||
|
||||
console.log(`=== KEY CUT ${i} GENERATION ===`);
|
||||
console.log(` Pin properties: keyPinLength=${keyPinLength}, driverPinLength=${pin.driverPinLength}`);
|
||||
console.log(` Key blade top: ${keyBladeTop_world}, shear line: ${shearLine_world}`);
|
||||
console.log(` Gap from key blade top to shear line: ${gapFromKeyBladeTopToShearLine}`);
|
||||
console.log(` Cut calculation: cutDepth_needed=${cutDepth_needed} (${keyPinLength} - ${gapFromKeyBladeTopToShearLine})`);
|
||||
console.log(` Final cut: cutDepth=${clampedCutDepth}px (max ${keyBladeHeight}px)`);
|
||||
console.log(`=====================================`);
|
||||
|
||||
cuts.push(clampedCutDepth);
|
||||
}
|
||||
|
||||
this.keyData = { cuts: cuts };
|
||||
console.log('Generated key data from pins:', this.keyData);
|
||||
}
|
||||
|
||||
createKey() {
|
||||
if (!this.keyMode) return;
|
||||
|
||||
// Generate key data from actual pin heights if not provided
|
||||
if (!this.keyData) {
|
||||
this.generateKeyDataFromPins();
|
||||
}
|
||||
|
||||
// Key dimensions - make keyway higher so key pins align at shear line
|
||||
const keywayWidth = 400; // Width of the keyway
|
||||
const keywayHeight = 120; // Increased height to accommodate key cuts
|
||||
const keywayStartX = 100; // Left edge of keyway
|
||||
const keywayStartY = 170; // Moved higher (was 200) so key pins align at shear line
|
||||
|
||||
// Key parts dimensions
|
||||
const keyCircleRadius = 140; // Circle (handle) - 2x larger (was 15)
|
||||
const keyShoulderWidth = 20; // Shoulder width (short)
|
||||
const keyShoulderHeight = keywayHeight + 10; // Slightly taller than keyway
|
||||
const keyBladeWidth = keywayWidth + 20; // Blade length (reaches end of keyway)
|
||||
const keyBladeHeight = keywayHeight - 10; // Slightly smaller than keyway
|
||||
|
||||
// Key starting position (just outside the keyway to the LEFT) - ready to be inserted
|
||||
// Account for full key length: circle + shoulder + blade
|
||||
const fullKeyLength = keyCircleRadius * 2 + keyShoulderWidth + keyBladeWidth;
|
||||
const keyStartX = keywayStartX - fullKeyLength + 20; // Just the blade tip visible at keyway entrance
|
||||
const keyStartY = keywayStartY + keywayHeight / 2; // Centered in keyway
|
||||
|
||||
// Create key container
|
||||
this.keyGroup = this.scene.add.container(keyStartX, keyStartY);
|
||||
|
||||
// Create key graphics
|
||||
this.keyGraphics = this.scene.add.graphics();
|
||||
this.keyGraphics.fillStyle(0xcccccc); // Silver color
|
||||
// Removed outline - no lineStyle
|
||||
|
||||
// Draw key parts from right to left
|
||||
// 1. Circle (handle) - rightmost part
|
||||
this.keyGraphics.fillCircle(keyCircleRadius, 0, keyCircleRadius);
|
||||
|
||||
// 2. Shoulder - wider rectangle
|
||||
const shoulderX = keyCircleRadius * 1.9; // After circle
|
||||
this.keyGraphics.fillRect(shoulderX, -keyShoulderHeight/2, keyShoulderWidth, keyShoulderHeight);
|
||||
|
||||
// 3. Blade - main part with cuts/ridges
|
||||
const bladeX = shoulderX + keyShoulderWidth;
|
||||
this.keyGraphics.fillRect(bladeX, -keyBladeHeight/2, keyBladeWidth, keyBladeHeight);
|
||||
|
||||
// Add cuts/ridges to the blade based on key data
|
||||
this.addKeyCuts(bladeX, -keyBladeHeight/2, keyBladeWidth, keyBladeHeight, keyBladeWidth);
|
||||
|
||||
this.keyGroup.add(this.keyGraphics);
|
||||
|
||||
// Set key graphics to low z-index so it appears behind pins
|
||||
this.keyGroup.setDepth(1); // Set low z-index so key appears behind pins
|
||||
|
||||
// Create click zone covering the entire keyway area in key mode
|
||||
// Position click zone to cover from leftmost blade tip through the entire keyway and beyond
|
||||
const keywayClickWidth = fullKeyLength + keywayWidth + 200; // Extend much further right
|
||||
const clickZone = this.scene.add.rectangle(0, 0,
|
||||
keywayClickWidth, keyShoulderHeight, 0x000000, 0);
|
||||
clickZone.setDepth(9999); // Very high z-index for clickability
|
||||
clickZone.setInteractive();
|
||||
|
||||
// Add click zone to key group and sync its position
|
||||
this.keyGroup.add(clickZone);
|
||||
this.keyClickZone = clickZone;
|
||||
console.log('Key click zone created:', {
|
||||
width: keywayClickWidth,
|
||||
height: keyShoulderHeight,
|
||||
position: '0,0 relative to key group'
|
||||
});
|
||||
|
||||
// Store key configuration
|
||||
this.keyConfig = {
|
||||
startX: keyStartX,
|
||||
startY: keyStartY,
|
||||
circleRadius: keyCircleRadius,
|
||||
shoulderWidth: keyShoulderWidth,
|
||||
shoulderHeight: keyShoulderHeight,
|
||||
bladeWidth: keyBladeWidth,
|
||||
bladeHeight: keyBladeHeight,
|
||||
keywayStartX: keywayStartX,
|
||||
keywayStartY: keywayStartY,
|
||||
keywayWidth: keywayWidth,
|
||||
keywayHeight: keywayHeight
|
||||
};
|
||||
|
||||
// Create collision rectangles for the key blade surface (after config is set)
|
||||
this.createKeyBladeCollision();
|
||||
|
||||
console.log('Key created with config:', this.keyConfig);
|
||||
}
|
||||
|
||||
addKeyCuts(bladeX, bladeY, bladeWidth, bladeHeight, totalBladeWidth) {
|
||||
if (!this.keyData || !this.keyData.cuts) return;
|
||||
|
||||
// Draw cuts/ridges on the blade
|
||||
this.keyGraphics.fillStyle(0x2a2a2a); // Dark color for cuts
|
||||
|
||||
const cutWidth = 24; // Width of each cut (same as pin width)
|
||||
|
||||
// Calculate pin spacing to match the lock's pin positions
|
||||
const pinSpacing = 400 / (this.pinCount + 1);
|
||||
const margin = pinSpacing * 0.75;
|
||||
|
||||
// Space cuts to align with actual pin positions
|
||||
this.keyData.cuts.forEach((cutDepth, index) => {
|
||||
if (index >= this.pinCount) return; // Only create cuts for existing pins
|
||||
|
||||
// Calculate pin position in the lock
|
||||
const pinX = 100 + margin + index * pinSpacing;
|
||||
|
||||
// Calculate cut position relative to the blade
|
||||
const cutX = bladeX + (pinX - 100); // Offset from blade start to match pin position
|
||||
|
||||
// The cut depth directly represents how deep the divot should be (in pixels)
|
||||
// Small pin = small cut, Large pin = large cut
|
||||
const cutHeight = cutDepth; // cutDepth is already in pixels
|
||||
|
||||
// Draw main cut only on the top of the key (divot)
|
||||
this.keyGraphics.fillRect(cutX - cutWidth/2, bladeY, cutWidth, cutHeight);
|
||||
|
||||
// Draw triangular cuts extending to halfway between pins
|
||||
if (index < this.pinCount - 1) {
|
||||
// Calculate next pin position
|
||||
const nextPinX = 100 + margin + (index + 1) * pinSpacing;
|
||||
const nextCutX = bladeX + (nextPinX - 100);
|
||||
|
||||
// Calculate halfway point between current and next cut
|
||||
const halfwayX = cutX + (nextCutX - cutX) / 2;
|
||||
|
||||
// Draw triangular cut from current cut to halfway point (right side)
|
||||
this.drawTriangularCut(cutX + cutWidth/2, bladeY, halfwayX, bladeY, cutHeight);
|
||||
}
|
||||
|
||||
// Draw triangular cut from previous halfway point to current cut (left side)
|
||||
if (index > 0) {
|
||||
// Calculate previous pin position
|
||||
const prevPinX = 100 + margin + (index - 1) * pinSpacing;
|
||||
const prevCutX = bladeX + (prevPinX - 100);
|
||||
|
||||
// Calculate halfway point between previous and current cut
|
||||
const halfwayX = prevCutX + (cutX - prevCutX) / 2;
|
||||
|
||||
// Draw triangular cut from halfway point to current cut (left side)
|
||||
// For left triangles, we want height to increase as we move toward the cut
|
||||
this.drawTriangularCut(halfwayX, bladeY, cutX - cutWidth/2, bladeY, cutHeight, true);
|
||||
} else if (index === 0) {
|
||||
// For the first pin, draw triangular cut from left edge of key blade to first cut
|
||||
const keyLeftEdge = bladeX;
|
||||
|
||||
// Draw triangular cut from left edge to first cut (height increases toward the cut)
|
||||
this.drawTriangularCut(keyLeftEdge, bladeY, cutX - cutWidth/2, bladeY, cutHeight, true);
|
||||
}
|
||||
|
||||
// For the last pin, add triangular cuts extending to the right edge of the key
|
||||
if (index === this.pinCount - 1) {
|
||||
// Calculate hypothetical next pin position
|
||||
const nextPinX = 100 + margin + (index + 1) * pinSpacing;
|
||||
const nextCutX = bladeX + (nextPinX - 100);
|
||||
|
||||
// Calculate halfway point between last pin and hypothetical next pin
|
||||
const halfwayX = cutX + (nextCutX - cutX) / 2;
|
||||
|
||||
// Calculate the right edge of the key blade
|
||||
const keyRightEdge = bladeX + totalBladeWidth;
|
||||
|
||||
// Draw triangular cut from last pin to halfway point
|
||||
this.drawTriangularCut(cutX + cutWidth/2, bladeY, halfwayX, bladeY, cutHeight);
|
||||
|
||||
// Draw triangular end that meets in the middle (pointed tip)
|
||||
// Use half the blade height for a consistent 45-degree angle regardless of cut depth
|
||||
const triangularEndHeight = bladeHeight / 2;
|
||||
this.drawTriangularEnd(halfwayX, bladeY, keyRightEdge, bladeY + bladeHeight, triangularEndHeight);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset fill style
|
||||
this.keyGraphics.fillStyle(0xcccccc);
|
||||
}
|
||||
|
||||
drawTriangularCut(startX, startY, endX, endY, height, isLeftTriangle = false) {
|
||||
// Draw a triangular cut using pixel art style
|
||||
const width = Math.abs(endX - startX);
|
||||
const stepSize = 4; // Consistent pixel size for steps
|
||||
const steps = Math.max(1, Math.floor(width / stepSize)); // Create steps for pixel art effect
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const progress = i / steps;
|
||||
const x = startX + (endX - startX) * progress;
|
||||
|
||||
let stepHeight;
|
||||
|
||||
if (isLeftTriangle) {
|
||||
// Left triangle: height increases as we move toward the cut
|
||||
stepHeight = height * progress;
|
||||
} else {
|
||||
// Right triangle: height decreases as we move away from the cut
|
||||
stepHeight = height * (1 - progress);
|
||||
}
|
||||
|
||||
// Draw horizontal line at this step
|
||||
const stepWidth = width / steps;
|
||||
this.keyGraphics.fillRect(x, startY, stepWidth, stepHeight);
|
||||
}
|
||||
}
|
||||
|
||||
drawTriangularCutDownward(startX, startY, endX, endY, height) {
|
||||
// Draw a downward triangular cut using pixel art style (cuts off top corner)
|
||||
const width = Math.abs(endX - startX);
|
||||
const stepSize = 4; // Consistent pixel size for steps
|
||||
const steps = Math.max(1, Math.floor(width / stepSize)); // Create steps for pixel art effect
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const progress = i / steps;
|
||||
const x = startX + (endX - startX) * progress;
|
||||
|
||||
// Height increases as we move toward the end (cutting deeper into the key)
|
||||
const stepHeight = height * progress;
|
||||
|
||||
// Draw horizontal line at this step, starting from the top and cutting downward
|
||||
const stepWidth = width / steps;
|
||||
this.keyGraphics.fillRect(x, startY, stepWidth, stepHeight);
|
||||
}
|
||||
}
|
||||
|
||||
drawTriangularCutUpward(startX, startY, endX, endY, height) {
|
||||
// Draw an upward triangular cut using pixel art style (cuts off bottom corner)
|
||||
const width = Math.abs(endX - startX);
|
||||
const stepSize = 4; // Consistent pixel size for steps
|
||||
const steps = Math.max(1, Math.floor(width / stepSize)); // Create steps for pixel art effect
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const progress = i / steps;
|
||||
const x = startX + (endX - startX) * progress;
|
||||
|
||||
// Height increases as we move toward the end (cutting deeper into the key)
|
||||
const stepHeight = height * progress;
|
||||
|
||||
// Draw horizontal line at this step, starting from the bottom and cutting upward
|
||||
const stepWidth = width / steps;
|
||||
this.keyGraphics.fillRect(x, startY - stepHeight, stepWidth, stepHeight);
|
||||
}
|
||||
}
|
||||
|
||||
drawTriangularEnd(startX, startY, endX, endY, maxHeight) {
|
||||
// Draw a triangular end that creates a pointed tip by cutting from both top and bottom
|
||||
// The cuts meet in the middle to form a point
|
||||
const width = Math.abs(endX - startX);
|
||||
const stepSize = 4; // Consistent pixel size for steps
|
||||
const steps = Math.max(1, Math.floor(width / stepSize)); // Create steps for pixel art effect
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const progress = i / steps;
|
||||
const x = startX + (endX - startX) * progress;
|
||||
|
||||
// Calculate height for this step - creates a triangular shape
|
||||
// Height increases as we move toward the end, but we cut from both top and bottom
|
||||
const stepHeight = maxHeight * progress;
|
||||
|
||||
// Draw horizontal line at this step, cutting from the top
|
||||
const stepWidth = width / steps;
|
||||
this.keyGraphics.fillRect(x, startY, stepWidth, stepHeight);
|
||||
|
||||
// Draw horizontal line at this step, cutting from the bottom
|
||||
this.keyGraphics.fillRect(x, endY - stepHeight, stepWidth, stepHeight);
|
||||
}
|
||||
}
|
||||
|
||||
startKeyInsertion() {
|
||||
console.log('startKeyInsertion called with:', {
|
||||
hasKeyGroup: !!this.keyGroup,
|
||||
hasKeyConfig: !!this.keyConfig,
|
||||
keyInserting: this.keyInserting
|
||||
});
|
||||
|
||||
if (!this.keyGroup || !this.keyConfig || this.keyInserting) {
|
||||
console.log('startKeyInsertion early return - missing requirements');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting key insertion animation...');
|
||||
this.keyInserting = true;
|
||||
this.updateFeedback("Inserting key...");
|
||||
|
||||
// Calculate target position (shoulder stops at keyway edge, not fully inserted)
|
||||
const targetX = this.keyConfig.keywayStartX - this.keyConfig.shoulderWidth;
|
||||
const startX = this.keyGroup.x;
|
||||
|
||||
// Calculate target position - move key so RIGHT edge of shoulder touches LEFT edge of keyway
|
||||
// Key starts at -600px, needs to move so shoulder right edge (at position 286px from center) touches keyway left edge
|
||||
const keywayLeftEdge = this.keyConfig.keywayStartX; // 100px
|
||||
const shoulderRightEdge = this.keyConfig.circleRadius * 1.9 + this.keyConfig.shoulderWidth; // 266 + 20 = 286px from key group center
|
||||
const targetKeyGroupX = keywayLeftEdge - shoulderRightEdge; // 100 - 286 = -186px
|
||||
const halfwayX = targetKeyGroupX;
|
||||
|
||||
// Create smooth animation from left to right
|
||||
this.scene.tweens.add({
|
||||
targets: this.keyGroup,
|
||||
x: halfwayX,
|
||||
duration: 4000, // 4 seconds for slower insertion
|
||||
ease: 'Cubic.easeInOut',
|
||||
onUpdate: (tween) => {
|
||||
// Calculate progress (0 to 1) - key moves from left to right
|
||||
const progress = (this.keyGroup.x - startX) / (halfwayX - startX);
|
||||
this.keyInsertionProgress = Math.max(0, Math.min(1, progress));
|
||||
|
||||
console.log('Animation update - key position:', this.keyGroup.x, 'progress:', this.keyInsertionProgress);
|
||||
|
||||
// Update pin positions based on key cuts as the key is inserted
|
||||
this.updatePinsWithKeyInsertion(this.keyInsertionProgress);
|
||||
},
|
||||
onComplete: () => {
|
||||
this.keyInserting = false;
|
||||
this.keyInsertionProgress = 0.5; // Only halfway inserted
|
||||
this.checkKeyCorrectness();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateKeyPosition(progress) {
|
||||
if (!this.keyGroup || !this.keyConfig) return;
|
||||
|
||||
// Calculate new position based on insertion progress
|
||||
// Key moves from left (off-screen) to right (shoulder touches lock edge)
|
||||
const targetX = this.keyConfig.keywayStartX - this.keyConfig.shoulderWidth; // Shoulder touches lock edge
|
||||
const currentX = this.keyConfig.startX + (targetX - this.keyConfig.startX) * progress;
|
||||
|
||||
this.keyGroup.x = currentX;
|
||||
this.keyInsertionProgress = progress;
|
||||
|
||||
// If fully inserted, check if key is correct
|
||||
if (progress >= 1.0) {
|
||||
this.checkKeyCorrectness();
|
||||
}
|
||||
}
|
||||
|
||||
checkKeyCorrectness() {
|
||||
if (!this.keyData || !this.keyData.cuts) return;
|
||||
|
||||
// Check if pins are aligned at the shear line
|
||||
let isCorrect = true;
|
||||
const shearLineY = -45; // Shear line position
|
||||
const tolerance = 5; // Tolerance for shear line alignment
|
||||
|
||||
this.pins.forEach((pin, index) => {
|
||||
if (index >= this.pinCount) return;
|
||||
|
||||
// Calculate the current position of the key pin top
|
||||
const pinWorldY = 200;
|
||||
const pinCurrentY = pinWorldY - 50 + pin.driverPinLength + pin.keyPinLength - pin.currentHeight;
|
||||
const keyPinTop = pinCurrentY - pin.keyPinLength;
|
||||
|
||||
// Check if the key pin top is at the shear line
|
||||
const distanceToShearLine = Math.abs(keyPinTop - shearLineY);
|
||||
if (distanceToShearLine > tolerance) {
|
||||
isCorrect = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (isCorrect) {
|
||||
// Key is correct - all pins are aligned at the shear line
|
||||
this.updateFeedback("Key fits perfectly! Lock unlocked.");
|
||||
|
||||
// Play success sound
|
||||
if (this.sounds.success) {
|
||||
this.sounds.success.play();
|
||||
}
|
||||
|
||||
// Complete the minigame
|
||||
setTimeout(() => {
|
||||
this.complete(true);
|
||||
}, 1000);
|
||||
} else {
|
||||
// Key is wrong
|
||||
this.updateFeedback("Wrong key! Try a different one.");
|
||||
|
||||
// Play wrong sound
|
||||
if (this.sounds.wrong) {
|
||||
this.sounds.wrong.play();
|
||||
}
|
||||
|
||||
// Reset key position
|
||||
setTimeout(() => {
|
||||
this.updateKeyPosition(0);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
liftPinsWithKey() {
|
||||
if (!this.keyData || !this.keyData.cuts) return;
|
||||
|
||||
// Lift each pin to the correct height based on key cuts
|
||||
this.keyData.cuts.forEach((cutDepth, index) => {
|
||||
if (index >= this.pinCount) return;
|
||||
|
||||
const pin = this.pins[index];
|
||||
|
||||
// Calculate the height needed to lift the pin so it aligns at the shear line
|
||||
const shearLineY = -45; // Shear line position
|
||||
const keyPinTopAtShearLine = shearLineY; // Key pin top should be at shear line
|
||||
const keyPinBottomAtRest = -50 + pin.driverPinLength + pin.keyPinLength; // Key pin bottom when not lifted
|
||||
const requiredLift = keyPinBottomAtRest - keyPinTopAtShearLine; // How much to lift
|
||||
|
||||
// The cut depth should match the required lift
|
||||
const maxLift = pin.keyPinLength; // Maximum possible lift (full key pin height)
|
||||
const requiredCutDepth = (requiredLift / maxLift) * 100; // Convert to percentage
|
||||
|
||||
// Calculate the actual lift based on the key cut depth
|
||||
const actualLift = (cutDepth / 100) * maxLift;
|
||||
|
||||
// Animate pin to correct position
|
||||
this.scene.tweens.add({
|
||||
targets: { height: 0 },
|
||||
height: actualLift,
|
||||
duration: 500,
|
||||
ease: 'Cubic.easeOut',
|
||||
onUpdate: (tween) => {
|
||||
pin.currentHeight = tween.targets[0].height;
|
||||
this.updatePinVisuals(pin);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updatePinsWithKeyInsertion(progress) {
|
||||
if (!this.keyCollisionRects || !this.keyConfig) return;
|
||||
|
||||
console.log('Updating pins with key insertion, progress:', progress);
|
||||
|
||||
|
||||
|
||||
// Calculate key blade position relative to the lock
|
||||
const keyBladeStartX = this.keyGroup.x + this.keyConfig.circleRadius * 2 + this.keyConfig.shoulderWidth;
|
||||
|
||||
// Check each pin for collision with the key blade
|
||||
this.pins.forEach((pin, index) => {
|
||||
if (index >= this.pinCount) return;
|
||||
|
||||
// Calculate pin position in the lock
|
||||
const pinSpacing = 400 / (this.pinCount + 1);
|
||||
const margin = pinSpacing * 0.75;
|
||||
const pinX = 100 + margin + index * pinSpacing;
|
||||
|
||||
// Calculate pin's current world position
|
||||
const pinWorldY = 200;
|
||||
const pinCurrentY = pinWorldY - 50 + pin.driverPinLength + pin.keyPinLength - pin.currentHeight;
|
||||
const keyPinTop = pinCurrentY - pin.keyPinLength;
|
||||
const keyPinBottom = pinCurrentY;
|
||||
|
||||
// Create pin collision rectangle
|
||||
const pinRect = new Phaser.Geom.Rectangle(pinX - 12, keyPinTop, 24, pin.keyPinLength);
|
||||
|
||||
console.log(`Pin ${index} collision rect: x=${pinX - 12}, y=${keyPinTop}, width=24, height=${pin.keyPinLength}`);
|
||||
|
||||
// Direct collision detection - check if pin is under the key blade
|
||||
let collision = false;
|
||||
let keySurfaceY = 0;
|
||||
let invertedCutDepth = 0; // Store the inverted cut depth for use in collision block
|
||||
let cutDepth = 0; // Store the cut depth for use in collision block
|
||||
let surfaceHeight = 0; // Store the surface height for use in collision block
|
||||
|
||||
console.log(`Pin ${index} at X: ${pinX}, key blade start: ${keyBladeStartX}, keyData cuts: ${JSON.stringify(this.keyData.cuts)}`);
|
||||
|
||||
// Check if this pin is under the key blade
|
||||
const keyBladeEndX = keyBladeStartX + this.keyConfig.bladeWidth;
|
||||
if (pinX >= keyBladeStartX && pinX <= keyBladeEndX) {
|
||||
collision = true;
|
||||
|
||||
// Find which key cut corresponds to this pin
|
||||
const pinSpacing = 400 / (this.pinCount + 1);
|
||||
const margin = pinSpacing * 0.75;
|
||||
|
||||
for (let i = 0; i < this.pinCount; i++) {
|
||||
const cutPinX = 100 + margin + i * pinSpacing;
|
||||
if (Math.abs(pinX - cutPinX) < 12) { // Within pin width
|
||||
cutDepth = this.keyData.cuts[i] || 50;
|
||||
const bladeHeight = this.keyConfig.bladeHeight;
|
||||
|
||||
// The cut depth directly represents how deep the divot is
|
||||
// Small pin = small cut, Large pin = large cut
|
||||
invertedCutDepth = cutDepth; // Store for use in collision block (no inversion needed)
|
||||
const cutHeight = (bladeHeight / 2) * (cutDepth / 100);
|
||||
surfaceHeight = bladeHeight - cutHeight;
|
||||
|
||||
// Calculate key surface Y position using the same logic as key generation
|
||||
// The key surface should be positioned so that when the key pin bottom sits on it,
|
||||
// the key pin top aligns with the shear line
|
||||
const shearLineY = -45; // Shear line position
|
||||
const keyPinBottomAtShearLine = shearLineY + pin.keyPinLength;
|
||||
|
||||
// Convert from pin container coordinates to world coordinates
|
||||
const pinContainerY = 200; // Pin container Y position
|
||||
keySurfaceY = keyPinBottomAtShearLine + pinContainerY;
|
||||
|
||||
console.log(`Pin ${index} collision with key cut ${i} - cutDepth: ${cutDepth}, invertedCutDepth: ${invertedCutDepth}, surfaceHeight: ${surfaceHeight}, keySurfaceY: ${keySurfaceY}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collision) {
|
||||
// Key is under this pin - calculate lift to sit on key surface
|
||||
// The key surface is at keySurfaceY, and the pin should sit on it
|
||||
|
||||
// Calculate the pin's rest position (when not lifted)
|
||||
const pinRestY = pinWorldY - 50 + pin.driverPinLength + pin.keyPinLength;
|
||||
const keyPinBottomAtRest = pinRestY;
|
||||
|
||||
// Calculate where the key pin bottom should be to sit on the key surface
|
||||
const targetKeyPinBottom = keySurfaceY;
|
||||
|
||||
// Calculate required lift to move key pin bottom from rest to key surface
|
||||
const requiredLift = keyPinBottomAtRest - targetKeyPinBottom;
|
||||
|
||||
// Quick lift when key is under the pin
|
||||
const targetLift = Math.max(0, requiredLift);
|
||||
const currentLift = Math.min(targetLift, pin.currentHeight + 8); // Fast lift
|
||||
|
||||
// Calculate the key pin top position after lift (in pin container coordinates)
|
||||
// keyPinBottomAtRest is in world coordinates, need to convert to pin container coordinates
|
||||
const pinContainerY = 200; // Pin container Y position
|
||||
const keyPinBottomAtRest_container = keyPinBottomAtRest - pinContainerY;
|
||||
const keyPinTopAfterLift = keyPinBottomAtRest_container - pin.keyPinLength - currentLift;
|
||||
const shearLineY = -45;
|
||||
const distanceToShearLine = Math.abs(keyPinTopAfterLift - shearLineY);
|
||||
|
||||
console.log(`=== PIN ${index} DETAILED DEBUG ===`);
|
||||
console.log(` Pin properties: keyPinLength=${pin.keyPinLength}, driverPinLength=${pin.driverPinLength}`);
|
||||
console.log(` World coordinates: keyPinBottomAtRest=${keyPinBottomAtRest}, keySurfaceY=${keySurfaceY}`);
|
||||
console.log(` Container coordinates: keyPinBottomAtRest_container=${keyPinBottomAtRest_container}`);
|
||||
console.log(` Lift calculation: requiredLift=${requiredLift}, currentLift=${currentLift}`);
|
||||
console.log(` Final positions: keyPinTopAfterLift=${keyPinTopAfterLift}, shearLineY=${shearLineY}`);
|
||||
console.log(` Alignment: distanceToShearLine=${distanceToShearLine} (should be close to 0)`);
|
||||
console.log(` Key cut info: cutDepth=${cutDepth}, surfaceHeight=${surfaceHeight}`);
|
||||
console.log(`=====================================`);
|
||||
|
||||
// Update pin height
|
||||
pin.currentHeight = currentLift;
|
||||
} else {
|
||||
// Key is not under this pin - let it fall back down quickly
|
||||
const newHeight = Math.max(0, pin.currentHeight - 4);
|
||||
console.log(`Pin ${index} no collision - currentHeight: ${pin.currentHeight}, newHeight: ${newHeight}`);
|
||||
pin.currentHeight = newHeight;
|
||||
}
|
||||
|
||||
// Update pin visuals
|
||||
this.updatePinVisuals(pin);
|
||||
});
|
||||
}
|
||||
|
||||
createKeyBladeCollision() {
|
||||
if (!this.keyData || !this.keyData.cuts || !this.keyConfig) return;
|
||||
|
||||
// Create collision rectangles for each section of the key blade
|
||||
this.keyCollisionRects = [];
|
||||
|
||||
const pinSpacing = 400 / (this.pinCount + 1);
|
||||
const margin = pinSpacing * 0.75;
|
||||
const bladeStartX = this.keyConfig.circleRadius * 2 + this.keyConfig.shoulderWidth;
|
||||
|
||||
console.log('Creating key collision rectangles, bladeStartX:', bladeStartX);
|
||||
|
||||
// Create collision rectangles for each pin position
|
||||
for (let i = 0; i < this.pinCount; i++) {
|
||||
const cutDepth = this.keyData.cuts[i] || 50;
|
||||
const bladeHeight = this.keyConfig.bladeHeight;
|
||||
|
||||
// The cut depth directly represents how deep the divot is
|
||||
// Small pin = small cut, Large pin = large cut
|
||||
const cutHeight = (bladeHeight / 2) * (cutDepth / 100);
|
||||
const surfaceHeight = bladeHeight - cutHeight;
|
||||
|
||||
// Calculate pin position in the lock
|
||||
const pinX = 100 + margin + i * pinSpacing;
|
||||
|
||||
// Create collision rectangle for this section - position relative to blade start
|
||||
const rect = {
|
||||
x: pinX - 100, // Position relative to blade start (not absolute)
|
||||
y: -bladeHeight/2 + cutHeight, // Position relative to key center
|
||||
width: 24, // Pin width
|
||||
height: surfaceHeight,
|
||||
cutDepth: cutDepth
|
||||
};
|
||||
|
||||
console.log(`Key collision rect ${i}: x=${rect.x}, y=${rect.y}, width=${rect.width}, height=${rect.height}`);
|
||||
this.keyCollisionRects.push(rect);
|
||||
}
|
||||
}
|
||||
|
||||
getKeySurfaceHeightAtPosition(pinX, keyBladeStartX) {
|
||||
if (!this.keyCollisionRects || !this.keyConfig) return this.keyConfig ? this.keyConfig.bladeHeight : 0;
|
||||
|
||||
// Find the collision rectangle for this pin position
|
||||
const pinSpacing = 400 / (this.pinCount + 1);
|
||||
const margin = pinSpacing * 0.75;
|
||||
|
||||
for (let i = 0; i < this.pinCount; i++) {
|
||||
const cutPinX = 100 + margin + i * pinSpacing;
|
||||
if (Math.abs(pinX - cutPinX) < 12) { // Within pin width
|
||||
return this.keyCollisionRects[i].height;
|
||||
}
|
||||
}
|
||||
|
||||
// If no cut found, return full blade height
|
||||
return this.keyConfig.bladeHeight;
|
||||
}
|
||||
|
||||
hideLockpickingTools() {
|
||||
// Hide tension wrench and hook pick in key mode
|
||||
if (this.tensionWrench) {
|
||||
this.tensionWrench.setVisible(false);
|
||||
}
|
||||
if (this.hookGroup) {
|
||||
this.hookGroup.setVisible(false);
|
||||
}
|
||||
|
||||
// Hide labels
|
||||
if (this.wrenchText) {
|
||||
this.wrenchText.setVisible(false);
|
||||
}
|
||||
if (this.hookPickLabel) {
|
||||
this.hookPickLabel.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
updateHookPosition(pinIndex) {
|
||||
if (!this.hookGroup || !this.hookConfig) return;
|
||||
|
||||
@@ -758,10 +1442,21 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
|
||||
updatePinVisuals(pin) {
|
||||
console.log(`Updating pin ${pin.index} visuals - currentHeight: ${pin.currentHeight}`);
|
||||
|
||||
// Update key pin visual
|
||||
pin.keyPin.clear();
|
||||
pin.keyPin.fillStyle(0xdd3333);
|
||||
|
||||
// Calculate new position based on currentHeight
|
||||
const newKeyPinY = -50 + pin.driverPinLength - pin.currentHeight;
|
||||
const keyPinTopY = newKeyPinY;
|
||||
const keyPinBottomY = newKeyPinY + pin.keyPinLength;
|
||||
const shearLineY = -45;
|
||||
const distanceToShearLine = Math.abs(keyPinTopY - shearLineY);
|
||||
|
||||
console.log(`Pin ${pin.index} final positioning: keyPinTopY=${keyPinTopY}, keyPinBottomY=${keyPinBottomY}, distanceToShearLine=${distanceToShearLine}`);
|
||||
|
||||
// Draw rectangular part of key pin
|
||||
pin.keyPin.fillRect(-12, -50 + pin.driverPinLength - pin.currentHeight, 24, pin.keyPinLength - 8);
|
||||
|
||||
@@ -1097,10 +1792,15 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
this.gameState.mouseDown = false;
|
||||
|
||||
// Always return hook to resting position when mouse is released
|
||||
if (this.hookPickGraphics && this.hookConfig) {
|
||||
// Only return hook to resting position if not in key mode
|
||||
if (!this.keyMode && this.hookPickGraphics && this.hookConfig) {
|
||||
this.returnHookToStart();
|
||||
}
|
||||
|
||||
// Stop key insertion if in key mode
|
||||
if (this.keyMode) {
|
||||
this.keyInserting = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Add keyboard bindings
|
||||
@@ -1283,9 +1983,33 @@ export class LockpickingMinigamePhaser extends MinigameScene {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add key interaction handlers if in key mode
|
||||
if (this.keyMode && this.keyClickZone) {
|
||||
console.log('Setting up key click handler...');
|
||||
this.keyClickZone.on('pointerdown', (pointer) => {
|
||||
console.log('Key clicked! Event triggered.');
|
||||
// Prevent this event from bubbling up to global handlers
|
||||
pointer.event.stopPropagation();
|
||||
|
||||
if (!this.keyInserting) {
|
||||
console.log('Starting key insertion animation...');
|
||||
this.startKeyInsertion();
|
||||
} else {
|
||||
console.log('Key insertion already in progress, ignoring click.');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Key mode or click zone not available:', { keyMode: this.keyMode, hasClickZone: !!this.keyClickZone });
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
// Skip normal lockpicking logic if in key mode
|
||||
if (this.keyMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lockState.currentPin && this.gameState.mouseDown) {
|
||||
this.liftPin();
|
||||
}
|
||||
|
||||
484
key-demo.html
Normal file
484
key-demo.html
Normal file
@@ -0,0 +1,484 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lockpicking Key Mode Demo</title>
|
||||
|
||||
<!-- Google Fonts - Press Start 2P, VT323 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Web Font Loader script to ensure fonts load properly -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
|
||||
<script>
|
||||
WebFont.load({
|
||||
google: {
|
||||
families: ['Press Start 2P', 'VT323']
|
||||
},
|
||||
active: function() {
|
||||
console.log('Fonts loaded successfully');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'VT323', monospace;
|
||||
background: #333;
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
/* Remove text-align center to prevent canvas positioning issues */
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 2px solid #444;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 24px;
|
||||
margin-bottom: 15px;
|
||||
text-shadow: 0 0 10px #00ff00;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.demo-container {
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
border: 2px solid #444;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
background: #1a1a1a;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.key-preset {
|
||||
background: #444;
|
||||
color: #00ff00;
|
||||
border: 2px solid #00ff00;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.key-preset:hover {
|
||||
background: #00ff00;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.key-preset.active {
|
||||
background: #00ff00;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.mode-toggle {
|
||||
background: #666;
|
||||
color: #fff;
|
||||
border: 2px solid #666;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mode-toggle:hover {
|
||||
background: #888;
|
||||
}
|
||||
|
||||
.mode-toggle.active {
|
||||
background: #00ff00;
|
||||
color: #000;
|
||||
border-color: #00ff00;
|
||||
}
|
||||
|
||||
.key-info {
|
||||
background: #2a2a2a;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #444;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.key-info h3 {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 14px;
|
||||
margin: 0 0 10px 0;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.key-cuts {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cut-indicator {
|
||||
background: #333;
|
||||
border: 1px solid #666;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
min-width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: #2a2a2a;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #444;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.instructions h3 {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 14px;
|
||||
margin: 0 0 10px 0;
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.instructions ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #00ff00;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
font-family: 'VT323', monospace;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
border: 1px solid #00ff00;
|
||||
min-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Prevent canvas positioning issues */
|
||||
canvas {
|
||||
display: block !important;
|
||||
margin: 0 auto !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
#gameContainer {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="header">
|
||||
<div class="title">🔑 LOCKPICKING KEY MODE DEMO 🔑</div>
|
||||
<div class="description">
|
||||
Test the new key mode functionality! Try different keys to see which ones work with the lock.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-container">
|
||||
<div class="controls">
|
||||
<button class="mode-toggle active" id="keyModeToggle">Key Mode</button>
|
||||
<button class="mode-toggle" id="pickModeToggle">Pick Mode</button>
|
||||
</div>
|
||||
|
||||
<div class="key-info" id="keyInfo">
|
||||
<h3>Current Key</h3>
|
||||
<div class="key-cuts" id="keyCuts">
|
||||
<div class="cut-indicator">25%</div>
|
||||
<div class="cut-indicator">50%</div>
|
||||
<div class="cut-indicator">75%</div>
|
||||
<div class="cut-indicator">30%</div>
|
||||
<div class="cut-indicator">60%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="key-preset active" data-key="key1">Key 1 (Correct)</div>
|
||||
<div class="key-preset" data-key="key2">Key 2 (Wrong)</div>
|
||||
<div class="key-preset" data-key="key3">Key 3 (Partial)</div>
|
||||
<div class="key-preset" data-key="key4">Key 4 (Random)</div>
|
||||
<div class="key-preset" data-key="key5">Key 5 (Deep)</div>
|
||||
</div>
|
||||
|
||||
<div class="game-container">
|
||||
<div id="gameContainer"></div>
|
||||
<div class="feedback" id="feedback">Select a key and try inserting it into the lock</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-container">
|
||||
<div class="instructions">
|
||||
<h3>How to Use</h3>
|
||||
<ul>
|
||||
<li><strong>Key Mode:</strong> Click and drag the key from left to right to insert it into the lock</li>
|
||||
<li><strong>Pick Mode:</strong> Use the tension wrench and hook pick to manually pick the lock</li>
|
||||
<li><strong>Key Cuts:</strong> The numbers show the depth of each cut as a percentage (0-100%)</li>
|
||||
<li><strong>Success:</strong> When the key cuts match the pin heights, the lock will unlock</li>
|
||||
<li><strong>Failure:</strong> Wrong keys will be rejected and the key will reset</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
|
||||
<script type="module">
|
||||
import { LockpickingMinigamePhaser } from './js/minigames/lockpicking/lockpicking-game-phaser.js';
|
||||
|
||||
class KeyDemo {
|
||||
constructor() {
|
||||
this.currentGame = null;
|
||||
this.currentMode = 'key';
|
||||
this.currentKey = 'key1';
|
||||
|
||||
// Key presets with different cut patterns
|
||||
this.keyPresets = {
|
||||
key1: {
|
||||
name: "Key 1 (Correct)",
|
||||
cuts: [25, 50, 75, 30, 60], // 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],
|
||||
description: "This key has cuts that don't match the pins"
|
||||
},
|
||||
key3: {
|
||||
name: "Key 3 (Partial)",
|
||||
cuts: [25, 50, 75, 40, 60],
|
||||
description: "This key has some correct cuts but not all"
|
||||
},
|
||||
key4: {
|
||||
name: "Key 4 (Random)",
|
||||
cuts: [15, 45, 80, 35, 55],
|
||||
description: "Random cut pattern - might work by chance"
|
||||
},
|
||||
key5: {
|
||||
name: "Key 5 (Deep)",
|
||||
cuts: [80, 90, 95, 85, 88],
|
||||
description: "Very deep cuts - probably too deep"
|
||||
}
|
||||
};
|
||||
|
||||
this.initializeUI();
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
initializeUI() {
|
||||
// Mode toggle buttons
|
||||
document.getElementById('keyModeToggle').addEventListener('click', () => {
|
||||
this.setMode('key');
|
||||
});
|
||||
|
||||
document.getElementById('pickModeToggle').addEventListener('click', () => {
|
||||
this.setMode('pick');
|
||||
});
|
||||
|
||||
// Key preset buttons
|
||||
document.querySelectorAll('.key-preset').forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
this.setKey(button.dataset.key);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setMode(mode) {
|
||||
this.currentMode = mode;
|
||||
|
||||
// Update button states
|
||||
document.getElementById('keyModeToggle').classList.toggle('active', mode === 'key');
|
||||
document.getElementById('pickModeToggle').classList.toggle('active', mode === 'pick');
|
||||
|
||||
// Show/hide key info
|
||||
document.getElementById('keyInfo').style.display = mode === 'key' ? 'block' : 'none';
|
||||
|
||||
// Restart game with new mode
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
setKey(keyId) {
|
||||
this.currentKey = keyId;
|
||||
|
||||
// Update button states
|
||||
document.querySelectorAll('.key-preset').forEach(button => {
|
||||
button.classList.toggle('active', button.dataset.key === keyId);
|
||||
});
|
||||
|
||||
// Update key cuts display
|
||||
this.updateKeyDisplay();
|
||||
|
||||
// Restart game with new key
|
||||
if (this.currentMode === 'key') {
|
||||
this.startGame();
|
||||
}
|
||||
}
|
||||
|
||||
updateKeyDisplay() {
|
||||
const keyData = this.keyPresets[this.currentKey];
|
||||
const keyCutsContainer = document.getElementById('keyCuts');
|
||||
|
||||
keyCutsContainer.innerHTML = '';
|
||||
|
||||
if (this.currentKey === 'key1' && this.currentGame && this.currentGame.keyData) {
|
||||
// Show the actual generated cuts for the correct key
|
||||
this.currentGame.keyData.cuts.forEach(cut => {
|
||||
const cutElement = document.createElement('div');
|
||||
cutElement.className = 'cut-indicator';
|
||||
cutElement.textContent = cut + '%';
|
||||
cutElement.style.backgroundColor = '#00ff00'; // Green for correct key
|
||||
keyCutsContainer.appendChild(cutElement);
|
||||
});
|
||||
} else {
|
||||
// Show the preset cuts for other keys
|
||||
keyData.cuts.forEach(cut => {
|
||||
const cutElement = document.createElement('div');
|
||||
cutElement.className = 'cut-indicator';
|
||||
cutElement.textContent = cut + '%';
|
||||
keyCutsContainer.appendChild(cutElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startGame() {
|
||||
if (this.currentGame) {
|
||||
this.currentGame.cleanup();
|
||||
}
|
||||
|
||||
const params = {
|
||||
pinCount: 5,
|
||||
difficulty: 'medium',
|
||||
thresholdSensitivity: 5,
|
||||
highlightBindingOrder: true,
|
||||
pinAlignmentHighlighting: true,
|
||||
liftSpeed: 1.0,
|
||||
lockable: { id: 'key-demo-lock' },
|
||||
closeButtonText: 'Reset',
|
||||
closeButtonAction: 'reset'
|
||||
};
|
||||
|
||||
// Add key mode parameters if in key mode
|
||||
if (this.currentMode === 'key') {
|
||||
params.keyMode = true;
|
||||
|
||||
// For the "correct" key, don't pass keyData so it generates from pins
|
||||
// For other keys, use the preset cuts
|
||||
if (this.currentKey === 'key1') {
|
||||
// Let the game generate the correct key from actual pin heights
|
||||
console.log('Using auto-generated correct key from pin heights');
|
||||
} else {
|
||||
params.keyData = {
|
||||
cuts: this.keyPresets[this.currentKey].cuts
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Starting game with params:', params);
|
||||
|
||||
this.currentGame = new LockpickingMinigamePhaser(
|
||||
document.getElementById('gameContainer'),
|
||||
params
|
||||
);
|
||||
|
||||
this.currentGame.init();
|
||||
this.currentGame.start();
|
||||
|
||||
// Update key display after game starts (for auto-generated correct key)
|
||||
if (this.currentMode === 'key' && this.currentKey === 'key1') {
|
||||
setTimeout(() => {
|
||||
this.updateKeyDisplay();
|
||||
}, 100); // Small delay to ensure game is initialized
|
||||
}
|
||||
|
||||
// Override the complete method to handle game completion
|
||||
const originalComplete = this.currentGame.complete.bind(this.currentGame);
|
||||
this.currentGame.complete = (success) => {
|
||||
console.log('Game completed with success:', success);
|
||||
|
||||
if (success) {
|
||||
document.getElementById('feedback').textContent =
|
||||
`🎉 Success! ${this.keyPresets[this.currentKey].name} worked!`;
|
||||
} else {
|
||||
document.getElementById('feedback').textContent =
|
||||
`❌ Failed! ${this.keyPresets[this.currentKey].name} didn't work.`;
|
||||
}
|
||||
|
||||
// Restart after a delay
|
||||
setTimeout(() => {
|
||||
this.startGame();
|
||||
}, 3000);
|
||||
|
||||
originalComplete(success);
|
||||
};
|
||||
|
||||
// Update feedback message
|
||||
if (this.currentMode === 'key') {
|
||||
document.getElementById('feedback').textContent =
|
||||
`Try inserting ${this.keyPresets[this.currentKey].name} into the lock (drag from left to right)`;
|
||||
} else {
|
||||
document.getElementById('feedback').textContent =
|
||||
"Use the tension wrench and hook pick to manually pick the lock";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the demo when the page loads
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new KeyDemo();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user