From e0cef60f37acdcaeb3227810abc89b60cb5b5d26 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Tue, 4 Nov 2025 21:31:00 +0000 Subject: [PATCH] feat(npc): Add talk icon system and update NPC sprite configurations --- assets/characters/hacker-red-talk.png | Bin 0 -> 6719 bytes assets/characters/hacker-red.png | Bin 0 -> 38309 bytes js/core/game.js | 77 ++++--- js/core/rooms.js | 5 + js/minigames/helpers/chat-helpers.js | 28 ++- .../person-chat/person-chat-minigame.js | 51 +++-- js/systems/interactions.js | 53 ++++- js/systems/npc-talk-icons.js | 206 ++++++++++++++++++ scenarios/npc-sprite-test.json | 4 +- 9 files changed, 361 insertions(+), 63 deletions(-) create mode 100644 assets/characters/hacker-red-talk.png create mode 100644 assets/characters/hacker-red.png create mode 100644 js/systems/npc-talk-icons.js diff --git a/assets/characters/hacker-red-talk.png b/assets/characters/hacker-red-talk.png new file mode 100644 index 0000000000000000000000000000000000000000..d945da100d5b1ae0e30475eda93008bf7864681e GIT binary patch literal 6719 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 literal 0 HcmV?d00001 diff --git a/assets/characters/hacker-red.png b/assets/characters/hacker-red.png new file mode 100644 index 0000000000000000000000000000000000000000..0fb2c3f6211bd935c415fc12995e89ff69ac560e GIT binary patch literal 38309 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 literal 0 HcmV?d00001 diff --git a/js/core/game.js b/js/core/game.js index 1cf59fd..3f4655d 100644 --- a/js/core/game.js +++ b/js/core/game.js @@ -67,6 +67,7 @@ export function preload() { this.load.image('keyway', 'assets/icons/keyway.png'); this.load.image('password', 'assets/icons/password.png'); this.load.image('pin', 'assets/icons/pin.png'); + this.load.image('talk', 'assets/icons/talk.png'); // Load new object sprites from Tiled map tileset // These are the key objects that appear in the new room_reception2.json @@ -357,41 +358,47 @@ export function preload() { this.load.image('torch-1', 'assets/objects/torch-1.png'); // Load character sprite sheet instead of single image - this.load.spritesheet('hacker', 'assets/characters/hacker.png', { - frameWidth: 64, - frameHeight: 64 - }); - - // Animated plant textures are loaded above - - // Load swivel chair rotation images - this.load.image('chair-exec-rotate1', 'assets/objects/chair-exec-rotate1.png'); - this.load.image('chair-exec-rotate2', 'assets/objects/chair-exec-rotate2.png'); - this.load.image('chair-exec-rotate3', 'assets/objects/chair-exec-rotate3.png'); - this.load.image('chair-exec-rotate4', 'assets/objects/chair-exec-rotate4.png'); - this.load.image('chair-exec-rotate5', 'assets/objects/chair-exec-rotate5.png'); - this.load.image('chair-exec-rotate6', 'assets/objects/chair-exec-rotate6.png'); - this.load.image('chair-exec-rotate7', 'assets/objects/chair-exec-rotate7.png'); - this.load.image('chair-exec-rotate8', 'assets/objects/chair-exec-rotate8.png'); - - // Load white chair rotation images - this.load.image('chair-white-1-rotate1', 'assets/objects/chair-white-1-rotate1.png'); - this.load.image('chair-white-1-rotate2', 'assets/objects/chair-white-1-rotate2.png'); - this.load.image('chair-white-1-rotate3', 'assets/objects/chair-white-1-rotate3.png'); - this.load.image('chair-white-1-rotate4', 'assets/objects/chair-white-1-rotate4.png'); - this.load.image('chair-white-1-rotate5', 'assets/objects/chair-white-1-rotate5.png'); - this.load.image('chair-white-1-rotate6', 'assets/objects/chair-white-1-rotate6.png'); - this.load.image('chair-white-1-rotate7', 'assets/objects/chair-white-1-rotate7.png'); - this.load.image('chair-white-1-rotate8', 'assets/objects/chair-white-1-rotate8.png'); - - this.load.image('chair-white-2-rotate1', 'assets/objects/chair-white-2-rotate1.png'); - this.load.image('chair-white-2-rotate2', 'assets/objects/chair-white-2-rotate2.png'); - this.load.image('chair-white-2-rotate3', 'assets/objects/chair-white-2-rotate3.png'); - this.load.image('chair-white-2-rotate4', 'assets/objects/chair-white-2-rotate4.png'); - this.load.image('chair-white-2-rotate5', 'assets/objects/chair-white-2-rotate5.png'); - this.load.image('chair-white-2-rotate6', 'assets/objects/chair-white-2-rotate6.png'); - this.load.image('chair-white-2-rotate7', 'assets/objects/chair-white-2-rotate7.png'); - this.load.image('chair-white-2-rotate8', 'assets/objects/chair-white-2-rotate8.png'); + this.load.spritesheet('hacker', 'assets/characters/hacker.png', { + frameWidth: 64, + frameHeight: 64 + }); + + // Load character sprite sheet instead of single image + this.load.spritesheet('hacker-red', 'assets/characters/hacker-red.png', { + frameWidth: 64, + frameHeight: 64 + }); + + // Animated plant textures are loaded above + + // Load swivel chair rotation images + this.load.image('chair-exec-rotate1', 'assets/objects/chair-exec-rotate1.png'); + this.load.image('chair-exec-rotate2', 'assets/objects/chair-exec-rotate2.png'); + this.load.image('chair-exec-rotate3', 'assets/objects/chair-exec-rotate3.png'); + this.load.image('chair-exec-rotate4', 'assets/objects/chair-exec-rotate4.png'); + this.load.image('chair-exec-rotate5', 'assets/objects/chair-exec-rotate5.png'); + this.load.image('chair-exec-rotate6', 'assets/objects/chair-exec-rotate6.png'); + this.load.image('chair-exec-rotate7', 'assets/objects/chair-exec-rotate7.png'); + this.load.image('chair-exec-rotate8', 'assets/objects/chair-exec-rotate8.png'); + + // Load white chair rotation images + this.load.image('chair-white-1-rotate1', 'assets/objects/chair-white-1-rotate1.png'); + this.load.image('chair-white-1-rotate2', 'assets/objects/chair-white-1-rotate2.png'); + this.load.image('chair-white-1-rotate3', 'assets/objects/chair-white-1-rotate3.png'); + this.load.image('chair-white-1-rotate4', 'assets/objects/chair-white-1-rotate4.png'); + this.load.image('chair-white-1-rotate5', 'assets/objects/chair-white-1-rotate5.png'); + this.load.image('chair-white-1-rotate6', 'assets/objects/chair-white-1-rotate6.png'); + this.load.image('chair-white-1-rotate7', 'assets/objects/chair-white-1-rotate7.png'); + this.load.image('chair-white-1-rotate8', 'assets/objects/chair-white-1-rotate8.png'); + + this.load.image('chair-white-2-rotate1', 'assets/objects/chair-white-2-rotate1.png'); + this.load.image('chair-white-2-rotate2', 'assets/objects/chair-white-2-rotate2.png'); + this.load.image('chair-white-2-rotate3', 'assets/objects/chair-white-2-rotate3.png'); + this.load.image('chair-white-2-rotate4', 'assets/objects/chair-white-2-rotate4.png'); + this.load.image('chair-white-2-rotate5', 'assets/objects/chair-white-2-rotate5.png'); + this.load.image('chair-white-2-rotate6', 'assets/objects/chair-white-2-rotate6.png'); + this.load.image('chair-white-2-rotate7', 'assets/objects/chair-white-2-rotate7.png'); + this.load.image('chair-white-2-rotate8', 'assets/objects/chair-white-2-rotate8.png'); // Load audio files // NPC system sounds diff --git a/js/core/rooms.js b/js/core/rooms.js index be0bafd..8760171 100644 --- a/js/core/rooms.js +++ b/js/core/rooms.js @@ -1742,6 +1742,11 @@ export function updatePlayerRoom() { discoveredRooms.add(doorTransitionRoom); window.discoveredRooms = discoveredRooms; console.log(`✅ Marked room ${doorTransitionRoom} as discovered`); + + // Update NPC talk icons for the new room + if (window.npcTalkIcons && rooms[doorTransitionRoom].npcSprites) { + window.npcTalkIcons.init([], rooms[doorTransitionRoom].npcSprites); + } } if (previousRoom) { diff --git a/js/minigames/helpers/chat-helpers.js b/js/minigames/helpers/chat-helpers.js index 0c0e995..04a813c 100644 --- a/js/minigames/helpers/chat-helpers.js +++ b/js/minigames/helpers/chat-helpers.js @@ -11,6 +11,7 @@ /** * Process game action tags from Ink story * Tags format: # unlock_door:ceo, # give_item:keycard|CEO Keycard, etc. + * Filters out speaker tags (player, npc, speaker:player, speaker:npc) * * @param {Array} tags - Array of tag strings from Ink story * @param {Object} ui - UI controller with showNotification method @@ -26,11 +27,25 @@ export function processGameActionTags(tags, ui) { return []; } - console.log('🏷️ Processing game action tags:', tags); + // Filter out speaker tags - only process action tags + const actionTags = tags.filter(tag => { + const action = tag.split(':')[0].trim().toLowerCase(); + return action !== 'player' && + action !== 'npc' && + action !== 'speaker' && + !tag.includes('speaker:'); + }); + + if (actionTags.length === 0) { + // No action tags to process (all were speaker tags) + return []; + } + + console.log('🏷️ Processing game action tags:', actionTags); const results = []; - tags.forEach(tag => { + actionTags.forEach(tag => { const trimmedTag = tag.trim(); // Skip empty tags @@ -177,15 +192,18 @@ export function getActionTags(tags) { /** * Determine speaker from tags + * Finds the LAST speaker tag (most recent/current speaker) + * * @param {Array} tags - Tags from story * @param {string} defaultSpeaker - Default speaker if not found in tags * @returns {string} Speaker ('npc' or 'player') */ export function determineSpeaker(tags, defaultSpeaker = 'npc') { - if (!tags) return defaultSpeaker; + if (!tags || tags.length === 0) return defaultSpeaker; - for (const tag of tags) { - const trimmed = tag.trim().toLowerCase(); + // Check tags in REVERSE order to find the last speaker tag (current speaker) + for (let i = tags.length - 1; i >= 0; i--) { + const trimmed = tags[i].trim().toLowerCase(); if (trimmed === 'player' || trimmed === 'speaker:player') { return 'player'; } diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js index 3213a73..bc5f768 100644 --- a/js/minigames/person-chat/person-chat-minigame.js +++ b/js/minigames/person-chat/person-chat-minigame.js @@ -229,20 +229,8 @@ export class PersonChatMinigame extends MinigameScene { * @returns {string} Speaker ('npc' or 'player') */ determineSpeaker(result) { - // Check for speaker tag in result - if (result.tags) { - for (const tag of result.tags) { - if (tag === 'player' || tag === 'speaker:player') { - return 'player'; - } - if (tag === 'npc' || tag === 'speaker:npc') { - return 'npc'; - } - } - } - - // Default: alternate speakers, or start with NPC - return this.currentSpeaker === 'player' ? 'npc' : 'npc'; + // Use the shared helper function from chat-helpers + return determineSpeakerFromTags(result.tags, 'npc'); } /** @@ -268,7 +256,40 @@ export class PersonChatMinigame extends MinigameScene { // Then display the result (NPC response) after a small delay setTimeout(() => { - this.displayDialogueResult(result); + // Extract NPC-only content from the accumulated result + // Split by speaker tag to separate player and NPC dialogue + let npcText = ''; + let npcTags = []; + + // Find the section with speaker:npc tag + if (result.tags && result.tags.includes('speaker:npc')) { + // Split the text by lines and reconstruct based on tags + const lines = result.text.split('\n').filter(line => line.trim()); + const tagIndex = result.tags.indexOf('speaker:npc'); + + // Get number of player lines before NPC (based on speaker:player tag position) + const playerTagIndex = result.tags.indexOf('speaker:player'); + let playerLineCount = playerTagIndex >= 0 ? 1 : 0; + + // Skip player lines and collect NPC lines + npcText = lines.slice(playerLineCount).join('\n').trim(); + npcTags = result.tags.filter((tag, idx) => idx >= tagIndex); + + console.log(`📄 Extracted NPC text: "${npcText.substring(0, 50)}..."`); + } else { + // Fallback: use full result if no speaker tag + npcText = result.text; + npcTags = result.tags; + } + + // Create a new result with only the NPC's dialogue + const npcOnlyResult = { + ...result, + text: npcText, + tags: npcTags + }; + + this.displayDialogueResult(npcOnlyResult); }, 1500); } catch (error) { console.error('❌ Error handling choice:', error); diff --git a/js/systems/interactions.js b/js/systems/interactions.js index 28a9cbf..34e3a03 100644 --- a/js/systems/interactions.js +++ b/js/systems/interactions.js @@ -266,18 +266,36 @@ export function checkObjectInteractions() { if (distanceSq <= INTERACTION_RANGE_SQ) { if (!sprite.isHighlighted) { sprite.isHighlighted = true; - sprite.setTint(0x4da6ff); // Blue tint for interactable NPCs - // Add interaction indicator sprite - addInteractionIndicator(sprite); + // Add talk icon indicator for NPC (created on first highlight) + if (!sprite.interactionIndicator) { + addInteractionIndicator(sprite); + } + // Show talk icon and don't apply tint - icon provides visual feedback + if (sprite.interactionIndicator) { + sprite.interactionIndicator.setVisible(true); + sprite.talkIconVisible = true; + } + } else if (sprite.interactionIndicator && !sprite.talkIconVisible) { + // Update position of talk icon to stay pixel-perfect on NPC + const iconX = Math.round(sprite.x + 0); + const iconY = Math.round(sprite.y - 48); + sprite.interactionIndicator.setPosition(iconX, iconY); + sprite.interactionIndicator.setVisible(true); + sprite.talkIconVisible = true; } } else if (sprite.isHighlighted) { sprite.isHighlighted = false; sprite.clearTint(); - // Clean up interaction sprite if exists + // Hide talk icon when out of range if (sprite.interactionIndicator) { - sprite.interactionIndicator.destroy(); - delete sprite.interactionIndicator; + sprite.interactionIndicator.setVisible(false); + sprite.talkIconVisible = false; } + } else if (sprite.interactionIndicator && sprite.talkIconVisible) { + // Update position even when not highlighted (for smooth following) + const iconX = Math.round(sprite.x + 0); + const iconY = Math.round(sprite.y - 48); + sprite.interactionIndicator.setPosition(iconX, iconY); } }); } @@ -341,6 +359,29 @@ function addInteractionIndicator(obj) { return; } + // NPCs get the talk icon above their heads with pixel-perfect positioning + if (obj._isNPC) { + try { + // Talk icon positioned above NPC with pixel-perfect coordinates + const talkIconX = Math.round(obj.x + 0); // Centered above + const talkIconY = Math.round(obj.y - 48); // 48 pixels above + + const indicator = obj.scene.add.image(talkIconX, talkIconY, 'talk'); + indicator.setDepth(obj.depth + 1); + indicator.setOrigin(0.5, 0.5); + indicator.setScale(0.75); // Slightly smaller than full size + indicator.setVisible(false); // Hidden until player is in range + + // Store reference for cleanup and visibility management + obj.interactionIndicator = indicator; + obj.talkIconVisible = false; + } catch (error) { + console.warn('Failed to add talk icon for NPC:', error); + } + return; + } + + // Non-NPC objects use the standard interaction indicator sprite const spriteKey = getInteractionSpriteKey(obj); if (!spriteKey) return; diff --git a/js/systems/npc-talk-icons.js b/js/systems/npc-talk-icons.js new file mode 100644 index 0000000..df74179 --- /dev/null +++ b/js/systems/npc-talk-icons.js @@ -0,0 +1,206 @@ +/** + * NPC Talk Icon System + * + * Displays a "talk" icon above NPC heads when the player is within interaction range. + * Manages icon creation, positioning, and visibility based on player proximity. + * + * @module npc-talk-icons + */ + +export class NPCTalkIconSystem { + constructor(scene) { + 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.INTERACTION_RANGE = 64; // Pixels + this.UPDATE_INTERVAL = 200; // ms between updates + this.lastUpdate = 0; + } + + /** + * Initialize talk icons for all NPCs in the current room + * @param {Array} npcs - Array of NPC objects + * @param {Array} sprites - Array of NPC sprite objects + */ + init(npcs, sprites) { + this.npcs = npcs || []; + this.sprites = sprites || []; + + // Create icons for each NPC sprite + if (this.sprites && Array.isArray(this.sprites)) { + this.sprites.forEach(spriteObj => { + this.createIconForNPC(spriteObj); + }); + } + + console.log(`💬 Initialized ${this.npcIcons.size} talk icons`); + } + + /** + * Create a talk icon for an NPC sprite + * @param {Object} spriteObj - NPC sprite object + */ + createIconForNPC(spriteObj) { + if (!spriteObj || !spriteObj.npcId) return; + + // Don't create duplicate icons + 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); + const iconY = Math.round(spriteObj.y + this.ICON_OFFSET.y); + + // Create the icon image + const icon = this.scene.add.image(iconX, iconY, 'talk'); + + // 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); + + // Store reference + this.npcIcons.set(spriteObj.npcId, { + npc: spriteObj, + icon: icon, + visible: false + }); + + console.log(`💬 Created talk icon for NPC: ${spriteObj.npcId}`); + } catch (error) { + console.error(`❌ Error creating talk icon for ${spriteObj.npcId}:`, error); + } + } + + /** + * Update icon visibility based on player proximity + * @param {Object} player - Player sprite object + */ + update(player) { + if (!player) return; + + // Throttle updates + const now = Date.now(); + if (now - this.lastUpdate < this.UPDATE_INTERVAL) { + return; + } + this.lastUpdate = now; + + // Check distance to each NPC + this.npcIcons.forEach((iconData, npcId) => { + const distance = Phaser.Math.Distance.Between( + player.x, + player.y, + iconData.npc.x, + iconData.npc.y + ); + + const shouldShow = distance <= this.INTERACTION_RANGE; + + // Update icon visibility and position + if (shouldShow !== iconData.visible) { + iconData.icon.setVisible(shouldShow); + iconData.visible = shouldShow; + } + + // 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); + const newY = Math.round(iconData.npc.y + this.ICON_OFFSET.y); + iconData.icon.setPosition(newX, newY); + + // Update depth if needed + const expectedDepth = iconData.npc.depth + 1; + if (iconData.icon.depth !== expectedDepth) { + iconData.icon.setDepth(expectedDepth); + } + }); + } + + /** + * Show icon for a specific NPC + * @param {string} npcId - NPC ID + */ + showIcon(npcId) { + const iconData = this.npcIcons.get(npcId); + if (iconData && !iconData.visible) { + iconData.icon.setVisible(true); + iconData.visible = true; + } + } + + /** + * Hide icon for a specific NPC + * @param {string} npcId - NPC ID + */ + hideIcon(npcId) { + const iconData = this.npcIcons.get(npcId); + if (iconData && iconData.visible) { + iconData.icon.setVisible(false); + iconData.visible = false; + } + } + + /** + * Hide all talk icons + */ + hideAll() { + this.npcIcons.forEach(iconData => { + iconData.icon.setVisible(false); + iconData.visible = false; + }); + } + + /** + * Show all talk icons + */ + showAll() { + this.npcIcons.forEach(iconData => { + iconData.icon.setVisible(true); + iconData.visible = true; + }); + } + + /** + * Remove talk icon for a specific NPC + * @param {string} npcId - NPC ID + */ + removeIcon(npcId) { + const iconData = this.npcIcons.get(npcId); + if (iconData) { + iconData.icon.destroy(); + this.npcIcons.delete(npcId); + } + } + + /** + * Cleanup all icons + */ + destroy() { + this.npcIcons.forEach(iconData => { + iconData.icon.destroy(); + }); + this.npcIcons.clear(); + } + + /** + * Set interaction range for showing icons + * @param {number} range - Range in pixels + */ + setInteractionRange(range) { + this.INTERACTION_RANGE = range; + } + + /** + * Set icon offset from NPC position + * @param {Object} offset - {x, y} offset + */ + setIconOffset(offset) { + this.ICON_OFFSET = offset; + } +} + +export default NPCTalkIconSystem; diff --git a/scenarios/npc-sprite-test.json b/scenarios/npc-sprite-test.json index ad1b19d..53e4245 100644 --- a/scenarios/npc-sprite-test.json +++ b/scenarios/npc-sprite-test.json @@ -22,8 +22,8 @@ "npcType": "person", "roomId": "test_room", "position": { "x": 5, "y": 3 }, - "spriteSheet": "hacker", - "spriteTalk": "assets/characters/hacker-talk.png", + "spriteSheet": "hacker-red", + "spriteTalk": "assets/characters/hacker-red-talk.png", "spriteConfig": { "idleFrameStart": 20, "idleFrameEnd": 23