diff --git a/repo/js/AutoLeyLineOutcrop/README.md b/repo/js/AutoLeyLineOutcrop/README.md index 6ea55b6a..7bbdb914 100644 --- a/repo/js/AutoLeyLineOutcrop/README.md +++ b/repo/js/AutoLeyLineOutcrop/README.md @@ -7,7 +7,7 @@ 右键脚本选择修改js脚本自定义配置,根据你的需要对脚本进行配置。 ### 配置地图追踪策略和战斗策略 -点击配置组设置,打开地图追踪设置,**打开自动拾取**,**开启允许在js中使用**和**覆盖js中的自动战斗策略** +点击配置组设置,打开地图追踪设置,**打开自动拾取**,**打开只在传送点时回复**,**开启允许在js中使用**和**覆盖js中的自动战斗策略** 开启战斗策略配置,**关闭自动检测战斗结束**,其他的根据你的队伍进行配置 ### 七天神像 在bgi设置内,关闭就近七天神像,指定一个七天神像,如无特殊要求推荐选择须弥道成林。 @@ -34,6 +34,13 @@ wgc的bug,请使用bitblt截图方式,脚本只支持这个 打开大地图,点击左下角设置,开启自定义标记。 脚本非正常结束运行时会出现该问题。 ## 更新日志 +### 3.3 +- 战斗失败路线重试支持 +- 导航到地脉花优化 + +### 3.2 +- 新增通知选项 + ### 3.1 - 新增璃月、须弥部分路线 - 修改寻找地脉花领奖的逻辑 diff --git a/repo/js/AutoLeyLineOutcrop/assets/pathing/路线修改进度.md b/repo/js/AutoLeyLineOutcrop/assets/pathing/路线修改进度.md deleted file mode 100644 index 9ac125d1..00000000 --- a/repo/js/AutoLeyLineOutcrop/assets/pathing/路线修改进度.md +++ /dev/null @@ -1,77 +0,0 @@ -## 战斗以及之后的点位删除,按f点位改成无 -- 稻妻3-八酝岛-1 - -- 枫丹1-秋分山西侧-1 -- 枫丹1-秋分山西侧-2 -- 枫丹1-秋分山西侧-3 -- 枫丹1-秋分山西侧-4 -- 枫丹1-秋分山西侧-5 -- 枫丹2-芒索斯山东麓-1 -- 枫丹2-芒索斯山东麓-2 -- 枫丹2-芒索斯山东麓-3 -- 枫丹2-芒索斯山东麓-4 -- 枫丹3-新枫丹科学院-1 -- 枫丹3-新枫丹科学院-2 -- 枫丹3-新枫丹科学院-3 -- 枫丹3-新枫丹科学院-4 -- 枫丹4-柔灯港-1 -- 枫丹4-柔灯港-2 -- 枫丹4-柔灯港-3 -- 枫丹4-柔灯港-4 -- 枫丹5-秋分山东侧-1 -- 枫丹5-秋分山东侧-2 -- 枫丹5-秋分山东侧-3 -- 枫丹5-秋分山东侧-4 -- 枫丹6-厄里那斯-1 -- 枫丹6-厄里那斯-2 -- 枫丹6-厄里那斯-3 -- 枫丹6-厄里那斯-4 -- 枫丹6-厄里那斯-5 -- 枫丹6-厄里那斯-6 - -- 璃月1-石门-1 -- 璃月1-石门-2 -- 璃月1-石门-3 -- 璃月1-石门-4 -- 璃月3-瑶光滩-1 - -- 蒙德2-清泉镇-1 -- 蒙德2-清泉镇-2 -- 蒙德2-清泉镇-3 -- 蒙德2-清泉镇-4 -- 蒙德3-奔狼领-1 -- 蒙德3-奔狼领-2 -- 蒙德3-奔狼领-3 -- 蒙德3-奔狼领-4 -- 蒙德4-风龙废墟-1 -- 蒙德4-风龙废墟-2 -- 蒙德4-风龙废墟-3 -- 蒙德4-风龙废墟-4 -- 蒙德5-千风神殿-1 -- 蒙德5-千风神殿-2 -- 蒙德5-千风神殿-3 -- 蒙德5-千风神殿-4 -- 蒙德5-千风神殿-5 -- 蒙德6-望风山地-1 -- 蒙德6-望风山地-2 -- 蒙德6-望风山地-3 -- 蒙德6-望风山地-4 -- 蒙德7-达达乌帕谷-1 -- 蒙德7-达达乌帕谷-2 -- 蒙德7-达达乌帕谷-3 -- 蒙德7-达达乌帕谷-4 -- 蒙德7-达达乌帕谷-5 - -- 纳塔1-隆崛坡-1 -- 纳塔1-隆崛坡-2 -- 纳塔1-隆崛坡-3 -- 纳塔1-隆崛坡-4 -- 纳塔4-溶水域-1 -- 纳塔4-溶水域-2 -- 纳塔5-安饶之野-1 -- 纳塔5-安饶之野-2 -- 纳塔5-安饶之野-3 -- 纳塔6-圣火竞技场-1 -- 纳塔6-圣火竞技场-2 -- 纳塔6-圣火竞技场-3 -- 纳塔6-圣火竞技场-4 \ No newline at end of file diff --git a/repo/js/AutoLeyLineOutcrop/main.js b/repo/js/AutoLeyLineOutcrop/main.js index 6765e4bd..506d2d56 100644 --- a/repo/js/AutoLeyLineOutcrop/main.js +++ b/repo/js/AutoLeyLineOutcrop/main.js @@ -13,7 +13,20 @@ let retryCount = 0; // 重试次数 let marksStatus = true; // 自定义标记状态 let currentRunTimes = 0; // 当前运行次数 let isNotification = false; // 是否发送通知 +let config = {}; // 全局配置对象 +const ocrRegion1 = { x: 800, y: 200, width: 300, height: 100 }; // 中心区域 +const ocrRegion2 = { x: 0, y: 200, width: 300, height: 300 }; // 追踪任务区域 +const ocrRegion3 = { x: 1200, y: 520, width: 300, height: 300 }; // 拾取区域 +// 预定义识别对象 +const openRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/open.png")); +const closeRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/close.png")); +const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0); +const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/box.png")); +const ocrRo1 = RecognitionObject.ocr(ocrRegion1.x, ocrRegion1.y, ocrRegion1.width, ocrRegion1.height); +const ocrRo2 = RecognitionObject.ocr(ocrRegion2.x, ocrRegion2.y, ocrRegion2.width, ocrRegion2.height); +const ocrRo3 = RecognitionObject.ocr(ocrRegion3.x, ocrRegion3.y, ocrRegion3.width, ocrRegion3.height); +const ocrRoThis = RecognitionObject.ocrThis; /** * 主函数 - 脚本入口点 */ @@ -26,8 +39,8 @@ let isNotification = false; // 是否发送通知 if (isNotification) { notification.error("出错了! {error}", error.message); } - if (!marksStatus) { - await openCustomMarks(); + if (!marksStatus) { + await openCustomMarks(); } } })(); @@ -37,26 +50,20 @@ let isNotification = false; // 是否发送通知 * @returns {Promise} */ async function runLeyLineOutcropScript() { - // 初始化 + // 初始化加载配置和设置并校验 await initializeGame(); - - // 加载配置和设置并校验 - const config = await loadConfig(); - const settings = loadSettings(); + await loadConfig(); + loadSettings(); retryCount = 0; - - // 显示设置信息 - logSettings(settings); - - // 开局准备 - await prepareForLeyLineRun(settings); - + + await prepareForLeyLineRun(); + // 执行地脉花挑战 - await runLeyLineChallenges(config, settings); - + await runLeyLineChallenges(); + // 完成后恢复自定义标记 - if (!marksStatus) { - await openCustomMarks(); + if (!marksStatus) { + await openCustomMarks(); } } @@ -69,34 +76,15 @@ async function initializeGame() { setGameMetrics(1920, 1080, 1); } -/** - * 记录设置信息到日志 - * @param {Object} settings - 用户设置对象 - */ -function logSettings(settings) { - log.info(`地脉花类型:${settings.leyLineOutcropType}`); - log.info(`国家:${settings.country}`); - - if (settings.friendshipTeam) { - log.info(`好感队:${settings.friendshipTeam}`); - } - - log.info(`刷取次数:${settings.timesValue}`); - - if (isNotification) { - notification.info("全自动地脉花开始运行,以下是本次运行的配置:\n\n地脉花类型:{1}\n国家:{2}\n刷取次数:{3}", settings.leyLineOutcropType, settings.country, settings.timesValue); - } -} /** * 执行地脉花挑战前的准备工作 - * @param {Object} settings - 用户设置对象 * @returns {Promise} */ -async function prepareForLeyLineRun(settings) { +async function prepareForLeyLineRun() { // 开局传送到七天神像 await genshin.tpToStatueOfTheSeven(); - + // 切换战斗队伍 if (settings.team) { log.info(`切换至队伍 ${settings.team}`); @@ -106,20 +94,16 @@ async function prepareForLeyLineRun(settings) { /** * 执行地脉花挑战的主要逻辑 - * @param {Object} config - 配置对象 - * @param {Object} settings - 用户设置对象 * @returns {Promise} */ -async function runLeyLineChallenges(config, settings) { - - +async function runLeyLineChallenges() { while (currentRunTimes < settings.timesValue) { // 寻找地脉花位置 await findLeyLineOutcrop(settings.country, settings.leyLineOutcropType); - + // 查找并执行对应的策略 - const foundStrategy = await executeMatchingStrategy(config, settings); - + const foundStrategy = await executeMatchingStrategy(); + // 未找到策略的错误处理 if (!foundStrategy) { handleNoStrategyFound(); @@ -128,78 +112,89 @@ async function runLeyLineChallenges(config, settings) { } } +/** + * 切换指定的队伍 + * @param {string} teamName - 队伍名称 + * @returns {Promise} + */ +async function switchTeam(teamName) { + try { + return await genshin.switchParty(teamName); + } catch (error) { + log.error(`切换队伍时出错: ${error.message}`); + return false; + } +} + /** * 执行匹配的地脉花策略 - * @param {Object} config - 配置对象 - * @param {Object} settings - 用户设置对象 * @returns {Promise} 是否找到并执行了策略 */ -async function executeMatchingStrategy(config, settings) { +async function executeMatchingStrategy() { let foundStrategy = false; - + // 从配置中查找匹配的位置和策略 if (config.leyLinePositions[settings.country]) { const positions = config.leyLinePositions[settings.country]; - + for (const position of positions) { if (isNearPosition(leyLineX, leyLineY, position.x, position.y, config.errorThreshold)) { foundStrategy = true; strategyName = position.strategy; order = position.order; log.info(`找到匹配的地脉花策略:${strategyName},次序:${order}`); - + // 使用 LeyLineOutcropData.json 数据处理路径 - await executePathsUsingNodeData(position, settings); + await executePathsUsingNodeData(position); break; } } } - + return foundStrategy; } /** * 使用节点数据执行路径 * @param {Object} position - 位置对象 - * @param {Object} settings - 用户设置对象 * @returns {Promise} */ -async function executePathsUsingNodeData(position, settings) { +async function executePathsUsingNodeData(position) { try { - const nodeData = await loadNodeData(); + const nodeData = await loadNodeData(); let currentNodePosition = position; - const targetNode = findTargetNodeByPosition(nodeData, currentNodePosition.x, currentNodePosition.y); - + const targetNode = findTargetNodeByPosition(nodeData, currentNodePosition.x, currentNodePosition.y); + if (!targetNode) { log.error(`未找到与坐标(${currentNodePosition.x}, ${currentNodePosition.y})匹配的目标节点`); return; } - log.info(`找到目标节点: ID ${targetNode.id}, 位置(${targetNode.position.x}, ${targetNode.position.y})`); + // log.info(`找到目标节点: ID ${targetNode.id}, 位置(${targetNode.position.x}, ${targetNode.position.y})`); const paths = findPathsToTarget(nodeData, targetNode); - + if (paths.length === 0) { log.error(`未找到通向目标节点(ID: ${targetNode.id})的路径`); return; } - + // 选择最短的路径执行 const optimalPath = selectOptimalPath(paths); - log.info(`选择了含有 ${optimalPath.routes.length} 个路径点的最优路径`); - + // log.info(`选择了含有 ${optimalPath.routes.length} 个路径点的最优路径`); + // 执行路径 - await executePath(optimalPath, settings); + await executePath(optimalPath); currentRunTimes++; - + // 如果达到刷取次数上限,退出循环 if (currentRunTimes >= settings.timesValue) { return; } - + // 循环检查并执行当前节点的单一next路径,直到遇到没有next或有多个next的情况 let currentNode = targetNode; - + while (currentNode.next && currentRunTimes < settings.timesValue) { - if(currentNode.next.length === 1){ + if (currentNode.next.length === 1) { // 获取下一个节点的ID 和 路径,并在节点数据中找到下一个节点 const nextNodeId = currentNode.next[0].target; const nextRoute = currentNode.next[0].route; @@ -208,8 +203,6 @@ async function executePathsUsingNodeData(position, settings) { if (!nextNode) { return; } - - // 创建一个适合executePath函数的路径对象 const pathObject = { startNode: currentNode, targetNode: nextNode, @@ -217,65 +210,65 @@ async function executePathsUsingNodeData(position, settings) { }; log.info(`直接执行下一个节点路径: ${nextRoute}`); - await executePath(pathObject, settings); + await executePath(pathObject); currentRunTimes++; log.info(`完成节点 ID ${nextNodeId}, 已执行 ${currentRunTimes}/${settings.timesValue} 次`); - + // 更新当前节点为下一个节点,继续检查 currentNode = nextNode; currentNodePosition = { x: nextNode.position.x, y: nextNode.position.y }; } - else if(currentNode.next.length > 1){ + else if (currentNode.next.length > 1) { // 如果存在分支路线,先打开大地图判断下一个地脉花的位置,根据下一个地脉花的位置选择路线 log.info("检测到多个分支路线,开始查找下一个地脉花位置"); - + // 备份当前地脉花坐标 const currentLeyLineX = leyLineX; const currentLeyLineY = leyLineY; - + // 打开大地图 await genshin.returnMainUi(); keyPress("M"); await sleep(1000); - + // 查找下一个地脉花 const found = await locateLeyLineOutcrop(settings.leyLineOutcropType); await genshin.returnMainUi(); - + if (!found) { log.warn("无法在分支点找到下一个地脉花,退出本次循环"); return; } log.info(`找到下一个地脉花,位置: (${leyLineX}, ${leyLineY})`); - + // 计算每个分支节点到地脉花的距离,选择最近的路径 let closestRoute = null; let closestDistance = Infinity; let closestNodeId = null; - + for (const nextRoute of currentNode.next) { const nextNodeId = nextRoute.target; const nextNode = nodeData.node.find(node => node.id === nextNodeId); - + if (!nextNode) continue; - + const distance = calculate2DDistance( leyLineX, leyLineY, nextNode.position.x, nextNode.position.y ); - + log.info(`路线到地脉花距离: ID ${nextNodeId}, 距离: ${distance.toFixed(2)}`); - + if (distance < closestDistance) { closestDistance = distance; closestRoute = nextRoute.route; closestNodeId = nextNodeId; } } - + if (!closestRoute) { log.error("无法找到合适的路线,终止执行"); // 恢复原始坐标 @@ -283,30 +276,36 @@ async function executePathsUsingNodeData(position, settings) { leyLineY = currentLeyLineY; return; } - + const nextNode = nodeData.node.find(node => node.id === closestNodeId); log.info(`选择最近的路线: ${closestRoute}, 目标节点ID: ${closestNodeId}。`); - + // 创建路径对象并执行 const pathObject = { startNode: currentNode, targetNode: nextNode, routes: [closestRoute] }; - - await executePath(pathObject, settings); + + await executePath(pathObject); currentRunTimes++; - + // 更新当前节点为下一个节点,继续检查 currentNode = nextNode; currentNodePosition = { x: nextNode.position.x, y: nextNode.position.y }; - } - else{ + } + else { log.info("当前路线完成,退出循环"); break; } } - } catch (error) { + } + catch (error) { + if(error.message.includes("战斗失败")) { + log.error("战斗失败,重新寻找地脉花后重试"); + return; + } + // 其他错误需要向上传播 log.error(`执行路径时出错: ${error.message}`); throw error; } @@ -335,15 +334,15 @@ async function loadNodeData() { */ function findTargetNodeByPosition(nodeData, x, y) { const errorThreshold = 50; // 坐标匹配误差范围 - + for (const node of nodeData.node) { - if (node.type === "blossom" && - Math.abs(node.position.x - x) <= errorThreshold && + if (node.type === "blossom" && + Math.abs(node.position.x - x) <= errorThreshold && Math.abs(node.position.y - y) <= errorThreshold) { return node; } } - + return null; } @@ -359,9 +358,9 @@ function findPathsToTarget(nodeData, targetNode) { nodeData.node.forEach(node => { nodeMap[node.id] = node; }); - + log.info(`目标节点ID: ${targetNode.id}, 类型: ${targetNode.type}, 区域: ${targetNode.region}`); - + // 采用广度优先搜索查找所有可能路径 return breadthFirstPathSearch(nodeData, targetNode, nodeMap); } @@ -376,24 +375,24 @@ function findPathsToTarget(nodeData, targetNode) { function breadthFirstPathSearch(nodeData, targetNode, nodeMap) { // 存储找到的所有有效路径 const validPaths = []; - + // 获取所有传送点作为起点 const teleportNodes = nodeData.node.filter(node => node.type === "teleport"); - log.info(`找到 ${teleportNodes.length} 个传送点作为可能的起点`); - + //log.info(`找到 ${teleportNodes.length} 个传送点作为可能的起点`); + // 对每个传送点,尝试查找到目标的路径 for (const startNode of teleportNodes) { // 初始化队列,每个元素包含 [当前节点, 路径信息] - const queue = [[startNode, { + const queue = [[startNode, { startNode: startNode, routes: [], visitedNodes: new Set([startNode.id]) }]]; - + // 广度优先搜索 while (queue.length > 0) { const [currentNode, pathInfo] = queue.shift(); - + // 如果已经到达目标节点 if (currentNode.id === targetNode.id) { validPaths.push({ @@ -403,40 +402,40 @@ function breadthFirstPathSearch(nodeData, targetNode, nodeMap) { }); continue; // 找到一条路径,继续搜索其他可能路径 } - + // 检查当前节点的下一个连接 if (currentNode.next && currentNode.next.length > 0) { for (const nextRoute of currentNode.next) { const nextNodeId = nextRoute.target; - + // 避免循环 if (pathInfo.visitedNodes.has(nextNodeId)) { continue; } - + const nextNode = nodeMap[nextNodeId]; if (!nextNode) { continue; } - + // 创建新的路径信息 const newPathInfo = { startNode: pathInfo.startNode, routes: [...pathInfo.routes, nextRoute.route], visitedNodes: new Set([...pathInfo.visitedNodes, nextNodeId]) }; - + // 加入队列 queue.push([nextNode, newPathInfo]); } } } } - + // 检查是否存在反向路径 const reversePaths = findReversePathsIfNeeded(nodeData, targetNode, nodeMap, validPaths); validPaths.push(...reversePaths); - + log.info(`共找到 ${validPaths.length} 条有效路径`); return validPaths; } @@ -454,22 +453,22 @@ function findReversePathsIfNeeded(nodeData, targetNode, nodeMap, existingPaths) if (existingPaths.length > 0 || !targetNode.prev || targetNode.prev.length === 0) { return []; } - + const reversePaths = []; - + // 检查每个前置节点 for (const prevNodeId of targetNode.prev) { const prevNode = nodeMap[prevNodeId]; if (!prevNode) continue; - + // 找到从前置节点到传送点的路径 const pathsToPrevNode = []; - + // 获取所有能从这个前置节点到达的传送点 - const teleportNodes = nodeData.node.filter(node => + const teleportNodes = nodeData.node.filter(node => node.type === "teleport" && node.next.some(route => route.target === prevNode.id) ); - + for (const teleportNode of teleportNodes) { // 寻找传送点到前置节点的路径 const route = teleportNode.next.find(r => r.target === prevNode.id); @@ -486,7 +485,7 @@ function findReversePathsIfNeeded(nodeData, targetNode, nodeMap, existingPaths) } } } - + return reversePaths; } @@ -499,65 +498,60 @@ function selectOptimalPath(paths) { if (!paths || paths.length === 0) { throw new Error("没有可用路径"); } - + // 按路径段数从少到多排序 paths.sort((a, b) => a.routes.length - b.routes.length); - + // 记录路径选择日志 for (let i = 0; i < Math.min(paths.length, 3); i++) { - log.info(`路径选项 ${i+1}: 起点ID ${paths[i].startNode.id}, ${paths[i].routes.length} 段路径`); + // log.info(`路径选项 ${i + 1}: 起点ID ${paths[i].startNode.id}, ${paths[i].routes.length} 段路径`); for (let j = 0; j < paths[i].routes.length; j++) { - log.info(` - 路径 ${j+1}: ${paths[i].routes[j]}`); + // log.info(` - 路径 ${j + 1}: ${paths[i].routes[j]}`); } } - + return paths[0]; // 返回路径段最少的路径 } /** * 执行路径 * @param {Object} path - 路径对象 - * @param {Object} settings - 用户设置对象 * @returns {Promise} */ -async function executePath(path, settings) { +async function executePath(path) { log.info(`开始执行路径,起始点ID: ${path.startNode.id}, 目标点ID: ${path.targetNode.id}`); log.info(`路径包含 ${path.routes.length} 个路径段`); - + // 依次执行每个路径段 for (let i = 0; i < path.routes.length; i++) { const routePath = path.routes[i]; - log.info(`执行路径 ${i+1}/${path.routes.length}: ${routePath}`); - + log.info(`执行路径 ${i + 1}/${path.routes.length}: ${routePath}`); + try { // 运行路径文件 await pathingScript.runFile(routePath); } catch (error) { - log.error(`执行路径段 ${i+1} 时出错: ${error.message}`); + log.error(`执行路径 ${i + 1} 时出错: ${error.message}`); throw error; } } const routePath = path.routes[path.routes.length - 1]; const targetPath = routePath.replace('assets/pathing/', 'assets/pathing/target/').replace('-rerun', ''); - // 处理地脉花 - log.info(`处理地脉花: ${targetPath}`); await processLeyLineOutcrop(settings.timeout, targetPath); - await switchToFriendshipTeamIfNeeded(settings); - await attemptReward(settings); + await attemptReward(); } /** * 如果需要,切换到好感队 - * @param {Object} settings - 用户设置对象 * @returns {Promise} */ -async function switchToFriendshipTeamIfNeeded(settings) { +async function switchToFriendshipTeamIfNeeded() { if (!settings.friendshipTeam) { return; } - + log.info(`切换至队伍 ${settings.friendshipTeam}`); - + try { await genshin.switchParty(settings.friendshipTeam); } catch (error) { @@ -567,8 +561,14 @@ async function switchToFriendshipTeamIfNeeded(settings) { keyPress("ESCAPE"); await sleep(500); await genshin.returnMainUi(); - log.info(`重新切换至队伍 ${settings.friendshipTeam}`); - await genshin.switchParty(settings.friendshipTeam); + log.info(`再次切换至队伍 ${settings.friendshipTeam}`); + try { + await genshin.switchParty(settings.friendshipTeam); + } catch (error) { + // 如果切换队伍失败,记录日志并继续执行 + log.warn(`切换队伍失败: ${error.message}`); + log.warn("跳过切换队伍,直接领取奖励"); + } } } @@ -585,18 +585,14 @@ async function handleNoStrategyFound() { } } -/** - * 配置相关函数 - */ - /** * 加载配置文件 - * @returns {Object} 配置对象 + * @returns {Promise} */ async function loadConfig() { try { const configData = JSON.parse(await file.readText("config.json")); - return configData; + config = configData; // 直接赋值给全局变量 } catch (error) { log.error(`加载配置文件失败: ${error.message}`); throw new Error("配置文件加载失败,请检查config.json文件是否存在"); @@ -604,64 +600,73 @@ async function loadConfig() { } /** - * 加载并验证用户设置 + * 加载、验证、输出用户设置 * @returns {Object} 处理过的设置对象 */ function loadSettings() { try { - // 从全局settings加载用户设置 - const settingsData = { - start: settings.start, - leyLineOutcropType: settings.leyLineOutcropType, - country: settings.country, - team: settings.team, - friendshipTeam: settings.friendshipTeam, - timeout: settings.timeout * 1000 ? settings.timeout * 1000 : 120000, - count: settings.count ? settings.count : "6", - isNotification: settings.isNotification - }; + // 直接使用全局settings对象而不是重新创建 + // 这样能保留原始设置内容 // 验证必要的设置 - if (!settingsData.start) { - throw new Error("请仔细阅读脚本介绍,并在{1}内进行配置,如果你是直接运行的脚本,请将脚本加入{1}内运行!", "调度器"); + if (!settings.start) { + throw new Error("请仔细阅读脚本介绍,并在{0}内进行配置,如果你是直接运行的脚本,请将脚本加入{0}内运行!", "调度器"); } - if (!settingsData.leyLineOutcropType) { - throw new Error("请在游戏中确认地脉花的类型,然后在js设置中选择地脉花的类型。"); + if (!settings.leyLineOutcropType) { + throw new Error("请选择你要刷取的地脉花类型(经验书/摩拉)"); } - if (!settingsData.country) { + if (!settings.country) { throw new Error("请在游戏中确认地脉花的第一个点的位置,然后在js设置中选择地脉花所在的国家。"); } - if (settingsData.friendshipTeam && !settingsData.team) { + if (settings.friendshipTeam && !settings.team) { throw new Error("未配置战斗队伍!当配置了好感队时必须配置战斗队伍!"); } + // 为了向后兼容,确保某些设置有默认值 + settings.timeout = settings.timeout * 1000 || 120000; + // 处理刷取次数 - if (!/^-?\d+\.?\d*$/.test(settingsData.count)) { - log.warn(`刷取次数 ${settingsData.count} 不是数字,使用默认次数6次`); - settingsData.timesValue = 6; + if (!settings.count || !/^-?\d+\.?\d*$/.test(settings.count)) { + log.warn(`刷取次数 ${settings.count} 不是数字,使用默认次数6次`); + settings.timesValue = 6; } else { // 转换为数字 - const num = parseFloat(settingsData.count); + const num = parseFloat(settings.count); // 范围检查 if (num < 1) { - settingsData.timesValue = 1; + settings.timesValue = 1; log.info(`⚠️ 次数 ${num} 小于1,已调整为1`); } else { // 处理小数 if (!Number.isInteger(num)) { - settingsData.timesValue = Math.floor(num); - log.info(`⚠️ 次数 ${num} 不是整数,已向下取整为 ${settingsData.timesValue}`); + settings.timesValue = Math.floor(num); + log.info(`⚠️ 次数 ${num} 不是整数,已向下取整为 ${settings.timesValue}`); } else { - settingsData.timesValue = num; + settings.timesValue = num; } } } - return settingsData; + // 记录使用的设置 + log.info(`地脉花类型:${settings.leyLineOutcropType}`); + log.info(`国家:${settings.country}`); + + if (settings.friendshipTeam) { + log.info(`好感队:${settings.friendshipTeam}`); + } + + log.info(`刷取次数:${settings.timesValue}`); + + // 设置通知状态 + isNotification = settings.isNotification; + + if (isNotification) { + notification.send("全自动地脉花开始运行,以下是本次运行的配置:\n\n地脉花类型:{1}\n国家:{2}\n刷取次数:{3}", settings.leyLineOutcropType, settings.country, settings.timesValue); + } } catch (error) { log.error(`加载设置失败: ${error.message}`); throw error; @@ -679,45 +684,27 @@ function loadSettings() { * @returns {Promise} */ async function findLeyLineOutcrop(country, type) { - const config = await loadConfig(); - const maxRetries = 6; - + currentFlower = null; + keyPress("M"); + await sleep(1000); + await closeCustomMarks(); + await sleep(1000); log.info("开始寻找地脉花"); - if (!config.mapPositions[country] || config.mapPositions[country].length === 0) { throw new Error(`未找到国家 ${country} 的位置信息`); } - const defaultPos = config.mapPositions[country][0]; - await genshin.moveMapTo(defaultPos.x, defaultPos.y, country); - await sleep(1000); // 移动完等地图稳定 - - currentFlower = null; - - for (let retryCount = 0; retryCount < maxRetries; retryCount++) { - log.info(`第 ${retryCount + 1} 次尝试定位地脉花`); - + const positions = config.mapPositions[country]; + await genshin.moveMapTo(positions[0].x, positions[0].y, country); + for (let retryCount = 1; retryCount < positions.length; retryCount++) { const found = await locateLeyLineOutcrop(type); + if (found) return; + const position = positions[retryCount]; + log.info(`第 ${retryCount + 1} 次尝试定位地脉花`); + log.info(`移动到位置:(${position.x}, ${position.y}), ${position.name || '未命名位置'}`); + await genshin.moveMapTo(position.x, position.y); - if (found) { - return; // 找到就直接结束 - } - - // 第一次失败,执行特殊操作 - if (retryCount === 0) { - log.warn("未找到地脉花,关闭自定义标记并继续尝试"); - await closeCustomMarks(); - } - - // 如果 shouldMoveMap 建议移动地图,就移动一下 - if (shouldMoveMap(country, retryCount)) { - const position = await getMapPosition(country, retryCount, config); - log.info(`移动到特定位置:(${position.x},${position.y}), ${position.name}`); - await genshin.moveMapTo(position.x, position.y); - await sleep(1000); // 移动后也等一下 - } - - await sleep(1000); // 每次循环结束也等一下,防止太快 + await sleep(1000); // 移动后等一下 } // 如果到这里还没找到 @@ -744,8 +731,6 @@ async function locateLeyLineOutcrop(type) { currentFlower = flowerList[0]; const flowerType = type === "蓝花(经验书)" ? "经验" : "摩拉"; - log.info(`找到${flowerType}地脉花,位置:(${currentFlower.x},${currentFlower.y})`); - const center = genshin.getPositionFromBigMap(); const mapZoomLevel = genshin.getBigMapZoomLevel(); const mapScaleFactor = 2.361; @@ -753,7 +738,7 @@ async function locateLeyLineOutcrop(type) { leyLineX = (960 - currentFlower.x - 25) * mapZoomLevel / mapScaleFactor + center.x; leyLineY = (540 - currentFlower.y - 25) * mapZoomLevel / mapScaleFactor + center.y; - log.info(`地脉花的实际坐标:(${leyLineX},${leyLineY})`); + log.info(`找到地脉花的坐标:(${leyLineX}, ${leyLineY})`); return true; } else { log.warn("未找到地脉花"); @@ -761,52 +746,6 @@ async function locateLeyLineOutcrop(type) { } } - -/** - * 判断是否需要移动地图 - * @param {string} country - 国家名称 - * @param {number} retryCount - 重试次数 - * @returns {boolean} 是否需要移动地图 - */ -function shouldMoveMap(country, retryCount) { - if (retryCount == 0) return false; - - // 不同国家的重试策略 - const countryRetryMap = { - "蒙德": [0, 1, 2], - "璃月": [0, 1, 2, 3], - "稻妻": [0, 1, 2, 3, 4], - "须弥": [0, 1, 2, 3, 4, 5, 6], - "枫丹": [0, 1, 2], - "纳塔": [0, 1, 2, 3, 4] - }; - - return countryRetryMap[country] && countryRetryMap[country].includes(retryCount); -} - -/** - * 获取地图移动位置 - * @param {string} country - 国家名称 - * @param {number} retryCount - 重试次数 - * @param {Object} config - 配置对象 - * @returns {Object} 包含x,y坐标的位置对象 - */ -async function getMapPosition(country, retryCount, config) { - // 从配置文件获取位置 - if (config.mapPositions[country]) { - const positions = config.mapPositions[country]; - // 确保retryCount不超过位置数量 - let index = Math.min(retryCount, positions.length - 1); - log.warn(`retryCount:${retryCount}`); - log.warn(`countryIndex:${index}`); - return positions[index]; - } - - // 默认返回 - log.warn(`未找到国家 ${country} 的位置信息`); - return { x: 0, y: 0, name: "默认位置" }; -} - /** * 判断坐标是否在指定位置附近(误差范围内) * @param {number} x - 当前X坐标 @@ -853,39 +792,43 @@ async function processLeyLineOutcrop(timeout, targetPath, retries = 0) { if (retries >= MAX_RETRIES) { log.error(`打开地脉花失败,已重试${MAX_RETRIES}次,终止处理`); log.error("我辣么大一个地脉花哪去了?"); - throw new Error("打开地脉花失败"); + return; } - let ocr = captureGameRegion().find(RecognitionObject.ocrThis); - if (ocr && ocr.text.includes("地脉溢口")) { - log.info("识别到地脉溢口"); - // await openOutcrop(targetPath); - keyPress("F"); - await autoFight(timeout); - await autoNavigateToReward(); - } else if (ocr && ocr.text.includes("打倒所有敌人")) { - log.info("地脉花已经打开,直接战斗"); - await autoFight(timeout); - await autoNavigateToReward(); - } else if (ocr && ocr.text.includes("地脉之花")) { + let captureRegion = captureGameRegion(); + let result = captureRegion.find(ocrRo2); + let result2 = captureRegion.find(ocrRo3); + if (result2.text.includes("地脉之花")) { log.info("识别到地脉之花"); + await switchToFriendshipTeamIfNeeded(); + return; + } + if (result2.text.includes("地脉溢口")) { + log.info("识别到地脉溢口"); + keyPress("F"); + } else if (result.text.includes("打倒所有敌人")) { + log.info("地脉花已经打开,直接战斗"); } else { log.warn(`未识别到地脉花文本,当前重试次数: ${retries + 1}/${MAX_RETRIES}`); try { await pathingScript.runFile(targetPath); await processLeyLineOutcrop(timeout, targetPath, retries + 1); } catch (error) { - throw new Error(`打开地脉花失败: ${error.message}`); + throw new Error(`未识别到地脉花: ${error.message}`); } } + if(!await autoFight(timeout)){ + throw new Error("战斗失败"); + } + await switchToFriendshipTeamIfNeeded(); + await autoNavigateToReward(); } /** * 尝试领取地脉花奖励 - * @param {Object} settings - 设置对象,包含friendshipTeam * @returns {Promise} */ -async function attemptReward(settings) { +async function attemptReward() { const MAX_RETRY = 5; // 超时处理 @@ -899,7 +842,7 @@ async function attemptReward(settings) { await sleep(500); // 识别是否为地脉之花界面 - let resList = captureGameRegion().findMulti(RecognitionObject.ocrThis); + let resList = captureGameRegion().findMulti(ocrRoThis); // 使用预定义的ocrRoThis对象 let isValid = false; let condensedResin = null; let originalResin = null; @@ -929,32 +872,27 @@ async function attemptReward(settings) { if (originalResin && dobuleReward == true) { log.info("选择使用原粹树脂,获得双倍产出"); click(Math.round(originalResin.x + originalResin.width / 2), Math.round(originalResin.y + originalResin.height / 2)); - if (settings.friendshipTeam) { - log.info("切换回战斗队伍"); - await genshin.switchParty(settings.team); - } - return; } else if (condensedResin) { log.info("选择使用浓缩树脂"); click(Math.round(condensedResin.x + condensedResin.width / 2), Math.round(condensedResin.y + condensedResin.height / 2)); - if (settings.friendshipTeam) { - log.info("切换回战斗队伍"); - await genshin.switchParty(settings.team); - } - return; } else if (originalResin) { log.info("选择使用原粹树脂"); click(Math.round(originalResin.x + originalResin.width / 2), Math.round(originalResin.y + originalResin.height / 2)); - if (settings.friendshipTeam) { - log.info("切换回战斗队伍"); - await genshin.switchParty(settings.team); - } - return; } else if (isResinEmpty) { log.error("识别到补充原粹树脂,看来树脂用完了呢"); await keyPress("VK_ESCAPE"); throw new Error("树脂已用完"); } + if (settings.friendshipTeam) { + log.info("切换回战斗队伍"); + const switchSuccess = await switchTeam(settings.team); + // if (!switchSuccess) { + // log.warn("切换队伍失败,返回七天神像切换"); + // await genshin.tpToStatueOfTheSeven(); + // await genshin.switchParty(settings.team); + // throw new Error("切换队伍失败"); + // } + } } // 界面不正确,尝试重试 @@ -964,26 +902,24 @@ async function attemptReward(settings) { await sleep(1000); retryCount++; await autoNavigateToReward(); - await attemptReward(settings); + await attemptReward(); } } /** - * 开启地脉花 + * 打开地脉花 * @param {string} targetPath - 目标路径 * @returns {Promise} 区域是否出现地脉任务 */ async function openOutcrop(targetPath) { - let ocrRegion1 = { x: 800, y: 200, width: 300, height: 100 }; // 中心区域 - let ocrRegion2 = { x: 0, y: 200, width: 300, height: 300 }; // 追踪任务区域 let startTime = Date.now(); let recognized = false; keyPress("F"); - // 前5秒识别中心区域弹出的横幅任务提示 while (Date.now() - startTime < 5000) { - if (recognizeFightText(ocrRegion1)) { + captureRegion = captureGameRegion(); + if (recognizeFightText(captureRegion)) { recognized = true; break; } @@ -991,29 +927,17 @@ async function openOutcrop(targetPath) { await sleep(500); } - // 如果5秒内没有识别成功,再追加识别追踪任务区域及尝试重新开启地脉花 - if (!recognized) { - let secondStartTime = Date.now(); - while (Date.now() - secondStartTime < 60000) { - if (recognizeFightText(ocrRegion1) || recognizeFightText(ocrRegion2)) { - recognized = true; - break; - } - await pathingScript.runFile(targetPath); - keyPress("F"); - await sleep(500); - } - } + // 返回识别结果 + return recognized; } /** * 识别地脉开启进入战斗文本 - * @param {Object} ocrRegion - OCR识别区域 * @returns {Promise} 区域是否出现战斗文本 */ -function recognizeFightText(ocrRegion) { +function recognizeFightText(captureRegion) { try { - let result = captureGameRegion().find(RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height)); + let result = captureRegion.find(ocrRo2); let text = result.text; keywords = ["打倒", "所有", "敌人"]; for (let keyword of keywords) { @@ -1028,69 +952,321 @@ function recognizeFightText(ocrRegion) { } /** - * 自动战斗 + * 自动异步战斗 * @param {number} timeout - 超时时间 - * @returns {Promise} + * @returns {Promise} 战斗是否成功 */ async function autoFight(timeout) { const cts = new CancellationTokenSource(); - try { - const ocrRegionX = 850; - const ocrRegionY = 230; - const ocrRegionWidth = 1040 - ocrRegionX; - const ocrRegionHeight = 300 - ocrRegionY; - let ocrRegion = { x: ocrRegionX, y: ocrRegionY, width: ocrRegionWidth, height: ocrRegionHeight }; - - log.info("开始战斗"); - dispatcher.RunTask(new SoloTask("AutoFight"), cts); - let fightResult = await recognizeTextInRegion(ocrRegion, timeout) ? "成功" : "失败"; - log.info(`战斗结束,战斗结果:${fightResult}`); - cts.cancel(); - } catch (error) { - log.error(`执行过程中出错: ${error}`); - } + log.info("开始战斗"); + dispatcher.RunTask(new SoloTask("AutoFight"), cts); + let fightResult = await recognizeTextInRegion(timeout); + logFightResult = fightResult ? "成功" : "失败"; + log.info(`战斗结束,战斗结果:${logFightResult}`); + cts.cancel(); + return fightResult; } /** * 识别战斗结果 - * @param {Object} ocrRegion - OCR识别区域 * @param {number} timeout - 超时时间 * @returns {Promise} 战斗是否成功 */ -async function recognizeTextInRegion(ocrRegion, timeout) { - let startTime = Date.now(); - const successKeywords = ["挑战达成", "战斗胜利", "挑战成功"]; - const failureKeywords = ["挑战失败"]; +async function recognizeTextInRegion(timeout) { + return new Promise((resolve, reject) => { + (async () => { + try { + let startTime = Date.now(); + let noTextCount = 0; + const successKeywords = ["挑战达成", "战斗胜利", "挑战成功"]; + const failureKeywords = ["挑战失败"]; - // 循环检测直到超时 - while (Date.now() - startTime < timeout) { - try { - let result = captureGameRegion().find(RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height)); - let text = result.text; + // 循环检测直到超时 + while (Date.now() - startTime < timeout) { + try { + let captureRegion = captureGameRegion(); + let result = captureRegion.find(ocrRo1); + let text = result.text; - // 检查成功关键词 - for (let keyword of successKeywords) { - if (text.includes(keyword)) { - log.info("检测到战斗成功关键词: {0}", keyword); - return true; + // 检查成功关键词 + for (let keyword of successKeywords) { + if (text.includes(keyword)) { + log.info("检测到战斗成功关键词: {0}", keyword); + resolve(true); + return; + } + } + + // 检查失败关键词 + for (let keyword of failureKeywords) { + if (text.includes(keyword)) { + log.warn("检测到战斗失败关键词: {0}", keyword); + resolve(false); + return; + } + } + + + let foundText = recognizeFightText(captureRegion); + if (!foundText) { + noTextCount++; + log.info(`检测到可能离开战斗区域,当前计数: ${noTextCount}`); + + if (noTextCount >= 10) { + log.warn("已离开战斗区域"); + resolve(false); + return; + } + } + else { + noTextCount = 0; // 重置计数 + } + } + catch (error) { + log.error("OCR过程中出错: {0}", error); + } + + await sleep(1000); // 检查间隔 } - } - // 检查失败关键词 - for (let keyword of failureKeywords) { - if (text.includes(keyword)) { - log.warn("检测到战斗失败关键词: {0}", keyword); - return false; + log.warn("在超时时间内未检测到战斗结果"); + resolve(false); + } catch (error) { + reject(error); + } + })(); + }); +} + + + + + + + +// 地脉花奖励相关函数 +/** + * 自动导航到地脉花奖励点 + * @returns {Promise} + */ +async function autoNavigateToReward() { + // 定义识别对象 + const cts = new CancellationTokenSource(); + const MAX_RETRY = 3; // 最大重试次数 + let retryCount = 0; + + try { + // 调整初始视角为俯视角 + log.info("调整视角..."); + middleButtonClick(); + await sleep(300); + + while (retryCount < MAX_RETRY) { + try { + log.info(`开始自动导航到地脉花...(尝试 ${retryCount + 1}/${MAX_RETRY})`); + let rewardDetectionPromise = startRewardTextDetection(cts); + + // 启动导航任务,添加超时参数 + await Promise.race([ + navigateTowardReward(60000, cts.token), // 设置60秒超时 + rewardDetectionPromise + ]); + + // 取消导航任务 + cts.cancel(); + keyUp("w"); // 确保停止前进 + log.info("已到达领奖点"); + return; // 成功完成 + } catch (error) { + retryCount++; + cts.cancel(); // 确保取消旧的令牌 + keyUp("w"); // 确保停止前进 + + if (error.message === '前进时间超时') { + log.warn(`导航超时,正在重试 (${retryCount}/${MAX_RETRY})`); + + // 尝试进行恢复操作 + keyPress("x"); // 尝试重置视角 + await sleep(500); + keyDown("s"); + await sleep(1000); + keyUp("s"); + await sleep(500); + + // 创建新的令牌 + cts = new CancellationTokenSource(); + } else { + // 对于其他错误,直接抛出 + throw error; } } } - catch (error) { - log.error("OCR过程中出错: {0}", error); + + // 如果达到最大重试次数仍然失败 + throw new Error(`导航到地脉花失败,已尝试 ${MAX_RETRY} 次`); + } catch (error) { + // 确保清理 + cts.cancel(); + keyUp("w"); + log.error(`导航过程中出错: ${error}`); + throw error; + } +} + +/** + * 监测文字区域,检测到地脉之花文字时返回 + * @param {CancellationTokenSource} cts - 取消令牌源 + * @returns {Promise} - 是否检测到文字 + */ +async function startRewardTextDetection(cts) { + return new Promise((resolve, reject) => { + (async () => { + try { + while (!cts.token.isCancellationRequested) { + // 首先检查异常界面 + let captureRegion = captureGameRegion(); + + // 检查是否误触发其他页面 + if (captureRegion.Find(paimonMenuRo).IsEmpty()) { + log.info("误触发其他页面,尝试关闭页面"); + await genshin.returnMainUi(); + await sleep(300); + continue; + } + + // 检查是否已经到达领奖界面 + let resList = captureRegion.findMulti(ocrRoThis); // 使用预定义的ocrRoThis对象 + if (resList && resList.count > 0) { + for (let i = 0; i < resList.count; i++) { + if (resList[i].text.includes("原粹树脂")) { + log.info("已到达领取页面,可以领奖"); + resolve(true); + return; + } + } + } + + let ocrResults = captureRegion.findMulti(ocrRo3); + + if (ocrResults && ocrResults.count > 0) { + for (let i = 0; i < ocrResults.count; i++) { + if (ocrResults[i].text.includes("接触") || + ocrResults[i].text.includes("地脉") || + ocrResults[i].text.includes("之花")) { + log.info("检测到文字: " + ocrResults[i].text); + resolve(true); + return; + } + } + } + + await sleep(200); + } + } catch (error) { + reject(error); + } + })(); + }); +} + +/** + * 导航向奖励点 + * @param {number} timeout - 超时时间 + * @param {CancellationToken} token - 取消令牌 + * @returns {Promise} + */ +async function navigateTowardReward(timeout, token) { + let navigateStartTime = Date.now(); + try { + while (!token.isCancellationRequested) { + if (await adjustViewForReward(boxIconRo, token)) { + keyDown("w"); + await sleep(300); + } else if (!token.isCancellationRequested) { // 如果没有取消,则继续尝试调整 + keyPress("x"); + keyUp("w"); + keyDown("s"); + await sleep(1000); + keyUp("s"); + keyDown("w"); + } + + if (Date.now() - navigateStartTime > timeout) { + keyUp("w"); + throw new Error('前进时间超时'); + } + + // 增加短暂延迟以避免过于频繁的检测 + await sleep(100); + } + } catch (error) { + keyUp("w"); // 确保释放按键 + throw error; + } finally { + keyUp("w"); // 确保释放按键 + } +} + +/** + * 调整视野直到图标位于正前方 + * @param {Object} boxIconRo - 宝箱图标识别对象 + * @param {CancellationToken} token - 取消令牌 + * @returns {Promise} + */ +async function adjustViewForReward(boxIconRo, token) { + const screenCenterX = 960; + const screenCenterY = 540; + const maxAngle = 10; // 最大允许偏离角度(度) + + for (let i = 0; i < 20; i++) { + // 检查是否取消操作 + if (token && token.isCancellationRequested) { + log.info("视角调整已取消"); + return false; + } + + let captureRegion = captureGameRegion(); + let iconRes = captureRegion.Find(boxIconRo); + + if (!iconRes) { + throw new Error('未找到图标,没有地脉花'); + } + + // 计算图标相对于屏幕中心的位置 + const xOffset = iconRes.x - screenCenterX; + const yOffset = screenCenterY - iconRes.y; // 注意:y坐标向下增加,所以翻转差值 + + // 计算图标与中心垂直线的角度 + const angleInRadians = Math.atan2(Math.abs(xOffset), yOffset); + const angleInDegrees = angleInRadians * (180 / Math.PI); + + // 检查图标是否在中心上方,且角度在允许范围内 + const isAboveCenter = iconRes.y < screenCenterY; + const isWithinAngle = angleInDegrees <= maxAngle; + + log.info(`图标位置: (${iconRes.x}, ${iconRes.y}), 角度: ${angleInDegrees.toFixed(2)}°`); + + if (isAboveCenter && isWithinAngle) { + log.info(`视野调整成功,图标角度: ${angleInDegrees.toFixed(2)}°,在${maxAngle}°范围内`); + return true; + } else { + keyUp("w"); // 确保停止前进 + // 调整视野方向,根据图标位置调整鼠标移动 + moveMouseBy(xOffset > 0 ? Math.min(xOffset, 300) : Math.max(xOffset, -300), 0); + await sleep(100); + + if (!isAboveCenter) { + log.warn("图标不在屏幕中心上方"); + // 尝试将视角向下调整 + moveMouseBy(0, 500); + await sleep(100); + } else if (!isWithinAngle) { + log.warn(`图标角度${angleInDegrees.toFixed(2)}°不在范围内`); + } } - await sleep(1000); // 检查间隔 } - log.warn("在超时时间内未检测到战斗结果"); + log.warn("调整视野20次后仍未成功"); return false; } @@ -1109,7 +1285,7 @@ async function closeCustomMarks() { click(60, 1020); await sleep(600); - let button = captureGameRegion().find(RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/open.png"),)); + let button = captureGameRegion().find(openRo); if (button) { marksStatus = false; log.info("关闭自定义标记"); @@ -1130,7 +1306,7 @@ async function openCustomMarks() { click(60, 1020); await sleep(600); - let button = captureGameRegion().findMulti(RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/close.png"),)); + let button = captureGameRegion().findMulti(closeRo); if (button) { for (let i = 0; i < button.count; i++) { let b = button[i]; @@ -1144,162 +1320,3 @@ async function openCustomMarks() { log.error("未找到开关按钮"); } } - -const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0); -const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/box.png")); -/** - * 自动导航到地脉花奖励点 - * @returns {Promise} - */ -async function autoNavigateToReward() { - // 定义识别对象 - - const cts = new CancellationTokenSource(); - - try { - // 调整初始视角为俯视角 - log.info("调整视角..."); - middleButtonClick(); - await sleep(300); - - log.info("开始自动导航到地脉花..."); - - // 启动文字检测任务 - let rewardDetectionPromise = startRewardTextDetection(cts); - - // 启动导航任务 - navigateTowardReward(cts.token); - - // 等待文字检测任务完成 - await rewardDetectionPromise; - - // 取消导航任务 - cts.cancel(); - keyUp("w"); - log.info("已到达领奖点"); - } catch (error) { - cts.cancel(); - log.error(`导航过程中出错: ${error}`); - throw error; - } -} - -/** - * 监测文字区域,检测到地脉之花文字时返回 - * @param {CancellationTokenSource} cts - 取消令牌源 - * @returns {Promise} - 是否检测到文字 - */ -async function startRewardTextDetection(cts) { - return new Promise((resolve, reject) => { - (async () => { - try { - while (!cts.token.isCancellationRequested) { - let captureRegion = captureGameRegion(); - let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 200); - let ocrResults = rewardTextArea.findMulti(RecognitionObject.ocrThis); - - if (ocrResults.count > 0) { - for (let i = 0; i < ocrResults.count; i++) { - if (ocrResults[i].text.includes("接触") - || ocrResults[i].text.includes("地脉") - || ocrResults[i].text.includes("之花") - ) { - log.info("检测到文字: " + ocrResults[i].text); - resolve(true); - return; - } - } - } - - await sleep(200); - } - } catch (error) { - reject(error); - } - })(); - }); -} - -/** - * 导航向奖励点 - * @param {CancellationToken} token - 取消令牌 - * @returns {Promise} - */ -async function navigateTowardReward(token) { - let advanceNum = 0; // 前进次数 - - try { - while (!token.isCancellationRequested) { - if (advanceNum % 15 == 0 && advanceNum >= 10) { - log.warn("前进又超时15次啦,先往旁边挪挪再继续试试") - keyUp("w"); - keyDown("s"); - await sleep(1000); - keyUp("s"); - keyDown("w"); // 恢复前进 - } - else if (advanceNum > 45) { - keyUp("w"); - throw new Error('前进时间超时'); - } - - // 调整视野 - await adjustViewForReward(boxIconRo, advanceNum); - keyDown("w"); - // 小暂停让角色移动 - await sleep(300); - advanceNum++; - } - } finally { - // 确保任何情况下都会释放按键 - keyUp("w"); - } -} - -/** - * 调整视野直到图标位于正前方 - * @param {Object} boxIconRo - 宝箱图标识别对象 - * @returns {Promise} - */ -async function adjustViewForReward(boxIconRo) { - const screenCenterX = 960; - const screenCenterY = 540; - - let captureRegion = captureGameRegion(); - let resList = captureRegion.findMulti(RecognitionObject.ocrThis) - if(captureRegion.Find(paimonMenuRo).IsEmpty()) { - log.info("误触发其他页面,尝试关闭页面") - await genshin.returnMainUi(); - } else if(resList.count > 0) { - for (let i = 0; i < resList.count; i++) { - let res = resList[i]; - if (res.text.includes("原粹树脂")) { - log.info("误触发领取页面,尝试关闭页面") - keyPress("ESCAPE"); - await sleep(500); - keyPress("ESCAPE"); - await sleep(500); - await genshin.returnMainUi(); - } - } - } - let iconRes = captureRegion.Find(boxIconRo); - - if (!iconRes) { - throw new Error('未找到图标,没有地脉花'); - } - - // 检查图标是否位于中心正上方 - const xOffset = iconRes.x - screenCenterX; - const horizontalTolerance = 40; - const isHorizontallyAligned = Math.abs(xOffset) <= horizontalTolerance; - const isAboveCenter = iconRes.y < screenCenterY; - - if (isHorizontallyAligned && isAboveCenter) { - return true; - } else { - moveMouseBy(xOffset, 0); - await sleep(100); - } -} - diff --git a/repo/js/AutoLeyLineOutcrop/manifest.json b/repo/js/AutoLeyLineOutcrop/manifest.json index 979278ae..8179d553 100644 --- a/repo/js/AutoLeyLineOutcrop/manifest.json +++ b/repo/js/AutoLeyLineOutcrop/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "全自动地脉花", - "version": "3.2", + "version": "3.3.0", "tags": ["地脉花"], "bgi_version": "0.44.7", "description": "基于OCR图像识别的全自动刷取地脉花。\n💡更多信息请查看在线手册:https://hcnsvf0s8d0s.feishu.cn/wiki/Tb1twpThLi7UlykqcYOcuccTnjJ \n\n----------注意事项----------\n●仅支持BetterGI 0.44.7 及以上版本!\n●部分地脉花因特殊原因不支持全自动,具体的点位请在手册中查看。\n●请确保队伍的练度足够,战斗时出现角色血量过低或倒下会无法继续。\n●树脂使用的优先级:2倍原粹树脂 > 浓缩树脂 > 原粹树脂。\n●运行时会传送到七天神像设置中设置的七天神像,可能需要关闭七天神像设置中的“是否就近七天神像恢复血量”,并指定七天神像。\n●战斗策略注意调度器设置中地图追踪行走配置里的“允许在JsSpript中使用”和“覆盖JS中的自动战斗配置”,只有在都打开的情况下脚本才会使用下面的战斗配置,否则会使用独立任务中的战斗策略。战斗超时时间不能大于脚本自定义配置中的时间。\n\n如果遇到问题,请先参照手册中的方法进行解决。",