脚本操作框架

调用现成的函数接口, 方便地实现脚本操作.

已不建议使用 pyvz, 推荐功能更全精度更高的 AvZ.

pyvz v4 相比 v3 的主要变化:
删除了日志输出函数
删除了窗口置顶函数
删除了倒计时阻塞函数
删除了暂停恢复函数
删除了读内存随机选炮函数
删除了铲种炮函数
删除了女仆秘籍函数线程
删除了坚果修复函数线程
删除了一些读内存获取信息的函数
删除了一些挂机相关函数
删除了一些修改器功能函数
删除了鼠标移动位置再点击的功能
删除了一些内部优化, 现在精度更低了
发炮操作不再支持额外延时参数
自动找炮不再支持按冷却时间排序
部分函数名称修改
内部重整为 core extra 两大模块
需求 Python 版本下调至 3.4
向下兼容至 XP 操作系统
提供离线使用版本
示例教程文档翻新

项目简介

蟒蛇大战僵尸 (Python vs. Zombies) 是一个用 Python 语言写成的用于植物大战僵尸的 TAS 框架. 可简称为 pvz.py 或者 pvz 或者 pyvz.

TAS (tool-assisted speedrun/superplay) 即 “工具辅助竞速”, 指在不变动游戏本体的前提下, 利用代码工具模拟手动操作, 以较高的精度来完成游戏. 属于利用外挂来增强挑战性和趣味性的高级玩法.

2018 年之前这个游戏的 TAS 操作主要借助按键精灵来完成, 因此也被称作 “键控”. (注意不是 “键盘控制”.)

所谓框架, 即是使用底层函数封装构建的, 供使用者调用的一系列现成的基础接口. 对这个游戏来说所提供的就是 选卡/点炮/用卡/存冰/捡钱 等操作的函数调用. 使用本项目可以免去配置构建半自动化脚本所需基础框架的繁重工作, 用户无需了解其中的实现细节和复杂逻辑, 只要使用暴露出来的接口函数即可, 从而可以将精力集中在轨道主流程处理上, 有效快速地完成脚本编写和视频制作.

本项目只支持在 Windows XP SP3 以及更高版本的系统上面使用, 目前需求的 Python 版本 >= 3.4.0, 支持植物大战僵尸一代原始版 (1.0.0.1051 1.2.0.1065).

编程语言

点击下载 Python 安装包

点击下载 VC 运行库

本项目依赖于 Python3. 对于 XP 和 Vista 系统用户请务必使用 3.4.4 版本, win7 及以上系统用户则可以用较新版 (例如 3.8.x).

根据自己的操作系统位数选择合适的版本, 32 位系统选择 “x86”, 64 位系统选择 “x86-64” / “AMD64”.

32 位软件能正常运行在 32 位和 64 位系统上, 而 64 位软件只能运行于 64 位系统. 如果不清楚自己的操作系统位数, 就选择 32 位的安装包.

Windows 版本的 Python 可执行程序使用 Visual C++ 编译, 需要 VC 运行库的支持. 3.4 版本的 Python 对应的是 VS 2010, 3.5 及以上版本对应的是 VS 2015-2019.

Python 版本选择

安装时记得勾选 “添加到环境变量”. (对于其他的功能如果不确定需不需要就把所有功能的钩全都选上.)

Python 3.7 安装

打开命令提示符运行 python --version 来检查, 安装成功则会输出版本号.

Python 安装检查

脚本运行方法: (假设文件名为 xxx.py)

  • 在文本编辑器/集成开发环境里点击调试运行菜单. (推荐)

  • 如果启用了文件关联, 直接双击脚本文件即可自动调用 Python.

  • 打开 “命令提示符”, 切换到脚本所在目录, 执行 python xxx.py. 1 2 3

一些在线教程:

需要学习了解的内容主要包括:

  • 文件编码 (utf-8)
  • 注释格式 (单行 / 多行)
  • 代码块的缩进层级与空行
  • 调试输出 (print 函数)
  • 导入模块 (import 语句)
  • 变量及其作用域 (global 语句)
  • 数据类型 (数字 / 字符串 / 列表 / 元组)
  • 运算符 (算术 / 比较 / 赋值 / 逻辑 / 成员)
  • 流程控制 (if 语句 / for 语句 / range 函数)
  • 函数的定义与调用 (默认 / 关键字 / 可变参数)
  • 多线程的概念和装饰器的使用方法

框架安装

本项目无需安装, 下载后将自己的脚本 xxxxx.py 和 pvz 文件夹放置在同一位置即可.

xxxxx.py
pvz
├─ __init__.py
├─ core.py
└─ extra.py

蓝奏云下载    密码: python

备用下载地址

