From 31a09e87ab72c8ffa6f5d8c9cc2c3cdccb506012 Mon Sep 17 00:00:00 2001 From: Jamis Date: Fri, 1 Aug 2025 18:24:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0JS=E8=84=9A=E6=9C=AC=EF=BC=9A?= =?UTF-8?q?=E5=B9=BB=E6=83=B3=E7=9C=9F=E5=A2=83=E5=89=A7=E8=AF=97=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E6=8A=A2=E5=A5=BD=E5=8F=8B=E5=88=97=E8=A1=A8=E9=87=8C?= =?UTF-8?q?=E5=A4=A7=E4=BD=AC=E7=9A=84=E6=BB=A1=E5=91=BD=E8=A7=92=E8=89=B2?= =?UTF-8?q?=20(#1446)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/幻想真境剧诗抢大佬的满命角色/README.md | 9 + repo/js/幻想真境剧诗抢大佬的满命角色/main.js | 248 ++++++++++++++++++ .../manifest.json | 13 + .../settings.json | 7 + 4 files changed, 277 insertions(+) create mode 100644 repo/js/幻想真境剧诗抢大佬的满命角色/README.md create mode 100644 repo/js/幻想真境剧诗抢大佬的满命角色/main.js create mode 100644 repo/js/幻想真境剧诗抢大佬的满命角色/manifest.json create mode 100644 repo/js/幻想真境剧诗抢大佬的满命角色/settings.json diff --git a/repo/js/幻想真境剧诗抢大佬的满命角色/README.md b/repo/js/幻想真境剧诗抢大佬的满命角色/README.md new file mode 100644 index 00000000..bf2a5aea --- /dev/null +++ b/repo/js/幻想真境剧诗抢大佬的满命角色/README.md @@ -0,0 +1,9 @@ + +- 需配合其他定时运行手段使用 + +- 建议定时在每月1日凌晨4点01分运行一次,抢好友提前设置好的助演角色 + +- 早上7~8点再运行一次,抢新设置的助演角色 + +- 可以配置要抢的角色。如果不设置则默认抢一些主流C位,目前仅支持抢满命角色(非满命似乎没有抢的必要) + diff --git a/repo/js/幻想真境剧诗抢大佬的满命角色/main.js b/repo/js/幻想真境剧诗抢大佬的满命角色/main.js new file mode 100644 index 00000000..5ab3d7b1 --- /dev/null +++ b/repo/js/幻想真境剧诗抢大佬的满命角色/main.js @@ -0,0 +1,248 @@ +async function wait_until_region_has_text(x, y, w, h, expected_text, max_times, intercalary) { + for (var _ = 0; _ < max_times; ++_) { + const img = captureGameRegion(); + const ocr_result = img.findMulti(RecognitionObject.ocr(x, y, w, h)); + for (var i = 0; i < ocr_result.count; ++i) { + const t = ocr_result[i].text; + if (t === expected_text) { + return { + x: ocr_result[i].x, + y: ocr_result[i].y, + w: ocr_result[i].width, + h: ocr_result[i].Height, + center: [Math.round(ocr_result[i].x + ocr_result[i].width / 2), Math.round(ocr_result[i].y + ocr_result[i].Height / 2)], + }; + } + } + if (intercalary) { + intercalary(); + } + await sleep(1000); + } + return null; +} + +async function click_dialog(t) { + const pos = await wait_until_region_has_text(1170, 328, 494, 594, t, 5, null); + if (pos) { + log.info("Found text {t} at {x}, {y}", t, pos.x, pos.y); + keyDown("VK_MENU"); + await sleep(500); + moveMouseTo(...pos.center); + leftButtonClick(); + await sleep(500); + keyUp("VK_MENU"); + return true; + } else { + log.warn("Couldn't find text {t}", t); + return false; + } +} + +async function teleport_to_theater_lobby() { + await genshin.returnMainUi(); + await sleep(3000); + + keyPress("m"); + await sleep(1000); + click(1841, 1019); + await sleep(500); + click(1436, 142); + await sleep(500); + click(1711, 49); + await sleep(500); + await click(1685, 1005); + await sleep(500); + +} + + +async function scroll_up_cast_page() { + await sleep(200); + moveMouseTo(996, 483); + await sleep(200); + leftButtonDown(); + await sleep(200); + var total_pixels_to_move = 190; + while (total_pixels_to_move > 0) { + await sleep(10); + const to_move = Math.min(total_pixels_to_move, 10); + moveMouseBy(0, -to_move); + total_pixels_to_move -= to_move; + } + await sleep(800); + leftButtonUp(); + await sleep(200); +} + +async function filter_cast(allowed_cast, min_constellation) { + const region_x = 333; + const region_y = 296; + const region_w = 767; + const region_h = 429; + const gold_color_rgb = [250, 211, 68]; + + function ocr_region(img, x, y, w, h) { + const ocr_result = img.findMulti(RecognitionObject.ocr(x, y, w, h)); + return ocr_result.count > 0 ? ocr_result[0].text : null; + } + + var last_screenshot_text = null; + var text_unchange_times = 0; + while (true) { + const img = captureGameRegion(); + const ocr_result = img.findMulti(RecognitionObject.ocr(region_x, region_y, region_w, region_h)); + var ordered_result = []; + var ordered_text = []; + for (var i = 0; i < ocr_result.count; ++i) { + ordered_result.push([ocr_result[i].x, ocr_result[i].y, ocr_result[i].text]); + ordered_text.push(ocr_result[i].text); + } + ordered_result.sort(); + ordered_text.sort(); + const joined_text = ordered_text.join(""); + if (joined_text === last_screenshot_text) { + text_unchange_times += 1; + } else { + text_unchange_times = 0; + last_screenshot_text = joined_text; + } + if (text_unchange_times === 1) { + break; + } + for (const [x, y, t] of ordered_result) { + if (t.startsWith("Lv") || t.startsWith("LV")) { + if (y <= 301) { + continue; + } + const crop = img.DeriveCrop(x - 17, y - 17, 14, 14); + const mean_color = crop.srcMat.mean(); + const dist = 0.3 * Math.pow(mean_color.val2 - gold_color_rgb[0], 2) + + 0.59 * Math.pow(mean_color.val1 - gold_color_rgb[1], 2) + 0.11 * Math.pow(mean_color.val0 - gold_color_rgb[2], 2); + if (dist > 5000) { + continue; + } + + click(x, y); + await sleep(100); + var img2 = null; + var character_name = null; + var constellation = null; + for (var i = 0; i < 5; ++i) { + img2 = captureGameRegion(); + const cn = ocr_region(img2, 1292, 317, 278, 44); + const cs = ocr_region(img2, 1292, 408, 278, 44); + log.debug("{cn} {cs}, {dist}, ({x}, {y})", cn, cs, dist, x, y); + if (!cn) { + await sleep(500); + continue; + } + character_name = cn; + constellation = parseInt((cs || "命之座0层").replace("命之座", "").replace("层", "")); + break; + } + const msg = ocr_region(img2, 341, 745, 1239, 55); + + if (msg === "每期仅能向同一好友邀请一位助演角色" && allowed_cast.includes(character_name) && constellation >= min_constellation) { + log.info("邀请 {constellation}命{character_name}", constellation, character_name); + return true; + } + + } + } + await scroll_up_cast_page(); + } + return false; +} + + +(async function() { + const dry_run = false; + const default_allowed_cast = ["丝柯克", "瓦雷莎", "玛薇卡", "恰斯卡", "玛拉妮", "基尼奇", "克洛琳德", "阿蕾奇诺", "千织", "娜维娅", "那维莱特", "莱欧斯利", "艾尔海森"]; + const min_constellation = 6; // Only `6` is supported now + const allow_override = false; + + const custom_allowed_cast = (settings.allowed_cast || "").split(" ").filter(i => i); + const allowed_cast = custom_allowed_cast.length === 0 ? default_allowed_cast : custom_allowed_cast; + + log.info("备选角色:{cast}", allowed_cast.join(" ")); + + await teleport_to_theater_lobby(); + await teleport_to_theater_lobby(); + await sleep(4000); + if (!await click_dialog("幻想真境剧诗")) { + return; + } + const prepare_button = await wait_until_region_has_text(1530, 732, 161, 45, "演出准备", 10, () => { + click(960, 912); + }); + await sleep(500); + click(...prepare_button.center); + await sleep(500); + + const easy_mode = await wait_until_region_has_text(196, 173, 148, 50, "轻简模式", 10, () => { + click(128, 1020); + }); + await sleep(500); + click(...easy_mode.center); + + await sleep(500); + click(1624, 1020); // party setup + await sleep(500); + click(200, 415); // select all trial characters + click(350, 415); + click(500, 415); + click(650, 415); + click(800, 415); + click(900, 415); + click(1624, 1020); // next + + await wait_until_region_has_text(1022, 440, 774, 100, "试用", 3, null); + const supporting_cast = await wait_until_region_has_text(1022, 440, 774, 100, "助演", 1, null); + if (supporting_cast && !allow_override) { + log.info("已有选择的助演角色,退出"); + await genshin.returnMainUi(); + return; + } + await sleep(500); + click(1644, 414); // invite supporting cast + await sleep(500); + + const find_result = await filter_cast(allowed_cast, min_constellation); + if (!find_result) { + log.info("未找到符合条件的角色,去多加一些满命大佬吧"); + await genshin.returnMainUi(); + return; + } else { + await sleep(500); + click(1134, 850); + await sleep(500); + click(1180, 760); + await sleep(500); + } + + await sleep(500); + click(400, 1020); // auto select team members + await sleep(500); + click(960, 540); + await sleep(500); + click(1624, 1020); // start + await sleep(500); + click(1142, 909); // confirm start + await sleep(500); + + const confirm_select = await wait_until_region_has_text(1344, 945, 156, 36, "确认选择", 15, () => { + click(436, 577); + }); + click(1414, 963); + await sleep(500); + click(1167, 814); + await sleep(500); + const can_press_esc = await wait_until_region_has_text(913, 638, 137, 37, "暂离演出", 10, () => { + keyPress("VK_ESCAPE"); + }); + + // now we've locked the character, safe to exit + await genshin.TpToStatueOfTheSeven(); +})(); diff --git a/repo/js/幻想真境剧诗抢大佬的满命角色/manifest.json b/repo/js/幻想真境剧诗抢大佬的满命角色/manifest.json new file mode 100644 index 00000000..f18e1939 --- /dev/null +++ b/repo/js/幻想真境剧诗抢大佬的满命角色/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": 1, + "name": "幻想真境剧诗抢大佬的满命角色", + "version": "1.0", + "description": "幻想真境剧诗抢好友大佬的满命角色,建议预约每月1号凌晨4点过一丢丢开始运行", + "authors": [ + { + "name": "芝士贝果" + } + ], + "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 new file mode 100644 index 00000000..df2d130f --- /dev/null +++ b/repo/js/幻想真境剧诗抢大佬的满命角色/settings.json @@ -0,0 +1,7 @@ +[ + { + "name": "allowed_cast", + "type": "input-text", + "label": "要借用的角色,空格分隔,默认抢主流的C" + } +] \ No newline at end of file