diff --git a/repo/js/AutoHoeingOneDragon/main.js b/repo/js/AutoHoeingOneDragon/main.js index 0f1cf8a3e..92cd8734f 100644 --- a/repo/js/AutoHoeingOneDragon/main.js +++ b/repo/js/AutoHoeingOneDragon/main.js @@ -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; } diff --git a/repo/js/AutoHoeingOneDragon/manifest.json b/repo/js/AutoHoeingOneDragon/manifest.json index bc0be6f7f..00a32e9b9 100644 --- a/repo/js/AutoHoeingOneDragon/manifest.json +++ b/repo/js/AutoHoeingOneDragon/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "锄地一条龙", - "version": "1.13.1", + "version": "1.14.0", "description": "一站式解决自动化锄地,支持只拾取狗粮,请仔细阅读README.md后使用", "authors": [ { diff --git a/repo/js/AutoHoeingOneDragon/settings.json b/repo/js/AutoHoeingOneDragon/settings.json index 29f4df5b6..5961f772d 100644 --- a/repo/js/AutoHoeingOneDragon/settings.json +++ b/repo/js/AutoHoeingOneDragon/settings.json @@ -98,7 +98,7 @@ { "name": "tagsForGroup1", "type": "input-text", - "label": "允许使用的标签:\n水免,次数盾,高危,传奇,蕈兽,小怪,沙暴\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签", + "label": "允许使用的标签:\n水免,次数盾,高危,传奇,蕈兽,小怪,沙暴]\n允许使用自定义标签,文件路径或描述包含时将会视为路线含有该标签\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签", "default": "蕈兽,传奇" }, { diff --git a/repo/js/采集cd管理/assets/RecognitionObject/酸奶油1.png b/repo/js/采集cd管理/assets/RecognitionObject/酸奶油1.png index 840fe090b..f16173f56 100644 Binary files a/repo/js/采集cd管理/assets/RecognitionObject/酸奶油1.png and b/repo/js/采集cd管理/assets/RecognitionObject/酸奶油1.png differ diff --git a/repo/js/采集cd管理/main.js b/repo/js/采集cd管理/main.js index 665964d66..908b627bb 100644 --- a/repo/js/采集cd管理/main.js +++ b/repo/js/采集cd管理/main.js @@ -99,6 +99,8 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150) let underWater = false; +let checkInterval = +settings.checkInterval || 50; + (async function () { /* ===== 零基构建 settings.json(BEGIN) ===== */ 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; -} \ No newline at end of file +} + +/** + * 判断当前人物是否已到达指定路线的终点 + * @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; + } +} diff --git a/repo/js/采集cd管理/manifest.json b/repo/js/采集cd管理/manifest.json index fe9750bee..035de5aa0 100644 --- a/repo/js/采集cd管理/manifest.json +++ b/repo/js/采集cd管理/manifest.json @@ -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": [