Files

469 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {getJsonPath, toMainUi, throwError, findImgAndClick} from "./tool";
//====================================================
const genshinJson = {
width: 1920,//genshin.width,
height: 1080,//genshin.height,
}
// const MinPhysical = settings.minPhysical?parseInt(settings.minPhysical+''):parseInt(20+'')
// const OpenModeCountMin = settings.openModeCountMin
// let AlreadyRunsCount=0
// let NeedRunsCount=0
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会根据图标宽度动态设置
}
};
//====================================================
/**
* 从字符串中提取数字并组合成一个整数
* @param {string} str - 包含数字的字符串
* @returns {number} - 由字符串中所有数字组合而成的整数
*/
async function saveOnlyNumber(str, defaultValue = 0) {
// 使用正则表达式匹配字符串中的所有数字
// \d+ 匹配一个或多个数字
// .join('') 将匹配到的数字数组连接成一个字符串
// parseInt 将连接后的字符串转换为整数
try {
return parseInt(str.match(/\d+/g).join(''));
} catch (e) {
return defaultValue
}
}
/**
* 识别原粹树脂(体力)的函数
* @param {boolean} [opToMainUi=false] - 是否操作到主界面
* @param {boolean} [openMap=false] - 是否打开地图界面
* @param {number} [minPhysical=20] - 最小可执行体力值
* @param {boolean} [isResinExhaustionMode=true] - 是否启用体力识别功能
* @returns {Promise<Object>} 返回一个包含识别结果的Promise对象
* - ok {boolean}: 是否可执行(体力是否足够)
* - min {number}: 最小可执行体力值
* - current {number}: 当前剩余体力值
*/
async function ocrPhysical(opToMainUi = false, openMap = false, minPhysical = 20, isResinExhaustionMode = true) {
// 检查是否启用体力识别功能,如果未启用则直接返回默认结果
if (!isResinExhaustionMode) {
log.info(`===未启用===`)
return {
ok: true,
min: 0,
current: 0,
}
}
log.debug(`===开始识别原粹树脂===`)
let ms = 1000 // 定义操作延迟时间(毫秒)
if (opToMainUi) {
await sleep(ms)
await toMainUi(); // 切换到主界面
}
if (openMap) {
await sleep(ms)
//打开地图界面
await keyPress('M')
}
await sleep(ms)
log.debug(`===[点击+]===`)
// //点击+ 按钮 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}`,
x: 1373,
y: 22,
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()
// }
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(`===[定位原粹树脂]===`)
//定位月亮
let jsonPath = getJsonPath('yue');
let tmJson = {
path: `${jsonPath.path}${jsonPath.name}${jsonPath.type}`,
x: TemplateOrcJson.x,
y: TemplateOrcJson.y,
width: TemplateOrcJson.width,
height: TemplateOrcJson.height,
}
let templateMatchButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`${tmJson.path}`), tmJson.x, tmJson.y, tmJson.width, tmJson.height);
let region = captureGameRegion()
let button
try {
button = region.find(templateMatchButtonRo);
await sleep(ms)
if ((!button) || !button.isExist()) {
log.error(`${tmJson.path} 匹配异常`)
// throwError(`${tmJson.path} 匹配异常`)
return undefined
}
} finally {
region.dispose()
}
log.debug(`===[识别原粹树脂]===`)
//识别体力 x=1625,y=31,width=79,height=30 / x=1689,y=35,width=15,height=26
let ocr_obj = {
// x: 1623,
x: button.x + button.width,
// y: 32,
y: button.y,
// width: 61,
width: Math.abs(genshinJson.width - button.x - button.width),
height: 26
}
log.debug(`ocr_obj: x={x},y={y},width={width},height={height}`, ocr_obj.x, ocr_obj.y, ocr_obj.width, ocr_obj.height)
let region3 = captureGameRegion()
try {
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 current = await saveOnlyNumber(text)
let execute = (current - minPhysical) >= 0
log.debug(`最小可执行原粹树脂:{min},原粹树脂:{key}`, minPhysical, current,)
// await keyPress('VK_ESCAPE')
return {
ok: execute,
min: minPhysical,
current: current,
}
} catch (e) {
// throwError(`识别失败,err:${e.message}`)
log.error(`识别失败,err:${e.message}`)
return undefined
} finally {
region3.dispose()
//返回地图操作
if (opToMainUi) {
await toMainUi(); // 切换到主界面
}
}
}
// ==================== 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,
}