Files
bettergi-scripts-list/repo/js/七圣召唤七日历练全自动/main.js
2025-07-22 10:28:22 +08:00

641 lines
21 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.

// 切换到指定的队伍
async function switchCardTeam(Name) {
let captureRegion = captureGameRegion();
let teamName = captureRegion.find(RecognitionObject.ocr(1305, 793, 206, 46));
log.info("当前队伍名称: {text}", teamName.text);
if (teamName.text != Name) {
click(1312, 812); //点击队伍名称的糟糕UI
await sleep(1000);
moveMouseTo(100, 200);
leftButtonDown();
// 不能一次移动太多,否则会丢拖动
for (let i = 1; i <= 9; i++) {
await sleep(50);
moveMouseTo(200 * i, 200);
}
await sleep(200);
leftButtonUp();
await sleep(1000);
captureRegion = captureGameRegion();
for (let i = 0; i < 4; i++) {
let x = 135 + 463 * i;
let res = captureRegion.find(RecognitionObject.ocr(x, 762, 230, 46));
if (res.text == Name) {
log.info("切换至队伍: {text}", res.text);
res.click();
await sleep(500);
click(1164, 1016); // 选择
await sleep(4000); // 等待"出战牌组"的强制延时框消失
break;
}
}
}
}
(async function () {
// 存储挑战玩家信息
let textArray = [];
let skipNum = 0;
/**
* 判断任务是否已刷新
* @param {string} filePath - 存储最后完成时间的文件路径
* @param {object} options - 配置选项
* @param {string} [options.refreshType] - 刷新类型: 'hourly'|'daily'|'weekly'|'monthly'|'custom'
* @param {number} [options.customHours] - 自定义小时数(用于'custom'类型)
* @param {number} [options.dailyHour=4] - 每日刷新的小时(0-23)
* @param {number} [options.weeklyDay=1] - 每周刷新的星期(0-6, 0是周日)
* @param {number} [options.weeklyHour=4] - 每周刷新的小时(0-23)
* @param {number} [options.monthlyDay=1] - 每月刷新的日期(1-31)
* @param {number} [options.monthlyHour=4] - 每月刷新的小时(0-23)
* @returns {Promise<boolean>} - 是否已刷新
*/
async function isTaskRefreshed(filePath, options = {}) {
const {
refreshType = 'hourly', // 默认每小时刷新
customHours = 24, // 自定义刷新小时数默认24
dailyHour = 4, // 每日刷新默认凌晨4点
weeklyDay = 1, // 每周刷新默认周一(0是周日)
weeklyHour = 4, // 每周刷新默认凌晨4点
monthlyDay = 1, // 每月刷新默认第1天
monthlyHour = 4 // 每月刷新默认凌晨4点
} = options;
try {
// 读取文件内容
let content = await file.readText(filePath);
const lastTime = new Date(content);
const nowTime = new Date();
let shouldRefresh = false;
switch (refreshType) {
case 'hourly': // 每小时刷新
shouldRefresh = (nowTime - lastTime) >= 3600 * 1000;
break;
case 'daily': // 每天固定时间刷新
// 检查是否已经过了当天的刷新时间
const todayRefresh = new Date(nowTime);
todayRefresh.setHours(dailyHour, 0, 0, 0);
// 如果当前时间已经过了今天的刷新时间,检查上次完成时间是否在今天刷新之前
if (nowTime >= todayRefresh) {
shouldRefresh = lastTime < todayRefresh;
} else {
// 否则检查上次完成时间是否在昨天刷新之前
const yesterdayRefresh = new Date(todayRefresh);
yesterdayRefresh.setDate(yesterdayRefresh.getDate() - 1);
shouldRefresh = lastTime < yesterdayRefresh;
}
break;
case 'weekly': // 每周固定时间刷新
// 获取本周的刷新时间
const thisWeekRefresh = new Date(nowTime);
// 计算与本周指定星期几的差值
const dayDiff = (thisWeekRefresh.getDay() - weeklyDay + 7) % 7;
thisWeekRefresh.setDate(thisWeekRefresh.getDate() - dayDiff);
thisWeekRefresh.setHours(weeklyHour, 0, 0, 0);
// 如果当前时间已经过了本周的刷新时间
if (nowTime >= thisWeekRefresh) {
shouldRefresh = lastTime < thisWeekRefresh;
} else {
// 否则检查上次完成时间是否在上周刷新之前
const lastWeekRefresh = new Date(thisWeekRefresh);
lastWeekRefresh.setDate(lastWeekRefresh.getDate() - 7);
shouldRefresh = lastTime < lastWeekRefresh;
}
break;
case 'monthly': // 每月固定时间刷新
// 获取本月的刷新时间
const thisMonthRefresh = new Date(nowTime);
// 设置为本月指定日期的凌晨
thisMonthRefresh.setDate(monthlyDay);
thisMonthRefresh.setHours(monthlyHour, 0, 0, 0);
// 如果当前时间已经过了本月的刷新时间
if (nowTime >= thisMonthRefresh) {
shouldRefresh = lastTime < thisMonthRefresh;
} else {
// 否则检查上次完成时间是否在上月刷新之前
const lastMonthRefresh = new Date(thisMonthRefresh);
lastMonthRefresh.setMonth(lastMonthRefresh.getMonth() - 1);
shouldRefresh = lastTime < lastMonthRefresh;
}
break;
case 'custom': // 自定义小时数刷新
shouldRefresh = (nowTime - lastTime) >= customHours * 3600 * 1000;
break;
default:
throw new Error(`未知的刷新类型: ${refreshType}`);
}
// 如果文件内容无效或不存在,视为需要刷新
if (!content || isNaN(lastTime.getTime())) {
await file.writeText(filePath, '');
shouldRefresh = true;
}
if (shouldRefresh) {
notification.send(`七圣召唤七日历练周期已经刷新,执行脚本`);
return true;
} else {
notification.send(`七圣召唤七日历练未刷新,冷却还有${((nowTime - lastTime)/3600).toFixed(1)}小时`);
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//检查挑战结果 await checkChallengeResults();
async function checkChallengeResults() {
const region1 = RecognitionObject.ocr(785, 890, 340, 82); // 对话区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
if (res1.isEmpty()) {
await sleep(1000);
click(960, 540);
await sleep(500);
click(1860, 50); //避免失败卡死
await sleep(1000);
click(1600, 260);
await sleep(1000);
click(1180, 756);
await sleep(6000);
click(754, 915); //退出挑战
await sleep(4000);
await autoConversation();
await sleep(1000);
return;
} else {
await sleep(1000);
click(754, 915); //退出挑战
await sleep(4000);
await autoConversation();
await sleep(1000);
return;
}
}
//通过f和空格自动对话对话标志消失时停止await autoConversation();
async function autoConversation() {
await sleep(500); //点击后等待一段时间避免误判
const talkRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/talkSymbol.png"));
let talkTime = 0;
let talkTimes = 0;
log.info("准备开始对话");
//最多10次对话
while (talkTime < 30) {
let talk = captureGameRegion().find(talkRo);
if (talk.isExist()) {
await sleep(300);
keyPress("VK_SPACE");
await sleep(300);
keyPress("F");
talkTimes++;
await sleep(1500);
}
else if(talkTimes){
log.info("对话结束");
return ;
}
talkTime++;
await sleep(1200);
}
throw new Error("对话时间超时");
}
//检测传送结束
async function tpEndDetection() {
const region = RecognitionObject.ocr(1690, 230, 75, 350); // 队伍名称区域
let tpTime = 0;
await sleep(500); //点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res = capture.find(region);
if (!res.isEmpty()) {
log.info("传送完成");
await sleep(1200); //传送结束后有僵直
return;
}
tpTime++;
await sleep(100);
}
throw new Error("传送时间超时");
}
// 打开地图,查看玩家位置,并前往相应位置
const cardPlayerRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/cardPlayer.png"));
const detectCardPlayer = async () => {
// 定义要检测的6个点位及对应的处理函数
let i = 0;
let findNum = 0;
const checkPoints = [
{ x: 1475, y: 730, action: async () => await gotoTable1() }, // 1号桌
{ x: 1680, y: 780, action: async () => await gotoTable2() }, // 2号桌
{ x: 1645, y: 575, action: async () => await gotoTable3() }, // 3号桌
{ x: 1460, y: 360, action: async () => await gotoTable4() }, // 4号桌
{ x: 1550, y: 0 , action: async () => await gotoTable5() }, // 包间1
{ x: 1130, y: 520, action: async () => await gotoTable6() }, // 包间2
];
keyPress("M");
await sleep(1200);
await genshin.setBigMapZoomLevel(1.0); //放大地图
await sleep(300);
//地图拖动到指定位置
moveMouseTo(200, 200);
leftButtonDown();
await sleep(500);
moveMouseTo(170, 320);
await sleep(500);
moveMouseTo(970, 1000);
await sleep(500);
leftButtonUp();
await sleep(500);
// 获取游戏区域截图
const captureRegion = captureGameRegion();
for (const point of checkPoints) {
i++;
// 遍历所有检测点位
const cropRegion = captureRegion.DeriveCrop(point.x, point.y, 160, 160);
// 在裁剪区域中查找卡片
const result = cropRegion.Find(cardPlayerRo);
// 如果找到卡片
if (!result.IsEmpty()) {
findNum++;
if (findNum - skipNum == 1) {
log.info(`在点位${i}找到玩家,执行对应操作`);
await sleep(1000);
keyPress("ESCAPE");
await sleep(1500);
await point.action(); // 调用该点位对应的函数
return true; // 返回true表示已找到并处理
}
}
}
// 所有点位都未找到
log.info("未在任何检测点找到玩家");
textArray.length = 0;
return false;
};
//获取挑战对象名称
async function captureAndStoreTexts() {
// 清空数组
textArray = [];
// 四个固定位置坐标
const positions = [
{ x: 450, y: 620 },
{ x: 760, y: 620 },
{ x: 1070, y: 620 },
{ x: 1380, y: 620 },
];
// 截取区域大小
const width = 240;
const height = 56;
await sleep(500);
keyPress("F6");
await sleep(1000);
click(300, 370); //点击七日历练
await sleep(1000);
// 获取游戏区域截图
const captureRegion = captureGameRegion();
// 遍历四个位置进行OCR识别
for (const pos of positions) {
// 创建OCR识别区域
const ocrRo = RecognitionObject.ocr(pos.x, pos.y, width, height); //挑战者名字区域
const ocrRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/completed.png"),pos.x, pos.y + 60, width, height+80);
// 在指定区域进行OCR识别
const result = captureRegion.find(ocrRo);
let res2 = captureRegion.find(ocrRo2);
if (!result.isEmpty() && result.text) {
// 存储识别结果和对应位置
if (res2.isExist()) {
log.info(`识别到文本: ${result.text} 位置: (${pos.x}, ${pos.y})`);
textArray.push({
text: result.text.trim(),
x: pos.x + width / 2, // 点击中心位置
y: pos.y + height / 2,
});
}
} else {
log.warn(`位置 (${pos.x}, ${pos.y}) 未识别到文本`);
}
}
log.info(`剩余挑战人数:${textArray.length}`);
keyPress("ESCAPE");
await sleep(1000);
}
//检查是否有对应的挑战对手
async function searchAndClickTexts() {
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(800);
moveMouseBy(0, 1030);
await sleep(800);
// 限定区域坐标和大小
const searchX = 1210;
const searchY = 440;
const searchWidth = 150;
const searchHeight = 195;
// 获取游戏区域截图
const captureRegion = captureGameRegion();
// 在限定区域内进行OCR识别
const ocrRo = RecognitionObject.ocr(searchX, searchY, searchWidth, searchHeight);
const results = captureRegion.findMulti(ocrRo);
// 遍历OCR结果
for (let i = 0; i < results.count; i++) {
const res = results[i];
const resText = res.text.trim();
// 在存储的文本数组中查找匹配项
const index = textArray.findIndex((item) => item.text === resText);
if (index !== -1) {
// 找到匹配项,点击对应位置
log.info(`找到匹配文本: ${resText}`);
skipNum = 0;
// 点击存储的位置
await keyMouseScript.runFile(`assets/ALT点击.json`);
await sleep(500);
res.click();
await sleep(500);
await keyMouseScript.runFile(`assets/ALT释放.json`);
await Playcards();
// 从数组中移除已处理的文本
textArray.splice(index, 1);
return true;
}
}
log.info(`未找到匹配文本`);
skipNum++;
return false;
}
//函数:打开地图前往猫尾酒馆
async function gotoTavern() {
const tavernRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/tavern.png"));
await genshin.returnMainUi();
await sleep(1000);
keyPress("m");
await sleep(1500);
click(1841, 1015); //地图选择
await sleep(1000);
click(1460, 140); //蒙德
await sleep(1200);
//放大地图
await genshin.setBigMapZoomLevel(1.0);
await sleep(400);
click(1000, 645); //猫尾酒馆
await sleep(600);
let tavern = captureGameRegion().find(tavernRo);
if (tavern.isExist()) {
tavern.click();
await sleep(500);
} else {
throw new Error("未能找到猫尾酒馆");
}
click(1707, 1010); //确认传送
await sleep(1000);
await tpEndDetection();
}
async function waitOrCheckMaxCoin(wait_time_ms) {
const startTime = new Date().getTime();
while (new Date().getTime() - startTime < wait_time_ms) {
let captureRegion = captureGameRegion();
let result = captureRegion.find(RecognitionObject.ocr(578, 600, 763, 41));
// 道具已达到容量上限,无法获取对应奖励且挑战目标无法完成,是否继续进行挑战
if (!result.isEmpty() && result.text.includes("道具已达到容量上限")) {
let coin = "?";
let result2 = captureRegion.find(RecognitionObject.ocr(916, 530, 89, 41));
if (!result2.isEmpty()) {
coin = result2.text.trim();
}
click(733, 730); //点击取消
await sleep(1000);
click(1860, 250); //点击右上角X退出打牌对话界面
throw new Error(`幸运牌币${coin},已达到容量上限,无法获取对应奖励且挑战目标无法完成`);
}
await sleep(1000);
// 无break以确保牌币未满时延时行为与此前一致
}
}
//函数:对话和打牌
async function Playcards() {
await sleep(800); //略微俯视,避免名字出现在选项框附近,导致错误点击
moveMouseBy(0, 1030);
await sleep(1000);
await autoConversation();
log.info("对话完成");
await sleep(1500);
if (settings.partyName != undefined) {
await switchCardTeam(settings.partyName);
}
click(1610, 900); //点击挑战
await waitOrCheckMaxCoin(8000);
await dispatcher.runTask(new SoloTask("AutoGeniusInvokation"));
await sleep(3000);
await checkChallengeResults();
await sleep(1000);
}
//前往一号桌
async function gotoTable1() {
log.info(`前往1号桌`);
keyDown("d");
await sleep(1500);
keyUp("d");
keyDown("w");
await sleep(400);
keyUp("w");
keyDown("d");
keyDown("w");
await sleep(1200);
keyUp("d");
keyUp("w");
await sleep(700);
}
//前往二号桌
async function gotoTable2() {
log.info(`前往2号桌`);
keyDown("d");
await sleep(1500);
keyUp("d");
keyDown("w");
await sleep(400);
keyUp("w");
keyDown("d");
keyDown("w");
await sleep(1200);
keyUp("d");
keyUp("w");
keyDown("s");
await sleep(700);
keyUp("s");
await sleep(700);
}
//前往三号桌
async function gotoTable3() {
log.info(`前往3号桌`);
keyDown("w");
await sleep(2000);
keyUp("w");
keyDown("d");
await sleep(5000);
keyUp("d");
keyDown("a");
await sleep(1500);
keyUp("a");
await sleep(700);
}
//前往四号桌
async function gotoTable4() {
log.info(`前往4号桌`);
keyDown("w");
await sleep(2000);
keyUp("w");
keyDown("d");
await sleep(5000);
keyUp("d");
keyDown("a");
await sleep(1500);
keyUp("a");
keyDown("d");
await sleep(200);
keyUp("d");
keyDown("w");
await sleep(2000);
keyUp("w");
await sleep(700);
}
//前往一号包间
async function gotoTable5() {
log.info(`前往1号包间`);
keyDown("w");
await sleep(2500);
keyUp("w");
keyDown("d");
await sleep(200);
keyUp("d");
await sleep(500);
keyPress("ESCAPE");
await sleep(1500);
keyPress("ESCAPE");
await sleep(1500);
keyDown("w");
await sleep(5900);
keyUp("w");
await sleep(700);
}
//前往二号包间
async function gotoTable6() {
log.info(`前往2号包间`);
await sleep(1500);
keyDown("d");
await sleep(1500);
keyUp("d");
keyDown("w");
keyDown("d");
await sleep(4000);
keyUp("d");
keyUp("w");
keyDown("a");
await sleep(1500);
keyUp("a");
keyDown("w");
await sleep(3000);
keyPress("VK_SPACE");
await sleep(1000);
keyUp("w");
keyDown("s");
await sleep(1000);
keyPress("VK_SPACE");
await sleep(700);
keyUp("s");
await sleep(500);
}
async function main() {
//主流程
const nowTime = new Date();
log.info(`前往猫尾酒馆`);
await gotoTavern();
await captureAndStoreTexts();
if (textArray.length != 0) {
await detectCardPlayer();
await searchAndClickTexts();
}
for (let i = 0; i < 20; i++) {
//循环兜底,避免角色未到达指定位置
if (textArray.length === 0) break;
await gotoTavern();
await detectCardPlayer();
await searchAndClickTexts();
}
await genshin.returnMainUi();
await captureAndStoreTexts();
notification.send(`打牌结束、剩余挑战人数:${textArray.length}`);
// 更新最后完成时间
if(textArray.length === 0) await file.writeText("assets/weekly.txt", nowTime.toISOString());
}
if( await isTaskRefreshed("assets/weekly.txt", {
refreshType: 'weekly',
weeklyDay: 1, // 周一
weeklyHour: 4 // 凌晨4点
})){
await main();
}
})();