feat(npc-interaction): Enhance NPC interaction system with click handling and distance checks

This commit is contained in:
Z. Cliffe Schreuders
2025-11-04 23:25:41 +00:00
parent 2c9d3a739c
commit d217e5a02a
4 changed files with 121 additions and 12 deletions

View File

@@ -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
*

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);