From 5ecfa5db27e7846666c8c0ed6c52d95966f23b77 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Mon, 24 Nov 2025 13:41:35 +0000 Subject: [PATCH] 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 --- public/break_escape/js/core/game.js | 119 +----------------- .../lockpicking/key-data-generator.js | 43 ++----- .../js/systems/key-lock-system.js | 62 ++------- .../js/systems/minigame-starters.js | 69 ++-------- .../js/utils/key-cut-calculator.js | 55 ++++++++ scenarios/ceo_exfil/scenario.json.erb | 16 +-- 6 files changed, 92 insertions(+), 272 deletions(-) create mode 100644 public/break_escape/js/utils/key-cut-calculator.js diff --git a/public/break_escape/js/core/game.js b/public/break_escape/js/core/game.js index 870b065..7cab5ae 100644 --- a/public/break_escape/js/core/game.js +++ b/public/break_escape/js/core/game.js @@ -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) { diff --git a/public/break_escape/js/minigames/lockpicking/key-data-generator.js b/public/break_escape/js/minigames/lockpicking/key-data-generator.js index 8b3f70b..68f56b9 100644 --- a/public/break_escape/js/minigames/lockpicking/key-data-generator.js +++ b/public/break_escape/js/minigames/lockpicking/key-data-generator.js @@ -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); diff --git a/public/break_escape/js/systems/key-lock-system.js b/public/break_escape/js/systems/key-lock-system.js index 5f3f8f6..51ecb6d 100644 --- a/public/break_escape/js/systems/key-lock-system.js +++ b/public/break_escape/js/systems/key-lock-system.js @@ -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); diff --git a/public/break_escape/js/systems/minigame-starters.js b/public/break_escape/js/systems/minigame-starters.js index cc8fe9b..983ba72 100644 --- a/public/break_escape/js/systems/minigame-starters.js +++ b/public/break_escape/js/systems/minigame-starters.js @@ -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); } diff --git a/public/break_escape/js/utils/key-cut-calculator.js b/public/break_escape/js/utils/key-cut-calculator.js new file mode 100644 index 0000000..30c3b88 --- /dev/null +++ b/public/break_escape/js/utils/key-cut-calculator.js @@ -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; diff --git a/scenarios/ceo_exfil/scenario.json.erb b/scenarios/ceo_exfil/scenario.json.erb index ee81d01..47da6b6 100644 --- a/scenarios/ceo_exfil/scenario.json.erb +++ b/scenarios/ceo_exfil/scenario.json.erb @@ -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'" } ]