Files
bettergi-scripts-list/repo/js/OCR购买食材/main.js
2025-04-01 21:20:24 +08:00

325 lines
14 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.

// 定义所有食材及其对应的路径文件和 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`;
// 定义所有可能的食材,注意料理名字长度可能超过识图范围
const ingredients = [
"枫达", "盐", "洋葱", "牛奶", "番茄", "卷心菜", "土豆", "小麦", "胡椒","稻米", "豆腐", "杏仁", "鱼肉", "螃蟹", "虾仁", "咖啡豆", "秃秃豆", "发酵果实汁"
];
// 筛选出用户选择的食材及其对应的路径文件和 NPC
const selectedIngredients = []; // 在函数外部声明一次
const selectedPaths = new Map();
const ingredientPaths = {
"枫达": [fengdanGroceryFilePath, cafeLuzheFilePath],
"盐": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"洋葱": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"牛奶": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"番茄": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"卷心菜": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"土豆": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"小麦": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"胡椒": [mondstadtGroceryFilePath, liyueGroceryFilePath, groceryFilePath, fengdanGroceryFilePath],
"稻米": [liyueGroceryFilePath, groceryFilePath],
"虾仁": [liyueGroceryFilePath, groceryFilePath, sumiCityFishPath, omosPortFishPath],
"豆腐": [liyueGroceryFilePath, groceryFilePath],
"杏仁": [liyueGroceryFilePath, fengdanGroceryFilePath],
"鱼肉": [liyueWanminFilePath, charcoalFilePath, sumiCityFishPath, omosPortFishPath, azaleVillMerPath],
"螃蟹": [liyueWanminFilePath, charcoalFilePath, sumiCityFishPath, omosPortFishPath],
"秃秃豆": [fengdanGroceryFilePath, azaleVillMerPath],
"咖啡豆": [cafeLuzheFilePath],
"发酵果实汁": [fengdanGroceryFilePath]
};
// 定义所有NPC名注意名字长度可能超过识图范围
const npcNames = {
[mondstadtGroceryFilePath]: ["布兰琪"],
[liyueGroceryFilePath]: ["东升"],
[liyueWanminFilePath]: ["卵师傅", "卯师傅"],
[groceryFilePath]: ["葵"],
[charcoalFilePath]: ["志村勘"],
[fengdanGroceryFilePath]: ["布希柯"],
[cafeLuzheFilePath]: ["阿鲁埃"],
[sumiCityFishPath]: ["珀姆"],
[omosPortFishPath]: ["布特罗斯"],
[azaleVillMerPath]: ["阿扎莱"]
};
for (const ingredient of ingredients) {
if (settings[ingredient]) {
selectedIngredients.push(ingredient);
ingredientPaths[ingredient].forEach(path => {
if (!selectedPaths.has(path)) {
selectedPaths.set(path, []);
}
selectedPaths.get(path).push(ingredient);
});
}
}
if (selectedIngredients.length === 0) {
log.error("未选择任何食材,退出任务");
throw new Error("未选择任何食材,任务终止"); // 抛出异常以终止任务
}
// 汇总即将购买的食材信息
const purchaseSummary = selectedIngredients.join(", ");
log.info(`即将购买: ${purchaseSummary}`);
// 定义一个函数用于模拟按键操作
async function simulateKeyOperations(key, duration) {
keyDown(key);
await sleep(duration);
keyUp(key);
await sleep(500); // 释放按键后等待 500 毫秒
}
// 定义一个函数用于识别并点击用户选择的食材
async function clickSelectedIngredients(selectedIngredients, filePath, npcNames) {
log.info(`加载路径文件: ${filePath}`);
await pathingScript.runFile(filePath);
await sleep(1000);
log.info("路径文件执行完成");
// 识别并交互 NPC
let attempts = 0;
const maxAttempts = 5; // 最大尝试次数
const npcxRange = { min: 1190, max: 1320 }; // X轴固定区间
const npcyRanges = [478, 514, 552]; // 可能的Y轴坐标
const tolerance = 10; // 容错区间
const npctolerance = 10; // 容错区间
let npcOcrResult = { success: false }; // 初始化 npcOcrResult
// 判断 Y 坐标是否在容错范围内
function isYWithinTolerance(y, targetY, tolerance) {
return y >= targetY - tolerance && y <= targetY + tolerance;
}
// 执行点击操作
async function performClickOperations(filePath) {
if (filePath === liyueGroceryFilePath || filePath === groceryFilePath || filePath === sumiCityFishPath) {
log.info("执行璃月杂货商或稻妻九十九物店主的点击操作");
await click(1300, 650); await sleep(1000);
await click(1300, 650); await sleep(1000);
await click(1600, 1020); await sleep(1000);
} else {
log.info("执行其他路径文件的点击操作");
await click(1300, 580); await sleep(1000);
await click(1300, 580); await sleep(1000);
await click(1600, 1020); await sleep(1000);
}
}
while (attempts < maxAttempts) {
attempts++;
log.info(`尝试识别 NPC尝试次数: ${attempts}`);
for (const npcName of npcNames) {
log.info(`尝试识别 NPC: ${npcName}`);
npcOcrResult = await performOcr(npcName, npcxRange, { min: 470, max: 602 }, tolerance);
if (npcOcrResult.success) {
if (
isYWithinTolerance(npcOcrResult.y, 478, npctolerance) ||
isYWithinTolerance(npcOcrResult.y, 514, npctolerance)
) {
// 如果 Y 坐标在 478 或 514 的容错范围内,直接按下 F 键
log.info(`直接按下 F 键与 NPC ${npcName} 交互...`);
keyPress("F");
await sleep(2000);
// 执行点击操作
await performClickOperations(filePath);
break; // 成功交互,退出内层循环
} else if (isYWithinTolerance(npcOcrResult.y, 552, npctolerance)) {
// 如果 Y 坐标在 552 的容错范围内,调整当前路径
log.info(`Y 坐标在 552 的容错范围内,调整当前路径`);
await simulateKeyOperations("S", 600); // 后退 600 毫秒
await simulateKeyOperations("W", 800); // 前进 800 毫秒
} else {
log.error(`识别到的 Y 坐标 ${npcOcrResult.y} 不在预期范围内,尝试次数: ${attempts}`);
}
break; // 成功识别到 NPC退出内层循环
} else {
if (attempts === 1) {
// 第一次失败时,尝试调整位置
await simulateKeyOperations("S", 600); // 后退 600 毫秒
await simulateKeyOperations("W", 800); // 前进 800 毫秒
} else if (attempts === 2) {
// 第二次失败时,重新加载路径文件
log.info("重新加载路径文件");
await pathingScript.runFile(filePath);
await sleep(1000);
}
log.error(`OCR 识别未找到 NPC: ${npcName},尝试次数: ${attempts}`);
}
}
if (npcOcrResult.success) {
break; // 成功识别到 NPC退出外层循环
}
}
if (attempts >= maxAttempts) {
log.error(`识别 NPC 失败 ${maxAttempts} 次,放弃该路线`);
return; // 放弃该路线
}
// 记录已购买的食材
const purchasedIngredients = new Set();
// 继续后续操作
const ingredientXRange = { min: 220, max: 390 }; // X坐标范围
const ingredientYRange = { min: 95, max: 950 }; // Y坐标范围
const ingredientTolerance = 10; // 容错区间
const clickOffset = 30; // 点击坐标容错
let allIngredientsFound = false; // 标记是否所有食材都已找到
let scrollAttempts = 0;
const maxScrollAttempts = 3; // 最大翻页次数
while (!allIngredientsFound && scrollAttempts < maxScrollAttempts) {
allIngredientsFound = true; // 假设本轮所有食材都已找到,若后续发现未找到则修改为 false
for (const ingredient of selectedIngredients) {
if (purchasedIngredients.has(ingredient)) {
log.info(`跳过已购买的食材: ${ingredient}`);
continue; // 跳过已购买的食材
}
const 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); // 点击空白
// 记录已购买的食材
purchasedIngredients.add(ingredient);
} else {
log.error(`OCR 识别未找到 '${ingredient}'`);
allIngredientsFound = false; // 本轮有食材未找到
}
}
if (!allIngredientsFound) {
log.info(`在当前页面未找到所有食材,尝试翻页`);
await PageScroll(1); // 每轮翻页滑动1次
await sleep(600);
scrollAttempts++;
}
}
if (!allIngredientsFound) {
log.error(`在所有页面中未找到所有食材,跳过该路径`);
}
// 最后点击退出按钮
log.info("点击退出按钮...");
await click(1845, 45); // 退出
await sleep(2000);
}
// 自动执行划页操作
async function PageScroll(scrollCount) {
try {
const clickX = 1200; // 假设点击的起始坐标
const clickY = 900;
const totalDistance = 500; // 假设每次滑动的总距离
const stepDistance = 15; // 每步移动的距离
for (let i = 0; i < scrollCount; ++i) {
log.info(`开始第 ${i + 1} 次滑动`);
// 如果点击坐标为 (0, 0),则跳过点击
if (clickX !== 0 || clickY !== 0) {
moveMouseTo(clickX, clickY); // 移动到指定坐标
await sleep(100);
}
// 按住鼠标左键
leftButtonDown();
// 将鼠标移动到目标位置,模拟更自然的拖动操作
log.info("移动鼠标");
const steps = totalDistance / stepDistance; // 分成若干步移动
for (let j = 0; j < steps; j++) {
moveMouseBy(0, -stepDistance); // 每次移动 stepDistance 像素
await sleep(10); // 每次移动后延迟10毫秒
}
// 释放鼠标左键
await sleep(700);
leftButtonUp();
await sleep(100);
}
} catch (error) {
log.error(`执行划页操作时发生错误:${error.message}`);
}
}
// 定义一个函数用于执行OCR识别
async function performOcr(targetText, xRange, yRange, tolerance) {
// 调整区域范围以包含容错区间
const adjustedXMin = xRange.min - tolerance;
const adjustedXMax = xRange.max + tolerance;
const adjustedYMin = yRange.min - tolerance;
const adjustedYMax = yRange.max + tolerance;
/*/ log.info(`
adjustedXMin: ${adjustedXMin}
adjustedXMax: ${adjustedXMax}
adjustedYMin: ${adjustedYMin}
adjustedYMax: ${adjustedYMax}
`);*/
// 在捕获的区域内进行OCR识别
const ra = captureGameRegion();
const resList = ra.findMulti(RecognitionObject.ocr(
adjustedXMin, adjustedYMin,
adjustedXMax - adjustedXMin, adjustedYMax - adjustedYMin
));
// log.info(`OCR 识别数量: ${resList.count}`);
// 遍历识别结果,检查是否找到目标文本
for (let i = 0; i < resList.count; i++) {
if (resList[i].text.includes(targetText)) {
// 如果找到目标文本,直接返回坐标
return { success: true, x: resList[i].x, y: resList[i].y }; // 找到符合条件的文本,返回坐标
}
}
return { success: false }; // 未找到符合条件的文本
}
// 主函数
async function AutoPath() {
log.info("开始执行自动寻路任务");
// 加载路径文件和 NPC 名称
for (const [path, ingredients] of selectedPaths) {
const npcName = npcNames[path];
await clickSelectedIngredients(ingredients, path, npcName);
}
}
// 执行主函数
(async function () {
setGameMetrics(1920, 1080, 1);
await genshin.returnMainUi();
await AutoPath();
})();