From 92051f5370cca0d79b314245eae76c6377a8e530 Mon Sep 17 00:00:00 2001 From: Patrick-Ze <19711799+Patrick-Ze@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:17:18 +0800 Subject: [PATCH] =?UTF-8?q?js:=20CD-Aware-AutoGather:=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?OCR=E8=AF=86=E5=88=AB`CountInventoryItem`=E5=B0=9A=E4=B8=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=9A=84=E6=9D=90=E6=96=99=20(#2738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 其他改进: - 改进背包扫描重试机制 - 包装`captureGameRegion`为带上下文管理的函数 --- repo/js/CD-Aware-AutoGather/README.md | 6 +- .../assets/images/CustomScan/冬凌草.png | Bin 0 -> 9784 bytes .../assets/images/CustomScan/冷鲜肉.png | Bin 0 -> 8498 bytes .../images/CustomScan/奇异的「牙齿」.png | Bin 0 -> 8267 bytes .../assets/images/CustomScan/松珀香.png | Bin 0 -> 8445 bytes .../assets/images/CustomScan/红果果菇.png | Bin 0 -> 9172 bytes repo/js/CD-Aware-AutoGather/lib/inventory.js | 240 +++++++++++- repo/js/CD-Aware-AutoGather/lib/ocr.js | 352 +++++++++--------- repo/js/CD-Aware-AutoGather/main.js | 8 +- repo/js/CD-Aware-AutoGather/manifest.json | 5 +- 10 files changed, 418 insertions(+), 193 deletions(-) create mode 100644 repo/js/CD-Aware-AutoGather/assets/images/CustomScan/冬凌草.png create mode 100644 repo/js/CD-Aware-AutoGather/assets/images/CustomScan/冷鲜肉.png create mode 100644 repo/js/CD-Aware-AutoGather/assets/images/CustomScan/奇异的「牙齿」.png create mode 100644 repo/js/CD-Aware-AutoGather/assets/images/CustomScan/松珀香.png create mode 100644 repo/js/CD-Aware-AutoGather/assets/images/CustomScan/红果果菇.png diff --git a/repo/js/CD-Aware-AutoGather/README.md b/repo/js/CD-Aware-AutoGather/README.md index 4e7693b39..c2e8bdcfc 100644 --- a/repo/js/CD-Aware-AutoGather/README.md +++ b/repo/js/CD-Aware-AutoGather/README.md @@ -1,5 +1,3 @@ -**由于使用了尚处于测试版BetterGI中的API,使用稳定版BetterGI的用户请等待`0.54.1`或更高的版本发布后再订阅此脚本** - (在脚本仓库页面阅读此文档,会比在BGI的已订阅脚本界面获得更好的渲染效果) # 功能特点 @@ -11,6 +9,8 @@ - 可设置一个或多个不运行的时间段 - 采集过程自动切换合适的队伍 +**若脚本有问题,可[点击此处进行反馈](https://github.com/babalae/bettergi-scripts-list/issues/new?template=bug_report.yml&script-name=CD-Aware-AutoGather:2.1.0&additional-info=保留此行以便通知作者:%20@Patrick-Ze%0A%0A---%0A)** + # 使用前准备 **双击运行脚本所在目录下的`SymLink.bat`文件,以创建符号链接。** @@ -171,6 +171,8 @@ - 感谢[this-Fish](https://github.com/this-Fish)的改进,基于坐标判断是否更新记录、将材料是否刷新的检查提前都是沿袭的TA的思路 +- 参考了[吉吉喵](https://github.com/JJMdzh)的背包扫描,增加了使用补充OCR的方式识别物品数量的机制 + 最后,要特别感谢绫华,是她陪伴了我的提瓦特之旅。在弃坑之后,唯有这份牵挂,支撑着我重新回到这里。 没有她就没有今天的这个脚本。 diff --git a/repo/js/CD-Aware-AutoGather/assets/images/CustomScan/冬凌草.png b/repo/js/CD-Aware-AutoGather/assets/images/CustomScan/冬凌草.png new file mode 100644 index 0000000000000000000000000000000000000000..64e602d1df4c137183e649cbf53cb77a0e94b77c GIT binary patch literal 9784 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 literal 0 HcmV?d00001 diff --git a/repo/js/CD-Aware-AutoGather/assets/images/CustomScan/奇异的「牙齿」.png b/repo/js/CD-Aware-AutoGather/assets/images/CustomScan/奇异的「牙齿」.png new file mode 100644 index 0000000000000000000000000000000000000000..6b0e68d55a601c8434cb17103a8de318210060a5 GIT binary patch literal 8267 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 { + const parsed = parseInt(value.trim(), 10); + return Number.isNaN(parsed) ? defaultValue : parsed; + }; + if (remainingItems.length > 0) { + log.info(`将通过补充OCR识别{0}等{1}类物品的数量`, remainingItems[0], remainingItems.length); + } + + // 2. 扫描循环:直到所有材料都找到,或者当前画面不再有新目标 + while (remainingItems.length > 0 && !shouldStop()) { + // 使用 withCapture 确保每一帧 region 都能正确 dispose + const foundInThisFrame = await withCapture(async (region) => { + const foundList = []; + for (const itemName of remainingItems) { + const result = region.find(templates[itemName]); + if (result.isExist()) { + // 计算 OCR 区域坐标, 抄了 吉吉喵 大佬的方法 + const tolerance = 1; + const rect = { + x: result.x - tolerance, + y: result.y + 97 - tolerance, + width: 66 + 2 * tolerance, + height: 22 + 2 * tolerance, + }; + // drawRegion(rect); // 调试用 + let ocrResult = region.find(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height)); + if (ocrResult && ocrResult.text) { + const count = _parseInt(ocrResult.text, -2); + sharedResults[itemName] = count; + log.info("{0}: {1}", itemName, count); + foundList.push(itemName); // 记录本帧识别到的条目 + } + } + } + return foundList; // 将本帧找到的列表返回给外部 + }); + + // 3. 更新剩余待扫描列表 + if (foundInThisFrame.length > 0) { + remainingItems = remainingItems.filter((item) => !foundInThisFrame.includes(item)); + } + if (remainingItems.length > 0 && !shouldStop()) { + await sleep(100); // 如果仍有剩余材料,短暂停顿 + } else { + break; // 全部找到,退出循环 + } + } + + return sharedResults; +} + /** * 获取背包中物品的数量。 * 如果没有找到,则为-1;如果找到了但数字识别失败,则为-2 - * - * 暂不支持 冷鲜肉, 红果果菇, 奇异的「牙齿」 */ -async function getItemCount(itemList=null, retry=true) { +async function getItemCount(itemList=null) { if (Object.keys(materialMetadata).length === 0) { Object.assign(materialMetadata, parseCsvTextToDict()); } @@ -194,17 +296,105 @@ async function getItemCount(itemList=null, retry=true) { } else if (itemList == null || itemList.length === 0) { itemList = Object.keys(materialMetadata); } - const renameMap = {"晶蝶": "晶核", "「冷鲜肉」":"冷鲜肉"}; + const normalizedItemList = itemList.map((itemName) => { + return renameMap[itemName] || itemName; + }); + const result = await mainScan(normalizedItemList); + return result; +} + +/** + * 合并扫描结果并实现特征库自动更新(自愈) + * @param {Object} nativeResults - BetterGI 返回的结果 { "鸣草": 10, ... } + * @param {Object} manualResults - 手动 OCR 返回的结果 { "新材料": 5, ... } + * @param {string[]} targetManualList - 本次扫描中,原本判定为需要“手动处理”的材料名清单 + * @returns {Object} 合并后的最终清单 + */ +function mergeAndAutoRepair(nativeResults, manualResults, manualCandidateList) { + // 1. 以原生结果为基础,合并手动结果 + const finalData = { ...nativeResults }; + const newlySupported = []; + + for (const name in manualResults) { + // 如果 nativeResults 里没有这个材料,采用手动结果 + if (!finalData[name] || finalData[name] < 0) { + finalData[name] = manualResults[name]; + } + } + + // 2. 自愈检查:遍历原本以为需要手动的列表 + for (const name of manualCandidateList) { + // 如果 BetterGI 的返回结果里包含了这个材料说明特征库更新了 + if (nativeResults.hasOwnProperty(name) && nativeResults[name] > 0) { + newlySupported.push(name); + } + } + + // 3. 更新本地记录文件 + if (newlySupported.length > 0) { + updateNativeSupportedJson(newlySupported); + } + + return finalData; +} + +/** + * 更新本地受支持列表的持久化存储 + */ +function updateNativeSupportedJson(newItems) { + let registry = {}; + try { + registry = JSON.parse(readTextSync(supportFile) || "{}"); + } catch(e) {} + + const today = new Date().toISOString().split('T')[0]; + newItems.forEach(item => { + registry[item] = today; + log.info(`发现{0}已被内置接口支持,未来将跳过它的补充OCR`, item); + }); + + file.WriteTextSync(supportFile, JSON.stringify(registry, null, 2)); +} + +async function mainScan(normalizedItemList) { + const sharedOcrResults = {}; // 创建共享对象 + let isApiFinished = false; + + const manualList = normalizedItemList.filter(name => manualOcrMaterials.hasOwnProperty(name)); + + // 并发启动 + const apiPromise = getItemCountWithApi(normalizedItemList, sharedOcrResults) + .finally(() => isApiFinished = true); + const manualPromise = manualOcr(manualList, () => isApiFinished, sharedOcrResults); + + // 等待全部完成 + const [apiResults, _] = await Promise.all([apiPromise, manualPromise]); + + // 最后合并结果(此时 sharedOcrResults 已经包含了所有 OCR 成功识别的内容) + const results = mergeAndAutoRepair(apiResults, sharedOcrResults, manualList); + const finalResults = {}; + const reverseRenameMap = Object.entries(renameMap).reduce((acc, [key, value]) => { + acc[value] = key; + return acc; + }, {}); + for (const itemName of normalizedItemList) { + // 如果某个元素没有找到,则不会存在对应的键值,赋值为-1以保持与单个物品查找时一致的行为 + const originName = reverseRenameMap[itemName] || itemName; + finalResults[originName] = results[itemName] ?? -1; + } + return finalResults; +} + +async function getItemCountWithApi(normalizedItemList, sharedOcrResults) { const groupByType = {}; - for (const itemName of itemList) { + for (const itemName of normalizedItemList) { const metadata = materialMetadata[itemName]; const itemType = metadata?.type ?? "Materials"; if (!metadata?.type) { log.warn("未查找到{0}所属的背包分类,默认它是{1}", itemName, itemType); } - const normalizedName = renameMap[itemName] || itemName; groupByType[itemType] = groupByType[itemType] || []; - groupByType[itemType].push(normalizedName); + groupByType[itemType].push(itemName); } let results = {}; @@ -216,26 +406,38 @@ async function getItemCount(itemList=null, retry=true) { })); Object.assign(results, countResult); } - if (retry && itemList.some(item => !(item in results))) { + + // 逻辑:既不在 API 的结果里,也不在 OCR 已经实时识别到的结果里 + const missingItems = normalizedItemList.filter(name => + !(name in results) && !(name in sharedOcrResults) + ); + if (missingItems.length > 0) { // 即使在白天,大多数情况也能识别成功,因此不作为常态机制,仅在失败时使用 - log.info("部分物品识别失败,调整时间和视角后重试"); + log.info(`${missingItems.length}个物品识别失败,调整时间和视角后重试`); await genshin.returnMainUi(); await genshin.setTime(0, 0); await sleep(300); moveMouseBy(0, 9280); await sleep(300); - const retryResults = await getItemCount(itemList, false); + + // 只针对缺失的物品进行重试 + for (const type in groupByType) { + const namesToRetry = groupByType[type].filter(name => missingItems.includes(name)); + if (namesToRetry.length > 0) { + const retryCountResult = await dispatcher.runTask(new SoloTask("CountInventoryItem", { + "gridScreenName": type, + "itemNames": namesToRetry, + })); + // 将重试结果合并到原始 results 中 + Object.assign(results, retryCountResult); + } + } + + // 恢复视角 await genshin.returnMainUi(); keyPress("MBUTTON"); - return retryResults; } - const finalResults = {}; - for (const itemName of itemList) { - const normalizedName = renameMap[itemName] || itemName; - // 如果某个元素没有找到,则不会存在对应的键值,赋值为-1以保持与单个物品查找时一致的行为 - finalResults[itemName] = results[normalizedName] ?? -1; - } - return finalResults; + return results; } function getItemCD(itemName) { diff --git a/repo/js/CD-Aware-AutoGather/lib/ocr.js b/repo/js/CD-Aware-AutoGather/lib/ocr.js index 90ec5efc3..a582e0304 100644 --- a/repo/js/CD-Aware-AutoGather/lib/ocr.js +++ b/repo/js/CD-Aware-AutoGather/lib/ocr.js @@ -1,200 +1,214 @@ -const defaultReplacementMap = { +// 模块级变量,存储默认映射表 +let replacementMap = { 监: "盐", 卵: "卯", }; -/** - * 绘制方框以标记OCR区域 - * - * @param {number[4] | {x: number, y: number, width: number, height: number}} ocrRegion - OCR 区域,数组或对象形式,表示 [x, y, width, height] - * @param {object|null} [captureRegion=null] - 截图区域对象。为 null 时自动调用 captureGameRegion() - * @throws {TypeError} 当 ocrRegion 既不是数组也不是对象时抛出。 - */ -function drawOcrRegion(ocrRegion, captureRegion = null) { - let x, y, width, height; - if (Array.isArray(ocrRegion)) { - [x, y, width, height] = ocrRegion; - } else if (typeof ocrRegion === "object" && ocrRegion !== null) { - ({ x, y, width, height } = ocrRegion); - } else { - throw new TypeError("'ocrRegion' must be an array [x, y, width, height] or an object with x, y, width, height properties"); - } - - let auto_created = false; - if (captureRegion === null) { - captureRegion = captureGameRegion(); - auto_created = true; - } - let region = captureRegion.DeriveCrop(x, y, width, height); - let name = [x, y, width, height].toString(); - region.DrawSelf(name); - region.dispose(); - if (auto_created) { - captureRegion.dispose(); +/** 允许用户在运行时动态修改库的默认映射表 */ +function setGlobalReplacementMap(newMap) { + if (typeof newMap === "object" && newMap !== null) { + replacementMap = newMap; } } -async function getTextInRegion(ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = defaultReplacementMap) { - let x, y, width, height; - if (Array.isArray(ocrRegion)) { - [x, y, width, height] = ocrRegion; - } else if (typeof ocrRegion === "object" && ocrRegion !== null) { - ({ x, y, width, height } = ocrRegion); - } else { - throw new Error("Invalid parameter 'ocrRegion'"); +/** + * 内部私有工具:校正文本 + */ +function _correctText(text, customMap) { + if (!text) return ""; + const map = customMap || replacementMap; // 如果没传自定义的,就用默认的 + let correctedText = text; + for (const [wrong, right] of Object.entries(map)) { + correctedText = correctedText.replace(new RegExp(wrong, "g"), right); } + return correctedText.trim(); +} +/** + * 解析坐标 + * @param {Array|Object} area - 坐标 [x, y, w, h] 或 {x, y, width, height} + */ +function parseRect(area) { + let x, y, width, height; + if (Array.isArray(area)) { + [x, y, width, height] = area; + } else if (typeof area === "object" && area !== null) { + ({ x, y, width, height } = area); + } else { + throw new TypeError("Invalid area format"); + } + return { x, y, width, height }; +} + +/** + * 屏幕截图包装器 + * @param {Function} action - 接收 region 对象的异步函数 + */ +async function withCapture(action) { + const region = captureGameRegion(); + try { + return await action(region); + } finally { + region.dispose?.(); + } +} + +/** + * 局部裁剪包装器 + * @param {object} region - 父级截图区域 + * @param {object} rect - {x, y, width, height} + * @param {Function} action - 接收 crop 对象的异步函数 + */ +async function withCrop(region, rect, action) { + const crop = region.DeriveCrop(rect.x, rect.y, rect.width, rect.height); + try { + return await action(crop); + } finally { + crop.dispose?.(); + } +} + +/** + * 安全地绘制区域标识(自动处理坐标解析与资源释放) + * @param {Array|Object} area - 坐标 [x, y, w, h] 或 {x, y, width, height} + * @param {object} [existingFrame=null] - (可选) 已有的截图对象。如果不传,则自动创建并释放新截图。 + * @param {string} label - 绘制在框上的标签名 + */ +async function drawRegion(area, existingFrame = null, label = null) { + const rect = parseRect(area); + + // 内部绘制逻辑:只负责裁切和画图 + const doDraw = async (f) => { + await withCrop(f, rect, async (crop) => { + const mark = label ? label : `rect_${rect.x}_${rect.y}_${rect.width}_${rect.height}`; + crop.DrawSelf(mark); + }); + }; + + if (existingFrame) { + // 如果外部传了 frame,我们只负责释放 crop,不释放外部的 frame + await doDraw(existingFrame); + } else { + // 如果没传,我们截一张新图,并在画完释放自己截的这张图 + await withCapture(async (tempFrame) => { + await doDraw(tempFrame); + }); + } +} + +/** + * 快速判断区域内是否存在文本(单次检查) + */ +async function isTextExistedInRegion(searchText, ocrRegion) { + const rect = parseRect(ocrRegion); + + return await withCapture(async (captureRegion) => { + const result = captureRegion.find(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height)); + if (!result || !result.text) return false; + + const text = result.text; + return (searchText instanceof RegExp) ? !!text.match(searchText) : text.includes(searchText); + }); +} + + +/** + * 获取区域内的文本(带重试机制) + */ +async function getTextInRegion(ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = null) { + const rect = parseRect(ocrRegion); const debugThreshold = timeout / retryInterval / 3; - let startTime = Date.now(); - let retryCount = 0; // 重试计数 - while (Date.now() - startTime < timeout) { - let captureRegion = captureGameRegion(); - try { - // 尝试 OCR 识别 - let resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, width, height)); // 指定识别区域 - // 遍历识别结果,检查是否找到目标文本 - for (let res of resList) { - // 后处理:根据替换映射表检查和替换错误识别的字符 - let correctedText = res.text; - for (let [wrongChar, correctChar] of Object.entries(replacementMap)) { - correctedText = correctedText.replace(new RegExp(wrongChar, "g"), correctChar); - } + const startTime = Date.now(); + let retryCount = 0; - captureRegion.dispose(); - return correctedText.trim(); + while (Date.now() - startTime < timeout) { + // 使用 withCapture 自动管理截图资源的生命周期 + const result = await withCapture(async (captureRegion) => { + try { + const resList = captureRegion.findMulti(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height)); + // resList 通常不是真正的 JS Array,使用 .count 且使用下标访问 + const count = resList.count || resList.Count || 0; + for (let i = 0; i < count; i++) { + const res = resList[i]; + if (!res || !res.text) continue; + const corrected = _correctText(res.text, replacementMap); + // 如果识别到了有效文本(不为空),则返回 + if (corrected) { + return corrected; + } + } + } catch (error) { + log.warn(`OCR 识别失败,正在进行第 ${retryCount} 次重试...`); } - } catch (error) { - log.warn(`页面标志识别失败,正在进行第 ${retryCount} 次重试...`); - } - retryCount++; // 增加重试计数 - if (retryCount > debugThreshold) { - let region = captureRegion.DeriveCrop(x, y, width, height); - region.DrawSelf("debug"); - region.dispose(); - } - captureRegion.dispose(); + + // 达到调试阈值时画框 + if (++retryCount > debugThreshold) { + await drawRegion(rect, captureRegion, "debug"); + } + return null; + }); + + if (result !== null) return result; await sleep(retryInterval); } return null; } -async function waitForTextAppear(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = defaultReplacementMap) { - let x, y, width, height; - - if (Array.isArray(ocrRegion)) { - [x, y, width, height] = ocrRegion; - } else if (typeof ocrRegion === "object" && ocrRegion !== null) { - ({ x, y, width, height } = ocrRegion); - } else { - throw new Error("Invalid parameter 'ocrRegion'"); - } - - const debugThreshold = timeout / retryInterval / 3; - let startTime = Date.now(); - let retryCount = 0; // 重试计数 +/** + * 等待特定文本出现 + */ +async function waitForTextAppear(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = null) { + const startTime = Date.now(); + + // 循环复用 getTextInRegion 的逻辑思想 while (Date.now() - startTime < timeout) { - let captureRegion = captureGameRegion(); - try { - // 尝试 OCR 识别 - let resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, width, height)); // 指定识别区域 - // 遍历识别结果,检查是否找到目标文本 - for (let res of resList) { - // 后处理:根据替换映射表检查和替换错误识别的字符 - let correctedText = res.text; - for (let [wrongChar, correctChar] of Object.entries(replacementMap)) { - correctedText = correctedText.replace(new RegExp(wrongChar, "g"), correctChar); - } - - if (correctedText.includes(targetText)) { - captureRegion.dispose(); - return { success: true, wait_time: Date.now() - startTime }; - } - } - } catch (error) { - log.warn(`页面标志识别失败,正在进行第 ${retryCount} 次重试...`); + const currentText = await getTextInRegion(ocrRegion, retryInterval, retryInterval, replacementMap); + if (currentText && currentText.includes(targetText)) { + return { success: true, wait_time: Date.now() - startTime }; } - retryCount++; // 增加重试计数 - if (retryCount > debugThreshold) { - let region = captureRegion.DeriveCrop(x, y, width, height); - region.DrawSelf("debug"); - region.dispose(); - } - captureRegion.dispose(); - await sleep(retryInterval); + // 此处不需要额外 sleep,因为 getTextInRegion 内部已经耗费了时间 } return { success: false }; } -async function recognizeTextAndClick(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = defaultReplacementMap) { - let x, y, width, height; - - if (Array.isArray(ocrRegion)) { - [x, y, width, height] = ocrRegion; - } else if (typeof ocrRegion === "object" && ocrRegion !== null) { - ({ x, y, width, height } = ocrRegion); - } else { - throw new Error("Invalid parameter 'ocrRegion'"); - } - +/** + * 识别文本并点击中心点 + */ +async function recognizeTextAndClick(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = null) { + const rect = parseRect(ocrRegion); const debugThreshold = timeout / retryInterval / 3; - let startTime = Date.now(); - let retryCount = 0; // 重试计数 - while (Date.now() - startTime < timeout) { - let captureRegion = captureGameRegion(); - try { - // 尝试 OCR 识别 - let resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, width, height)); // 指定识别区域 - // 遍历识别结果,检查是否找到目标文本 - for (let res of resList) { - // 后处理:根据替换映射表检查和替换错误识别的字符 - let correctedText = res.text; - for (let [wrongChar, correctChar] of Object.entries(replacementMap)) { - correctedText = correctedText.replace(new RegExp(wrongChar, "g"), correctChar); - } + const startTime = Date.now(); + let retryCount = 0; - if (correctedText.includes(targetText)) { - // 如果找到目标文本,计算并点击文字的中心坐标 - let centerX = Math.round(res.x + res.width / 2); - let centerY = Math.round(res.y + res.height / 2); - await click(centerX, centerY); - await sleep(50); - captureRegion.dispose(); - return { success: true, x: centerX, y: centerY }; + while (Date.now() - startTime < timeout) { + const clicked = await withCapture(async (captureRegion) => { + try { + const resList = captureRegion.findMulti(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height)); + // resList 通常不是真正的 JS Array,使用 .count 且使用下标访问 + const count = resList.count || resList.Count || 0; + for (let i = 0; i < count; i++) { + const res = resList[i]; + const correctedText = _correctText(res.text, replacementMap); + if (correctedText.includes(targetText)) { + const centerX = Math.round(res.x + res.width / 2); + const centerY = Math.round(res.y + res.height / 2); + await click(centerX, centerY); + await sleep(50); + return { success: true, x: centerX, y: centerY }; + } } + } catch (e) { + log.warn(`识别点击失败重试中...`); } - } catch (error) { - log.warn(`页面标志识别失败,正在进行第 ${retryCount} 次重试...`); - } - retryCount++; // 增加重试计数 - if (retryCount > debugThreshold) { - let region = captureRegion.DeriveCrop(x, y, width, height); - region.DrawSelf("debug"); - region.dispose(); - } - captureRegion.dispose(); + + if (++retryCount > debugThreshold) { + await drawRegion(rect, captureRegion); + } + return null; + }); + + if (clicked) return clicked; await sleep(retryInterval); } return { success: false }; } - -async function isTextExistedInRegion(searchText, ocrRegion) { - let x, y, width, height; - if (Array.isArray(ocrRegion)) { - [x, y, width, height] = ocrRegion; - } else if (typeof ocrRegion === "object" && ocrRegion !== null) { - ({ x, y, width, height } = ocrRegion); - } else { - throw new Error("Invalid parameter 'ocrRegion'"); - } - let captureRegion = captureGameRegion(); - const result = captureRegion.find(RecognitionObject.ocr(x, y, width, height)); - captureRegion.dispose(); - if (result.text) { - if (typeof searchText === "string") { - return result.text.includes(searchText); - } else if (searchText instanceof RegExp) { - return result.text.match(searchText); - } - } - return false; -} diff --git a/repo/js/CD-Aware-AutoGather/main.js b/repo/js/CD-Aware-AutoGather/main.js index c97d64b30..0eed75ba1 100644 --- a/repo/js/CD-Aware-AutoGather/main.js +++ b/repo/js/CD-Aware-AutoGather/main.js @@ -229,7 +229,13 @@ async function runGatherMode() { const sortedTasksToRun = sortTasksByGap(groupedTasksToRun); // file.writeTextSync("sortedTasksToRun.json", JSON.stringify(sortedTasksToRun, null, 2)); - log.info("共{0}种材料需要采集,将从缺失数量最多的材料开始", Object.keys(sortedTasksToRun).length); + const taskCount = Object.keys(sortedTasksToRun).length; + if (taskCount === 0) { + log.info("所有材料的数量均已达标"); + return; + } + + log.info("共{0}种材料需要采集,将从缺失数量最多的材料开始", taskCount); for (const [name, { target, current, tasks }] of Object.entries(sortedTasksToRun)) { const coolType = tasks[0].coolType; const targetTxt = target === null ? "∞" : target; diff --git a/repo/js/CD-Aware-AutoGather/manifest.json b/repo/js/CD-Aware-AutoGather/manifest.json index 49539d1a0..27b0b529b 100644 --- a/repo/js/CD-Aware-AutoGather/manifest.json +++ b/repo/js/CD-Aware-AutoGather/manifest.json @@ -1,8 +1,9 @@ { "manifest_version": 1, "name": "带CD管理和目标设定的自动采集", - "version": "2.0.2", - "bgi_version": "0.54.1-alpha.1", + "_version_note": "更新版本号时请一并更新README的问题反馈链接中的版本号", + "version": "2.1.0", + "bgi_version": "0.55.0", "description": "自动同步你通过BetterGI订阅的地图追踪任务,执行采集任务,并管理材料采集目标和刷新时间。\n支持联机模式采集和多账号记录。\n首次使用前请先简单阅读说明", "authors": [ {