js:锄地一条龙 (#3063)

1.移除所有原有截图和释放逻辑,统一管理
2.重命名部分变量
3.修改部分路线
This commit is contained in:
mno
2026-04-05 15:33:55 +08:00
committed by GitHub
parent e436d062ee
commit 2db9f20309
3 changed files with 104 additions and 75 deletions

View File

@@ -43,16 +43,23 @@ const scrollRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/
//全局通用变量声明
let gameRegion;
let targetItems;
let doFurinaSwitch = false;
let lastRoll = new Date();
let shouldSwitchFurina = false;
let lastRollTime = new Date();
let blacklist = [];
let blacklistSet = new Set();
let state;
let pathings;
let localeWorks;
let lastEatBuff = 0;
let localeTimeSupported;
let lastBuffTime = 0;
let currentFood = "";
let monsterInfoObject;
const gameRegionManager = {
newGameRegion: null,
oldGameRegion: null,
lastCapture: new Date(),
isDisposing: false,
isCapturing: false
};
(async function () {
//通用预处理
@@ -65,7 +72,7 @@ let monsterInfoObject;
await switchPartyTask;
}
targetItems = await loadTargetItems();
localeWorks = await checkLocaleTimeSupport();
localeTimeSupported = await checkLocaleTimeSupport();
dispatcher.AddTrigger(new RealtimeTimer("AutoSkip"));
await loadBlacklist(true);
await rotateWarnIfAccountEmpty();
@@ -956,17 +963,17 @@ async function copyPathingsByGroup(pathings) {
* - blacklistTask背包满时OCR识别并拉黑多余物品
* - dumperTask可选接近战斗坐标时自动切人放E
* 3. 全部子任务完成后返回state.running 被置 false
* 依赖全局settings、state、pathings、targetItems、dumpers、doFurinaSwitch、lastEatBuff 等
* 依赖全局settings、state、pathings、targetItems、dumpers、shouldSwitchFurina、lastBuffTime
*/
async function runPath(fullPath, map_name, pm, pe) {
//当需要切换芙宁娜形态时,执行一次强制黑芙
if (doFurinaSwitch) {
if (shouldSwitchFurina) {
log.info("上条路线识别到白芙,开始强制切换黑芙")
doFurinaSwitch = false;
shouldSwitchFurina = false;
await pathingScript.runFile("assets/强制黑芙.json");
}
if (settings.eatBuff) {
if (new Date() - lastEatBuff > 300 * 1000) {
if (new Date() - lastBuffTime > 300 * 1000) {
// 1. 数据预处理:分割、去空、去重
let res = settings.eatBuff
.split('')
@@ -1009,7 +1016,7 @@ async function runPath(fullPath, map_name, pm, pe) {
await findAndClick("assets/使用.png");
}
await genshin.returnMainUi();
lastEatBuff = new Date();
lastBuffTime = new Date();
}
}
@@ -1142,10 +1149,10 @@ async function runPath(fullPath, map_name, pm, pe) {
}
continue;
}
if (!doFurinaSwitch) {
if (!shouldSwitchFurina) {
if (await findAndClick(whiteFurinaRo, false, 2, 3)) {
log.info("检测到白芙,本路线运行结束后切换芙宁娜形态");
doFurinaSwitch = true;
shouldSwitchFurina = true;
continue;
}
}
@@ -1175,8 +1182,7 @@ async function runPath(fullPath, map_name, pm, pe) {
let attempts = 0;
while (attempts < maxAttempts && state.running) {
try {
gameRegion.dispose();
gameRegion = captureGameRegion();
gameRegion = await getGameRegion();
const result = gameRegion.find(itemFullRo);
if (result.isExist()) {
return true;
@@ -1304,18 +1310,16 @@ async function recognizeAndInteract() {
let lastItemName = "";
let thisMoveUpTime = 0;
let lastMoveDown = 0;
gameRegion = captureGameRegion();
let itemName;
//主循环
while (state.running) {
//log.info("调试-交互拾取进行中");
gameRegion.dispose();
gameRegion = captureGameRegion();
gameRegion = await getGameRegion();
let centerYF = await findFIcon();
if (!centerYF) {
if (new Date() - lastRoll >= 200) {
lastRoll = new Date();
if (new Date() - lastRollTime >= 200) {
lastRollTime = new Date();
if (await hasScroll()) {
await keyMouseScript.runFile(`assets/滚轮下翻.json`);
}
@@ -1578,13 +1582,9 @@ async function dumper(pathFilePath, map_name) {
const maxAttempts = 10;
let attempts = 0;
let dodispose = false;
while (attempts < maxAttempts && state.running) {
try {
if (!gameRegion) {
gameRegion = captureGameRegion();
dodispose = true;
}
gameRegion = await getGameRegion()
let result = gameRegion.find(recognitionObject);
if (result.isExist()) {
return true; // 如果找到图标,返回 true
@@ -1595,9 +1595,6 @@ async function dumper(pathFilePath, map_name) {
}
attempts++; // 增加尝试次数
await sleep(200); // 每次检测间隔 200 毫秒
if (dodispose) {
gameRegion.dispose();
}
}
return false; // 如果尝试次数达到上限或取消,返回 false
}
@@ -1811,7 +1808,7 @@ async function processPathingsByGroup(pathings, accountName) {
}
// 更新路径的 cdTime
pathing.cdTime = newCDTime.toLocaleString();
if (!localeWorks) pathing.cdTime = newCDTime.toISOString();
if (!localeTimeSupported) pathing.cdTime = newCDTime.toISOString();
await updateRecords(pathings, accountName);
}
@@ -1824,7 +1821,7 @@ async function processPathingsByGroup(pathings, accountName) {
* - 为每条路线赋予 cdTime本地或UTC与最近7次运行时长
* - 拾取历史仅保留最后20个不重复项
* 若记录文件缺失则初始化为7条-1
* 依赖file、accountName、localeWorks
* 依赖file、accountName、localeTimeSupported
*/
async function initializeCdTime(pathings, accountName) {
try {
@@ -1845,7 +1842,7 @@ async function initializeCdTime(pathings, accountName) {
? new Date(entry.cdTime).toLocaleString()
: new Date(0).toLocaleString();
if (!localeWorks) pathing.cdTime = entry
if (!localeTimeSupported) pathing.cdTime = entry
? new Date(entry.cdTime).toISOString()
: new Date(0).toISOString();
// 确保当前 records 是数组
@@ -1867,7 +1864,7 @@ async function initializeCdTime(pathings, accountName) {
pathing.cdTime = new Date(0).toLocaleString();
pathing.records = new Array(7).fill(-1);
});
if (!localeWorks) pathings.forEach(pathing => {
if (!localeTimeSupported) pathings.forEach(pathing => {
pathing.cdTime = new Date(0).toISOString();
pathing.records = new Array(7).fill(-1);
});
@@ -1957,12 +1954,8 @@ async function switchPartyIfNeeded(partyName) {
*/
async function isMainUI(maxDuration = 10) {
const start = Date.now();
let dodispose = false;
while (Date.now() - start < maxDuration) {
if (!gameRegion) {
gameRegion = captureGameRegion();
dodispose = true;
}
gameRegion = await getGameRegion();
try {
const result = gameRegion.find(mainUIRo);
if (result.isExist()) return true;
@@ -1971,10 +1964,6 @@ async function isMainUI(maxDuration = 10) {
return false; // 一旦出现异常直接退出,不再重试
}
await sleep(checkDelay); // 识别间隔
if (dodispose) {
gameRegion.dispose();
dodispose = false; // 已经释放,标记避免重复 dispose
}
}
/* 超时仍未识别到,返回失败 */
return false;
@@ -1986,12 +1975,8 @@ async function isMainUI(maxDuration = 10) {
*/
async function hasScroll(maxDuration = 10) {
const start = Date.now();
let dodispose = false;
while (Date.now() - start < maxDuration) {
if (!gameRegion) {
gameRegion = captureGameRegion();
dodispose = true;
}
gameRegion = await getGameRegion();
try {
const result = gameRegion.find(scrollRo);
if (result.isExist()) return true;
@@ -2000,10 +1985,6 @@ async function hasScroll(maxDuration = 10) {
return false; // 一旦出现异常直接退出,不再重试
}
await sleep(checkDelay); // 识别间隔
if (dodispose) {
gameRegion.dispose();
dodispose = false; // 已经释放,标记避免重复 dispose
}
}
/* 超时仍未识别到,返回失败 */
return false;
@@ -2341,25 +2322,21 @@ async function findAndClick(target,
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
const gameRegion = await getGameRegion();
// 依次尝试每一个 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();
}
if (found) break; // 成功即跳出 while
await sleep(interval); // 没找到时等待
}
@@ -2370,4 +2347,65 @@ async function findAndClick(target,
log.error(`执行通用识图时出现错误:${error.message}`);
return retType === 0 ? false : null;
}
}
/**
* 获取游戏区域截图,根据时间间隔决定是否重新捕获
*
* @param {number} [minInterval=17] - 最小截图间隔毫秒默认17ms约60fps
* @param {boolean} [asyncDispose=false] - 是否异步释放旧截图默认false
* @returns {Promise<Object>} 游戏区域截图对象
*
* @description
* 使用 gameRegionManager 对象管理以下属性:
* - newGameRegion: 存储最新的游戏区域截图对象
* - oldGameRegion: 存储上一个游戏区域截图对象,用于资源释放
* - lastCapture: 上一次捕获游戏区域的时间戳
* - isDisposing: 标记是否正在释放旧截图,用于安全锁
* - isCapturing: 标记是否正在执行截图操作,用于全局锁
*/
async function getGameRegion(minInterval = 17, asyncDispose = false) {
async function disposeOldGameRegion() {
if (gameRegionManager.oldGameRegion) {
gameRegionManager.isDisposing = true;
try {
gameRegionManager.oldGameRegion.dispose();
} catch (error) {
log.error(`释放旧游戏区域截图失败: ${error.message}`);
} finally {
gameRegionManager.isDisposing = false;
gameRegionManager.oldGameRegion = gameRegionManager.newGameRegion;
}
} else {
gameRegionManager.oldGameRegion = gameRegionManager.newGameRegion;
}
}
// 等待其他任务完成截图
while (gameRegionManager.isCapturing) {
await sleep(1);
}
gameRegionManager.isCapturing = true;
try {
if (new Date() - gameRegionManager.lastCapture >= minInterval || !gameRegionManager.newGameRegion) {
while (gameRegionManager.isDisposing) {
await sleep(1);
}
gameRegionManager.lastCapture = new Date();
gameRegionManager.newGameRegion = captureGameRegion();
// 根据参数决定是否等待释放完成
if (asyncDispose) {
disposeOldGameRegion();
} else {
await disposeOldGameRegion();
}
}
} catch (error) {
log.error(`获取游戏区域截图失败: ${error.message}`);
} finally {
gameRegionManager.isCapturing = false;
return gameRegionManager.newGameRegion;
}
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "锄地一条龙",
"version": "2.6.13",
"version": "2.7.0",
"description": "一站式解决自动化锄地支持只拾取狗粮请仔细阅读README.md后使用",
"authors": [
{

View File

@@ -62,15 +62,6 @@
"type": "path",
"x": 9666.83203125,
"y": 5347.07421875
},
{
"action": "",
"action_params": "",
"id": 6,
"move_mode": "walk",
"type": "teleport",
"x": 9364.5771484375,
"y": 1427.001953125
}
]
}