From 5f26c7270319d186eb7d628085bf2ba8f6cccca6 Mon Sep 17 00:00:00 2001 From: Patrick-Ze <19711799+Patrick-Ze@users.noreply.github.com> Date: Sun, 6 Jul 2025 13:52:25 +0800 Subject: [PATCH] =?UTF-8?q?js:=20GeniusInvokationTCGQuickProficiency:=20?= =?UTF-8?q?=E4=B8=83=E5=9C=A3=E5=8F=AC=E5=94=A4=E5=8D=A1=E7=89=8C=E7=86=9F?= =?UTF-8?q?=E7=BB=83=E5=BA=A6=E9=80=9F=E5=88=B7=20(#1282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../README.md | 45 +++ .../assets/ALT点击.json | 2 + .../assets/ALT释放.json | 2 + .../assets/AdventurersGuild.png | Bin 0 -> 1965 bytes .../assets/RewardIcon.png | Bin 0 -> 1339 bytes .../assets/TavernIcon.png | Bin 0 -> 1579 bytes .../lib/ocr.js | 97 ++++++ .../main.js | 326 ++++++++++++++++++ .../manifest.json | 15 + .../settings.json | 17 + 10 files changed, 504 insertions(+) create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/README.md create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT点击.json create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT释放.json create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/assets/AdventurersGuild.png create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/assets/RewardIcon.png create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/assets/TavernIcon.png create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/lib/ocr.js create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/main.js create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/manifest.json create mode 100644 repo/js/GeniusInvokationTCGQuickProficiency/settings.json diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/README.md b/repo/js/GeniusInvokationTCGQuickProficiency/README.md new file mode 100644 index 00000000..a0dfc295 --- /dev/null +++ b/repo/js/GeniusInvokationTCGQuickProficiency/README.md @@ -0,0 +1,45 @@ +你是否也曾为了收集卡牌特效,周旋于各个智障NPC牌局中? + +你是否也曾看着拖拖拉拉的打牌动画而感慨生命的浪费? + +来了,它来了,它在BetterGI的加持下向我们走来了! + +## 使用说明 + +此脚本利用联机对局快速提升卡牌的好感度。需要你有两个游戏账号,其中待提升熟练度的号以下称为**大号**,用来在对局中投降的号以下称为**小号**。 + +可以[点击这个链接查看使用效果视频](https://github.com/babalae/bettergi-scripts-list/pull/1282)(如果你能打开网页的话) + +**注意: 通过投降来刷熟练度,会产生连续的对局记录。如果你担心重复投降会带来账号风险,请不要使用。** + +> 在运行脚本前,最好确保你已在Better GI的`快捷键`设置中设置了用于`停止当前脚本`的快捷键 + +1. 用小号申请加入大号的世界,进入双人联机模式。 + +2. **可选**:如果你有两台电脑,那可以在登录了小号的电脑上运行此脚本,运行模式选择`作为辅助账号:快速投降`。 + + 如果你只有一台电脑,则需要用手机或者平板等设备登录小号,每次收到大号的打牌邀请时手动点击同意,并在进入牌局后快速投降。 + +3. 在登录了大号的电脑上运行此脚本,首次运行建议先选择`扫描当前账号卡牌熟练度`模式,扫描的数据将用于为后续刷熟练度的角色规划提供建议。 + +4. 在登录了大号的电脑上,以`作为主账号:速刷熟练度`模式运行脚本。 + +## 常见问题 + +- **如果只有一台电脑怎么办?** + + 原神不允许在同一台物理设备上多开,即使多个Windows账号也不行(曾经有短暂的时间段开放了多开,但是很快又关闭了并直到现在)。 + + 这种情况只能手动操作小号投降(见[使用说明](#使用说明))。由于发起邀请的角色(大号)操作会更复杂,建议用脚本来发起邀请。 + +- **只有一台电脑,能不能运行一个原神,运行一个云原神?** + + Better GI在检测到已经有运行中的实例时不会再启动第二份,但并不像原神那样针对同一台物理设备。我也尝试过通过创建多个Windows账号,以双开Better GI,但是后启动的截图器会报错,可能是无法同时运行多个截图器。 + + 因此目前来看也是不行的(如果你找到了解决方法,欢迎[来Github反馈 😊](https://github.com/babalae/bettergi-scripts-list/issues),最好@一下我 `@Patrick-Ze`) + +- **能不能在当前队伍角色的熟练度刷满后,自动切换熟练度未满的角色?** + + 七圣召唤的牌组编辑界面不显示卡牌名,下方的卡牌槽不随详情页切换卡牌而滚动,换卡必须拖拽到目标位置。要实现自动切换角色,必须图像识别所有角色卡牌,过于离谱了。因此目前不支持此功能。 + + 考虑到原神大概率也不会改进七圣召唤的UI和UX,未来也不会支持此功能。 diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT点击.json b/repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT点击.json new file mode 100644 index 00000000..06142456 --- /dev/null +++ b/repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT点击.json @@ -0,0 +1,2 @@ +{"macroEvents":[{"type":0,"keyCode":164,"mouseX":0,"mouseY":0,"time":100}], +"info":{"name":"","description":"","x":0,"y":0,"width":1920,"height":1080,"recordDpi":1}} \ No newline at end of file diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT释放.json b/repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT释放.json new file mode 100644 index 00000000..6ad3a230 --- /dev/null +++ b/repo/js/GeniusInvokationTCGQuickProficiency/assets/ALT释放.json @@ -0,0 +1,2 @@ +{"macroEvents":[{"type":1,"keyCode":164,"mouseX":0,"mouseY":0,"time":100}], +"info":{"name":"","description":"","x":0,"y":0,"width":1920,"height":1080,"recordDpi":1}} \ No newline at end of file diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/assets/AdventurersGuild.png b/repo/js/GeniusInvokationTCGQuickProficiency/assets/AdventurersGuild.png new file mode 100644 index 0000000000000000000000000000000000000000..dd41ea3ba088bea47a5bcdd7583718a69581945e GIT binary patch literal 1965 zcmV;e2U7TnP)P000>X1^@s6#OZ}&00005W@JS}QY4e( z0OhMV@X6oRCod9#ACE2XWD?_`+2Ks^VsLKZN#=SGf4)?*tTukwh@eC+h*H5 zeXPx@dY^CAkF(ZZy*im_rf0tQ$8}LE6*Yd%K!cD$R{I-pD6D@o}!X<{C1Y46UuL_`VN7qEe~Q+0{jNcMnZX zO)OlvkT47ZNQem8Y?ijRHim|V_{A@N!B2nsQ?_p1%8COH!`yuH&3ydhA7{so9ZZ=r z1yv=EV*n;qt5s&roJnhI3jhZlbP&cERFzs$6p_tl$>nlfe);A6;upVQ+qP|(&wcK5 zY}>YtIdkT6_~D1s)zwWL$5bj6;yA`yOD>mV*|KG{wzdMWV#NyD+uKo9thIz;NV!}l zlgY4P!2(vSSb;Hyzy0lR{OM1B!mL@dhK`O7hK2^|>FMF@v(MtN!w#dpqXSi?P$CBlkhhniv7>0PB$K1JdIq}33Ip&yS0I=2)$1#^*emSPIvy*H# zOQBHU;fEilv9XaOjyQtnoOvckAAK}+b#-)fbkNn+h39zyq|<2*Iph$AhK86scP`I+ z-t##9^wU|gWC;@!6KveL5mlw3p@E${cVc|sCyry%=`@b>|now1`ySr&=X(0?lR@1)RI61U zee_YHC}Qo}wOoDm)s#vlmM&dNYilb2lNK*t%+jSxx%b|C`RiZ*#_;ekgM)*tU%wt> z3?Qf~sZ<&RkPs2l=`=fc?&O9WZs7XsuV>x5bu=|KF?H%RJkJAQQaYVx<;s=p-o2aE zt5v5?p^HJ z(@UvT#4K5|gpYjWBmC-Dzv3T%|2tp%+SfSq%rj|gYXe|X7>4})?|(SJ&*SGcFf=Z{&(L0{`Yg-amSI*=K@kz zD{i~(HrB6S&%nR{)oK-hgsO7D0W0~`r#{8^zV|&|^{Q8q&*uS1s462PBW&5Sl?NVp zfPehsAAIwh-{gJodoOQ)`#UhBqoZ7V?X~>qM?d02ANmlV_{1mp@sEGZrI%jHLk~Sf ze}6yLT113{4mybEJm)zyH8lZHtyWpRdNnuQbQ8b)<*)es=Re0sKl)L=@|Caf*T4Ri zwQJX6ZH&ogbF{U!)7aR=#KZ*m+;b2A{O84dF)00o_p@$ zuYdh38#itQU~gAmc@Gh!DpSvuDp{!GZ-G zb=1)ucGzJQ3Pm<-*g&;f1z=L4P~i64Z)4uPd7OIcsT^|1AuL$1fVS3Fq9_C)VXehj zYXPW+RT>)`S-5Z!p64-TN)Lw}b{Ik6^T;EQFgQ2}z@&#CewbCOR1l zcinXtVHmP~`*yCo?m8ZQ^ikqCX8H2vOzG(%ibAT@DyoVZ7#N^ZtuSTE6sAv~!M*q1 z%cYlI%FQ?5!q%fKFXC>Ud1)nT*JV? zAPW{OAd|^5JUqm_dGj!5opl!5w{64seQv+~HmWBp68JW{D6sx0uMd( z5LPX3d)r%i?sK1uX>M-jyWjmTFL=QV*tKgXrBV?Q0bo)(oo46Gos0|*1Mu*}50g%( z5fK1X6%j#I85tQSpU?B54}OrBzx?H7G8z64eGobQ+SA9S00000NkvXXu0mjfK$qxs literal 0 HcmV?d00001 diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/assets/RewardIcon.png b/repo/js/GeniusInvokationTCGQuickProficiency/assets/RewardIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..befef1e981bb2957e08b1dc078ef9a9a2f6c68b7 GIT binary patch literal 1339 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 debugThreshold) { + let region = captureRegion.DeriveCrop(x, y, width, height); + region.DrawSelf("debug"); + } + await sleep(retryInterval); + } + return { success: false }; +} + +async function recognizeTextAndClick(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = defaultReplacementMap) { + let x, y, width, height; + + if (Array.isArray(ocrRegion)) { + [x, y, width, height] = ocrRegion; + } else if (typeof ocrRegion === "object" && ocrRegion !== null) { + ({ x, y, width, height } = ocrRegion); + } else { + throw new Error("Invalid parameter 'ocrRegion'"); + } + + const debugThreshold = timeout / retryInterval / 3; + let startTime = Date.now(); + let retryCount = 0; // 重试计数 + while (Date.now() - startTime < timeout) { + let captureRegion = captureGameRegion(); + try { + // 尝试 OCR 识别 + let resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, width, height)); // 指定识别区域 + // 遍历识别结果,检查是否找到目标文本 + for (let res of resList) { + // 后处理:根据替换映射表检查和替换错误识别的字符 + let correctedText = res.text; + for (let [wrongChar, correctChar] of Object.entries(replacementMap)) { + correctedText = correctedText.replace(new RegExp(wrongChar, "g"), correctChar); + } + + if (correctedText.includes(targetText)) { + // 如果找到目标文本,计算并点击文字的中心坐标 + let centerX = Math.round(res.x + res.width / 2); + let centerY = Math.round(res.y + res.height / 2); + await click(centerX, centerY); + await sleep(50); + return { success: true, x: centerX, y: centerY }; + } + } + } catch (error) { + log.warn(`页面标志识别失败,正在进行第 ${retryCount} 次重试...`); + } + retryCount++; // 增加重试计数 + if (retryCount > debugThreshold) { + let region = captureRegion.DeriveCrop(x, y, width, height); + region.DrawSelf("debug"); + } + await sleep(retryInterval); + } + return { success: false }; +} diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/main.js b/repo/js/GeniusInvokationTCGQuickProficiency/main.js new file mode 100644 index 00000000..351ed8ec --- /dev/null +++ b/repo/js/GeniusInvokationTCGQuickProficiency/main.js @@ -0,0 +1,326 @@ +eval(file.readTextSync("lib/ocr.js")); + +const rewardIcon = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RewardIcon.png"), 1430, 760, 100, 70); +const tavernRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/TavernIcon.png"), 800, 450, 500, 330); +const adventurersRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/AdventurersGuild.png"), 800, 450, 500, 330); + +const outFile = "卡牌熟练度.json"; +let currentProficiencys = {}; +try { + currentProficiencys = JSON.parse(file.readTextSync(outFile)); +} catch (error) { + log.debug("历史熟练度文件不存在"); +} + +(async function () { + setGameMetrics(1920, 1080, 1.25); + log.info("首次运行前请阅读脚本说明"); + log.info("请确保已进入双人联机模式"); + await sleep(1000); + + let runMode = settings.runMode; + if (!runMode) { + runMode = "作为主账号:速刷熟练度"; + log.info("未配置运行模式,默认以{0}模式运行", runMode); + } + + if (runMode === "作为主账号:速刷熟练度") { + await runAsMain(settings.targetProficiency); + } else if (runMode === "作为辅助账号:快速投降") { + await runAsPartner(); + } else if (runMode === "扫描当前账号卡牌熟练度") { + await scanCardsProficiency(); + } else { + log.error("不支持的运行模式: {0}", runMode); + } +})(); + +// 需要刷熟练度的角色的执行逻辑 +async function runAsMain(targetProficiency) { + log.info("主账号开始工作,如需停止请按下你设置的BetterGI停止按键"); + if (!targetProficiency) { + targetProficiency = 30; + } + let loopCount = -1; + await genshin.returnMainUi(); + log.info("前往猫尾酒馆"); + await teleportToTheCatsTail(); + do { + await waitTpFinish(); + await gotoInvitationBoard(); + await waitForTextAppear("邀请队友", [1332, 886, 130, 49]); + if (loopCount < 0) { + log.info("获取当前熟练度信息"); + loopCount = await calcRepeatTimes(targetProficiency); + if (loopCount <= 0) { + recommendNextTeam(targetProficiency); + return; + } + log.info("需重复执行{0}次以达成{1}熟练度", loopCount, targetProficiency); + } + + // 循环次数为0时已经无需再打牌,只是再走到位置然后领取奖励 + if (loopCount === 0) { + await calcRepeatTimes(targetProficiency, true); + break; + } + + log.info("邀请队友"); + for (let i = 0; i < 60; i++) { + await recognizeTextAndClick("邀请队友", [1332, 886, 130, 49]); + let r = await waitForTextAppear("正在匹配对局", [870, 385, 184, 42], 500); + if (r.success) { + break; + } + await sleep(500); + } + + log.info("等待对方同意"); + await recognizeTextAndClick("正在匹配对局", [871, 386, 182, 39], 30000); + + log.info("等待加载"); + await waitForTextAppear("七圣召唤", [902, 870, 119, 46]); + + await waitForTextAppear("请选择要替换的手牌", [847, 232, 229, 39], 30000); + log.info("选择初始手牌"); + await recognizeTextAndClick("确定", [937, 922, 82, 54], 30000); + + log.info("等待对方认输"); + await waitForTextAppear("退出挑战", [913, 893, 130, 43], 60000); + await sleep(300); + await recognizeTextAndClick("退出挑战", [913, 893, 130, 43]); + + try { + await sleep(10); + } catch (error) { + log.info("用户停止运行"); + break; + } + + loopCount--; + log.info(`对局结束,当前熟练度 ${targetProficiency - loopCount}/${targetProficiency}`); + } while (loopCount >= 0); +} + +// 小号的执行逻辑 +async function runAsPartner() { + log.info("辅助账号开始工作,如需停止请按下你设置的BetterGI停止按键"); + await genshin.tp(-575.60, 1859.34); + await waitTpFinish(); + while (true) { + log.info("等待对局邀请"); + await waitForTextAppear("点击进行准备", [820, 70, 296, 31], 180000); + keyPress("Y"); + await recognizeTextAndClick("接受", [1177, 713, 82, 55]); + + log.info("等待加载"); + await waitForTextAppear("七圣召唤", [902, 870, 119, 46]); + + await waitForTextAppear("请选择要替换的手牌", [847, 232, 229, 39], 30000); + log.info("选择初始手牌"); + await recognizeTextAndClick("确定", [937, 922, 82, 54], 30000); + + log.info("等待进入牌桌界面"); + await waitForTextAppear("出战角色", [1766, 850, 118, 43]); + + log.info("认输"); + click(1864, 46); + await sleep(500); + await recognizeTextAndClick("放弃对局", [1543, 172, 112, 43]); + await waitForTextAppear("确定要放弃", [785, 497, 339, 39]); + await recognizeTextAndClick("确认", [1142, 732, 78, 51]); + + await waitForTextAppear("退出挑战", [913, 893, 130, 43], 60000); + await sleep(300); + await recognizeTextAndClick("退出挑战", [913, 893, 130, 43]); + log.info("退出挑战"); + + try { + await sleep(3000); + } catch (error) { + log.info("用户停止运行"); + break; + } + } +} + +async function gotoInvitationBoard() { + keyDown("MBUTTON"); + await sleep(500); + + keyDown("A"); + await sleep(1500); + keyUp("A"); + + await sleep(1000); + await keyMouseScript.runFile(`assets/ALT点击.json`); + await sleep(500); + await recognizeTextAndClick("联机对局", [1217, 411, 220, 280]); + await sleep(500); + await keyMouseScript.runFile(`assets/ALT释放.json`); +} + +function recommendNextTeam(targetProficiency) { + log.info("当前牌组中卡牌已达成熟练度目标,请更换牌组内角色"); + const nextTeam = Object.fromEntries( + Object.entries(currentProficiencys) + .filter(([_, v]) => v < targetProficiency) + .slice(0, 3) + ); + if (Object.keys(nextTeam).length > 0) { + let text = JSON.stringify(nextTeam).replace(/[{}"]/g, ''); + log.info("熟练度未满的角色推荐: {0}", text); + } +} + +async function calcRepeatTimes(targetProficiency, taskFinished = false) { + // 需要在选择“我方出战牌组”的界面执行 + await waitForTextAppear("我方出战牌组", [1248, 688, 156, 36]); + + click(1306, 747); + log.info("等待进入牌组界面"); + await recognizeTextAndClick("编辑牌组", [733, 997, 129, 48]); + + await waitForTextAppear("更改牌组外观", [1592, 186, 139, 35]); + click(700, 276); // +257 + + await waitForTextAppear("卡牌", [867, 266, 68, 47]); + + let characterProficiencys = {}; + for (let i = 0; i < 3; i++) { + let captureRegion = captureGameRegion(); + if (taskFinished) { + const icon = captureRegion.find(rewardIcon); + if (icon.isExist()) { + icon.click(); + await sleep(100); + icon.click(); + await sleep(100); + } + } + let ch_result = captureRegion.find(RecognitionObject.ocr(851, 326, 398, 65)); + let proficiencyResult = captureRegion.find(RecognitionObject.ocr(855, 780, 210, 40)); + if (ch_result.text && proficiencyResult.text) { + let character = ch_result.text.trim(); + const match = proficiencyResult.text.match(/(\d+)/); + let proficiency = parseInt(match[0]); + characterProficiencys[character] = proficiency; + click(1635, 538); + await sleep(500); + } + } + Object.assign(currentProficiencys, characterProficiencys); + file.writeTextSync(outFile, JSON.stringify(currentProficiencys, null, 2)); + log.info("当前熟练度: {0}", JSON.stringify(characterProficiencys).replace(/[{}"]/g, '')); + if (taskFinished) { + recommendNextTeam(targetProficiency); + return; + } + const loopCount = targetProficiency - Math.min(...Object.values(characterProficiencys)); + + // 返回对战页面 + keyPress("VK_ESCAPE"); + await waitForTextAppear("更改牌组外观", [1592, 186, 139, 35]); + keyPress("VK_ESCAPE"); + await waitForTextAppear("编辑牌组", [733, 997, 129, 48]); + keyPress("VK_ESCAPE"); + await waitForTextAppear("我方出战牌组", [1248, 688, 156, 36]); + return loopCount; +} + +/** + * 等待传送结束 + * @param {Int} timeout 单位为ms + * @note 参考了七圣召唤七日历练脚本 + */ +async function waitTpFinish(timeout=30000) { + const region = RecognitionObject.ocr(1690, 230, 75, 350); // 队伍名称区域 + const startTime = new Date(); + + await sleep(1000); //点击传送后等待一段时间避免误判 + while (new Date() - startTime < timeout) { + let res = captureGameRegion().find(region); + if (!res.isEmpty()) { + await sleep(600); //传送结束后有僵直 + return; + } + await sleep(100); + } + throw new Error("传送时间超时"); +} + +/** 传送到猫尾酒馆 */ +async function teleportToTheCatsTail() { + await genshin.moveMapTo(-867, 2281, "蒙德"); + await genshin.setBigMapZoomLevel(1.0); + let clickIcon = null; + for (let i = 0; i < 5; i ++) { + const region = captureGameRegion(); + const tarvern = region.find(tavernRo); + clickIcon = tarvern.isExist() ? tarvern : region.find(adventurersRo); + if (clickIcon.isExist()) { + clickIcon.click(); + await sleep(500); + break; + } + await sleep(500); + } + if (!(clickIcon && clickIcon.isExist())) { + throw new Error("找不到猫尾酒馆,如果2P标志遮挡了酒馆图标,请将2P玩家传送至别处"); + } + await recognizeTextAndClick("猫尾酒馆", [1320, 560, 300, 410]) + await recognizeTextAndClick("传送至", [1580,980,225,59]) + await waitTpFinish(); +} + +async function scanCardsProficiency() { + await genshin.returnMainUi(); + log.info("前往猫尾酒馆"); + await teleportToTheCatsTail(); + await gotoInvitationBoard(); + await waitForTextAppear("我方出战牌组", [1248, 688, 156, 36]); + + click(1306, 747); + log.info("等待进入牌组界面"); + await recognizeTextAndClick("编辑牌组", [733, 997, 129, 48]); + + await waitForTextAppear("更改牌组外观", [1592, 186, 139, 35]); + click(198, 717); + + let characterProficiencys = {}; + let last_char = null; + let retry = 0; + for (let i = 0; i < 300; i++) { + let captureRegion = captureGameRegion(); + let ch_result = captureRegion.find(RecognitionObject.ocr(851, 326, 398, 65)); + let proficiencyResult = captureRegion.find(RecognitionObject.ocr(855, 780, 210, 40)); + if (ch_result.text && proficiencyResult.text) { + let character = ch_result.text.trim().replace("t", "七"); + const match = proficiencyResult.text.match(/(\d+)/); + let proficiency = parseInt(match[0]); + characterProficiencys[character] = proficiency; + log.info("{ch} = {v}", character, proficiency); + if (last_char === character) { + log.info("已到达角色列表末尾"); + break; + } + click(1635, 538); + retry = 0; + last_char = character; + } else { + retry++; + if (retry >= 3) { + log.warn("OCR无法识别当前角色名称,跳过"); + click(1635, 538); + retry = 0; + } + } + await sleep(500); + } + + const sortedProficiencys = Object.entries(characterProficiencys).sort((a, b) => b[1] - a[1]); + currentProficiencys = Object.fromEntries(sortedProficiencys); + file.writeTextSync(outFile, JSON.stringify(currentProficiencys, null, 2)); + + log.info("卡牌熟练度数据已写入{0}", outFile); +} diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/manifest.json b/repo/js/GeniusInvokationTCGQuickProficiency/manifest.json new file mode 100644 index 00000000..4058344c --- /dev/null +++ b/repo/js/GeniusInvokationTCGQuickProficiency/manifest.json @@ -0,0 +1,15 @@ +{ + "manifest_version": 1, + "name": "七圣召唤卡牌熟练度速刷", + "version": "1.0", + "bgi_version": "0.45.0", + "description": "七圣召唤卡牌熟练度速刷", + "authors": [ + { + "name": "Ayaka-Main", + "link": "https://github.com/Patrick-Ze" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} diff --git a/repo/js/GeniusInvokationTCGQuickProficiency/settings.json b/repo/js/GeniusInvokationTCGQuickProficiency/settings.json new file mode 100644 index 00000000..ca565d41 --- /dev/null +++ b/repo/js/GeniusInvokationTCGQuickProficiency/settings.json @@ -0,0 +1,17 @@ +[ + { + "name": "runMode", + "type": "select", + "label": "运行模式", + "options": [ + "作为主账号:速刷熟练度", + "作为辅助账号:快速投降", + "扫描当前账号卡牌熟练度" + ] + }, + { + "name": "targetProficiency", + "type": "input-text", + "label": "目标熟练度 (未设置时默认刷到30)" + } +]