# Gateway

> WebSocket-/JSON-Gateway: Die öffentlichen APIs, die das Python-SDK aufrufen kann, stehen auch JS, C#, Go, Rust, Browserseiten und Programmen auf anderen Rechnern offen. Drei Rollen, mit JS-Client und Demoseite für den Browser; die Latenz ist die der Schnellspur plus ca. 1 ms.

Quelle: https://war3ai.com/de/docs/gateway/

Das Gateway verpackt Schnellspur und gepushten Zustand als **WebSocket / JSON**. Die öffentlichen APIs aus dem [API-Katalog](https://war3ai.com/de/api/), die das Python-SDK aufrufen kann, stehen damit auch JS, C#, Go, Rust, Browserseiten, Programmen auf anderen Rechnern und LLMs offen – mit denselben Methodennamen und Parametern. Die Latenz ist die der Schnellspur plus ca. 1 ms.

**Am einfachsten: „Kontrollzentrum“ auf der Startseite von Farsight → Gateway → Starten** (Stoppen, Neu starten, Logs ansehen und die Demoseite öffnen gehen ebenfalls über diese Karte). Über die Kommandozeile:

```bash
python gateway/server.py                 # ws://127.0.0.1:8870/ws (Port unter ports.gateway in openwar3.json)
python gateway/server.py --open          # wie oben, öffnet die Demoseite http://127.0.0.1:8870/demo, sobald der Port lauscht
python gateway/server.py --host 0.0.0.0  # fürs LAN: verlangt automatisch ein Token (bin/gateway/token.txt)
python gateway/server.py --allow-origin http://localhost:5173   # damit sich auch deine eigene Webseite verbinden kann
```

## Verbindung und Rollen

Verbindungsadresse: `ws://127.0.0.1:8870/ws?inst=9&role=dev` (statt `inst=` geht auch `pid=`; wird ein Token verlangt, `&token=` anhängen).

| Rolle | Darf aufrufen | Geeignet für |
|---|---|---|
| `dev` | alles: Beobachten, Befehle, Spielsteuerung, Sandbox (Welt per JASS verändern), Oberfläche zeichnen | lokale Tools, [Gameplay-Mods](https://war3ai.com/de/docs/mods/), Begleiter |
| `player` (mit `&player=N`) | Beobachten, Einheiten von Spieler N befehligen, Oberfläche zeichnen; **standardmäßig im Fair-Modus**, sieht nur, was in der Sicht von Spieler N liegt (`&fair=0` schaltet das ab) | Bots oder LLMs, die für einen bestimmten Spieler antreten |
| `observer` | nur lesen (die Runtime lehnt seine Befehle direkt ab) | Zuschauen, Kommentar, Datenerfassung |

`player` bekommt nicht: Spielsteuerung wie Spiel beenden, Tempo ändern oder Pausieren, `players` und `enemy_ai_plan`, die die verdeckten Karten der anderen zeigen, `canvas.image`, das den Spielprozess eine lokale Datei öffnen ließe, und JASS. Abfragen mit Spielernummer wie `resources`, `tech` und `stats` gehen nur für den eigenen Spieler.

Eine Verbindung ist eine Sitzung und belegt eine Schnellspur (die Runtime hat insgesamt 16). Das Gateway erlaubt höchstens 12 Sitzungen gleichzeitig, damit ein paar für Bots, Mods und Farsight frei bleiben. Beim Trennen wird nur entfernt, was diese Sitzung selbst gezeichnet hat, samt ihren Hotkeys; was andere Programme gezeichnet haben, bleibt.

## Nachrichten

Nach dem Verbinden kommt zuerst `hello`: Protokollversion, Rolle, Prozess-ID des Spiels und die Liste der Methoden, die diese Rolle aufrufen darf. Danach trägt jede Anfrage eine `id`, und die Antwort trägt dieselbe `id`:

```json
→ {"id": 1, "op": "call", "method": "units", "args": ["me"]}
← {"id": 1, "ok": true, "result": [{"addr": 123456, "type": "hpea", "owner": 0, "x": -4480, "y": 2120, "hp": 220, "hp_max": 220}]}

→ {"id": 2, "op": "call", "method": "move", "args": [[{"unit": 123456}], 100, 200]}
→ {"id": 3, "op": "call", "method": "ui.button", "args": ["shop", "Trank kaufen"], "kwargs": {"screen": [40, 300]}}
→ {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true}
← {"type": "state", ...}    {"type": "events", ...}        danach laufend gepusht
→ {"id": 5, "op": "overview"}                             Lage auf einer Seite: Ressourcen, Einheitenzahlen, Helden, sichtbare Gegner, Produktion
→ {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"}    nur für dev
→ {"id": 7, "op": "api"}                                  Methodenkatalog (außerdem ping / unsubscribe)
```

- **Einheitenparameter** schreibst du als `{"unit": Adresse}`; die Adresse ist `addr` aus dem Einheiten-JSON. Optional mit `"handle": [lo, hi]`, um zu prüfen, dass die Adresse nicht von einer anderen Einheit wiederverwendet wurde.
- **Methodennamen** sind die öffentlichen Methoden von Game, dazu `ui.*` (button / choice / toast / hotkey / mouse / cursor …), `canvas.*` (text / panel / bar / image / circle / path / remove …) und `jass.<Funktionsname>` (nur für dev).
- Callback-Funktionen lassen sich von entfernt nicht übergeben: Klicks und Hotkeys kommen über die Event-Pushes, das `ui.click`-Event trägt den `key`. Siehe [Oberfläche & Eingabe](https://war3ai.com/de/docs/ui-input/).
- Schlägt ein Aufruf fehl, betrifft die Antwort nur diesen einen (`ok: false` plus `error`); die Verbindung bleibt bestehen. Das gilt auch, wenn das Gesendete kein JSON ist.
- Die Felder im Event-JSON entsprechen dem [W3P-Protokoll](https://war3ai.com/de/docs/protocol/), dazu kommen Komfortfelder (`spell`, `key`, `text`, `chat`, `button`, `player`, `mods`).

HTTP geht auch, praktisch für einzelne Aufrufe und curl: `GET /api?role=player` listet den Methodenkatalog, `POST /call` mit `inst`, `role`, `method`, `args`, `kwargs` ruft einmal auf. `/call` verwendet Sitzungen wieder: Startet das Spiel neu und läuft in einem neuen Prozess, wird automatisch eine neue Sitzung geöffnet; Sitzungen, die 10 Minuten untätig waren, werden geschlossen.

## Clients

**JS** (Browser oder Node 22+, ohne Abhängigkeiten): `gateway/clients/js/openwar3.mjs`

```js
import { OpenWar3, unit } from "./openwar3.mjs";

const ow = new OpenWar3("ws://127.0.0.1:8870/ws", { inst: 9, role: "dev" });
await ow.connect();
const mine = await ow.api.units("me");
await ow.api.move(mine.slice(0, 3).map(unit), 100, 200);
await ow.api.ui.button("hi", "Klick mich", { screen: [40, 300] });   // letztes einfaches Objekt = Keyword-Argumente
ow.on("event:ui.click", (e) => console.log("Geklickt:", e.key));
await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true });
```

Node 20 / 21 braucht `--experimental-websocket`. Ein vollständiges Beispiel liegt in `gateway/clients/js/example.mjs`.

**Demoseite für den Browser** `http://127.0.0.1:8870/demo`: Lage, Tabelle der eigenen Einheiten, einen Button ins Spiel setzen, Event-Stream – alles auf einer Seite.

**Andere Sprachen**: Eine beliebige WebSocket-Bibliothek + das JSON oben genügt; Shared Memory musst du nicht anfassen.

**LLMs**: Nimm direkt den [MCP-Server](https://war3ai.com/de/docs/mcp/) – er stellt die häufigen Aufgaben als fertige Tools bereit.

## Gemessen

2026-09-25, Punkt für Punkt an einer echten Partie geprüft: 16/16 (Gateway 9 + MCP 7): Handshake (Rolle dev mit 121 Methoden), `units('me')`, Lage auf einer Seite, Bildschirmhinweis, Button setzen; nach dem Abonnieren den Button im Spiel angeklickt → `ui.click` wird zum Client gepusht; JASS; eine ungültige Einheit führt nur bei diesem einen Aufruf zu einem Fehler; HTTP `/call` (Rolle observer).

Auch der JS-Client (Node) und die Demoseite wurden getestet: Der von der Webseite gesetzte Button wurde im Spiel angeklickt, und das Event-Log der Webseite hat `ui.click` empfangen.

## Sicherheit

- Standardmäßig lauscht es nur lokal auf `127.0.0.1`, ohne Token (wie Farsight). Ist `--host` keine lokale Adresse, wird automatisch ein Token verlangt; `--auth` verlangt es auch lokal.
- **Andere Websites im Browser kommen nicht rein**: Verbindungen aus dem Browser tragen immer eine Herkunft (`Origin`); das Gateway akzeptiert nur seine eigene Demoseite und die mit `--allow-origin` angegebenen Adressen. Programme wie Python, Node oder curl senden keine Herkunft und verbinden sich ganz normal. Lauscht es nur lokal, prüft es zusätzlich `Host` und blockt so Angriffe, die eine externe Domain auf den lokalen Rechner auflösen lassen.
- Die Rolle deklariert der Client beim Verbinden selbst: Im lokalen Modus ist sie eine Konvention, keine Sicherheitsgrenze. In der Arena muss der Schiedsrichter-Prozess entscheiden, wer welche Rolle bekommt, siehe [Arena](https://war3ai.com/de/arena/).
