背包统计JS增加独立名单拾取,增加弹窗模块,增加对怪物名的支持 (#2101)

* 背包js增加独立名单拾取,增加弹窗模块,增加对怪物名的支持

v2.5  增加独立名单拾取,增加弹窗模块,这个2个是背包材料统计JS运行时实时运行。增加对怪物名的支持

* Delete repo/js/背包材料统计/materialsCD directory

* Add files via upload

* Add files via upload

更新说明

* Update manifest.json
This commit is contained in:
JJMdzh
2025-10-09 00:42:19 +08:00
committed by GitHub
parent 59c8b6c763
commit 5223a95ab2
30 changed files with 3240 additions and 1726 deletions

View File

@@ -1,58 +1,37 @@
// ==UserScript==
// @name 背包统计采集系统
// @version 2.42
// @description 识别路径文件,根据材料数量,自动执行路线,或者主动选择材料类别,统计材料数量
// @author 吉吉喵
// @match 原神版本5.6BGI 版本0.44.8
// ==/UserScript==
/**
* === 重要免责声明 ===
* 1. 使用风险
* - 本脚本为开源学习项目,禁止用于商业用途或违反游戏条款的行为。
* - 滥用可能导致游戏账号封禁,开发者不承担任何直接或间接责任。
*
* 2. 责任限制
* - 本脚本按“现状”提供,不承诺兼容性、安全性或功能完整性。
* - 因使用本脚本导致的账号、数据、设备损失,开发者概不负责。
*
* 3. 禁止条款
* - 严禁逆向工程、恶意篡改或用于外挂等非法用途。
* - 如游戏运营商提出要求,开发者保留随时停止维护的权利。
*
* 使用即表示您已阅读并同意上述条款。
*
* Last Updated: 2025-09-23
*/
# 背包材料统计
# 背包材料统计 v2.5
## 简介:
背包材料统计,可统计背包养成道具、部分食物、素材的数量并保存本地还可根据设定数量自动根据CD执行采集路径。
可统计背包养成道具、部分食物、素材的数量根据设定数量、根据材料刷新CD执行挖矿、采集、刷怪等的路径。优势
+ 1.自动判断材料CD不需要管材料CD有没有好
+ 2.可以随意添加路径,能自动排除低效、无效路径;
+ 3.有独立名单识别不会交互路边的npc或是神像
+ 4.有实时的弹窗模块,提供了常见的几种;
## 文件结构
./📁BetterGI/📁User/📁JsScript/
📁背包材料统计/
📁pathing/
📁 薄荷/
📄 薄荷1.json
📁 薄荷效率/
📄 薄荷-吉吉喵.json
📁 苹果/
📄 旅行者的果园.json
📁 名刀镡/
📄 旅行者的名刀镡.json
📁history_record/ 背包统计 自动生成每类的历史记录每类中旧纪录上限为365个
📁overwrite_record/ 背包统计 自动生成,每类的最新一次记录
📁pathing_record/ 自动生成,路径运行时间记录
📁materialsCD/ 路径CD管理 自带的CD文件
📄 latest_record.txt 背包统计 自动生成,最新一次统计
📁assets 图包
## 用前须知要有一点的动手能力该JS不提供路径文件需要文件夹操作
## 使用方法
1. 将脚本添加至调度器。
2. 右键点击脚本以修改JS自定义配置刷怪、采集推荐:2.仅📁pathing材料纯材料数量识别选3.仅【材料分类】勾选。
3. 执行路径功能前需要📁pathing有【材料正式名】的文件夹具体可参考BetterGI\Repos\bettergi-scripts-list-git\repo\pathing目录选取【材料正式名】的文件夹内有子文件夹或者路径。复制到BetterGI\User\JsScript\背包材料统计\pathing目录具体参考## 文件结构
## 基础教程会订阅路径、添加JS的可跳过
+ 1.仓库 订阅 所需路径文件;
+ 2.打开路径所在文件夹BetterGI\Repos\bettergi-scripts-list-git\repo\pathing
把地方特产、敌人与魔物、矿物、食材与炼金四个文件夹复制到背包材料统计JS目录BetterGI\User\JsScript\背包材料统计\pathing中最好手动去除重复多余路径
+ 3.推荐根据队伍来把路径分组放置。比如
背包统计刷怪组,适合挂机输出的队:火神 奶奶 钟离 万叶,能够胜任需要刷怪的路径;
背包统计附魔材料组,附魔队:钟离 芭芭拉 久岐忍 砂糖或班尼特,适合附魔采集;
背包统计采集组,生存队:迪希雅 芭芭拉 瑶瑶 草神。适合一般情况下的采集;
+ 4.找到背包材料统计js右键 背包材料统计选择JS修改脚本自定义设置
## JS的自定义设置说明
+ 1.目标数量,是扫描背包中材料数量,只有低于目标的材料,其路径才会被纳入执行序列;
+ 2.优先级材料,是最高优先级,直接无视上述目标数量,纳入执行序列最顶层;
+ 3.时间成本在一个路径存在3到5次记录时会计算时间成本单个材料获取时间超过默认30秒的则跳过
+ 4.发送通知每类材料跑完会通知一次全部材料跑完会汇总通知一次。需在BGI通知里开启接收端企业微信的使用自行寻找
+ 5.取消扫描数量,是取消每个路径执行完的扫描;当大部分需要的路径都有足够多记录,就可以不需要新增运行记录,可以不扫描了,以节约时间;但全部材料执行始末会各扫一次,以汇总材料信息。
+ 6.仅pathing材料是平衡背包材料统计和路径CD管理的选项选择仅pathing材料则直接无视下方勾选的材料分类只扫描pathing文件夹已有的路径材料没有的就不扫以缩短扫描时间
+ 7.弹窗名不填默认assets\imageClick文件夹下所有弹窗循环执行弹窗模块会在背包统计运行时全程保护路径防止弹窗影响路径执行
+ 8.采用的CD分类不在materialsCD文件夹的不执行这个文件夹可以按格式新增材料CD分类txt只要背包材料统计的图库里有路径所在的文件夹名正确就能按CD执行
+ 9.targetText文件夹里的是独立名单识别的名单可以按随意新增txt内容按格式英文冒号前的名字也随便写冒号后的文字会被当成目标来识别不在targetText文件夹下不识别。
## 注意
因食物部分图片未补足,为适配快速滑页,苹果、日落果、星蕈、活化的星蕈、枯焦的星蕈、泡泡桔、烛伞蘑菇、美味的宝石闪闪,这八个食物必须有,且在第一行。不然这几个食物会无法识别。
@@ -81,7 +60,8 @@
+ v2.27 修复计算材料数错误、目标数量临界值、"3"识别成"三"等bug
+ v2.28 材料更变时初始数量更新正常记录排除0位移和0数量的路径记录(可能是卡路径需手动根据0记录去甄别)新增材料名0后缀本地记录新增背包弹窗识别
+ v2.29 新增排除提示;调整平均时间成本计算;过滤掉差异较大的记录;
+ v2.30 更改路径专注模式默认值加log提示去除注释掉的调试log背包材料统计更改名为背包统计采集系统
+ v2.30 更改路径专注模式默认值加log提示去除注释掉的调试log
+ v2.40 优化背包识别时占用的内存;增加通知;
+ v2.41 修复勾选分类的本地记录bug新增仅背包统计选项如果本地记录已经遭到破坏。比如每条路径都产生大量材料名-0.txt就只能手动清理或者删除本地记录pathing_record重新跑
+ v2.42 增加无路径间的扫描、无数量记录的noRecord模式适合路径记录已经炼成的玩家。新增怪物材料CD文件以支持轻度刷怪需求。
+ v2.42 增加无路径间的扫描、无数量记录的noRecord模式适合路径记录已经炼成的玩家。新增怪物材料CD文件以支持轻度刷怪需求。
+ v2.5 增加独立名单拾取增加弹窗模块这个2个是背包材料统计JS运行时实时运行。增加对怪物名的支持。

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

View File

@@ -0,0 +1,53 @@
丘丘人:破损的面具,污秽的面具,不祥的面具
丘丘萨满:导能绘卷,封魔绘卷,禁咒绘卷
丘丘人射手:锐利的箭簇,牢固的箭簇,历战的箭簇
丘丘暴徒/丘丘王:沉重号角,黑铜号角,黑晶号角
丘丘游侠:来自何处的待放之花,何人所珍藏之花,漫游者的盛放之花
愚人众先遣队:新兵的徽记,士官的徽记,尉官的徽记
萤术士:雾虚灯芯,雾虚草囊,雾虚花粉
债务处理人:特工祭刀,猎兵祭刀,督察长祭刀
冬国仕女:黯淡棱晶,水晶棱晶,偏光棱晶
愚人众风役人:老旧的役人怀表,役人的制式怀表,役人的时时刻刻
愚人众特辖队:磨损的执凭,精致的执凭,霜镌的执凭
盗宝团:寻宝鸦印,藏银鸦印,攫金鸦印
野伏众/海乱鬼:破旧的刀镡,影打刀镡,名刀镡
镀金旅团:褪色红绸,镶边红绸,织金红绸
黑蛇众/黯色空壳:晦暗刻像,夤夜刻像,幽邃刻像
部族龙形武士:卫从的木哨,战士的铁哨,龙冠武士的金哨
遗迹机械:混沌装置,混沌回路,混沌炉心
元能构装体:混浊棱晶,破缺棱晶,辉光棱晶
遗迹机兵:混沌机关,混沌枢纽,混沌真眼
遗迹龙兽:混沌锚栓,混沌模块,混沌容器
发条机关:啮合齿轮,机关正齿轮,奇械机芯齿轮
秘源机兵:秘源轴,秘源机鞘,秘源真芯
巡陆艇:毁损机轴,精制机轴,加固机轴
史莱姆:史莱姆凝液,史莱姆原浆,史莱姆清
骗骗花:骗骗花蜜,微光花蜜,原素花蜜
飘浮灵:浮游干核,浮游幽核,浮游晶化核
蕈兽:蕈兽孢子,荧光孢粉,孢囊晶尘
蕈兽:失活菌核,休眠菌核,茁壮菌核
浊水幻灵:浊水的一滴,浊水的一掬,初生的浊水幻灵
原海异种:异海凝珠,异海之块,异色结晶石
隙境原体:隙间之核,外世突触,异界生命核
魔像禁卫:残毁的剑柄,裂断的剑柄,未熄的剑柄
大灵显化身:意志破碎的残片,意志明晰的寄偶,意志巡游的符像
熔岩游像:聚燃的石块,聚燃的命种,聚燃的游像眼
龙蜥:脆弱的骨片,结实的骨片,石化的骨片
圣骸兽:残毁的横脊,密固的横脊,锲纹的横脊
玄文兽:羽状鳍翅,月色鳍翅,渊光鳍翅
纳塔龙众:稚嫩的尖齿,老练的坚齿,横行霸者的利齿
炉壳山鼬:冷裂壳块,蕴热的背壳,明燃的棱状壳
蕴光异兽:失光块骨,稀光遗骼,繁光躯外骸
深渊法师/深渊使徒:地脉的旧枝,地脉的枯叶,地脉的新芽
兽境群狼:隐兽利爪,隐兽指爪,隐兽鬼爪
深邃拟覆叶:折光的胚芽,迷光的蜷叶之心,惑光的阔叶
荒野狂猎:幽雾片甲,幽雾兜盔,幽雾化形
霜夜灵嗣:霜夜的煌荣,霜夜的柔辉,霜夜的残照
地脉花:流浪者的经验,冒险家的经验,大英雄的经验

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,3 @@
{"macroEvents":[{"type":6,"mouseX":0,"mouseY":-120,"time":0},
{"type":6,"mouseX":0,"mouseY":0,"time":5}],
"info":{"name":"","description":"","x":0,"y":0,"width":1920,"height":1080,"recordDpi":1}}

View File

@@ -0,0 +1,234 @@
/*
// 自动拾取逻辑
*/
// 解析文件内容,提取分类信息
function parseCategoryContent(content) {
if (!content) {
log.warn(`文件内容为空`);
return {};
}
const lines = content.split('\n').map(line => line.trim());
const categories = {};
lines.forEach(line => {
if (!line.includes(':')) return;
const [category, items] = line.split(':');
if (!category || !items) return;
categories[category.trim()] = items.split(',').map(item => item.trim()).filter(item => item !== '');
});
return categories;
}
// 从 targetText 文件夹中读取分类信息
function readtargetTextCategories(targetTextDir) {
// log.info(`开始读取材料分类信息:${targetTextDir}`);
const targetTextFilePaths = readAllFilePaths(targetTextDir, 0, 1);
const materialCategories = {};
for (const filePath of targetTextFilePaths) {
if (state.cancelRequested) break;
const content = file.readTextSync(filePath);
if (!content) {
log.error(`加载文件失败:${filePath}`);
continue; // 跳过当前文件
}
const sourceCategory = basename(filePath).replace('.txt', ''); // 去掉文件扩展名
materialCategories[sourceCategory] = parseCategoryContent(content);
}
// log.info(`完成材料分类信息读取,分类信息:${JSON.stringify(materialCategories, null, 2)}`);
return materialCategories;
}
// 定义替换映射表
const replacementMap = {
"监": "盐",
"炽": "烬",
"盞": "盏",
"攜": "携",
"於": "于",
"卵": "卯"
};
/**
* 执行OCR识别并匹配目标文本
* @param {string[]} targetTexts - 待匹配的目标文本列表
* @param {Object} xRange - X轴范围 { min: number, max: number }
* @param {Object} yRange - Y轴范围 { min: number, max: number }
* @param {number} timeout - 超时时间(毫秒)默认200ms
* @param {Object} ra - 图像捕获对象(外部传入,需确保已初始化)
* @returns {Promise<Object[]>} 识别结果数组,包含匹配目标的文本及坐标信息
*/
async function performOcr(targetTexts, xRange, yRange, timeout = 10, ra = null) {
// 正则特殊字符转义工具函数(避免替换时的正则语法错误)
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const startTime = Date.now(); // 记录开始时间,用于超时判断
// 2. 计算识别区域宽高xRange.max - xRange.min 为宽度y同理
const regionWidth = xRange.max - xRange.min;
const regionHeight = yRange.max - yRange.min;
if (regionWidth <= 0 || regionHeight <= 0) {
throw new Error(`无效的识别区域:宽=${regionWidth}, 高=${regionHeight}`);
}
// 在超时时间内循环重试识别(处理临时识别失败)
while (Date.now() - startTime < timeout) {
try {
// 1. 检查图像捕获对象是否有效
if (!ra) {
throw new Error("图像捕获对象(ra)未初始化");
}
// 3. 执行OCR识别在指定区域内查找多结果
const resList = ra.findMulti(
RecognitionObject.ocr(xRange.min, yRange.min, regionWidth, regionHeight)
);
// 4. 处理识别结果(文本修正 + 目标匹配)
const results = [];
for (let i = 0; i < resList.count; i++) {
const res = resList[i];
let correctedText = res.text; // 原始识别文本
log.info(`原始识别文本: ${res.text}`);
// 4.1 修正识别错误(替换错误字符)
for (const [wrongChar, correctChar] of Object.entries(replacementMap)) {
const escapedWrong = escapeRegExp(wrongChar); // 转义特殊字符
correctedText = correctedText.replace(new RegExp(escapedWrong, 'g'), correctChar);
}
// 4.2 检查是否包含任意目标文本(避免重复添加同个结果)
const isTargetMatched = targetTexts.some(target => correctedText.includes(target));
if (isTargetMatched) {
results.push({
text: correctedText,
x: res.x,
y: res.y,
width: res.width,
height: res.height
});
}
}
// 5. 识别成功,返回结果(无论是否匹配到目标,均返回当前识别到的有效结果)
return results;
} catch (error) {
// 识别异常时记录日志,继续重试(直到超时)
log.error(`OCR识别异常将重试: ${error.message}`);
// 短暂等待后重试,避免高频失败占用资源
await sleep(1);
}
await sleep(5); // 每次间隔 5 毫秒
}
// 超时未完成识别,返回空数组
log.warn(`OCR识别超时超过${timeout}ms`);
return [];
}
// const OCRdelay = Math.min(100, Math.max(0, Math.floor(Number(settings.OcrDelay) || 2))); // F识别基准时长
// 尝试找到 F 图标并返回其坐标
async function findFIcon(recognitionObject, timeout = 10, ra = null) {
let startTime = Date.now();
while (Date.now() - startTime < timeout && !state.cancelRequested) {
try {
let result = ra.find(recognitionObject);
if (result.isExist() && result.x !== 0 && result.y !== 0) {
return { success: true, x: result.x, y: result.y, width: result.width, height: result.height };
}
} catch (error) {
log.error(`识别图像时发生异常: ${error.message}`);
if (state.cancelRequested) {
break; // 如果请求了取消,则退出循环
}
return null;
}
await sleep(5); // 每次检测间隔 5 毫秒
}
if (state.cancelRequested) {
log.info("图像识别任务已取消");
}
return null;
}
// 对齐并交互目标
async function alignAndInteractTarget(targetTexts, fDialogueRo, textxRange, texttolerance, cachedFrame=null) {
let lastLogTime = Date.now();
// 记录每个材料的识别次数(文本+坐标 → 计数)
const recognitionCount = new Map();
while (!state.completed && !state.cancelRequested) {
const currentTime = Date.now();
if (currentTime - lastLogTime >= 10000) { // 每5秒记录一次日志
log.info("检测中...");
lastLogTime = currentTime;
}
await sleep(50); // 关键50时可避免F多目标滚动中拾取错背包js这边有弹窗模块就没必要增加延迟降低效率了
cachedFrame?.dispose();
cachedFrame = captureGameRegion();
// 尝试找到 F 图标
let fRes = await findFIcon(fDialogueRo, 10, cachedFrame);
if (!fRes) {
continue;
}
// 获取 F 图标的中心点 Y 坐标
let centerYF = fRes.y + fRes.height / 2;
// 在当前屏幕范围内进行 OCR 识别
let ocrResults = await performOcr(targetTexts, textxRange, { min: fRes.y - 3, max: fRes.y + 37 }, 10, cachedFrame);
// 检查所有目标文本是否在当前页面中
let foundTarget = false;
for (let targetText of targetTexts) {
let targetResult = ocrResults.find(res => res.text.includes(targetText));
if (targetResult) {
log.info(`找到目标文本: ${targetText}`);
// 生成唯一标识并更新识别计数(文本+Y坐标
const materialId = `${targetText}-${targetResult.y}`;
recognitionCount.set(materialId, (recognitionCount.get(materialId) || 0) + 1);
let centerYTargetText = targetResult.y + targetResult.height / 2;
if (Math.abs(centerYTargetText - centerYF) <= texttolerance) {
// log.info(`目标文本 '${targetText}' 和 F 图标水平对齐`);
if (recognitionCount.get(materialId) >= 1) {
keyPress("F"); // 执行交互操作
// log.info(`F键执行成功识别计数: ${recognitionCount.get(materialId)}`);
// F键后清除计数确保单次交互
recognitionCount.delete(materialId);
}
foundTarget = true;
break; // 成功交互后退出当前循环,但继续检测
}
}
}
// 如果在当前页面中没有找到任何目标文本,则滚动到下一页
if (!foundTarget) {
await keyMouseScript.runFile(`assets/滚轮下翻.json`);
// verticalScroll(-20);
}
if (state.cancelRequested) {
break;
}
cachedFrame?.dispose();
}
if (state.cancelRequested) {
log.info("检测任务已取消");
} else if (!state.completed) {
log.error("未能找到正确的目标文本或未成功交互,跳过该目标文本");
}
}

View File

@@ -0,0 +1,733 @@
eval(file.readTextSync("lib/region.js"));
const holdX = Math.min(1920, Math.max(0, Math.floor(Number(settings.HoldX) || 1050)));
const holdY = Math.min(1080, Math.max(0, Math.floor(Number(settings.HoldY) || 750)));
const totalPageDistance = Math.max(10, Math.floor(Number(settings.PageScrollDistance) || 711));
const targetCount = Math.min(9999, Math.max(0, Math.floor(Number(settings.TargetCount) || 5000))); // 设定的目标数量
const imageDelay = Math.min(1000, Math.max(0, Math.floor(Number(settings.ImageDelay) || 0))); // 识图基准时长 await sleep(imageDelay);
// 配置参数
const pageScrollCount = 22; // 最多滑页次数
// 材料分类映射表
const materialTypeMap = {
"锻造素材": "5",
"怪物掉落素材": "3",
"一般素材": "5",
"周本素材": "3",
"烹饪食材": "5",
"角色突破素材": "3",
"木材": "5",
"宝石": "3",
"鱼饵鱼类": "5",
"角色天赋素材": "3",
"武器突破素材": "3",
"采集食物": "4",
"料理": "4",
};
// 材料前位定义
const materialPriority = {
"养成道具": 1,
"祝圣精华": 2,
"锻造素材": 1,
"怪物掉落素材": 1,
"采集食物": 1,
"一般素材": 2,
"周本素材": 2,
"料理": 2,
"烹饪食材": 3,
"角色突破素材": 3,
"木材": 4,
"宝石": 4,
"鱼饵鱼类": 5,
"角色天赋素材": 5,
"武器突破素材": 6,
};
// 提前计算所有动态坐标
// 物品区左顶处物品左上角坐标(117,121)
// 物品图片大小(123,152)
// 物品间隔(24,24)
// 第一点击区位置:123/2+117=178.5; 152/2+121=197
// const menuClickX = Math.round(575 + (Number(menuOffset) - 1) * 96.25); // 背包菜单的 X 坐标
// log.info(`材料分类: ${materialsCategory}, 菜单偏移值: ${menuOffset}, 计算出的点击 X 坐标: ${menuClickX}`);
// OCR识别文本
async function recognizeText(ocrRegion, timeout = 1000, retryInterval = 20, maxAttempts = 10, maxFailures = 3, ra = null) {
let startTime = Date.now();
let retryCount = 0;
let failureCount = 0; // 用于记录连续失败的次数
// const results = [];
const frequencyMap = {}; // 用于记录每个结果的出现次数
const numberReplaceMap = {
"O": "0", "o": "0", "Q": "0", "": "0",
"I": "1", "l": "1", "i": "1", "": "1", "一": "1",
"Z": "2", "z": "2", "": "2", "二": "2",
"E": "3", "e": "3", "": "3", "三": "3",
"A": "4", "a": "4", "": "4",
"S": "5", "s": "5", "": "5",
"G": "6", "b": "6", "": "6",
"T": "7", "t": "7", "": "7",
"B": "8", "θ": "8", "": "8",
"g": "9", "q": "9", "": "9",
};
while (Date.now() - startTime < timeout && retryCount < maxAttempts) {
let ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height);
ocrObject.threshold = 0.85; // 适当降低阈值以提高速度
let resList = ra.findMulti(ocrObject);
if (resList.count === 0) {
failureCount++;
if (failureCount >= maxFailures) {
ocrRegion.x += 3; // 每次缩小6像素
ocrRegion.width -= 6; // 每次缩小6像素
retryInterval += 10;
if (ocrRegion.width <= 12) {
return false;
}
}
retryCount++;
await sleep(retryInterval);
continue;
}
for (let res of resList) {
let text = res.text;
text = text.split('').map(char => numberReplaceMap[char] || char).join('');
// results.push(text);
if (!frequencyMap[text]) {
frequencyMap[text] = 0;
}
frequencyMap[text]++;
if (frequencyMap[text] >= 2) {
return text;
}
}
await sleep(retryInterval);
}
const sortedResults = Object.keys(frequencyMap).sort((a, b) => frequencyMap[b] - frequencyMap[a]);
return sortedResults.length > 0 ? sortedResults[0] : false;
}
// 优化后的滑动页面函数(基于通用函数)
async function scrollPage(totalDistance, stepDistance = 10, delayMs = 5) {
await mouseDrag({
holdMouseX: holdX, // 固定起点X
holdMouseY: holdY, // 固定起点Y
totalDistance: totalDistance, // 原逻辑中是向上滑动(原代码中是-moveDistance
stepDistance,
stepInterval: delayMs,
waitBefore: 50,
waitAfter: 700, // 原逻辑中松开后等待700ms
repeat: 1
});
}
// 通用鼠标拖动函数(提取重复逻辑)
/**
* 通用鼠标拖动工具函数
* @param {Object} options 拖动参数
* @param {number} [options.holdX] 拖动起点X坐标默认当前鼠标位置
* @param {number} [options.holdY] 拖动起点Y坐标默认当前鼠标位置
* @param {number} options.totalDistance 总拖动距离(垂直方向,正数向下,负数向上)
* @param {number} [options.stepDistance=10] 每步拖动距离
* @param {number} [options.stepInterval=20] 步间隔时间(ms)
* @param {number} [options.waitBefore=50] 按下左键前的等待时间(ms)
* @param {number} [options.waitAfter=100] 拖动结束后的等待时间(ms)
* @param {number} [options.repeat=1] 重复拖动次数
* @param {Object} [options.state] 状态对象(用于外部控制中断)
*/
async function mouseDrag({
holdMouseX,
holdMouseY,
totalDistance,
stepDistance = 10,
stepInterval = 20,
waitBefore = 50,
waitAfter = 100,
repeat = 1,
state = { isRunning: true }
}) {
try {
// 移动到起点(如果指定了起点)
if (holdMouseX !== undefined && holdMouseY !== undefined) {
moveMouseTo(holdMouseX, holdMouseY);
await sleep(waitBefore);
}
leftButtonDown();
await sleep(waitBefore); // 按下后短暂等待
for (let r = 0; r < repeat; r++) {
if (!state.isRunning) break;
const steps = Math.ceil(Math.abs(totalDistance) / stepDistance);
const direction = totalDistance > 0 ? 1 : -1; // 方向:正向下,负向上
for (let s = 0; s < steps; s++) {
if (!state.isRunning) break;
// 计算当前步实际移动距离最后一步可能不足stepDistance
const remaining = Math.abs(totalDistance) - s * stepDistance;
const move = Math.min(stepDistance, remaining) * direction;
moveMouseBy(0, move); // 垂直拖动
await sleep(stepInterval);
}
// 更新进度(如果有状态对象)
if (state) {
state.progress = Math.min(100, Math.round((r + 1) / repeat * 100));
}
await sleep(waitAfter);
}
leftButtonUp();
await sleep(waitBefore); // 松开后等待稳定
} catch (e) {
log.error(`拖动出错: ${e.message}`);
leftButtonUp(); // 确保鼠标抬起,避免卡死
}
}
function filterMaterialsByPriority(materialsCategory) {
// 获取当前材料分类的优先级
const currentPriority = materialPriority[materialsCategory];
if (currentPriority === undefined) {
throw new Error(`Invalid materialsCategory: ${materialsCategory}`);
}
// 获取当前材料分类的 materialTypeMap 对应值
const currentType = materialTypeMap[materialsCategory];
if (currentType === undefined) {
throw new Error(`Invalid materialTypeMap for: ${materialsCategory}`);
}
// 获取所有优先级更高的材料分类(前位材料)
const frontPriorityMaterials = Object.keys(materialPriority)
.filter(mat => materialPriority[mat] < currentPriority && materialTypeMap[mat] === currentType);
// 获取所有优先级更低的材料分类(后位材料)
const backPriorityMaterials = Object.keys(materialPriority)
.filter(mat => materialPriority[mat] > currentPriority && materialTypeMap[mat] === currentType);
// 合并当前和后位材料分类
const finalFilteredMaterials = [...backPriorityMaterials,materialsCategory ];// 当前材料
return finalFilteredMaterials
}
// 扫描材料
async function scanMaterials(materialsCategory, materialCategoryMap) {
// 获取当前+后位材料名单
const priorityMaterialNames = [];
const finalFilteredMaterials = await filterMaterialsByPriority(materialsCategory);
for (const category of finalFilteredMaterials) {
const materialIconDir = `assets/images/${category}`;
const materialIconFilePaths = file.ReadPathSync(materialIconDir);
for (const filePath of materialIconFilePaths) {
const name = basename(filePath).replace(".png", ""); // 去掉文件扩展名
priorityMaterialNames.push({ category, name });
}
}
// 根据材料分类获取对应的材料图片文件夹路径
const materialIconDir = `assets/images/${materialsCategory}`;
// 使用 ReadPathSync 读取所有材料图片路径
const materialIconFilePaths = file.ReadPathSync(materialIconDir);
// 创建材料种类集合
const materialCategories = [];
const allMaterials = new Set(); // 用于记录所有需要扫描的材料名称
const materialImages = {}; // 用于缓存加载的图片
// 检查 materialCategoryMap 中当前分类的数组是否为空
const categoryMaterials = materialCategoryMap[materialsCategory] || [];
const shouldScanAllMaterials = categoryMaterials.length === 0; // 如果为空,则扫描所有材料
for (const filePath of materialIconFilePaths) {
const name = basename(filePath).replace(".png", ""); // 去掉文件扩展名
// 如果 materialCategoryMap 中当前分类的数组不为空
// 且当前材料名称不在指定的材料列表中,则跳过加载
if (pathingMode.onlyPathing && !shouldScanAllMaterials && !categoryMaterials.includes(name)) {
continue;
}
const mat = file.readImageMatSync(filePath);
if (mat.empty()) {
log.error(`加载图标失败:${filePath}`);
continue; // 跳过当前文件
}
materialCategories.push({ name, filePath });
allMaterials.add(name); // 将材料名称添加到集合中
materialImages[name] = mat; // 缓存图片
}
// 已识别的材料集合,避免重复扫描
const recognizedMaterials = new Set();
const unmatchedMaterialNames = new Set(); // 未匹配的材料名称
const materialInfo = []; // 存储材料名称和数量
// 扫描参数
const tolerance = 1;
const startX = 117;
const startY = 121;
const OffsetWidth = 146.428; // 146.428
const columnWidth = 123;
const columnHeight = 680;
const maxColumns = 8;
// 跟踪已截图的区域(避免重复)
const capturedRegions = new Set();
// 扫描状态
let hasFoundFirstMaterial = false;
let lastFoundTime = null;
let shouldEndScan = false;
let foundPriorityMaterial = false;
// 俏皮话逻辑
const scanPhrases = [
"扫描中... 太好啦,有这么多素材!",
"扫描中... 不错的珍宝!",
"扫描中... 侦查骑士,发现目标!",
"扫描中... 嗯哼,意外之喜!",
"扫描中... 嗯?",
"扫描中... 很好,没有放过任何角落!",
"扫描中... 会有烟花材料嘛?",
"扫描中... 嗯,这是什么?",
"扫描中... 这些宝藏积灰了,先清洗一下",
"扫描中... 哇!都是好东西!",
"扫描中... 不虚此行!",
"扫描中... 瑰丽的珍宝,令人欣喜。",
"扫描中... 是对长高有帮助的东西吗?",
"扫描中... 嗯!品相卓越!",
"扫描中... 虽无法比拟黄金,但终有价值。",
"扫描中... 收获不少,可以拿去换几瓶好酒啦。",
"扫描中... 房租和伙食费,都有着落啦!",
"扫描中... 还不赖。",
"扫描中... 荒芜的世界,竟藏有这等瑰宝。",
"扫描中... 运气还不错。",
];
let tempPhrases = [...scanPhrases];
tempPhrases.sort(() => Math.random() - 0.5); // 打乱数组顺序,确保随机性
let phrasesStartTime = Date.now();
// 扫描背包中的材料
for (let scroll = 0; scroll <= pageScrollCount; scroll++) {
const ra = captureGameRegion();
if (!foundPriorityMaterial) {
for (const { category, name } of priorityMaterialNames) {
if (recognizedMaterials.has(name)) {
continue; // 如果已经识别过,跳过
}
const filePath = `assets/images/${category}/${name}.png`;
const mat = file.readImageMatSync(filePath);
if (mat.empty()) {
log.error(`加载材料图库失败:${filePath}`);
continue; // 跳过当前文件
}
const recognitionObject = RecognitionObject.TemplateMatch(mat, 1142, startY, columnWidth, columnHeight);
recognitionObject.threshold = 0.8; // 设置识别阈值
recognitionObject.Use3Channels = true;
const result = ra.find(recognitionObject);
if (result.isExist() && result.x !== 0 && result.y !== 0) {
foundPriorityMaterial = true; // 标记找到前位材料
// drawAndClearRedBox(result, ra, 100);// 调用异步函数绘制红框并延时清除
log.info(`发现当前或后位材料: ${name},开始全列扫描`);
break; // 发现前位材料后,退出当前循环
}
}
}
if (foundPriorityMaterial) {
for (let column = 0; column < maxColumns; column++) {
const scanX0 = startX + column * OffsetWidth;
const scanX = Math.round(scanX0);
for (let i = 0; i < materialCategories.length; i++) {
const { name } = materialCategories[i];
if (recognizedMaterials.has(name)) {
continue; // 如果已经识别过,跳过
}
const mat = materialImages[name];
const recognitionObject = RecognitionObject.TemplateMatch(mat, scanX, startY, columnWidth, columnHeight);
recognitionObject.threshold = 0.85;
recognitionObject.Use3Channels = true;
const result = ra.find(recognitionObject);
if (result.isExist() && result.x !== 0 && result.y !== 0) {
recognizedMaterials.add(name);
moveMouseTo(result.x, result.y);
// drawAndClearRedBox(result, ra, 100);// 调用异步函数绘制红框并延时清除
const ocrRegion = {
x: result.x - tolerance,
y: result.y + 97 - tolerance,
width: 66 + 2 * tolerance,
height: 22 + 2 * tolerance
};
const ocrResult = await recognizeText(ocrRegion, 1000, 10, 10, 3, ra);
materialInfo.push({ name, count: ocrResult || "?" });
if (!hasFoundFirstMaterial) {
hasFoundFirstMaterial = true;
lastFoundTime = Date.now();
} else {
lastFoundTime = Date.now();
}
}
await sleep(imageDelay);
}
}
}
// 每5秒输出一句俏皮话
const phrasesTime = Date.now();
if (phrasesTime - phrasesStartTime >= 5000) {
const selectedPhrase = tempPhrases.shift();
log.info(selectedPhrase);
if (tempPhrases.length === 0) {
tempPhrases = [...scanPhrases];
tempPhrases.sort(() => Math.random() - 0.5);
}
phrasesStartTime = phrasesTime;
}
// 检查是否结束扫描
if (recognizedMaterials.size === allMaterials.size) {
log.info("所有材料均已识别!");
shouldEndScan = true;
break;
}
if (hasFoundFirstMaterial && Date.now() - lastFoundTime > 5000) {
log.info("未发现新的材料,结束扫描");
shouldEndScan = true;
break;
}
// 检查是否到达最后一页
const sliderBottomRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/SliderBottom.png"), 1284, 916, 9, 26);
sliderBottomRo.threshold = 0.8;
const sliderBottomResult = ra.find(sliderBottomRo);
if (sliderBottomResult.isExist()) {
log.info("已到达最后一页!");
shouldEndScan = true;
break;
}
// 滑动到下一页
if (scroll < pageScrollCount) {
await scrollPage(-totalPageDistance, 10, 5);
await sleep(100);
}
}
// 处理未匹配的材料
for (const name of allMaterials) {
if (!recognizedMaterials.has(name)) {
unmatchedMaterialNames.add(name);
}
}
// 日志记录
const now = new Date();
const formattedTime = now.toLocaleString();
const scanMode = shouldScanAllMaterials ? "全材料扫描" : "指定材料扫描";
const logContent = `
${formattedTime}
${scanMode} - ${materialsCategory} 种类: ${recognizedMaterials.size} 数量:
${materialInfo.map(item => `${item.name}: ${item.count}`).join(",")}
未匹配的材料 种类: ${unmatchedMaterialNames.size} 数量:
${Array.from(unmatchedMaterialNames).join(",")}
`;
const categoryFilePath = `history_record/${materialsCategory}.txt`; // 勾选【材料分类】的历史记录
const overwriteFilePath = `overwrite_record/${materialsCategory}.txt`; // 所有的历史记录分类储存
const latestFilePath = "latest_record.txt"; // 所有的历史记录集集合
if (pathingMode.onlyCategory) {
writeLog(categoryFilePath, logContent);
}
writeLog(overwriteFilePath, logContent);
writeLog(latestFilePath, logContent); // 覆盖模式?
// 返回结果
return materialInfo;
}
function writeLog(filePath, logContent) {
try {
// 1. 读取现有内容(原样读取,不做任何分割处理)
let existingContent = "";
try {
existingContent = file.readTextSync(filePath);
} catch (e) {
// 文件不存在则保持空
}
// 2. 拼接新记录(新记录加在最前面,用两个换行分隔,保留原始格式)
const finalContent = logContent + "\n\n" + existingContent;
// 3. 按行分割保留最近365条完整记录按原始换行分割不过滤
const lines = finalContent.split("\n");
const keepLines = lines.length > 365 * 5 ? lines.slice(0, 365 * 5) : lines; // 假设每条记录最多5行
const result = file.writeTextSync(filePath, keepLines.join("\n"), false);
if (result) {
log.info(`写入成功: ${filePath}`);
} else {
log.error(`写入失败: ${filePath}`);
}
} catch (error) {
// 只在文件完全不存在时创建,避免覆盖
file.writeTextSync(filePath, logContent, false);
log.info(`创建新文件: ${filePath}`);
}
}
// 定义所有图标的图像识别对象,每个图片都有自己的识别区域
const BagpackRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Bagpack.png"), 58, 31, 38, 38);
const MaterialsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Materials.png"), 941, 29, 38, 38);
const CultivationItemsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/CultivationItems.png"), 749, 30, 38, 38);
const FoodRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Food.png"), 845, 31, 38, 38);
const specialMaterials = [
"水晶块", "魔晶块", "星银矿石", "紫晶块", "萃凝晶", "铁块", "白铁块",
"精锻用魔矿", "精锻用良矿", "精锻用杂矿"
];
function filterLowCountMaterials(pathingMaterialCounts, materialCategoryMap) {
// 将 materialCategoryMap 中的所有材料名提取出来
const allMaterials = Object.values(materialCategoryMap).flat();
// 筛选 pathingMaterialCounts 中的材料,只保留 materialCategoryMap 中定义的材料,并且数量低于 targetCount 或 count 为 "?" 或 name 在 specialMaterials 中
return pathingMaterialCounts
.filter(item =>
allMaterials.includes(item.name) &&
(item.count < targetCount || item.count === "?")
)
.map(item => {
// 如果 name 在 specialMaterials 数组中
if (specialMaterials.includes(item.name)) {
// 如果 count 是 "?",直接保留
if (item.count === "?") {
return item;
}
// 否则,将 count 除以 10 并向下取整
item.count = Math.floor(item.count / 10);
}
return item;
});
}
function dynamicMaterialGrouping(materialCategoryMap) {
// 初始化动态分组对象
const dynamicMaterialGroups = {};
// 遍历 materialCategoryMap 的 entries
for (const category in materialCategoryMap) {
const type = materialTypeMap[category]; // 获取材料分类对应的组编号3、4、5
if (!dynamicMaterialGroups[type]) {
dynamicMaterialGroups[type] = []; // 初始化组
}
dynamicMaterialGroups[type].push(category); // 将分类加入对应组
}
// 对每组内的材料分类按照 materialPriority 排序
for (const type in dynamicMaterialGroups) {
dynamicMaterialGroups[type].sort((a, b) => materialPriority[a] - materialPriority[b]);
}
// 将分组结果转换为数组并按类型排序3, 4, 5
const sortedGroups = Object.entries(dynamicMaterialGroups)
.map(([type, categories]) => ({ type: parseInt(type), categories }))
.sort((a, b) => a.type - b.type);
// 返回分组结果
return sortedGroups;
}
// 主逻辑函数
async function MaterialPath(materialCategoryMap, cachedFrame = null) {
// 1. 先记录原始名称与别名的映射关系(用于最后反向转换)
const nameMap = new Map();
Object.values(materialCategoryMap).flat().forEach(originalName => {
const aliasName = MATERIAL_ALIAS[originalName] || originalName;
nameMap.set(aliasName, originalName); // 存储:别名→原始名
});
// 2. 转换materialCategoryMap为别名用于内部处理
const processedMap = {};
Object.entries(materialCategoryMap).forEach(([category, names]) => {
processedMap[category] = names.map(name => MATERIAL_ALIAS[name] || name);
});
materialCategoryMap = processedMap;
const maxStage = 4; // 最大阶段数
let stage = 0; // 当前阶段
let currentGroupIndex = 0; // 当前处理的分组索引
let currentCategoryIndex = 0; // 当前处理的分类索引
let materialsCategory = ""; // 当前处理的材料分类名称
const allLowCountMaterials = []; // 用于存储所有识别到的低数量材料信息
const sortedGroups = dynamicMaterialGrouping(materialCategoryMap);
// log.info("材料 动态[分组]结果:");
sortedGroups.forEach(group => {
log.info(`类型 ${group.type} | 包含分类: ${group.categories.join(', ')}`);
});
while (stage <= maxStage) {
switch (stage) {
case 0: // 返回主界面
log.info("返回主界面");
await genshin.returnMainUi();
await sleep(500);
stage = 1; // 进入下一阶段
break;
case 1: // 打开背包界面
// log.info("打开背包界面");
keyPress("B"); // 打开背包界面
await sleep(1000);
cachedFrame?.dispose();
cachedFrame = captureGameRegion();
const backpackResult = await recognizeImage(BagpackRo, cachedFrame, 2000);
if (backpackResult.isDetected) {
// log.info("成功识别背包图标");
stage = 2; // 进入下一阶段
} else {
log.warn("未识别到背包图标,重新尝试");
stage = 0; // 回退
}
break;
case 2: // 按分组处理材料分类
if (currentGroupIndex < sortedGroups.length) {
const group = sortedGroups[currentGroupIndex];
if (currentCategoryIndex < group.categories.length) {
materialsCategory = group.categories[currentCategoryIndex];
const offset = materialTypeMap[materialsCategory];
const menuClickX = Math.round(575 + (offset - 1) * 96.25);
// log.info(`点击坐标 (${menuClickX},75)`);
click(menuClickX, 75);
await sleep(500);
cachedFrame?.dispose();
cachedFrame = captureGameRegion();
stage = 3; // 进入下一阶段
} else {
currentGroupIndex++;
currentCategoryIndex = 0; // 重置分类索引
stage = 2; // 继续处理下一组
}
} else {
stage = 5; // 跳出循环
}
break;
case 3: // 识别材料分类
let CategoryObject;
switch (materialsCategory) {
case "锻造素材":
case "一般素材":
case "烹饪食材":
case "木材":
case "鱼饵鱼类":
CategoryObject = MaterialsRo;
break;
case "采集食物":
case "料理":
CategoryObject = FoodRo;
break;
case "怪物掉落素材":
case "周本素材":
case "角色突破素材":
case "宝石":
case "角色天赋素材":
case "武器突破素材":
CategoryObject = CultivationItemsRo;
break;
default:
log.error("未知的材料分类");
stage = 0; // 回退到阶段0
return;
}
const CategoryResult = await recognizeImage(CategoryObject, cachedFrame);
if (CategoryResult.isDetected) {
log.info(`识别到${materialsCategory} 所在分类。`);
stage = 4; // 进入下一阶段
} else {
log.warn("未识别到材料分类图标,重新尝试");
log.warn(`识别结果:${JSON.stringify(CategoryResult)}`);
stage = 2; // 回退到阶段2
}
break;
case 4: // 扫描材料
log.info("芭芭拉,冲鸭!");
await moveMouseTo(1288, 124); // 移动鼠标至滑条顶端
await sleep(200);
leftButtonDown(); // 长按左键重置材料滑条
await sleep(300);
leftButtonUp();
await sleep(200);
// 扫描材料并获取低于目标数量的材料
const lowCountMaterials = await scanMaterials(materialsCategory, materialCategoryMap);
allLowCountMaterials.push(lowCountMaterials);
currentCategoryIndex++;
stage = 2; // 返回阶段2处理下一个分类
break;
case 5: // 所有分组处理完毕
log.info("所有分组处理完毕,返回主界面");
await genshin.returnMainUi();
stage = maxStage + 1; // 确保退出循环
break;
}
}
await genshin.returnMainUi(); // 返回主界面
log.info("扫描流程结束");
// 3. 处理完成后,将输出结果转换回原始名称
const finalResult = allLowCountMaterials.map(categoryMaterials => {
return categoryMaterials.map(material => {
// 假设material包含name属性将别名转回原始名
return {
...material,
name: nameMap.get(material.name) || material.name // 反向映射
};
});
});
cachedFrame?.dispose();
return finalResult; // 返回转换后的结果(如"晶蝶"
}

