# 閘道

> WebSocket / JSON 閘道：Python SDK 能呼叫的公開 API，JS、C#、Go、Rust、瀏覽器頁面、另一台電腦上的程式都能呼叫。三種角色，附 JS 用戶端和瀏覽器示範頁；延遲是快車道再加約 1 ms。

來源: https://war3ai.com/zh-tw/docs/gateway/

閘道把快車道和推送狀態包裝成 **WebSocket / JSON**。[API 目錄](https://war3ai.com/zh-tw/api/) 裡 Python SDK 能呼叫的公開 API，JS、C#、Go、Rust、瀏覽器頁面、另一台電腦上的程式、LLM 都能呼叫，方法名稱和參數都一樣。延遲是快車道再加約 1 ms。

**最省事：遠見首頁「控制中心」→ 網關 → 啟動**（停止、重新啟動、看日誌、開啟示範頁也在那張卡片上）。命令列：

```bash
python gateway/server.py                 # ws://127.0.0.1:8870/ws（連接埠在 openwar3.json 的 ports.gateway）
python gateway/server.py --open          # 同上，連接埠開始監聽後開啟示範頁 http://127.0.0.1:8870/demo
python gateway/server.py --host 0.0.0.0  # 給區域網路使用：自動要求權杖（bin/gateway/token.txt）
python gateway/server.py --allow-origin http://localhost:5173   # 讓你自己的網頁也能連
```

## 連線和角色

連線位址：`ws://127.0.0.1:8870/ws?inst=9&role=dev`（也可以用 `pid=` 代替 `inst=`；需要權杖時加上 `&token=`）。

| 角色 | 能呼叫 | 適合 |
|---|---|---|
| `dev` | 全部：觀察、命令、遊戲控制、沙盒（JASS 改動世界）、畫介面 | 本機工具、[玩法模組](https://war3ai.com/zh-tw/docs/mods/)、玩伴 |
| `player`（加上 `&player=N`） | 觀察、指揮 N 號玩家的單位、畫介面；**預設為公平模式**，只看得見 N 號玩家視野內的東西（`&fair=0` 關閉） | 替某個玩家上場的 Bot 或 LLM |
| `observer` | 唯讀（執行環境直接拒絕它下的命令） | 觀戰、解說、資料蒐集 |

`player` 拿不到：結束遊戲、調整速度、暫停這類遊戲控制，看得見別人底牌的 `players`、`enemy_ai_plan`，會讓遊戲行程開啟本機檔案的 `canvas.image`，還有 JASS。`resources`、`tech`、`stats` 這類帶玩家編號的查詢只能查自己。

一個連線就是一個工作階段，占用一條快車道（執行環境總共 16 條）。閘道同時最多 12 個工作階段，給 Bot、模組和遠見留幾條。中斷連線時只收掉這個工作階段自己畫的東西和熱鍵，別的程式畫的不動。

## 訊息

連上之後會先收到 `hello`：協定版本、角色、遊戲行程 ID、這個角色能呼叫的方法清單。之後每個請求帶一個 `id`，回應帶同一個 `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", "買藥水"], "kwargs": {"screen": [40, 300]}}
→ {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true}
← {"type": "state", ...}    {"type": "events", ...}        之後持續推送
→ {"id": 5, "op": "overview"}                             一頁局面：資源、各兵種數量、英雄、看得見的敵人、生產
→ {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"}    只開放給 dev
→ {"id": 7, "op": "api"}                                  方法目錄（另有 ping / unsubscribe）
```

- **單位參數**寫成 `{"unit": 位址}`，位址就是單位 JSON 裡的 `addr`；可以附上 `"handle": [lo, hi]`，核對這個位址沒有被別的單位重複使用。
- **方法名稱**就是 Game 的公開方法，另外加上 `ui.*`（button / choice / toast / hotkey / mouse / cursor…）、`canvas.*`（text / panel / bar / image / circle / path / remove…）、`jass.<函式名稱>`（只開放給 dev）。
- 遠端無法傳入回呼函式：點擊、熱鍵從事件推送裡接收，`ui.click` 事件帶有 `key`。見 [介面與輸入](https://war3ai.com/zh-tw/docs/ui-input/)。
- 一個呼叫出錯只會回報這一個（`ok: false` 加上 `error`），連線不會中斷；送來的不是 JSON 也一樣。
- 事件 JSON 的欄位和 [W3P 協定](https://war3ai.com/zh-tw/docs/protocol/) 一致，另外帶有便捷欄位（`spell`、`key`、`text`、`chat`、`button`、`player`、`mods`）。

也可以用 HTTP，適合一次性的呼叫和 curl：`GET /api?role=player` 列出方法目錄，`POST /call` 帶上 `inst`、`role`、`method`、`args`、`kwargs` 呼叫一次。`/call` 會重複使用工作階段：遊戲重開換了行程就自動換新的，閒置 10 分鐘的會收掉。

## 用戶端

**JS**（瀏覽器或 Node 22+，零相依套件）：`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", "點我", { screen: [40, 300] });     // 最後一個一般物件 = 關鍵字參數
ow.on("event:ui.click", (e) => console.log("點了", e.key));
await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true });
```

Node 20 / 21 需要加上 `--experimental-websocket`。完整範例在 `gateway/clients/js/example.mjs`。

**瀏覽器示範頁** `http://127.0.0.1:8870/demo`：局面、我方單位表、在遊戲裡放一個按鈕、事件流，一頁看完。

**其他語言**：任何 WebSocket 函式庫 + 上面的 JSON 就夠了，不必碰共用記憶體。

**LLM**：直接用 [MCP 伺服器](https://war3ai.com/zh-tw/docs/mcp/)，它把常用的事情做成了現成的工具。

## 實測

2026-09-25，連上一局真實對局逐項核對 16/16（閘道 9 項 + MCP 7 項）：交握（dev 角色 121 個方法）、`units('me')`、一頁局面、螢幕提示、放按鈕；訂閱之後在遊戲裡點那個按鈕 → `ui.click` 推送到用戶端；JASS；傳入一個無效單位只回報這一個錯誤；HTTP `/call`（observer 角色）。

JS 用戶端（Node）和瀏覽器示範頁也跑過：網頁上放的按鈕在遊戲裡被點擊，網頁的事件日誌收到 `ui.click`。

## 安全

- 預設只監聽本機 `127.0.0.1`，不需要權杖（和遠見一樣）。`--host` 不是本機位址時自動要求權杖；`--auth` 讓本機也需要權杖。
- **瀏覽器裡別的網站連不上**：瀏覽器發起的連線都帶有來源（`Origin`），閘道只認自己的示範頁和 `--allow-origin` 指定的網址；Python、Node、curl 這類程式不帶來源，照常連線。只監聽本機時還會核對 `Host`，擋住把外部網域解析到本機的攻擊。
- 角色是連線時自行宣告的：在本機模式下它是約定，不是安全邊界。對戰平台要由裁判行程決定誰拿到什麼角色，見 [對戰平台](https://war3ai.com/zh-tw/arena/)。
