mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-21 11:18:08 +00:00
feat(npc): Enhance NPC interaction with room navigation events and refined greeting logic
This commit is contained in:
@@ -121,14 +121,22 @@ VAR has_given_item = false
|
||||
|
||||
### 2. Define the Entry Points
|
||||
|
||||
Every Ink story needs two key knots:
|
||||
Every Ink story needs a properly structured start flow to avoid repeating messages:
|
||||
|
||||
```ink
|
||||
// Initial greeting - shown only on first contact
|
||||
// State variable to track if greeting has been shown
|
||||
VAR has_greeted = false
|
||||
|
||||
// Initial entry point - shows greeting once, then goes to menu
|
||||
=== start ===
|
||||
Hello! I'm here to help you with your mission. 👋
|
||||
How are things going?
|
||||
-> main_menu
|
||||
{ has_greeted:
|
||||
-> main_menu
|
||||
- else:
|
||||
Hello! I'm here to help you with your mission. 👋
|
||||
How are things going?
|
||||
~ has_greeted = true
|
||||
-> main_menu
|
||||
}
|
||||
|
||||
// Main menu - shown when returning to conversation
|
||||
=== main_menu ===
|
||||
@@ -137,10 +145,20 @@ How are things going?
|
||||
+ [Say goodbye] -> goodbye
|
||||
```
|
||||
|
||||
**Key Pattern**:
|
||||
- `start` shows the initial greeting message and immediately redirects to `main_menu`
|
||||
- `main_menu` presents only choices (no repeated text) since all messages are in conversation history
|
||||
- All conversation knots should redirect to `main_menu` (not `start`) to avoid repeating the greeting
|
||||
**Key Pattern for Avoiding Repeated Messages**:
|
||||
- Add a `has_greeted` variable at the top of your Ink file
|
||||
- `start` knot checks if greeting has been shown:
|
||||
- If already greeted, skip directly to `main_menu`
|
||||
- If not greeted, show greeting text, set `has_greeted = true`, then go to `main_menu`
|
||||
- `main_menu` presents only choices (no text) since conversation history shows all previous messages
|
||||
- All conversation knots redirect to `main_menu` (not `start`) to avoid re-triggering the greeting
|
||||
- Event-triggered barks also redirect to `main_menu` for seamless conversation continuation
|
||||
|
||||
**Why This Works**:
|
||||
- First contact: Player sees greeting, then choices
|
||||
- After barks: Player sees bark message (in history), then choices - no repeated greeting
|
||||
- Reopening conversation: Player sees full history, then choices - no repeated greeting
|
||||
- The greeting appears in conversation history but never repeats as a new message
|
||||
|
||||
---
|
||||
|
||||
@@ -248,6 +266,21 @@ Excellent work on that challenge! 🎯
|
||||
- ❌ Using `-> END` - prevents conversation from continuing
|
||||
- ❌ Long messages - barks should be brief notifications
|
||||
|
||||
**Important: Avoiding Repeated Greetings**
|
||||
|
||||
When a bark redirects to `main_menu` (not `start`), the conversation flow works like this:
|
||||
|
||||
1. Player performs action (e.g., enters a room)
|
||||
2. Bark notification appears with contextual message
|
||||
3. Player clicks the bark to open conversation
|
||||
4. Conversation shows:
|
||||
- Original greeting (in history)
|
||||
- Bark message (just clicked)
|
||||
- Menu choices (from `main_menu`)
|
||||
5. **No repeated greeting** because we skipped the `start` knot
|
||||
|
||||
If barks redirect to `start`, the `has_greeted` check prevents re-showing the greeting text, but it's cleaner to go straight to `main_menu`.
|
||||
|
||||
---
|
||||
|
||||
## Configuring NPCs in Scenario JSON
|
||||
@@ -444,10 +477,12 @@ The JSON file is what gets loaded by the game engine.
|
||||
### Conversation Design
|
||||
|
||||
1. **Use clear variable names**: `trust_level`, `has_given_keycard`, `knows_secret`
|
||||
2. **Gate important actions behind trust**: Players should build rapport before getting help
|
||||
3. **Provide multiple conversation paths**: Not everyone plays the same way
|
||||
4. **Use emojis sparingly**: They add personality but shouldn't overwhelm
|
||||
5. **Keep initial greeting brief**: Players want to get to choices quickly
|
||||
2. **Always add `has_greeted` variable**: Prevents repeated greetings across sessions
|
||||
3. **Gate important actions behind trust**: Players should build rapport before getting help
|
||||
4. **Provide multiple conversation paths**: Not everyone plays the same way
|
||||
5. **Use emojis sparingly**: They add personality but shouldn't overwhelm
|
||||
6. **Keep initial greeting brief**: Players want to get to choices quickly
|
||||
7. **Check `has_greeted` in start knot**: Skip directly to `main_menu` if already greeted
|
||||
|
||||
### Bark Design
|
||||
|
||||
@@ -555,11 +590,17 @@ VAR trust_level = 0
|
||||
VAR has_given_advice = false
|
||||
VAR mission_briefed = false
|
||||
VAR rooms_discovered = 0
|
||||
VAR has_greeted = false
|
||||
|
||||
=== start ===
|
||||
Hey, I'm glad you're on this case. This is going to be tricky. 🕵️
|
||||
Let me know if you need guidance.
|
||||
-> main_menu
|
||||
{ has_greeted:
|
||||
-> main_menu
|
||||
- else:
|
||||
Hey, I'm glad you're on this case. This is going to be tricky. 🕵️
|
||||
Let me know if you need guidance.
|
||||
~ has_greeted = true
|
||||
-> main_menu
|
||||
}
|
||||
|
||||
=== main_menu ===
|
||||
+ [What should I be looking for?] -> mission_briefing
|
||||
@@ -705,7 +746,8 @@ When integrating a new NPC:
|
||||
**Ink Story Creation**:
|
||||
- [ ] Create `.ink` file in `scenarios/ink/`
|
||||
- [ ] Define state variables at top of file
|
||||
- [ ] Create `start` knot with initial greeting
|
||||
- [ ] Add `has_greeted` variable to prevent repeated greetings
|
||||
- [ ] Create `start` knot with greeting + `has_greeted` check
|
||||
- [ ] Create `main_menu` knot with choices (no repeated text)
|
||||
- [ ] Create conversation knots that redirect to `main_menu`
|
||||
- [ ] Create event-triggered bark knots (also redirect to `main_menu`)
|
||||
|
||||
@@ -53,6 +53,11 @@ import { initializeCollision, createWallCollisionBoxes, removeTilesUnderDoor, re
|
||||
export let rooms = {};
|
||||
export let currentRoom = '';
|
||||
export let currentPlayerRoom = '';
|
||||
// Track which rooms have been DISCOVERED by the player
|
||||
// NOTE: "Discovered" means the player has ENTERED the room via door transition.
|
||||
// This is separate from "revealed" (graphics visible). Rooms can be revealed
|
||||
// (loaded for graphics/performance) without being discovered (player hasn't entered yet).
|
||||
// This distinction is important for NPC event triggers like "room_discovered".
|
||||
export let discoveredRooms = new Set();
|
||||
|
||||
// Helper function to check if a position overlaps with existing items
|
||||
@@ -516,6 +521,10 @@ function loadRoom(roomId) {
|
||||
|
||||
console.log(`Lazy loading room: ${roomId}`);
|
||||
createRoom(roomId, roomData, position);
|
||||
|
||||
// Reveal (make visible) but do NOT mark as discovered
|
||||
// The room will only be marked as "discovered" when the player
|
||||
// actually enters it via door transition
|
||||
revealRoom(roomId);
|
||||
}
|
||||
|
||||
@@ -527,6 +536,9 @@ export function initializeRooms(gameInstance) {
|
||||
currentRoom = '';
|
||||
currentPlayerRoom = '';
|
||||
window.currentPlayerRoom = '';
|
||||
|
||||
// Clear discovered rooms on scenario load
|
||||
// This ensures "first visit" detection works correctly for NPC events
|
||||
discoveredRooms = new Set();
|
||||
// Update global reference
|
||||
window.discoveredRooms = discoveredRooms;
|
||||
@@ -1603,6 +1615,19 @@ export function createRoom(roomId, roomData, position) {
|
||||
}
|
||||
|
||||
export function revealRoom(roomId) {
|
||||
// IMPORTANT: revealRoom() makes graphics VISIBLE but does NOT mark as DISCOVERED
|
||||
//
|
||||
// "Revealed" = graphics are loaded and visible (for rendering/performance)
|
||||
// "Discovered" = player has actually ENTERED the room (for gameplay/events)
|
||||
//
|
||||
// This separation allows us to:
|
||||
// 1. Preload/reveal rooms for performance without marking them as "visited"
|
||||
// 2. Trigger "room_discovered" events when player first ENTERS a room
|
||||
// 3. Keep "first visit" detection accurate for NPC reactions
|
||||
//
|
||||
// Rooms are marked as "discovered" in the door transition code, AFTER
|
||||
// the room_discovered event is emitted.
|
||||
|
||||
if (rooms[roomId]) {
|
||||
const room = rooms[roomId];
|
||||
|
||||
@@ -1637,9 +1662,10 @@ export function revealRoom(roomId) {
|
||||
console.log(`No objects found in room ${roomId}`);
|
||||
}
|
||||
|
||||
discoveredRooms.add(roomId);
|
||||
// Update global reference
|
||||
window.discoveredRooms = discoveredRooms;
|
||||
// NOTE: We do NOT add to discoveredRooms here!
|
||||
// Rooms are only marked as "discovered" when the player actually enters them
|
||||
// via door transition. This allows revealRoom() to be used for preloading/visibility
|
||||
// without affecting the "first visit" detection for NPC events.
|
||||
}
|
||||
currentRoom = roomId;
|
||||
}
|
||||
@@ -1662,13 +1688,23 @@ export function updatePlayerRoom() {
|
||||
currentPlayerRoom = doorTransitionRoom;
|
||||
window.currentPlayerRoom = doorTransitionRoom;
|
||||
|
||||
// Reveal the room if not already discovered
|
||||
// Check if this is the first time the player has ENTERED this room
|
||||
// NOTE: The room may already be "revealed" (graphics visible) from preloading,
|
||||
// but we only mark it as "discovered" when the player actually walks through
|
||||
// a door into it. This keeps first-visit detection accurate for NPC events.
|
||||
const isFirstVisit = !discoveredRooms.has(doorTransitionRoom);
|
||||
|
||||
if (isFirstVisit) {
|
||||
// Reveal graphics if needed (may already be revealed from preloading)
|
||||
revealRoom(doorTransitionRoom);
|
||||
}
|
||||
|
||||
// Emit NPC event for room entry
|
||||
console.log(`🚪 Door transition detected: ${previousRoom} → ${doorTransitionRoom}`);
|
||||
console.log(` eventDispatcher exists: ${!!window.eventDispatcher}`);
|
||||
console.log(` previousRoom !== doorTransitionRoom: ${previousRoom !== doorTransitionRoom}`);
|
||||
console.log(` isFirstVisit: ${isFirstVisit}`);
|
||||
|
||||
if (window.eventDispatcher && previousRoom !== doorTransitionRoom) {
|
||||
console.log(`🚪 Emitting room_entered event: ${doorTransitionRoom} (firstVisit: ${isFirstVisit})`);
|
||||
window.eventDispatcher.emit('room_entered', {
|
||||
@@ -1691,6 +1727,16 @@ export function updatePlayerRoom() {
|
||||
roomId: doorTransitionRoom,
|
||||
previousRoom: previousRoom
|
||||
});
|
||||
|
||||
// Mark as discovered AFTER emitting the event
|
||||
// This is the ONLY place where rooms are added to discoveredRooms!
|
||||
// By marking discovered here (not in revealRoom), we ensure:
|
||||
// 1. The first door transition into a room triggers room_discovered
|
||||
// 2. NPCs can react to the player's first visit
|
||||
// 3. Subsequent visits don't re-trigger the event
|
||||
discoveredRooms.add(doorTransitionRoom);
|
||||
window.discoveredRooms = discoveredRooms;
|
||||
console.log(`✅ Marked room ${doorTransitionRoom} as discovered`);
|
||||
}
|
||||
|
||||
if (previousRoom) {
|
||||
@@ -1699,6 +1745,8 @@ export function updatePlayerRoom() {
|
||||
nextRoom: doorTransitionRoom
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ NOT emitting room events - eventDispatcher: ${!!window.eventDispatcher}, previousRoom: ${previousRoom}, doorTransitionRoom: ${doorTransitionRoom}`);
|
||||
}
|
||||
|
||||
// Player depth is now handled by the simplified updatePlayerDepth function in player.js
|
||||
|
||||
@@ -325,10 +325,22 @@
|
||||
- [x] Verify bark frequency limits (maxTriggers) ✅ Implemented!
|
||||
|
||||
6. **Polish UI/UX** 🔄 IN PROGRESS
|
||||
- [ ] Sound effects (message_received.wav, bark_notification.wav)
|
||||
- [ ] Better NPC avatars (32x32px pixel art)
|
||||
- [x] Room navigation events (Priority 2) ✅ COMPLETE (2024-10-31)
|
||||
- [x] Added `room_entered`, `room_entered:${roomId}`, `room_discovered`, `room_exited` events
|
||||
- [x] Created Ink reaction knots (on_room_discovered, on_ceo_office_entered)
|
||||
- [x] Added event mappings to scenario JSON
|
||||
- [x] Fixed conversation flow (main_menu vs start)
|
||||
- [x] Updated unlock messages to be generic (key or lockpick)
|
||||
- [ ] Sound effects (Priority 1)
|
||||
- [ ] Message received sound - assets/sounds/message_received.mp3
|
||||
- [ ] NPC avatars (Priority 3)
|
||||
- [ ] Create default avatars (helper, adversary, neutral)
|
||||
- [ ] Add avatar support in scenarios
|
||||
- [ ] More game events (Priority 2 continued)
|
||||
- [ ] objective_completed
|
||||
- [ ] evidence_collected
|
||||
- [ ] player_detected
|
||||
- [ ] Objective notification system
|
||||
- [ ] Secret/discovery UI
|
||||
- [ ] Achievement/progress tracking
|
||||
|
||||
7. **Performance optimization** ⏳ NEXT
|
||||
@@ -338,8 +350,33 @@
|
||||
- [ ] Optimize bark rendering for multiple simultaneous barks
|
||||
|
||||
---
|
||||
**Last Updated:** 2024-10-31 (Phase 4 Event-Driven Reactions COMPLETE & TESTED)
|
||||
**Status:** Phase 4 Complete ✅ - Moving to Phase 5: Polish & Additional Features
|
||||
**Last Updated:** 2024-10-31 (Phase 5 Room Navigation Events COMPLETE)
|
||||
**Status:** Phase 5 In Progress - Room navigation events ✅, moving to sound effects and additional events
|
||||
|
||||
## Recent Improvements (2024-10-31 - Phase 5)
|
||||
|
||||
### ✅ Room Navigation Events (Priority 2 - Partial)
|
||||
- **Event emissions in rooms.js**:
|
||||
- `room_entered` - General room change event
|
||||
- `room_entered:${roomId}` - Specific room entry
|
||||
- `room_discovered` - First-time room visits
|
||||
- `room_exited` - Leaving a room
|
||||
- **Ink reactions in helper-npc.ink**:
|
||||
- `on_room_discovered` - Generic exploration encouragement
|
||||
- `on_ceo_office_entered` - Special CEO office reaction with trust reward
|
||||
- **Event mappings configured**:
|
||||
- `room_discovered` with 15s cooldown, max 5 triggers
|
||||
- `room_entered:ceo` one-time only reaction
|
||||
- **Conversation flow refinements**:
|
||||
- Split `start` and `main_menu` knots
|
||||
- Barks redirect to `main_menu` (not `start`) to avoid repeated greeting
|
||||
- "What can I do for you?" only appears in initial greeting
|
||||
- **Message updates**:
|
||||
- Unlock messages now generic (work for key or lockpick)
|
||||
- Lockpicking success bark doesn't assume method
|
||||
- **Documentation created**:
|
||||
- `NPC_INTEGRATION_GUIDE.md` - Comprehensive guide for adding NPCs to scenarios
|
||||
- Includes phone setup, Ink structure, event mappings, testing checklist
|
||||
|
||||
## Recent Improvements (2025-10-30)
|
||||
|
||||
|
||||
@@ -75,6 +75,12 @@
|
||||
"targetKnot": "on_item_found",
|
||||
"cooldown": 20000
|
||||
},
|
||||
{
|
||||
"eventPattern": "room_entered",
|
||||
"targetKnot": "on_room_entered",
|
||||
"cooldown": 45000,
|
||||
"maxTriggers": 3
|
||||
},
|
||||
{
|
||||
"eventPattern": "room_discovered",
|
||||
"targetKnot": "on_room_discovered",
|
||||
|
||||
@@ -7,11 +7,17 @@ VAR has_unlocked_ceo = false
|
||||
VAR has_given_lockpick = false
|
||||
VAR saw_lockpick_used = false
|
||||
VAR saw_door_unlock = false
|
||||
VAR has_greeted = false
|
||||
|
||||
=== start ===
|
||||
Hey there! I'm here to help you out if you need it. 👋
|
||||
What can I do for you?
|
||||
-> main_menu
|
||||
{ has_greeted:
|
||||
-> main_menu
|
||||
- else:
|
||||
Hey there! I'm here to help you out if you need it. 👋
|
||||
What can I do for you?
|
||||
~ has_greeted = true
|
||||
-> main_menu
|
||||
}
|
||||
|
||||
=== main_menu ===
|
||||
+ [Who are you?] -> who_are_you
|
||||
@@ -139,10 +145,29 @@ That door's locked tight. You'll need to find a way to unlock it. 🔒
|
||||
}
|
||||
-> main_menu
|
||||
|
||||
// Triggered when player enters any room (general progress check)
|
||||
=== on_room_entered ===
|
||||
{ has_unlocked_ceo:
|
||||
Keep searching for that evidence! 🔍
|
||||
- else:
|
||||
{ trust_level >= 1:
|
||||
You're making progress through the building. 🚶
|
||||
- else:
|
||||
Exploring new areas... 🚶
|
||||
}
|
||||
}
|
||||
-> main_menu
|
||||
|
||||
// Triggered when player discovers a new room for the first time
|
||||
=== on_room_discovered ===
|
||||
{ trust_level >= 1:
|
||||
Interesting! You've found a new area. Be careful exploring. 🗺️
|
||||
{ trust_level >= 2:
|
||||
Great find! This new area might have what we need. 🗺️✨
|
||||
- else:
|
||||
{ trust_level >= 1:
|
||||
Interesting! You've found a new area. Be careful exploring. 🗺️
|
||||
- else:
|
||||
A new room... wonder what's inside. 🚪
|
||||
}
|
||||
}
|
||||
-> main_menu
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user