mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-25 04:59:52 +08:00
js:采集cd管理和锄地一条龙优化 (#2566)
* js:锄地一条龙 1.优化坐标检验,新增与地图追踪中坐标比较 2.新增自定义标签处理,允许填自定义标签 3.增加怪物数量警告,配置不合理时将警告去读readme 4.增加分辨率警告,不为1080p时警告 5.修改默认匹配阈值为0.9 * js:采集cd管理 1.修改默认拾取阈值为0.9 2.优化路径组循环判定 3.增加校验逻辑,需要与终点距离30以内才视为完成路线 * Update main.js 1.优化加工速度 2.更正变量名 * Update 酸奶油1.png
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//当前js版本1.13.1
|
||||
//当前js版本1.14.0
|
||||
|
||||
let timeMoveUp;
|
||||
let timeMoveDown;
|
||||
@@ -56,6 +56,12 @@ let state;
|
||||
const accountName = settings.accountName || "默认账户";
|
||||
let pathings;
|
||||
let localeWorks;
|
||||
|
||||
const priorityTags = (settings.priorityTags || "").split(",").map(tag => tag.trim()).filter(tag => tag.length > 0);
|
||||
const excludeTags = (settings.excludeTags || "").split(",").map(tag => tag.trim()).filter(tag => tag.length > 0);
|
||||
|
||||
let runningFailCount = 0;
|
||||
|
||||
(async function () {
|
||||
targetItems = await loadTargetItems();
|
||||
//自定义配置处理
|
||||
@@ -102,8 +108,6 @@ let localeWorks;
|
||||
const groupTags = groupSettings.map(str => str.split(',').filter(Boolean));
|
||||
groupTags[0] = [...new Set(groupTags.flat())];
|
||||
|
||||
const priorityTags = (settings.priorityTags || "").split(",").map(tag => tag.trim()).filter(tag => tag.length > 0);
|
||||
const excludeTags = (settings.excludeTags || "").split(",").map(tag => tag.trim()).filter(tag => tag.length > 0);
|
||||
if (pickup_Mode != "模板匹配拾取,拾取狗粮和怪物材料" && pickup_Mode != "模板匹配拾取,只拾取狗粮") {
|
||||
excludeTags.push("沙暴");
|
||||
log.warn("拾取模式不是模板匹配,无法处理沙暴路线,自动排除所有沙暴路线");
|
||||
@@ -129,7 +133,7 @@ let localeWorks;
|
||||
}
|
||||
|
||||
//预处理路线并建立对象
|
||||
pathings = await processPathings();
|
||||
pathings = await processPathings(groupTags);
|
||||
|
||||
//按照用户配置标记路线
|
||||
await markPathings(pathings, groupTags, priorityTags, excludeTags);
|
||||
@@ -177,6 +181,28 @@ let localeWorks;
|
||||
log.info('当前队伍:' + teamStr);
|
||||
|
||||
switch (true) {
|
||||
case targetEliteNum <= 350 && targetMonsterNum >= 100:
|
||||
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
|
||||
await sleep(5000);
|
||||
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
|
||||
await sleep(5000);
|
||||
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
|
||||
await sleep(5000);
|
||||
log.warn("目标怪物数量配置不合理,建议重新阅读 readme 相关部分");
|
||||
await sleep(5000);
|
||||
break;
|
||||
|
||||
case width !== 1920 || height !== 1080:
|
||||
log.warn("游戏窗口非 1920×1080,可能导致图像识别失败,如果执意使用可能造成拾取等行为异常,后果自负");
|
||||
await sleep(5000);
|
||||
log.warn("游戏窗口非 1920×1080,可能导致图像识别失败,如果执意使用可能造成拾取等行为异常,后果自负");
|
||||
await sleep(5000);
|
||||
log.warn("游戏窗口非 1920×1080,可能导致图像识别失败,如果执意使用可能造成拾取等行为异常,后果自负");
|
||||
await sleep(5000);
|
||||
log.warn("游戏窗口非 1920×1080,可能导致图像识别失败,如果执意使用可能造成拾取等行为异常,后果自负");
|
||||
await sleep(5000);
|
||||
break;
|
||||
|
||||
case ['钟离', '芙宁娜', '纳西妲', '雷电将军'].every(n => avatars.includes(n)):
|
||||
log.warn("四神队不适合锄地,建议重新阅读 readme 相关部分");
|
||||
await sleep(10000);
|
||||
@@ -210,7 +236,7 @@ let localeWorks;
|
||||
})();
|
||||
|
||||
//预处理路线,建立对象
|
||||
async function processPathings() {
|
||||
async function processPathings(groupTags) {
|
||||
// 读取怪物信息
|
||||
const monsterInfoContent = await file.readText("assets/monsterInfo.json");
|
||||
const monsterInfoObject = JSON.parse(monsterInfoContent);
|
||||
@@ -290,53 +316,22 @@ async function processPathings() {
|
||||
}
|
||||
}
|
||||
|
||||
const allTags = groupTags[0]; // 已经是 [...new Set(...)] 的结果
|
||||
// 2. 待匹配文本:路径名 + 描述
|
||||
const textToMatch = (pathing.fullPath + " " + (description || ""));
|
||||
// 3. 反查补 tag
|
||||
allTags.forEach(tag => {
|
||||
if (textToMatch.includes(tag)) {
|
||||
pathing.tags.push(tag);
|
||||
}
|
||||
});
|
||||
|
||||
// 去除重复标签
|
||||
pathing.tags = [...new Set(pathing.tags)];
|
||||
// 处理 map_name 属性
|
||||
pathing.map_name = parsedContent.info?.map_name || "Teyvat"; // 如果有 map_name,则使用其值,否则默认为 "Teyvat"
|
||||
}
|
||||
|
||||
//优先使用index中的数据
|
||||
// 更新 pathings 的函数,接受索引文件路径作为参数
|
||||
async function updatePathings(indexFilePath) {
|
||||
try {
|
||||
// 读取文件内容
|
||||
const fileContent = await file.readText(indexFilePath);
|
||||
// 将文件内容解析为 JSON 格式
|
||||
const data = JSON.parse(fileContent);
|
||||
|
||||
// 遍历解析后的 JSON 数据
|
||||
for (const item of data) {
|
||||
// 检查 pathings 中是否存在某个对象的 fileName 属性与 item.fileName 相同
|
||||
const existingPathing = pathings.find(pathing => pathing.fileName === item.fileName);
|
||||
|
||||
if (existingPathing) {
|
||||
// 直接覆盖其他字段,但先检查是否存在有效值
|
||||
if (item.时间 !== undefined) existingPathing.t = item.时间;
|
||||
if (item.精英摩拉 !== undefined) existingPathing.mora_e = item.精英摩拉;
|
||||
if (item.小怪摩拉 !== undefined) existingPathing.mora_m = item.小怪摩拉;
|
||||
if (item.小怪数量 !== undefined) existingPathing.m = item.小怪数量;
|
||||
if (item.精英数量 !== undefined) existingPathing.e = item.精英数量;
|
||||
|
||||
// 使用 Set 来存储 tags,避免重复项
|
||||
const tagsSet = new Set(existingPathing.tags);
|
||||
for (const key in item) {
|
||||
if (key !== "fileName" && key !== "时间" && key !== "精英摩拉" && key !== "小怪摩拉" && key !== "小怪数量" && key !== "精英数量") {
|
||||
if (item[key] === 1) {
|
||||
tagsSet.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
existingPathing.tags = Array.from(tagsSet);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Error:", error);
|
||||
}
|
||||
}
|
||||
//await updatePathings("assets/index1.json");
|
||||
//await updatePathings("assets/index2.json");
|
||||
|
||||
for (const pathing of pathings) {
|
||||
if (!settings.disableSelfOptimization && pathing.records) {
|
||||
//如果用户没有禁用自动优化,则参考运行记录更改预期用时
|
||||
@@ -1005,9 +1000,9 @@ async function loadTargetItems() {
|
||||
let itsThreshold;
|
||||
if (match) {
|
||||
const val = parseFloat(match[1]);
|
||||
itsThreshold = (!isNaN(val) && val >= 0 && val <= 1) ? val : 0.85;
|
||||
itsThreshold = (!isNaN(val) && val >= 0 && val <= 1) ? val : 0.9;
|
||||
} else {
|
||||
itsThreshold = 0.85;
|
||||
itsThreshold = 0.9;
|
||||
}
|
||||
it.roi.Threshold = itsThreshold;
|
||||
it.roi.InitTemplate();
|
||||
@@ -1313,7 +1308,6 @@ async function copyPathingsByGroup(pathings) {
|
||||
async function processPathingsByGroup(pathings, accountName) {
|
||||
let lastX = 0;
|
||||
let lastY = 0;
|
||||
let runningFailCount = 0;
|
||||
|
||||
// 定义路径组名称到组号的映射(10 个)
|
||||
const groupMapping = {
|
||||
@@ -1432,27 +1426,47 @@ async function processPathingsByGroup(pathings, accountName) {
|
||||
}
|
||||
await fakeLog(`${pathing.fileName}`, false, false, 0);
|
||||
|
||||
let fileEndX = 0, fileEndY = 0;
|
||||
try {
|
||||
const raw = file.readTextSync(pathing.fullPath);
|
||||
const json = JSON.parse(raw);
|
||||
if (Array.isArray(json.positions)) {
|
||||
for (let i = json.positions.length - 1; i >= 0; i--) {
|
||||
const p = json.positions[i];
|
||||
if (p.type !== 'orientation' &&
|
||||
typeof p.x === 'number' &&
|
||||
typeof p.y === 'number') {
|
||||
fileEndX = p.x;
|
||||
fileEndY = p.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) { /* 读文件失败就留 0,0 继续走后面逻辑 */ }
|
||||
|
||||
try {
|
||||
await genshin.returnMainUi();
|
||||
const miniMapPosition = await genshin.getPositionFromMap(pathing.map_name);
|
||||
// 比较坐标
|
||||
const diffX = Math.abs(lastX - miniMapPosition.X);
|
||||
const diffY = Math.abs(lastY - miniMapPosition.Y);
|
||||
const endDiffX = Math.abs(fileEndX - miniMapPosition.X);
|
||||
const endDiffY = Math.abs(fileEndY - miniMapPosition.Y);
|
||||
|
||||
lastX = miniMapPosition.X;
|
||||
lastY = miniMapPosition.Y;
|
||||
if ((diffX + diffY) < 5) {
|
||||
|
||||
if ((diffX + diffY) < 5 || (endDiffX + endDiffY) > 30) {
|
||||
runningFailCount++;
|
||||
} else {
|
||||
runningFailCount = 0;
|
||||
}
|
||||
//log.info(`当前位于${pathing.map_name}地图的(${miniMapPosition.X},${miniMapPosition.Y},距离上次距离${(diffX + diffY)}`);
|
||||
} catch (error) {
|
||||
log.error(`获取坐标时发生错误:${error.message}`);
|
||||
runningFailCount++;
|
||||
}
|
||||
|
||||
if (runningFailCount >= 1) {
|
||||
log.error("出发点与终点过于接近,或坐标获取异常,不记录运行数据");
|
||||
log.error("出发点与终点过于接近,终点偏差大于30,或坐标获取异常,不记录运行数据");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "锄地一条龙",
|
||||
"version": "1.13.1",
|
||||
"version": "1.14.0",
|
||||
"description": "一站式解决自动化锄地,支持只拾取狗粮,请仔细阅读README.md后使用",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
{
|
||||
"name": "tagsForGroup1",
|
||||
"type": "input-text",
|
||||
"label": "允许使用的标签:\n水免,次数盾,高危,传奇,蕈兽,小怪,沙暴\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签",
|
||||
"label": "允许使用的标签:\n水免,次数盾,高危,传奇,蕈兽,小怪,沙暴]\n允许使用自定义标签,文件路径或描述包含时将会视为路线含有该标签\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签",
|
||||
"default": "蕈兽,传奇"
|
||||
},
|
||||
{
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -99,6 +99,8 @@ const mainUiRo = RecognitionObject.TemplateMatch(mainUITemplate, 0, 0, 150, 150)
|
||||
|
||||
let underWater = false;
|
||||
|
||||
let checkInterval = +settings.checkInterval || 50;
|
||||
|
||||
(async function () {
|
||||
/* ===== 零基构建 settings.json(BEGIN) ===== */
|
||||
const SETTINGS_FILE = `settings.json`;
|
||||
@@ -194,6 +196,12 @@ let underWater = false;
|
||||
"type": "input-text",
|
||||
"label": "食材数量\n数量对应上方的食材\n用中文逗号,分隔"
|
||||
},
|
||||
{
|
||||
"name": "checkInterval",
|
||||
"type": "input-text",
|
||||
"label": "食材加工中的识别间隔(毫秒),设备反应较慢出现识别错误时适当调大",
|
||||
"default": "50"
|
||||
},
|
||||
{
|
||||
"name": "setTimeMode",
|
||||
"type": "select",
|
||||
@@ -832,7 +840,10 @@ let underWater = false;
|
||||
|
||||
/* 4-4 计算CD(掉落材料决定)*/
|
||||
const timeDiff = new Date() - startTime;
|
||||
if (timeDiff > 3000) {
|
||||
let pathRes = isArrivedAtEndPoint(filePath);
|
||||
|
||||
// >>> 仅当 >3s 才更新 CD 并立即写回整条记录(含 history) <<<
|
||||
if (timeDiff > 3000 && pathRes) {
|
||||
/* 1) 如果runPickupLog中不含优先材料,则按其他材料查找,使用最晚刷新时间 */
|
||||
let hasPriority = state.runPickupLog.some(name => priorityItemSet.has(name));
|
||||
let hitMaterials;
|
||||
@@ -901,15 +912,14 @@ let underWater = false;
|
||||
|
||||
await appendDailyPickup(state.runPickupLog);
|
||||
state.runPickupLog = [];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
let runnedAnyPath = true;
|
||||
let loopattempts = 0;
|
||||
// ==================== 路径组循环 ====================
|
||||
while (runnedAnyPath) {
|
||||
runnedAnyPath = false;
|
||||
while (loopattempts < 2) {
|
||||
loopattempt++;
|
||||
if (await isTimeRestricted(settings.timeRule, 10)) break;
|
||||
for (let i = 1; i <= groupCount; i++) {
|
||||
if (await isTimeRestricted(settings.timeRule, 10)) break;
|
||||
@@ -1117,7 +1127,6 @@ let underWater = false;
|
||||
|
||||
state.running = true;
|
||||
const pickupTask = recognizeAndInteract();
|
||||
runnedAnyPath = true;
|
||||
|
||||
log.info(`当前进度:路径组${i} ${folder} ${fileName} 为第 ${groupFiles.indexOf(filePath) + 1}/${groupFiles.length} 个`);
|
||||
log.info(`当前路线分均效率为 ${(filePath._efficiency ?? 0).toFixed(2)}`);
|
||||
@@ -1146,8 +1155,10 @@ let underWater = false;
|
||||
state.running = false;
|
||||
await pickupTask;
|
||||
|
||||
let pathRes = isArrivedAtEndPoint(filePath.fullPath);
|
||||
|
||||
// >>> 仅当 >3s 才更新 CD 并立即写回整条记录(含 history) <<<
|
||||
if (timeDiff > 3000) {
|
||||
if (timeDiff > 3000 && pathRes) {
|
||||
let newTimestamp = new Date(startTime);
|
||||
|
||||
switch (currentCdType) {
|
||||
@@ -1199,7 +1210,7 @@ let underWater = false;
|
||||
// 清空本次记录
|
||||
state.runPickupLog = [];
|
||||
|
||||
log.info(`本任务执行大于3秒,cd信息已更新,下一次可用时间为 ${newTimestamp.toLocaleString()}`);
|
||||
log.info(`本任务cd信息已更新,下一次可用时间为 ${newTimestamp.toLocaleString()}`);
|
||||
}
|
||||
}
|
||||
log.info(`路径组${groupNumber} 的所有任务运行完成`);
|
||||
@@ -1208,6 +1219,7 @@ let underWater = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -1433,9 +1445,9 @@ async function loadTargetItems() {
|
||||
/* ---------- 1. 解析小括号阈值 ---------- */
|
||||
const match = it.fullPath.match(/[((](.*?)[))]/);
|
||||
const itsThreshold = (match => {
|
||||
if (!match) return 0.85;
|
||||
if (!match) return 0.9;
|
||||
const v = parseFloat(match[1]);
|
||||
return !isNaN(v) && v >= 0 && v <= 1 ? v : 0.85;
|
||||
return !isNaN(v) && v >= 0 && v <= 1 ? v : 0.9;
|
||||
})(match);
|
||||
it.roi.Threshold = itsThreshold;
|
||||
it.roi.InitTemplate();
|
||||
@@ -1807,12 +1819,12 @@ async function ingredientProcessing() {
|
||||
"奶酪", "培根", "香肠"
|
||||
]);
|
||||
if (targetFoods.has(Foods[i])) {
|
||||
if (await clickPNG("全部领取", 10)) {
|
||||
if (await clickPNG("全部领取", 3)) {
|
||||
await clickPNG("点击空白区域继续");
|
||||
await findPNG("食材加工2");
|
||||
await sleep(100);
|
||||
}
|
||||
let res1 = await clickPNG(Foods[i] + "1", 10);
|
||||
let res1 = await clickPNG(Foods[i] + "1", 5);
|
||||
|
||||
if (res1) {
|
||||
log.info(`${Foods[i]}已找到`);
|
||||
@@ -1837,7 +1849,7 @@ async function ingredientProcessing() {
|
||||
click(item.x, item.y);
|
||||
await sleep(200);
|
||||
click(item.x, item.y);
|
||||
if (await findPNG(Foods[i] + "2", 15)) {
|
||||
if (await findPNG(Foods[i] + "2", 5)) {
|
||||
log.info(`${Foods[i]}已找到`);
|
||||
res1 = true;
|
||||
break;
|
||||
@@ -1942,7 +1954,7 @@ async function appendDailyPickup(pickupLog) {
|
||||
}
|
||||
}
|
||||
|
||||
async function clickPNG(png, maxAttempts = 60) {
|
||||
async function clickPNG(png, maxAttempts = 20) {
|
||||
//log.info(`调试-点击目标${png},重试次数${maxAttempts}`);
|
||||
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
|
||||
pngRo.Threshold = 0.95;
|
||||
@@ -1950,7 +1962,7 @@ async function clickPNG(png, maxAttempts = 60) {
|
||||
return await findAndClick(pngRo, true, maxAttempts);
|
||||
}
|
||||
|
||||
async function findPNG(png, maxAttempts = 60) {
|
||||
async function findPNG(png, maxAttempts = 20) {
|
||||
//log.info(`调试-识别目标${png},重试次数${maxAttempts}`);
|
||||
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
|
||||
pngRo.Threshold = 0.95;
|
||||
@@ -1963,9 +1975,48 @@ async function findAndClick(target, doClick = true, maxAttempts = 60) {
|
||||
const rg = captureGameRegion();
|
||||
try {
|
||||
const res = rg.find(target);
|
||||
if (res.isExist()) { await sleep(200); if (doClick) { res.click(); } return true; }
|
||||
if (res.isExist()) { await sleep(checkInterval * 2 + 50); if (doClick) { res.click(); } return true; }
|
||||
} finally { rg.dispose(); }
|
||||
if (i < maxAttempts - 1) await sleep(50);
|
||||
if (i < maxAttempts - 1) await sleep(checkInterval);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前人物是否已到达指定路线的终点
|
||||
* @param {string} fullPath 路线文件完整路径(.json)
|
||||
* @returns {boolean} true = 已到达;false = 未到达/读文件失败/取坐标失败
|
||||
*/
|
||||
function isArrivedAtEndPoint(fullPath) {
|
||||
try {
|
||||
/* 1. 读路线文件,取终点坐标 */
|
||||
const raw = file.readTextSync(fullPath);
|
||||
const json = JSON.parse(raw);
|
||||
if (!Array.isArray(json.positions)) return false;
|
||||
|
||||
let endX = 0, endY = 0;
|
||||
for (let i = json.positions.length - 1; i >= 0; i--) {
|
||||
const p = json.positions[i];
|
||||
if (p.type !== 'orientation' &&
|
||||
typeof p.x === 'number' &&
|
||||
typeof p.y === 'number') {
|
||||
endX = p.x;
|
||||
endY = p.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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); // 同步 API
|
||||
const curX = pos.X;
|
||||
const curY = pos.Y;
|
||||
|
||||
/* 3. 曼哈顿距离 ≤30 视为到达 */
|
||||
return Math.abs(endX - curX) + Math.abs(endY - curY) <= 30;
|
||||
} catch (e) {
|
||||
/* 任何异常(读盘失败、解析失败、API 异常)都算“未到达” */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "采集cd管理",
|
||||
"version": "2.7.0",
|
||||
"version": "2.8.0",
|
||||
"bgi_version": "0.44.8",
|
||||
"description": "仅面对会操作文件和读readme的用户,基于文件夹操作自动管理采集路线的cd,会按照路径组的顺序依次运行,直到指定的时间,并会按照给定的cd类型,自动跳过未刷新的路线",
|
||||
"saved_files": [
|
||||
|
||||
Reference in New Issue
Block a user