js:采集cd管理和锄地一条龙优化 (#2566)

* js:锄地一条龙

1.优化坐标检验,新增与地图追踪中坐标比较
2.新增自定义标签处理,允许填自定义标签
3.增加怪物数量警告,配置不合理时将警告去读readme
4.增加分辨率警告,不为1080p时警告
5.修改默认匹配阈值为0.9

* js:采集cd管理

1.修改默认拾取阈值为0.9
2.优化路径组循环判定
3.增加校验逻辑,需要与终点距离30以内才视为完成路线

* Update main.js

1.优化加工速度
2.更正变量名

* Update 酸奶油1.png
This commit is contained in:
mno
2025-12-24 19:19:10 +08:00
committed by GitHub
parent 42cac37cfe
commit 19b89d8a76
6 changed files with 139 additions and 74 deletions

View File

@@ -1,4 +1,4 @@
//当前js版本1.13.1
//当前js版本1.14.0
let timeMoveUp;
let timeMoveDown;
@@ -56,6 +56,12 @@ let state;
const accountName = settings.accountName || "默认账户";
let pathings;
let localeWorks;
const priorityTags = (settings.priorityTags || "").split("").map(tag => tag.trim()).filter(tag => tag.length > 0);
const excludeTags = (settings.excludeTags || "").split("").map(tag => tag.trim()).filter(tag => tag.length > 0);
let runningFailCount = 0;
(async function () {
targetItems = await loadTargetItems();
//自定义配置处理
@@ -102,8 +108,6 @@ let localeWorks;
const groupTags = groupSettings.map(str => str.split('').filter(Boolean));
groupTags[0] = [...new Set(groupTags.flat())];
const priorityTags = (settings.priorityTags || "").split("").map(tag => tag.trim()).filter(tag => tag.length > 0);
const excludeTags = (settings.excludeTags || "").split("").map(tag => tag.trim()).filter(tag => tag.length > 0);
if (pickup_Mode != "模板匹配拾取,拾取狗粮和怪物材料" && pickup_Mode != "模板匹配拾取,只拾取狗粮") {
excludeTags.push("沙暴");
log.warn("拾取模式不是模板匹配,无法处理沙暴路线,自动排除所有沙暴路线");
@@ -129,7 +133,7 @@ let localeWorks;
}
//预处理路线并建立对象
pathings = await processPathings();
pathings = await processPathings(groupTags);
//按照用户配置标记路线
await markPathings(pathings, groupTags, priorityTags, excludeTags);
@@ -177,6 +181,28 @@ let localeWorks;
log.info('当前队伍:' + teamStr);
switch (true) {
case targetEliteNum <= 350 && targetMonsterNum >= 100:
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
await sleep(5000);
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
await sleep(5000);
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
await sleep(5000);
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
await sleep(5000);
break;
case width !== 1920 || height !== 1080:
log.warn("游戏窗口非 1920×1080可能导致图像识别失败如果执意使用可能造成拾取等行为异常后果自负");
await sleep(5000);
log.warn("游戏窗口非 1920×1080可能导致图像识别失败如果执意使用可能造成拾取等行为异常后果自负");
await sleep(5000);
log.warn("游戏窗口非 1920×1080可能导致图像识别失败如果执意使用可能造成拾取等行为异常后果自负");
await sleep(5000);
log.warn("游戏窗口非 1920×1080可能导致图像识别失败如果执意使用可能造成拾取等行为异常后果自负");
await sleep(5000);
break;
case ['钟离', '芙宁娜', '纳西妲', '雷电将军'].every(n => avatars.includes(n)):
log.warn("四神队不适合锄地,建议重新阅读 readme 相关部分");
await sleep(10000);
@@ -210,7 +236,7 @@ let localeWorks;
})();
//预处理路线,建立对象
async function processPathings() {
async function processPathings(groupTags) {
// 读取怪物信息
const monsterInfoContent = await file.readText("assets/monsterInfo.json");
const monsterInfoObject = JSON.parse(monsterInfoContent);
@@ -290,53 +316,22 @@ async function processPathings() {
}
}
const allTags = groupTags[0]; // 已经是 [...new Set(...)] 的结果
// 2. 待匹配文本:路径名 + 描述
const textToMatch = (pathing.fullPath + " " + (description || ""));
// 3. 反查补 tag
allTags.forEach(tag => {
if (textToMatch.includes(tag)) {
pathing.tags.push(tag);
}
});
// 去除重复标签
pathing.tags = [...new Set(pathing.tags)];
// 处理 map_name 属性
pathing.map_name = parsedContent.info?.map_name || "Teyvat"; // 如果有 map_name则使用其值否则默认为 "Teyvat"
}
//优先使用index中的数据
// 更新 pathings 的函数,接受索引文件路径作为参数
async function updatePathings(indexFilePath) {
try {
// 读取文件内容
const fileContent = await file.readText(indexFilePath);
// 将文件内容解析为 JSON 格式
const data = JSON.parse(fileContent);
// 遍历解析后的 JSON 数据
for (const item of data) {
// 检查 pathings 中是否存在某个对象的 fileName 属性与 item.fileName 相同
const existingPathing = pathings.find(pathing => pathing.fileName === item.fileName);
if (existingPathing) {
// 直接覆盖其他字段,但先检查是否存在有效值
if (item.时间 !== undefined) existingPathing.t = item.时间;
if (item.精英摩拉 !== undefined) existingPathing.mora_e = item.精英摩拉;
if (item.小怪摩拉 !== undefined) existingPathing.mora_m = item.小怪摩拉;
if (item.小怪数量 !== undefined) existingPathing.m = item.小怪数量;
if (item.精英数量 !== undefined) existingPathing.e = item.精英数量;
// 使用 Set 来存储 tags避免重复项
const tagsSet = new Set(existingPathing.tags);
for (const key in item) {
if (key !== "fileName" && key !== "时间" && key !== "精英摩拉" && key !== "小怪摩拉" && key !== "小怪数量" && key !== "精英数量") {
if (item[key] === 1) {
tagsSet.add(key);
}
}
}
existingPathing.tags = Array.from(tagsSet);
}
}
} catch (error) {
log.error("Error:", error);
}
}
//await updatePathings("assets/index1.json");
//await updatePathings("assets/index2.json");
for (const pathing of pathings) {
if (!settings.disableSelfOptimization && pathing.records) {
//如果用户没有禁用自动优化,则参考运行记录更改预期用时
@@ -1005,9 +1000,9 @@ async function loadTargetItems() {
let itsThreshold;
if (match) {
const val = parseFloat(match[1]);
itsThreshold = (!isNaN(val) && val >= 0 && val <= 1) ? val : 0.85;
itsThreshold = (!isNaN(val) && val >= 0 && val <= 1) ? val : 0.9;
} else {
itsThreshold = 0.85;
itsThreshold = 0.9;
}
it.roi.Threshold = itsThreshold;
it.roi.InitTemplate();
@@ -1313,7 +1308,6 @@ async function copyPathingsByGroup(pathings) {
async function processPathingsByGroup(pathings, accountName) {
let lastX = 0;
let lastY = 0;
let runningFailCount = 0;
// 定义路径组名称到组号的映射10 个)
const groupMapping = {
@@ -1432,27 +1426,47 @@ async function processPathingsByGroup(pathings, accountName) {
}
await fakeLog(`${pathing.fileName}`, false, false, 0);
let fileEndX = 0, fileEndY = 0;
try {
const raw = file.readTextSync(pathing.fullPath);
const json = JSON.parse(raw);
if (Array.isArray(json.positions)) {
for (let i = json.positions.length - 1; i >= 0; i--) {
const p = json.positions[i];
if (p.type !== 'orientation' &&
typeof p.x === 'number' &&
typeof p.y === 'number') {
fileEndX = p.x;
fileEndY = p.y;
break;
}
}
}
} catch (e) { /* 读文件失败就留 0,0 继续走后面逻辑 */ }
try {
await genshin.returnMainUi();
const miniMapPosition = await genshin.getPositionFromMap(pathing.map_name);
// 比较坐标
const diffX = Math.abs(lastX - miniMapPosition.X);
const diffY = Math.abs(lastY - miniMapPosition.Y);
const endDiffX = Math.abs(fileEndX - miniMapPosition.X);
const endDiffY = Math.abs(fileEndY - miniMapPosition.Y);
lastX = miniMapPosition.X;
lastY = miniMapPosition.Y;
if ((diffX + diffY) < 5) {
if ((diffX + diffY) < 5 || (endDiffX + endDiffY) > 30) {
runningFailCount++;
} else {
runningFailCount = 0;
}
//log.info(`当前位于${pathing.map_name}地图的(${miniMapPosition.X}${miniMapPosition.Y},距离上次距离${(diffX + diffY)}`);
} catch (error) {
log.error(`获取坐标时发生错误:${error.message}`);
runningFailCount++;
}
if (runningFailCount >= 1) {
log.error("出发点与终点过于接近,或坐标获取异常,不记录运行数据");
log.error("出发点与终点过于接近,终点偏差大于30或坐标获取异常,不记录运行数据");
continue;
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "锄地一条龙",
"version": "1.13.1",
"version": "1.14.0",
"description": "一站式解决自动化锄地支持只拾取狗粮请仔细阅读README.md后使用",
"authors": [
{

View File

@@ -98,7 +98,7 @@
{
"name": "tagsForGroup1",
"type": "input-text",
"label": "允许使用的标签:\n水免次数盾高危传奇蕈兽小怪沙暴\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签",
"label": "允许使用的标签:\n水免次数盾高危传奇蕈兽小怪沙暴]\n允许使用自定义标签文件路径或描述包含时将会视为路线含有该标签\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签",
"default": "蕈兽,传奇"
},
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -99,6 +99,8 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150)
let underWater = false;
let checkInterval = +settings.checkInterval || 50;
(async function () {
/* ===== 零基构建 settings.jsonBEGIN ===== */
const SETTINGS_FILE = `settings.json`;
@@ -194,6 +196,12 @@ let underWater = false;
"type": "input-text",
"label": "食材数量\n数量对应上方的食材\n用中文逗号分隔"
},
{
"name": "checkInterval",
"type": "input-text",
"label": "食材加工中的识别间隔(毫秒),设备反应较慢出现识别错误时适当调大",
"default": "50"
},
{
"name": "setTimeMode",
"type": "select",
@@ -832,7 +840,10 @@ let underWater = false;
/* 4-4 计算CD掉落材料决定*/
const timeDiff = new Date() - startTime;
if (timeDiff > 3000) {
let pathRes = isArrivedAtEndPoint(filePath);
// >>> 仅当 >3s 才更新 CD 并立即写回整条记录(含 history <<<
if (timeDiff > 3000 && pathRes) {
/* 1) 如果runPickupLog中不含优先材料则按其他材料查找使用最晚刷新时间 */
let hasPriority = state.runPickupLog.some(name => priorityItemSet.has(name));
let hitMaterials;
@@ -901,15 +912,14 @@ let underWater = false;
await appendDailyPickup(state.runPickupLog);
state.runPickupLog = [];
}
}
}
let runnedAnyPath = true;
let loopattempts = 0;
// ==================== 路径组循环 ====================
while (runnedAnyPath) {
runnedAnyPath = false;
while (loopattempts < 2) {
loopattempt++;
if (await isTimeRestricted(settings.timeRule, 10)) break;
for (let i = 1; i <= groupCount; i++) {
if (await isTimeRestricted(settings.timeRule, 10)) break;
@@ -1117,7 +1127,6 @@ let underWater = false;
state.running = true;
const pickupTask = recognizeAndInteract();
runnedAnyPath = true;
log.info(`当前进度:路径组${i} ${folder} ${fileName} 为第 ${groupFiles.indexOf(filePath) + 1}/${groupFiles.length}`);
log.info(`当前路线分均效率为 ${(filePath._efficiency ?? 0).toFixed(2)}`);
@@ -1146,8 +1155,10 @@ let underWater = false;
state.running = false;
await pickupTask;
let pathRes = isArrivedAtEndPoint(filePath.fullPath);
// >>> 仅当 >3s 才更新 CD 并立即写回整条记录(含 history <<<
if (timeDiff > 3000) {
if (timeDiff > 3000 && pathRes) {
let newTimestamp = new Date(startTime);
switch (currentCdType) {
@@ -1199,7 +1210,7 @@ let underWater = false;
// 清空本次记录
state.runPickupLog = [];
log.info(`本任务执行大于3秒cd信息已更新下一次可用时间为 ${newTimestamp.toLocaleString()}`);
log.info(`本任务cd信息已更新下一次可用时间为 ${newTimestamp.toLocaleString()}`);
}
}
log.info(`路径组${groupNumber} 的所有任务运行完成`);
@@ -1208,6 +1219,7 @@ let underWater = false;
}
}
}
await sleep(1000);
}
} catch (error) {
@@ -1433,9 +1445,9 @@ async function loadTargetItems() {
/* ---------- 1. 解析小括号阈值 ---------- */
const match = it.fullPath.match(/[(](.*?)[)]/);
const itsThreshold = (match => {
if (!match) return 0.85;
if (!match) return 0.9;
const v = parseFloat(match[1]);
return !isNaN(v) && v >= 0 && v <= 1 ? v : 0.85;
return !isNaN(v) && v >= 0 && v <= 1 ? v : 0.9;
})(match);
it.roi.Threshold = itsThreshold;
it.roi.InitTemplate();
@@ -1807,12 +1819,12 @@ async function ingredientProcessing() {
"奶酪", "培根", "香肠"
]);
if (targetFoods.has(Foods[i])) {
if (await clickPNG("全部领取", 10)) {
if (await clickPNG("全部领取", 3)) {
await clickPNG("点击空白区域继续");
await findPNG("食材加工2");
await sleep(100);
}
let res1 = await clickPNG(Foods[i] + "1", 10);
let res1 = await clickPNG(Foods[i] + "1", 5);
if (res1) {
log.info(`${Foods[i]}已找到`);
@@ -1837,7 +1849,7 @@ async function ingredientProcessing() {
click(item.x, item.y);
await sleep(200);
click(item.x, item.y);
if (await findPNG(Foods[i] + "2", 15)) {
if (await findPNG(Foods[i] + "2", 5)) {
log.info(`${Foods[i]}已找到`);
res1 = true;
break;
@@ -1942,7 +1954,7 @@ async function appendDailyPickup(pickupLog) {
}
}
async function clickPNG(png, maxAttempts = 60) {
async function clickPNG(png, maxAttempts = 20) {
//log.info(`调试-点击目标${png},重试次数${maxAttempts}`);
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
pngRo.Threshold = 0.95;
@@ -1950,7 +1962,7 @@ async function clickPNG(png, maxAttempts = 60) {
return await findAndClick(pngRo, true, maxAttempts);
}
async function findPNG(png, maxAttempts = 60) {
async function findPNG(png, maxAttempts = 20) {
//log.info(`调试-识别目标${png},重试次数${maxAttempts}`);
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
pngRo.Threshold = 0.95;
@@ -1963,9 +1975,48 @@ async function findAndClick(target, doClick = true, maxAttempts = 60) {
const rg = captureGameRegion();
try {
const res = rg.find(target);
if (res.isExist()) { await sleep(200); if (doClick) { res.click(); } return true; }
if (res.isExist()) { await sleep(checkInterval * 2 + 50); if (doClick) { res.click(); } return true; }
} finally { rg.dispose(); }
if (i < maxAttempts - 1) await sleep(50);
if (i < maxAttempts - 1) await sleep(checkInterval);
}
return false;
}
}
/**
* 判断当前人物是否已到达指定路线的终点
* @param {string} fullPath 路线文件完整路径(.json
* @returns {boolean} true = 已到达false = 未到达/读文件失败/取坐标失败
*/
function isArrivedAtEndPoint(fullPath) {
try {
/* 1. 读路线文件,取终点坐标 */
const raw = file.readTextSync(fullPath);
const json = JSON.parse(raw);
if (!Array.isArray(json.positions)) return false;
let endX = 0, endY = 0;
for (let i = json.positions.length - 1; i >= 0; i--) {
const p = json.positions[i];
if (p.type !== 'orientation' &&
typeof p.x === 'number' &&
typeof p.y === 'number') {
endX = p.x;
endY = p.y;
break;
}
}
if (endX === 0 && endY === 0) return false; // 没找到有效点
/* 2. 取当前人物坐标 */
const mapName = (json.info?.map_name && json.info.map_name.trim()) ? json.info.map_name : 'Teyvat';
const pos = genshin.getPositionFromMap(mapName); // 同步 API
const curX = pos.X;
const curY = pos.Y;
/* 3. 曼哈顿距离 ≤30 视为到达 */
return Math.abs(endX - curX) + Math.abs(endY - curY) <= 30;
} catch (e) {
/* 任何异常读盘失败、解析失败、API 异常)都算“未到达” */
return false;
}
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "采集cd管理",
"version": "2.7.0",
"version": "2.8.0",
"bgi_version": "0.44.8",
"description": "仅面对会操作文件和读readme的用户基于文件夹操作自动管理采集路线的cd会按照路径组的顺序依次运行直到指定的时间并会按照给定的cd类型自动跳过未刷新的路线",
"saved_files": [