【铁匠铺js】2.3.0更新 (#1745)
* 【铁匠铺js】路径优化与新增
修复:枫丹铁匠铺走过头的问题
新增:纳塔铁匠铺
* 【铁匠铺js】整理模板匹配图像
* 【铁匠铺js】2.3.0更新
- **修复**:枫丹铁匠铺跑过头导致失败的问题
- **优化**:仅领取锻造时,先检测大地图左上角是否有【锻造奖励】图标
- **新增**:
1. 模式选择(默认模式一)
- 模式一:自动识别背包中数量最多的矿石锻造,失败自动切换到手动优先级(可能存在数量识别错误)
- 模式二:手动优先级,依次尝试主选及备选矿石
2. 支持纳塔铁匠铺
---------
Co-authored-by: 起个名字好难的喵 <25520958+MisakaAldrich@users.noreply.github.com>
@@ -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)
|
||||
|
||||
- 优化矿石选取方式,改用识图模式
|
||||
> 💡 提示:建议定期检查更新以获取最佳体验和最新功能
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
40
repo/js/铁匠铺/assets/Pathing/纳塔铁匠铺.json
Normal file
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
repo/js/铁匠铺/assets/RecognitionObject/Icon/MapForge.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
repo/js/铁匠铺/assets/RecognitionObject/Icon/右上角巨诗.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
repo/js/铁匠铺/assets/RecognitionObject/ItemImage/AmethystLump.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
BIN
repo/js/铁匠铺/assets/RecognitionObject/ItemImage/CrystalChunk.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
@@ -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<Object>} 返回识别结果对象
|
||||
*/
|
||||
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();
|
||||
|
||||
//后退两步
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
"水晶块",
|
||||
"萃凝晶",
|
||||
"星银矿石",
|
||||
//"星银矿石",
|
||||
//"白铁块",
|
||||
//"铁块",
|
||||
"紫晶块"
|
||||
|
||||