mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
feat: Include objectives state in server response and implement event emissions for door unlocks and key pickups
This commit is contained in:
@@ -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")}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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** |
|
||||
|
||||
@@ -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
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user