Files
bettergi-scripts-list/repo/js/WeeklyBoss/utils.js
2025-07-16 00:38:50 +08:00

9386 lines
336 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 weeklyBoss1() {
//北风狼
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));//领奖图标
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(boxIconRo);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.isExist();
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进第${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进第${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(200);
keyUp("w");
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await pathingScript.runFile("assets/前往狼王.json");
await eatFood();//嗑药
await sleep(1000);
keyPress("F");
await sleep(13000);
await autoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本1北风狼挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本1北风狼挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss1.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本1北风狼已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss1.txt", new Date().toISOString());
else notification.send(`周本1北风狼领奖失败请检查相关设置`);
notification.send(`周本1北风狼挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本1北风狼挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本1北风狼可能无法领取奖励`);
}
}
else log.info("当前周本1北风狼冷却未刷新");
}
async function weeklyBoss2() {
//风魔龙
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
//征讨之花领奖(无图标前进检测)
const autoNavigateToReward = async () => {
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 30){
throw new Error('领奖前进时间超时');
}
// 前进一小步
keyDown("w");
await sleep(200);
keyUp("w");
advanceNum++;
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(122.6259765625,2657.634521484375,);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss2.txt", new Date().toISOString());
throw new Error('周本2风魔龙已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyDown("w");
await sleep(4000);
keyUp("w");
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
keyDown("s");
await sleep(1000);
keyUp("s");
keyDown("d");
await sleep(300);
keyUp("d");
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本2风魔龙挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本2风魔龙挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss2.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本2风魔龙已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss2.txt", new Date().toISOString());
else notification.send(`周本2风魔龙领奖失败请检查相关设置`);
notification.send(`周本2风魔龙挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本2风魔龙挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本2风魔龙可能无法领取奖励`);
}
}
else log.info("当前周本2风魔龙冷却未刷新");
}
async function weeklyBoss3() {
//公子
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
await sleep(200);
keyDown("s");
await sleep(1000);
keyUp("s");
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
// 其他情况: 可能处于转场动画,尝试点击快进
else if (!hasText2 && !hasText3) {
log.info("进入过场动画尝试快进");
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
await sleep(4000);
keyDown("s");
await sleep(2500);
keyUp("s");
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
//征讨之花领奖(无图标前进检测)
const autoNavigateToReward = async () => {
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
log.info(`总计前进步数:${advanceNum}`);
return;
}
else if(advanceNum > 4200){
log.info(`总计前进步数:${advanceNum}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('前进时间超时');
}
// 前进一小步
if((advanceNum%140)<70){
keyDown("w");
await sleep(200);
keyUp("w");
}
else if((advanceNum%140)>70){
keyDown("s");
await sleep(200);
keyUp("s");
}
else {
keyDown("d");
await sleep(600);
keyUp("d");
}
advanceNum++;
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(254.45,-904.58,);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss3.txt", new Date().toISOString());
throw new Error('周本3公子已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyDown("s");
await sleep(2400);
keyUp("s");
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
keyDown("s");
await sleep(2400);
keyUp("s");
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本3公子挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本3公子挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss3.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本3公子已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss3.txt", new Date().toISOString());
else notification.send(`周本3公子领奖失败请检查相关设置`);
notification.send(`周本3公子挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本3公子挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本3公子可能无法领取奖励`);
}
}
else log.info("当前周本3公子冷却未刷新");
}
async function weeklyBoss4() {
//若陀龙王
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(100);
keyUp("w");
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(1753.89,614.28);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss4.txt", new Date().toISOString());
throw new Error('周本4若陀龙王已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
await sleep(500);
keyDown("w");
await sleep(2000);
keyUp("w");
await sleep(10000);
keyDown("s");
await sleep(500);
keyDown("SHIFT");
await sleep(1000);
keyUp("SHIFT");
await sleep(500);
keyUp("s");
await sleep(800);
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本4若陀龙王挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本4若陀龙王挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss4.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本4若陀龙王已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss4.txt", new Date().toISOString());
else notification.send(`周本4若陀龙王领奖失败请检查相关设置`);
notification.send(`周本4若陀龙王挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本4若陀龙王挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本4若陀龙王可能无法领取奖励`);
}
}
else log.info("当前周本4若陀龙王冷却未刷新");
}
async function weeklyBoss5() {
//女士
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖(无图标前进检测)
const autoNavigateToReward = async () => {
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
log.info(`总计前进步数:${advanceNum}`);
return;
}
else if(advanceNum > 1250){
log.info(`总计前进步数:${advanceNum}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('前进时间超时');
}
// 前进一小步
if((advanceNum%100)<50){
keyDown("w");
await sleep(200);
keyUp("w");
}
else if((advanceNum%100)>50){
keyDown("s");
await sleep(200);
keyUp("s");
}
else {
keyDown("d");
await sleep(700);
keyUp("d");
}
advanceNum++;
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
await sleep(200);
keyDown("s");
await sleep(1000);
keyUp("s");
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
// 其他情况: 三个区域均无文字,可能处于转场动画,尝试点击快进
else if (!hasText2 && !hasText3){
log.info("进入过场动画尝试快进");
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
keyDown("s");
await sleep(1800);
keyUp("s");
await autoFightAsync();
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(-4561.248046875,-3353.7587890625);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss5.txt", new Date().toISOString());
throw new Error('周本5女士已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyPress("1");
await sleep(1000);//切回固定行走位
keyDown("w");
await sleep(1500);
keyUp("w");
await sleep(8500);
keyDown("s");
await sleep(200);
keyDown("SHIFT");
await sleep(300);
keyUp("SHIFT");
await sleep(400);
keyUp("s");
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await sleep(1000);
keyDown("s");
await sleep(4000);
keyUp("s");
await sleep(4000);
keyDown("a");
await sleep(300);
keyUp("a");
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本5女士挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本5女士挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss5.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本5女士已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss5.txt", new Date().toISOString());
else notification.send(`周本5女士领奖失败请检查相关设置`);
notification.send(`周本5女士挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本5女士挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本5女士可能无法领取奖励`);
}
}
else log.info("当前周本5女士冷却未刷新");
}
async function weeklyBoss6() {
//雷神
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖(无图标前进检测)
const autoNavigateToReward = async () => {
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
log.info(`总计前进步数:${advanceNum}`);
return;
}
else if(advanceNum > 2100){
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
log.info(`总计前进步数:${advanceNum}`);
throw new Error('前进时间超时');
}
// 前进一小步
if((advanceNum%140)<70){
keyDown("w");
await sleep(200);
keyUp("w");
}
else if((advanceNum%140)>70){
keyDown("s");
await sleep(200);
keyUp("s");
}
else {
keyDown("d");
await sleep(600);
keyUp("d");
}
advanceNum++;
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
await sleep(200);
keyDown("s");
await sleep(1000);
keyUp("s");
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(-4403.830078125,-2481.962890625,);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss6.txt", new Date().toISOString());
throw new Error('周本6雷神已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyPress("1");
await sleep(1000);//切回固定行走位
keyDown("s");
await sleep(300);
keyDown("SHIFT");
await sleep(300);
keyUp("SHIFT");
await sleep(1000);
keyUp("s");
await autoFightAsync();
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
keyDown("s");
await sleep(4000);
keyUp("s");
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本6雷神挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本6雷神挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss6.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本6雷神已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss6.txt", new Date().toISOString());
else notification.send(`周本6雷神领奖失败请检查相关设置`);
notification.send(`周本6雷神挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本6雷神挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本6雷神可能无法领取奖励`);
}
}
else log.info("当前周本6雷神冷却未刷新");
}
async function weeklyBoss7() {
//散兵
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
await sleep(200);
keyDown("s");
await sleep(1000);
keyUp("s");
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
// 其他情况: 可能处于转场动画,尝试点击快进
else if (!hasText2 && !hasText3) {
log.info("进入过场动画尝试快进");
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
await sleep(4000);
keyDown("s");
await sleep(2500);
keyUp("s");
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
//征讨之花领奖(无图标前进检测)
const autoNavigateToReward = async () => {
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
log.info(`总计前进步数:${advanceNum}`);
return;
}
else if(advanceNum > 80){
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
log.info(`总计前进步数:${advanceNum}`);
throw new Error('前进时间超时');
}
// 前进一小步
keyDown("w");
await sleep(200);
keyUp("w");
advanceNum++;
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(2537.87,-522.9,);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss7.txt", new Date().toISOString());
throw new Error('周本7散兵已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
await sleep(1000);
await repeatOperationUntilTextFound({x: 700,y: 0,width: 450,height: 100,targetText: "七叶",stepDuration: 300});//前进直到出现BOSS名字
await sleep(6000);
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
keyDown("s");
await sleep(5000);
keyUp("s");
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本7散兵挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本7散兵挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss7.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本7散兵已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss7.txt", new Date().toISOString());
else notification.send(`周本7散兵领奖失败请检查相关设置`);
notification.send(`周本7散兵挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本7散兵挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本7散兵可能无法领取奖励`);
}
}
else log.info("当前周本7散兵冷却未刷新");
}
async function weeklyBoss8() {
//草龙
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(100);
keyUp("w");
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
// 其他情况: 三个区域均无文字,可能处于转场动画,尝试点击快进
else if (!hasText2 && !hasText3){
log.info("进入过场动画尝试快进");
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(5747.71533203125,-210.318359375);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss8.txt", new Date().toISOString());
throw new Error('周本8阿佩普已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyPress("1");
await sleep(1000);//切回固定行走位
await repeatOperationUntilTextFound({x: 700,y: 0,width: 450,height: 100,targetText: "绿",stepDuration: 300});//前进直到出现BOSS名字
await sleep(6500);
keyDown("e");
await sleep(1000);//钟离开盾
keyUp("e");
await sleep(200);
keyDown("a");
await sleep(2200);
keyUp("a");
keyDown("w");
await sleep(3600);
keyUp("w");
keyDown("d");
await sleep(3600);
keyUp("d");
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本8阿佩普挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本8阿佩普挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss8.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本8阿佩普已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss8.txt", new Date().toISOString());
else notification.send(`周本8阿佩普领奖失败请检查相关设置`);
notification.send(`周本8阿佩普挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本8阿佩普挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本8阿佩普可能无法领取奖励`);
}
}
else log.info("当前周本8阿佩普冷却未刷新");
}
async function weeklyBoss9() {
//吞星之鲸
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进第${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进第${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(200);
keyUp("w");
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(4022.01171875,3063.54931640625);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss9.txt", new Date().toISOString());
throw new Error('周本9吞星之鲸已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
await sleep(500);
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本9吞星之鲸挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本9吞星之鲸挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss9.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本9吞星之鲸已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss9.txt", new Date().toISOString());
else notification.send(`周本9吞星之鲸领奖失败请检查相关设置`);
notification.send(`周本9吞星之鲸挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本9吞星之鲸挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本9吞星之鲸可能无法领取奖励`);
}
}
else log.info("当前周本9吞星之鲸冷却未刷新");
}
async function weeklyBoss10() {
//仆人
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进第${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进第${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(100);
keyUp("w");
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
else if (!hasText2 && !hasText1 && hasText3) {
log.info("检测到BOSS进入二阶段");
keyDown("w");
await sleep(1000);
keyDown("VK_SHIFT");
await sleep(200);
keyUp("VK_SHIFT");
await sleep(400);
keyDown("VK_SHIFT");
await sleep(200);
keyUp("VK_SHIFT");
keyUp("w");
await sleep(1000);
await autoFightAsync();
}
// 情况4: 三个区域均无文字,可能处于转场动画,尝试点击快进
else if (!hasText2 && !hasText3){
log.info("进入过场动画尝试快进");
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
await sleep(1000);
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(4935.07861328125,4183.38818359375);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss10.txt", new Date().toISOString());
throw new Error('周本10仆人已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
await repeatOperationUntilTextFound({x: 700,y: 0,width: 450,height: 100,targetText: "仆人",stepDuration: 300});//前进直到出现BOSS名字
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本10仆人挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本10仆人挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss10.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本10仆人已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss10.txt", new Date().toISOString());
else notification.send(`周本10仆人领奖失败请检查相关设置`);
notification.send(`周本10仆人挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本10仆人挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本10仆人可能无法领取奖励`);
}
}
else log.info("当前周本10仆人冷却未刷新");
}
async function weeklyBoss11() {
//源焰之主
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进第${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进第${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(100);
keyUp("w");
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
// if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(9481.6123046875,-1931.45166015625,);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss11.txt", new Date().toISOString());
throw new Error('周本11源焰之主已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyPress("1");
await sleep(1000);//切回固定行走位
keyDown("s");
await sleep(200);
keyUp("s");
keyDown("e");
await sleep(1000);
keyDown("e");
keyDown("w");
await sleep(1000);
keyDown("VK_SHIFT");
await sleep(200);
keyUp("VK_SHIFT");
await sleep(1000);
keyDown("VK_SHIFT");
await sleep(200);
keyUp("VK_SHIFT");
await sleep(1000);
keyDown("VK_SHIFT");
await sleep(200);
keyUp("VK_SHIFT");
await sleep(1000);
keyDown("VK_SHIFT");
await sleep(200);
keyUp("VK_SHIFT");
await sleep(1000);
keyUp("w");
keyDown("d");
await sleep(500);
keyUp("d");
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本11源焰之主挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本11源焰之主挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss11.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本11源焰之主已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss11.txt", new Date().toISOString());
else notification.send(`周本11源焰之主领奖失败请检查相关设置`);
notification.send(`周本11源焰之主挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本11源焰之主挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本11源焰之主可能无法领取奖励`);
}
}
else log.info("当前周本11源焰之主冷却未刷新");
}
async function weeklyBoss12() {
//门扉前的弈局
/**
* 自动导航直到检测到指定文字
* @param {Object} options 配置选项
* @param {number} [options.x=1210] 检测区域左上角x坐标
* @param {number} [options.y=515] 检测区域左上角y坐标
* @param {number} [options.width=200] 检测区域宽度
* @param {number} [options.height=50] 检测区域高度
* @param {string|RegExp} [options.targetText="奖励"] 要检测的目标文字
* @param {number} [options.maxSteps=100] 最大检查次数
* @param {number} [options.stepDuration=200] 每步前进持续时间(ms)
* @param {number} [options.waitTime=10] 单次等待时间(ms)
* @param {string} [options.moveKey="w"] 前进按键
* @param {boolean} [options.ifClick=false] 是否点击
* @returns {Promise<void>}
* await repeatOperationUntilTextFound(); 默认F区域检测到任何文字即停止前进
* await repeatOperationUntilTextFound({targetText: "日落果"}); F区域检测到指定文字即停止前进
*await repeatOperationUntilTextFound({x: 10,y: 10,width: 100,height: 100,targetText: "奖励",stepDuration: 0,waitTime: 100,ifClick: true});//用来等待点击文字,10s等待
*/
const repeatOperationUntilTextFound = async ({
//默认区域为单个F图标右边的文字最多6个
x = 1210,
y = 515,
width = 200,
height = 50,
targetText = null,
maxSteps = 100,
stepDuration = 200,
waitTime = 10,
moveKey = "w",
ifClick = false,
} = {}) => {
/**
* 转义正则表达式中的特殊字符
* @param {string} string 要转义的字符串
* @returns {string} 转义后的字符串
*/
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
};
// 预编译正则表达式(如果是字符串则转换并转义)
const textPattern = typeof targetText === 'string'
? new RegExp(escapeRegExp(targetText))
: targetText;
let stepsTaken = 0;
while (stepsTaken <= maxSteps) {
// 1. 捕获游戏区域并裁剪出检测区域
const captureRegion = captureGameRegion();
const textArea = captureRegion.DeriveCrop(x, y, width, height);
// 2. 执行OCR识别
const ocrResult = textArea.find(RecognitionObject.ocrThis);
const hasAnyText = ocrResult.text.trim().length > 0;
const matchesTarget = targetText === null
? hasAnyText
: textPattern.test(ocrResult.text);
if (matchesTarget) {
log.info(`检测到${targetText === null ? '文字' : '目标文字'}: ${ocrResult.text}`);
await sleep(1000);
if (ifClick) click(Math.round(x + width / 2), Math.round(y + height / 2));
return true;
}
// 4. 检查步数限制
if (stepsTaken >= maxSteps) {
throw new Error(`检查次数超过最大限制: ${maxSteps},未查询到文字"${targetText}"`);
}
// 5. 前进一小步
if (stepDuration != 0) {
keyDown(moveKey);
await sleep(stepDuration);
keyUp(moveKey);
}
await sleep(waitTime);
stepsTaken++;
}
}
//执行战斗并检测结束
async function restoredEnergyAutoFightAndEndDetection() {
// 定义两个检测区域
const region2 = RecognitionObject.ocr(840, 935, 230, 40);//区域二 成功倒计时
const region3 = RecognitionObject.ocr(1690, 230, 75, 350);//区域三 队伍名称
let challengeTime = 0;
//2分钟兜底
while (challengeTime < 5000) {
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res2 = capture.find(region2);
let res3 = capture.find(region3);
let hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
let hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
// 情况1: 区域2无文字 且 区域3有文字 → 执行AutoFight
if (!hasText2 && hasText3) {
keyPress("1");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("2");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("3");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
keyPress("4");
await sleep(500);
leftButtonClick();
await sleep(400);
keyDown("e");
await sleep(400);
keyUp("e");
await sleep(600);
challengeTime = challengeTime + 200;
}
// 情况2: 区域2有文字且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(800);
//二次检验
capture = captureGameRegion();
res2 = capture.find(region2);
res3 = capture.find(region3);
hasText2 = !res2.isEmpty() && res2.text.trim().length > 0;
hasText3 = !res3.isEmpty() && res3.text.trim().length > 0;
if (hasText2 && hasText3) {
log.info("检测到挑战成功");
log.info("能量充满,任务结束");
return;
}
}
challengeTime = challengeTime + 1;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
log.info("挑战超时,可能充能失败");
}
async function restoredEnergy() {
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像,避免有倒下的角色
//传送到蒙德武器副本
await genshin.tp(-238,2256);
await sleep(1000);
await repeatOperationUntilTextFound();//
await sleep(1000);
keyPress("F");
await sleep(5000);
click( 380,300 );//选择难度最低的关卡
await sleep(1000);
click( 1700,1000 );//单人挑战
await sleep(200);
click( 1100,750 );//避免没有体力掐死
await sleep(1500);
click( 1700,1000 );//开始挑战
await tpEndDetection();
await repeatOperationUntilTextFound({targetText: "启动"});
await sleep(200);
keyPress("F");
await restoredEnergyAutoFightAndEndDetection();//一直战斗直到检测到结束
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
}
//检测传送结束 await tpEndDetection();
async function tpEndDetection() {
const region1 = RecognitionObject.ocr(1690, 230, 75, 350);// 队伍名称区域
const region2 = RecognitionObject.ocr(872, 681, 180, 30);// 点击任意处关闭
let tpTime = 0;
await sleep(1500);//点击传送后等待一段时间避免误判
//最多30秒传送时间
while (tpTime < 300) {
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (!res1.isEmpty()|| !res2.isEmpty()){
log.info("传送完成");
await sleep(1000);//传送结束后有僵直
click(960, 810);//点击任意处
await sleep(500);
return;
}
tpTime++;
await sleep(100);
}
throw new Error('传送时间超时');
}
//吃料理
async function eatFood() {
let foodName = settings.foodName ?? 0;
if(foodName){
const foodSum = foodName.split('-');
log.info("开始吃菜");
await sleep(1000);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
for(let i = 0; i < foodSum.length; i++){
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(foodSum[i]);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
}
keyPress("ESCAPE");
await sleep(1500);
}}
//检测角色是否阵亡,并前往吃药复活
async function resurgenceDetectionAndEatFood() {
const region1 = RecognitionObject.ocr(1170, 780, 75, 35);// 复活料理区域
const region2 = RecognitionObject.ocr(545, 360, 800, 45);// 料理冷却区域
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
if(1){
keyPress("1");
await sleep(100);
keyPress("2");
await sleep(100);
keyPress("3");
await sleep(100);
keyPress("4");
await sleep(1000);
let capture = captureGameRegion();
let res1 = capture.find(region1);
let res2 = capture.find(region2);
if (res1.isEmpty()){
return;
}
else if (!res1.isEmpty() && !res2.isEmpty()) {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('复活料理处于冷却中,战斗失败');
return;
}
else if (!res1.isEmpty() && res2.isEmpty()) {
log.info("检测到阵亡角色……复活吧!我的爱人!!!");
if(resurgenceFoodName && recoveryFoodName){
keyPress("ESCAPE");
await eatResurgenceFood();//满血复活
return;
}
else {
keyPress("ESCAPE");
await sleep(1000);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
throw new Error('未填写复活及恢复料理复活失败T^T');
return;
}
}
}}
//吃料理复活
async function eatResurgenceFood() {
let recoveryFoodName = settings.recoveryFoodName ?? 0;
let resurgenceFoodName = settings.resurgenceFoodName ?? 0;
const region = RecognitionObject.ocr(800, 200, 315, 32);// 复活对象检测
const clickPositions = [
{ x: 760, y: 440 }, // 角色1
{ x: 900, y: 440 }, // 角色2
{ x: 1040, y: 440 }, // 角色3
{ x: 1180, y: 440 } // 角色4
];
if(resurgenceFoodName && recoveryFoodName){
log.info("开始吃菜");
await sleep(500);
keyPress("B");//打开背包
await sleep(2000);
click(863, 51);//选择食物
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(200);
click(110, 110);
await sleep(1000);
inputText(`${resurgenceFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(1000);
// 使用 for 循环点击每个位置
for (let i = 0; i < clickPositions.length; i++) {
const position = clickPositions[i];
click(position.x, position.y);
await sleep(800);
click(1200,770);//确认
await sleep(800);
let capture = captureGameRegion();
let res = capture.find(region);
if (res.isEmpty()){
keyPress("ESCAPE");
await sleep(1000);
click(170, 1020);//筛选
await sleep(1000);
click(195, 1020);//重置
await sleep(1000);
click(110, 110);//输入名字
await sleep(1000);
inputText(`${recoveryFoodName}`);
await sleep(500);
click(490, 1020);//确认筛选
await sleep(1000);
click(180, 180);//选择第一个食物
await sleep(1000);
click(1690, 1015);//使用
await sleep(500);
click(position.x, position.y);
await sleep(500);
click(1200,770);//吃第一个
await sleep(500);
click(1200,770);//吃第二个
await sleep(500);
click(1350,290);//退出
await sleep(500);
keyPress("ESCAPE");
await sleep(400);
log.info("我又好了,嘿嘿");
break;
}
await sleep(1000);
}
}
}
//异步调用战斗
async function autoFightAsync() {
try {
const cts = new CancellationTokenSource();
dispatcher.RunTask(new SoloTask("AutoFight"), cts);
await sleep(1000*settings.challengeTime);//
cts.cancel();
} catch (error) {
log.info("启动战斗失败,尝试重新启动");
}
}
//返回当前体力值
async function queryStaminaValue() {
try {
await genshin.returnMainUi();
await sleep(1000);
keyPress("F1");
await sleep(2000);
click(300, 540);
await sleep(1000);
click(1570, 203);
await sleep(1000);
const region1 = RecognitionObject.ocr(1630, 34, 60, 30); // 体力区域
let capture = captureGameRegion();
let res1 = capture.find(region1);
let Stamina = res1.text;
let validatedStamina = positiveIntegerJudgment(Stamina);
log.info(`剩余体力为:${validatedStamina}`);
await genshin.returnMainUi();
return validatedStamina; // 返回体力值而不是修改全局变量
} catch (error) {
log.info(`剩余体力小于10`);
await genshin.returnMainUi();
return 0; // 返回0而不是修改全局变量
}
}
//检查是否为正整数
function positiveIntegerJudgment(testNumber) {
// 如果输入是字符串,尝试转换为数字
if (typeof testNumber === 'string') {
// 移除可能存在的非数字字符(如空格、百分号等)
const cleaned = testNumber.replace(/[^\d]/g, '');
testNumber = parseInt(cleaned, 10);
}
// 检查是否为有效的数字
if (typeof testNumber !== 'number' || isNaN(testNumber)) {
throw new Error(`无效的值: ${testNumber} (必须为数字)`);
}
// 检查是否为整数
if (!Number.isInteger(testNumber)) {
throw new Error(`必须为整数: ${testNumber}`);
}
return testNumber;
}
/**
* 判断任务是否已刷新
* @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) {
//刷新返回true
// 更新最后完成时间
return true;
} else {
//未刷新返回false
return false;
}
} catch (error) {
// 如果文件不存在创建新文件并返回true(视为需要刷新)
const createResult = await file.writeText(filePath, '');
if (createResult) {
log.info("创建新时间记录文件成功,执行脚本");
return true;
}
else throw new Error(`创建新文件失败`);
}
}
//通用函数分界线,上面通用
//征讨之花领奖
const autoNavigateToReward = async () => {
// 定义识别对象
const boxIconRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/box.png"));
const rewardTextRo = RecognitionObject.Ocr(1210, 515, 200, 50);//领奖区域检测
let advanceNum = 0;//前进次数
//调整为俯视视野
middleButtonClick();
await sleep(800);
moveMouseBy(0, 1030);
await sleep(400);
moveMouseBy(0, 920);
await sleep(400);
moveMouseBy(0, 710);
log.info("开始领奖");
while (true) {
// 1. 优先检查是否已到达领奖点
let captureRegion = captureGameRegion();
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
// 检测到特点文字则结束!!!
if (rewardResult.text == "接触征讨之花") {
log.info(`总计前进第${advanceNum}`);
log.info("已到达领奖点,检测到文字: " + rewardResult.text);
return;
}
else if(advanceNum > 150){
log.info(`总计前进第${advanceNum}`);
throw new Error('前进时间超时');
}
// 2. 未到达领奖点,则调整视野
for(let i = 0; i < 100; i++){
captureRegion = captureGameRegion();
let iconRes = captureRegion.Find(boxIconRo);
let climbTextArea = captureRegion.DeriveCrop(1685, 1030, 65, 25);
let climbResult = climbTextArea.find(RecognitionObject.ocrThis);
// 检查是否处于攀爬状态
if (climbResult.text == "Space"){
log.info("检侧进入攀爬状态,尝试脱离");
keyPress("x");
await sleep(1000);
keyDown("a");
await sleep(800);
keyUp("a");
keyDown("w");
await sleep(800);
keyUp("w");
}
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
advanceNum++;
break;
} else {
// 小幅度调整
if(iconRes.y >= 520) moveMouseBy(0, 920);
let adjustAmount = iconRes.x < 920 ? -20 : 20;
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放最小为1
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
moveMouseBy(adjustAmount * adjustAmount2, 0);
await sleep(100);
}
if(i > 20) throw new Error('视野调整超时');
}
// 3. 前进一小步
keyDown("w");
await sleep(100);
keyUp("w");
}
}
//执行战斗并检测结束
async function autoFightAndEndDetection() {
// 定义两个检测区域
const region1 = RecognitionObject.ocr(700, 0, 450, 100);//区域一 BOSS名称
const region2 = RecognitionObject.ocr(820, 935, 280, 50);//区域二 成功倒计时
const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0);
const teamRo1 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team1.png"), 1820, 240, 80, 400);
const teamRo2 = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/team2.png"), 1820, 240, 80, 400);
let challengeTime = 0;
let challengeNum = 0;
//10分钟兜底
while (challengeTime < 600000) {
await resurgenceDetectionAndEatFood();//检查吃药复活
challengeTime = challengeTime + 1000;
// 捕获游戏区域
let capture = captureGameRegion();
// 检测两个区域的OCR结果
let res1 = capture.find(region1);
let res2 = capture.find(region2);
let teamRes1 = capture.find(teamRo1);
let teamRes2 = capture.find(teamRo2);
let hasText1 = !res1.isEmpty() && res1.text.trim().length > 0;
let hasText2 = res2.text.includes("自动退出");
let hasText3 = teamRes1.isExist() || teamRes2.isExist();
let paimon = capture.find(paimonMenuRo);
if (paimon.isExist()) throw new Error('复活次数用尽,挑战失败');
// 情况1: 区域1有文字 且 区域2无文字 且 区域3有文字 → 执行AutoFight
if (hasText1 && !hasText2 && hasText3) {
challengeNum++;
challengeTime = challengeTime + 1000*settings.challengeTime;
log.info(`执行第${challengeNum}次战斗`);
await autoFightAsync();
}
// 情况2: 区域2有文字 且 区域1无文字 且 区域3有文字 → 结束循环
else if (hasText2 && hasText3) {
await sleep(500);
log.info("检测到挑战成功");
break;
}
// 情况3: 区域2无文字区域1无文字区域3有文字 →BOSS二阶段需要移动触发
else if (!hasText2 && !hasText1 && hasText3) {
log.info("检测到BOSS进入二阶段");
keyDown("w");
await sleep(1000);//多前进一段位置,避免无法触发冻结反应,建议使用丝柯克
keyUp("w");
await autoFightAsync();
}
// 情况4: 三个区域均无文字,可能处于转场动画,尝试点击快进
else if (!hasText2 && !hasText3){
log.info("进入过场动画尝试快进");
await sleep(400);
click(1765, 55);
await sleep(400);
click(1765, 55);
log.info("等待技能CD");
await sleep(15000);
keyPress("1");
await sleep(300);
keyDown("s");
await sleep(300);
keyUp("s");
await sleep(400);
keyDown("e");
await sleep(1000);//护盾角色开盾
keyUp("e");
await sleep(14000);
}
challengeTime = challengeTime + 100;
// 每次检测间隔100毫秒避免CPU占用过高
await sleep(100);
}
}
async function main() {
//检验输入参数
if(!settings.challengeTime) throw new Error('未输入单轮战斗时长');
if(!settings.teamName) throw new Error('未输入队伍名称');
//通用:前往副本(副本外)
await genshin.returnMainUi();
//切换队伍
await genshin.switchParty(settings.teamName);
//前往充满能量
if(settings.energyMax) await restoredEnergy();
else await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await genshin.tp(-1608.205078125,1730.2724609375,true);//传送到周本
await sleep(1000);
await repeatOperationUntilTextFound();
await sleep(500);
keyPress("F");
await sleep(2000);
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "单人挑战",stepDuration: 0,waitTime: 100});//等待点击单人挑战
await sleep(500);
if(!settings.fightMode){
let capture = captureGameRegion();
const region = RecognitionObject.ocr(1320, 10, 290, 80);//领奖次数区域
let res = capture.find(region);
if(res.text.includes("倒计时")){
log.info("领奖次数耗尽,任务结束");
await file.writeText("assets/weeklyBoss12.txt", new Date().toISOString());
throw new Error('周本12门扉前的弈局已经领过奖了');
}
else log.info("检测到还有领奖次数,开始挑战");
await sleep(500);
}
click(1725, 1020);//点击单人挑战
await sleep(200);
click(1180, 760);//队伍等级偏低、体力不够可能会出弹窗
await repeatOperationUntilTextFound({x: 1650,y: 1000,width: 160,height: 45,targetText: "开始挑战",stepDuration: 0,waitTime: 100,ifClick: true});//等待点击开始挑战
await sleep(2000);
await tpEndDetection();
//副本内前往BOSS处
await eatFood();//嗑药
keyPress("1");
await sleep(300);
keyDown("s");
await sleep(300);
keyUp("s");
await sleep(400);
keyDown("e");
await sleep(1000);//护盾角色开盾
keyUp("e");
await sleep(12000);
keyDown("w");
await sleep(500);
keyDown("SHIFT");
await sleep(300);
keyUp("SHIFT");
await sleep(500);
keyDown("SHIFT");
await sleep(300);
keyUp("SHIFT");
await sleep(500);
keyUp("w");
await autoFightAsync();
await autoFightAndEndDetection();//一直战斗直到检测到结束
//领奖并退出
await autoNavigateToReward();//前往地脉之花
await sleep(1000);
keyPress("F");//领奖
await sleep(1000);
click(950, 750);//使用树脂
await sleep(6000);
click(975, 1000);//退出秘境
await tpEndDetection();
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
await sleep(1000);
log.info('周本12门扉前的弈局挑战完成');
}
if(settings.fightMode){
log.info("启用战斗模式,不检测刷新周期和体力值");//不检测刷新周期和体力值,但没领过奖还是会领奖
try {
await main();
}
catch (error) {
notification.send(`周本12门扉前的弈局挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else if(await isTaskRefreshed("assets/weeklyBoss12.txt", {refreshType: 'weekly',weeklyDay: 1, weeklyHour: 4 }) && !settings.fightMode){
let afterStamina = await queryStaminaValue();
let beforeStamina = afterStamina;//获取挑战前的体力值
if (afterStamina >=60 ){
try {
notification.send(`周本12门扉前的弈局已经刷新开始挑战当前体力${afterStamina}`);
await main();
afterStamina = await queryStaminaValue();//获取挑战后的体力值
if(beforeStamina - afterStamina > 0) await file.writeText("assets/weeklyBoss12.txt", new Date().toISOString());
else notification.send(`周本12门扉前的弈局领奖失败请检查相关设置`);
notification.send(`周本12门扉前的弈局挑战结束剩余体力${afterStamina}`);
}
catch (error) {
notification.send(`周本12门扉前的弈局挑战失败错误信息: ${error}`);
await genshin.tp(2297.6201171875,-824.5869140625);//传送到神像回血
}
}
else{
notification.send(`体力值为${afterStamina},周本12门扉前的弈局可能无法领取奖励`);
}
}
else log.info("当前周本12门扉前的弈局冷却未刷新");
}
async function weeklyBoss13() {
}
this.utils = {
weeklyBoss1,
weeklyBoss2,
weeklyBoss3,
weeklyBoss4,
weeklyBoss5,
weeklyBoss6,
weeklyBoss7,
weeklyBoss8,
weeklyBoss9,
weeklyBoss10,
weeklyBoss11,
weeklyBoss12
};