diff --git a/repo/js/AutoHoeingOneDragon/README.md b/repo/js/AutoHoeingOneDragon/README.md index 53e2fd2a..7a661ef0 100644 --- a/repo/js/AutoHoeingOneDragon/README.md +++ b/repo/js/AutoHoeingOneDragon/README.md @@ -50,6 +50,7 @@ - - 小怪 :表明路线只含小怪,战斗强度低,且无需携带万叶来拾取可能掉落的狗粮,可以适当携带等级较低或不上场的角色来获取经验收益 - - 分组逻辑:不含路径组1排除标签和任何其他组标签的路径会进入路径组1,剩余路径若含有路径组x的标签之一,则会进入路径组x - - 使用示例:路径组一填写蕈兽,禁用蕈兽路线,路径组二填写次数盾,水免,处理路径组一的配队难以处理的次数盾和水免怪物,路径组三填写小怪,队伍中放升级中角色获取经验 + - **输入不运行的时间或时间段的小时数** 当你需要让js在特定的时间终止运行时,按描述填写,js会在距离目标时间小于五分钟时终止运行并等待到目标时间 - **路线效率计算权重:** 影响js评估路线价值,计算公式如下,权重越大越看重总收益 - $$ 怪均^k \times 秒均 $$ - **目标数量:** 选取路线目标达到的精英怪数量,默认为400,同理小怪数量默认为2000 @@ -71,5 +72,9 @@ --- ### 更新日志 -### 1.0(2025.07.07) +### 1.1.0(2025.07.08) +1. 优化日志输出 +2. 新增估算剩余时间,并适配abgi +3. 新增终止运行时间的配置 +### 1.0.0(2025.07.07) 1. 初始版本发布,支持自动锄地规划和黑白名单模式拾取。 diff --git a/repo/js/AutoHoeingOneDragon/main.js b/repo/js/AutoHoeingOneDragon/main.js index 16859da7..b6857b0a 100644 --- a/repo/js/AutoHoeingOneDragon/main.js +++ b/repo/js/AutoHoeingOneDragon/main.js @@ -40,29 +40,24 @@ const timeMoveDown = 1200; } //预处理路线并建立对象 - log.info("开始预处理地图追踪文件"); pathings = await processPathings(); - - log.info("开始合并index数据"); //优先使用index中的数据 await updatePathings("assets/index1.json"); await updatePathings("assets/index2.json"); //加载路线cd信息 - await initializeCdTime(pathings, accountName) + await initializeCdTime(pathings, accountName); //按照用户配置标记可用路线 - log.info("开始按照用户配置标记可用路线"); await markPathings(pathings, group1Tags, group2Tags, group3Tags, group4Tags, excludeTags); //找出最优组合 - log.info("开始寻找最优组合"); await findBestRouteGroups(pathings, k, targetEliteNum, targetMonsterNum); //分配到不同路径组 - log.info("开始分配到不同路径组"); groupCounts = await assignGroups(pathings, group1Tags, group2Tags, group3Tags, group4Tags); /* + //分配结果输出 pathings.forEach((pathing, index) => { log.info(`路径 ${index + 1}:`); log.info(` fullPath: ${pathing.fullPath}`); @@ -90,7 +85,7 @@ const timeMoveDown = 1200; log.info("开始运行锄地路线"); await processPathingsByGroup(pathings, targetTexts, blacklistKeywords, accountName); } else { - log.info("开始强制刷新CD"); + log.info("强制刷新所有路线CD"); await initializeCdTime(pathings, ""); await updateCdTimeRecord(pathings, accountName); } @@ -319,7 +314,7 @@ async function findBestRouteGroups(pathings, k, targetEliteNum, targetMonsterNum const minutes = Math.floor((totalTimeCombined % 3600) / 60); const seconds = totalTimeCombined % 60; - log.info(`总用时: ${hours} 时 ${minutes} 分 ${seconds.toFixed(0)} 秒`); + log.info(`预计总用时: ${hours} 时 ${minutes} 分 ${seconds.toFixed(0)} 秒`); } async function assignGroups(pathings, group1Tags, group2Tags, group3Tags, group4Tags) { @@ -376,6 +371,8 @@ async function runPathWithOcr(pathFilePath, targetTexts, blacklistKeywords) { }; let thisMoveUpTime = 0; let lastMoveUp = 0; + + let lastPickupTime = new Date(); // 定义状态变量 let state = { completed: false, cancelRequested: false }; // 定义图像路径和目标文本列表 @@ -391,7 +388,6 @@ async function runPathWithOcr(pathFilePath, targetTexts, blacklistKeywords) { log.error(`执行路径文件时发生错误:${error.message}`); state.cancelRequested = true; // 修改状态变量 } - log.info(`路径文件 ${filePath} 执行完成`); state.completed = true; // 修改状态变量 } @@ -527,31 +523,14 @@ async function runPathWithOcr(pathFilePath, targetTexts, blacklistKeywords) { // 检查是否包含黑名单关键词 let containsBlacklistKeyword = blacklistKeywords.some(blacklistKeyword => ocrResult.text.includes(blacklistKeyword)); if (containsBlacklistKeyword) { - log.debug(`包含黑名单,不拾取: ${ocrResult.text}`); continue; } - - log.info(`交互或拾取: "${ocrResult.text}"`); - // 获取当前时间并格式化为 HH:mm:ss.sss 格式 - function formatTime(date) { - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - const milliseconds = String(date.getMilliseconds()).padStart(3, '0'); - return `${hours}:${minutes}:${seconds}.${milliseconds}`; + if ((new Date() - lastPickupTime) > 200) { + log.info(`交互或拾取:"${ocrResult.text}"`); + lastPickupTime = new Date(); } - - // 获取当前时间 - const now = new Date(); - - // 格式化当前时间 - const formattedTime = formatTime(now); - - // 输出日志 - log.debug(`[${formattedTime}][INF] BetterGenshinImpact.GameTask.AutoPick.AutoPickTrigger\n交互或拾取:"${ocrResult.text}"`); - - + // 计算目标文本的中心Y坐标 let centerYTargetText = ocrResult.y + ocrResult.height / 2; if (Math.abs(centerYTargetText - centerYF) <= texttolerance) { @@ -612,8 +591,6 @@ async function runPathWithOcr(pathFilePath, targetTexts, blacklistKeywords) { // 定义 readFolder 函数 async function readFolder(folderPath, onlyJson) { - log.info(`开始读取文件夹:${folderPath}`); - // 新增一个堆栈,初始时包含 folderPath const folderStack = [folderPath]; @@ -735,6 +712,10 @@ async function processPathingsByGroup(pathings, targetTexts, blacklistKeywords, const seconds = totalEstimatedTime % 60; log.info(`预计用时: ${hours} 时 ${minutes} 分 ${seconds.toFixed(0)} 秒`); + const groupStartTime = new Date(); + let remainingEstimatedTime = totalEstimatedTime; + let skippedTime = 0; + // 遍历 pathings 数组 for (const pathing of pathings) { // 检查路径是否属于指定的组 @@ -743,7 +724,7 @@ async function processPathingsByGroup(pathings, targetTexts, blacklistKeywords, groupPathCount++; // 输出当前路径的序号信息 - log.info(`当前路径 ${pathing.fileName} 是第 ${targetGroup} 组第 ${groupPathCount}/${totalPathsInGroup} 条`); + log.info(`开始处理第 ${targetGroup} 组第 ${groupPathCount}/${totalPathsInGroup} 个${pathing.fileName}`); // 获取当前时间 const now = new Date(); @@ -752,9 +733,15 @@ async function processPathingsByGroup(pathings, targetTexts, blacklistKeywords, const cdTime = new Date(pathing.cdTime); if (cdTime > now) { log.info(`该路线未刷新,跳过。`); + skippedTime += pathing.t; + remainingEstimatedTime -= pathing.t; continue; } + if (await isTimeRestricted(settings.timeRule)) { + break; + } + // 输出路径已刷新并开始处理的信息 log.info(`该路线已刷新,开始处理。`); await fakeLog(`${pathing.fileName}`, false, true, 0); @@ -781,10 +768,15 @@ async function processPathingsByGroup(pathings, targetTexts, blacklistKeywords, // 更新路径的 cdTime pathing.cdTime = nextEightClock.toLocaleString(); - await updateCdTimeRecord(pathings, accountName) - - // 输出路径的下次可用时间(本地时间格式) - log.info(`路径 ${pathing.fileName} 下次可用时间为 ${nextEightClock.toLocaleString()}`); + await updateCdTimeRecord(pathings, accountName); + remainingEstimatedTime -= pathing.t; + const actualUsedTime = (new Date() - groupStartTime) / 1000; + const predictRemainingTime = remainingEstimatedTime * actualUsedTime / (totalEstimatedTime - remainingEstimatedTime - skippedTime); + // 将预计剩余时间转换为时、分、秒表示 + const remaininghours = Math.floor(predictRemainingTime / 3600); + const remainingminutes = Math.floor((predictRemainingTime % 3600) / 60); + const remainingseconds = predictRemainingTime % 60; + log.info(`当前进度:第 ${targetGroup} 组第 ${groupPathCount}/${totalPathsInGroup} 个 ${pathing.fileName}已完成,该组预计剩余: ${remaininghours} 时 ${remainingminutes} 分 ${remainingseconds.toFixed(0)} 秒`); } } } @@ -837,8 +829,6 @@ async function updateCdTimeRecord(pathings, accountName) { // 将更新后的内容写回文件 await file.writeText(filePath, JSON.stringify(cdTimeData, null, 2), false); - // 输出日志 - log.info(`所有路径的 cdTime 已更新并保存到文件 ${filePath}`); } catch (error) { // 捕获并记录错误 log.error(`更新 cdTime 时出错: ${error.message}`); @@ -990,7 +980,76 @@ async function switchPartyIfNeeded(partyName) { } } - +/** + * 检查当前时间是否处于限制时间内或即将进入限制时间 + * @param {string} timeRule - 时间规则字符串,格式如 "4, 4-6, 10-12" + * @param {number} [threshold=5] - 接近限制时间的阈值(分钟) + * @returns {Promise} - 如果处于限制时间内或即将进入限制时间,则返回 true,否则返回 false + */ +async function isTimeRestricted(timeRule, threshold = 5) { + // 如果输入的时间规则为 undefined 或空字符串,视为不进行时间处理,返回 false + if (timeRule === undefined || timeRule === "") { + return false; + } + + // 初始化 0-23 小时为可用状态 + const hours = Array(24).fill(false); + + // 解析时间规则 + const rules = timeRule.split(',').map(rule => rule.trim()); + + // 校验输入的字符串是否符合规则 + for (const rule of rules) { + if (rule.includes('-')) { + // 处理时间段,如 "4-6" + const [startHour, endHour] = rule.split('-').map(Number); + if (isNaN(startHour) || isNaN(endHour) || startHour < 0 || startHour >= 24 || endHour <= startHour || endHour > 24) { + // 如果时间段格式不正确或超出范围,则报错并返回 true + log.error("时间填写不符合规则,请检查"); + return true; + } + for (let i = startHour; i < endHour; i++) { + hours[i] = true; // 标记为不可用 + } + } else { + // 处理单个时间点,如 "4" + const hour = Number(rule); + if (isNaN(hour) || hour < 0 || hour >= 24) { + // 如果时间点格式不正确或超出范围,则报错并返回 true + log.error("时间填写不符合规则,请检查"); + return true; + } + hours[hour] = true; // 标记为不可用 + } + } + + // 获取当前时间 + const now = new Date(); + const currentHour = now.getHours(); + const currentMinute = now.getMinutes(); + + // 检查当前时间是否处于限制时间内 + if (hours[currentHour]) { + log.warn("处于限制时间内"); + return true; // 当前时间处于限制时间内 + } + + // 检查当前时间是否即将进入限制时间 + for (let i = 0; i < 24; i++) { + if (hours[i]) { + const nextHour = i; + const timeUntilNextHour = (nextHour - currentHour - 1) * 60 + (60 - currentMinute); + if (timeUntilNextHour > 0 && timeUntilNextHour <= threshold) { + // 如果距离下一个限制时间小于等于阈值,则等待到限制时间开始 + log.warn("接近限制时间,开始等待"); + await sleep(timeUntilNextHour * 60 * 1000); + return true; + } + } + } + log.info("不处于限制时间"); + return false; // 当前时间不在限制时间内 +} diff --git a/repo/js/AutoHoeingOneDragon/manifest.json b/repo/js/AutoHoeingOneDragon/manifest.json index c22bfee6..2e2cc6e4 100644 --- a/repo/js/AutoHoeingOneDragon/manifest.json +++ b/repo/js/AutoHoeingOneDragon/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "锄地一条龙", - "version": "1.0.0", + "version": "1.1.0", "description": "一站式解决自动化锄地,支持只拾取狗粮,请阅读README.md后使用", "authors": [ { diff --git a/repo/js/AutoHoeingOneDragon/settings.json b/repo/js/AutoHoeingOneDragon/settings.json index 87294bac..e07465b0 100644 --- a/repo/js/AutoHoeingOneDragon/settings.json +++ b/repo/js/AutoHoeingOneDragon/settings.json @@ -59,6 +59,11 @@ "type": "input-text", "label": "路径组四要【选择】的标签" }, + { + "name": "timeRule", + "type": "input-text", + "label": "输入不运行的时间或时间段的小时数,以本地时间为准\n将提前5分钟结束路线运行,并等待到对应时间\n允许填入【单个数字】如4表示小时数为4时不运行\n或【时间段】如4-6表示小时数为4-6时都不运行\n允许多项,由【中文逗号】分隔" + }, { "name": "efficiencyIndex", "type": "input-text",