View File

@@ -0,0 +1,62 @@
/*
// 位移计算逻辑
*/
// 辅助函数:计算两点之间的距离
function calculateDistance(initialPosition, finalPosition) {
const deltaX = finalPosition.X - initialPosition.X;
const deltaY = finalPosition.Y - initialPosition.Y;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
/*
// 位移监测函数
async function monitorDisplacement(monitoring, resolve) {
// 获取对象的实际初始位置
let lastPosition = genshin.getPositionFromMap();
let cumulativeDistance = 0; // 初始化累计位移量
let lastUpdateTime = Date.now(); // 记录上一次位置更新的时间
while (monitoring) {
const currentPosition = genshin.getPositionFromMap(); // 获取当前位置
const currentTime = Date.now(); // 获取当前时间
// 计算位移量
const deltaX = currentPosition.X - lastPosition.X;
const deltaY = currentPosition.Y - lastPosition.Y;
let distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// 如果位移量小于0.5则视为0
if (distance < 0.5) {
distance = 0;
}
// 如果有位移,更新累计位移量和最后更新时间
if (distance > 0) {
cumulativeDistance += distance; // 累计位移量
lastUpdateTime = currentTime; // 更新最后更新时间
}
// 检测是否超过5秒没有位移
if (currentTime - lastUpdateTime >= 5000) {
// 触发跳跃
keyPress(VK_SPACE);
lastUpdateTime = currentTime; // 重置最后更新时间
}
// 输出位移信息和累计位移量
log.info(`时间:${(currentTime - lastUpdateTime) / 1000}秒,位移信息: X=${currentPosition.X}, Y=${currentPosition.Y}, 当前位移量=${distance.toFixed(2)}, 累计位移量=${cumulativeDistance.toFixed(2)}`);
// 更新最后位置
lastPosition = currentPosition;
// 等待1秒再次检查
await sleep(1000);
}
// 当监测结束时,返回累计位移量
resolve(cumulativeDistance);
}
*/

View File

@@ -0,0 +1,159 @@
// ===================== 狗粮模式专属函数 =====================
// 1. 狗粮分解配置与OCR区域
const AUTO_SALVAGE_CONFIG = {
autoSalvage3: settings.autoSalvage3 || "是",
autoSalvage4: settings.autoSalvage4 || "是"
};
const OCR_REGIONS = {
expStorage: { x: 1472, y: 883, width: 170, height: 34 },
expCount: { x: 1472, y: 895, width: 170, height: 34 }
};
// 2. 数字替换映射表处理OCR识别误差
const numberReplaceMap = {
"O": "0", "o": "0", "Q": "0", "": "0",
"I": "1", "l": "1", "i": "1", "": "1", "一": "1",
"Z": "2", "z": "2", "": "2", "二": "2",
"E": "3", "e": "3", "": "3", "三": "3",
"A": "4", "a": "4", "": "4",
"S": "5", "s": "5", "": "5",
"G": "6", "b": "6", "": "6",
"T": "7", "t": "7", "": "7",
"B": "8", "θ": "8", "": "8",
"g": "9", "q": "9", "": "9",
};
// 3. OCR文本处理
function processExpText(text) {
let correctedText = text || "";
let removedSymbols = [];
// 替换错误字符
for (const [wrong, correct] of Object.entries(numberReplaceMap)) {
correctedText = correctedText.replace(new RegExp(wrong, 'g'), correct);
}
// 提取纯数字
let finalText = '';
for (const char of correctedText) {
if (/[0-9]/.test(char)) {
finalText += char;
} else if (char.trim() !== '') {
removedSymbols.push(char);
}
}
return {
processedText: finalText,
removedSymbols: [...new Set(removedSymbols)]
};
}
// 4. OCR识别EXP
async function recognizeExpRegion(regionName, ra = null, timeout = 2000) {
const ocrRegion = OCR_REGIONS[regionName];
if (!ocrRegion) {
log.error(`[狗粮OCR] 无效区域:${regionName}`);
return { success: false, expCount: 0 };
}
log.info(`[狗粮OCR] 识别${regionName}x=${ocrRegion.x}, y=${ocrRegion.y}`);
const startTime = Date.now();
let retryCount = 0;
while (Date.now() - startTime < timeout) {
try {
const ocrResult = ra.find(RecognitionObject.ocr(
ocrRegion.x,
ocrRegion.y,
ocrRegion.width,
ocrRegion.height
));
log.info(`[狗粮OCR] 原始文本:${ocrResult.text}`);
if (ocrResult?.text) {
const { processedText, removedSymbols } = processExpText(ocrResult.text);
if (removedSymbols.length > 0) {
log.info(`[狗粮OCR] 去除无效字符:${removedSymbols.join(', ')}`);
}
const expCount = processedText ? parseInt(processedText, 10) : 0;
log.info(`[狗粮OCR] ${regionName}结果:${expCount}`);
return { success: true, expCount };
}
} catch (error) {
retryCount++;
log.warn(`[狗粮OCR] ${regionName}${retryCount}次识别失败:${error.message}`);
}
await sleep(500);
}
log.error(`[狗粮OCR] ${regionName}超时未识别默认0`);
return { success: false, expCount: 0 };
}
// 5. 狗粮分解流程
async function executeSalvageWithOCR() {
log.info("[狗粮分解] 开始执行分解流程");
let storageExp = 0;
let countExp = 0;
let cachedFrame = null;
try {
keyPress("B"); await sleep(1000);
const coords = [
[670, 40], // 打开背包
[660, 1010], // 打开分解
[300, 1020], // 打开分解选项页面
// [200, 150, 500], // 勾选1星狗粮
// [200, 220, 500], // 勾选2星狗粮
[200, 300, 500, AUTO_SALVAGE_CONFIG.autoSalvage3 !== '否'], // 3星按配置
[200, 380, 500, AUTO_SALVAGE_CONFIG.autoSalvage4 !== '否'], // 4星按配置
[340, 1000], // 确认选择
[1720, 1015], // 点击是否分解
[1320, 756], // 确认分解
[1840, 45, 1500], // 退出
[1840, 45], // 退出
[1840, 45], // 退出
];
for (const coord of coords) {
const [x, y, delay = 1000, condition = true] = coord;
if (condition) {
click(x, y);
await sleep(delay);
log.debug(`[狗粮分解] 点击(${x},${y}),延迟${delay}ms`);
// 分解前识别储存EXP
if (x === 660 && y === 1010) {
cachedFrame?.dispose();
cachedFrame = captureGameRegion();
const { expCount } = await recognizeExpRegion("expStorage", cachedFrame, 1000);
storageExp = expCount;
}
// 分解后识别新增EXP
if (x === 340 && y === 1000) {
cachedFrame?.dispose();
cachedFrame = captureGameRegion();
const { expCount } = await recognizeExpRegion("expCount", cachedFrame, 1000);
countExp = expCount;
}
}
}
const totalExp = countExp - storageExp; // 分解新增EXP = 分解后 - 分解前
log.info(`[狗粮分解] 完成新增EXP${totalExp}(分解前:${storageExp},分解后:${countExp}`);
return { success: true, totalExp: Math.max(totalExp, 0) }; // 避免负数
} catch (error) {
log.error(`[狗粮分解] 失败:${error.message}`);
return { success: false, totalExp: 0 };
}
}
// 6. 判断是否为狗粮资源(关键词匹配)
function isFoodResource(resourceName) {
const foodKeywords = ["12h狗粮", "24h狗粮"];
return resourceName && foodKeywords.some(keyword => resourceName.includes(keyword));
}

View File

@@ -0,0 +1,153 @@
// ==============================================
// 5. 角色识别与策略执行相关函数(保留原始功能)
// ==============================================
// 工具函数
function basename(filePath) {
if (!filePath || typeof filePath !== 'string') return '';
const normalizedPath = filePath.replace(/\\/g, '/');
const lastSlashIndex = normalizedPath.lastIndexOf('/');
return lastSlashIndex !== -1 ? normalizedPath.substring(lastSlashIndex + 1) : normalizedPath;
}
/*
// 如果路径存在且返回的是数组则认为是目录Folder
function pathExists(path) {
try { return file.readPathSync(path)?.length >= 0; }
catch { return false; }
}
*/
function pathExists(path) {
try {
return file.isFolder(path);
} catch {
return false;
}
}
// 判断文件是否存在(非目录且能读取)
function fileExists(filePath) {
try {
// 先排除目录(是目录则不是文件)
if (file.isFolder(filePath)) {
return false;
}
// 尝试读取文件(能读则存在)
file.readTextSync(filePath);
return true;
} catch {
return false;
}
}
// 带深度限制的非递归文件夹读取
function readAllFilePaths(dir, depth = 0, maxDepth = 3, includeExtensions = ['.png', '.json', '.txt'], includeDirs = false) {
if (!pathExists(dir)) {
log.error(`目录 ${dir} 不存在`);
return [];
}
try {
const filePaths = [];
const stack = [[dir, depth]]; // 存储(路径, 当前深度)的栈
while (stack.length > 0) {
const [currentDir, currentDepth] = stack.pop();
const entries = file.readPathSync(currentDir);
for (const entry of entries) {
const isDirectory = pathExists(entry);
if (isDirectory) {
if (includeDirs) filePaths.push(entry);
if (currentDepth < maxDepth) stack.push([entry, currentDepth + 1]);
} else {
const ext = entry.substring(entry.lastIndexOf('.')).toLowerCase();
if (includeExtensions.includes(ext)) filePaths.push(entry);
}
}
}
// log.info(`完成目录 ${dir} 的递归读取,共找到 ${filePaths.length} 个文件`);
return filePaths;
} catch (error) {
log.error(`读取目录 ${dir} 错误: ${error}`);
return [];
}
}
/*
// 新记录在最下面
async function writeFile(filePath, content, isAppend = false, maxRecords = 365) {
try {
if (isAppend) {
// 读取现有内容(如果文件不存在则为空)
const existingContent = file.readTextSync(filePath) || "";
// 分割成记录数组(过滤空字符串)
const records = existingContent.split("\n\n").filter(Boolean);
// 关键修复将新内容添加到末尾然后只保留最后maxRecords条
const allRecords = [...records, content]; // 新内容放在最后
const latestRecords = allRecords.slice(-maxRecords); // 整体截取最新的maxRecords条
// 拼接成最终内容
const finalContent = latestRecords.join("\n\n");
const result = file.WriteTextSync(filePath, finalContent, false);
// 日志输出(可根据需要启用)
// log.info(result ? `[追加] 成功写入: ${filePath}` : `[追加] 写入失败: ${filePath}`);
return result;
} else {
// 非追加模式:直接覆盖写入
const result = file.WriteTextSync(filePath, content, false);
// log.info(result ? `[覆盖] 成功写入: ${filePath}` : `[覆盖] 写入失败: ${filePath}`);
return result;
}
} catch (error) {
// 发生错误时尝试创建文件并写入
const result = file.WriteTextSync(filePath, content, false);
log.info(result ? `[新建] 成功创建: ${filePath}` : `[新建] 创建失败: ${filePath}`);
return result;
}
}
*/
// 新记录在最上面20250531
async function writeFile(filePath, content, isAppend = false, maxRecords = 365) {
try {
if (isAppend) {
// 读取现有内容,处理文件不存在的情况
let existingContent = "";
try {
existingContent = file.readTextSync(filePath);
} catch (err) {
// 文件不存在时视为空内容
existingContent = "";
}
// 分割现有记录并过滤空记录
const records = existingContent.split("\n\n").filter(Boolean);
// 新内容放在最前面,形成完整记录列表
const allRecords = [content, ...records];
// 只保留最新的maxRecords条超过则删除最老的
const keptRecords = allRecords.slice(0, maxRecords);
// 拼接记录并写入文件
const finalContent = keptRecords.join("\n\n");
const result = file.WriteTextSync(filePath, finalContent, false);
// log.info(result ? `[追加] 成功写入: ${filePath}` : `[追加] 写入失败: ${filePath}`);
return result;
} else {
// 覆盖模式直接写入
const result = file.WriteTextSync(filePath, content, false);
// log.info(result ? `[覆盖] 成功写入: ${filePath}` : `[覆盖] 写入失败: ${filePath}`);
return result;
}
} catch (error) {
// 出错时尝试创建/写入文件
const result = file.WriteTextSync(filePath, content, false);
log.info(result ? `[新建/恢复] 成功处理: ${filePath}` : `[新建/恢复] 处理失败: ${filePath}`);
return result;
}
}

View File

@@ -0,0 +1,195 @@
// 新增:独立的预加载函数,负责所有资源预处理
async function preloadImageResources(specificNames) {
log.info("开始预加载所有图片资源");
// 统一参数格式(与原逻辑一致)
let preSpecificNames = specificNames;
if (typeof specificNames === 'string') {
preSpecificNames = [specificNames];
}
const isAll = !preSpecificNames || preSpecificNames.length === 0;
if (isAll) {
log.info("未指定具体弹窗名称,将执行所有弹窗目录处理");
} else {
log.info(`指定处理弹窗名称:${preSpecificNames.join(', ')}`);
}
// 定义根目录(与原代码一致)
const rootDir = "assets/imageClick";
// 获取所有子目录(与原代码一致)
const subDirs = readAllFilePaths(rootDir, 0, 0, [], true);
// 筛选目标目录(与原代码一致)
const targetDirs = isAll
? subDirs
: subDirs.filter(subDir => {
const dirName = basename(subDir);
return preSpecificNames.includes(dirName);
});
if (targetDirs.length === 0) {
log.info(`未找到与指定名称匹配的目录,名称列表:${preSpecificNames?.join(', ') || '所有'}`);
return [];
}
// 预加载所有目录的资源原imageClick内的资源加载逻辑
const preloadedResources = [];
for (const subDir of targetDirs) {
const dirName = basename(subDir);
// log.info(`开始预处理弹窗类型:${dirName}`);
// 查找icon和Picture文件夹与原代码一致
const entries = readAllFilePaths(subDir, 0, 1, [], true);
const iconDir = entries.find(entry => entry.endsWith('\icon'));
const pictureDir = entries.find(entry => entry.endsWith('\Picture'));
if (!iconDir) {
log.warn(`未找到 icon 文件夹,跳过分类文件夹:${subDir}`);
continue;
}
if (!pictureDir) {
log.warn(`未找到 Picture 文件夹,跳过分类文件夹:${subDir}`);
continue;
}
// 读取图片文件(与原代码一致)
const iconFilePaths = readAllFilePaths(iconDir, 0, 0, ['.png', '.jpg', '.jpeg']);
const pictureFilePaths = readAllFilePaths(pictureDir, 0, 0, ['.png', '.jpg', '.jpeg']);
// 预创建icon识别对象与原代码一致
const iconRecognitionObjects = [];
for (const filePath of iconFilePaths) {
const mat = file.readImageMatSync(filePath);
if (mat.empty()) {
log.error(`加载图标失败:${filePath}`);
continue;
}
const recognitionObject = RecognitionObject.TemplateMatch(mat, 0, 0, 1920, 1080);
iconRecognitionObjects.push({ name: basename(filePath), ro: recognitionObject, iconDir });
}
// 预创建图库区域(与原代码一致)
const pictureRegions = [];
for (const filePath of pictureFilePaths) {
const mat = file.readImageMatSync(filePath);
if (mat.empty()) {
log.error(`加载图库图片失败:${filePath}`);
continue;
}
pictureRegions.push({ name: basename(filePath), region: new ImageRegion(mat, 0, 0) });
}
// 预计算匹配区域(与原代码一致)
const foundRegions = [];
for (const picture of pictureRegions) {
for (const icon of iconRecognitionObjects) {
const foundRegion = picture.region.find(icon.ro);
if (foundRegion.isExist()) {
foundRegions.push({
pictureName: picture.name,
iconName: icon.name,
region: foundRegion,
iconDir: icon.iconDir
});
}
}
}
// 保存预处理结果
preloadedResources.push({
dirName,
foundRegions
});
}
log.info(`预加载完成,共处理 ${preloadedResources.length} 个目录`);
return preloadedResources;
}
// 新增imageClick后台任务函数
async function imageClickBackgroundTask() {
log.info("imageClick后台任务已启动");
const imageClickDelay = Math.min(99, Math.max(1, Math.floor(Number(settings.PopupClickDelay) || 5)))*1000;
// 可以根据需要配置要处理的弹窗名称
const specificNamesStr = settings.PopupNames || "";
const specificNames = specificNamesStr
.split(/[,,、 \s]+/)
.map(name => name.trim())
.filter(name => name !== "");
// 调用独立预加载函数(循环前仅执行一次)
const preloadedResources = await preloadImageResources(specificNames);
if (preloadedResources.length === 0) {
log.info("无可用预加载资源,任务结束");
return { success: false };
}
// 循环执行,仅使用预加载资源
while (!state.completed && !state.cancelRequested) {
try {
// 调用imageClick时传入预加载资源
await imageClick(preloadedResources, null, specificNames, true);
} catch (error) {
log.info(`弹窗识别失败(继续重试):${error.message}`);
}
// 短暂等待后再次执行
await sleep(imageClickDelay);
}
log.info("imageClick后台任务已结束");
return { success: true };
}
// 优化:使用预加载资源,保留所有原执行逻辑
async function imageClick(preloadedResources, ra = null, specificNames = null, useNewScreenshot = false) {
// 保留原参数格式处理(兼容历史调用)
if (typeof specificNames === 'string') {
specificNames = [specificNames];
}
const isAll = !specificNames || specificNames.length === 0;
// 遍历预处理好的资源原targetDirs循环逻辑
for (const resource of preloadedResources) {
const { dirName, foundRegions } = resource;
for (const foundRegion of foundRegions) {
// 保留原识别对象创建逻辑(使用预处理的路径)
const tolerance = 1;
const iconMat = file.readImageMatSync(`${foundRegion.iconDir}/${foundRegion.iconName}`);
const recognitionObject = RecognitionObject.TemplateMatch(
iconMat,
foundRegion.region.x - tolerance,
foundRegion.region.y - tolerance,
foundRegion.region.width + 2 * tolerance,
foundRegion.region.height + 2 * tolerance
);
recognitionObject.threshold = 0.85;
// 保留原识别逻辑
const result = await recognizeImage(
recognitionObject,
ra,
1000, // timeout
500, // interval
useNewScreenshot,
dirName // iconType
);
// 保留原点击逻辑
if (result.isDetected && result.x !== 0 && result.y !== 0) {
const x = Math.round(result.x + result.width / 2);
const y = Math.round(result.y + result.height / 2);
log.info(`即将点击【${dirName}】类型下的图标:${foundRegion.iconName},位置: (${x}, ${y})`);
await click(x, y);
log.info(`点击【${dirName}】类型下的${foundRegion.iconName}成功`);
await sleep(10);
return { success: true };
} else {
// log.info(`未发现弹窗【${dirName}】的图标:${foundRegion.iconName}`);
}
}
}
// 所有目标处理完毕仍未成功(保留原返回逻辑)
return { success: false };
}

View File

@@ -0,0 +1,151 @@
// ###########################################################################
// 【核心工具函数】
// ###########################################################################
var globalLatestRa = null;
async function recognizeImage(
recognitionObject,
ra,
timeout = 1000,
interval = 500,
useNewScreenshot = false,
iconType = null
) {
let startTime = Date.now();
globalLatestRa = ra;
const originalRa = ra;
let tempRa = null; // 用于管理临时创建的资源
try {
while (Date.now() - startTime < timeout) {
let currentRa;
if (useNewScreenshot) {
// 释放之前的临时资源
if (tempRa) {
tempRa.dispose();
}
tempRa = captureGameRegion();
currentRa = tempRa;
globalLatestRa = currentRa;
} else {
// 不使用新截图时直接使用原始ra不重复释放
currentRa = originalRa;
}
if (currentRa) {
try {
const result = currentRa.find(recognitionObject);
if (result.isExist() && result.x !== 0 && result.y !== 0) {
return {
isDetected: true,
iconType: iconType,
x: result.x,
y: result.y,
width: result.width,
height: result.height,
ra: globalLatestRa,
usedNewScreenshot: useNewScreenshot
};
}
} catch (error) {
log.error(`${iconType || '未知'}识别异常】: ${error.message}`);
}
}
await sleep(interval);
}
} finally {
// 释放临时资源但保留全局引用的资源
if (tempRa && tempRa !== globalLatestRa) {
tempRa.dispose();
}
}
return {
isDetected: false,
iconType: iconType,
x: null,
y: null,
width: null,
height: null,
ra: globalLatestRa,
usedNewScreenshot: useNewScreenshot
};
}
// 定义一个异步函数来绘制红框并延时清除
async function drawAndClearRedBox(searchRegion, ra, delay = 500) {
let drawRegion = null;
try {
// 创建绘制区域
drawRegion = ra.DeriveCrop(
searchRegion.x, searchRegion.y,
searchRegion.width, searchRegion.height
);
drawRegion.DrawSelf("icon"); // 绘制红框
// 等待指定时间
await sleep(delay);
// 清除红框 - 使用更可靠的方式
if (drawRegion && typeof drawRegion.DrawSelf === 'function') {
// 可能需要使用透明绘制来清除或者绘制一个0大小的区域
ra.DeriveCrop(0, 0, 0, 0).DrawSelf("icon");
}
} catch (e) {
log.error("红框绘制异常:" + e.message);
} finally {
// 正确释放资源如果dispose方法存在的话
if (drawRegion && typeof drawRegion.dispose === 'function') {
drawRegion.dispose();
}
}
}
// 截图保存函数
function imageSaver(mat, saveFile) {
// 获取当前时间并格式化为 "YYYY-MM-DD_HH-MM-SS"
const now = new Date();
const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}-${String(now.getMinutes()).padStart(2, '0')}-${String(now.getSeconds()).padStart(2, '0')}`;
// 获取当前脚本所在的目录
const scriptDir = getScriptDirPath();
if (!scriptDir) {
log.error("无法获取脚本目录");
return;
}
// 构建完整的目标目录路径和文件名
const savePath = `${scriptDir}/${saveFile}/screenshot_${timestamp}.png`;
const tempFilePath = `${scriptDir}/${saveFile}`;
// 检查临时文件是否存在,如果不存在则创建目录
try {
// 尝试读取临时文件
file.readPathSync(tempFilePath);
log.info("目录存在,继续执行保存图像操作");
} catch (error) {
log.error(`确保目录存在时出错: ${error}`);
return;
}
// 保存图像
try {
mat.saveImage(savePath);
// log.info(`图像已成功保存到: ${savePath}`);
} catch (error) {
log.error(`保存图像失败: ${error}`);
}
}
// 获取脚本目录
function getScriptDirPath() {
try {
file.readTextSync(`temp-${Math.random()}.txt`);
} catch (e) {
const match = e.toString().match(/'([^']+)'/);
return match ? match[1].replace(/\\[^\\]+$/, "") : null;
}
return null;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
{
"manifest_version": 1,
"name": "背包统计采集系统",
"version": "2.42",
"name": "背包统计采集",
"version": "2.5",
"bgi_version": "0.44.8",
"description": "模板匹配材料OCR识别数量\n支持背包材料的数量统计+路径CD管理自动优选路径\n具体支持看材料CD文件可自行增减材料CD。",
"saved_files": [
"history_record/*.txt",
"overwrite_record/*.txt",
"pathing/",
"history_record/",
"overwrite_record/",
"latest_record.txt",
"pathing_record/*.txt",
"pathing_record/noRecord/*.txt"
"pathing_record/"
],
"authors": [
{

View File

@@ -0,0 +1,2 @@
4点丘丘人丘丘萨满丘丘人射手丘丘暴徒丘丘王丘丘游侠愚人众先遣队萤术士债务处理人冬国仕女愚人众风役人愚人众特辖队盗宝团野伏众海乱鬼镀金旅团黑蛇众黯色空壳部族龙形武士遗迹机械元能构装体遗迹机兵遗迹龙兽发条机关秘源机兵巡陆艇史莱姆骗骗花飘浮灵蕈兽浊水幻灵原海异种隙境原体魔像禁卫大灵显化身熔岩游像龙蜥圣骸兽玄文兽纳塔龙众炉壳山鼬蕴光异兽深渊法师深渊使徒兽境群狼深邃拟覆叶荒野狂猎霜夜灵嗣地脉花

View File

@@ -0,0 +1,3 @@
12小时12h狗粮
24小时24h狗粮

View File

@@ -1,20 +1,20 @@
12小时晶蝶野猪松鼠狐狸鸭子路边闪光点兽肉禽肉神秘的肉鱼肉鳗肉螃蟹青蛙发光髓蜥蜴尾巴晶核鳅鳅宝玉吉光虫燃素蜜虫固晶甲虫月萤虫
12小时12h狗粮晶蝶,野猪,松鼠,狐狸,鼬,鸟,鱼,鸭子,路边闪光点,兽肉,禽肉,神秘的肉,鱼肉,鳗肉,螃蟹,青蛙,发光髓,蜥蜴尾巴,晶核,鳅鳅宝玉,吉光虫,燃素蜜虫,固晶甲虫,月萤虫,
24小时沉玉仙茗狗粮,
24小时24h狗粮沉玉仙茗,
46小时小灯草嘟嘟莲落落莓塞西莉亚花慕风蘑菇蒲公英籽钩钩果风车菊霓裳花清心琉璃袋琉璃百合夜泊石绝云椒椒星螺石珀清水玉海灵芝鬼兜虫绯樱绣球鸣草珊瑚真珠晶化骨髓血斛天云草实幽灯蕈沙脂蛹月莲帕蒂沙兰树王圣体菇圣金虫万相石悼灵花劫波莲赤念果苍晶螺海露花柔灯铃子探测单元湖光铃兰幽光星星虹彩蔷薇初露之源浪沫羽鳃灼灼彩菊肉龙掌青蜜莓枯叶紫英微光角菌云岩裂叶琉鳞石奇异的「牙齿」冰雾花花朵烈焰花花蕊便携轴承霜盏花月落银
46小时小灯草嘟嘟莲落落莓塞西莉亚花慕风蘑菇蒲公英籽钩钩果风车菊霓裳花清心琉璃袋琉璃百合夜泊石绝云椒椒星螺石珀清水玉海灵芝鬼兜虫绯樱绣球鸣草珊瑚真珠晶化骨髓血斛天云草实幽灯蕈沙脂蛹月莲帕蒂沙兰树王圣体菇圣金虫万相石悼灵花劫波莲赤念果苍晶螺海露花柔灯铃子探测单元湖光铃兰幽光星星虹彩蔷薇初露之源浪沫羽鳃灼灼彩菊肉龙掌青蜜莓枯叶紫英微光角菌云岩裂叶琉鳞石奇异的「牙齿」冰雾花花朵烈焰花花蕊便携轴承霜盏花月落银
72小时
1次0点铁块甜甜花胡萝卜蘑菇松茸松果金鱼草莲蓬薄荷鸟蛋马尾,树莓,白萝卜,苹果,日落果,竹笋,海草,堇瓜,星蕈,墩墩桃,须弥蔷薇,香辛果,枣椰,泡泡桔,汐藻,茉洁草,久雨莲,颗粒果,烛伞蘑菇,澄晶实,红果果菇,苦种,烬芯花,夏槲果,白灵果,寒涌石,宿影花,
1次0点铁块甜甜花胡萝卜蘑菇松茸松果金鱼草莲蓬薄荷鸟蛋树莓白萝卜苹果马尾,日落果,竹笋,海草,堇瓜,星蕈,墩墩桃,须弥蔷薇,香辛果,枣椰,泡泡桔,汐藻,茉洁草,久雨莲,颗粒果,烛伞蘑菇,澄晶实,红果果菇,苦种,烬芯花,夏槲果,白灵果,寒涌石,宿影花,
2次0点白铁块星银矿石
2次0点白铁块星银矿石电气水晶,
3次0点水晶块紫晶块萃凝晶魔晶块钓鱼点虹滴晶
4点胡椒洋葱牛奶番茄卷心菜土豆小麦稻米虾仁豆腐杏仁发酵果实汁咖啡豆秃秃豆面粉奶油熏禽肉黄油火腿香辛料蟹黄果酱奶酪培根香肠「冷鲜肉」黑麦黑麦粉酸奶油
4点胡椒洋葱牛奶番茄卷心菜土豆小麦稻米虾仁豆腐杏仁发酵果实汁咖啡豆秃秃豆面粉奶油熏禽肉黄油火腿香辛料蟹黄果酱奶酪培根香肠「冷鲜肉」黑麦黑麦粉酸奶油冷鲜肉,
6点
即时刷新:蝴蝶,萤火虫,蝴蝶的翅膀,发光髓
即时刷新:蝴蝶,萤火虫,蝴蝶的翅膀,

View File

@@ -2,7 +2,7 @@
{
"name": "TargetCount",
"type": "input-text",
"label": "js目录下默认扫描的文件结构\n./📁BetterGI/📁User/📁JsScript/\n📁背包材料统计/\n 📁pathing/\n 📁 薄荷/\n 📄 薄荷1.json\n 📁 薄荷效率/\n 📄 薄荷-吉吉喵.json\n 📁 名刀镡/\n 📄 旅行者的名刀镡.json\n----------------------------------\n目标数量默认5000\n给📁pathing下材料设定的目标数"
"label": "js目录下默认扫描的文件结构\n./📁BetterGI/📁User/📁JsScript/\n📁背包材料统计/\n 📁pathing/\n 📁 薄荷/\n 📄 薄荷1.json\n 📁 薄荷效率/\n 📄 薄荷-吉吉喵.json\n 📁 苹果/\n 📄 旅行者的果园.json\n----------------------------------\n目标数量默认5000\n给📁pathing下材料设定的目标数"
},
{
"name": "TargetresourceName",
@@ -22,12 +22,12 @@
{
"name": "noRecord",
"type": "checkbox",
"label": "----------------------------------\n取消扫描数量。默认否\n勾选将不进行单路径的扫描但保留时间记录\n(推荐路径记录炼成后启用)"
"label": "----------------------------------\n取消扫描数量。默认否\n勾选将不进行单路径的扫描但保留时间记录\n(推荐路径记录炼成后启用)"
},
{
"name": "Pathing",
"type": "select",
"label": "====================\n扫描📁pathing下的\n或勾选【材料分类】的材料。默认兼并",
"label": "====================\n扫描📁pathing下的\n或勾选【材料分类】的材料。默认仅📁pathing材料",
"options": [
"1.兼并:📁pathing材料+【材料分类】",
"2.仅📁pathing材料",
@@ -37,7 +37,7 @@
{
"name": "Smithing",
"type": "checkbox",
"label": "刷怪、采集推荐:2.仅📁pathing材料\n----------------------------------\n【锻造素材】"
"label": "\n----------------------------------\n【锻造素材】"
},
{
"name": "Drops",
@@ -72,7 +72,7 @@
{
"name": "CharAscension",
"type": "checkbox",
"label": "----------------------------------\n\n【角色突破素材】"
"label": "----------------------------------\n\n【角色培养素材】世界BOSS树脂材料"
},
{
"name": "Fishing",
@@ -87,7 +87,7 @@
{
"name": "Talent",
"type": "checkbox",
"label": "----------------------------------\n\n【角色天赋素材】"
"label": "----------------------------------\n\n【角色天赋素材】天赋书"
},
{
"name": "WeaponAscension",
@@ -95,9 +95,18 @@
"label": "----------------------------------\n\n【武器突破素材】"
},
{
"name": "ImageDelay",
"name": "PopupNames",
"type": "input-text",
"label": "数字太小可能无法识别,用?代替\n====================\n识图延迟时间(默认:10 毫秒)"
"label": "数字太小可能无法识别,用?代替\n====================\n弹窗名(默认:全部)"
},
{
"name": "PopupClickDelay",
"type": "input-text",
"label": "如 过期物品,信件,自定义文件夹名。注意分隔符和文件夹格式\n----------------------------------\n弹窗循环间隔默认:5 秒)"
},
{
"name": "CDCategories",
"type": "input-text",
"label": "----------------------------------\n\n采用的CD分类默认:全部)"
}
]
]

View File

@@ -0,0 +1,3 @@
狗粮:冒险家头带,冒险家金杯,冒险家怀表,冒险家尾羽,冒险家之花,幸运儿银冠,幸运儿之杯,幸运儿沙漏,幸运儿鹰羽,幸运儿绿花,游医的方巾,游医的药壶,游医的怀钟,游医的枭羽,游医的银莲,感别之冠,异国之盏,逐光之石,归乡之羽,故人之心,奇迹耳坠,奇迹之杯,奇迹之沙,奇迹之羽,奇迹之花,战狂的鬼面,战狂的骨杯,战狂的时计,战狂的翎羽,战狂的蔷薇,教官的帽子,教官的茶杯,教官的怀表,教官的羽饰,教官的胸花,流放者头冠,流放者之杯,流放者怀表,流放者之羽,流放者之花,守护束带,守护之皿,守护座钟,守护徽印,守护之花,勇士的勋章,勇士的期许,勇士的坚毅,勇士的壮行,勇士的冠冕,武人的红花,武人的羽饰,武人的水漏,武人的酒杯,武人的头巾,赌徒的胸花,赌徒的羽饰,赌徒的怀表,赌徒的骰蛊,赌徒的耳环,学士的书签,学士的羽笔,学士的时钟,学士的墨杯,学士的镜片,祭雷礼冠,祭火礼冠,祭水礼冠,祭冰礼冠,
交互:触摸,调查,拾取,

View File

@@ -0,0 +1,4 @@
武器:无锋剑,银剑,旅行剑,吃虎鱼刀,训练大剑,佣兵重剑,飞天大御剑,白铁大剑,新手长枪,铁尖枪,钺矛,白缨枪,猎弓,历练的猎弓,信使,反曲弓,学徒笔记,口袋魔导书,甲级宝珏,异世界行记,
养成:精锻用,的教导,的经验

View File

@@ -0,0 +1,2 @@
掉落:偏光棱镜,地脉的旧枝,大英雄的经验,特工祭刀,冒险家的经验,水晶棱镜,混沌炉心,猎兵祭刀,流浪者的经验,混沌回路,石化的骨片,黯淡棱镜,混沌装置,结实的骨片,隐兽鬼爪,黑晶号角,脆弱的骨片,隐兽利爪,雾虚灯芯,黑铜号角,沉重号角,混沌真眼,隐兽指爪,雾虚草囊,地脉的新芽,幽邃刻像,混沌枢纽,雾虚花粉,地脉的枯叶,夤夜刻像,混沌机关,督察长祭刀,初生的浊水幻灵,晦暗刻像,混浊棱晶,老旧的役人怀表,浊水的一掬,渊光鳍翅,破缺棱晶,茁壮菌核,休眠菌核,月色鳍翅,浊水的一滴,锲纹的横脊,失活菌核,密固的横脊,异界生命核,羽状鳍翅,外世突触,未熄的剑柄,残毁的横脊,混沌锚栓,混沌模块,漫游者的盛放之花,裂断的剑柄,隙间之核,何人所珍藏之花,役人的时时刻刻,残毁的剑柄,混沌容器,役人的制式怀表,意志巡游的符像,来自何处的待放之花,辉光棱晶,史莱姆凝液,意志明晰的寄偶,繁光躯外骸,迷光的蜷叶之心,不祥的面具,惑光的阔叶,意志破碎的残片,稀光遗骼,失光块骨,折光的胚芽,污秽的面具,聚燃的游像眼,幽雾兜盔,破损的面具,聚燃的命种,明燃的棱状壳,蕴热的背壳,冷裂壳块,幽雾片甲,禁咒绘卷,聚燃的石块,封魔绘卷,幽雾化形,秘源真芯,霜夜的煌荣,史莱姆原浆,导能绘卷,秘源机鞘,霜夜的柔辉,历战的箭簇,史莱姆清,秘源轴,霜夜的残照,原素花蜜,异海之块,浮游干核,锐利的箭簇,孢囊晶尘,异海凝珠,微光花蜜,牢固的箭簇,奇械机芯齿轮,尉官的徽记,荧光孢粉,骗骗花蜜,名刀镡,士官的徽记,机关正齿轮,蕈兽孢子,啮合齿轮,影打刀镡,新兵的徽记,织金红绸,攫金鸦印,横行霸者的利齿,破旧的刀镡,镶边红绸,浮游晶化核,老练的坚齿,藏银鸦印,褪色红绸,寻宝鸦印,异色结晶石,浮游幽核,稚嫩的尖齿,磨损的执凭,龙冠武士的金哨,战士的铁哨,卫从的木哨,精制机轴,加固机轴,毁损机轴,霜镌的执凭,精致的执凭

View File

@@ -0,0 +1,10 @@
小动物:蜜虫,甲虫,陆鳗,海鳗,沙鳗,青蛙,泥蛙,蓝蛙,树蛙,金虫,角蜥,髓蜥,尾蜥,蝴蝶,鳅鳅,晶蝶,金蟹,太阳蟹,海蓝蟹,将军蟹,薄红蟹,光虫,子探测,黑背鲈,蓝鳍鲈,黄金鲈,火虫,月萤虫,
素材:塞西莉亚花,霓裳花,夜泊石,落落莓,绝云椒椒,钩钩果,蒲公英籽,嘟嘟莲,小灯草,慕风蘑菇,风车菊,初露之源,树王圣体菇,绯樱绣球,万相石,幽灯蕈,鬼兜虫,天云草实,悼灵花,清水玉,湖光铃兰,幽光星星,沙脂蛹,珊瑚真珠,石珀,海灵芝,琉璃袋,虹彩蔷薇,赤念果,劫波莲,星螺,柔灯铃,鸣草,月莲,海露花,清心,血斛,帕蒂沙兰,晶化骨髓,琉璃百合,苍晶螺,琉鳞石,蜥蜴尾巴,云岩裂叶,枯叶紫英,肉龙掌,烈焰花,冰雾花,青蜜莓,灼灼彩菊,浪沫羽鳃,奇异的「牙齿」,便携轴承,霜盏花,月落银,芯花,微光角菌,苦种,久雨莲,毗波耶,马尾,寒涌石,宿影花,电气水晶
食材:苹果,日落果,星蕈,泡泡桔,烛伞蘑菇,胡萝卜,白萝卜,金鱼草,薄荷,小麦,卷心菜,松果,蘑菇,甜甜花,莲蓬,树莓,海草,稻米,兽肉,堇瓜,冷鲜肉,番茄,香辛果,鸟蛋,土豆,墩墩桃,松茸,禽肉,须弥蔷薇,鱼肉,枣椰,神秘的肉,茉洁草,汐藻,洋葱,竹笋,沉玉仙茗,颗粒果,澄晶实,红果,黑麦,黑麦粉,酸奶油,夏槲果,白灵果,
矿石:萃凝晶,紫晶块,星银矿石,魔晶块,水晶块,白铁块,铁块,虹滴晶,
狗粮:冒险家头带,冒险家金杯,冒险家怀表,冒险家尾羽,冒险家之花,幸运儿银冠,幸运儿之杯,幸运儿沙漏,幸运儿鹰羽,幸运儿绿花,游医的方巾,游医的药壶,游医的怀钟,游医的枭羽,游医的银莲,感别之冠,异国之盏,逐光之石,归乡之羽,故人之心,奇迹耳坠,奇迹之杯,奇迹之沙,奇迹之羽,奇迹之花,战狂的鬼面,战狂的骨杯,战狂的时计,战狂的翎羽,战狂的蔷薇,教官的帽子,教官的茶杯,教官的怀表,教官的羽饰,教官的胸花,流放者头冠,流放者之杯,流放者怀表,流放者之羽,流放者之花,守护束带,守护之皿,守护座钟,守护徽印,守护之花,勇士的勋章,勇士的期许,勇士的坚毅,勇士的壮行,勇士的冠冕,武人的红花,武人的羽饰,武人的水漏,武人的酒杯,武人的头巾,赌徒的胸花,赌徒的羽饰,赌徒的怀表,赌徒的骰蛊,赌徒的耳环,学士的书签,学士的羽笔,学士的时钟,学士的墨杯,学士的镜片,祭雷礼冠,祭火礼冠,祭水礼冠,祭冰礼冠,