JS,新增仅背包统计选项 (#1420)

* JS,新增仅背包统计选项

+ v2.41 修复勾选分类的本地记录bug,新增仅背包统计选项;

* JS,新增仅背包统计选项;修复bug

+ v2.41 修复勾选分类的本地记录bug,新增仅背包统计选项;如果本地记录已经遭到破坏。比如每条路径都产生大量材料名-0.txt,就只能手动清理或者删除本地记录pathing_record,重新跑。个人推荐需要路径功能的选择仅路径。

* Update settings.json

---------

Co-authored-by: 起个名字好难的喵 <25520958+MisakaAldrich@users.noreply.github.com>
This commit is contained in:
JJMdzh
2025-07-27 16:38:53 +08:00
committed by GitHub
parent 7a219dffa9
commit 9323fda85b
4 changed files with 141 additions and 65 deletions

View File

@@ -80,4 +80,5 @@
+ v2.28 材料更变时初始数量更新正常记录排除0位移和0数量的路径记录(可能是卡路径需手动根据0记录去甄别)新增材料名0后缀本地记录新增背包弹窗识别
+ v2.29 新增排除提示;调整平均时间成本计算;过滤掉差异较大的记录;
+ v2.30 更改路径专注模式默认值加log提示去除注释掉的调试log背包材料统计更改名为背包统计采集系统
+ v2.40 优化背包识别时占用的内存;增加通知;
+ v2.40 优化背包识别时占用的内存;增加通知;
+ v2.41 修复勾选分类的本地记录bug新增仅背包统计选项如果本地记录已经遭到破坏。比如每条路径都产生大量材料名-0.txt就只能手动清理或者删除本地记录pathing_record重新跑

View File

@@ -19,10 +19,36 @@ const material_mapping = {
"Talent": "角色天赋素材",
"WeaponAscension": "武器突破素材"
}
const isOnlyPathing = settings.onlyPathing === "是" ? true : false;
// 安全获取 Pathing 的前缀数字(处理 undefined 或非字符串的情况)
const pathingValue = settings.Pathing || ''; // 若未定义,用空字符串兜底
const pathingPrefix = String(pathingValue).split('.')[0]; // 确保转为字符串后再分割
if (isOnlyPathing) {
log.warn("已开启路径专注模式,将忽略勾选的分类");
// 根据三个选项值设置不同的逻辑标识
const pathingMode = {
// 二者兼并📁pathing材料覆盖【材料分类】
includeBoth: pathingPrefix === "1",
// 无视【材料分类】勾选只扫描pathing下的材料不考虑【材料分类】勾选
onlyPathing: pathingPrefix === "2",
// 无视pathing材料不扫描pathing下的材料只考虑【材料分类】勾选
onlyCategory: pathingPrefix === "3"
};
// 增加默认模式兜底(当 prefix 不是 1/2/3 时)
const isInvalidMode = !pathingMode.includeBoth && !pathingMode.onlyPathing && !pathingMode.onlyCategory;
if (isInvalidMode) {
log.warn(`检测到无效的 Pathing 设置(${pathingValue}),自动切换为默认模式`);
pathingMode.includeBoth = true; // 强制启用默认模式
}
// 输出当前模式日志
if (pathingMode.includeBoth) {
log.warn("默认模式📁pathing材料 将覆盖 勾选的分类");
}
if (pathingMode.onlyCategory) {
log.warn("已开启【背包统计】专注模式将忽略📁pathing材料");
}
if (pathingMode.onlyPathing) {
log.warn("已开启【路径材料】专注模式,将忽略勾选的分类");
}
// 初始化 settings将 material_mapping 中的所有键设置为 false
const initialSettings = Object.keys(material_mapping).reduce((acc, key) => {
@@ -238,7 +264,7 @@ async function scanMaterials(materialsCategory, materialCategoryMap) {
// 如果 materialCategoryMap 中当前分类的数组不为空
// 且当前材料名称不在指定的材料列表中,则跳过加载
if (isOnlyPathing && !shouldScanAllMaterials && !categoryMaterials.includes(name)) {
if (pathingMode.onlyPathing && !shouldScanAllMaterials && !categoryMaterials.includes(name)) {
continue;
}
@@ -430,39 +456,46 @@ ${materialInfo.map(item => `${item.name}: ${item.count}`).join(",")}
${Array.from(unmatchedMaterialNames).join(",")}
`;
// 写入历史记录文件
const categoryFilePath = `history_record/${materialsCategory}.txt`;
const overwriteFilePath = `overwrite_record/${materialsCategory}.txt`;
const latestFilePath = "latest_record.txt";
await writeLog(categoryFilePath, logContent);
await writeLog(overwriteFilePath, logContent);
await writeLog(latestFilePath, logContent);
const categoryFilePath = `history_record/${materialsCategory}.txt`; // 勾选【材料分类】的历史记录
const overwriteFilePath = `overwrite_record/${materialsCategory}.txt`; // 所有的历史记录分类储存
const latestFilePath = "latest_record.txt"; // 所有的历史记录集集合
if (pathingMode.onlyCategory) {
writeLog(categoryFilePath, logContent);
}
writeLog(overwriteFilePath, logContent);
writeLog(latestFilePath, logContent); // 覆盖模式?
// 返回结果
return materialInfo;
}
async function writeLog(filePath, logContent) {
function writeLog(filePath, logContent) {
try {
const existingContent = file.readTextSync(filePath);
const records = existingContent.split("\n\n");
const latestRecords = records.slice(-365).join("\n\n");
const finalContent = `${logContent}\n\n${latestRecords}`;
const result = file.WriteTextSync(filePath, finalContent, false);
// 1. 读取现有内容(原样读取,不做任何分割处理)
let existingContent = "";
try {
existingContent = file.readTextSync(filePath);
} catch (e) {
// 文件不存在则保持空
}
// 2. 拼接新记录(新记录加在最前面,用两个换行分隔,保留原始格式)
const finalContent = logContent + "\n\n" + existingContent;
// 3. 按行分割保留最近365条完整记录按原始换行分割不过滤
const lines = finalContent.split("\n");
const keepLines = lines.length > 365 * 5 ? lines.slice(0, 365 * 5) : lines; // 假设每条记录最多5行
const result = file.writeTextSync(filePath, keepLines.join("\n"), false);
if (result) {
log.info(`成功将日志写入文件 ${filePath}`);
log.info(`写入成功: ${filePath}`);
} else {
log.error(`写入文件 ${filePath} 失败`);
log.error(`写入失败: ${filePath}`);
}
} catch (error) {
log.warn(`文件 ${filePath} 不存在,将创建新文件`);
const result = file.WriteTextSync(filePath, logContent, false);
if (result) {
log.info(`成功创建并写入文件 ${filePath}`);
} else {
log.error(`创建文件 ${filePath} 失败`);
}
// 只在文件完全不存在时创建,避免覆盖
file.writeTextSync(filePath, logContent, false);
log.info(`创建新文件: ${filePath}`);
}
}
@@ -548,6 +581,20 @@ function dynamicMaterialGrouping(materialCategoryMap) {
// 主逻辑函数
async function MaterialPath(materialCategoryMap) {
// 1. 先记录原始名称与别名的映射关系(用于最后反向转换)
const nameMap = new Map();
Object.values(materialCategoryMap).flat().forEach(originalName => {
const aliasName = MATERIAL_ALIAS[originalName] || originalName;
nameMap.set(aliasName, originalName); // 存储:别名→原始名
});
// 2. 转换materialCategoryMap为别名用于内部处理
const processedMap = {};
Object.entries(materialCategoryMap).forEach(([category, names]) => {
processedMap[category] = names.map(name => MATERIAL_ALIAS[name] || name);
});
materialCategoryMap = processedMap;
const maxStage = 4; // 最大阶段数
let stage = 0; // 当前阶段
let currentGroupIndex = 0; // 当前处理的分组索引
@@ -672,8 +719,19 @@ async function MaterialPath(materialCategoryMap) {
await genshin.returnMainUi(); // 返回主界面
log.info("扫描流程结束");
// 返回所有识别到的材料信息
return allLowCountMaterials;
// 3. 处理完成后,将输出结果转换回原始名称
const finalResult = allLowCountMaterials.map(categoryMaterials => {
return categoryMaterials.map(material => {
// 假设material包含name属性将别名转回原始名
return {
...material,
name: nameMap.get(material.name) || material.name // 反向映射
};
});
});
return finalResult; // 返回转换后的结果(如"晶蝶"
}
// 自定义 basename 函数
@@ -888,7 +946,7 @@ function recordRunTime(resourceName, pathName, startTime, endTime, runTime, reco
if (runTime >= 3) {
// 检查 materialCountDifferences 中是否存在材料数目为 0 的情况
for (const [material, count] of Object.entries(materialCountDifferences)) {
if (count === 0) {
if (material === resourceName && count === 0) {
// 如果材料数目为 0记录到单独的文件
const zeroMaterialPath = `${recordDir}/${material}-0.txt`; // 材料数目为0的记录文件路径
const zeroMaterialContent = `路径名: ${pathName}\n开始时间: ${startTime}\n结束时间: ${endTime}\n运行时间: ${runTime}\n数量变化: ${JSON.stringify(materialCountDifferences)}\n\n`;
@@ -1165,22 +1223,26 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
.filter(name => name) || []; // 确保 resourceNames 是一个数组
// 生成材料与分类的映射对象
const materialCategoryMap = resourceNames.reduce((acc, resourceName) => {
const category = matchImageAndGetCategory(resourceName, imagesDir); // 获取材料的分类
if (category) {
// 初始化分类键(如果不存在)
if (!acc[category]) acc[category] = [];
// 将材料名加入对应分类数组(避免重复)
if (!acc[category].includes(resourceName)) {
acc[category].push(resourceName);
let materialCategoryMap = {};
// 选项2: +选项1: 二者兼并 - 把路径材料名resourceNames纳入materialCategoryMap
if (!pathingMode.onlyCategory) {
materialCategoryMap = resourceNames.reduce((acc, resourceName) => {
const category = matchImageAndGetCategory(resourceName, imagesDir); // 获取材料的分类
if (category) {
// 初始化分类键(如果不存在)
if (!acc[category]) acc[category] = [];
// 将材料名加入对应分类数组(避免重复)
if (!acc[category].includes(resourceName)) {
acc[category].push(resourceName);
}
}
}
return acc;
}, {});
return acc;
}, {});
}
// 确保 selected_materials_array 中的分类被初始化为空数组
if (Object.keys(selected_materials_array).length === 0) {
log.warn("==================\n 未选择【材料分类】!\n ==================");
log.warn("==================\n 未选择【材料分类】!将采用【兼容模式】\n ==================");
} else {
selected_materials_array.forEach(selectedCategory => {
if (!materialCategoryMap[selectedCategory]) {
@@ -1189,8 +1251,8 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
});
}
// 如果 isOnlyPathing 为 true移除 materialCategoryMap 中的空数组
if (isOnlyPathing) {
// 选项2: 仅路径材料 - 移除空数组
if (pathingMode.onlyPathing) {
Object.keys(materialCategoryMap).forEach(category => {
if (materialCategoryMap[category].length === 0) {
delete materialCategoryMap[category];
@@ -1200,7 +1262,11 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
// 调用背包材料统计
const pathingMaterialCounts = await MaterialPath(materialCategoryMap);
log.info(`materialCategoryMap文本${JSON.stringify(materialCategoryMap)}`);
log.info(`目标文本:${JSON.stringify(pathingMaterialCounts)}`);
if (pathingMode.onlyCategory) {
return;
}
// 调用 filterLowCountMaterials 过滤材料信息,先将嵌套数组展平,然后再进行筛选
const lowCountMaterialsFiltered = filterLowCountMaterials(pathingMaterialCounts.flat(), materialCategoryMap);
@@ -1212,6 +1278,11 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
// 提取低数量材料的名称
const lowCountMaterialNames = flattenedLowCountMaterials.map(material => material.name);
// 当低数量材料名称数组为空时,输出所有路径材料都高于目标数量的日志
if (lowCountMaterialNames.length === 0) {
log.info(`所有路径材料的数量均高于目标数量${targetCount}`);
}
log.info(`目标文本:${JSON.stringify(lowCountMaterialNames)}`);
// 将路径文件按是否为目标材料分类
const prioritizedPaths = [];
const normalPaths = [];
@@ -1352,20 +1423,24 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
const originalCount = parseInt(originalMaterial.count, 10);
const updatedCount = parseInt(updatedMaterial.count, 10);
const difference = updatedCount - originalCount;
materialCountDifferences[updatedMaterial.name] = difference;
// 更新全局累积差值
if (globalAccumulatedDifferences[updatedMaterial.name]) {
globalAccumulatedDifferences[updatedMaterial.name] += difference;
} else {
globalAccumulatedDifferences[updatedMaterial.name] = difference;
}
// 只记录非零差值或者是当前处理的resourceName即使差值为0
if (difference !== 0 || updatedMaterial.name === resourceName) {
materialCountDifferences[updatedMaterial.name] = difference;
// 更新当前材料的累积差值
if (materialAccumulatedDifferences[resourceName][updatedMaterial.name]) {
materialAccumulatedDifferences[resourceName][updatedMaterial.name] += difference;
} else {
materialAccumulatedDifferences[resourceName][updatedMaterial.name] = difference;
// 更新全局累积差值
if (globalAccumulatedDifferences[updatedMaterial.name]) {
globalAccumulatedDifferences[updatedMaterial.name] += difference;
} else {
globalAccumulatedDifferences[updatedMaterial.name] = difference;
}
// 更新当前材料的累积差值
if (materialAccumulatedDifferences[resourceName][updatedMaterial.name]) {
materialAccumulatedDifferences[resourceName][updatedMaterial.name] += difference;
} else {
materialAccumulatedDifferences[resourceName][updatedMaterial.name] = difference;
}
}
}
});

View File

@@ -1,13 +1,12 @@
{
"manifest_version": 1,
"name": "背包统计采集系统",
"version": "2.40",
"version": "2.41",
"bgi_version": "0.44.8",
"description": "默认四行为一页模板匹配材料OCR识别数量。\n数字太小可能无法识别用?代替。\n目前支持采集材料的数量统计+路径CD管理。",
"authors": [
{
"name": "吉吉喵",
"links": "https://github.com/JJMdzh"
"name": "吉吉喵"
}
],
"settings_ui": "settings.json",

View File

@@ -20,12 +20,13 @@
"label": "----------------------------------\n是否发送通知。默认否\n需在BGI开启JS通知并设置通知地址"
},
{
"name": "onlyPathing",
"name": "Pathing",
"type": "select",
"label": "====================\n扫描📁pathing下的材料\n无视【材料分类】勾选。默认:",
"label": "====================\n扫描📁pathing下的\n或勾选【材料分类】的材料。默认:兼并",
"options": [
"",
"否"
"1.兼并:📁pathing材料+【材料分类】",
"2.仅📁pathing材料",
"3.仅【材料分类】勾选"
]
},
{