Added real timetable support
This commit is contained in:
@@ -1,20 +1,86 @@
|
||||
# Vehicle Timetables: Usage Guide
|
||||
|
||||
This guide covers `OpenTTDClient`'s timetable API: `change_timetable()`, `autofill_timetable()`,
|
||||
`set_timetable_start()`, `set_vehicle_on_time()`, and `get_vehicle_timetable()`. For wire-format
|
||||
internals (packet layout, varuint encoding, command IDs), see [PROTOCOL.md](PROTOCOL.md#vehicle-timetables-game-port-docommands).
|
||||
This guide covers the timetable API: writing via `OpenTTDClient`'s `change_timetable()`,
|
||||
`autofill_timetable()`, `set_timetable_start()`, `set_vehicle_on_time()`, and reading via
|
||||
`OpenTTDAdminClient.get_timetable()` (authoritative, recommended) or `OpenTTDClient`'s
|
||||
`get_vehicle_timetable()` (passive change observer). For wire-format internals (packet layout,
|
||||
varuint encoding, command IDs), see [PROTOCOL.md](PROTOCOL.md#vehicle-timetables-game-port-docommands).
|
||||
This guide is about *how to call these methods and what their parameters mean*, with worked examples.
|
||||
|
||||
## The two things you must know before using this API
|
||||
## Reading: `get_timetable()` — authoritative snapshot (recommended)
|
||||
|
||||
1. **You must join the vehicle's own company.** These are real game commands (`DoCommand`s), not
|
||||
admin-network calls. Join with `client.join_company(company_id=<id>, company_password=...)`
|
||||
using the id of the company that owns the vehicle — spectators (`company_id=255`, the default)
|
||||
are rejected. Sending a command for a vehicle you don't own also fails.
|
||||
2. **There is no "get timetable" query.** OpenTTD's protocol has no command that returns a
|
||||
vehicle's current timetable. `get_vehicle_timetable()` works by passively watching the
|
||||
An **awaitable** method on `OpenTTDAdminClient` (admin port, TCP 3977) that queries the real
|
||||
timetable state from the running game via the AdminBridge GameScript. Unlike the observer
|
||||
approach below, it works for timetables set **before** you connected and returns the game's
|
||||
**actual** state, not the last requested change. No `join_company` needed — it's read-only and
|
||||
sees every company's vehicles.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from openttd import OpenTTDAdminClient
|
||||
|
||||
async def main():
|
||||
admin = OpenTTDAdminClient("127.0.0.1", admin_name="TimetableReader")
|
||||
await admin.connect(admin_password="asd")
|
||||
await admin.joined.wait()
|
||||
|
||||
data = await admin.get_timetable(7)
|
||||
print(data)
|
||||
|
||||
await admin.quit()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
Returns a `dict` shaped like:
|
||||
|
||||
```python
|
||||
{
|
||||
"command": "get_timetable",
|
||||
"vehicle_id": 7,
|
||||
"lateness": 0, # ticks late; negative = running early
|
||||
"start_tick": 1000000, # absolute StateTicks the timetable starts at; 0 = not started
|
||||
"current_order_time": 42, # ticks spent on the current order so far
|
||||
"total_duration": 5400, # full timetable round-trip in ticks; -1 = timetable incomplete
|
||||
"orders": [
|
||||
{
|
||||
"position": 0,
|
||||
"wait_time": 120, # ticks (raw stored value)
|
||||
"travel_time": 300, # ticks (raw stored value)
|
||||
"wait_timetabled": 1, # 1 = wait time explicitly set, 0 = not timetabled
|
||||
"travel_timetabled": 1,
|
||||
"wait_fixed": 0, # 1 = locked against autofill
|
||||
"travel_fixed": 0,
|
||||
"leave_type": 0, # 0 normal, 1 leave early, 2 early if any cargo full, 3 early if all full
|
||||
"max_speed": 65535, # order speed cap; 65535 = no cap
|
||||
},
|
||||
# ... one entry per order position
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Errors: raises `ValueError` when the GameScript reports one (`invalid_vehicle` for a nonexistent
|
||||
vehicle id; `response_too_large` if a very long order list overflows the admin packet limit),
|
||||
`asyncio.TimeoutError` when no reply arrives within `timeout` (default 5.0s — note GameScripts
|
||||
don't run while the game is **paused**, so a paused server always times out), and
|
||||
`ConnectionError` if the admin connection drops mid-query.
|
||||
|
||||
Requirements: the server must run the bundled AdminBridge GameScript **and** the patched JGRPP
|
||||
build with the `GSOrder` timetable getters (both included in this repo's `docker/` setup — see
|
||||
`docker/patches/README.md`). The Gamescript update-frequency subscription it needs is set up
|
||||
automatically on first call.
|
||||
|
||||
## Writing (and the legacy observer): things you must know
|
||||
|
||||
1. **You must join the vehicle's own company to write.** The `change_timetable()` family are real
|
||||
game commands (`DoCommand`s) on the game port, not admin-network calls. Join with
|
||||
`client.join_company(company_id=<id>, company_password=...)` using the id of the company that
|
||||
owns the vehicle — spectators (`company_id=255`, the default) are rejected. Sending a command
|
||||
for a vehicle you don't own also fails.
|
||||
2. **`get_vehicle_timetable()` is a passive observer, not a query.** It watches the
|
||||
`ServerCommand` broadcasts the server sends to every joined client whenever *anyone* changes a
|
||||
timetable. This means:
|
||||
timetable. Use it for live change monitoring on the game port; prefer `get_timetable()` above
|
||||
for reading actual state. Its limitations:
|
||||
- It only reflects changes made **after your client joined**. A vehicle's pre-existing
|
||||
timetable (set before you connected) is invisible until something changes it again.
|
||||
- It reflects what was **requested**, not a confirmed result — the wire protocol has no
|
||||
|
||||
Reference in New Issue
Block a user