另外也可以使用包管理器全局在线安装. 在命令提示符下运行 pip install pvz 来安装, 运行 pip list 来检查已安装的包, 若显示有 pvz 则安装成功. 后续可运行 pip install --upgrade pvz 进行升级. (官方安装源位于境外, 若遇到访问障碍可 更换国内源.)

如果已经在线安装过了并且脚本同文件夹下也有该项目, 脚本会优先选择同文件夹下的包.

代码示例

阵图/示例/视频

本项目部分示例在编写过程中得到了 冰巫师墨舞 提供的帮助和优化.

部分示例文件后期有更新, 可能会出现脚本操作和视频内容不一致的情况.

开发环境

编写代码最好是用专业的文本编辑器, 推荐使用 Visual Studio Code. 不要用记事本!!!

在拓展页面搜索 “Chinese” 安装中文语言包, 安装 “Python” 插件后可使用自动补全和错误检查等功能.

点左下角 “设置” 图标, 在 “颜色主题” 里可安装配置各种语法高亮配色方案.

最佳实践

声明编码为 UTF-8, 同时保存文件的时候也要使用该编码.

# coding=utf-8

导入本项目的基础函数, 几种方式任选其一即可.

# 方式 1
# 常规导入, 调用时需要加上 pvz. 前缀
import pvz
pvz.Pao((2, 9), (5, 9))
pvz.Card("小喷", (3, 9))

# 方式 2
# 导入所有内容, 本教程示例均用此种方式
from pvz import *
Pao((2, 9), (5, 9))
Card("小喷", (3, 9))

# 方式 3
# 只导入需要的内容, 可给函数取别名
from pvz import Pao
from pvz import Card as 用卡
Pao((2, 9), (5, 9))
用卡("小喷", (3, 9))

启用一些常用的修改器功能.

# 1.0.0.1051

WriteMemory("short", 0x00eb, 0x0054eba8)  # 允许后台运行
WriteMemory("int", 0x00679300, 0x0040fced)  # 取消点炮限制

WriteMemory("byte", 0x71, 0x0052dfe8)  # 舞王伴舞转身后不前进
WriteMemory("unsigned short", 0xe990, 0x0041d025)  # 僵尸死后不掉钱
WriteMemory("unsigned short", 0xd231, 0x0041a68d)  # 浓雾透视

WriteMemory("int", 8000, 0x6a9ec0, 0x768, 0x5560)  # 阳光 8000
WriteMemory("int", 0, 0x6a9ec0, 0x82c, 0x28)  # 金钱 0 * 10
WriteMemory("int", int(2020/2), 0x6a9ec0, 0x768, 0x160, 0x6c)  # 完成 2020 面旗帜数
# 1.2.0.1065

WriteMemory("short", 0x00eb, 0x0054edd8)  # 允许后台运行
WriteMemory("int", 0x00679458, 0x0040fcdd)  # 取消点炮限制

WriteMemory("byte", 0x71, 0x0052e338)  # 舞王伴舞转身后不前进
WriteMemory("unsigned short", 0xe990, 0x0041d055)  # 僵尸死后不掉钱
WriteMemory("unsigned short", 0xd231, 0x0041a6ad)  # 浓雾透视

把稍微复杂的操作封装成函数, 需要运行在子线程则用 RunningInThread 装饰.

# 种垫材后铲除
# 在子线程里运行
@RunningInThread
def DianCai():
    Card("阳光菇", (5, 9))
    Card("小喷菇", (6, 9))
    Delay(30)
    Shovel((5, 9))
    Shovel((6, 9))

# 吹 10 次风扇
# 在子线程里运行
@RunningInThread
def BloverThread():
    for _ in range(10):
        Card("三叶草", (1, 1))
        Delay(2700)

修改出怪, 选卡并点击 “Let’s Rock!”, 更新炮列表.

SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"])

SelectCards(["复制冰", "寒冰菇", "咖啡豆", "樱桃", "坚果", "倭瓜", "花盆", "胆小", "阳光", "小喷"])

# UpdatePaoList()

启动 自动收集 自动存冰 等线程.

# 收集钻石和阳光, 间隔 0.15s
AutoCollect([3, 4, 5, 6], 15)

# 往 6-1 5-1 2-1 1-1 累计存 8 个寒冰菇
IceSpots([(6, 1), (5, 1), (2, 1), (1, 1)], 8)

正式编写具体操作.

用一个循环变量 wave 遍历 1~20, 对应每次选卡共 20 波僵尸, 把相同操作的波次写在同一个分支里.

# 左闭右开, 包括 1 不包括 21, 即 1 ~ 20
for wave in range(1, 21):
    Prejudge(-190, wave)
    if wave == 10:
        # 第 10 波操作
        pass
    elif wave == 20:
        # 第 20 波操作
        pass
    elif wave in (1, 3, 5, 7, 9, 12, 14, 16, 18):
        # 第 1/3/5/7/9/12/14/16/18 波操作
        pass
    else:
        # 剩余波次操作
        pass

