js:采集cd管理2.10.1 (#2868)

修好了
This commit is contained in:
mno
2026-02-12 23:33:28 +08:00
committed by GitHub
parent 48228070e0
commit 4f38ddc862
7 changed files with 227 additions and 63 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,76 @@
{
"info": {
"authors": [
{
"name": "芝士贝果",
"links": "https://github.com/cheese-bagel"
}
],
"bgi_version": "0.54.1-alpha.3",
"description": "",
"enable_monster_loot_split": false,
"last_modified_time": 1765869417061,
"map_match_method": "",
"map_name": "SeaOfBygoneEras",
"name": "E00-卡皮托利姆旧宫(学习螃蟹技能)",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "walk",
"type": "teleport",
"x": 3543.8477,
"y": 1380.2609
},
{
"action": "",
"action_params": "",
"id": 2,
"move_mode": "run",
"type": "path",
"x": 3556.3594,
"y": 1374.9818
},
{
"action": "combat_script",
"action_params": "keydown(VK_LCONTROL);wait(3);keyup(VK_LCONTROL);click(middle);wait(0.5);moveby(0,700)",
"id": 3,
"move_mode": "run",
"type": "path",
"x": 3569.1558,
"y": 1379.5138
},
{
"action": "",
"action_params": "",
"id": 4,
"move_mode": "dash",
"type": "path",
"x": 3600.522,
"y": 1386.7323
},
{
"action": "combat_script",
"action_params": "attack",
"id": 5,
"move_mode": "walk",
"type": "orientation",
"x": 3613.7422,
"y": 1393.2585
},
{
"action": "combat_script",
"action_params": "attack",
"id": 6,
"move_mode": "walk",
"type": "orientation",
"x": 3614.2134,
"y": 1384.8744
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -35,13 +35,13 @@ const userSettings = {
disableJsons: settings.disableJsons || ""
};
let ingredientProcessingFood = settings.ingredientProcessingFood;
let foodCounts = settings.foodCount;
let processingIngredient = settings.processingIngredient;
let firstCook = true;
let firstsettime = true;
let lastCookTime = new Date();
let lastsettimeTime = new Date();
let lastMapName = "";
// 解析禁用名单
let disableArray = [];
@@ -89,7 +89,6 @@ let lastRoll = new Date();
let checkDelay = Math.round(findFInterval / 2);
let Foods = [];
let foodCount = [];
const FiconRo = RecognitionObject.TemplateMatch(fIcontemplate, 1102, 335, 34, 400);
FiconRo.Threshold = 0.95;
@@ -97,8 +96,6 @@ FiconRo.InitTemplate();
const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150);
let underWater = false;
let checkInterval = +settings.checkInterval || 50;
(async function () {
@@ -203,14 +200,29 @@ let checkInterval = +settings.checkInterval || 50;
"default": "100"
},
{
"name": "ingredientProcessingFood",
"type": "input-text",
"label": "食材名称\n用中文逗号分隔"
},
{
"name": "foodCount",
"type": "input-text",
"label": "食材数量\n数量对应上方的食材\n用中文逗号分隔"
"name": "processingIngredient",
"type": "multi-checkbox",
"label": "要加工的食材种类",
"default": [],
"options": [
"面粉",
"兽肉",
"鱼肉",
"神秘的肉",
"黑麦粉",
"奶油",
"熏禽肉",
"黄油",
"火腿",
"糖",
"香辛料",
"酸奶油",
"蟹黄",
"果酱",
"奶酪",
"培根",
"香肠"
]
},
{
"name": "checkInterval",
@@ -438,12 +450,9 @@ let checkInterval = +settings.checkInterval || 50;
log.error(`写入文件失败: ${recordFilePath}`);
}
if (typeof ingredientProcessingFood === 'string' && ingredientProcessingFood.trim()) {
Foods = ingredientProcessingFood
.split(/[,;\s]+/) // 支持中英文逗号、分号、空格
.map(word => word.trim())
.filter(word => word.length > 0);
}
try {
Foods = Array.from(processingIngredient);
} catch (e) { Foods = []; }
if (typeof foodCounts === 'string' && foodCounts.trim()) {
foodCount = foodCounts
@@ -667,8 +676,8 @@ let checkInterval = +settings.checkInterval || 50;
const pickedCounter = {};
priorityItemSet.forEach(n => pickedCounter[n] = 0);
/* ===== 每轮开始输出剩余物品 ===== */
log.info(`剩余目标材料 ${priorityList.map(t => `${t.itemName}*${t.count}`).join(', ')} `);
/* ===== 剩余物品 ===== */
let remaining = priorityList.map(t => `${t.itemName}*${t.count}`).join(', ');
/* 4-1 扫描 + 读 record + 前置过滤(禁用/时间/材料相关)+ 计算效率 + CD后置排除 */
const allFiles = await readFolder('pathing', true);
const rawRecord = await file.readText(`${recordFolder}/${subFolderName}/record.json`);
@@ -783,12 +792,14 @@ let checkInterval = +settings.checkInterval || 50;
}
}
log.info(`当前进度:执行路线 ${fileName},剩余优先材料:${remaining}`);
let timeNow = new Date();
if (Foods.length != 0 && (((timeNow - lastCookTime) > cookInterval) || firstCook)) {
firstCook = false;
await ingredientProcessing();
lastCookTime = new Date();
underWater = false;
lastMapName = "Teyvat";
}
if (settings.setTimeMode && settings.setTimeMode != "不调节时间" && (((timeNow - lastsettimeTime) > settimeInterval) || firstsettime)) {
@@ -804,16 +815,28 @@ let checkInterval = +settings.checkInterval || 50;
runOnce.push(fileName);
/* ================================= */
log.info(`当前进度:执行路线 ${fileName}`);
state.running = true;
const raw = file.readTextSync(filePath);
const json = JSON.parse(raw);
const mapName = (json.info?.map_name && json.info.map_name.trim()) ? json.info.map_name : 'Teyvat';
if (filePath.includes('枫丹水下')) {
log.info("当前路线为水下路线,检查螃蟹技能");
let skillRes = await findAndClick("assets/螃蟹技能图标.png", false, 1000);
if (!skillRes || lastMapName != mapName) {
log.info("识别到没有螃蟹技能或上一条路线处于其他地图,前往获取螃蟹技能");
if (mapName === "SeaOfBygoneEras") {
await pathingScript.runFile("assets/学习螃蟹技能2.json");
}
else {
"assets/学习螃蟹技能1.json";
}
}
}
lastMapName = mapName;
const pickupTask = recognizeAndInteract();
if (!underWater && filePath.includes('枫丹水下')) {
await pathingScript.runFile("assets/A00-塞洛海原(学习螃蟹技能).json");
underWater = true;
}
if (underWater && !filePath.includes('枫丹水下')) {
underWater = false;
}
try {
await pathingScript.runFile(filePath);
} catch (e) {
@@ -1116,7 +1139,7 @@ let checkInterval = +settings.checkInterval || 50;
firstCook = false;
await ingredientProcessing();
lastCookTime = new Date();
underWater = false;
lastMapName = "Teyvat";
}
if (settings.setTimeMode && settings.setTimeMode != "不调节时间" && (((timeNow - lastsettimeTime) > settimeInterval) || firstsettime)) {
@@ -1155,17 +1178,29 @@ let checkInterval = +settings.checkInterval || 50;
/* ======================================= */
state.running = true;
const raw = file.readTextSync(filePath.fullPath);
const json = JSON.parse(raw);
const mapName = (json.info?.map_name && json.info.map_name.trim()) ? json.info.map_name : 'Teyvat';
if (filePath.fullPath.includes('枫丹水下')) {
log.info("当前路线为水下路线,检查螃蟹技能");
let skillRes = await findAndClick("assets/螃蟹技能图标.png", false);
if (!skillRes || lastMapName != mapName) {
log.info("识别到没有螃蟹技能或上一条路线处于其他地图,前往获取螃蟹技能");
if (mapName === "SeaOfBygoneEras") {
await pathingScript.runFile("assets/学习螃蟹技能2.json");
}
else {
"assets/学习螃蟹技能1.json";
}
}
}
lastMapName = mapName;
const pickupTask = recognizeAndInteract();
log.info(`当前进度:路径组${i} ${folder} ${fileName}${groupFiles.indexOf(filePath) + 1}/${groupFiles.length}`);
log.info(`当前进度:执行路线 ${fileName}路径组${i} ${folder}${groupFiles.indexOf(filePath) + 1}/${groupFiles.length}`);
log.info(`当前路线分均效率为 ${(filePath._efficiency ?? 0).toFixed(2)}`);
if (!underWater && filePath.fullPath.includes('枫丹水下')) {
await pathingScript.runFile("assets/A00-塞洛海原(学习螃蟹技能).json");
underWater = true;
}
if (underWater && !filePath.fullPath.includes('枫丹水下')) {
underWater = false;
}
try {
state.runPickupLog = []; // 新路线开始前清空
await pathingScript.runFile(filePath.fullPath);
@@ -1797,7 +1832,7 @@ async function isTimeRestricted(timeRule, threshold = 5) {
/**
* 食材加工主函数,用于自动前往指定地点进行食材的加工
*
* 该函数会根据 Foods 和 foodCount 数组中的食材名称和数量,依次查找并制作对应的料食材
* 该函数会根据 Foods 数组中的食材名称,依次查找并制作对应的料食材
* 支持调味品类食材(直接在“食材加工”界面查找)
*
* @returns {Promise<void>} 无返回值,执行完所有加工流程后退出
@@ -1809,11 +1844,9 @@ async function ingredientProcessing() {
"奶酪", "培根", "香肠"
];
if (Foods.length == 0) { log.error("未选择要加工的食材"); return; }
if (Foods.length != foodCount.length) { log.error("请检查食材与对应的数量是否一致!"); return; }
const taskList = Foods.map((name, i) => `${name}*${foodCount[i]}`).join("");
const tasks = Foods.map((name, idx) => ({
const taskList = Foods.map((name) => `${name}`).join("");
const tasks = Foods.map((name) => ({
name,
count: Number(foodCount[idx]) || 0,
done: false
}));
log.info(`本次加工食材:${taskList}`);
@@ -1889,7 +1922,6 @@ async function ingredientProcessing() {
await sleep(100);
}
Foods.splice(i, 1);
foodCount.splice(i, 1);
return false;
}
@@ -1898,9 +1930,9 @@ async function ingredientProcessing() {
await findPNG("选择加工数量");
click(960, 460);
await sleep(800);
inputText(String(tasks[i].count));
inputText(String(99));
log.info(`尝试制作${tasks[i].name} ${tasks[i].count}`);
log.info(`尝试制作${tasks[i].name} 99`);
await clickPNG("确认加工");
await sleep(500);
@@ -1917,7 +1949,6 @@ async function ingredientProcessing() {
await sleep(100);
}
Foods.splice(i, 1);
foodCount.splice(i, 1);
return false;
}
@@ -1954,7 +1985,7 @@ async function ingredientProcessing() {
}
}
const remain1 = tasks.filter(t => !t.done).map(t => `${t.name}*${t.count}`).join("") || "无";
const remain1 = tasks.filter(t => !t.done).map(t => `${t.name}`).join("") || "无";
log.info(`剩余待加工食材:${remain1}`);
if (remain1 === "无") {
@@ -1980,7 +2011,7 @@ async function ingredientProcessing() {
for (const item of foodItems) {
click(item.x, item.y); await sleep(1 * checkInterval);
click(item.x, item.y); await sleep(6 * checkInterval);
click(item.x, item.y); await sleep(3 * checkInterval);
for (let round = 0; round < 5; round++) {
const rg = captureGameRegion();
@@ -2022,7 +2053,7 @@ async function ingredientProcessing() {
}
}
const remain = tasks.filter(t => !t.done).map(t => `${t.name}*${t.count}`).join("") || "无";
const remain = tasks.filter(t => !t.done).map(t => `${t.name}`).join("") || "无";
log.info(`剩余待加工食材:${remain}`);
@@ -2075,7 +2106,7 @@ async function clickPNG(png, maxAttempts = 20) {
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
pngRo.Threshold = 0.95;
pngRo.InitTemplate();
return await findAndClick(pngRo, true, maxAttempts);
return await findAndClick(pngRo, true, maxAttempts * checkInterval, checkInterval);
}
async function findPNG(png, maxAttempts = 20) {
@@ -2083,19 +2114,75 @@ async function findPNG(png, maxAttempts = 20) {
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
pngRo.Threshold = 0.95;
pngRo.InitTemplate();
return await findAndClick(pngRo, false, maxAttempts);
return await findAndClick(pngRo, false, maxAttempts * checkInterval, checkInterval);
}
async function findAndClick(target, doClick = true, maxAttempts = 60) {
for (let i = 0; i < maxAttempts; i++) {
const rg = captureGameRegion();
try {
const res = rg.find(target);
if (res.isExist()) { await sleep(checkInterval * 2 + 50); if (doClick) { res.click(); } await sleep(50); return true; }
} finally { rg.dispose(); }
if (i < maxAttempts - 1) await sleep(checkInterval);
/**
* 通用找图/找RO并可选点击支持单图片文件路径、单RO、图片文件路径数组、RO数组
* @param {string|string[]|RecognitionObject|RecognitionObject[]} target
* @param {boolean} [doClick=true] 是否点击
* @param {number} [timeout=3000] 识别时间上限ms
* @param {number} [interval=50] 识别间隔ms
* @param {number} [retType=0] 0-返回布尔1-返回 Region 结果
* @param {number} [preClickDelay=50] 点击前等待
* @param {number} [postClickDelay=50] 点击后等待
* @returns {boolean|Region} 根据 retType 返回是否成功或最终 Region
*/
async function findAndClick(target,
doClick = true,
timeout = 3000,
interval = 50,
retType = 0,
preClickDelay = 50,
postClickDelay = 50) {
try {
// 1. 统一转成 RecognitionObject 数组
let ros = [];
if (Array.isArray(target)) {
ros = target.map(t =>
(typeof t === 'string')
? RecognitionObject.TemplateMatch(file.ReadImageMatSync(t))
: t
);
} else {
ros = [(typeof target === 'string')
? RecognitionObject.TemplateMatch(file.ReadImageMatSync(target))
: target];
}
const start = Date.now();
let found = null;
while (Date.now() - start <= timeout) {
const gameRegion = captureGameRegion();
try {
// 依次尝试每一个 ro
for (const ro of ros) {
const res = gameRegion.find(ro);
if (!res.isEmpty()) { // 找到
found = res;
if (doClick) {
await sleep(preClickDelay);
res.click();
await sleep(postClickDelay);
}
break; // 成功即跳出 for
}
}
if (found) break; // 成功即跳出 while
} finally {
gameRegion.dispose();
}
await sleep(interval); // 没找到时等待
}
// 3. 按需返回
return retType === 0 ? !!found : (found || null);
} catch (error) {
log.error(`执行通用识图时出现错误:${error.message}`);
return retType === 0 ? false : null;
}
return false;
}
/**
@@ -2128,6 +2215,7 @@ function isArrivedAtEndPoint(fullPath) {
if (endX === 0 && endY === 0) return false; // 没找到有效点
/* 2. 取当前人物坐标 */
const mapName = (json.info?.map_name && json.info.map_name.trim()) ? json.info.map_name : 'Teyvat';
const pos = genshin.getPositionFromMap(mapName, 3000);
const curX = pos.X;

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "采集cd管理",
"version": "2.9.5",
"version": "2.10.1",
"bgi_version": "0.44.8",
"description": "仅面对会操作文件和读readme的用户基于文件夹操作自动管理采集路线的cd会按照路径组的顺序依次运行直到指定的时间并会按照给定的cd类型自动跳过未刷新的路线",
"saved_files": [