js,食材购买 (#521)

* update repo.json

* js,对NPC和材料进行文字、图像识别

* update repo.json

---------

Co-authored-by: JJMdzh <205532257+JJMdzh@users.noreply.github.com>
This commit is contained in:
JJMdzh
2025-04-05 08:55:54 +08:00
committed by GitHub
parent 83dd944af9
commit f2f989f590
36 changed files with 601 additions and 99 deletions

View File

@@ -1,23 +1,41 @@
// 定义所有食材及其对应的路径文件和 NPC
const mondstadtGroceryFilePath = `assets/蒙德百货销售员布兰琪.json`;
const liyueGroceryFilePath = `assets/璃月荣发商铺店主东升.json`;
const liyueWanminFilePath = `assets/璃月万民堂老板卯师傅.json`;
const groceryFilePath = `assets/稻妻九十九物店主葵.json`;
const charcoalFilePath = `assets/稻妻志村屋店主志村勘兵卫.json`;
const fengdanGroceryFilePath = `assets/枫丹达莫维百货店主布希柯.json`;
const cafeLuzheFilePath = `assets/枫丹咖啡厅露泽店主阿鲁埃.json`;
const sumiCityFishPath = `assets/须弥城鱼贩珀姆.json`;
const omosPortFishPath = `assets/须弥奥摩斯港鱼贩布特罗斯.json`;
const azaleVillMerPath = `assets/须弥阿如村商人阿扎莱.json`;
// 定义所有食材的图像识别对象
let FengdaRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/FengdaRo.png"));
let SaltRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/SaltRo.png"));
let PepperRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/PepperRo.png"));
let OnionRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/OnionRo.png"));
let MilkRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/MilkRo.png"));
let TomatoRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/TomatoRo.png"));
let SpicesRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/SpicesRo.png"));
let CabbageRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/CabbageRo.png"));
let PotatoRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/PotatoRo.png"));
let WheatRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/WheatRo.png"));
let RiceRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/RiceRo.png"));
let TofuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/TofuRo.png"));
let AlmondRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/AlmondRo.png"));
let FishRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/FishRo.png"));
let CrabRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/CrabRo.png"));
let ShrimpRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/ShrimpRo.png"));
let CoffeeRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/CoffeeRo.png"));
let ToutuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/ToutuRo.png"));
let FermentRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/FermentRo.png"));
// 定义所有可能的食材,注意料理名字长度可能超过识图范围
const ingredients = [
"枫达", "盐", "洋葱", "牛奶", "番茄", "香辛料", "卷心菜", "土豆", "小麦", "胡椒","稻米", "豆腐", "杏仁", "鱼肉", "螃蟹", "虾仁", "咖啡豆", "秃秃豆", "发酵果实汁"
"枫达", "盐", "胡椒", "洋葱", "牛奶", "番茄", "香辛料", "卷心菜", "土豆", "小麦", "稻米", "豆腐", "杏仁", "鱼肉", "螃蟹", "虾仁", "咖啡豆", "秃秃豆", "发酵果实汁"
];
// 筛选出用户选择的食材及其对应的路径文件和 NPC
let selectedIngredients = []; // 在函数外部声明一次
let selectedPaths = new Map();
// 定义所有食材及其对应的路径文件和 NPC
const mondstadtGroceryFilePath = `assets/Pathing/蒙德百货销售员布兰琪.json`;
const liyueGroceryFilePath = `assets/Pathing/璃月荣发商铺店主东升.json`;
const liyueWanminFilePath = `assets/Pathing/璃月万民堂老板卯师傅.json`;
const groceryFilePath = `assets/Pathing/稻妻九十九物店主葵.json`;
const charcoalFilePath = `assets/Pathing/稻妻志村屋店主志村勘兵卫.json`;
const fengdanGroceryFilePath = `assets/Pathing/枫丹达莫维百货店主布希柯.json`;
const cafeLuzheFilePath = `assets/Pathing/枫丹咖啡厅露泽店主阿鲁埃.json`;
const sumiCitycafeFilePath = `assets/Pathing/须弥城咖啡馆代理店长恩忒卡.json`;
const sumiCityFishPath = `assets/Pathing/须弥城鱼贩珀姆.json`;
const omosPortFishPath = `assets/Pathing/须弥奥摩斯港鱼贩布特罗斯.json`;
const azaleVillMerPath = `assets/Pathing/须弥阿如村商人阿扎莱.json`;
const ingredientPaths = {
"枫达": [fengdanGroceryFilePath, cafeLuzheFilePath],
@@ -36,10 +54,40 @@ const ingredientPaths = {
"鱼肉": [liyueWanminFilePath, charcoalFilePath, sumiCityFishPath, omosPortFishPath, azaleVillMerPath],
"螃蟹": [liyueWanminFilePath, charcoalFilePath, sumiCityFishPath, omosPortFishPath],
"秃秃豆": [fengdanGroceryFilePath, azaleVillMerPath],
"咖啡豆": [cafeLuzheFilePath],
"咖啡豆": [sumiCitycafeFilePath, cafeLuzheFilePath],
"香辛料": [azaleVillMerPath],
"发酵果实汁": [fengdanGroceryFilePath]
};
// 定义食材名称和图片文件名的映射表
const ingredientImageMap = {
"枫达": "FengdaRo.png",
"盐": "SaltRo.png",
"洋葱": "OnionRo.png",
"牛奶": "MilkRo.png",
"番茄": "TomatoRo.png",
"卷心菜": "CabbageRo.png",
"土豆": "PotatoRo.png",
"小麦": "WheatRo.png",
"胡椒": "PepperRo.png",
"稻米": "RiceRo.png",
"虾仁": "ShrimpRo.png",
"豆腐": "TofuRo.png",
"杏仁": "AlmondRo.png",
"鱼肉": "FishRo.png",
"螃蟹": "CrabRo.png",
"秃秃豆": "ToutuRo.png",
"咖啡豆": "CoffeeRo.png",
"香辛料": "SpicesRo.png",
"发酵果实汁": "FermentRo.png",
// 可以继续添加更多食材的映射
};
// 定义替换映射表
const replacementMap = {
"监": "盐",
"卵": "卯"
};
// 定义所有NPC名注意名字长度可能超过识图范围
const npcNames = {
[mondstadtGroceryFilePath]: ["布兰琪"],
@@ -50,10 +98,15 @@ const npcNames = {
[fengdanGroceryFilePath]: ["布希柯"],
[cafeLuzheFilePath]: ["阿鲁埃"],
[sumiCityFishPath]: ["珀姆"],
[sumiCitycafeFilePath]: ["恩忒卡"],
[omosPortFishPath]: ["布特罗斯"],
[azaleVillMerPath]: ["阿扎莱"]
};
// 筛选出用户选择的食材及其对应的路径文件和 NPC
let selectedIngredients = []; // 在函数外部声明一次
let selectedPaths = new Map();
for (let ingredient of ingredients) {
if (settings[ingredient]) {
selectedIngredients.push(ingredient);
@@ -82,12 +135,95 @@ async function simulateKeyOperations(key, duration) {
await sleep(500); // 释放按键后等待 500 毫秒
}
// 定义一个函数用于购买食材
async function purchaseIngredient(ingredient) {
log.info(`购买食材: ${ingredient}`);
// 模拟购买操作的后续点击
await click(1600, 1020); await sleep(1000); // 购买
await click(1181, 600); await sleep(200); // 选择100个
await click(1320, 780); await sleep(1000); // 最终确认
await click(1320, 780); await sleep(1000); // 点击空白
}
// 定义一个通用的图像识别函数
function recognizeImage(templatePath, xMin, yMin, width, height) {
let template = file.ReadImageMatSync(templatePath);
let recognitionObject = RecognitionObject.TemplateMatch(template, xMin, yMin, width, height);
let result = captureGameRegion().find(recognitionObject);
return result.isExist() ? result : null;
}
// 定义一个函数用于执行OCR识别
async function performOcr(targetText, xRange, yRange, tolerance) {
// 调整区域范围以包含容错区间
let adjustedXMin = xRange.min - tolerance;
let adjustedXMax = xRange.max + tolerance;
let adjustedYMin = yRange.min - tolerance;
let adjustedYMax = yRange.max + tolerance;
// 在捕获的区域内进行OCR识别
let ra = captureGameRegion();
let resList = ra.findMulti(RecognitionObject.ocr(
adjustedXMin, adjustedYMin,
adjustedXMax - adjustedXMin, adjustedYMax - adjustedYMin
));
// 遍历识别结果,检查是否找到目标文本
for (let i = 0; i < resList.count; i++) {
let res = resList[i];
// 后处理:根据替换映射表检查和替换错误识别的字符
let correctedText = res.text;
for (let [wrongChar, correctChar] of Object.entries(replacementMap)) {
correctedText = correctedText.replace(wrongChar, correctChar);
}
if (correctedText.includes(targetText)) {
// 如果找到目标文本,直接返回坐标
return { success: true, x: res.x, y: res.y, width: res.width, height: res.height };
}
}
return { success: false }; // 未找到符合条件的文本
}
// 定义一个函数用于识别食材
async function recognizeIngredient(ingredient) {
let recognized = false;
const clickOffset = 30; // 点击坐标偏移
// 尝试 OCR 识别
let ocrResult = await performOcr(ingredient, { min: 210, max: 390 }, { min: 105, max: 950 }, 10);
if (ocrResult.success) {
log.info(`通过 OCR 识别找到食材: ${ingredient}`);
log.info(`坐标: x=${ocrResult.x}, y=${ocrResult.y}`);
await click(ocrResult.x, ocrResult.y + clickOffset);
await sleep(1000);
recognized = true;
} else {
// OCR 识别失败,尝试图像识别
let imagePath = `assets/Picture/${ingredientImageMap[ingredient]}`;
if (!imagePath) {
log.error(`未找到食材 '${ingredient}' 的图片文件`);
return recognized;
}
let imageResult = recognizeImage(imagePath, 120, 90, 95, 865);
if (imageResult) {
log.info(`通过图像识别找到食材: ${ingredient}`);
imageResult.click();
await sleep(1000);
recognized = true;
} else {
log.error(`未能识别到食材: ${ingredient}`);
}
}
return recognized;
}
// 定义一个函数用于识别并点击用户选择的食材
async function clickSelectedIngredients(selectedIngredients, filePath, npcNames) {
log.info(`加载路径文件: ${filePath}`);
await pathingScript.runFile(filePath);
await sleep(1000);
// log.info("路径文件执行完成");
// 识别并交互 NPC
const npcxRange = { min: 1190, max: 1320 }; // npc X轴区间
@@ -97,8 +233,6 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
const tolerance = 12; // 容错区间
const npctolerance = 5; // 容错区间
let npcOcrResult = { success: false }; // 初始化 npcOcrResult
// 执行点击操作
async function performClickOperations(filePath) {
if (filePath === liyueGroceryFilePath || filePath === groceryFilePath || filePath === sumiCityFishPath) {
@@ -144,15 +278,13 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
log.error(`尝试 ${f_attempts + 1}:寻找 F 图标`);
}
}
// log.info(`F 图标x: ${fRes.x},y: ${fRes.y},width: ${fRes.width},height: ${fRes.height}`);
// 获取 F 图标的中心点 Y 坐标
let centerYF = fRes.y + fRes.height / 2;
// 在 F 图标右侧水平方向上识别 NPC 名称
let ocrResult = await performOcr(npcName, npcxRange, { min: fRes.y, max: fRes.y + fRes.height }, tolerance);
// log.info(`NPC 名称x: ${ocrResult.x},y: ${ocrResult.y},width: ${ocrResult.width},height: ${ocrResult.height}`);
if (!ocrResult.success) {
log.error(`OCR 识别未找到 NPC: ${npcName}`);
log.error(`OCR 识别未找到 NPC: ${npcName},尝试滚动`);
return false;
}
@@ -161,10 +293,9 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
// 检查 NPC 名称和 F 图标的中心点 Y 坐标是否在容错范围内
if (Math.abs(centerYnpcName - centerYF) <= npctolerance) {
// log.info(`NPC '${npcName}' 和 F 图标水平对齐,执行交互, NPC: ${centerYnpcName}, F 图标: ${centerYF}`);
return true;
} else {
log.info(`NPC '${npcName}' 和 F 图标未水平对齐,尝试滚动, NPC: ${centerYnpcName}, F 图标: ${centerYF}`);
log.info(`NPC '${npcName}' 和 F 图标未水平对齐, NPC: ${centerYnpcName}, F 图标: ${centerYF}`);
return false;
}
}
@@ -205,12 +336,6 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
// 记录已购买的食材
let purchasedIngredients = new Set();
// 继续后续操作
const ingredientXRange = { min: 210, max: 390 }; // X坐标范围
const ingredientYRange = { min: 105, max: 960 }; // Y坐标范围
const ingredientTolerance = 10; // 容错区间
const clickOffset = 30; // 点击坐标容错
let allIngredientsFound = false; // 标记是否所有食材都已找到
let scrollAttemptsForIngredients = 0;
const maxScrollAttemptsForIngredients = 3; // 最大翻页次数
@@ -223,23 +348,16 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
log.info(`跳过已购买的食材: ${ingredient}`);
continue; // 跳过已购买的食材
}
// await sleep(1000);
let ocrResult = await performOcr(ingredient, ingredientXRange, ingredientYRange, ingredientTolerance);
if (ocrResult.success) {
log.info(`识别到 '${ingredient}',坐标: x=${ocrResult.x}, y=${ocrResult.y}`);
await click(ocrResult.x, ocrResult.y + clickOffset);
await sleep(1000);
// 模拟购买操作的后续点击
await click(1600, 1020); await sleep(1000); // 购买
await click(1181, 600); await sleep(200); // 选择100个
await click(1320, 780); await sleep(1000); // 最终确认
await click(1320, 780); await sleep(1000); // 点击空白
// 记录已购买的食材
// 尝试识别食材
let recognized = await recognizeIngredient(ingredient);
if (recognized) {
log.info(`识别到 '${ingredient}',执行购买操作`);
await purchaseIngredient(ingredient);
purchasedIngredients.add(ingredient);
} else {
log.error(`OCR 识别未找到 '${ingredient}'`);
log.error(`未能识别到食材: ${ingredient}`);
allIngredientsFound = false; // 本轮有食材未找到
}
}
@@ -247,7 +365,7 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
if (!allIngredientsFound) {
log.info(`在当前页面未找到所有食材,尝试翻页`);
await PageScroll(1); // 每轮翻页滑动1次
await sleep(600);
await sleep(1000);
scrollAttemptsForIngredients++;
}
}
@@ -279,9 +397,6 @@ async function clickSelectedIngredients(selectedIngredients, filePath, npcNames)
log.error("未能找到正确的 NPC 或未成功交互,跳过该路径");
}
// 自动执行划页操作
async function PageScroll(scrollCount) {
try {
@@ -303,7 +418,6 @@ async function PageScroll(scrollCount) {
leftButtonDown();
// 将鼠标移动到目标位置,模拟更自然的拖动操作
// log.info("移动鼠标");
const steps = totalDistance / stepDistance; // 分成若干步移动
for (let j = 0; j < steps; j++) {
@@ -321,52 +435,6 @@ async function PageScroll(scrollCount) {
}
}
// 定义替换映射表
const replacementMap = {
"监": "盐",
"卵": "卯"
};
// 定义一个函数用于执行OCR识别
async function performOcr(targetText, xRange, yRange, tolerance) {
// 调整区域范围以包含容错区间
let adjustedXMin = xRange.min - tolerance;
let adjustedXMax = xRange.max + tolerance;
let adjustedYMin = yRange.min - tolerance;
let adjustedYMax = yRange.max + tolerance;
/*log.info(`
adjustedXMin: ${adjustedXMin}
adjustedXMax: ${adjustedXMax}
adjustedYMin: ${adjustedYMin}
adjustedYMax: ${adjustedYMax}
`);*/
// 在捕获的区域内进行OCR识别
let ra = captureGameRegion();
let resList = ra.findMulti(RecognitionObject.ocr(
adjustedXMin, adjustedYMin,
adjustedXMax - adjustedXMin, adjustedYMax - adjustedYMin
));
// log.info(`OCR 识别数量: ${resList.count}`);
// 遍历识别结果,检查是否找到目标文本
for (let i = 0; i < resList.count; i++) {
let res = resList[i];
// log.info("0CR结果-"+ res.text);
// 后处理:根据替换映射表检查和替换错误识别的字符
let correctedText = res.text;
for (let [wrongChar, correctChar] of Object.entries(replacementMap)) {
correctedText = correctedText.replace(wrongChar, correctChar);
}
if (correctedText.includes(targetText)) {
// 如果找到目标文本,直接返回坐标
return { success: true, x: resList[i].x, y: resList[i].y, width: resList[i].width, height: resList[i].height }; // 找到符合条件的文本,返回坐标
}
}
return { success: false }; // 未找到符合条件的文本
}
// 主函数
async function AutoPath() {
log.info("开始执行自动寻路任务");