Files
bettergi-scripts-list/repo/js/OcrMonsterNum/main.js
2025-09-13 17:46:31 +08:00

194 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
async function getMonsterCounts() {
/* 0. 读取怪物列表 */
const raw = file.readTextSync('assets/info.json');
const monsterList = JSON.parse(raw).map(it => it.name);
const monsterCounts = {};
/* 1. 外层循环:最多 3 次进入生物志 */
let attempt = 0;
while (attempt < 3) {
attempt++;
log.info(`${attempt} 次尝试进入生物志`);
await genshin.returnMainUi();
keyPress('VK_ESCAPE');
await sleep(1500);
const archiveTpl = RecognitionObject.TemplateMatch(
file.readImageMatSync('assets/RecognitionObject/图鉴.png'), 0, 0, 1920, 1080);
if (!(await findAndClick(archiveTpl))) continue;
const faunaTpl = RecognitionObject.TemplateMatch(
file.readImageMatSync('assets/RecognitionObject/生物志.png'), 0, 0, 1920, 1080);
if (!(await findAndClick(faunaTpl))) continue;
click(1355, 532);
await sleep(2000);
break;
}
if (attempt >= 3) {
log.error('连续 3 次无法进入生物志,脚本终止');
await genshin.returnMainUi();
return {};
}
/* ===== 工具函数 ===== */
async function findAndClick(target, maxAttempts = 20) {
for (let attempts = 0; attempts < maxAttempts; attempts++) {
const gameRegion = captureGameRegion();
try {
const result = gameRegion.find(target);
if (result.isExist()) {
result.click();
return true; // 成功立刻返回
}
} catch (err) {
} finally {
gameRegion.dispose();
}
if (attempts < maxAttempts - 1) { // 最后一次不再 sleep
await sleep(50);
}
}
//log.error("已达到重试次数上限,仍未找到目标");
return false;
}
async function scrollPage(totalDistance, stepDistance = 10, delayMs = 5) {
moveMouseTo(400, 750); // 移动到屏幕水平中心垂直750坐标
await sleep(50);
leftButtonDown();
// 计算滚动方向和总步数
const isDownward = totalDistance < 0; // 如果totalDistance为负数则向下滑动
const steps = Math.ceil(Math.abs(totalDistance) / stepDistance); // 使用绝对值计算步数
for (let j = 0; j < steps; j++) {
const remainingDistance = Math.abs(totalDistance) - j * stepDistance;
const moveDistance = remainingDistance < stepDistance ? remainingDistance : stepDistance;
// 根据滚动方向调整移动方向
const direction = isDownward ? 1 : -1; // 向下滑动为正方向,向上滑动为负方向
moveMouseBy(0, 1.2 * direction * moveDistance); // 根据方向调整滚动方向
await sleep(delayMs);
}
await sleep(200);
leftButtonUp();
await sleep(500);
}
async function readKillCount(maxTry = 5) {
const ocrObj = RecognitionObject.Ocr(865, 980, 150, 50);
for (let t = 0; t < maxTry; t++) {
const region = captureGameRegion();
const results = region.findMulti(ocrObj);
region.dispose();
for (let i = 0; i < results.count; i++) {
const str = results[i].text.trim();
// 必须是纯数字
if (/^\d+$/.test(str)) {
return { success: true, count: parseInt(str, 10) };
}
}
if (t < maxTry - 1) await sleep(200); // 最后一次不重试
}
return { success: false, count: -1 };
}
async function readKillCountStable(prevCount, sameTolerance = 5) {
let lastCount = -1;
for (let r = 0; r < sameTolerance; r++) {
await sleep(50 * r);
//log.info(`执行第${r}次ocr`)
const ocrRet = await readKillCount(5);
if (!ocrRet.success) break; // 真的读不到数字就放弃
lastCount = ocrRet.count;
if (lastCount !== prevCount) return { success: true, count: lastCount }; // 变了→成功
}
// 3 次仍相同→返回最后一次相同值
return { success: true, count: lastCount };
}
async function findMonsterIcon(monsterId, iconRetry = 3) {
const tpl = RecognitionObject.TemplateMatch(
file.readImageMatSync(`assets/monster/${monsterId.trim()}.png`), 130, 80, 670, 970);
let pageTurnsUp = 0;
while (pageTurnsUp < 1) {
let pageTurns = 0;
while (pageTurns < 2) {
//log.info("执行一次模板识别");
if (await findAndClick(tpl, iconRetry)) return true;
await scrollPage(300);
pageTurns++;
}
for (let j = 0; j < 2; j++) {
await scrollPage(-300);
}
pageTurnsUp++;
}
return false;
}
/* ===== 主循环 ===== */
let prevCount = -1; // 上一轮 OCR 结果
let retryMask = 0; // 位掩码:第 i 位为 1 表示已回退过
let prevFinalCount = -1; // 上一只怪物的最终击杀数
for (let i = 0; i < monsterList.length; i++) {
const monsterId = monsterList[i];
let time0 = new Date();
/* 1. 找怪 + OCR */
if (!(await findMonsterIcon(monsterId, 3))) {
log.info(`怪物: ${monsterId.trim()}, 未找到图标`);
monsterCounts[monsterId.trim()] = -1;
prevCount = -1; // 重置
continue;
}
let time1 = new Date();
//log.info(`寻找图标用时${time1 - time0}`);
/* 2. OCR与上一只结果比较原地重试 3 次 */
const ocr = await readKillCountStable(prevFinalCount, 3);
const count = ocr.success ? ocr.count : -1;
let time2 = new Date();
//log.info(`ocr用时${time2 - time1}`);
/* 2. 结果相同且本行还没回退过 → 回退一次 */
if (count === prevCount && !(retryMask & (1 << i))) {
retryMask |= (1 << i); // 标记已回退
i--; // 回退同一 i 一次
continue;
}
/* 3. 正常记录 */
monsterCounts[monsterId.trim()] = count;
log.info(`怪物: ${monsterId.trim()}, 数量: ${count}`);
prevCount = count;
prevFinalCount = count; // 记录本次最终值,供下一只比对
}
return monsterCounts;
}
(async function () {
const monsterCounts = await getMonsterCounts();
/* 1. 控制台打印 */
log.info("怪物数量统计结果:");
for (const [monsterName, count] of Object.entries(monsterCounts)) {
log.info(`${monsterName}: ${count}`);
}
/* 2. 写入 record.txtJSON*/
try {
const filePath = 'record.txt';
const json = JSON.stringify(monsterCounts, null, 2);
await file.writeText(filePath, json, false);
log.info(`结果已写入 ${filePath}`);
} catch (error) {
log.error(`写入 record.txt 时出错: ${error.message}`);
}
})();