上面的写法适合有相当多的波次操作完全相同的情况, 不同波次操作几乎都不相同则逐波写出来.

# 第 1 波
Prejudge(-100, 1)
pass

# 第 2 波
Prejudge(-100, 2)
pass

# 第 3 波
Prejudge(-100, 3)
pass

# ...
# ...
# ...

# 第 20 波
Prejudge(-150, 20)
pass

每个具体的操作可分为两部分: 阻塞延时, 执行操作.

以泳池场地第 3 波预判 100cs P 延迟 90cs D 的双边 PD 操作为例, 以下几种写法均可:

# 1
Prejudge(-100, 3)
Pao((2, 9), (5, 9))
Delay(90)
Pao((2, 9), (5, 9))

# 2
Prejudge(-198, 3)
Until(-100)
Pao((2, 9), (5, 9))
Until(-100 + 90)
Pao((2, 9), (5, 9))

# 3
Prejudge(-198, 3)
Until(-100)
Pao([(2, 9), (5, 9)])
Delay(90)
Pao([(2, 9), (5, 9)])

常见问题

功能局限性

本项目使用调用 Win32 API 发送事件的方法来实现模拟按键和鼠标的效果, 使用公认的运行效率较低的 Python 语言来实现各种逻辑, 再考虑到 Windows 非实时操作系统等原因, 可能会出现运行效率低下和操作精度不足的问题, 如果有更高的需求可考虑使用 C++ 语言作为接口的以及汇编注入实现操作的 AvZ 框架.

由于游戏机制问题, 咖啡豆和模仿者寒冰菇的生效时间是不确定的, 直接导致了 100% ICE3 不可能实现.

本项目不支持 “操作乱序” “铲种炮” “非定态” “波长设定” “精准冰三” “女仆秘籍” “植物修补” “捕捉键盘” 等高级功能.

如果坚持使用本项目, 在遇到复杂需求的时候请想办法自己实现. 参考 PE裸奔十六炮 PE最后之作 和 PE半场十二炮 的示例.

在 PE最后之作 中为了实现水路自动高坚果编写了一个略微复杂的函数, PE半场十二炮 中为了实现自动补南瓜同样自定义了一个复杂的函数. 不同的是, 前者采用的全都是公开接口, 后者则是直接调用框架内的私有接口, 如果是对框架源码不够熟悉的话不推荐这么做.

界面缩放

缩放比例

建议在 100% DPI 缩放下使用. 如果修改了系统缩放比例, 本项目会自动获取以调整点击事件的参数.

不过万一出现了获取失误的情况 (表现为实际点击位置会以窗口左上角为原点出现偏移), 可在 pvz/__init__.py 中找到包含 set_dpi_scale 的这行代码手动设置缩放比例.

字符串参数

SelectCards ClickSeed Card SetZombies 函数支持用 卡片/僵尸名称 字符串作为参数.

预判函数的作用

Prejudge 函数在且仅在每波最开始调用一次. 所得到的刷新时机会被后续的 Until 函数所用.

第二个参数设定后续操作的波次, 第一个参数为相对于设定波次刷新时间点的时间差, 用于预判时为负值.

两种延时方式的区别

Sleep 借助于操作系统实现延时, 由于 Windows 不是实时操作系统所以实际睡眠时间会有波动.

Delay 靠读取游戏内部时钟来实现延时, 相比线程睡眠更加精确, 也不用担心游戏中途突然暂停的影响.

操作和波次的对应

理想情况下是把轨道里本波的所有操作都写在本波.

但是某些操作离其生效时间较长, 这个时候应该把操作写在上一波. 比如 预判冰点咖啡豆 预判复制冰 等.

另外某些操作执行的时候已经到了下一波触发刷新的时机了, 则写在下一波. 比如 垫撑杆防跳超前置炮 等.

操作时机计算

Until 的参数为 “与本波刷新时间点的差值”, 该函数需要配合 Prejudge 使用.

刷新前 95: -95.

刷新后 180: 180.

刷新后 444 生效炮发射时机: 444 - 373.

白天点下一波预判冰的咖啡豆时机, (假设为加速波) 波长 601, 使冰在下一波刷新后 20 生效, 点下咖啡豆到冰生效 298, 可得: 601 + 20 - 298.

夜间种植下一波预判复制冰时机, (假设为减速波) 波长 1350, 使冰在下一波刷新后 5 生效, 冰种植到生效 100, 模仿者变身耗时 320, 可得: 1350 + 5 - 100 - 320.

