feat: Include objectives state in server response and implement event emissions for door unlocks and key pickups

This commit is contained in:
Z. Cliffe Schreuders
2025-11-25 23:19:11 +00:00
parent 575dc9aad0
commit 150518b4c4
7 changed files with 114 additions and 227 deletions

View File

@@ -22,6 +22,12 @@ module BreakEscape
# Remove 'requires' fields recursively for security
filter_requires_recursive(filtered)
# Include objectives state for page reload recovery
# This allows the client to restore completed/progress state
if @game.player_state['objectivesState'].present?
filtered['objectivesState'] = @game.player_state['objectivesState']
end
render json: filtered
rescue => e
Rails.logger.error "[BreakEscape] scenario error: #{e.message}\n#{e.backtrace.first(5).join("\n")}"

View File

@@ -1282,81 +1282,39 @@ if (gameScenario.objectives && window.objectivesManager) {
> **CRITICAL**: Objectives data initialization MUST happen in `game.js create()` function, NOT in `main.js`. The scenario JSON is not available until `create()` runs. The manager is created in `main.js`, but `initialize()` with data happens in `game.js`.
### 6. Door Unlock Event Fix
### 6. Door Unlock Events - Already Implemented ✅
**File:** Update `public/break_escape/js/systems/doors.js`
**File:** `public/break_escape/js/systems/unlock-system.js` (line 560)
**CRITICAL**: The current codebase does NOT emit `door_unlocked` events. Add event emission to the `unlockDoor()` function:
Door unlock events are already emitted from the central `unlock-system.js`:
```javascript
function unlockDoor(doorSprite, roomData) {
const props = doorSprite.doorProperties;
console.log(`Unlocking door: ${props.roomId} -> ${props.connectedRoom}`);
// Mark door as unlocked
props.locked = false;
// If roomData was provided from server unlock response, cache it
if (roomData && window.roomDataCache) {
console.log(`📦 Caching room data for ${props.connectedRoom} from unlock response`);
window.roomDataCache.set(props.connectedRoom, roomData);
}
// Emit door unlocked event for objectives system
if (window.eventDispatcher) {
window.eventDispatcher.emit('door_unlocked', {
roomId: props.roomId,
connectedRoom: props.connectedRoom,
direction: props.direction
});
console.log(`📋 Emitted door_unlocked event: ${props.roomId} -> ${props.connectedRoom}`);
}
// TODO: Implement unlock animation/effect
// Open the door
openDoor(doorSprite);
}
// In unlockTarget() function (line 560)
window.eventDispatcher.emit('door_unlocked', {
roomId: doorProps.roomId,
connectedRoom: doorProps.connectedRoom,
direction: doorProps.direction,
lockType: doorProps.lockType
});
```
> **NOTE**: Use `data.connectedRoom` when listening to this event - that's the room being unlocked.
### 7. Key Item Event Fix
### 7. Key Item Events - Now Implemented ✅
**File:** Update `public/break_escape/js/systems/inventory.js`
**File:** `public/break_escape/js/systems/inventory.js`
In the `addKeyToInventory()` function, add event emission after adding the key:
Key pickup events are now emitted in `addKeyToInventory()`:
```javascript
function addKeyToInventory(sprite) {
// ... existing code to add key to keyRing ...
// Add the key to the key ring
window.inventory.keyRing.keys.push(sprite);
// Emit item_picked_up event for keys too (for objectives tracking)
// NOTE: Keys currently don't emit events - this is a required fix
if (window.eventDispatcher) {
window.eventDispatcher.emit(`item_picked_up:${sprite.scenarioData.type}`, {
itemType: sprite.scenarioData.type,
itemName: sprite.scenarioData.name,
roomId: window.currentPlayerRoom,
isKey: true
});
// Also emit specific key_id event if available
const keyId = sprite.scenarioData?.key_id || sprite.key_id;
if (keyId) {
window.eventDispatcher.emit(`item_picked_up:key:${keyId}`, {
itemType: 'key',
keyId: keyId,
itemName: sprite.scenarioData.name,
roomId: window.currentPlayerRoom
});
}
}
// ... rest of existing code ...
// Emit item_picked_up event for keys (matching regular item pickup event format)
if (window.eventDispatcher) {
window.eventDispatcher.emit(`item_picked_up:key`, {
itemType: 'key',
itemName: sprite.scenarioData?.name || 'Unknown Key',
keyId: keyId,
roomId: window.currentPlayerRoom
});
}
```
@@ -1364,18 +1322,18 @@ function addKeyToInventory(sprite) {
## Implementation TODO List
### Phase 0: Prerequisites (Do First)
- [ ] 0.1 Add key pickup events to `inventory.js` `addKeyToInventory()` function
- [ ] 0.2 Verify `item_unlocked` event name in unlock-system.js (currently line 587)
- [ ] 0.3 Verify `door_unlocked` event provides `connectedRoom` property
### Phase 0: Prerequisites ✅ COMPLETED
- [x] 0.1 Verify door_unlocked events exist - Already emitted from `unlock-system.js:560`
- [x] 0.2 Add key pickup events to `inventory.js` - Implemented
- [x] 0.3 Verify `item_unlocked` event name - Confirmed (line 587, 621)
- [x] 0.4 Add `objectivesState` to server bootstrap - Implemented in `games_controller.rb`
### Phase 1: Core Infrastructure (Foundation)
- [ ] 1.1 Create database migration for objectives tracking
- [ ] 1.2 Add objective methods to `Game` model
- [ ] 1.3 Create API endpoints with RESTful routes (`/objectives/tasks/:task_id`)
- [ ] 1.4 Update scenario action to include `objectivesState`
- [ ] 1.5 Create `objectives-manager.js` client module
- [ ] 1.6 Add objectives CSS file
- [ ] 1.4 Create `objectives-manager.js` client module
- [ ] 1.5 Add objectives CSS file
### Phase 2: Event Integration
- [ ] 2.1 Subscribe to `item_picked_up:*` wildcard events in ObjectivesManager

View File

@@ -118,9 +118,9 @@ window.debugObjectives.reset();
## Key Gotchas
1. **CRITICAL**: `door_unlocked` events NOT emitted in current codebase - must add to `doors.js`
2. **Event name**: `item_unlocked` NOT `object_unlocked`
3. **Door event**: `door_unlocked` should provide both `roomId` and `connectedRoom` (use `connectedRoom`)
4. **Keys don't emit events**: Need to add event to `addKeyToInventory()`
1. **Event name**: `item_unlocked` NOT `object_unlocked`
2. **Door unlock events**: Emitted from `unlock-system.js:560` (NOT doors.js)
3. **Door event data**: Provides both `roomId` AND `connectedRoom` (use `connectedRoom` for unlock tasks)
4. **Key pickup events**: Now emitted as `item_picked_up:key` from `addKeyToInventory()`
5. **State restoration**: Server passes `objectivesState` in scenario bootstrap
6. **Reconciliation**: Call `reconcileWithGameState()` after init for late-loaded scenarios

View File

@@ -4,11 +4,11 @@ Track implementation progress here. Check off items as completed.
---
## Phase 0: Prerequisites (Do First)
- [ ] 0.1 **CRITICAL**: Add `door_unlocked` event emission to `doors.js` `unlockDoor()` function
- [ ] 0.2 Add key pickup events to `inventory.js` `addKeyToInventory()` function
- [ ] 0.3 Verify `item_unlocked` event name in `unlock-system.js` (line ~587) - ✅ VERIFIED
- [ ] 0.4 Verify `room_entered` events are emitted in `rooms.js`
## Phase 0: Prerequisites (Do First)
- [x] 0.1 **CRITICAL**: Verify `door_unlocked` event emission exists in `unlock-system.js` - ✅ VERIFIED (line 560)
- [x] 0.2 Add key pickup events to `inventory.js` `addKeyToInventory()` function - ✅ IMPLEMENTED
- [x] 0.3 Verify `item_unlocked` event name in `unlock-system.js` (line ~587) - ✅ VERIFIED
- [x] 0.4 Add `objectivesState` to server bootstrap in `games_controller.rb` - ✅ IMPLEMENTED
## Phase 1: Core Infrastructure ⬜
- [ ] 1.1 Create database migration `db/migrate/XXXXXX_add_objectives_to_games.rb`
@@ -121,7 +121,7 @@ _Add implementation notes, blockers, or decisions here:_
| Phase | Status | Completed |
|-------|--------|-----------|
| Phase 0: Prerequisites | | 0/4 |
| Phase 0: Prerequisites | | 4/4 |
| Phase 1: Core Infrastructure | ⬜ | 0/7 |
| Phase 2: Event Integration | ⬜ | 0/6 |
| Phase 3: UI Implementation | ⬜ | 0/7 |
@@ -131,4 +131,4 @@ _Add implementation notes, blockers, or decisions here:_
| Phase 7: Reconciliation | ⬜ | 0/6 |
| Phase 8: Testing | ⬜ | 0/13 |
| Phase 9: Documentation | ⬜ | 0/4 |
| **Total** | **⬜** | **0/64** |
| **Total** | **⬜** | **4/64** |

View File

@@ -1,114 +1,74 @@
# Corrected Code Snippets - Review 2
All code ready to copy-paste into implementation.
All prerequisites have been implemented. This document now contains reference code for the objectives system implementation.
---
## Critical Fix: Door Unlock Event Emission
## Door Unlock Events - Already Working
### File: `public/break_escape/js/systems/doors.js`
### File: `public/break_escape/js/systems/unlock-system.js`
**Location**: `unlockDoor()` function (around line 585)
**Location**: `unlockTarget()` function (line 560)
**Current Code** (MISSING event emission):
Door unlock events are ALREADY emitted from the central unlock-system.js:
```javascript
function unlockDoor(doorSprite, roomData) {
const props = doorSprite.doorProperties;
console.log(`Unlocking door: ${props.roomId} -> ${props.connectedRoom}`);
// Mark door as unlocked
props.locked = false;
// If roomData was provided from server unlock response, cache it
if (roomData && window.roomDataCache) {
console.log(`📦 Caching room data for ${props.connectedRoom} from unlock response`);
window.roomDataCache.set(props.connectedRoom, roomData);
}
// TODO: Implement unlock animation/effect
// Open the door
openDoor(doorSprite);
}
window.eventDispatcher.emit('door_unlocked', {
roomId: doorProps.roomId,
connectedRoom: doorProps.connectedRoom,
direction: doorProps.direction,
lockType: doorProps.lockType
});
```
**CORRECTED Code** (with event emission):
```javascript
function unlockDoor(doorSprite, roomData) {
const props = doorSprite.doorProperties;
console.log(`Unlocking door: ${props.roomId} -> ${props.connectedRoom}`);
// Mark door as unlocked
props.locked = false;
// If roomData was provided from server unlock response, cache it
if (roomData && window.roomDataCache) {
console.log(`📦 Caching room data for ${props.connectedRoom} from unlock response`);
window.roomDataCache.set(props.connectedRoom, roomData);
}
// Emit door unlocked event for objectives system
if (window.eventDispatcher) {
window.eventDispatcher.emit('door_unlocked', {
roomId: props.roomId,
connectedRoom: props.connectedRoom,
direction: props.direction
});
console.log(`📋 Emitted door_unlocked event: ${props.roomId} -> ${props.connectedRoom}`);
}
// TODO: Implement unlock animation/effect
// Open the door
openDoor(doorSprite);
}
```
**Why This Matters**: Without this event, `unlock_room` type objectives will never auto-complete.
**No changes needed** - the door_unlocked event was always emitted, just from unlock-system.js not doors.js.
---
## Already Documented Fix: Key Pickup Event Emission
## ✅ Key Pickup Events - Now Implemented
### File: `public/break_escape/js/systems/inventory.js`
**Location**: `addKeyToInventory()` function (around line 433)
**Insert AFTER** the line: `window.inventory.keyRing.keys.push(sprite);`
**Location**: `addKeyToInventory()` function
**Implemented code** (now in codebase):
```javascript
// Emit item_picked_up event for keys too (for objectives tracking)
// NOTE: Keys currently don't emit events - this is a required fix
// Emit item_picked_up event for keys (matching regular item pickup event format)
if (window.eventDispatcher) {
window.eventDispatcher.emit(`item_picked_up:${sprite.scenarioData.type}`, {
itemType: sprite.scenarioData.type,
itemName: sprite.scenarioData.name,
roomId: window.currentPlayerRoom,
isKey: true
window.eventDispatcher.emit(`item_picked_up:key`, {
itemType: 'key',
itemName: sprite.scenarioData?.name || 'Unknown Key',
keyId: keyId,
roomId: window.currentPlayerRoom
});
// Also emit specific key_id event if available
const keyId = sprite.scenarioData?.key_id || sprite.key_id;
if (keyId) {
window.eventDispatcher.emit(`item_picked_up:key:${keyId}`, {
itemType: 'key',
keyId: keyId,
itemName: sprite.scenarioData.name,
roomId: window.currentPlayerRoom
});
}
}
```
---
## ✅ Server Bootstrap - Now Implemented
### File: `app/controllers/break_escape/games_controller.rb`
**Location**: `scenario` action
**Implemented code** (now in codebase):
```ruby
# Include objectives state for page reload recovery
# This allows the client to restore completed/progress state
if @game.player_state['objectivesState'].present?
filtered['objectivesState'] = @game.player_state['objectivesState']
end
```
---
## ObjectivesManager Event Listener Setup
### File: `public/break_escape/js/systems/objectives-manager.js`
**Function**: `setupEventListeners()`
**CORRECTED version** with proper door event handling:
**Reference implementation** for the objectives system:
```javascript
/**

View File

@@ -3,23 +3,21 @@
**Date**: November 25, 2025
**Reviewer**: AI Assistant
**Plan Version**: 1.1
**Status**: ✅ APPROVED with minor corrections
**Status**: ✅ APPROVED - All prerequisites implemented
---
## Executive Summary
The implementation plan has been thoroughly reviewed against the current codebase. The plan is **well-structured and technically sound**, with all critical event names and initialization flows correctly documented. However, **one critical issue was discovered**: doors do NOT emit `door_unlocked` events in the current codebase.
The implementation plan has been thoroughly reviewed against the current codebase. The plan is **well-structured and technically sound**. All critical event names and initialization flows are correctly documented.
### Critical Finding
### Phase 0 Prerequisites - COMPLETED ✅
**❌ MISSING: Door unlock events are NOT emitted**
All prerequisite issues have been resolved:
The current codebase in `doors.js` does NOT emit any events when doors are unlocked. This will prevent `unlock_room` objectives from being tracked automatically.
### Recommendation
**REQUIRED FIX**: Add event emission to `doors.js` in the `unlockDoor()` function.
1. **✅ Door unlock events** - Already emitted from `unlock-system.js:560` (initial review incorrectly searched only doors.js)
2. **✅ Key pickup events** - Now implemented in `inventory.js:addKeyToInventory()`
3. **✅ Server bootstrap** - Added `objectivesState` to `games_controller.rb` scenario response
---
@@ -30,72 +28,27 @@ The current codebase in `doors.js` does NOT emit any events when doors are unloc
| Event Name | Location | Status |
|------------|----------|--------|
| `item_picked_up:*` | `inventory.js:369-374` | ✅ Correct (wildcard works) |
| `item_picked_up:key` | `inventory.js:addKeyToInventory()` | ✅ NOW IMPLEMENTED |
| `item_unlocked` | `unlock-system.js:587, 621` | ✅ Correct (NOT object_unlocked) |
| `room_entered` | `rooms.js` (via updatePlayerRoom) | ✅ Correct |
| `door_unlocked` | **MISSING** | ❌ NOT EMITTED |
| `door_unlocked` | `unlock-system.js:560` | ✅ Already implemented |
### ❌ CRITICAL: Door Unlock Events Missing
### Door Unlock Events - VERIFIED WORKING
**File**: `public/break_escape/js/systems/doors.js`
**Function**: `unlockDoor()` (lines ~585-600)
**File**: `public/break_escape/js/systems/unlock-system.js`
**Function**: `unlockTarget()` (line 560)
**Current Code** (No event emission):
The door_unlocked event IS emitted from the central unlock-system.js:
```javascript
function unlockDoor(doorSprite, roomData) {
const props = doorSprite.doorProperties;
console.log(`Unlocking door: ${props.roomId} -> ${props.connectedRoom}`);
// Mark door as unlocked
props.locked = false;
// If roomData was provided from server unlock response, cache it
if (roomData && window.roomDataCache) {
console.log(`📦 Caching room data for ${props.connectedRoom} from unlock response`);
window.roomDataCache.set(props.connectedRoom, roomData);
}
// TODO: Implement unlock animation/effect
// Open the door
openDoor(doorSprite);
}
window.eventDispatcher.emit('door_unlocked', {
roomId: doorProps.roomId,
connectedRoom: doorProps.connectedRoom,
direction: doorProps.direction,
lockType: doorProps.lockType
});
```
**Required Fix**:
```javascript
function unlockDoor(doorSprite, roomData) {
const props = doorSprite.doorProperties;
console.log(`Unlocking door: ${props.roomId} -> ${props.connectedRoom}`);
// Mark door as unlocked
props.locked = false;
// If roomData was provided from server unlock response, cache it
if (roomData && window.roomDataCache) {
console.log(`📦 Caching room data for ${props.connectedRoom} from unlock response`);
window.roomDataCache.set(props.connectedRoom, roomData);
}
// Emit door unlocked event for objectives system
if (window.eventDispatcher) {
window.eventDispatcher.emit('door_unlocked', {
roomId: props.roomId,
connectedRoom: props.connectedRoom,
direction: props.direction
});
console.log(`📋 Emitted door_unlocked event: ${props.roomId} -> ${props.connectedRoom}`);
}
// TODO: Implement unlock animation/effect
// Open the door
openDoor(doorSprite);
}
```
**Impact**: Without this fix, `unlock_room` type objectives will NOT auto-complete when players unlock doors.
### ✅ CORRECT: Key Items Do NOT Emit Events
### ✅ IMPLEMENTED: Key Pickup Events
**File**: `public/break_escape/js/systems/inventory.js`
**Function**: `addKeyToInventory()` (lines 410-465)

View File

@@ -440,6 +440,16 @@ function addKeyToInventory(sprite) {
lockType: sprite.scenarioData?.lockType
});
// Emit item_picked_up event for keys (matching regular item pickup event format)
if (window.eventDispatcher) {
window.eventDispatcher.emit(`item_picked_up:key`, {
itemType: 'key',
itemName: sprite.scenarioData?.name || 'Unknown Key',
keyId: keyId,
roomId: window.currentPlayerRoom
});
}
// Update or create the key ring display
updateKeyRingDisplay();