diff --git a/docs/TIMED_CONVERSATIONS.md b/docs/TIMED_CONVERSATIONS.md new file mode 100644 index 0000000..ae01724 --- /dev/null +++ b/docs/TIMED_CONVERSATIONS.md @@ -0,0 +1,365 @@ +# 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 +