js,材料路径选择优化 (#1013)

* js,材料路径选择优化

+ v2.29 新增排除提示;调整平均时间成本计算;过滤掉差异较大的记录;

* Add files via upload
This commit is contained in:
JJMdzh
2025-06-07 00:13:06 +08:00
committed by GitHub
parent e4dc882833
commit 8b52b0342b
4 changed files with 186 additions and 127 deletions

View File

@@ -77,4 +77,5 @@
+ v2.25 当前、后位材料识别(加速扫描),新增只扫描路径材料名选项(内存占用更小)
+ v2.26 修复读取材料时间错误等bug新增路径材料时间成本计算
+ v2.27 修复计算材料数错误、目标数量临界值、"3"识别成"三"等bug
+ v2.28 材料更变时初始数量更新正常记录排除0位移0数量的路径记录(可能是卡路径需手动根据0记录去甄别)新增材料名0后缀本地记录新增背包弹窗识别
+ v2.28 材料更变时初始数量更新正常记录排除0位移0数量的路径记录(可能是卡路径需手动根据0记录去甄别)新增材料名0后缀本地记录新增背包弹窗识别
+ v2.29 新增排除提示;调整平均时间成本计算;过滤掉差异较大的记录;

View File

@@ -884,7 +884,39 @@ function writeContentToFile(filePath, content) {
}
}
function recordRunTime(resourceName, pathName, startTime, endTime, runTime, recordDir, materialCountDifferences = {}) {
function checkPathNameFrequency(recordDir, resourceName, pathName) {
const recordPath = `${recordDir}/${resourceName}-0.txt`; // 记录文件路径,以 resourceName-0.txt 命名
try {
const content = file.readTextSync(recordPath); // 同步读取记录文件
const lines = content.split('\n');
let totalCount = 0; // 用于记录路径名出现的总次数
// 从文件内容的开头开始查找
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('路径名: ')) {
const currentPathName = lines[i].split('路径名: ')[1];
if (currentPathName === pathName) {
totalCount++; // 如果当前路径名匹配计数加1
}
}
}
// 如果路径名出现次数超过3次返回 false
if (totalCount > 3) {
return false;
log.info(`路径文件: ${pathName}, 多次0采集请检查后删除记录再执行`);
}
// 如果路径名出现次数不超过3次返回 true
return true;
} catch (error) {
log.warn(`读取文件时发生错误: ${recordPath}`, error);
return true; // 如果文件不存在或读取失败认为路径名出现次数不超过3次
}
}
function recordRunTime(resourceName, pathName, startTime, endTime, runTime, recordDir, materialCountDifferences = {}, finalCumulativeDistance) {
const recordPath = `${recordDir}/${resourceName}.txt`; // 正常记录文件路径
const normalContent = `路径名: ${pathName}\n开始时间: ${startTime}\n结束时间: ${endTime}\n运行时间: ${runTime}\n数量变化: ${JSON.stringify(materialCountDifferences)}\n\n`;
@@ -898,22 +930,36 @@ function recordRunTime(resourceName, pathName, startTime, endTime, runTime, reco
const zeroMaterialPath = `${recordDir}/${material}-0.txt`; // 材料数目为0的记录文件路径
const zeroMaterialContent = `路径名: ${pathName}\n开始时间: ${startTime}\n结束时间: ${endTime}\n运行时间: ${runTime}\n数量变化: ${JSON.stringify(materialCountDifferences)}\n\n`;
writeContentToFile(zeroMaterialPath, zeroMaterialContent); // 写入材料数目为0的记录
log.warn(`材料数目为0已写入单独文件: ${zeroMaterialPath}`);
}
}
// 检查是否需要记录正常内容
if (!(Object.values(materialCountDifferences).includes(0) && finalCumulativeDistance === 0)) {
const hasZeroMaterial = Object.values(materialCountDifferences).includes(0);
const isFinalCumulativeDistanceZero = finalCumulativeDistance === 0;
if (!(hasZeroMaterial && isFinalCumulativeDistanceZero)) {
// 写入正常记录的内容
writeContentToFile(recordPath, normalContent);
log.info(`正常记录已写入: ${recordPath}`);
} else {
if (hasZeroMaterial) {
log.warn(`存在材料数目为0的情况: ${JSON.stringify(materialCountDifferences)}`);
}
if (isFinalCumulativeDistanceZero) {
log.warn(`累计距离为0: finalCumulativeDistance=${finalCumulativeDistance}`);
}
log.warn(`未写入正常记录: ${recordPath}`);
}
} else {
log.info(`运行时间小于3秒请检查路径要求: ${recordPath}`);
log.warn(`运行时间小于3秒未满足记录条件: ${recordPath}`);
}
} catch (error) {
log.error(`记录运行时间失败: ${error}`);
}
}
// 读取材料对应的文件,获取上次运行的结束时间
function getLastRunEndTime(resourceName, pathName, recordDir) {
const recordPath = `${recordDir}/${resourceName}.txt`; // 记录文件路径,以材料名命名
@@ -961,8 +1007,13 @@ function calculatePerTime(resourceName, pathName, recordDir) {
const quantityChange = JSON.parse(quantityChangeLine.split('数量变化: ')[1]);
// 检查数量变化是否有效
if (quantityChange[resourceName] !== undefined && quantityChange[resourceName] !== 0) {
const perTime = runTime / quantityChange[resourceName];
if (quantityChange[resourceName] !== undefined) {
let perTime;
if (quantityChange[resourceName] !== 0) {
// 保留两位小数
perTime = parseFloat((runTime / quantityChange[resourceName]).toFixed(2));
perTime = Infinity; // 数量变化为 0 时,设置为 Infinity
}
completeRecords.push(perTime);
}
}
@@ -972,31 +1023,34 @@ function calculatePerTime(resourceName, pathName, recordDir) {
// 如果完整记录少于3条返回 null
if (completeRecords.length < 3) {
log.warn(`完整记录不足3条无法计算有效的时间成本: ${recordPath}`);
log.warn(` ${pathName}完整记录不足3条无法计算有效的时间成本: ${recordPath}`);
return null;
}
// 只考虑最近的5条记录
const recentRecords = completeRecords.slice(-5);
// 只考虑最近的5条记录 过滤掉 Infinity 和 NaN 值
const recentRecords = completeRecords.slice(-5).filter(record => !isNaN(record) && record !== Infinity);
// 打印最近的记录
log.info(` ${pathName}最近的记录: ${JSON.stringify(recentRecords)}`);
// 计算平均值和标准差
const mean = recentRecords.reduce((acc, val) => acc + val, 0) / recentRecords.length;
const stdDev = Math.sqrt(recentRecords.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / recentRecords.length);
// 排除差异过大的数据
const filteredRecords = recentRecords.filter(record => Math.abs(record - mean) <= 2 * stdDev);
const filteredRecords = recentRecords.filter(record => Math.abs(record - mean) <= 1 * stdDev);// 使用1倍标准差作为过滤条件
// 如果过滤后没有剩余数据,返回 null
if (filteredRecords.length === 0) {
log.warn(`所有记录数据差异过大,无法计算有效的时间成本: ${recordPath}`);
log.warn(` ${pathName}记录数据差异过大,无法计算有效的时间成本: ${recordPath}`);
return null;
}
// 计算平均时间成本
const averagePerTime = filteredRecords.reduce((acc, val) => acc + val, 0) / filteredRecords.length;
const averagePerTime = parseFloat((filteredRecords.reduce((acc, val) => acc + val, 0) / filteredRecords.length).toFixed(2));
return averagePerTime;
} catch (error) {
log.warn(`缺失耗时或者数量变化,无法计算时间成本: ${recordPath}`);
log.warn(`缺失耗时或者数量变化,无法计算 ${pathName}时间成本: ${recordPath}`);
}
return null; // 如果未找到记录文件或效率数据,返回 null
}
@@ -1234,135 +1288,139 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
dispatcher.addTimer(new RealtimeTimer("AutoPick", { "forceInteraction": false }));
// 假设 flattenedLowCountMaterials 是一个全局变量或在外部定义的变量
let currentMaterialName = null; // 用于记录当前材料名
// 假设 flattenedLowCountMaterials 是一个全局变量或在外部定义的变量
let currentMaterialName = null; // 用于记录当前材料名
// 遍历所有路径文件
for (const { path: pathingFilePath, resourceName } of allPaths) {
const pathName = basename(pathingFilePath); // 假设路径文件名即为材料路径
// log.info(`处理路径文件:${pathingFilePath},材料名:${resourceName},材料路径:${pathName}`);
// 遍历所有路径文件
for (const { path: pathingFilePath, resourceName } of allPaths) {
const pathName = basename(pathingFilePath); // 假设路径文件名即为材料路径
// log.info(`处理路径文件:${pathingFilePath},材料名:${resourceName},材料路径:${pathName}`);
// 查找材料对应的CD分类
let categoryFound = false;
for (const [category, materials] of Object.entries(materialCategories)) {
for (const [refreshCDKey, materialList] of Object.entries(materials)) {
const refreshCD = JSON.parse(refreshCDKey);
if (materialList.includes(resourceName)) {
// 获取当前时间
const currentTime = getCurrentTimeInHours();
// 查找材料对应的CD分类
let categoryFound = false;
for (const [category, materials] of Object.entries(materialCategories)) {
for (const [refreshCDKey, materialList] of Object.entries(materials)) {
const refreshCD = JSON.parse(refreshCDKey);
if (materialList.includes(resourceName)) {
// 获取当前时间
const currentTime = getCurrentTimeInHours();
// 读取上次运行的结束时间
const lastEndTime = getLastRunEndTime(resourceName, pathName, recordDir);
// 读取上次运行的结束时间
const lastEndTime = getLastRunEndTime(resourceName, pathName, recordDir);
// 计算效率
const perTime = calculatePerTime(resourceName, pathName, recordDir);
// 计算效率
const perTime = calculatePerTime(resourceName, pathName, recordDir);
log.info(`路径文件:${pathName} 单个材料耗时:${perTime}`);
// 判断是否可以运行脚本
if (canRunPathingFile(currentTime, lastEndTime, refreshCD, pathName) && (perTime === null || perTime <= timeCost)) {
log.info(`可调用路径文件:${pathName}`);
log.info(`路径文件:${pathName} 单个材料耗时:${perTime}`);
// 判断是否可以运行脚本
if (
canRunPathingFile(currentTime, lastEndTime, refreshCD, pathName) &&
checkPathNameFrequency(recordDir, resourceName, pathName) &&
(perTime === null || perTime <= timeCost)
) {
log.info(`可调用路径文件:${pathName}`);
// 根据 materialCategoryMap 构建 resourceCategoryMap
const resourceCategoryMap = {};
for (const [materialCategory, materialList] of Object.entries(materialCategoryMap)) {
if (materialList.includes(resourceName)) {
resourceCategoryMap[materialCategory] = [resourceName];
break;
}
}
// 输出 resourceCategoryMap 以供调试
log.info(`resourceCategoryMap: ${JSON.stringify(resourceCategoryMap, null, 2)}`);
// 如果材料名发生变化,更新 flattenedLowCountMaterials
if (currentMaterialName !== resourceName) {
currentMaterialName = resourceName; // 更新当前材料名
// 调用背包材料统计(获取当前材料数量)
const updatedLowCountMaterials = await MaterialPath(resourceCategoryMap);
// 展平数组并按数量从小到大排序
flattenedLowCountMaterials = updatedLowCountMaterials
.flat()
.sort((a, b) => parseInt(a.count, 10) - parseInt(b.count, 10));
log.info(`材料名变更,更新了 flattenedLowCountMaterials`);
}
// 记录开始时间
const startTime = new Date().toLocaleString();
// 在路径执行前执行一次位移监测
const initialPosition = genshin.getPositionFromMap();
let initialCumulativeDistance = 0;
// 调用路径文件
await pathingScript.runFile(pathingFilePath);
// 在路径执行后执行一次位移监测
const finalPosition = genshin.getPositionFromMap();
const finalCumulativeDistance = calculateDistance(initialPosition, finalPosition);
// 记录结束时间
const endTime = new Date().toLocaleString();
// 计算运行时间
const runTime = (new Date(endTime) - new Date(startTime)) / 1000; // 秒
// 调用背包材料统计(获取调用路径文件后的材料数量)
const updatedLowCountMaterials = await MaterialPath(resourceCategoryMap);
// 展平数组并按数量从小到大排序
const flattenedUpdatedMaterialCounts = updatedLowCountMaterials
.flat()
.sort((a, b) => parseInt(a.count, 10) - parseInt(b.count, 10));
// 提取更新后的低数量材料的名称
const updatedLowCountMaterialNames = flattenedUpdatedMaterialCounts.map(material => material.name);
// 创建一个映射,用于存储更新前后的数量差值
const materialCountDifferences = {};
// 遍历更新后的材料数量,计算差值
flattenedUpdatedMaterialCounts.forEach(updatedMaterial => {
const originalMaterial = flattenedLowCountMaterials.find(material => material.name === updatedMaterial.name);
if (originalMaterial) {
const originalCount = parseInt(originalMaterial.count, 10);
const updatedCount = parseInt(updatedMaterial.count, 10);
const difference = updatedCount - originalCount;
materialCountDifferences[updatedMaterial.name] = difference;
// 根据 materialCategoryMap 构建 resourceCategoryMap
const resourceCategoryMap = {};
for (const [materialCategory, materialList] of Object.entries(materialCategoryMap)) {
if (materialList.includes(resourceName)) {
resourceCategoryMap[materialCategory] = [resourceName];
break;
}
}
});
// 更新 flattenedLowCountMaterials 为最新的材料数量
flattenedLowCountMaterials = flattenedLowCountMaterials.map(material => {
// 找到对应的更新后的材料数量
const updatedMaterial = flattenedUpdatedMaterialCounts.find(updated => updated.name === material.name);
if (updatedMaterial) {
return { ...material, count: updatedMaterial.count }; // 更新数量
// 输出 resourceCategoryMap 以供调试
log.info(`resourceCategoryMap: ${JSON.stringify(resourceCategoryMap, null, 2)}`);
// 如果材料名发生变化,更新 flattenedLowCountMaterials
if (currentMaterialName !== resourceName) {
currentMaterialName = resourceName; // 更新当前材料名
// 调用背包材料统计(获取当前材料数量)
const updatedLowCountMaterials = await MaterialPath(resourceCategoryMap);
// 展平数组并按数量从小到大排序
flattenedLowCountMaterials = updatedLowCountMaterials
.flat()
.sort((a, b) => parseInt(a.count, 10) - parseInt(b.count, 10));
log.info(`材料名变更,更新了 flattenedLowCountMaterials`);
}
return material;
});
// 打印数量差值
log.info(`数量变化: ${JSON.stringify(materialCountDifferences, null, 2)}`);
// 记录开始时间
const startTime = new Date().toLocaleString();
// 记录运行时间到材料对应的文件中
recordRunTime(resourceName, pathName, startTime, endTime, runTime, recordDir, materialCountDifferences, finalCumulativeDistance);
log.info(`当前材料名: ${JSON.stringify(resourceName, null, 2)}`);
// 在路径执行前执行一次位移监测
const initialPosition = genshin.getPositionFromMap();
let initialCumulativeDistance = 0;
categoryFound = true;
// 调用路径文件
await pathingScript.runFile(pathingFilePath);
break;
} else {
if (perTime !== null && perTime > timeCost) {
log.info(`路径文件 ${pathName} 的单个材料耗时大于 ${timeCost} ,不执行`);
} else {
log.info(`路径文件 ${pathName} 未能执行!`);
// 在路径执行后执行一次位移监测
const finalPosition = genshin.getPositionFromMap();
const finalCumulativeDistance = calculateDistance(initialPosition, finalPosition);
// 记录结束时间
const endTime = new Date().toLocaleString();
// 计算运行时间
const runTime = (new Date(endTime) - new Date(startTime)) / 1000; // 秒
// 调用背包材料统计(获取调用路径文件后的材料数量)
const updatedLowCountMaterials = await MaterialPath(resourceCategoryMap);
// 展平数组并按数量从小到大排序
const flattenedUpdatedMaterialCounts = updatedLowCountMaterials
.flat()
.sort((a, b) => parseInt(a.count, 10) - parseInt(b.count, 10));
// 提取更新后的低数量材料的名称
const updatedLowCountMaterialNames = flattenedUpdatedMaterialCounts.map(material => material.name);
// 创建一个映射,用于存储更新前后的数量差值
const materialCountDifferences = {};
// 遍历更新后的材料数量,计算差值
flattenedUpdatedMaterialCounts.forEach(updatedMaterial => {
const originalMaterial = flattenedLowCountMaterials.find(material => material.name === updatedMaterial.name);
if (originalMaterial) {
const originalCount = parseInt(originalMaterial.count, 10);
const updatedCount = parseInt(updatedMaterial.count, 10);
const difference = updatedCount - originalCount;
materialCountDifferences[updatedMaterial.name] = difference;
}
});
// 更新 flattenedLowCountMaterials 为最新的材料数量
flattenedLowCountMaterials = flattenedLowCountMaterials.map(material => {
// 找到对应的更新后的材料数量
const updatedMaterial = flattenedUpdatedMaterialCounts.find(updated => updated.name === material.name);
if (updatedMaterial) {
return { ...material, count: updatedMaterial.count }; // 更新数量
}
return material;
});
// 打印数量差值
log.info(`数量变化: ${JSON.stringify(materialCountDifferences, null, 2)}`);
// 记录运行时间到材料对应的文件中
recordRunTime(resourceName, pathName, startTime, endTime, runTime, recordDir, materialCountDifferences, finalCumulativeDistance);
log.info(`当前材料名: ${JSON.stringify(resourceName, null, 2)}`);
categoryFound = true;
break;
} else {
if (perTime !== null && perTime > timeCost) {
log.info(`路径文件 ${pathName} 的单个材料耗时大于 ${timeCost} ,不执行`);
} else {
log.info(`路径文件 ${pathName} 未能执行!`);
}
}
}
}
if (categoryFound) break;
}
}
if (categoryFound) break;
}
}
} catch (error) {
log.error(`操作失败: ${error}`);
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "背包材料统计",
"version": "2.28",
"version": "2.29",
"bgi_version": "0.44.8",
"description": "默认四行为一页模板匹配材料OCR识别数量。\n数字太小可能无法识别用?代替。\n目前支持采集材料的数量统计+路径CD管理。",
"authors": [

View File

@@ -41,7 +41,7 @@
{
"name": "ForagedFood",
"type": "checkbox",
"label": "----------------------------------\n【采集食物】食用回血"
"label": "如:经验书、怪物掉落\n----------------------------------\n【采集食物】食用回血"
},
{
"name": "General",