Files
bettergi-scripts-list/repo/js/AutoEnter/main.js
mno 69d9f906cd js:联机团购 (#2985)
* js:联机狗粮团购

增加额外的检测点

* js:联机团购增加保留拾取图片

* 修复autoenter以适配更新后的ui

---------

Co-authored-by: 起个名字好难的喵 <25520958+MisakaAldrich@users.noreply.github.com>
2026-03-13 16:47:53 +08:00

463 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const leaveTeamRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/leaveTeam.png"));
/**
* 递归读取目录下所有文件
* @param {string} folderPath 起始目录
* @param {string} [ext=''] 需要的文件后缀,空字符串表示不限制;例如 'json' 或 '.json' 均可
* @returns {Array<{fullPath:string, fileName:string, folderPathArray:string[]}>}
*/
async function readFolder(folderPath, ext = '') {
// 统一后缀格式:确保前面有一个点,且全小写
const targetExt = ext ? (ext.startsWith('.') ? ext : `.${ext}`).toLowerCase() : '';
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 {
// 后缀过滤
if (targetExt) {
const fileExt = filePath.toLowerCase().slice(filePath.lastIndexOf('.'));
if (fileExt !== targetExt) continue;
}
const fileName = filePath.split('\\').pop();
const folderPathArray = filePath.split('\\').slice(0, -1);
files.push({ fullPath: filePath, fileName, folderPathArray });
}
}
// 保持同层顺序reverse 后仍按原顺序入栈
folderStack.push(...subFolders.reverse());
}
return files;
}
(async function () {
await autoEnter(settings);
}
)();
/**
* 自动联机脚本(整体打包为一个函数)
* @param {Object} autoEnterSettings 配置对象
* enterMode: "进入他人世界" | "等待他人进入"
* enteringUID: string | null
* permissionMode: "无条件通过" | "白名单"
* nameToPermit1/2/3: string | null
* timeout: 分钟
* maxEnterCount: number
*/
async function autoEnter(autoEnterSettings) {
// ===== 配置解析 =====
const enterMode = autoEnterSettings.enterMode || "进入他人世界";
const enteringUID = autoEnterSettings.enteringUID;
const permissionMode = autoEnterSettings.permissionMode || "无条件通过";
const timeout = +autoEnterSettings.timeOut || 5;
const maxEnterCount = +autoEnterSettings.maxEnterCount || 3;
// 白名单
const targetList = [];
[autoEnterSettings.nameToPermit1, autoEnterSettings.nameToPermit2, autoEnterSettings.nameToPermit3]
.forEach(v => v && targetList.push(v));
// ===== 模板 / 路径 =====
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, 600);
const yUIRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/yUI.png"));
const allowEnterRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/allowEnter.png"));
const targetsPath = "targets";
// ===== 状态 =====
let enterCount = 0;
let targetsRo = [];
let checkToEnd = false;
// ===== 初始化 =====
setGameMetrics(1920, 1080, 1);
const start = new Date();
log.info(`当前模式为:${enterMode}`);
// 加载目标 PNG
const targetPngs = await readFolder(targetsPath, ".png");
for (const f of targetPngs) {
const mat = file.ReadImageMatSync(f.fullPath);
const ro = RecognitionObject.TemplateMatch(mat, 664, 481, 1355 - 668, 588 - 484);
const baseName = f.fileName.replace(/\.png$/i, '');
targetsRo.push({ ro, baseName });
}
log.info(`加载完成共 ${targetsRo.length} 个目标`);
// ===== 主循环 =====
while (new Date() - start < timeout * 60 * 1000) {
if (enterMode === "进入他人世界") {
const playerSign = await getPlayerSign();
await sleep(500);
if (playerSign > 1) {
log.info(`加入成功,队伍编号 ${playerSign}`);
break;
} else if (playerSign === -1) {
log.warn("队伍编号识别异常尝试按0p处理");
}
log.info('不处于多人世界,开始尝试加入');
await genshin.returnMainUi(); await sleep(500);
if (!enteringUID) { log.error('未填写有效 UID'); break; }
await keyPress("F2"); await sleep(2000);
if (!await findAndClick(enterUIDRo)) { await genshin.returnMainUi(); continue; }
await sleep(1000); inputText(enteringUID);
await sleep(1000);
if (!await findAndClick(searchRo)) { await genshin.returnMainUi(); continue; }
await sleep(500);
if (!await confirmSearchResult()) { await genshin.returnMainUi(); log.warn("无搜索结果"); continue; }
await sleep(500);
if (!await findAndClick(requestEnterRo)) { await genshin.returnMainUi(); continue; }
await waitForMainUI(true, 20 * 1000);
} else { // 等待他人进入
const playerSign = await getPlayerSign();
if (playerSign > 1) {
log.warn("处于他人世界,先尝试退出");
let leaveAttempts = 0;
while (leaveAttempts < 10) {
if (await getPlayerSign() === 0) {
break;
}
await keyPress("F2");
await sleep(1000);
await findAndClick(leaveTeamRo);
await sleep(1000);
keyPress("VK_ESCAPE");
await waitForMainUI(true);
await genshin.returnMainUi();
}
}
if (enterCount >= maxEnterCount) break;
if (await isYUI()) keyPress("VK_ESCAPE"); await sleep(500);
await genshin.returnMainUi();
keyPress("Y"); await sleep(250);
if (!await isYUI()) continue;
log.info("处于 Y 界面,开始识别");
let attempts = 0;
while (attempts++ < 5) {
if (permissionMode === "无条件通过") {
if (await findAndClick(allowEnterRo)) {
await waitForMainUI(true, 20 * 1000);
enterCount++;
break;
}
} else {
const result = await recognizeRequest();
if (result) {
if (await findAndClick(allowEnterRo)) {
await waitForMainUI(true, 20 * 1000);
enterCount++;
log.info(`允许 ${result} 加入`);
notification.send(`允许 ${result} 加入`);
if (await isYUI()) { keyPress("VK_ESCAPE"); await sleep(500); await genshin.returnMainUi(); }
break;
} else {
if (await isYUI()) { keyPress("VK_ESCAPE"); await sleep(500); await genshin.returnMainUi(); }
}
}
}
await sleep(500);
}
if (await isYUI()) { keyPress("VK_ESCAPE"); await genshin.returnMainUi(); }
if (enterCount >= maxEnterCount || checkToEnd) {
checkToEnd = true;
await sleep(20000);
if (await findTotalNumber() === maxEnterCount + 1) {
notification.send(`已达到预定人数:${maxEnterCount + 1}`);
break;
}
else enterCount--;
}
}
}
if (new Date() - start >= timeout * 60 * 1000) {
log.warn("超时未达到预定人数");
notification.error(`超时未达到预定人数`);
}
async function confirmSearchResult() {
for (let i = 0; i < 4; i++) {
const gameRegion = captureGameRegion();
const res = gameRegion.find(requestEnter2Ro);
gameRegion.dispose();
if (res.isExist()) return false;
if (i < 4) await sleep(250);
}
return true;
}
async function isYUI() {
for (let i = 0; i < 5; i++) {
const gameRegion = captureGameRegion();
const res = gameRegion.find(yUIRo);
gameRegion.dispose();
if (res.isExist()) return true;
await sleep(250);
}
return false;
}
async function recognizeRequest() {
try {
const gameRegion = captureGameRegion();
for (const { ro, baseName } of targetsRo) {
if (gameRegion.find(ro).isExist()) { gameRegion.dispose(); return baseName; }
}
gameRegion.dispose();
} catch { }
try {
const gameRegion = captureGameRegion();
const resList = gameRegion.findMulti(RecognitionObject.ocr(664, 481, 1355 - 668, 588 - 484));
gameRegion.dispose();
let hit = null;
for (const res of resList) {
const txt = res.text.trim();
if (targetList.includes(txt)) { hit = txt; break; }
}
if (!hit) resList.forEach(r => log.warn(`识别到"${r.text.trim()}",不在白名单`));
return hit;
} catch { return null; }
}
}
/**
* 通用找图/找RO并可选点击支持单图片文件路径、单RO、图片文件路径数组、RO数组
* @param {string|string[]|RecognitionObject|RecognitionObject[]} target
* @param {boolean} [doClick=true] 是否点击
* @param {number} [timeout=3000] 识别时间上限ms
* @param {number} [interval=50] 识别间隔ms
* @param {number} [retType=0] 0-返回布尔1-返回 Region 结果
* @param {number} [preClickDelay=50] 点击前等待
* @param {number} [postClickDelay=50] 点击后等待
* @returns {boolean|Region} 根据 retType 返回是否成功或最终 Region
*/
async function findAndClick(target,
doClick = true,
timeout = 3000,
interval = 50,
retType = 0,
preClickDelay = 50,
postClickDelay = 50) {
try {
// 1. 统一转成 RecognitionObject 数组
let ros = [];
if (Array.isArray(target)) {
ros = target.map(t =>
(typeof t === 'string')
? RecognitionObject.TemplateMatch(file.ReadImageMatSync(t))
: t
);
} else {
ros = [(typeof target === 'string')
? RecognitionObject.TemplateMatch(file.ReadImageMatSync(target))
: target];
}
const start = Date.now();
let found = null;
while (Date.now() - start <= timeout) {
const gameRegion = captureGameRegion();
try {
// 依次尝试每一个 ro
for (const ro of ros) {
const res = gameRegion.find(ro);
if (!res.isEmpty()) { // 找到
found = res;
if (doClick) {
await sleep(preClickDelay);
res.click();
await sleep(postClickDelay);
}
break; // 成功即跳出 for
}
}
if (found) break; // 成功即跳出 while
} finally {
gameRegion.dispose();
}
await sleep(interval); // 没找到时等待
}
// 3. 按需返回
return retType === 0 ? !!found : (found || null);
} catch (error) {
log.error(`执行通用识图时出现错误:${error.message}`);
return retType === 0 ? false : null;
}
}
//等待主界面状态
async function waitForMainUI(requirement, timeOut = 60 * 1000) {
log.info(`等待至多${timeOut}毫秒`)
const startTime = Date.now();
let logcount = 0;
while (Date.now() - startTime < timeOut) {
const mainUIState = await isMainUI();
logcount++;
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;
if (logcount >= 50) {
logcount = 0;
log.info(`已等待 ${min}${sec}${ms}毫秒`);
}
await sleep(200);
}
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(250); // 每次检测间隔 250 毫秒
}
return false; // 如果尝试次数达到上限或取消,返回 false
}
//获取联机世界的当前玩家标识
async function getPlayerSign() {
let attempts = 0;
while (attempts < 10) {
attempts++;
const picDic = {
"0P": "assets/RecognitionObject/0P.png",
"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 p0Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["0P"]), 200, 10, 400, 70);
p0Ro.Threshold = 0.95;
p0Ro.InitTemplate();
const p1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["1P"]), 200, 10, 400, 70);
p1Ro.Threshold = 0.95;
p1Ro.InitTemplate();
const p2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["2P"]), 200, 10, 400, 70);
p2Ro.Threshold = 0.95;
p2Ro.InitTemplate();
const p3Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["3P"]), 200, 10, 400, 70);
p3Ro.Threshold = 0.95;
p3Ro.InitTemplate();
const p4Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(picDic["4P"]), 200, 10, 400, 70);
p4Ro.Threshold = 0.95;
p4Ro.InitTemplate();
moveMouseTo(1555, 860); // 移走鼠标,防止干扰识别
const gameRegion = captureGameRegion();
// 当前页面模板匹配
let p0 = gameRegion.Find(p0Ro);
let p1 = gameRegion.Find(p1Ro);
let p2 = gameRegion.Find(p2Ro);
let p3 = gameRegion.Find(p3Ro);
let p4 = gameRegion.Find(p4Ro);
gameRegion.dispose();
if (p0.isExist()) { log.info("识别结果为0P"); return 0; }
if (p1.isExist()) { log.info("识别结果为1P"); return 1; }
if (p2.isExist()) { log.info("识别结果为2P"); return 2; }
if (p3.isExist()) { log.info("识别结果为3P"); return 3; }
if (p4.isExist()) { log.info("识别结果为4P"); return 4; }
await genshin.returnMainUi();
await sleep(250);
}
log.warn("超时仍未识别到队伍编号");
return -1;
}
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;
}