# 玩法模组

> 方案不只是一个替你打的 AI，也可以是一套规则：你自己在游戏窗口里玩，模组负责布置开局、刷怪、给奖励、在屏幕上给你按钮和选项卡、判胜负。继承 openwar3.Mod，一个文件就是一套玩法。

来源: https://war3ai.com/docs/mods/

[AI 方案](https://war3ai.com/docs/schemes/) 有两种：`kind: bot` 是一个 AI，替你打；`kind: mod` 是**一套规则** —— 你自己在游戏窗口里玩，模组出题：开局怎么布置、按时间或事件刷怪、给什么奖励、屏幕上给你哪些按钮和选项卡、什么时候算赢。

模组用到的全是现成的能力：[界面与输入](https://war3ai.com/docs/ui-input/)（能点的按钮、卡片、热键、点地面）、[画板](https://war3ai.com/docs/canvas/)（面板、进度条、路线）、[JASS 通道](https://war3ai.com/docs/jass/)（刷单位、改属性、给物品）、事件流（死亡、升级、放技能、聊天）。

## 两个示例

在远见的「AI 方案」→「内置」里就能选：

| 模组 | 玩法 | 用到的能力 |
|---|---|---|
| **英雄肉鸽** `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/docs/schemes/)。模组也是代码，别人的模组第一次运行前同样要确认信任。

## 实测

2026-09-25，演练实例上：

- **英雄肉鸽**：第一波刷出来，右上角面板在走；把英雄提到 3 级 → 屏幕中间弹出卡片、游戏时钟停住；点两次卡片 → 两次强化生效（力量 22 → 27），时钟恢复。
- **无尽守城**：面板、地上的路线、按钮都在；F8 + 点地面 → 主城边上多了一座防御塔；这一波还没清完就点按钮 → 提示「这一波还没清完」。

## 边界

- **只能单人局**：刷单位、改属性走 JASS 通道，多人局里会不同步。这是锁步模型决定的，多人玩法要等同步通道（见 [路线图](https://war3ai.com/roadmap/)）。
- 模组看全图 —— 它是出题的，不是玩家。
- 对战图里的电脑对手只是被「压住」，没有被移除（移除会触发对战规则的胜利判定）。
