# 玩法模組

> 方案不只是一個替你打的 AI，也可以是一套規則：你自己在遊戲視窗裡玩，模組負責布置開局、刷怪、給獎勵、在螢幕上提供按鈕和選項卡片、判定勝負。繼承 openwar3.Mod，一個檔案就是一套玩法。

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

[AI 方案](https://war3ai.com/zh-tw/docs/schemes/) 有兩種：`kind: bot` 是一個 AI，替你打；`kind: mod` 是**一套規則** —— 你自己在遊戲視窗裡玩，模組出題：開局怎麼布置、依時間或事件刷怪、給什麼獎勵、螢幕上提供哪些按鈕和選項卡片、什麼時候算贏。

模組用到的全是現成的能力：[介面與輸入](https://war3ai.com/zh-tw/docs/ui-input/)（能點的按鈕、卡片、熱鍵、點地面）、[畫板](https://war3ai.com/zh-tw/docs/canvas/)（面板、進度條、路線）、[JASS 通道](https://war3ai.com/zh-tw/docs/jass/)（刷單位、改屬性、給物品）、事件流（死亡、升級、放技能、聊天）。

## 兩個範例

在遠見的「AI 方案」→「內建」裡就能選：

| 模組 | 玩法 | 用到的能力 |
|---|---|---|
| **英雄 Roguelike** `builtin/hero-roguelike` | 你只有一個聖騎士，怪物一波波從四面包圍上來；每升一級，螢幕中間三選一強化（選擇時遊戲暫停）；撐過 10 波就贏，英雄死了就輸 | `g.ui.choice`（可點擊卡片 + 暫停）、`hero.levelup` / `killed` / `spell.cast` 事件、聊天 `-help`、JASS 改英雄屬性、給物品 |
| **無盡守城** `builtin/endless-defense` | 怪物從對面出生點沿著地上的紅線衝向主城；每守住一波就給金幣；點螢幕按鈕或按 F7 提前叫下一波，獎勵 ×1.5；按 F8 再用左鍵點地面，放一座免費箭塔（右鍵取消） | `g.ui.button`、`g.ui.hotkey`、`g.ui.mouse`（擷取地面點擊）、畫板面板 / 進度條 / 路線、JASS 刷怪和加金幣 |

兩個範例各 150 行左右，程式碼在 `brains/examples/mod_hero_roguelike.py` 和 `brains/examples/mod_endless_defense.py`。

```bash
python tools/play.py --bot brains/examples/mod_hero_roguelike.py --inst 9     # 開一局，模組接手，你在遊戲視窗裡玩
```

## 寫一個模組

```python
from openwar3 import Mod

class Survive(Mod):
    name = "survive"

    def on_start(self, g):
        super().on_start(g)                        # 單人局檢查 + 壓住電腦對手
        self.foe = self.wave_player(g)             # 用一個空槽位玩家當「刷怪方」：不和任何人結盟、沒有電腦 AI
        self.every(30, self.wave)                  # 每 30 遊戲秒一波（暫停時不計時）
        g.ui.hotkey("F7", lambda g, ev: self.wave(g))

    def wave(self, g):
        self.spawn_ring(g, self.foe, "ugho", 6, self.home(g), 1400, attack_to=self.home(g))

    def on_event(self, g, ev):
        if ev.kind == "unit.died" and ev.type == "htow":
            self.finish("loss", "主城被推倒了")
```

`Mod` 在 `Bot` 的基礎上多了這些：

| 方法 / 屬性 | 說明 |
|---|---|
| `on_start / on_tick / on_event / on_end` | 和 Bot 一樣；覆寫 `on_start` / `on_tick` 時記得先呼叫 `super()` |
| `every(秒, fn, first=)` / `after(秒, fn)` | 依**遊戲時間**計時的計時器，回呼是 `fn(g)` |
| `finish(result, reason)` | 結束這一局（`'win'` / `'loss'` / `'unknown'`）：執行器下一拍停止，螢幕中間畫出結果面板，方案戰績依它記錄 |
| `wave_player(g)` | 第一個空槽位玩家，拿來當刷怪方 |
| `spawn_ring(g, 玩家, 單位, 個數, 中心, 半徑, attack_to=)` | 在一圈上刷單位，一波幾十個也不卡；回傳 JASS 控制代碼 |
| `alive_of(g, 玩家)` / `attack_move_all(g, 玩家, 點)` | 某個玩家還活著的單位 / 全部攻擊移動過去（每隔幾秒呼叫一次，怪物就會追著走） |
| `home(g)` / `hud(g, 標題, 行)` | 我方主城的位置 / 右上角的資訊面板 |
| `neutralize_ai = True` | 開局把電腦對手壓住：它的單位每 5 秒暫停一次，金幣和木材歸零。對戰地圖裡總有一個電腦，模組自訂規則時不要讓它攪局 |
| `single_player_only = True` | 有其他真人玩家就拒絕執行（改動世界的 JASS 會讓別人不同步） |
| `linger_s = 6` | 分出勝負後，在結果畫面停留幾秒再結束 |

`finish()` 在 `Bot` 上也能用：一般 Bot 也可以自己宣布結束。

## 做成方案、分享

`scheme.json` 裡寫 `"kind": "mod"`，入口檔案裡定義一個 `Mod` 的子類別：

```json
{"id": "survive", "name": "堅守 10 波", "kind": "mod", "entry": "survive.py", "class": "Survive"}
```

模組固定**不走公平模式**（它是出題的裁判，要看全圖、改動世界），也**不依對戰規則判定勝負**（勝負由 `finish` 回報）；說明書裡寫了 `fair` / `judge` 也不會生效。匯出 zip、匯入、信任、戰績都和 Bot 方案完全一樣，見 [AI 方案](https://war3ai.com/zh-tw/docs/schemes/)。模組也是程式碼，別人的模組第一次執行前同樣要確認信任。

## 實測

2026-09-25，測試實例上：

- **英雄 Roguelike**：第一波刷出來，右上角面板在更新；把英雄提升到 3 級 → 螢幕中間跳出卡片、遊戲時鐘停住；點兩次卡片 → 兩次強化生效（力量 22 → 27），時鐘恢復。
- **無盡守城**：面板、地上的路線、按鈕都在；F8 + 點地面 → 主城旁邊多了一座防禦塔；這一波還沒清完就點按鈕 → 提示「這一波還沒清完」。

## 邊界

- **只能用於單人局**：刷單位、改屬性走的是 JASS 通道，在多人局裡會不同步。這是鎖步模型決定的，多人玩法要等同步通道（見 [路線圖](https://war3ai.com/zh-tw/roadmap/)）。
- 模組看得到全圖 —— 它是出題的，不是玩家。
- 對戰地圖裡的電腦對手只是被「壓住」，並沒有被移除（移除會觸發對戰規則的勝利判定）。
