haxball 1.3
A powerful library for interacting with the Haxball Headless API
License: MIT
Requires Python: >=3.10
Keywords: haxball, headless, bot, room, game

# haxball.py

A powerful Python library for interacting with the HaxBall Headless API.

[![Python Version](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

## Features

- Async/await based API
- **Pluggable WebRTC backend system** (aiortc default, extensible)
- Event-driven architecture
- Type hints throughout
- Proxy support (HTTP + SOCKS5)
- Complete HaxBall Headless API implementation
- High-performance buffer pooling

## haxball.py vs haxball.js

[haxball.js](https://github.com/mertushka/haxball.js) ile karşılaştırma:

| Feature | haxball.py | haxball.js |
|---------|:----------:|:----------:|
| **Language** | Python 3.10+ | Node.js 18+ |
| **Documentation** | Full docs | README only |
| **Example Bots** | 8 | 1 |
| **Proxy Support** | HTTP + SOCKS5 | HTTP only |
| **Custom WebRTC** | Yes (Pluggable) | Yes |
| **Source Code** | Readable | Minified |

📖 **[Full Comparison](docs/comparison.md)** - Code examples and syntax reference

## Installation

```bash
pip install haxball
```

Or install from source:

```bash
git clone https://github.com/haxball-py/haxball.py
cd haxball.py
pip install -e .
```

## Quick Start

```python
import asyncio
from haxball import HaxballClient

async def main():
    client = HaxballClient()

    room = await client.create_room(
        roomName="My Python Room",
        maxPlayers=16,
        public=False,
        token="YOUR_TOKEN_HERE",  # Get from https://www.haxball.com/headlesstoken
    )

    # Event handlers
    room.onRoomLink = lambda link: print(f"Room: {link}")
    room.onPlayerJoin = lambda p: print(f"{p.name} joined!")
    room.onPlayerLeave = lambda p: print(f"{p.name} left!")

    # Chat handler
    def on_chat(player, message):
        print(f"{player.name}: {message}")
        if message == "!hello":
            room.sendChat(f"Hello {player.name}!")
            return False  # Don't broadcast the command
        return True  # Broadcast the message

    room.onPlayerChat = on_chat

    # Room settings
    room.setScoreLimit(5)
    room.setTimeLimit(7)
    room.setDefaultStadium("Classic")

    await room.run_forever()

asyncio.run(main())
```

## API Reference

### HaxballClient

Main client class for creating rooms.

```python
client = HaxballClient()

# Or with specific WebRTC backend
client = HaxballClient(webrtc_backend="aiortc")

# List available backends
print(HaxballClient.available_backends())  # ['aiortc']

# Get geographic location
geo = await client.get_geo()

# Create room
room = await client.create_room(
    roomName="Room Name",
    playerName="Host",
    maxPlayers=16,
    public=False,
    password=None,
    token="TOKEN",
    geo=None,  # Auto-detected
    noPlayer=False,
    proxy="http://host:port",  # Optional
)
```

### Room

#### Properties

- `room_link` - Room URL
- `player_count` - Current player count

#### Player Management

```python
# Get players
player = room.getPlayer(player_id)
players = room.getPlayerList()

# Admin
room.setPlayerAdmin(player_id, True)

# Teams
room.setPlayerTeam(player_id, Team.RED)
room.setPlayerTeam(player_id, Team.BLUE)
room.setPlayerTeam(player_id, Team.SPECTATORS)

# Kick/Ban
room.kickPlayer(player_id, "Reason", ban=False)
room.clearBan(conn_id)
room.clearBans()
```

#### Messaging

```python
# Chat
room.sendChat("Message")
room.sendChat("Private message", targetId=player_id)

# Announcements
room.sendAnnouncement(
    "Message",
    targetId=None,  # All players
    color=0xFFFFFF,
    style="normal",  # normal, bold, italic, small, small-bold, small-italic
    sound=1,
)
```

#### Game Control

```python
room.startGame()
room.stopGame()
room.pauseGame()
room.unpauseGame()

room.setScoreLimit(5)
room.setTimeLimit(7)
room.setTeamsLock(True)
room.setDefaultStadium("Classic")
room.setPassword("secret")
```

#### Disc Properties

```python
# Get disc count
count = room.getDiscCount()

# Get/set ball (disc 0) properties
props = room.getDiscProperties(0)
room.setDiscProperties(0, DiscPropertiesObject(x=100, y=50))

# Get/set player disc properties
props = room.getPlayerDiscProperties(player_id)
room.setPlayerDiscProperties(player_id, DiscPropertiesObject(xspeed=10))
```

#### Event Handlers

```python
room.onRoomLink = lambda link: ...
room.onPlayerJoin = lambda player: ...
room.onPlayerLeave = lambda player: ...
room.onPlayerChat = lambda player, message: ...  # Return False to block
room.onPlayerKicked = lambda player, reason, ban, by_player: ...
room.onPlayerTeamChange = lambda player, by_player: ...
room.onPlayerAdminChange = lambda player, by_player: ...
room.onPlayerBallKick = lambda player: ...
room.onPlayerActivity = lambda player: ...
room.onGameStart = lambda by_player: ...
room.onGameStop = lambda by_player: ...
room.onGamePause = lambda by_player: ...
room.onGameUnpause = lambda by_player: ...
room.onGameTick = lambda: ...
room.onTeamGoal = lambda team: ...
room.onPositionsReset = lambda: ...
room.onTeamVictory = lambda scores: ...
room.onStadiumChange = lambda stadium_name, by_player: ...
```

### Types

```python
from haxball import Team, PlayerObject, ScoresObject, Vector2, CollisionFlags
from haxball import get_backend, list_backends, ConnectionState

# Teams
Team.SPECTATORS  # 0
Team.RED         # 1
Team.BLUE        # 2

# WebRTC Connection States
ConnectionState.NEW
ConnectionState.CONNECTING
ConnectionState.CONNECTED
ConnectionState.DISCONNECTED
ConnectionState.FAILED
ConnectionState.CLOSED

# Player
player = PlayerObject(
    id=1,
    name="Player",
    team=Team.SPECTATORS,
    admin=False,
    position=Vector2(x=0, y=0),
    auth="auth_string",
    conn="connection_id",
)

# Scores
scores = ScoresObject(
    red=3,
    blue=2,
    time=120.5,
    scoreLimit=5,
    timeLimit=7,
)

# Collision Flags
CollisionFlags.BALL   # 1
CollisionFlags.RED    # 2
CollisionFlags.BLUE   # 4
CollisionFlags.WALL   # 32
CollisionFlags.ALL    # 63
```

## Custom Stadium

```python
import json

stadium_json = json.dumps({
    "name": "Custom Stadium",
    "width": 420,
    "height": 200,
    "spawnDistance": 170,
    "goals": [
        {"p0": [-370, -64], "p1": [-370, 64], "team": "red"},
        {"p0": [370, -64], "p1": [370, 64], "team": "blue"},
    ],
})

room.setCustomStadium(stadium_json)
```

## Recording/Replay

```python
# Start recording
room.startRecording()

# ... game plays ...

# Stop and get replay data
replay_data = room.stopRecording()

# Save to file
with open("replay.hbr2", "wb") as f:
    f.write(replay_data)
```

## Proxy Support

HaxBall limits 2 rooms per IP. Use a proxy for multiple rooms:

```python
room = await client.create_room(
    roomName="Room",
    token="TOKEN",
    proxy="http://username:password@host:port",
)
```

Supports HTTP/HTTPS and SOCKS5 proxies.

## Examples

The `examples/` folder contains ready-to-use bot implementations:

| Example | Description |
|---------|-------------|
| `basic_room.py` | Simple room with basic event handlers |
| `admin_bot.py` | Admin commands, AFK detection, muting |
| `auto_balance_bot.py` | Auto-balance teams, captains mode |
| `stats_bot.py` | Track player goals, wins, play time |
| `fun_bot.py` | Fun commands, mini-games, trivia |
| `tournament_bot.py` | Knockout tournament management |
| `physics_bot.py` | Physics manipulation, power-ups |
| `custom_stadium.py` | Custom stadium creation |

Run any example:

```bash
export HAXBALL_TOKEN="your_token_here"
python examples/basic_room.py
```

## Documentation

See the [docs/](docs/) folder for detailed documentation:

- [Full Documentation](docs/README.md) - Complete API reference and guides

## Requirements

- Python 3.10+
- websockets
- aiortc
- aiohttp
- json5
- cryptography
- aiohttp-socks

## License

MIT License - see [LICENSE](LICENSE) for details.

## Credits

- HaxBall by Mario Carbajal