From 11ac3ab5e48d6c201ddf989d7ffe4b278ea11a92 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Wed, 18 Feb 2026 13:36:21 +0000 Subject: [PATCH] fix: update item ID handling and prevent local state overwrite in room sync --- public/break_escape/js/core/rooms.js | 9 +++++++- .../js/systems/room-state-sync.js | 22 ++++++++++++++----- scenarios/m01_first_contact/scenario.json.erb | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/public/break_escape/js/core/rooms.js b/public/break_escape/js/core/rooms.js index f4a7b97..94f8eef 100644 --- a/public/break_escape/js/core/rooms.js +++ b/public/break_escape/js/core/rooms.js @@ -342,11 +342,18 @@ function applyScenarioProperties(sprite, scenarioObj, roomId, index) { sprite.scenarioData = scenarioObj; sprite.interactable = true; // Mark scenario items as interactable sprite.name = scenarioObj.name; - sprite.objectId = `${roomId}_${scenarioObj.type}_${index}`; + // Prefer the item's own id (set for server-synced dropped items) so that + // removeItemFromRoom sends the correct id when the player picks it up. + sprite.objectId = scenarioObj.id || `${roomId}_${scenarioObj.type}_${index}`; sprite.setInteractive({ useHandCursor: true }); // Store all scenario properties for interaction system + // IMPORTANT: Skip 'texture' - it is a Phaser-internal property (TextureFrame object). + // Synced items from the server include texture as a plain string (e.g. 'id_badge'). + // Overwriting sprite.texture with a string breaks sprite.texture.key, which the + // inventory system uses to build the icon image path. Object.keys(scenarioObj).forEach(key => { + if (key === 'texture') return; sprite[key] = scenarioObj[key]; }); diff --git a/public/break_escape/js/systems/room-state-sync.js b/public/break_escape/js/systems/room-state-sync.js index e8ab7ef..c2fc643 100644 --- a/public/break_escape/js/systems/room-state-sync.js +++ b/public/break_escape/js/systems/room-state-sync.js @@ -42,11 +42,23 @@ export async function addItemToRoom(roomId, item, options = {}) { if (result.success) { console.log(`✅ Synced item add to room ${roomId}:`, item.type); - // Update local room state if room is loaded - if (window.rooms && window.rooms[roomId]) { - window.rooms[roomId].objects = window.rooms[roomId].objects || {}; - window.rooms[roomId].objects[item.id] = item; - } + // CRITICAL: DO NOT update local room state here! + // + // The calling code (e.g., npc-hostile.js dropNPCItems) has already: + // 1. Created a Phaser sprite for the item + // 2. Made it interactive (setInteractive, interactable=true, takeable=true) + // 3. Stored it in room.objects[item.id] = sprite + // + // If we overwrite room.objects[item.id] with a plain JS object here, + // the Phaser sprite reference is lost, and the interaction system + // can no longer detect/interact with the item! + // + // The local state is ALREADY correct - this sync is just persisting + // to the database for reload. When the page reloads, the server + // returns the item in roomData, and rooms.js creates a fresh sprite. + // + // Bug fixed: Items dropped by NPCs are now pickupable immediately, + // not just after refresh. return true; } else { diff --git a/scenarios/m01_first_contact/scenario.json.erb b/scenarios/m01_first_contact/scenario.json.erb index 85c5521..c61bc0d 100644 --- a/scenarios/m01_first_contact/scenario.json.erb +++ b/scenarios/m01_first_contact/scenario.json.erb @@ -285,7 +285,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "displayName": "Sarah Martinez", "npcType": "person", "position": { "x": 4, "y": 1.5 }, - "spriteSheet": "female_blowse", + "spriteSheet": "female_office_worker", "spriteTalk": "assets/characters/hacker-red-talk.png", "spriteConfig": { "idleFrameRate": 2,