diff --git a/repo/js/AutoEnter/README.md b/repo/js/AutoEnter/README.md new file mode 100644 index 000000000..6165f980e --- /dev/null +++ b/repo/js/AutoEnter/README.md @@ -0,0 +1,4 @@ +自动进入联机状态js测试版本,出什么bug都是正常的,加qq718135749去超市作者 + +组队和更多使用说明请加qq群1057307824 + diff --git a/repo/js/AutoEnter/assets/RecognitionObject/1P.png b/repo/js/AutoEnter/assets/RecognitionObject/1P.png new file mode 100644 index 000000000..b187e2032 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/1P.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/2P.png b/repo/js/AutoEnter/assets/RecognitionObject/2P.png new file mode 100644 index 000000000..4f8edeef3 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/2P.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/3P.png b/repo/js/AutoEnter/assets/RecognitionObject/3P.png new file mode 100644 index 000000000..04cf2d4bc Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/3P.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/4P.png b/repo/js/AutoEnter/assets/RecognitionObject/4P.png new file mode 100644 index 000000000..888043566 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/4P.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/MainUI.png b/repo/js/AutoEnter/assets/RecognitionObject/MainUI.png new file mode 100644 index 000000000..690f1d079 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/MainUI.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/allowEnter.png b/repo/js/AutoEnter/assets/RecognitionObject/allowEnter.png new file mode 100644 index 000000000..8c6cd7632 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/allowEnter.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/enterUID.png b/repo/js/AutoEnter/assets/RecognitionObject/enterUID.png new file mode 100644 index 000000000..d1041e548 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/enterUID.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/requestEnter.png b/repo/js/AutoEnter/assets/RecognitionObject/requestEnter.png new file mode 100644 index 000000000..0b2e42bf4 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/requestEnter.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/search.png b/repo/js/AutoEnter/assets/RecognitionObject/search.png new file mode 100644 index 000000000..d53060012 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/search.png differ diff --git a/repo/js/AutoEnter/assets/RecognitionObject/yUI.png b/repo/js/AutoEnter/assets/RecognitionObject/yUI.png new file mode 100644 index 000000000..4e365bb76 Binary files /dev/null and b/repo/js/AutoEnter/assets/RecognitionObject/yUI.png differ diff --git a/repo/js/AutoEnter/main.js b/repo/js/AutoEnter/main.js new file mode 100644 index 000000000..13b75edcf --- /dev/null +++ b/repo/js/AutoEnter/main.js @@ -0,0 +1,447 @@ +//获取自定义配置 +const enterMode = settings.enterMode || "进入他人世界"; +const enteringUID = settings.enteringUID; +const permissionMode = settings.permissionMode || "无条件通过"; +const nameToPermit1 = settings.nameToPermit1; +const nameToPermit2 = settings.nameToPermit2; +const nameToPermit3 = settings.nameToPermit3; +const timeOut = +settings.timeout || 5; +const maxEnterCount = +settings.maxEnterCount || 3; + + +const enterUIDRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/enterUID.png")); +const searchRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/search.png")); +const requestEnterRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/requestEnter.png")); +const requestEnter2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/requestEnter.png"), 1480, 300, 280, 100); +const yUIRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/yUI.png")); +const allowEnterRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/allowEnter.png"), 1250, 300, 150, 130); +const targetsPath = "targets"; +let enterCount = 0; +let targetsRo; +// 先初始化空数组 +let targetList = []; + +(async function () { + setGameMetrics(1920, 1080, 1); + const start = new Date(); + log.info(`当前模式为:${enterMode}`); + + // 依次判断并加入 + if (settings.nameToPermit1) targetList.push(settings.nameToPermit1); + if (settings.nameToPermit2) targetList.push(settings.nameToPermit2); + if (settings.nameToPermit3) targetList.push(settings.nameToPermit3); + + // 获取指定文件夹下所有文件 + let targetPngs = await readFolder(targetsPath, false); + + // 生成 targetsRo(使用 for...of) + targetsRo = []; + for (const f of targetPngs) { + log.info(`找到文件${f.fullPath}`); + if (f.fullPath.endsWith('.png')) { + const mat = file.ReadImageMatSync(f.fullPath); + const ro = RecognitionObject.TemplateMatch(mat, 650, 320, 350, 60); + const baseName = f.fileName.replace(/\.png$/i, ''); + targetsRo.push({ ro, baseName }); + } + } + + + log.info(`加载完成共${targetPngs.length}个目标`); + + while (new Date() - start < timeOut * 60 * 1000) { + if (enterMode === "进入他人世界") { + //反复敲门进入他人世界 + //我要cia进来llo + if (enteringUID) { + await genshin.returnMainUi(); + await sleep(500); + await keyPress("F2"); + //点击输入uid + await sleep(500); + if (!await findAndClick(enterUIDRo)) { + await genshin.returnMainUi(); + continue; + } + inputText(enteringUID); + //点击搜索 + await sleep(500); + if (!await findAndClick(searchRo)) { + await genshin.returnMainUi(); + continue; + } + //判断是否成功搜索 + if (await confirmSearchResult()) { + await genshin.returnMainUi(); + continue; + } + + //点击申请加入 + await sleep(500); + if (!await findAndClick(requestEnterRo)) { + await genshin.returnMainUi(); + continue; + } + //等待加入完成 + await waitForMainUI(true, 5 * 1000); + + //检验新的队伍编号 + const playerSign2 = await getPlayerSign(); + await sleep(500); + if (playerSign2 != 0) { + log.info(`加入世界成功,在队伍中的编号为${playerSign2}`); + break; + } else { + log.error(`加入世界失败,开始重试`); + await genshin.returnMainUi(); + await sleep(500); + continue; + } + } else { + log.error("未填写有效的uid,请检查后重试"); + break; + } + } else { + //等待他人进入世界 + if (await isYUI()) { + keyPress("VK_ESCAPE"); + } + await genshin.returnMainUi(); + keyPress("Y"); + await sleep(250); + if (await isYUI()) { + log.info("处于y界面开始识别"); + let attempts = 0; + while (attempts < 20) { + attempts++; + if (permissionMode === "无条件通过") { + //无需筛选,全部通过 + if (await findAndClick(allowEnterRo)) { + enterCount++; + break; + } + } else { + //需要筛选,开始识别第一行申请 + const result = await recognizeRequest(); + if (result) { + await findAndClick(allowEnterRo); + log.info(`允许${result}加入世界`); + enterCount++; + break; + } + } + await sleep(500); + } + } + if (await isYUI()) { + keyPress("VK_ESCAPE"); + } + } + if (enterCount >= maxEnterCount) { + log.info("已达到预定人数,结束js"); + break; + } + } + +} +)(); + +//等待主界面状态 +async function waitForMainUI(requirement, timeOut = 60 * 1000) { + + log.info(`等待至多${timeOut}毫秒`) + const startTime = Date.now(); + while (Date.now() - startTime < timeOut) { + const mainUIState = await isMainUI(); + if (mainUIState === requirement) return true; + + const elapsed = Date.now() - startTime; + const min = Math.floor(elapsed / 60000); + const sec = Math.floor((elapsed % 60000) / 1000); + const ms = elapsed % 1000; + log.info(`已等待 ${min}分 ${sec}秒 ${ms}毫秒`); + + await sleep(1000); + } + log.error("超时仍未到达指定状态"); + return false; +} + +//检查是否在主界面 +async function isMainUI() { + // 修改后的图像路径 + const imagePath = "assets/RecognitionObject/MainUI.png"; + // 修改后的识别区域(左上角区域) + const xMin = 0; + const yMin = 0; + const width = 150; // 识别区域宽度 + const height = 150; // 识别区域高度 + let template = file.ReadImageMatSync(imagePath); + let recognitionObject = RecognitionObject.TemplateMatch(template, xMin, yMin, width, height); + + // 尝试次数设置为 5 次 + const maxAttempts = 5; + + let attempts = 0; + while (attempts < maxAttempts) { + try { + + let gameRegion = captureGameRegion(); + let result = gameRegion.find(recognitionObject); + gameRegion.dispose(); + if (result.isExist()) { + //log.info("处于主界面"); + return true; // 如果找到图标,返回 true + } + } catch (error) { + log.error(`识别图像时发生异常: ${error.message}`); + + return false; // 发生异常时返回 false + } + attempts++; // 增加尝试次数 + await sleep(50); // 每次检测间隔 50 毫秒 + } + 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" + } + 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; +} + +async function findTotalNumber() { + await genshin.returnMainUi(); + await keyPress("F2"); + await sleep(2000); + + // 定义模板 + const kick2pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 277, 230, 120); + const kick3pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 400, 230, 120); + const kick4pRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/kickButton.png"), 1520, 527, 230, 120); + + moveMouseTo(1555, 860); // 防止鼠标干扰 + const gameRegion = captureGameRegion(); + await sleep(200); + + let count = 1; // 先算上自己 + + // 依次匹配 2P + if (gameRegion.Find(kick2pRo).isExist()) { + log.info("发现 2P"); + count++; + } + + // 依次匹配 3P + if (gameRegion.Find(kick3pRo).isExist()) { + log.info("发现 3P"); + count++; + } + + // 依次匹配 4P + if (gameRegion.Find(kick4pRo).isExist()) { + log.info("发现 4P"); + count++; + } + + gameRegion.dispose(); + + log.info(`当前联机世界玩家总数(含自己):${count}`); + return count; +} + +async function confirmSearchResult() { + maxAttempts = 3; + for (let attempts = 0; attempts < maxAttempts; attempts++) { + const gameRegion = captureGameRegion(); + try { + const result = gameRegion.find(requestEnter2Ro); + if (result.isExist) { + return false; // 成功立刻返回 + } + } catch (err) { + } finally { + gameRegion.dispose(); + } + if (attempts < maxAttempts - 1) { // 最后一次不再 sleep + await sleep(250); + } + } + return true; +} + +async function findAndClick(target, maxAttempts = 20) { + for (let attempts = 0; attempts < maxAttempts; attempts++) { + const gameRegion = captureGameRegion(); + try { + const result = gameRegion.find(target); + if (result.isExist) { + result.click(); + return true; // 成功立刻返回 + } + log.warn(`识别失败,第 ${attempts + 1} 次重试`); + } catch (err) { + } finally { + gameRegion.dispose(); + } + if (attempts < maxAttempts - 1) { // 最后一次不再 sleep + await sleep(250); + } + } + log.error("已达到重试次数上限,仍未找到目标"); + return false; +} + +//检查是否在主界面 +async function isYUI() { + // 尝试次数设置为 5 次 + const maxAttempts = 5; + let attempts = 0; + while (attempts < maxAttempts) { + try { + let gameRegion = captureGameRegion(); + let result = gameRegion.find(yUIRo); + gameRegion.dispose(); + if (result.isExist()) { + //log.info("处于Y界面"); + return true; // 如果找到图标,返回 true + } + } catch (error) { + log.error(`识别图像时发生异常: ${error.message}`); + + return false; // 发生异常时返回 false + } + attempts++; // 增加尝试次数 + await sleep(250); // 每次检测间隔 50 毫秒 + } + return false; // 如果尝试次数达到上限或取消,返回 false +} + +// 确认当前申请是否为目标 +async function recognizeRequest() { + // 1. 模板匹配 + try { + const gameRegion = captureGameRegion(); + for (const { ro, baseName } of targetsRo) { + if (gameRegion.find(ro).isExist()) { + gameRegion.dispose(); + return baseName; + } + } + gameRegion.dispose(); + } catch (err) { + log.error(`模板匹配异常: ${err.message}`); + } + + // 2. OCR 仅识别一次,固定区域 650,320,350,60 + try { + const gameRegion = captureGameRegion(); + const resList = gameRegion.findMulti( + RecognitionObject.ocr(650, 320, 350, 60) + ); + gameRegion.dispose(); + + let hit = null; + for (const res of resList) { + const txt = res.text.trim(); + for (const whiteItem of targetList) { + if (txt === whiteItem) { + hit = whiteItem; + break; + } + } + if (hit) break; + } + + // 如果未命中,输出所有识别结果 + if (!hit) { + for (const res of resList) { + log.warn(`识别到"${res.text.trim()}",不在白名单`); + } + } + + return hit; // 命中返回白名单项,未命中返回 null + } catch (err) { + log.error(`OCR 识别异常: ${err.message}`); + } + + return null; +} + + +// 定义 readFolder 函数 +async function readFolder(folderPath, onlyJson) { + // 新增一个堆栈,初始时包含 folderPath + const folderStack = [folderPath]; + + // 新增一个数组,用于存储文件信息对象 + const files = []; + + // 当堆栈不为空时,继续处理 + while (folderStack.length > 0) { + // 从堆栈中弹出一个路径 + const currentPath = folderStack.pop(); + + // 读取当前路径下的所有文件和子文件夹路径 + const filesInSubFolder = file.ReadPathSync(currentPath); + + // 临时数组,用于存储子文件夹路径 + const subFolders = []; + for (const filePath of filesInSubFolder) { + if (file.IsFolder(filePath)) { + // 如果是文件夹,先存储到临时数组中 + subFolders.push(filePath); + } else { + // 如果是文件,根据 onlyJson 判断是否存储 + if (onlyJson) { + if (filePath.endsWith(".json")) { + const fileName = filePath.split('\\').pop(); // 提取文件名 + const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 + files.push({ + fullPath: filePath, + fileName: fileName, + folderPathArray: folderPathArray + }); + //log.info(`找到 JSON 文件:${filePath}`); + } + } else { + const fileName = filePath.split('\\').pop(); // 提取文件名 + const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组 + files.push({ + fullPath: filePath, + fileName: fileName, + folderPathArray: folderPathArray + }); + //log.info(`找到文件:${filePath}`); + } + } + } + // 将临时数组中的子文件夹路径按原顺序压入堆栈 + folderStack.push(...subFolders.reverse()); // 反转子文件夹路径 + } + + return files; +} \ No newline at end of file diff --git a/repo/js/AutoEnter/manifest.json b/repo/js/AutoEnter/manifest.json new file mode 100644 index 000000000..f3f0f71cc --- /dev/null +++ b/repo/js/AutoEnter/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 1, + "name": "进入联机状态", + "version": "0.0.1", + "tags": [ + "狗粮" + ], + "description": "配合AAA狗粮联机团购使用,自动进入别人世界或批准别人加入", + "saved_files": [ + "records/*.txt" + ], + "authors": [ + { + "name": "mno", + "links": "https://github.com/Bedrockx" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} \ No newline at end of file diff --git a/repo/js/AutoEnter/settings.json b/repo/js/AutoEnter/settings.json new file mode 100644 index 000000000..4fc0aec4d --- /dev/null +++ b/repo/js/AutoEnter/settings.json @@ -0,0 +1,54 @@ +[ + { + "name": "enterMode", + "type": "select", + "label": "进入世界选项", + "options": [ + "进入他人世界", + "等待他人进入" + ], + "default": "进入他人世界" + }, + { + "name": "enteringUID", + "type": "input-text", + "label": "要进入的世界的uid,只在进入他人世界状态下生效" + }, + { + "name": "permissionMode", + "type": "select", + "label": "允许加入模式,只在等待他人加入状态下生效", + "options": [ + "无条件通过", + "只通过特定用户" + ], + "default": "无条件通过" + }, + { + "name": "maxEnterCount", + "type": "input-text", + "label": "最大加入人数", + "default": 3 + }, + { + "name": "nameToPermit1", + "type": "input-text", + "label": "允许加入的用户名称1" + }, + { + "name": "nameToPermit2", + "type": "input-text", + "label": "允许加入的用户名称2" + }, + { + "name": "nameToPermit3", + "type": "input-text", + "label": "允许加入的用户名称3" + }, + { + "name": "timeOut", + "type": "input-text", + "label": "超时时间,单位分钟", + "default": "5" + } +] \ No newline at end of file diff --git a/repo/js/AutoEnter/targets/火山老师.png b/repo/js/AutoEnter/targets/火山老师.png new file mode 100644 index 000000000..6c047f973 Binary files /dev/null and b/repo/js/AutoEnter/targets/火山老师.png differ