fix(bot): correct combat estimates and enforce client and runner failures

This commit is contained in:
0xrsydn 2026-09-22 12:45:46 +07:00
commit 3f243eaeee
10 changed files with 859 additions and 273 deletions

30
sts2.py
View file

@ -18,6 +18,7 @@ Never precompute an action list.
from __future__ import annotations
import http.client
import json
import urllib.error
import urllib.request
@ -78,7 +79,7 @@ class ActionResult:
return self.status == "ok"
def _request(path: str, payload: dict | None = None, timeout: float = TIMEOUT) -> dict:
def _read(path: str, payload: dict | None = None, timeout: float = TIMEOUT) -> bytes:
url = BASE + path
if payload is None:
request = urllib.request.Request(url, method="GET")
@ -93,17 +94,30 @@ def _request(path: str, payload: dict | None = None, timeout: float = TIMEOUT) -
with urllib.request.urlopen(request, timeout=timeout) as response:
body = response.read()
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", "replace")
try:
detail = exc.read().decode("utf-8", "replace")
except (OSError, http.client.HTTPException):
detail = "could not read the error body"
raise Sts2Error(f"HTTP {exc.code} on {path}: {detail[:300]}") from exc
except urllib.error.URLError as exc:
raise Sts2Error(
f"cannot reach the game on {BASE}. Is STS2 running with the mod loaded? ({exc})"
) from exc
except (OSError, http.client.HTTPException) as exc:
# A failed POST may already have reached the game. Never retry it here.
raise Sts2Error(f"transport failure on {path}: {type(exc).__name__}: {exc}") from exc
return body
def _request(path: str, payload: dict | None = None, timeout: float = TIMEOUT) -> dict:
body = _read(path, payload, timeout)
try:
return json.loads(body)
except json.JSONDecodeError as exc:
data = json.loads(body)
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
raise Sts2Error(f"non-JSON response from {path}: {body[:200]!r}") from exc
if not isinstance(data, dict):
raise Sts2Error(f"expected a JSON object from {path}, got {type(data).__name__}")
return data
# --------------------------------------------------------------------------
@ -121,9 +135,11 @@ def is_up() -> bool:
def state(fmt: str = "json") -> dict:
"""Current game state. fmt: 'json' or 'markdown' (markdown returns text)."""
if fmt == "markdown":
req = urllib.request.Request(f"{BASE}/api/v1/singleplayer?format=markdown")
with urllib.request.urlopen(req, timeout=TIMEOUT) as response:
return {"markdown": response.read().decode("utf-8")}
body = _read("/api/v1/singleplayer?format=markdown")
try:
return {"markdown": body.decode("utf-8")}
except UnicodeDecodeError as exc:
raise Sts2Error("invalid UTF-8 markdown response") from exc
return _request("/api/v1/singleplayer?format=json")