Skip to content

cainky/QuakeLiveInterface

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

QuakeLiveInterface

Quake-Live Logo|%20

QuakeLiveInterface is a Python library designed to provide a comprehensive, object-oriented interface to a Quake Live game server. It serves as a real-time Python interface for Quake Live, allowing you to extract game state and send commands.

Python 3.9+ License: GPL v3

What's New

This project includes a custom fork of minqlx with C-level entity introspection:

  • get_entity_info(entity_id) - Direct access to game entities from Python
  • Reliable item tracking - Query item spawn states, positions, and respawn timers
  • Docker-based build system - Compiles minqlx from source with our custom patches

Quick Start

# 1. Clone and install
git clone https://github.com/cainky/QuakeLiveInterface.git
cd QuakeLiveInterface
poetry install

# 2. Start the server stack
docker-compose up -d

# 3. Connect to localhost:27960 in Quake Live

# 4. Verify game state is streaming
poetry run python tools/test_subscribe.py

Parallel Training (4 servers)

# Start 4 independent Quake servers for parallel RL training
docker-compose -f docker-compose.multi.yml up -d

# Each server uses a unique env_id for Redis namespacing:
#   Server 0: ql:0:agent:*, port 27960
#   Server 1: ql:1:agent:*, port 27961
#   Server 2: ql:2:agent:*, port 27962
#   Server 3: ql:3:agent:*, port 27963

How It Works

┌─────────────────┐     ┌─────────────┐     ┌──────────────────┐
│   Quake Live    │────▶│    Redis    │◀────│   Your Code      │
│     Server      │     │   Pub/Sub   │     │                  │
│                 │◀────│             │────▶│ • RL Agents     │
│  minqlx plugin  │     │   ~60Hz     │     │  • Analytics     │
└─────────────────┘     └─────────────┘     │  • Bots          │
        ▲                                   │  • Visualizers   │
        │              Game Commands        │  • Anything!     │
        └───────────────────────────────────└──────────────────┘

A minqlx plugin extracts game state at ~60Hz and publishes to Redis. Subscribe to get real-time data, publish commands to control the game.

Features

  • Real-time Game State: Position, velocity, health, armor, weapons at ~60Hz
  • Entity Introspection: C-level get_entity_info() for reliable item/powerup tracking
  • Bidirectional: Read state AND send movement/attack commands
  • Docker Stack: One command to run Quake Live server + Redis
  • Gymnasium Compatible: Optional RL environment wrapper included
  • 2D Visualizer: Real-time map viewer for debugging agents
  • Multiple Game Modes: FFA, Duel, TDM, CTF

Game State

Subscribe to ql:game:state for real-time JSON:

{
  "agent": {
    "steam_id": 72561195012232678,
    "name": "Snap",
    "health": 100,
    "armor": 50,
    "position": {"x": -328.37, "y": 1615.47, "z": 1276.33},
    "velocity": {"x": -419.45, "y": 65.81, "z": -133.0},
    "view_angles": {"pitch": 0.0, "yaw": 90.0, "roll": 0.0},
    "is_alive": true,
    "team": "free"
  },
  "opponents": [
    {
      "steam_id": 72561195012232678,
      "name": "Anarki",
      "health": 125,
      "armor": 100,
      "position": {"x": 512.0, "y": -128.0, "z": 64.0},
      "velocity": {"x": 0.0, "y": 0.0, "z": 0.0},
      "view_angles": {"pitch": 0.0, "yaw": 180.0, "roll": 0.0},
      "is_alive": true,
      "in_fov": true,
      "team": "free"
    }
  ],
  "items": [
    {"name": "item_armor_mega", "position": {"x": 0, "y": 0, "z": 0}, "is_available": true, "time_to_spawn_ms": 0},
    {"name": "item_health_mega", "position": {"x": -384, "y": 640, "z": 32}, "is_available": false, "time_to_spawn_ms": 35000}
  ],
  "game_in_progress": true,
  "game_type": "duel",
  "map_name": "toxicity",
  "agent_kills": 2,
  "agent_deaths": 1,
  "server_time_ms": 1704567890123,
  "game_time_ms": 45000
}

Sending Commands

Publish to ql:agent:command:

import redis
import json

r = redis.Redis('localhost', 6379)

