fix: Correct conversation ending pattern to preserve state at mission_hub

BREAKING CHANGE: Updated conversation ending behavior

Old Pattern (WRONG):
- Conversations ended with #exit_conversation
- Diverted to -> END (lost state)
- Game code navigated back to mission_hub
- State not preserved between interactions

New Pattern (CORRECT):
- Conversations end with #end_conversation
- Divert to -> mission_hub (preserves state)
- Game code closes UI window
- Next interaction resumes from mission_hub with full context

Why This Matters:
- State preservation: NPC remembers where conversation left off
- Flexible re-entry: Player can talk to NPC multiple times
- Context awareness: Hub can show different options based on prior discussions

Files Changed:
- All *_ongoing_conversations.ink files: Updated conversation_end knots
  - Changed #exit_conversation -> #end_conversation
  - Changed -> END -> -> mission_hub
- PersonChatConversation.js: Renamed handleExitConversation -> handleEndConversation
  - Now dispatches 'npc-conversation-ended' event to close UI
  - Ink handles state preservation via -> mission_hub
- PhoneChatConversation.js: Same changes as PersonChatConversation
- INK_BEST_PRACTICES.md: Updated documentation with correct pattern
  - Fixed conversation ending examples
  - Updated checklists
  - Added state preservation explanation
- NPC_HUB_ARCHITECTURE.md: Updated mission hub flow documentation
- Recompiled all hub JSON files

This ensures conversation state is properly maintained across
multiple interactions with the same NPC within a play session.
This commit is contained in:
Z. Cliffe Schreuders
2025-11-19 13:44:30 +00:00
parent 1f5c9e29e8
commit acc793815b
10 changed files with 89 additions and 82 deletions

View File

