25953bea063c74972410b676733414d4e932696d
Resolve 51 findings from the I/RUF/BLE/TRY002/S110/PLR0402 rule set: - Sort imports and __all__ (I001, RUF022, PLR0402). The sys.path.insert calls in check_public_calls.py and tests/test_e2e.py still precede the openttd imports that depend on them. - Replace unused unpacked values with _ (RUF059) and annotate the two timetable lookup tables as ClassVar (RUF012). - Narrow the best-effort excepts in OpenTTDClient.quit and OpenTTDAdminClient.quit to (OSError, SocketClosed) and log at debug rather than swallowing silently (BLE001, S110). The test doubles now raise an OSError subclass so they still exercise that branch. - Narrow the gamescript JSON fallback to json.JSONDecodeError. The broad catch in receive_packet keeps a noqa: it guards untrusted wire data and must degrade to a no-op packet instead of killing the connection. - Use contextlib.suppress instead of try/except/pass in tests. ruff check . is clean, 102 tests pass, coverage stays at 100%. Co-Authored-By: Claude <[email protected]>
OpenTTD Python Client
A high-performance, Object-Oriented Python client for OpenTTD servers, specifically optimized for JGR Patch Pack (JGRPP). This client handles the modern secure handshake, including X25519 PAKE authentication and AEAD stream encryption.
🚀 Features
- Secure Authentication: Full implementation of X25519 PAKE (Password-Authenticated Key Exchange).
- Stream Encryption: Automatic XChaCha20-Poly1305 authenticated encryption for all game traffic.
- Modular Design: Separates low-level binary protocol handling from high-level game logic.
- State Management: Handles the full join sequence including Map download and synchronization.
- Comprehensive Testing: Robustly tested with unit, logic, and E2E tests (including 100% coverage for unit/logic tests).
- Vehicle Listing: Query vehicle data via the Admin GameScript channel with
list_vehicles(). - Vehicle Timetables: Read and modify a vehicle's timetable (
change_timetable(),autofill_timetable(),set_timetable_start(),set_vehicle_on_time(),get_vehicle_timetable()) via real game-protocol commands. - Order Editing: Add and remove a vehicle's orders (
add_order()inserts a "go to station" stop,remove_order()deletes one) via real game-protocol commands. - Scheduled Dispatch (JGRPP): Edit a vehicle's dispatch schedules and departure slots over the game port (
add_dispatch_schedule(),add_dispatch_slot(),set_dispatch_duration(),set_scheduled_dispatch(), and more), and read them back authoritatively withOpenTTDAdminClient.get_dispatch()(via a patched GameScript API + the AdminBridge GS). - Authoritative Timetable Reads:
OpenTTDAdminClient.get_timetable()fetches the real, current timetable of any vehicle from the running game (via a patched GameScript API + the AdminBridge GS) — no company join needed, works for timetables set before connecting. - Station Listing: Enumerate stations via the Admin GameScript channel with
list_stations(). - Station Cargo Snapshots:
OpenTTDAdminClient.get_station()returns a station's live per-cargo state from the running game — both the real-time amount waiting and the planned flow through the cargodist link graph — over the AdminBridge GS (stock GameScript API, no server patch needed). - Cargo Flow Breakdown:
OpenTTDAdminClient.get_station_cargo()breaks one cargo type down by source station and next hop (routing destination) for both waiting (real-time) and planned amounts, with optionalfrom_station/via_stationfilters.
🛠 Setup
Prerequisites
- Python 3.11+
- A running OpenTTD server (preferably JGRPP)
Installation
- Create and activate a virtual environment:
python3 -m venv venv source venv/bin/activate # Linux/macOS - Install dependencies:
pip install openttd-protocol pymonocypher
📖 Usage
Running the default client
The main.py script is configured to join the local server and the company "Én transport".
python3 main.py [Username] [CompanyID]
Example:
python3 main.py MyBot 0
Module Integration
You can use the openttd package in your own projects:
from openttd import OpenTTDClient
client = OpenTTDClient(host="127.0.0.1", username="BotName")
await client.connect(server_password="asd")
await client.join_company(company_id=0, company_password="asd123")
await client.joined.wait()
# Your logic here...
📂 Project Structure
main.py: Main entry point and usage example.lib/openttd/: Core package containing the protocol and client logic.docs/: Extensive documentation on architecture, protocol, and contributing.tests/: Comprehensive test suite (Logic, Protocol, E2E).
🧪 Testing
We maintain high test coverage. To run normal (non-E2E) tests:
pytest -m "not e2e"
For detailed instructions on E2E testing and coverage reports, see the Testing Guide.
📜 Documentation
Languages
Python
86.6%
Squirrel
12.7%
Dockerfile
0.7%