减速波激活炸时机, 假设波长为 1200, 从加速运算量生效到下一波刷出 200, 玉米炮飞行时间 373, 可得: 1200 - 200 - 373.

白天减速波核蘑菇激活点咖啡时机, 假设波长为 1150, 从加速运算量生效到下一波刷出 200, 核蘑菇被唤醒到生效 100, 咖啡豆种下到完成唤醒 198, 可得: 1150 - 200 - 100 - 198.

严格时间顺序

多个 Until 连用以及与 Sleep Delay 混用时, 需要注意把延时函数和对应操作按照时间顺序 (参数大小) 排列.

在下面的例子中, 本意是在冰波激活炸之后尾炸小鬼再点下一波的预判冰, 由于尾炸延时较长, 执行尾炸发炮操作时已经超过了本应点预判冰的时机, 脚本逻辑出错.

只要把每个时机的具体数字算出来就能发现问题所在了, 将时机和对应的操作按照大小顺序排序即可解决.

# 错误示范
# 727 < 1077 < 1022  (✗)
Until(1300 - 200 - 373)  # 727
Pao((2, 8.8), (5, 8.8))  # 冰波激活炸
Delay(350)               # 1077
Pao((1, 2.4), (5, 2.4))  # 减速尾炸
Until(1300 + 20 - 298)   # 1022
Coffee()                 # 20cs 预判冰

# 正确示范
# 727 < 1022 < 1077  (✓)
Until(1300 - 200 - 373)        # 727
Pao((2, 8.8), (5, 8.8))        # 冰波激活炸
Until(1300 + 20 - 298)         # 1022
Coffee()                       # 20cs 预判冰
Until(1300 - 200 - 373 + 350)  # 1077
Pao((1, 2.4), (5, 2.4))        # 减速尾炸

玉米炮粘手

游戏设定: 点炮后 30cs 内落点位置和点炮位置的距离小于 100px 时无法发出.

可以使用 UpdatePaoList() 函数手动调整炮序来规避某些位置的玉米炮不能秒射自己的问题.

既然这个问题总是可以解决的(?), 为了避免调炮序这个繁琐的工作建议直接修改游戏机制.

WriteMemory("int", 0x00679300, 0x0040fced)  # 取消点炮限制

把本来读取 100.00 数值的地方改成读 0.50, 也就意味着只要落点位置和点炮位置不同炮就能即时发出.

自动存冰与点冰函数

Coffee 需要与 IceSpots 配合使用. 为了减少资源占用该函数存冰并不及时.

如果需要更精确的控制存冰时间可参考 StoreIceUseIce 示例自己实现存冰和点冰函数.

# 存冰函数在子线程里运行
# 选卡开场后立即运行
# 存冰位 3-1 3-2 3-3 3-4
# 优先存复制冰, 优先存在永久位
# 每 5001cs 存两个冰, 共存 10 个
@RunningInThread
def StoreIce():
    ice_spots = [(3, 1), (3, 2), (3, 3), (3, 4)]
    for i in range(5):
        MouseLock().acquire()
        SafeClick()
        ClickSeed("复制冰")
        for spot in ice_spots:
            ClickGrid(spot)
        SafeClick()
        ClickSeed("原版冰")
        for spot in ice_spots:
            ClickGrid(spot)
        SafeClick()
        MouseLock().release()
        if i != (5 - 1):
            Delay(5001 + 1)

# 点冰
def UseIce():
    ice_spots = [(3, 1), (3, 2), (3, 3), (3, 4)]
    MouseLock().acquire()
    SafeClick()
    ClickSeed("咖啡豆")
    for spot in reversed(ice_spots):
        ClickGrid(spot)
    SafeClick()
    MouseLock().release()

多线程操作

RunningInThread 的异步功能是靠开新线程实现的, 建议只在精度要求不高 (电脑配置很好) 的情况下使用.

注意这并不是一个普通的函数, 而是一个装饰器. 定义一个函数然后用这个装饰器装饰, 该函数在调用的时候会运行在单独的线程当中.

建议把一些与主线程操作关系不大的操作放在子线程里, 比如 自动存冰 补南瓜 铲种垫材 等等.

内置唯一鼠标锁

本项目为了功能上的方便使用了多线程技术, 而鼠标却只有一个, 不可避免的涉及到操作权的争夺.

为了避免脚本不同操作之间以及键控与手控之间的冲突, 在进行不可中断的多次点击操作的时候, 需要通过内置唯一鼠标锁获取操作权, 操作完毕后再释放.

封装的高级函数 (Card Shovel Pao RoofPao AutoCollect IceSpots Coffee) 内部均有使用鼠标锁, 使用的时候无需担心冲突. 但是在使用基础点击函数 (LeftClick RightClick ButtonClick SafeClick ClickSeed ClickShovel ClickGrid) 构建自定义操作的时候, 为了脚本的可靠性建议加锁.

