feat: Normalize keyPins in starting inventory and generate cuts from lock configuration in minigames

This commit is contained in:
Z. Cliffe Schreuders
2025-10-28 20:01:47 +00:00
parent 5cd07b5d4b
commit cf3268e6b5
3 changed files with 109 additions and 14 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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
};
});