diff --git a/repo/js/5_7PVP_Auto/assets/buff.png b/repo/js/5_7PVP_Auto/assets/buff.png new file mode 100644 index 00000000..12ab681a Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/buff.png differ diff --git a/repo/js/5_7PVP_Auto/assets/button1.png b/repo/js/5_7PVP_Auto/assets/button1.png new file mode 100644 index 00000000..3e0f08a6 Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/button1.png differ diff --git a/repo/js/5_7PVP_Auto/assets/button2.png b/repo/js/5_7PVP_Auto/assets/button2.png new file mode 100644 index 00000000..507339ba Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/button2.png differ diff --git a/repo/js/5_7PVP_Auto/assets/button3.png b/repo/js/5_7PVP_Auto/assets/button3.png new file mode 100644 index 00000000..63f58467 Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/button3.png differ diff --git a/repo/js/5_7PVP_Auto/assets/finish1.png b/repo/js/5_7PVP_Auto/assets/finish1.png new file mode 100644 index 00000000..4156c88c Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/finish1.png differ diff --git a/repo/js/5_7PVP_Auto/assets/finish2.png b/repo/js/5_7PVP_Auto/assets/finish2.png new file mode 100644 index 00000000..4dd8f3ce Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/finish2.png differ diff --git a/repo/js/5_7PVP_Auto/assets/goal.png b/repo/js/5_7PVP_Auto/assets/goal.png new file mode 100644 index 00000000..f0fe2fb8 Binary files /dev/null and b/repo/js/5_7PVP_Auto/assets/goal.png differ diff --git a/repo/js/5_7PVP_Auto/lib.js b/repo/js/5_7PVP_Auto/lib.js new file mode 100644 index 00000000..76b952eb --- /dev/null +++ b/repo/js/5_7PVP_Auto/lib.js @@ -0,0 +1,103 @@ +//eval(file.readTextSync("lib.js")); + + +const width = genshin.width; +const height = genshin.height; + +function clickf(x, y) { + click(Math.round(width * x), Math.round(height * y)); +} +function movetof(x, y) { + moveMouseTo(Math.round(width * x), Math.round(height * y)); +} +function get_config(name, defval) { + let t = settings[name]; + return typeof (t) === 'undefined' ? defval : t; +} +function get_config_int(name, defval) { + return parseInt(get_config(name, defval), 10); +} + + +class OCRError extends Error { + constructor(message, options) { super(message, options); } +} + + +setGameMetrics(genshin.width, genshin.height, 1); // 设置游戏窗口大小和DPI +function test1() { + log.info(`窗口大小: ${genshin.width} * ${genshin.height}`); + let a = captureGameRegion(); + log.info(`截图:x=${a.x} y=${a.y} w=${a.width} h=${a.height} l=${a.left} t=${a.top} r=${a.right} b=${a.bottom}`); +} +const global_region = captureGameRegion(); +//test1(); + +function template(filename, x, y, w, h, center = true) { + if (center) { x = x - w / 2; y = y - h / 2; } + return RecognitionObject.TemplateMatch(file.ReadImageMatSync(filename), 1920 * x, 1080 * y, 1920 * w, 1080 * h); + //return RecognitionObject.TemplateMatch(file.ReadImageMatSync(filename), genshin.width * x, genshin.height * y, genshin.width * w, genshin.height * h); +} +function template_ocr(x, y, w, h, center = true) { + if (center) { x = x - w / 2; y = y - h / 2; } + return RecognitionObject.Ocr(1920 * x, 1080 * y, 1920 * w, 1080 * h); +} + +function draw_obj(obj, name = "test") { + const r = obj.RegionOfInterest; + let s = global_region.deriveCrop(r.x, r.y, r.width, r.height); + s.DrawSelf(name); +} + +async function match_click(obj, desc, click = true, timeout = 15000) { + draw_obj(obj, "match"); + await sleep(1000); + const start = Date.now(); + let x = 1; + while (Date.now() - start < timeout) { + let result = captureGameRegion().Find(obj); + await sleep(800); + if (result.isExist()) { + result.drawSelf("match_found"); + if (click) { + result.click(); + log.info(`成功识别并点击 ${desc},耗时${Date.now() - start}ms`); + } else { + log.info(`成功识别到 ${desc},耗时${Date.now() - start}ms`); + } + return result; + } + log.info(`第${x}次识别并点击 ${desc} 失败,耗时${Date.now() - start}ms`); + x++; + await sleep(1000); + } + throw new OCRError(`等待超时,未找到目标 ${desc}`); +} + +async function ocr_click(obj, desc, click = true, timeout = 15000) { + draw_obj(obj, "ocr"); + await sleep(1000); + const start = Date.now(); + let x = 1; + while (Date.now() - start < timeout) { + let results = captureGameRegion().findMulti(obj); + if (results.Count != 1) { + log.warn(`搜索到${results.Count}个结果(预期为1个)`); + } + await sleep(800); + if (results.Count == 1) { + results[0].drawSelf("ocr_found"); + if (click) { + results[0].click(); + log.info(`成功Ocr识别并点击 ${desc}, 耗时${Date.now() - start}ms`); + } else { + log.info(`成功Ocr识别到 ${desc}, 耗时${Date.now() - start}ms`) + } + return results[0]; + } + log.info(`第${x}次Ocr识别并点击 ${desc} 失败,耗时${Date.now() - start}ms`); + x++; + await sleep(1000); + } + throw new OCRError(`等待超时,未找到Ocr目标 ${desc}`); +} diff --git a/repo/js/5_7PVP_Auto/main.js b/repo/js/5_7PVP_Auto/main.js new file mode 100644 index 00000000..1734502c --- /dev/null +++ b/repo/js/5_7PVP_Auto/main.js @@ -0,0 +1,257 @@ +eval(file.readTextSync("lib.js")); + +const CHAR_X = get_config_int("char_x", 0); +const CHAR_Y = get_config_int("char_y", 0); + +const global_region = captureGameRegion(); + +const btn_details = template("assets/button1.png", 0.77, 0.75, 0.04, 0.08, true); +const btn_startmatch = template("assets/button2.png", 0.832, 0.95, 0.04, 0.08, true); +const btn_acceptmatch = template("assets/button1.png", 0.528, 0.68, 0.04, 0.08, true); +const btn_closetip = template("assets/button3.png", 0.978, 0.35, 0.043, 0.075, true); +const buff_icon = template("assets/buff.png", 0.068, 0.204, 0.03, 0.05, true); +const btn_confirm = template("assets/button1.png", 0.832, 0.95, 0.04, 0.08, true); +const goal_icon = template("assets/goal.png", 0.031, 0.3125 + 0.035 * 1, 0.012, 0.018 + 0.035 * 2); +const btn_finish1 = template("assets/finish1.png", 0.5, 0.805, 0.042, 0.026, true); +const btn_finish2 = template("assets/finish2.png", 0.5, 0.805, 0.029, 0.026, true); + +const score = template_ocr(0.162, 0.415, 0.1, 0.06, false); +const matching_tip = template_ocr(0.35, 0.77, 0.12, 0.08, false); + +const ACT_KEYS = ["W", "S", "A", "D"]; + +/// Press F5 and click on Details button +async function F5_and_click() { + await keyPress("F5"); + await sleep(1000); + + for (let i = 0; i < 3; i++) { + clickf(0.182, 0.255); + await sleep(500); + } + + await match_click(btn_details, "活动详情按钮"); + await sleep(2000); +} + +async function start_minigame() { + let should_retry = false; + while (true) { + // 点击开始匹配按钮 + try { + await match_click(btn_startmatch, "开始匹配", true, 10000); + await sleep(2000); + } catch (e) { + log.warn("点击匹配按钮失败,暂时跳过"); + } + + // 匹配中,准备点击接受按钮 + should_retry = false; + while (true) { + let screen = captureGameRegion(); + await sleep(800); + + // 确保还在匹配状态 + let tip = screen.Find(matching_tip); + if (tip.isExist() && tip.text.startsWith("匹配中")) { + } else { + log.warn("匹配状态异常,即将重试"); + should_retry = true; + break; + } + + let btn = screen.Find(btn_acceptmatch); + if (btn.isExist()) { + log.info("匹配成功,接受"); + await sleep(500); + btn.click(); + break; + } + + await sleep(1000); + } + if (should_retry) { + await sleep(5000); + continue; + } + + await sleep(8000); + + // 可能进入战斗准备界面,或匹配失败退回 + should_retry = false; + log.info("等待进入选择角色界面") + while (true) { + await sleep(1000); + let screen = captureGameRegion(); + draw_obj(btn_startmatch, "start"); + draw_obj(btn_closetip, "close"); + await sleep(800); + + if (screen.Find(btn_startmatch).isExist()) { + log.warn("匹配失败,即将重试"); + await sleep(3000); + should_retry = true; + break; + } + + let close = screen.Find(btn_closetip); + if (close.isExist()) { + close.drawSelf("close_found"); + log.info("已经进入选择角色界面"); + await sleep(500); + close.click(); + return; + } + } + if (should_retry) { + await sleep(5000); + continue; + } + } + + throw new Error("Unreachable"); +} + +function click_char(x, y) { + movetof(0.057 + x * 0.073, 0.169 + y * 0.157); + clickf(0.057 + x * 0.073, 0.169 + y * 0.157); +} + +async function play(times) { + for (let ii = 0; ii < times; ii++) { + keyPress("Q"); + await sleep(500); + keyPress("E"); + await sleep(500); + let act = Math.floor(Math.random() * 8); + let act_key = ACT_KEYS[act % 4]; + switch (act) { + case 0: case 1: case 2: case 3: + keyDown("SHIFT"); + keyDown(act_key); + await sleep(1000); + keyUp(act_key); + keyUp("SHIFT"); + break; + case 4: + // 空格跳跃1秒 + keyDown("SPACE"); + await sleep(1000); + keyUp("SPACE"); + break; + case 5: case 6: case 7: + // 左键连续普攻 + for (let i = 0; i < 5; i++) { + leftButtonDown(); + await sleep(100); + leftButtonUp(); + await sleep(100); + } + middleButtonClick(); + break; + default: + break; + } + await sleep(500); + } +} + + + +async function one_shot() { + await sleep(1000); + log.info("任务开始"); + await genshin.returnMainUi(); + await sleep(1000); + + log.info("进入活动界面"); + await F5_and_click(); + + log.info("读取当前分数"); + let score_obj = await ocr_click(score, "活动分数", false); + let score_re = score_obj.text.match(/(\d+)\s*\/\s*(\d+)/); + let cur_score = -1, max_score = -1; + if (score_re && score_re.length >= 3) { + cur_score = Number(score_re[1]); + max_score = Number(score_re[2]); + log.info(`当前分数:${cur_score}/${max_score}`); + } else { + throw new Error(`分数解析失败:${score_obj.text}`); + } + + if (cur_score >= max_score) { + log.info("活动完成"); + return false; + } + + log.info("尝试匹配并进入小游戏"); + await start_minigame(); + await sleep(3000); + + log.info("选择角色"); + await sleep(500); + click_char(CHAR_X, CHAR_Y); + await sleep(500); + await match_click(btn_confirm, "确认角色选择"); + await sleep(500); + + log.info("等待buff选择界面"); + while (true) { + if (captureGameRegion().Find(buff_icon).isExist()) { + break; + } + await sleep(5000); + } + await sleep(1000); + + log.info("选择buff"); + clickf(0.25, 0.26); + await sleep(500); + await match_click(btn_confirm, "确认buff选择"); + await sleep(500); + + log.info("等待游戏开始"); + await sleep(5000); + log.info("等待游戏提示出现"); + while (true) { + draw_obj(goal_icon); + let result = captureGameRegion().FindMulti(goal_icon); + await sleep(500); + if (result.count != 3) { await sleep(5000); continue; } + await sleep(1000); + break; + } + + log.info("随机行动"); + while (true) { + await play(15); + let result = captureGameRegion().Find(btn_finish1); + if (result.isExist()) { + log.info("完成,点击结算按钮1"); + result.click(); + break; + } + await sleep(1000); + } + await sleep(1000); + await match_click(btn_finish2, "退出活动"); + await sleep(10000); + + return true; +} + +(async function () { + let count = 0; + while (true) { + count++; + if (!await one_shot()) break; + await genshin.returnMainUi(); + await sleep(2000); + await genshin.returnMainUi(); + await sleep(2000); + log.info("第${count}次游戏结束,重新开始"); + } + log.info("结束运行,可能是分数已满"); + + return; +})(); \ No newline at end of file diff --git a/repo/js/5_7PVP_Auto/manifest.json b/repo/js/5_7PVP_Auto/manifest.json new file mode 100644 index 00000000..4a160042 --- /dev/null +++ b/repo/js/5_7PVP_Auto/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "name": "PVP活动自动化:5.7", + "version": "5.7.0", + "bgi_version": "0.45.0", + "description": "自动完成5.7版本联机PVP活动。可在脚本设置中自行选择上场角色。如果之前没有玩过这个活动,第一次运行建议人工监视到进入战斗界面,中间弹出的教学界面需要手动关掉,否则会执行失败!作者为:pans82@proton.me", + "authors": [ + { + "name": "pans82" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} diff --git a/repo/js/5_7PVP_Auto/settings.json b/repo/js/5_7PVP_Auto/settings.json new file mode 100644 index 00000000..f5c32b08 --- /dev/null +++ b/repo/js/5_7PVP_Auto/settings.json @@ -0,0 +1,12 @@ +[ + { + "name": "char_x", + "type": "input-text", + "label": "角色列号,0~3,默认0" + }, + { + "name": "char_y", + "type": "input-text", + "label": "角色行号,0~4,默认0" + } +] \ No newline at end of file