Add list_cargo() to the admin client
Continuous Integration / lint-and-security (pull_request) Successful in 20s
Continuous Integration / tests-and-coverage (pull_request) Successful in 25s

The station queries and the cargo events name a cargo only by a bare
numeric id -- get_station()'s and get_station_cargo()'s cargo_id, the
cargo_waiting events, the per-cargo load on vehicle events. Those ids
index the cargo table the loaded NewGRFs build for the running game, so
the same id is coal in one save and grain in another and callers had no
way to resolve them. The AdminBridge GameScript has answered a list_cargo
command all along; no method on OpenTTDAdminClient sent it. This adds the
missing half, so no GameScript change is needed for it to work.

It goes through _gs_query() like get_station(), inheriting the request_id
correlation and the Gamescript auto-subscribe, with one difference worth
knowing: the GS handler defines no error reply for this command, so unlike
the other queries it can time out but can never raise ValueError.

The reply lists cargo in GSCargoList order rather than by id -- against
the dev server the ids come back 10 down to 0 -- so the docstring and
PROTOCOL.md both warn to index the list by cargo_id and not by position.

Also teaches the main_admin.py demo to resolve the labels before printing
a station's cargo, which is what the bare ids in its output were asking
for all along.

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
2026-08-31 18:33:05 +02:00
co-authored by Claude
parent 90a07392cf
commit ba26b59c40
6 changed files with 114 additions and 2 deletions
+8 -2
View File
@@ -67,17 +67,23 @@ async def run_admin():
if stations and stations[-1]:
sid = stations[-1][0]["id"]
try:
# Cargo ids come from the loaded NewGRFs, so resolve them to labels to print.
labels = {c["cargo_id"]: c["label"]
for c in (await admin.list_cargo(timeout=10.0))["cargo"]}
data = await admin.get_station(sid, timeout=10.0)
print(f"--- Station {sid} ({data.get('name')}) cargo: real-time waiting vs planned ---")
for cargo in data.get("cargo", []):
print(f" cargo {cargo['cargo_id']}: waiting={cargo['waiting']} "
print(f" {labels.get(cargo['cargo_id'], cargo['cargo_id'])}: "
f"waiting={cargo['waiting']} "
f"planned={cargo['planned']} rating={cargo['rating']}")
# Break the first cargo down by source station and by next hop (routing destination).
if data.get("cargo"):
cid = data["cargo"][0]["cargo_id"]
flow = await admin.get_station_cargo(sid, cid, timeout=10.0)
print(f"--- Station {sid} cargo {cid} flow breakdown (station 65535 = none/deleted) ---")
print(f"--- Station {sid} cargo {labels.get(cid, cid)} flow breakdown "
f"(station 65535 = none/deleted) ---")
print(f" waiting by source: {flow['waiting_by_from']}")
print(f" waiting by next hop: {flow['waiting_by_via']}")
print(f" planned by source: {flow['planned_by_from']}")