Added real timetable support
This commit is contained in:
@@ -347,6 +347,11 @@ class OpenTTDAdminClient:
|
||||
self.on_console = None
|
||||
self.on_gamescript = None
|
||||
|
||||
# GameScript request/response correlation
|
||||
self._gs_request_id = 0
|
||||
self._gs_futures = {}
|
||||
self._gs_subscribed = False
|
||||
|
||||
async def connect(self, admin_password="", secure=False):
|
||||
"""Connect to the admin port and initiate handshake."""
|
||||
self._admin_password = admin_password
|
||||
@@ -376,6 +381,10 @@ class OpenTTDAdminClient:
|
||||
def disconnect(self, source):
|
||||
"""Library callback for when connection is lost."""
|
||||
self.log.info("Admin disconnected.")
|
||||
for fut in self._gs_futures.values():
|
||||
if not fut.done():
|
||||
fut.set_exception(ConnectionError("admin disconnected"))
|
||||
self._gs_futures.clear()
|
||||
self.shutdown_event.set()
|
||||
|
||||
async def quit(self):
|
||||
@@ -445,6 +454,39 @@ class OpenTTDAdminClient:
|
||||
payload["company_id"] = company_id
|
||||
await self.send_gamescript(payload)
|
||||
|
||||
async def get_timetable(self, vehicle_id, timeout=5.0):
|
||||
"""Fetch an authoritative timetable snapshot for a vehicle via the AdminBridge GameScript.
|
||||
|
||||
Unlike the game client's passive observer, this queries the real game state: it works for
|
||||
timetables set before this client connected and reflects the actual (not requested) values.
|
||||
Auto-subscribes to Gamescript updates on first use; if you manage update frequencies
|
||||
yourself, ensure update_frequency(Gamescript, Automatic) is active before calling.
|
||||
|
||||
Returns a dict with vehicle-level keys (lateness, start_tick, current_order_time,
|
||||
total_duration) and an "orders" list of per-order dicts (position, wait_time, travel_time,
|
||||
wait_timetabled, travel_timetabled, wait_fixed, travel_fixed, leave_type, max_speed).
|
||||
|
||||
Raises asyncio.TimeoutError if no reply arrives (e.g. game paused, GS not loaded),
|
||||
ValueError on a GameScript-reported error (invalid_vehicle, response_too_large), and
|
||||
ConnectionError if the admin connection drops while waiting.
|
||||
"""
|
||||
from .protocol import AdminUpdateType, AdminUpdateFrequency
|
||||
if not self._gs_subscribed:
|
||||
await self.update_frequency(AdminUpdateType.Gamescript, AdminUpdateFrequency.Automatic)
|
||||
self._gs_subscribed = True
|
||||
self._gs_request_id += 1
|
||||
rid = self._gs_request_id
|
||||
fut = asyncio.get_running_loop().create_future()
|
||||
self._gs_futures[rid] = fut
|
||||
try:
|
||||
await self.send_gamescript({"command": "get_timetable", "vehicle_id": vehicle_id, "request_id": rid})
|
||||
data = await asyncio.wait_for(fut, timeout)
|
||||
finally:
|
||||
self._gs_futures.pop(rid, None)
|
||||
if "error" in data:
|
||||
raise ValueError(f"get_timetable({vehicle_id}): {data['error']}")
|
||||
return data
|
||||
|
||||
async def send_gamescript(self, json_data):
|
||||
"""Send a JSON string to the GameScript."""
|
||||
import json
|
||||
@@ -540,10 +582,17 @@ class OpenTTDAdminClient:
|
||||
self.log.info(f"Admin: Company {kwargs.get('company_id')} Stats: Vehicles={kwargs.get('vehicles')}, Stations={kwargs.get('stations')}")
|
||||
|
||||
async def receive_ServerGamescript(self, source, **kwargs):
|
||||
data = kwargs.get('data')
|
||||
if isinstance(data, dict):
|
||||
fut = self._gs_futures.get(data.get('request_id'))
|
||||
if fut is not None:
|
||||
if not fut.done():
|
||||
fut.set_result(data)
|
||||
return
|
||||
if self.on_gamescript:
|
||||
self.on_gamescript(kwargs.get('data'))
|
||||
self.on_gamescript(data)
|
||||
else:
|
||||
self.log.info(f"GAMESCRIPT: {kwargs.get('data')}")
|
||||
self.log.info(f"GAMESCRIPT: {data}")
|
||||
|
||||
async def receive_ServerDate(self, source, **kwargs): pass
|
||||
async def receive_ServerFull(self, source, **kwargs): await self.quit()
|
||||
|
||||
Reference in New Issue
Block a user