MouseLock().acquire()
SafeClick()
# 一些点击操作
SafeClick()
MouseLock().release()

跳炮和收尾

第 9/19 波执行完主要操作后的手动收尾需要额外用炮时, 调用 Skip 跳过一定数量的炮数使第 10/20 波自动选择的炮位相应地延后.

if wave in (9, 19):  # 9/19 波收尾预留 4 门炮
    Skip(4)

当然了也可以算好时间写好脚本自动执行收尾操作.

本项目的部分示例中的收尾代码为了省事重复使用了主线操作中的现成代码, 事实上不只是开场可以特化收尾也是可以的.

以 PE垫材二十四炮 为例, 计算波长和冷却后卡着玉米炮的装填时机发射, 把最后一对炮的发射时机提前了, 能够节省下来一轮垫材.

Prejudge(-147, wave)
Pao((2, 9), (5, 9))
Until(-147 + 81)
DianCai()
Until(-147 + 108)
Pao((1, 8), (5, 8))
if wave in (9, 19):  # 9/19 波收尾
    Until(601 - 147)
    Pao((2, 9), (5, 9))
    Until(601 - 147 + 81)
    DianCai()
    Until(601 - 147 + 108)
    Pao((1, 8), (5, 8))
    Until(601 + 601 - 147 - (601 * 6 - 3475))
    Pao((2, 9), (5, 9))
    # Until(601 + 601 - 147 + 81)
    # DianCai()

脚本调试省时

在调试脚本的时候可以把选卡函数注释掉, 手动选卡进入游戏场景然后立即退回主界面, 找到存档位置设置存档只读. 这样每次调试的时候是直接从选完卡后开始, 省去重新选卡和切换画面的时间.

同样可以在积累了一定工作量 (比如上半场前九波完成) 之后存个档, 注释掉前面已完成的代码 (跳过一定炮数并从第 10 波开始) 再继续之后步骤的编写调试. 等到全部完成后再从选卡阶段录制完整的表演视频.

举个栗子:

手动选卡, 开场即退出, 设置存档只读, 编写调试前 9 波.

# 这里注释掉了选卡函数
# 从第 1 波开始操作

# SelectCards(cards)
for wave in range(1, 21):
    pass

第 10 波刷新前取消存档只读, 退回游戏主菜单, 再次设置存档只读, 跳过一定炮数, 编写调试第 10~19(20) 波.

# 这里注释掉了选卡函数
# 按顺序跳过了一定炮数
# 从第 10 波开始操作

# SelectCards(cards)
Skip(x)
for wave in range(10, 21):
    pass

待脚本编写完成后用未选完卡的存档重新开始.

# 这里启用了选卡函数
# 从头开始运行完整的操作

SelectCards(cards)
for wave in range(1, 21):
    pass

时长测量

可在脚本中插入获取时间或者读游戏内部时间戳的代码来测量实际运行时间.

打印输出的开销比读取内存要大, 应该先进行数据获取, 把输出操作放在后面进行.

import time
t0 = time.clock()
Sleep(100)
t1 = time.clock()
print("运行时间: %s 秒." % str(t1 - t0))
t_start = ReadMemory("int", 0x6A9EC0, 0x768, 0x5568)
Sleep(100)
t_end = ReadMemory("int", 0x6A9EC0, 0x768, 0x5568)
print("操作前时间戳: " + str(t_start))
print("操作后时间戳: " + str(t_end))
print("操作耗时(cs): " + str(t_end - t_start))

视频录制完成后可用 PotPlayer 打开, 按 D / F 键逐帧播放, 找到关键帧画面并测量计算时间差.

在下面的例子中, 两张图分别是开场红字刚刚消失那一帧和结尾白字刚刚消失那一帧.

时间差计算

可用两种方法计算这一轮选卡的总时长:
7 * 60 + 45.815 - 13.016 = 452.799 s
(27949 - 780) / 60 ≈ 452.817 s

很显然录制帧率越高测得的数据误差越小, 有条件应该尽量用 60 或者 100 帧来录制视频.

接口函数

本项目发布的时候为了照顾按键精灵用户的习惯公开函数均采用大驼峰命名法, 用户若不习惯可在导入的时候为函数设置别名:

# 其他命名风格
from pvz import Pao as fire_cob
from pvz import Card as use_seed

# 使用中文名
from pvz import Pao as 发炮
from pvz import Card as 用卡

# 兼容旧脚本
from pvz import Skip as SkipPao
from pvz import AutoCollect as StartAutoCollectThread
from pvz import IceSpots as StartAutoFillIceThread

读写内存

ReadMemory(data_type, *address, array=1)

读取内存数据.

