# 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.
