增加JS脚本:幻想真境剧诗定时抢好友列表里大佬的满命角色 (#1446)
This commit is contained in:
9
repo/js/幻想真境剧诗抢大佬的满命角色/README.md
Normal file
9
repo/js/幻想真境剧诗抢大佬的满命角色/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
- 需配合其他定时运行手段使用
|
||||
|
||||
- 建议定时在每月1日凌晨4点01分运行一次,抢好友提前设置好的助演角色
|
||||
|
||||
- 早上7~8点再运行一次,抢新设置的助演角色
|
||||
|
||||
- 可以配置要抢的角色。如果不设置则默认抢一些主流C位,目前仅支持抢满命角色(非满命似乎没有抢的必要)
|
||||
|
||||
248
repo/js/幻想真境剧诗抢大佬的满命角色/main.js
Normal file
248
repo/js/幻想真境剧诗抢大佬的满命角色/main.js
Normal file
@@ -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();
|
||||
})();
|
||||
13
repo/js/幻想真境剧诗抢大佬的满命角色/manifest.json
Normal file
13
repo/js/幻想真境剧诗抢大佬的满命角色/manifest.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "幻想真境剧诗抢大佬的满命角色",
|
||||
"version": "1.0",
|
||||
"description": "幻想真境剧诗抢好友大佬的满命角色,建议预约每月1号凌晨4点过一丢丢开始运行",
|
||||
"authors": [
|
||||
{
|
||||
"name": "芝士贝果"
|
||||
}
|
||||
],
|
||||
"settings_ui": "settings.json",
|
||||
"main": "main.js"
|
||||
}
|
||||
7
repo/js/幻想真境剧诗抢大佬的满命角色/settings.json
Normal file
7
repo/js/幻想真境剧诗抢大佬的满命角色/settings.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "allowed_cast",
|
||||
"type": "input-text",
|
||||
"label": "要借用的角色,空格分隔,默认抢主流的C"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user