8.0 KiB
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 identifiercooldown_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_idtolocation_idby searching through world locations - Broadcasts
interactable_readymessage 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:
- Enemy spawn/despawn
- Dropped item decay
- Stamina regeneration
- Combat timers
- Corpse decay
- Status effects processor
- 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_idremove_expired_interactable_cooldowns(): Removes expired cooldowns and returns count
4. Frontend Real-Time Countdown
Location: pwa/src/components/Game.tsx
State Management
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 interactsinteractable_ready: Removes cooldown from state when expired
Live Countdown Timer
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}snext 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:
-
Player A clicks "Search Dumpster" button
-
API receives interaction
- Sets 60-second cooldown in database
- Broadcasts to all players in location:
{ "type": "interactable_cooldown", "data": { "instance_id": "start_point_dumpster", "cooldown_expiry": 1731108178.5, "message": "PlayerA interacted with Dumpster" } }
-
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
- Add cooldown to state:
-
After 60 Seconds - Background Task
- Detects cooldown expired
- Removes from database
- Broadcasts to all players in location:
{ "type": "interactable_ready", "data": { "instance_id": "start_point_dumpster", "message": "Dumpster is ready to use again" } }
-
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.pyline 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
-
api/main.py- Added
timeimport - Updated
/api/game/interactendpoint to broadcast cooldown start
- Added
-
api/database.py- Added
get_expired_interactable_cooldowns() - Added
remove_expired_interactable_cooldowns()
- Added
-
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 acceptworld_locations - Updated
lifespan()in main.py to passLOCATIONS
- Added
Frontend
pwa/src/components/Game.tsx- Added
interactableCooldownsstate - Added
interactable_cooldownWebSocket handler - Added
interactable_readyWebSocket handler - Added live countdown timer effect
- Updated interactable rendering with live countdown display
- Added
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:
- Variable cooldown durations per interactable type
- Cooldown persistence across server restarts (already in DB)
- Sound notification when interactable becomes ready
- Visual effects like pulsing when cooldown expires
- Skill-based cooldown reduction (faster cooldowns for skilled players)
- Multiple interaction types per interactable with separate cooldowns
Deployment
# 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.