From 518d8916be82889be2ba369d78998d1df8630b29 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Tue, 4 Nov 2025 22:11:32 +0000 Subject: [PATCH] feat(person-chat): Implement multi-character support and enhance dialogue handling --- .../person-chat/person-chat-minigame.js | 283 +++++++++++++++--- js/minigames/person-chat/person-chat-ui.js | 71 +++-- scenarios/ink/test.ink | 102 ++++--- scenarios/ink/test.ink.json | 1 - scenarios/ink/test2.ink | 61 ++++ scenarios/ink/test2.json | 1 + ...sprite-test.json => npc-sprite-test2.json} | 2 +- 7 files changed, 400 insertions(+), 121 deletions(-) delete mode 100644 scenarios/ink/test.ink.json create mode 100644 scenarios/ink/test2.ink create mode 100644 scenarios/ink/test2.json rename scenarios/{npc-sprite-test.json => npc-sprite-test2.json} (96%) diff --git a/js/minigames/person-chat/person-chat-minigame.js b/js/minigames/person-chat/person-chat-minigame.js index bc5f768..67f0209 100644 --- a/js/minigames/person-chat/person-chat-minigame.js +++ b/js/minigames/person-chat/person-chat-minigame.js @@ -60,18 +60,67 @@ export class PersonChatMinigame extends MinigameScene { spriteSheet: 'hacker' }; + // Build character index for multi-character support + this.characters = this.buildCharacterIndex(); + // Modules this.ui = null; this.conversation = null; // State this.isConversationActive = false; - this.currentSpeaker = null; // Track current speaker ('npc', 'player', or NPC id) + this.currentSpeaker = null; // Track current speaker ID ('player' or NPC id) this.lastResult = null; // Store last continue() result for choice handling console.log(`🎭 PersonChatMinigame created for NPC: ${this.npcId}`); } + /** + * Build index of all available characters (player + NPCs) + * @returns {Object} Map of character ID to character data + */ + buildCharacterIndex() { + const characters = {}; + + // Add player + characters['player'] = this.playerData; + + // Add main NPC + characters[this.npc.id] = this.npc; + + // Add other NPCs from scenario if available + if (this.scenario.npcs && Array.isArray(this.scenario.npcs)) { + this.scenario.npcs.forEach(npc => { + if (npc.id !== this.npc.id) { + characters[npc.id] = npc; + } + }); + } + + console.log(`πŸ‘₯ Built character index with ${Object.keys(characters).length} characters:`, Object.keys(characters)); + return characters; + } + + /** + * Get character data by ID + * @param {string} characterId - Character ID (player, npc_id, etc.) + * @returns {Object} Character data + */ + getCharacterById(characterId) { + if (!characterId) return this.npc; // Fallback to main NPC + + // Handle legacy speaker values + if (characterId === 'npc') { + return this.npc; + } + if (characterId === 'player') { + return this.playerData; + } + + // Look up by ID + return this.characters[characterId] || this.npc; + } + /** * Initialize the minigame UI and components */ @@ -93,7 +142,8 @@ export class PersonChatMinigame extends MinigameScene { game: this.game, npc: this.npc, playerSprite: this.player, - playerData: this.playerData + playerData: this.playerData, + characters: this.characters // Pass multi-character support }, this.npcManager); this.ui.render(); @@ -224,13 +274,51 @@ export class PersonChatMinigame extends MinigameScene { } /** - * Determine who is speaking based on Ink tags or content + * Determine who is speaking based on Ink tags + * Supports speaker tags like: + * - # speaker:player + * - # speaker:npc (defaults to main NPC) + * - # speaker:npc_id:character_id (specific character) + * * @param {Object} result - Result from conversation.continue() - * @returns {string} Speaker ('npc' or 'player') + * @returns {string} Character ID of speaker (player, npc_id, or main NPC id) */ determineSpeaker(result) { - // Use the shared helper function from chat-helpers - return determineSpeakerFromTags(result.tags, 'npc'); + if (!result.tags || result.tags.length === 0) { + return this.npc.id; // Default to main NPC + } + + // Check tags in reverse order to find the last speaker tag (current speaker) + for (let i = result.tags.length - 1; i >= 0; i--) { + const tag = result.tags[i].trim().toLowerCase(); + + // Handle multi-part speaker tags like "speaker:npc:test_npc_back" + if (tag.startsWith('speaker:')) { + const parts = tag.split(':'); + + if (parts.length === 2) { + // Simple speaker tag: speaker:player or speaker:npc + const speaker = parts[1]; + if (speaker === 'player') return 'player'; + if (speaker === 'npc') return this.npc.id; // Default NPC + } else if (parts.length === 3) { + // Specific character tag: speaker:npc:character_id + const characterId = parts[2]; + return this.characters[characterId] ? characterId : this.npc.id; + } else if (parts.length > 3) { + // Handle IDs with colons like speaker:npc:test_npc_back + const characterId = parts.slice(2).join(':'); + return this.characters[characterId] ? characterId : this.npc.id; + } + } + + // Fallback for non-speaker: tags + if (tag === 'player') return 'player'; + if (tag === 'npc') return this.npc.id; + } + + // No speaker tag found - default to main NPC + return this.npc.id; } /** @@ -246,6 +334,9 @@ export class PersonChatMinigame extends MinigameScene { // Get the choice text from lastResult before making the choice const choiceText = this.lastResult.choices[choiceIndex]?.text || ''; + // Clear choice buttons immediately + this.ui.hideChoices(); + // Make choice in conversation (this also calls continue() internally) const result = this.conversation.makeChoice(choiceIndex); @@ -254,42 +345,10 @@ export class PersonChatMinigame extends MinigameScene { this.ui.showDialogue(choiceText, 'player'); } - // Then display the result (NPC response) after a small delay + // Then display the result (dialogue blocks) after a small delay setTimeout(() => { - // 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); + // Process accumulated dialogue by splitting into individual speaker blocks + this.displayAccumulatedDialogue(result); }, 1500); } catch (error) { console.error('❌ Error handling choice:', error); @@ -297,6 +356,128 @@ export class PersonChatMinigame extends MinigameScene { } } + /** + * Display accumulated dialogue by splitting into individual speaker blocks + * @param {Object} result - Result with potentially multiple lines and tags + */ + displayAccumulatedDialogue(result) { + if (!result.text || !result.tags) { + // No content to display + if (result.hasEnded) { + this.endConversation(); + } + return; + } + + // Split text into lines + const lines = result.text.split('\n').filter(line => line.trim()); + + // We have lines and tags - pair them up + // Each tag corresponds to a line (or group of lines before the next tag) + if (lines.length === 0) { + if (result.hasEnded) { + this.endConversation(); + } + return; + } + + // Create dialogue blocks: each block is one or more consecutive lines with the same speaker + const dialogueBlocks = this.createDialogueBlocks(lines, result.tags); + + // Display blocks sequentially with delays + this.displayDialogueBlocksSequentially(dialogueBlocks, result, 0); + } + + /** + * Create dialogue blocks from lines and speaker tags + * @param {Array} lines - Text lines + * @param {Array} tags - Speaker tags + * @returns {Array} Array of {speaker, text} blocks + */ + createDialogueBlocks(lines, tags) { + const blocks = []; + let blockIndex = 0; + + // Group lines by speaker based on tags + for (let tagIdx = 0; tagIdx < tags.length; tagIdx++) { + const tag = tags[tagIdx]; + + // Determine speaker from tag - support multiple formats + let speaker = 'npc'; // default + if (tag.includes('speaker:player')) { + speaker = 'player'; + } else if (tag.includes('speaker:npc:')) { + // Extract character ID from speaker:npc:character_id format + const match = tag.match(/speaker:npc:(\S+)/); + if (match && match[1]) { + speaker = match[1]; + } + } else if (tag === 'player' || tag.includes('player')) { + speaker = 'player'; + } + + // Find how many lines belong to this speaker (until next tag or end) + const nextTagIdx = tagIdx + 1; + const startLineIdx = blockIndex; + + // Count lines for this speaker - lines between this tag and the next + let endLineIdx = lines.length; + if (nextTagIdx < tags.length) { + // There's another tag coming, but we need to figure out how many lines + // For now, assume 1 line per tag (common case) + endLineIdx = startLineIdx + 1; + } + + // Collect the text for this speaker + const blockText = lines.slice(startLineIdx, endLineIdx).join('\n').trim(); + if (blockText) { + blocks.push({ speaker, text: blockText, tag }); + } + + blockIndex = endLineIdx; + } + + return blocks; + } + + /** + * Display dialogue blocks sequentially + * @param {Array} blocks - Array of dialogue blocks + * @param {Object} originalResult - Original result from Ink + * @param {number} blockIndex - Current block index + */ + displayDialogueBlocksSequentially(blocks, originalResult, blockIndex) { + if (blockIndex >= blocks.length) { + // All blocks displayed, check if story has ended + if (originalResult.hasEnded) { + setTimeout(() => this.endConversation(), 1000); + } else { + // Try to continue for more dialogue + console.log('⏸️ Blocks finished, checking for more dialogue...'); + setTimeout(() => { + const nextLine = this.conversation.continue(); + if (nextLine.text && nextLine.text.trim()) { + this.displayAccumulatedDialogue(nextLine); + } else if (nextLine.hasEnded) { + this.endConversation(); + } + }, 2000); + } + return; + } + + // Display current block + const block = blocks[blockIndex]; + console.log(`πŸ“‹ Displaying block ${blockIndex + 1}/${blocks.length}: ${block.speaker}`); + + this.ui.showDialogue(block.text, block.speaker); + + // Display next block after delay + setTimeout(() => { + this.displayDialogueBlocksSequentially(blocks, originalResult, blockIndex + 1); + }, 2000); + } + /** * Display dialogue from a result object (without calling continue() again) * @param {Object} result - Story result from conversation.continue() @@ -340,9 +521,23 @@ export class PersonChatMinigame extends MinigameScene { console.log('⏳ Auto-continuing in 2 seconds...'); setTimeout(() => this.showCurrentDialogue(), 2000); } else { - // No choices and can't continue - story will end - console.log('βœ“ Waiting for story to end...'); - setTimeout(() => this.endConversation(), 1000); + // No choices and can't continue - check if there's more content + // Try to continue anyway (for linear scripted conversations) + console.log('⏸️ No more choices, attempting to continue for next line...'); + setTimeout(() => { + const nextLine = this.conversation.continue(); + if (nextLine.text && nextLine.text.trim()) { + // There's more dialogue to show + this.displayDialogueResult(nextLine); + } else if (nextLine.hasEnded) { + // Story has truly ended + this.endConversation(); + } else { + // No text but story isn't ended - wait a bit and end + console.log('βœ“ No more dialogue - ending conversation'); + setTimeout(() => this.endConversation(), 1000); + } + }, 2000); } } catch (error) { console.error('❌ Error displaying dialogue:', error); diff --git a/js/minigames/person-chat/person-chat-ui.js b/js/minigames/person-chat/person-chat-ui.js index c8fc4ac..d3183ba 100644 --- a/js/minigames/person-chat/person-chat-ui.js +++ b/js/minigames/person-chat/person-chat-ui.js @@ -17,7 +17,7 @@ export default class PersonChatUI { /** * Create UI component * @param {HTMLElement} container - Container for UI - * @param {Object} params - Configuration (game, npc, playerSprite) + * @param {Object} params - Configuration (game, npc, playerSprite, characters) * @param {NPCManager} npcManager - NPC manager for sprite access */ constructor(container, params, npcManager) { @@ -28,6 +28,7 @@ export default class PersonChatUI { this.npc = params.npc; this.playerSprite = params.playerSprite; this.playerData = params.playerData || {}; + this.characters = params.characters || {}; // Multi-character support // UI elements this.elements = { @@ -48,7 +49,7 @@ export default class PersonChatUI { this.portraitRenderer = null; // State - this.currentSpeaker = null; // 'npc' or 'player' + this.currentSpeaker = null; // Character ID this.hasContinued = false; // Track if user has clicked continue console.log('πŸ“± PersonChatUI created'); @@ -173,25 +174,36 @@ export default class PersonChatUI { /** * Display dialogue text with speaker * @param {string} text - Dialogue text to display - * @param {string} speaker - Speaker name ('npc' or 'player') + * @param {string} characterId - Character ID ('player', 'npc', or specific NPC ID) */ - showDialogue(text, speaker = 'npc') { - this.currentSpeaker = speaker; + showDialogue(text, characterId = 'npc') { + this.currentSpeaker = characterId; - console.log(`πŸ“ showDialogue called with speaker: ${speaker}, text length: ${text?.length || 0}`); - console.log(`πŸ“ dialogueText element:`, this.elements.dialogueText); - console.log(`πŸ“ speakerName element:`, this.elements.speakerName); + console.log(`πŸ“ showDialogue called with character: ${characterId}, text length: ${text?.length || 0}`); + + // Get character data + let character = this.characters[characterId]; + if (!character) { + // Fallback for legacy speaker values + if (characterId === 'player') { + character = this.playerData; + } else if (characterId === 'npc' || !characterId) { + character = this.npc; + } + } + + // Determine display name + const displayName = character?.displayName || (characterId === 'player' ? 'You' : 'NPC'); + const speakerType = characterId === 'player' ? 'player' : 'npc'; - // Update speaker name and label - const displayName = speaker === 'npc' ? (this.npc?.displayName || 'NPC') : 'You'; this.elements.portraitLabel.textContent = displayName; this.elements.speakerName.textContent = displayName; console.log(`πŸ“ Set speaker name to: ${displayName}`); // Update speaker styling - this.elements.portraitSection.className = `person-chat-portrait-section speaker-${speaker}`; - this.elements.speakerName.className = `person-chat-speaker-name ${speaker}-speaker`; + this.elements.portraitSection.className = `person-chat-portrait-section speaker-${speakerType}`; + this.elements.speakerName.className = `person-chat-speaker-name ${speakerType}-speaker`; // Update dialogue text this.elements.dialogueText.textContent = text; @@ -199,7 +211,7 @@ export default class PersonChatUI { console.log(`πŸ“ Set dialogue text, element content: "${this.elements.dialogueText.textContent}"`); // Reset portrait for new speaker - this.updatePortraitForSpeaker(speaker); + this.updatePortraitForSpeaker(characterId, character); // Reset continue button state this.hasContinued = false; @@ -207,33 +219,32 @@ export default class PersonChatUI { /** * Update portrait for the current speaker - * @param {string} speaker - 'npc' or 'player' + * @param {string} characterId - Character ID + * @param {Object} character - Character data */ - updatePortraitForSpeaker(speaker) { + updatePortraitForSpeaker(characterId, character) { try { - if (!this.portraitRenderer) { + if (!this.portraitRenderer || !character) { return; } // Update sprite data for current speaker - if (speaker === 'npc' && this.npc) { - // Use the actual NPC object to preserve all properties (including spriteTalk) - this.portraitRenderer.npc = this.npc; - this.portraitRenderer.setupSpriteInfo(); - this.portraitRenderer.render(); - } else if (speaker === 'player') { - // Create player NPC object from playerData with spriteTalk + if (characterId === 'player' || character.id === 'player') { + // Create player object for portrait rendering this.portraitRenderer.npc = { id: 'player', - displayName: this.playerData.displayName || 'Agent 0x00', - spriteSheet: this.playerData.spriteSheet || 'hacker', - spriteTalk: this.playerData.spriteTalk || 'assets/characters/hacker-talk.png', - spriteConfig: this.playerData.spriteConfig || {}, - _sprite: this.playerSprite + displayName: character.displayName || 'Agent 0x00', + spriteSheet: character.spriteSheet || 'hacker', + spriteTalk: character.spriteTalk || 'assets/characters/hacker-talk.png', + spriteConfig: character.spriteConfig || {} }; - this.portraitRenderer.setupSpriteInfo(); - this.portraitRenderer.render(); + } else { + // Use NPC character object + this.portraitRenderer.npc = character; } + + this.portraitRenderer.setupSpriteInfo(); + this.portraitRenderer.render(); } catch (error) { console.error('❌ Error updating portrait:', error); } diff --git a/scenarios/ink/test.ink b/scenarios/ink/test.ink index 15b98f6..af39d86 100644 --- a/scenarios/ink/test.ink +++ b/scenarios/ink/test.ink @@ -1,49 +1,61 @@ -// Test Ink script for development -VAR test_counter = 0 -VAR player_visited_room = false - -=== start === -# speaker: TestNPC -# type: bark -Hello! This is a test message from Ink. -~ test_counter++ --> END - -=== test_room_reception === -# speaker: TestNPC -# type: bark -{player_visited_room: - You're back in reception. -- else: - Welcome to reception! This is your first time here. - ~ player_visited_room = true -} --> END - -=== test_item_lockpick === -# speaker: TestNPC -# type: bark -You picked up a lockpick! Nice find. -~ test_counter++ --> END +// Test Ink script - Multi-character conversation with camera focus +// Demonstrates player, Front NPC (test_npc_front), and Back NPC (test_npc_back) in dialogue +VAR conversation_started = false === hub === -# speaker: TestNPC -# type: conversation -What would you like to test? -Counter: {test_counter} -+ [Test choice 1] -> test_1 -+ [Test choice 2] -> test_2 -+ [Exit] -> END +# speaker:npc:test_npc_back +Welcome! This is a group conversation test. Let me introduce you to my colleague. ++ [Listen in on the introduction] -> group_meeting + +=== group_meeting === +# speaker:npc:test_npc_back +Agent, meet my colleague from the back office. BACK +-> colleague_introduction + +=== colleague_introduction === +# speaker:npc:test_npc_front +Nice to meet you! I'm the lead technician here. FRONT. +-> player_question + +=== player_question === +# speaker:player +What kind of work do you both do here? +-> front_npc_explains + +=== front_npc_explains === +# speaker:npc:test_npc_back +Well, I handle the front desk operations and guest interactions. But my colleague here... +-> colleague_responds + +=== colleague_responds === +# speaker:npc:test_npc_front +I manage all the backend systems and security infrastructure. Together, we keep everything running smoothly. +-> player_follow_up + +=== player_follow_up === +# speaker:player +That sounds like a well-coordinated operation! +-> front_npc_agrees + +=== front_npc_agrees === +# speaker:npc:test_npc_back +It really is! We've been working together for several years now. Communication is key. +-> colleague_adds + +=== colleague_adds === +# speaker:npc:test_npc_front +Exactly. And we're always looking for talented people like you to join our team. +-> player_closing + +=== player_closing === +# speaker:player +I appreciate the opportunity. I'll definitely consider it. +-> conversation_end + +=== conversation_end === +# speaker:npc:test_npc_back +Great! Feel free to explore and let us know if you have any questions. +-> END + -=== test_1 === -# speaker: TestNPC -You selected test choice 1! -Counter: {test_counter} --> hub -=== test_2 === -# speaker: TestNPC -You selected test choice 2! -~ test_counter++ --> hub diff --git a/scenarios/ink/test.ink.json b/scenarios/ink/test.ink.json deleted file mode 100644 index 6f2990f..0000000 --- a/scenarios/ink/test.ink.json +++ /dev/null @@ -1 +0,0 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["#","^speaker: TestNPC","/#","#","^type: bark","/#","^Hello! This is a test message from Ink.","\n","ev",{"VAR?":"test_counter"},1,"+",{"VAR=":"test_counter","re":true},"/ev","end",null],"test_room_reception":["#","^speaker: TestNPC","/#","#","^type: bark","/#","ev",{"VAR?":"player_visited_room"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^You're back in reception.","\n",{"->":".^.^.^.11"},null]}],[{"->":".^.b"},{"b":["\n","^Welcome to reception! This is your first time here.","\n","ev",true,"/ev",{"VAR=":"player_visited_room","re":true},{"->":".^.^.^.11"},null]}],"nop","\n","end",null],"test_item_lockpick":["#","^speaker: TestNPC","/#","#","^type: bark","/#","^You picked up a lockpick! Nice find.","\n","ev",{"VAR?":"test_counter"},1,"+",{"VAR=":"test_counter","re":true},"/ev","end",null],"hub":[["#","^speaker: TestNPC","/#","#","^type: conversation","/#","^What would you like to test?","\n","^Counter: ","ev",{"VAR?":"test_counter"},"out","/ev","\n","ev","str","^Test choice 1","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Test choice 2","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Exit","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["^ ",{"->":"test_1"},"\n",null],"c-1":["^ ",{"->":"test_2"},"\n",null],"c-2":["^ ","end","\n",null]}],null],"test_1":["#","^speaker: TestNPC","/#","^You selected test choice 1!","\n","^Counter: ","ev",{"VAR?":"test_counter"},"out","/ev","\n",{"->":"hub"},null],"test_2":["#","^speaker: TestNPC","/#","^You selected test choice 2!","\n","ev",{"VAR?":"test_counter"},1,"+",{"VAR=":"test_counter","re":true},"/ev",{"->":"hub"},null],"global decl":["ev",0,{"VAR=":"test_counter"},false,{"VAR=":"player_visited_room"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/ink/test2.ink b/scenarios/ink/test2.ink new file mode 100644 index 0000000..af39d86 --- /dev/null +++ b/scenarios/ink/test2.ink @@ -0,0 +1,61 @@ +// Test Ink script - Multi-character conversation with camera focus +// Demonstrates player, Front NPC (test_npc_front), and Back NPC (test_npc_back) in dialogue +VAR conversation_started = false + +=== hub === +# speaker:npc:test_npc_back +Welcome! This is a group conversation test. Let me introduce you to my colleague. ++ [Listen in on the introduction] -> group_meeting + +=== group_meeting === +# speaker:npc:test_npc_back +Agent, meet my colleague from the back office. BACK +-> colleague_introduction + +=== colleague_introduction === +# speaker:npc:test_npc_front +Nice to meet you! I'm the lead technician here. FRONT. +-> player_question + +=== player_question === +# speaker:player +What kind of work do you both do here? +-> front_npc_explains + +=== front_npc_explains === +# speaker:npc:test_npc_back +Well, I handle the front desk operations and guest interactions. But my colleague here... +-> colleague_responds + +=== colleague_responds === +# speaker:npc:test_npc_front +I manage all the backend systems and security infrastructure. Together, we keep everything running smoothly. +-> player_follow_up + +=== player_follow_up === +# speaker:player +That sounds like a well-coordinated operation! +-> front_npc_agrees + +=== front_npc_agrees === +# speaker:npc:test_npc_back +It really is! We've been working together for several years now. Communication is key. +-> colleague_adds + +=== colleague_adds === +# speaker:npc:test_npc_front +Exactly. And we're always looking for talented people like you to join our team. +-> player_closing + +=== player_closing === +# speaker:player +I appreciate the opportunity. I'll definitely consider it. +-> conversation_end + +=== conversation_end === +# speaker:npc:test_npc_back +Great! Feel free to explore and let us know if you have any questions. +-> END + + + diff --git a/scenarios/ink/test2.json b/scenarios/ink/test2.json new file mode 100644 index 0000000..3b0081c --- /dev/null +++ b/scenarios/ink/test2.json @@ -0,0 +1 @@ +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"hub":[["#","^speaker:npc:test_npc_back","/#","^Welcome! This is a group conversation test. Let me introduce you to my colleague.","\n","ev","str","^Listen in on the introduction","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ",{"->":"group_meeting"},"\n",null]}],null],"group_meeting":["#","^speaker:npc:test_npc_back","/#","^Agent, meet my colleague from the back office. BACK","\n",{"->":"colleague_introduction"},null],"colleague_introduction":["#","^speaker:npc:test_npc_front","/#","^Nice to meet you! I'm the lead technician here. FRONT.","\n",{"->":"player_question"},null],"player_question":["#","^speaker:player","/#","^What kind of work do you both do here?","\n",{"->":"front_npc_explains"},null],"front_npc_explains":["#","^speaker:npc:test_npc_back","/#","^Well, I handle the front desk operations and guest interactions. But my colleague here...","\n",{"->":"colleague_responds"},null],"colleague_responds":["#","^speaker:npc:test_npc_front","/#","^I manage all the backend systems and security infrastructure. Together, we keep everything running smoothly.","\n",{"->":"player_follow_up"},null],"player_follow_up":["#","^speaker:player","/#","^That sounds like a well-coordinated operation!","\n",{"->":"front_npc_agrees"},null],"front_npc_agrees":["#","^speaker:npc:test_npc_back","/#","^It really is! We've been working together for several years now. Communication is key.","\n",{"->":"colleague_adds"},null],"colleague_adds":["#","^speaker:npc:test_npc_front","/#","^Exactly. And we're always looking for talented people like you to join our team.","\n",{"->":"player_closing"},null],"player_closing":["#","^speaker:player","/#","^I appreciate the opportunity. I'll definitely consider it.","\n",{"->":"conversation_end"},null],"conversation_end":["#","^speaker:npc:test_npc_back","/#","^Great! Feel free to explore and let us know if you have any questions.","\n","end",null],"global decl":["ev",false,{"VAR=":"conversation_started"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/npc-sprite-test.json b/scenarios/npc-sprite-test2.json similarity index 96% rename from scenarios/npc-sprite-test.json rename to scenarios/npc-sprite-test2.json index 53e4245..c6dfe15 100644 --- a/scenarios/npc-sprite-test.json +++ b/scenarios/npc-sprite-test2.json @@ -42,7 +42,7 @@ "idleFrameStart": 20, "idleFrameEnd": 23 }, - "storyPath": "scenarios/ink/test.ink.json", + "storyPath": "scenarios/ink/test2.json", "currentKnot": "hub" } ],