@参数 data_type(str): 数据类型, 取自 C/C++ 语言关键字, 可选值 [“char”, “bool”, “unsigned char”, “short”, “unsigned short”, “int”, “unsigned int”, “long”, “unsigned long”, “long long”, “unsigned long long”, “float”, “double”].

@参数 address(int): 地址, 可为多级偏移.

@参数 array(int): 数量. 默认一个, 大于一个时需要显式指定关键字参数.

@返回值 (int/float/bool/tuple): 默认情况下返回单个数值, 获取多个数据则返回一个长度为指定数量的元组.

@示例:

ReadMemory("int", 0x6a9ec0, 0x768, 0x5560)
8000

ReadMemory("byte", 0x0041d7d0, array=3)
(81, 131, 248)

WriteMemory(data_type, values, *address)

写入内存数据.

@参数 data_type(str): 数据类型, 取自 C/C++ 语言关键字, 可选值 [“char”, “bool”, “unsigned char”, “short”, “unsigned short”, “int”, “unsigned int”, “long”, “unsigned long”, “long long”, “unsigned long long”, “float”, “double”].

@参数 values(int/float/bool/list/tuple): 需要写入的数据, 多个数据采用列表或者元组形式.

@参数 address(int): 地址, 可为多级偏移.

@示例:

WriteMemory("int", 8000, 0x6a9ec0, 0x768, 0x5560)

WriteMemory("unsigned char", [0xb0, 0x01, 0xc3], 0x0041d7d0)

模拟鼠标点击

LeftClick(x, y)

鼠标左键单击.

@参数 x(int): 横坐标, 单位像素. 建议范围 [0, 799].

@参数 y(int): 纵坐标, 单位像素. 建议范围 [0, 599].

@示例:

LeftClick(108, 42)  # 左键单击卡槽第一张卡片的位置

RightClick(x, y)

鼠标右键单击.

@参数 x(int): 横坐标, 单位像素. 建议范围 [0, 799].

@参数 y(int): 纵坐标, 单位像素. 建议范围 [0, 599].

@示例:

RightClick(399, 299)  # 右键单击场地中间位置

ButtonClick(x, y)

适用于模仿者按钮和菜单按钮的特殊点击.

调用的时候不要把鼠标光标放在游戏窗口内.

@参数 x(int): 横坐标, 单位像素. 建议范围 [0, 799].

@参数 y(int): 纵坐标, 单位像素. 建议范围 [0, 599].

@示例:

ButtonClick(490, 550)  # 选卡界面点击模仿者卡片

ButtonClick(740, 10)  # 点击菜单按钮

模拟键盘敲击

PressEsc()

敲击 退出 键.

PressSpace()

敲击 空格 键.

PressEnter()

敲击 回车 键.

PressKeys(keys)

敲击一系列按键.

@参数 keys(str): 按键字符串, 由 ‘0’ - ‘9’ ‘A’ - ‘Z’ 组成.

@示例:

PressKeys("FUTURE")  # 智慧树指令, 使僵尸带上眼镜

功能修改

SetZombies(zombies=None, mode=”极限刷怪”)

设置出怪.

旗帜只会在每个大波出现一只, 雪人只会出现一只, 蹦极只会在大波出现.

@参数 zombies(list[str/int]): 包含僵尸名称或代号的列表, 建议 8~12 种.

@参数 mode(str): 刷怪模式, 默认使用极限刷怪. 可选值 “自然刷怪” “极限刷怪”.

自然刷怪只改变出怪种类, 再由游戏内置的函数生成出怪列表.

极限刷怪是把所选僵尸种类按顺序均匀地填充到出怪列表.

@示例:

SetZombies(["撑杆", "舞王", "冰车", "海豚", "气球", "矿工", "跳跳", "扶梯", "白眼", "红眼"])

选卡/更新炮列表

SelectCards(seeds_selected=None)

选卡并开始游戏.

选择所有卡片, 点击开始游戏, 更新场景数据, 更新卡片列表, 更新加农炮列表, 等待开场红字消失.

建议把鼠标光标移出窗口外以避免可能出现的模仿者选卡失败.

@参数 seeds_selected(list): 卡片列表, 长度不大于卡槽格数.

列表为空默认选择八张紫卡和两张免费卡, 卡片个数小于卡槽数则用默认卡片填充.

单张卡片 seed 可用 int/tuple/str 表示, 不同的表示方法可混用.

seed(int): 卡片序号, 0 为豌豆射手, 47 为玉米加农炮, 对于模仿者这个数字再加上 48.

seed(tuple): 卡片位置, 用 (行, 列, 是否模仿者) 表示, 第三项可省略, 默认非模仿者.

seed(str): 卡片名称, 参考 seeds_string, 包含了一些常用名字.

@示例:

SelectCards()

