Files
echoes-of-the-ash/old/INTERACTABLE_COOLDOWN_SYSTEM.md
2025-11-27 16:27:01 +01:00

243 lines
8.0 KiB
Markdown

# Interactable Cooldown System - Real-Time Updates
## Overview
Implemented a complete real-time notification and live countdown system for interactable cooldowns, similar to the combat turn timer system.
## Implementation Date
November 8, 2025
## Features Implemented
### 1. WebSocket Broadcasts on Interaction
**Location**: `api/main.py` - `/api/game/interact` endpoint
When a player interacts with an object:
- **Broadcast sent to all players in location** with message type `interactable_cooldown`
- Includes:
- `instance_id`: The interactable's unique identifier
- `cooldown_expiry`: Unix timestamp when cooldown expires (60 seconds from interaction)
- `message`: "{username} interacted with {interactable_name}"
- All players in the same location see the cooldown start immediately
### 2. Background Task for Cooldown Expiry
**Location**: `api/background_tasks.py` - `cleanup_interactable_cooldowns()`
- **Runs every 30 seconds** to check for expired cooldowns
- Gets expired cooldowns from database before removal
- Maps `instance_id` to `location_id` by searching through world locations
- **Broadcasts `interactable_ready` message** to all players in affected locations
- Message: "{interactable_name} is ready to use again"
- Added as 7th background task in the system
**Task Count**: System now runs 7 background tasks:
1. Enemy spawn/despawn
2. Dropped item decay
3. Stamina regeneration
4. Combat timers
5. Corpse decay
6. Status effects processor
7. **Interactable cooldown cleanup** ← NEW
### 3. Database Functions
**Location**: `api/database.py`
Added two new functions:
- `get_expired_interactable_cooldowns()`: Returns list of expired cooldowns with instance_id
- `remove_expired_interactable_cooldowns()`: Removes expired cooldowns and returns count
### 4. Frontend Real-Time Countdown
**Location**: `pwa/src/components/Game.tsx`
#### State Management
```typescript
const [interactableCooldowns, setInteractableCooldowns] = useState<Record<string, number>>({})
```
- Stores mapping of `instance_id``expiry_timestamp`
#### WebSocket Handlers
Two new message types:
- **`interactable_cooldown`**: Adds cooldown to state when someone interacts
- **`interactable_ready`**: Removes cooldown from state when expired
#### Live Countdown Timer
```typescript
useEffect(() => {
const timer = setInterval(() => {
const now = Date.now() / 1000
setInteractableCooldowns(prev => {
// Remove expired cooldowns every second
// Updates UI automatically
})
}, 1000)
}, [interactableCooldowns])
```
#### Updated Rendering
- **Live calculation** of remaining seconds: `Math.ceil(cooldownExpiry - now)`
- **Dynamic display**: Shows `⏳{remainingSeconds}s` next to interactable name
- **Live button state**: Disables button when cooldown > 0
- **Live tooltip**: Updates every second with current remaining time
- **Automatic cleanup**: Timer removed when cooldown reaches 0
## Message Flow
### When Player A Interacts with Dumpster:
1. **Player A clicks "Search Dumpster" button**
2. **API receives interaction**
- Sets 60-second cooldown in database
- Broadcasts to all players in location:
```json
{
"type": "interactable_cooldown",
"data": {
"instance_id": "start_point_dumpster",
"cooldown_expiry": 1731108178.5,
"message": "PlayerA interacted with Dumpster"
}
}
```
3. **All Players' Clients (including Player A)**
- Add cooldown to state: `interactableCooldowns["start_point_dumpster"] = 1731108178.5`
- Start live countdown: `⏳60s → ⏳59s → ⏳58s...`
- Disable interaction buttons
- Show message in location log: "PlayerA interacted with Dumpster"
- Refresh game data to update inventory/location state
4. **After 60 Seconds - Background Task**
- Detects cooldown expired
- Removes from database
- Broadcasts to all players in location:
```json
{
"type": "interactable_ready",
"data": {
"instance_id": "start_point_dumpster",
"message": "Dumpster is ready to use again"
}
}
```
5. **All Players' Clients**
- Remove cooldown from state
- Enable interaction buttons
- Show message in location log: "Dumpster is ready to use again"
- Refresh game data
## Key Benefits
### 1. **Real-Time Synchronization**
- All players see cooldowns at the same time
- No stale data from page loads
- Automatic updates without manual refresh
### 2. **Live Countdown Display**
- Updates every second like combat turn timer
- Shows exact time remaining: `⏳5s`
- More engaging than static "on cooldown" message
### 3. **Consistent UX**
- Same pattern as combat turn timer
- Familiar to players
- Professional feel
### 4. **Efficient Updates**
- Targeted broadcasts only to players in affected locations
- No unnecessary network traffic
- Client-side countdown reduces server load
### 5. **Clear Feedback**
- Players know who interacted ("PlayerA interacted with Dumpster")
- Know when it's ready again ("Dumpster is ready to use again")
- See exact time remaining in both tooltip and display
## Technical Details
### Cooldown Duration
- **Default**: 60 seconds (hardcoded in `game_logic.py` line 271)
- Can be modified per-interactable if needed
### Timer Precision
- **Backend check**: Every 30 seconds
- **Frontend update**: Every 1 second
- **Display**: Rounds up to nearest second (shows 1s until truly expired)
### Performance Considerations
- Background task only runs in one worker (file lock)
- Broadcasts only to affected locations (not global)
- Client-side countdown reduces API calls
- Timer automatically cleared when no cooldowns active
## Files Modified
### Backend
1. `api/main.py`
- Added `time` import
- Updated `/api/game/interact` endpoint to broadcast cooldown start
2. `api/database.py`
- Added `get_expired_interactable_cooldowns()`
- Added `remove_expired_interactable_cooldowns()`
3. `api/background_tasks.py`
- Added `cleanup_interactable_cooldowns()` task
- Updated `start_background_tasks()` to include new task (7 total)
- Updated `start_background_tasks()` signature to accept `world_locations`
- Updated `lifespan()` in main.py to pass `LOCATIONS`
### Frontend
1. `pwa/src/components/Game.tsx`
- Added `interactableCooldowns` state
- Added `interactable_cooldown` WebSocket handler
- Added `interactable_ready` WebSocket handler
- Added live countdown timer effect
- Updated interactable rendering with live countdown display
## Testing Checklist
✅ Player interacts with dumpster → All players see cooldown start
✅ Cooldown shows live countdown: `⏳60s → ⏳59s → ...`
✅ Button disabled during cooldown
✅ Tooltip shows remaining time
✅ After 60 seconds, all players see "ready" message
✅ Button re-enabled when cooldown expires
✅ Multiple interactables can have independent cooldowns
✅ Players in different locations don't see each other's cooldowns
✅ Background task runs every 30 seconds (check logs)
✅ 7 background tasks started (check startup logs)
## Future Enhancements
### Potential Improvements:
1. **Variable cooldown durations** per interactable type
2. **Cooldown persistence** across server restarts (already in DB)
3. **Sound notification** when interactable becomes ready
4. **Visual effects** like pulsing when cooldown expires
5. **Skill-based cooldown reduction** (faster cooldowns for skilled players)
6. **Multiple interaction types** per interactable with separate cooldowns
## Deployment
```bash
# Build both containers
docker compose build echoes_of_the_ashes_api echoes_of_the_ashes_pwa
# Deploy
docker compose up -d
# Verify 7 background tasks started
docker compose logs echoes_of_the_ashes_api | grep "background tasks"
# Output: ✅ Started 7 background tasks in this worker
```
## Related Systems
This implementation follows the same pattern as:
- **Combat turn timer** (PvP countdown)
- **Movement cooldown** (travel between locations)
- **Location messages log** (activity feed)
All use WebSocket broadcasts + client-side countdown for smooth real-time experience.