mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
Enhance NPCManager to support timed conversations and update dialogue handling in PersonChatMinigame
- Added functionality to schedule timed conversations in NPCManager, allowing NPCs to automatically initiate dialogues after a specified delay. - Updated PersonChatMinigame to handle multiple dialogue lines and speakers, improving the display logic for accumulated dialogue. - Modified scenario JSON and Ink files to include timed conversation configurations for NPCs, enhancing narrative flow.
This commit is contained in:
@@ -365,15 +365,28 @@ export class PersonChatMinigame extends MinigameScene {
|
||||
|
||||
// Display choices if available (check this first, before text)
|
||||
if (result.choices && result.choices.length > 0) {
|
||||
// At a choice point - display choices
|
||||
this.ui.showChoices(result.choices);
|
||||
console.log(`📋 ${result.choices.length} choices available`);
|
||||
console.log(`📋 pendingContinueCallback NOT set - waiting for choice selection`);
|
||||
|
||||
// Also display any accompanying text if present
|
||||
// Check if we have accompanying text
|
||||
if (result.text && result.text.trim()) {
|
||||
console.log(`🗣️ Calling showDialogue with speaker: ${speaker}`);
|
||||
this.ui.showDialogue(result.text, speaker, true); // preserveChoices=true
|
||||
// Check if we have multiple lines/speakers (accumulated dialogue)
|
||||
const hasMultipleLines = result.text.includes('\n');
|
||||
const hasMultipleSpeakers = result.tags && result.tags.filter(t => t.includes('speaker:')).length > 1;
|
||||
|
||||
if (hasMultipleLines || hasMultipleSpeakers) {
|
||||
// Multiple dialogue lines - display them sequentially, choices shown at end
|
||||
console.log(`🗣️ Initial dialogue has multiple lines/speakers - using block display`);
|
||||
this.displayAccumulatedDialogue(result);
|
||||
} else {
|
||||
// Single line - display immediately with choices
|
||||
console.log(`🗣️ Single line dialogue - showing with choices immediately`);
|
||||
this.ui.showChoices(result.choices);
|
||||
this.ui.showDialogue(result.text, speaker, true); // preserveChoices=true
|
||||
}
|
||||
} else {
|
||||
// No text, just choices - show them immediately
|
||||
this.ui.showChoices(result.choices);
|
||||
console.log(`📋 No text, just showing choices`);
|
||||
}
|
||||
} else if (result.text && result.text.trim()) {
|
||||
// Have text but no choices - display and continue
|
||||
|
||||
@@ -10,6 +10,7 @@ export default class NPCManager {
|
||||
this.triggeredEvents = new Map(); // Track which events have been triggered per NPC
|
||||
this.conversationHistory = new Map(); // Track conversation history per NPC: { npcId: [ {type, text, timestamp, choiceText} ] }
|
||||
this.timedMessages = []; // Scheduled messages: { npcId, text, triggerTime, delivered, phoneId }
|
||||
this.timedConversations = []; // Scheduled conversations: { npcId, targetKnot, triggerTime, delivered }
|
||||
this.gameStartTime = Date.now(); // Track when game started for timed messages
|
||||
this.timerInterval = null; // Timer for checking timed messages
|
||||
|
||||
@@ -92,6 +93,16 @@ export default class NPCManager {
|
||||
console.log(`[NPCManager] Scheduled ${entry.timedMessages.length} timed messages for ${realId}`);
|
||||
}
|
||||
|
||||
// Schedule timed conversations if any are defined
|
||||
if (entry.timedConversation) {
|
||||
this.scheduleTimedConversation({
|
||||
npcId: realId,
|
||||
targetKnot: entry.timedConversation.targetKnot,
|
||||
delay: entry.timedConversation.delay
|
||||
});
|
||||
console.log(`[NPCManager] Scheduled timed conversation for ${realId} to knot: ${entry.timedConversation.targetKnot}`);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -410,6 +421,51 @@ export default class NPCManager {
|
||||
console.log(`[NPCManager] Scheduled timed message from ${npcId} at ${actualTriggerTime}ms:`, text);
|
||||
}
|
||||
|
||||
// Schedule a timed conversation to start after a delay
|
||||
// Similar to timedMessages but for person NPCs (opens person-chat minigame)
|
||||
//
|
||||
// opts: { npcId, targetKnot, triggerTime (ms from game start) OR delay (ms from now) }
|
||||
//
|
||||
// Example: After 3 seconds, automatically open a conversation with test_npc_back at the "group_meeting" knot
|
||||
// scheduleTimedConversation({
|
||||
// npcId: 'test_npc_back',
|
||||
// targetKnot: 'group_meeting',
|
||||
// delay: 3000
|
||||
// })
|
||||
//
|
||||
// USAGE IN SCENARIO JSON:
|
||||
// {
|
||||
// "id": "test_npc_back",
|
||||
// "displayName": "Back NPC",
|
||||
// "npcType": "person",
|
||||
// "storyPath": "scenarios/ink/test2.json",
|
||||
// "currentKnot": "hub",
|
||||
// "timedConversation": {
|
||||
// "delay": 3000, // 3 seconds
|
||||
// "targetKnot": "group_meeting"
|
||||
// }
|
||||
// }
|
||||
scheduleTimedConversation(opts) {
|
||||
const { npcId, targetKnot, triggerTime, delay } = opts;
|
||||
|
||||
if (!npcId || !targetKnot) {
|
||||
console.error('[NPCManager] scheduleTimedConversation requires npcId and targetKnot');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use triggerTime if provided, otherwise use delay (defaults to 0)
|
||||
const actualTriggerTime = triggerTime !== undefined ? triggerTime : (delay || 0);
|
||||
|
||||
this.timedConversations.push({
|
||||
npcId,
|
||||
targetKnot,
|
||||
triggerTime: actualTriggerTime, // milliseconds from game start
|
||||
delivered: false
|
||||
});
|
||||
|
||||
console.log(`[NPCManager] Scheduled timed conversation from ${npcId} at ${actualTriggerTime}ms to knot: ${targetKnot}`);
|
||||
}
|
||||
|
||||
// Start checking for timed messages (call this when game starts)
|
||||
startTimedMessages() {
|
||||
if (this.timerInterval) {
|
||||
@@ -445,6 +501,14 @@ export default class NPCManager {
|
||||
message.delivered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check timed conversations
|
||||
for (const conversation of this.timedConversations) {
|
||||
if (!conversation.delivered && elapsed >= conversation.triggerTime) {
|
||||
this._deliverTimedConversation(conversation);
|
||||
conversation.delivered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deliver a timed message (add to history and show bark)
|
||||
@@ -482,6 +546,32 @@ export default class NPCManager {
|
||||
console.log(`[NPCManager] Delivered timed message from ${message.npcId}:`, message.text);
|
||||
}
|
||||
|
||||
// Deliver a timed conversation (start person-chat minigame at specified knot)
|
||||
_deliverTimedConversation(conversation) {
|
||||
const npc = this.getNPC(conversation.npcId);
|
||||
if (!npc) {
|
||||
console.warn(`[NPCManager] Cannot deliver timed conversation: NPC ${conversation.npcId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update NPC's current knot to the target knot
|
||||
npc.currentKnot = conversation.targetKnot;
|
||||
|
||||
// Check if MinigameFramework is available to start the person-chat minigame
|
||||
if (window.MinigameFramework && typeof window.MinigameFramework.startMinigame === 'function') {
|
||||
console.log(`🎭 Starting timed conversation for ${conversation.npcId} at knot: ${conversation.targetKnot}`);
|
||||
|
||||
window.MinigameFramework.startMinigame('person-chat', null, {
|
||||
npcId: conversation.npcId,
|
||||
title: npc.displayName || conversation.npcId
|
||||
});
|
||||
} else {
|
||||
console.warn(`[NPCManager] MinigameFramework not available to start person-chat for timed conversation`);
|
||||
}
|
||||
|
||||
console.log(`[NPCManager] Delivered timed conversation from ${conversation.npcId} to knot: ${conversation.targetKnot}`);
|
||||
}
|
||||
|
||||
// Load timed messages from scenario data
|
||||
// timedMessages: [ { npcId, text, triggerTime, phoneId } ]
|
||||
loadTimedMessages(timedMessages) {
|
||||
|
||||
@@ -4,58 +4,54 @@ VAR conversation_started = false
|
||||
|
||||
=== hub ===
|
||||
# speaker:npc:test_npc_back
|
||||
Welcome! This is a group conversation test. Let me introduce you to my colleague.
|
||||
Woop! 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
|
||||
+ [Continue] -> colleague_introduction
|
||||
|
||||
=== colleague_introduction ===
|
||||
# speaker:npc:test_npc_front
|
||||
Nice to meet you! I'm the lead technician here. FRONT.
|
||||
-> player_question
|
||||
+ [Ask about their work] -> player_question
|
||||
|
||||
=== player_question ===
|
||||
# speaker:player
|
||||
What kind of work do you both do here?
|
||||
-> front_npc_explains
|
||||
+ [Listen] -> 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
|
||||
+ [Continue listening] -> 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
|
||||
+ [Respond] -> player_follow_up
|
||||
|
||||
=== player_follow_up ===
|
||||
# speaker:player
|
||||
That sounds like a well-coordinated operation!
|
||||
-> front_npc_agrees
|
||||
+ [Listen more] -> 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
|
||||
+ [Hear more] -> 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
|
||||
+ [Respond] -> 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
|
||||
+ [I appreciate the opportunity. I'll definitely consider it.] #exit_conversation
|
||||
Thank you.
|
||||
-> hub
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +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":{}}
|
||||
{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"hub":[["#","^speaker:npc:test_npc_back","/#","^Woop! 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","/#","ev","str","^I appreciate the opportunity. I'll definitely consider it.","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["^ ","#","^exit_conversation","/#","\n","^Thank you.","\n",{"->":"hub"},null]}],null],"global decl":["ev",false,{"VAR=":"conversation_started"},"/ev","end",null]}],"listDefs":{}}
|
||||
@@ -45,7 +45,11 @@
|
||||
"idleFrameEnd": 23
|
||||
},
|
||||
"storyPath": "scenarios/ink/test2.json",
|
||||
"currentKnot": "hub"
|
||||
"currentKnot": "hub",
|
||||
"timedConversation": {
|
||||
"delay": 3000,
|
||||
"targetKnot": "group_meeting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user