自动体力计划 0.0.3 修复:健全体力识别(双重识别兜底) (#2987)

This commit is contained in:
云端客
2026-03-19 14:33:39 +08:00
committed by GitHub
parent 8910330e58
commit d6254f06b7
10 changed files with 440 additions and 52 deletions

View File

@@ -12,6 +12,8 @@
- 限制只在特定星期几执行(例如周日限定本、周本)
- 设置执行优先级(数字越大越先跑)
- 支持三种配置来源(输入 / UID专属 / bgi_tools远程
- 自动检测幽境开启状态
![check](md/check.jpg)
## 配置项说明
@@ -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 拉取/推送配置

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -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());

View File

@@ -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) {
//体力耗尽

View File

@@ -1,6 +1,6 @@
{
"name": "自动体力计划",
"version": "0.0.2",
"version": "0.0.3",
"description": "",
"settings_ui": "settings.json",
"main": "main.js",

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -17,6 +17,11 @@
"label": "自动秘境计划配置\n语法:说明太长 去看文档",
"default": ""
},
{
"name": "exclude_run_exception",
"type": "checkbox",
"label": "忽略运行异常\n(当前计划出现异常后,会执行下一个计划)与循环体力计划冲突"
},
{
"name": "loop_plan",
"type": "checkbox",

View File

@@ -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"})
)

View File

@@ -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,
}

View File

@@ -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,
}