SelectCards([14, 14 + 48, 17, 2, 3, 30, 33, 13, 9, 8])

SelectCards([(2, 7), (2, 7, True), (3, 2), (1, 3, False), (1, 4, False), (4, 7), (5, 2), (2, 6), (2, 2), (2, 1),])

SelectCards(["寒冰菇", "复制冰", "窝瓜", "樱桃", "坚果", "南瓜", "花盆", "胆小", "阳光", "小喷"])

SelectCards(["小喷菇", "模仿者小喷菇"])

UpdatePaoList(cobs=None)

更新玉米加农炮列表.

选卡时会自动调用, 空参数则自动找炮. 若需要自定义顺序请在选卡函数后面使用.

@参数 cobs(list): 加农炮列表, 包括若干个 (行, 列) 元组, 以后轮坐标为准.

@示例:

UpdatePaoList()

UpdatePaoList([(3, 1), (4, 1), (3, 3), (4, 3), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5)])

UpdatePaoList(
    [
        (r, c)
        for r in (1, 2, 3, 4, 5, 6)
        for c in (1, 3, 5, 7)
        if not (r in (3, 4) and c == 7)
    ]
)

UpdatePaoList([
                    (1, 5), (1, 7),
    (2, 1),         (2, 5), (2, 7),
    (3, 1), (3, 3), (3, 5), (3, 7),
    (4, 1), (4, 3), (4, 5), (4, 7),
    (5, 1),         (5, 5), (5, 7),
                    (6, 5), (6, 7),
    ])

阻塞延时

Sleep(time_cs)

线程睡眠延时.

实际睡眠时间依赖于操作系统线程切换时间片精度.

@参数 time_cs(float): 时间, 单位 cs, 精度 0.1.

Delay(time_cs)

游戏内部时钟延时. 相对于线程睡眠更准确.

只能在战斗界面使用, 游戏暂停时计时同样暂停.

@参数 time_cs(int): 时间, 单位 cs, 精度 1.

Prejudge(time_relative_cs, wave)

读内存获取刷新状况, 等待直至与设定波次刷新时间点的差值达到指定值.

该函数须在每波操作开始时执行一次. 通常用于预判 (在设定波次刷新前调用), 也可以在设定波次刷新之后调用.

@参数 time_relative_cs(int): 与刷新时间点的相对时间, 单位 cs, 精度 1. 建议范围 [-200, 400].

第一波最早 -599, 旗帜波最早 -750. 为了方便可统一给每波设置类似 -180 等数值.

因为脚本语言的精度问题, 设置成 -599/-750/-200 等过早的边界值时可能会因为实际达到时间已经超过该值而引起报错.

@参数 wave(int): 波数. 用于内部判断刷新状况以及本波是否为旗帜波.

@示例:

Prejudge(-100, wave)  # 第 wave 波刷新前 100cs 预判

Prejudge(-150, 20)  # 第 20 波预判炮炸珊瑚时机

Prejudge(300, wave)  # 第 wave 波刷新后 300cs

Prejudge(900 - 200 - 373, wave)  # 第 wave 波 900cs 波长激活炸时机

Until(time_relative_cs)

等待直至当前时间戳与本波刷新时间点的差值达到指定值.

该函数需要配合 Prejudge() 使用. 多个 Until() 连用时注意调用顺序必须符合时间先后顺序.

@参数 time_relative_cs(int): 相对时间, 单位 cs, 精度 1. 建议范围 [-200, 2300].

@示例:

Until(-95)  # 刷新前 95cs

Until(180)  # 刷新后 180cs

Until(-150)  # 炮炸珊瑚可用时机

Until(444 - 373)  # 444cs 生效炮发射时机

Until(601 + 20 - 298)  # 加速波下一波 20cs 预判冰点咖啡豆时机

Until(601 + 5 - 100 - 320)  # 加速波下一波 5cs 预判冰复制冰种植时机

Until(1200 - 200 - 373)  # 1200cs 波长激活炸时机

Until(4500 - 5)  # 收尾波拖满时红字出现时机

Until(5500)  # 最后一大波白字出现时机

场地相关点击函数

MouseLock()

获取鼠标锁, 进行完整的 (不可分割的) 鼠标操作前加锁, 操作完毕后释放.

@返回值 (object): 唯一内置鼠标锁.

@示例:

MouseLock().acquire()  # 获取鼠标操作权
SafeClick()            # 安全右键避免冲突
pass                   # 干点什么
MouseLock().release()  # 释放鼠标操作权

with MouseLock():  # 获取鼠标操作权, 代码块结束后自动释放
    SafeClick()    # 安全右键避免冲突
    pass           # 干点什么

SafeClick()

安全右键.

即右键单击左上角, 用于取消之前的 (可能未完成的) 操作以避免冲突.

