@@ -1,6 +1,7 @@
const targetCount = Math . min ( 9999 , Math . max ( 0 , Math . floor ( Number ( settings . targetCount ) || 5000 ) ) ) ; // 设定的目标数量
const OCRdelay = Math . min ( 50 , Math . max ( 0 , Math . floor ( Number ( settings . OcrDelay ) || 10 ) ) ) ; // OCR基准时长
const Imagedelay = Math . min ( 1000 , Math . max ( 0 , Math . floor ( Number ( settings . ImageDelay ) || 0 ) ) ) ; // 识图基准时长
// 定义映射表"unselected": "反选材料分类",
const material _mapping = {
"General" : "一般素材" ,
@@ -16,6 +17,8 @@ const material_mapping = {
"Talent" : "角色天赋素材" ,
"WeaponAscension" : "武器突破素材"
}
const isOnlyPathing = settings . onlyPathing === "否" ? false : true ;
// 检查是否启用反选功能
const isUnselected = settings . unselected === true ;
@@ -173,271 +176,290 @@ const selected_materials_array = Object.keys(settings)
await sleep ( 100 ) ;
}
function filterMaterialsByPriority ( materialsCategory ) {
// 获取当前材料分类的优先级
const currentPriority = materialPriority [ materialsCategory ] ;
if ( currentPriority === undefined ) {
throw new Error ( ` Invalid materialsCategory: ${ materialsCategory } ` ) ;
}
// 获取当前材料分类的 materialTypeMap 对应值
const currentType = materialTypeMap [ materialsCategory ] ;
if ( currentType === undefined ) {
throw new Error ( ` Invalid materialTypeMap for: ${ materialsCategory } ` ) ;
}
// 获取所有优先级更高的材料分类(前位材料)
const frontPriorityMaterials = Object . keys ( materialPriority )
. filter ( mat => materialPriority [ mat ] < currentPriority && materialTypeMap [ mat ] === currentType ) ;
// 获取所有优先级更低的材料分类(后位材料)
const backPriorityMaterials = Object . keys ( materialPriority )
. filter ( mat => materialPriority [ mat ] > currentPriority && materialTypeMap [ mat ] === currentType ) ;
// 合并当前和后位材料分类
const finalFilteredMaterials = [ ... backPriorityMaterials , materialsCategory ] ; // 当前材料
return finalFilteredMaterials
}
// 扫描材料
async function scanMaterials ( materialsCategory , materialCategoryMap ) {
async function scanMaterials ( materialsCategory , materialCategoryMap , targetResourceName = false , targetMaterialNames = [ ] ) {
// 获取当前+后位材料名单
const priorityMaterialNames = [ ] ;
const finalFilteredMaterials = await filterMaterialsByPriority ( materialsCategory ) ;
for ( const category of finalFilteredMaterials ) {
const materialIconDir = ` assets/images/ ${ category } ` ;
const materialIconFilePaths = file . ReadPathSync ( materialIconDir ) ;
for ( const filePath of materialIconFilePaths ) {
const name = basename ( filePath ) . replace ( ".png" , "" ) ; // 去掉文件扩展名
priorityMaterialNames . push ( { category , name } ) ;
}
}
// 根据材料分类获取对应的材料图片文件夹路径
const materialIconDir = ` assets/images/ ${ materialsCategory } ` ;
// 使用 ReadPathSync 读取所有材料图片路径
const materialIconFilePaths = file . ReadPathSync ( materialIconDir ) ;
// 创建材料种类集合
const materialCategories = [ ] ;
const allMaterials = new Set ( ) ; // 用于记录所有需要扫描的材料名称
for ( const filePath of materialIconFilePaths ) {
const mat = file . readImageMatSync ( filePath ) ;
if ( mat . empty ( ) ) {
log . error ( ` 加载图标失败: ${ filePath } ` ) ;
continue ; // 跳过当前文件
}
const name = basename ( f ilePath ) . replace ( ".png" , "" ) ; // 去掉文件扩展名
materialCategories . push ( { name : name , filePath : filePath } ) ;
allMaterials . add ( name ) ; // 将材料名称添加到集合中
// 创建材料种类集合
const materialCategories = [ ] ;
const allMaterials = new Set ( ) ; // 用于记录所有需要扫描的材料名称
const materialImages = { } ; // 用于缓存加载的图片
// 检查 materialCategoryMap 中当前分类的数组是否为空
const categoryMaterials = materialCategoryMap [ materialsCategory ] || [ ] ;
const shouldScanAllMaterials = categoryMaterials . length === 0 ; // 如果为空,则扫描所有材料
for ( const filePath of materialIconF ilePaths ) {
const name = base name( filePath ) . replace ( ".png" , "" ) ; // 去掉文件扩展名
// 如果 materialCategoryMap 中当前分类的数组不为空
// 且当前材料名称不在指定的材料列表中,则跳过加载
if ( isOnlyPathing && ! shouldScanAllMaterials && ! categoryMaterials . includes ( name ) ) {
continue ;
}
// 已识别的材料集合,避免重复扫描
const recognizedMaterials = new Set ( ) ;
// 扫描背包中的材料
const tolerance = 1 ; // 容错区间
const startX = 117 ;
const startY = 121 ;
const OffsetWidth = 147 ;
const columnWidth = 123 ;
const columnHeight = 750 ;
const maxColumns = 8 ;
// 用于存储图片名和材料数量的数组
const materialInfo = [ ] ;
const unmatchedMaterialNames = new Set ( ) ; // 使用 Set 来存储未匹配的材料名称,确保不重复
// 是否已经开始计时
let hasFoundFirstMaterial = false ;
// 记录上一次发现材料的时间
let lastFoundTime = null ;
// 初始化标志变量,确保在整个扫描过程中保持状态
// let foundPriorityMaterial = false;
let shouldEndScan = false ;
for ( let scroll = 0 ; scroll <= pageScrollCount ; scroll ++ ) {
// log.info(`第 ${scroll+1} 页`);
// 随机选择一句俏皮话
const scanPhrases = [
"扫描中... 太好啦,有这么多素材!" ,
"扫描中... 不错的珍宝!" ,
"扫描中... 侦查骑士,发现目标!" ,
"扫描中... 嗯哼,意外之喜!" ,
"扫描中... 嗯?" ,
"扫描中... 很好,没有放过任何角落!" ,
"扫描中... 会有烟花材料嘛?" ,
"扫描中... 嗯,这是什么?" ,
"扫描中... 这些宝藏积灰了,先清洗一下" ,
"扫描中... 哇!都是好东西!" ,
"扫描中... 不虚此行!" ,
"扫描中... 瑰丽的珍宝,令人欣喜。" ,
"扫描中... 是对长高有帮助的东西吗?" ,
"扫描中... 嗯!品相卓越!" ,
"扫描中... 虽无法比拟黄金,但终有价值。" ,
"扫描中... 收获不少,可以拿去换几瓶好酒啦。" ,
"扫描中... 房租和伙食费,都有着落啦!" ,
"扫描中... 还不赖。" ,
"扫描中... 荒芜的世界,竟藏有这等瑰宝。" ,
"扫描中... 运气还不错。" ,
] ;
// 创建一个数组,用于存储未使用的俏皮话
let tempPhrases = [ ... scanPhrases ] ;
// 打乱数组顺序,确保随机性
tempPhrases . sort ( ( ) => Math . random ( ) - 0.5 ) ;
// 记录扫描开始时间
let phrasesStartTime = Date . now ( ) ;
for ( let column = 0 ; column < maxColumns ; column ++ ) {
const scanX = startX + column * OffsetWidth ;
for ( let i = 0 ; i < materialCategories . length ; i ++ ) {
const { name , filePath } = materialCategories [ i ] ;
if ( recognizedMaterials . has ( name ) ) {
materialCategories . splice ( i , 1 ) ; // 从数组中移除已识别的材料
i -- ; // 调整索引
continue ; // 如果已经识别过,跳过
}
const mat = file . readImageMatSync ( filePath ) ;
if ( mat . empty ( ) ) {
log . error ( ` 加载材料图库失败: ${ filePath } ` ) ;
continue ; // 跳过当前文件
}
const recognitionObject = RecognitionObject . TemplateMatch ( mat , scanX , startY , columnWidth , columnHeight ) ;
recognitionObject . threshold = 0.85 ; // 设置识别阈值为 0.85
const result = captureGameRegion ( ) . find ( recognitionObject ) ;
if ( result . isExist ( ) ) {
recognizedMaterials . add ( name ) ; // 标记为已识别
await moveMouseTo ( result . x , result . y ) ; // 移动鼠标至图片
const ocrRegion = {
x : result . x - tolerance ,
y : result . y + 97 - tolerance ,
width : 66 + 2 * tolerance ,
height : 22 + 2 * tolerance
} ;
const ocrResult = await recognizeText ( ocrRegion , 1000 , OCRdelay , 10 , 3 ) ;
if ( ocrResult . success ) {
materialInfo . push ( { name : name , count : ocrResult . text } ) ;
} else {
log . warn ( ` 芝麻大的数看不清(>ε<) ` ) ;
materialInfo . push ( { name : name , count : "?" } ) ;
}
// 如果是第一次发现材料,开始计时
if ( ! hasFoundFirstMaterial ) {
hasFoundFirstMaterial = true ;
lastFoundTime = Date . now ( ) ;
} else {
// 更新上一次发现材料的时间
lastFoundTime = Date . now ( ) ;
}
}
const mat = file . readImageMatSync ( filePath ) ;
if ( mat . empty ( ) ) {
log . error ( ` 加载图标失败: ${ filePath } ` ) ;
continue ; // 跳过当前文件
}
materialCategories . push ( { name , filePath } ) ;
allMaterials . add ( name ) ; // 将材料名称添加到集合中
materialImages [ name ] = mat ; // 缓存图片
}
// 每2秒输出一句俏皮话
const phrasesTime = Date . now ( ) ;
if ( phrasesTi me - phrasesStartTime >= 2000 ) {
// 输出当前数组的第一句俏皮话
const selectedPhrase = tempPhrases . shift ( ) ;
log . info ( selectedPhrase ) ;
// 已识别的材料集合,避免重复扫描
const recognizedMaterials = new Set ( ) ;
const unmatchedMaterialNa mes = new Set ( ) ; // 未匹配的材料名称
const materialInfo = [ ] ; // 存储材料名称和数量
// 如果数组为空,重新加载并打乱所有俏皮话
if ( tempPhrases . length === 0 ) {
tempPhrases = [ ... scanPhrases ] ;
tempPhrases . sort ( ( ) => Math . random ( ) - 0.5 ) ;
// 扫描参数
const tolerance = 1 ;
const startX = 117 ;
const startY = 121 ;
const OffsetWidth = 147 ;
const columnWidth = 123 ;
const columnHeight = 750 ;
const maxColumns = 8 ;
// 扫描状态
let hasFoundFirstMaterial = false ;
let lastFoundTime = null ;
let shouldEndScan = false ;
let foundPriorityMaterial = false ;
// 俏皮话逻辑
const scanPhrases = [
"扫描中... 太好啦,有这么多素材!" ,
"扫描中... 不错的珍宝!" ,
"扫描中... 侦查骑士,发现目标!" ,
"扫描中... 嗯哼,意外之喜!" ,
"扫描中... 嗯?" ,
"扫描中... 很好,没有放过任何角落!" ,
"扫描中... 会有烟花材料嘛?" ,
"扫描中... 嗯,这是什么?" ,
"扫描中... 这些宝藏积灰了,先清洗一下" ,
"扫描中... 哇!都是好东西!" ,
"扫描中... 不虚此行!" ,
"扫描中... 瑰丽的珍宝,令人欣喜。" ,
"扫描中... 是对长高有帮助的东西吗?" ,
"扫描中... 嗯!品相卓越!" ,
"扫描中... 虽无法比拟黄金,但终有价值。" ,
"扫描中... 收获不少,可以拿去换几瓶好酒啦。" ,
"扫描中... 房租和伙食费,都有着落啦!" ,
"扫描中... 还不赖。" ,
"扫描中... 荒芜的世界,竟藏有这等瑰宝。" ,
"扫描中... 运气还不错。" ,
] ;
let tempPhrases = [ ... scanPhrases ] ;
tempPhrases . sort ( ( ) => Math . random ( ) - 0.5 ) ; // 打乱数组顺序,确保随机性
let phrasesStartTime = Date . now ( ) ;
// 扫描背包中的材料
for ( let scroll = 0 ; scroll <= pageScrollCount ; scroll ++ ) {
if ( ! foundPriorityMaterial ) {
for ( const { category , name } of priorityMaterialNames ) {
if ( recognizedMaterials . has ( name ) ) {
continue ; // 如果已经识别过,跳过
}
const filePath = ` assets/images/ ${ category } / ${ name } .png ` ;
const mat = file . readImageMatSync ( filePath ) ;
if ( mat . empty ( ) ) {
log . error ( ` 加载材料图库失败: ${ filePath } ` ) ;
continue ; // 跳过当前文件
}
const recognitionObject = RecognitionObject . TemplateMatch ( mat , 1146 , startY , columnWidth , columnHeight ) ;
recognitionObject . threshold = 0.8 ; // 设置识别阈值
const result = captureGameRegion ( ) . find ( recognitionObject ) ;
if ( result . isExist ( ) && result . x !== 0 && result . y !== 0 ) {
foundPriorityMaterial = true ; // 标记找到前位材料
log . info ( ` 发现当前或后位材料: ${ name } ,开始全列扫描 ` ) ;
break ; // 发现前位材料后,退出当前循环
}
}
}
if ( foundPriorityMaterial ) {
for ( let column = 0 ; column < maxColumns ; column ++ ) {
const scanX = startX + column * OffsetWidth ;
for ( let i = 0 ; i < materialCategories . length ; i ++ ) {
const { name } = materialCategories [ i ] ;
if ( recognizedMaterials . has ( name ) ) {
continue ; // 如果已经识别过,跳过
}
const mat = materialImages [ name ] ;
const recognitionObject = RecognitionObject . TemplateMatch ( mat , scanX , startY , columnWidth , columnHeight ) ;
recognitionObject . threshold = 0.85 ;
const result = captureGameRegion ( ) . find ( recognitionObject ) ;
await sleep ( Imagedelay ) ;
if ( result . isExist ( ) && result . x !== 0 && result . y !== 0 ) {
recognizedMaterials . add ( name ) ;
await moveMouseTo ( result . x , result . y ) ;
const ocrRegion = {
x : result . x - tolerance ,
y : result . y + 97 - tolerance ,
width : 66 + 2 * tolerance ,
height : 22 + 2 * tolerance
} ;
const ocrResult = await recognizeText ( ocrRegion , 1000 , OCRdelay , 10 , 3 ) ;
materialInfo . push ( { name , count : ocrResult . success ? ocrResult . text : "?" } ) ;
if ( ! hasFoundFirstMaterial ) {
hasFoundFirstMaterial = true ;
lastFoundTime = Date . now ( ) ;
} else {
lastFoundTime = Date . now ( ) ;
}
}
}
}
}
// 每2秒输出一句俏皮话
const phrasesTime = Date . now ( ) ;
if ( phrasesTime - phrasesStartTime >= 2000 ) {
const selectedPhrase = tempPhrases . shift ( ) ;
log . info ( selectedPhrase ) ;
if ( tempPhrases . length === 0 ) {
tempPhrases = [ ... scanPhrases ] ;
tempPhrases . sort ( ( ) => Math . random ( ) - 0.5 ) ;
}
phrasesStartTime = phrasesTime ;
}
// 检查材料识别情况
// 检查是否结束扫描
if ( recognizedMaterials . size === allMaterials . size ) {
log . info ( "所有材料均已识别!" ) ;
shouldEndScan = true ;
break ; // 立即退出当前循环
break ;
}
// 如果已经发现过材料, 检查是否超过3秒未发现新的材料
if ( hasFoundFirstMaterial ) {
const currentTime = Date . now ( ) ;
if ( currentTime - lastFoundTime > 5000 ) {
log . info ( "未发现新的材料,结束扫描" ) ;
shouldEndScan = true ;
break ; // 立即退出当前循环
}
// 如果未超过3秒, 继续扫描( 无需额外操作)
if ( hasFoundFirstMaterial && Date . now ( ) - lastFoundTime > 5000 ) {
log . info ( "未发现新的材料,结束扫描" ) ;
shouldEndScan = true ;
break ;
}
// 检查是否已经滑 到最后一页
// 检查是否到达 最后一页
const sliderBottomRo = RecognitionObject . TemplateMatch ( file . ReadImageMatSync ( "assets/SliderBottom.png" ) , 1284 , 916 , 9 , 26 ) ;
sliderBottomRo . threshold = 0.8 ;
const sliderBottomResult = captureGameRegion ( ) . find ( sliderBottomRo ) ;
if ( sliderBottomResult . isExist ( ) ) {
log . info ( "已到达最后一页!" ) ;
shouldEndScan = true ;
break ; // 如果识别到滑动条底部,终止滑动
break ;
}
// 如果还没有到达最后一页,继续 滑页
// 滑动到下一 页
if ( scroll < pageScrollCount ) {
await scrollPage ( 680 , 10 , 5 ) ;
await sleep ( 10 ) ; // 滑动后等待10毫秒
await sleep ( 10 ) ;
}
}
const lowCountMaterials = filterLowCountMaterials ( materialInfo , materialCategoryMap ) ;
// log.info(`低于目标的导入材料名 ${JSON.stringify(lowCountMaterials, null, 2)}`);
// 检查是否需要结束扫描
if ( shouldEndScan ) {
// 输出识别到的材料数量
log . info ( ` 共识别到 ${ recognizedMaterials . size } 种材料 ` ) ;
const now = new Date ( ) ; // 获取当前时间
const formattedTime = now . toLocaleString ( ) ; // 使用本地时间格式化
const allMaterialsArray = Array . from ( allMaterials ) ;
// 过滤 allMaterials, 找出不在 recognizedMaterials 中的材料名称
for ( const name of allMaterials ) {
if ( ! recognizedMaterials . has ( name ) ) {
unmatchedMaterialNames . add ( name ) ; // 使用 Set 的 add 方法添加名称
}
// 处理未匹配的材料
for ( const name of allMaterials ) {
if ( ! recognizedMaterials . has ( name ) ) {
unmatchedMaterialNames . add ( name ) ;
}
const unmatchedMaterialNamesArray = Array . from ( unmatchedMaterialNames ) ;
}
// 准备日志内容
const logContent = `
// 日志记录
const now = new Date ( ) ;
const formattedTime = now . toLocaleString ( ) ;
const scanMode = shouldScanAllMaterials ? "全材料扫描" : "指定材料扫描" ;
const logContent = `
${ formattedTime }
${ materialsCategory } 种类: ${ recognizedMaterials . size } 数量:
${ scanMode } - ${ materialsCategory } 种类: ${ recognizedMaterials . size } 数量:
${ materialInfo . map ( item => ` ${ item . name } : ${ item . count } ` ) . join ( "," ) }
未匹配的材料 种类: ${ unmatchedMaterialNamesArray . length } 数量:
${ unmatchedMaterialNamesArray . join ( "," ) }
未匹配的材料 种类: ${ unmatchedMaterialNames. size } 数量:
${ Array . from ( unmatchedMaterialNames) . join ( "," ) }
` ;
// 按材料分类类别分文件记录
// 写入历史记录文件
const categoryFilePath = ` history_record/ ${ materialsCategory } .txt ` ;
let categoryLogContent = ` ${ logContent } \n \n ` ; // 添加换行分隔
const overwriteFilePath = ` overwrite_record/ ${ materialsCategory } .txt ` ;
const latestFilePath = "latest_record.txt" ;
await writeLog ( categoryFilePath , logContent ) ;
await writeLog ( overwriteFilePath , logContent ) ;
await writeLog ( latestFilePath , logContent ) ;
// 返回结果
return filterLowCountMaterials ( materialInfo , materialCategoryMap ) ;
}
async function writeLog ( filePath , logContent ) {
try {
// 读取现有文件内容
const existingContent = file . readTextSync ( categoryFilePath ) ;
// 按记录分隔,假设每条记录之间用两个换行符分隔
const existingContent = file . readTextSync ( filePath ) ;
const records = existingContent . split ( "\n\n" ) ;
// 截取最新的365个记录
const latestRecords = records . slice ( - 365 ) . join ( "\n\n" ) ;
categoryLog Content = ` ${ logContent } \n \n ${ latestRecords } ` ; // 将新内容拼接到最前面
const final Content = ` ${ logContent } \n \n ${ latestRecords } ` ;
const result = file . WriteTextSync ( filePath , finalContent , false ) ;
if ( result ) {
log . info ( ` 成功将日志写入文件 ${ filePath } ` ) ;
} else {
log . error ( ` 写入文件 ${ filePath } 失败 ` ) ;
}
} catch ( error ) {
// 如果文件不存在,直接使用新内容
log . warn ( ` 文件 ${ categoryFilePath } 不存在,将创建新文件 ` ) ;
}
// 写回文件
const categoryResult = file . WriteTextSync ( categoryFilePath , categoryLogContent , false ) ; // 覆盖模式
if ( categoryResult ) {
log . info ( ` 成功将 ${ materialsCategory } 的材料写入历史文件 ` ) ;
} else {
log . error ( ` 写入 ${ materialsCategory } 的本地文件失败 ` ) ;
}
// 按材料分类类别分文件覆写记录
const overwriteFilePath = ` overwrite_record/ ${ materialsCategory } .txt ` ;
const overwriteLogContent = ` ${ logContent }
图库的材料 种类: ${ allMaterialsArray . length } 数量:
${ allMaterialsArray . join ( "," ) } \n \n ` ; // 添加换行分隔
const overwriteResult = file . WriteTextSync ( overwriteFilePath , overwriteLogContent , false ) ; // 覆盖模式
if ( overwriteResult ) {
log . info ( ` 成功将 ${ materialsCategory } 的记录写入覆写文件 ` ) ;
log . warn ( ` 文件 ${ filePath } 不存在,将创建新文件 ` ) ;
const result = file . WriteTextSync ( filePath , logContent , false ) ;
if ( result ) {
log . info ( ` 成功创建并写入文件 ${ filePath } ` ) ;
} else {
log . error ( ` 覆写 ${ materialsCategory } 到本地文件 失败 ` ) ;
}
// 最新的历史覆写记录
const latestFilePath = "latest_record.txt" ;
const latestLogContent = ` ${ logContent }
图库的材料 种类: ${ allMaterialsArray . length } 数量:
${ allMaterialsArray . join ( "," ) } \n \n ` ; // 添加换行分隔
const latestResult = file . WriteTextSync ( latestFilePath , latestLogContent , false ) ; // 覆盖模式
if ( latestResult ) {
log . info ( "成功将最新的历史记录写入js根目录" ) ;
} else {
log . error ( "写入最新的历史记录失败" ) ;
log . error ( ` 创建文件 ${ filePath } 失败 ` ) ;
}
}
return lowCountMaterials ;
}
// 定义所有图标的图像识别对象,每个图片都有自己的识别区域
@@ -452,8 +474,8 @@ async function recognizeImage(recognitionObject, timeout = 5000) {
while ( Date . now ( ) - startTime < timeout ) {
try {
// 尝试识别图像
le t imageResult = captureGameRegion ( ) . find ( recognitionObject ) ;
if ( imageResult ) {
cons t imageResult = captureGameRegion ( ) . find ( recognitionObject ) ;
if ( imageResult . isExist ( ) && imageResult . x !== 0 && imageResult . y !== 0 ) {
// log.info(`成功识别图像,坐标: x=${imageResult.x}, y=${imageResult.y}`);
// log.info(`图像尺寸: width=${imageResult.width}, height=${imageResult.height}`);
return { success : true , x : imageResult . x , y : imageResult . y } ;
@@ -612,7 +634,7 @@ async function MaterialPath(materialCategoryMap) {
}
let CategoryResult = await recognizeImage ( CategoryObject , 2000 ) ;
if ( CategoryResult . success && CategoryResult . x !== 0 && CategoryResult . y !== 0 ) {
if ( CategoryResult . success ) {
log . info ( ` 识别到 ${ materialsCategory } 所在分类。 ` ) ;
stage = 4 ; // 进入下一阶段
} else {
@@ -1042,7 +1064,16 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
materialCategoryMap [ selectedCategory ] = [ ] ;
}
} ) ;
// log.info(JSON.stringify( materialCategoryMap, null, 2));
// 如果 isOnlyPathing 为 true, 移除 materialCategoryMap 中的空数组
// 如果 isOnlyPathing 为 true, 移除 materialCategoryMap 中的空数组
if ( isOnlyPathing ) {
Object . keys ( materialCategoryMap ) . forEach ( category => {
if ( materialCategoryMap [ category ] . length === 0 ) {
delete materialCategoryMap [ category ] ;
}
} ) ;
}
log . info ( JSON . stringify ( materialCategoryMap , null , 2 ) ) ;
// 调用背包材料统计
const lowCountMaterialsFiltered = await MaterialPath ( materialCategoryMap ) ;