From bbbe6169b64d31bcfd2f34cdd2f51e6ff45c5f8e Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Wed, 18 Feb 2026 23:39:21 +0000 Subject: [PATCH] fix: refactor item giving and unlock handling for improved async processing and server synchronization --- .../js/minigames/helpers/chat-helpers.js | 28 +++++++++++-------- .../js/systems/minigame-starters.js | 14 ++++++++-- .../js/systems/npc-game-bridge.js | 16 ++++++++--- .../break_escape/js/systems/unlock-system.js | 9 ++++-- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/public/break_escape/js/minigames/helpers/chat-helpers.js b/public/break_escape/js/minigames/helpers/chat-helpers.js index 784a079..97e3558 100644 --- a/public/break_escape/js/minigames/helpers/chat-helpers.js +++ b/public/break_escape/js/minigames/helpers/chat-helpers.js @@ -99,17 +99,23 @@ export function processGameActionTags(tags, ui) { break; } - const giveResult = window.NPCGameBridge.giveItem(npcId, itemType); - if (giveResult.success) { - result.success = true; - result.message = `📦 Received: ${giveResult.item.name}`; - if (ui) ui.showNotification(result.message, 'success'); - console.log('✅ Item given successfully:', giveResult); - } else { - result.message = `⚠️ ${giveResult.error}`; - if (ui) ui.showNotification(result.message, 'warning'); - console.warn('⚠️ Item give failed:', giveResult); - } + // giveItem is async (awaits addToInventory so server inventory is confirmed). + // Use .then() since we can't await inside forEach. + // Mark result as success optimistically - the conversation continues + // immediately while the inventory sync happens in the background. + result.success = true; + result.message = `📦 Receiving: ${itemType}`; + window.NPCGameBridge.giveItem(npcId, itemType).then(giveResult => { + if (giveResult.success) { + console.log('✅ Item given and server inventory synced:', giveResult); + if (ui) ui.showNotification(`📦 Received: ${giveResult.item.name}`, 'success'); + } else { + console.warn('⚠️ Item give failed:', giveResult); + if (ui) ui.showNotification(`⚠️ ${giveResult.error}`, 'warning'); + } + }).catch(error => { + console.error('❌ giveItem error:', error); + }); } else { result.message = '⚠️ give_item requires item type parameter'; console.warn(result.message); diff --git a/public/break_escape/js/systems/minigame-starters.js b/public/break_escape/js/systems/minigame-starters.js index 983ba72..5a4374f 100644 --- a/public/break_escape/js/systems/minigame-starters.js +++ b/public/break_escape/js/systems/minigame-starters.js @@ -392,12 +392,20 @@ export function startKeySelectionMinigame(lockable, type, playerKeys, requiredKe requiredKeyId: requiredKeyId, onComplete: (success, result) => { if (success) { - console.log('KEY SELECTION SUCCESS'); - window.gameAlert('Successfully unlocked with the correct key!', 'success', 'Unlock Successful', 4000); + // Detect which mode completed the unlock while currentMinigame is still live. + // keyMode is true → player used a physical key + // keyMode is false → player switched to and completed lockpick mode + const keyMode = window.MinigameFramework?.currentMinigame?.keyMode; + const unlockMethod = keyMode === false ? 'lockpick' : 'key'; + console.log(`🔓 KEY SELECTION SUCCESS via method='${unlockMethod}' (keyMode=${keyMode})`); + const successMsg = unlockMethod === 'lockpick' + ? 'Successfully picked the lock!' + : 'Successfully unlocked with the correct key!'; + window.gameAlert(successMsg, 'success', 'Unlock Successful', 4000); // Small delay to ensure minigame cleanup completes before room loading if (unlockTargetCallback) { setTimeout(() => { - unlockTargetCallback(lockable, type, lockable.layer); + unlockTargetCallback(lockable, type, lockable.layer, unlockMethod); }, 100); } } else { diff --git a/public/break_escape/js/systems/npc-game-bridge.js b/public/break_escape/js/systems/npc-game-bridge.js index 6413950..61ca1b0 100644 --- a/public/break_escape/js/systems/npc-game-bridge.js +++ b/public/break_escape/js/systems/npc-game-bridge.js @@ -171,7 +171,7 @@ export class NPCGameBridge { * @param {string} itemType - Type of item to give (optional - gives first if null) * @returns {Object} Result with success status */ - giveItem(npcId, itemType = null) { + async giveItem(npcId, itemType = null) { if (!npcId) { const result = { success: false, error: 'No npcId provided' }; this._logAction('giveItem', { npcId, itemType }, result); @@ -224,10 +224,18 @@ export class NPCGameBridge { texture: { key: item.type } }; - // Add to player inventory - window.addToInventory(tempSprite); + // Await addToInventory so server inventory is confirmed before removing from NPC. + // This prevents the race condition where validate_unlock is called before the + // inventory POST /inventory request completes, causing a spurious 422. + const added = await window.addToInventory(tempSprite); - // Remove from NPC's inventory + if (!added) { + // addToInventory returns false for duplicates (already in inventory) or server rejection. + // Treat already-in-inventory as success; true failures are logged by addToInventory itself. + console.warn(`[NPCGameBridge] addToInventory returned false for ${item.type} from ${npcId} - may already be in inventory`); + } + + // Remove from NPC's inventory (after server confirms) npc.itemsHeld.splice(itemIndex, 1); // Emit event to update Ink variables diff --git a/public/break_escape/js/systems/unlock-system.js b/public/break_escape/js/systems/unlock-system.js index 1662820..f593f27 100644 --- a/public/break_escape/js/systems/unlock-system.js +++ b/public/break_escape/js/systems/unlock-system.js @@ -161,9 +161,12 @@ export function handleUnlock(lockable, type) { if (playerKeys.length > 0) { // Keys take priority - go straight to key selection console.log('KEYS AVAILABLE - STARTING KEY SELECTION'); - // Wrap unlockTarget to notify server first - const unlockWithServerNotification = async (lockable, type, layer) => { - const serverResponse = await notifyServerUnlock(lockable, type, 'key'); + // Wrap unlockTarget to notify server first. + // method is passed from the key selection minigame's onComplete: + // 'key' → player used a physical key from inventory + // 'lockpick' → player switched to and completed pick mode + const unlockWithServerNotification = async (lockable, type, layer, method = 'key') => { + const serverResponse = await notifyServerUnlock(lockable, type, method); unlockTarget(lockable, type, layer, serverResponse); }; startKeySelectionMinigame(lockable, type, playerKeys, requiredKey, unlockWithServerNotification);