[AutoLeyLineOutcrop]修复潜在的已释放对象访问问题 (#2161)

* [AutoLeyLineOutcrop]修复潜在的已释放对象访问问题

* update manifest.json
This commit is contained in:
Colin Xu
2025-10-17 17:59:46 +08:00
committed by GitHub
parent d5b80887a4
commit d623e90ad5
4 changed files with 106 additions and 54 deletions

View File

@@ -525,13 +525,15 @@ async function openOutcrop(targetPath) {
keyPress("F");
while (Date.now() - startTime < 5000) {
captureRegion = captureGameRegion();
if (recognizeFightText(captureRegion)) {
recognized = true;
let captureRegion = captureGameRegion();
try {
if (recognizeFightText(captureRegion)) {
recognized = true;
break;
}
} finally {
captureRegion.dispose();
break;
}
captureRegion.dispose();
keyPress("F");
await sleep(500);
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "全自动地脉花",
"version": "4.3.1",
"version": "4.3.2",
"tags": ["地脉花"],
"bgi_version": "0.44.7",
"description": "基于OCR图像识别的全自动刷取地脉花。\n💡更多信息请查看在线手册https://hcnsvf0s8d0s.feishu.cn/wiki/Tb1twpThLi7UlykqcYOcuccTnjJ \n\n----------注意事项----------\n●仅支持BetterGI 0.44.7 及以上版本!\n●部分地脉花因特殊原因不支持全自动具体的点位请在手册中查看。\n●树脂使用的优先级2倍原粹树脂 > 浓缩树脂 > 原粹树脂。\n●运行时会传送到七天神像设置中设置的七天神像需要关闭七天神像设置中的“是否就近七天神像恢复血量”并指定七天神像。\n●战斗策略注意调度器设置中地图追踪行走配置里的“允许在JsSpript中使用”和“覆盖JS中的自动战斗配置”只有在都打开的情况下脚本才会使用下面的战斗配置否则会使用独立任务中的战斗策略。战斗超时时间不能大于脚本自定义配置中的时间。\n\n如果遇到问题请先参照手册中的方法进行解决。",

View File

@@ -43,13 +43,14 @@ this.clickWithVerification = async function(x, y, targetText, maxRetries = 20) {
* @returns {Promise<boolean>}
*/
this.verifyRewardPage = async function() {
let captureRegion = null;
try {
let captureRegion = captureGameRegion();
captureRegion = captureGameRegion();
// 使用OCR识别上半区域
let ocrRo = RecognitionObject.Ocr(0, 0, captureRegion.width, captureRegion.height / 2);
let textList = captureRegion.findMulti(ocrRo);
captureRegion.dispose();
let isValid = false;
if (textList && textList.count > 0) {
@@ -74,17 +75,24 @@ this.verifyRewardPage = async function() {
} catch (error) {
log.error(`验证奖励界面失败: ${error.message}`);
return false;
} finally {
if (captureRegion) {
captureRegion.dispose();
}
}
}
/**
* 检查原粹树脂是否耗尽通过OCR识别"补充"文字)
* 如果原粹树脂耗尽,第一个按钮会变成"补充"按钮
* @param {ImageRegion} captureRegion - 截图区域
* @returns {Promise<boolean>}
*/
async function checkOriginalResinEmpty(captureRegion) {
async function checkOriginalResinEmpty() {
let captureRegion = null;
try {
captureRegion = captureGameRegion();
// 使用OCR识别"补充"文字
let ocrRo = RecognitionObject.Ocr(0, 0, captureRegion.width, captureRegion.height);
let textList = captureRegion.findMulti(ocrRo);
@@ -103,17 +111,24 @@ async function checkOriginalResinEmpty(captureRegion) {
} catch (error) {
log.error(`检查原粹树脂状态失败: ${error.message}`);
return false;
} finally {
if (captureRegion) {
captureRegion.dispose();
}
}
}
/**
* 查找并排序所有使用按钮通过OCR识别"使用"文字)
* 注意:如果原粹树脂耗尽,第一个位置是"补充"按钮,不会被识别为"使用"按钮
* @param {ImageRegion} captureRegion - 截图区域
* @returns {Promise<Array>}
*/
async function findAndSortUseButtons(captureRegion) {
async function findAndSortUseButtons() {
let captureRegion = null;
try {
captureRegion = captureGameRegion();
// 使用OCR识别所有"使用"文字
let ocrRo = RecognitionObject.Ocr(0, 0, captureRegion.width, captureRegion.height);
let textList = captureRegion.findMulti(ocrRo);
@@ -170,18 +185,25 @@ async function findAndSortUseButtons(captureRegion) {
} catch (error) {
log.error(`查找使用按钮失败: ${error.message}`);
return [];
} finally {
if (captureRegion) {
captureRegion.dispose();
}
}
}
/**
* 分析树脂选项并决定使用哪个
* @param {ImageRegion} captureRegion - 截图区域
* @param {Array} sortedButtons - 排序后的使用按钮数组
* @param {boolean} isOriginalResinEmpty - 原粹树脂是否耗尽
* @returns {Promise<Object|null>}
*/
async function analyzeResinOptions(captureRegion, sortedButtons, isOriginalResinEmpty) {
async function analyzeResinOptions(sortedButtons, isOriginalResinEmpty) {
let captureRegion = null;
try {
captureRegion = captureGameRegion();
// OCR识别整个界面的文本
let ocrRo = RecognitionObject.Ocr(0, 0, captureRegion.width, captureRegion.height);
let textList = captureRegion.findMulti(ocrRo);
@@ -287,7 +309,7 @@ async function analyzeResinOptions(captureRegion, sortedButtons, isOriginalResin
if (hasDoubleReward && (hasOriginalResin20 || hasOriginalResin40)) {
// 如果当前是20个原粹树脂先尝试切换到40个
if (hasOriginalResin20 && !hasOriginalResin40) {
let switchSuccess = await trySwitch20To40Resin(captureRegion);
let switchSuccess = await trySwitch20To40Resin();
if (switchSuccess) {
choice = {
type: "使用40个原粹树脂从20切换双倍产出",
@@ -329,7 +351,7 @@ async function analyzeResinOptions(captureRegion, sortedButtons, isOriginalResin
else if (hasOriginalResin20 || hasOriginalResin40) {
// 如果当前是20个原粹树脂先尝试切换到40个
if (hasOriginalResin20 && !hasOriginalResin40) {
let switchSuccess = await trySwitch20To40Resin(captureRegion);
let switchSuccess = await trySwitch20To40Resin();
if (switchSuccess) {
choice = {
type: "使用40个原粹树脂从20切换",
@@ -363,7 +385,7 @@ async function analyzeResinOptions(captureRegion, sortedButtons, isOriginalResin
else if (sortedButtons.length >= 1) {
// 尝试切换到40个原粹树脂如果当前是20个
if (hasOriginalResin20 && !hasOriginalResin40) {
let switchSuccess = await trySwitch20To40Resin(captureRegion);
let switchSuccess = await trySwitch20To40Resin();
choice = {
type: switchSuccess ? "默认使用40个原粹树脂从20切换" : "默认使用20个原粹树脂",
button: sortedButtons[0],
@@ -384,25 +406,35 @@ async function analyzeResinOptions(captureRegion, sortedButtons, isOriginalResin
} catch (error) {
log.error(`分析树脂选项失败: ${error.message}`);
return null;
} finally {
if (captureRegion) {
captureRegion.dispose();
}
}
}
/**
* 尝试将20个原粹树脂切换到40个原粹树脂
* @param {ImageRegion} captureRegion - 截图区域
* @returns {Promise<boolean>} 是否成功切换
*/
async function trySwitch20To40Resin(captureRegion) {
async function trySwitch20To40Resin() {
let switchButtonIcon = null;
let switchButtonRo = null;
let currentCaptureRegion = null;
let newCaptureRegion = null;
try {
log.info("检测到20个原粹树脂尝试切换到40个");
currentCaptureRegion = captureGameRegion();
// 检测切换按钮
const switchButtonIcon = file.ReadImageMatSync("RecognitionObject/switch_button.png");
const switchButtonRo = RecognitionObject.TemplateMatch(switchButtonIcon);
switchButtonIcon = file.ReadImageMatSync("RecognitionObject/switch_button.png");
switchButtonRo = RecognitionObject.TemplateMatch(switchButtonIcon);
switchButtonRo.threshold = 0.7; // 设置合适的阈值
// 查找切换按钮
let switchButtonPos = captureRegion.find(switchButtonRo);
let switchButtonPos = currentCaptureRegion.find(switchButtonRo);
if (!switchButtonPos || switchButtonPos.isEmpty()) {
log.info("未找到切换按钮树脂不足40或按钮不可用保持使用20个原粹树脂");
@@ -415,10 +447,9 @@ async function trySwitch20To40Resin(captureRegion) {
await sleep(800);
// 验证是否切换成功
let newCaptureRegion = captureGameRegion();
newCaptureRegion = captureGameRegion();
let ocrRo = RecognitionObject.Ocr(0, 0, newCaptureRegion.width, newCaptureRegion.height);
let textList = newCaptureRegion.findMulti(ocrRo);
newCaptureRegion.dispose();
if (textList && textList.count > 0) {
for (let i = 0; i < textList.count; i++) {
@@ -437,6 +468,17 @@ async function trySwitch20To40Resin(captureRegion) {
} catch (error) {
log.error(`切换树脂数量失败: ${error.message}`);
return false;
} finally {
if (currentCaptureRegion) {
currentCaptureRegion.dispose();
}
if (newCaptureRegion) {
newCaptureRegion.dispose();
}
if (switchButtonIcon) {
switchButtonIcon.dispose();
}
switchButtonRo = null;
}
}
@@ -473,7 +515,7 @@ this.ensureExitRewardPage = async function() {
attempts++;
// 检测是否在奖励界面
let isInRewardPage = await verifyRewardPage();
let isInRewardPage = await this.verifyRewardPage();
if (!isInRewardPage) {
log.info("已确认不在奖励界面");
@@ -510,7 +552,7 @@ this.attemptReward = async function (retryCount = 0) {
await sleep(800);
// 步骤1: 验证是否在奖励界面
if (!await verifyRewardPage()) {
if (!await this.verifyRewardPage()) {
log.warn("当前不在奖励界面,尝试重试");
await genshin.returnMainUi();
await sleep(1000);
@@ -518,32 +560,41 @@ this.attemptReward = async function (retryCount = 0) {
return await this.attemptReward(++retryCount);
}
let captureRegion = captureGameRegion();
let isOriginalResinEmpty = false;
let sortedButtons = [];
let resinChoice = null;
// 步骤2: 检查原粹树脂是否耗尽(通过"补充"按钮)
let isOriginalResinEmpty = await checkOriginalResinEmpty(captureRegion);
// 步骤3: 识别所有使用按钮并排序
let sortedButtons = await findAndSortUseButtons(captureRegion);
if (sortedButtons.length === 0) {
log.error("未找到任何使用按钮");
captureRegion.dispose();
try {
// 步骤2: 检查原粹树脂是否耗尽(通过"补充"按钮)
isOriginalResinEmpty = await checkOriginalResinEmpty();
// 步骤3: 识别所有使用按钮并排序
sortedButtons = await findAndSortUseButtons();
if (sortedButtons.length === 0) {
log.error("未找到任何使用按钮");
keyPress("VK_ESCAPE");
await sleep(500);
await this.ensureExitRewardPage();
return false;
}
// 步骤4: 根据原粹树脂状态调整决策逻辑
resinChoice = await analyzeResinOptions(sortedButtons, isOriginalResinEmpty);
if (!resinChoice) {
// 已在 analyzeResinOptions 中输出详细错误信息,这里不再重复
keyPress("VK_ESCAPE");
await sleep(500);
await this.ensureExitRewardPage();
return false;
}
} catch (error) {
log.error(`处理奖励界面时出错: ${error.message}`);
keyPress("VK_ESCAPE");
await sleep(500);
await ensureExitRewardPage();
return false;
}
// 步骤4: 根据原粹树脂状态调整决策逻辑
let resinChoice = await analyzeResinOptions(captureRegion, sortedButtons, isOriginalResinEmpty);
if (!resinChoice) {
// 已在 analyzeResinOptions 中输出详细错误信息,这里不再重复
captureRegion.dispose();
keyPress("VK_ESCAPE");
await sleep(500);
await ensureExitRewardPage();
await this.ensureExitRewardPage();
return false;
}
@@ -557,13 +608,11 @@ this.attemptReward = async function (retryCount = 0) {
await switchBackToCombatTeam();
}
captureRegion.dispose();
// 等待领奖动画/道具到账
await sleep(1200);
// 确保完全退出奖励界面
await ensureExitRewardPage();
await this.ensureExitRewardPage();
return true;
}

View File

@@ -59,8 +59,9 @@ async function() {
await sleep(2000);
// 浓缩树脂识别
let Condensed = captureGameRegion().find(CondensedRo);
Condensed.dispose();
let condensedCaptureRegion = captureGameRegion();
let Condensed = condensedCaptureRegion.find(CondensedRo);
condensedCaptureRegion.dispose();
let Isfive = false;
if (Condensed.isExist()) {
log.info("识别到浓缩树脂");