├── .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 | } --------------------------------------------------------------------------------