mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat: Implement KeyCutCalculator utility for consistent key cut depth calculations across the game
refactor: Update key-lock system and minigame starters to utilize KeyCutCalculator for cut depth generation chore: Normalize keyPins in scenario data to align with new cut depth calculations
This commit is contained in:
@@ -506,9 +506,6 @@ export async function create() {
|
||||
window.gameState.globalVariables = {};
|
||||
}
|
||||
|
||||
// Normalize keyPins in all rooms and objects from 0-100 scale to 25-65 scale
|
||||
normalizeScenarioKeyPins(gameScenario);
|
||||
|
||||
// Debug: log what we loaded
|
||||
console.log('🎮 Loaded gameScenario with rooms:', Object.keys(gameScenario?.rooms || {}));
|
||||
if (gameScenario?.rooms?.office1) {
|
||||
@@ -969,121 +966,7 @@ function findNPCAtPosition(worldX, worldY) {
|
||||
return closestNPC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize keyPins from 0-100 scale to 25-65 scale in entire scenario
|
||||
*
|
||||
* KEYPIN TERMINOLOGY AND RELATIONSHIPS:
|
||||
* =====================================
|
||||
*
|
||||
* Lock Pin Anatomy:
|
||||
* - Each lock chamber contains TWO pins stacked vertically:
|
||||
* 1. DRIVER PIN (top, spring-loaded, blocks rotation)
|
||||
* 2. KEY PIN (bottom, rests on key when inserted)
|
||||
*
|
||||
* - The SHEAR LINE is the boundary between the plug and housing
|
||||
* - Lock opens when ALL key pins' tops align exactly at the shear line
|
||||
*
|
||||
* KeyPins Property:
|
||||
* - "keyPins" represents the LENGTH of the key pins (bottom pins) in the lock
|
||||
* - In scenarios: Defined on a 0-100 authoring scale for ease of design
|
||||
* - In game: Converted to 25-65 pixel range for actual rendering
|
||||
* - Example: [100, 0, 100, 0] → [65, 25, 65, 25] pixels
|
||||
*
|
||||
* KeyPins on LOCKS vs KEYS:
|
||||
* - On a LOCK/DOOR: The actual pin lengths in that lock
|
||||
* - On a KEY: The lock configuration this key is designed to open
|
||||
* (i.e., a key with keyPins: [65, 25, 65, 25] opens locks with those pin lengths)
|
||||
*
|
||||
* Relationship: KeyPins → Cuts:
|
||||
* - CUTS are the depths of notches carved into the key blade
|
||||
* - Formula: cutDepth = keyPinLength - gapFromKeyBladeTopToShearLine
|
||||
* - Where gap = 20 pixels (175 - 155)
|
||||
* - Example: keyPin 65 → cut 45, keyPin 25 → cut 5
|
||||
* - When key is inserted, pin rests on cut surface, lifting to shear line
|
||||
*
|
||||
* Why Normalization is Critical:
|
||||
* - Scenarios use 0-100 for easy authoring (percentages)
|
||||
* - Game needs 25-65 pixel range for proper physics/rendering
|
||||
* - Normalization must happen EXACTLY ONCE to avoid double-conversion
|
||||
* - This function runs during scenario load before any sprites are created
|
||||
*/
|
||||
function normalizeScenarioKeyPins(scenario) {
|
||||
|
||||
// Helper function to convert a single keyPins value
|
||||
function convertKeyPin(value) {
|
||||
// Convert from 0-100 scale to 25-65 scale
|
||||
// Formula: 25 + (value / 100) * 40
|
||||
// This gives us a 40-pixel range centered in the lock chamber
|
||||
return Math.round(25 + (value / 100) * 40);
|
||||
}
|
||||
|
||||
// IMPORTANT: Normalize keyPins in ALL places they appear in the scenario!
|
||||
// KeyPins must be normalized exactly once at scenario load time, BEFORE any items are dropped or used.
|
||||
// If keyPins appear in multiple places (startItemsInInventory, room objects, NPC itemsHeld, etc.),
|
||||
// they ALL need normalization or keys/locks won't match when used.
|
||||
// For example:
|
||||
// - A key in an NPC's itemsHeld must be normalized the same way as a room object key
|
||||
// - The door's room-level keyPins must be normalized the same way
|
||||
// - Otherwise when the NPC drops the key, it won't open the door!
|
||||
|
||||
// Normalize keyPins in startItemsInInventory (for starting keys)
|
||||
if (scenario.startItemsInInventory && Array.isArray(scenario.startItemsInInventory)) {
|
||||
scenario.startItemsInInventory.forEach((item, index) => {
|
||||
if (item.keyPins && Array.isArray(item.keyPins)) {
|
||||
item.keyPins = item.keyPins.map(convertKeyPin);
|
||||
console.log(`🔄 Normalized startItem keyPins [${index}] (${item.type} "${item.name}"):`, item.keyPins);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Iterate through all rooms
|
||||
Object.entries(scenario.rooms).forEach(([roomId, roomData]) => {
|
||||
if (!roomData) return;
|
||||
|
||||
// Convert room-level keyPins (for door locks)
|
||||
if (roomData.keyPins && Array.isArray(roomData.keyPins)) {
|
||||
roomData.keyPins = roomData.keyPins.map(convertKeyPin);
|
||||
console.log(`🔄 Normalized room keyPins for ${roomId}:`, roomData.keyPins);
|
||||
}
|
||||
|
||||
// Normalize NPC itemsHeld keyPins (items NPCs start with/carry)
|
||||
// CRITICAL: This ensures keys dropped by NPCs match the door's keyPins
|
||||
if (roomData.npcs && Array.isArray(roomData.npcs)) {
|
||||
roomData.npcs.forEach((npc, npcIndex) => {
|
||||
if (npc.itemsHeld && Array.isArray(npc.itemsHeld)) {
|
||||
npc.itemsHeld.forEach((item, itemIndex) => {
|
||||
if (item.keyPins && Array.isArray(item.keyPins)) {
|
||||
item.keyPins = item.keyPins.map(convertKeyPin);
|
||||
console.log(`🔄 Normalized NPC itemsHeld keyPins for ${roomId}.npcs[${npcIndex}].itemsHeld[${itemIndex}] (${item.type}):`, item.keyPins);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Convert keyPins for all objects in the room
|
||||
if (roomData.objects && Array.isArray(roomData.objects)) {
|
||||
roomData.objects.forEach((obj, index) => {
|
||||
if (obj.keyPins && Array.isArray(obj.keyPins)) {
|
||||
obj.keyPins = obj.keyPins.map(convertKeyPin);
|
||||
console.log(`🔄 Normalized object keyPins for ${roomId}[${index}] (${obj.type}):`, obj.keyPins);
|
||||
}
|
||||
|
||||
// Also check contents of objects (for keys in briefcases, etc.)
|
||||
if (obj.contents && Array.isArray(obj.contents)) {
|
||||
obj.contents.forEach((content, contentIndex) => {
|
||||
if (content.keyPins && Array.isArray(content.keyPins)) {
|
||||
content.keyPins = content.keyPins.map(convertKeyPin);
|
||||
console.log(`🔄 Normalized content keyPins for ${roomId}[${index}].contents[${contentIndex}] (${content.type}):`, content.keyPins);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✓ All keyPins normalized to 25-65 range');
|
||||
}
|
||||
|
||||
|
||||
// Hide a room
|
||||
function hideRoom(roomId) {
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
* - this.parent.lockState (lock state object)
|
||||
* etc.
|
||||
*/
|
||||
|
||||
import KeyCutCalculator from '../../utils/key-cut-calculator.js';
|
||||
|
||||
export class KeyDataGenerator {
|
||||
|
||||
constructor(parent) {
|
||||
@@ -20,42 +23,12 @@ export class KeyDataGenerator {
|
||||
|
||||
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)
|
||||
// Uses KeyCutCalculator utility for consistent calculation across all code paths
|
||||
const keyPinLengths = this.parent.pins
|
||||
.slice(0, this.parent.pinCount)
|
||||
.map(pin => pin.keyPinLength);
|
||||
|
||||
for (let i = 0; i < this.parent.pinCount; i++) {
|
||||
const pin = this.parent.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);
|
||||
}
|
||||
const cuts = KeyCutCalculator.calculateCutDepthsRounded(keyPinLengths);
|
||||
|
||||
this.parent.keyData = { cuts: cuts };
|
||||
console.log('Generated key data from pins:', this.parent.keyData);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* This ensures consistent lock configurations and key cuts throughout the game.
|
||||
*/
|
||||
|
||||
import KeyCutCalculator from '../utils/key-cut-calculator.js';
|
||||
|
||||
// Global key-lock mapping system
|
||||
// This ensures each key matches exactly one lock in the game
|
||||
window.keyLockMappings = window.keyLockMappings || {};
|
||||
@@ -252,35 +254,13 @@ export function generateKeyCutsForLock(key, lockable, overrideKeyPins = null) {
|
||||
} else if (lockable?.keyPins || lockable?.key_pins) {
|
||||
keyPinsToUse = lockable.keyPins || lockable.key_pins;
|
||||
console.log(`✓ Using keyPins from lockable object:`, keyPinsToUse);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If we have keyPins from the scenario, use them directly
|
||||
if (keyPinsToUse && Array.isArray(keyPinsToUse)) {
|
||||
console.log(`Generating cuts for key "${key.scenarioData.name}" using scenario keyPins:`, keyPinsToUse);
|
||||
|
||||
const cuts = [];
|
||||
for (let i = 0; i < keyPinsToUse.length; i++) {
|
||||
const keyPinLength = keyPinsToUse[i];
|
||||
|
||||
// Calculate cut depth with relationship to key pin length
|
||||
// Based on the lockpicking minigame formula:
|
||||
// Cut depth = key pin length - gap from key blade top to shear line
|
||||
const keyBladeTop_world = 175; // Key blade top position
|
||||
const shearLine_world = 155; // Shear line position
|
||||
const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; // 20
|
||||
|
||||
// Calculate the required cut depth
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine;
|
||||
|
||||
// Clamp to valid range (0 to 110, which is key blade height)
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed));
|
||||
|
||||
cuts.push(Math.round(clampedCutDepth));
|
||||
|
||||
console.log(`Pin ${i}: keyPinLength=${keyPinLength}, cutDepth=${clampedCutDepth} (gap=${gapFromKeyBladeTopToShearLine})`);
|
||||
}
|
||||
|
||||
const cuts = KeyCutCalculator.calculateCutDepthsRounded(keyPinsToUse);
|
||||
console.log(`Generated cuts for key ${keyId} using scenario keyPins:`, cuts);
|
||||
return cuts;
|
||||
}
|
||||
@@ -298,23 +278,7 @@ export function generateKeyCutsForLock(key, lockable, overrideKeyPins = null) {
|
||||
|
||||
for (let i = 0; i < lockConfig.pinCount; i++) {
|
||||
const keyPinLength = pinHeights[i] || 30; // Use predefined pin height
|
||||
|
||||
// Calculate cut depth with relationship to key pin length
|
||||
// Based on the lockpicking minigame formula:
|
||||
// Cut depth = key pin length - gap from key blade top to shear line
|
||||
const keyBladeTop_world = 175; // Key blade top position
|
||||
const shearLine_world = 155; // Shear line position
|
||||
const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; // 20
|
||||
|
||||
// Calculate the required cut depth
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine;
|
||||
|
||||
// Clamp to valid range (0 to 110, which is key blade height)
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed));
|
||||
|
||||
cuts.push(Math.round(clampedCutDepth));
|
||||
|
||||
console.log(`Pin ${i}: keyPinLength=${keyPinLength}, cutDepth=${clampedCutDepth} (gap=${gapFromKeyBladeTopToShearLine})`);
|
||||
cuts.push(KeyCutCalculator.calculateCutDepth(keyPinLength));
|
||||
}
|
||||
|
||||
console.log(`Generated cuts for key ${keyId} (assigned to ${mapping.lockId}):`, cuts);
|
||||
@@ -356,20 +320,8 @@ export function generateKeyCutsForLock(key, lockable, overrideKeyPins = null) {
|
||||
for (let i = 0; i < lockConfig.pinCount; i++) {
|
||||
const keyPinLength = pinHeights[i] || (25 + Math.random() * 37.5); // Default if missing
|
||||
|
||||
// Calculate cut depth with INVERSE relationship to key pin length
|
||||
// Based on the lockpicking minigame formula:
|
||||
// Cut depth = key pin length - gap from key blade top to shear line
|
||||
const keyBladeTop_world = 175; // Key blade top position
|
||||
const shearLine_world = 155; // Shear line position
|
||||
const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; // 20
|
||||
|
||||
// Calculate the required cut depth
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine;
|
||||
|
||||
// Clamp to valid range (0 to 110, which is key blade height)
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed));
|
||||
|
||||
cuts.push(Math.round(clampedCutDepth));
|
||||
// Calculate cut depth using utility
|
||||
cuts.push(KeyCutCalculator.calculateCutDepth(keyPinLength));
|
||||
}
|
||||
|
||||
console.log(`Generated cuts for key ${key.scenarioData.key_id}:`, cuts);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { generateKeyCutsForLock, doesKeyMatchLock, PREDEFINED_LOCK_CONFIGS } from './key-lock-system.js';
|
||||
import KeyCutCalculator from '../utils/key-cut-calculator.js';
|
||||
|
||||
export function startLockpickingMinigame(lockable, scene, difficulty = 'medium', callback, keyPins = null) {
|
||||
console.log('🎮 startLockpickingMinigame called with:', {
|
||||
@@ -125,20 +126,13 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium',
|
||||
|
||||
// KEYPIN TO CUT CONVERSION (for available keys):
|
||||
// If no cuts but keyPins exists, generate cuts from the lock configuration.
|
||||
// keyPins on a key = the lock configuration this key opens (already normalized to 25-65)
|
||||
// keyPins on a key = the lock configuration this key opens (in pixel units: 25-65)
|
||||
if (!cuts && (key.scenarioData.keyPins || key.keyPins)) {
|
||||
const lockKeyPins = key.scenarioData.keyPins || key.keyPins;
|
||||
console.log(`Generating cuts from lock keyPins for available key "${key.scenarioData.name}":`, lockKeyPins);
|
||||
|
||||
// Convert lock pin lengths to key cut depths
|
||||
cuts = lockKeyPins.map(keyPinLength => {
|
||||
const keyBladeTop_world = 175; // Key blade top when inserted
|
||||
const shearLine_world = 155; // Shear line position
|
||||
const gapFromKeyBladeTopToShearLine = 20; // Distance between them
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine; // Cut depth formula
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed)); // Clamp to blade height
|
||||
return Math.round(clampedCutDepth);
|
||||
});
|
||||
// Convert lock pin lengths to key cut depths using utility
|
||||
cuts = KeyCutCalculator.calculateCutDepthsRounded(lockKeyPins);
|
||||
|
||||
console.log(`Generated cuts for key "${key.scenarioData.name}":`, cuts);
|
||||
}
|
||||
@@ -160,19 +154,11 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium',
|
||||
let cuts = keyData.cuts;
|
||||
|
||||
// KEYPIN TO CUT CONVERSION (for key ring keys):
|
||||
// Same conversion as above - keyPins → cuts using the formula
|
||||
// Convert keyPins to cuts using KeyCutCalculator utility
|
||||
if (!cuts && keyData.keyPins) {
|
||||
const lockKeyPins = keyData.keyPins;
|
||||
console.log(`Generating cuts from lock keyPins for key ring key "${keyData.name}":`, lockKeyPins);
|
||||
|
||||
cuts = lockKeyPins.map(keyPinLength => {
|
||||
const keyBladeTop_world = 175; // Key blade top when inserted
|
||||
const shearLine_world = 155; // Shear line position
|
||||
const gapFromKeyBladeTopToShearLine = 20; // Distance between them
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine; // Cut depth formula
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed)); // Clamp to blade height
|
||||
return Math.round(clampedCutDepth);
|
||||
});
|
||||
cuts = KeyCutCalculator.calculateCutDepthsRounded(lockKeyPins);
|
||||
|
||||
console.log(`Generated cuts for key ring key "${keyData.name}":`, cuts);
|
||||
}
|
||||
@@ -268,34 +254,18 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe
|
||||
// If no cuts but keyPins exists, we need to generate cuts from the lock configuration.
|
||||
//
|
||||
// Remember: keyPins on a KEY represent the LOCK configuration this key is designed to open.
|
||||
// The keyPins values have already been normalized from 0-100 to 25-65 pixel range.
|
||||
// The keyPins values are in pixel units (25-65 range).
|
||||
//
|
||||
// We convert keyPins (lock pin lengths) to cuts (key blade notch depths) using the formula:
|
||||
// cutDepth = keyPinLength - gapFromKeyBladeTopToShearLine
|
||||
// cutDepth = keyPinLength + 8px (for the curved bottom of the pin)
|
||||
//
|
||||
// This ensures when the key is inserted, each pin rests on its corresponding cut,
|
||||
// lifting the pin so its top aligns exactly with the shear line.
|
||||
// This ensures when the key is inserted, each pin rests on its corresponding cut.
|
||||
if (!cuts && (key.scenarioData.keyPins || key.keyPins)) {
|
||||
const lockKeyPins = key.scenarioData.keyPins || key.keyPins;
|
||||
console.log(`Generating cuts from lock keyPins for key "${key.scenarioData.name}":`, lockKeyPins);
|
||||
|
||||
// Generate cuts that match this lock configuration
|
||||
cuts = lockKeyPins.map(keyPinLength => {
|
||||
// Key blade geometry (in world coordinates):
|
||||
const keyBladeTop_world = 175; // Top surface of key blade when inserted
|
||||
const shearLine_world = 155; // Where lock plug meets housing
|
||||
const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; // 20 pixels
|
||||
|
||||
// Formula: cutDepth = keyPinLength - gap
|
||||
// Example: If keyPin is 65 pixels long, cut must be 45 pixels deep (65 - 20)
|
||||
// This ensures the pin's bottom rests on the cut, lifting its top to the shear line
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine;
|
||||
|
||||
// Clamp to valid range (0 to 110, which is key blade height)
|
||||
// This prevents negative cuts or cuts deeper than the blade itself
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed));
|
||||
return Math.round(clampedCutDepth);
|
||||
});
|
||||
// Generate cuts that match this lock configuration using utility
|
||||
cuts = KeyCutCalculator.calculateCutDepthsRounded(lockKeyPins);
|
||||
|
||||
console.log(`Generated cuts for key "${key.scenarioData.name}":`, cuts);
|
||||
}
|
||||
@@ -447,25 +417,12 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe
|
||||
let cuts = key.scenarioData.cuts;
|
||||
|
||||
// KEYPIN TO CUT CONVERSION (deferred update):
|
||||
// Same as above - convert normalized keyPins (25-65) to cuts for visualization
|
||||
// Convert keyPins to cuts for visualization using utility
|
||||
// This ensures each key displays its actual unique cut pattern
|
||||
if (!cuts && (key.scenarioData.keyPins || key.keyPins)) {
|
||||
const lockKeyPins = key.scenarioData.keyPins || key.keyPins;
|
||||
console.log(`Generating cuts from lock keyPins for key "${key.scenarioData.name}":`, lockKeyPins);
|
||||
|
||||
// Generate cuts that match this lock configuration
|
||||
cuts = lockKeyPins.map(keyPinLength => {
|
||||
const keyBladeTop_world = 175; // Key blade top position
|
||||
const shearLine_world = 155; // Shear line position
|
||||
const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; // 20
|
||||
|
||||
// Calculate the required cut depth
|
||||
const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine;
|
||||
|
||||
// Clamp to valid range (0 to 110, which is key blade height)
|
||||
const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed));
|
||||
return Math.round(clampedCutDepth);
|
||||
});
|
||||
cuts = KeyCutCalculator.calculateCutDepthsRounded(lockKeyPins);
|
||||
|
||||
console.log(`Generated cuts for key "${key.scenarioData.name}":`, cuts);
|
||||
}
|
||||
|
||||
55
public/break_escape/js/utils/key-cut-calculator.js
Normal file
55
public/break_escape/js/utils/key-cut-calculator.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* KeyCutCalculator
|
||||
*
|
||||
* Utility for calculating key cut depths based on key pin lengths
|
||||
* Uses the geometric formula: cutDepth = keyPinLength - gapFromKeyBladeTopToShearLine
|
||||
*
|
||||
* The gap of 20px accounts for:
|
||||
* - Key blade top at Y=175 (keyway center 230 - blade height/2 55)
|
||||
* - Shear line at Y=155 (pin container 200 - shear line local -45)
|
||||
* - Gap = 175 - 155 = 20px
|
||||
*/
|
||||
export class KeyCutCalculator {
|
||||
|
||||
// The geometric gap between key blade top and shear line
|
||||
static GAP_FROM_KEY_BLADE_TOP_TO_SHEAR_LINE = 20;
|
||||
|
||||
// Maximum key blade height (constraint)
|
||||
static MAX_KEY_BLADE_HEIGHT = 110;
|
||||
|
||||
// Minimum cut depth
|
||||
static MIN_CUT_DEPTH = 0;
|
||||
|
||||
/**
|
||||
* Calculate cut depth for a single key pin
|
||||
* @param {number} keyPinLength - Height of the key pin in pixels
|
||||
* @returns {number} Cut depth in pixels, clamped to valid range
|
||||
*/
|
||||
static calculateCutDepth(keyPinLength) {
|
||||
const cutDepth = keyPinLength - this.GAP_FROM_KEY_BLADE_TOP_TO_SHEAR_LINE;
|
||||
return Math.max(
|
||||
this.MIN_CUT_DEPTH,
|
||||
Math.min(this.MAX_KEY_BLADE_HEIGHT, cutDepth)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate cut depths for an array of key pin lengths
|
||||
* @param {number[]} keyPinLengths - Array of key pin heights
|
||||
* @returns {number[]} Array of cut depths
|
||||
*/
|
||||
static calculateCutDepths(keyPinLengths) {
|
||||
return keyPinLengths.map(keyPinLength => this.calculateCutDepth(keyPinLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and round cut depths for an array of key pin lengths
|
||||
* @param {number[]} keyPinLengths - Array of key pin heights
|
||||
* @returns {number[]} Array of rounded cut depths
|
||||
*/
|
||||
static calculateCutDepthsRounded(keyPinLengths) {
|
||||
return this.calculateCutDepths(keyPinLengths).map(depth => Math.round(depth));
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyCutCalculator;
|
||||
@@ -189,7 +189,7 @@
|
||||
"name": "Office Key",
|
||||
"takeable": true,
|
||||
"key_id": "office1_key",
|
||||
"keyPins": [100, 0, 100, 0],
|
||||
"keyPins": [65, 25, 65, 25],
|
||||
"observations": "A key to access the office areas"
|
||||
},
|
||||
{
|
||||
@@ -224,7 +224,7 @@
|
||||
"locked": true,
|
||||
"lockType": "key",
|
||||
"requires": "office1_key",
|
||||
"keyPins": [100, 0, 100, 0],
|
||||
"keyPins": [65, 25, 65, 25],
|
||||
"difficulty": "easy",
|
||||
"door_sign": "4A Hot Desks",
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
"name": "CEO Office Key",
|
||||
"takeable": true,
|
||||
"key_id": "ceo_office_key",
|
||||
"keyPins": [0, 50, 100, 150],
|
||||
"keyPins": [25, 45, 65, 85],
|
||||
"observations": "A spare key to the CEO's office, carelessly left behind"
|
||||
}
|
||||
]
|
||||
@@ -332,7 +332,7 @@
|
||||
"locked": true,
|
||||
"lockType": "key",
|
||||
"requires": "ceo_office_key",
|
||||
"keyPins": [0, 50, 100, 150],
|
||||
"keyPins": [25, 45, 65, 85],
|
||||
"difficulty": "easy",
|
||||
"objects": [
|
||||
{
|
||||
@@ -355,7 +355,7 @@
|
||||
"locked": true,
|
||||
"lockType": "key",
|
||||
"requires": "briefcase_key",
|
||||
"keyPins": [50, 25, 0, 75],
|
||||
"keyPins": [45, 35, 25, 55],
|
||||
"difficulty": "medium",
|
||||
"observations": "An expensive leather briefcase with a sturdy lock",
|
||||
"contents": [
|
||||
@@ -372,7 +372,7 @@
|
||||
"name": "Safe Key",
|
||||
"takeable": true,
|
||||
"key_id": "safe_key",
|
||||
"keyPins": [68, 10, 48, 30],
|
||||
"keyPins": [52, 29, 44, 37],
|
||||
"observations": "A heavy-duty safe key hidden behind server equipment"
|
||||
}
|
||||
]
|
||||
@@ -405,7 +405,7 @@
|
||||
"locked": true,
|
||||
"lockType": "key",
|
||||
"requires": "safe_key",
|
||||
"keyPins": [68, 10, 48, 30],
|
||||
"keyPins": [52, 29, 44, 37],
|
||||
"difficulty": "hard",
|
||||
"observations": "A well-hidden wall safe behind a painting",
|
||||
"contents": [
|
||||
@@ -441,7 +441,7 @@
|
||||
"name": "Briefcase Key",
|
||||
"takeable": true,
|
||||
"key_id": "briefcase_key",
|
||||
"keyPins": [50, 25, 0, 75],
|
||||
"keyPins": [45, 35, 25, 55],
|
||||
"observations": "A small key labeled 'Personal - Do Not Copy'"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user