mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 19:28:03 +00:00
366 lines
9.6 KiB
Markdown
366 lines
9.6 KiB
Markdown
|
|
# Timed Conversations for Person NPCs
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The **Timed Conversation** feature allows you to automatically trigger a person-to-person conversation with an NPC after a specified delay. This is similar to `timedMessages` for phone NPCs, but instead of a text message, it automatically opens the person-chat minigame and navigates to a specific conversation knot.
|
||
|
|
|
||
|
|
### Use Cases
|
||
|
|
|
||
|
|
- **Opening sequences**: Automatically start a dialogue when a scenario loads
|
||
|
|
- **Scripted conversations**: Trigger a cutscene-like conversation after the player enters a room
|
||
|
|
- **Delayed reactions**: Have an NPC approach the player after a delay with a specific message
|
||
|
|
- **Story progression**: Advance the narrative with timed character interactions
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Basic Structure
|
||
|
|
|
||
|
|
Add a `timedConversation` property to any person NPC in your scenario JSON:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "test_npc_back",
|
||
|
|
"displayName": "Back NPC",
|
||
|
|
"npcType": "person",
|
||
|
|
"position": { "x": 6, "y": 8 },
|
||
|
|
"spriteSheet": "hacker",
|
||
|
|
"storyPath": "scenarios/ink/test2.json",
|
||
|
|
"currentKnot": "hub",
|
||
|
|
"timedConversation": {
|
||
|
|
"delay": 3000,
|
||
|
|
"targetKnot": "group_meeting"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Properties
|
||
|
|
|
||
|
|
- **`delay`** (number, milliseconds): How long to wait before opening the conversation
|
||
|
|
- `0` = immediately when scenario loads
|
||
|
|
- `3000` = 3 seconds
|
||
|
|
- `60000` = 1 minute
|
||
|
|
|
||
|
|
- **`targetKnot`** (string): The Ink knot to navigate to when the conversation opens
|
||
|
|
- Must exist in the NPC's story file
|
||
|
|
- The conversation will start at this knot instead of the default `currentKnot`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Complete Example
|
||
|
|
|
||
|
|
### Scenario JSON
|
||
|
|
|
||
|
|
**File:** `scenarios/my-scenario.json`
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"scenario_brief": "A test scenario with timed NPC conversation",
|
||
|
|
"startRoom": "meeting_room",
|
||
|
|
|
||
|
|
"player": {
|
||
|
|
"id": "player",
|
||
|
|
"displayName": "Agent 0x00",
|
||
|
|
"spriteSheet": "hacker"
|
||
|
|
},
|
||
|
|
|
||
|
|
"rooms": {
|
||
|
|
"meeting_room": {
|
||
|
|
"type": "room_office",
|
||
|
|
"connections": {},
|
||
|
|
"npcs": [
|
||
|
|
{
|
||
|
|
"id": "colleague",
|
||
|
|
"displayName": "Junior Agent",
|
||
|
|
"npcType": "person",
|
||
|
|
"position": { "x": 4, "y": 5 },
|
||
|
|
"spriteSheet": "hacker-red",
|
||
|
|
"storyPath": "scenarios/ink/colleague.json",
|
||
|
|
"currentKnot": "idle",
|
||
|
|
"timedConversation": {
|
||
|
|
"delay": 5000,
|
||
|
|
"targetKnot": "briefing"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Ink Story
|
||
|
|
|
||
|
|
**File:** `scenarios/ink/colleague.ink`
|
||
|
|
|
||
|
|
```ink
|
||
|
|
VAR briefing_given = false
|
||
|
|
|
||
|
|
=== idle ===
|
||
|
|
# speaker:npc:colleague
|
||
|
|
Hey, I'm just waiting around for now.
|
||
|
|
-> END
|
||
|
|
|
||
|
|
=== briefing ===
|
||
|
|
# speaker:npc:colleague
|
||
|
|
Agent! I've been briefed on the situation. We need to discuss the plan.
|
||
|
|
~ briefing_given = true
|
||
|
|
-> main_menu
|
||
|
|
|
||
|
|
=== main_menu ===
|
||
|
|
+ [Tell me more about the situation] -> explain_situation
|
||
|
|
+ [What do you need from me?] -> ask_help
|
||
|
|
+ [Let's get moving] -> END
|
||
|
|
|
||
|
|
=== explain_situation ===
|
||
|
|
# speaker:npc:colleague
|
||
|
|
We've got a security breach in the east wing. Three suspects, two exit routes.
|
||
|
|
-> main_menu
|
||
|
|
|
||
|
|
=== ask_help ===
|
||
|
|
# speaker:npc:colleague
|
||
|
|
I need you to help me secure the perimeter while I investigate the breach.
|
||
|
|
-> main_menu
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## How It Works
|
||
|
|
|
||
|
|
### Initialization Flow
|
||
|
|
|
||
|
|
1. **Game starts** → `initializeGame()` is called
|
||
|
|
2. **NPCManager created** → `window.npcManager = new NPCManager(...)`
|
||
|
|
3. **Timed messages started** → `window.npcManager.startTimedMessages()`
|
||
|
|
- This starts a timer that checks every 1 second
|
||
|
|
4. **Scenario loads** → NPCs are registered via `registerNPC(npcDef)`
|
||
|
|
5. **NPCManager detects `timedConversation`** → Calls `scheduleTimedConversation()`
|
||
|
|
|
||
|
|
### Trigger Flow
|
||
|
|
|
||
|
|
1. **Timer ticks** → Every 1 second, `_checkTimedMessages()` runs
|
||
|
|
2. **Time threshold reached** → If `elapsed >= conversation.triggerTime`:
|
||
|
|
- `_deliverTimedConversation()` is called
|
||
|
|
- Updates NPC's `currentKnot` to `targetKnot`
|
||
|
|
- Opens person-chat minigame via `MinigameFramework.startMinigame('person-chat', ...)`
|
||
|
|
3. **Conversation opens** → Player sees the person-chat UI at the specified knot
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Reference
|
||
|
|
|
||
|
|
### NPCManager Methods
|
||
|
|
|
||
|
|
#### `scheduleTimedConversation(opts)`
|
||
|
|
|
||
|
|
Manually schedule a timed conversation (usually called automatically from `registerNPC`).
|
||
|
|
|
||
|
|
**Parameters:**
|
||
|
|
```javascript
|
||
|
|
{
|
||
|
|
npcId: string, // ID of the person NPC
|
||
|
|
targetKnot: string, // Ink knot to navigate to
|
||
|
|
delay?: number, // Milliseconds from now (if triggerTime not provided)
|
||
|
|
triggerTime?: number // Milliseconds from game start (if delay not provided)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```javascript
|
||
|
|
window.npcManager.scheduleTimedConversation({
|
||
|
|
npcId: 'colleague',
|
||
|
|
targetKnot: 'briefing',
|
||
|
|
delay: 5000
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `_deliverTimedConversation(conversation)`
|
||
|
|
|
||
|
|
Internal method called when a timed conversation is ready to trigger. Opens the person-chat minigame.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Details
|
||
|
|
|
||
|
|
### Code Changes
|
||
|
|
|
||
|
|
**File:** `js/systems/npc-manager.js`
|
||
|
|
|
||
|
|
**Constructor:** Added `this.timedConversations = []` to track scheduled conversations
|
||
|
|
|
||
|
|
**`registerNPC()`:** Added code to detect and schedule `timedConversation` property:
|
||
|
|
```javascript
|
||
|
|
if (entry.timedConversation) {
|
||
|
|
this.scheduleTimedConversation({
|
||
|
|
npcId: realId,
|
||
|
|
targetKnot: entry.timedConversation.targetKnot,
|
||
|
|
delay: entry.timedConversation.delay
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**`_checkTimedMessages()`:** Updated to also check timed conversations:
|
||
|
|
```javascript
|
||
|
|
for (const conversation of this.timedConversations) {
|
||
|
|
if (!conversation.delivered && elapsed >= conversation.triggerTime) {
|
||
|
|
this._deliverTimedConversation(conversation);
|
||
|
|
conversation.delivered = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**New Method: `_deliverTimedConversation()`:**
|
||
|
|
- Updates NPC's `currentKnot` to match `targetKnot`
|
||
|
|
- Calls `window.MinigameFramework.startMinigame('person-chat', null, { npcId, title })`
|
||
|
|
- Logs the action for debugging
|
||
|
|
|
||
|
|
### Integration Points
|
||
|
|
|
||
|
|
- **NPCManager:** Handles scheduling and delivery
|
||
|
|
- **MinigameFramework:** Opens the person-chat minigame
|
||
|
|
- **PersonChatMinigame:** Uses the updated `currentKnot` to start at the correct conversation point
|
||
|
|
- **Game initialization:** `startTimedMessages()` is already called in `js/main.js:87`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Comparison: Timed Messages vs Timed Conversations
|
||
|
|
|
||
|
|
| Feature | Timed Messages | Timed Conversations |
|
||
|
|
|---------|---|---|
|
||
|
|
| NPC Type | Phone NPCs | Person NPCs |
|
||
|
|
| Result | Bark + message notification | Automatic minigame open |
|
||
|
|
| Display | Phone chat minigame | Person-chat minigame |
|
||
|
|
| Interaction | Player clicks bark to open chat | Chat opens automatically |
|
||
|
|
| Use Case | Reactive notifications | Scripted sequences |
|
||
|
|
| Config Property | `timedMessages` (array) | `timedConversation` (object) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
### 1. Design Your Conversation Flow
|
||
|
|
|
||
|
|
Always have a main menu knot that conversations return to:
|
||
|
|
|
||
|
|
```ink
|
||
|
|
=== briefing ===
|
||
|
|
# speaker:npc:colleague
|
||
|
|
Here's the mission briefing...
|
||
|
|
-> main_menu
|
||
|
|
|
||
|
|
=== main_menu ===
|
||
|
|
+ [Ask about details] -> details
|
||
|
|
+ [Ready to go] -> END
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Account for Timed Conversation Delays
|
||
|
|
|
||
|
|
When designing your scenario, consider:
|
||
|
|
- Total elapsed time before conversation appears
|
||
|
|
- Player may not be ready (still learning controls)
|
||
|
|
- Consider adding text/UI guidance beforehand
|
||
|
|
|
||
|
|
### 3. Use Delays Appropriately
|
||
|
|
|
||
|
|
- **0-2 seconds:** Immediate (feels abrupt but useful for sequential conversations)
|
||
|
|
- **3-5 seconds:** Short delay (allows player to orient themselves)
|
||
|
|
- **10+ seconds:** Long delay (gives player time to explore before scripted event)
|
||
|
|
|
||
|
|
### 4. Combine with Events
|
||
|
|
|
||
|
|
You can also trigger conversations through game events instead of just time delays:
|
||
|
|
|
||
|
|
```json
|
||
|
|
"eventMappings": [
|
||
|
|
{
|
||
|
|
"eventPattern": "room_entered:briefing_room",
|
||
|
|
"targetKnot": "briefing",
|
||
|
|
"bark": "Agent, we need to talk!"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Debugging
|
||
|
|
|
||
|
|
### Enable NPC Debug Mode
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
window.NPC_DEBUG = true; // In browser console
|
||
|
|
```
|
||
|
|
|
||
|
|
This will log all timed conversation scheduling and delivery.
|
||
|
|
|
||
|
|
### Check Scheduled Conversations
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
console.log(window.npcManager.timedConversations);
|
||
|
|
```
|
||
|
|
|
||
|
|
Shows all pending timed conversations with their status.
|
||
|
|
|
||
|
|
### Verify NPC Registration
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
console.log(window.npcManager.getNPC('colleague'));
|
||
|
|
```
|
||
|
|
|
||
|
|
Shows the NPC object, including the `timedConversation` property.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test Scenario
|
||
|
|
|
||
|
|
See `scenarios/npc-sprite-test2.json` for a working example where:
|
||
|
|
- `test_npc_back` automatically opens a conversation after 3 seconds
|
||
|
|
- Conversation starts at the `group_meeting` knot
|
||
|
|
- Player can interact with multiple NPCs in the dialogue
|
||
|
|
|
||
|
|
To test:
|
||
|
|
```
|
||
|
|
Open: http://localhost:8000/index.html?scenario=npc-sprite-test2
|
||
|
|
Wait 3 seconds for automatic conversation to open
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Conversation Doesn't Open
|
||
|
|
|
||
|
|
1. Check browser console for errors
|
||
|
|
2. Verify `MinigameFramework` is available: `console.log(window.MinigameFramework)`
|
||
|
|
3. Confirm NPC ID matches: `console.log(window.npcManager.getNPC('npcId'))`
|
||
|
|
4. Verify targetKnot exists in Ink story
|
||
|
|
|
||
|
|
### Conversation Opens at Wrong Knot
|
||
|
|
|
||
|
|
1. Verify `targetKnot` spelling matches Ink file
|
||
|
|
2. Check that knot exists in compiled JSON (not just `.ink` source)
|
||
|
|
3. Ensure JSON was recompiled after Ink changes
|
||
|
|
|
||
|
|
### Timer Not Running
|
||
|
|
|
||
|
|
1. Verify `window.npcManager.startTimedMessages()` was called
|
||
|
|
2. Check `window.npcManager.timerInterval` is not null
|
||
|
|
3. Verify game time is advancing (check `window.npcManager.gameStartTime`)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Limitations & Future Enhancements
|
||
|
|
|
||
|
|
### Current Limitations
|
||
|
|
|
||
|
|
- Only one `timedConversation` per NPC (can add multiple via `scheduleTimedConversation()` API)
|
||
|
|
- Uses global timer (all timed events checked once per second)
|
||
|
|
- No built-in "wait for user input" before triggering
|
||
|
|
|
||
|
|
### Potential Enhancements
|
||
|
|
|
||
|
|
- Support multiple conversations per NPC with conditions
|
||
|
|
- Trigger on specific player actions (e.g., "when player approaches NPC")
|
||
|
|
- Cinematic camera focus before opening conversation
|
||
|
|
- Animation/transition effects when opening timed conversation
|
||
|
|
|