diff --git a/js/core/game.js b/js/core/game.js index 31a05d7..e5a1111 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -942,6 +942,15 @@ function normalizeScenarioKeyPins(scenario) { 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) => { @@ -962,6 +971,21 @@ function normalizeScenarioKeyPins(scenario) { 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) => { diff --git a/js/core/rooms.js b/js/core/rooms.js index 37be7d1..bcfbc8b 100644 --- a/js/core/rooms.js +++ b/js/core/rooms.js @@ -1979,14 +1979,29 @@ function createNPCSpritesForRoom(roomId, roomData) { console.log(` npcManager: ${!!window.npcManager}`); console.log(` gameRef: ${!!gameRef}`); - // Get the current scene - use gameRef.scene (the current scene manager) or fall back to window.game - const currentScene = gameRef.scene || window.game?.scene?.scenes?.[0]; - console.log(` currentScene: ${!!currentScene}, key: ${currentScene?.key}`); + // Get the current scene instance - need to get it from the scene manager + // gameRef.scene is the SceneManager, we need gameRef.scene.getScene() to get the actual scene + let currentScene = null; + if (gameRef && gameRef.scene && typeof gameRef.scene.getScene === 'function') { + // Get the running scene from the scene manager + currentScene = gameRef.scene.getScene('default') || + gameRef.scene.scenes?.[0]; + } + if (!currentScene && window.game?.scene) { + currentScene = window.game.scene.getScene('default') || + window.game.scene.scenes?.[0]; + } - if (currentScene) { + console.log(` currentScene: ${!!currentScene}, key: ${currentScene?.key}, isScene: ${currentScene?.add ? 'yes' : 'no'}`); + + if (currentScene && typeof currentScene.add?.graphics === 'function') { window.npcManager.setLOSVisualization(true, currentScene); } else { - console.warn(`⚠️ Cannot get current scene for LOS visualization`); + console.warn(`⚠️ Cannot get valid Phaser scene for LOS visualization`, { + currentScene: !!currentScene, + hasAddMethod: !!currentScene?.add, + hasGraphicsMethod: typeof currentScene?.add?.graphics + }); } } else { console.log(`👁️ No NPCs requesting LOS visualization in room ${roomId}`); diff --git a/js/core/title-screen.js b/js/core/title-screen.js new file mode 100644 index 0000000..e69de29 diff --git a/js/systems/doors.js b/js/systems/doors.js index 1e7db45..fa6e516 100644 --- a/js/systems/doors.js +++ b/js/systems/doors.js @@ -227,6 +227,18 @@ export function createDoorSpritesForRoom(roomId, position) { // Check for both keyPins (camelCase) and key_pins (snake_case) in the room data const keyPinsArray = connectedRoomData?.keyPins || connectedRoomData?.key_pins; + // DEBUG: Log what we're finding + if (connectedRoomData?.locked) { + console.log(`🔍 Door keyPins lookup for ${connectedRoom}:`, { + connectedRoomData_keyPins: connectedRoomData?.keyPins, + connectedRoomData_key_pins: connectedRoomData?.key_pins, + finalKeyPinsArray: keyPinsArray, + locked: connectedRoomData?.locked, + lockType: connectedRoomData?.lockType, + requires: connectedRoomData?.requires + }); + } + // Set up door properties doorSprite.doorProperties = { roomId: roomId, diff --git a/js/systems/inventory.js b/js/systems/inventory.js index 3585371..b37bc4b 100644 --- a/js/systems/inventory.js +++ b/js/systems/inventory.js @@ -364,12 +364,22 @@ function addKeyToInventory(sprite) { }; } + // DEBUG: Check properties before adding + const keyId = sprite.scenarioData?.key_id || sprite.key_id; + const keyPins = sprite.scenarioData?.keyPins || sprite.keyPins; + console.log(`🔑 BEFORE adding key to ring (sprite object):`, { + sprite_key_id: sprite.key_id, + sprite_keyPins: sprite.keyPins, + scenarioData_key_id: sprite.scenarioData?.key_id, + scenarioData_keyPins: sprite.scenarioData?.keyPins, + resolved_key_id: keyId, + resolved_keyPins: keyPins + }); + // Add the key to the key ring window.inventory.keyRing.keys.push(sprite); // Log key storage with keyPins - const keyId = sprite.scenarioData?.key_id || sprite.key_id; - const keyPins = sprite.scenarioData?.keyPins || sprite.keyPins; console.log(`✓ Key "${sprite.scenarioData?.name}" added to key ring:`, { key_id: keyId, keyPins: keyPins, @@ -380,6 +390,13 @@ function addKeyToInventory(sprite) { // Update or create the key ring display updateKeyRingDisplay(); + // IMPORTANT: Reinitialize key-lock mappings now that we have a new key + // This is critical for newly acquired keys (e.g., dropped by NPCs) to unlock doors + if (window.initializeKeyLockMappings) { + console.log('🔑 Reinitializing key-lock mappings after adding key to inventory'); + window.initializeKeyLockMappings(); + } + // Apply pulse animation to the key ring slot instead of showing notification const keyRingSlot = window.inventory.keyRing.slot; if (keyRingSlot) { @@ -436,12 +453,18 @@ function updateKeyRingDisplay() { tooltip.textContent = keyRing.keys.length === 1 ? keyRing.keys[0].scenarioData.name : 'Key Ring'; // Add item data - use the first key's data as the primary data + const allKeysData = keyRing.keys.map(k => k.scenarioData); + console.log(`🔑 Building key ring scenarioData with ${keyRing.keys.length} keys:`, { + firstKeyScenarioData: keyRing.keys[0].scenarioData, + allKeysData: allKeysData + }); + itemImg.scenarioData = { ...keyRing.keys[0].scenarioData, name: keyRing.keys.length === 1 ? keyRing.keys[0].scenarioData.name : 'Key Ring', type: 'key_ring', keyCount: keyRing.keys.length, - allKeys: keyRing.keys.map(k => k.scenarioData) + allKeys: allKeysData }; itemImg.name = 'key'; itemImg.objectId = 'inventory_key_ring'; diff --git a/js/systems/key-lock-system.js b/js/systems/key-lock-system.js index 6bc99d4..5f3f8f6 100644 --- a/js/systems/key-lock-system.js +++ b/js/systems/key-lock-system.js @@ -114,14 +114,36 @@ function assignKeysToLocks() { const key = playerKeys.find(k => k.scenarioData.key_id === keyId); if (key) { + // Get the actual scenario keyPins for this lock + let scenarioKeyPins = null; + if (lock.objectIndex !== undefined) { + // Object lock - get keyPins from the object + const obj = window.gameState?.scenario?.rooms?.[lock.roomId]?.objects?.[lock.objectIndex]; + scenarioKeyPins = obj?.keyPins || obj?.key_pins; + } else { + // Room lock - get keyPins from the room + const room = window.gameState?.scenario?.rooms?.[lock.roomId]; + scenarioKeyPins = room?.keyPins || room?.key_pins; + } + + // Use scenario keyPins if available, otherwise generate random ones + const pinHeights = scenarioKeyPins || generatePinHeightsForLock(lock.roomId, keyId); + // Create a lock configuration for this specific lock const lockConfig = { id: `${lock.roomId}_${lock.objectIndex !== undefined ? `obj_${lock.objectIndex}` : 'room'}`, - pinCount: 4, // Default pin count - pinHeights: generatePinHeightsForLock(lock.roomId, keyId), // Generate consistent pin heights + pinCount: pinHeights?.length || 4, // Use actual pin count from keyPins, default 4 + pinHeights: pinHeights, // Use scenario keyPins or generated ones difficulty: 'medium' }; + console.log(`📌 Lock mapping for key "${key.scenarioData.name}" (${keyId}):`, { + lockLocation: `${lock.roomName}${lock.objectName ? ` - ${lock.objectName}` : ''}`, + scenarioKeyPins: scenarioKeyPins, + pinHeights: pinHeights, + pinCount: lockConfig.pinCount + }); + // Store the mapping window.keyLockMappings[keyId] = { lockId: lockConfig.id, diff --git a/js/systems/npc-hostile.js b/js/systems/npc-hostile.js index ce657e9..b549c79 100644 --- a/js/systems/npc-hostile.js +++ b/js/systems/npc-hostile.js @@ -177,6 +177,17 @@ function dropNPCItems(npcId) { interactable: true }; + // DEBUG: Log key properties if this is a key + if (item.type === 'key') { + console.log(`🔑 Dropped key "${item.name}":`, { + source: 'npc-hostile.js dropNPCItems', + item_key_id: item.key_id, + item_keyPins: item.keyPins, + droppedItemData_key_id: droppedItemData.key_id, + droppedItemData_keyPins: droppedItemData.keyPins + }); + } + // Apply scenario properties to sprite spriteObj.scenarioData = droppedItemData; spriteObj.interactable = true;