Files
bettergi-scripts-list/repo/js/自动精英锄地规划/main.js
2025-06-25 07:52:48 +08:00

486 lines
26 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.

// 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.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);
}
}
// 定义自定义函数 basename用于获取文件名
function basename(filePath) {
const lastSlashIndex = filePath.lastIndexOf('\\'); // 或者使用 '/',取决于你的路径分隔符
return filePath.substring(lastSlashIndex + 1);
}
(async function () {
// 初始化配置
const operationType = settings.operationType || "执行路径组文件1"; // 操作模式
let excludeTagsForPathGroup1 = settings.excludeTagsForPathGroup1 || ""; // 路径组1排除标签
let selectTagsForPathGroup2 = settings.selectTagsForPathGroup2 || ""; // 路径组2选择标签
let selectTagsForPathGroup3 = settings.selectTagsForPathGroup3 || ""; // 路径组3选择标签
const disableAutoPickup = settings.disableAutoPickup || false; // 是否禁用自动拾取
const disableRouteCdCheck = settings.disableRouteCdCheck || false; // 是否禁用路线CD检测
const requiredMonsterCount = parseInt(settings.requiredMonsterCount, 10) || 405; // 目标怪物数量
const minSecPerMonster = parseFloat(settings.minSecPerMonster) || 0.1; // 最低秒均
const accountName = settings.accountName || "一个账户名"; // 账户名
// 新增全局排除关键词的配置
const excludeTagsForAll = settings.excludeTagsForAll || ""; // 全局排除关键词
const excludeTagsForAllArray = excludeTagsForAll.split('').map(tag => tag.trim()).filter(tag => tag !== "");
// 生成默认的文件夹/文件名,现在默认为 accountName
let outputFolderName = accountName;
// 日志输出配置信息
log.info(`配置信息:操作类型=${operationType}, 路径组1排除标签=${excludeTagsForPathGroup1}, 路径组2选择标签=${selectTagsForPathGroup2}, 路径组3选择标签=${selectTagsForPathGroup3}, 是否禁用自动拾取=${disableAutoPickup}, 是否禁用路线CD检测=${disableRouteCdCheck}, 目标怪物数量=${requiredMonsterCount}, 最低秒均=${minSecPerMonster}, 全局排除关键词=${excludeTagsForAll}, 账户名=${accountName}`);
log.info(`解析的全局排除关键词:${excludeTagsForAllArray.join(', ')}`);
// 根据 disableRouteCdCheck 的值设置 enableRouteCdCheck
const enableRouteCdCheck = !disableRouteCdCheck; // 如果 disableRouteCdCheck 为 true则 enableRouteCdCheck 为 false反之亦然
// 初始化文件路径(直接内置生成)
const indexPath = 'index.txt';
const pathingDir = 'pathing/';
const routeDir = 'route/';
// 定义需要检查的字段
const tags = ["水免", "次数盾", "传奇", "高危"];
// 标准化 excludeTagsForPathGroup1
excludeTagsForPathGroup1 = tags.filter(tag => excludeTagsForPathGroup1.includes(tag)).join('.');
// 标准化 selectTagsForPathGroup2
selectTagsForPathGroup2 = tags.filter(tag => selectTagsForPathGroup2.includes(tag)).join('.');
// 标准化 selectTagsForPathGroup3
selectTagsForPathGroup3 = tags.filter(tag => selectTagsForPathGroup3.includes(tag)).join('.');
// 初始化一个变量,用于判断是否是执行路径组文件的模式
let executePathGroupMode = 0;
// 分别检验是否是执行路径组文件1、执行路径组文件2、执行路径组文件3
if (operationType === "执行路径组文件1") {
executePathGroupMode = 1;
} else if (operationType === "执行路径组文件2") {
executePathGroupMode = 2;
} else if (operationType === "执行路径组文件3") {
executePathGroupMode = 3;
}
//当用户所有自定义配置均为默认时警告用户
if (
operationType === "执行路径组文件1" &&
excludeTagsForPathGroup1 === "" &&
selectTagsForPathGroup2 === "" &&
selectTagsForPathGroup3 === "" &&
disableAutoPickup === false &&
disableRouteCdCheck === false &&
requiredMonsterCount === 405 &&
minSecPerMonster === 0.1 &&
accountName === "一个账户名" &&
excludeTagsForAll === "不跑"
) {
log.warn("所有配置项均为默认状态你没有修改自定义配置请在配置组界面中右键本js以修改自定义配置。");
await sleep(5000);
}
// 尝试判断路径组文件是否存在
const pathGroupNumber = executePathGroupMode;
// 构造路径组文件路径
const pathGroupFilePath = `${routeDir}路径组${pathGroupNumber}-${outputFolderName}.txt`;
// 读取routeDir文件夹中的所有文件路径
const filesInRouteDir = await file.ReadPathSync(routeDir);
// 检查路径组文件是否存在
let doGenerate = true;
for (const filePath of filesInRouteDir) {
const fileName = basename(filePath); // 提取文件名
if (fileName === `路径组${pathGroupNumber}-${outputFolderName}.txt`) {
doGenerate = false;
break;
}
}
if (doGenerate) {
log.warn(`路径组文件不存在,尝试先生成路径组文件`);
}
if (executePathGroupMode === 0) {
doGenerate = true;
log.info(`尝试生成路径组文件`);
}
{
if (doGenerate) {
// 不为执行模式或路径组文件不存在,进行排序、筛选和选取路径
let totalMonsterCount = 0;
try {
const indexContent = await file.readText(indexPath);
const pathingFiles = indexContent.trim().split('\n').slice(1).map(line => {
const data = line.trim().split('\t');
if (data.length !== 11) { // 列数为11
log.warn(`不符合预期格式的行:${line}`);
return null;
}
const keywords = data[0].trim(); // 新的第一列是关键词信息
return {
keywords: keywords,
legendary: parseInt(data[1], 10) === 1,
waterFree: parseInt(data[2], 10) === 1,
timeshield: parseInt(data[3], 10) === 1,
highRisk: parseInt(data[4], 10) === 1,
monsterCount: parseFloat(data[5]),
secPerMonster: parseFloat(data[9]),
name: data[10].trim(),
group: 0 // 新增 group 参数,初始值为 0
};
}).filter(route => route !== null);
// 筛选和排序路径
const priority600Routes = pathingFiles
.filter(route => route.name.includes('600') && !excludeTagsForAllArray.some(tag => route.name.includes(tag) || route.keywords.includes(tag)))
.sort((a, b) => {
if (a.secPerMonster === b.secPerMonster) {
return a.monsterCount - b.monsterCount; // 次要排序依据:怪物数量升序
}
return b.secPerMonster - a.secPerMonster; // 主要排序依据:秒均降序
});
const priority400Routes = pathingFiles
.filter(route => route.name.includes('400') && !excludeTagsForAllArray.some(tag => route.name.includes(tag) || route.keywords.includes(tag)))
.sort((a, b) => {
if (a.secPerMonster === b.secPerMonster) {
return a.monsterCount - b.monsterCount; // 次要排序依据:怪物数量升序
}
return b.secPerMonster - a.secPerMonster; // 主要排序依据:秒均降序
});
const remainingRoutes = pathingFiles
.filter(route => !priority600Routes.includes(route) && !priority400Routes.includes(route) && !excludeTagsForAllArray.some(tag => route.name.includes(tag) || route.keywords.includes(tag)))
.sort((a, b) => {
if (a.secPerMonster === b.secPerMonster) {
return a.monsterCount - b.monsterCount; // 次要排序依据:怪物数量升序
}
return b.secPerMonster - a.secPerMonster; // 主要排序依据:秒均降序
});
// 合并所有路径
const sortedRoutes = [...priority600Routes, ...priority400Routes, ...remainingRoutes];
// 分配路径到路径组
const pathGroups = [[], [], []];
for (const path of sortedRoutes) {
if (totalMonsterCount >= requiredMonsterCount) {
break;
}
// 为每个路径组生成一个对应的字符串
const excludeTags1 = excludeTagsForPathGroup1.split('.');
const excludeTags1String = excludeTags1.map(tag => {
switch (tag) {
case "传奇":
return path.legendary ? '1' : '0';
case "水免":
return path.waterFree ? '1' : '0';
case "次数盾":
return path.timeshield ? '1' : '0';
case "高危":
return path.highRisk ? '1' : '0';
default:
return '0';
}
}).join('');
const selectTags2 = selectTagsForPathGroup2.split('.');
const selectTags2String = selectTags2.map(tag => {
switch (tag) {
case "传奇":
return path.legendary ? '1' : '0';
case "水免":
return path.waterFree ? '1' : '0';
case "次数盾":
return path.timeshield ? '1' : '0';
case "高危":
return path.highRisk ? '1' : '0';
default:
return '0';
}
}).join('');
const selectTags3 = selectTagsForPathGroup3.split('.');
const selectTags3String = selectTags3.map(tag => {
switch (tag) {
case "传奇":
return path.legendary ? '1' : '0';
case "水免":
return path.waterFree ? '1' : '0';
case "次数盾":
return path.timeshield ? '1' : '0';
case "高危":
return path.highRisk ? '1' : '0';
default:
return '0';
}
}).join('');
// 检查路径是否符合路径组1的条件
const excludeTags1Mask = excludeTags1.map(tag => tag === '1' ? '1' : '0').join('');
if (excludeTags1String === excludeTags1Mask) { // 反转逻辑如果路径包含任何排除标签则入选路径组1
path.group = 1;
pathGroups[0].push(path);
totalMonsterCount += path.monsterCount;
continue;
}
// 检查路径是否符合路径组2的条件
const selectTags2Mask = selectTags2.map(tag => tag === '1' ? '1' : '0').join('');
if (selectTags2String !== selectTags2Mask) { // 反转逻辑如果路径不包含任何选择的标签则入选路径组2
path.group = 2;
pathGroups[1].push(path);
totalMonsterCount += path.monsterCount;
continue;
}
// 检查路径是否符合路径组3的条件
const selectTags3Mask = selectTags3.map(tag => tag === '1' ? '1' : '0').join('');
if (selectTags3String !== selectTags3Mask) { // 反转逻辑如果路径不包含任何选择的标签则入选路径组3
path.group = 3;
pathGroups[2].push(path);
totalMonsterCount += path.monsterCount;
continue;
}
}
// 再次根据 operationType 决定生成路径组文件还是输出地图追踪文件
if (operationType === "强制刷新路径组文件" || executePathGroupMode > 0) {
// 生成路径组文件的逻辑
for (let i = 0; i < 3; i++) {
const pathGroup = pathGroups[i];
const pathGroupFilePath = `${routeDir}路径组${i + 1}-${outputFolderName}.txt`;
if (pathGroup.length > 0) {
// 按照 index.txt 中的顺序输出路径,并在每个路径名称后添加 "2000-01-01T00:00:00.000Z",以初始化cd信息
const sortedPathNames = pathingFiles
.filter(path => pathGroup.some(groupPath => groupPath.name === path.name))
.map(path => `${path.name}::2000-01-01T00:00:00.000Z`);
const pathGroupContent = sortedPathNames.join('\n');
await file.writeText(pathGroupFilePath, pathGroupContent);
log.info(`生成并刷新路径组${i + 1}文件成功,路径数:${sortedPathNames.length}`);
}
}
log.info(`怪物总数${totalMonsterCount}请修改js自定义配置中的操作模式以执行文件`);
} else if (operationType === "输出地图追踪文件") {
// 输出地图追踪文件的逻辑
for (let i = 0; i < 3; i++) {
const pathGroup = pathGroups[i];
const outputFolder = `pathingout/${outputFolderName}/路径组${i + 1}`;
let successCount = 0;
let failCount = 0;
for (const path of pathGroup) {
const sourceFilePath = `${pathingDir}${path.name}.json`;
const targetFilePath = `${outputFolder}/${path.name}.json`;
try {
const fileContent = await file.readText(sourceFilePath);
await file.writeText(targetFilePath, fileContent);
successCount++;
} catch (error) {
log.warn(`复制 ${sourceFilePath}${targetFilePath} 失败:${error}`);
failCount++;
}
}
log.info(`路径组${i + 1}复制完成:成功 ${successCount} 个,失败 ${failCount}`);
}
log.info(`怪物总数${totalMonsterCount}请前往pathingout文件夹提取文件`);
}
} catch (error) {
log.error(`读取索引文件失败:${error}`);
}
}
{
// 执行路径组文件的模式
if (executePathGroupMode > 0) {
// 检查是否禁用了自动拾取
if (!disableAutoPickup) {
// 启用自动拾取
dispatcher.addTimer(new RealtimeTimer("AutoPick"));
} else {
// 禁用自动拾取
log.warn("自动拾取已禁用");
}
//伪造js结束的记录
await fakeLog("自动精英锄地规划", true, false, 1);
try {
// 读取路径组文件内容
const pathGroupContent = await file.readText(pathGroupFilePath);
const pathLines = pathGroupContent.trim().split('\n'); // 定义 pathLines 变量
// 遍历路径名称列表,依次执行每个路径
for (let i = 0; i < pathLines.length; i++) {
const pathLine = pathLines[i];
// 解析路径名称和后面的 ISO 时间字符串
const [pathName, pathCD] = pathLine.split('::').map(part => part.trim());
const pathFilePath = `${pathingDir}${pathName}.json`;
// 获取当前时间
const now = new Date();
const startTime = now.toISOString(); // 获取开始时间
// 如果启用了路线CD检测检查当前时间是否不早于附加时间戳
if (enableRouteCdCheck) {
const pathCDDate = new Date(pathCD); // 将附加时间戳转换为Date对象
if (now < pathCDDate) {
log.info(`当前路线 ${pathName} 为第 ${i + 1}/${pathLines.length} 个,当前路线未刷新,放弃该路径`);
await sleep(10);
continue; // 放弃该路径
}
}
log.info(`当前路线 ${pathName} 为第 ${i + 1}/${pathLines.length}当前路线已刷新或未启用cd检测执行路径`);
//伪造地图追踪开始的日志记录
await fakeLog(pathName + ".json", false, true, 0); // 开始时 duration 通常为 0
// 输出进度让autoBGI匹配
log.info(`当前进度:${pathName} 为第 ${i + 1}/${pathLines.length}`);
// 执行路径文件
await pathingScript.runFile(pathFilePath);
//捕获任务取消的错误并跳出循环
try {
await sleep(10);
} catch (error) {
log.error(`发生错误: ${error}`);
break; // 终止循环
}
// 获取结束时间
const endTime = new Date();
const endTimeISO = endTime.toISOString(); // 获取结束时间
// 检查执行时间是否超过3秒
const executionTime = endTime.getTime() - now.getTime();
//伪造地图追踪结束的记录
await fakeLog(pathName + ".json", false, false, executionTime);
// 如果启用了CD检测且结束时间与开始时间相差超过3秒则更新附加时间戳为开始时间后的第一个凌晨四点
if (enableRouteCdCheck && executionTime > 3000) {
// 计算开始时间后的第一个凌晨四点
const nextFourClock = new Date(startTime);
nextFourClock.setHours(4, 0, 0, 0); // 设置时间为凌晨四点
if (nextFourClock <= new Date(startTime)) {
// 如果设置的时间小于等于开始时间,说明需要取下一个凌晨四点
nextFourClock.setHours(4 + 24, 0, 0, 0); // 设置时间为下一个凌晨四点
}
// 转换为北京时间UTC+8
const nextFourClockBeijing = new Date(nextFourClock.getTime());
const nextFourClockFormatted = `${nextFourClockBeijing.getFullYear()}-${String(nextFourClockBeijing.getMonth() + 1).padStart(2, '0')}-${String(nextFourClockBeijing.getDate()).padStart(2, '0')} ${String(nextFourClockBeijing.getHours()).padStart(2, '0')}:${String(nextFourClockBeijing.getMinutes()).padStart(2, '0')}:${String(nextFourClockBeijing.getSeconds()).padStart(2, '0')}`;
// 更新路径组文件中的附加时间戳
pathLines[i] = `${pathName}::${nextFourClock.toISOString()}`;
log.info(`当前路线 ${pathName} 执行完成,可用时间更新为:${nextFourClockFormatted}`);
// 将更新后的内容写回路径组文件
const updatedPathGroupContent = pathLines.join('\n');
await file.writeText(pathGroupFilePath, updatedPathGroupContent);
} else {
log.info(`当前路线 ${pathName} 执行完成但不更新可用时间因为执行时间不足10秒或未启用cd检测。`);
}
}
} catch (error) {
log.error(`读取路径组文件失败:${error}`);
return;
}
//伪造一个js开始的记录
await fakeLog("自动精英锄地规划", true, true, 0);
}
}
}
}
)();