├── .github
└── ISSUE_TEMPLATE
│ └── bug.yml
├── .gitignore
├── README.md
├── components
├── choose.ahk
├── enterDennyFuben.ahk
├── enterFuben.ahk
├── enterHDD.ahk
├── enterHollowZero.ahk
├── exitFuben.ahk
├── fight.ahk
├── getDenny.ahk
├── getMoney.ahk
├── isLimited.ahk
├── reachEnd.ahk
├── recogLocation.ahk
├── refuse.ahk
└── saveBank.ahk
├── utils
├── Config.ahk
├── Controller.ahk
├── CoordsData.ahk
├── Panel.ahk
├── ScreenBitmap.ahk
└── common.ahk
├── 后台刷取示例.jpg
├── 控制面板.jpg
└── 零号业绩.ahk
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug
2 | description: 提交异常、错误报告
3 | title: 问题:
4 | labels: ["bug"]
5 | body:
6 |
7 | - type: checkboxes
8 | attributes:
9 | label: 这个问题是否已有说明?
10 | options:
11 | - label: 我已查看[注意事项](https://gitee.com/UCPr251/zzzAuto#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)及现有issues:[Gitee](https://gitee.com/UCPr251/zzzAuto/issues) [Github](https://github.com/UCPr251/zzzAuto/issues)并未发现相关说明
12 | required: true
13 |
14 | - type: textarea
15 | attributes:
16 | label: 你所遇见的问题
17 | description: 请简短准确地描述你所遇到的问题
18 | validations:
19 | required: true
20 |
21 | - type: dropdown
22 | attributes:
23 | label: 出现频率
24 | description: 该问题出现的频率
25 | options:
26 | - 仅此一次
27 | - 偶尔出现
28 | - 经常出现
29 | - 每次出现
30 | validations:
31 | required: true
32 |
33 | - type: input
34 | attributes:
35 | label: 录屏链接
36 | description: 请输入该问题出现前后全过程的录屏链接。除非问题无法复现,应尽量提供录屏,这是查找并解决问题的最佳方式
37 | placeholder: 只接受蓝奏云或阿里云链接
38 | validations:
39 | required: true
40 |
41 | - type: textarea
42 | attributes:
43 | label: 控制面板截图
44 | description: 请提供Alt+C控制面板截图
45 | validations:
46 | required: true
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | /历史版本
3 | /备份
4 | /取坐标.ahk
5 | /数据
6 | /活动
7 | *.exe
8 | test*.ahk
9 | /toString.ahk
10 | /*.ini
11 | /*.ahk
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 绝区零零号业绩刷取脚本
2 |
3 | ## 简介
4 |
5 | - 针对 **旧都列车·前线**、 **拿命验收** 关卡设计
6 | - 基于 [**AutoHotKey v2**](https://www.autohotkey.com) 实现的 **零号业绩** 、 **零号空洞银行存款** 、 **丁尼** 自动刷取
7 | > **业绩**约2分钟一把,可获得30业绩+银行存款,1小时+即可刷满一周900业绩上限并拿满周任务奖励
8 | >
**丁尼**约30秒一把,可获得80丁尼,每小时可刷取80+次(约7k丁尼);**每个服务器日**刷取上限为625次(5w丁尼整,约需8h)
9 | - 支持多比例 **全屏** 或 **窗口** 运行
10 | - 简单轻量开箱即用,对于不同 **分辨率、帧率、缩放、HDR、字体等** 具有良好的适应性
11 |
12 | ## 使用前提
13 |
14 | ### 零号空洞
15 |
16 | - 1. 默认 **3位及以上** 战斗角色
17 | - 2. 已激活作战攻略的 **开局炸弹补给**
18 | - 3. 请根据游戏内快捷键设置调整[控制面板](#使用方法)中的 **使用炸弹** 和 **快捷手册**
19 |
20 | ### 拿命验收
21 |
22 | - 1. HDD关卡选择界面需提前切换至 **间章第二章**
23 | - 2. 默认编队首位必须为 **比利**
24 | - 3. 请在副本内调整 **视角转动值** 以确保对齐NPC
25 |
26 | ## 下载方法
27 |
28 | > PS: 程序报毒系正常现象,担心的话可以跑源码或者自行编译,怕别用用别怕
29 |
30 | 选择其一即可:
31 |
32 | - 方法一:使用编译好的程序(推荐,**下载即用**)
33 |
34 | [<<<点击进入>>>](https://github.com/UCPr251/zzzAuto/releases/latest)release最新版本,下载并运行该exe文件
35 |
36 | - 方法二:使用源码
37 |
38 | 克隆/下载源码(需已安装好[Autohotkey](https://www.autohotkey.com) v2版解释器),运行[零号业绩.ahk](./零号业绩.ahk)文件
39 |
40 | ## 使用方法
41 |
42 | - Alt+P :暂停/恢复刷取快捷指令,也可通过控制面板修改
43 | - Alt+C :打开/关闭[控制面板](./控制面板.jpg),相关信息:
44 | > **休眠系数**:调整脚本在加载动画时的等待时长倍率
45 | >
**异常重试次数**:允许异常后尝试重试/重启的总次数,发生异常后值减一,双击可查看历史异常
46 | >
**颜色搜索允许RGB容差**:过大可能误点,过小可能匹配失败,按需调整
47 | >
**二号位角色**:丁尼模式下队伍二号位角色,按照实际选择即可:通用 / 艾莲 / 星见雅
48 | >
**视角转动值**:丁尼模式下进入副本后的视角转动值,**首次运行时请调整该值以确保对齐NPC**:转动幅度过大则需减小,反之则需增大。转动效果受**游戏帧率**设置影响,若存在浮动系正常现象
49 | >
**使用炸弹**:修改走格子邦布插件快捷使用方式,**请保持和游戏内一致**:长按/点击
50 | >
**快捷手册**:修改打开快捷手册的快捷键,**请保持和游戏内一致**,游戏默认F2
51 | >
**战斗模式**:通用 / 艾莲 / 星见雅
52 | >
**刷取模式**:业绩+银行 / 只要业绩 / 只存银行
53 | >
**循环模式**:选择 刷至达上限 / 无限循环 / 指定次数
54 | >
**子循环模式**:业绩模式下,**双击**循环模式的**业绩上限**选项可切换至丁尼上限/全部上限模式。丁尼上限模式下将不再获取业绩,第一层boss战斗结束后将直接退出副本进入下一循环,该模式用于刷完业绩后刷取零号空洞的丁尼。全部上限模式下将一直刷取直至业绩、丁尼皆达到周上限,此模式下当刷取业绩达上限时,会自动切换至丁尼上限模式
55 | >
**异常处理**:关闭后,任何意料之外的情况都将直接结束刷取并抛出错误,用于排查错误
56 | >
**步骤信息弹窗**:是否输出刷取过程中每个步骤的信息弹窗,用于排查错误
57 | >
**战斗红光自动闪避**:开启此模式后:业绩模式战斗期间会识别红光进行闪避;战斗期间CPU占用会略微提高;代理人事件会选择取消接应。效果受RGB容差影响,推荐在战斗时长>30s(游戏内计时,不含动画)的情况下开启,自行测试按需开关
58 | >
**刷完关闭**:业绩/丁尼达上限/达指定刷取次数 时关闭游戏/电脑;开启无限循环后此项无效
59 | >
**退出**:退出脚本
60 | >
**重启**:重启脚本,修改缩放、分辨率后建议重启脚本
61 | >
**暂停**:暂停当前刷取线程,可使用Alt+P快捷暂停/恢复
62 | >
**刷取统计**:刷取次数、总计耗时、平均战斗耗时、平均刷取耗时、每次刷取详情
63 | >
**开始刷取**:开始循环刷取,默认情况下会一直刷取直至零号业绩达到周上限
64 | >
**本轮结束**:在当前执行的刷取任务完成后结束循环刷取
65 |
66 | 控制面板图例:
67 |
68 |
69 |
70 |
71 |
72 | ## 后台刷取教程
73 |
74 | 使用Windows多用户远程桌面连接,可实现后台独立刷取而不影响前台操作(即刷取的同时正常使用电脑)。建议有后台刷取需求且 **有一定电脑基础** 的用户尝试,具体可参考[此教程](https://github.com/sMythicalBird/ZenlessZoneZero-Auto/wiki/Windows%E5%A4%9A%E7%94%A8%E6%88%B7%E5%90%8C%E6%97%B6%E8%BF%9C%E7%A8%8B%E6%9C%AC%E5%9C%B0%E6%A1%8C%E9%9D%A2)
75 |
76 | 远程连接进入游戏后:
77 |
78 | - 由于不同电脑用户的脚本数据独立,启动脚本后请注意重新设置参数
79 |
80 | - 建议修改游戏显示模式为 **1280*720窗口** ,画面质量等调整至最低,以减少资源占用
81 |
82 | > 网络上相关教程众多,如遇问题请先使用搜索引擎搜索,若搜索无果可issue提问
83 |
84 |
85 | 后台刷取示例
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | ## 注意事项
94 |
95 | 1. 本脚本基于固定坐标和像素点颜色查找实现,设计分辨率比例:**21:9、16:9、16:10、4:3、5:4**,应已满足大部分情况。
96 |
如果脚本无法正常运行,建议 **显示器和游戏** 的画面分辨率设置为 **长宽比16:9** 且 **关闭HDR等** 重新运行脚本
97 |
98 | 2. 若脚本仍无法正常运行,请提出[issue](https://github.com/UCPr251/zzzAuto/issues/new?template=bug.yml)并按照要求描述提供所需信息
99 |
100 | 3. 数据文件路径: **C:/Users/用户名/Documents/autoZZZ.ini**,启动脚本后自动生成
101 |
102 | 4. 由于电脑配置的差异,实际动画加载时长可能与预设数据不符,可微调 **休眠系数** 延长全局的等待时长
103 | > 不建议减小该值,压缩不了多少时间反而容易引发问题
104 |
105 | 5. 业绩模式下战斗模式可选通用、鲨鱼妹或雅,只要哪怕一直a都能S评价即可
106 | > 战斗时长一般在5~50s以内(游戏内计时,不含动画),超过30s建议开启战斗红光自动闪避试试看
107 |
108 | 6. 丁尼模式下建议提前取消任务追踪,减少传送时识别地图耗时
109 |
110 | 7. 为避免消息弹窗等的影响,建议在脚本运行期间开启 **免打扰模式**
111 |
112 | 8. 请勿在脚本运行期间操作键鼠,若需操作请先暂停脚本: **Alt+P**
113 |
114 | 9. 本脚本完全免费公开, **严禁用于任何商业用途** ,仅供学习交流使用
115 |
--------------------------------------------------------------------------------
/components/choose.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 选择增益
3 | */
4 | choose(step := 5) {
5 | activateZZZ()
6 | stepLog("【step" step "】选择增益")
7 |
8 | ; 进入对话
9 | Press("w", 3)
10 | RandomSleep(1000, 1200)
11 | ; 对话
12 | Press("Space", 16)
13 | rgbs := [
14 | [0xb2eb47, () => 1], ; 恢复身体
15 | [0x10cbf4, () => pixelSearchAndClick(c.空洞.2.降压准备*)], ; 降压准备
16 | [0xaa7cff, 2], ; 垃圾物资或催化
17 | [0xc01c00, 1], ; 侵蚀物资
18 | [0xff802c, () => pixelSearchAndClick(c.空洞.2.降压准备*)], ; 邦布插件
19 | ]
20 | clickFnc := 0
21 | uc:
22 | loop (30) {
23 | for (rgb in rgbs) {
24 | clone := c.空洞.2.选项框.Clone()
25 | clone.Push(rgb[1])
26 | PixelSearchPre(&FoundX, &FoundY, clone*)
27 | if (FoundX && FoundY) {
28 | SimulateClick(FoundX, FoundY)
29 | clickFnc := rgb[2]
30 | break uc
31 | }
32 | }
33 | Press("Space")
34 | }
35 | ; 未找到对应选项
36 | if (clickFnc = 0) {
37 | return false
38 | } else if (clickFnc = 1) { ; 侵蚀物资
39 | ; 领取铭徽
40 | MingHui()
41 | ; 加载侵蚀动画
42 | RandomSleep(1800, 2000)
43 | loop (10) {
44 | ; 确认侵蚀 或 集齐四个同类铭徽触发的赠送铭徽确认(特殊)
45 | if (PixelSearchPre(&X, &Y, c.空洞.确认*)) {
46 | SimulateClick(X, Y)
47 | RandomSleep(1800, 2000)
48 | }
49 | Sleep(100)
50 | }
51 | } else if (clickFnc = 2) { ; 垃圾物资或催化
52 | RandomSleep(2800, 3000)
53 | loop (10) {
54 | ; 点击确定
55 | if (PixelSearchPre(&X, &Y, c.空洞.确定*)) {
56 | SimulateClick(X, Y)
57 | RandomSleep(2800, 3000)
58 | }
59 | Sleep(100)
60 | }
61 | } else {
62 | clickFnc()
63 | }
64 | Press("Space", 10)
65 | return true
66 | }
--------------------------------------------------------------------------------
/components/enterDennyFuben.ahk:
--------------------------------------------------------------------------------
1 | #Include ../utils/ScreenBitmap.ahk
2 |
3 | global FoundDennyFuben := false
4 | /**
5 | * HDD关卡选择界面查找副本并进入
6 | */
7 | enterDennyFuben(step := 2) {
8 | activateZZZ()
9 | stepLog("【step" step "】进入丁尼副本")
10 |
11 | static x := 251
12 | static y := Floor(c.height * 0.9)
13 | static ymin := c.height // 10
14 | static maxO := Ceil(c.height / 2160 * 24)
15 | static GetPixel := PixelGetColor
16 | static isPageDown := false
17 | static calY(x0, y0, r, x) {
18 | ySqPart := sqrt(Abs(r ** 2 - (x - x0) ** 2))
19 | y1 := Floor(y0 + ySqPart)
20 | y2 := Ceil(y0 - ySqPart)
21 | return [y1, y2]
22 | }
23 | static pagedown() {
24 | MouseMove(c.width // 4 * 3, c.height // 2)
25 | loop (10) {
26 | Click('WD 1')
27 | }
28 | Sleep(1000)
29 | }
30 | static getLen(direction := 'x', bl := 1) {
31 | len := 0
32 | if (direction = 'x') {
33 | s := () => GetPixel(x + bl * len, y)
34 | } else {
35 | s := () => GetPixel(x, y + bl * len)
36 | }
37 | loop {
38 | len++
39 | if (len > maxO) {
40 | break
41 | }
42 | color := s()
43 | if (color = '0x000000') {
44 | break
45 | }
46 | }
47 | return len
48 | }
49 | static choose() {
50 | pixelSearchAndClick(c.旧都列车.下一步*)
51 | RandomSleep(251, 300)
52 | SimulateClick(, , 6)
53 | }
54 | global FoundDennyFuben
55 | if (FoundDennyFuben) {
56 | if (isPageDown) {
57 | pagedown()
58 | }
59 | SimulateClick(x, y, 2)
60 | choose()
61 | return
62 | }
63 | xmin := Floor(c.拿命验收.xmin * c.width / 1920)
64 | xmax := Ceil(c.拿命验收.xmax * c.width / 1920)
65 | pToken := LoadLibrary()
66 | start := A_TickCount
67 | ; 不用OCR的痛苦 :)
68 | loop (2) {
69 | if (A_Index = 2) {
70 | isPageDown := true
71 | pagedown()
72 | }
73 | if (c.windowed) {
74 | WinGetClientPos(&zzzX, &zzzY, &zzzW, &zzzH, ZZZ)
75 | BitMap := ScreenBitmap(zzzX, zzzY, zzzX + zzzW, zzzY + zzzH)
76 | } else {
77 | BitMap := ScreenBitmap()
78 | }
79 | GetPixel := GetBitMapPixel.Bind(BitMap)
80 | loop (xmax - xmin + 1) {
81 | x := xmin + A_Index - 1
82 | y := Floor(c.height * 0.9)
83 | loop {
84 | y--
85 | if (y < ymin) {
86 | break
87 | }
88 | color := GetPixel(x, y)
89 | if (color = '0xFFFFFF') {
90 | ; 确定圆心X
91 | xLen1 := getLen('x', 1)
92 | if (xLen1 > maxO) {
93 | continue
94 | }
95 | xLen2 := getLen('x', -1)
96 | if (xLen2 > maxO) {
97 | continue
98 | }
99 | xTotal := xLen1 + xLen2
100 | if (xTotal > maxO) {
101 | continue
102 | }
103 | oldX := x
104 | oldY := y
105 | reset() {
106 | x := oldX
107 | y := oldY
108 | }
109 | x := x - xLen2 + Floor(xTotal / 2)
110 | ; 确定圆心Y
111 | yLen1 := getLen('y', 1)
112 | if (yLen1 > maxO) {
113 | reset()
114 | continue
115 | }
116 | yLen2 := getLen('y', -1)
117 | if (yLen2 > maxO) {
118 | reset()
119 | continue
120 | }
121 | yTotal := yLen1 + yLen2
122 | if (yTotal > maxO) {
123 | reset()
124 | continue
125 | }
126 | y := y - yLen2 + Floor(yTotal / 2)
127 | ; 重新获取X方向的圆直径
128 | xLen1 := getLen('x', 1)
129 | if (xLen1 > maxO) {
130 | reset()
131 | continue
132 | }
133 | xLen2 := getLen('x', -1)
134 | if (xLen2 > maxO) {
135 | reset()
136 | continue
137 | }
138 | xTotal := xLen1 + xLen2
139 | if (xTotal > maxO) {
140 | reset()
141 | continue
142 | }
143 | if (Abs(xTotal - yTotal) > 4) {
144 | reset()
145 | continue
146 | }
147 | offSet := 0 ; 同心圆半径偏移量
148 | loop {
149 | offSet++
150 | if (GetPixel(x - offSet, y) = '0x000000') {
151 | offSet++
152 | break
153 | }
154 | }
155 | radius := (xTotal + yTotal) // 4 + offSet ; 大同心圆半径
156 | check := (x, y) => GetPixel(x, y) = '0x000000'
157 | flag := true
158 | loop (radius * 2 + 1) {
159 | _x := x - radius + A_Index - 1
160 | Ys := calY(x, y, radius, _x)
161 | if (!check(_x, Ys[1]) || !check(_x, Ys[2])) {
162 | flag := false
163 | break
164 | }
165 | }
166 | if (!flag) {
167 | reset()
168 | continue
169 | }
170 | DisposeImage(BitMap)
171 | FreeLibrary(pToken)
172 | FoundDennyFuben := true
173 | SimulateClick(x, y)
174 | choose()
175 | ; MsgBox(Format("X: {}, Y: {}, xTotal: {}, yTotal: {}, seconds: {}, offSet: {}", x, y, xTotal, yTotal, Round((A_TickCount - start) / 1000, 3), offSet))
176 | return
177 | }
178 | }
179 | }
180 | DisposeImage(BitMap)
181 | }
182 | FreeLibrary(pToken)
183 | MsgBox("自动搜索失败,采用手动方案:`n请在关闭该弹窗后3s内将鼠标移动至「真 · 拿命验收」副本位置", "警告", "Icon! 0x40000")
184 | ; MsgBox(Format("自动搜索失败,X: {}, Y: {}, xTotal: {}, yTotal: {}, seconds: {}", x, y, IsSet(xTotal) ? xTotal : -1, IsSet(yTotal) ? yTotal : -1, Round((A_TickCount - start) / 1000, 3)), "警告", "Icon! 0x40000")
185 | Sleep(3000)
186 | MouseGetPos(&x, &y)
187 | FoundDennyFuben := true
188 | MsgBox(Format("后续选择丁尼副本位置将取当前鼠标位置:({},{})`n重启脚本后重置", x, y), , "0x40000 T3")
189 | SimulateClick(x, y)
190 | choose()
191 | return
192 | }
--------------------------------------------------------------------------------
/components/enterFuben.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 进入副本
3 | */
4 | enterFuben(step := 1) {
5 | activateZZZ()
6 | stepLog("【step" step "】进入副本")
7 |
8 | pixelSearchAndClick(c.零号选择.旧都列车*)
9 | ; 连续运行,无需重复选择
10 | if (!Ctrl.continuous) {
11 | pixelSearchAndClick(c.旧都列车.前线*)
12 | Ctrl.continuous := true
13 | }
14 | pixelSearchAndClick(c.旧都列车.下一步*)
15 | RandomSleep(251, 300)
16 | SimulateClick(, , 6)
17 |
18 | Sleep(10000)
19 | awaitLoading() {
20 | static startX := Integer(c.width * 0.3), endX := Integer(c.width * 0.7)
21 | static startY := Integer(c.height * 0.8), endY := Integer(c.height * 0.95)
22 | loop (5) {
23 | if (!PixelSearch(&X, &Y, startX, startY, endX, endY, 0x009dff, setting.variation)) {
24 | return false
25 | }
26 | Sleep(100)
27 | }
28 | return true
29 | }
30 | loop {
31 | if (awaitLoading()) {
32 | break
33 | }
34 | if (A_Index > 100) {
35 | if (!setting.errHandler) {
36 | throw Error('识别进入副本·第一层失败')
37 | }
38 | break
39 | }
40 | Sleep(200)
41 | }
42 | RandomSleep(2000, 2200)
43 | }
--------------------------------------------------------------------------------
/components/enterHDD.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * 进入HDD页面
3 | */
4 | enterHDD(step := 1) {
5 | activateZZZ()
6 | stepLog("【step" step "】进入HDD页面")
7 |
8 | patterns := [
9 | c.地图.RandomPlay,
10 | c.地图.2F,
11 | c.地图.HDD
12 | ]
13 | FoundX := 0, FoundY := 0
14 | judge() {
15 | for (pattern in patterns) {
16 | if (!PixelSearchPre(&FoundX, &FoundY, pattern*)) {
17 | return false
18 | }
19 | }
20 | return true
21 | }
22 | Press('m')
23 | RandomSleep(800, 900)
24 | RandomMouseMove(c.width // 2, c.height // 2)
25 | loop (32) {
26 | if (judge()) {
27 | SimulateClick(FoundX, FoundY)
28 | pixelSearchAndClick(c.空洞.退出副本.确认*)
29 | while (recogLocation() != 1) {
30 | if (A_Index > 6) {
31 | break
32 | }
33 | if (PixelSearchPre(&FoundX, &FoundY, c.空洞.退出副本.确认*)) {
34 | SimulateClick(FoundX, FoundY)
35 | }
36 | Sleep(500)
37 | }
38 | Press('f', 3)
39 | RandomSleep(1400, 1500)
40 | return true
41 | }
42 | Click('WD')
43 | RandomSleep(700, 720)
44 | }
45 | return false
46 | }
--------------------------------------------------------------------------------
/components/enterHollowZero.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 角色操作界面进入零号空洞选择界面
3 | */
4 | enterHollowZero() {
5 | activateZZZ()
6 | ; 进入快捷手册
7 | Press(setting.handbook)
8 | RandomSleep(1000, 1200)
9 | if (PixelSearchPre(&X, &Y, c.快捷手册.挑战_灰色*)) {
10 | SimulateClick(X, Y)
11 | } else {
12 | pixelSearchAndClick(c.快捷手册.挑战_黑色*)
13 | }
14 | pixelSearchAndClick(c.快捷手册.零号空洞*)
15 | pixelSearchAndClick(c.快捷手册.前往*)
16 | pixelSearchAndClick(c.快捷手册.传送*)
17 | RandomSleep()
18 | while (!PixelSearchPre(&X, &Y, c.拿命验收.返回键*)) {
19 | Sleep(100)
20 | if (A_Index > 50) {
21 | return false
22 | }
23 | }
24 | RandomSleep(400, 600)
25 | Press('Escape')
26 | while (recogLocation() != 1) {
27 | Sleep(100)
28 | if (A_Index > 3) {
29 | return false
30 | }
31 | Press('Escape')
32 | }
33 | RandomSleep()
34 | Press('F', 2)
35 | RandomSleep()
36 | while (!PixelSearchPre(&X, &Y, c.拿命验收.返回键*)) {
37 | Sleep(100)
38 | if (A_Index > 50) {
39 | return false
40 | }
41 | }
42 | RandomSleep()
43 | SimulateClick(c.width // 4 * 3, c.height // 2, 3)
44 | ; 验证是否成功进入零号空洞选择界面
45 | mode := recogLocation()
46 | if (mode != 2) {
47 | return false
48 | }
49 | return true
50 | }
--------------------------------------------------------------------------------
/components/exitFuben.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 退出副本
3 | */
4 | exitFuben(step := 8) {
5 | activateZZZ()
6 | stepLog("【step" step "】退出副本")
7 |
8 | Press("Escape")
9 | success := 0
10 | loop (40) {
11 | success := PixelSearchPre(&X, &Y, c.空洞.退出副本.放弃*)
12 | if (success) {
13 | SimulateClick(X, Y)
14 | break
15 | }
16 | Sleep(100)
17 | }
18 | if (!success) { ; 可能是存银行的时候卡四铭徽了
19 | MingHui(true)
20 | RandomSleep(600, 800)
21 | Press("Escape")
22 | pixelSearchAndClick(c.空洞.退出副本.放弃*)
23 | }
24 | pixelSearchAndClick(c.空洞.退出副本.确认*)
25 | RandomSleep(200, 251)
26 | }
--------------------------------------------------------------------------------
/components/fight.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 战斗
3 | */
4 | fight(step := 4) {
5 | activateZZZ()
6 | stepLog("【step" step "】战斗")
7 |
8 | ; 进入战斗
9 | Press("1", 3)
10 |
11 | /** 通过三个特殊定位点判断所处界面 */
12 | patterns := [
13 | c.空洞.1.战斗.计时,
14 | c.空洞.1.战斗.确定键,
15 | c.空洞.1.战斗.确定绿勾
16 | ]
17 |
18 | ; 加载动画
19 | Sleep(10000)
20 | while (!PixelSearchPre(&X, &Y, c.空洞.1.战斗.开始*)) {
21 | if (A_Index > 251) {
22 | if (!setting.errHandler) {
23 | throw Error('识别战斗开始画面失败')
24 | }
25 | break
26 | }
27 | Sleep(100)
28 | }
29 |
30 | static XStart := 0.2, XEnd := 0.8, XHierarchy := 3, XInteval := 0.03
31 | static YStart := 0.1, YEnd := 0.8, YHierarchy := 4, YInteval := 0
32 | variation := Min(Max(Round(setting.variation * 0.5), 10), 50)
33 | X := Cal(c.width, XStart, XEnd, XHierarchy, XInteval)
34 | Y := Cal(c.height, YStart, YEnd, YHierarchy, YInteval)
35 | ; 战斗开始
36 | Ctrl.startFight()
37 | ; 通用·普通攻击战斗模式
38 | if (setting.fightMode = 1) {
39 | Send("{w Down}") ; 向前
40 | Press("Shift", 2)
41 | autoDodge(200, 300)
42 | Send("{w Up}")
43 | ; 约5s一循环
44 | loop (10) {
45 | ; 战斗动作
46 | sAttack() ; 使用技能
47 | attack(4)
48 | if (!setting.isAutoDodge) {
49 | Press("Shift") ; 闪避
50 | }
51 | if (fightIsOver(patterns)) {
52 | return true
53 | }
54 | sAttack() ; 使用技能
55 | attack(8)
56 | if (fightIsOver(patterns)) {
57 | return true
58 | }
59 | if (!setting.isAutoDodge) {
60 | Press("Shift") ; 闪避
61 | }
62 | Send("{w Down}") ; 向前
63 | autoDodge(500, 600)
64 | Send("{w Up}") ; 向前
65 | }
66 | ; 艾莲战斗模式
67 | } else if (setting.fightMode = 2) {
68 | ; 约10s一循环
69 | loop (5) {
70 | if (A_Index != 1 && !setting.isAutoDodge) {
71 | Press("Shift") ; 闪避
72 | }
73 | Send("{w Down}") ; 向前
74 | autoDodge(80, 100)
75 | Click("Right Down") ; 右键蓄力,进入快蓄
76 | autoDodge(420, 430)
77 | Click("Right Up") ; 快蓄完毕,释放右键
78 | Click("Left Down") ; 普攻蓄力
79 | autoDodge()
80 | if (fightIsOver(patterns)) {
81 | return true
82 | }
83 | autoDodge(1500, 1600)
84 | Click("Left Up") ; 完成蓄力普攻
85 | Send("{w Up}") ; 停止移动
86 | autoDodge()
87 | ; 战斗动作
88 | loop (2) {
89 | sAttack() ; 使用技能
90 | attack(4) ; 普攻
91 | sAttack() ; 使用技能
92 | attack(8) ; 普攻
93 | if (!setting.isAutoDodge) {
94 | Press("Shift") ; 闪避
95 | }
96 | if (fightIsOver(patterns)) {
97 | return true
98 | }
99 | }
100 | }
101 | ; 雅战斗模式
102 | } else if (setting.fightMode = 3) {
103 | ; 约12s一循环
104 | loop (4) {
105 | if (A_Index = 1) {
106 | ; 长闪避近身
107 | Send("{w Down}")
108 | Send("{Shift Down}")
109 | Sleep(Random(360, 400))
110 | Send("{Shift Up}")
111 | RandomSleep(200, 220)
112 | Send("{w Up}")
113 | }
114 | ; 蓄力斩
115 | Click("Left Down")
116 | Sleep(Random(1660, 1720))
117 | Click("Left Up")
118 | attack(10)
119 | if (fightIsOver(patterns)) {
120 | return true
121 | }
122 | attack(6)
123 | autoDodge(500, 520)
124 | sAttack()
125 | sAttack()
126 | autoDodge(460, 520)
127 | sAttack()
128 | sAttack()
129 | if (fightIsOver(patterns)) {
130 | return true
131 | }
132 | attack(12)
133 | }
134 | }
135 |
136 | ; 如果战斗时长超过设置好的循环次数,可能是因为周上限提示需要点击确定,尝试使用Esc退出确认窗口,否则可以暂停战斗
137 | Press('Escape')
138 | RandomSleep(800, 1000)
139 | return fightIsOver(patterns)
140 |
141 | /** 自动闪避 */
142 | autoDodge(ms1 := 50, ms2 := 100) {
143 | if (!setting.isAutoDodge) {
144 | return RandomSleep(ms1, ms2)
145 | }
146 | static lastTick := 0
147 | randomMs := Random(ms1, ms2)
148 | start := A_TickCount
149 | loop {
150 | if (A_TickCount - lastTick < 500) { ; 闪避CD中
151 | leftCD := 500 - (A_TickCount - lastTick) ; 剩余CD时长
152 | passed := A_TickCount - start ; 已过的时长
153 | if (passed + leftCD > randomMs) { ; 如果剩余CD时长+已过的时长>需休眠时长,休眠至函数结束
154 | return Sleep(randomMs - passed)
155 | } else { ; 否则休眠至CD结束,继续下一次检测
156 | Sleep(leftCD)
157 | }
158 | }
159 | if (HierarchicalSearch(X, XHierarchy, Y, YHierarchy, 0xff6565, variation)) {
160 | lastTick := A_TickCount
161 | Send("{Shift Down}")
162 | Sleep(Random(80, 100))
163 | Send("{Shift Up}")
164 | Sleep(Random(120, 140))
165 | attack(1)
166 | }
167 | if (A_TickCount - start > randomMs - 30) {
168 | return
169 | }
170 | }
171 | }
172 |
173 | ; 分层搜索,需Y轴某一层匹配成功,X轴每一层都匹配成功
174 | static HierarchicalSearch(X, XHierarchy, Y, YHierarchy, Color, variation) {
175 | loop (YHierarchy) { ; 对Y轴每层进行遍历
176 | YNowStart := Y[A_Index * 2 - 1] ; Y轴该层的起始坐标
177 | YNowEnd := Y[A_Index * 2] ; Y轴该层的终止坐标
178 | if (PixelSearch(&Xmatch, &Ymatch, X[1], YNowStart, X[2], YNowEnd, Color, variation)) { ; 对X轴第一层进行匹配
179 | loop (XHierarchy - 1) { ; 对X轴其余每一层进行匹配
180 | if (!PixelSearch(&Xmatch, &Ymatch, X[A_Index * 2 + 1], Ymatch - 30, X[A_Index * 2 + 2], Ymatch + 30, Color, variation)) {
181 | return false
182 | }
183 | }
184 | return true ; X轴每层都匹配成功
185 | }
186 | }
187 | return false
188 | }
189 |
190 | static Cal(len, Start, End, Hierarchy, Inteval) {
191 | data := [Round(len * Start)]
192 | IntevalLen := Round(len * Inteval)
193 | HierarchyLen := Round((len * (End - Start) - IntevalLen * (Hierarchy - 1)) / Hierarchy)
194 | loop (Hierarchy * 2 - 1) {
195 | data.Push(data[A_Index] + (Mod(A_Index, 2) ? HierarchyLen : IntevalLen))
196 | }
197 | return data
198 | }
199 |
200 | /** 普攻 */
201 | attack(times) {
202 | Loop (times) {
203 | Click("Left Down")
204 | autoDodge()
205 | Click("Left Up")
206 | autoDodge(160, 200)
207 | }
208 | }
209 |
210 | /** E技能 */
211 | sAttack() {
212 | Send("{e Down}")
213 | autoDodge()
214 | Send("{e Up}")
215 | autoDodge(200, 220)
216 | }
217 |
218 | /** 判断战斗是否结束 */
219 | static fightIsOver(patterns) {
220 | judge() {
221 | loop (10) {
222 | for (pattern in patterns) {
223 | if (!PixelSearchPre(&FoundX, &FoundY, pattern*)) {
224 | return false
225 | }
226 | }
227 | Sleep(10)
228 | }
229 | return true
230 | }
231 | FoundX := 0, FoundY := 0
232 | if (!judge()) {
233 | return false
234 | }
235 | Ctrl.finishFight()
236 | stepLog("【战斗】战斗结束")
237 | ; 点击确定
238 | SimulateClick(FoundX, FoundY, 2)
239 | RandomSleep(800, 1000)
240 | MingHui()
241 |
242 | Sleep(3000)
243 | awaitLoading() {
244 | static startX := Integer(c.width * 0.3), endX := Integer(c.width * 0.7)
245 | static startY := Integer(c.height * 0.7), endY := Integer(c.height * 0.95)
246 | loop (5) {
247 | if (!PixelSearch(&X, &Y, startX, startY, endX, endY, 0x009dff, setting.variation)) {
248 | return false
249 | }
250 | Sleep(100)
251 | }
252 | return true
253 | }
254 | loop {
255 | if (awaitLoading()) {
256 | break
257 | }
258 | if (A_Index > 50) {
259 | if (!setting.errHandler) {
260 | throw Error('识别进入副本·第二层失败')
261 | }
262 | break
263 | }
264 | Sleep(200)
265 | }
266 | RandomSleep(1000, 1200)
267 | return true
268 | }
269 | }
--------------------------------------------------------------------------------
/components/getDenny.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * 完成丁尼副本
3 | */
4 | getDenny(step := 3) {
5 | activateZZZ()
6 | stepLog("【step" step "】完成丁尼副本")
7 |
8 | static depth := 1
9 | static isFighting() {
10 | return PixelSearchPre(&FoundX, &FoundY, c.空洞.1.战斗.开始*)
11 | }
12 |
13 | Sleep(10000)
14 | while (!isFighting()) {
15 | if (A_Index > 251) {
16 | if (!setting.errHandler) {
17 | throw Error('识别战斗开始画面失败')
18 | }
19 | break
20 | }
21 | Sleep(100)
22 | }
23 | Sleep(100)
24 | DllCall("mouse_event", "UInt", 1, "UInt", setting.rotateCoords, "UInt", 0)
25 | Send("{w Down}")
26 | ; 比利闪避长按攻击
27 | Press('Shift')
28 | Click('Left Down')
29 | Sleep(200)
30 | Click('Left Up')
31 | ; 切人
32 | Press('Space')
33 | if (setting.fightModeDenny = 1) {
34 | ; 通用连续闪避
35 | loop(6) {
36 | Press('Shift')
37 | Sleep(Random(300, 320))
38 | }
39 | } else if (setting.fightModeDenny = 2) {
40 | ; 艾莲长按闪避
41 | Send('{Shift Down}')
42 | RandomSleep(2510, 2600)
43 | Send('{Shift Up}')
44 | } else if (setting.fightModeDenny = 3) {
45 | ; 雅连续长按闪避
46 | loop (2) {
47 | Send("{Shift Down}")
48 | Sleep(Random(300, 320))
49 | Send("{Shift Up}")
50 | RandomSleep(180, 200)
51 | }
52 | }
53 |
54 | while (isFighting()) {
55 | if (A_Index > 12) {
56 | break
57 | }
58 | Press('f')
59 | }
60 | Send("{w Up}")
61 |
62 | RandomSleep(251, 300)
63 | if (isFighting()) {
64 | ; if (!setting.errHandler) {
65 | ; throw Error('丁尼刷取交互失败')
66 | ; }
67 | Press('Escape')
68 | if (++depth > 4) {
69 | ; 若存在刷取成功的数据,证明非配置问题,尝试重进副本刷取
70 | if (setting.statisticsDenny.length) {
71 | return false
72 | }
73 | throw Error('获取丁尼重试次数过多,请确保:`n1、进入了正确的副本:第二章间章「真·拿命验收」`n2、编队首位必须为「比利」`n3、在控制面板中设置了合适的“视角转动值”')
74 | }
75 | pixelSearchAndClick(c.拿命验收.重新开始*)
76 | pixelSearchAndClick(c.空洞.退出副本.确认*)
77 | return getDenny(step)
78 | } else {
79 | loop (2) {
80 | Press('Space', 3)
81 | Press('1', 2)
82 | Press('Space', 3)
83 | }
84 | depth := 1
85 | return true
86 | }
87 | }
--------------------------------------------------------------------------------
/components/getMoney.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 获得零号业绩
3 | */
4 | getMoney(step := 6) {
5 | activateZZZ()
6 | stepLog("【step" step "】获得零号业绩")
7 |
8 | ; 进入零号业绩格子
9 | Press('d')
10 | Press('w', 2)
11 | Press('d', 1)
12 | RandomSleep(400, 500)
13 | Press('1', 3)
14 | RandomSleep(2200, 2300)
15 | Press('d', 2)
16 | ; 获得零号业绩(如果有)
17 | Press('w')
18 | ; 加载动画
19 | RandomSleep(1600, 1800)
20 | X := 1800, Y := 888
21 | preprocess(&X, &Y)
22 | ; 点击确定(如果有)
23 | SimulateClick(X, Y, 2)
24 | RandomSleep(700, 800)
25 | }
--------------------------------------------------------------------------------
/components/isLimited.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 判断是否上限
3 | */
4 | isLimited() {
5 | activateZZZ()
6 | ; stepLog("【step9】判断是否上限")
7 | ; 结算界面判断是否达到周/日上限
8 | static search() {
9 | if (setting.mode = 'YeJi' && (setting.subLoopMode != 1)) {
10 | return PixelSearchPre(&FoundX, &FoundY, c.空洞.结算.零号业绩*)
11 | } else {
12 | return PixelSearchPre(&FoundX, &FoundY, c.拿命验收.丁尼*)
13 | }
14 | }
15 | loop (20) {
16 | if (search()) {
17 | Sleep(500)
18 | if (search()) {
19 | return false
20 | }
21 | }
22 | Sleep(300)
23 | }
24 | return true
25 | }
--------------------------------------------------------------------------------
/components/reachEnd.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 抵达终点
3 | */
4 | reachEnd(step := 3) {
5 | activateZZZ()
6 | stepLog("【step" step "】前往终点")
7 |
8 | ; 判断地图类型
9 | mode := judgeMap()
10 | if (!mode) {
11 | return false
12 | }
13 |
14 | if (mode = 1) {
15 | above()
16 | } else {
17 | below()
18 | }
19 | ; 加载动画
20 | RandomSleep(1000, 1200)
21 | return true
22 |
23 | /** 判断地图类型 */
24 | static judgeMap() {
25 | mode := 0
26 | loop (60) {
27 | ; 对指定区域进行RGB检测
28 | if (PixelSearchPre(&FoundX, &FoundY, c.空洞.1.右上终点*)) {
29 | mode := 1
30 | break
31 | }
32 | ; 对指定区域进行RGB检测
33 | if (PixelSearchPre(&FoundX, &FoundY, c.空洞.1.右下终点*)) {
34 | mode := 2
35 | break
36 | }
37 | Sleep(100)
38 | }
39 | return mode
40 | }
41 |
42 | /** 使用炸弹 */
43 | static bomb() {
44 | if (setting.bombMode = 1) {
45 | Send("{r Down}")
46 | RandomSleep(800, 1000)
47 | Send("{r Up}")
48 | } else if (setting.bombMode = 2) {
49 | Press("r")
50 | }
51 | RandomSleep(2200, 2400)
52 | }
53 |
54 | /** 右上终点 */
55 | static above() {
56 | ; 使用炸弹
57 | bomb()
58 | ; 向右移动
59 | Press("d")
60 | RandomSleep(1300, 1500)
61 | ; 选择铭徽
62 | MingHui()
63 | RandomSleep(1000, 1200)
64 | ; 进入终点
65 | Press("d")
66 | Press("w", 2)
67 | }
68 |
69 | /** 右下终点 */
70 | static below() {
71 | ; 代理人接应窗口
72 | Press('s')
73 | RandomSleep(1000, 1200)
74 | Press("Space", 4)
75 | RandomSleep(800, 1000)
76 | ; 开启战斗自动躲避红光后,选择取消接应
77 | if (setting.isAutoDodge) {
78 | Press('2', 2)
79 | RandomSleep(800, 900)
80 | Press('3', 3)
81 | RandomSleep(800, 900)
82 | ; 通过Esc确认
83 | Press('Escape')
84 | } else {
85 | ; 接应
86 | Press('1', 2)
87 | RandomSleep(800, 900)
88 | Press('1', 2)
89 | RandomSleep(800, 900)
90 | pixelSearchAndClick(c.空洞.确认*)
91 | }
92 | RandomSleep(1800, 2000)
93 | ; 使用炸弹
94 | bomb()
95 | Press('s')
96 | RandomSleep(1800, 2000)
97 | ; 选择铭徽
98 | MingHui()
99 | RandomSleep(800, 1200)
100 | ; 进入终点
101 | Press('d', 2)
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/components/recogLocation.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * 识别所处界面
3 | *
4 | * 返回值:
5 | * - 0:未知界面
6 | * - 1:角色操作界面
7 | * - 2:零号空洞关卡选择界面
8 | * - 3:HDD关卡选择界面
9 | */
10 | recogLocation(loopTimes := 30) {
11 | activateZZZ()
12 |
13 | /** 通过特殊定位点判断所处界面 */
14 | patterns := [[
15 | c.角色操作.M,
16 | c.角色操作.Q,
17 | [c.角色操作.T, c.角色操作.Tab]
18 | ], [
19 | c.零号选择.资质考核,
20 | c.零号选择.旧都列车,
21 | c.零号选择.施工废墟
22 | ], [
23 | c.拿命验收.返回键,
24 | c.拿命验收.难度格,
25 | c.拿命验收.推荐等级
26 | ]]
27 | static judge(patterns) {
28 | UC:
29 | for (pattern in patterns) {
30 | if (pattern[1] is Array) {
31 | for (p in pattern) {
32 | if (PixelSearchPre(&FoundX, &FoundY, p*)) {
33 | continue UC
34 | }
35 | }
36 | } else if (PixelSearchPre(&FoundX, &FoundY, pattern*)) {
37 | continue
38 | }
39 | return false
40 | }
41 | return true
42 | }
43 | page := 0
44 | UC:
45 | loop (loopTimes) {
46 | for (i, pattern in patterns) {
47 | if (judge(pattern)) {
48 | page := i
49 | break UC
50 | }
51 | }
52 | Sleep(100)
53 | }
54 | return page
55 | }
--------------------------------------------------------------------------------
/components/refuse.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 拒绝好意
3 | */
4 | refuse(step := 2) {
5 | activateZZZ()
6 | stepLog("【step" step "】拒绝好意")
7 |
8 | ; 开局铭徽(如果有)
9 | MingHui(true, 15)
10 | ; 对话
11 | Press("Space", 10)
12 | ; 拒绝
13 | Press('2', 2)
14 | ; 对话
15 | Press("Space", 10)
16 | ; 加载动画
17 | RandomSleep(200, 251)
18 | }
--------------------------------------------------------------------------------
/components/saveBank.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * - 存银行
3 | */
4 | saveBank(step := 7, gainMode := 0) {
5 | activateZZZ()
6 | stepLog("【step" step "】存银行")
7 |
8 | static reachDoor(gainMode) {
9 | if (gainMode = 2) { ; 只存银行,直接去银行
10 | Press('a')
11 | Press('w', 2)
12 | Press('a', 2)
13 | } else { ; 业绩到银行
14 | Press('s')
15 | Press('a', 3)
16 | Press('s')
17 | Press('a', 3)
18 | Press('w')
19 | Press('a')
20 | }
21 | }
22 |
23 | ; 进检疫门
24 | reachDoor(gainMode)
25 | RandomSleep(2400, 2500)
26 | ; 强行闯入
27 | Press("2")
28 | ; 对话
29 | Press('Space', 12)
30 | ; 确认侵蚀
31 | ; pixelSearchAndClick(c.空洞.确认*)
32 | Press('Escape')
33 | RandomSleep(3800, 4000)
34 | Press('a', 3)
35 | ; 对话
36 | Press('Space', 16)
37 | ; 存款
38 | loop (5) {
39 | Press('1')
40 | Press('Space', 2)
41 | ; 如果弹出选择铭徽界面
42 | MingHui(true, 3)
43 | Press('Space', 2)
44 | }
45 | ; 最后判断一下是否有铭徽选择框
46 | RandomSleep()
47 | MingHui(true, 5)
48 | }
--------------------------------------------------------------------------------
/utils/Config.ahk:
--------------------------------------------------------------------------------
1 | /** 配置 */
2 | Class Config {
3 |
4 | fightModeArr := ['通用', '艾莲', '星见雅']
5 |
6 | /** 默认配置 */
7 | oriSetting := {
8 | /** 业绩或丁尼 'YeJi' or 'Denny' */
9 | mode: 'YeJi',
10 | /** 炸弹使用:长按1,点击2 */
11 | bombMode: 1,
12 | /** 快捷手册 */
13 | handbook: 'F2',
14 | /** 休眠系数,加载动画等待时长在原基础上的倍率,可通过修改该值延长/缩短全局等待时长 */
15 | sleepCoefficient: 1.0,
16 | /** 允许总的异常时重试的次数 */
17 | retryTimes: 3,
18 | /** 颜色搜索允许的RGB值容差 */
19 | variation: 60,
20 | /** 战斗模式 */
21 | fightMode: 3,
22 | /** 刷取模式:0:全都要;1:只要业绩;2:只存银行 */
23 | gainMode: 0,
24 | /** 循环模式:0:业绩上限;-1:无限循环;正整数:刷取指定次数 */
25 | loopMode: 0,
26 | /** 异常处理 */
27 | errHandler: true,
28 | /** 是否开启步骤信息弹窗 */
29 | isStepLog: false,
30 | /** 刷完后是否自动关闭游戏/电脑:0:禁用;1:关闭游戏;2:关闭电脑 */
31 | isAutoClose: 0,
32 | /** 战斗时是否自动识别红光闪避 */
33 | isAutoDodge: false,
34 | /** 拿命验收二号位角色:1:通用;2:艾莲;3:星见雅 */
35 | fightModeDenny: 3,
36 | /** 循环模式:0:丁尼上限;-1:无限循环;正整数:刷取指定次数 */
37 | loopModeDenny: 0,
38 | /** 拿命验收破坏箱子前视角向右转动的坐标,调整该值以确保视角对齐NPC */
39 | rotateCoords: 200,
40 | /** 子循环模式:0:业绩上限;1:丁尼上限;2:全部上限 */
41 | subLoopMode: 0,
42 | GamePath: ''
43 | }
44 |
45 | __New() {
46 | /** 配置文件路径 */
47 | this.iniFile := A_MyDocuments "\autoZZZ.ini"
48 | this.section1 := "Settings"
49 | this.section2 := "Statistics"
50 | this.section3 := "StatisticsDenny"
51 | /** 业绩刷取统计数据 */
52 | this.statistics := []
53 | /** 丁尼刷取统计数据 */
54 | this.statisticsDenny := []
55 | /** 设置项 */
56 | this.setting := this.oriSetting.Clone()
57 | this.Load()
58 | this.watch()
59 | this.__Set := this.__Set__.Bind(this)
60 | }
61 |
62 | __Get(Key, *) {
63 | return this.setting.%Key%
64 | }
65 |
66 | __Set__(thisObj, Key, Param, Value?) {
67 | if (IsSet(Value)) {
68 | if (this.setting.HasProp(Key)) {
69 | this.setting.%Key% := Value
70 | try {
71 | IniWrite(Value, this.iniFile, this.section1, Key)
72 | }
73 | }
74 | }
75 | }
76 |
77 | Load(*) {
78 | this.LoadSetting()
79 | this.LoadStatistics()
80 | this.LoadStatisticsDenny()
81 | }
82 |
83 | Save(*) {
84 | this.SaveSetting()
85 | this.SaveStatistics()
86 | this.SaveStatisticsDenny()
87 | try {
88 | IniWrite(Version, this.iniFile, 'Version', 'Version')
89 | }
90 | }
91 |
92 | isFirst(key) {
93 | static lastVer := IniRead(this.iniFile, 'Version', 'Version', 'v1.0.0')
94 | static set := {}
95 | result := true
96 | if (lastVer != Version && !set.HasProp(key)) {
97 | set.%key% := true
98 | } else {
99 | result := +IniRead(this.iniFile, 'isFirst', key, true)
100 | }
101 | if (result) {
102 | try {
103 | IniWrite(false, this.iniFile, 'isFirst', key)
104 | }
105 | }
106 | return result
107 | }
108 |
109 | LoadSetting(*) {
110 | if (FileExist(this.iniFile)) {
111 | try {
112 | for (key, value in this.setting.OwnProps()) {
113 | value := IniRead(this.iniFile, this.section1, key, value)
114 | if (IsInteger(this.setting.%key%)) {
115 | value := Integer(value)
116 | } else if (IsFloat(this.setting.%key%)) {
117 | value := Round(value, 2)
118 | }
119 | this.setting.%key% := value
120 | }
121 | }
122 | }
123 | }
124 |
125 | LoadStatistics(*) {
126 | if (FileExist(this.iniFile)) {
127 | try {
128 | Loop {
129 | time := IniRead(this.iniFile, this.section2, "time" A_Index, "")
130 | fightDuration := IniRead(this.iniFile, this.section2, "fightDuration" A_Index, 0)
131 | duration := IniRead(this.iniFile, this.section2, "duration" A_Index, 0)
132 | if (time = "" && duration = 0 && fightDuration = 0) {
133 | break
134 | }
135 | if (time && duration) {
136 | this.statistics.Push({ time: time, fightDuration: Integer(fightDuration), duration: Integer(duration) })
137 | }
138 | }
139 | }
140 | }
141 | }
142 |
143 | LoadStatisticsDenny(*) {
144 | if (FileExist(this.iniFile)) {
145 | try {
146 | Loop {
147 | time := IniRead(this.iniFile, this.section3, "time" A_Index, "")
148 | duration := IniRead(this.iniFile, this.section3, "duration" A_Index, 0)
149 | if (time = "" && duration = 0) {
150 | break
151 | }
152 | if (time && duration) {
153 | this.statisticsDenny.Push({ time: time, duration: Integer(duration) })
154 | }
155 | }
156 | }
157 | }
158 | }
159 |
160 | SaveSetting(*) {
161 | try {
162 | for (key, value in this.setting.OwnProps()) {
163 | IniWrite(value, this.iniFile, this.section1, key)
164 | }
165 | }
166 | }
167 |
168 | SaveStatistics(*) {
169 | try {
170 | IniDelete(this.iniFile, this.section2) ; 清空
171 | Loop (this.statistics.Length) {
172 | item := this.statistics[A_Index]
173 | IniWrite(item.time, this.iniFile, this.section2, "time" A_Index)
174 | IniWrite(item.fightDuration, this.iniFile, this.section2, "fightDuration" A_Index)
175 | IniWrite(item.duration, this.iniFile, this.section2, "duration" A_Index)
176 | }
177 | }
178 | }
179 |
180 | SaveStatisticsDenny(*) {
181 | try {
182 | IniDelete(this.iniFile, this.section3) ; 清空
183 | Loop (this.statisticsDenny.Length) {
184 | item := this.statisticsDenny[A_Index]
185 | IniWrite(item.time, this.iniFile, this.section3, "time" A_Index)
186 | IniWrite(item.duration, this.iniFile, this.section3, "duration" A_Index)
187 | }
188 | }
189 | }
190 |
191 | newStatistics(time, fightDuration, duration) {
192 | this.statistics.Push({ time: time, fightDuration: fightDuration, duration: duration })
193 | this.SaveStatistics()
194 | p.newDetail(time, fightDuration, duration)
195 | }
196 |
197 | newStatisticsDenny(time, duration) {
198 | this.statisticsDenny.Push({ time: time, duration: duration })
199 | this.SaveStatisticsDenny()
200 | p.newDetailDenny(time, duration)
201 | }
202 |
203 | watch() {
204 | OnExit(this.Save.Bind(this))
205 | }
206 |
207 | }
--------------------------------------------------------------------------------
/utils/Controller.ahk:
--------------------------------------------------------------------------------
1 | /** 控制器 */
2 | class Controller {
3 | __New() {
4 | /** 正在刷取 */
5 | this.ing := false
6 | /** 下次退出 */
7 | this.nextExit := false
8 | this.startTime := 0
9 | this.finishTime := 0
10 | this.startFightTime := 0
11 | this.fightDuration := 0
12 | this.continuous := 0
13 | }
14 |
15 | /** 开始刷取 */
16 | start() {
17 | this.ing := true
18 | ; 时长统计
19 | this.startTime := A_Now
20 | }
21 |
22 | /** 战斗开始 */
23 | startFight() {
24 | this.startFightTime := A_Now
25 | }
26 |
27 | /** 战斗完成 */
28 | finishFight() {
29 | this.fightDuration := DateDiff(A_Now, this.startFightTime, "Seconds")
30 | this.startFightTime := 0
31 | }
32 |
33 | /** 刷取终止 */
34 | stop() {
35 | this.nextExit := false
36 | this.ing := false
37 | this.startTime := 0
38 | this.finishTime := 0
39 | this.continuous := 0
40 | }
41 |
42 | /** 完成刷取 */
43 | finish() {
44 | this.finishTime := A_Now
45 | duration := DateDiff(this.finishTime, this.startTime, "Seconds")
46 | if (setting.mode = 'YeJi') {
47 | setting.newStatistics(FormatTime(this.startTime, "M-dd HH:mm:ss"), this.fightDuration, duration)
48 | this.fightDuration := 0
49 | } else {
50 | setting.newStatisticsDenny(FormatTime(this.startTime, "M-dd HH:mm:ss"), duration)
51 | }
52 | this.startTime := 0
53 | this.finishTime := 0
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/utils/CoordsData.ahk:
--------------------------------------------------------------------------------
1 | /** 坐标数据 */
2 | class CoordsData {
3 | /** 预设坐标数据 */
4 | data := {
5 | 21x9: {
6 | mode: "21x9",
7 | ratio: 2.33,
8 | 角色操作: {
9 | M: [1800, 880, 1821, 908, 0xffffff],
10 | Q: [1799, 1021, 1821, 1046, 0xffffff],
11 | T: [355, 35, 375, 60, 0xffffff],
12 | Tab: [1825, 116, 1865, 141, 0xffffff],
13 | 取消正在处理: [763, 608, 777, 644, 0xcb0000]
14 | },
15 | 快捷手册: {
16 | 挑战_灰色: [1230, 130, 1280, 170, 0xbbbbbb],
17 | 挑战_黑色: [1230, 130, 1280, 170, 1256, 151, 0x000000],
18 | 零号空洞: [445, 454, 535, 489, 491, 477, 0xaaaaaa],
19 | 前往: [1318, 420, 1365, 454, 1341, 433, 0xffffff],
20 | 传送: [982, 601, 1003, 647, 1083, 627, 0x00cc0d]
21 | },
22 | 零号选择: {
23 | 资质考核: [419, 721, 469, 749, 0x78cc00],
24 | 旧都列车: [676, 824, 809, 856, 742, 788, 0x78cc00],
25 | 施工废墟: [750, 410, 846, 435, 0x78cc00]
26 | },
27 | 旧都列车: {
28 | 前线: [1683, 340, 1829, 412, 1753, 377, 0xbff700],
29 | 下一步: [1723, 1010, 1791, 1046, 1757, 1025, 0xffffff]
30 | },
31 | 空洞: {
32 | 确认: [877, 605, 889, 660, 971, 626, 0x00cc0d],
33 | 确定: [938, 776, 985, 850, 960, 791, 0xffffff, 0.5],
34 | 1: {
35 | 右上终点: [1211, 210, 1290, 240, 0xf63d49],
36 | 右下终点: [1211, 860, 1290, 920, 0xe8202a],
37 | 战斗: {
38 | 开始: [200, 50, 280, 90, 0x00ff44],
39 | 计时: [1150, 400, 1300, 650, 0xffc300],
40 | 确定键: [1050, 850, 1200, 1050, 0xffffff],
41 | 确定绿勾: [900, 850, 1050, 1050, 0x00cc0d]
42 | }
43 | },
44 | 2: {
45 | 选项框: [1497, 488, 1522, 770],
46 | 降压准备: [876, 713, 888, 770, 973, 740, 0x00cc0d]
47 | },
48 | 退出副本: {
49 | 放弃: [1663, 1003, 1680, 1048, 1768, 1027, 0xcb0000],
50 | 确认: [991, 605, 1002, 651, 1084, 627, 0x00cc0d]
51 | },
52 | 结算: {
53 | 零号业绩: [1480, 614, 1553, 649, 0xffb500, 0.5],
54 | 完成: [1733, 1015, 1775, 1044, 1753, 1026, 0xffffff]
55 | }
56 | },
57 | 拿命验收: {
58 | xmin: 1325,
59 | xmax: 1350,
60 | 返回键: [44, 20, 118, 79, 0xc01c00],
61 | 难度格: [870, 803, 888, 824, 0xe74706],
62 | 推荐等级: [70, 747, 149, 848, 0xcb3f4a],
63 | 重新开始: [1425, 1011, 1441, 1045, 1532, 1029, 0xff5d00],
64 | 丁尼: [1480, 614, 1553, 649, 0x00a9ff, 0.5]
65 | },
66 | 地图: {
67 | RandomPlay: [930, 440, 955, 475, 0xedd145, 0.5],
68 | 2F: [1235, 945, 1278, 997, 0x793023],
69 | HDD: [553, 971, 564, 985, 0x386f89],
70 | }
71 | },
72 | 16x9: {
73 | mode: "16x9",
74 | ratio: 1.78,
75 | 快捷手册: {
76 | 挑战_灰色: [1330, 130, 1400, 170, 0xbbbbbb],
77 | 挑战_黑色: [1330, 130, 1400, 170, 1359, 144, 0x000000],
78 | 零号空洞: [275, 463, 380, 490, 330, 475, 0xaaaaaa],
79 | 前往: [1443, 419, 1508, 453, 1475, 438, 0xffffff],
80 | 传送: [1000, 600, 1100, 640, 1125, 620, 0x00cc0d]
81 | },
82 | 角色操作: {
83 | M: [1170, 880, 1800, 920, 0xffffff],
84 | Q: [1770, 1020, 1790, 1050, 0xffffff],
85 | T: [480, 35, 510, 60, 0xffffff],
86 | Tab: [1800, 100, 1850, 130, 0xffffff],
87 | 取消正在处理: [694, 605, 715, 646, 0xcb0000]
88 | },
89 | 零号选择: {
90 | 资质考核: [240, 700, 260, 750, 0x78cc00],
91 | 旧都列车: [580, 780, 600, 850, 660, 770, 0x78cc00],
92 | 施工废墟: [680, 410, 700, 450, 0x78cc00]
93 | },
94 | 旧都列车: {
95 | 前线: [1600, 349, 1800, 420, 1706, 374, 0xbff700],
96 | 下一步: [1660, 960, 1740, 1040, 1707, 1028, 0xffffff]
97 | },
98 | 空洞: {
99 | 确认: [840, 620, 1000, 810, 975, 630, 0x00cc0d],
100 | 确定: [930, 750, 985, 850, 960, 790, 0xffffff, 0.5],
101 | 1: {
102 | 右上终点: [1300, 210, 1400, 251, 0xf63d49],
103 | 右下终点: [1310, 850, 1420, 930, 0xe8202a],
104 | 战斗: {
105 | 开始: [257, 41, 380, 96, 0x00ff44],
106 | 计时: [1230, 430, 1370, 610, 0xffc300],
107 | 确定键: [1130, 840, 1190, 1020, 0xffffff],
108 | 确定绿勾: [1000, 840, 1040, 1020, 0x00cc0d]
109 | }
110 | },
111 | 2: {
112 | 选项框: [1360, 490, 1390, 763],
113 | 降压准备: [850, 700, 870, 780, 960, 735, 0x00cc0d]
114 | },
115 | 退出副本: {
116 | 放弃: [1580, 960, 1600, 1050, 1712, 1026, 0xcb0000],
117 | 确认: [1000, 600, 1100, 640, 1125, 620, 0x00cc0d]
118 | },
119 | 结算: {
120 | 零号业绩: [1320, 600, 1440, 666, 0xffb500, 0.5],
121 | 完成: [1670, 970, 1730, 1040, 1700, 1027, 0xffffff]
122 | }
123 | },
124 | 拿命验收: {
125 | xmin: 1125,
126 | xmax: 1150,
127 | 返回键: [55, 19, 153, 77, 0xc01c00],
128 | 难度格: [511, 803, 535, 827, 0xe74706],
129 | 推荐等级: [99, 747, 201, 773, 0xcb3f4a],
130 | 重新开始: [1258, 1010, 1273, 1046, 1397, 1027, 0xff5d00],
131 | 丁尼: [1320, 600, 1440, 666, 0x00a9ff, 0.5]
132 | },
133 | 地图: {
134 | RandomPlay: [927, 447, 947, 465, 0xedd145, 0.5],
135 | 2F: [1334, 944, 1369, 1006, 0x793023],
136 | HDD: [415, 973, 430, 984, 0x386f89],
137 | }
138 | },
139 | 16x10: {
140 | mode: "16x10", ; 2560x1600
141 | ratio: 1.60,
142 | 角色操作: {
143 | M: [1170, 880, 1800, 920, 0xffffff],
144 | Q: [1770, 1020, 1790, 1050, 0xffffff],
145 | T: [480, 30, 500, 55, 0xffffff],
146 | Tab: [1800, 100, 1850, 130, 0xffffff],
147 | 取消正在处理: [690, 600, 720, 640, 0xcb0000]
148 | },
149 | 快捷手册: {
150 | 挑战_灰色: [1330, 170, 1400, 210, 0xbbbbbb],
151 | 挑战_黑色: [1330, 170, 1400, 210, 1361, 186, 0x000000],
152 | 零号空洞: [275, 463, 380, 490, 330, 475, 0xaaaaaa],
153 | 前往: [1447, 433, 1507, 461, 1473, 448, 0xffffff],
154 | 传送: [1000, 600, 1100, 640, 1125, 620, 0x00cc0d]
155 | },
156 | 零号选择: {
157 | 资质考核: [240, 700, 260, 750, 0x78cc00],
158 | 旧都列车: [580, 780, 600, 850, 660, 770, 0x78cc00],
159 | 施工废墟: [680, 410, 700, 450, 0x78cc00]
160 | },
161 | 旧都列车: {
162 | 前线: [1600, 349, 1800, 420, 1706, 374, 0xbff700],
163 | 下一步: [1660, 960, 1740, 1040, 1707, 1028, 0xffffff]
164 | },
165 | 空洞: {
166 | 确认: [840, 620, 1000, 810, 975, 630, 0x00cc0d],
167 | 确定: [930, 750, 985, 850, 960, 790, 0xffffff, 0.5],
168 | 1: {
169 | 右上终点: [1300, 220, 1400, 280, 0xf63d49],
170 | 右下终点: [1300, 840, 1400, 920, 0xe8202a],
171 | 战斗: {
172 | 开始: [261, 46, 369, 72, 0x00ff44],
173 | 计时: [1230, 430, 1370, 610, 0xffc300],
174 | 确定键: [1130, 840, 1190, 1020, 0xffffff],
175 | 确定绿勾: [1000, 840, 1040, 1020, 0x00cc0d]
176 | }
177 | },
178 | 2: {
179 | 选项框: [1360, 490, 1390, 763],
180 | 降压准备: [850, 700, 870, 750, 970, 720, 0x00cc0d]
181 | },
182 | 退出副本: {
183 | 放弃: [1580, 960, 1600, 1050, 1712, 1026, 0xcb0000],
184 | 确认: [1000, 600, 1100, 640, 1125, 620, 0x00cc0d]
185 | },
186 | 结算: {
187 | 零号业绩: [1320, 600, 1440, 666, 0xffb500, 0.5],
188 | 完成: [1670, 970, 1730, 1040, 1700, 1027, 0xffffff]
189 | }
190 | },
191 | 拿命验收: {
192 | xmin: 1125,
193 | xmax: 1150,
194 | 返回键: [66, 74, 152, 124, 0xc01c00],
195 | 难度格: [507, 778, 533, 797, 0xe74706],
196 | 推荐等级: [101, 730, 200, 815, 0xcb3f4a],
197 | 重新开始: [1256, 964, 1274, 996, 1399, 981, 0xff5d00],
198 | 丁尼: [1320, 600, 1440, 666, 0x00a9ff, 0.5]
199 | },
200 | 地图: {
201 | RandomPlay: [927, 454, 944, 472, 0xedd145, 0.5],
202 | 2F: [1337, 905, 1383, 953, 0x793023],
203 | HDD: [412, 929, 430, 940, 0x386f89],
204 | }
205 | },
206 | 4x3: {
207 | mode: "4x3", ; 2560x1920
208 | ratio: 1.33,
209 | 角色操作: {
210 | M: [1769, 935, 1787, 948, 0xffffff],
211 | Q: [1768, 1039, 1785, 1052, 0xffffff],
212 | T: [480, 25, 500, 50, 0xffffff],
213 | Tab: [1804, 90, 1846, 104, 0xffffff],
214 | 取消正在处理: [690, 590, 720, 620, 0xcb0000]
215 | },
216 | 快捷手册: {
217 | 挑战_灰色: [1330, 230, 1400, 260, 0xbbbbbb],
218 | 挑战_黑色: [1330, 230, 1400, 260, 1359, 246, 0x000000],
219 | 零号空洞: [277, 481, 378, 498, 329, 492, 0xaaaaaa],
220 | 前往: [1445, 450, 1510, 472, 1475, 462, 0xffffff],
221 | 传送: [998, 587, 1015, 619, 1130, 604, 0x00cc0d]
222 | },
223 | 零号选择: {
224 | 资质考核: [242, 681, 294, 692, 0x78cc00],
225 | 旧都列车: [589, 754, 741, 768, 662, 730, 0x78cc00],
226 | 施工废墟: [678, 444, 806, 460, 0x78cc00]
227 | },
228 | 旧都列车: {
229 | 前线: [1610, 391, 1798, 445, 1700, 415, 0xbff700],
230 | 下一步: [1667, 896, 1737, 912, 1706, 903, 0xffffff]
231 | },
232 | 空洞: {
233 | 确认: [850, 605, 865, 636, 977, 621, 0x00cc0d],
234 | 确定: [926, 706, 993, 780, 960, 731, 0xffffff, 0.5],
235 | 1: {
236 | 右上终点: [1290, 290, 1400, 340, 0xf63d49],
237 | 右下终点: [1290, 770, 1400, 830, 0xe8202a],
238 | 战斗: {
239 | 开始: [261, 39, 372, 60, 0x00ff44],
240 | 计时: [1223, 385, 1379, 590, 0xffc300],
241 | 确定键: [1131, 780, 1208, 940, 0xffffff],
242 | 确定绿勾: [1022, 780, 1054, 940, 0x00cc0d]
243 | }
244 | },
245 | 2: {
246 | 选项框: [1356, 501, 1388, 709],
247 | 降压准备: [848, 672, 863, 710, 980, 690, 0x00cc0d]
248 | },
249 | 退出副本: {
250 | 放弃: [1578, 890, 1602, 919, 1721, 907, 0xcb0000],
251 | 确认: [1001, 592, 1017, 620, 1130, 602, 0x00cc0d]
252 | },
253 | 结算: {
254 | 零号业绩: [1334, 606, 1433, 624, 0xffb500, 0.5],
255 | 完成: [1673, 898, 1724, 916, 1699, 905, 0xffffff]
256 | }
257 | },
258 | 拿命验收: {
259 | xmin: 1125,
260 | xmax: 1150,
261 | 返回键: [61, 149, 154, 191, 0xc01c00],
262 | 难度格: [510, 736, 534, 753, 0xe74706],
263 | 推荐等级: [97, 698, 199, 768, 0xcb3f4a],
264 | 重新开始: [1256, 890, 1276, 922, 1399, 904, 0xff5d00],
265 | 丁尼: [1334, 606, 1433, 624, 0x00a9ff, 0.5]
266 | },
267 | 地图: {
268 | RandomPlay: [923, 468, 946, 483, 0xedd145, 0.5],
269 | 2F: [1332, 845, 1379, 883, 0x793023],
270 | HDD: [412, 864, 427, 874, 0x386f89],
271 | }
272 | },
273 | 5x4: {
274 | mode: "5x4", ; 2560x2048
275 | ratio: 1.25,
276 | 角色操作: {
277 | M: [1768, 943, 1787, 957, 0xffffff],
278 | Q: [1769, 1042, 1786, 1054, 0xffffff],
279 | T: [480, 20, 510, 50, 0xffffff],
280 | Tab: [1802, 84, 1847, 97, 0xffffff],
281 | 取消正在处理: [700, 580, 720, 620, 0xcb0000]
282 | },
283 | 快捷手册: {
284 | 挑战_灰色: [1330, 250, 1390, 280, 0xbbbbbb],
285 | 挑战_黑色: [1330, 250, 1390, 280, 1359, 263, 0x000000],
286 | 零号空洞: [273, 483, 383, 503, 326, 493, 0xaaaaaa],
287 | 前往: [1445, 457, 1505, 477, 1474, 468, 0xffffff],
288 | 传送: [986, 589, 1016, 612, 1127, 598, 0x00cc0d]
289 | },
290 | 零号选择: {
291 | 资质考核: [240, 670, 295, 686, 0x78cc00],
292 | 旧都列车: [581, 740, 746, 755, 662, 715, 0x78cc00],
293 | 施工废墟: [681, 449, 804, 465, 0x78cc00]
294 | },
295 | 旧都列车: {
296 | 前线: [1608, 401, 1796, 448, 1696, 425, 0xbff700],
297 | 下一步: [1669, 872, 1737, 890, 1700, 881, 0xffffff]
298 | },
299 | 空洞: {
300 | 确认: [848, 600, 863, 628, 970, 610, 0x00cc0d],
301 | 确定: [935, 710, 989, 750, 960, 718, 0xffffff, 0.5],
302 | 1: {
303 | 右上终点: [1300, 300, 1400, 350, 0xf63d49],
304 | 右下终点: [1300, 750, 1400, 800, 0xe8202a],
305 | 战斗: {
306 | 开始: [260, 36, 372, 56, 0x00ff44],
307 | 计时: [1234, 400, 1371, 750, 0xffc300],
308 | 确定键: [1130, 600, 1190, 950, 0xffffff],
309 | 确定绿勾: [1100, 600, 1040, 950, 0x00cc0d]
310 | }
311 | },
312 | 2: {
313 | 选项框: [1358, 503, 1388, 696],
314 | 降压准备: [850, 664, 870, 700, 979, 680, 0x00cc0d]
315 | },
316 | 退出副本: {
317 | 放弃: [1579, 871, 1599, 895, 1719, 880, 0xcb0000],
318 | 确认: [1003, 592, 1018, 615, 1124, 600, 0x00cc0d]
319 | },
320 | 结算: {
321 | 零号业绩: [1342, 594, 1427, 626, 0xffb500, 0.5],
322 | 完成: [1676, 872, 1727, 891, 1700, 882, 0xffffff]
323 | }
324 | },
325 | 拿命验收: {
326 | xmin: 1125,
327 | xmax: 1150,
328 | 返回键: [65, 176, 150, 215, 0xc01c00],
329 | 难度格: [510, 725, 532, 740, 0xe74706],
330 | 推荐等级: [98, 687, 200, 756, 0xcb3f4a],
331 | 重新开始: [1259, 869, 1274, 897, 1400, 882, 0xff5d00],
332 | 丁尼: [1342, 594, 1427, 626, 0x00a9ff, 0.5]
333 | },
334 | 地图: {
335 | RandomPlay: [928, 473, 946, 488, 0xedd145, 0.5],
336 | 2F: [1336, 826, 1379, 863, 0x793023],
337 | HDD: [412, 844, 428, 851, 0x386f89],
338 | }
339 | },
340 | ; 模板: {
341 | ; 角色操作: {
342 | ; M: [0xffffff], ; 4个所需坐标参数,左上XY、右下XY
343 | ; Q: [0xffffff],
344 | ; T: [0xffffff],
345 | ; Tab: [0xffffff],
346 | ; 取消正在处理: [0xcb0000]
347 | ; },
348 | ; 快捷手册: {
349 | ; 挑战_灰色: [0xbbbbbb],
350 | ; 挑战_黑色: [ 0x000000], ; 6个所需坐标参数,左上XY、右下XY、默认XY
351 | ; 零号空洞: [ 0xaaaaaa],
352 | ; 前往: [ 0xffffff],
353 | ; 传送: [ 0x00cc0d]
354 | ; },
355 | ; 零号选择: {
356 | ; 资质考核: [0x78cc00],
357 | ; 旧都列车: [ 0x78cc00],
358 | ; 施工废墟: [0x78cc00]
359 | ; },
360 | ; 旧都列车: {
361 | ; 前线: [ 0xbff700],
362 | ; 下一步: [ 0xffffff]
363 | ; },
364 | ; 空洞: {
365 | ; 确认: [ 0x00cc0d], ; 有绿勾,确认侵蚀、代理人
366 | ; 确定: [ 0xffffff, 0.5], ; 无绿勾,铭徽确定、催化、选择
367 | ; 1: {
368 | ; 右上终点: [0xf63d49],
369 | ; 右下终点: [0xe8202a],
370 | ; 战斗: {
371 | ; 开始: [0x00ff44],
372 | ; 计时: [0xffc300], ; Y±180
373 | ; 确定键: [0xffffff],
374 | ; 确定绿勾: [0x00cc0d]
375 | ; }
376 | ; },
377 | ; 2: {
378 | ; 选项框: [],
379 | ; 降压准备: [ 0x00cc0d]
380 | ; },
381 | ; 退出副本: {
382 | ; 放弃: [ 0xcb0000],
383 | ; 确认: [ 0x00cc0d]
384 | ; },
385 | ; 结算: {
386 | ; 零号业绩: [0xffb500, 0.5],
387 | ; 完成: [ 0xffffff]
388 | ; }
389 | ; },
390 | ; 拿命验收: {
391 | ; xmin: 1125, ; 圆心搜索范围
392 | ; xmax: 1150,
393 | ; 返回键: [0xc01c00],
394 | ; 难度格: [0xe74706],
395 | ; 推荐等级: [0xcb3f4a],
396 | ; 重新开始: [ 0xff5d00],
397 | ; 丁尼: [0x00a9ff, 0.5]
398 | ; },
399 | ; 地图: {
400 | ; RandomPlay: [0xedd145, 0.5], ; 播放三角形
401 | ; 2F: [0x793023], ; 右砖
402 | ; HDD: [0x386f89], ; 第二个D
403 | ; }
404 | ; }
405 | }
406 |
407 | __New() {
408 | this.c := {}
409 | this.reset()
410 | this.watch()
411 | }
412 |
413 | __Get(Key, *) {
414 | return this.c.%Key%
415 | }
416 |
417 | reset(*) {
418 | windowed := false
419 | w := A_ScreenWidth, h := A_ScreenHeight
420 | if (WinExist(ZZZ)) {
421 | setting.GamePath := WinGetProcessPath(ZZZ)
422 | WinGetClientPos(, , &w, &h, ZZZ)
423 | if (w != A_ScreenWidth || h != A_ScreenHeight) {
424 | windowed := true
425 | }
426 | }
427 | if (this.c.HasProp('width') && w = this.c.width && h = this.c.height && this.c.windowed = windowed) {
428 | return
429 | }
430 | actualRatio := Round(w / h, 2)
431 | bestRatio := {
432 | diff: 251,
433 | key: "16x9"
434 | }
435 | for (key, value in this.data.OwnProps()) {
436 | designRatio := value.ratio
437 | ratioDiff := Abs(designRatio - actualRatio)
438 | if (ratioDiff < bestRatio.diff) {
439 | bestRatio.diff := ratioDiff
440 | bestRatio.key := key
441 | }
442 | if (ratioDiff = 0) {
443 | break
444 | }
445 | }
446 | this.c := this.data.%bestRatio.key%
447 | this.c.compatible := bestRatio.diff > 0.07
448 | this.c.windowed := windowed
449 | this.c.width := w, this.c.height := h
450 | global FoundDennyFuben := false ; 重置丁尼副本Found标记
451 | }
452 |
453 | watch() {
454 | OnMessage(0x7E, (*) => SetTimer(this.reset.Bind(this), -3000))
455 | }
456 |
457 | }
--------------------------------------------------------------------------------
/utils/Panel.ahk:
--------------------------------------------------------------------------------
1 | /** 面板 */
2 | class Panel {
3 |
4 | __New() {
5 | this.CP := 0
6 | this.SP := 0
7 | this.UP := 0
8 | this.sum := 0
9 | this.detail := []
10 | this.fightSum := 0
11 | this.sumDenny := 0
12 | this.detailDenny := []
13 | this.general := ""
14 | this.paused := false
15 | this.CPLastPos := 0
16 | this.SPLastPos := 0
17 | this.init()
18 | }
19 |
20 | /** 控制面板 */
21 | ControlPanel(*) {
22 | if (this.CP) {
23 | return destroyGui()
24 | }
25 | if (Ctrl.ing) {
26 | this.paused := A_IsPaused
27 | Pause(1)
28 | } else {
29 | this.paused := false
30 | }
31 | A_TrayMenu.Check('控制面板')
32 | isYeJi := setting.mode = 'YeJi'
33 |
34 | this.CP := Gui('AlwaysOnTop -MinimizeBox', '零号业绩控制面板 ' Version)
35 | this.CP.destroyGui := destroyGui
36 | this.CP.SetFont('s9', '微软雅黑')
37 | this.CP.MarginX := 15
38 | this.CP.AddText('X62 w251', '分辨率:' c.width "x" c.height " 模式:" c.mode (c.compatible ? (c.windowed ? " (窗口兼容)" : " (全屏兼容)") : (c.windowed ? " (窗口)" : " (全屏)")))
39 | this.CP.SetFont('s13')
40 |
41 | this.CP.AddText('X30 Y40', '休眠系数:')
42 | EG := this.CP.AddEdit('X+10 w60 h25 Limit4', setting.sleepCoefficient)
43 | EG.OnEvent('Change', changeSleepCoefficient)
44 | this.CP.AddUpDown('-2', setting.sleepCoefficient).OnEvent('Change', UpDownChange)
45 |
46 | this.CP.AddText('X30', '异常重试次数:').OnEvent('DoubleClick', (*) => MsgBox(retry(), '历史异常', '0x40000'))
47 | this.CP.AddEdit('X+10 w48 h25 Limit2 Number').OnEvent('Change', changeRetryTimes)
48 | this.CP.AddUpDown('Range0-99', setting.retryTimes).OnEvent('Change', changeRetryTimes)
49 |
50 | this.CP.AddText('X30', '颜色搜索允许RGB容差:')
51 | this.CP.AddEdit('X+10 w60 h25 Limit3 Number').OnEvent('Change', changeVariation)
52 | this.CP.AddUpDown('Range0-255', setting.variation).OnEvent('Change', changeVariation)
53 |
54 | this.CP.AddButton('X15 w320 h30', '切换至' (isYeJi ? '丁尼' : '业绩') '模式').OnEvent('Click', switchMode)
55 |
56 | if (isYeJi) {
57 | this.CP.AddText('X30', '使用炸弹:')
58 | this.CP.AddRadio('X+5 Checked' (setting.bombMode = 1), '长按').OnEvent('Click', (*) => setting.bombMode := 1)
59 | this.CP.AddRadio('X+5 Checked' (setting.bombMode = 2), '点击').OnEvent('Click', (*) => setting.bombMode := 2)
60 |
61 | this.CP.AddText('X30', '快捷手册:')
62 | this.CP.AddHotkey('X+10 w60 h25 Limit14', setting.handbook).OnEvent('Change', changeHandbook)
63 |
64 | ; this.CP.AddText('X30 Y+10 w286 h1 BackgroundGray')
65 |
66 | this.CP.AddText('X30', '战斗模式:')
67 | this.CP.AddDropDownList("X+10 W80 Choose" setting.fightMode, setting.fightModeArr).OnEvent("Change", (g, *) => setting.fightMode := Integer(g.Value))
68 |
69 | this.CP.AddText('X30', '刷取模式:')
70 | this.CP.SetFont('s10')
71 | this.CP.AddRadio('X50 Y+10 vgain0 Checked' (setting.gainMode = 0), '我全都要').OnEvent('Click', gainModeSelected)
72 | this.CP.AddRadio('X+2 vgain1 Checked' (setting.gainMode = 1), '只要业绩').OnEvent('Click', gainModeSelected)
73 | this.CP.AddRadio('X+2 vgain2 Checked' (setting.gainMode = 2), '只存银行').OnEvent('Click', gainModeSelected)
74 | } else {
75 | this.CP.AddText('X30', '二号位角色:')
76 | this.CP.AddDropDownList("X+10 W80 Choose" setting.fightModeDenny, setting.fightModeArr).OnEvent("Change", (g, *) => setting.fightModeDenny := Integer(g.Value))
77 |
78 | this.CP.AddText('X30', '视角转动值:')
79 | this.CP.AddEdit('X+10 w80 h25 Limit3 Number').OnEvent('Change', changeRotateCoords)
80 | this.CP.AddUpDown('Range0-9999', setting.rotateCoords).OnEvent('Change', changeRotateCoords)
81 | }
82 |
83 | this.CP.SetFont('s13')
84 | this.CP.AddText('X30', '循环模式:')
85 | this.CP.SetFont('s10')
86 | nowLoopMode := isYeJi ? setting.loopMode : setting.loopModeDenny
87 | AddRadio := this.CP.AddRadio('X50 Y+10 vloop0 Checked' (nowLoopMode = 0), !isYeJi ? '丁尼上限' : !setting.subLoopMode ? '业绩上限' : setting.subLoopMode = 1 ? '丁尼上限' : '全部上限')
88 | AddRadio.OnEvent('Click', loopModeSelected)
89 | AddRadio.OnEvent('DoubleClick', subLoopModeSwitch)
90 | this.CP.AddRadio('X+2 vloop-1 Checked' (nowLoopMode = -1), '无限循环').OnEvent('Click', loopModeSelected)
91 | this.CP.AddRadio('X+2 vloopN Checked' (nowLoopMode > 0), '指定次数').OnEvent('Click', loopModeSelected)
92 | this.loopEditGui := this.CP.AddEdit('X+0 w45 h20 Number Limit3 Hidden' (nowLoopMode <= 0), nowLoopMode > 0 ? nowLoopMode : isYeJi ? 50 : 99)
93 | this.loopEditGui.OnEvent('Change', changeLoopMode)
94 | this.loopUpDownGui := this.CP.AddUpDown('Range1-999 Hidden' (nowLoopMode <= 0), nowLoopMode > 0 ? nowLoopMode : isYeJi ? 50 : 99)
95 | this.loopUpDownGui.OnEvent('Change', changeLoopMode)
96 |
97 | ; this.CP.AddText('X30 Y+10 w286 h1 BackgroundGray')
98 |
99 | this.CP.SetFont('s13')
100 | this.CP.AddCheckBox('verrHandler X30 Checked' setting.errHandler, '异常处理').OnEvent('Click', switchSetting)
101 | this.CP.AddCheckBox('visStepLog Checked' setting.isStepLog, '步骤信息弹窗').OnEvent('Click', switchSetting)
102 | if (isYeJi)
103 | this.CP.AddCheckBox('visAutoDodge Checked' setting.isAutoDodge, '战斗红光自动闪避').OnEvent('Click', switchSetting)
104 |
105 | this.CP.SetFont('s12')
106 | this.CP.AddText('X30', '刷完关闭:')
107 | this.CP.AddRadio('X+3 Checked' (setting.isAutoClose = 0), '禁用').OnEvent('Click', (*) => setting.isAutoClose := 0)
108 | this.CP.AddRadio('X+3 Checked' (setting.isAutoClose = 1), '游戏').OnEvent('Click', (*) => setting.isAutoClose := 1)
109 | this.CP.AddRadio('X+3 Checked' (setting.isAutoClose = 2), '电脑').OnEvent('Click', (*) => setting.isAutoClose := 2)
110 |
111 | this.CP.AddButton('X15 Y+15 w75', '&Q 退出').OnEvent('Click', (*) => ExitApp())
112 | this.CP.AddButton('X+5 w75', '&R 重启').OnEvent('Click', (*) => Reload())
113 | this.CP.pauseButton := this.CP.AddButton('X+5 w75', '&P ' (this.paused ? '继续' : '暂停'))
114 | this.CP.pauseButton.OnEvent('Click', pauseS)
115 | this.CP.AddButton('X+5 Default w75', '&C 确定').OnEvent('Click', destroyGui)
116 | this.CP.AddButton('X15 Y+10 w100', '&U 检查更新').OnEvent('Click', this.checkUpdate.Bind(this))
117 | this.CP.AddButton('X+8 w100', '&T 刷取统计').OnEvent('Click', this.StatisticsPanel.Bind(this))
118 | this.CP.AddButton('X+8 w100', '&Z ' (Ctrl.ing ? (Ctrl.nextExit ? '取消结束' : '本轮结束') : '开始刷取')).OnEvent('Click', start)
119 | this.CP.AddStatusBar(, '`tAlt+字母 = 点击按钮')
120 |
121 | if (this.CPLastPos) {
122 | this.CP.Show('Hide')
123 | this.CP.Move(this.CPLastPos*)
124 | this.CP.Show()
125 | this.CPLastPos := 0
126 | } else {
127 | this.CP.Show()
128 | }
129 |
130 | this.CP.OnEvent('Close', destroyGui)
131 | this.CP.OnEvent('Escape', destroyGui)
132 |
133 | lastUpDownValue := 1
134 | UpDownChange(g, *) {
135 | if (g.value < lastUpDownValue || (g.value = lastUpDownValue && (g.value = -1 || g.value = 0))) {
136 | newValue := Round(EG.Value - 0.01, 2)
137 | } else {
138 | newValue := Round(EG.Value + 0.01, 2)
139 | }
140 | if (newValue < 0) {
141 | newValue := 1.00
142 | } else if (newValue > 10) {
143 | newValue := 10.00
144 | }
145 | setting.sleepCoefficient := EG.Value := newValue
146 | lastUpDownValue := g.value
147 | }
148 |
149 | static destroyGui(*) {
150 | A_TrayMenu.Uncheck('控制面板')
151 | if (!p.paused) {
152 | Pause(0)
153 | }
154 | if (p.CP) {
155 | if (!p.CPLastPos) {
156 | p.CP.GetPos(&x, &y)
157 | p.CPLastPos := [x * 96 / A_ScreenDPI, y * 96 / A_ScreenDPI]
158 | }
159 | p.CP.Destroy()
160 | p.CP := 0
161 | }
162 | if (p.SP) {
163 | p.SP.destroyGui()
164 | }
165 | if (p.UP) {
166 | p.UP.destroyGui()
167 | }
168 | }
169 |
170 | static switchSetting(g, *) {
171 | setting.%g.Name% := !setting.%g.Name%
172 | }
173 |
174 | static changeHandbook(g, *) {
175 | value := g.Value
176 | if (value) {
177 | if (Instr(value, '^!') || StrLen(value) > 2) {
178 | g.Value := setting.handbook
179 | } else {
180 | setting.handbook := value
181 | }
182 | } else {
183 | g.Value := setting.handbook
184 | }
185 | }
186 |
187 | static loopModeSelected(g, *) {
188 | name := StrReplace(g.Name, 'loop', '')
189 | key := setting.mode = 'YeJi' ? 'loopMode' : 'loopModeDenny'
190 | if (name = '0') {
191 | setting.%key% := 0
192 | } else if (name = '-1') {
193 | setting.%key% := -1
194 | }
195 | if (name = 'N') {
196 | value := setting.mode = 'YeJi' ? 30 : 650
197 | setting.%key% := value
198 | p.loopEditGui.Value := value
199 | p.loopEditGui.Visible := true
200 | p.loopUpDownGui.Visible := true
201 | } else {
202 | p.loopEditGui.Visible := false
203 | p.loopUpDownGui.Visible := false
204 | }
205 | }
206 |
207 | static subLoopModeSwitch(g, *) {
208 | if (setting.mode != 'YeJi')
209 | return
210 | if (Ctrl.ing)
211 | return MsgBox("当前正在刷取中,请先结束刷取再切换模式", , "Icon! 0x40000 T3")
212 | ; 业绩上限 → 丁尼上限 → 全部上限
213 | setting.subLoopMode := Mod(setting.subLoopMode + 1, 3)
214 | if (setting.subLoopMode = 0) {
215 | g.Text := '业绩上限'
216 | } else if (setting.subLoopMode = 1) {
217 | g.Text := '丁尼上限'
218 | if (setting.isFirst('subLoopMode1')) {
219 | MsgBox('首次使用丁尼上限模式,请注意:`n1、此模式下将不再获取业绩`n2、第一层boss战斗结束后将直接退出副本进入下一循环`n3、该模式用于刷完业绩后刷取零号空洞的丁尼', '丁尼上限模式注意事项', 'Icon! 0x40000')
220 | }
221 | } else if (setting.subLoopMode = 2) {
222 | g.Text := '全部上限'
223 | if (setting.isFirst('subLoopMode2')) {
224 | MsgBox('首次使用全部上限模式,请注意:`n1、此模式下将一直刷取直至业绩、丁尼皆达到周上限`n2、此模式下当刷取业绩达上限时,会自动切换至丁尼上限模式', '全部上限模式注意事项', 'Icon! 0x40000')
225 | }
226 | }
227 | }
228 |
229 | static gainModeSelected(g, *) {
230 | setting.gainMode := Integer(StrReplace(g.Name, 'gain', ''))
231 | }
232 |
233 | static changeLoopMode(g, *) {
234 | value := g.Value
235 | if (IsInteger(value) && value >= 1) {
236 | if (setting.mode = 'YeJi') {
237 | setting.loopMode := Integer(value)
238 | } else {
239 | setting.loopModeDenny := Integer(value)
240 | }
241 | }
242 | }
243 |
244 | static changeVariation(g, *) {
245 | value := g.Value
246 | if (IsInteger(value)) {
247 | if (value >= 0 && value <= 255) {
248 | setting.variation := Integer(value)
249 | } else {
250 | MsgBox('颜色搜索允许渐变值须介于0~255', '错误', 'Iconx 0x40000')
251 | g.Value := setting.variation
252 | }
253 | }
254 | }
255 |
256 | static changeRotateCoords(g, *) {
257 | value := g.Value
258 | if (IsInteger(value)) {
259 | setting.rotateCoords := Integer(value)
260 | }
261 | }
262 |
263 | static switchMode(*) {
264 | if (Ctrl.ing)
265 | return MsgBox("当前正在刷取中,请先结束刷取再切换模式", , "Icon! 0x40000 T3")
266 | setting.mode := setting.mode = 'YeJi' ? 'Denny' : 'YeJi'
267 | p.CP.GetPos(&x, &y)
268 | p.CPLastPos := [x * 96 / A_ScreenDPI, y * 96 / A_ScreenDPI]
269 | destroyGui()
270 | if (setting.mode = 'Denny' && setting.isFirst('Denny')) {
271 | MsgBox('首次使用丁尼模式,请注意:`n`n1、HDD关卡选择界面需提前切换至「间章第二章」`n2、编队首位必须为「比利」`n3、请在副本内调整「视角转动值」以确保对齐NPC`n4、转动效果受游戏帧率设置影响`n5、转动效果存在浮动系正常现象', '丁尼模式注意事项', 'Icon! 0x40000')
272 | }
273 | p.ControlPanel()
274 | }
275 |
276 | static changeRetryTimes(g, *) {
277 | value := g.Value
278 | if (IsInteger(value)) {
279 | if (value >= 0 && value <= 99) {
280 | setting.retryTimes := Integer(value)
281 | } else {
282 | g.Value := setting.retryTimes
283 | }
284 | }
285 | }
286 |
287 | static changeSleepCoefficient(g, *) {
288 | value := g.Value
289 | if (IsNumber(value)) {
290 | setting.sleepCoefficient := Round(value, 2)
291 | } else {
292 | MsgBox('休眠系数必须为数字类型', '错误', 'Iconx 0x40000')
293 | g.Value := setting.sleepCoefficient
294 | }
295 | }
296 |
297 | static pauseS(g, *) {
298 | if (!Ctrl.ing) {
299 | return MsgBox("当前未处于刷取期间", , "Icon! 0x40000 T3")
300 | }
301 | if (p.paused) {
302 | Pause(0)
303 | destroyGui()
304 | } else {
305 | Pause(1)
306 | g.Text := "&P 继续"
307 | }
308 | p.paused := !p.paused
309 | }
310 |
311 | static start(g, *) {
312 | global Ctrl
313 | if (Ctrl.ing) {
314 | Ctrl.nextExit := !Ctrl.nextExit
315 | if (Ctrl.nextExit) {
316 | g.Text := "&Z 取消结束"
317 | } else {
318 | g.Text := "&Z 本轮结束"
319 | }
320 | } else {
321 | if (!WinExist(ZZZ) && setting.GamePath && FileExist(setting.GamePath)) {
322 | MsgBox('未运行游戏,3s后将尝试自动启动游戏', , 'Icon! 0x40000 T3')
323 | destroyGui()
324 | Ctrl.ing := 1
325 | try {
326 | RestartGame()
327 | }
328 | } else {
329 | destroyGui()
330 | }
331 | activateZZZ()
332 | main()
333 | }
334 | }
335 |
336 | }
337 |
338 | /** 检查更新 */
339 | checkUpdate(*) {
340 | if (this.UP) {
341 | return destroyGui()
342 | }
343 | A_TrayMenu.Check('检查更新')
344 |
345 | static urls := ["https://github.com/UCPr251/zzzAuto/releases/latest", "https://gitee.com/UCPr251/zzzAuto/releases/latest"]
346 | err := 0
347 | for (url in urls) {
348 | try {
349 | hObject := ComObject("WinHttp.WinHttpRequest.5.1")
350 | hObject.SetTimeouts(1000, 1000, 5000, 5000)
351 | hObject.Open("GET", url)
352 | hObject.Send()
353 | hObject.WaitForResponse()
354 | text := hObject.responseText
355 | RegExMatch(text, "v\d+\.\d+\.\d+", &OutputVar)
356 | } catch Error as e {
357 | err := e
358 | }
359 | if (IsSet(OutputVar) && OutputVar[0]) {
360 | latestVersion := OutputVar[0]
361 | if (Version = latestVersion) {
362 | this.UP := Gui('AlwaysOnTop -MinimizeBox' (this.CP ? ' +Owner' this.CP.Hwnd : ''), '检查更新')
363 | this.UP.destroyGui := destroyGui
364 | this.UP.SetFont('s12', '微软雅黑')
365 | this.UP.AddLink('x60 h25 w251', '当前已是最新版本: ' latestVersion ' ').OnEvent('Click', (Ctrl, ID, HREF) => Run(HREF) || destroyGui() || (this.CP ? this.CP.destroyGui() : 0))
366 | this.UP.Show()
367 | this.UP.OnEvent('Close', destroyGui)
368 | this.UP.OnEvent('Escape', destroyGui)
369 | return
370 | } else {
371 | if (this.CP)
372 | this.CP.destroyGui()
373 | destroyGui()
374 | return Run(url)
375 | }
376 | }
377 | }
378 | if (err) {
379 | throw err
380 | } else {
381 | MsgBox('检查更新失败,请稍后重试', '错误', 'Iconx 0x40000')
382 | }
383 |
384 | destroyGui(*) {
385 | A_TrayMenu.Uncheck('检查更新')
386 | if (this.UP) {
387 | this.UP.Destroy()
388 | this.UP := 0
389 | }
390 | if (this.SP) {
391 | this.SP.destroyGui()
392 | }
393 | }
394 | }
395 |
396 | /** 初始化刷取统计数据 */
397 | init() {
398 | for (value in setting.statistics) {
399 | this.newDetail(value.time, value.fightDuration, value.duration)
400 | }
401 | for (value in setting.statisticsDenny) {
402 | this.newDetailDenny(value.time, value.duration)
403 | }
404 | }
405 |
406 | newDetail(time, fightDuration, duration) {
407 | this.sum += duration
408 | this.fightSum += fightDuration
409 | this.detail.Push([time, fightDuration = 0 ? '无' : (fightDuration '秒'), (duration // 60) "分" SubStr("0" Mod(duration, 60), -2) "秒"])
410 | }
411 |
412 | newDetailDenny(time, duration) {
413 | this.sumDenny += duration
414 | this.detailDenny.Push([time, (duration // 60) "分" SubStr("0" Mod(duration, 60), -2) "秒"])
415 | }
416 |
417 | refreshGeneral() {
418 | isYeJi := setting.mode = 'YeJi'
419 | if (isYeJi) {
420 | count := this.detail.Length
421 | sum := this.sum
422 | } else {
423 | count := this.detailDenny.Length
424 | sum := this.sumDenny
425 | }
426 | this.general := "已刷取" count "次"
427 | this.general .= "`n总计耗时:" (sum // 3600) "小时" Round(Mod(sum, 3600) / 60) "分钟"
428 | if (count > 0) {
429 | if (isYeJi) {
430 | this.general .= "`n平均战斗耗时:" Round(this.fightSum // count) "秒"
431 | }
432 | this.general .= "`n平均刷取耗时:" (sum // count // 60) "分" Mod(sum // count, 60) "秒"
433 | }
434 | }
435 |
436 | /** 刷取统计 */
437 | StatisticsPanel(*) {
438 | if (this.SP) {
439 | return destroyGui()
440 | }
441 | A_TrayMenu.Check('刷取统计')
442 |
443 | isYeJi := setting.mode = 'YeJi'
444 | if (isYeJi) {
445 | detail := this.detail
446 | } else {
447 | detail := this.detailDenny
448 | }
449 | this.SP := Gui("AlwaysOnTop -MinimizeBox" (this.CP ? ' +Owner' this.CP.Hwnd : ''), (isYeJi ? '零号业绩' : '拿命验收') "刷取统计")
450 | this.SP.destroyGui := destroyGui
451 | this.SP.changed := 0
452 | this.SP.SetFont('s13', '微软雅黑')
453 | this.refreshGeneral()
454 | this.SP.AddText(, this.general)
455 | TC := this.SP.AddText('w251', '已选中0项')
456 | LV := this.SP.AddListView('w' (isYeJi ? '375' : '320') ' Checked Count' detail.Length ' LV0x1 r' Min(16, detail.Length) ' ReadOnly', isYeji ? ["序号", "开始时间", "战斗", "耗时"] : ["序号", "开始时间", "耗时"])
457 | LV.ModifyCol(1, '55 Integer Center')
458 | LV.ModifyCol(2, '150 Center')
459 | if (isYeJi) {
460 | LV.ModifyCol(3, '60 Center')
461 | LV.ModifyCol(4, '90 Center')
462 | } else {
463 | LV.ModifyCol(3, '90 Center')
464 | }
465 | for (item in detail) {
466 | LV.Add(, A_Index, item*)
467 | }
468 | LV.OnEvent('Click', Select)
469 | width := isYeJi ? 84 : 70
470 | this.SP.AddButton('w' width, '复位').OnEvent('Click', Reset)
471 | this.SP.AddButton('x+6 w' width, '反选').OnEvent('Click', InvertSelect)
472 | this.SP.AddButton('x+6 w' (width + 20), '删除选中').OnEvent('Click', Delete)
473 | this.SP.AddButton('x+6 w' width ' Default', '确定').OnEvent('Click', destroyGui)
474 | if (this.SPLastPos) {
475 | this.SP.Show('Hide')
476 | this.SP.Move(this.SPLastPos*)
477 | this.SP.Show()
478 | this.SPLastPos := 0
479 | } else {
480 | this.SP.Show()
481 | }
482 |
483 | this.SP.OnEvent("Close", destroyGui)
484 | this.SP.OnEvent("Escape", destroyGui)
485 |
486 | destroyGui(*) {
487 | A_TrayMenu.Uncheck('刷取统计')
488 | if (this.SP) {
489 | if (this.SP.changed) {
490 | ; 处理稀疏数组
491 | newStatistics := []
492 | statisticsKey := isYeJi ? 'statistics' : 'statisticsDenny'
493 | for (item in setting.%statisticsKey%) {
494 | if (IsSet(item) && item.time && item.duration) {
495 | newStatistics.Push(item)
496 | }
497 | }
498 | setting.%statisticsKey% := newStatistics
499 | if (isYeJi) {
500 | setting.SaveStatistics()
501 | } else {
502 | setting.SaveStatisticsDenny()
503 | }
504 | newDetail := []
505 | detailKey := isYeJi ? 'detail' : 'detailDenny'
506 | for (item in this.%detailKey%) {
507 | if (IsSet(item) && item[1] && item[2]) {
508 | newDetail.Push(item)
509 | }
510 | }
511 | this.%detailKey% := newDetail
512 | }
513 | this.SP.Destroy()
514 | this.SP := 0
515 | }
516 | }
517 |
518 | Select(g, item, *) {
519 | if (!item) {
520 | return
521 | }
522 | ItemState := SendMessage(0x102C, item - 1, 0xF000, LV)
523 | IsChecked := (ItemState >> 12) - 1
524 | LV.Modify(item, (IsChecked ? '-' : '') "Check")
525 | RefreshCheckedNumber()
526 | }
527 |
528 | Reset(*) {
529 | loop (detail.Length) {
530 | LV.Modify(A_Index, "-Check")
531 | }
532 | RefreshCheckedNumber()
533 | }
534 |
535 | InvertSelect(*) {
536 | loop (detail.Length) {
537 | item := A_Index
538 | ItemState := SendMessage(0x102C, item - 1, 0xF000, LV)
539 | IsChecked := (ItemState >> 12) - 1
540 | LV.Modify(item, (IsChecked ? '-' : '') "Check")
541 | }
542 | RefreshCheckedNumber()
543 | }
544 |
545 | RefreshCheckedNumber(*) {
546 | TC.Text := '已选中' CountChecked() '项'
547 | }
548 |
549 | CountChecked(*) {
550 | count := 0
551 | loop (detail.Length) {
552 | item := A_Index
553 | ItemState := SendMessage(0x102C, item - 1, 0xF000, LV)
554 | count += (ItemState >> 12) - 1
555 | }
556 | return count
557 | }
558 |
559 | Delete(*) {
560 | item := 0
561 | sign := 0
562 | Loop {
563 | item := LV.GetNext(item, 'Checked')
564 | if (!item) {
565 | break
566 | }
567 | index := LV.GetText(item, 1)
568 | if (isYeJi) {
569 | this.sum -= setting.statistics[index].duration
570 | this.fightSum -= setting.statistics[index].fightDuration
571 | setting.statistics.Delete(index)
572 | this.detail.Delete(index)
573 | } else {
574 | this.sumDenny -= setting.statisticsDenny[index].duration
575 | setting.statisticsDenny.Delete(index)
576 | this.detailDenny.Delete(index)
577 | }
578 | sign := 1
579 | }
580 | if (!sign) {
581 | return
582 | }
583 | this.SP.changed := 1
584 | this.SP.GetPos(&x, &y)
585 | this.SPLastPos := [x * 96 / A_ScreenDPI, y * 96 / A_ScreenDPI]
586 | destroyGui()
587 | this.StatisticsPanel()
588 | }
589 |
590 | }
591 |
592 | }
--------------------------------------------------------------------------------
/utils/ScreenBitmap.ahk:
--------------------------------------------------------------------------------
1 | LoadLibrary() {
2 | if (!DllCall("GetModuleHandle", "str", "gdiplus", "UPtr"))
3 | DllCall("LoadLibrary", "str", "gdiplus")
4 | si := Buffer(A_PtrSize = 8 ? 32 : 20, 0)
5 | DllCall("RtlFillMemory", "UPtr", si.Ptr, "UInt", 1, "UChar", 1)
6 | DllCall("gdiplus\GdiplusStartup", "UPtr*", &pToken := 0, "UPtr", si.Ptr, "UPtr", 0)
7 | return pToken
8 | }
9 |
10 | ScreenBitmap(x1 := 0, y1 := 0, x2 := A_ScreenWidth, y2 := A_ScreenHeight) {
11 | w := x2 - x1
12 | h := y2 - y1
13 | chdc := DllCall("CreateCompatibleDC", "UPtr", 0)
14 | BI := Buffer(40, 0)
15 | NumPut("UInt", 40, BI, 0)
16 | NumPut("UInt", w, BI, 4)
17 | NumPut("UInt", h, BI, 8)
18 | NumPut("Ushort", 1, BI, 12)
19 | NumPut("Ushort", 32, BI, 14)
20 | NumPut("UInt", 0, BI, 16)
21 | hBitMap := DllCall("CreateDIBSection", "UPtr", chdc, "UPtr", BI.Ptr, "UInt", 0, "UPtr*", 0, "UPtr", 0, "UInt", 0, "UPtr")
22 | oldBitMap := DllCall("SelectObject", "UPtr", chdc, "UPtr", hBitMap)
23 | hhdc := DllCall("GetDC", "UPtr", 0)
24 | DllCall("gdi32\BitBlt", "UPtr", chdc, "Int", 0, "Int", 0, "Int", w, "Int", h, "UPtr", hhdc, "Int", x1, "Int", y1, "UInt", 0x00CC0020)
25 | DllCall("ReleaseDC", "UPtr", 0, "UPtr", hhdc)
26 | DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "UPtr", hBitMap, "UPtr", 0, "UPtr*", &newBitmap := 0)
27 | DllCall("SelectObject", "UPtr", chdc, "UPtr", oldBitMap)
28 | DllCall("DeleteObject", "UPtr", hBitMap)
29 | DllCall("DeleteDC", "UPtr", hhdc)
30 | DllCall("DeleteDC", "UPtr", chdc)
31 | return newBitmap
32 | }
33 |
34 | GetBitMapPixel(pBitmap, x, y) {
35 | DllCall("gdiplus\GdipBitmapGetPixel", "UPtr", pBitmap, "Int", x, "Int", y, "UInt*", &ARGB := 0)
36 | if (!IsInteger(ARGB)) {
37 | throw Error('ARGB值非整数')
38 | }
39 | Red := (ARGB >> 16) & 0xFF
40 | Green := (ARGB >> 8) & 0xFF
41 | Blue := ARGB & 0xFF
42 | return Format("0x{:02X}{:02X}{:02X}", Red, Green, Blue)
43 | }
44 |
45 | DisposeImage(pBitmap) {
46 | DllCall("gdiplus\GdipDisposeImage", "UPtr", pBitmap)
47 | }
48 |
49 | FreeLibrary(pToken) {
50 | DllCall("gdiplus\GdiplusShutdown", "UPtr", pToken)
51 | hModule := DllCall("GetModuleHandle", "Str", "gdiplus", "UPtr")
52 | if (hModule)
53 | DllCall("FreeLibrary", "UPtr", hModule)
54 | }
--------------------------------------------------------------------------------
/utils/common.ahk:
--------------------------------------------------------------------------------
1 | /** 激活绝区零窗口 */
2 | activateZZZ() {
3 | try {
4 | WinActivate(ZZZ)
5 | RandomSleep()
6 | } catch {
7 | MsgBox("未找到绝区零窗口,请进入游戏后重试", "错误", "Iconx 0x40000 T3")
8 | Ctrl.stop()
9 | Exit()
10 | }
11 | }
12 |
13 | stepLog(str) {
14 | if (setting.isStepLog) {
15 | MsgBox(str, "步骤信息", "T1")
16 | RandomSleep()
17 | }
18 | }
19 |
20 | /** 随机休眠,默认50~100ms */
21 | RandomSleep(ms1 := 50, ms2 := 100) => Sleep(Random(Round(ms1 * setting.sleepCoefficient), Round(ms2 * setting.sleepCoefficient)))
22 |
23 | /** 选择铭徽 */
24 | MingHui(isTry := false, searchTimes := 30) {
25 | X := 0, Y := 0
26 | loop (searchTimes) {
27 | if (PixelSearchPre(&X, &Y, c.空洞.确定*)) {
28 | break
29 | }
30 | Sleep(100)
31 | }
32 | if (!X && !Y) {
33 | if (isTry) {
34 | return false
35 | }
36 | if (!setting.errHandler) {
37 | throw Error('未找到铭徽选择框')
38 | }
39 | MsgBox("未找到铭徽选择框,将使用默认位置", "警告", "Icon! T1")
40 | X := c.空洞.确定[5], Y := c.空洞.确定[6]
41 | preprocess(&X, &Y) ; 缩放处理默认坐标
42 | }
43 | SimulateClick(X, Y)
44 | return true
45 | }
46 |
47 | /**
48 | * 点按按键
49 | * @param {"Space"|"Escape"|"Shift"|"w"|"s"|"a"|"d"|"e"} key 需要点按的键位
50 | * @param {Integer} times 点按次数
51 | */
52 | Press(key, times := 1) {
53 | loop (times) {
54 | Send("{" key " Down}")
55 | Sleep(Random(60, 80))
56 | Send("{" key " Up}")
57 | Sleep(Random(200, 220))
58 | }
59 | }
60 |
61 | /** 对坐标进行缩放处理,使设计坐标与实际坐标匹配 */
62 | preprocess(&X, &Y) {
63 | scaleX := c.width / 1920
64 | scaleY := c.height / 1080
65 | X := Round(X * scaleX)
66 | Y := Round(Y * scaleY)
67 | }
68 |
69 | /** 鼠标随机移动至指定真实坐标 */
70 | RandomMouseMove(TargetX, TargetY) {
71 | static MinSpeed := 48
72 | static MaxSpeed := 52
73 | activateZZZ()
74 | MouseGetPos(&StartX, &StartY)
75 | Distance := Sqrt((TargetX - StartX) ** 2 + (TargetY - StartY) ** 2)
76 | ; 鼠标移动速度,适当缩放确保效果
77 | Speed := (MinSpeed + Random() * (MaxSpeed - MinSpeed)) * (A_ScreenWidth / 1920)
78 | ; 生成随机控制点用于贝塞尔曲线
79 | ControlPoint1X := StartX + Random() * (TargetX - StartX) / 2
80 | ControlPoint1Y := StartY + Random() * (TargetY - StartY) / 2
81 | ControlPoint2X := StartX + (TargetX - StartX) / 2 + Random() * (TargetX - StartX) / 2
82 | ControlPoint2Y := StartY + (TargetY - StartY) / 2 + Random() * (TargetY - StartY) / 2
83 | ; 使用贝塞尔曲线计算移动路径
84 | Steps := Ceil(Distance / Speed)
85 | Loop (Steps) {
86 | t := A_Index / Steps
87 | x := (1 - t) ** 3 * StartX + 3 * (1 - t) ** 2 * t * ControlPoint1X + 3 * (1 - t) * t ** 2 * ControlPoint2X + t ** 3 * TargetX
88 | y := (1 - t) ** 3 * StartY + 3 * (1 - t) ** 2 * t * ControlPoint1Y + 3 * (1 - t) * t ** 2 * ControlPoint2Y + t ** 3 * TargetY
89 | MouseMove(x, y, 0)
90 | RandomSleep(5, 10)
91 | }
92 | }
93 |
94 | /** 模拟点击行为,移动至指定真实坐标点击n次 */
95 | SimulateClick(x?, y?, clickCount := 1) {
96 | if (IsSet(x) && IsSet(y)) {
97 | RandomMouseMove(x + Random(-2, 2), y + Random(-2, 2))
98 | }
99 | Loop (clickCount) {
100 | Click("Left Down")
101 | Sleep(Random(50, 80))
102 | Click("Left Up")
103 | Sleep(Random(160, 251))
104 | }
105 | }
106 |
107 | /** 对坐标进行缩放预处理的像素搜索,返回真实坐标 */
108 | PixelSearchPre(&X, &Y, X1, Y1, X2, Y2, Color, Tolerance := 1, transColor?, transTolerance?) {
109 | if (IsSet(transColor)) {
110 | Color := transColor
111 | if (IsSet(transTolerance)) {
112 | Tolerance := transTolerance
113 | } else {
114 | Tolerance := 1
115 | }
116 | }
117 | Tolerance := Round(Tolerance * setting.variation)
118 | preprocess(&X1, &Y1)
119 | preprocess(&X2, &Y2)
120 | try {
121 | return PixelSearch(&X, &Y, X1, Y1, X2, Y2, Color, Tolerance)
122 | } catch {
123 | return false
124 | }
125 | }
126 |
127 | /**
128 | * 搜索并点击像素点,返回真实坐标数组
129 | * @param X1 搜索起点
130 | * @param Y1 搜索起点
131 | * @param X2 搜索终点
132 | * @param Y2 搜索终点
133 | * @param defaultX 默认X
134 | * @param defaultY 默认Y
135 | * @param Color 搜索颜色
136 | * @param Tolerance 容差倍率
137 | */
138 | pixelSearchAndClick(X1, Y1, X2, Y2, defaultX, defaultY, Color, Tolerance := 1) {
139 | X := 0, Y := 0
140 | loop (30) {
141 | if (PixelSearchPre(&X, &Y, X1, Y1, X2, Y2, Color, Tolerance)) {
142 | break
143 | }
144 | Sleep(100)
145 | }
146 | if (!X || !Y) {
147 | if (!setting.errHandler) {
148 | throw Error(Format("未找到像素点{1:#x}", Color))
149 | }
150 | MsgBox(Format("未找到像素点{1:#x},使用默认位置" defaultX " " defaultY, Color), "警告", "Icon! T1")
151 | X := defaultX, Y := defaultY
152 | preprocess(&X, &Y) ; 缩放处理默认坐标
153 | }
154 | SimulateClick(X, Y)
155 | RandomSleep()
156 | return [X, Y]
157 | }
158 |
159 | /** 检查RGB渐变 */
160 | ; CheckVariation(RGB1, RGB2, Variation := 5) {
161 | ; R1 := (RGB1 >> 16) & 0xFF
162 | ; G1 := (RGB1 >> 8) & 0xFF
163 | ; B1 := RGB1 & 0xFF
164 | ; R2 := (RGB2 >> 16) & 0xFF
165 | ; G2 := (RGB2 >> 8) & 0xFF
166 | ; B2 := RGB2 & 0xFF
167 | ; RDiff := Abs(R1 - R2)
168 | ; GDiff := Abs(G1 - G2)
169 | ; BDiff := Abs(B1 - B2)
170 | ; return RDiff <= Variation && GDiff <= Variation && BDiff <= Variation
171 | ; }
172 |
173 | ; /**
174 | ; * 获取图片中心绝对坐标
175 | ; * @param File 文件路径
176 | ; * @param CoordX X坐标内存地址
177 | ; * @param CoordY Y坐标内存地址
178 | ; * @returns {Integer} 是否成功
179 | ; */
180 | ; CenterImgSrchCoords(File, &CoordsX, &CoordsY) {
181 | ; try {
182 | ; g := Gui()
183 | ; g.Opt("-DPIScale")
184 | ; LoadedPic := g.Add("Picture", "", File)
185 | ; LoadedPic.GetPos(, , &Width, &Height)
186 | ; g.Destroy()
187 | ; CoordsX += Width // 2
188 | ; CoordsY += Height // 2
189 | ; } catch {
190 | ; return false
191 | ; } else {
192 | ; return true
193 | ; }
194 | ; }
195 |
196 | ; /** 对坐标进行缩放预处理的图片搜索 */
197 | ; ImageSearchPre(&X, &Y, X1, Y1, X2, Y2, File) {
198 | ; preprocess(&X1, &Y1)
199 | ; preprocess(&X2, &Y2)
200 | ; return ImageSearch(&X, &Y, X1, Y1, X2, Y2, File)
201 | ; }
202 |
203 | ; /** 获取图片坐标并返回是否成功获取 */
204 | ; getImageXY(&X, &Y, params*) {
205 | ; loop (20) {
206 | ; if (ImageSearchPre(&X, &Y, params*)) {
207 | ; return true
208 | ; }
209 | ; }
210 | ; return false
211 | ; }
212 |
213 | ; /**
214 | ; * 搜索并点击图片
215 | ; * @param File 文件名
216 | ; * @param defaultX 默认X
217 | ; * @param defaultY 默认Y
218 | ; * @param params 图片搜索参数
219 | ; */
220 | ; imageSearchAndClick(File, defaultX, defaultY, params*) {
221 | ; X := 0, Y := 0
222 | ; preprocess(&X, &Y) ; 预处理默认坐标
223 | ; if (!getImageXY(&X, &Y, params*)) {
224 | ; if (!setting.errHandler) {
225 | ; throw Error("未找到" File)
226 | ; }
227 | ; MsgBox("未找到" File ",将使用默认位置", "警告", "Icon! T1")
228 | ; } else {
229 | ; CenterImgSrchCoords(File, &X, &Y)
230 | ; X := defaultX, Y := defaultY
231 | ; preprocess(&X, &Y) ; 缩放处理默认坐标
232 | ; }
233 | ; RandomSleep()
234 | ; SimulateClick(X, Y)
235 | ; return [X, Y]
236 | ; }
237 |
--------------------------------------------------------------------------------
/后台刷取示例.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCPr251/zzzAuto/56357c02c8df1be3b84e19d4fa54189a9c98a321/后台刷取示例.jpg
--------------------------------------------------------------------------------
/控制面板.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCPr251/zzzAuto/56357c02c8df1be3b84e19d4fa54189a9c98a321/控制面板.jpg
--------------------------------------------------------------------------------
/零号业绩.ahk:
--------------------------------------------------------------------------------
1 | /************************************************************************
2 | * @description 绝区零零号空洞零号业绩自动刷取、自动银行存款脚本
3 | * @file 零号业绩.ahk
4 | * @author UCPr
5 | * @date 2025/3/25
6 | * @version v2.2.4
7 | * @link https://github.com/UCPr251/zzzAuto
8 | * @warning 请勿用于任何商业用途,仅供学习交流使用
9 | ***********************************************************************/
10 | #Requires AutoHotkey v2
11 | #SingleInstance Force
12 | #WinActivateForce
13 | SetWorkingDir(A_ScriptDir)
14 | CoordMode("Mouse", "Client")
15 | CoordMode("Pixel", "Client")
16 | SendMode("Input")
17 | ListLines(0)
18 | SetCapsLockState(0)
19 | SetControlDelay(1)
20 | SetWinDelay(0)
21 | SetKeyDelay(-1)
22 | SetMouseDelay(-1)
23 | #Include ./utils
24 | #Include common.ahk
25 | #Include Config.ahk
26 | #Include Controller.ahk
27 | #Include CoordsData.ahk
28 | #Include Panel.ahk
29 | #Include ../components
30 | #Include choose.ahk
31 | #Include enterHollowZero.ahk
32 | #Include enterFuben.ahk
33 | #Include exitFuben.ahk
34 | #Include fight.ahk
35 | #Include getMoney.ahk
36 | #Include isLimited.ahk
37 | #Include reachEnd.ahk
38 | #Include recogLocation.ahk
39 | #Include refuse.ahk
40 | #Include saveBank.ahk
41 | #Include enterDennyFuben.ahk
42 | #Include getDenny.ahk
43 | #Include enterHDD.ahk
44 |
45 | global Version := "v2.2.4"
46 | global ZZZ := "ahk_exe ZenlessZoneZero.exe"
47 |
48 | init()
49 |
50 | /** Alt+P 暂停/恢复线程 */
51 | !p:: {
52 | if (!Ctrl.ing) {
53 | return MsgBox("当前未处于刷取期间", , "Icon! 0x40000 T3")
54 | }
55 | if (p.CP) {
56 | if (!p.paused) {
57 | if (p.CP.pauseButton) {
58 | Pause(1)
59 | p.CP.pauseButton.Text := "&P 继续"
60 | p.paused := !p.paused
61 | }
62 | return
63 | }
64 | p.CP.destroyGui()
65 | } else {
66 | MsgBox("已" (A_IsPaused ? "恢复" : "暂停") "脚本,再次Alt+P可切换状态", , "T1")
67 | }
68 | Pause(-1)
69 | }
70 |
71 | /** Alt+C 控制面板 */
72 | !c:: p.ControlPanel()
73 |
74 | !t:: {
75 | if (p.CP) {
76 | p.StatisticsPanel()
77 | }
78 | }
79 |
80 | /** 初始化 */
81 | init() {
82 | if (!A_IsAdmin) {
83 | cmd := DllCall("GetCommandLine", "str")
84 | if (!RegExMatch(cmd, " /restart(?!\S)")) {
85 | try {
86 | if (A_IsCompiled) {
87 | Run('*RunAs "' A_ScriptFullPath '" /restart')
88 | } else {
89 | Run('*RunAs "' A_AhkPath '" /restart "' A_ScriptFullPath '"')
90 | }
91 | } catch {
92 | MsgBox("自动获取管理员权限失败,请右键脚本选择以管理员身份运行", "错误", "Iconx 0x40000")
93 | }
94 | } else {
95 | MsgBox("自动获取管理员权限失败,请右键脚本选择以管理员身份运行", "错误", "Iconx 0x40000")
96 | }
97 | ExitApp()
98 | }
99 | global setting := Config()
100 | global c := CoordsData()
101 | global p := Panel()
102 | A_TrayMenu.Delete()
103 | A_TrayMenu.Add("控制面板", p.ControlPanel.Bind(p))
104 | A_TrayMenu.Default := "控制面板"
105 | A_TrayMenu.Add("刷取统计", p.StatisticsPanel.Bind(p))
106 | A_TrayMenu.Add()
107 | A_TrayMenu.Add("前往仓库", (*) => Run('https://github.com/UCPr251/zzzAuto') || (p.CP && p.CP.destroyGui()))
108 | A_TrayMenu.Add("检查更新", p.checkUpdate.Bind(p))
109 | A_TrayMenu.Add()
110 | A_TrayMenu.Add("重启", (*) => Reload())
111 | A_TrayMenu.Add("退出", (*) => ExitApp())
112 | global Ctrl := Controller()
113 | OnError(errHandler)
114 | errHandler(err, *) {
115 | Ctrl.stop()
116 | throw err
117 | }
118 | if (c.compatible) {
119 | MsgBox("警告:`n当前显示模式分辨率" c.width "x" c.height "无内置数据`n将使用" c.mode "的分辨率比例数据进行缩放兼容处理`n`n若无法正常运行,请更改游戏显示模式,如1920*1080", "警告", "Icon! 0x40000")
120 | }
121 | if (setting.isFirst('Start')) {
122 | MsgBox("`t`t绝区零自动刷取`n`n支持界面:窗口、全屏`n支持刷取:拿命验收、零号业绩、零号银行`n`n使用方法 :`n`tAlt+P :暂停/恢复脚本`n`tAlt+C :打开/关闭控制面板`n`n当前版本 :" Version "`n仓库地址 :https://github.com/UCPr251/zzzAuto", "UCPr", "0x40000")
123 | }
124 | p.ControlPanel()
125 | }
126 |
127 | /** 开始,检测所在页面 */
128 | main() {
129 | c.reset()
130 | Ctrl.stop() ; 重置
131 | Ctrl.ing := true
132 | isYeJi := setting.mode = 'YeJi'
133 | page := recogLocation()
134 | switch (page) {
135 | case 0: ; 未知界面
136 | {
137 | Ctrl.ing := false
138 | if (isYeJi) {
139 | return MsgBox("请位于 <零号空洞主页> 或 <角色操作界面> 重试", "错误", "Iconx 0x40000")
140 | } else {
141 | return MsgBox("请位于 或 <角色操作界面> 重试", "错误", "Iconx 0x40000")
142 | }
143 | }
144 | case 1:
145 | {
146 | stepLog("【开始】界面:角色操作界面")
147 | }
148 | case 2:
149 | {
150 | stepLog("【开始】界面:零号空洞主页")
151 | if (!isYeJi) {
152 | loop (2) {
153 | Press('Escape')
154 | RandomSleep(1100, 1200)
155 | }
156 | }
157 | }
158 | case 3:
159 | {
160 | stepLog("【开始】界面:HDD关卡选择界面")
161 | if (isYeJi) {
162 | loop (2) {
163 | Press('Escape')
164 | RandomSleep(1100, 1200)
165 | }
166 | }
167 | }
168 | }
169 | runAutoZZZ()
170 | }
171 |
172 | /** 出现异常后重试 */
173 | retry(reason?) {
174 | static errReasons := []
175 | static getErrorMsg() {
176 | if (!errReasons.Length) {
177 | return "无历史异常"
178 | }
179 | errMsg := "历史异常:"
180 | loop (errReasons.Length) {
181 | err := errReasons[A_Index]
182 | errMsg .= "`n异常" A_Index ":[" err.time "] " err.reason
183 | }
184 | return errMsg
185 | }
186 | if (!IsSet(reason) || !reason) {
187 | return getErrorMsg()
188 | }
189 | isYeJi := setting.mode = 'YeJi'
190 | if (!setting.errHandler || ((isYeJi ? setting.statistics : setting.statisticsDenny).Length = 0)) {
191 | throw Error(reason)
192 | }
193 | errReasons.Push({ time: FormatTime(A_Now, "HH:mm:ss"), reason: reason })
194 | setting.retryTimes--
195 | if (setting.retryTimes < 0) {
196 | Ctrl.stop()
197 | errReasons := []
198 | setting.retryTimes := setting.oriSetting.retryTimes
199 | return MsgBox("【错误】异常重试次数过多,脚本结束`n" getErrorMsg(), "错误", "Iconx 0x40000")
200 | }
201 | MsgBox("【错误】连续刷取过程中出现异常:`n" reason "`n`n将在3s后重试", "错误", "Iconx T3 0x40000")
202 | RandomSleep()
203 | page := 0
204 | ; 卡在空洞走格子、交互、确认界面,子界面
205 | UC:
206 | loop (6) {
207 | Press('Space', 2)
208 | if (PixelSearchPre(&X, &Y, c.空洞.确认*)) { ; 确认?
209 | SimulateClick(X, Y)
210 | RandomSleep(1800, 2000)
211 | }
212 | SimulateClick(1, 1) ; 点击?
213 | Press('Space', 3) ; 交互?
214 | if (isYeJi)
215 | MingHui(true, 5) ; 铭徽?
216 | Press(1, 3) ; 选项?
217 | Sleep(200)
218 | Press('Space', 3)
219 | page := recogLocation(5)
220 | if (page = 1) {
221 | break
222 | } else if (page = 2 || page = 3) {
223 | loop (2) {
224 | Press('Escape')
225 | RandomSleep(1100, 1200)
226 | }
227 | return runAutoZZZ()
228 | }
229 | Press('Escape')
230 | RandomSleep(1300, 1500)
231 | ; 副本内
232 | if (PixelSearchPre(&X, &Y, c.空洞.退出副本.放弃*)) {
233 | SimulateClick(X, Y)
234 | RandomSleep(700, 800)
235 | loop (5) {
236 | if (PixelSearchPre(&X, &Y, c.空洞.退出副本.确认*)) {
237 | SimulateClick(X, Y, 2)
238 | RandomSleep(6400, 6500)
239 | pixelSearchAndClick(c.空洞.结算.完成*)
240 | RandomSleep(5600, 5800)
241 | page := recogLocation()
242 | if (page)
243 | break UC
244 | }
245 | Sleep(100)
246 | }
247 | }
248 | ; 确认界面
249 | if (PixelSearchPre(&X, &Y, c.空洞.1.战斗.确定绿勾*)) {
250 | SimulateClick(X, Y, 2)
251 | RandomSleep(500, 600)
252 | if (isYeJi) {
253 | MingHui()
254 | RandomSleep(7500, 8000)
255 | exitFuben()
256 | RandomSleep(800, 1000)
257 | pixelSearchAndClick(c.空洞.结算.完成*)
258 | RandomSleep(5600, 5800)
259 | }
260 | }
261 | ; 副本结算界面
262 | if (PixelSearchPre(&X, &Y, c.空洞.结算.完成*)) {
263 | SimulateClick(X, Y, 2)
264 | RandomSleep(5600, 5800)
265 | }
266 | }
267 | Sleep(251)
268 | ; 重新识别所处界面
269 | page := recogLocation()
270 | if (page = 1) {
271 | return runAutoZZZ()
272 | } else if (page = 2 || page = 3) {
273 | loop (2) {
274 | Press('Escape')
275 | RandomSleep(1100, 1200)
276 | }
277 | return runAutoZZZ()
278 | }
279 | try {
280 | if (RestartGame()) {
281 | runAutoZZZ()
282 | } else {
283 | throw Error("重启游戏执行失败")
284 | }
285 | } catch Error as e {
286 | Ctrl.stop()
287 | errReasons := []
288 | MsgBox("【重启失败】异常重启游戏失败,脚本结束`n重启失败原因:" e.Message "`n重启原因:" reason "`n异常总次数:" errReasons.Length "`n`n" getErrorMsg(), "错误", "Iconx 0x40000")
289 | }
290 | }
291 |
292 | RestartGame(GamePath := setting.GamePath) {
293 | if (!IsSet(GamePath) || !GamePath || !FileExist(GamePath)) {
294 | if (!WinExist(ZZZ))
295 | return false
296 | GamePath := WinGetProcessPath(ZZZ)
297 | setting.GamePath := GamePath
298 | }
299 | if (WinExist(ZZZ)) {
300 | WinClose(ZZZ)
301 | Sleep(1000)
302 | while (WinExist(ZZZ)) {
303 | Sleep(1000)
304 | }
305 | Sleep(6000)
306 | }
307 | Run(GamePath)
308 | Sleep(3000)
309 | while (!WinExist(ZZZ)) {
310 | if (A_Index > 100)
311 | throw Error("重启游戏失败:等待游戏启动超时")
312 | Sleep(251)
313 | }
314 | Sleep(8000)
315 | c.reset()
316 | loop (3) {
317 | while (recogLocation(10) != 1) {
318 | if (A_Index > 20)
319 | throw Error("重启游戏失败:等待进入角色操作界面超时")
320 | if (PixelSearchPre(&X, &Y, c.角色操作.取消正在处理*)) {
321 | SimulateClick(X, Y)
322 | } else {
323 | SimulateClick(c.width // 2, c.height // 2)
324 | }
325 | Sleep(1000)
326 | }
327 | Sleep(1000)
328 | if (recogLocation(10) = 1) {
329 | return true
330 | }
331 | }
332 | return false
333 | }
334 |
335 | /** 运行刷取脚本 */
336 | runAutoZZZ() {
337 | switch (setting.mode) {
338 | case 'YeJi':
339 | YeJi()
340 | case 'Denny':
341 | Denny()
342 | default:
343 | YeJi()
344 | }
345 | }
346 |
347 | /** 业绩模式 */
348 | YeJi() {
349 | static limited := 0
350 | page := recogLocation()
351 | if (page = 1) {
352 | if (!enterHollowZero()) {
353 | return retry("进入零号空洞主页失败")
354 | }
355 | } else if (page != 2) {
356 | return retry("未找到零号空洞主页")
357 | }
358 | Ctrl.start()
359 | status := 0
360 | step := 0
361 | ; 进入副本
362 | enterFuben(++step)
363 | ; 拒绝好意
364 | refuse(++step)
365 | ; 前往终点
366 | status := reachEnd(++step)
367 | if (status = 0) {
368 | return retry("地图类型识别失败")
369 | }
370 | ; 战斗
371 | status := fight(++step)
372 | if (status = 0) {
373 | return retry("战斗超时或检测异常")
374 | }
375 | ; 空洞丁尼模式打完第一层直接退出副本
376 | if (setting.subLoopMode != 1) {
377 | ; 选择增益
378 | status := choose(++step)
379 | if (status = 0) {
380 | return retry("未找到对应增益选项")
381 | }
382 | gainMode := setting.gainMode
383 | ; 全都要
384 | if (gainMode = 0) {
385 | getMoney(++step)
386 | saveBank(++step, gainMode)
387 | ; 只要业绩
388 | } else if (gainMode = 1) {
389 | getMoney(++step)
390 | ; 只存银行
391 | } else if (gainMode = 2) {
392 | saveBank(++step, gainMode)
393 | }
394 | }
395 | ; 退出副本
396 | exitFuben(++step)
397 | Ctrl.finish()
398 | ; 本轮结束
399 | if (Ctrl.nextExit) {
400 | if (setting.loopMode > 0) {
401 | setting.loopMode--
402 | }
403 | Ctrl.stop()
404 | return MsgBox("本次刷取已结束。共刷取" setting.statistics.Length "次")
405 | }
406 | while (!PixelSearchPre(&X, &Y, c.空洞.结算.完成*)) {
407 | Sleep(251)
408 | if (A_Index > 50)
409 | return retry("等待结算完毕超时")
410 | }
411 | ; 业绩上限模式
412 | if (setting.loopMode = 0) {
413 | ; 判断是否达到上限
414 | if (isLimited()) { ; 达到上限
415 | limited++
416 | if (limited >= 2) { ; 连续两次判断为已达上限
417 | limited := 0
418 | if (setting.subLoopMode = 2) { ; 全部上限模式,业绩达上限时自动切换丁尼上限模式
419 | setting.subLoopMode := 1
420 | } else {
421 | Ctrl.stop()
422 | msg := (setting.subLoopMode = 1 ? "丁尼" : "业绩") "已达周上限,脚本结束。共刷取" setting.statistics.Length "次"
423 | if (setting.isAutoClose) {
424 | WinClose(ZZZ)
425 | if (setting.isAutoClose = 2) {
426 | return ShutdownPC(msg)
427 | }
428 | }
429 | return MsgBox(msg)
430 | }
431 | }
432 | } else {
433 | limited := 0
434 | }
435 | stepLog((setting.subLoopMode = 1 ? "丁尼" : "业绩") "未达周上限,继续刷取。已刷取" setting.statistics.Length "次")
436 | ; 无限循环模式
437 | } else if (setting.loopMode = -1) {
438 | stepLog("无限循环模式。已刷取" setting.statistics.Length "次")
439 | ; 指定次数模式
440 | } else {
441 | setting.loopMode--
442 | ; 刷取完毕
443 | if (setting.loopMode = 0) {
444 | Ctrl.stop()
445 | msg := "已刷完指定次数,脚本结束。共刷取" setting.statistics.Length "次"
446 | if (setting.isAutoClose) {
447 | WinClose(ZZZ)
448 | if (setting.isAutoClose = 2) {
449 | return ShutdownPC(msg)
450 | }
451 | }
452 | return MsgBox(msg)
453 | ; 未刷完
454 | } else {
455 | stepLog("指定次数剩余" setting.loopMode "次,继续刷取。已刷取" setting.statistics.Length "次")
456 | }
457 | }
458 | RandomSleep(888, 1000)
459 | pixelSearchAndClick(c.空洞.结算.完成*)
460 | SimulateClick(, , 3)
461 | while (recogLocation(10) != 2) {
462 | if (A_Index < 3) {
463 | if (PixelSearchPre(&X, &Y, c.空洞.结算.完成*)) {
464 | SimulateClick(X, Y)
465 | }
466 | }
467 | if (A_Index > 10)
468 | break
469 | Sleep(200)
470 | }
471 | Sleep(100)
472 | YeJi()
473 | }
474 |
475 | /** 丁尼模式 */
476 | Denny() {
477 | static limited := 0
478 | status := 0
479 | step := 0
480 | page := recogLocation(40)
481 | if (page = 0) { ; 休息
482 | Press('Space', 2)
483 | while (not page := recogLocation(3)) {
484 | Press('Space', 2)
485 | if (A_Index > 20)
486 | return retry("确认休息进入角色操作界面超时")
487 | }
488 | }
489 | if (page = 1 || recogLocation() = 1) {
490 | RandomSleep(300, 320)
491 | status := enterHDD(++step)
492 | if (!status) {
493 | return retry("进入HDD界面失败")
494 | }
495 | SimulateClick(c.width // 2, c.height * 7 // 10, 3) ; 进入战斗委托
496 | RandomSleep(251, 300)
497 | }
498 | if (recogLocation() != 3) {
499 | return retry("未找到HDD关卡选择界面")
500 | }
501 | RandomSleep()
502 | Ctrl.start()
503 | enterDennyFuben(++step)
504 | status := getDenny(++step)
505 | if (!status) {
506 | return retry("丁尼副本刷取重试次数过多")
507 | }
508 | Ctrl.finish()
509 | RandomSleep(2000, 2200)
510 | ; 本轮结束
511 | if (Ctrl.nextExit) {
512 | if (setting.loopModeDenny > 0) {
513 | setting.loopModeDenny--
514 | }
515 | Ctrl.stop()
516 | return MsgBox("本次刷取已结束。共刷取" setting.statisticsDenny.Length "次")
517 | }
518 | while (!PixelSearchPre(&X, &Y, c.空洞.结算.完成*)) {
519 | Sleep(251)
520 | if (A_Index > 50)
521 | return retry("等待结算完毕超时")
522 | }
523 | ; 丁尼上限模式
524 | if (setting.loopModeDenny = 0) {
525 | if (isLimited()) { ; 已达到上限
526 | limited++
527 | if (limited >= 2) { ; 连续两次判断为已达上限
528 | limited := 0
529 | Ctrl.stop()
530 | msg := "丁尼已达日上限,脚本结束。共刷取" setting.statisticsDenny.Length "次"
531 | if (setting.isAutoClose) {
532 | WinClose(ZZZ)
533 | if (setting.isAutoClose = 2) {
534 | return ShutdownPC(msg)
535 | }
536 | }
537 | return MsgBox(msg)
538 | }
539 | } else {
540 | limited := 0
541 | }
542 | stepLog("丁尼未达日上限,继续刷取。已刷取" setting.statisticsDenny.Length "次")
543 | ; 无限循环模式
544 | } else if (setting.loopModeDenny = -1) {
545 | stepLog("无限循环模式。已刷取" setting.statisticsDenny.Length "次")
546 | ; 指定次数模式
547 | } else {
548 | setting.loopModeDenny--
549 | ; 刷取完毕
550 | if (setting.loopModeDenny = 0) {
551 | Ctrl.stop()
552 | msg := "已刷完指定次数,脚本结束。共刷取" setting.statisticsDenny.Length "次"
553 | if (setting.isAutoClose) {
554 | WinClose(ZZZ)
555 | if (setting.isAutoClose = 2) {
556 | return ShutdownPC(msg)
557 | }
558 | }
559 | return MsgBox(msg)
560 | ; 未刷完
561 | } else {
562 | stepLog("指定次数剩余" setting.loopModeDenny "次,继续刷取。已刷取" setting.statisticsDenny.Length "次")
563 | }
564 | }
565 | RandomSleep(500, 600)
566 | pixelSearchAndClick(c.空洞.结算.完成*)
567 | SimulateClick(, , 3)
568 | loop (3) {
569 | if (PixelSearchPre(&X, &Y, c.空洞.结算.完成*)) {
570 | SimulateClick(X, Y)
571 | Sleep(1000)
572 | }
573 | }
574 | Denny()
575 | }
576 |
577 | ShutdownPC(msg := '', Code := 1) {
578 | r := MsgBox(msg '`n`n将在12s后自动关机,点击取消可取消关机', '刷取完毕自动关机', 'Icon! T12 OC Default2')
579 | if (r != 'Cancel') {
580 | Shutdown(Code)
581 | }
582 | }
--------------------------------------------------------------------------------