# 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>({}) ``` - 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.