mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-15 03:23:22 +08:00
更新识别模式 (#2693)
This commit is contained in:
@@ -1,127 +1,120 @@
|
||||
# 药品消耗统计脚本使用指南
|
||||
|
||||
### 药品消耗统计脚本使用指南(更新版)
|
||||
## 📋 一、脚本概述
|
||||
|
||||
本脚本是一款专为《原神》设计的自动化工具,主要功能如下:
|
||||
|
||||
- **自动识别**:通过OCR技术读取背包中指定药品的数量
|
||||
- **智能统计**:记录每日药品消耗/新增变化,统计周期为当日4点至次日4点
|
||||
- **数据管理**:自动保存30天历史记录,过期数据自动清理
|
||||
- **智能提醒**:通过系统通知推送药品变化信息
|
||||
本脚本是专为《原神》设计的自动化药品消耗统计工具,核心能力升级如下:
|
||||
- **多模式识别**:支持「营养袋模式」「筛选模式」双模式适配不同场景的药品识别
|
||||
- **精准OCR读取**:自动识别背包中指定回血/复活药品数量,适配不同界面布局
|
||||
- **灵活时间配置**:支持自定义每日统计刷新时间(默认4:00,可设0-24小时及一位小数)
|
||||
- **智能数据管理**:自动保留30天历史记录,按自定义时间周期统计药品消耗/新增
|
||||
- **多账户隔离**:不同账户数据独立存储,支持合规账户名校验(1-20位中英文/数字)
|
||||
- **异常友好处理**:识别失败自动兜底、参数错误智能修正,拒绝进入世界的申请
|
||||
|
||||
## 🛠️ 二、环境与工具要求
|
||||
|
||||
### 必备工具:茶包版BGI
|
||||
|
||||
**为什么不推荐公版BGI?**
|
||||
|
||||
公版BGI执行JS脚本时默认关闭自动吃药功能,且无法通过脚本代码开启。本脚本的设计逻辑完全依赖自动吃药功能的正常运行:
|
||||
|
||||
- 药品消耗主要发生在自动吃药场景中
|
||||
- 脚本未内置开启自动吃药的适配代码
|
||||
公版BGI默认关闭自动吃药功能且无法通过脚本开启,本脚本依赖该功能实现消耗统计,因此**必须使用茶包版BGI**。
|
||||
|
||||
### 系统配置要求
|
||||
|
||||
1. **游戏分辨率**:1920×1080(固定不可改)
|
||||
2. **游戏内设置**:
|
||||
- 装备「便携营养袋」
|
||||
- 确保游戏界面无遮挡、无缩放
|
||||
3. **模板文件**:确认`assets/RecognitionObject/`文件夹包含所有必要的模板图片
|
||||
1. **游戏分辨率**:固定1920×1080(不可修改,影响OCR识别区域定位)
|
||||
2. **游戏和js设置**:
|
||||
- 需装备「便携营养袋」
|
||||
- 启用「筛选模式」需要填写完整的药名
|
||||
|
||||
## 📥 三、获取茶包版BGI
|
||||
1. 优先方式:在脚本仓库搜索关键词「茶包」,进入作者主页下载
|
||||
2. 备用方式:查阅「锄地一条龙js」的README文档,通过文档末尾群号加入测测莫酱交流群获取
|
||||
|
||||
1. **直接获取**:在脚本仓库搜索关键词「茶包」,进入作者主页下载
|
||||
2. **备用方案**:查阅「锄地一条龙js」的README文档,通过文档末尾的群号加入测测莫酱交流群获取
|
||||
## ⚙️ 四、全参数配置说明(新增/细化)
|
||||
| 参数项 | 类型 | 填写要求 | 默认值 | 核心说明 |
|
||||
|--------|------|----------|--------|----------|
|
||||
| **runMode** | 下拉选择 | 二选一:「营养袋模式」/「筛选模式」 | 营养袋模式 | 营养袋模式:读取便携营养袋内药品;筛选模式:通过背包筛选搜索药品 |
|
||||
| **initSelect** | 复选框 | 仅初始化/重置时临时勾选 | false | 勾选后运行脚本会删除当日同名记录,重新初始化数据(使用后务必取消) |
|
||||
| **recoveryFoodName** | 文本输入 | 与游戏内回血药名称完全一致 | 空 | 例:「美味的甜甜花酿鸡」,名称错误会导致识别失败 |
|
||||
| **resurrectionFoodName** | 文本输入 | 与游戏内复活药名称完全一致 | 空 | 例:「美味的提瓦特煎蛋」,支持全量游戏内可食用复活类物品 |
|
||||
| **userName** | 文本输入 | 1-20字符,仅支持中英文、数字 | 默认账户 | 多账户区分核心,违规名称自动替换为默认账户 |
|
||||
| **refreshTime** | 文本输入 | 0-24之间,支持一位小数 | 4.0 | 自定义每日统计周期分界点(如4.5=4:30),错误值自动修正为4.0 |
|
||||
| **loadDelay** | 文本输入 | 非负整数(单位:毫秒) | 800 | 界面打开/切换的等待延迟,低配置设备可适当增大 |
|
||||
| **stepDelay** | 文本输入 | 非负整数(单位:毫秒) | 500 | OCR识别/输入药名前的短暂等待,保证识别稳定性 |
|
||||
|
||||
## ⚙️ 四、使用前配置
|
||||
|
||||
### 1. 基础检查
|
||||
|
||||
- [ ] 游戏分辨率为1920×1080
|
||||
- [ ] 已装备「便携营养袋」
|
||||
- [ ] 茶包版BGI中已开启「自动吃药」功能
|
||||
|
||||
### 2. 参数设置(关键步骤)
|
||||
|
||||
| 参数项 | 填写要求 | 示例 |
|
||||
|--------|----------|------------|
|
||||
| **userName** | 1-20字符,仅支持中英文、数字 | "旅行者001" |
|
||||
| **recoveryFoodName** | 与游戏内名称完全一致 | "美味的甜甜花酿鸡" |
|
||||
| **resurrectionFoodName** | 与游戏内名称完全一致 | "美味的提瓦特煎蛋" |
|
||||
| **initSelect** | 默认关闭,仅初始化时临时开启 | 取消勾选 |
|
||||
|
||||
**重要**:药品名称必须完全匹配。
|
||||
|
||||
## 🔄 五、操作流程
|
||||
### 配置优先级
|
||||
脚本运行时参数读取规则:`自定义配置 > 默认值`,参数错误时自动兜底为默认值并输出警告日志。
|
||||
|
||||
## 🔄 五、操作流程(适配双模式)
|
||||
### 场景一:首次使用 / 新账户
|
||||
1. 启用筛选模式需要完成参数配置(填写药品名称),启用营养袋模式则需要营养袋装备两种药品
|
||||
2. 确认游戏分辨率1920×1080、BGI自动吃药功能开启,便携营养袋已装备
|
||||
3. 运行脚本,自动执行:
|
||||
- 校验账户名合法性 → 打开背包 → 按选定模式识别药品数量
|
||||
- 在`assets/`目录生成「账户名.txt」记录文件
|
||||
4. 收到「今日初始化完成」通知,首次配置完成
|
||||
|
||||
1. 完成上述配置后运行脚本
|
||||
2. 脚本自动执行:
|
||||
- 校验账户名合法性
|
||||
- 打开背包识别药品数量
|
||||
- 创建记录文件(路径:`assets/账户名.txt`)
|
||||
3. 收到「今日初始化完成」通知
|
||||
### 场景二:日常使用(分模式)
|
||||
#### 营养袋模式
|
||||
- 脚本自动打开背包→进入小道具→读取便携营养袋内回血/复活药数量
|
||||
- 无需手动筛选,识别速度更快,依赖便携营养袋装备状态
|
||||
|
||||
### 场景二:日常使用
|
||||
#### 筛选模式
|
||||
- 脚本自动打开背包→进入食物分类→筛选搜索指定药品→读取数量
|
||||
- 无需装备营养袋,适配未配置营养袋的场景,识别流程稍长
|
||||
|
||||
- **每日首次运行**(4点后):记录为当日初始值
|
||||
- **后续运行**:对比最新数量,计算消耗/新增
|
||||
- **自动记录**:每次运行都会保存一次记录数据
|
||||
- **智能清理**:仅保留30天内数据
|
||||
#### 通用规则
|
||||
- 每日首次运行(自定义refreshTime后):记录为当日初始值
|
||||
- 后续运行:对比初始值计算消耗/新增(正数=消耗,负数=新增)
|
||||
- 每次运行自动保存记录,仅保留30天内数据,过期自动清理
|
||||
|
||||
### 场景三:更换药品或重置数据
|
||||
### 场景三:更换药品/重置统计数据
|
||||
1. 在设置中勾选`initSelect`选项
|
||||
2. 确认药品名称已更新为新名称(如需更换)
|
||||
3. 运行脚本一次,收到「强制初始化完成」通知
|
||||
4. **立即取消`initSelect`勾选**(避免后续运行重复初始化)
|
||||
|
||||
1. 在设置中**勾选`initSelect`选项**
|
||||
2. 运行脚本一次
|
||||
3. **务必取消勾选`initSelect`**
|
||||
|
||||
**效果**:删除当日同名药品记录,以当前数量重新初始化。
|
||||
|
||||
## 📊 六、核心机制说明
|
||||
|
||||
### 统计周期规则
|
||||
|
||||
- 以**凌晨4点**为分界点
|
||||
- 举例:当前时间3点,统计昨天4点至今的数据
|
||||
- 当前时间5点,统计今天4点起的数据
|
||||
|
||||
### 记录格式示例
|
||||
## 📊 六、核心机制(新增/补充)
|
||||
### 1. 自定义统计周期计算规则
|
||||
以`refreshTime`为分界点,脚本自动判定统计范围:
|
||||
- 当前时间 < 当日refreshTime:统计「昨日refreshTime → 今日refreshTime」数据
|
||||
- 当前时间 ≥ 当日refreshTime:统计「今日refreshTime → 明日refreshTime」数据
|
||||
示例:refreshTime=4.5(4:30),当前时间3:00 → 统计昨天4:30至今;当前时间5:00 → 统计今天4:30起。
|
||||
|
||||
### 2. 记录文件格式
|
||||
```
|
||||
时间:2023/10/15 14:30:45-美味的甜甜花酿鸡-18
|
||||
时间:2023/10/15 14:30:45-美味的提瓦特煎蛋-6
|
||||
时间:202X/XX/XX XX:XX:XX-【药品名称】-【数量】
|
||||
时间:202X/XX/XX XX:XX:XX-【药品名称】-【数量】
|
||||
```
|
||||
每条记录包含时间戳+药品名+数量,双药品记录成对生成,保证数据完整性。
|
||||
|
||||
### 异常处理机制
|
||||
### 3. OCR识别异常处理
|
||||
| 异常场景 | 处理逻辑 | 通知/日志 |
|
||||
|----------|----------|-----------|
|
||||
| OCR未识别到文本 | 数量设为0 | 推送「未识别到XX药数量,设置为0」通知 |
|
||||
| 药品名称为空 | 识别结果无效 | log.warn「XX药名字没填」 |
|
||||
| refreshTime非法 | 自动修正为4.0 | log.warn「刷新时间设置错误,使用默认值4.0」 |
|
||||
| 账户名违规 | 替换为默认账户 | log.error「账户名XX违规,使用默认账户」 |
|
||||
|
||||
| 异常情况 | 自动处理方式 |
|
||||
|----------|-------------|
|
||||
| 药品未识别 | 数量设为0,发送通知提醒 |
|
||||
| 记录文件格式错误 | 自动重置文件 |
|
||||
## ⚠️ 七、重要注意事项(强化)
|
||||
### 1. 运行时机建议
|
||||
- 每日首次运行:在锄地/刷本前、药品制作后执行,保证初始值准确
|
||||
- 避免在两次脚本运行之间大批量制作/使用药品,防止统计偏差
|
||||
- 低配置设备:适当增大loadDelay/stepDelay,提升识别成功率
|
||||
|
||||
## ⚠️ 七、重要注意事项
|
||||
### 2. 双模式适配要点
|
||||
| 模式 | 优势 | 注意事项 |
|
||||
|------|------|----------|
|
||||
| 营养袋模式 | 识别快、步骤少 | 必须装备便携营养袋,仅识别营养袋内药品 |
|
||||
| 筛选模式 | 无需装备营养袋 | 药品名称必须精准,依赖背包筛选功能正常 |
|
||||
|
||||
### 每日首次使用时机建议
|
||||
### 3. 常见误区修正
|
||||
❌ 错误:`initSelect`长期勾选 → 每次运行都重置数据,无法统计消耗
|
||||
✅ 正确:仅重置/换药品时勾选,运行后立即取消
|
||||
|
||||
- **在锄地一条龙前**
|
||||
- **在药品制作后**
|
||||
- **避免**在两次脚本运行之间制作大量药品
|
||||
❌ 错误:随意修改refreshTime后未重启脚本 → 统计周期混乱
|
||||
✅ 正确:修改refreshTime后重新运行脚本,生效新的统计分界点
|
||||
|
||||
### 常见误区
|
||||
|
||||
1. **`initSelect`常开**:导致每次运行都重新初始化
|
||||
2. **名称不准确**:最常导致识别失败的原因
|
||||
3. **窗口遮挡**:影响OCR识别准确率
|
||||
4. **多账户混淆**:每个账户有独立记录文件
|
||||
|
||||
## 🔧 八、故障排查指南
|
||||
|
||||
### 快速自查表
|
||||
|
||||
| 问题现象 | 优先检查项 |
|
||||
|----------|------------|
|
||||
| 药品数量识别为0 | 1. 药品名称准确性<br>2. 背包中是否有该药品<br>3. 分辨率是否为1920×1080 |
|
||||
| 统计数据异常 | 1. 是否在脚本运行期间操作药品<br>2. 是否每日4点后首次运行<br>3. `initSelect`是否误开启 |
|
||||
| 自动吃药无效 | 1. 是否使用茶包版BGI<br>2. BGI中自动吃药功能是否开启 |
|
||||
❌ 错误:分辨率改为非1920×1080 → OCR识别区域错位
|
||||
✅ 正确:固定游戏分辨率为1920×1080,不可自定义
|
||||
|
||||
## 🔧 八、故障排查指南(新增模式/参数排查)
|
||||
| 问题现象 | 优先检查项 | 解决方案 |
|
||||
|----------|------------|----------|
|
||||
| 药品数量识别为0 | 1. 运行模式是否匹配场景<br>2. 药品名称是否完全一致<br>| 1. 营养袋模式需装备营养袋;筛选模式检查背包食物分类<br>2. 复制游戏内药品全名(含「美味的/冷的」等前缀)<br>|
|
||||
| 统计周期错误 | 1. refreshTime是否合法<br>2. 系统时间是否准确 | 1. 确认值在0-24之间(如4.0/12.5),错误值会自动修正<br>2. 同步系统时间到网络标准时间 |
|
||||
| 脚本运行卡顿/超时 | 1. loadDelay/stepDelay是否过小<br>2. 设备性能是否不足 | 1. 逐步增大延迟值(如loadDelay改为1000)<br>2. 关闭后台无关程序,保证游戏前台运行 |
|
||||
| 多账户数据混淆 | 1. userName是否唯一<br>2. 记录文件是否存在重名 | 1. 为每个账户设置唯一名称(如「旅行者001/旅行者002」)<br>2. 检查assets目录,删除重名的错误记录文件 |
|
||||
| 模式切换后识别失败 | 1. 对应模式的模板图片是否存在<br>2. 点击坐标是否匹配分辨率 | 1. 确认assets目录有「营养袋.png」「筛选1.png」「筛选2.png」等<br>2. 重新确认游戏分辨率为1920×1080 |
|
||||
|
||||
BIN
repo/js/营养袋吃药统计/assets/拒绝.png
Normal file
BIN
repo/js/营养袋吃药统计/assets/拒绝.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
repo/js/营养袋吃药统计/assets/营养袋.png
Normal file
BIN
repo/js/营养袋吃药统计/assets/营养袋.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -1,14 +1,36 @@
|
||||
let userName = settings.userName || "默认账户";
|
||||
const recoveryFoodName = settings.recoveryFoodName || "回血药名字没填";
|
||||
const resurrectionFoodName = settings.resurrectionFoodName || "复活药名字没填";
|
||||
const mode = settings.runMode || "营养袋模式"
|
||||
let recoveryFoodName = settings.recoveryFoodName || "回血药名字没填";
|
||||
let resurrectionFoodName = settings.resurrectionFoodName || "复活药名字没填";
|
||||
const ocrRegion = {
|
||||
x: 110,
|
||||
x: 1422,
|
||||
y: 586,
|
||||
width: 300,
|
||||
height: 40
|
||||
};
|
||||
const ocrRegion1 = {
|
||||
x: 1420,
|
||||
y: 687,
|
||||
width: 300,
|
||||
height: 40
|
||||
};
|
||||
const ocrRegion2 = {
|
||||
x: 105,
|
||||
y: 242,
|
||||
width: 124,
|
||||
height: 32
|
||||
width: 140,
|
||||
height: 40
|
||||
};
|
||||
const loadDelay = +settings.loadDelay || 800;
|
||||
const stepDelay = +settings.stepDelay || 500;
|
||||
let refreshTime = parseFloat(settings.refreshTime) || 4.0;
|
||||
if (isNaN(refreshTime) || refreshTime < 0 || refreshTime >= 24) {
|
||||
refreshTime = 4.0;
|
||||
log.warn(`刷新时间设置错误,使用默认值4.0`);
|
||||
}
|
||||
// 计算刷新时间的小时和分钟
|
||||
const refreshHour = Math.floor(refreshTime);
|
||||
const refreshMinute = Math.floor((refreshTime - refreshHour) * 60);
|
||||
log.info(`刷新时间为: ${refreshHour}:${String(refreshMinute).padStart(2, '0')}`);
|
||||
(async function () {
|
||||
// 检验账户名
|
||||
async function getUserName() {
|
||||
@@ -21,6 +43,23 @@ const stepDelay = +settings.stepDelay || 500;
|
||||
return userName;
|
||||
}
|
||||
|
||||
async function close_join_world_popup_window() {
|
||||
const game_region = captureGameRegion();
|
||||
const text_x = 762;
|
||||
const text_y = 29;
|
||||
const text_w = 210;
|
||||
const text_h = 40;
|
||||
const ocr_res = game_region.find(RecognitionObject.ocr(text_x, text_y, text_w, text_h));
|
||||
if (ocr_res) {
|
||||
if (ocr_res.text.includes("进入世界申请")) {
|
||||
log.info("检测到有人申请进入世界,拒绝申请");
|
||||
click(1051, 51);//选择珍贵物品
|
||||
await clickPNG('拒绝', 10);
|
||||
}
|
||||
}
|
||||
game_region.dispose();
|
||||
}
|
||||
|
||||
async function close_expired_stuff_popup_window() {
|
||||
const game_region = captureGameRegion();
|
||||
const text_x = 850;
|
||||
@@ -62,26 +101,24 @@ const stepDelay = +settings.stepDelay || 500;
|
||||
|
||||
if (lines.length === 0) return result;
|
||||
|
||||
// 获取当前时间范围(当天4点至次日4点)
|
||||
// 获取当前时间范围(根据自定义刷新时间)
|
||||
const now = new Date();
|
||||
let startTime, endTime;
|
||||
|
||||
if (now.getHours() < 4) {
|
||||
// 当前时间在4点前,时间范围为昨天4点至今天4点
|
||||
startTime = new Date(now);
|
||||
startTime.setDate(now.getDate() - 1);
|
||||
startTime.setHours(4, 0, 0, 0);
|
||||
// 创建今天的刷新时间点
|
||||
const todayRefresh = new Date(now);
|
||||
todayRefresh.setHours(refreshHour, refreshMinute, 0, 0);
|
||||
|
||||
endTime = new Date(now);
|
||||
endTime.setHours(4, 0, 0, 0);
|
||||
if (now < todayRefresh) {
|
||||
// 当前时间在刷新时间前,时间范围为昨天刷新时间至今天刷新时间
|
||||
startTime = new Date(todayRefresh);
|
||||
startTime.setDate(startTime.getDate() - 1);
|
||||
endTime = new Date(todayRefresh);
|
||||
} else {
|
||||
// 当前时间在4点后,时间范围为今天4点至明天4点
|
||||
startTime = new Date(now);
|
||||
startTime.setHours(4, 0, 0, 0);
|
||||
|
||||
endTime = new Date(now);
|
||||
endTime.setDate(now.getDate() + 1);
|
||||
endTime.setHours(4, 0, 0, 0);
|
||||
// 当前时间在刷新时间后,时间范围为今天刷新时间至明天刷新时间
|
||||
startTime = new Date(todayRefresh);
|
||||
endTime = new Date(todayRefresh);
|
||||
endTime.setDate(endTime.getDate() + 1);
|
||||
}
|
||||
|
||||
// 时间格式正则:匹配 "时间:YYYY/MM/DD HH:mm:ss"
|
||||
@@ -195,22 +232,23 @@ const stepDelay = +settings.stepDelay || 500;
|
||||
|
||||
// 如果需要删除当天同名记录
|
||||
if (deleteSameDayRecords) {
|
||||
// 获取当前时间范围(当天4点至次日4点)
|
||||
// 获取当前时间范围(根据自定义刷新时间)
|
||||
let startTime, endTime;
|
||||
if (now.getHours() < 4) {
|
||||
// 当前时间在4点前,时间范围为昨天4点至今天4点
|
||||
startTime = new Date(now);
|
||||
startTime.setDate(now.getDate() - 1);
|
||||
startTime.setHours(4, 0, 0, 0);
|
||||
endTime = new Date(now);
|
||||
endTime.setHours(4, 0, 0, 0);
|
||||
|
||||
// 创建今天的刷新时间点
|
||||
const todayRefresh = new Date(now);
|
||||
todayRefresh.setHours(refreshHour, refreshMinute, 0, 0);
|
||||
|
||||
if (now < todayRefresh) {
|
||||
// 当前时间在刷新时间前,时间范围为昨天刷新时间至今天刷新时间
|
||||
startTime = new Date(todayRefresh);
|
||||
startTime.setDate(startTime.getDate() - 1);
|
||||
endTime = new Date(todayRefresh);
|
||||
} else {
|
||||
// 当前时间在4点后,时间范围为今天4点至明天4点
|
||||
startTime = new Date(now);
|
||||
startTime.setHours(4, 0, 0, 0);
|
||||
endTime = new Date(now);
|
||||
endTime.setDate(now.getDate() + 1);
|
||||
endTime.setHours(4, 0, 0, 0);
|
||||
// 当前时间在刷新时间后,时间范围为今天刷新时间至明天刷新时间
|
||||
startTime = new Date(todayRefresh);
|
||||
endTime = new Date(todayRefresh);
|
||||
endTime.setDate(endTime.getDate() + 1);
|
||||
}
|
||||
|
||||
// 创建药品匹配正则
|
||||
@@ -275,7 +313,6 @@ const stepDelay = +settings.stepDelay || 500;
|
||||
if (!res || !res.text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const numberMatch = res.text.match(pattern);
|
||||
if (numberMatch) {
|
||||
const number = parseInt(numberMatch[1] || numberMatch[0]);
|
||||
@@ -296,6 +333,55 @@ const stepDelay = +settings.stepDelay || 500;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function recognizeFoodItemByOCR(ocrRegion, pattern) {
|
||||
let captureRegion = null;
|
||||
try {
|
||||
const ocrRo = RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height);
|
||||
captureRegion = captureGameRegion();
|
||||
const resList = captureRegion.findMulti(ocrRo);
|
||||
|
||||
if (!resList || resList.length === 0) {
|
||||
log.warn("OCR未识别到任何文本");
|
||||
return { name: null, count: null };
|
||||
}
|
||||
|
||||
for (const res of resList) {
|
||||
if (!res || !res.text) {
|
||||
continue;
|
||||
}
|
||||
const match = res.text.match(pattern);
|
||||
if (match) {
|
||||
let name = null;
|
||||
let count = null;
|
||||
|
||||
if (match[1]) {
|
||||
name = match[1].trim();
|
||||
}
|
||||
|
||||
if (match[2]) {
|
||||
count = parseInt(match[2]);
|
||||
if (isNaN(count)) {
|
||||
count = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (name || count) {
|
||||
return { name, count };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
log.error(`OCR识别时发生异常: ${error.message}`);
|
||||
}
|
||||
finally {
|
||||
if (captureRegion) {
|
||||
captureRegion.dispose();
|
||||
}
|
||||
}
|
||||
return { name: null, count: null };
|
||||
}
|
||||
|
||||
async function findAndClick(target, doClick = true, maxAttempts = 60) {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const rg = captureGameRegion();
|
||||
@@ -308,72 +394,155 @@ const stepDelay = +settings.stepDelay || 500;
|
||||
return false;
|
||||
}
|
||||
|
||||
async function clickPNG(png, maxAttempts = 20) {
|
||||
async function clickPNG(png, maxAttempts = 20, doClick=true) {
|
||||
// log.info(`调试-点击目标${png},重试次数${maxAttempts}`);
|
||||
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/${png}.png`));
|
||||
pngRo.Threshold = 0.95;
|
||||
pngRo.InitTemplate();
|
||||
return await findAndClick(pngRo, true, maxAttempts);
|
||||
return await findAndClick(pngRo, doClick, maxAttempts);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 设置分辨率和缩放
|
||||
setGameMetrics(1920, 1080, 1);
|
||||
await genshin.returnMainUi();
|
||||
keyPress("B");//打开背包
|
||||
await sleep(1000);
|
||||
await close_expired_stuff_popup_window()
|
||||
await sleep(loadDelay);
|
||||
click(863, 51);//选择食物
|
||||
await sleep(loadDelay);
|
||||
await clickPNG('筛选1', 1);
|
||||
await clickPNG('筛选2', 1);
|
||||
await clickPNG('重置');
|
||||
await sleep(stepDelay);
|
||||
await clickPNG('搜索');
|
||||
await sleep(loadDelay);
|
||||
log.info(`搜索${recoveryFoodName}`)
|
||||
inputText(recoveryFoodName);
|
||||
await clickPNG('确认筛选');
|
||||
await sleep(stepDelay);
|
||||
let recoveryNumber=await recognizeNumberByOCR(ocrRegion,/\d+/) //识别回血药数量
|
||||
// 处理回血药识别结果
|
||||
if (recoveryNumber === null) {
|
||||
recoveryNumber = 0;
|
||||
notification.send(`未识别到回血药数量(数量小于10识别不到),设置数量为0,药品名:${recoveryFoodName}`)
|
||||
await sleep(5000);
|
||||
click(863, 51);//选择食物
|
||||
let recoveryNumber = 0;
|
||||
let resurrectionNumber = 0;
|
||||
// 设置分辨率和缩放
|
||||
setGameMetrics(1920, 1080, 1);
|
||||
await genshin.returnMainUi();
|
||||
keyPress("B");//打开背包
|
||||
await sleep(1000);
|
||||
// 关闭弹窗
|
||||
await close_expired_stuff_popup_window();
|
||||
await close_join_world_popup_window();
|
||||
await sleep(loadDelay);
|
||||
// 打开界面
|
||||
let maxRetries = 5; // 最大重试次数
|
||||
let retryCount = 0;
|
||||
let successClick = false;
|
||||
// 根据模式选择点击的位置
|
||||
let clickX, clickY;
|
||||
if (mode === "营养袋模式") {
|
||||
clickX = 1051; // 选择小道具
|
||||
clickY = 51;
|
||||
} else if (mode === "筛选模式") {
|
||||
clickX = 863; // 选择食物
|
||||
clickY = 51;
|
||||
}
|
||||
while (retryCount < maxRetries && !successClick) {
|
||||
retryCount++;
|
||||
await close_join_world_popup_window();
|
||||
click(clickX, clickY);
|
||||
await sleep(loadDelay);
|
||||
// 检查是否进入了申请界面(通过查找"拒绝"按钮)
|
||||
if (await clickPNG('拒绝', 3)) { // 找到拒绝按钮,说明在申请界面
|
||||
log.info("检测到进入世界申请,已拒绝,重新尝试点击分类标签");
|
||||
await sleep(stepDelay);
|
||||
continue; // 继续下一次循环
|
||||
}
|
||||
if (mode === "营养袋模式") {
|
||||
if (await clickPNG('营养袋', 1, false)) { // 只检查不点击
|
||||
successClick = true;
|
||||
log.info("成功进入小道具界面");
|
||||
break;
|
||||
}
|
||||
} else if (mode === "筛选模式") {
|
||||
if (await clickPNG('筛选1', 1, false)||await clickPNG('筛选2', 1, false)) { // 只检查不点击
|
||||
successClick = true;
|
||||
log.info("成功进入食物界面");
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.warn(`尝试点击分类标签失败,第${retryCount}次重试`);
|
||||
await sleep(stepDelay);
|
||||
}
|
||||
if (!successClick) {
|
||||
log.error("多次尝试点击分类标签失败,脚本终止");
|
||||
return { recoveryNumber, resurrectionNumber};
|
||||
}
|
||||
if (mode === "营养袋模式") {
|
||||
// 营养袋模式
|
||||
await clickPNG('营养袋', 1);
|
||||
await sleep(loadDelay);
|
||||
const pattern = /(.+?)\s*[(\(](\d+)[份\s]*[)\)]/;
|
||||
// 识别回血药
|
||||
let result = await recognizeFoodItemByOCR(ocrRegion, pattern);
|
||||
if (result.name && result.count !== null) {
|
||||
log.info(`识别到: ${result.name}, 份数: ${result.count}`);
|
||||
} else {
|
||||
log.warn("未识别到有效的回血药信息");
|
||||
}
|
||||
recoveryNumber = result.count; // 识别回血药数量
|
||||
recoveryFoodName = result.name || '未识别到回血药名称'; // 如果识别失败,使用settings中的名字
|
||||
// 处理回血药识别结果
|
||||
if (recoveryNumber === null) {
|
||||
recoveryNumber = 0;
|
||||
notification.send(`未识别到回血药数量,设置数量为0,药品名:${recoveryFoodName}`);
|
||||
}
|
||||
// 识别复活药
|
||||
result = await recognizeFoodItemByOCR(ocrRegion1, pattern);
|
||||
if (result.name && result.count !== null) {
|
||||
log.info(`识别到: ${result.name}, 份数: ${result.count}`);
|
||||
} else {
|
||||
log.warn("未识别到有效的复活药信息");
|
||||
}
|
||||
resurrectionNumber = result.count; // 识别复活药数量
|
||||
resurrectionFoodName = result.name || '未识别到复活药名称'; // 如果识别失败,使用settings中的名字
|
||||
// 处理复活药识别结果
|
||||
if (resurrectionNumber === null) {
|
||||
resurrectionNumber = 0;
|
||||
notification.send(`未识别到复活药数量,设置数量为0,药品名:${resurrectionFoodName}`);
|
||||
}
|
||||
} else if (mode === "筛选模式") {
|
||||
// 食物筛选模式
|
||||
// 先识别回血药
|
||||
await clickPNG('筛选1', 1);
|
||||
await clickPNG('筛选2', 1);
|
||||
await clickPNG('重置');
|
||||
await sleep(stepDelay);
|
||||
await clickPNG('搜索');
|
||||
await sleep(loadDelay);
|
||||
log.info(`搜索${recoveryFoodName}`);
|
||||
inputText(recoveryFoodName);
|
||||
await clickPNG('确认筛选');
|
||||
await sleep(loadDelay);
|
||||
recoveryNumber = await recognizeNumberByOCR(ocrRegion2, /\d+/); // 识别回血药数量
|
||||
// 处理回血药识别结果
|
||||
if (recoveryNumber === null) {
|
||||
recoveryNumber = 0;
|
||||
notification.send(`未识别到回血药数量,设置数量为0,药品名:${recoveryFoodName}`);
|
||||
await sleep(5000);
|
||||
click(863, 51); // 选择食物
|
||||
await sleep(1000);
|
||||
}
|
||||
// 重置筛选,识别复活药
|
||||
await clickPNG('筛选1', 1);
|
||||
await clickPNG('筛选2', 1);
|
||||
await clickPNG('重置');
|
||||
await sleep(stepDelay);
|
||||
await clickPNG('搜索');
|
||||
await sleep(loadDelay);
|
||||
log.info(`搜索${resurrectionFoodName}`);
|
||||
inputText(resurrectionFoodName);
|
||||
await clickPNG('确认筛选');
|
||||
await sleep(loadDelay);
|
||||
resurrectionNumber = await recognizeNumberByOCR(ocrRegion2, /\d+/); // 识别复活药数量
|
||||
// 处理复活药识别结果
|
||||
if (resurrectionNumber === null) {
|
||||
resurrectionNumber = 0;
|
||||
notification.send(`未识别到复活药数量,设置数量为0,药品名:${resurrectionFoodName}`);
|
||||
await sleep(5000);
|
||||
click(863, 51); // 选择食物
|
||||
await sleep(1000);
|
||||
}
|
||||
// 重置筛选
|
||||
await clickPNG('筛选1', 1);
|
||||
await clickPNG('筛选2', 1);
|
||||
await clickPNG('重置');
|
||||
await sleep(stepDelay);
|
||||
await clickPNG('确认筛选');
|
||||
}
|
||||
await genshin.returnMainUi();
|
||||
return { recoveryNumber, resurrectionNumber };
|
||||
}
|
||||
await sleep(loadDelay);
|
||||
await clickPNG('筛选1', 1);
|
||||
await clickPNG('筛选2', 1);
|
||||
await clickPNG('重置');
|
||||
await sleep(stepDelay);
|
||||
await clickPNG('搜索');
|
||||
await sleep(loadDelay);
|
||||
log.info(`搜索${resurrectionFoodName}`)
|
||||
inputText(resurrectionFoodName);
|
||||
await clickPNG('确认筛选');
|
||||
await sleep(stepDelay);
|
||||
let resurrectionNumber=await recognizeNumberByOCR(ocrRegion,/\d+/) //识别复活药数量
|
||||
// 处理复活药识别结果
|
||||
if (resurrectionNumber === null) {
|
||||
resurrectionNumber = 0;
|
||||
notification.send(`未识别到复活药数量(数量小于10识别不到),设置数量为0,药品名:${resurrectionFoodName}`)
|
||||
await sleep(5000);
|
||||
click(863, 51);//选择食物
|
||||
await sleep(1000);
|
||||
}
|
||||
await clickPNG('筛选1', 1);
|
||||
await clickPNG('筛选2', 1);
|
||||
await clickPNG('重置');
|
||||
await sleep(stepDelay);
|
||||
await clickPNG('确认筛选');
|
||||
await genshin.returnMainUi();
|
||||
return { recoveryNumber, resurrectionNumber };
|
||||
}
|
||||
|
||||
// 主执行流程
|
||||
userName = await getUserName();
|
||||
const recordPath = `assets/${userName}.txt`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "吃药统计",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7",
|
||||
"bgi_version": "0.51",
|
||||
"description": "用于统计指定两个食物的消耗,推荐锄地前后使用",
|
||||
"authors": [
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
[
|
||||
{
|
||||
"name": "userName",
|
||||
"type": "input-text",
|
||||
"label": "账户名称\n用于多账户运行时区分不同账户",
|
||||
"default": "默认账户"
|
||||
"name": "runMode",
|
||||
"type": "select",
|
||||
"label": "运行模式",
|
||||
"options": [
|
||||
"营养袋模式",
|
||||
"筛选模式"
|
||||
],
|
||||
"default": "营养袋模式"
|
||||
},
|
||||
{
|
||||
"name": "initSelect",
|
||||
@@ -11,7 +15,7 @@
|
||||
"label": "重新初始化药品数量",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "recoveryFoodName",
|
||||
"type": "input-text",
|
||||
"label": "回血药名称",
|
||||
@@ -23,16 +27,28 @@
|
||||
"label": "复活药名称",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "userName",
|
||||
"type": "input-text",
|
||||
"label": "账户名称\n用于多账户运行时区分不同账户",
|
||||
"default": "默认账户"
|
||||
},
|
||||
{
|
||||
"name": "refreshTime",
|
||||
"type": "input-text",
|
||||
"label": "每日刷新时间\n用来定义当日刷新时间(0-24之间,支持一位小数)",
|
||||
"default": "4.0"
|
||||
},
|
||||
{
|
||||
"name": "loadDelay",
|
||||
"type": "input-text",
|
||||
"label": "加载等待延迟\n用于界面打开和切换的等待\n默认800,单位毫秒",
|
||||
"label": "加载等待延迟\n用于界面打开和切换的等待(默认800,单位毫秒)",
|
||||
"default": "800"
|
||||
},
|
||||
{
|
||||
"name": "stepDelay",
|
||||
"type": "input-text",
|
||||
"label": "操作间隔延迟\n用于OCR前和输入药名前的短暂等待\n默认500,单位毫秒",
|
||||
"label": "操作间隔延迟\n用于OCR前和输入药名前的短暂等待(默认500,单位毫秒)",
|
||||
"default": "500"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user