mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat: Implement keyPins normalization and conversion logic for accurate cut generation in minigames
This commit is contained in:
@@ -672,13 +672,51 @@ function findObjectsAtPosition(worldX, worldY) {
|
||||
return objectsAtPosition;
|
||||
}
|
||||
|
||||
// Normalize keyPins from 0-100 scale to 25-65 scale in entire scenario
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -299,10 +299,20 @@ function applyTiledProperties(sprite, tiledItem) {
|
||||
/**
|
||||
* Helper: Apply game logic properties from scenario to sprite
|
||||
* Stores scenario data and makes sprite interactive
|
||||
*
|
||||
* IMPORTANT - KeyPins Normalization:
|
||||
* ===================================
|
||||
* KeyPins are already normalized by normalizeScenarioKeyPins() in game.js during scenario load.
|
||||
* This happens BEFORE any sprites are created, converting 0-100 scale to 25-65 pixel range.
|
||||
*
|
||||
* Do NOT normalize keyPins here - it would cause double normalization:
|
||||
* - Original: [100, 0, 100, 0]
|
||||
* - After 1st normalization (game.js): [65, 25, 65, 25] ✓
|
||||
* - After 2nd normalization (here): [51, 35, 51, 35] ✗ WRONG!
|
||||
*
|
||||
* The sprite simply receives the already-normalized values from scenarioObj.
|
||||
*/
|
||||
function applyScenarioProperties(sprite, scenarioObj, roomId, index) {
|
||||
// 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
|
||||
|
||||
@@ -123,20 +123,20 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium',
|
||||
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
|
||||
// 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)
|
||||
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);
|
||||
console.log(`Generating cuts from lock keyPins for available 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
|
||||
// Convert lock pin lengths to key cut depths
|
||||
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));
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -159,17 +159,18 @@ export function startLockpickingMinigame(lockable, scene, difficulty = 'medium',
|
||||
keyRingItem.scenarioData.allKeys.forEach(keyData => {
|
||||
let cuts = keyData.cuts;
|
||||
|
||||
// If no cuts but keyPins exists, generate cuts from lock configuration
|
||||
// KEYPIN TO CUT CONVERSION (for key ring keys):
|
||||
// Same conversion as above - keyPins → cuts using the formula
|
||||
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));
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -262,23 +263,36 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe
|
||||
// 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
|
||||
// KEYPIN TO CUT CONVERSION:
|
||||
// ==========================
|
||||
// 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.
|
||||
//
|
||||
// We convert keyPins (lock pin lengths) to cuts (key blade notch depths) using the formula:
|
||||
// cutDepth = keyPinLength - gapFromKeyBladeTopToShearLine
|
||||
//
|
||||
// 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.
|
||||
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
|
||||
// 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
|
||||
|
||||
// Calculate the required cut depth
|
||||
// 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);
|
||||
});
|
||||
@@ -427,18 +441,19 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe
|
||||
// Wait for the minigame to be fully initialized and lock configuration to be saved
|
||||
setTimeout(() => {
|
||||
if (window.MinigameFramework.currentMinigame && window.MinigameFramework.currentMinigame.startWithKeySelection) {
|
||||
// Regenerate keys with the actual lock configuration now that it's been created
|
||||
// DEFERRED KEY PREPARATION:
|
||||
// Regenerate keys after minigame initialization to ensure lock config is saved
|
||||
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
|
||||
// KEYPIN TO CUT CONVERSION (deferred update):
|
||||
// Same as above - convert normalized keyPins (25-65) to cuts for visualization
|
||||
// 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
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user