js:自动狗粮重制版 (#938)
This commit is contained in:
697
repo/js/AutoArtifactsPro/main.js
Normal file
697
repo/js/AutoArtifactsPro/main.js
Normal file
@@ -0,0 +1,697 @@
|
||||
const DEFAULT_OCR_TIMEOUT_SECONDS = 10;
|
||||
const DEFAULT_FIGHT_TIMEOUT_SECONDS = 120;
|
||||
|
||||
(async function () {
|
||||
// 启用自动拾取的实时任务
|
||||
dispatcher.addTimer(new RealtimeTimer("AutoPick"));
|
||||
|
||||
//伪造js结束记录
|
||||
await fakeLog("自动狗粮重制版", true, true, 0);
|
||||
|
||||
//预处理
|
||||
//settings 获取自定义配置
|
||||
const minIntervalTime = settings.minIntervalTime;
|
||||
const waitTimePeriod = settings.waitTimePeriod;
|
||||
const friendshipPartyName = settings.friendshipPartyName;
|
||||
const grindPartyName = settings.grindPartyName;
|
||||
|
||||
//处理卡时间信息
|
||||
// 异步读取文件内容
|
||||
const content = await file.readText("record.txt");
|
||||
|
||||
// 初始化变量并赋予默认值
|
||||
let lastRunDate = "未知"; // 默认值
|
||||
let lastEndTime = new Date(); // 默认值为当前时间
|
||||
let lastRunRoute = "未知"; // 默认值
|
||||
|
||||
// 按行分割内容
|
||||
const lines = content.split('\n');
|
||||
|
||||
// 逐行处理
|
||||
for (const line of lines) {
|
||||
// 跳过空行
|
||||
if (line.trim() === '') continue;
|
||||
|
||||
// 检查每行的起始部分
|
||||
if (line.startsWith("上次运行完成日期:")) {
|
||||
lastRunDate = line.substring("上次运行完成日期:".length).trim();
|
||||
}
|
||||
|
||||
if (line.startsWith("上次结束时间:")) {
|
||||
const timeString = line.substring("上次结束时间:".length).trim();
|
||||
if (timeString) {
|
||||
lastEndTime = new Date(timeString);
|
||||
if (isNaN(lastEndTime.getTime())) {
|
||||
throw new Error(`无效的时间值: ${timeString}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (line.startsWith("上次运行路线:")) {
|
||||
lastRunRoute = line.substring("上次运行路线:".length).trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 输出变量值
|
||||
log.info(`上次运行完成日期: ${lastRunDate}`);
|
||||
log.info(`上次狗粮开始时间: ${lastEndTime.toISOString()}`);
|
||||
log.info(`上次运行路线: ${lastRunRoute}`);
|
||||
// 拆分 lastRunDate 为年、月、日
|
||||
const [year, month, day] = lastRunDate.split('/').map(Number);
|
||||
|
||||
// 生成这个日期凌晨四点的时间
|
||||
const lastRunMidnight = new Date(year, month - 1, day, 4, 0, 0);
|
||||
|
||||
// 获取当前时间
|
||||
const now = new Date();
|
||||
|
||||
// 计算当前时间与 lastRunMidnight 之间的时间差(单位:毫秒)
|
||||
const timeDifference = now - lastRunMidnight;
|
||||
|
||||
// 如果当前时间减去 lastRunMidnight 小于 24 小时(24 * 60 * 60 * 1000 毫秒),则终止程序运行
|
||||
if (timeDifference < 24 * 60 * 60 * 1000) {
|
||||
log.info("今日已经运行完成狗粮路线,终止程序运行");
|
||||
return; // 提前退出函数
|
||||
}
|
||||
|
||||
// 如果时间差大于或等于 24 小时,程序继续运行
|
||||
log.info("今日还没有运行完成狗粮路线,程序继续运行");
|
||||
|
||||
let endTime = await getEndTime(minIntervalTime, lastEndTime);
|
||||
|
||||
// 解析 waitTimePeriod
|
||||
const [startTimeStr, endTimeStr] = waitTimePeriod.split('-').map(time => time.trim());
|
||||
|
||||
// 将时间字符串转换为小时和分钟
|
||||
const [startHour, startMinute] = startTimeStr.split(':').map(Number);
|
||||
const [endHour, endMinute] = endTimeStr.split(':').map(Number);
|
||||
|
||||
// 获取当前日期
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0); // 将时间设置为当天的午夜
|
||||
|
||||
// 创建等待时间段的开始时间和结束时间的 Date 对象
|
||||
const waitStartTime = new Date(today);
|
||||
waitStartTime.setHours(startHour, startMinute, 0, 0);
|
||||
|
||||
const waitEndTime = new Date(today);
|
||||
waitEndTime.setHours(endHour, endMinute, 0, 0);
|
||||
|
||||
// 新增变量,初始值为 true,用于标识今天跑的路线
|
||||
let runRouteA = true;
|
||||
|
||||
// 获取当前时间
|
||||
const timeNow = new Date();
|
||||
|
||||
// 检查 endTime 是否晚于当天的结束时间
|
||||
if (endTime > waitEndTime) {
|
||||
// 如果 endTime 晚于当天的结束时间,则将其改为当天的开始时间
|
||||
endTime = new Date(waitStartTime);
|
||||
// 同时将 runRouteA 改为 false,今天运行B路线
|
||||
runRouteA = false;
|
||||
}
|
||||
|
||||
// 检查 lastRunRoute 是否为 "B"
|
||||
if (lastRunRoute === "B") {
|
||||
// 如果 lastRunRoute 为 "B",则将 endTime 改为当天的开始时间
|
||||
endTime = new Date(waitStartTime);
|
||||
// 同时将 runRouteA 改为 true
|
||||
runRouteA = true;
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
log.info(`预期开始狗粮时间: ${endTime.toTimeString().slice(0, 8)}`);
|
||||
|
||||
// 获取敌人类型设置,默认为盗宝团
|
||||
const enemyType = "盗宝团";
|
||||
|
||||
// 检查当前时间是否晚于 endTime
|
||||
if (timeNow > endTime) {
|
||||
log.warn('无需好感卡时间')
|
||||
} else {
|
||||
// 清理丘丘人(仅盗宝团需要)
|
||||
//切换至好感队
|
||||
await switchPartyIfNeeded(friendshipPartyName);
|
||||
log.info(endTime.toLocaleString());
|
||||
log.info(`清理原住民...`);
|
||||
await AutoPath('盗宝团-准备');
|
||||
//好感卡时间
|
||||
|
||||
// 验证超时设置
|
||||
const ocrTimeout = validateTimeoutSetting(settings.ocrTimeout, DEFAULT_OCR_TIMEOUT_SECONDS, "OCR");
|
||||
const fightTimeout = validateTimeoutSetting(settings.fightTimeout, DEFAULT_FIGHT_TIMEOUT_SECONDS, "战斗");
|
||||
|
||||
// 好感循环开始
|
||||
await AutoFriendshipDev(50, ocrTimeout, fightTimeout, enemyType, endTime);
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
const waitStartNow = new Date();
|
||||
|
||||
// 计算 endTime 与当前时间的差值(单位:毫秒),以防好感度运行完了还没到时间
|
||||
const timeDiff = endTime - waitStartNow;
|
||||
if (timeDiff > 0) {
|
||||
log.info(`当前时间与预期时间的差值为 ${timeDiff} 毫秒,等待该时间`);
|
||||
await sleep(timeDiff);
|
||||
} else {
|
||||
log.info("当前时间已晚于预期时间,无需等待");
|
||||
}
|
||||
|
||||
//切换至狗粮队
|
||||
await switchPartyIfNeeded(grindPartyName);
|
||||
|
||||
//更新运行数据
|
||||
{
|
||||
// 获取当前日期和时间
|
||||
const finishDate = new Date();
|
||||
|
||||
// 格式化当前日期为 "YYYY/MM/DD" 格式
|
||||
const currentDateString = `${finishDate.getFullYear()}/${String(finishDate.getMonth() + 1).padStart(2, '0')}/${String(finishDate.getDate()).padStart(2, '0')}`;
|
||||
|
||||
// 根据 runRouteA 的值更新 lastRunRoute
|
||||
lastRunRoute = runRouteA ? "A" : "B";
|
||||
|
||||
// 更新 lastRunDate 为当前日期
|
||||
lastRunDate = currentDateString;
|
||||
|
||||
// 更新 lastEndTime 为当前时间
|
||||
lastEndTime = new Date(); // 使用 new Date() 获取当前时间
|
||||
|
||||
//按格式输出今日狗粮路线信息
|
||||
log.info(`今日运行狗粮路线:${runRouteA ? 'A' : 'B'},开始时间:${lastEndTime.toLocaleString()}`);
|
||||
}
|
||||
|
||||
// 开始运行狗粮路线
|
||||
let runArtifactsResult = true;
|
||||
runArtifactsResult = await runArtifactsPaths(runRouteA);
|
||||
|
||||
if (runArtifactsResult) {
|
||||
//修改文件内容
|
||||
log.info('尝试修改记录文件');
|
||||
await writeRecordFile(lastRunDate, lastEndTime, lastRunRoute);
|
||||
}
|
||||
|
||||
//完成剩下好感
|
||||
if (settings.completeRemainingFriendship) {
|
||||
//切换至好感队
|
||||
await switchPartyIfNeeded(friendshipPartyName);
|
||||
// 验证超时设置
|
||||
const ocrTimeout = validateTimeoutSetting(settings.ocrTimeout, DEFAULT_OCR_TIMEOUT_SECONDS, "OCR");
|
||||
const fightTimeout = validateTimeoutSetting(settings.fightTimeout, DEFAULT_FIGHT_TIMEOUT_SECONDS, "战斗");
|
||||
// 好感循环开始
|
||||
await AutoFriendshipDev(50, ocrTimeout, fightTimeout, enemyType, endTime + 24 * 60 * 60 * 1000);
|
||||
}
|
||||
//伪造js开始记录
|
||||
await fakeLog("自动狗粮重制版", true, false, 0);
|
||||
})();
|
||||
|
||||
// 异步函数,用于将变量内容写回到文件
|
||||
async function writeRecordFile(lastRunDate, lastEndTime, lastRunRoute) {
|
||||
try {
|
||||
// 构造要写入文件的内容
|
||||
const content = [
|
||||
`上次运行完成日期: ${lastRunDate}`,
|
||||
`上次结束时间: ${lastEndTime.toISOString()}`,
|
||||
`上次运行路线: ${lastRunRoute}`
|
||||
].join('\n');
|
||||
|
||||
// 异步写入文件
|
||||
const result = await file.writeText("record.txt", content, false); // 覆盖写入
|
||||
if (result) {
|
||||
log.info("文件写入成功");
|
||||
} else {
|
||||
log.error("文件写入失败");
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`写入文件时出错: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
//运行狗粮路线的逻辑
|
||||
async function runArtifactsPaths(runRouteA) {
|
||||
// 根据 runRouteA 的值给 runningRoute 赋值
|
||||
const runningRoute = runRouteA ? "A" : "B";
|
||||
|
||||
// 定义文件夹路径
|
||||
const folderName = `${runningRoute}路线`;
|
||||
const filePathNormal = `assets/ArtifactsPath/${folderName}/01普通`;
|
||||
const filePathEnding = `assets/ArtifactsPath/${folderName}/02收尾`;
|
||||
const filePathExtra = `assets/ArtifactsPath/${folderName}/03额外`;
|
||||
|
||||
// 运行普通路线
|
||||
{
|
||||
// 读取文件夹中的文件名并处理
|
||||
const filePaths = file.readPathSync(filePathNormal);
|
||||
const jsonFileNames = [];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const fileName = basename(filePath); // 提取文件名
|
||||
if (fileName.endsWith('.json')) { // 检查文件名是否以 .json 结尾
|
||||
jsonFileNames.push(fileName); // 存储文件名
|
||||
}
|
||||
}
|
||||
|
||||
// 执行普通路线的地图追踪文件
|
||||
for (const fileName of jsonFileNames) {
|
||||
const fullPath = fileName;
|
||||
await fakeLog(fileName, false, true, 0);
|
||||
await pathingScript.runFile(fullPath);
|
||||
//捕获任务取消的信息并跳出循环
|
||||
try {
|
||||
await sleep(10); // 假设 sleep 是一个异步函数,休眠 10 毫秒
|
||||
} catch (error) {
|
||||
log.error(`发生错误: ${error}`);
|
||||
return false; // 终止循环
|
||||
}
|
||||
await fakeLog(fileName, false, false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行收尾路线
|
||||
{
|
||||
// 读取文件夹中的文件名并处理
|
||||
const filePaths = file.readPathSync(filePathEnding);
|
||||
const jsonFileNames = [];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const fileName = basename(filePath); // 提取文件名
|
||||
if (fileName.endsWith('.json')) { // 检查文件名是否以 .json 结尾
|
||||
jsonFileNames.push(fileName); // 存储文件名
|
||||
}
|
||||
}
|
||||
|
||||
// 执行收尾路线的地图追踪文件
|
||||
for (const fileName of jsonFileNames) {
|
||||
const fullPath = fileName;
|
||||
await fakeLog(fileName, false, true, 0);
|
||||
await pathingScript.runFile(fullPath);
|
||||
//捕获任务取消的信息并跳出循环
|
||||
try {
|
||||
await sleep(10); // 假设 sleep 是一个异步函数,休眠 10 毫秒
|
||||
} catch (error) {
|
||||
log.error(`发生错误: ${error}`);
|
||||
return false; // 终止循环
|
||||
}
|
||||
await fakeLog(fileName, false, false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行额外路线
|
||||
{
|
||||
// 读取文件夹中的文件名并处理
|
||||
const filePaths = file.readPathSync(filePathExtra);
|
||||
const jsonFileNames = [];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
const fileName = basename(filePath); // 提取文件名
|
||||
if (fileName.endsWith('.json')) { // 检查文件名是否以 .json 结尾
|
||||
jsonFileNames.push(fileName); // 存储文件名
|
||||
}
|
||||
}
|
||||
|
||||
// 执行额外路线的地图追踪文件
|
||||
for (const fileName of jsonFileNames) {
|
||||
const fullPath = fileName;
|
||||
await fakeLog(fileName, false, true, 0);
|
||||
await pathingScript.runFile(fullPath);
|
||||
//捕获任务取消的信息并跳出循环
|
||||
try {
|
||||
await sleep(10); // 假设 sleep 是一个异步函数,休眠 10 毫秒
|
||||
} catch (error) {
|
||||
log.error(`发生错误: ${error}`);
|
||||
return false; // 终止循环
|
||||
}
|
||||
await fakeLog(fileName, false, false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// fakeLog 函数,使用方法:将本函数放在主函数前,调用时请务必使用await,否则可能出现v8白框报错
|
||||
//在js开头处伪造该js结束运行的日志信息,如 await fakeLog("js脚本", true, true, 0);
|
||||
//在js结尾处伪造该js开始运行的日志信息,如 await fakeLog("js脚本", true, false, 2333);
|
||||
//duration项目仅在伪造结束信息时有效,且无实际作用,可以任意填写,当你需要在日志中输出特定值时才需要,单位为毫秒
|
||||
//在调用地图追踪前伪造该地图追踪开始运行的日志信息,如 await fakeLog(`地图追踪.json`, false, true, 0);
|
||||
//在调用地图追踪后伪造该地图追踪结束运行的日志信息,如 await fakeLog(`地图追踪.json`, false, false, 0);
|
||||
//如此便可以在js运行过程中伪造地图追踪的日志信息,可以在日志分析等中查看
|
||||
|
||||
async function fakeLog(name, isJs, isStart, duration) {
|
||||
await sleep(10);
|
||||
const currentTime = Date.now();
|
||||
// 参数检查
|
||||
if (typeof name !== 'string') {
|
||||
log.error("参数 'name' 必须是字符串类型!");
|
||||
return;
|
||||
}
|
||||
if (typeof isJs !== 'boolean') {
|
||||
log.error("参数 'isJs' 必须是布尔型!");
|
||||
return;
|
||||
}
|
||||
if (typeof isStart !== 'boolean') {
|
||||
log.error("参数 'isStart' 必须是布尔型!");
|
||||
return;
|
||||
}
|
||||
if (typeof currentTime !== 'number' || !Number.isInteger(currentTime)) {
|
||||
log.error("参数 'currentTime' 必须是整数!");
|
||||
return;
|
||||
}
|
||||
if (typeof duration !== 'number' || !Number.isInteger(duration)) {
|
||||
log.error("参数 'duration' 必须是整数!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 将 currentTime 转换为 Date 对象并格式化为 HH:mm:ss.sss
|
||||
const date = new Date(currentTime);
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
|
||||
const formattedTime = `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
||||
|
||||
// 将 duration 转换为分钟和秒,并保留三位小数
|
||||
const durationInSeconds = duration / 1000; // 转换为秒
|
||||
const durationMinutes = Math.floor(durationInSeconds / 60);
|
||||
const durationSeconds = (durationInSeconds % 60).toFixed(3); // 保留三位小数
|
||||
|
||||
// 使用四个独立的 if 语句处理四种情况
|
||||
if (isJs && isStart) {
|
||||
// 处理 isJs = true 且 isStart = true 的情况
|
||||
const logMessage = `正在伪造js开始的日志记录\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`------------------------------\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`→ 开始执行JS脚本: "${name}"`;
|
||||
log.info(logMessage);
|
||||
}
|
||||
if (isJs && !isStart) {
|
||||
// 处理 isJs = true 且 isStart = false 的情况
|
||||
const logMessage = `正在伪造js结束的日志记录\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}分${durationSeconds}秒\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`------------------------------`;
|
||||
log.info(logMessage);
|
||||
}
|
||||
if (!isJs && isStart) {
|
||||
// 处理 isJs = false 且 isStart = true 的情况
|
||||
const logMessage = `正在伪造地图追踪开始的日志记录\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`------------------------------\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`→ 开始执行地图追踪任务: "${name}"`;
|
||||
log.info(logMessage);
|
||||
}
|
||||
if (!isJs && !isStart) {
|
||||
// 处理 isJs = false 且 isStart = false 的情况
|
||||
const logMessage = `正在伪造地图追踪结束的日志记录\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`→ 脚本执行结束: "${name}", 耗时: ${durationMinutes}分${durationSeconds}秒\n\n` +
|
||||
`[${formattedTime}] [INF] BetterGenshinImpact.Service.ScriptService\n` +
|
||||
`------------------------------`;
|
||||
log.info(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:提取文件名
|
||||
function basename(filePath) {
|
||||
return filePath.split('/').pop();
|
||||
}
|
||||
|
||||
//用于获取结束时间
|
||||
async function getEndTime(minIntervalTime, lastEndTime) {
|
||||
const minIntervalTimeInMs = minIntervalTime * 60 * 1000; // 将分钟转换为毫秒
|
||||
return new Date(lastEndTime.getTime() + 24 * 60 * 60 * 1000 + minIntervalTimeInMs);
|
||||
}
|
||||
|
||||
// 执行 好感度的 path 任务
|
||||
async function AutoPath(locationName) {
|
||||
try {
|
||||
const filePath = `assets/AutoPath/${locationName}.json`;
|
||||
await pathingScript.runFile(filePath);
|
||||
} catch (error) {
|
||||
log.error(`执行 ${locationName} 路径时发生错误: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
//好感度任务的逻辑
|
||||
async function AutoFriendshipDev(times, ocrTimeout, fightTimeout, enemyType = "盗宝团", endTime) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
|
||||
// 获取当前时间
|
||||
const now = new Date();
|
||||
|
||||
// 比较当前时间与 endTime,若晚于 endTime 则跳出循环
|
||||
if (now > endTime) {
|
||||
log.info("当前时间已晚于预期时间,终止好感任务");
|
||||
break;
|
||||
}
|
||||
|
||||
await fakeLog(`第${i + 1}次盗宝团好感`, false, true, 0);
|
||||
|
||||
await AutoPath(`${enemyType}-触发点`);
|
||||
// 启动路径导航任务
|
||||
let pathTaskPromise = AutoPath(`${enemyType}-战斗点`);
|
||||
|
||||
// 根据敌人类型设置不同的OCR检测关键词
|
||||
const ocrKeywords = getOcrKeywords(enemyType);
|
||||
|
||||
// OCR检测
|
||||
let ocrStatus = false;
|
||||
let ocrStartTime = Date.now();
|
||||
while (Date.now() - ocrStartTime < ocrTimeout * 1000 && !ocrStatus) {
|
||||
let captureRegion = captureGameRegion();
|
||||
let resList = captureRegion.findMulti(RecognitionObject.ocr(0, 200, 300, 300));
|
||||
for (let o = 0; o < resList.count; o++) {
|
||||
let res = resList[o];
|
||||
for (let keyword of ocrKeywords) {
|
||||
if (res.text.includes(keyword)) {
|
||||
ocrStatus = true;
|
||||
log.info("检测到突发任务触发");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ocrStatus) break;
|
||||
}
|
||||
if (!ocrStatus) {
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (ocrStatus) {
|
||||
const cts = new CancellationTokenSource();
|
||||
try { // 设置最大等待时间为15秒
|
||||
const maxWaitTime = 15000;
|
||||
const waitStartTime = Date.now();
|
||||
|
||||
// 根据敌人类型设置不同的目标坐标
|
||||
const targetCoords = getTargetCoordinates(enemyType);
|
||||
const maxDistance = 10; // 10米距离判定
|
||||
|
||||
// 等待角色到达指定位置附近
|
||||
let isNearTarget = false;
|
||||
let pathTaskFinished = false;
|
||||
|
||||
// 简单监控路径任务完成
|
||||
pathTaskPromise.then(() => {
|
||||
pathTaskFinished = true;
|
||||
log.info("路径任务已完成");
|
||||
}).catch(error => {
|
||||
pathTaskFinished = true;
|
||||
log.error(`路径任务出错: ${error}`);
|
||||
});
|
||||
// 等待角色到达目标位置或超时
|
||||
while (!isNearTarget && !pathTaskFinished && (Date.now() - waitStartTime < maxWaitTime)) {
|
||||
const pos = genshin.getPositionFromMap();
|
||||
if (pos) {
|
||||
const distance = Math.sqrt(Math.pow(pos.x - targetCoords.x, 2) + Math.pow(pos.y - targetCoords.y, 2));
|
||||
if (distance <= maxDistance) {
|
||||
isNearTarget = true;
|
||||
log.info(`已到达目标点附近,距离: ${distance.toFixed(2)}米`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
await sleep(1000);
|
||||
} log.info("开始战斗...");
|
||||
const battleTask = dispatcher.RunTask(new SoloTask("AutoFight"), cts);
|
||||
const fightResultPromise = waitForBattleResult(fightTimeout * 1000, enemyType, cts);
|
||||
|
||||
// 使用 Promise.all 等待两个任务完成
|
||||
const [battleResult, fightResult] = await Promise.all([
|
||||
battleTask.catch(error => {
|
||||
return { success: false, error: error };
|
||||
}),
|
||||
fightResultPromise // 不捕获超时错误,让它直接抛到外层
|
||||
]);
|
||||
await pathTaskPromise; // 等待路径任务完成
|
||||
cts.cancel();
|
||||
} catch (error) {
|
||||
cts.cancel();
|
||||
if (error.message && error.message.includes("战斗超时")) {
|
||||
log.error(`战斗超时,终止整个任务: ${error.message}`);
|
||||
await genshin.tpToStatueOfTheSeven(); // 超时回到七天神像终止任务
|
||||
throw error; // 重新抛出超时错误,终止整个任务
|
||||
}
|
||||
log.error(`执行过程中出错: ${error}`);
|
||||
}
|
||||
} else {
|
||||
notification.send(`未识别到突发任务,${enemyType}好感结束`);
|
||||
log.info(`未识别到突发任务,${enemyType}好感结束`);
|
||||
return false;
|
||||
}
|
||||
|
||||
await fakeLog(`第${i + 1}次盗宝团好感`, false, false, 0);
|
||||
}
|
||||
log.info(`${enemyType}好感已完成`);
|
||||
await genshin.tpToStatueOfTheSeven();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证输入是否是正整数
|
||||
function isPositiveInteger(value) {
|
||||
return Number.isInteger(value) && value > 0;
|
||||
}
|
||||
|
||||
// 根据敌人类型获取OCR关键词
|
||||
function getOcrKeywords(enemyType) {
|
||||
if (enemyType === "愚人众") {
|
||||
return ["买卖", "不成", "正义存", "愚人众", "禁止", "危险", "运输", "打倒", "盗宝团"];
|
||||
}
|
||||
else if (enemyType === "盗宝团") {
|
||||
return ["岛上", "无贼", "消灭", "鬼鬼祟祟", "盗宝团"];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据敌人类型获取目标战斗点坐标
|
||||
function getTargetCoordinates(enemyType) {
|
||||
if (enemyType === "愚人众") {
|
||||
// 愚人众战斗点坐标(需要根据实际位置调整)
|
||||
return { x: 4840.55, y: -3078.01 }; // 这里需要替换为实际的愚人众战斗点坐标
|
||||
} else {
|
||||
// 盗宝团战斗点坐标
|
||||
return { x: -2757.281, y: -3468.437 };
|
||||
}
|
||||
}
|
||||
|
||||
//切换队伍
|
||||
async function switchPartyIfNeeded(partyName) {
|
||||
if (!partyName) {
|
||||
await genshin.returnMainUi();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("正在尝试切换至" + partyName);
|
||||
if (!await genshin.switchParty(partyName)) {
|
||||
log.info("切换队伍失败,前往七天神像重试");
|
||||
await genshin.tpToStatueOfTheSeven();
|
||||
await genshin.switchParty(partyName);
|
||||
}
|
||||
} catch {
|
||||
log.error("队伍切换失败,可能处于联机模式或其他不可切换状态");
|
||||
notification.error(`队伍切换失败,可能处于联机模式或其他不可切换状态`);
|
||||
await genshin.returnMainUi();
|
||||
}
|
||||
}
|
||||
|
||||
//等待战斗结果
|
||||
async function waitForBattleResult(timeout = 2 * 60 * 1000, enemyType = "盗宝团", cts = new CancellationTokenSource()) {
|
||||
let fightStartTime = Date.now();
|
||||
const successKeywords = ["事件", "完成"];
|
||||
const failureKeywords = ["失败"];
|
||||
const eventKeywords = getOcrKeywords(enemyType);
|
||||
let notFind = 0;
|
||||
|
||||
while (Date.now() - fightStartTime < timeout) {
|
||||
try {
|
||||
// 简化OCR检测,只使用一个try-catch块
|
||||
let result = captureGameRegion().find(RecognitionObject.ocr(850, 150, 200, 80));
|
||||
let result2 = captureGameRegion().find(RecognitionObject.ocr(0, 200, 300, 300));
|
||||
let text = result.text;
|
||||
let text2 = result2.text;
|
||||
|
||||
// 检查成功关键词
|
||||
for (let keyword of successKeywords) {
|
||||
if (text.includes(keyword)) {
|
||||
log.info("检测到战斗成功关键词: {0}", keyword);
|
||||
log.info("战斗结果:成功");
|
||||
cts.cancel(); // 取消任务
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查失败关键词
|
||||
for (let keyword of failureKeywords) {
|
||||
if (text.includes(keyword)) {
|
||||
log.warn("检测到战斗失败关键词: {0}", keyword);
|
||||
log.warn("战斗结果:失败,回到七天神像重试");
|
||||
cts.cancel(); // 取消任务
|
||||
await genshin.tpToStatueOfTheSeven();
|
||||
if (enemyType === "愚人众") {
|
||||
await AutoPath('愚人众-准备');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查事件关键词
|
||||
let find = 0;
|
||||
for (let keyword of eventKeywords) {
|
||||
if (text2.includes(keyword)) {
|
||||
find++;
|
||||
}
|
||||
}
|
||||
|
||||
if (find === 0) {
|
||||
notFind++;
|
||||
log.info("未检测到任务触发关键词:{0} 次", notFind);
|
||||
} else {
|
||||
notFind = 0;
|
||||
}
|
||||
|
||||
if (notFind > 10) {
|
||||
log.warn("不在任务触发区域,战斗失败");
|
||||
cts.cancel(); // 取消任务
|
||||
if (enemyType === "愚人众") {
|
||||
log.warn("回到愚人众准备点");
|
||||
await AutoPath('愚人众-准备');
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
log.error("OCR过程中出错: {0}", error);
|
||||
// 出错后继续循环,不进行额外嵌套处理
|
||||
}
|
||||
|
||||
// 统一的检查间隔
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
log.warn("在超时时间内未检测到战斗结果");
|
||||
cts.cancel(); // 取消任务
|
||||
throw new Error("战斗超时,未检测到结果");
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证超时时间设置
|
||||
* @param {number|string} value - 用户设置的超时时间(秒)
|
||||
* @param {number} defaultValue - 默认超时时间(秒)
|
||||
* @param {string} timeoutType - 超时类型名称
|
||||
* @returns {number} - 验证后的超时时间(秒)
|
||||
*/
|
||||
function validateTimeoutSetting(value, defaultValue, timeoutType) {
|
||||
// 转换为数字
|
||||
const timeout = Number(value);
|
||||
|
||||
// 检查是否为有效数字且大于0
|
||||
if (!isFinite(timeout) || timeout <= 0) {
|
||||
log.warn(`${timeoutType}超时设置无效,必须是大于0的数字,将使用默认值 ${defaultValue} 秒`);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
log.info(`${timeoutType}超时设置为 ${timeout} 秒`);
|
||||
return timeout;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user