From 496c6203e08c0e993d6b1e451a093cc90a8e79d5 Mon Sep 17 00:00:00 2001 From: mno <718135749@qq.com> Date: Fri, 5 Sep 2025 02:11:19 +0800 Subject: [PATCH] repo/js/ArtifactsGroupPurchasing: error handling improvements and bug fixes for the multiplayer coordination functionality (#1820) --- repo/js/ArtifactsGroupPurchasing/README.md | 31 +++- .../assets/RecognitionObject/0P.png | Bin 0 -> 1789 bytes repo/js/ArtifactsGroupPurchasing/main.js | 151 ++++++++++++------ .../js/ArtifactsGroupPurchasing/manifest.json | 2 +- 4 files changed, 130 insertions(+), 54 deletions(-) create mode 100644 repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/0P.png diff --git a/repo/js/ArtifactsGroupPurchasing/README.md b/repo/js/ArtifactsGroupPurchasing/README.md index 4109d997f..29579ca4b 100644 --- a/repo/js/ArtifactsGroupPurchasing/README.md +++ b/repo/js/ArtifactsGroupPurchasing/README.md @@ -1,10 +1,31 @@ -联机狗粮js测试版本,出什么bug都是正常的,加qq718135749去超市作者 +## 琪零、功能及部分原理说明 -路线有问题去超市汐佬 +1. 配合AAA狗粮批发实现联机收尾(需要在AAA狗粮批发中勾选联机狗粮选项),增加收尾路线数量,获取更高收益 +2. 支持半自动和全自动模式 +3. 半自动模式(手动进入后运行):所有人手动进入世界后开启js,队员将自动前往占位点,房主将在识别到队员到位后按照人数执行对应的收尾路线,并在结束后自动解散队伍 +4. 全自动模式(按照下列配置自动进入并运行):参考下列配置配置好后,自动依次进入各个成员的世界 +5. 原理:等汐佬来写,我摆了 -配置方法:啥也不用配置,记得在狗粮批发那边勾选联机狗粮选项 +## 芽一、使用方法及配置 -分解和统计功能暂时懒得做,经验自己数 +1. 将脚本添加至调度器。 +2. 右键点击脚本以修改 JS 自定义配置。 -组队和更多使用说明请加qq群1057307824 +* **运行额外路线**:勾选后,处于单人世界时将执行额外路线,全自动模式下所有人的收尾路线完成后将回到自己世界执行额外路线 +* **联机运行配置**:找到至多不超过四个人组队(参考运行时间,双人2✖19分钟,三人3✖40分钟,四人4✖50分钟),确定好每个成员的序号,按照序号填写对应的uid和名称,名称以申请进入世界界面为准,如果存在特殊字符等难以识别的情况,建议修改备注。更多细节可以加入qq群1057307824了解。 +* **运行时使用队伍\&运行时使用角色的序号**:用于指定使用的角色,推荐的角色有:万叶(吸取掉落物),迪希雅(传奇耐肘王) +* **路线选择**:非必要不建议更改,确实需要更改时建议多加尝试 +* **调试时勾选,跳过路线执行逻辑** :用于调试,初次组队时建议先找个时间勾选该选项运行一次,确保流程正常后再全自动运行。此外,当狗粮批发运行路线为收尾额外B时,无法获取联机狗粮收益,也可以勾选该选项来跳过你的路线,节约时间。 + +## 三、其他建议 + +* **为万叶佩戴生命主词条或武器**:多人运行时队伍中只有一个角色,一旦角色死亡将进入复苏界面,bgi暂时没有对该界面进行处理 +* **ocr识别失败**:等待队员进入世界时会识别申请列表中的名字,部分设备或环境可能ocr效果不好,当你发现无法正常让队友进入时,可以截图申请进入的界面(参考js文件夹中的targets文件夹中图片),截图并裁剪只剩下名字部分后放入该文件夹 +* **预留足够的背包空间**:运行AAA狗粮批发将获取约150个圣遗物,运行本js将获取约230个圣遗物,请确保你的背包有足够的空间容纳这些圣遗物,建议在AAA狗粮批发中选择分解或摧毁,并预留250+的空间 + +## 更新日志 + +### 1.2.0(2025.09.03) + +1.增加几处错误处理,增加容错 diff --git a/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/0P.png b/repo/js/ArtifactsGroupPurchasing/assets/RecognitionObject/0P.png new file mode 100644 index 0000000000000000000000000000000000000000..a1afdd58dc65f0f6fdfde699f9f70631b3ba19b2 GIT binary patch literal 1789 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D29ilcK~zXfm6ls? z6WbNXe|s*T@pzor@jJ1>5@j4*4}%q|N5_+g_264U~A)Fyn6Kt&-2iA zou#EEu3o(gLV&J=5+L~hM1c(8?c29}`0$oSvw>2I>({Sy<;oR=5C|10)vBKG&tD(X zZnv>*o6DCkb8&e&G9vn3qu*{j!1sNc&E~)nkW41AEDJkn{twVFfH!Yn@bcwLgb<|N zG~fUJdxo=FG$icp?csSIVHk4${CO^3ya-BtD2e;%^Qlv zBFD8RVGu>%7tMe|p}@?{44&uFJZVxYl>iX+gJbph@nc$kom?)*{rmR^wD+V{ud};% z7)L!kJd6;6WHJdryWNgi3B!3e_DBP zbaaG~&{$epVq|$;4LWJo4cxUP#* zib|zIwOYk>(_`c7%bLYtACP*Xb9D5DR;vZ*fKI4%&QH$bq=u>0 zPtcT77=}SAl^VQO&}y|1Lg2b?q|<>x8rk07#`k?XoetaE+oxJDlwcSJLqkIthQaai zaSTw?G<03ZG)G9(fSs?{pK@Au9$Ow%NpOb!5;W>l#tK!$WW9i)`Fu8VFMv0-#5ZhvBZeVt0B z0zkLhrC2QTqSxpCtLfs~Z?N_2yW=6+)&4U>EK?y<76!i5V3Mn}i6EQ?0HP9l-OO@Gy8 z!29>_3BwTW+|(pTwJM+YN`qlyCvb++)S3+f6sBWC!az@1-2dYr`1z-w$vP202sEWgXgX)dC-8kAp$UX0D3{ArDivmDXVK>8=W$(^Mx%iW zqkO6`nhoRQ<1`u#WFTR%9w-zFe0SwL9LK>lOco1^EMHzmw|Z3f?shxfs0*Zw6e`kH zt5qDwVPRnbEt}0UJw46A!9iRLLW}a8a#LuU7WHtSYYE9AhsDLkcurisc6H#q=LiL1 z7-G4}!CDvK`#w86JLL0u&WxOiuoemhlu{fX9>y#nA|_I)6sBnsc+nu~FN67oc{0P% zpqZVW9RO4ibbUI_HfC=VVrnF;1Tv5uA0MNXVs35@`ebFZS;of3*xcNtR;%?aMTF>Dh;6`ji{eYTs7^gZczy3}elHa0fM=kw%pIZz;^ z3YF)32np}rz2oHMgj=_6MZ-x%5lJc0g&r4aUxH4=iea13OLHv!{~|qo$M5js#S3iP z=F+80I4KAC3V>}`v|25S#Uh4bFgZDi<2W&hZ%OU23P5Ho`)vRr$gYo+lHU$WwA*bK z78bB=JDMK?G(~S02(WFN$;nA9%c5SdM{Bgd>%SrE>+7tqum89Dlt?M@eIH%dnVOm+ fnM@*jQxxDoPTT#fOPe=%00000NkvXXu0mjfiX2gv literal 0 HcmV?d00001 diff --git a/repo/js/ArtifactsGroupPurchasing/main.js b/repo/js/ArtifactsGroupPurchasing/main.js index 19d9e4173..929d4813f 100644 --- a/repo/js/ArtifactsGroupPurchasing/main.js +++ b/repo/js/ArtifactsGroupPurchasing/main.js @@ -1,5 +1,8 @@ +const runExtra = settings.runExtra || false; +const leaveTeamRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/leaveTeam.png")); + (async function () { - if (settings.groupMode != "按照下列配置自动进入并运行") await runGroupPurchasing(); + if (settings.groupMode != "按照下列配置自动进入并运行") await runGroupPurchasing(runExtra); if (settings.groupMode != "手动进入后运行") { await switchPartyIfNeeded(settings.partyName) //解析与输出自定义配置 @@ -26,27 +29,29 @@ //构造加入idx号世界的autoEnter的settings let autoEnterSettings; if (idx === yourIndex) { - // 构造房主配置 - autoEnterSettings = { - enterMode: "等待他人进入", - permissionMode: "白名单", - timeout: 5, - maxEnterCount: enteringIndex.length - 1 - }; + // 1. 先收集真实存在的白名单 const permits = {}; let permitIndex = 1; for (const otherIdx of enteringIndex) { if (otherIdx !== yourIndex) { const pName = settings[`p${otherIdx}Name`]; - if (pName) { + if (pName) { // 过滤掉空/undefined permits[`nameToPermit${permitIndex}`] = pName; permitIndex++; } } } - // 把构造的permits合并到autoEnterSettings中 + + // 2. 用真正写进去的人数作为 maxEnterCount + autoEnterSettings = { + enterMode: "等待他人进入", + permissionMode: "白名单", + timeout: 5, + maxEnterCount: Object.keys(permits).length + }; + Object.assign(autoEnterSettings, permits); - log.info(`等待他人进入自己世界`); + log.info(`等待他人进入自己世界,白名单人数:${autoEnterSettings.maxEnterCount}`); } else { // 构造队员配置 autoEnterSettings = { @@ -57,7 +62,6 @@ log.info(`将要进入序号${idx},uid为${settings[`p${idx}UID`]}的世界`); } let attempts = 0; - const leaveTeamRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/leaveTeam.png")); while (attempts < 5) { attempts++; await autoEnter(autoEnterSettings); @@ -68,20 +72,29 @@ } else { //进入了错误的世界,退出世界并重新加入,最后一次不检查 await sleep(1000); - await findAndClick(leaveTeamRo); - await waitForMainUI(true); - continue; + let leaveAttempts = 0; + while (leaveAttempts < 10) { + leaveAttempts++; + if (await getPlayerSign() === 0) { + break; + } + await keyPress("F2"); + await sleep(1000); + await findAndClick(leaveTeamRo); + await waitForMainUI(true); + await genshin.returnMainUi(); + } } } else { break; } } //执行对应的联机狗粮 - await runGroupPurchasing(); + await runGroupPurchasing(false); } //如果勾选了额外,在结束后再执行一次额外路线 if (settings.runExtra) { - await runGroupPurchasing(); + await runGroupPurchasing(runExtra); } } } @@ -113,13 +126,12 @@ async function checkP1Name(p1Name) { * 群收尾 / 额外路线统一入口 * */ -async function runGroupPurchasing() { +async function runGroupPurchasing(runExtra) { // ===== 1. 读取配置 ===== const p1EndingRoute = settings.p1EndingRoute || "枫丹高塔"; const p2EndingRoute = settings.p2EndingRoute || "度假村"; const p3EndingRoute = settings.p3EndingRoute || "智障厅"; const p4EndingRoute = settings.p4EndingRoute || "踏鞴砂"; - const runExtra = settings.runExtra || false; const forceGroupNumber = settings.forceGroupNumber || 0; // ===== 2. 图标模板 ===== @@ -137,6 +149,7 @@ async function runGroupPurchasing() { // ===== 4. 主流程 ===== setGameMetrics(1920, 1080, 1); await genshin.clearPartyCache(); + log.info("开始识别队伍编号"); let groupNumBer = await getPlayerSign(); if (groupNumBer !== 0) log.info(`在队伍中编号为${groupNumBer}`); else log.info(`不处于联机模式或识别异常`); @@ -163,7 +176,7 @@ async function runGroupPurchasing() { await findAndClick(confirmKickRo); await waitForMainUI(true); await genshin.returnMainUi(); - if (getPlayerSign() != 0) { + if (await getPlayerSign() === 0) { await genshin.returnMainUi(); break; } @@ -172,11 +185,29 @@ async function runGroupPurchasing() { } } else if (groupNumBer > 1) { await goToTarget(groupNumBer); - if (await waitForMainUI(false, 2 * 60 * 60 * 1000)) { + let start = new Date(); + while (new Date() - start < 2 * 60 * 60 * 1000) { + if (await waitForMainUI(false, 60 * 60 * 1000)) { + await waitForMainUI(true); + await genshin.returnMainUi(); + if (await getPlayerSign() === 0) { + break; + } + } + } + if (new Date() - start > 2 * 60 * 60 * 1000) { + log.warn("超时仍未回到主界面,主动退出"); + } + let leaveAttempts = 0; + while (leaveAttempts < 10) { + if (await getPlayerSign() === 0) { + break; + } + await keyPress("F2"); + await sleep(1000); + await findAndClick(leaveTeamRo); await waitForMainUI(true); await genshin.returnMainUi(); - } else { - log.info("超时仍未回到主界面,主动退出"); } } else if (runExtra) { log.info("请确保联机收尾已结束,将开始运行额外路线"); @@ -652,6 +683,21 @@ async function autoEnter(autoEnterSettings) { await waitForMainUI(true, 20 * 1000); } else { // 等待他人进入 + const playerSign = await getPlayerSign(); + if (playerSign > 1) { + log.warn("处于他人世界,先尝试退出"); + let leaveAttempts = 0; + while (leaveAttempts < 10) { + if (await getPlayerSign() === 0) { + break; + } + await keyPress("F2"); + await sleep(1000); + await findAndClick(leaveTeamRo); + await waitForMainUI(true); + await genshin.returnMainUi(); + } + } if (enterCount >= maxEnterCount) break; if (await isYUI()) keyPress("VK_ESCAPE"); await sleep(500); await genshin.returnMainUi(); @@ -836,38 +882,47 @@ async function isMainUI() { return false; // 发生异常时返回 false } attempts++; // 增加尝试次数 - await sleep(50); // 每次检测间隔 50 毫秒 + await sleep(250); // 每次检测间隔 250 毫秒 } return false; // 如果尝试次数达到上限或取消,返回 false } //获取联机世界的当前玩家标识 async function getPlayerSign() { - const picDic = { - "1P": "assets/RecognitionObject/1P.png", - "2P": "assets/RecognitionObject/2P.png", - "3P": "assets/RecognitionObject/3P.png", - "4P": "assets/RecognitionObject/4P.png" + let attempts = 0; + let result = 0; + while (attempts < 5) { + attempts++; + const picDic = { + "0P": "assets/RecognitionObject/0P.png", + "1P": "assets/RecognitionObject/1P.png", + "2P": "assets/RecognitionObject/2P.png", + "3P": "assets/RecognitionObject/3P.png", + "4P": "assets/RecognitionObject/4P.png" + } + await genshin.returnMainUi(); + await sleep(500); + const p0Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["0P"]), 344, 22, 45, 45); + const p1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["1P"]), 344, 22, 45, 45); + const p2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["2P"]), 344, 22, 45, 45); + const p3Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["3P"]), 344, 22, 45, 45); + const p4Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["4P"]), 344, 22, 45, 45); + moveMouseTo(1555, 860); // 移走鼠标,防止干扰识别 + const gameRegion = captureGameRegion(); + // 当前页面模板匹配 + let p0 = gameRegion.Find(p0Ro); + let p1 = gameRegion.Find(p1Ro); + let p2 = gameRegion.Find(p2Ro); + let p3 = gameRegion.Find(p3Ro); + let p4 = gameRegion.Find(p4Ro); + gameRegion.dispose(); + if (p0.isExist()) { result = 0; break; } + if (p1.isExist()) { result = 1; break; } + if (p2.isExist()) { result = 2; break; } + if (p3.isExist()) { result = 3; break; } + if (p4.isExist()) { result = 4; break; } } - await genshin.returnMainUi(); - await sleep(500); - const p1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["1P"]), 344, 22, 45, 45); - const p2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["2P"]), 344, 22, 45, 45); - const p3Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["3P"]), 344, 22, 45, 45); - const p4Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["4P"]), 344, 22, 45, 45); - moveMouseTo(1555, 860); // 移走鼠标,防止干扰识别 - const gameRegion = captureGameRegion(); - // 当前页面模板匹配 - let p1 = gameRegion.Find(p1Ro); - let p2 = gameRegion.Find(p2Ro); - let p3 = gameRegion.Find(p3Ro); - let p4 = gameRegion.Find(p4Ro); - gameRegion.dispose(); - if (p1.isExist()) return 1; - if (p2.isExist()) return 2; - if (p3.isExist()) return 3; - if (p4.isExist()) return 4; - return 0; + return result; } async function findTotalNumber() { diff --git a/repo/js/ArtifactsGroupPurchasing/manifest.json b/repo/js/ArtifactsGroupPurchasing/manifest.json index 0c8fb292b..9de10f14c 100644 --- a/repo/js/ArtifactsGroupPurchasing/manifest.json +++ b/repo/js/ArtifactsGroupPurchasing/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "AAA狗粮联机团购", - "version": "1.1.2", + "version": "1.2.0", "tags": [ "狗粮" ],