From 1344e10dd1b27a9509972458c5362ade03d3410b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A9=BA?= <2296903176@qq.com> Date: Fri, 27 Mar 2026 11:43:16 +0800 Subject: [PATCH] =?UTF-8?q?JS=EF=BC=9A=E5=85=A8=E9=98=9F=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=9B=9E=E6=BB=A1=E5=A4=A7=E6=8B=9B=E8=83=BD=E9=87=8F=20(#3038?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加了几个开关以方便更多场景的需求 * style: 修改缩进 --------- Co-authored-by: 起个名字好难的喵 <25520958+MisakaAldrich@users.noreply.github.com> --- repo/js/全队自动回满大招能量/README.md | 18 + repo/js/全队自动回满大招能量/main.js | 367 +++++++++++---------- repo/js/全队自动回满大招能量/manifest.json | 30 +- repo/js/全队自动回满大招能量/settings.json | 36 +- 4 files changed, 260 insertions(+), 191 deletions(-) create mode 100644 repo/js/全队自动回满大招能量/README.md diff --git a/repo/js/全队自动回满大招能量/README.md b/repo/js/全队自动回满大招能量/README.md new file mode 100644 index 000000000..f3e4a1b42 --- /dev/null +++ b/repo/js/全队自动回满大招能量/README.md @@ -0,0 +1,18 @@ +# 大招能量一键拉满 +### 功能说明 +本工具需开启璃月远古副本使用,支持选择指定队伍完成全队大招能量自动回满操作(由空修改优化)。 + +### 开关说明 +| 开关名称 | 类型 | 默认值 | 功能说明 | +|--------|------|--------|----------| +| 切换队伍前回七天神像回血 | 复选框 | 开启 | 切换队伍前自动回血 | +| 开启第二次充能战斗(循环充能) | 复选框 | 关闭 | 额外执行一次充能 | +| 充能结束后回七天神像回血 | 复选框 | 开启 | 充能完成后自动回血 | +| 充能结束后传送到利亚姆传送点 | 复选框 | 关闭 | 充能完成后自动传送到枫丹利亚姆位置 | + +### 其他配置 +- **队伍名称**:输入指定队伍名称,不填则默认对当前队伍进行操作。 + +### 作者信息 +- 柒叶子:https://github.com/5117600049 +- 空:https://github.com/whd-dad \ No newline at end of file diff --git a/repo/js/全队自动回满大招能量/main.js b/repo/js/全队自动回满大招能量/main.js index 6edd1abde..4c5add606 100644 --- a/repo/js/全队自动回满大招能量/main.js +++ b/repo/js/全队自动回满大招能量/main.js @@ -1,179 +1,202 @@ (async function () { -//检测传送结束 await tpEndDetection(); -async function tpEndDetection() { - const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域 - const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭 - let tpTime = 0; - await sleep(1500);//点击传送后等待一段时间避免误判 - //最多30秒传送时间 - while (tpTime < 300) { - - let capture = captureGameRegion(); - let res1 = capture.find(region1); - let res2 = capture.find(region2); - capture.dispose(); - if (!res1.isEmpty() || !res2.isEmpty()){ - log.info("传送完成"); - await sleep(1000);//传送结束后有僵直 - click(960, 810);//点击任意处 - await sleep(500); - return; - } - tpTime++; - await sleep(100); - } - throw new Error('传送时间超时'); -} - -/** - * 自动导航直到检测到指定文字 - * @param {Object} options 配置选项 - * @param {number} [options.x=1210] 检测区域左上角x坐标 - * @param {number} [options.y=515] 检测区域左上角y坐标 - * @param {number} [options.width=200] 检测区域宽度 - * @param {number} [options.height=50] 检测区域高度 - * @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字 - * @param {number} [options.maxSteps=100] 最大检查次数 - * @param {number} [options.stepDuration=200] 每步前进持续时间(ms) - * @param {number} [options.waitTime=10] 单次等待时间(ms) - * @param {string} [options.moveKey="w"] 前进按键 - * @param {boolean} [options.ifClick=false] 是否点击 - * @returns {Promise} - * await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进 - * await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进 - *await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待 - */ -const repeatOperationUntilTextFound = async ({ - //默认区域为单个F图标右边的文字,最多6个 - x = 1210, - y = 515, - width = 200, - height = 50, - targetText = null, - maxSteps = 100, - stepDuration = 200, - waitTime = 10, - moveKey = "w", - ifClick = false, -} = {}) => { - /** - * 转义正则表达式中的特殊字符 - * @param {string} string 要转义的字符串 - * @returns {string} 转义后的字符串 - */ - const escapeRegExp = (string) => { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - }; - - // 预编译正则表达式(如果是字符串则转换并转义) - const textPattern = typeof targetText === 'string' - ? new RegExp(escapeRegExp(targetText)) - : targetText; - - let stepsTaken = 0; - - while (stepsTaken <= maxSteps) { - // 1. 捕获游戏区域并裁剪出检测区域 - const captureRegion = captureGameRegion(); - const textArea = captureRegion.DeriveCrop(x, y, width, height); - - // 2. 执行OCR识别 - const ocrResult = textArea.find(RecognitionObject.ocrThis); - captureRegion.dispose(); - textArea.dispose(); - - const hasAnyText = ocrResult.text.trim().length > 0; - const matchesTarget = targetText === null - ? hasAnyText - : textPattern.test(ocrResult.text); - - if (matchesTarget) { - log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`); - await sleep(1000); - if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2)); - return true; - } - - // 4. 检查步数限制 - if (stepsTaken >= maxSteps) { - throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`); - } - - // 5. 前进一小步 - if (stepDuration != 0) { - keyDown(moveKey); - await sleep(stepDuration); - keyUp(moveKey); - } - await sleep(waitTime); - stepsTaken++; - } -} - -//执行战斗并检测结束 -async function restoredEnergyAutoFightAndEndDetection() { -await genshin.tp(178.55,384.4); -await repeatOperationUntilTextFound();// -keyPress("F"); -await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击单人挑战 -await sleep(200); -click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗 -await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战 -await sleep(2000); -await tpEndDetection(); -keyDown("w"); -await sleep(200); -keyDown("SHIFT"); -await sleep(300); -keyUp("SHIFT"); -await sleep(500); -keyDown("SHIFT"); -await sleep(300); -keyUp("SHIFT"); -await sleep(1000); -keyDown("SHIFT"); -await sleep(300); -keyUp("SHIFT"); -await sleep(500); -keyUp("w"); -let challengeTime = 0; - //2分钟兜底 - while (challengeTime < 5000) { - for (let i = 1;i < 5; i++) { - keyPress(i.toString()); - await sleep(300); - leftButtonClick(); - await sleep(400); - keyDown("e"); - await sleep(400); - keyUp("e"); - await sleep(500); - leftButtonClick(); + //检测传送结束 await tpEndDetection(); + async function tpEndDetection() { + const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域 + const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭 + let tpTime = 0; + await sleep(1500);//点击传送后等待一段时间避免误判 + //最多30秒传送时间 + while (tpTime < 300) { + let capture = captureGameRegion(); + let res1 = capture.find(region1); + let res2 = capture.find(region2); + capture.dispose(); + if (!res1.isEmpty() || !res2.isEmpty()) { + log.info("传送完成"); + await sleep(1000);//传送结束后有僵直 + click(960, 810);//点击任意处 + await sleep(500); + return; + } + tpTime++; await sleep(100); - const ro = captureGameRegion(); - let res = ro.find(RecognitionObject.ocr(840, 935, 230, 40)); - ro.dispose(); - if (res.text.includes("自动退出")) { - log.info("检测到挑战成功"); - return; + } + throw new Error('传送时间超时'); + } + + /** + * 自动导航直到检测到指定文字 + * @param {Object} options 配置选项 + * @param {number} [options.x=1210] 检测区域左上角x坐标 + * @param {number} [options.y=515] 检测区域左上角y坐标 + * @param {number} [options.width=200] 检测区域宽度 + * @param {number} [options.height=50] 检测区域高度 + * @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字 + * @param {number} [options.maxSteps=100] 最大检查次数 + * @param {number} [options.stepDuration=200] 每步前进持续时间(ms) + * @param {number} [options.waitTime=10] 单次等待时间(ms) + * @param {string} [options.moveKey="w"] 前进按键 + * @param {boolean} [options.ifClick=false] 是否点击 + * @returns {Promise} + * await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进 + * await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字 + */ + const repeatOperationUntilTextFound = async ({ + x = 1210, + y = 515, + width = 200, + height = 50, + targetText = null, + maxSteps = 100, + stepDuration = 200, + waitTime = 10, + moveKey = "w", + ifClick = false, + } = {}) => { + const escapeRegExp = (string) => { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + }; + + const textPattern = typeof targetText === 'string' + ? new RegExp(escapeRegExp(targetText)) + : targetText; + + let stepsTaken = 0; + + while (stepsTaken <= maxSteps) { + const captureRegion = captureGameRegion(); + const textArea = captureRegion.DeriveCrop(x, y, width, height); + const ocrResult = textArea.find(RecognitionObject.ocrThis); + captureRegion.dispose(); + textArea.dispose(); + + const hasAnyText = ocrResult.text.trim().length > 0; + const matchesTarget = targetText === null + ? hasAnyText + : textPattern.test(ocrResult.text); + + if (matchesTarget) { + log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`); + await sleep(1000); + if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2)); + return true; + } + + if (stepsTaken >= maxSteps) { + throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`); + } + + if (stepDuration != 0) { + keyDown(moveKey); + await sleep(stepDuration); + keyUp(moveKey); + } + await sleep(waitTime); + stepsTaken++; + } + } + + //执行战斗并检测结束 + async function restoredEnergyAutoFightAndEndDetection() { + await genshin.tp(178.55, 384.4); + await repeatOperationUntilTextFound(); + keyPress("F"); + await repeatOperationUntilTextFound({ x: 1650, y: 1000, width: 160, height: 45, targetText: "单人挑战", stepDuration: 0, waitTime: 100, ifClick: true }); + await sleep(200); + click(1180, 760); + await repeatOperationUntilTextFound({ x: 1650, y: 1000, width: 160, height: 45, targetText: "开始挑战", stepDuration: 0, waitTime: 100, ifClick: true }); + await sleep(2000); + await tpEndDetection(); + keyDown("w"); + await sleep(200); + keyDown("SHIFT"); + await sleep(300); + keyUp("SHIFT"); + await sleep(500); + keyDown("SHIFT"); + await sleep(300); + keyUp("SHIFT"); + await sleep(1000); + keyDown("SHIFT"); + await sleep(300); + keyUp("SHIFT"); + await sleep(500); + keyUp("w"); + let challengeTime = 0; + while (challengeTime < 5000) { + for (let i = 1; i < 5; i++) { + keyPress(i.toString()); + await sleep(300); + leftButtonClick(); + await sleep(400); + keyDown("e"); + await sleep(400); + keyUp("e"); + await sleep(500); + leftButtonClick(); + await sleep(100); + const ro = captureGameRegion(); + let res = ro.find(RecognitionObject.ocr(840, 935, 230, 40)); + ro.dispose(); + if (res.text.includes("自动退出")) { + log.info("检测到挑战成功"); + return; } } - challengeTime = challengeTime + 200; - await sleep(100); - } -log.info("挑战超时,可能充能失败"); -} + challengeTime = challengeTime + 200; + await sleep(100); + } + log.info("挑战超时,可能充能失败"); + } -async function restoredEnergy() { -await genshin.returnMainUi(); - if(settings.teamName) await genshin.switchParty(settings.teamName); -await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色 -await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束 -await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束 - log.info("能量充满,任务结束"); -await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血 -} + // 回七天神像 + async function backToStatue() { + log.info("前往七天神像..."); + await genshin.tp(2297.6201171875, -824.5869140625); + await sleep(1500); + } -await restoredEnergy(); -})(); + async function restoredEnergy() { + await genshin.returnMainUi(); + + // 开关1 + if (settings.healBeforeSwitchTeam) { + await backToStatue(); + } + + // 切换队伍 + if (settings.teamName) { + await genshin.switchParty(settings.teamName); + await sleep(1000); + } + + // 充能战斗 + await restoredEnergyAutoFightAndEndDetection(); + + // 开关2 + if (settings.enableSecondFight) { + log.info("开启第二次充能战斗..."); + await restoredEnergyAutoFightAndEndDetection(); + } + + log.info("充能完成"); + + // 开关3:回血 + if (settings.backToStatueAfterCharge) { + await backToStatue(); + log.info("已返回七天神像回血"); + } + + // 开关4:充能结束后传送到利亚姆传送点 + if (settings.teleportToLiam) { + await genshin.tp(3035.6297000135746, 3727.409085358373); + await sleep(1500); + log.info("已传送到 利亚姆 传送点"); + } + + log.info("任务结束"); + } + + await restoredEnergy(); +})(); \ No newline at end of file diff --git a/repo/js/全队自动回满大招能量/manifest.json b/repo/js/全队自动回满大招能量/manifest.json index 1e7c3b054..f7787b19c 100644 --- a/repo/js/全队自动回满大招能量/manifest.json +++ b/repo/js/全队自动回满大招能量/manifest.json @@ -1,14 +1,18 @@ { - "manifest_version": 1, - "name": "大招能量一键拉满", - "version": "1.6", - "description": "需要开启璃月远古副本,可选择指定队伍", - "authors": [ - { - "name": "柒叶子", - "links": "https://github.com/5117600049" - } - ], - "settings_ui": "settings.json", - "main": "main.js" -} + "manifest_version": 1, + "name": "大招能量一键拉满", + "version": "1.7", + "description": "需要开启璃月远古副本,可选择指定队伍", + "authors": [ + { + "name": "柒叶子", + "links": "https://github.com/5117600049" + }, + { + "name": "空", + "links": "https://github.com/whd-dad" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} \ No newline at end of file diff --git a/repo/js/全队自动回满大招能量/settings.json b/repo/js/全队自动回满大招能量/settings.json index 635d7f76d..9edeec024 100644 --- a/repo/js/全队自动回满大招能量/settings.json +++ b/repo/js/全队自动回满大招能量/settings.json @@ -1,7 +1,31 @@ [ - { - "name": "teamName", - "type": "input-text", - "label": "请输入队伍名称(不填默认当前队伍)" - } - ] + { + "name": "teamName", + "type": "input-text", + "label": "请输入队伍名称(不填默认当前队伍)" + }, + { + "name": "healBeforeSwitchTeam", + "type": "checkbox", + "label": "切换队伍前回七天神像回血", + "default": true + }, + { + "name": "enableSecondFight", + "type": "checkbox", + "label": "开启第二次充能战斗(循环充能)", + "default": false + }, + { + "name": "backToStatueAfterCharge", + "type": "checkbox", + "label": "充能结束后回七天神像回血", + "default": true + }, + { + "name": "teleportToLiam", + "type": "checkbox", + "label": "充能结束后传送到利亚姆传送点", + "default": false + } +] \ No newline at end of file