From d217e5a02a0df3e566d08118b5b90573db4e70cf Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Tue, 4 Nov 2025 23:25:41 +0000 Subject: [PATCH] feat(npc-interaction): Enhance NPC interaction system with click handling and distance checks --- js/core/game.js | 74 ++++++++++++++++++- .../person-chat/person-chat-minigame.js | 8 ++ js/systems/interactions.js | 23 ++++++ js/systems/npc-talk-icons.js | 28 ++++--- 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/js/core/game.js b/js/core/game.js index 3f4655d..fd95458 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -597,7 +597,39 @@ export function create() { const worldX = this.cameras.main.scrollX + pointer.x; const worldY = this.cameras.main.scrollY + pointer.y; - // Check for objects at the clicked position first + // Check for NPC sprites at the clicked position first + const npcAtPosition = findNPCAtPosition(worldX, worldY); + if (npcAtPosition) { + // Try to interact with the NPC + if (window.tryInteractWithNPC) { + const player = window.player; + if (player) { + window.preventPlayerMovement = true; + const previousX = player.x; + const previousY = player.y; + + // Try the interaction + window.tryInteractWithNPC(npcAtPosition); + + // If the interaction didn't move the player (NPC was out of range), + // treat this as a movement request to that NPC instead + if (player.x === previousX && player.y === previousY) { + // Reset the flag and move toward the NPC + window.preventPlayerMovement = false; + movePlayerToPoint(npcAtPosition.x, npcAtPosition.y); + return; + } + + // Interaction was successful + setTimeout(() => { + window.preventPlayerMovement = false; + }, 100); + return; // Exit early after handling the interaction + } + } + } + + // Check for objects at the clicked position const objectsAtPosition = findObjectsAtPosition(worldX, worldY); if (objectsAtPosition.length > 0) { @@ -748,6 +780,46 @@ function findObjectsAtPosition(worldX, worldY) { return objectsAtPosition; } +/** + * Find an NPC sprite at the clicked position + * @param {number} worldX - World X coordinate + * @param {number} worldY - World Y coordinate + * @returns {Object|null} NPC sprite if found, null otherwise + */ +function findNPCAtPosition(worldX, worldY) { + let closestNPC = null; + let closestDistance = Infinity; + + // Check all rooms for NPC sprites at the given position + Object.entries(window.rooms).forEach(([roomId, room]) => { + if (room.npcSprites && Array.isArray(room.npcSprites)) { + room.npcSprites.forEach(npcSprite => { + if (npcSprite && !npcSprite.destroyed && npcSprite.visible) { + // Get NPC bounds + const bounds = npcSprite.getBounds(); + + // Check if click is within bounds + if (worldX >= bounds.left && worldX <= bounds.right && + worldY >= bounds.top && worldY <= bounds.bottom) { + // Calculate distance from click to NPC center + const dx = worldX - npcSprite.x; + const dy = worldY - npcSprite.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // Keep the closest NPC + if (distance < closestDistance) { + closestDistance = distance; + closestNPC = npcSprite; + } + } + } + }); + } + }); + + return closestNPC; +} + /** * Normalize keyPins from 0-100 scale to 25-65 scale in entire scenario * diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js index 67f0209..a58bf5d 100644 --- a/js/minigames/person-chat/person-chat-minigame.js +++ b/js/minigames/person-chat/person-chat-minigame.js @@ -456,8 +456,16 @@ export class PersonChatMinigame extends MinigameScene { console.log('⏸️ Blocks finished, checking for more dialogue...'); setTimeout(() => { const nextLine = this.conversation.continue(); + + // Store for choice handling + this.lastResult = nextLine; + if (nextLine.text && nextLine.text.trim()) { this.displayAccumulatedDialogue(nextLine); + } else if (nextLine.choices && nextLine.choices.length > 0) { + // Back to choices - display them + console.log(`πŸ“‹ Back to choices: ${nextLine.choices.length} options available`); + this.ui.showChoices(nextLine.choices); } else if (nextLine.hasEnded) { this.endConversation(); } diff --git a/js/systems/interactions.js b/js/systems/interactions.js index 34e3a03..3d68e97 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -961,8 +961,31 @@ export function tryInteractWithNearest() { } } +// Handle NPC interaction by sprite reference +export function tryInteractWithNPC(npcSprite) { + if (!npcSprite || !npcSprite._isNPC) { + return; + } + + const player = window.player; + if (!player) { + return; + } + + // Check if NPC is within interaction range of the player + const distanceSq = getInteractionDistance(player, npcSprite.x, npcSprite.y); + const distance = Math.sqrt(distanceSq); + + // Only interact if within range + if (distance <= INTERACTION_RANGE) { + handleObjectInteraction(npcSprite); + } + // If out of range, the click handler will treat it as a movement request instead +} + // Export for global access window.checkObjectInteractions = checkObjectInteractions; window.handleObjectInteraction = handleObjectInteraction; window.handleContainerInteraction = handleContainerInteraction; window.tryInteractWithNearest = tryInteractWithNearest; +window.tryInteractWithNPC = tryInteractWithNPC; diff --git a/js/systems/npc-talk-icons.js b/js/systems/npc-talk-icons.js index df74179..e1c049d 100644 --- a/js/systems/npc-talk-icons.js +++ b/js/systems/npc-talk-icons.js @@ -12,10 +12,11 @@ export class NPCTalkIconSystem { this.scene = scene; this.npcIcons = new Map(); // { npcId: { npc, icon, sprite } } // Offset from NPC position - use whole pixels to avoid sub-pixel rendering - this.ICON_OFFSET = { x: 0, y: -48 }; + this.ICON_OFFSET = { x: 0, y: 0 }; this.INTERACTION_RANGE = 64; // Pixels this.UPDATE_INTERVAL = 200; // ms between updates this.lastUpdate = 0; + this.ICON_WIDTH = 21; // Talk icon width in pixels } /** @@ -48,8 +49,14 @@ export class NPCTalkIconSystem { if (this.npcIcons.has(spriteObj.npcId)) return; try { - // Calculate pixel-perfect position (round to avoid sub-pixel rendering) - const iconX = Math.round(spriteObj.x + this.ICON_OFFSET.x); + // Calculate the offset to align icon's right edge with sprite's right edge + // Get sprite's actual width in pixels (accounting for scale) + const spriteWidth = spriteObj.width; + // Offset from sprite center to align right edges + const offsetX = 0; //(spriteWidth / 2) - (scaledIconWidth / 2); + + // Calculate pixel-perfect position (round to whole pixels) + const iconX = Math.round(spriteObj.x + offsetX); const iconY = Math.round(spriteObj.y + this.ICON_OFFSET.y); // Create the icon image @@ -58,18 +65,17 @@ export class NPCTalkIconSystem { // Hide by default icon.setVisible(false); icon.setDepth(spriteObj.depth + 1); - icon.setScale(0.75); // Slightly smaller than full size - // Disable antialiasing to keep pixels sharp - icon.setOrigin(0.5, 0.5); + // icon.setOrigin(0.5, 0.5); - // Store reference + // Store reference with calculated offset for consistent positioning this.npcIcons.set(spriteObj.npcId, { npc: spriteObj, icon: icon, - visible: false + visible: false, + offsetX: offsetX // Store for consistent updates }); - console.log(`πŸ’¬ Created talk icon for NPC: ${spriteObj.npcId}`); + console.log(`πŸ’¬ Created talk icon for NPC: ${spriteObj.npcId} at offset x=${offsetX}`); } catch (error) { console.error(`❌ Error creating talk icon for ${spriteObj.npcId}:`, error); } @@ -107,8 +113,8 @@ export class NPCTalkIconSystem { } // Update position to follow NPC with pixel-perfect alignment - // Round to whole pixels to avoid sub-pixel rendering - const newX = Math.round(iconData.npc.x + this.ICON_OFFSET.x); + // Use stored offset to ensure consistent positioning without recalculating bounds + const newX = Math.round(iconData.npc.x + iconData.offsetX); const newY = Math.round(iconData.npc.y + this.ICON_OFFSET.y); iconData.icon.setPosition(newX, newY);