# Movement input (buttons held each frame)
r.publish('ql:agent:command', json.dumps({
    'command': 'input',
    'forward': 1,      # 0 or 1
    'back': 0,
    'left': 0,
    'right': 1,        # Strafe right
    'jump': 1,         # Jump
    'attack': 1,       # Fire
    'pitch_delta': 0,  # Look up/down
    'yaw_delta': 5     # Turn right
}))

Use Cases

Reinforcement Learning

from stable_baselines3 import PPO
from QuakeLiveInterface.env import QuakeLiveEnv

env = QuakeLiveEnv(redis_host='localhost')
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=100000)

Real-time Visualizer

poetry run python visualizer.py

Custom Bot

import redis
import json

r = redis.Redis('localhost', 6379, decode_responses=True)
ps = r.pubsub()
ps.subscribe('ql:game:state')

for msg in ps.listen():
    if msg['type'] == 'message':
        state = json.loads(msg['data'])
        # Your logic here
        if state['agent']['health'] < 50:
            # Run away!
            r.publish('ql:agent:command', json.dumps({
                'command': 'input',
                'forward': 1,
                'yaw_delta': 10
            }))

Analytics / Recording

# Log all game events to a file
with open('game_log.jsonl', 'a') as f:
    for msg in ps.listen():
        if msg['type'] == 'message':
            f.write(msg['data'] + '\n')

Configuration

Environment Variables (docker-compose.yml)

Variable Description
QLX_AGENTSTEAMID Steam ID of the controlled account
QLX_REDISADDRESS Redis hostname (default: redis)
MAP_POOL Map and game type (e.g., toxicity|duel)

Project Structure

QuakeLiveInterface/
├── QuakeLiveInterface/       # Python package
│   ├── env.py               # Gymnasium environment
│   ├── client.py            # Redis client wrapper
│   ├── state.py             # Game state parsing
│   └── rewards.py           # Reward functions for RL
├── minqlx-fork/              # Custom minqlx with get_entity_info()
│   ├── dllmain.c            # Entry point and hooks
│   ├── pyminqlx.c           # Python bindings
│   └── python_embed.c       # Custom C API extensions
├── minqlx-plugin/            # Server-side plugin
│   └── ql_agent_plugin.py   # State extraction & Redis pub/sub
├── agents/                   # Example agents
│   ├── random_agent.py
│   └── rules_based_agent.py
├── tools/
│   ├── test_subscribe.py    # Test Redis connection
│   └── simulator.py         # Offline game state simulator
├── tests/                    # Pytest test suite
├── docker-compose.yml        # Full stack deployment
├── Dockerfile.server         # Multi-stage minqlx build
└── visualizer.py             # 2D map viewer

Redis Channels

Channel Direction Description
ql:game:state Server → Client Game state at ~60Hz
ql:agent:command Client → Server Movement/action commands
ql:admin:command Client → Server Admin commands (restart, record)
ql:game:events Server → Client Damage, frag, death, pickup events

Parallel Environment Namespacing

For parallel training, channels are namespaced with env_id:

Single Env Parallel Env (id=0) Parallel Env (id=1)
ql:game:state ql:0:game:state ql:1:game:state
ql:agent:command ql:0:agent:command ql:1:agent:command

Documentation

Tips & Gotchas

Things learned while building RL agents:

  • Decision rate is ~40Hz (~25ms per step). Plan your action discretization accordingly.
  • Verify actions work first. If forward movement doesn't change position, nothing else matters.
  • Frag detection uses minqlx stats.kills/stats.deaths, not is_alive transitions. Bots respawn too fast between frames.
  • Docker rebuild after plugin changes: docker-compose build --no-cache && docker-compose up -d
  • Verify Redis stream: redis-cli SUBSCRIBE ql:game:state should show ~60Hz JSON.
  • Parallel training uses env_id for Redis key namespacing (ql:0:game:state, ql:1:game:state, etc.)

Troubleshooting

"Connection refused to Redis"

  • Ensure Docker containers are running: docker ps
  • On Windows, use 127.0.0.1:6379:6379 in docker-compose.yml

"Agent not detected"

  • Verify QLX_AGENTSTEAMID matches your Steam account
  • Check: docker exec ql-redis redis-cli GET ql:agent:debug

"No game state"

  • Connect to the server with the correct Steam account first

Testing

poetry run pytest

Credits

License

GPL v3 License - see LICENSE for details.

Sponsor this project

 

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •