From cf3268e6b58bb770d11f46cde773e76e9e89de18 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Tue, 28 Oct 2025 20:01:47 +0000 Subject: [PATCH] feat: Normalize keyPins in starting inventory and generate cuts from lock configuration in minigames --- js/core/game.js | 10 ++++ js/core/rooms.js | 12 +--- js/systems/minigame-starters.js | 101 ++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 14 deletions(-) diff --git a/js/core/game.js b/js/core/game.js index 649d0fd..fe957d7 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -682,6 +682,16 @@ function normalizeScenarioKeyPins(scenario) { return Math.round(25 + (value / 100) * 40); } + // 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; diff --git a/js/core/rooms.js b/js/core/rooms.js index 2668877..26ffbbc 100644 --- a/js/core/rooms.js +++ b/js/core/rooms.js @@ -301,16 +301,8 @@ function applyTiledProperties(sprite, tiledItem) { * Stores scenario data and makes sprite interactive */ function applyScenarioProperties(sprite, scenarioObj, roomId, index) { - // Convert keyPins from 0-100 scale to 25-65 scale if needed - if (scenarioObj.keyPins && Array.isArray(scenarioObj.keyPins)) { - scenarioObj.keyPins = scenarioObj.keyPins.map(value => { - // Convert from 0-100 scale to 25-65 scale - // Formula: 25 + (value / 100) * 40 - const converted = 25 + (value / 100) * 40; - return Math.round(converted); - }); - console.log(`🔄 Converted keyPins to valid range (25-65):`, scenarioObj.keyPins); - } + // NOTE: keyPins are already normalized by normalizeScenarioKeyPins() in game.js + // Do NOT normalize here again to avoid double normalization sprite.scenarioData = scenarioObj; sprite.interactable = true; // Mark scenario items as interactable diff --git a/js/systems/minigame-starters.js b/js/systems/minigame-starters.js index 10954cb..f3c2f96 100644 --- a/js/systems/minigame-starters.js +++ b/js/systems/minigame-starters.js @@ -121,10 +121,32 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium', item.scenarioData.type === 'key' ); individualKeys.forEach(key => { + let cuts = key.scenarioData.cuts; + + // If no cuts but keyPins exists, keyPins represents the LOCK configuration this key matches + // Generate the cuts that would work with that lock configuration + 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 + // Use the generateKeyCutsForLock function with the key's keyPins as the lock config + cuts = lockKeyPins.map(keyPinLength => { + const keyBladeTop_world = 175; + const shearLine_world = 155; + const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; + const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine; + const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed)); + return Math.round(clampedCutDepth); + }); + + console.log(`Generated cuts for key "${key.scenarioData.name}":`, cuts); + } + keys.push({ id: key.scenarioData.key_id, name: key.scenarioData.name, - cuts: key.scenarioData.cuts || [] + cuts: cuts || [] }); }); @@ -135,10 +157,29 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium', ); if (keyRingItem && keyRingItem.scenarioData.allKeys) { keyRingItem.scenarioData.allKeys.forEach(keyData => { + let cuts = keyData.cuts; + + // If no cuts but keyPins exists, generate cuts from lock configuration + 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; + const shearLine_world = 155; + const gapFromKeyBladeTopToShearLine = keyBladeTop_world - shearLine_world; + const cutDepth_needed = keyPinLength - gapFromKeyBladeTopToShearLine; + const clampedCutDepth = Math.max(0, Math.min(110, cutDepth_needed)); + return Math.round(clampedCutDepth); + }); + + console.log(`Generated cuts for key ring key "${keyData.name}":`, cuts); + } + keys.push({ id: keyData.key_id, name: keyData.name, - cuts: keyData.cuts || [] + cuts: cuts || [] }); }); } @@ -220,6 +261,32 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe const inventoryKeys = keysToShow.map(key => { // Generate cuts data if not present let cuts = key.scenarioData.cuts; + + // If no cuts but keyPins exists, keyPins represents the LOCK configuration this key matches + // Generate the cuts that would work with that lock configuration + 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 + // keyPins on a key represent the lock's pin configuration, not the key's own properties + 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); + }); + + console.log(`Generated cuts for key "${key.scenarioData.name}":`, cuts); + } + + // If still no cuts, generate from lock configuration if (!cuts) { // Generate cuts that match the lock's pin configuration cuts = generateKeyCutsForLock(key, lockable); @@ -229,7 +296,7 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe id: key.scenarioData.key_id, name: key.scenarioData.name, cuts: cuts, - pinCount: key.scenarioData.pinCount || 4, // Default to 4 pins to match most locks + pinCount: cuts.length || key.scenarioData.pinCount || 4, // Use cuts length or default to 4 pins matchesLock: doesKeyMatchLock(key.scenarioData.key_id, lockId) // Add flag for matching }; }); @@ -363,6 +430,32 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe // Regenerate keys with the actual lock configuration now that it's been created const updatedInventoryKeys = playerKeys.map(key => { let cuts = key.scenarioData.cuts; + + // If no cuts but keyPins exists, keyPins represents the LOCK configuration this key matches + // Generate the cuts that would work with that lock configuration + 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 + // keyPins on a key represent the lock's pin configuration, not the key's own properties + 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); + }); + + console.log(`Generated cuts for key "${key.scenarioData.name}":`, cuts); + } + + // If still no cuts, generate from lock configuration if (!cuts) { cuts = generateKeyCutsForLock(key, lockable); } @@ -371,7 +464,7 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe id: key.scenarioData.key_id, name: key.scenarioData.name, cuts: cuts, - pinCount: key.scenarioData.pinCount || 4 + pinCount: cuts.length || key.scenarioData.pinCount || 4 }; });