From d2675a9610e98148360125ca8c1bcc7f33fbb7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=E7=B4=AB=E7=83=9Fowo?= <232029620+jiangziyanowo@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:56:36 +0800 Subject: [PATCH] AutoMonday-V1.3.4 & YNF-AutoEat-V1.6.1 update (#2309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AutoMonday-V1.3.4 update 1. 针对资源释放重构了文字识别代码 2. 优化了部分代码,添加了调试信息 * YNF-AutoEat V1.6.1 update 1. 针对资源释对文字识别部分进行了重构 2. 优化了代码结构,添加了调试信息 * Update repo/js/YNF-AutoEat/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: 躁动的氨气 <131591012+zaodonganqi@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- repo/js/AutoMonday/README.md | 3 + repo/js/AutoMonday/assets/四方八方之网.json | 11 +- repo/js/AutoMonday/main.js | 239 +++++++++++++------- repo/js/AutoMonday/manifest.json | 2 +- repo/js/YNF-AutoEat/README.md | 13 +- repo/js/YNF-AutoEat/main.js | 235 +++++++++++++------ repo/js/YNF-AutoEat/manifest.json | 2 +- 7 files changed, 339 insertions(+), 166 deletions(-) diff --git a/repo/js/AutoMonday/README.md b/repo/js/AutoMonday/README.md index 5d253d38b..260767a31 100644 --- a/repo/js/AutoMonday/README.md +++ b/repo/js/AutoMonday/README.md @@ -41,6 +41,9 @@ --------------------------------------------------------------------------------------------------------------------------------- ## 更新日志 +### 1.3.4(2025.11.04) +1. 针对资源释放重构了文字识别代码 +2. 优化了部分代码,添加了调试信息 ### 1.3.3(2025.10.27) 1. 修复了部分设备在某些任务运行结束后未能及时回到主页面导致后续任务无法正常执行的bug 2. 修复了偶尔会被电气水晶摸到的bug diff --git a/repo/js/AutoMonday/assets/四方八方之网.json b/repo/js/AutoMonday/assets/四方八方之网.json index 29b29e617..22fe54525 100644 --- a/repo/js/AutoMonday/assets/四方八方之网.json +++ b/repo/js/AutoMonday/assets/四方八方之网.json @@ -24,7 +24,7 @@ "description": "设计师若紫,购买四方八方之网", "enable_monster_loot_split": false, "items": [], - "last_modified_time": 1759502044257, + "last_modified_time": 1762243516854, "map_match_method": "", "map_name": "Teyvat", "name": "四方八方之网", @@ -54,9 +54,10 @@ "y": -3001.84765625 }, { - "action": "", + "action": "combat_script", + "action_params": "wait(0.5)", "id": 2, - "move_mode": "run", + "move_mode": "walk", "point_ext_params": { "description": "", "enable_monster_loot_split": false, @@ -69,8 +70,8 @@ } }, "type": "target", - "x": -4242.96875, - "y": -2999.5625 + "x": -4242.6875, + "y": -2999.65625 } ] } \ No newline at end of file diff --git a/repo/js/AutoMonday/main.js b/repo/js/AutoMonday/main.js index 14c8fa8a9..00a5e4aab 100644 --- a/repo/js/AutoMonday/main.js +++ b/repo/js/AutoMonday/main.js @@ -193,55 +193,135 @@ } /** - * 文字OCR识别封装函数(测试中,未封装完成,后续会优化逻辑) - * @param text 要识别的文字,默认为"空参数" - * @param timeout 超时时间,单位为秒,默认为10秒 - * @param afterBehavior 点击模式,0表示不点击,1表示点击识别到文字的位置,2表示输出模式,默认为0 - * @param debugmodel 调试代码,0表示输入判断模式,1表示输出位置信息,2表示输出判断模式,默认为0 - * @param x OCR识别区域的起始X坐标,默认为0 - * @param y OCR识别区域的起始Y坐标,默认为0 - * @param w OCR识别区域的宽度,默认为1920 - * @param h OCR识别区域的高度,默认为1080 - * @returns 包含识别结果的对象,包括识别的文字、坐标和是否找到的结果 - */ - async function textOCR(text = "空参数", timeout = 10, afterBehavior = 0, debugmodel = 0, x = 0, y = 0, w = 1920, h = 1080) { - const startTime = new Date(); - var Outcheak = 0 - for (var ii = 0; ii < 10; ii++) { - // 获取一张截图 - var captureRegion = captureGameRegion(); - var res1 - var res2 - var conuntcottimecot = 1; - var conuntcottimecomp = 1; - // 对整个区域进行 OCR - var resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, w, h)); - //log.info("OCR 全区域识别结果数量 {len}", resList.count); - if (resList.count !== 0) { - for (let i = 0; i < resList.count; i++) { // 遍历的是 C# 的 List 对象,所以要用 count,而不是 length - let res = resList[i]; - res1 = res.text - conuntcottimecomp++; - if (res.text.includes(text) && debugmodel == 3) { return result = { text: res.text, x: res.x, y: res.y, found: true }; } - if (res.text.includes(text) && debugmodel !== 2) { - conuntcottimecot++; - if (debugmodel === 1 & x === 0 & y === 0) { log.info("全图代码位置:({x},{y},{h},{w})", res.x - 10, res.y - 10, res.width + 10, res.Height + 10); } - if (afterBehavior === 1) { await sleep(1000); click(res.x, res.y); } else { if (debugmodel === 1 & x === 0 & y === 0) { log.info("点击模式:关") } } - if (afterBehavior === 2) { await sleep(100); keyPress("F"); } else { if (debugmodel === 1 & x === 0 & y === 0) { log.info("F模式:关"); } } - if (conuntcottimecot >= conuntcottimecomp / 2) { return result = { text: res.text, x: res.x, y: res.y, found: true }; } else { return result = { found: false }; } + * 文字OCR识别封装函数(支持空文本匹配任意文字) + * @param {string} text - 要识别的文字,默认为"空参数",空字符串会匹配任意文字 + * @param {number} timeout - 超时时间,单位为秒,默认为10秒 + * @param {number} afterBehavior - 点击模式,0=不点击,1=点击文字位置,2=按F键,默认为0 + * @param {number} debugmodel - 调试模式,0=无输出,1=基础日志,2=详细输出,3=立即返回,默认为0 + * @param {number} x - OCR识别区域起始X坐标,默认为0 + * @param {number} y - OCR识别区域起始Y坐标,默认为0 + * @param {number} w - OCR识别区域宽度,默认为1920 + * @param {number} h - OCR识别区域高度,默认为1080 + * @param {number} matchMode - 匹配模式,0=包含匹配,1=精确匹配,默认为0 + * @returns {object} 包含识别结果的对象 {text, x, y, found} + */ + async function textOCREnhanced( + text = "空参数", + timeout = 10, + afterBehavior = 0, + debugmodel = 0, + x = 0, + y = 0, + w = 1920, + h = 1080, + matchMode = 0 + ) { + const startTime = Date.now(); + const timeoutMs = timeout * 1000; + let lastResult = null; + let captureRegion = null; // 用于存储截图对象 + + // 只在调试模式1下输出基本信息 + if (debugmodel === 1) { + if (text === "") { + log.info(`OCR: 空文本模式 - 匹配任意文字`); + } else if (text === "空参数") { + log.warn(`OCR: 使用默认参数"空参数"`); + } + } + + while (Date.now() - startTime < timeoutMs) { + try { + // 获取截图并进行OCR识别 + captureRegion = captureGameRegion(); + const resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, w, h)); + + // 遍历识别结果 + for (let i = 0; i < resList.count; i++) { + const res = resList[i]; + + // 检查是否匹配 + let isMatched = false; + if (text === "") { + // 空文本匹配任意文字 + isMatched = true; + } else if (matchMode === 1) { + // 精确匹配 + isMatched = res.text === text; + } else { + // 包含匹配(默认) + isMatched = res.text.includes(text); } - if (debugmodel === 2) { - if (res1 === res2) { conuntcottimecot++; res2 = res1; } - //log.info("输出模式:全图代码位置:({x},{y},{h},{w},{string})", res.x-10, res.y-10, res.width+10, res.Height+10, res.text); - if (Outcheak === 1) { if (conuntcottimecot >= conuntcottimecomp / 2) { return result = { text: res.text, x: res.x, y: res.y, found: true }; } else { return result = { found: false }; } } + + if (isMatched) { + // 只在调试模式1下输出匹配成功信息 + if (debugmodel === 1) { + log.info(`OCR成功: "${res.text}" 位置(${res.x},${res.y})`); + } + + // 调试模式3: 立即返回 + if (debugmodel === 3) { + // 释放内存 + if (captureRegion) { + captureRegion.dispose(); + } + return { text: res.text, x: res.x, y: res.y, found: true }; + } + + // 执行后续行为 + switch (afterBehavior) { + case 1: // 点击文字位置 + await sleep(1000); + click(res.x, res.y); + break; + case 2: // 按F键 + await sleep(100); + keyPress("F"); + break; + default: + // 不执行任何操作 + break; + } + + // 记录最后一个匹配结果但不立即返回 + lastResult = { text: res.text, x: res.x, y: res.y, found: true }; } } + + // 释放截图对象内存 + if (captureRegion) { + captureRegion.dispose(); + } + + // 如果找到匹配结果,根据调试模式决定是否立即返回 + if (lastResult && debugmodel !== 2) { + return lastResult; + } + + // 短暂延迟后继续下一轮识别 + await sleep(100); + + } catch (error) { + // 发生异常时释放内存 + if (captureRegion) { + captureRegion.dispose(); + } + log.error(`OCR异常: ${error.message}`); + await sleep(100); } - const NowTime = new Date(); - if ((NowTime - startTime) > timeout * 1000) { if (debugmodel === 2) { if (resList.count === 0) { return result = { found: false }; } else { Outcheak = 1; ii = 2; } } else { Outcheak = 0; if (debugmodel === 1 & x === 0 & y === 0) { log.info(`${timeout}秒超时退出,"${text}"未找到`) }; return result = { found: false }; } } - else { ii = 2; if (debugmodel === 1 & x === 0 & y === 0) { log.info(`"${text}"识别中……`); } } - await sleep(100); } + + if (debugmodel === 1) { + // 超时处理 + if (text === "") { + log.info(`OCR超时: ${timeout}秒内未找到任何文字`); + } else { + log.info(`OCR超时: ${timeout}秒内未找到"${text}"`); + } + } + + // 返回最后一个结果或未找到 + return lastResult || { found: false }; } //判断队内角色 @@ -279,7 +359,7 @@ if (!ifbblIn) { throw new Error("队伍中未包含角色:芭芭拉"); } } while ((NowTime - startTime) < actiontime * 1000) { - const ocrRes = await textOCR("质变产生了以下物质", 0.7, 1, 0, 539, 251, 800, 425); + const ocrRes = await textOCREnhanced("质变产生了以下物质", 0.7, 1, 0, 539, 251, 800, 425); if (ocrRes.found) { click(970, 760); if (!ifAkf) { return true; } @@ -311,8 +391,8 @@ await includes("芭芭拉"); } while ((NowTime - startTime) < actiontime * 1000) { - let result = await textOCR("获得", 0.2, 0, 3, 159, 494, 75, 44); - if (result.found) { + const ifEarn = await textOCREnhanced("获得", 0.2, 0, 3, 159, 494, 75, 44); + if (ifEarn.found) { const currentTime = new Date().getTime(); if (currentTime - lastIncrementTime >= intervalTime) { getFood++; @@ -349,10 +429,10 @@ await sleep(1000); await click(1067, 57);//点开背包,可做图像识别优化 - const bagOk = await textOCR("小道具", 3, 0, 0, 126, 17, 99, 53); if (!bagOk.found) { throw new Error("未打开'小道具'页面,请确保背包已正确打开并切换到小道具标签页"); }//确认在小道具界面 + const bagOk = await textOCREnhanced("小道具", 3, 0, 3, 126, 17, 99, 53); if (!bagOk.found) { throw new Error("未打开'小道具'页面,请确保背包已正确打开并切换到小道具标签页"); }//确认在小道具界面 await sleep(500); - await imageRecognitionEnhanced(ZHIBIANYI, 1, 1, 0);//识别质变仪图片 - if (!result.found) { + const ZbyResult = await imageRecognitionEnhanced(ZHIBIANYI, 1, 1, 0);//识别质变仪图片 + if (!ZbyResult.found) { await genshin.returnMainUi(); log.warn("'质变仪CD中'或'未找到质变仪!'"); return false;//质变仪找不到就直接退出 @@ -371,26 +451,27 @@ //检测并进入质变仪界面 await middleButtonClick(); await sleep(1000); - let Fmeun = await textOCR("参量质变仪", 2, 2, 3, 1205, 508, 140, 53);//单条F检测 + let Fmeun = await textOCREnhanced("参量质变仪", 2, 2, 0, 1205, 508, 140, 53);//单条F检测 await keyPress("F"); let CHAx = await imageRecognitionEnhanced(CHA, 3, 0, 0, 1766, 3, 140, 90); if (!Fmeun.found && !CHAx.found) { return false; } //检测是否到达材料页面 - await textOCR("进行质变", 3, 0, 0, 1675, 994, 150, 50); if (!result.found) { throw new Error("质变仪页面未打开"); }//单条F检测 + const startTransform = await textOCREnhanced("进行质变", 3, 0, 3, 1675, 994, 150, 50); if (!startTransform.found) { throw new Error("质变仪页面未打开"); }//单条F检测 await sleep(500); + let itemResult; switch (ITEM) { case 1: await click(863, 47); // 初始化与'1养成道具'相关的设置或资源 - await textOCR("养成道具", 3, 0, 0, 120, 19, 240, 50); if (!result.found) { throw new Error("'养成道具'页面未打开"); } + itemResult = await textOCREnhanced("养成道具", 3, 0, 3, 120, 19, 240, 50); if (!itemResult.found) { throw new Error("'养成道具'页面未打开"); } break; case 2: await click(959, 45);// 初始化与'2食物'相关的设置或资源 - await textOCR("食物", 3, 0, 0, 124, 16, 93, 63); if (!result.found) { throw new Error("'食物'页面未打开"); } + itemResult = await textOCREnhanced("食物", 3, 0, 3, 124, 16, 93, 63); if (!itemResult.found) { throw new Error("'食物'页面未打开"); } break; case 3: await click(1050, 50); // 初始化与'3材料'相关的设置或资源 - await textOCR("材料", 3, 0, 0, 124, 16, 93, 63); if (!result.found) { throw new Error("'材料'页面未打开"); } + itemResult = await textOCREnhanced("材料", 3, 0, 3, 124, 16, 93, 63); if (!itemResult.found) { throw new Error("'材料'页面未打开"); } break; default: // 处理未知ITEM值的情况 @@ -408,16 +489,16 @@ const maxRetries = 20; // 最大重试次数 let retries = 0; // 当前重试次数 while (retries < maxRetries) { - const result = await imageRecognitionEnhanced(BH, 1, 0, 0, 115, 115, 1155, 845); - if (result.found) { + const ifBh = await imageRecognitionEnhanced(BH, 1, 0, 0, 115, 115, 1155, 845); + if (ifBh.found) { await leftButtonUp(); await sleep(500); - await click(result.x, result.y); + await click(ifBh.x, ifBh.y); await sleep(1000); await click(440, 1008); //选择最大数量 await sleep(1000); await click(1792, 1019); //质变按钮 - const zbPanel = await textOCR("参量质变仪", 3, 0, 0, 828, 253, 265, 73); if (!zbPanel.found) { log.error("单种材料不足,退出!"); } + const zbPanel = await textOCREnhanced("参量质变仪", 3, 0, 3, 828, 253, 265, 73); if (!zbPanel.found) { log.error("单种材料不足,退出!"); } await sleep(1000); await click(1183, 764); //确认 ; await sleep(1000); @@ -473,7 +554,7 @@ // 背包过期物品识别 async function handleExpiredItems() { - const ifGuoqi = await textOCR("物品过期", 1.5, 0, 0, 870, 280, 170, 40); + const ifGuoqi = await textOCREnhanced("物品过期", 1.5, 0, 3, 870, 280, 170, 40); if (ifGuoqi.found) { log.info("检测到过期物品,正在处理..."); await sleep(500); @@ -642,17 +723,17 @@ await sleep(1500); keyPress("F"); - await textOCR("单人挑战", 8, 1, 1, 1615, 990, 220, 50);// 等待“单人挑战”出现 + await textOCREnhanced("单人挑战", 8, 1, 0, 1615, 990, 220, 50);// 等待“单人挑战”出现 await sleep(10); - await textOCR("开始挑战", 8, 1, 1, 1615, 990, 220, 50);// 等待“开始挑战”出现 + await textOCREnhanced("开始挑战", 8, 1, 0, 1615, 990, 220, 50);// 等待“开始挑战”出现 await sleep(10); - await textOCR("地脉异常", 10, 1, 1, 840, 405, 180, 55);// 等待“地脉异常”出现 + await textOCREnhanced("地脉异常", 10, 1, 0, 840, 405, 180, 55);// 等待“地脉异常”出现 await sleep(1000); let success; for (let attempt = 0; attempt < 10; attempt++) { - success = await textOCR("启动", 0.5, 0, 3, 1210, 500, 85, 85); + success = await textOCREnhanced("启动", 0.5, 0, 3, 1210, 500, 85, 85); if (!success || !success.found) { keyPress("F"); break; @@ -689,7 +770,7 @@ await sleep(1000); // 判断是否诱捕完成 - const res2 = await textOCR("晶蝶诱捕装置", 1, 0, 3, 0, 0, 360, 500); + const res2 = await textOCREnhanced("晶蝶诱捕装置", 1, 0, 3, 0, 0, 360, 500); if (!res2.found) { log.warn("诱捕未完成,不执行后续操作"); @@ -752,7 +833,7 @@ await sleep(1000); await click(1067, 57);//点开背包,可做图像识别优化 - await textOCR("小道具", 3, 0, 0, 126, 17, 99, 53); if (!result.found) { throw new Error("未打开'小道具'页面,请确保背包已正确打开并切换到小道具标签页"); }//确认在小道具界面 + const ifXdj = await textOCREnhanced("小道具", 3, 0, 3, 126, 17, 99, 53); if (!ifXdj.found) { throw new Error("未打开'小道具'页面,请确保背包已正确打开并切换到小道具标签页"); }//确认在小道具界面 await sleep(500); const res1 = await imageRecognitionEnhanced(ZHIBIANYI, 1, 1, 0);//识别质变仪图片 if (res1.found) { @@ -808,7 +889,7 @@ keyDown("VK_MENU"); await sleep(500); - const res1 = await textOCR("烹饪", 5, 0, 0, 1150, 460, 155, 155); + const res1 = await textOCREnhanced("烹饪", 5, 0, 3, 1150, 460, 155, 155); if (res1.found) { click(res1.x + 15, res1.y + 15); } @@ -837,7 +918,7 @@ const maxRetries = 20; // 最大重试次数 let retries = 0; // 当前重试次数 while (retries < maxRetries) { - const res2 = await textOCR(food, 1, 0, 3, 116, 116, 1165, 880); + const res2 = await textOCREnhanced(food, 1, 0, 3, 116, 116, 1165, 880); if (res2.found) { await leftButtonUp(); await sleep(500); @@ -848,7 +929,7 @@ click(1700, 1020);// 制作 await sleep(1000); - await textOCR("自动烹饪", 5, 1, 0, 725, 1000, 130, 45); + await textOCREnhanced("自动烹饪", 5, 1, 0, 725, 1000, 130, 45); await sleep(800); click(960, 460); await sleep(800); @@ -899,20 +980,20 @@ log.info("正在执行本周锻造任务……"); await AutoPath("瓦格纳"); keyDown("VK_MENU"); - await textOCR("瓦格纳", 5, 1, 0, 1150, 460, 155, 155); + await textOCREnhanced("瓦格纳", 5, 1, 0, 1150, 460, 155, 155); await sleep(800); keyUp("VK_MENU"); click(960, 540);// 对话 await sleep(1000); - await textOCR("委托锻造", 5, 1, 0, 1150, 400, 300, 300); + await textOCREnhanced("委托锻造", 5, 1, 0, 1150, 400, 300, 300); await sleep(1500); click(960, 540);// 对话 await sleep(1500); - const res1 = await textOCR("可收取", 1, 0, 2, 625, 265, 130, 50); + const res1 = await textOCREnhanced("可收取", 1, 0, 3, 625, 265, 130, 50); if (res1.found) { click(180, 1015);// 全部领取 await sleep(1500); @@ -926,7 +1007,7 @@ click(360, 1015);// 筛选按钮 await sleep(1500); - await textOCR("武器升级材料", 5, 1, 0, 30, 170, 410, 60); + await textOCREnhanced("武器升级材料", 5, 1, 0, 30, 170, 410, 60); await sleep(1500); await imageRecognitionEnhanced(mineralFile, 10, 1, 0, 40, 210, 720, 770) @@ -1050,10 +1131,10 @@ click(960, 540);// 对话 await sleep(1000); - await textOCR("购买", 3, 1, 0, 1320, 630, 130, 60);// 对话:购买四方八方之网 + await textOCREnhanced("购买", 3, 1, 0, 1320, 630, 130, 60);// 对话:购买四方八方之网 await sleep(1000); - const res1 = await textOCR("已售罄", 0.5, 0, 0, 1515, 920, 90, 35); + const res1 = await textOCREnhanced("已售罄", 1, 0, 3, 1515, 920, 90, 35); if (!res1.found) { click(1670, 1015); await sleep(800); @@ -1064,7 +1145,7 @@ } click(1175, 780);// 点击购买 - await sleep(100); + await sleep(300); await genshin.returnMainUi(); log.info("本周四方网购买任务已完成!"); @@ -1101,13 +1182,13 @@ click(960, 540); await sleep(2000); - await textOCR("我要结算", 3, 1, 0, 1325, 550, 250, 55); + await textOCREnhanced("我要结算", 3, 1, 0, 1325, 550, 250, 55); await sleep(1000); click(960, 540); await sleep(1000); - const notHave = await textOCR("没有投资券", 3, 0, 0, 830, 925, 160, 50); + const notHave = await textOCREnhanced("没有投资券", 3, 0, 3, 830, 925, 160, 50); if (notHave.found) { click(960, 540); await sleep(1500); @@ -1133,7 +1214,7 @@ click(960, 540); await sleep(1500); - await textOCR("我要结算", 3, 1, 0, 1325, 500, 250, 80); + await textOCREnhanced("我要结算", 3, 1, 0, 1325, 500, 250, 80); await sleep(1000); click(960, 540); diff --git a/repo/js/AutoMonday/manifest.json b/repo/js/AutoMonday/manifest.json index aff0c98ee..ee353e490 100644 --- a/repo/js/AutoMonday/manifest.json +++ b/repo/js/AutoMonday/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "全自动周一", - "version": "1.3.3", + "version": "1.3.4", "tags": [ "周常", "纪行", diff --git a/repo/js/YNF-AutoEat/README.md b/repo/js/YNF-AutoEat/README.md index d43026734..011baaff9 100644 --- a/repo/js/YNF-AutoEat/README.md +++ b/repo/js/YNF-AutoEat/README.md @@ -17,19 +17,16 @@ 3. 要使用的食材尽量在顶端,尽管js能够找到,但是在多次运行时会很浪费时间 -## 作者留言 -这个脚本是作者第一次pr的脚本。大家可以发现更新频率很高,但是对于这种小功能的脚本来说,更新频率高也意味着欠缺的地方还有很多。 -作者也在慢慢学习,慢慢在摸索,偶尔可能会出现新版本不如上一版本的情况…… -那么……欢迎使用此脚本,毕竟使用的过程也是帮作者发现bug的过程。 -有问题请及时到频道反馈,作者会高频刷帖的,看到之后会很快处理,来频道尽情蹂虐作者吧! -~~(PS:许愿也行,需求尽管提,没回就是做不到,欸嘿)~~ - - ## 致谢 1. 感谢古又老师的悉心指导,没有古又老师就没有现在的~~伊涅芙灭绝计划~~调味品批量生产计划 2. 感谢氨气老师和秋秋云老师,在审核时提供了宝贵的意见和建议。 --------------------------------------------------------------------------------------------------------------------------------- ## 更新日志 +### 1.6.1(2025.11.04) +1. 针对资源释放的文字识别部分进行了重构 +2. 优化了代码结构,添加了调试信息 +### 1.6(2025.11.02) +1. 批量添加dispose ### 1.5(2025.10.16) 1. 对部分场景衔接的地方做了优化,脚本运行更丝滑 2. 处理了部分运行错误的情况,在某些场景下发生特定错误时不会直接停止运行,提高了脚本运行效率 diff --git a/repo/js/YNF-AutoEat/main.js b/repo/js/YNF-AutoEat/main.js index 9a43d2793..9aba06b21 100644 --- a/repo/js/YNF-AutoEat/main.js +++ b/repo/js/YNF-AutoEat/main.js @@ -1,4 +1,25 @@ (async function () { + // ===== 1. 预处理部分 ===== + + const party = settings.n;//设置好要切换的队伍 + const food = settings.food;//设置要吃的食物 + const foodCount = settings.foodNumber - 1;//点击“+”的次数,比食物数量少1 + const n = settings.runNumber;//运行次数 + + const Dm = `assets/地脉.png` + const pingguo = `assets/${food}.png`;//食物图片路径 + const zjz = `assets/zhengjianzhao.png`;//伊涅芙证件照 + const foodbag = `assets/foodbag.png`;//背包的“食物”界面 + + const eater = "伊涅芙";//客官一位~ + + // 添加验证 + if (!party) { log.error("队伍名为空,请仔细阅读readme并进行设置后再使用此脚本!"); return; }// 利用队伍是否为空判断用户有没有进行设置 + if (foodCount > 98 || foodCount < 0) { log.error("食材数量请填写1-99之间的数字!"); return; }//确保食材数量1~99 + if (n <= 0) { log.error("不是哥们,运行次数还能小于0???"); return; }//确保运行次数合法 + + // ===== 2. 子函数定义部分 ===== + /** * 封装函数,执行图片识别及点击操作(测试中,未封装完成,后续会优化逻辑) * @param {string} imagefilePath - 模板图片路径 @@ -129,56 +150,135 @@ } /** - * 文字OCR识别封装函数(测试中,未封装完成,后续会优化逻辑) - * @param text 要识别的文字,默认为"空参数" - * @param timeout 超时时间,单位为秒,默认为10秒 - * @param afterBehavior 点击模式,0表示不点击,1表示点击识别到文字的位置,2表示输出模式,默认为0 - * @param debugmodel 调试代码,0表示输入判断模式,1表示输出位置信息,2表示输出判断模式,默认为0 - * @param x OCR识别区域的起始X坐标,默认为0 - * @param y OCR识别区域的起始Y坐标,默认为0 - * @param w OCR识别区域的宽度,默认为1920 - * @param h OCR识别区域的高度,默认为1080 - * @returns 包含识别结果的对象,包括识别的文字、坐标和是否找到的结果 - */ - async function textOCR(text = "空参数", timeout = 10, afterBehavior = 0, debugmodel = 0, x = 0, y = 0, w = 1920, h = 1080) { - const startTime = new Date(); - var Outcheak = 0 - for (var ii = 0; ii < 10; ii++) { - // 获取一张截图 - var captureRegion = captureGameRegion(); - var res1 - var res2 - var conuntcottimecot = 1; - var conuntcottimecomp = 1; - // 对整个区域进行 OCR - var resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, w, h)); - captureRegion.dispose(); - //log.info("OCR 全区域识别结果数量 {len}", resList.count); - if (resList.count !== 0) { - for (let i = 0; i < resList.count; i++) { // 遍历的是 C# 的 List 对象,所以要用 count,而不是 length - let res = resList[i]; - res1 = res.text - conuntcottimecomp++; - if (res.text.includes(text) && debugmodel == 3) { return result = { text: res.text, x: res.x, y: res.y, found: true }; } - if (res.text.includes(text) && debugmodel !== 2) { - conuntcottimecot++; - if (debugmodel === 1 & x === 0 & y === 0) { log.info("全图代码位置:({x},{y},{h},{w})", res.x - 10, res.y - 10, res.width + 10, res.Height + 10); } - if (afterBehavior === 1) { await sleep(1000); click(res.x, res.y); } else { if (debugmodel === 1 & x === 0 & y === 0) { log.info("点击模式:关") } } - if (afterBehavior === 2) { await sleep(100); keyPress("F"); } else { if (debugmodel === 1 & x === 0 & y === 0) { log.info("F模式:关"); } } - if (conuntcottimecot >= conuntcottimecomp / 2) { return result = { text: res.text, x: res.x, y: res.y, found: true }; } else { return result = { found: false }; } + * 文字OCR识别封装函数(支持空文本匹配任意文字) + * @param {string} text - 要识别的文字,默认为"空参数",空字符串会匹配任意文字 + * @param {number} timeout - 超时时间,单位为秒,默认为10秒 + * @param {number} afterBehavior - 点击模式,0=不点击,1=点击文字位置,2=按F键,默认为0 + * @param {number} debugmodel - 调试模式,0=无输出,1=基础日志,2=详细输出,3=立即返回,默认为0 + * @param {number} x - OCR识别区域起始X坐标,默认为0 + * @param {number} y - OCR识别区域起始Y坐标,默认为0 + * @param {number} w - OCR识别区域宽度,默认为1920 + * @param {number} h - OCR识别区域高度,默认为1080 + * @param {number} matchMode - 匹配模式,0=包含匹配,1=精确匹配,默认为0 + * @returns {object} 包含识别结果的对象 {text, x, y, found} + */ + async function textOCREnhanced( + text = "空参数", + timeout = 10, + afterBehavior = 0, + debugmodel = 0, + x = 0, + y = 0, + w = 1920, + h = 1080, + matchMode = 0 + ) { + const startTime = Date.now(); + const timeoutMs = timeout * 1000; + let lastResult = null; + let captureRegion = null; // 用于存储截图对象 + + // 只在调试模式1下输出基本信息 + if (debugmodel === 1) { + if (text === "") { + log.info(`OCR: 空文本模式 - 匹配任意文字`); + } else if (text === "空参数") { + log.warn(`OCR: 使用默认参数"空参数"`); + } + } + + while (Date.now() - startTime < timeoutMs) { + try { + // 获取截图并进行OCR识别 + captureRegion = captureGameRegion(); + const resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, w, h)); + + // 遍历识别结果 + for (let i = 0; i < resList.count; i++) { + const res = resList[i]; + + // 检查是否匹配 + let isMatched = false; + if (text === "") { + // 空文本匹配任意文字 + isMatched = true; + } else if (matchMode === 1) { + // 精确匹配 + isMatched = res.text === text; + } else { + // 包含匹配(默认) + isMatched = res.text.includes(text); } - if (debugmodel === 2) { - if (res1 === res2) { conuntcottimecot++; res2 = res1; } - //log.info("输出模式:全图代码位置:({x},{y},{h},{w},{string})", res.x-10, res.y-10, res.width+10, res.Height+10, res.text); - if (Outcheak === 1) { if (conuntcottimecot >= conuntcottimecomp / 2) { return result = { text: res.text, x: res.x, y: res.y, found: true }; } else { return result = { found: false }; } } + + if (isMatched) { + // 只在调试模式1下输出匹配成功信息 + if (debugmodel === 1) { + log.info(`OCR成功: "${res.text}" 位置(${res.x},${res.y})`); + } + + // 调试模式3: 立即返回 + if (debugmodel === 3) { + // 释放内存 + if (captureRegion) { + captureRegion.dispose(); + } + return { text: res.text, x: res.x, y: res.y, found: true }; + } + + // 执行后续行为 + switch (afterBehavior) { + case 1: // 点击文字位置 + await sleep(1000); + click(res.x, res.y); + break; + case 2: // 按F键 + await sleep(100); + keyPress("F"); + break; + default: + // 不执行任何操作 + break; + } + + // 记录最后一个匹配结果但不立即返回 + lastResult = { text: res.text, x: res.x, y: res.y, found: true }; } } + + // 释放截图对象内存 + if (captureRegion) { + captureRegion.dispose(); + } + + // 如果找到匹配结果,根据调试模式决定是否立即返回 + if (lastResult && debugmodel !== 2) { + return lastResult; + } + + // 短暂延迟后继续下一轮识别 + await sleep(100); + + } catch (error) { + // 发生异常时释放内存 + if (captureRegion) { + captureRegion.dispose(); + } + log.error(`OCR异常: ${error.message}`); + await sleep(100); } - const NowTime = new Date(); - if ((NowTime - startTime) > timeout * 1000) { if (debugmodel === 2) { if (resList.count === 0) { return result = { found: false }; } else { Outcheak = 1; ii = 2; } } else { Outcheak = 0; if (debugmodel === 1 & x === 0 & y === 0) { log.info(`${timeout}秒超时退出,"${text}"未找到`) }; return result = { found: false }; } } - else { ii = 2; if (debugmodel === 1 & x === 0 & y === 0) { log.info(`"${text}"识别中……`); } } - await sleep(100); } + + if (debugmodel === 1) { + // 超时处理 + if (text === "") { + log.info(`OCR超时: ${timeout}秒内未找到任何文字`); + } else { + log.info(`OCR超时: ${timeout}秒内未找到"${text}"`); + } + } + + // 返回最后一个结果或未找到 + return lastResult || { found: false }; } //判断队内角色 @@ -224,9 +324,9 @@ keyPress("F"); - await textOCR("单人挑战", 8, 1, 1, 1615, 990, 220, 50);//等待“单人挑战”出现 - await textOCR("开始挑战", 8, 1, 1, 1615, 990, 220, 50);//等待“开始挑战”出现 - await textOCR("地脉异常", 15, 1, 1, 840, 405, 180, 55);//等待“地脉异常”出现 + await textOCREnhanced("单人挑战", 8, 1, 0, 1615, 990, 220, 50);//等待“单人挑战”出现 + await textOCREnhanced("开始挑战", 8, 1, 0, 1615, 990, 220, 50);//等待“开始挑战”出现 + await textOCREnhanced("地脉异常", 15, 1, 0, 840, 405, 180, 55);//等待“地脉异常”出现 await sleep(1000); return true; @@ -365,9 +465,20 @@ } } + // 版本信息 + async function outputVersion() { + let scriptVersion, scriptname; + const manifestContent = file.readTextSync("manifest.json"); + const manifest = JSON.parse(manifestContent); + scriptVersion = manifest.version; + scriptname = manifest.name; + + log.warn(`${scriptname}:V${scriptVersion}`); + } + // 背包过期物品识别,需要在背包界面,并且是1920x1080分辨率下使用 async function handleExpiredItems() { - const ifGuoqi = await textOCR("物品过期", 1.5, 0, 0, 870, 280, 170, 40); + const ifGuoqi = await textOCREnhanced("物品过期", 1.5, 0, 3, 870, 280, 170, 40); if (ifGuoqi.found) { log.info("检测到过期物品,正在处理..."); await sleep(500); @@ -376,34 +487,14 @@ // else { log.info("未检测到过期物品"); }//频繁开关背包,不需要每次都提示 } - - // ===== MAIN EXECUTION ===== - - - // 预处理 - const party = settings.n;//设置好要切换的队伍 - const food = settings.food;//设置要吃的食物 - const foodCount = settings.foodNumber - 1;//点击“+”的次数,比食物数量少1 - const n = settings.runNumber;//运行次数 - - const Dm = `assets/地脉.png` - const pingguo = `assets/${food}.png`;//食物图片路径 - const zjz = `assets/zhengjianzhao.png`;//伊涅芙证件照 - const foodbag = `assets/foodbag.png`;//背包的“食物”界面 - - const eater = "伊涅芙";//客官一位~ - - // 添加验证 - if (!party) { log.error("队伍名为空,请仔细阅读readme并进行设置后再使用此脚本!"); return; }// 利用队伍是否为空判断用户有没有进行设置 - if (foodCount > 98 || foodCount < 0) { log.error("食材数量请填写1-99之间的数字!"); return; }//确保食材数量1~99 - if (n <= 0) { log.error("不是哥们,运行次数还能小于0???"); return; }//确保运行次数合法 - + // ===== 3. 主函数执行部分 ===== //设置分辨率和缩放 setGameMetrics(1920, 1080, 1); - await genshin.returnMainUi();//回到主界面,在秘境中可能会卡几秒 + await outputVersion(); + log.warn("使用前请仔细阅读readme并进行相关设置!"); log.warn("请确保食材充足!"); diff --git a/repo/js/YNF-AutoEat/manifest.json b/repo/js/YNF-AutoEat/manifest.json index e2146be24..b8a36e377 100644 --- a/repo/js/YNF-AutoEat/manifest.json +++ b/repo/js/YNF-AutoEat/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "伊涅芙の自助餐", - "version": "1.6", + "version": "1.6.1", "tags": [ "伊涅芙", "调味品"