Add game event support to the admin client
Continuous Integration / lint-and-security (pull_request) Successful in 41s
Continuous Integration / tests-and-coverage (pull_request) Successful in 28s

Everything on the admin GameScript channel so far has been request/reply.
This adds the other direction: subscribe_events() opens a push stream so a
bot can react to the game instead of polling it, consumed either by awaiting
wait_for_event() or via an on_event callback. Both see every event; an event
goes to at most one waiter, and unclaimed ones sit in a bounded buffer.

Sixteen kinds, from two sources. The engine raises no GameScript event for a
vehicle reaching a stop or cargo arriving, so vehicle_arrive, vehicle_depart
and cargo_waiting are synthesised by the bridge sampling state every
`interval` ticks and diffing against the previous sample -- which means a
stop shorter than the interval is never reported, and the first sample only
establishes a baseline. The rest (crashes, industries, towns, companies,
subsidies) are engine events forwarded verbatim. vehicle_lost,
vehicle_waiting_in_depot and vehicle_unprofitable are deliberately absent:
the engine raises those only for AI companies, so a GameScript can never
observe them.

The server-side half lives in the AdminBridge GameScript, which is not in
this repo -- docker/config is gitignored -- so it has to be updated
separately for any of this to work.

Also repoints the scheduled-dispatch E2E test at a dedicated vehicle
(DISPATCH_VEHICLE_ID). It had been silently skipping because vehicle 7
carries a hand-built annual dispatch schedule, which left eight dispatch
methods unverified end to end while check_public_calls.py reported them
green off static analysis of the call sites.

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
2026-08-31 18:14:40 +02:00
co-authored by Claude
parent 28f247799e
commit d87c779d94
10 changed files with 800 additions and 10 deletions
+21 -2
View File
@@ -7,7 +7,7 @@ import sys
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
from openttd import OpenTTDAdminClient
from openttd.protocol import AdminUpdateFrequency, AdminUpdateType
from openttd.protocol import AdminUpdateFrequency, AdminUpdateType, GameEventType
# Configuration
SERVER_HOST = "127.0.0.1"
@@ -85,7 +85,26 @@ async def run_admin():
except Exception as e: # noqa: BLE001 - demo script: one failed station query should not abort the walk
print(f"!!! station query failed: {e}")
await asyncio.sleep(5)
# Watch the game live: vehicles reaching/leaving stops and cargo piling up at stations.
print("--- Subscribing to game events ---")
try:
accepted = await admin.subscribe_events(
events=[GameEventType.VehicleArrive, GameEventType.VehicleDepart,
GameEventType.CargoWaiting],
interval=5, timeout=10.0)
print(f" subscribed to {accepted['events']} every {accepted['interval']} ticks")
for _ in range(5):
try:
event = await admin.wait_for_event(timeout=15.0)
except asyncio.TimeoutError:
print(" (nothing happened -- is the server paused or idle?)")
break
print(f">>> [EVENT] {event}")
await admin.unsubscribe_events()
except Exception as e: # noqa: BLE001 - demo script: report and carry on to a clean quit
print(f"!!! event subscription failed: {e}")
print("--- Quitting ---")
await admin.quit()