mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-19 03:59:51 +08:00
215 lines
7.6 KiB
JavaScript
215 lines
7.6 KiB
JavaScript
// 模块级变量,存储默认映射表
|
||
let replacementMap = {
|
||
监: "盐",
|
||
卵: "卯",
|
||
};
|
||
|
||
/** 允许用户在运行时动态修改库的默认映射表 */
|
||
function setGlobalReplacementMap(newMap) {
|
||
if (typeof newMap === "object" && newMap !== null) {
|
||
replacementMap = newMap;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 内部私有工具:校正文本
|
||
*/
|
||
function _correctText(text, customMap) {
|
||
if (!text) return "";
|
||
const map = customMap || replacementMap; // 如果没传自定义的,就用默认的
|
||
let correctedText = text;
|
||
for (const [wrong, right] of Object.entries(map)) {
|
||
correctedText = correctedText.replace(new RegExp(wrong, "g"), right);
|
||
}
|
||
return correctedText.trim();
|
||
}
|
||
|
||
/**
|
||
* 解析坐标
|
||
* @param {Array|Object} area - 坐标 [x, y, w, h] 或 {x, y, width, height}
|
||
*/
|
||
function parseRect(area) {
|
||
let x, y, width, height;
|
||
if (Array.isArray(area)) {
|
||
[x, y, width, height] = area;
|
||
} else if (typeof area === "object" && area !== null) {
|
||
({ x, y, width, height } = area);
|
||
} else {
|
||
throw new TypeError("Invalid area format");
|
||
}
|
||
return { x, y, width, height };
|
||
}
|
||
|
||
/**
|
||
* 屏幕截图包装器
|
||
* @param {Function} action - 接收 region 对象的异步函数
|
||
*/
|
||
async function withCapture(action) {
|
||
const region = captureGameRegion();
|
||
try {
|
||
return await action(region);
|
||
} finally {
|
||
region.dispose?.();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 局部裁剪包装器
|
||
* @param {object} region - 父级截图区域
|
||
* @param {object} rect - {x, y, width, height}
|
||
* @param {Function} action - 接收 crop 对象的异步函数
|
||
*/
|
||
async function withCrop(region, rect, action) {
|
||
const crop = region.DeriveCrop(rect.x, rect.y, rect.width, rect.height);
|
||
try {
|
||
return await action(crop);
|
||
} finally {
|
||
crop.dispose?.();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 安全地绘制区域标识(自动处理坐标解析与资源释放)
|
||
* @param {Array|Object} area - 坐标 [x, y, w, h] 或 {x, y, width, height}
|
||
* @param {object} [existingFrame=null] - (可选) 已有的截图对象。如果不传,则自动创建并释放新截图。
|
||
* @param {string} label - 绘制在框上的标签名
|
||
*/
|
||
async function drawRegion(area, existingFrame = null, label = null) {
|
||
const rect = parseRect(area);
|
||
|
||
// 内部绘制逻辑:只负责裁切和画图
|
||
const doDraw = async (f) => {
|
||
await withCrop(f, rect, async (crop) => {
|
||
const mark = label ? label : `rect_${rect.x}_${rect.y}_${rect.width}_${rect.height}`;
|
||
crop.DrawSelf(mark);
|
||
});
|
||
};
|
||
|
||
if (existingFrame) {
|
||
// 如果外部传了 frame,我们只负责释放 crop,不释放外部的 frame
|
||
await doDraw(existingFrame);
|
||
} else {
|
||
// 如果没传,我们截一张新图,并在画完释放自己截的这张图
|
||
await withCapture(async (tempFrame) => {
|
||
await doDraw(tempFrame);
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 快速判断区域内是否存在文本(单次检查)
|
||
*/
|
||
async function isTextExistedInRegion(searchText, ocrRegion) {
|
||
const rect = parseRect(ocrRegion);
|
||
|
||
return await withCapture(async (captureRegion) => {
|
||
const result = captureRegion.find(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height));
|
||
if (!result || !result.text) return false;
|
||
|
||
const text = result.text;
|
||
return (searchText instanceof RegExp) ? !!text.match(searchText) : text.includes(searchText);
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* 获取区域内的文本(带重试机制)
|
||
*/
|
||
async function getTextInRegion(ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = null) {
|
||
const rect = parseRect(ocrRegion);
|
||
const debugThreshold = timeout / retryInterval / 3;
|
||
const startTime = Date.now();
|
||
let retryCount = 0;
|
||
|
||
while (Date.now() - startTime < timeout) {
|
||
// 使用 withCapture 自动管理截图资源的生命周期
|
||
const result = await withCapture(async (captureRegion) => {
|
||
try {
|
||
const resList = captureRegion.findMulti(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height));
|
||
// resList 通常不是真正的 JS Array,使用 .count 且使用下标访问
|
||
const count = resList.count || resList.Count || 0;
|
||
for (let i = 0; i < count; i++) {
|
||
const res = resList[i];
|
||
if (!res || !res.text) continue;
|
||
const corrected = _correctText(res.text, replacementMap);
|
||
// 如果识别到了有效文本(不为空),则返回
|
||
if (corrected) {
|
||
return corrected;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
log.warn(`OCR 识别失败,正在进行第 ${retryCount} 次重试...`);
|
||
}
|
||
|
||
// 达到调试阈值时画框
|
||
if (++retryCount > debugThreshold) {
|
||
await drawRegion(rect, captureRegion, "debug");
|
||
}
|
||
return null;
|
||
});
|
||
|
||
if (result !== null) return result;
|
||
await sleep(retryInterval);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 等待特定文本出现
|
||
*/
|
||
async function waitForTextAppear(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = null) {
|
||
const startTime = Date.now();
|
||
|
||
// 循环复用 getTextInRegion 的逻辑思想
|
||
while (Date.now() - startTime < timeout) {
|
||
const currentText = await getTextInRegion(ocrRegion, retryInterval, retryInterval, replacementMap);
|
||
if (currentText && currentText.includes(targetText)) {
|
||
return { success: true, wait_time: Date.now() - startTime };
|
||
}
|
||
// 此处不需要额外 sleep,因为 getTextInRegion 内部已经耗费了时间
|
||
}
|
||
return { success: false };
|
||
}
|
||
|
||
/**
|
||
* 识别文本并点击中心点
|
||
*/
|
||
async function recognizeTextAndClick(targetText, ocrRegion, timeout = 5000, retryInterval = 50, replacementMap = null) {
|
||
const rect = parseRect(ocrRegion);
|
||
const debugThreshold = timeout / retryInterval / 3;
|
||
const startTime = Date.now();
|
||
let retryCount = 0;
|
||
|
||
while (Date.now() - startTime < timeout) {
|
||
const clicked = await withCapture(async (captureRegion) => {
|
||
try {
|
||
const resList = captureRegion.findMulti(RecognitionObject.ocr(rect.x, rect.y, rect.width, rect.height));
|
||
// resList 通常不是真正的 JS Array,使用 .count 且使用下标访问
|
||
const count = resList.count || resList.Count || 0;
|
||
for (let i = 0; i < count; i++) {
|
||
const res = resList[i];
|
||
const correctedText = _correctText(res.text, replacementMap);
|
||
if (correctedText.includes(targetText)) {
|
||
const centerX = Math.round(res.x + res.width / 2);
|
||
const centerY = Math.round(res.y + res.height / 2);
|
||
await click(centerX, centerY);
|
||
await sleep(50);
|
||
return { success: true, x: centerX, y: centerY };
|
||
}
|
||
}
|
||
} catch (e) {
|
||
log.warn(`识别点击失败重试中...`);
|
||
}
|
||
|
||
if (++retryCount > debugThreshold) {
|
||
await drawRegion(rect, captureRegion);
|
||
}
|
||
return null;
|
||
});
|
||
|
||
if (clicked) return clicked;
|
||
await sleep(retryInterval);
|
||
}
|
||
return { success: false };
|
||
}
|