From 3acc288d37f449bb72c7f8a3b91e88b76c0a9378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=B1=E5=91=B1z?= <131586533+jidingcai@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:33:57 +0800 Subject: [PATCH] =?UTF-8?q?=E9=93=81=E5=8C=A0=E9=93=BA2.4.0=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20(#2426)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 铁匠铺2.4.0更新 ### 2.4.0`(2025-11-29)` - **优化**:纳塔铁匠铺路线可能出现跑过头的问题 - **新增**: 1. 支持月国铁匠铺,矿石虹滴晶的使用 2. 重构全新的矿石使用排序方式 3. 选择矿石排序为0时,可以排除对应矿石 * Add files via upload * Add files via upload * Add files via upload * Add files via upload 修复背包界面内矿物识别不到的问题 * Add files via upload --- repo/js/铁匠铺/README.md | 29 +- .../铁匠铺/assets/Pathing/挪德卡莱铁匠铺.json | 67 ++ repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json | 85 +- .../assets/Picture/RainbowdropCrystal.png | Bin 0 -> 1068 bytes .../ItemImage/RainbowdropCrystal.png | Bin 0 -> 8031 bytes repo/js/铁匠铺/main.js | 851 +++++++++++------- repo/js/铁匠铺/manifest.json | 4 +- repo/js/铁匠铺/settings.json | 75 +- 8 files changed, 709 insertions(+), 402 deletions(-) create mode 100644 repo/js/铁匠铺/assets/Pathing/挪德卡莱铁匠铺.json create mode 100644 repo/js/铁匠铺/assets/Picture/RainbowdropCrystal.png create mode 100644 repo/js/铁匠铺/assets/RecognitionObject/ItemImage/RainbowdropCrystal.png diff --git a/repo/js/铁匠铺/README.md b/repo/js/铁匠铺/README.md index d1ba52275..bfac88669 100644 --- a/repo/js/铁匠铺/README.md +++ b/repo/js/铁匠铺/README.md @@ -53,9 +53,10 @@ BGI 0.48.2 版本 ```json { "smithyName": "枫丹铁匠铺", - "ore": "水晶块", - "secondaryOre": "紫晶块", - "tertiaryOre": "萃凝晶", + "CondessenceCrystal": "萃凝晶", + "CrystalChunk": "水晶块", + "AmethystLump": "紫晶块", + "RainbowdropCrystal": "虹滴晶", "notice": true, "forgedOrNot": "是", "model": "模式一" @@ -68,9 +69,13 @@ BGI 0.48.2 版本 - 水晶块 - 紫晶块 - 萃凝晶 +- 虹滴晶 -**扩展矿石配置**: -如需使用"星银矿石"、"白铁块"或"铁块",请在 `settings.json` 中取消对应矿石前的注释(`//`),保存后即可生效。 +**矿石优先级说明**: +- 每种矿石的优先级可在 `settings.json` 中配置,数值范围为 `0-4`。 +- **优先级为 0**:表示不使用该矿石,脚本会自动排除。 +- **优先级为 1-4**:数值越小,优先级越高。 +- **优先级相同时**:按照矿石的默认顺序(萃凝晶 > 水晶块 > 紫晶块 > 虹滴晶)决定使用顺序。 ## ⚙️ 功能详解 @@ -87,6 +92,7 @@ BGI 0.48.2 版本 - **智能兜底**:自动模式识别失败时,自动切换到手动优先级模式 - **通知与日志**:支持桌面通知和详细日志,便于追踪脚本执行状态 - **异常处理**:多次识别失败、材料不足等情况均有详细提示和自动处理 +- **优先级排序**:脚本会根据用户配置的矿石优先级进行排序,优先级为 0 的矿石会被自动排除。 ## 📋 使用注意事项 @@ -94,6 +100,7 @@ BGI 0.48.2 版本 2. **窗口状态**:运行脚本时请保持游戏窗口处于前台且未被遮挡 3. **权限设置**:确保已授予脚本必要的系统权限 3. **权限设置**:部分模块由于使用了快捷键,请确保快捷键为默认值。例如纳塔铁匠铺以及仅领取锻造前检查,使用地图快捷键"M";模式一矿物识别部分,使用背包快捷键"B"。因为模式一调用了OCR,数字识别部分可能出现错误 +4. **矿石优先级配置**:请确保至少有一种矿石的优先级大于 0,否则脚本将停止运行并提示错误。 ## ❓ 常见问题 @@ -114,6 +121,12 @@ Q:OCR识别矿石数量不准确怎么办? ## 📜 更新日志 +### 2.4.0`(2025-11-29)` +- **优化**:纳塔铁匠铺路线可能出现跑过头的问题 +- **新增**: + 1. 支持月国铁匠铺,矿石虹滴晶的使用 + 2. 重构全新的矿石使用排序方式 + 3. 选择矿石排序为0时,可以排除对应矿石 ### 2.3.1`(2025-08-30)` - **修复**: @@ -163,7 +176,9 @@ Q:OCR识别矿石数量不准确怎么办? --- -**最后更新**:2025 年 8 月 30 日 +**最后更新**:2025 年 11 月 29 日 **维护者**:@呱呱z -> 💡 提示:建议定期检查更新以获取最佳体验和最新功能 \ No newline at end of file +> 💡 提示:建议定期检查更新以获取最佳体验和最新功能 + +**矿石优先级说明**: \ No newline at end of file diff --git a/repo/js/铁匠铺/assets/Pathing/挪德卡莱铁匠铺.json b/repo/js/铁匠铺/assets/Pathing/挪德卡莱铁匠铺.json new file mode 100644 index 000000000..3bb756dd5 --- /dev/null +++ b/repo/js/铁匠铺/assets/Pathing/挪德卡莱铁匠铺.json @@ -0,0 +1,67 @@ +{ + "info": { + "authors": [ + { + "links": "", + "name": "呱呱z" + } + ], + "bgi_version": "0.45.0", + "description": "", + "enable_monster_loot_split": false, + "last_modified_time": 1759332476359, + "map_match_method": "", + "map_name": "Teyvat", + "name": "挪德卡莱铁匠铺", + "tags": [], + "type": "collect", + "version": "1.0" + }, + "positions": [ + { + "action": "", + "action_params": "", + "id": 1, + "move_mode": "walk", + "type": "teleport", + "x": 9458.0341796875, + "y": 1660.6650390625 + }, + { + "action": "", + "action_params": "", + "id": 2, + "move_mode": "fly", + "type": "path", + "x": 9448.4970703125, + "y": 1651.05859375 + }, + { + "action": "", + "action_params": "", + "id": 3, + "move_mode": "run", + "type": "path", + "x": 9419.3515625, + "y": 1640.990234375 + }, + { + "action": "", + "action_params": "", + "id": 4, + "move_mode": "run", + "type": "path", + "x": 9412.615234375, + "y": 1642.33447265625 + }, + { + "action": "", + "action_params": "", + "id": 5, + "move_mode": "dash", + "type": "target", + "x": 9410.013671875, + "y": 1643.15771484375 + } + ] +} \ No newline at end of file diff --git a/repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json b/repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json index e03c069f1..548985062 100644 --- a/repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json +++ b/repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json @@ -1,40 +1,49 @@ { - "info": { - "authors": [ - { - "links": "", - "name": "呱呱z" - } - ], - "bgi_version": "0.48.2", - "description": "", - "enable_monster_loot_split": false, - "last_modified_time": 1756367298175, - "map_match_method": "", - "map_name": "Teyvat", - "name": "纳塔铁匠铺", - "tags": [], - "type": "collect", - "version": "1.0" + "info": { + "authors": [ + { + "links": "", + "name": "呱呱z" + } + ], + "bgi_version": "0.45.0", + "description": "", + "enable_monster_loot_split": false, + "last_modified_time": 1759332274135, + "map_match_method": "", + "map_name": "Teyvat", + "name": "纳塔铁匠铺", + "tags": [], + "type": "collect", + "version": "1.1" + }, + "positions": [ + { + "action": "", + "action_params": "", + "id": 1, + "move_mode": "run", + "type": "path", + "x": 9025.548828125, + "y": -1913.60009765625 }, - "positions": [ - { - "action": "", - "action_params": "", - "id": 1, - "move_mode": "walk", - "type": "path", - "x": 9025.55078125, - "y": -1913.60107421875 - }, - { - "action": "", - "action_params": "", - "id": 2, - "move_mode": "dash", - "type": "target", - "x": 9036.7490234375, - "y": -1939.0771484375 - } - ] -} + { + "action": "", + "action_params": "", + "id": 2, + "move_mode": "run", + "type": "path", + "x": 9033.8525390625, + "y": -1938.1494140625 + }, + { + "action": "", + "action_params": "", + "id": 3, + "move_mode": "dash", + "type": "target", + "x": 9035.6044921875, + "y": -1938.7529296875 + } + ] +} \ No newline at end of file diff --git a/repo/js/铁匠铺/assets/Picture/RainbowdropCrystal.png b/repo/js/铁匠铺/assets/Picture/RainbowdropCrystal.png new file mode 100644 index 0000000000000000000000000000000000000000..08fb393a1de737569fcf0f6919b5f118ef14177d GIT binary patch literal 1068 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 timeout) { - log.warn(`识别失败 | 尝试: ${attemptCount}次,未能识别到目标图像`); - return { - ...resultTemplate, - attempts: attemptCount, - elapsed: Date.now() - startTime, - error: lastError?.message - }, - false; + if (!silentOnFail) { + log.warn(`模板匹配失败 | 尝试: ${attemptCount}次,未能识别到目标图像`); + } + return false; } await sleep(interval); } } -// 图像识别函数 -function recognizeImage(imagePath, x, y, searchWidth, searchHeight) { - try { - let template = file.ReadImageMatSync(imagePath); - let recognitionObject = RecognitionObject.TemplateMatch(template, x, y, searchWidth, searchHeight); - recognitionObject.threshold = 0.85; - recognitionObject.Use3Channels = true; - let ro = captureGameRegion(); - let result = ro.find(recognitionObject); - ro.dispose(); - return result.isExist() ? result : null; - } catch (error) { - if (notice) { - notification.error(`图像识别失败,路径: ${imagePath}, 错误: ${error.message}`); - } else { - log.error(`图像识别失败,路径: ${imagePath}, 错误: ${error.message}`); - } - return null; - } -} - -// 检查是否需要跳过该矿石(若已属于备选中) -function shouldSkipOre(targetOre, compareOres) { - return compareOres.includes(targetOre); -} - // 通知日志:使用矿石提示 function determineOre(oreType) { let message = `将使用 ${OreChineseMap[oreType]} 锻造矿石`; @@ -278,61 +282,202 @@ function determineOre(oreType) { return message; } +// 2.4.0*新增:过滤并排序列表函数 +/** + * 在锻造逻辑前,生成一个按优先级排序的矿石列表,数字为 0 的矿石不参与锻造: + * @param {Object} priorityConfig - 矿石优先级配置对象,键为矿石名称,值为优先级数字 // priorityConfig: { oreName: priorityNumber, ... } + * @param {Array} tieBreakOrder - 矿石昵称顺序数组,用于同优先级时的排序 // tieBreakOrder: 当优先级相同或重复时,按此数组的顺序决定先后(越靠前优先级越高) + * @returns {Array} 返回按优先级排序且去重的矿石名称数组 + */ +function getSortedOresByPriority(priorityConfig, tieBreakOrder = []) { + try { + if (!priorityConfig || typeof priorityConfig !== 'object') { + log.error('优先级配置无效'); + return []; + } + // 调试日志:显示原始配置 + //log.info(`原始优先级配置: ${JSON.stringify(priorityConfig)}`); + const entries = Object.entries(priorityConfig || {}).filter(([_, priority]) => Number(priority) > 0); + // 调试日志:显示过滤后的条目 + //log.info(`[排序调试] 过滤后的矿石条目: ${JSON.stringify(entries)}`); + // 去重并稳定排序:先按 priority 升序,同优先级时按 tieBreakOrder 的索引升序 + entries.sort((a, b) => { + const pa = Number(a[1]); + const pb = Number(b[1]); + // 调试日志:显示每次比较 + //log.info(`[排序调试] 比较 ${a[0]}=${pa} vs ${b[0]}=${pb}`); + if (pb !== pa) + return pa - pb; // 数字小的排前面(升序) + const ai = tieBreakOrder.indexOf(a[0]); + const bi = tieBreakOrder.indexOf(b[0]); + const aIdx = ai === -1 ? Number.MAX_SAFE_INTEGER : ai; + const bIdx = bi === -1 ? Number.MAX_SAFE_INTEGER : bi; + return aIdx - bIdx; + }); + // 调试日志:显示排序后的条目 + //log.info(`[排序调试] 排序后的矿石条目: ${JSON.stringify(entries)}`); + // 返回去重后的名称列表(保持排序) + const seen = new Set(); + const result = []; + for (const [name] of entries) { + if (!seen.has(name)) { + seen.add(name); + result.push(name); + } + } + // 调试日志:显示最终结果 + log.info(`矿石使用排序: ${JSON.stringify(result)}`); + return result; + } catch (error) { + log.error(`生成排序矿石列表失败,错误: ${error.message}`); + return []; + } +} + + +/*********************** 封装函数 ***********************/ + +// 2.4.0*新增:封装启用通知与否日志记录 +function logMessage(level, message) { + if (notice) { + notification[level](message); + } else { + log[level](message); + } +} + +// 2.4.0*新增:【背包界面】封装背包界面打开与检测 +async function openInventory() { + let maxAttempts = 3; // 最大尝试次数,防止无限循环 + + try { + // ========== 第一阶段:确保打开背包界面 ========== + log.info("开始尝试打开背包界面..."); + await sleep(1000); + + for (let bagAttempt = 1; bagAttempt <= maxAttempts; bagAttempt++) { + //log.info(`尝试打开背包 (第 ${bagAttempt}/${maxAttempts} 次)`); + + // 先执行一次标准的打开背包操作 + await genshin.returnMainUi(); + keyPress("B"); await sleep(1000); + + // 检测是否成功进入背包界面 + if (await findAndInteract(InventoryInterFaceRo, { singleAttempt: true })) { + log.info("✓ 成功进入背包界面"); + break; // 成功,跳出循环 + } + + //log.warn(`第 ${bagAttempt} 次打开背包失败`); + + // 如果这是最后一次尝试,直接失败返回 + if (bagAttempt === maxAttempts) { + log.error(`无法打开背包界面,已重试 ${maxAttempts} 次`); + return false; + } + } + + // ========== 第二阶段:切换到材料标签页 ========== + log.info("准备切换到材料界面..."); + + for (let materialAttempt = 1; materialAttempt <= maxAttempts; materialAttempt++) { + //log.info(`尝试进入材料界面 (第 ${materialAttempt}/${maxAttempts} 次)`); + + // 先检测是否已经在材料界面 + if (await findAndInteract(MaterialsFaceRo, { singleAttempt: true })) { + log.info("✓ 已处于材料界面"); + return true; // 完全成功 + } + + // 点击材料标签页图标 + //log.info("点击材料标签页图标..."); + await findAndInteract(DisabledMaterialsFaceRo, { useClick: true }); + await sleep(600); // 等待界面响应 + + // 检测点击后是否成功切换 + if (await findAndInteract(MaterialsFaceRo, { singleAttempt: true })) { + log.info("✓ 成功切换到材料界面"); + return true; // 完全成功 + } + + //log.warn(`第 ${materialAttempt} 次切换材料界面失败`); + + // 最后一次尝试也失败 + if (materialAttempt === maxAttempts) { + log.error("多次尝试后仍未能进入材料界面,请检查界面状态"); + return false; + } + } + } catch (error) { + logMessage('error', `打开背包界面失败,错误: ${error.message}`); + return false; + } +} + /*********************** 主逻辑函数 ***********************/ +// 2.4.0*新增:【配置检查】模块:当所有矿石优先级均为 0 时,停止脚本并通知用户 +async function configurationCheck() { + // 详细的所有矿石优先级为0的检查 + //log.info(`[配置检查] 开始检查矿石优先级配置`); + try { + // 2.4.0*新增:当所有矿石优先级均为 0 时,停止脚本并通知用户 + const allOrePriorities = Object.values(orePriorityConfig || {}).map(v => Number(v) || 0); + const allZero = allOrePriorities.length > 0 && allOrePriorities.every(v => v === 0); + if (allZero) { + const msg = "所有矿石优先级均为0或无效 ,已停止脚本。请在设置中至少启用一种矿石。"; + logMessage('error', msg); + return false; // 配置无效,停止脚本 + } + return true; + } catch (error) { + logMessage('error', `[配置检查] 失败,错误: ${error.message}`); + return false; // 出现错误,停止脚本 + } +} + + + + // 模式一:自动识别背包中数量最多的矿石 async function getMaxOreType() { try { - //开启背包 - await sleep(1000); - await genshin.returnMainUi(); - keyPress("B"); await sleep(1000); - //【背包】界面检测 - if (!await findAndInteract(InventoryInterFaceRo, { - singleAttempt: true - })) { - log.info("未检测到背包界面,尝试返回主界面并打开背包"); - await genshin.returnMainUi(); - keyPress("B"); await sleep(1000); - } else { - log.info("检测到处于背包界面"); - } - // 【材料】界面检测,多次尝试,避免过期道具卡弹窗 - let maxAttempts = 10; // 最大尝试次数,防止无限循环 - let attempts = 0; - - while (attempts < maxAttempts) { - if (await findAndInteract(MaterialsFaceRo, - { - singleAttempt: true - })) { - log.info("已经处于材料界面,准备识别矿物数量"); - break; // 成功进入界面,退出循环 - } else { - log.info("未处于材料界面,准备点击材料界面图标"); - await findAndInteract(DisabledMaterialsFaceRo, - { - useClick: true - }); - await sleep(600); // 等待界面响应 - attempts++; - } - } - if (attempts === maxAttempts) { - log.error("多次尝试后仍未能进入材料界面,请检查界面状态或操作逻辑"); - } else { - log.info("成功进入材料界面"); + if (!await openInventory()) { + return false; // 无法打开背包界面,停止操作 } const oreResults = [ - { name: "萃凝晶", ro: CondessenceCrystalRo }, + { name: "水晶块", ro: CrystalChunkRo }, { name: "紫晶块", ro: AmethystLumpRo }, - { name: "水晶块", ro: CrystalChunkRo } + { name: "萃凝晶", ro: CondessenceCrystalRo }, + { name: "虹滴晶", ro: RainbowdropCrystalRo } ]; + + // 定义日志收集对象 + const priorityLog = []; // 优先级检查日志 + // 过滤掉优先级为0的矿石 + //const validOres = oreResults.filter(ore => Number(orePriorityConfig[ore.name]) > 0); + const validOres = oreResults.filter(ore => { + const priority = Number(orePriorityConfig[ore.name]); + const isValid = priority > 0; + // 收集优先级检查日志 + priorityLog.push(`矿石 ${ore.name} 优先级: ${priority}, 是否使用对应矿: ${isValid}`); + //log.debug(`矿石 ${ore.name} 优先级: ${priority}, 是否有效: ${isValid}`); + return isValid; + + }); + // 在过滤完成后一次性输出所有日志 + if (priorityLog.length > 0) { + log.info(`矿石优先级检查详情:\n${priorityLog.join('\n')}`); + } + let maxOre = null; let maxCount = 0; - for (const ore of oreResults) { + // 定义日志收集对象 + const quantityLog = []; // 数量识别日志 + + for (const ore of validOres) { const result = await findAndInteract(ore.ro, { useClick: true, timeout: 5000, @@ -346,10 +491,11 @@ async function getMaxOreType() { let resList = ro.findMulti(RecognitionObject.ocr(ocrX, ocrY, 130, 55)); ro.dispose(); let oreNum = 0; - if (resList.count > 0) { + if (resList && resList.count > 0 && resList[0]?.text) { let text = resList[0].text.replace(/[^\d]/g, ""); oreNum = parseInt(text, 10) || 0; - log.info(`识别到 ${OreChineseMap[ore.name]} 数量: ${oreNum}`); + quantityLog.push(`识别到 ${OreChineseMap[ore.name]} 数量: ${oreNum}`); + //log.info(`识别到 ${OreChineseMap[ore.name]} 数量: ${oreNum}`); } if (oreNum > maxCount) { maxCount = oreNum; @@ -363,18 +509,20 @@ async function getMaxOreType() { */ } } - return maxOre ? { name: maxOre, count: maxCount } : null; // 修改返回值 - } catch (error) { - if (notice) { - notification.error(`自动识别背包中数量最多的矿石失败,错误: ${error.message}`); - } else { - log.error(`识自动识别背包中数量最多的矿石失败,错误: ${error.message}`); + + // 数量识别日志 + if (quantityLog.length > 0) { + log.info(`矿石数量识别:\n${quantityLog.join('\n')}`); } + return maxOre ? { name: maxOre, count: maxCount } : null; // 修改返回值 + + } catch (error) { + logMessage('error', `自动识别背包中数量最多的矿石失败,错误: ${error.message}`); return null; } } -// 自动前往铁匠铺 +// 【路径函数】自动前往铁匠铺 async function autoSmithy(smithyName) { await genshin.returnMainUi(); await sleep(1000); @@ -391,119 +539,120 @@ async function autoSmithy(smithyName) { } let filePath = `assets/Pathing/${smithyName}.json`; await pathingScript.runFile(filePath); - if (notice) { - notification.send(`已抵达 ${smithyName}`); - } else { - log.info(`已抵达 ${smithyName}`); - } } catch (error) { - if (notice) { - notification.error(`执行 ${smithyName} 路径时发生错误: ${error.toString()}`); - } else { - log.error(`执行 ${smithyName} 路径时发生错误: ${error.toString()}`); - } + logMessage('error', `执行 ${smithyName} 路径时发生错误: ${error.toString()}`); + return false; // 出现错误,停止脚本 } + if (notice) { + notification.send(`已抵达 ${smithyName}`); + } else { + log.info(`已抵达 ${smithyName}`); + } + return true; // 成功抵达 } -// 尝试识别并锻造矿石 -async function tryForgeOre(oreType, skipCheckOres = []) { - // 若矿石在跳过列表中则直接返回 - if (shouldSkipOre(oreType, skipCheckOres)) { - if (notice) { - //notification.send(`跳过 ${OreChineseMap[oreType]},因为已存在于优先选择中`); - } - return false; - } +// 【锻造界面】识别并进行锻造矿石 +async function tryForgeOre(oreType) { // 获取矿石图像路径 const imagePath = ingredientImageMap[oreType]; if (!imagePath) { - if (notice) { + /*if (notice) { notification.error(`未找到矿石图像路径: ${OreChineseMap[oreType]}`); } else { log.error(`未找到矿石图像路径: ${OreChineseMap[oreType]}`); } +*/ + logMessage('error', `未找到矿石图像路径: ${OreChineseMap[oreType]}`); return false; } - log.info(`开始识别矿石: ${OreChineseMap[oreType]}`); - const scanOffset = { x: -35, y: -35 }; + log.info(`【锻造界面】开始寻找矿石: ${OreChineseMap[oreType]}`); const maxAttempts = 3; for (let attempt = 0; attempt < maxAttempts; attempt++) { let found = false; - for (const coordinate of gridCoordinates) { - const scanX = coordinate.x + scanOffset.x; - const scanY = coordinate.y + scanOffset.y; - const imageResult = recognizeImage(imagePath, scanX, scanY, 70, 70); - if (imageResult) { - found = true; - imageResult.click(); - await sleep(250); - // if (notice) { - // notification.send(`找到矿石: ${OreChineseMap[oreType]}`); - // } else { - log.info(`找到矿石: ${OreChineseMap[oreType]}`); - // } - determineOre(oreType); - // 点击“开始锻造”按钮并进行OCR识别 - const ocrRegion = { x: 660, y: 495, width: 1250 - 660, height: 550 - 495 }; - let clickAttempts = 0; - let forgingTriggered = false; - while (clickAttempts < 4 && !forgingTriggered) { - let ro1 = captureGameRegion(); - let ConfirmButton = ro1.find(ConfirmDeployButtonRo); - ro1.dispose(); - if (ConfirmButton.isExist()) { - ConfirmButton.click(); - clickAttempts++; + // 使用模板匹配替代图像识别 + const templateMatchResult = await findAndInteract( + RecognitionObject.TemplateMatch( + file.ReadImageMatSync(imagePath), + 40, 200, 770, 720 + ), + { + useClick: true, // 是否点击 + timeout: 3000, // 超时时间 + interval: 500, // 重试间隔 + } + ); + await sleep(250); + + if (templateMatchResult && templateMatchResult.success) { + found = true; + log.info(`找到矿石: ${OreChineseMap[oreType]}`); + determineOre(oreType); + + // 点击“开始锻造”按钮并进行OCR识别 + const ocrRegion = { x: 660, y: 495, width: 1250 - 660, height: 550 - 495 }; + let clickAttempts = 0; + let forgingTriggered = false; + while (clickAttempts < 4 && !forgingTriggered) { + + if (await findAndInteract(ConfirmDeployButtonRo, + { + useClick: true, // 是否点击 + singleAttempt: true } - await sleep(200); - let ro2 = captureGameRegion(); - let ocrResults = ro2.find( - RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height) - ); - ro2.dispose(); - if (ocrResults) { - // log.info(`${ocrResults.text}`); - if (ocrResults.text.includes("今日已无法锻造")) { - if (notice) { - notification.send("检测到 今日已无法锻造 结束锻造"); - } else { - log.info("检测到 今日已无法锻造 结束锻造"); - } - await sleep(250); - await click(960, 1042); - await sleep(200); - await click(960, 1042);// 多次点击结束弹窗 - return true; // 终止锻造流程 - } - else if (ocrResults.text.includes("材料不足")) { - if (notice) { - notification.send("检测到 材料不足 跳过当前矿物。请检查背包,及时补充矿物。"); - } else { - log.info("检测到 材料不足 跳过当前矿物。请检查背包,及时补充矿物。"); - } - clickAttempts--; // 出现材料不足时减去一次点击计数 - await sleep(250); - await click(960, 1042); - await sleep(200); - await click(960, 1042);// 多次点击结束弹窗 - return false; // 跳过当前矿石 - } - } - if (clickAttempts === 4) { - await sleep(1000); + )) { + clickAttempts++; + } else { + log.warn("未能识别到确认按钮,尝试重新识别"); + } + await sleep(200); + + let ro2 = captureGameRegion(); + let ocrResults = ro2.find( + RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height) + ); + ro2.dispose(); + if (ocrResults) { + // log.info(`${ocrResults.text}`); + if (ocrResults.text.includes("今日已无法锻造")) { if (notice) { - notification.send(`锻造已完成,使用了 ${OreChineseMap[oreType]}`); + notification.send("检测到 今日已无法锻造 结束锻造"); } else { - // 偽造拾取 - log.info(`交互或拾取:"使用了[${OreChineseMap[oreType]}]"`); - log.info(`锻造已完成,使用了 ${OreChineseMap[oreType]}`); + log.info("检测到 今日已无法锻造 结束锻造"); } - return true; // 达到点击上限,终止锻造流程 + await sleep(250); + await click(960, 1042); + await sleep(200); + await click(960, 1042);// 多次点击结束弹窗 + return true; // 终止锻造流程 } + else if (ocrResults.text.includes("材料不足")) { + if (notice) { + notification.send("检测到 材料不足 跳过当前矿物。请检查背包,及时补充矿物。"); + } else { + log.info("检测到 材料不足 跳过当前矿物。请检查背包,及时补充矿物。"); + } + clickAttempts--; // 出现材料不足时减去一次点击计数 + await sleep(250); + await click(960, 1042); + await sleep(200); + await click(960, 1042);// 多次点击结束弹窗 + return false; // 跳过当前矿石 + } + } + if (clickAttempts === 4) { + await sleep(1000); + if (notice) { + notification.send(`锻造已完成,使用了 ${OreChineseMap[oreType]}`); + } else { + // 偽造拾取 + log.info(`交互或拾取:"使用了[${OreChineseMap[oreType]}]"`); + log.info(`锻造已完成,使用了 ${OreChineseMap[oreType]}`); + } + return true; // 达到点击上限,终止锻造流程 } } } @@ -512,11 +661,13 @@ async function tryForgeOre(oreType, skipCheckOres = []) { await sleep(1000); } } - if (notice) { + /*if (notice) { notification.error(`未能识别到矿石: ${OreChineseMap[oreType]},停止尝试`); } else { log.error(`未能识别到矿石: ${OreChineseMap[oreType]},停止尝试`); } +*/ + logMessage('error', `未能识别到矿石: ${OreChineseMap[oreType]},停止尝试`); return false; } @@ -534,35 +685,43 @@ async function forgeOre(smithyName, maxOre = null) { const maxAttempts = 3; let dialogFound = false; for (let attempt = 0; attempt < maxAttempts; attempt++) { - for (let i = 0; i < 3; i++) { - let ro = captureGameRegion(); - let Forge = ro.find(ForgeRo); - ro.dispose(); - if (Forge.isExist()) { - log.info("已找到对话界面锻造图标"); - await sleep(1000); - Forge.click(); - dialogFound = true; - break; - } else { - await sleep(1000); - await click(960, 1042); - } + if (await findAndInteract(ForgeRo, { + useClick: true, + timeout: 2000, + interval: 500, + postClickDelay: 1000, + singleAttempt: true + })) { + log.info("已找到对话界面锻造图标并点击"); + await sleep(1000); + await click(960, 1042); + await sleep(1000); + await click(960, 1042); + dialogFound = true; + break; + } else { + log.warn("未能识别到对话界面锻造图标,尝试重新点击"); + await sleep(1000); + await click(960, 1042); } - if (!dialogFound) - log.error("多次尝试未能识别到对话界面锻造图标"); - break; } - // 检测锻造界面是否出现 + + if (!dialogFound) { + log.error("多次尝试未能识别到对话界面锻造图标"); + return; // 退出函数 + } + + // 对话框内的锻造图标识别到后,进入锻造界面 if (dialogFound) { let interFaceFound = false; for (let attempt = 0; attempt < maxAttempts; attempt++) { + // 检测锻造界面是否出现 let innerFound = false; for (let i = 0; i < 3; i++) { - let ro = captureGameRegion(); - let ForgingInterFace = ro.find(ForgingInterFaceRo); - ro.dispose(); - if (ForgingInterFace.isExist()) { + if (await findAndInteract(ForgingInterFaceRo, + { + + })) { log.info("已进入锻造界面"); innerFound = true; break; @@ -571,63 +730,82 @@ async function forgeOre(smithyName, maxOre = null) { await click(960, 1042); } } + + // 检测到锻造界面出现 if (innerFound) { interFaceFound = true; // 领取操作:点击全部领取及确认领取 - let ro1 = captureGameRegion(); - let ClaimAll = ro1.find(ClaimAllRo); - ro1.dispose(); - if (ClaimAll.isExist()) { - ClaimAll.click(); + if (await findAndInteract(ClaimAllRo, { + useClick: true, + timeout: 2000, + interval: 500, + postClickDelay: 1000, + singleAttempt: true, + silentOnFail: true // 失败时不报错(静默模式) + + })) { await sleep(500); - let ro = captureGameRegion(); - let ConfirmButton = ro.find(ConfirmDeployButtonRo); - ro.dispose(); - if (ConfirmButton.isExist()) { - ConfirmButton.click(); + if (await findAndInteract(ConfirmDeployButtonRo, { + useClick: true, + timeout: 2000, + interval: 500, + postClickDelay: 1000, + singleAttempt: true + })) { await sleep(500); - if (forgedOrNot === "是") { - await click(220, 150); - await sleep(1000); // 点击进入锻造界面 - } } else { log.warn("未能识别到确定按钮"); } } + if (forgedOrNot === "是") { let forgeSuccess = false; + await click(220, 150); + await sleep(1000); // 点击进入锻造界面 // 模式一:自动模式:自动选择数量最多的矿石锻造 if (model === "模式一" && maxOre) { - primaryOre = maxOre; - log.info(`自动选择数量最多的矿石为: ${primaryOre}`); - forgeSuccess = await tryForgeOre(primaryOre, []); + //log.info(`自动选择数量最多的矿石为: ${maxOre}`); + forgeSuccess = await tryForgeOre(maxOre); if (!forgeSuccess) { - log.warn("自动模式锻造未成功,切换到手动备选矿石模式"); + log.warn("自动模式锻造未成功,切换到手动排序选矿模式"); } } - // 模式二或模式一失败时,依次尝试主选及备选矿石 + // 处于模式二或模式一失败时,则按配置优先级依次尝试 if (model === "模式二" || !forgeSuccess) { - if (await tryForgeOre(primaryOre, [])) { - forgeSuccess = true; - } else if (await tryForgeOre(secondaryOre, [primaryOre])) { - forgeSuccess = true; - } else if (await tryForgeOre(tertiaryOre, [primaryOre, secondaryOre])) { - forgeSuccess = true; - } else { - if (notice) { - notification.error("所有备选矿石都未能识别,结束锻造"); - } else { - log.error("所有备选矿石都未能识别,结束锻造"); + // 生成按优先级的候选矿石列表(只包含优先级大于0的项) + let orderedOres = getSortedOresByPriority(orePriorityConfig, nicknameOrder); + // 如果之前已尝试过 maxOre,则从列表中过滤掉它,避免重复尝试 + if (maxOre) { + orderedOres = orderedOres.filter(o => o !== maxOre); + } + + // 按顺序逐个尝试 + for (const oreName of orderedOres) { + //if (!oreName) continue;//如果 oreName 是假值,就跳过当前循环迭代,继续下一个 + log.info(`按优先级尝试使用矿石: ${oreName} 锻造`); + try { + if (await tryForgeOre(oreName)) { + forgeSuccess = true; + break; + } else { + log.info(`${oreName} 尝试未成功,继续下一个`); + } + } catch (error) { + log.error(`tryForgeOre(${oreName}) 报错: ${error.message}`); } } + + if (!forgeSuccess) { + logMessage('error', "所有候选矿石均未能成功锻造,结束锻造"); + } } } // 退出锻造前判断配方,点击“锻造队列”,再次确认使用的矿物已经锻造结果 const ocrRegionAfter = { x: 185, y: 125, width: 670 - 185, height: 175 - 125 }; - let ro2 =captureGameRegion(); + let ro2 = captureGameRegion(); let ocrResultsAfter = ro2.find( RecognitionObject.ocr(ocrRegionAfter.x, ocrRegionAfter.y, ocrRegionAfter.width, ocrRegionAfter.height) ); @@ -658,10 +836,13 @@ async function forgeOre(smithyName, maxOre = null) { await genshin.returnMainUi(); } + + + /*********************** 主执行入口 ***********************/ (async function () { - // 初始化及前往铁匠铺 + // 1. 初始化 setGameMetrics(1920, 1080, 1.25); await genshin.returnMainUi(); if (notice) { @@ -670,62 +851,82 @@ async function forgeOre(smithyName, maxOre = null) { log.info("自动锻造矿石脚本开始"); } - let maxOre = null; - if (forgedOrNot === "是") { - if (model === "模式一") { - const maxOreResult = await getMaxOreType(); - if (maxOreResult) { - maxOre = maxOreResult.name; - primaryOre = maxOre; - //log.info(`自动选择数量最多的矿石为: ${maxOre}`); - if (notice) { - notification.send(`当前最多矿石为: ${OreChineseMap[maxOre]},数量: ${maxOreResult.count}`); + // 2. 主流程 + try { + // 2.1 配置检查 + if (!(await configurationCheck())) return; + + // 2.2.1 锻造选择-是 + let maxOre = null; + if (forgedOrNot === "是") { + if (model === "模式一") { + const maxOreResult = await getMaxOreType(); + if (maxOreResult) { + maxOre = maxOreResult.name; + //log.info(`自动选择数量最多的矿石为: ${maxOre}`); + if (notice) { + notification.send(`自动选择当前数量最多矿石: ${OreChineseMap[maxOre]},数量: ${maxOreResult.count}`); + } else { + log.info(`自动选择当前数量最多矿石: ${OreChineseMap[maxOre]},数量: ${maxOreResult.count}`); + } } else { - log.info(`当前最多矿石为: ${OreChineseMap[maxOre]},数量: ${maxOreResult.count}`); + log.warn("自动识别矿石失败,将使用默认配置"); } - } else { - log.warn("自动识别矿石失败,将使用默认配置"); } - } - await genshin.returnMainUi(); - await autoSmithy(smithyName); - await forgeOre(smithyName, maxOre); - } - - if (forgedOrNot === "否") { - keyPress("M"); await sleep(1000); - - if (!await findAndInteract(MapRo, - { - singleAttempt: true - })) { - const smithyInfo = smithyMap[smithyName]; - if (smithyInfo) { - await genshin.moveMapTo(smithyInfo.x, smithyInfo.y, smithyInfo.country); - } - } - if (!await findAndInteract(MapForgeRo, - { - })) { await genshin.returnMainUi(); - log.info("未能识别到锻造完成图标,无需前往领取。结束脚本"); - return; // 若没有锻造图标则跳出 + if (!await autoSmithy(smithyName)) return; + await forgeOre(smithyName, maxOre); } - await autoSmithy(smithyName);//路径函数,前往铁匠铺 - await forgeOre(smithyName); + // 2.2.2 锻造选择-否 + if (forgedOrNot === "否") { + keyPress("M"); await sleep(1000); + + if (!await findAndInteract(MapRo, + { + singleAttempt: true + })) { + const smithyInfo = smithyMap[smithyName]; + if (smithyInfo) { + await genshin.moveMapTo(smithyInfo.x, smithyInfo.y, smithyInfo.country); + } + } + if (!await findAndInteract(MapForgeRo, + { + })) { + await genshin.returnMainUi(); + log.info("未能识别到锻造完成图标,无需前往领取。结束脚本"); + return; // 若没有锻造图标则跳出 + } + + if (!await autoSmithy(smithyName)) return; //路径函数,前往铁匠铺 + await forgeOre(smithyName); + } + + } catch (error) { + logMessage('error', `脚本执行过程中发生错误: ${error.message}`); } - await genshin.returnMainUi(); + // 3. 结束流程 + finally { + // 3.1 返回主界面 + await genshin.returnMainUi(); - //后退两步 - { keyDown("S"); await sleep(1000); keyUp("S"); await sleep(1000); } + //3.2 后退两步 + { keyDown("S"); await sleep(1000); keyUp("S"); await sleep(1000); } + + //if (notice) { + // notification.send("自动锻造矿石脚本结束"); + //} + //else { + + //3.3 发送通知 + log.info("自动锻造矿石脚本结束"); + //} + + } - //if (notice) { - // notification.send("自动锻造矿石脚本结束"); - //} - //else { - log.info("自动锻造矿石脚本结束"); - //} })(); + + diff --git a/repo/js/铁匠铺/manifest.json b/repo/js/铁匠铺/manifest.json index d64a7b273..35784e789 100644 --- a/repo/js/铁匠铺/manifest.json +++ b/repo/js/铁匠铺/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "自动锻造魔矿(识图版)", - "version": "2.3.2", + "version": "2.4.0", "bgi_version": "0.48.2", "description": "自动选择铁匠铺和使用矿物去锻造精锻矿。\n \n使用前请阅读“readme”文件以获取更多详细信息。 \n---更新说明--- \n- 请查阅readme", "tags": [ @@ -27,4 +27,4 @@ ], "settings_ui": "settings.json", "main": "main.js" -} \ No newline at end of file +} diff --git a/repo/js/铁匠铺/settings.json b/repo/js/铁匠铺/settings.json index 8c1243c16..ec99c58ca 100644 --- a/repo/js/铁匠铺/settings.json +++ b/repo/js/铁匠铺/settings.json @@ -7,7 +7,7 @@ { "name": "model", "type": "select", - "label": "模式选择(默认:模式一)\n【模式一】自动模式:自动选择数量最多的矿石锻造\n【模式二】手动模式:依次尝试主选及备选矿石\n注:模式一可能会因OCR识别数量不准导致选择错误", + "label": "模式选择(默认:模式一)\n【模式一】自动模式:\n自动选择数量最多的矿石锻造\n【模式二】手动模式:\n依次按照矿石定义顺序尝试\n注:模式一可能会因OCR识别数量不准导致选择错误", "options": [ "模式一", "模式二" @@ -24,7 +24,8 @@ "稻妻铁匠铺", "须弥铁匠铺", "枫丹铁匠铺", - "纳塔铁匠铺" + "纳塔铁匠铺", + "挪德卡莱铁匠铺" ], "default": "蒙德铁匠铺" }, @@ -35,45 +36,59 @@ "options": [ "是", "否" - ] + ], + "default": "是" }, { - "name": "ore", + "name": "CondessenceCrystal", "type": "select", - "label": "当矿石未能识别/模式二时依次使用\n锻造用矿(默认:水晶块)", + "label": "【锻造用矿排序】\n1.当矿石未能识别或处于模式二时,按照定义顺序使用\n2.设置为0的矿石自动排除\n3.相同排序时,按照定义框从上至下优先\n\n萃凝晶", "options": [ - "水晶块", - "萃凝晶", - //"星银矿石", - //"白铁块", - //"铁块", - "紫晶块" - ] + "0", + "1", + "2", + "3", + "4" + ], + "default": "1" }, { - "name": "secondaryOre", + "name": "CrystalChunk", "type": "select", - "label": "备用矿石1(默认:萃凝晶)", + "label": "水晶块", "options": [ - "水晶块", - "萃凝晶", - //"星银矿石", - //"白铁块", - //"铁块", - "紫晶块" - ] + "0", + "1", + "2", + "3", + "4" + ], + "default": "2" }, { - "name": "tertiaryOre", + "name": "AmethystLump", "type": "select", - "label": "备用矿石2(默认:紫晶块)", + "label": "紫晶块", "options": [ - "水晶块", - "萃凝晶", - //"星银矿石", - //"白铁块", - //"铁块", - "紫晶块" - ] + "0", + "1", + "2", + "3", + "4" + ], + "default": "3" + }, + { + "name": "RainbowdropCrystal", + "type": "select", + "label": "虹滴晶", + "options": [ + "0", + "1", + "2", + "3", + "4" + ], + "default": "4" } ] \ No newline at end of file