const SettingsButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/SettingsButton.png"), 0, 650, 100, 300); const RestoreButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/RestoreButton.png"), 1400, 950, 130, 130); const MaterialsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/Materials.png"), 900, 0, 100, 100); const MaterialsSelectedRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/MaterialsSelected.png"), 900, 0, 100, 100); const SliderBottomRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/SliderBottom.png"), 1280, 110, 25, 845); const CabbageRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/Cabbage.png"), 110, 90, 1170, 875); const RadishRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/Radish.png"), 110, 90, 1170, 875); const AbandonCurrentHangoutEventButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/AbandonCurrentHangoutEventButton.png"), 1400, 950, 130, 130); const ConfirmButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/ConfirmButton.png")); const StoryQuestsButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/StoryQuestsButton.png"), 90, 900, 150, 150); const InProgressButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/InProgressButton.png"), 700, 0, 500, 100); const InProgressButtonSelectedRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/InProgressButtonSelected.png"), 700, 0, 500, 100); const HangoutEventButtonSelected1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/HangoutEventButtonSelected1.png"), 800, 0, 350, 100); const HangoutEventButtonSelected2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/HangoutEventButtonSelected2.png"), 800, 0, 350, 100); const HangoutEventButtonSelected3Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/HangoutEventButtonSelected3.png"), 800, 0, 350, 100); const PlusButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/PlusButton.png"), 0, 325, 140, 200); const MaxRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/Max.png"), 0, 325, 140, 200); const LocationButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/LocationButton.png"), 1300, 0, 450, 100); /** * @returns {Promise} */ (async function () { // 留空或曾经设定后删除运行30轮,否则为设定值 let runTimes = isNaN(settings.runTimes) || settings.runTimes === '' ? 50 : settings.runTimes; const messages = [ '1.请使用0.44.0或版本号更高的BetterGI运行本JS', `2.确保完成班尼特邀约任务`, `3.行走位最好是E恢复角色`, `4.关闭E技能回血'${settings.disableHealHP ? "是" : "否"}'`, `5.预期执行轮数'${runTimes}'`, ]; for (let message of messages) { log.info(message); await sleep(100); } const startTime = Date.now(); await genshin.returnMainUi(); // 实时拾取极速版 dispatcher.addTimer(new RealtimeTimer("AutoPick", { "forceInteraction": true })); // 拾取状态检测 let pickStatus = await UpperLimitPreciousChest(); // 没超过返回true,超过返回false // 默认按键状态检测 let keyStatus = await CheckKeyBindlings(); // 正常返回true,异常返回false log.warn(`启动预检测结束,本机当前状态:${keyStatus ? "正常" : "异常"},系统当前状态:${pickStatus ? "正常" : "异常"}`); if (keyStatus && pickStatus) { // 获取等效摩拉计算所需的物品初始数量 if (settings.mora) { const StartNum = await CabbageRadishNum(); RadishStartNum = StartNum.RadishNum; CabbageStartNum = StartNum.CabbageNum; } log.info(`开始刷取珍贵宝箱`); if (!settings.enableSimplifyMode) { await AutoPath(`前往地点${settings.disableFullMode ? "(工程模式)" : ""}`); } // let prePickStatus = true; // 测试用 let prePickStatus = await UpperLimitPreciousChest(); // 到达地点时检查一次 log.warn(`检查点检测结束,系统当前状态:${prePickStatus ? "正常" : "异常"}`); if (prePickStatus) { await AbandonHangoutEvent(0); // 防止之前没删任务,先删一遍 // 刷宝箱循环 const estimatedStartTime = Date.now(); for (let i = 0; i < runTimes; i++) { // 滑动接取邀约任务 await ReceiveHangoutEvent(i); // 路径执行前检查一次 await sleep(500); let pickStatusBefore = await UpperLimitPreciousChest(); log.warn(`第 ${i + 1} 次预检测结束,系统当前状态 ${pickStatus ? "正常" : "异常"}`); if (!pickStatusBefore) { log.warn("本次预检测到超过 3 次宝箱未成功拾取,推测已触发系统风控,主动终止运行"); break; } // 执行拾取宝箱路径 await AutoPath(`宝箱(${settings.disableHealHP ? "" : "HP版"}循环)`); // 路径完成后检查一次 let pickStatusAfter = await UpperLimitPreciousChest(); if (!pickStatusAfter) { log.warn(`第 ${i + 1} 次循环执行完成,本轮检测到超过 3 次宝箱未成功拾取,推测已触发系统风控,主动终止运行`); break; } // 统计用时 log.info(`已完成循环:${i + 1}/${runTimes}`); logTimeTaken(startTime); if (i > 1) { const estimatedCompletion = calculateEstimatedCompletion(estimatedStartTime, i + 1, runTimes); log.info(`预计完成时间:${estimatedCompletion}`); } // 开启任务菜单放弃邀约任务 await AbandonHangoutEvent(i + 1); } // 统计摩拉 if (settings.mora) { const EndNum = await CabbageRadishNum(); RadishEndNum = EndNum.RadishNum; CabbageEndNum = EndNum.CabbageNum; let totalSeconds = logTimeTaken(startTime); RadishMora = Math.ceil(RadishEndNum - RadishStartNum) * 315 CabbageMora = Math.ceil(CabbageEndNum - CabbageStartNum) * 105 log.info(`获得白萝卜:${RadishEndNum - RadishStartNum},卷心菜:${CabbageEndNum - CabbageStartNum}`); log.info(`等效摩拉:${RadishMora + CabbageMora},摩拉效率:${((RadishMora + CabbageMora) / totalSeconds).toFixed(2)} 摩拉/秒`); } } else { log.warn("检查点检测结束,检测未能通过: 宝箱未成功拾取,推测当前刷取的宝箱数量已触发风控,被系统禁止拾取,主动终止运行"); } } else if (!keyStatus) { log.warn("按键或识别出现异常,可能卡在某个交互页面中,请自行检查"); } else if (!pickStatus) { log.warn("当前刷取的宝箱已触发风控,被系统禁止拾取,主动终止运行"); } else { log.warn("按键或识别出现异常,请自行检查"); } // 以下部分为封装函数 // 默认按键检测 async function CheckKeyBindlings() { let keyStatus = false; for (let i = 0; i < 2; i++) { await TryOpenQuestMenu(0); let StoryQuestsButton = captureGameRegion().find(StoryQuestsButtonRo); if (StoryQuestsButton.isExist()) { log.info("检测到任务菜单已开启,按键正常"); await sleep(1000); keyStatus = true; } else { log.info("检测到任务菜单没有开启,推测快捷键不是默认值,尝试恢复"); await KeyBindlings(); // 恢复默认键位 await TryOpenQuestMenu(0); let StoryQuestsButton = captureGameRegion().find(StoryQuestsButtonRo); if (StoryQuestsButton.isExist()) { log.info("识别到传说任务按钮,按键正常"); await sleep(1000); keyStatus = true; } else { log.warn("尝试恢复任务菜单快捷键默认值失败"); keyStatus = false; } } break; } await genshin.returnMainUi(); await sleep(1000); return keyStatus; } // 尝试打开任务菜单 async function TryOpenQuestMenu() { keyPress("J"); await sleep(1500); } // 恢复任务菜单按键 async function KeyBindlings() { await genshin.returnMainUi(); await sleep(2000); keyPress("ESCAPE"); await sleep(2000); let SettingsButton = captureGameRegion().find(SettingsButtonRo); if (SettingsButton.isExist()) { log.info("识别到设置按钮"); SettingsButton.click(); await sleep(2000); let captureRegion = captureGameRegion(); let resList = captureRegion.findMulti(RecognitionObject.ocr(100, 100, 300, 300)); for (let i = 0; i < resList.count; i++) { let res = resList[i]; if (res.text.includes("Key") || res.text.includes("Bindings") || res.text.includes("按键") || res.text.includes("按鍵")) { log.info("按键选项卡位置:({x},{y},{h},{w}), 文本{text}", res.x, res.y, res.width, res.Height, res.text); res.click(); await sleep(2000); let RestoreButton = captureGameRegion().find(RestoreButtonRo); if (RestoreButton.isExist()) { log.info("识别到恢复默认按钮"); RestoreButton.click(); await sleep(1500); let ConfirmButton = captureGameRegion().find(ConfirmButtonRo); if (ConfirmButton.isExist()) { log.info("识别到确认按钮"); ConfirmButton.click(); await sleep(1500); } } } } } await genshin.returnMainUi(); } // 识别卷心菜、白萝卜数量 async function CabbageRadishNum() { setGameMetrics(1920, 1080, 1); await genshin.returnMainUi(); await sleep(1000); keyPress("B"); await sleep(1500); let RadishNum = 0 let CabbageNum = 0 let Materials = captureGameRegion().find(MaterialsRo); let MaterialsSelected = captureGameRegion().find(MaterialsSelectedRo); if (Materials.isExist()) { log.info("识别到材料按钮"); Materials.click(); await sleep(1500); } else if (MaterialsSelected.isExist()) { log.info("识别到材料按钮"); MaterialsSelected.click(); await sleep(1500); } for (let i = 0; i < 10; i++) { let Radish = captureGameRegion().find(RadishRo); if (Radish.isExist()) { // log.info("识别到白萝卜,进行OCR获取数量"); let resList = captureGameRegion().findMulti(RecognitionObject.ocr(Radish.x, Radish.y + Radish.width, Radish.Width, Radish.Height)); for (let i = 0; i < resList.count; i++) { let Radish = resList[i]; log.info("白萝卜识别结果:({x},{y},{h},{w}), 数量:{text}", Radish.x, Radish.y, Radish.Width, Radish.Height, Radish.text); RadishNum = Radish.text break; } } let Cabbage = captureGameRegion().find(CabbageRo); if (Cabbage.isExist()) { // log.info("识别到卷心菜,进行OCR获取数量"); let resList = captureGameRegion().findMulti(RecognitionObject.ocr(Cabbage.x, Cabbage.y + Cabbage.width, Cabbage.Width, Cabbage.Height)); for (let i = 0; i < resList.count; i++) { let Cabbage = resList[i]; log.info("卷心菜识别结果:({x},{y},{h},{w}), 数量:{text}", Cabbage.x, Cabbage.y, Cabbage.Width, Cabbage.Height, Cabbage.text); CabbageNum = Cabbage.text break; } break; } let SliderBottom = captureGameRegion().find(SliderBottomRo); if (SliderBottom.isExist()) { log.info("识别到滑块,当前页面没有目标物品,向下滑动"); // log.info("滑块当前位置:({x},{y},{h},{w})", SliderBottom.x, SliderBottom.y, SliderBottom.Width, SliderBottom.Height); click(Math.ceil(SliderBottom.x + SliderBottom.Width / 2), Math.ceil(SliderBottom.y + SliderBottom.Height + SliderBottom.Height / 2)); await moveMouseTo(0, 0) await sleep(250); } } await genshin.returnMainUi(); // log.info(`白萝卜数量:${RadishNum},卷心菜数量: ${CabbageNum}`) return { RadishNum, CabbageNum }; } // 接取任务 async function ReceiveHangoutEvent(times) { setGameMetrics(3840, 2160, 2); for (let r = 0; r < 7; r++) { await sleep(500); click(3724, 786); // 切角色邀约,第七次切到班尼特 } await sleep(1000); click(3120, 920); if (times == 0) { await sleep(500); await moveMouseTo(105, 787) for (let p = 0; p < 40; p++) { let captureRegion = captureGameRegion(); let MaxStatus = captureRegion.Find(MaxRo); if (MaxStatus.isEmpty()) { click(105, 787); // 调整缩放到最大 } else if (MaxStatus.isExist()) { log.info("缩放已调整至最大"); break; } await sleep(40); } } if (times == 0) { let LocationButton = captureGameRegion().find(LocationButtonRo); if (LocationButton.isExist()) { log.info("识别到定位当前节点按钮"); LocationButton.click(); await sleep(1000); } } await sleep(1000); click(3500, 1330); // 蒲公英原始坐标 await sleep(500); click(3500, 2030); // 继续按钮 } // 开任务列表删任务 async function AbandonHangoutEvent(times) { keyPress("J"); await sleep(1500); if (times == 0) { let InProgressButton = captureGameRegion().find(InProgressButtonRo); if (InProgressButton.isExist()) { log.info("识别到进行中任务按钮"); InProgressButton.click(); await sleep(1500); } } let AbandonCurrentHangoutEventButton = captureGameRegion().find(AbandonCurrentHangoutEventButtonRo); if (AbandonCurrentHangoutEventButton.isExist()) { log.info("识别到放弃按钮"); AbandonCurrentHangoutEventButton.click(); await sleep(1500); } let ConfirmButton = captureGameRegion().find(ConfirmButtonRo); if (ConfirmButton.isExist()) { log.info("识别到确认按钮"); ConfirmButton.click(); await sleep(1500); } let StoryQuestsButton = captureGameRegion().find(StoryQuestsButtonRo); if (StoryQuestsButton.isExist()) { log.info("识别到传说任务按钮"); StoryQuestsButton.click(); await sleep(1500); } if (times == 0) { let captureRegion = captureGameRegion(); let HangoutEventButtonSelected1 = captureRegion.find(HangoutEventButtonSelected1Ro); let HangoutEventButtonSelected2 = captureRegion.find(HangoutEventButtonSelected2Ro); let HangoutEventButtonSelected3 = captureRegion.find(HangoutEventButtonSelected3Ro); if (HangoutEventButtonSelected1.isExist()) { log.info("识别到邀约事件选项卡按钮"); HangoutEventButtonSelected1.click(); await sleep(1500); } else if (HangoutEventButtonSelected2.isExist()) { log.info("识别到邀约事件选项卡按钮"); HangoutEventButtonSelected2.click(); await sleep(1500); } else if (HangoutEventButtonSelected3.isExist()) { log.info("识别到邀约事件选项卡按钮"); HangoutEventButtonSelected3.click(); await sleep(1500); } } } // 检测宝箱是否已到上限 async function UpperLimitPreciousChest() { let pickStatus = true; let failureCount = 0; // 计数检测到宝箱失败的次数 for (let p = 0; p < 5; p++) { let captureRegion = captureGameRegion(); let resList = captureRegion.findMulti(RecognitionObject.ocr(960, 0, 650, 1080)); for (let i = 0; i < resList.count; i++) { let res = resList[i]; if (res.text.includes("Precious") || res.text.includes("Chest") || res.text.includes("箱") || res.text.includes("珍貴") || res.text.includes("珍贵")) { failureCount++; log.warn(`检测到宝箱未被拾取 (${failureCount}/5) 次`); if (failureCount >= 3) { // log.warn("检测到超过 3 次宝箱未成功拾取,推测已触发系统风控,主动终止运行"); pickStatus = false; break; } } } } return pickStatus; } // 执行路径 async function AutoPath(locationName) { try { let filePath = `assets/AutoPath/${locationName}.json`; await pathingScript.runFile(filePath); } catch (error) { log.error(`执行 ${locationName} 路径时发生错误`); log.error(error.message); } await sleep(2000); } // 运行用时 function logTimeTaken(startTime) { const currentTime = Date.now(); const totalTimeInSeconds = (currentTime - startTime) / 1000; const minutes = Math.floor(totalTimeInSeconds / 60); const seconds = totalTimeInSeconds % 60; const formattedTime = `${minutes}分${seconds.toFixed(0).padStart(2, '0')}秒`; log.info(`当前运行总时长:${formattedTime}`); return totalTimeInSeconds; } // 预估时间 function calculateEstimatedCompletion(estimatedStartTime, current, total) { if (current === 0) return "计算中..."; const elapsedTime = Date.now() - estimatedStartTime; const timePerTask = elapsedTime / current; const remainingTasks = total - current; const remainingTime = timePerTask * remainingTasks; const completionDate = new Date(Date.now() + remainingTime); return `${completionDate.toLocaleTimeString()} (约 ${Math.round(remainingTime / 60000)} 分钟)`; } })();