* js:锄地一条龙 新增“自动优化”,根据运行记录按一定权重修改路线的预期用时 * js:锄地路线测试 用于测试锄地路线的怪物信息,运行时间,并根据运行记录修改description字段,以适配js锄地一条龙
892 lines
36 KiB
JavaScript
892 lines
36 KiB
JavaScript
|
||
// 定义替换映射表
|
||
const replacementMap = {
|
||
"监": "盐",
|
||
"卵": "卯"
|
||
};
|
||
|
||
async function readFolder(folderPath, onlyJson) {
|
||
log.info(`开始读取文件夹:${folderPath}`);
|
||
|
||
// 新增一个堆栈,初始时包含 folderPath
|
||
const folderStack = [folderPath];
|
||
|
||
// 新增一个数组,用于存储文件信息对象
|
||
const files = [];
|
||
|
||
// 当堆栈不为空时,继续处理
|
||
while (folderStack.length > 0) {
|
||
// 从堆栈中弹出一个路径
|
||
const currentPath = folderStack.pop();
|
||
|
||
// 读取当前路径下的所有文件和子文件夹路径
|
||
const filesInSubFolder = file.ReadPathSync(currentPath);
|
||
|
||
// 临时数组,用于存储子文件夹路径
|
||
const subFolders = [];
|
||
for (const filePath of filesInSubFolder) {
|
||
if (file.IsFolder(filePath)) {
|
||
// 如果是文件夹,先存储到临时数组中
|
||
subFolders.push(filePath);
|
||
} else {
|
||
// 如果是文件,根据 onlyJson 判断是否存储
|
||
if (onlyJson) {
|
||
if (filePath.endsWith(".json")) {
|
||
const fileName = filePath.split('\\').pop(); // 提取文件名
|
||
const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组
|
||
files.push({
|
||
fullPath: filePath,
|
||
fileName: fileName,
|
||
folderPathArray: folderPathArray
|
||
});
|
||
//log.info(`找到 JSON 文件:${filePath}`);
|
||
}
|
||
} else {
|
||
const fileName = filePath.split('\\').pop(); // 提取文件名
|
||
const folderPathArray = filePath.split('\\').slice(0, -1); // 提取文件夹路径数组
|
||
files.push({
|
||
fullPath: filePath,
|
||
fileName: fileName,
|
||
folderPathArray: folderPathArray
|
||
});
|
||
//log.info(`找到文件:${filePath}`);
|
||
}
|
||
}
|
||
}
|
||
// 将临时数组中的子文件夹路径按原顺序压入堆栈
|
||
folderStack.push(...subFolders.reverse()); // 反转子文件夹路径
|
||
}
|
||
|
||
return files;
|
||
}
|
||
|
||
|
||
async function getMonsterCounts() {
|
||
// 初始化结果对象
|
||
const monsterCounts = {};
|
||
|
||
async function scrollPage(totalDistance, stepDistance = 10, delayMs = 5) {
|
||
moveMouseTo(400, 750); // 移动到屏幕水平中心,垂直750坐标
|
||
await sleep(50);
|
||
leftButtonDown();
|
||
|
||
// 计算滚动方向和总步数
|
||
const isDownward = totalDistance < 0; // 如果totalDistance为负数,则向下滑动
|
||
const steps = Math.ceil(Math.abs(totalDistance) / stepDistance); // 使用绝对值计算步数
|
||
|
||
for (let j = 0; j < steps; j++) {
|
||
const remainingDistance = Math.abs(totalDistance) - j * stepDistance;
|
||
const moveDistance = remainingDistance < stepDistance ? remainingDistance : stepDistance;
|
||
|
||
// 根据滚动方向调整移动方向
|
||
const direction = isDownward ? 1 : -1; // 向下滑动为正方向,向上滑动为负方向
|
||
moveMouseBy(0, 1.2 * direction * moveDistance); // 根据方向调整滚动方向
|
||
await sleep(delayMs);
|
||
}
|
||
|
||
await sleep(200);
|
||
leftButtonUp();
|
||
await sleep(100);
|
||
}
|
||
|
||
await genshin.returnMainUi(); // 返回主界面
|
||
keyPress("VK_ESCAPE"); // 打开派蒙菜单
|
||
await sleep(1500); // 等待1.5秒
|
||
|
||
// 1. 识别并点击【图鉴】
|
||
const archiveTemplate = RecognitionObject.TemplateMatch(
|
||
file.readImageMatSync("assets/RecognitionObject/图鉴.png"),
|
||
0, 0, 1920, 1080
|
||
);
|
||
const archiveRegion = captureGameRegion().find(archiveTemplate);
|
||
if (!archiveRegion.isEmpty()) {
|
||
archiveRegion.click();
|
||
}
|
||
await sleep(3000); // 等待3秒
|
||
|
||
// 2. 识别并点击【生物志】
|
||
const faunaTemplate = RecognitionObject.TemplateMatch(
|
||
file.readImageMatSync("assets/RecognitionObject/生物志.png"),
|
||
0, 0, 1920, 1080
|
||
);
|
||
const faunaRegion = captureGameRegion().find(faunaTemplate);
|
||
if (!faunaRegion.isEmpty()) {
|
||
faunaRegion.click();
|
||
}
|
||
await sleep(400);
|
||
click(1355, 532);
|
||
await sleep(2000); // 等待2秒
|
||
|
||
// 3. 循环处理怪物识别
|
||
// 读取 name.txt 文件中的怪物名称列表
|
||
const monsterList = file.readTextSync("assets/name.txt").split('\n').filter(name => name.trim() !== '');
|
||
let monsterNum = 1;
|
||
|
||
let previousMonsterCount = -1;
|
||
|
||
let failCount = 0;
|
||
|
||
for (let i = 0; i < monsterList.length; i++) {
|
||
const monsterId = monsterList[i];
|
||
let monsterRegion = null;
|
||
|
||
let pageTurnsUp = 0;
|
||
while (pageTurnsUp < 5) {
|
||
let pageTurns = 0;
|
||
while (pageTurns < 5) {
|
||
let tryTimes = 0;
|
||
while (tryTimes < 5) {
|
||
// 4a. 识别怪物图片
|
||
const monsterTemplate = RecognitionObject.TemplateMatch(
|
||
file.readImageMatSync(`assets/monster/${monsterId.trim()}.png`),
|
||
0, 0, 1920, 1080
|
||
);
|
||
monsterRegion = captureGameRegion().find(monsterTemplate);
|
||
if (!monsterRegion.isEmpty()) {
|
||
break; // 识别到怪物,跳出翻页循环
|
||
}
|
||
//未识别到时重试
|
||
tryTimes++;
|
||
}
|
||
if (!monsterRegion.isEmpty()) {
|
||
break; // 识别到怪物,跳出翻页循环
|
||
}
|
||
// 未识别到则向下翻页
|
||
await scrollPage(300); // 调用翻页函数
|
||
pageTurns++;
|
||
}
|
||
if (!monsterRegion.isEmpty()) {
|
||
break; // 识别到怪物,跳出翻页循环
|
||
}
|
||
// 未识别到则向上翻页
|
||
await scrollPage(-1800); // 调用翻页函数
|
||
pageTurnsUp++;
|
||
}
|
||
|
||
if (!monsterRegion || monsterRegion.isEmpty()) {
|
||
log.info(`怪物: ${monsterId.trim()}, 没有找到`);
|
||
monsterCounts[monsterId.trim()] = -1;
|
||
continue; // 达到翻页上限仍未找到,处理下一个怪物
|
||
}
|
||
|
||
monsterRegion.click(); // 点击怪物图标
|
||
await sleep(10); // 等待界面加载
|
||
|
||
// 4b. 识别数量区域(870,1000,100,30)
|
||
const countRegion = new ImageRegion(
|
||
captureGameRegion().SrcMat,
|
||
830, 980,
|
||
null, // owner 参数设置为 null
|
||
null, // converter 参数设置为 null
|
||
null // drawContent 参数设置为 null
|
||
);
|
||
// 创建OCR识别对象
|
||
const ocrObject = RecognitionObject.Ocr(830, 980, 140, 70);
|
||
const countResults = countRegion.findMulti(ocrObject);
|
||
let monsterCount = "-1";
|
||
|
||
if (countResults.count > 0) {
|
||
for (let i = 0; i < countResults.count; i++) {
|
||
const text = countResults[i].text;
|
||
const numbers = text.match(/\d+/);
|
||
if (numbers) {
|
||
monsterCount = numbers[0];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ((monsterCount === -1 || monsterCount === previousMonsterCount) && failCount <= 5) {
|
||
log.warn(`识别失败或结果与上次相同,重新识别第 ${i + 1} 个怪物 ${monsterId}`);
|
||
i--; // 将索引减 1,使得下一次循环重新执行当前索引
|
||
failCount++;//失败计数加一
|
||
continue; // 跳过当前迭代的剩余部分
|
||
} else {
|
||
failCount = 0;//重置失败计数
|
||
}
|
||
|
||
previousMonsterCount = monsterCount;
|
||
|
||
|
||
// 4c. 输出日志
|
||
log.info(`NO.${monsterNum} 怪物名称: ${monsterId.trim()}, 数量: ${monsterCount}`);
|
||
monsterNum++;
|
||
|
||
// 4d. 存储结果到对象
|
||
monsterCounts[monsterId.trim()] = monsterCount;
|
||
}
|
||
|
||
return monsterCounts;
|
||
}
|
||
|
||
// 定义所有图标的图像识别对象,每个图片都有自己的识别区域
|
||
let CharacterMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/CharacterMenu.png"), 60, 991, 38, 38);
|
||
|
||
// 定义一个函数用于识别图像
|
||
async function recognizeImage(recognitionObject, timeout = 5000) {
|
||
log.info(`开始图像识别,超时时间: ${timeout}ms`);
|
||
let startTime = Date.now();
|
||
while (Date.now() - startTime < timeout) {
|
||
try {
|
||
// 尝试识别图像
|
||
let imageResult = captureGameRegion().find(recognitionObject);
|
||
if (imageResult) {
|
||
log.info(`成功识别图像,坐标: x=${imageResult.x}, y=${imageResult.y}`);
|
||
return { success: true, x: imageResult.x, y: imageResult.y };
|
||
}
|
||
} catch (error) {
|
||
log.error(`识别图像时发生异常: ${error.message}`);
|
||
}
|
||
await sleep(500); // 短暂延迟,避免过快循环
|
||
}
|
||
log.warn(`经过多次尝试,仍然无法识别图像`);
|
||
return { success: false };
|
||
}
|
||
|
||
// 定义一个函数用于识别文字并点击
|
||
async function recognizeTextAndClick(targetText, ocrRegion, timeout = 5000) {
|
||
log.info(`开始文字识别,目标文本: ${targetText},区域: x=${ocrRegion.x}, y=${ocrRegion.y}, width=${ocrRegion.width}, height=${ocrRegion.height}`);
|
||
let startTime = Date.now();
|
||
while (Date.now() - startTime < timeout) {
|
||
try {
|
||
// 尝试 OCR 识别
|
||
let resList = captureGameRegion().findMulti(RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height)); // 指定识别区域
|
||
// 遍历识别结果,检查是否找到目标文本
|
||
for (let res of resList) {
|
||
// 后处理:根据替换映射表检查和替换错误识别的字符
|
||
let correctedText = res.text;
|
||
for (let [wrongChar, correctChar] of Object.entries(replacementMap)) {
|
||
correctedText = correctedText.replace(new RegExp(wrongChar, 'g'), correctChar);
|
||
}
|
||
|
||
if (correctedText.includes(targetText)) {
|
||
// 如果找到目标文本,计算并点击文字的中心坐标
|
||
let centerX = res.x + res.width / 2;
|
||
let centerY = res.y + res.height / 2;
|
||
log.info(`识别到目标文本: ${correctedText},点击坐标: x=${centerX}, y=${centerY}`);
|
||
await click(centerX, centerY);
|
||
await sleep(500); // 确保点击后有足够的时间等待
|
||
return { success: true, x: centerX, y: centerY };
|
||
}
|
||
}
|
||
} catch (error) {
|
||
log.warn(`页面标志识别失败,正在进行重试... 错误信息: ${error.message}`);
|
||
}
|
||
await sleep(1000); // 短暂延迟,避免过快循环
|
||
}
|
||
log.warn(`经过多次尝试,仍然无法识别文字: ${targetText}`);
|
||
return { success: false };
|
||
}
|
||
|
||
// 定义一个独立的函数用于在指定区域进行 OCR 识别并输出识别内容
|
||
async function recognizeTextInRegion(ocrRegion, timeout = 5000) {
|
||
log.info(`开始 OCR 识别,区域: x=${ocrRegion.x}, y=${ocrRegion.y}, width=${ocrRegion.width}, height=${ocrRegion.height}`);
|
||
let startTime = Date.now();
|
||
while (Date.now() - startTime < timeout) {
|
||
try {
|
||
// 在指定区域进行 OCR 识别
|
||
let ocrResult = captureGameRegion().find(RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height));
|
||
if (ocrResult) {
|
||
log.info(`OCR 识别成功,原始文本: ${ocrResult.text}`);
|
||
// 后处理:根据替换映射表检查和替换错误识别的字符
|
||
let correctedText = ocrResult.text;
|
||
for (let [wrongChar, correctChar] of Object.entries(replacementMap)) {
|
||
correctedText = correctedText.replace(new RegExp(wrongChar, 'g'), correctChar);
|
||
}
|
||
log.info(`修正后文本: ${correctedText}`);
|
||
return correctedText; // 返回识别到的内容
|
||
} else {
|
||
log.warn(`OCR 识别区域未找到内容`);
|
||
return null; // 如果 OCR 未识别到内容,返回 null
|
||
}
|
||
} catch (error) {
|
||
log.error(`OCR 摩拉数识别失败,错误信息: ${error.message}`);
|
||
}
|
||
await sleep(500); // 短暂延迟,避免过快循环
|
||
}
|
||
log.warn(`经过多次尝试,仍然无法在指定区域识别到文字`);
|
||
return null; // 如果未识别到文字,返回 null
|
||
}
|
||
|
||
// 定义 mora 函数
|
||
async function mora() {
|
||
log.info("开始执行 mora 函数");
|
||
// 设置游戏分辨率和 DPI 缩放比例
|
||
setGameMetrics(1920, 1080, 1);
|
||
log.info("游戏分辨率和 DPI 设置完成");
|
||
|
||
// 返回游戏主界面
|
||
await genshin.returnMainUi();
|
||
log.info("返回游戏主界面");
|
||
|
||
// 按下 C 键
|
||
keyPress("C");
|
||
log.info("按下 C 键");
|
||
await sleep(1500);
|
||
|
||
let recognized = false;
|
||
|
||
// 识别“角色菜单”图标或“天赋”文字
|
||
let startTime = Date.now();
|
||
while (Date.now() - startTime < 5000) {
|
||
// 尝试识别“角色菜单”图标
|
||
let characterMenuResult = await recognizeImage(CharacterMenuRo, 5000);
|
||
if (characterMenuResult.success) {
|
||
await click(177, 433);
|
||
log.info("点击角色菜单图标");
|
||
await sleep(500);
|
||
recognized = true;
|
||
break;
|
||
}
|
||
|
||
// 尝试识别“天赋”文字
|
||
let targetText = "天赋";
|
||
let ocrRegion = { x: 133, y: 395, width: 115, height: 70 }; // 设置对应的识别区域
|
||
let talentResult = await recognizeTextAndClick(targetText, ocrRegion);
|
||
if (talentResult.success) {
|
||
log.info(`点击天赋文字,坐标: x=${talentResult.x}, y=${talentResult.y}`);
|
||
recognized = true;
|
||
break;
|
||
}
|
||
|
||
await sleep(1000); // 短暂延迟,避免过快循环
|
||
}
|
||
|
||
// 如果识别到了“角色菜单”或“天赋”,则识别“摩拉数值”
|
||
if (recognized) {
|
||
let ocrRegionMora = { x: 1620, y: 25, width: 152, height: 46 }; // 设置对应的识别区域
|
||
let recognizedText = await recognizeTextInRegion(ocrRegionMora);
|
||
if (recognizedText) {
|
||
log.info(`成功识别到摩拉数值: ${recognizedText}`);
|
||
return recognizedText; // 返回识别到的摩拉数值
|
||
} else {
|
||
log.warn("未能识别到摩拉数值。");
|
||
}
|
||
} else {
|
||
log.warn("未能识别到角色菜单或天赋,跳过摩拉数值识别。");
|
||
}
|
||
|
||
await sleep(500);
|
||
await genshin.returnMainUi();
|
||
log.info("返回游戏主界面");
|
||
|
||
return null; // 如果未能识别到摩拉数值,返回 null
|
||
}
|
||
|
||
// 定义自定义函数 basename,用于获取文件名
|
||
function basename(filePath) {
|
||
const lastSlashIndex = filePath.lastIndexOf('\\'); // 或者使用 '/',取决于你的路径分隔符
|
||
return filePath.substring(lastSlashIndex + 1);
|
||
}
|
||
|
||
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.debug(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.debug(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.debug(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.debug(logMessage);
|
||
}
|
||
}
|
||
|
||
// 主逻辑
|
||
(async function () {
|
||
const name1 = "锄地路线测试";
|
||
// 调用 fakeLog 函数,输出 JavaScript 的结尾日志,耗时 1.234 秒
|
||
const duration1 = 1234; // 1.234 秒
|
||
await fakeLog(name1, true, false, duration1);
|
||
// 启用自动拾取的实时任务
|
||
log.info("启用自动拾取的实时任务");
|
||
dispatcher.addTimer(new RealtimeTimer("AutoPick"));
|
||
|
||
// 从自定义配置中获取 startRouteNumber,默认值为 1
|
||
const startRouteNumber = settings.startRouteNumber || 1;
|
||
|
||
// 定义 pathing 文件夹路径
|
||
const pathingFolderPath = "pathing";
|
||
|
||
// 定义 result 文件夹路径
|
||
const resultFolderPath = "records";
|
||
|
||
// 读取文件内容并解析为对象
|
||
const fileInfo = file.readTextSync("assets/info.json");
|
||
const infoData = JSON.parse(fileInfo);
|
||
|
||
if (!settings.doTest) {
|
||
// 在 for 循环之前获取一次时间,用于命名记录文件
|
||
const startTime = new Date();
|
||
const formattedStartTime = startTime.toISOString().replace(/[^0-9]/g, ''); // 去掉所有非数字的字符
|
||
|
||
const recordFileName = `${formattedStartTime}.json`;
|
||
|
||
const recordFilePath = resultFolderPath + '/' + recordFileName; // 使用字符串拼接
|
||
|
||
// 读取 pathing 文件夹中的所有文件路径
|
||
const routes = await readFolder(pathingFolderPath, true);
|
||
|
||
let Mora = -1; // 初始化为 -1,表示失败
|
||
let attempts = 0; // 初始化尝试次数
|
||
|
||
while (attempts < 5) {
|
||
const result = await mora(); // 调用 mora() 获取结果
|
||
if (result !== null) {
|
||
Mora = parseInt(result.match(/\d+/g).join(''), 10); // 处理结果并赋值
|
||
break; // 成功获取后退出循环
|
||
}
|
||
attempts++; // 增加尝试次数
|
||
log.warn(`获取的 mora 值为 null,尝试次数 ${attempts}/5,重新获取...`);
|
||
}
|
||
|
||
if (Mora === -1) {
|
||
log.warn('尝试 5 次后仍未获取到有效的 mora 值,记为 -1');
|
||
}
|
||
|
||
|
||
// 在循环前获取初始的怪物数量信息
|
||
let MonsterInfo = await getMonsterCounts();
|
||
|
||
let routeTime = new Date();
|
||
|
||
|
||
|
||
// 遍历 routes 数组,处理每个文件的 fullPath
|
||
for (let i = startRouteNumber - 1; i < routes.length; i++) {
|
||
await genshin.tpToStatueOfTheSeven();
|
||
const route = routes[i];
|
||
log.info(`完整路径:${route.fullPath}`);
|
||
|
||
// 初始化 expectMora, eliteNum 和 normalNum
|
||
route.expectMora = 0;
|
||
route.eliteNum = 0;
|
||
route.normalNum = 0;
|
||
|
||
// 输出地图追踪开始的日志
|
||
const duration2 = 0; // 地图追踪开始时,耗时为 0
|
||
await fakeLog(route.fullPath, false, true, duration2);
|
||
|
||
routeTime = new Date();
|
||
|
||
log.info(`这是第 ${i + 1}条路线:${route.fullPath}`)
|
||
|
||
// 执行路线文件
|
||
await pathingScript.runFile(route.fullPath);
|
||
|
||
// 再次获取当前时间
|
||
let newDate = new Date();
|
||
|
||
// 计算时间差(以秒为单位)
|
||
const timeDiffInSeconds = (newDate - routeTime) / 1000;
|
||
|
||
// 将时间差添加到对应 routes 中的 routeTime 子项
|
||
route.routeTime = timeDiffInSeconds;
|
||
|
||
// 调用 fakeLog 函数,输出地图追踪结束的日志,耗时 5.000 秒
|
||
const duration3 = 5000; // 5.000 秒
|
||
await fakeLog(route.fullPath, false, false, duration3);
|
||
|
||
try {
|
||
await sleep(10);
|
||
} catch (error) {
|
||
log.error(`运行中断: ${error}`);
|
||
break;
|
||
}
|
||
|
||
// 再次获取怪物数量信息
|
||
let currentMonsterInfo = await getMonsterCounts();
|
||
|
||
const monsterDifferences = {};
|
||
for (const monster in currentMonsterInfo) {
|
||
// 检查当前怪物数量或初始怪物数量是否为 -1
|
||
if (currentMonsterInfo[monster] !== MonsterInfo[monster] &&
|
||
currentMonsterInfo[monster] !== -1 &&
|
||
MonsterInfo[monster] !== -1) {
|
||
monsterDifferences[monster] = currentMonsterInfo[monster] - MonsterInfo[monster];
|
||
}
|
||
}
|
||
|
||
// 将不为 0 且不涉及 -1 的怪物及其数量添加到对应 routes 中的 monsterNum 子项
|
||
route.monsterNum = monsterDifferences;
|
||
|
||
|
||
// 更新 MonsterInfo 为当前的怪物数量信息,以便下一次循环使用
|
||
MonsterInfo = currentMonsterInfo;
|
||
|
||
let currentMora = -1; // 初始化为 -1,表示失败
|
||
attempts = 0; // 初始化尝试次数
|
||
|
||
while (attempts < 5) {
|
||
const result = await mora(); // 调用 mora() 获取结果
|
||
if (result !== null) {
|
||
currentMora = parseInt(result.match(/\d+/g).join(''), 10); // 处理结果并赋值
|
||
break; // 成功获取后退出循环
|
||
}
|
||
attempts++; // 增加尝试次数
|
||
log.warn(`获取的 mora 值为 null,尝试次数 ${attempts}/5,重新获取...`);
|
||
}
|
||
|
||
if (Mora === -1) {
|
||
log.warn('尝试 5 次后仍未获取到有效的 mora 值,记为 -1');
|
||
}
|
||
|
||
// 计算摩拉数量的差值
|
||
const moraDiff = currentMora - Mora;
|
||
|
||
// 将摩拉数量的差值添加到对应 routes 中的 moraDiff 子项
|
||
route.moraDiff = moraDiff;
|
||
|
||
// 更新 Mora 为当前的摩拉数量,以便下一次循环使用
|
||
Mora = currentMora;
|
||
|
||
// 处理怪物数量信息
|
||
for (const [monsterName, count] of Object.entries(route.monsterNum)) {
|
||
const monsterInfo = infoData.find(item => item.name === monsterName);
|
||
if (monsterInfo) {
|
||
if (monsterInfo.type === "普通") {
|
||
route.normalNum += count;
|
||
route.expectMora += count * monsterInfo.moraRate * 40.5;
|
||
} else if (monsterInfo.type === "精英") {
|
||
route.eliteNum += count;
|
||
route.expectMora += count * monsterInfo.moraRate * 200;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 将已经运行过的 routes 写入记录文件
|
||
let recordContent = JSON.stringify(routes.slice(startRouteNumber - 1, i + 1), null, 2);
|
||
log.debug(recordContent);
|
||
// 将文件名写入记录文件
|
||
try {
|
||
await file.writeText(recordFilePath, recordContent);
|
||
log.info(`记录文件已写入 ${recordFilePath}`);
|
||
} catch (error) {
|
||
log.error(`写入记录文件失败: ${error.message}`);
|
||
}
|
||
await sleep(1000);
|
||
}
|
||
} else {
|
||
// 在 else 分支中读取 records 文件夹中的文件
|
||
log.info("doTest 设置为 false,读取 records 文件夹中的文件");
|
||
|
||
// 读取 pathing 文件夹中的所有文件路径
|
||
const pathingFolderPath = "pathing";
|
||
const routes = await readFolder(pathingFolderPath, true);
|
||
log.info(`找到 ${routes.length} 个路径文件`);
|
||
|
||
// 读取 records 文件夹中的所有文件路径
|
||
const recordsFolderPath = "records";
|
||
const records = await readFolder(recordsFolderPath, true);
|
||
log.info(`找到 ${records.length} 个记录文件`);
|
||
|
||
// 创建一个对象来存储每个 fullPath 的最近五次记录
|
||
const recordMap = {};
|
||
|
||
// 遍历读取到的记录文件路径
|
||
for (const record of records) {
|
||
log.info(`处理文件:${record.fullPath}`);
|
||
|
||
try {
|
||
// 读取文件内容
|
||
const fileContent = file.readTextSync(record.fullPath);
|
||
log.info(`文件内容:${fileContent}`);
|
||
|
||
// 解析文件内容
|
||
const jsonData = JSON.parse(fileContent);
|
||
|
||
// 如果 jsonData 是一个数组,遍历数组中的每一项
|
||
if (Array.isArray(jsonData)) {
|
||
for (const entry of jsonData) {
|
||
// 提取需要的信息
|
||
// 逐项解析
|
||
const fullPath = entry.fullPath;
|
||
const monsterNum = entry.monsterNum;
|
||
const moraDiff = entry.moraDiff;
|
||
const routeTime = entry.routeTime;
|
||
const expectMora = entry.expectMora;
|
||
const normalNum = entry.normalNum;
|
||
const eliteNum = entry.eliteNum;
|
||
// 如果 fullPath 不存在或为空,跳过该记录
|
||
if (!fullPath) {
|
||
log.warn(`文件 ${record.fileName} 中的 fullPath 不存在或为空,跳过该记录`);
|
||
continue;
|
||
}
|
||
|
||
// 如果 recordMap 中没有这个 fullPath,初始化一个数组
|
||
if (!recordMap[fullPath]) {
|
||
recordMap[fullPath] = [];
|
||
}
|
||
|
||
// 将当前记录添加到数组中
|
||
recordMap[fullPath].push({
|
||
fullPath,
|
||
monsterNum,
|
||
moraDiff,
|
||
routeTime,
|
||
expectMora,
|
||
normalNum,
|
||
eliteNum
|
||
});
|
||
|
||
// 确保每个 fullPath 的记录不超过七次
|
||
if (recordMap[fullPath].length > 7) {
|
||
recordMap[fullPath].shift(); // 移除最早的记录
|
||
}
|
||
}
|
||
} else {
|
||
log.warn(`文件 ${record.fileName} 的内容不是数组,跳过该文件`);
|
||
}
|
||
} catch (error) {
|
||
log.error(`读取或解析文件 ${record.fileName} 时出错:${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 对 recordMap 中的每个 fullPath 进行处理
|
||
const finalRecords = [];
|
||
for (const fullPath in recordMap) {
|
||
const records = recordMap[fullPath];
|
||
|
||
// 对每个字段分别处理
|
||
const fields = ["routeTime"];
|
||
const processedRecord = { fullPath, records: {} };
|
||
|
||
// 处理数值字段
|
||
fields.forEach(field => {
|
||
// 提取每个记录的字段值
|
||
const values = records.map(record => record[field]);
|
||
|
||
// 剔除小于等于 0 的项
|
||
const positiveValues = values.filter(val => val > 0);
|
||
|
||
// 如果过滤后的数组长度为 0,设置结果为 0
|
||
if (positiveValues.length === 0) {
|
||
processedRecord.records[field] = 0;
|
||
} else if (positiveValues.length < 5) {
|
||
// 如果记录数量小于五个,直接取平均值并保留两位小数
|
||
processedRecord.records[field] = parseFloat((positiveValues.reduce((sum, val) => sum + val, 0) / positiveValues.length).toFixed(2));
|
||
} else {
|
||
// 如果记录数量大于等于五个,去除一个最大值和一个最小值,取平均值并保留两位小数
|
||
let maxVal = Math.max(...positiveValues);
|
||
let minVal = Math.min(...positiveValues);
|
||
|
||
// 去掉一个最大值和一个最小值
|
||
const filteredValues = positiveValues.filter(val => {
|
||
if (val === maxVal) {
|
||
maxVal = null; // 确保只去掉一个最大值
|
||
return false;
|
||
}
|
||
if (val === minVal) {
|
||
minVal = null; // 确保只去掉一个最小值
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
|
||
// 计算平均值并保留两位小数
|
||
processedRecord.records[field] = parseFloat((filteredValues.reduce((sum, val) => sum + val, 0) / filteredValues.length).toFixed(2));
|
||
}
|
||
});
|
||
|
||
// 处理 monsterNum 字段
|
||
const allMonsters = records.flatMap(record => Object.keys(record.monsterNum));
|
||
const uniqueMonsters = [...new Set(allMonsters)];
|
||
processedRecord.records.monsterNum = {};
|
||
|
||
uniqueMonsters.forEach(monster => {
|
||
// 提取每个怪物的数量,并将缺失或小于 0 的值视为 0
|
||
const counts = records.map(record => {
|
||
const count = record.monsterNum[monster];
|
||
return count > 0 ? count : 0; // 如果小于或等于 0,视为 0
|
||
});
|
||
|
||
// 如果所有记录中的数量都是 0,跳过该怪物
|
||
if (counts.every(count => count === 0)) {
|
||
return;
|
||
}
|
||
|
||
// 如果记录总数大于等于 5,依次去除最大值和最小值,直到记录总数小于 5
|
||
let removeMax = true; // 控制去除最大值或最小值
|
||
while (counts.length >= 5) {
|
||
if (removeMax) {
|
||
// 去掉一个最大值
|
||
const maxCount = Math.max(...counts);
|
||
counts.splice(counts.indexOf(maxCount), 1);
|
||
} else {
|
||
// 去掉一个最小值
|
||
const minCount = Math.min(...counts);
|
||
counts.splice(counts.indexOf(minCount), 1);
|
||
}
|
||
// 切换去除最大值或最小值的标志
|
||
removeMax = !removeMax;
|
||
}
|
||
|
||
// 计算平均值并保留两位小数
|
||
const average = parseFloat((counts.reduce((sum, val) => sum + val, 0) / counts.length).toFixed(2));
|
||
|
||
// 如果平均值小于等于 0 或无意义,跳过该怪物
|
||
if (average <= 0) {
|
||
return;
|
||
}
|
||
|
||
// 记录平均值
|
||
processedRecord.records.monsterNum[monster] = average;
|
||
});
|
||
|
||
|
||
|
||
// 使用处理后的 monsterNum 数据计算其他相关数值字段
|
||
processedRecord.records.normalNum = 0;
|
||
processedRecord.records.eliteNum = 0;
|
||
processedRecord.records.expectMora = 0;
|
||
|
||
for (const [monsterName, count] of Object.entries(processedRecord.records.monsterNum)) {
|
||
const monsterInfo = infoData.find(item => item.name === monsterName);
|
||
if (monsterInfo) {
|
||
if (monsterInfo.type === "普通") {
|
||
processedRecord.records.normalNum += count;
|
||
processedRecord.records.expectMora += count * monsterInfo.moraRate * 40.5;
|
||
} else if (monsterInfo.type === "精英") {
|
||
processedRecord.records.eliteNum += count;
|
||
processedRecord.records.expectMora += count * monsterInfo.moraRate * 200;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 将处理后的记录添加到最终结果中
|
||
finalRecords.push(processedRecord);
|
||
}
|
||
|
||
// 初始化计数器
|
||
let totalRoutes = routes.length;
|
||
let matchedCount = 0;
|
||
let unmatchedCount = 0;
|
||
|
||
// 遍历 finalRecords,更新 description 字段
|
||
for (const record of finalRecords) {
|
||
const { fullPath, records } = record;
|
||
|
||
// 检查 fullPath 是否在 routes 中
|
||
const route = routes.find(route => route.fullPath === fullPath);
|
||
|
||
if (!route) {
|
||
log.warn(`文件 ${fullPath} 不在 routes 中,跳过处理`);
|
||
unmatchedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 读取文件内容
|
||
const fileContent = file.readTextSync(fullPath);
|
||
const jsonData = JSON.parse(fileContent);
|
||
|
||
// 构建新的 description 内容
|
||
const {
|
||
routeTime,
|
||
expectMora,
|
||
normalNum,
|
||
eliteNum,
|
||
monsterNum
|
||
} = records;
|
||
|
||
// 生成怪物描述
|
||
let monsterDescription = Object.entries(monsterNum)
|
||
.map(([monster, count]) => `${count}只${monster}`)
|
||
.join('、');
|
||
|
||
let newDescription;
|
||
|
||
if (eliteNum === 0 && normalNum === 0) {
|
||
// 如果精英和小怪数量都为 0
|
||
newDescription = ` 路线信息:该路线预计用时${routeTime}秒,该路线不含任何精英或小怪。`;
|
||
} else {
|
||
newDescription = ` 路线信息:该路线预计用时${routeTime}秒,包含以下怪物:${monsterDescription}。`;
|
||
}
|
||
|
||
jsonData.info.description = `${newDescription}`;
|
||
|
||
// 将更新后的内容写回文件
|
||
// 替换第一个出现的 pathing 为 pathingOut
|
||
const modifiedFullPath = fullPath.replace("pathing", "pathingOut");
|
||
log.info(modifiedFullPath);
|
||
// 写入文件
|
||
await file.writeTextSync(modifiedFullPath, JSON.stringify(jsonData, null, 2));
|
||
log.info(`文件 ${fullPath} 的 description 已更新`);
|
||
|
||
matchedCount++;
|
||
}
|
||
|
||
// 输出最终统计信息
|
||
log.info(`总路径文件数:${totalRoutes}`);
|
||
log.info(`成功匹配并修改的文件数:${matchedCount}`);
|
||
log.info(`未匹配的记录数:${unmatchedCount}`);
|
||
}
|
||
|
||
// 调用 fakeLog 函数,输出 JavaScript 开始的日志
|
||
const duration4 = 0; // JS 开始时,耗时为 0
|
||
await fakeLog(name1, true, true, duration4);
|
||
})();
|