mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-21 04:19:51 +08:00
自动体力计划 0.0.3 修复:健全体力识别(双重识别兜底) (#2987)
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
- 限制只在特定星期几执行(例如周日限定本、周本)
|
||||
- 设置执行优先级(数字越大越先跑)
|
||||
- 支持三种配置来源(输入 / UID专属 / bgi_tools远程)
|
||||
- 自动检测幽境开启状态
|
||||

|
||||
|
||||
## 配置项说明
|
||||
|
||||
@@ -293,18 +295,20 @@
|
||||
欢迎提交 issue 或 PR 改进脚本~
|
||||
祝刷本愉快!
|
||||
|
||||
## 版本密钥
|
||||
## 版本对应表
|
||||
|
||||
| 版本 | 密钥 |
|
||||
|-------|---------------------|
|
||||
| 0.0.1 | oiJbmjU2R0NniiwiZxh |
|
||||
| 0.0.2 | oiJbmjU2R0NniiwiZxh |
|
||||
| 版本 | 密钥 | bettergi-scripts-tools对应版本 |
|
||||
|:-----:|:-------------------:|:--------------------------:|
|
||||
| 0.0.1 | oiJbmjU2R0NniiwiZxh | 0.0.4+ |
|
||||
| 0.0.2 | oiJbmjU2R0NniiwiZxh | 0.0.5+ |
|
||||
| 0.0.3 | oiJbmjU2R0NniiwiZxh | 0.0.6+ |
|
||||
|
||||
## 版本历史(简要)
|
||||
### 0.0.3 2026.03.13
|
||||
- 健全体力识别(双重识别兜底)
|
||||
### 0.0.2 2026.03.07
|
||||
- 新增幽境支持
|
||||
### 0.0.1 2026.01.30
|
||||
|
||||
- 基本功能完成
|
||||
- 支持三种配置加载方式
|
||||
- 支持 bgi_tools http 拉取/推送配置
|
||||
|
||||
BIN
repo/js/AutoPlan/assets/original_resin.png
Normal file
BIN
repo/js/AutoPlan/assets/original_resin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -3,6 +3,7 @@ import {ocrUid} from "../utils/uid";
|
||||
const config = {
|
||||
//setting设置放在这个json
|
||||
run: {
|
||||
exclude_run_exception: false,//忽略运行异常
|
||||
loop_plan: false,//启用循环体力计划
|
||||
retry_count: 3,//复活重试次数
|
||||
config: '',
|
||||
@@ -186,6 +187,8 @@ async function initConfig() {
|
||||
? retryCount
|
||||
: config.run.retry_count;
|
||||
|
||||
config.run.exclude_run_exception=settings.exclude_run_exception
|
||||
|
||||
config.run.loop_plan = settings.loop_plan !== undefined ? settings.loop_plan : config.run.loop_plan
|
||||
const bgi_tools_token = settings.bgi_tools_token || "Authorization= "
|
||||
// const list = Array.from(bgi_tools_token.split("=")).map(item => item.trim());
|
||||
|
||||
@@ -2,7 +2,7 @@ import {config, initConfig, initSettings, LoadType} from './config/config';
|
||||
import {ocrUid} from './utils/uid';
|
||||
import {getDayOfWeek, outDomainUI, outStygianOnslaughtUI, throwError,toMainUi} from './utils/tool';
|
||||
import {pullJsonConfig, pushAllCountryConfig, pushAllJsonConfig} from './utils/bgi_tools';
|
||||
import {ocrPhysical} from "./utils/physical";
|
||||
import {countOriginalResin, ocrPhysical,countAllResin} from "./utils/physical";
|
||||
import {findStygianOnslaught} from "./utils/activity";
|
||||
|
||||
/**
|
||||
@@ -54,9 +54,13 @@ async function autoDomain(autoFight) {
|
||||
// fragileResinUseCount: number;
|
||||
await sleep(1000)
|
||||
//流程->返回主页 打开地图 返回主页
|
||||
const physicalOcr = await ocrPhysical(true, true)
|
||||
config.user.physical.current = physicalOcr.current
|
||||
config.user.physical.min = physicalOcr.min
|
||||
// const physicalOcr = await ocrPhysical(true, true)
|
||||
// config.user.physical.current = physicalOcr.current
|
||||
// config.user.physical.min = physicalOcr.min
|
||||
|
||||
const currentPhysical = await countAllResin()
|
||||
config.user.physical.current = currentPhysical.originalResinCount;
|
||||
|
||||
const physical = config.user.physical
|
||||
if (domainParam.specifyResinUse && physical.current < physical.min && noOriginalSum <= 0 && originalSum > 0) {
|
||||
throwError(`体力不足,当前体力${physical.current},最低体力${physical.min},请手动补充体力后重试`)
|
||||
@@ -104,7 +108,9 @@ async function autoDomain(autoFight) {
|
||||
if (errorMessage.includes("复活") && domainParam.DomainName) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
if (!config.run.exclude_run_exception||config.run.loop_plan) {//排除异常 与循环计划互斥
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -172,7 +178,9 @@ async function autoLeyLineOutcrop(autoLeyLineOutcrop) {
|
||||
if (errorMessage.includes("复活")) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
if (!config.run.exclude_run_exception||config.run.loop_plan) {//排除异常 与循环计划互斥
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,9 +254,12 @@ async function autoStygianOnslaught(autoStygianOnslaught) {
|
||||
// fragileResinUseCount: number;
|
||||
await sleep(1000)
|
||||
//流程->返回主页 打开地图 返回主页
|
||||
const physicalOcr = await ocrPhysical(true, true)
|
||||
config.user.physical.current = physicalOcr.current
|
||||
config.user.physical.min = physicalOcr.min
|
||||
// const physicalOcr = await ocrPhysical(true, true)
|
||||
// config.user.physical.current = physicalOcr.current
|
||||
// config.user.physical.min = physicalOcr.min
|
||||
const currentPhysical = await countAllResin()
|
||||
config.user.physical.current = currentPhysical.originalResinCount;
|
||||
|
||||
const physical = config.user.physical
|
||||
if (physical.current < physical.min && noOriginalSum <= 0 && originalSum > 0) {
|
||||
throwError(`体力不足,当前体力${physical.current},最低体力${physical.min},请手动补充体力后重试`)
|
||||
@@ -278,7 +289,9 @@ async function autoStygianOnslaught(autoStygianOnslaught) {
|
||||
if (errorMessage.includes("复活")) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
if (!config.run.exclude_run_exception||config.run.loop_plan) {//排除异常 与循环计划互斥
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -676,7 +689,7 @@ async function main() {
|
||||
if (isStygianOnslaught) {
|
||||
//圣遗物秘境名称
|
||||
const holyRelicDomainNames = config.domainList.filter(item => !item.hasOrder).map(item => item.name);
|
||||
const filter = list.find(item => item.runType === config.user.runTypes[0] && holyRelicDomainNames.includes(item.autoFight.domainName));
|
||||
const filter = list.find(item => item.runType === config.user.runTypes[0] && holyRelicDomainNames.includes(item.autoFight?.domainName));
|
||||
if (filter) {
|
||||
// 幽境危战添加秘境顺序前
|
||||
list.forEach(item => {
|
||||
@@ -702,8 +715,9 @@ async function main() {
|
||||
await autoRunList(list);
|
||||
if (config.run.loop_plan) {
|
||||
// 重新获取当前体力值
|
||||
const physicalOcr = await ocrPhysical(true, true);
|
||||
config.user.physical.current = physicalOcr.current;
|
||||
// const physicalOcr = await ocrPhysical(true, true);
|
||||
const currentPhysical = await countAllResin()
|
||||
config.user.physical.current = currentPhysical.originalResinCount;
|
||||
//循环
|
||||
if (config.user.physical.current < config.user.physical.min) {
|
||||
//体力耗尽
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "自动体力计划",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "",
|
||||
"settings_ui": "settings.json",
|
||||
"main": "main.js",
|
||||
|
||||
BIN
repo/js/AutoPlan/md/check.jpg
Normal file
BIN
repo/js/AutoPlan/md/check.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -17,6 +17,11 @@
|
||||
"label": "自动秘境计划配置\n语法:说明太长 去看文档",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "exclude_run_exception",
|
||||
"type": "checkbox",
|
||||
"label": "忽略运行异常\n(当前计划出现异常后,会执行下一个计划)与循环体力计划冲突"
|
||||
},
|
||||
{
|
||||
"name": "loop_plan",
|
||||
"type": "checkbox",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @returns {Promise<HttpResponse>}
|
||||
*/
|
||||
async function pullJsonConfig(uid, http_api) {
|
||||
http_api += "?uid=" + uid
|
||||
http_api += "?uid=" + uid+"&enable=" + true
|
||||
const res = await http.request("GET", http_api
|
||||
// , JSON.stringify({"Content-Type": "application/json"})
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {getJsonPath, toMainUi,throwError} from "./tool";
|
||||
import {getJsonPath, toMainUi, throwError, findImgAndClick} from "./tool";
|
||||
//====================================================
|
||||
const genshinJson = {
|
||||
width: 1920,//genshin.width,
|
||||
@@ -8,7 +8,40 @@ const genshinJson = {
|
||||
// const OpenModeCountMin = settings.openModeCountMin
|
||||
// let AlreadyRunsCount=0
|
||||
// let NeedRunsCount=0
|
||||
const TemplateOrcJson={x: 1568, y: 16, width: 225, height: 60,}
|
||||
const TemplateOrcJson = {x: 1568, y: 16, width: 225, height: 60,}
|
||||
|
||||
// ==================== 常量定义 ====================
|
||||
|
||||
// 树脂图标识别对象
|
||||
const RESIN_ICONS = {
|
||||
ORIGINAL: RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/original_resin.png")),
|
||||
// CONDENSED: RecognitionObject.TemplateMatch(file.ReadImageMatSync("RecognitionObject/condensed_resin.png")),
|
||||
// FRAGILE: RecognitionObject.TemplateMatch(file.ReadImageMatSync("RecognitionObject/fragile_resin.png")),
|
||||
// TRANSIENT: RecognitionObject.TemplateMatch(file.ReadImageMatSync("RecognitionObject/transient_resin.png")),
|
||||
// REPLENISH_BUTTON: RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/replenish_resin_button.png"))
|
||||
};
|
||||
// 配置常量
|
||||
const CONFIG = {
|
||||
RECOGNITION_TIMEOUT: 2000, // 图像识别超时时间(毫秒)
|
||||
SLEEP_INTERVAL: 500, // 循环间隔时间(毫秒)
|
||||
UI_DELAY: 1500, // UI操作延迟时间(毫秒)
|
||||
MAP_ZOOM_LEVEL: 6, // 地图缩放级别
|
||||
|
||||
// 点击坐标
|
||||
COORDINATES: {
|
||||
MAP_SWITCH: {x: 1840, y: 1020}, // 地图右下角切换按钮
|
||||
MONDSTADT: {x: 1420, y: 180}, // 蒙德选择按钮
|
||||
AVOID_SELECTION: {x: 1090, y: 450} // 避免选中效果的点击位置
|
||||
},
|
||||
|
||||
// OCR识别区域配置
|
||||
OCR_REGIONS: {
|
||||
ORIGINAL_RESIN: {width: 200, height: 40},
|
||||
CONDENSED_RESIN: {width: 90, height: 40},
|
||||
OTHER_RESIN: {width: 0, height: 60} // width会根据图标宽度动态设置
|
||||
}
|
||||
};
|
||||
|
||||
//====================================================
|
||||
|
||||
|
||||
@@ -17,14 +50,14 @@ const TemplateOrcJson={x: 1568, y: 16, width: 225, height: 60,}
|
||||
* @param {string} str - 包含数字的字符串
|
||||
* @returns {number} - 由字符串中所有数字组合而成的整数
|
||||
*/
|
||||
async function saveOnlyNumber(str,defaultValue=0) {
|
||||
async function saveOnlyNumber(str, defaultValue = 0) {
|
||||
// 使用正则表达式匹配字符串中的所有数字
|
||||
// \d+ 匹配一个或多个数字
|
||||
// .join('') 将匹配到的数字数组连接成一个字符串
|
||||
// parseInt 将连接后的字符串转换为整数
|
||||
try {
|
||||
return parseInt(str.match(/\d+/g).join(''));
|
||||
}catch (e) {
|
||||
} catch (e) {
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
@@ -40,7 +73,7 @@ async function saveOnlyNumber(str,defaultValue=0) {
|
||||
* - min {number}: 最小可执行体力值
|
||||
* - current {number}: 当前剩余体力值
|
||||
*/
|
||||
async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isResinExhaustionMode=true) {
|
||||
async function ocrPhysical(opToMainUi = false, openMap = false, minPhysical = 20, isResinExhaustionMode = true) {
|
||||
// 检查是否启用体力识别功能,如果未启用则直接返回默认结果
|
||||
if (!isResinExhaustionMode) {
|
||||
log.info(`===未启用===`)
|
||||
@@ -57,14 +90,14 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
await toMainUi(); // 切换到主界面
|
||||
}
|
||||
|
||||
if (openMap){
|
||||
if (openMap) {
|
||||
await sleep(ms)
|
||||
//打开地图界面
|
||||
await keyPress('M')
|
||||
}
|
||||
await sleep(ms)
|
||||
log.debug(`===[点击+]===`)
|
||||
//点击+ 按钮 x=1264,y=39,width=18,height=19
|
||||
// //点击+ 按钮 x=1264,y=39,width=18,height=19
|
||||
let add_buttonJSON = getJsonPath('add_button');
|
||||
let add_objJson = {
|
||||
path: `${add_buttonJSON.path}${add_buttonJSON.name}${add_buttonJSON.type}`,
|
||||
@@ -73,23 +106,28 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
width: 52,
|
||||
height: 49,
|
||||
}
|
||||
let templateMatchAddButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`${add_objJson.path}`), add_objJson.x, add_objJson.y, add_objJson.width, add_objJson.height);
|
||||
let regionA = captureGameRegion()
|
||||
// let deriveCrop = regionA.DeriveCrop(add_objJson.x, add_objJson.y, add_objJson.width, add_objJson.height);
|
||||
try {
|
||||
let buttonA = regionA.find(templateMatchAddButtonRo);
|
||||
|
||||
await sleep(ms)
|
||||
if (!buttonA.isExist()) {
|
||||
log.error(`${add_objJson.path}匹配异常`)
|
||||
throwError(`${add_objJson.path}匹配异常`)
|
||||
}
|
||||
await buttonA.click()
|
||||
}finally {
|
||||
// deriveCrop.dispose()
|
||||
regionA.dispose()
|
||||
//
|
||||
// let templateMatchAddButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`${add_objJson.path}`), add_objJson.x, add_objJson.y, add_objJson.width, add_objJson.height);
|
||||
// let regionA = captureGameRegion()
|
||||
// // let deriveCrop = regionA.DeriveCrop(add_objJson.x, add_objJson.y, add_objJson.width, add_objJson.height);
|
||||
// try {
|
||||
// let buttonA = regionA.find(templateMatchAddButtonRo);
|
||||
//
|
||||
// await sleep(ms)
|
||||
// if (!buttonA.isExist()) {
|
||||
// log.error(`${add_objJson.path}匹配异常`)
|
||||
// throwError(`${add_objJson.path}匹配异常`)
|
||||
// }
|
||||
// await buttonA.click()
|
||||
// } finally {
|
||||
// // deriveCrop.dispose()
|
||||
// regionA.dispose()
|
||||
// }
|
||||
const addClick = await findImgAndClick(`${add_objJson.path}`, 1248, 21, 50, 50);
|
||||
if (addClick === null) {
|
||||
log.error(`${add_objJson.path}匹配异常`)
|
||||
return undefined
|
||||
}
|
||||
|
||||
await sleep(ms)
|
||||
|
||||
log.debug(`===[定位原粹树脂]===`)
|
||||
@@ -103,16 +141,17 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
height: TemplateOrcJson.height,
|
||||
}
|
||||
let templateMatchButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`${tmJson.path}`), tmJson.x, tmJson.y, tmJson.width, tmJson.height);
|
||||
let region =captureGameRegion()
|
||||
let region = captureGameRegion()
|
||||
let button
|
||||
try {
|
||||
button = region.find(templateMatchButtonRo);
|
||||
await sleep(ms)
|
||||
if ((!button)||!button.isExist()) {
|
||||
if ((!button) || !button.isExist()) {
|
||||
log.error(`${tmJson.path} 匹配异常`)
|
||||
throwError(`${tmJson.path} 匹配异常`)
|
||||
// throwError(`${tmJson.path} 匹配异常`)
|
||||
return undefined
|
||||
}
|
||||
}finally {
|
||||
} finally {
|
||||
region.dispose()
|
||||
}
|
||||
|
||||
@@ -125,7 +164,7 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
// y: 32,
|
||||
y: button.y,
|
||||
// width: 61,
|
||||
width: Math.abs(genshinJson.width - button.x - button.width),
|
||||
width: Math.abs(genshinJson.width - button.x - button.width),
|
||||
height: 26
|
||||
}
|
||||
|
||||
@@ -133,11 +172,11 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
let region3 = captureGameRegion()
|
||||
|
||||
try {
|
||||
let recognitionObjectOcr = RecognitionObject.Ocr(ocr_obj.x, ocr_obj.y, ocr_obj.width, ocr_obj.height);
|
||||
let recognitionObjectOcr = RecognitionObject.ocr(ocr_obj.x, ocr_obj.y, ocr_obj.width, ocr_obj.height);
|
||||
let res = region3.find(recognitionObjectOcr);
|
||||
|
||||
log.debug(`[OCR原粹树脂]识别结果: ${res.text}, 原始坐标: x=${res.x}, y=${res.y},width:${res.width},height:${res.height}`);
|
||||
let text=res.text.split('/')[0]
|
||||
let text = res.text.split('/')[0]
|
||||
let current = await saveOnlyNumber(text)
|
||||
let execute = (current - minPhysical) >= 0
|
||||
log.debug(`最小可执行原粹树脂:{min},原粹树脂:{key}`, minPhysical, current,)
|
||||
@@ -149,7 +188,9 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
current: current,
|
||||
}
|
||||
} catch (e) {
|
||||
throwError(`识别失败,err:${e.message}`)
|
||||
// throwError(`识别失败,err:${e.message}`)
|
||||
log.error(`识别失败,err:${e.message}`)
|
||||
return undefined
|
||||
} finally {
|
||||
region3.dispose()
|
||||
//返回地图操作
|
||||
@@ -160,6 +201,269 @@ async function ocrPhysical(opToMainUi = false,openMap=false,minPhysical=20,isRes
|
||||
|
||||
}
|
||||
|
||||
// ==================== UI操作函数 ====================
|
||||
|
||||
/**
|
||||
* 打开并设置地图界面
|
||||
*/
|
||||
async function openMap() {
|
||||
log.info("打开地图界面");
|
||||
await keyPress("M");
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
|
||||
// 切换到国家选择界面
|
||||
// click(CONFIG.COORDINATES.MAP_SWITCH.x, CONFIG.COORDINATES.MAP_SWITCH.y);
|
||||
// await sleep(CONFIG.UI_DELAY);
|
||||
|
||||
// 选择蒙德
|
||||
// click(CONFIG.COORDINATES.MONDSTADT.x, CONFIG.COORDINATES.MONDSTADT.y);
|
||||
// await sleep(CONFIG.UI_DELAY);
|
||||
// await switchtoCountrySelection(CONFIG.COORDINATES.MONDSTADT.x, CONFIG.COORDINATES.MONDSTADT.y)
|
||||
|
||||
// 设置地图缩放级别,排除识别干扰
|
||||
await genshin.setBigMapZoomLevel(CONFIG.MAP_ZOOM_LEVEL);
|
||||
log.info("地图界面设置完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计所有树脂数量的主函数
|
||||
* @returns {Object} 包含所有树脂数量的对象
|
||||
*/
|
||||
async function countAllResin() {
|
||||
let shouldRestoreMainUi = false
|
||||
try {
|
||||
// setGameMetrics(1920, 1080, 1);
|
||||
// log.info("开始统计树脂数量");
|
||||
let resinCounts = {
|
||||
original: 0,
|
||||
transient: undefined,
|
||||
fragile: undefined,
|
||||
condensed: undefined
|
||||
}
|
||||
await toMainUi();
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
shouldRestoreMainUi = true
|
||||
// 打开地图界面统计原粹/浓缩树脂
|
||||
await openMap();
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
let tryPass = true;
|
||||
try {
|
||||
// log.info("[开始]统计补充树脂界面中的树脂");
|
||||
resinCounts.original = await countOriginalResin(false, false);
|
||||
moveMouseTo(CONFIG.COORDINATES.AVOID_SELECTION.x, CONFIG.COORDINATES.AVOID_SELECTION.y)
|
||||
await sleep(500);
|
||||
// resinCounts.transient = await countTransientResin();
|
||||
// resinCounts.fragile = await countFragileResin();
|
||||
// log.info("[完成]统计补充树脂界面中的树脂");
|
||||
// 点击避免选中效果影响统计
|
||||
click(CONFIG.COORDINATES.AVOID_SELECTION.x, CONFIG.COORDINATES.AVOID_SELECTION.y);
|
||||
} catch (e) {
|
||||
tryPass = false
|
||||
}
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
log.info("开始统计地图界面中的树脂");
|
||||
if (!tryPass) {
|
||||
// 如果第一次尝试失败,则切换到蒙德
|
||||
await switchtoCountrySelection(CONFIG.COORDINATES.MONDSTADT.x, CONFIG.COORDINATES.MONDSTADT.y)
|
||||
resinCounts.original = await countOriginalResin(!tryPass);
|
||||
}
|
||||
// resinCounts.condensed = await countCondensedResin();
|
||||
// if (!tryPass) {
|
||||
// 打开补充树脂界面统计须臾/脆弱树脂
|
||||
// await openReplenishResinUi();
|
||||
// await sleep(CONFIG.UI_DELAY);
|
||||
|
||||
// 点击避免选中效果影响统计
|
||||
// click(CONFIG.COORDINATES.AVOID_SELECTION.x, CONFIG.COORDINATES.AVOID_SELECTION.y);
|
||||
// await sleep(500);
|
||||
|
||||
// log.info("开始统计补充树脂界面中的树脂");
|
||||
// resinCounts.transient = await countTransientResin();
|
||||
// resinCounts.fragile = await countFragileResin();
|
||||
// }
|
||||
// 显示结果
|
||||
displayResults(resinCounts);
|
||||
|
||||
// 返回主界面
|
||||
await genshin.returnMainUi();
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
|
||||
log.info("树脂统计完成");
|
||||
return {
|
||||
originalResinCount: resinCounts.original,
|
||||
condensedResinCount: resinCounts.condensed,
|
||||
transientResinCount: resinCounts.transient,
|
||||
fragileResinCount: resinCounts.fragile
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
log.error(`统计树脂数量时发生异常: ${error.message}`);
|
||||
throw error;
|
||||
} finally {
|
||||
if (shouldRestoreMainUi) {
|
||||
await toMainUi();
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到国家选择界面的异步函数
|
||||
* 通过点击指定坐标并等待界面加载来完成切换操作
|
||||
*/
|
||||
async function switchtoCountrySelection(x, y) {
|
||||
// 切换到国家选择界面
|
||||
click(CONFIG.COORDINATES.MAP_SWITCH.x, CONFIG.COORDINATES.MAP_SWITCH.y);
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
click(x, y);
|
||||
await sleep(CONFIG.UI_DELAY);
|
||||
}
|
||||
|
||||
function displayResults(results) {
|
||||
const resultText = `原粹:${results.original} 浓缩:${results.condensed} 须臾:${results.transient} 脆弱:${results.fragile}`;
|
||||
|
||||
log.info(`============ 树脂统计结果 ============`);
|
||||
log.info(`原粹树脂数量: ${results.original}`);
|
||||
log.info(`浓缩树脂数量: ${results.condensed}`);
|
||||
log.info(`须臾树脂数量: ${results.transient}`);
|
||||
log.info(`脆弱树脂数量: ${results.fragile}`);
|
||||
log.info(`====================================`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计原粹树脂数量
|
||||
* @returns {number} 原粹树脂数量
|
||||
*/
|
||||
async function countOriginalResin(tryOriginalMode, opToMainUi, openMap) {
|
||||
if (tryOriginalMode) {
|
||||
log.info("尝试使用原始模式");
|
||||
return await countOriginalResinBackup()
|
||||
} else {
|
||||
log.info('尝试使用优化模式');
|
||||
let ocr_physical = await ocrPhysical(opToMainUi, openMap);
|
||||
|
||||
log.debug(`ocrPhysical: {0}`, JSON.stringify(ocr_physical))
|
||||
await sleep(600)
|
||||
// ocrPhysical = false//模拟异常
|
||||
if (ocr_physical/* && ocrPhysical.ok*/) {
|
||||
return ocr_physical?.current;
|
||||
} else {
|
||||
//异常 退出至地图 尝试使用原始模式
|
||||
await keyPress("VK_ESCAPE")
|
||||
log.error(`ocrPhysical error`);
|
||||
throw new Error("ocrPhysical error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function countOriginalResinBackup() {
|
||||
const originalResin = await recognizeImage(RESIN_ICONS.ORIGINAL);
|
||||
if (!originalResin) {
|
||||
log.warn(`未找到原粹树脂图标`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ocrRegion = {
|
||||
x: originalResin.x,
|
||||
y: originalResin.y,
|
||||
width: CONFIG.OCR_REGIONS.ORIGINAL_RESIN.width,
|
||||
height: CONFIG.OCR_REGIONS.ORIGINAL_RESIN.height
|
||||
};
|
||||
|
||||
// 匹配 xxx/200 格式中的第一个数字(1-3位)
|
||||
const count = await recognizeNumberByOCR(ocrRegion, /(\d{1,3})\/\d+/);
|
||||
if (count !== null) {
|
||||
log.info(`原粹树脂数量: ${count}`);
|
||||
return count;
|
||||
}
|
||||
|
||||
log.warn(`未能识别原粹树脂数量`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ==================== 工具函数 ====================
|
||||
|
||||
/**
|
||||
* 通用图像识别函数
|
||||
* @param {Object} recognitionObject - 识别对象
|
||||
* @param {number} timeout - 超时时间(毫秒)
|
||||
* @returns {Object|null} 识别结果或null
|
||||
*/
|
||||
async function recognizeImage(recognitionObject, timeout = CONFIG.RECOGNITION_TIMEOUT) {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
let gameRegion = undefined
|
||||
try {
|
||||
gameRegion = captureGameRegion();
|
||||
// 直接链式调用,不保存gameRegion变量,避免内存管理问题
|
||||
const imageResult = gameRegion.find(recognitionObject);
|
||||
if (imageResult.isExist()) {
|
||||
return imageResult;
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`识别图像时发生异常: ${error.message}`);
|
||||
} finally {
|
||||
if (gameRegion) {
|
||||
gameRegion.dispose();
|
||||
}
|
||||
}
|
||||
await sleep(CONFIG.SLEEP_INTERVAL);
|
||||
}
|
||||
|
||||
log.warn(`经过多次尝试,仍然无法识别图像`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过OCR识别数字
|
||||
* @param {Object} ocrRegion - OCR识别区域
|
||||
* @param {RegExp} pattern - 匹配模式
|
||||
* @returns {number|null} 识别到的数字或null
|
||||
*/
|
||||
async function recognizeNumberByOCR(ocrRegion, pattern) {
|
||||
let resList = null;
|
||||
let captureRegion = null;
|
||||
try {
|
||||
const ocrRo = RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height);
|
||||
captureRegion = captureGameRegion();
|
||||
resList = captureRegion.findMulti(ocrRo);
|
||||
|
||||
if (!resList || resList.length === 0) {
|
||||
log.warn("OCR未识别到任何文本");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const res of resList) {
|
||||
if (!res || !res.text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const numberMatch = res.text.match(pattern);
|
||||
if (numberMatch) {
|
||||
const number = parseInt(numberMatch[1] || numberMatch[0]);
|
||||
if (!isNaN(number)) {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
log.error(`OCR识别时发生异常: ${error.message}`);
|
||||
return null;
|
||||
} finally {
|
||||
if (resList && typeof resList.dispose === 'function') {
|
||||
resList.dispose();
|
||||
}
|
||||
if (captureRegion && typeof captureRegion.dispose === 'function') {
|
||||
captureRegion.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
ocrPhysical,
|
||||
countOriginalResin,
|
||||
countAllResin,
|
||||
}
|
||||
@@ -368,7 +368,64 @@ async function findTextAndClick(
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 通用找图并点击(支持图片文件路径、Mat)
|
||||
* @param {string|Mat} target 图片路径或已构造的 Mat
|
||||
* @param {number} [x=0] 识别区域左上角 X
|
||||
* @param {number} [y=0] 识别区域左上角 Y
|
||||
* @param {number} [w=1920] 识别区域宽度
|
||||
* @param {number} [h=1080] 识别区域高度
|
||||
* @param {number} [timeout=1000] 识别时间上限(毫秒)
|
||||
* @param {number} [interval=50] 每次识别之间的等待间隔(毫秒)
|
||||
* @param {number} [preClickDelay=50] 点击前等待时间(毫秒)
|
||||
* @param {number} [postClickDelay=50] 点击后等待时间(毫秒)
|
||||
*
|
||||
* @returns
|
||||
* - RecognitionResult | null
|
||||
*/
|
||||
async function findImgAndClick(
|
||||
target,
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = 1920,
|
||||
h = 1080,
|
||||
timeout = 1000,
|
||||
interval = 50,
|
||||
preClickDelay = 50,
|
||||
postClickDelay = 50
|
||||
) {
|
||||
const ro =
|
||||
typeof target === 'string'
|
||||
? RecognitionObject.TemplateMatch(
|
||||
file.readImageMatSync(target),
|
||||
x, y, w, h
|
||||
)
|
||||
: RecognitionObject.TemplateMatch(
|
||||
target,
|
||||
x, y, w, h
|
||||
);
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start <= timeout) {
|
||||
const gameRegion = captureGameRegion();
|
||||
try {
|
||||
const res = gameRegion.find(ro);
|
||||
if (!res.isEmpty()) {
|
||||
await sleep(preClickDelay);
|
||||
res.click();
|
||||
await sleep(postClickDelay);
|
||||
return res;
|
||||
}
|
||||
} finally {
|
||||
gameRegion.dispose();
|
||||
}
|
||||
|
||||
await sleep(interval);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 抛出错误函数
|
||||
* 该函数用于显示错误通知并抛出错误对象
|
||||
@@ -395,5 +452,6 @@ export {
|
||||
isInOutStygianOnslaughtUI,
|
||||
outStygianOnslaughtUI,
|
||||
findTextAndClick,
|
||||
findImgAndClick,
|
||||
throwError,
|
||||
}
|
||||
Reference in New Issue
Block a user