diff --git a/repo/js/铁匠铺/README.md b/repo/js/铁匠铺/README.md index 2f171078c..1026bc400 100644 --- a/repo/js/铁匠铺/README.md +++ b/repo/js/铁匠铺/README.md @@ -1,97 +1,163 @@ -// ==UserScript== -// @name 自动锻造魔矿脚本 -// @version 2.2.1 -// @description 自动前往铁匠铺并锻造魔矿,通过识图模式自动选择矿石 -// @author 呱呱z -// @match 原神版本:5.6;BGI 版本:0.45.1 -// ==/UserScript== - -/\*\* - -- === 重要免责声明 === -- 1. 使用风险 -- - 本脚本为开源学习项目,禁止用于商业用途或违反游戏条款的行为。 -- - 滥用可能导致游戏账号封禁,开发者不承担任何直接或间接责任。 -- -- 2. 责任限制 -- - 本脚本按“现状”提供,不承诺兼容性、安全性或功能完整性。 -- - 因使用本脚本导致的账号、数据、设备损失,开发者概不负责。 -- -- 3. 禁止条款 -- - 严禁逆向工程、恶意篡改或用于外挂等非法用途。 -- - 如游戏运营商提出要求,开发者保留随时停止维护的权利。 -- -- 使用即表示您已阅读并同意上述条款。 -- -- Last Updated: 2025-05-12 - \*/ - # 自动锻造魔矿脚本 -## 简介 +## ⚠️ 免责声明 -本脚本可自动前往铁匠铺并利用识图模式选择需要的矿石锻造魔矿。 +> **重要提示:请在使用前仔细阅读以下内容** -## 文件结构 +### 使用风险 +本脚本为开源学习项目,仅限个人学习使用,**禁止用于商业用途或任何违反游戏条款的行为**。滥用可能导致游戏账号封禁,开发者不承担任何直接或间接责任。 -- **main.js**:负责核心业务逻辑,包括前往铁匠铺和执行锻造任务。 -- **manifest.json**:脚本配置文件,记录基本信息和设置。 -- **settings.json**:用户配置文件,用于选择目标铁匠铺和指定矿石。 +### 责任限制 +本脚本按"现状"提供,不承诺兼容性、安全性或功能完整性。因使用本脚本导致的账号、数据、设备等任何损失,开发者概不负责。 -## 使用方法 +### 禁止条款 +严禁对本脚本进行逆向工程、恶意篡改或用于外挂等非法用途。如游戏运营商提出要求,开发者保留随时停止维护的权利。 -1. 将脚本添加至调度器。 -2. 右键点击脚本以修改 JS 自定义配置。 -3. 在配置文件中选择目标城市的铁匠铺(默认选择枫丹铁匠铺),并设定所需矿石(默认:水晶矿)。 - - 注意:由于地图追踪功能尚不支持室内定位,故纳塔铁匠铺不可用。 - - 可选矿石: - - 默认:水晶矿 - - 其他:紫晶矿、萃凝晶 - - 如需使用“星银矿石”、“白铁块”或“铁块”,请打开 `settings.json`,删除对应矿石前的 `//` 注释后保存配置。 +**使用本脚本即表示您已阅读、理解并同意上述所有条款。** -## 后言 +## 📞 联系与反馈 -本脚本目前处于测试阶段,欢迎反馈问题至 QQ:1765137214。 +如有任何建议或问题,欢迎通过以下方式联系: +📧 QQ:1765137214 -## 更新日志 +--- -### 2.2.1(2025.08.03) +## ✨ 功能介绍 +适用于原神 5.6 +BGI 0.48.2 版本 +本脚本能够自动前往指定铁匠铺,通过智能图像识别技术自动选择或按预设优先级锻造魔矿: -- 加快锻造速度 -- 优化通知逻辑,减少通知被吞概率 +- 🗺️ **自动寻路** - 智能导航至指定铁匠铺 +- 🔍 **矿石识别** - 支持多种矿石的自动识别与选择 +- 🎯 **双模式锻造** - 提供自动和手动两种智能模式 +- 📊 **状态通知** - 实时桌面通知与详细执行日志 +- 🛡️ **异常处理** - 智能应对各种异常情况 + +## 📁 文件结构 + +``` +自动锻造魔矿脚本/ +├── main.js # 核心业务逻辑 +├── manifest.json # 脚本元信息与基础配置 +└── settings.json # 用户自定义配置文件 +``` + +## 🚀 快速开始 + +### 1. 安装脚本 +将本脚本添加至 BGI 脚本调度器。 + +### 2. 配置参数 +右键点击脚本,选择"编辑 JS 配置"或直接修改 `settings.json` 文件: + +```json +{ + "smithyName": "枫丹铁匠铺", + "ore": "水晶块", + "secondaryOre": "紫晶块", + "tertiaryOre": "萃凝晶", + "notice": true, + "forgedOrNot": "是", + "model": "模式一" +} +``` + +### 3. 矿石配置说明 + +**默认支持矿石**: +- 水晶块 +- 紫晶块 +- 萃凝晶 + +**扩展矿石配置**: +如需使用"星银矿石"、"白铁块"或"铁块",请在 `settings.json` 中取消对应矿石前的注释(`//`),保存后即可生效。 + +## ⚙️ 功能详解 + +### 🔧 锻造模式 + +| 模式 | 描述 | +|------|------| +| **模式一(自动)** | 自动识别背包中数量最多的矿石进行锻造,识别失败时自动切换到手动优先级模式(可能存在数量识别错误) | +| **模式二(手动)** | 按照用户配置的优先级顺序,依次尝试主选及备选矿石 | + +### 🎯 特殊功能 + +- **仅领取锻造**:如选择"否",则仅检测并领取已完成的锻造奖励 +- **智能兜底**:自动模式识别失败时,自动切换到手动优先级模式 +- **通知与日志**:支持桌面通知和详细日志,便于追踪脚本执行状态 +- **异常处理**:多次识别失败、材料不足等情况均有详细提示和自动处理 + +## 📋 使用注意事项 + +1. **环境要求**:确保游戏分辨率、BGI 版本与脚本要求一致 +2. **窗口状态**:运行脚本时请保持游戏窗口处于前台且未被遮挡 +3. **权限设置**:确保已授予脚本必要的系统权限 +3. **权限设置**:部分模块由于使用了快捷键,请确保快捷键为默认值。例如纳塔铁匠铺以及仅领取锻造前检查,使用地图快捷键"M";模式一矿物识别部分,使用背包快捷键"B"。因为模式一调用了OCR,数字识别部分可能出现错误 + +## ❓ 常见问题 + +Q:脚本运行后无反应? +**A**:请按以下步骤排查: +1. 检查游戏分辨率是否符合要求 +2. 确认 BGI 版本是否为 0.48.2 或更高 +3. 验证脚本配置是否正确 +4. 确保游戏窗口处于前台且未被遮挡 +5. 确认游戏快捷键设置为默认值(特别是地图"M"和背包"B"键) + +Q:OCR识别矿石数量不准确怎么办? +**A**:这是OCR技术的固有局限性,可尝试: + +切换到模式二(手动优先级模式) + +--- + +## 📜 更新日志 + +### 2.3.0`(2025-08-28)` +- **修复**:枫丹铁匠铺跑过头导致失败的问题 +- **优化**:仅领取锻造时,先检测大地图左上角是否有【锻造奖励】图标 +- **新增**: + 1. 模式选择(默认模式一) + - 模式一:自动识别背包中数量最多的矿石锻造,失败自动切换到手动优先级(可能存在数量识别错误) + - 模式二:手动优先级,依次尝试主选及备选矿石 + 2. 支持纳塔铁匠铺 + +### 2.2.1`(2025-08-03)` +- 加快锻造速度 +- 优化通知逻辑,减少通知被吞概率 ### 2.1 +- 使用新版版本号 +- 5.7 提高了锻造上限,锻造次数由 3 改为 4 -- 使用新版版本号 -- 5.7 提高了锻造上限,因此使用锻造次数从 3 改成了 4 +### 2.00523(2025-05-23) +- 新增仅领取锻造 +- 优化点击逻辑 +- 修复锻造点击次数超过 3 次的 bug -### 2.00523(2025.05.23) +### 2.0(2025-05-22) +- 新增矿石不足时自动选择备用矿石 -- 新增仅领取锻造 -- 优化点击逻辑 -- 修复点击锻造的次数超过 3 次的 bug +### 1.40521(2025-05-21) +- 新增矿石未能识别时自动选择备用矿石 -### 2.0(2025.05.22) +### 1.4(2025-05-20) +- 新增通知功能 -- 新增矿石不足时自动选择备用选矿 +### 1.3(2025-05-15) +- 修复选择"萃凝晶"时无法识别的问题 +- 仓库内新增标签 -### 1.40521(2025.05.21) +### 1.2(2025-05-12) +- 修复选择"萃凝晶"时无法识别的 bug -- 新增矿石未能识别时自动选择备用选矿 +### 1.1(2025-05-01) +- 优化矿石选取方式,改用识图模式 -### 1.4(2025.05.20) +--- -- 新增通知功能 +**最后更新**:2025 年 8 月 28 日 +**维护者**:@呱呱z -### 1.3(2025.05.15) - -- 修复选择“萃凝晶”时无法识别的问题 -- 仓库内新增标签。 - -### 1.2(2025.05.12) - -- 修复选择“萃凝晶”时无法识别的 bug - -### 1.1(2025.05.01) - -- 优化矿石选取方式,改用识图模式 +> 💡 提示:建议定期检查更新以获取最佳体验和最新功能 \ No newline at end of file diff --git a/repo/js/铁匠铺/assets/Pathing/枫丹铁匠铺.json b/repo/js/铁匠铺/assets/Pathing/枫丹铁匠铺.json index 31bd5e2c1..b7393f2b3 100644 --- a/repo/js/铁匠铺/assets/Pathing/枫丹铁匠铺.json +++ b/repo/js/铁匠铺/assets/Pathing/枫丹铁匠铺.json @@ -1,48 +1,62 @@ -{ +{ "info": { - "name": "枫丹铁匠铺", - "type": "collect", - "author": "寒露", - "version": "1.0", + "authors": [ + { + "links": "", + "name": "寒露" + }, + { + "links": "", + "name": "呱呱z" + } + ], + "bgi_version": "0.45.0", "description": "", - "bgi_version": "0.42.3" + "enable_monster_loot_split": false, + "last_modified_time": 1756367313887, + "map_match_method": "", + "map_name": "Teyvat", + "name": "枫丹铁匠铺", + "tags": [], + "type": "collect", + "version": "1.1" }, "positions": [ { - "id": 1, "action": "", + "action_params": "", + "id": 1, "move_mode": "run", "type": "teleport", "x": 4514.0908203125, - "y": 3630.588623046875, - "action_params": "" + "y": 3630.588623046875 }, { + "action": "", + "action_params": "", "id": 2, + "move_mode": "run", + "type": "path", "x": 4518.59765625, - "y": 3601.3916015625, - "type": "path", - "move_mode": "run", - "action": "", - "action_params": "" + "y": 3601.3916015625 }, { + "action": "", + "action_params": "", "id": 3, - "x": 4563.74755859375, - "y": 3593.390869140625, - "type": "path", "move_mode": "run", - "action": "", - "action_params": "" + "type": "path", + "x": 4563.74755859375, + "y": 3593.390869140625 }, { - "id": 4, - "x": 4580.56640625, - "y": 3603.6337890625, - "type": "path", - "move_mode": "walk", "action": "", - "action_params": "" + "action_params": "", + "id": 4, + "move_mode": "dash", + "type": "target", + "x": 4580.56640625, + "y": 3603.6337890625 } ] -} +} \ 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..e03c069f1 --- /dev/null +++ b/repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json @@ -0,0 +1,40 @@ +{ + "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" + }, + "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 + } + ] +} diff --git a/repo/js/铁匠铺/assets/RecognitionObject/Forge.png b/repo/js/铁匠铺/assets/RecognitionObject/DialogueInterface/Forge.png similarity index 100% rename from repo/js/铁匠铺/assets/RecognitionObject/Forge.png rename to repo/js/铁匠铺/assets/RecognitionObject/DialogueInterface/Forge.png diff --git a/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/DisabledMaterialsFace.png b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/DisabledMaterialsFace.png new file mode 100644 index 000000000..80aec2632 Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/DisabledMaterialsFace.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/ForgingInterface.png b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/ForgingInterface.png similarity index 100% rename from repo/js/铁匠铺/assets/RecognitionObject/ForgingInterface.png rename to repo/js/铁匠铺/assets/RecognitionObject/GameInterface/ForgingInterface.png diff --git a/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/InventoryInterface.png b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/InventoryInterface.png new file mode 100644 index 000000000..32a8086be Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/InventoryInterface.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/MaterialsFace.png b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/MaterialsFace.png new file mode 100644 index 000000000..420797a91 Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/GameInterface/MaterialsFace.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/Confirm Deploy Button.png b/repo/js/铁匠铺/assets/RecognitionObject/Icon/ConfirmDeployButton.png similarity index 100% rename from repo/js/铁匠铺/assets/RecognitionObject/Confirm Deploy Button.png rename to repo/js/铁匠铺/assets/RecognitionObject/Icon/ConfirmDeployButton.png diff --git a/repo/js/铁匠铺/assets/RecognitionObject/Icon/MapForge.png b/repo/js/铁匠铺/assets/RecognitionObject/Icon/MapForge.png new file mode 100644 index 000000000..99ee9c73b Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/Icon/MapForge.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/全部领取.png b/repo/js/铁匠铺/assets/RecognitionObject/Icon/全部领取.png similarity index 100% rename from repo/js/铁匠铺/assets/RecognitionObject/全部领取.png rename to repo/js/铁匠铺/assets/RecognitionObject/Icon/全部领取.png diff --git a/repo/js/铁匠铺/assets/RecognitionObject/Icon/右上角巨诗.png b/repo/js/铁匠铺/assets/RecognitionObject/Icon/右上角巨诗.png new file mode 100644 index 000000000..d5e2ed9b6 Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/Icon/右上角巨诗.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/AmethystLump.png b/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/AmethystLump.png new file mode 100644 index 000000000..975e3c837 Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/AmethystLump.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CondessenceCrystal.png b/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CondessenceCrystal.png new file mode 100644 index 000000000..0a7996416 Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CondessenceCrystal.png differ diff --git a/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CrystalChunk.png b/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CrystalChunk.png new file mode 100644 index 000000000..d30bb2d7f Binary files /dev/null and b/repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CrystalChunk.png differ diff --git a/repo/js/铁匠铺/main.js b/repo/js/铁匠铺/main.js index 237cc5edd..2d3e19913 100644 --- a/repo/js/铁匠铺/main.js +++ b/repo/js/铁匠铺/main.js @@ -1,12 +1,13 @@ /*********************** 配置与常量 ***********************/ // 用户配置 -let smithyName = settings.smithyName || "蒙德铁匠铺"; // 铁匠铺地区 +let smithyName = settings.smithyName || "枫丹铁匠铺"; // 铁匠铺地区 let primaryOre = settings.ore || "水晶块"; // 主选矿石 let secondaryOre = settings.secondaryOre || "萃凝晶"; // 备选矿石1 let tertiaryOre = settings.tertiaryOre || "紫晶块"; // 备选矿石2 -let notice = settings.notice ?? false; // 通知状态 +let notice = settings.notice ?? false; // 通知状态 let forgedOrNot = (settings.forgedOrNot && settings.forgedOrNot.trim() !== "") ? settings.forgedOrNot : "是"; // 是否锻造 +let model = settings.model || "模式一"; // 模式选择 // 矿石图像与中文名称映射 const ingredientImageMap = { @@ -27,22 +28,88 @@ const OreChineseMap = { 铁块: "铁块", }; -// 模板识别对象 -const ConfirmDeployButtonRo = RecognitionObject.TemplateMatch( - file.ReadImageMatSync("Assets/RecognitionObject/Confirm Deploy Button.png"), - 0, 870, 1920, 210 -); // 确定按钮 +const smithyMap = { + "蒙德铁匠铺": { x: -869, y: 2278, country: "蒙德" }, + "璃月铁匠铺": { x: 267, y: -665, country: "璃月" }, + "稻妻铁匠铺": { x: -4402, y: -3052, country: "稻妻" }, + "须弥铁匠铺": { x: 2786, y: -503, country: "须弥" }, + "枫丹铁匠铺": { x: 4507, y: 3630, country: "枫丹" }, + "纳塔铁匠铺": { x: 9085, y: -1964, country: "纳塔" } +}; -const ForgingInterfaceRo = RecognitionObject.TemplateMatch( - file.ReadImageMatSync("Assets/RecognitionObject/ForgingInterface.png"), +// 模板识别对象 +//游戏界面 +const InventoryInterFaceRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/GameInterface/InventoryInterFace.png"), + 0, 0, 140, 100 +); // 【背包界面】图标 +const DisabledMaterialsFaceRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/GameInterface/DisabledMaterialsFace.png"), + 0, 0, 1920, 100 +); // 【材料界面-未处于】图标 +const MaterialsFaceRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/GameInterface/MaterialsFace.png"), + 0, 0, 1920, 100 +); // 【材料界面-已处于】图标 +const ForgingInterFaceRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/GameInterface/ForgingInterFace.png"), 0, 0, 140, 100 ); // 锻造界面图标 + +//锻造界面物品图标-未使用这部分代码 +const CondessenceCrystalForgeRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/Picture/CondessenceCrystal.png"), + 0, 870, 1920, 210 +); // 【萃凝晶】 +const AmethystLumpForgeRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/Picture/AmethystLump.png"), + 0, 870, 1920, 210 +); // 【紫晶块】 +const CrystalChunkForgeRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/Picture/CrystalChunk.png"), + 0, 870, 1920, 210 +); // 【水晶块】 + +//背包界面物品图标 +const CondessenceCrystalRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/ItemImage/CondessenceCrystal.png"), + 115, 115, 1270, 625 +); // 【萃凝晶】 +const AmethystLumpRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/ItemImage/AmethystLump.png"), + 115, 115, 1165, 510 +); // 【紫晶块】 +const CrystalChunkRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/ItemImage/CrystalChunk.png"), + 115, 115, 1165, 510 +); // 【水晶块】 + +//对话框图标 const ForgeRo = RecognitionObject.TemplateMatch( - file.ReadImageMatSync("Assets/RecognitionObject/Forge.png"), + file.ReadImageMatSync("Assets/RecognitionObject/DialogueInterface/Forge.png"), 1260, 300, 600, 600 ); // 对话框中的锻造图标 +//图标 +const ConfirmDeployButtonRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/Icon/ConfirmDeployButton.png"), + 0, 870, 1920, 210 +); // 确定按钮 +const ClaimAllRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/Icon/全部领取.png"), + 0, 900, 1920, 180 +); +//地图界面图标 +const MapRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/icon/右上角巨诗.png"), + 945, 20, 975, 50 +); // 地图右上角【识别用】图标 +const MapForgeRo = RecognitionObject.TemplateMatch( + file.ReadImageMatSync("Assets/RecognitionObject/icon/MapForge.png"), + 0, 0, 400, 625 +); // 地图左上角【锻造】图标 + // 计算矿物图标的坐标(行列排列) const rows = [1, 2, 3]; const cols = [1, 2, 3, 4, 5]; @@ -57,6 +124,131 @@ for (const row of rows) { /*********************** 工具函数 ***********************/ +// 模板匹配与交互 +/** + * 图像识别与交互函数(优化版) + * @param {Object} target - 要识别的图像模板对象 + * @param {Object} [options={}] - 配置选项 + * @param {boolean} [options.useClick=false] - 是否执行点击操作 + * @param {number} [options.timeout=5000] - 识别超时时间(毫秒) + * @param {number} [options.interval=500] - 重试间隔(毫秒) + * @param {boolean} [options.clickCenter=true] - 是否点击中心点 + * @param {number} [options.postClickDelay=500] - 点击后等待时间(毫秒) + * @param {boolean} [options.singleAttempt=false] - 是否仅尝试一次 + * @returns {Promise} 返回识别结果对象 + */ +async function findAndInteract(target, options = {}) { + // 合并默认选项 + const { + useClick = false, + timeout = 3000, + interval = 300, + clickCenter = true, + postClickDelay = 300, + singleAttempt = false + } = options || {}; + + const startTime = Date.now(); + let attemptCount = 0; + let lastError = null; + + // 结果对象结构 + const resultTemplate = { + success: false, + position: null, // {x, y} 原始位置 + clickPosition: null, // {x, y} 点击位置 + dimensions: null, // {width, height} + attempts: 0, + elapsed: 0, + error: null + }; + + // 主识别循环 + while (true) { + attemptCount++; + let gameRegion = null; + try { + // 1. 捕获游戏区域 + gameRegion = captureGameRegion(); + // 2. 执行图像识别 + const found = gameRegion.find(target); + + if (found?.isExist?.() === true) { + // 构建成功结果 + const result = { + ...resultTemplate, + success: true, + position: { x: found.x, y: found.y }, + dimensions: { width: found.width, height: found.height }, + attempts: attemptCount, + elapsed: Date.now() - startTime + }; + + //log.info(`✅ 识别成功 | 位置: (${found.x}, ${found.y}) | 尺寸: ${found.width}x${found.height} | 尝试: ${attemptCount}次`); + + // 3. 处理点击交互 + if (useClick) { + // 计算点击位置 + const clickPos = clickCenter + ? { + x: Math.round(found.x + found.width / 2), + y: Math.round(found.y + found.height / 2) + } + : { x: Math.round(found.x), y: Math.round(found.y) }; + + result.clickPosition = clickPos; + + // 兼容同步/异步 click + if (typeof click === 'function') { + const clickResult = click(clickPos.x, clickPos.y); + if (clickResult && typeof clickResult.then === 'function') { + await clickResult; + } + } + //log.info(`🖱️ 已点击位置: (${clickPos.x}, ${clickPos.y})`); + + // 点击后延迟 + if (postClickDelay > 0) { + await sleep(postClickDelay); + } + } + + return result; + } + + // 未找到时的日志 + if (attemptCount % 3 === 0) { + log.debug(`⏳ 识别尝试中... 次数: ${attemptCount}, 已用时: ${Date.now() - startTime}ms`); + } + } catch (error) { + lastError = error; + log.error(`识别异常,错误: ${error.message}`); + } finally { + // 4. 资源清理 + if (gameRegion?.dispose) { + try { gameRegion.dispose(); } catch (e) { } + } + } + + // 退出条件 + if (singleAttempt || Date.now() - startTime >= timeout) { + log.warn(`识别失败 | 尝试: ${attemptCount}次,未能识别到目标图像`); + return { + ...resultTemplate, + attempts: attemptCount, + elapsed: Date.now() - startTime, + error: lastError?.message + }, + false; + } + await sleep(interval); + } +} + + + + + // 图像识别函数 function recognizeImage(imagePath, x, y, searchWidth, searchHeight) { try { @@ -88,12 +280,97 @@ function determineOre(oreType) { return message; } + /*********************** 主逻辑函数 ***********************/ +// 模式一:自动识别背包中数量最多的矿石 +async function getMaxOreType() { + try { + //开启背包 + //【背包】界面检测 + if (!await findAndInteract(InventoryInterFaceRo, { + singleAttempt: true + })) { + log.info("未检测到背包界面,尝试返回主界面并打开背包"); + await genshin.returnMainUi(); + keyPress("B"); await sleep(1000); + } else { + log.info("检测到处于背包界面"); + } + + // 【材料】见面检测 + if (!await findAndInteract(MaterialsFaceRo, + { + singleAttempt: true + })) { + log.info("未处于材料界面,准备点击材料界面图标"); + await findAndInteract(DisabledMaterialsFaceRo, + { + useClick: true + }) + await sleep(600); + } else { + log.info("已经处于材料界面,准备点击料理区域"); + } + + const oreResults = [ + { name: "萃凝晶", ro: CondessenceCrystalRo }, + { name: "紫晶块", ro: AmethystLumpRo }, + { name: "水晶块", ro: CrystalChunkRo } + ]; + let maxOre = null; + let maxCount = 0; + for (const ore of oreResults) { + const result = await findAndInteract(ore.ro, { + useClick: true, + timeout: 5000, + interval: 500, + postClickDelay: 500 + }); + if (!result || !result.success || !result.clickPosition) continue; + let ocrX = result.clickPosition.x - 63; + let ocrY = result.clickPosition.y + 60; + let resList = captureGameRegion().findMulti(RecognitionObject.ocr(ocrX, ocrY, 130, 55)); + let oreNum = 0; + if (resList.count > 0) { + let text = resList[0].text.replace(/[^\d]/g, ""); + oreNum = parseInt(text, 10) || 0; + log.info(`识别到 ${OreChineseMap[ore.name]} 数量: ${oreNum}`); + } + if (oreNum > maxCount) { + maxCount = oreNum; + maxOre = ore.name; + if (notice) { + notification.info(`当前最多矿石为: ${OreChineseMap[ore.name]} 数量: ${oreNum}`); + } else { + log.info(`当前最多矿石为: ${OreChineseMap[ore.name]} 数量: ${oreNum}`); + } + } + } + return maxOre; + } catch (error) { + if (notice) { + notification.error(`自动识别背包中数量最多的矿石失败,错误: ${error.message}`); + } else { + log.error(`识自动识别背包中数量最多的矿石失败,错误: ${error.message}`); + } + return null; + } + +} + // 自动前往铁匠铺 async function autoSmithy(smithyName) { log.info(`自动前往 ${smithyName}`); try { + if (smithyName === "纳塔铁匠铺") { + keyPress("M"); await sleep(1000); + click(1845, 1015); await sleep(100); + click(1650, 355); await sleep(100); + await genshin.setBigMapZoomLevel(1.0); + click(845, 615); await sleep(100); + click(1475, 1005); await sleep(100); + } let filePath = `assets/Pathing/${smithyName}.json`; await pathingScript.runFile(filePath); if (notice) { @@ -220,7 +497,7 @@ async function tryForgeOre(oreType, skipCheckOres = []) { } // 对话、领取、锻造操作 -async function forgeOre(smithyName) { +async function forgeOre(smithyName, maxOre = null) { // 对话部分 await sleep(1000); keyPress("F"); @@ -246,12 +523,13 @@ async function forgeOre(smithyName) { await click(960, 1042); } } - if (dialogFound) break; + if (!dialogFound) + log.error("多次尝试未能识别到对话界面锻造图标"); + break; } - // 检测锻造界面是否出现 if (dialogFound) { - let interfaceFound = false; + let interFaceFound = false; for (let attempt = 0; attempt < maxAttempts; attempt++) { const ocrRegion = { x: 185, y: 125, width: 670 - 185, height: 175 - 125 }; let ocrResults = captureGameRegion().find( @@ -259,8 +537,8 @@ async function forgeOre(smithyName) { ); let innerFound = false; for (let i = 0; i < 3; i++) { - let ForgingInterface = captureGameRegion().find(ForgingInterfaceRo); - if (ForgingInterface.isExist()) { + let ForgingInterFace = captureGameRegion().find(ForgingInterFaceRo); + if (ForgingInterFace.isExist()) { log.info("已进入锻造界面"); innerFound = true; break; @@ -270,13 +548,9 @@ async function forgeOre(smithyName) { } } if (innerFound) { - interfaceFound = true; + interFaceFound = true; // 领取操作:点击全部领取及确认领取 - const ClaimAllRo = RecognitionObject.TemplateMatch( - file.ReadImageMatSync("Assets/RecognitionObject/全部领取.png"), - 0, 900, 1920, 180 - ); let ClaimAll = captureGameRegion().find(ClaimAllRo); if (ClaimAll.isExist()) { ClaimAll.click(); @@ -285,26 +559,40 @@ async function forgeOre(smithyName) { if (ConfirmButton.isExist()) { ConfirmButton.click(); await sleep(500); - await click(220, 150); - await sleep(1000); // 点击进入锻造界面 + if (forgedOrNot === "是") { + await click(220, 150); + await sleep(1000); // 点击进入锻造界面 + } } else { log.warn("未能识别到确定按钮"); } } - // 若设置为锻造,则依次尝试主选及备选矿石 if (forgedOrNot === "是") { let forgeSuccess = false; - 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("所有备选矿石都未能识别,结束锻造"); + + // 模式一:自动模式:自动选择数量最多的矿石锻造 + if (model === "模式一" && maxOre) { + primaryOre = maxOre; + log.info(`自动选择数量最多的矿石为: ${primaryOre}`); + forgeSuccess = await tryForgeOre(primaryOre, []); + if (!forgeSuccess) { + 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 { - log.error("所有备选矿石都未能识别,结束锻造"); + if (notice) { + notification.error("所有备选矿石都未能识别,结束锻造"); + } else { + log.error("所有备选矿石都未能识别,结束锻造"); + } } } } @@ -324,7 +612,7 @@ async function forgeOre(smithyName) { break; // 退出锻造界面检测循环 } } - if (!interfaceFound) { + if (!interFaceFound) { log.error("经过多次尝试,未能进入锻造界面"); } } else { @@ -352,9 +640,45 @@ async function forgeOre(smithyName) { log.info("自动锻造矿石脚本开始"); } + let maxOre = null; + if (forgedOrNot === "是") { + if (model === "模式一") { + maxOre = await getMaxOreType(); + if (maxOre) { + log.info(`自动选择数量最多的矿石为: ${maxOre}`); + primaryOre = maxOre; + } else { + log.warn("自动识别矿石失败,将使用默认配置"); + } + } + 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; // 若没有锻造图标则跳出 + } + + await autoSmithy(smithyName);//路径函数,前往铁匠铺 + await forgeOre(smithyName); + } - await autoSmithy(smithyName); - await forgeOre(smithyName); await genshin.returnMainUi(); //后退两步 diff --git a/repo/js/铁匠铺/manifest.json b/repo/js/铁匠铺/manifest.json index b49322efe..9adca455e 100644 --- a/repo/js/铁匠铺/manifest.json +++ b/repo/js/铁匠铺/manifest.json @@ -1,28 +1,23 @@ { - "manifest_version": 1, - "name": "自动锻造魔矿(识图版)", - "version": "2.2.1", - "bgi_version": "0.44.0", - "description": "自动选择铁匠铺和使用矿物去锻造精锻矿。\n \n使用前请阅读“readme”文件以获取更多详细信息。 \n---更新说明--- \n- 请查阅readme", - "tags": [ - "铁匠铺", - "锻造", - "精锻用魔矿", - "模板匹配" - ], - "authors": [ - { - "name": "呱呱z", - "links": "https://github.com/jidingcai" - }, - { - "name": "寒露" - }, - { - "name": "蜜柑魚", - "links": "https://github.com/this-Fish" - } - ], - "settings_ui": "settings.json", - "main": "main.js" -} \ No newline at end of file + "manifest_version": 1, + "name": "自动锻造魔矿(识图版)", + "version": "2.3.0", + "bgi_version": "0.48.2", + "description": "自动选择铁匠铺和使用矿物去锻造精锻矿。\n \n使用前请阅读“readme”文件以获取更多详细信息。 \n---更新说明--- \n- 请查阅readme", + "tags": ["铁匠铺", "锻造", "精锻用矿"], + "authors": [ + { + "name": "呱呱z", + "links": "https://github.com/jidingcai" + }, + { + "name": "寒露" + }, + { + "name": "蜜柑魚", + "links": "https://github.com/this-Fish" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} diff --git a/repo/js/铁匠铺/settings.json b/repo/js/铁匠铺/settings.json index 515a0e659..ddef80c74 100644 --- a/repo/js/铁匠铺/settings.json +++ b/repo/js/铁匠铺/settings.json @@ -4,16 +4,27 @@ "type": "checkbox", "label": "通过BGI通知系统发送详细通知" }, + { + "name": "model", + "type": "select", + "label": "模式选择(默认:模式一)\n【模式一】自动模式:自动选择数量最多的矿石锻造\n【模式二】手动模式:依次尝试主选及备选矿石\n注:模式一可能会因OCR识别数量不准导致选择错误", + "options": [ + "模式一", + "模式二" + ], + "default": "模式一" + }, { "name": "smithyName", "type": "select", - "label": "选择铁匠铺(默认:蒙德铁匠铺)", + "label": "铁匠铺地区选择(默认:枫丹铁匠铺)", "options": [ "蒙德铁匠铺", "璃月铁匠铺", "稻妻铁匠铺", "须弥铁匠铺", - "枫丹铁匠铺" + "枫丹铁匠铺", + "纳塔铁匠铺" ] }, { @@ -28,11 +39,11 @@ { "name": "ore", "type": "select", - "label": "当矿石未能识别时依次使用\n锻造用矿(默认:水晶块)", + "label": "当矿石未能识别/模式二时依次使用\n锻造用矿(默认:水晶块)", "options": [ "水晶块", "萃凝晶", - "星银矿石", + //"星银矿石", //"白铁块", //"铁块", "紫晶块"