@@ -428,8 +428,8 @@ export default class PersonChatConversation {
this.handlePersonalSpace(params[0]);
break;
case 'exit_conversation':
this.handleExitConversation();
case 'end_conversation':
this.handleEndConversation();
break;
default:
@@ -667,26 +667,25 @@ export default class PersonChatConversation {
}
/**
* Handle exit_conversation tag - navigate back to NPC's mission hub
* Tag: #exit_conversation
* This returns the player to the mission hub after personal conversations
* Handle end_conversation tag - signal conversation should close
* Tag: #end_conversation
* The ink script has already diverted to mission_hub, preserving state.
* This signals the UI layer to close the conversation window.
* Next time player talks to this NPC, it will resume from mission_hub.
*/
handleExitConversation() {
console.log(`🔙 Exit conversation - navigating back to mission_hub for ${this.npc.id}`);
handleEndConversation() {
console.log(`👋 End conversation for ${this.npc.id} - conversation state preserved at mission_hub`);
// Navigate back to the mission_hub knot in this NPC's hub file
if (this.inkEngine) {
try {
this.inkEngine.goToKnot('mission_hub');
// Advance to get the new content at mission_hub
this.advance();
console.log(`✅ Returned to mission_hub for ${this.npc.id}`);
} catch (error) {
console.error(`❌ Error navigating to mission_hub:`, error);
// Dispatch event for UI layer to close the conversation window
const event = new CustomEvent('npc-conversation-ended', {
detail: {
npcId: this.npc.id,
preservedAtHub: true
}
}
});
window.dispatchEvent(event);
console.log(`✅ Conversation ended, will resume from mission_hub on next interaction`);
}
/**

View File

@@ -278,8 +278,8 @@ export default class PhoneChatConversation {
const [action, ...params] = tag.split(':');
switch (action.trim().toLowerCase()) {
case 'exit_conversation':
this.handleExitConversation();
case 'end_conversation':
this.handleEndConversation();
break;
default:
@@ -290,25 +290,25 @@ export default class PhoneChatConversation {
}
/**
* Handle exit_conversation tag - navigate back to NPC's mission hub
* Tag: #exit_conversation
* This returns the player to the mission hub after personal conversations
* Handle end_conversation tag - signal conversation should close
* Tag: #end_conversation
* The ink script has already diverted to mission_hub, preserving state.
* This signals the UI layer to close the conversation window.
* Next time player talks to this NPC, it will resume from mission_hub.
*/
handleExitConversation() {
console.log(`🔙 Exit conversation - navigating back to mission_hub for ${this.npcId}`);
handleEndConversation() {
console.log(`👋 End conversation for ${this.npcId} - conversation state preserved at mission_hub`);
// Navigate back to the mission_hub knot in this NPC's hub file
if (this.engine) {
try {
this.engine.goToKnot('mission_hub');
// Get the new content at mission_hub (but don't auto-continue)
// The UI will handle displaying it
console.log(`✅ Returned to mission_hub for ${this.npcId}`);
} catch (error) {
console.error(`❌ Error navigating to mission_hub:`, error);
// Dispatch event for UI layer to close the conversation window
const event = new CustomEvent('npc-conversation-ended', {
detail: {
npcId: this.npcId,
preservedAtHub: true
}
}
});
window.dispatchEvent(event);
console.log(`✅ Conversation ended, will resume from mission_hub on next interaction`);
}
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -40,26 +40,27 @@ Every NPC hub file uses a standardized `mission_hub` knot that serves as the cen
- Mission topics return to `mission_hub` after completion
- Player sees continuous conversation, game manages routing
### How #exit_conversation Works
### How #end_conversation Works
When personal conversations end, they use `#exit_conversation` tag to trigger navigation:
When personal conversations end, they use `#end_conversation` tag and return to mission_hub:
```ink
=== conversation_end ===
Dr. Chen: Great talking with you!
#exit_conversation
-> END
#end_conversation
-> mission_hub
```
The game code detects this tag and automatically calls:
```javascript
inkEngine.goToKnot('mission_hub');
```
**What happens:**
1. Ink script diverts to `mission_hub` - **preserving conversation state**
2. Game code detects `#end_conversation` tag and closes the UI
3. Next time player talks to this NPC, conversation resumes from `mission_hub`
4. NPC remembers where they left off, can offer new mission/personal topics
This returns the player to the NPC's hub menu where they can choose to:
- Continue with another personal topic
- Discuss mission-related matters
- End the conversation
**Why this matters:**
- **State preservation** - Conversation picks up where it left off
- **Flexible re-entry** - Player can talk to NPC multiple times in a session
- **Context awareness** - Hub can show different options based on what was discussed
---
@@ -322,7 +323,7 @@ Route to appropriate phase based on mission progress:
### Conversation End Pattern
Always end personal conversations with the exit tag:
Always end personal conversations by returning to mission_hub:
```ink
=== conversation_end ===
@@ -335,10 +336,12 @@ Always end personal conversations with the exit tag:
Dr. Chen: Talk later.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
```
**Important:** The divert to `mission_hub` preserves state. The `#end_conversation` tag signals the UI to close. Next interaction resumes from the hub with full context.
### Conditional Relationship Responses
Vary NPC responses based on relationship level:
@@ -406,7 +409,7 @@ Netherton: Do you think the ends justify the means?
- [ ] Declare all EXTERNAL functions at top of hub file
- [ ] Use `mission_hub` knot as central routing point
- [ ] End personal conversations with `#exit_conversation`
- [ ] End personal conversations with `#end_conversation` and `-> mission_hub`
- [ ] Add influence tags after every relationship variable change
- [ ] Use `has_available_personal_topics()` function
- [ ] Implement phase-based content routing
@@ -422,6 +425,8 @@ Netherton: Do you think the ends justify the means?
{player_name} // Missing parentheses
~ npc_chen_rapport += 5 // No visual feedback tag
-> chen_hub // Non-standard hub name
#exit_conversation // Old tag name
-> END // Doesn't preserve state
```
**Correct:**
@@ -430,6 +435,8 @@ Netherton: Do you think the ends justify the means?
~ npc_chen_rapport += 5 // Variable change
#rapport_gained:5 // Visual feedback tag
-> mission_hub // Standard hub knot name
#end_conversation // Correct tag to close UI
-> mission_hub // Preserves state for next interaction
```
---
@@ -472,7 +479,7 @@ When creating a new NPC:
- [ ] Phase hubs (phase_1_hub through phase_4_hub)
- [ ] `has_available_personal_topics()` function
- [ ] `jump_to_personal_conversations` knot
- [ ] `conversation_end` knot with `#exit_conversation`
- [ ] `conversation_end` knot with `#end_conversation` and `-> mission_hub`
- [ ] Influence tags on all relationship changes
3. **Mission-Specific Files** (optional, `npc_mission_*.ink`)

View File

@@ -437,23 +437,24 @@ All NPC hub files now use a standardized `mission_hub` knot that serves as the c
2. **Greeting**: Context-aware greeting based on location/phase
3. **Hub**: Automatically diverts to `mission_hub`
4. **Routing**: Player chooses personal or mission topics
5. **Return**: Personal conversations end with `#exit_conversation` tag
6. **Navigation**: Game code detects tag and calls `inkEngine.goToKnot('mission_hub')`
7. **Loop**: Player can discuss more topics or end conversation
5. **Return**: Personal conversations end with `#end_conversation` and `-> mission_hub`
6. **Close UI**: Game code detects `#end_conversation` tag and closes conversation window
7. **State Preserved**: Next interaction resumes from `mission_hub` with full context
### Benefits
- **Seamless Flow**: Player experiences continuous conversation
- **Clear Separation**: Personal vs mission content isolated in separate files
- **Easy Return**: `#exit_conversation` tag provides consistent return mechanism
- **State Preservation**: Conversation state maintained between interactions
- **Standard Pattern**: All NPCs use same `mission_hub` knot name
### Implementation Notes
- All hub files renamed from `npcname_main_hub` to `mission_hub`
- Personal conversation files always return via `#exit_conversation` tag
- Game code handles automatic navigation back to hub
- Mission-specific content can also return to `mission_hub`
- Personal conversation files always return to `mission_hub` with `#end_conversation` tag
- Game code detects `#end_conversation` and closes UI (doesn't navigate)
- Ink script handles navigation back to hub (preserves state)
- Next player interaction resumes from `mission_hub` seamlessly
For detailed implementation examples, see **INK_BEST_PRACTICES.md**.

View File

@@ -1940,8 +1940,8 @@ Dr. Chen: Peaceful. After years of fighting cyber threats. Just... peace.
Dr. Chen: Take care. Let me know if you need anything.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
=== conversation_end_phase4 ===
@@ -1954,8 +1954,8 @@ Dr. Chen: Peaceful. After years of fighting cyber threats. Just... peace.
Dr. Chen: Good talking. Be safe.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
=== conversation_end_phase1 ===
@@ -1969,8 +1969,8 @@ Dr. Chen: Peaceful. After years of fighting cyber threats. Just... peace.
Dr. Chen: Alright. Good luck out there.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
=== conversation_end_phase2 ===
@@ -1983,5 +1983,5 @@ Dr. Chen: Peaceful. After years of fighting cyber threats. Just... peace.
Dr. Chen: Talk later. Good luck.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub

View File

@@ -1053,8 +1053,8 @@ Haxolottle: But it's worth checking in with yourself. "Is this useful vigilance
Haxolottle: And hey... you're becoming a real friend. Within the constraints of Protocol 47-Alpha, but a friend nonetheless.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
// ===========================================
// STUB KNOTS - To be implemented in separate file

View File

@@ -1743,8 +1743,8 @@ Netherton: The fact that you understand that—that protocols are tools, not rep
Netherton: Dismissed.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
=== conversation_end_phase2 ===
@@ -1757,8 +1757,8 @@ Netherton: The fact that you understand that—that protocols are tools, not rep
Netherton: That will be all.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
=== conversation_end_phase3 ===
@@ -1771,8 +1771,8 @@ Netherton: The fact that you understand that—that protocols are tools, not rep
Netherton: Dismissed, Agent.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub
=== conversation_end_phase4 ===
@@ -1785,5 +1785,5 @@ Netherton: The fact that you understand that—that protocols are tools, not rep
Netherton: That will be all.
}
#exit_conversation
-> END
#end_conversation
-> mission_hub