mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-15 03:23:22 +08:00
194 lines
7.1 KiB
JavaScript
194 lines
7.1 KiB
JavaScript
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.txt(JSON)*/
|
||
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}`);
|
||
}
|
||
})();
|
||
|