diff --git a/repo/js/背包材料统计/README.md b/repo/js/背包材料统计/README.md
index aca28e379..84fc2575d 100644
--- a/repo/js/背包材料统计/README.md
+++ b/repo/js/背包材料统计/README.md
@@ -1,4 +1,4 @@
-# 背包材料统计 v2.52
+# 背包材料统计 v2.54
作者:吉吉喵
@@ -118,9 +118,10 @@
| 6. 仅 pathing 材料 | 仅扫描 `pathing` 文件夹内的材料,跳过其他分类,大幅缩短扫描时间 | 路径配置完成后开启,提升脚本运行效率 |
| 7. 弹窗名 | 不填则默认循环执行 `assets\imageClick` 文件夹下所有弹窗;填写则仅执行指定弹窗 | 推荐默认,需单独适配某类弹窗时填写(例:月卡,复苏) |
| 8. 采用的 CD 分类 | 不填则默认执行 `materialsCD` 文件夹内配置的CD分类;填写则仅执行指定CD分类 | 新增材料时,需在该文件夹同步配置CD规则(操作见「四、问题解答Q2」) |
-| 9. 采用的识别名单 | 不填则默认执行 `targetText` 文件夹内配置的识别名单;填写则仅执行指定识别名单 | 新增名单时,需符合配置规则(操作见「四、问题解答Q4」) |
-| 10. 超量阈值 | 首次扫描后,超量的路径材料,将从识别名单中剔除,默认5000 | 不推荐9999,怪物材料有几千就够了,采用默认数值,可自动避免爆背包 |
-| 11. 拖动距离 | 解决非1080p分辨率下“划页过头”问题,需调整到“一次划页≤4行” | 拖动点建议选“第五行材料附近”;大于1080p屏可适当减小数值 |
+| 9. 终止时刻 | 不填则不执行定时终止;路径无时间记录时,会预判路径耗时5分钟,且预留2分钟空闲 | 填写需要按24小时格式(例:4:10) |
+| 10. 采用的识别名单 | 不填则默认执行 `targetText` 文件夹内配置的识别名单;填写则仅执行指定识别名单 | 新增名单时,需符合配置规则(操作见「四、问题解答Q4」) |
+| 11. 超量阈值 | 首次扫描后,超量的路径材料,将从识别名单中剔除,默认5000 | 不推荐9999,怪物材料有几千就够了,采用默认数值,可自动避免爆背包 |
+| 12. 拖动距离 | 解决非1080p分辨率下“划页过头”问题,需调整到“一次划页≤4行” | 拖动点建议选“第五行材料附近”;大于1080p屏可适当减小数值 |
## 四、注意事项
@@ -129,7 +130,7 @@
3. **食物识别强制要求**:背包食物界面**第一行必须包含8种食物**(苹果、日落果、星蕈、活化的星蕈、枯焦的星蕈、泡泡桔、烛伞蘑菇、美味的宝石闪闪),缺少则这些食物无法识别;
4. **关键文件备份**:建议不定期备份 `pathing` 文件夹(路径文件)和 `pathing_record` 文件夹(路径运行记录),便于丢失后或记录被污染后恢复如初;
5. **OCR配置**:默认最新,调整识别名单时,用的是V5Auto;
-6. **手动终止运行**:如果要终止JS运行,推荐在当前路径采集前,或者采集完进入背包扫描时终止,以保护当前记录;如果是【取消扫描】模式,不会储存当前记录的材料数目,就随意。
+6. **手动终止运行**:如果要终止JS运行,推荐在当前路径采集到当前材料前,或者采集完进入背包扫描时终止(会在扫描结束后终止),以保护当前记录;如果是【取消扫描】模式,不会储存当前记录的材料数目,就随意。
## 五、问题解答
@@ -149,9 +150,18 @@ A:1. 打开 `materialsCD` 文件夹(脚本路径:`BetterGI\User\JsScript\
-### Q3:如何识别不规范命名的路径文件夹(如“纳塔食材一条龙”“果园.json”)?
-A:将不规范的文件夹/文件,放入**适配的材料文件夹**中即可(路径CD由“所在材料文件夹”决定)。
- 例:“果园.json”放入“苹果”文件夹,将按“苹果”的CD规则执行。
+### Q3:如何识别不规范命名的路径文件夹(如“纳塔食材一条龙”、“果园.json”)?
+A:1. 将不规范的文件夹/文件,放入**适配的材料文件夹**中即可(路径CD由“所在材料文件夹”决定)。
+ 2. 例:看「四、问题解答Q2」,① 把“纳塔食材一条龙”作为标准名,选择一个CD,② 在「JS 自定义设置」【优先级材料】里填入:纳塔食材一条龙,③将“纳塔食材一条龙”的文件夹放置到`pathing` 文件夹;锄地路径可放置到“锄地”文件夹里(没有就新建一个)**此方法无法使用 背包材料统计 的优选路径功能!**
+ 3. 「JS 自定义设置」勾选【取消扫描】后,就可以运行了!**此项不勾,将无CD记录!**;
+ 4. 例:“果园.json”放入“苹果”文件夹,将按“苹果”的CD规则执行。
+ 操作参考截图:
+
+
### Q4:如何自定义识别名单?
A:1. 打开 `targetText` 文件夹(脚本路径:`BetterGI\User\JsScript\背包材料统计\targetText`);
@@ -213,4 +223,5 @@ A:记录文件夹位于 `BetterGI\User\JsScript\背包材料统计\` 下,各
| v2.42 | 新增“无路径间扫描”“noRecord模式”(适合成熟路径);新增怪物材料CD文件 |
| v2.50 | 新增独立名单拾取、弹窗模块;支持怪物名识别 |
| v2.51 | 自定义设置新增“拖动距离/拖动点”;新增月卡弹窗识别;路径材料超量自动上黑名单;修复怪物0收获记录 |
-| v2.52 | 自定义设置新增“超量阈值”和“识别名单”输入框;新增多层弹窗逻辑 |
\ No newline at end of file
+| v2.52 | 自定义设置新增“超量阈值”和“识别名单”输入框;新增多层弹窗逻辑 |
+| v2.54 | 自定义设置新增“终止时刻”,修复bug,新增“添加不规范命名的路径文件夹”说明,新增一个“锄地”的怪物路径CD |
\ No newline at end of file
diff --git a/repo/js/背包材料统计/assets/Pic/Pic14.png b/repo/js/背包材料统计/assets/Pic/Pic14.png
new file mode 100644
index 000000000..84be9f70a
Binary files /dev/null and b/repo/js/背包材料统计/assets/Pic/Pic14.png differ
diff --git a/repo/js/背包材料统计/assets/Pic/Pic15.png b/repo/js/背包材料统计/assets/Pic/Pic15.png
new file mode 100644
index 000000000..a0ff74ac7
Binary files /dev/null and b/repo/js/背包材料统计/assets/Pic/Pic15.png differ
diff --git a/repo/js/背包材料统计/assets/Pic/Pic16.png b/repo/js/背包材料统计/assets/Pic/Pic16.png
new file mode 100644
index 000000000..5979aca2b
Binary files /dev/null and b/repo/js/背包材料统计/assets/Pic/Pic16.png differ
diff --git a/repo/js/背包材料统计/assets/Scroll.png b/repo/js/背包材料统计/assets/Scroll.png
new file mode 100644
index 000000000..44306d256
Binary files /dev/null and b/repo/js/背包材料统计/assets/Scroll.png differ
diff --git a/repo/js/背包材料统计/assets/滚轮下翻.json b/repo/js/背包材料统计/assets/滚轮下翻.json
index 83995604b..6808800b8 100644
--- a/repo/js/背包材料统计/assets/滚轮下翻.json
+++ b/repo/js/背包材料统计/assets/滚轮下翻.json
@@ -1,3 +1,3 @@
-{"macroEvents":[{"type":6,"mouseX":0,"mouseY":-120,"time":0},
-{"type":6,"mouseX":0,"mouseY":0,"time":5}],
-"info":{"name":"","description":"","x":0,"y":0,"width":1920,"height":1080,"recordDpi":1}}
\ No newline at end of file
+{"macroEvents":[
+{"type":6,"mouseX":0,"mouseY":-120,"time":25}
+],"info":{"name":"","description":"","x":0,"y":0,"width":1920,"height":1080,"recordDpi":1}}
\ No newline at end of file
diff --git a/repo/js/背包材料统计/lib/autoPick.js b/repo/js/背包材料统计/lib/autoPick.js
index 941db67c4..73804d597 100644
--- a/repo/js/背包材料统计/lib/autoPick.js
+++ b/repo/js/背包材料统计/lib/autoPick.js
@@ -158,29 +158,42 @@ async function findFIcon(recognitionObject, timeout = 10, ra = null) {
return { success: true, x: result.x, y: result.y, width: result.width, height: result.height };
}
} catch (error) {
- log.error(`识别图像时发生异常: ${error.message}`);
+ log.error(`识别图标异常: ${error.message}`);
if (state.cancelRequested) {
- break; // 如果请求了取消,则退出循环
+ break;
}
return null;
}
await sleep(5); // 每次检测间隔 5 毫秒
}
if (state.cancelRequested) {
- log.info("图像识别任务已取消");
+ log.info("图标识别任务已取消");
}
return null;
}
-// 对齐并交互目标
-async function alignAndInteractTarget(targetTexts, fDialogueRo, textxRange, texttolerance, cachedFrame=null) {
+// 定义Scroll.png识别对象(按需求使用TemplateMatch,包含指定范围)
+const ScrollRo = RecognitionObject.TemplateMatch(
+ file.ReadImageMatSync("assets/Scroll.png"),
+ 1055, 521, 15, 35 // 识别范围:x=1055, y=521, width=15, height=35
+);
+
+/**
+ * 对齐并交互目标(直接用findFIcon识别Scroll.png)
+ * @param {string[]} targetTexts - 待匹配的目标文本列表
+ * @param {Object} fDialogueRo - F图标的识别对象
+ * @param {Object} textxRange - 文本识别的X轴范围 { min: number, max: number }
+ * @param {number} texttolerance - 文本与F图标Y轴对齐的容差
+ * @param {Object} cachedFrame - 缓存的图像帧(可选)
+ */
+async function alignAndInteractTarget(targetTexts, fDialogueRo, textxRange, texttolerance, cachedFrame = null) {
let lastLogTime = Date.now();
// 记录每个材料的识别次数(文本+坐标 → 计数)
const recognitionCount = new Map();
while (!state.completed && !state.cancelRequested) {
const currentTime = Date.now();
- if (currentTime - lastLogTime >= 10000) { // 每5秒记录一次日志
+ if (currentTime - lastLogTime >= 10000) {
log.info("检测中...");
lastLogTime = currentTime;
}
@@ -191,13 +204,15 @@ async function alignAndInteractTarget(targetTexts, fDialogueRo, textxRange, text
// 尝试找到 F 图标
let fRes = await findFIcon(fDialogueRo, 10, cachedFrame);
if (!fRes) {
- continue;
+ const scrollRes = await findFIcon(ScrollRo, 10, cachedFrame); // 复用findFIcon函数
+ if (scrollRes) {
+ await keyMouseScript.runFile(`assets/滚轮下翻.json`); // 调用翻滚脚本
+ }
+ continue; // 继续下一轮检测
}
// 获取 F 图标的中心点 Y 坐标
let centerYF = fRes.y + fRes.height / 2;
-
- // 在当前屏幕范围内进行 OCR 识别
let ocrResults = await performOcr(targetTexts, textxRange, { min: fRes.y - 3, max: fRes.y + 37 }, 10, cachedFrame);
// 检查所有目标文本是否在当前页面中
@@ -206,31 +221,25 @@ async function alignAndInteractTarget(targetTexts, fDialogueRo, textxRange, text
let targetResult = ocrResults.find(res => res.text.includes(targetText));
if (targetResult) {
- // 生成唯一标识并更新识别计数(文本+Y坐标)
const materialId = `${targetText}-${targetResult.y}`;
recognitionCount.set(materialId, (recognitionCount.get(materialId) || 0) + 1);
let centerYTargetText = targetResult.y + targetResult.height / 2;
if (Math.abs(centerYTargetText - centerYF) <= texttolerance) {
- // log.info(`目标文本 '${targetText}' 和 F 图标水平对齐`);
if (recognitionCount.get(materialId) >= 1) {
- keyPress("F"); // 执行交互操作
+ keyPress("F");
log.info(`交互或拾取: ${targetText}`);
-
- // F键后清除计数,确保单次交互
recognitionCount.delete(materialId);
}
foundTarget = true;
- break; // 成功交互后退出当前循环,但继续检测
+ break;
}
}
}
- // 如果在当前页面中没有找到任何目标文本,则滚动到下一页
if (!foundTarget) {
await keyMouseScript.runFile(`assets/滚轮下翻.json`);
- // verticalScroll(-20);
}
if (state.cancelRequested) {
break;
diff --git a/repo/js/背包材料统计/main.js b/repo/js/背包材料统计/main.js
index 5d294ed94..713cebb84 100644
--- a/repo/js/背包材料统计/main.js
+++ b/repo/js/背包材料统计/main.js
@@ -51,6 +51,9 @@ var state = { completed: false, cancelRequested: false };
const timeCost = Math.min(300, Math.max(0, Math.floor(Number(settings.TimeCost) || 30)));
const notify = settings.notify || false;
const noRecord = settings.noRecord || false;
+const targetCount = Math.min(9999, Math.max(0, Math.floor(Number(settings.TargetCount) || 5000))); // 设定的目标数量
+const exceedCount = Math.min(9999, Math.max(0, Math.floor(Number(settings.ExceedCount) || 5000))); // 设定的超量目标数量
+const endTimeStr = settings.CurrentTime ? settings.CurrentTime : null;
// 解析需要处理的CD分类
const allowedCDCategories = (settings.CDCategories || "")
@@ -326,6 +329,32 @@ function getCurrentTimeInHours() {
return now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600;
}
+/**
+ * 计算当前时间到指定终止时间的剩余分钟数(处理跨天,单向倒计时)
+ * @param {string} endTimeStr - 指定终止时间(格式"HH:mm",如"4:00")
+ * @returns {number} 剩余分钟数(负数表示已过终止时间),无效格式返回-1
+ */
+function getRemainingMinutesToEndTime(endTimeStr) {
+ // 1. 解析终止时间
+ const [endHours, endMinutes] = endTimeStr.split(/[::]/).map(Number);
+ if (isNaN(endHours) || isNaN(endMinutes) || endHours < 0 || endHours >= 24 || endMinutes < 0 || endMinutes >= 60) {
+ log.error(`${CONSTANTS.LOG_MODULES.MAIN}无效终止时间格式:${endTimeStr},需为"HH:mm"(如"14:30")`);
+ return -1; // 无效格式视为“已过时间”
+ }
+
+ // 2. 转换为时间戳(当天终止时间 & 次日终止时间,处理跨天)
+ const now = new Date();
+ const todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), endHours, endMinutes);
+ const tomorrowEnd = new Date(todayEnd.getTime() + 24 * 60 * 60 * 1000); // 加1天
+
+ // 3. 确定有效终止时间(若当天已过,取次日)
+ const targetEndTime = now <= todayEnd ? todayEnd : tomorrowEnd;
+
+ // 4. 计算剩余分钟数(毫秒转分钟,保留整数)
+ const remainingMs = targetEndTime - now;
+ return Math.floor(remainingMs / (1000 * 60));
+}
+
// ==============================================
// 记录管理
// ==============================================
@@ -403,9 +432,6 @@ function recordRunTime(resourceName, pathName, startTime, endTime, runTime, reco
try {
if (runTime >= 3) { // 运行时间≥3秒才处理记录
- // ==============================================
- // 新增:怪物路径专用逻辑(判断对应材料总数量是否为0)
- // ==============================================
const isMonsterPath = monsterToMaterials.hasOwnProperty(resourceName); // 是否为怪物路径
if (isMonsterPath) {
// 1. 获取当前怪物对应的所有目标材料(从已有映射中取)
@@ -424,9 +450,6 @@ function recordRunTime(resourceName, pathName, startTime, endTime, runTime, reco
}
}
- // ==============================================
- // 原有:普通材料0记录逻辑(完全保留,不做修改)
- // ==============================================
for (const [material, count] of Object.entries(materialCountDifferences)) {
if (material === resourceName && count === 0) {
const zeroMaterialPath = `${recordDir}/${material}${CONSTANTS.ZERO_COUNT_SUFFIX}`;
@@ -436,9 +459,6 @@ function recordRunTime(resourceName, pathName, startTime, endTime, runTime, reco
}
}
- // ==============================================
- // 原有:正常记录生成逻辑(完全保留,不做修改)
- // ==============================================
const hasZeroMaterial = Object.values(materialCountDifferences).includes(0);
const isFinalCumulativeDistanceZero = finalCumulativeDistance === 0;
@@ -504,102 +524,194 @@ function getLastRunEndTime(resourceName, pathName, recordDir, noRecordDir) {
* @param {string} recordDir - 记录目录
* @returns {number|null} 时间成本(秒/中级单位),null=无法计算
*/
-function calculatePerTime(resourceName, pathName, recordDir) {
- const recordPath = `${recordDir}/${resourceName}.txt`;
+function getHistoricalPathRecords(resourceKey, pathName, recordDir, noRecordDir, isFood = false, cache = {}) {
+ // 1. 生成唯一缓存键(确保不同路径/不同文件的记录不混淆)
+ const isFoodSuffix = isFood ? CONSTANTS.FOOD_EXP_RECORD_SUFFIX : ".txt";
+ const recordFile = `${recordDir}/${resourceKey}${isFoodSuffix}`;
+ const cacheKey = `${recordFile}|${pathName}`; // 键格式:文件路径|路径名
+
+ // 2. 优先从缓存获取,命中则直接返回(不读文件)
+ if (cache[cacheKey]) {
+ log.debug(`${CONSTANTS.LOG_MODULES.RECORD}从缓存复用记录:${cacheKey}`);
+ return cache[cacheKey];
+ }
+
+ // 3. 缓存未命中,才读取文件
+ const records = [];
+ let targetFile = recordFile;
+ let content = "";
+
+ // 读主目录→读备用目录
try {
- const content = file.readTextSync(recordPath);
- const lines = content.split('\n');
- const completeRecords = [];
+ content = file.readTextSync(targetFile);
+ } catch (mainErr) {
+ targetFile = `${noRecordDir}/${resourceKey}${isFoodSuffix}`;
+ try {
+ content = file.readTextSync(targetFile);
+ log.debug(`${CONSTANTS.LOG_MODULES.RECORD}从备用目录读取记录:${targetFile}`);
+ } catch (backupErr) {
+ log.debug(`${CONSTANTS.LOG_MODULES.RECORD}无${resourceKey}的历史记录:${targetFile}`);
+ // 空记录也写入缓存,避免下次重复尝试读文件
+ cache[cacheKey] = records;
+ return records;
+ }
+ }
- // ==============================================
- // 怪物路径:改为以中级材料为基准(最低级÷3)
- // ==============================================
- if (monsterToMaterials.hasOwnProperty(resourceName)) {
- const monsterMaterials = monsterToMaterials[resourceName]; // 映射顺序:[最高级, 中级, 最低级]
- // 新比例:最高级×3(1最高级=3中级),中级×1(本身),最低级×1/3(3最低级=1中级 → 最低级÷3)
- const gradeRatios = [3, 1, 1/3];
+ // 解析记录(按原有格式提取runTime和quantityChange)
+ const lines = content.split('\n');
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].startsWith('路径名: ') && lines[i].split('路径名: ')[1] === pathName) {
+ const runTimeLine = lines[i + 3];
+ const quantityChangeLine = lines[i + 4] || "";
+ let runTime = 0;
+ let quantityChange = {};
- for (let i = 0; i < lines.length; i++) {
- if (lines[i].startsWith('路径名: ') && lines[i].split('路径名: ')[1] === pathName) {
- const runTimeLine = lines[i + 3];
- const quantityChangeLine = lines[i + 4];
-
- if (runTimeLine?.startsWith('运行时间: ') && quantityChangeLine?.startsWith('数量变化: ')) {
- // 1. 提取运行时间
- const runTime = parseInt(runTimeLine.split('运行时间: ')[1].split('秒')[0], 10);
- if (isNaN(runTime) || runTime <= 0) continue;
-
- // 2. 提取数量变化
- const quantityChange = JSON.parse(quantityChangeLine.split('数量变化: ')[1]);
-
- // 3. 按新比例计算“总中级单位数量”(最低级÷3)
- let totalMiddleCount = 0; // 变量名改为中级单位
- monsterMaterials.forEach((mat, index) => {
- const count = quantityChange[mat] || 0;
- const ratio = gradeRatios[index] || 1;
- totalMiddleCount += count * ratio; // 最低级此处等价于 count ÷ 3
- });
- // 保留两位小数(处理1/3导致的无限小数)
- totalMiddleCount = parseFloat(totalMiddleCount.toFixed(2));
-
- // 4. 过滤无效数据
- if (totalMiddleCount <= 0) continue;
-
- // 5. 计算时间成本(秒/中级单位)
- const perTime = parseFloat((runTime / totalMiddleCount).toFixed(2));
- completeRecords.push(perTime);
- // 日志更新为中级单位
- log.debug(`${CONSTANTS.LOG_MODULES.RECORD}怪物【${resourceName}】路径${pathName}:${runTime}秒/${totalMiddleCount}中级单位 → ${perTime}秒/单位`);
- }
+ // 提取运行时间(秒)
+ if (runTimeLine?.startsWith('运行时间: ')) {
+ runTime = parseInt(runTimeLine.split('运行时间: ')[1].split('秒')[0], 10) || 0;
+ }
+ // 提取数量变化(JSON格式)
+ if (quantityChangeLine.startsWith('数量变化: ')) {
+ try {
+ quantityChange = JSON.parse(quantityChangeLine.split('数量变化: ')[1]) || {};
+ } catch (e) {
+ log.warn(`${CONSTANTS.LOG_MODULES.RECORD}解析数量变化失败:${quantityChangeLine}`);
}
}
- }
- // ==============================================
- // 普通材料:完全保留原有逻辑
- // ==============================================
- else {
- for (let i = 0; i < lines.length; i++) {
- if (lines[i].startsWith('路径名: ') && lines[i].split('路径名: ')[1] === pathName) {
- const runTimeLine = lines[i + 3];
- const quantityChangeLine = lines[i + 4];
- if (runTimeLine?.startsWith('运行时间: ') && quantityChangeLine?.startsWith('数量变化: ')) {
- const runTime = parseInt(runTimeLine.split('运行时间: ')[1].split('秒')[0], 10);
- const quantityChange = JSON.parse(quantityChangeLine.split('数量变化: ')[1]);
-
- if (quantityChange[resourceName] !== undefined && quantityChange[resourceName] !== 0) {
- completeRecords.push(parseFloat((runTime / quantityChange[resourceName]).toFixed(2)));
- }
- }
- }
+ if (runTime > 0) {
+ records.push({ runTime, quantityChange });
}
}
+ }
- // ==============================================
- // 统一的异常值过滤和平均值计算(不变)
- // ==============================================
- if (completeRecords.length < 3) {
- log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}有效记录不足3条,无法计算时间成本`);
- return null;
- }
+ // 4. 将读取到的记录写入缓存,供后续复用
+ cache[cacheKey] = records;
+ log.debug(`${CONSTANTS.LOG_MODULES.RECORD}读取记录并缓存:${cacheKey}(${records.length}条)`);
+ return records;
+}
- const recentRecords = completeRecords.slice(-5).filter(record => !isNaN(record) && record !== Infinity);
- log.debug(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}最近记录: ${JSON.stringify(recentRecords)}`);
+/**
+ * 基于历史runTime预估路径总耗时(默认5分钟)
+ * @param {Object} entry - 路径条目
+ * @param {string} recordDir - 记录目录
+ * @param {string} noRecordDir - 备用目录
+ * @param {Object} cache - 缓存对象
+ * @returns {number} 预估耗时(秒)
+ */
+function estimatePathTotalTime(entry, recordDir, noRecordDir, cache = {}) {
+ const { resourceName, monsterName, path: pathingFilePath } = entry;
+ const pathName = basename(pathingFilePath);
+ const isFood = resourceName && isFoodResource(resourceName);
+ let resourceKey = isFood ? resourceName : (monsterName || resourceName);
- 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) <= 1 * stdDev);
+ // 无资源关联时,默认5分钟(300秒)
+ if (!resourceKey) {
+ log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}无资源关联,默认按300秒(5分钟)预估`);
+ return 300;
+ }
+
+ // 调用公共函数获取记录(复用缓存)
+ const historicalRecords = getHistoricalPathRecords(
+ resourceKey,
+ pathName,
+ recordDir,
+ noRecordDir,
+ isFood,
+ cache
+ );
+
+ // 无记录时,默认5分钟(300秒)
+ if (historicalRecords.length === 0) {
+ log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}无有效runTime记录,默认按300秒(5分钟)预估`);
+ return 300;
+ }
+
+ // 取最近5条记录计算平均值
+ const recentRecords = [...historicalRecords].reverse().slice(0, 5);
+ const avgRunTime = Math.round(
+ recentRecords.reduce((sum, record) => sum + record.runTime, 0) / recentRecords.length
+ );
+
+ log.debug(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}历史runTime(最近5条):${recentRecords.map(r => r.runTime)}秒,预估耗时:${avgRunTime}秒`);
+ return avgRunTime;
+}
+
+/**
+ * 计算单次时间成本(秒/单位材料)(复用缓存)
+ * @param {string} resourceName - 资源名
+ * @param {string} pathName - 路径名
+ * @param {string} recordDir - 记录目录
+ * @param {string} noRecordDir - 备用目录
+ * @param {boolean} isFood - 是否为狗粮路径
+ * @param {Object} cache - 缓存对象
+ * @returns {number|null} 时间成本
+ */
+function calculatePerTime(resourceName, pathName, recordDir, noRecordDir, isFood = false, cache = {}) {
+ const isMonster = monsterToMaterials.hasOwnProperty(resourceName);
+ // 调用公共函数获取记录(复用缓存)
+ const historicalRecords = getHistoricalPathRecords(
+ resourceName,
+ pathName,
+ recordDir,
+ noRecordDir,
+ isFood,
+ cache
+ );
+
+ // 有效记录不足3条,返回null
+ if (historicalRecords.length < 3) {
+ log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}有效记录不足3条,无法计算时间成本`);
+ return null;
+ }
+
+ const completeRecords = [];
+ if (isMonster) {
+ // 怪物路径:按中级单位计算
+ const monsterMaterials = monsterToMaterials[resourceName];
+ const gradeRatios = [3, 1, 1/3]; // 最高级×3,中级×1,最低级×1/3
+
+ historicalRecords.forEach(record => {
+ const { runTime, quantityChange } = record;
+ let totalMiddleCount = 0;
+
+ monsterMaterials.forEach((mat, index) => {
+ const count = quantityChange[mat] || 0;
+ totalMiddleCount += count * (gradeRatios[index] || 1);
+ });
+
+ totalMiddleCount = parseFloat(totalMiddleCount.toFixed(2));
+ if (totalMiddleCount > 0) {
+ completeRecords.push(parseFloat((runTime / totalMiddleCount).toFixed(2)));
+ }
+ });
+ } else {
+ // 普通材料路径:直接按材料数量计算
+ historicalRecords.forEach(record => {
+ const { runTime, quantityChange } = record;
+ if (quantityChange[resourceName] !== undefined && quantityChange[resourceName] !== 0) {
+ completeRecords.push(parseFloat((runTime / quantityChange[resourceName]).toFixed(2)));
+ }
+ });
+ }
+
+ // 异常值过滤与平均值计算(原有逻辑不变)
+ if (completeRecords.length < 3) {
+ log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}有效效率记录不足3条,无法计算时间成本`);
+ return null;
+ }
+
+ const recentRecords = completeRecords.slice(-5).filter(r => !isNaN(r) && r !== Infinity);
+ 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(r => Math.abs(r - mean) <= 1 * stdDev);
if (filteredRecords.length === 0) {
log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}记录数据差异过大,无法计算有效时间成本`);
return null;
}
- return parseFloat((filteredRecords.reduce((acc, val) => acc + val, 0) / filteredRecords.length).toFixed(2));
- } catch (error) {
- log.warn(`${CONSTANTS.LOG_MODULES.RECORD}路径${pathName}无有效记录,无法计算时间成本`);
- }
- return null;
+ return parseFloat((filteredRecords.reduce((acc, val) => acc + val, 0) / filteredRecords.length).toFixed(2));
}
// ==============================================
@@ -708,7 +820,7 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
const result = imageMapCache.get(imagesDir)[processedName] ?? null;
if (result) {
- log.debug(`${CONSTANTS.LOG_MODULES.MATERIAL}资源${resourceName}匹配分类:${result}`);
+ // log.debug(`${CONSTANTS.LOG_MODULES.MATERIAL}资源${resourceName}匹配分类:${result}`);
} else {
log.debug(`${CONSTANTS.LOG_MODULES.MATERIAL}资源${resourceName}未匹配到分类`);
}
@@ -720,6 +832,54 @@ function matchImageAndGetCategory(resourceName, imagesDir) {
return result;
}
+// ==============================================
+// 特殊材料与超量判断(核心新增逻辑)
+// ==============================================
+const specialMaterials = [
+ "水晶块", "魔晶块", "星银矿石", "紫晶块", "萃凝晶", "虹滴晶", "铁块", "白铁块",
+ "精锻用魔矿", "精锻用良矿", "精锻用杂矿"
+];
+
+let excessMaterialNames = []; // 超量材料名单
+
+// 筛选低数量材料(保留原逻辑+修正超量判断)
+function filterLowCountMaterials(pathingMaterialCounts, materialCategoryMap) {
+ // 超量阈值(普通材料9999,矿石处理后也是9999)
+ const EXCESS_THRESHOLD = exceedCount;
+ // 临时存储本次超量材料
+ const tempExcess = [];
+
+ // 提取所有需要扫描的材料(含怪物材料)
+ const allMaterials = Object.values(materialCategoryMap).flat();
+
+ const filteredMaterials = pathingMaterialCounts
+ .filter(item =>
+ allMaterials.includes(item.name) &&
+ (item.count < targetCount || item.count === "?")
+ )
+ .map(item => {
+ // 矿石数量÷10
+ let processedCount = item.count;
+ if (specialMaterials.includes(item.name) && item.count !== "?") {
+ processedCount = Math.floor(Number(item.count) / 10);
+ }
+
+ // 判断是否超量(用处理后数量对比阈值)
+ if (item.count !== "?" && processedCount >= EXCESS_THRESHOLD) {
+ tempExcess.push(item.name); // 记录超量材料名
+ }
+
+ return { ...item, count: processedCount };
+ });
+
+ tempExcess.push("OCR启动"); // 添加特殊标记,用于终止OCR等待
+ // 更新全局超量名单(去重)
+ excessMaterialNames = [...new Set(tempExcess)];
+ log.info(`【超量材料更新】共${excessMaterialNames.length}种:${excessMaterialNames.join("、")}`);
+
+ return filteredMaterials;
+}
+
// ==============================================
// 路径处理(拆分巨型函数)
// ==============================================
@@ -785,8 +945,16 @@ async function processMonsterPathEntry(entry, context) {
CDCategories, timeCost, recordDir, noRecordDir, imagesDir,
materialCategoryMap, flattenedLowCountMaterials,
currentMaterialName: prevMaterialName,
- materialAccumulatedDifferences, globalAccumulatedDifferences
+ materialAccumulatedDifferences, globalAccumulatedDifferences,
+ pathRecordCache // 新增:从上下文取缓存
} = context;
+ const monsterMaterials = monsterToMaterials[monsterName] || [];
+ const allExcess = monsterMaterials.every(mat => excessMaterialNames.includes(mat));
+ if (allExcess) {
+ log.warn(`${CONSTANTS.LOG_MODULES.MONSTER}怪物【${monsterName}】所有材料已超量,跳过路径:${pathName}`);
+ await sleep(1);
+ return context;
+ }
// 用怪物名查CD
let refreshCD = null;
@@ -811,7 +979,14 @@ async function processMonsterPathEntry(entry, context) {
const currentTime = getCurrentTimeInHours();
const lastEndTime = getLastRunEndTime(monsterName, pathName, recordDir, noRecordDir);
const isPathValid = checkPathNameFrequency(monsterName, pathName, recordDir);
- const perTime = noRecord ? null : calculatePerTime(monsterName, pathName, recordDir);
+ const perTime = noRecord ? null : calculatePerTime(
+ monsterName,
+ pathName,
+ recordDir,
+ noRecordDir,
+ false,
+ pathRecordCache // 新增:传递缓存
+ );
log.info(`${CONSTANTS.LOG_MODULES.PATH}怪物路径${pathName} 单个材料耗时:${perTime ?? '忽略'}`);
@@ -930,7 +1105,8 @@ async function processNormalPathEntry(entry, context) {
CDCategories, timeCost, recordDir, noRecordDir,
materialCategoryMap, flattenedLowCountMaterials,
currentMaterialName: prevMaterialName,
- materialAccumulatedDifferences, globalAccumulatedDifferences
+ materialAccumulatedDifferences, globalAccumulatedDifferences,
+ pathRecordCache // 新增:从上下文取缓存
} = context;
// 用材料名查CD
@@ -956,7 +1132,14 @@ async function processNormalPathEntry(entry, context) {
const currentTime = getCurrentTimeInHours();
const lastEndTime = getLastRunEndTime(resourceName, pathName, recordDir, noRecordDir);
const isPathValid = checkPathNameFrequency(resourceName, pathName, recordDir);
- const perTime = noRecord ? null : calculatePerTime(resourceName, pathName, recordDir);
+ const perTime = noRecord ? null : calculatePerTime(
+ resourceName,
+ pathName,
+ recordDir,
+ noRecordDir,
+ false,
+ pathRecordCache // 新增:传递缓存
+ );
log.info(`${CONSTANTS.LOG_MODULES.PATH}材料路径${pathName} 单个材料耗时:${perTime ?? '忽略'}`);
@@ -1066,42 +1249,84 @@ async function processNormalPathEntry(entry, context) {
* @param {string} recordDir - 记录目录
* @param {string} noRecordDir - 无记录目录
* @param {string} imagesDir - 图像目录
+ * @param {string} endTimeStr - 指定终止时间
* @returns {Object} 处理结果
*/
-async function processAllPaths(allPaths, CDCategories, materialCategoryMap, timeCost, flattenedLowCountMaterials, currentMaterialName, recordDir, noRecordDir, imagesDir) {
+async function processAllPaths(allPaths, CDCategories, materialCategoryMap, timeCost, flattenedLowCountMaterials, currentMaterialName, recordDir, noRecordDir, imagesDir, endTimeStr) {
try {
// 初始化累加器
let foodExpAccumulator = {};
const globalAccumulatedDifferences = {};
const materialAccumulatedDifferences = {};
+ // 新增:单路径处理周期内的记录缓存(处理完所有路径后自动释放)
+ const pathRecordCache = {};
let context = {
CDCategories, timeCost, recordDir, noRecordDir, imagesDir,
materialCategoryMap, flattenedLowCountMaterials,
currentMaterialName, materialAccumulatedDifferences,
- globalAccumulatedDifferences
+ globalAccumulatedDifferences,
+ pathRecordCache // 上下文加入缓存,供子函数使用
};
for (const entry of allPaths) {
- if (state.cancelRequested) break;
+ // 优先响应手动终止指令(原有逻辑保留)
+ if (state.cancelRequested) {
+ log.warn(`${CONSTANTS.LOG_MODULES.PATH}检测到手动终止指令,停止路径处理`);
+ break;
+ }
+
+ // 核心修改:仅当endTimeStr有值时才执行定时终止判断(默认不执行)
+ let skipPath = false;
+ if (endTimeStr) { // 只有用户显式配置了终止时间,才进入判断
+ const isValidEndTime = /^\d{1,2}[::]\d{1,2}$/.test(endTimeStr);
+ if (isValidEndTime) {
+ const remainingMinutes = getRemainingMinutesToEndTime(endTimeStr);
+ if (remainingMinutes <= 0) {
+ log.warn(`${CONSTANTS.LOG_MODULES.MAIN}已过指定终止时间(${endTimeStr}),停止路径处理`);
+ state.cancelRequested = true;
+ break;
+ }
+
+ const pathTotalTimeSec = estimatePathTotalTime(
+ entry,
+ recordDir,
+ noRecordDir,
+ pathRecordCache
+ );
+ const pathTotalTimeMin = pathTotalTimeSec / 60;
+ const requiredMin = pathTotalTimeMin + 2;
+
+ if (remainingMinutes <= requiredMin) {
+ log.warn(`${CONSTANTS.LOG_MODULES.MAIN}时间不足:剩余${remainingMinutes}分钟,需${requiredMin}分钟(含2分钟空闲)`);
+ state.cancelRequested = true;
+ skipPath = true;
+ break;
+ } else {
+ log.debug(`${CONSTANTS.LOG_MODULES.MAIN}时间充足:剩余${remainingMinutes}分钟,需${requiredMin}分钟`);
+ }
+ } else {
+ log.warn(`${CONSTANTS.LOG_MODULES.MAIN}终止时间格式无效(${endTimeStr}),跳过定时判断`);
+ }
+ } // 若endTimeStr为null(默认),则完全跳过定时终止逻辑
+
+ if (skipPath) break;
+
+ // 原有路径处理逻辑(仅新增缓存传递)
+ const { path: pathingFilePath, resourceName, monsterName } = entry;
+ log.info(`${CONSTANTS.LOG_MODULES.PATH}开始处理路径:${basename(pathingFilePath)}`);
try {
- const { path: pathingFilePath, resourceName, monsterName } = entry;
- log.info(`${CONSTANTS.LOG_MODULES.PATH}开始处理路径:${basename(pathingFilePath)}`);
-
- // 区分路径类型并处理
if (resourceName && isFoodResource(resourceName)) {
- // 狗粮路径
const result = await processFoodPathEntry(entry, {
foodExpAccumulator,
- currentMaterialName: context.currentMaterialName
+ currentMaterialName: context.currentMaterialName,
+ pathRecordCache // 传递缓存
}, recordDir, noRecordDir);
foodExpAccumulator = result.foodExpAccumulator;
context.currentMaterialName = result.currentMaterialName;
} else if (monsterName) {
- // 怪物路径
context = await processMonsterPathEntry(entry, context);
} else if (resourceName) {
- // 普通材料路径
context = await processNormalPathEntry(entry, context);
} else {
log.warn(`${CONSTANTS.LOG_MODULES.PATH}跳过无效路径条目:${JSON.stringify(entry)}`);
@@ -1109,6 +1334,10 @@ async function processAllPaths(allPaths, CDCategories, materialCategoryMap, time
} catch (singleError) {
log.error(`${CONSTANTS.LOG_MODULES.PATH}处理路径出错,已跳过:${singleError.message}`);
await sleep(1);
+ if (state.cancelRequested) {
+ log.warn(`${CONSTANTS.LOG_MODULES.PATH}检测到终止指令,停止处理`);
+ break;
+ }
}
}
@@ -1153,9 +1382,22 @@ async function processAllPaths(allPaths, CDCategories, materialCategoryMap, time
function classifyNormalPathFiles(pathingDir, targetResourceNames, lowCountMaterialNames, cdMaterialNames) {
const pathingFilePaths = readAllFilePaths(pathingDir, 0, 3, ['.json']);
const pathEntries = pathingFilePaths.map(path => {
- const { materialName } = extractResourceNameFromPath(path, cdMaterialNames);
- return { path, resourceName: materialName };
- }).filter(entry => entry.resourceName);
+ const { materialName, monsterName } = extractResourceNameFromPath(path, cdMaterialNames);
+ return { path, resourceName: materialName, monsterName }; // 新增monsterName字段
+ }).filter(entry => {
+ // 新增:过滤超量材料对应的路径(包括怪物材料)
+ if (entry.monsterName) {
+ // 怪物路径:检查其所有材料是否都超量
+ const monsterMaterials = monsterToMaterials[entry.monsterName] || [];
+ const allExcess = monsterMaterials.every(mat => excessMaterialNames.includes(mat));
+ return !allExcess; // 若所有材料超量则过滤该路径
+ }
+ if (entry.resourceName) {
+ // 普通材料路径:检查自身是否超量
+ return !excessMaterialNames.includes(entry.resourceName);
+ }
+ return false;
+ });
if (pathEntries.length > 0) {
log.info(`${CONSTANTS.LOG_MODULES.PATH}\n===== 匹配到的材料路径列表 =====`);
@@ -1170,16 +1412,28 @@ function classifyNormalPathFiles(pathingDir, targetResourceNames, lowCountMateri
const prioritizedPaths = [];
const normalPaths = [];
for (const entry of pathEntries) {
- if (targetResourceNames.includes(entry.resourceName)) {
- prioritizedPaths.push(entry);
- } else if (lowCountMaterialNames.includes(entry.resourceName)) {
- normalPaths.push(entry);
+ if (entry.monsterName) {
+ // 怪物路径:检查是否包含有效目标材料
+ const monsterMaterials = monsterToMaterials[entry.monsterName] || [];
+ const hasValidTarget = monsterMaterials.some(mat => targetResourceNames.includes(mat) || lowCountMaterialNames.includes(mat));
+ if (hasValidTarget) {
+ prioritizedPaths.push(entry);
+ } else {
+ normalPaths.push(entry);
+ }
+ } else if (entry.resourceName) {
+ // 普通材料路径
+ if (targetResourceNames.includes(entry.resourceName)) {
+ prioritizedPaths.push(entry);
+ } else if (lowCountMaterialNames.includes(entry.resourceName)) {
+ normalPaths.push(entry);
+ }
}
}
// 按低数量排序
normalPaths.sort((a, b) => {
- const indexA = lowCountMaterialNames.indexOf(a.resourceName);
- const indexB = lowCountMaterialNames.indexOf(b.resourceName);
+ const indexA = lowCountMaterialNames.indexOf(a.resourceName) || lowCountMaterialNames.indexOf(a.monsterName ? monsterToMaterials[a.monsterName]?.[0] : '');
+ const indexB = lowCountMaterialNames.indexOf(b.resourceName) || lowCountMaterialNames.indexOf(b.monsterName ? monsterToMaterials[b.monsterName]?.[0] : '');
return indexA - indexB;
});
return prioritizedPaths.concat(normalPaths);
@@ -1237,14 +1491,25 @@ async function generateAllPaths(pathingDir, targetResourceNames, cdMaterialNames
let processedMonsterPaths = monsterPaths;
let pathingMaterialCounts = [];
- if (normalPaths.length > 0) {
- log.info(`${CONSTANTS.LOG_MODULES.PATH}[普通材料] 执行背包扫描→低数量筛选`);
- pathingMaterialCounts = await MaterialPath(materialCategoryMap);
+ if (normalPaths.length > 0 || monsterPaths.length > 0) { // 包含怪物路径时也需要扫描
+ // 优化:一次扫描获取全量材料数量,同时服务于怪物和普通材料
+ log.info(`${CONSTANTS.LOG_MODULES.PATH}[材料扫描] 执行一次全量背包扫描(服务于怪物+普通路径)`);
+ const allMaterialCounts = await MaterialPath(materialCategoryMap); // 仅一次扫描
+ pathingMaterialCounts = allMaterialCounts; // 普通材料直接复用扫描结果
+
+ // 1. 怪物材料筛选(复用全量扫描结果)
+ log.info(`${CONSTANTS.LOG_MODULES.MONSTER}[怪物材料] 基于全量扫描结果筛选有效材料`);
+ const filteredMonsterMaterials = filterLowCountMaterials(allMaterialCounts.flat(), materialCategoryMap); // 复用结果
+ const validMonsterMaterialNames = filteredMonsterMaterials.map(m => m.name);
+ log.info(`${CONSTANTS.LOG_MODULES.MONSTER}[怪物材料] 筛选后有效材料:${validMonsterMaterialNames.join('、')}`);
+
+ // 2. 普通材料筛选(同样复用全量扫描结果,无需再次扫描)
if (pathingMode.onlyCategory) {
state.cancelRequested = true;
return { allPaths: [], pathingMaterialCounts };
- }
- const lowCountMaterialsFiltered = filterLowCountMaterials(pathingMaterialCounts.flat(), materialCategoryMap);
+}
+ log.info(`${CONSTANTS.LOG_MODULES.PATH}[普通材料] 基于全量扫描结果筛选低数量材料`);
+ const lowCountMaterialsFiltered = filterLowCountMaterials(allMaterialCounts.flat(), materialCategoryMap); // 复用结果
const flattenedLowCountMaterials = lowCountMaterialsFiltered.flat().sort((a, b) => a.count - b.count);
const lowCountMaterialNames = flattenedLowCountMaterials.map(material => material.name);
@@ -1283,7 +1548,8 @@ async function generateAllPaths(pathingDir, targetResourceNames, cdMaterialNames
source: processedMonsterPaths,
filter: e => {
const materials = monsterToMaterials[e.monsterName] || [];
- return !materials.some(mat => targetResourceNames.includes(mat));
+ return !materials.some(mat => targetResourceNames.includes(mat)) &&
+ materials.some(mat => !excessMaterialNames.includes(mat)); // 排除所有材料超量的怪物
}
},
// 6. 剩余普通材料
@@ -1358,9 +1624,6 @@ ${Object.entries(totalDifferences).map(([name, diff]) => ` ${name}: +${diff}个
=========================================\n\n`;
writeContentToFile(summaryPath, content);
log.info(`${CONSTANTS.LOG_MODULES.RECORD}最终汇总已记录至 ${summaryPath}`);
- // ==============================================
- // 新增:汇总后强制触发结束指令,确保程序终止
- // ==============================================
state.completed = true; // 标记任务完全完成
state.cancelRequested = true; // 终止所有后台任务(如图像点击、OCR)
}
@@ -1504,7 +1767,8 @@ ${Object.entries(totalDifferences).map(([name, diff]) => ` ${name}: +${diff}个
currentMaterialName,
CONSTANTS.RECORD_DIR,
CONSTANTS.NO_RECORD_DIR,
- CONSTANTS.IMAGES_DIR
+ CONSTANTS.IMAGES_DIR,
+ endTimeStr // 传递终止时间
);
// 汇总结果
diff --git a/repo/js/背包材料统计/manifest.json b/repo/js/背包材料统计/manifest.json
index ece08cf61..fa7ed6070 100644
--- a/repo/js/背包材料统计/manifest.json
+++ b/repo/js/背包材料统计/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "背包统计采集系统",
- "version": "2.53",
+ "version": "2.54",
"bgi_version": "0.44.8",
"description": "可统计背包养成道具、部分食物、素材的数量;根据设定数量、根据材料刷新CD执行挖矿、采集、刷怪等的路径。优势:\n+ 1. 自动判断材料CD,不需要管材料CD有没有好;\n+ 2. 可以随意添加路径,能自动排除低效、无效路径;\n+ 3. 有独立名单识别,不会交互路边的npc或是神像;可自定义识别名单,具体方法看【问题解答】增减识别名单\n+ 4. 有实时的弹窗模块,提供了常见的几种:路边信件、过期物品、月卡、调查;\n+ 5. 可识别爆满的路径材料,自动屏蔽;更多详细内容查看readme.md",
"saved_files": [
diff --git a/repo/js/背包材料统计/materialsCD/怪物.txt b/repo/js/背包材料统计/materialsCD/怪物.txt
index a25c9562d..327b276cc 100644
--- a/repo/js/背包材料统计/materialsCD/怪物.txt
+++ b/repo/js/背包材料统计/materialsCD/怪物.txt
@@ -1,2 +1,3 @@
-4点:丘丘人,丘丘萨满,丘丘人射手,丘丘暴徒,丘丘王,丘丘游侠,愚人众先遣队,萤术士,债务处理人,冬国仕女,愚人众风役人,愚人众特辖队,盗宝团,野伏众,海乱鬼,镀金旅团,黑蛇众,黯色空壳,部族龙形武士,遗迹机械,元能构装体,遗迹机兵,遗迹龙兽,发条机关,秘源机兵,巡陆艇,史莱姆,骗骗花,飘浮灵,蕈兽,浊水幻灵,原海异种,隙境原体,魔像禁卫,大灵显化身,熔岩游像,龙蜥,圣骸兽,玄文兽,纳塔龙众,炉壳山鼬,蕴光异兽,深渊法师,深渊使徒,兽境群狼,深邃拟覆叶,荒野狂猎,霜夜灵嗣,地脉花,
+4点:丘丘人,丘丘萨满,丘丘人射手,丘丘暴徒,丘丘王,丘丘游侠,愚人众先遣队,萤术士,债务处理人,冬国仕女,愚人众风役人,愚人众特辖队,盗宝团,野伏众,海乱鬼,镀金旅团,黑蛇众,黯色空壳,部族龙形武士,遗迹机械,元能构装体,遗迹机兵,遗迹龙兽,发条机关,秘源机兵,巡陆艇,史莱姆,骗骗花,飘浮灵,蕈兽,浊水幻灵,原海异种,隙境原体,魔像禁卫,大灵显化身,熔岩游像,龙蜥,圣骸兽,玄文兽,纳塔龙众,炉壳山鼬,蕴光异兽,深渊法师,深渊使徒,兽境群狼,深邃拟覆叶,荒野狂猎,霜夜灵嗣,地脉花,锄地
+
diff --git a/repo/js/背包材料统计/settings.json b/repo/js/背包材料统计/settings.json
index a1dca168c..06e6dc5e4 100644
--- a/repo/js/背包材料统计/settings.json
+++ b/repo/js/背包材料统计/settings.json
@@ -109,10 +109,15 @@
"type": "input-text",
"label": "====================\n\n采用的CD分类(默认:全部) 举例:采集,怪物,木材"
},
+ {
+ "name": "CurrentTime",
+ "type": "input-text",
+ "label": "根据CD分类来加载路径文件,具体看materialsCD目录\n====================\n\n终止时刻(默认:不执行) 例:4:00"
+ },
{
"name": "PickCategories",
"type": "input-text",
- "label": "根据CD分类来加载路径文件,具体看materialsCD目录\n====================\n\n采用的识别名单(默认:全部) 举例:交互,采集,宝箱"
+ "label": "====================\n\n采用的识别名单(默认:全部) 举例:交互,采集,宝箱"
},
{
"name": "ExceedCount",