ClickSeed(seed)

点击卡槽中的卡片.

@参数 seed(int/str): 卡槽第几格或者卡片名称.

@示例:

ClickSeed(5)  # 点击第 5 格卡槽

ClickSeed("樱桃")  # 点击卡槽中的樱桃卡片

ClickShovel()

点击铲子.

ClickGrid(*crood)

点击场上格点.

@参数 crood(float/tuple): 坐标, 两个分别表示 行/列 的数字或者一个 (行, 列) 元组, 数字可为小数.

@示例:

ClickGrid((2, 9))  # 点击 2 行 9 列

ClickGrid(2, 9)  # 同上

用卡用炮铲子操作

Card(seed, *crood)

用卡操作.

@参数 seed(int/str): 卡槽第几格或者卡片名称.

@参数 crood(int/tuple): 坐标, 两个分别表示 行/列 的数字或者一个 (行, 列) 元组, 数字均为整数.

@示例:

Card(1, (2, 3))  # 将卡槽中的第 1 张卡片种在 2 行 3 列

Card(1, 2, 3)  # 同上

Card("樱桃", (5, 9))  # 将樱桃种在 5 行 9 列

Card("樱桃", 5, 9)  # 同上

Shovel(*croods)

用铲子操作.

@参数 croods(float/tuple): 坐标, 两个分别表示 行/列 的数字或者一至多个 (行, 列) 元组, 数字可为小数.

@示例:

Shovel((3, 4))  # 铲掉 3 行 4 列的普通植物

Shovel(3, 4)  # 同上

Shovel((5 + 0.1, 6))  # 铲掉 5 行 6 列的南瓜头

Shovel((1, 9), (2, 9), (5, 9), (6, 9))  # 铲掉所有 9 列垫材

Pao(*croods)

用炮操作.

@参数 croods(float/tuple/list): 落点, 一至多个格式为 (行, 列) 的元组, 或者一个包含了这些元组的列表.

@示例:

Pao((2, 9))

Pao(2, 9)

Pao((2, 9), (5, 9))

Pao((2, 9), (5, 9), (2, 9), (5, 9))

Pao([(2, 9), (5, 9), (2, 9), (5, 9)])

RoofPao(*croods)

屋顶修正飞行时间发炮. 参数格式与 Pao() 相同.

此函数开新线程开销较大不适合精确键控, 只适用于前场落点 (约 7~9 列).

Skip(num)

按炮列表顺序跳过即将发射的一定数量的玉米炮, 通常用于 wave9/19 手动收尾.

@参数 num(int): 数量.

子线程操作

RunningInThread

将此装饰器应用到需要在子线程运行的函数上.

定义一个函数, 应用该装饰器, 则函数在调用的时候会运行在单独的线程中.

@示例:

@RunningInThread
def func():
    pass
# ...
func()

AutoCollect(collect_items=None, interval_cs=12)

自动收集场上资源, 在单独的子线程运行.

为了避免操作冲突, 当鼠标选中 卡片/铲子/玉米炮 时会暂停收集.

建议把鼠标光标移出窗口外以避免可能出现的游戏卡顿.

@参数 collect_items(list[str/int]): 包含需要收集的资源类型的列表, 默认所有.

可选值物品名称 [“银币”, “金币”, “钻石”, “阳光”, “小阳光”, “大阳光”, “幼苗”] 或者代号 [1, 2, 3, 4, 5, 6, 17].

@参数 interval_cs(float/int): 点击间隔, 单位 cs, 默认 12.

@示例:

AutoCollect()  # 自动收集所有资源

AutoCollect(["钻石", "阳光", "小阳光", "大阳光"], 20)  # 只收集钻石和各种阳光, 间隔 0.2s

IceSpots(spots=None, total=0x7FFFFFFF)

设置存冰位置和要存的数量, 将会在单独的子线程运行自动存冰.

@参数 spots(list): 存冰点, 包括若干个 (行, 列) 元组. 临时位在后. 默认为场上现有存冰的位置.

@参数 total(int): 总个数, 默认无限.

@示例:

IceSpots()

IceSpots([(6, 1), (5, 1), (2, 1), (1, 1)], 10)  # 往指定位置总计存 10 个冰

Coffee()

点冰. 使用咖啡豆激活存冰, 优先点临时位.

该函数需要配合自动存冰线程 IceSpots() 使用.

  1. 可以直接在脚本所在目录按住 “Shift” 并右键选择 “在此处打开命令窗口”. 

  2. 对于便携版/多版本共存/其他解释器的用户, 使用绝对路径来运行指定的 Python 解释器. 

  3. 正常情况下在命令界面按 Ctrl + C 即可结束脚本运行, 如果无法终止则去任务管理器里找到对应的 Python.exe 进程杀掉.