Files
1xygyty1 f9bf443a3b js: 识别队伍角色 (#1149)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

更新了读取五位数的原石和4位数的纠缠

* Add files via upload

减少了空间占用

* Add files via upload

* Delete repo/js/OCR读取当前原石以及纠缠之缘记录并发送通知 directory

* Add files via upload

* Add files via upload

* Delete 识别队伍角色 directory

* Delete repo/js/识别队伍角色 directory

* Add files via upload

* Delete repo/js/识别队伍角色/main.js

* Delete repo/js/识别队伍角色 directory

* Add files via upload

* Add files via upload

* Add files via upload
2025-07-10 09:31:37 +08:00

246 lines
12 KiB
JavaScript
Raw Permalink 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.

// ===== 配置与工具函数 =====
// 字符替换映射表修正OCR识别的常见错误字符
const replacementMap = {
"监": "盐",
"姐": "妲",
"干": "千",
"卵": "卯",
"凌": "绫",
"疑": "凝",
"沙": "砂",
"谣": "瑶",
"萤": "荧",
"霄": "宵"
};
// 原神角色列表用于OCR识别后的字符匹配
const genshinImpactCharacters = [
"荧", "空", "神里绫华", "琴", "丽莎", "芭芭拉", "凯亚", "迪卢克", "雷泽", "安柏", "温迪", "香菱",
"北斗", "行秋", "魈", "凝光", "可莉", "钟离", "菲谢尔", "班尼特", "达达利亚", "诺艾尔", "七七",
"重云", "甘雨", "阿贝多", "迪奥娜", "莫娜", "刻晴", "砂糖", "辛焱", "罗莎莉亚", "胡桃", "枫原万叶",
"烟绯", "宵宫", "托马", "优菈", "雷电将军", "早柚", "珊瑚宫心海", "五郎", "九条裟罗", "荒泷一斗",
"八重神子", "鹿野院平藏", "夜兰", "绮良良", "埃洛伊", "申鹤", "云堇", "久岐忍", "神里绫人", "柯莱",
"多莉", "提纳里", "妮露", "赛诺", "坎蒂丝", "纳西妲", "莱依拉", "流浪者", "珐露珊", "瑶瑶",
"艾尔海森", "迪希雅", "米卡", "卡维", "白术", "琳妮特", "林尼", "菲米尼", "莱欧斯利", "那维莱特",
"夏洛蒂", "芙宁娜", "夏沃蕾", "娜维娅", "嘉明", "闲云", "千织", "希格雯", "阿蕾奇诺", "赛索斯",
"克洛琳德", "艾梅莉埃", "卡齐娜", "基尼奇", "玛拉妮", "希诺宁", "恰斯卡", "欧洛伦", "玛薇卡",
"茜特菈莉", "蓝砚", "梦见月瑞希", "伊安珊", "瓦雷莎", "爱可菲", "伊法", "丝柯克", "塔利雅"
];
// 预加载所有模板图像:避免循环中重复读取文件,提升性能
const templates = {
wanderer: file.ReadImageMatSync("assets/wanderer_icon.png"), // 流浪者图标(活跃状态)
wandererInactive: file.ReadImageMatSync("assets/wanderer_icon_no_active.png"), // 流浪者图标(非活跃状态)
yin: file.ReadImageMatSync("assets/yin.png"), // 荧的图标模板1
yin2: file.ReadImageMatSync("assets/yin2.png"), // 荧的图标模板2
};
// 创建模板匹配对象的工具函数:统一参数格式,减少重复代码
function createTemplateMatch(templateKey, x, y, width, height) {
return RecognitionObject.TemplateMatch(
templates[templateKey], // 使用预加载的模板图像
x, y, // 识别区域左上角坐标
width, height // 识别区域宽高
);
}
// 过滤非中文字符的工具函数处理OCR结果中的无效字符
function filterChineseChars(text) {
return text?.replace(/[^\u4e00-\u9fa5]/g, '') || ''; // 保留中文字符,其他字符替换为空
}
// 图像识别函数:返回布尔值表示是否识别成功
async function recognizeImage(recognitionObject) {
await sleep(500); // 延迟500ms避免识别请求过于频繁
try {
const imageResult = captureGameRegion().find(recognitionObject);
// 当识别结果存在且坐标不为(0,0)时(排除无效识别)
return !!imageResult && imageResult.x !== 0 && imageResult.y !== 0;
} catch (error) {
log.error(`识别图像时发生异常: ${error.message}`);
return false;
}
}
// OCR文本识别函数在指定区域重试识别直到超时
async function recognizeTextInRegion(ocrRegion, timeout = 5000) {
let startTime = Date.now(); // 记录开始时间
let retryCount = 0; // 重试次数计数
// 循环直到超时
while (Date.now() - startTime < timeout) {
try {
// 创建OCR识别区域对象
const region = RecognitionObject.ocr(
ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height
);
// 绘制识别区域红框(调试用)
//captureGameRegion().find(region).DrawSelf("debug");
const ocrResult = captureGameRegion().find(region);
if (ocrResult) {
// 应用字符替换映射表修正识别结果
let correctedText = ocrResult.text;
for (const [wrongChar, correctChar] of Object.entries(replacementMap)) {
correctedText = correctedText.replace(new RegExp(wrongChar, 'g'), correctChar);
}
return correctedText; // 返回修正后的文本
} else {
log.warn(`OCR 识别区域未找到内容`);
return null;
}
} catch (error) {
// 记录重试日志
retryCount++;
log.warn(`OCR 识别失败,正在进行第 ${retryCount} 次重试...`);
}
await sleep(500); // 每次重试间隔500ms
}
log.warn(`经过多次尝试,仍然无法在指定区域识别到文字`);
return null;
}
// 定义识别对象
const paimonMenuRo = RecognitionObject.TemplateMatch(
file.ReadImageMatSync("assets/paimon_menu.png"),
0,
0,
genshin.width / 3.0,
genshin.width / 5.0
);
// 判断是否在主界面的函数
const isInMainUI = () => {
let captureRegion = captureGameRegion();
let res = captureRegion.Find(paimonMenuRo);
return !res.isEmpty();
};
// ===== 主流程逻辑 =====
(async function () {
// 初始化游戏界面(设置分辨率等)
setGameMetrics(1920, 1080, 1);
await genshin.returnMainUi(); // 返回游戏主界面
await sleep(2000); // 等待界面响应
if (isInMainUI(paimonMenuRo)) {
// 按下ESC键可能用于打开菜单或其他操作
keyPress("ESCAPE");
await sleep(1500); // 等待界面响应
// 识别玩家名称(主角名称)
const playerNameRegion = { x: 270, y: 40, width: 400, height: 40 };
let playerName = await recognizeTextInRegion(playerNameRegion);
playerName = filterChineseChars(playerName); // 过滤非中文字符
log.info(`识别到的主角为:${playerName}`);
// 返回主界面并等待加载
await genshin.returnMainUi();
await sleep(1000);
if (!isInMainUI(paimonMenuRo)) {
log.error(`返回主界面失败`);
return;
}
// 初始化角色识别结果数组
const CharacterJudgment = [false, false, false, false]; // 记录每个角色是否识别成功
const Character = []; // 存储识别到的角色名称
// 并行识别4个角色使用Promise.all提升效率
const recognitionPromises = Array(4).fill().map(async (_, i) => {
try {
// 计算第i个角色的OCR识别区域垂直排列间隔100px
const ocrRegion = {
x: 1620,
y: 225 + i * 100,
width: 160,
height: 60
};
// 执行OCR识别并过滤字符
const recognizedText = await recognizeTextInRegion(ocrRegion);
const filteredText = filterChineseChars(recognizedText);
//log.info(`[角色${i + 1}] 识别到的文本:${filteredText}`);
// 与角色列表进行匹配
let isCharacterMatched = false;
for (let j = 0; j < genshinImpactCharacters.length; j++) {
if (genshinImpactCharacters[j] === filteredText) {
// 匹配成功,记录结果
CharacterJudgment[i] = true;
log.info(`[角色${i + 1}] ${filteredText} 匹配成功`);
Character[i] = filteredText;
await sleep(500); // 等待后续操作
isCharacterMatched = true;
break;
}
}
// 当OCR未匹配到角色时尝试通过图像模板识别散兵/旅行者)
if (!isCharacterMatched) {
const { x, y, width, height } = ocrRegion; // 解构区域坐标
// 创建4个模板匹配对象流浪者和旅行者的不同状态
const wanderer1 = createTemplateMatch("wanderer", x + 100, y, width + 20, height + 20);
const wanderer2 = createTemplateMatch("wandererInactive", x + 100, y - 10, width + 20, height + 30);
const yin3 = createTemplateMatch("yin", x + 100, y - 10, width + 20, height + 30);
const yin4 = createTemplateMatch("yin2", x + 100, y - 10, width + 20, height + 30);
// 执行图像识别并处理结果添加catch避免异常中断
const result1 = await recognizeImage(wanderer1).catch(() => false);
const result2 = await recognizeImage(wanderer2).catch(() => false);
if (result1 || result2) {
// 识别到流浪者
Character[i] = "流浪者";
log.info(`[角色${i + 1}] 流浪者匹配成功`);
CharacterJudgment[i] = true;
} else {
// 尝试识别旅行者(荧或空)
const result3 = await recognizeImage(yin3).catch(() => false);
const result4 = await recognizeImage(yin4).catch(() => false);
if ((result3 || result4) && filteredText === playerName) {
Character[i] = "荧";
log.info(`[角色${i + 1}] 荧匹配成功`);
CharacterJudgment[i] = true;
} else if (filteredText === playerName) {
Character[i] = "空";
log.info(`[角色${i + 1}] 空匹配成功`);
CharacterJudgment[i] = true;
} else {
log.error(`[角色${i + 1}] 散兵/旅行者识别失败`);
}
}
}
} catch (error) {
// 记录详细错误到日志文件
const errorMsg = `[角色${i + 1}] 识别失败: ${error.message}`;
file.WriteTextSync("Resources_log.txt", errorMsg + "\n", true);
log.warn(errorMsg);
}
});
// 等待所有角色识别完成
await Promise.all(recognitionPromises);
// 检查是否全部识别成功
const allRecognized = CharacterJudgment.every(Boolean);
if (allRecognized) {
// 记录识别结果到日志文件
const now = new Date();
const logContent = `${now.toLocaleString()} —— 1: ${Character[0]} ——- 2:${Character[1]} ——- 3:${Character[2]} ———— 4:${Character[3]}\n`;
const writeSuccess = file.WriteTextSync("Character_log.txt", logContent, true);
log.info(writeSuccess ? "成功将队伍信息写入日志文件" : "写入日志文件失败");
} else {
log.error("未能完全识别队伍角色");
}
// 等待后返回主界面
await sleep(500);
await genshin.returnMainUi();
} else {
log.error(`返回主界面失败,跳过识别`);
}
})();