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

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

const [interactableCooldowns, setInteractableCooldowns] = useState<Record<string, number>>({})
  • Stores mapping of instance_idexpiry_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

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:
      {
        "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:
      {
        "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

# 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

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.