From a6bd9eaed18f1fb98f2892f841a6960533e6cdf7 Mon Sep 17 00:00:00 2001 From: JJMdzh Date: Sun, 11 May 2025 17:10:57 +0800 Subject: [PATCH] =?UTF-8?q?js,=E8=83=8C=E5=8C=85=E6=9D=90=E6=96=99?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=20(#719)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模板匹配材料,OCR识别数量。\n数字太小可能无法识别,用?代替。\n目前支持 养成道具 和 素材 的两个大类。\n材料种类数量或导入js本地\n图包文件夹images放入assets下\n链接看manifest.json说明 --- repo/js/背包材料统计/assets/Bagpack.png | Bin 0 -> 2208 bytes .../背包材料统计/assets/CultivationItems.png | Bin 0 -> 2138 bytes repo/js/背包材料统计/assets/Materials.png | Bin 0 -> 2815 bytes repo/js/背包材料统计/assets/SliderBottom.png | Bin 0 -> 312 bytes repo/js/背包材料统计/main.js | 528 ++++++++++++++++++ repo/js/背包材料统计/manifest.json | 14 + repo/js/背包材料统计/settings.json | 30 + 7 files changed, 572 insertions(+) create mode 100644 repo/js/背包材料统计/assets/Bagpack.png create mode 100644 repo/js/背包材料统计/assets/CultivationItems.png create mode 100644 repo/js/背包材料统计/assets/Materials.png create mode 100644 repo/js/背包材料统计/assets/SliderBottom.png create mode 100644 repo/js/背包材料统计/main.js create mode 100644 repo/js/背包材料统计/manifest.json create mode 100644 repo/js/背包材料统计/settings.json diff --git a/repo/js/背包材料统计/assets/Bagpack.png b/repo/js/背包材料统计/assets/Bagpack.png new file mode 100644 index 0000000000000000000000000000000000000000..361984014c24ab492d7a9cb4051ba997d8cf4122 GIT binary patch literal 2208 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 RYPkRa002ovPDHLkV1hm3a9RKW literal 0 HcmV?d00001 diff --git a/repo/js/背包材料统计/assets/SliderBottom.png b/repo/js/背包材料统计/assets/SliderBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..77ff483205cca77953fda650467c3f3811e04408 GIT binary patch literal 312 zcmV-80muG{P)PbXFR49?1kg-YwQ4mGX%q~P@RwJ7&L@PhRKee^8uob}+VkZ_N z*oas~uuu>S3yDZnk~KF7iC;TfA%NiGY@-WvDMLr$*-+zcMrDofg(RlWg;3s7Rl)*{vb0000< KMNUMnLSTZz9DpJK literal 0 HcmV?d00001 diff --git a/repo/js/背包材料统计/main.js b/repo/js/背包材料统计/main.js new file mode 100644 index 000000000..b051e22a7 --- /dev/null +++ b/repo/js/背包材料统计/main.js @@ -0,0 +1,528 @@ +(async function () { + // 初始化游戏窗口大小和返回主界面 + setGameMetrics(1920, 1080, 1); + + // 配置参数 + const pageScrollCount = 22; // 最多滑页次数 + const OCRdelay = Math.min(99, Math.max(0, Math.floor(Number(settings.OcrDelay) || 10))); // OCR基准时长 + + // 材料分类映射表 + const materialTypeMap = { + "锻造素材": "5", + "怪物掉落素材": "3", + "一般素材": "5", + "周本素材": "3", + "烹饪食材": "5", + "角色突破素材": "3", + "木材": "5", + "宝石": "3", + "鱼饵鱼类": "5", + "角色天赋素材": "3", + "武器突破素材": "3", + }; + + // 获取设置中的材料分类,默认为"一般素材" + const materialsCategory = settings.materials || "一般素材"; + + // 材料前位定义 + const materialPriority = { + "锻造素材": 1, + "怪物掉落素材": 1, + "一般素材": 2, + "周本素材": 2, + "烹饪食材": 3, + "角色突破素材": 3, + "木材": 4, + "宝石": 4, + "鱼饵鱼类": 5, + "角色天赋素材": 5, + "武器突破素材": 6, + }; + + // 获取当前材料分类的前位 + const currentPriority = materialPriority[materialsCategory]; + const previousPriority = Math.max(1, currentPriority - 1); // 获取上一个前位 + // log.info(`正在寻找前位为 "${previousPriority}" 的材料`); + + // 获取上一个前位的所有材料分类 + const previousPriorityMaterials = Object.keys(materialPriority) + .filter(mat => materialPriority[mat] === previousPriority); + + // 获取当前材料分类的 menuOffset 对应值 + const validValues = new Set([materialTypeMap[materialsCategory]]); + + // 过滤出符合条件的材料分类 + const finalFilteredMaterials = previousPriorityMaterials + .filter(mat => validValues.has(materialTypeMap[mat])); + + // 根据材料分类获取对应的 menuOffset + const menuOffset = materialTypeMap[materialsCategory]; + if (!menuOffset) { + log.error(`未找到材料分类 "${materialsCategory}" 的对应菜单偏移值`); + return; + } + + // 提前计算所有动态坐标 + const menuClickX = Math.round(575 + (Number(menuOffset) - 1) * 96.25); // 背包菜单的 X 坐标 + + // 自定义 basename 函数 + function basename(filePath) { + const lastSlashIndex = filePath.lastIndexOf('\\'); // 或者使用 '/',取决于你的路径分隔符 + return filePath.substring(lastSlashIndex + 1); + } + + // OCR识别文本 + async function recognizeText(ocrRegion, timeout = 10000, retryInterval = 20, maxAttempts = 10, maxFailures = 3) { + let startTime = Date.now(); + let retryCount = 0; + let failureCount = 0; // 用于记录连续失败的次数 + const results = []; + const frequencyMap = {}; // 用于记录每个结果的出现次数 + + const replacementMap = { + "O": "0", "o": "0", "Q": "0", "0": "0", + "I": "1", "l": "1", "i": "1", "1": "1", + "Z": "2", "z": "2", "2": "2", + "E": "3", "e": "3", "3": "3", + "A": "4", "a": "4", "4": "4", + "S": "5", "s": "5", "5": "5", + "G": "6", "b": "6", "6": "6", + "T": "7", "t": "7", "7": "7", + "B": "8", "b": "8", "8": "8", + "g": "9", "q": "9", "9": "9", + }; + + while (Date.now() - startTime < timeout && retryCount < maxAttempts) { + let captureRegion = captureGameRegion(); + let ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); + ocrObject.threshold = 0.85; // 适当降低阈值以提高速度 + let resList = captureRegion.findMulti(ocrObject); + + if (resList.count === 0) { + failureCount++; + if (failureCount >= maxFailures) { + ocrRegion.x += 3; // 每次缩小6像素 + ocrRegion.width -= 6; // 每次缩小6像素 + retryInterval += 10; + + if (ocrRegion.width <= 12) { + return { success: false }; + } + } + retryCount++; + await sleep(retryInterval); + continue; + } + + for (let res of resList) { + let text = res.text; + text = text.split('').map(char => replacementMap[char] || char).join(''); + results.push(text); + + if (!frequencyMap[text]) { + frequencyMap[text] = 0; + } + frequencyMap[text]++; + + if (frequencyMap[text] >= 2) { + return { success: true, text: text }; + } + } + + await sleep(retryInterval); + } + + const sortedResults = Object.keys(frequencyMap).sort((a, b) => frequencyMap[b] - frequencyMap[a]); + return sortedResults.length > 0 ? { success: true, text: sortedResults[0] } : { success: false }; + } + + // 滚动页面 + async function scrollPage(totalDistance, stepDistance = 10, delayMs = 5) { + moveMouseTo(999, 750); + await sleep(50); + leftButtonDown(); + const steps = Math.ceil(totalDistance / stepDistance); + for (let j = 0; j < steps; j++) { + const remainingDistance = totalDistance - j * stepDistance; + const moveDistance = remainingDistance < stepDistance ? remainingDistance : stepDistance; + moveMouseBy(0, -moveDistance); + await sleep(delayMs); + } + await sleep(700); + leftButtonUp(); + await sleep(100); + } + + // 扫描材料 + async function scanMaterials(materialsCategory) { + // 获取前位材料名单 + const priorityMaterialNames = []; + for (const category of finalFilteredMaterials) { + const materialIconDir = `assets/images/${category}`; + const materialIconFilePaths = file.ReadPathSync(materialIconDir); + for (const filePath of materialIconFilePaths) { + const name = basename(filePath).replace(".png", ""); // 去掉文件扩展名 + priorityMaterialNames.push(name); + } + } + + // 获取当前材料分类的材料图片文件夹路径 + const materialIconDir = `assets/images/${materialsCategory}`; + const materialIconFilePaths = file.ReadPathSync(materialIconDir); + + // 创建材料种类集合 + const materialCategories = []; + const allMaterials = new Set(); // 用于记录所有需要扫描的材料名称 + for (const filePath of materialIconFilePaths) { + const mat = file.readImageMatSync(filePath); + if (mat.empty()) { + log.error(`加载图标失败:${filePath}`); + continue; // 跳过当前文件 + } + const name = basename(filePath).replace(".png", ""); // 去掉文件扩展名 + materialCategories.push({ name: name, filePath: filePath }); + allMaterials.add(name); // 将材料名称添加到集合中 + } + + // 已识别的材料集合,避免重复扫描 + const recognizedMaterials = new Set(); + + // 扫描背包中的材料 + const tolerance = 1; // 容错区间 + const startX = 117; + const startY = 121; + const OffsetWidth = 147; + const columnWidth = 123; + const columnHeight = 750; + const maxColumns = 8; + + // 用于存储图片名和材料数量的数组 + const materialInfo = []; + const unmatchedMaterialNames = new Set();// 使用 Set 来存储未匹配的材料名称,确保不重复 + // 是否已经开始计时 + let hasFoundFirstMaterial = false; + // 记录上一次发现材料的时间 + let lastFoundTime = null; + + // 初始化标志变量,确保在整个扫描过程中保持状态 + let foundPriorityMaterial = false; + let shouldEndScan = false; + + for (let scroll = 0; scroll <= pageScrollCount; scroll++) { + // log.info(`第 ${scroll+1} 页`); + + // 随机选择一句俏皮话 + const scanPhrases = [ + "扫描中... 太好啦,有这么多素材!", + "扫描中... 不错的珍宝!", + "扫描中... 侦查骑士,发现目标!", + "扫描中... 嗯哼,意外之喜!", + "扫描中... 嗯?", + "扫描中... 很好,没有放过任何角落!", + "扫描中... 会有烟花材料嘛?", + "扫描中... 嗯,这是什么?", + "扫描中... 这些宝藏积灰了,先清洗一下", + "扫描中... 哇!都是好东西!", + "扫描中... 不虚此行!", + "扫描中... 瑰丽的珍宝,令人欣喜。", + "扫描中... 是对长高有帮助的东西吗?", + "扫描中... 嗯!品相卓越!", + "扫描中... 虽无法比拟黄金,但终有价值。", + "扫描中... 收获不少,可以拿去换几瓶好酒啦。", + "扫描中... 房租和伙食费,都有着落啦!", + "扫描中... 还不赖。", + "扫描中... 荒芜的世界,竟藏有这等瑰宝。", + "扫描中... 运气还不错。", + ]; + + // 创建一个数组,用于存储未使用的俏皮话 + let tempPhrases = [...scanPhrases]; + // 打乱数组顺序,确保随机性 + tempPhrases.sort(() => Math.random() - 0.5); + + // 记录扫描开始时间 + let phrasesStartTime = Date.now(); + + // 扫描 + const scanX = startX + (maxColumns - 1) * OffsetWidth; + const scanY = startY; + + if (!foundPriorityMaterial) { + for (const name of priorityMaterialNames) { + if (recognizedMaterials.has(name)) { + continue; // 如果已经识别过,跳过 + } + + const filePath = `assets/images/${finalFilteredMaterials}/${name}.png`; + const mat = file.readImageMatSync(filePath); + if (mat.empty()) { + log.error(`加载材料图库失败:${filePath}`); + continue; // 跳过当前文件 + } + + const recognitionObject = RecognitionObject.TemplateMatch(mat, 1146, scanY, columnWidth, columnHeight); + recognitionObject.threshold = 0.8; // 设置识别阈值 + + const result = captureGameRegion().find(recognitionObject); + if (result.isExist() && result.x !== 0 && result.y !== 0) { + + foundPriorityMaterial = true; // 标记找到前位材料 + log.info(`发现前位材料: ${name},开始全列扫描`); + break; // 发现前位材料后,退出当前循环 + } + } + } + + // 如果找到前位材料,则进行全列扫描 + if (foundPriorityMaterial) { + for (let column = maxColumns - 1; column >= 0; column--) { + const scanX = startX + column * OffsetWidth; + const scanY = startY; + + for (const { name, filePath } of materialCategories) { + if (recognizedMaterials.has(name)) { + continue; // 如果已经识别过,跳过 + } + + const mat = file.readImageMatSync(filePath); + if (mat.empty()) { + log.error(`加载图标失败:${filePath}`); + continue; // 跳过当前文件 + } + + const recognitionObject = RecognitionObject.TemplateMatch(mat, scanX, scanY, columnWidth, columnHeight); + recognitionObject.threshold = 0.9; // 设置识别阈值 + + const result = captureGameRegion().find(recognitionObject); + if (result.isExist()) { + recognizedMaterials.add(name); // 标记为已识别 + await moveMouseTo(result.x, result.y); // 移动鼠标至图片 + await sleep(10); + + const ocrRegion = { + x: result.x - 1 * tolerance, + y: result.y + 97 - 1 * tolerance, + width: 66 + 2 * tolerance, + height: 22 + 2 * tolerance + }; + const ocrResult = await recognizeText(ocrRegion, 1000, OCRdelay, 10, 3); + if (ocrResult.success) { + materialInfo.push({ name: name, count: ocrResult.text }); + } else { + log.warn("{芝麻大的数看不清(>ε<)}"); + materialInfo.push({ name: name, count: "?" }); + } + // 如果是第一次发现材料,开始计时 + if (!hasFoundFirstMaterial) { + hasFoundFirstMaterial = true; + lastFoundTime = Date.now(); + } else { + // 更新上一次发现材料的时间 + lastFoundTime = Date.now(); + } + } + } + } + } + + // 每2秒输出一句俏皮话 + const phrasesTime = Date.now(); + if (phrasesTime - phrasesStartTime >= 2000) { + const selectedPhrase = tempPhrases.shift(); + log.info(selectedPhrase); + + if (tempPhrases.length === 0) { + tempPhrases = [...scanPhrases]; + tempPhrases.sort(() => Math.random() - 0.5); + } + + phrasesStartTime = phrasesTime; + } + + // 检查材料识别情况 + if (recognizedMaterials.size === allMaterials.size) { + log.info("所有材料均已识别!"); + shouldEndScan = true; + break; // 立即退出当前循环 + } + + // 如果已经发现过材料,检查是否超过3秒未发现新的材料 + if (hasFoundFirstMaterial) { + const currentTime = Date.now(); + if (currentTime - lastFoundTime > 5000) { + log.info("未发现新的材料,结束扫描"); + shouldEndScan = true; + break; // 立即退出当前循环 + } + // 如果未超过3秒,继续扫描(无需额外操作) + } + + // 检查是否已经滑到最后一页 + const sliderBottomRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/SliderBottom.png"), 1284, 916, 9, 26); + sliderBottomRo.threshold = 0.8; + + const sliderBottomResult = captureGameRegion().find(sliderBottomRo); + if (sliderBottomResult.isExist()) { + log.info("已到达最后一页!"); + shouldEndScan = true; + break; // 如果识别到滑动条底部,终止滑动 + } + + // 如果还没有到达最后一页,继续滑页 + if (scroll < pageScrollCount) { + await scrollPage(680, 10, 5); + await sleep(10); // 滑动后等待10毫秒 + } + } +// 检查是否需要结束扫描 + if (shouldEndScan) { + // 输出识别到的材料数量 + log.info(`共识别到 ${recognizedMaterials.size} 种材料`); + + const now = new Date();// 获取当前时间 + const formattedTime = now.toLocaleString(); // 使用本地时间格式化 + + const allMaterialsArray = Array.from(allMaterials); + + // 过滤 allMaterials,找出不在 recognizedMaterials 中的材料名称 + for (const name of allMaterials) { + if (!recognizedMaterials.has(name)) { + unmatchedMaterialNames.add(name); // 使用 Set 的 add 方法添加名称 + } + } + const unmatchedMaterialNamesArray = Array.from(unmatchedMaterialNames); + + // 写入本地文件 + const filePath = "recognized_materials.txt"; + const logContent = `\n${formattedTime}\n ${materialsCategory} 种类: ${recognizedMaterials.size} 数量: \n${materialInfo.map(item => `${item.name}: ${item.count}`).join(",")}\n 未匹配的材料 种类: ${unmatchedMaterialNamesArray .length} 数量: \n${unmatchedMaterialNamesArray.join(",")}\n 图库的材料 种类: ${allMaterialsArray .length} 数量: \n${allMaterialsArray.join(",")}\n`; + const result = file.WriteTextSync(filePath, logContent, true); // 追加模式 + if (result) { + log.info("成功将识别到的材料写入本地文件"); + } else { + log.error("写入本地文件失败"); + } + } +} + +// 定义所有图标的图像识别对象,每个图片都有自己的识别区域 +const BagpackRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Bagpack.png"), 58, 31, 38, 38); +const MaterialsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Materials.png"), 941, 29, 38, 38); +const CultivationItemsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/CultivationItems.png"), 749, 30, 38, 38); + +// 定义一个函数用于识别图像 +async function recognizeImage(recognitionObject, timeout = 5000) { + let startTime = Date.now(); + while (Date.now() - startTime < timeout) { + try { + // 尝试识别图像 + let imageResult = captureGameRegion().find(recognitionObject); + if (imageResult) { + // log.info(`成功识别图像,坐标: x=${imageResult.x}, y=${imageResult.y}`); + // log.info(`图像尺寸: width=${imageResult.width}, height=${imageResult.height}`); + return { success: true, x: imageResult.x, y: imageResult.y }; + } + } catch (error) { + log.error(`识别图像时发生异常: ${error.message}`); + } + await sleep(500); // 短暂延迟,避免过快循环 + } + log.warn(`经过多次尝试,仍然无法识别图像`); + return { success: false }; +} + +// 主逻辑函数 +async function MaterialPath() { + const maxStage = 4; // 最大阶段数 + let stage = 0; // 当前阶段 + + while (stage <= maxStage) { + switch (stage) { + case 0: // 返回主界面 + await genshin.returnMainUi(); + await sleep(500); + stage = 1; + break; + + case 1: // 打开背包界面 + keyPress("B"); // 打开背包界面 + await sleep(1000); + + // 尝试识别背包图标 + let backpackResult = await recognizeImage(BagpackRo, 2000); + if (backpackResult.success) { + stage = 2; // 进入下一阶段 + } else { + log.warn("未识别到背包图标,重新尝试"); + stage = 0; // 回退到阶段0 + } + break; + + case 2: // 点击动态坐标 + click(menuClickX, 75); // 点击菜单 + await sleep(500); + stage = 3; // 进入下一阶段 + break; + + case 3: // 识别材料分类 + let CategoryObject; + switch (materialsCategory) { + case "锻造素材": + case "一般素材": + case "烹饪食材": + case "木材": + case "鱼饵鱼类": + CategoryObject = MaterialsRo; + break; + case "怪物掉落素材": + case "周本素材": + case "角色突破素材": + case "宝石": + case "角色天赋素材": + case "武器突破素材": + CategoryObject = CultivationItemsRo; + break; + default: + log.error("未知的材料分类"); + stage = 0; // 回退到阶段0 + return; + } + + // 尝试识别材料分类图标 + let CategoryResult = await recognizeImage(CategoryObject, 2000); + if (CategoryResult.success && CategoryResult.x !== 0 && CategoryResult.y !== 0) { + log.info(`识别到${materialsCategory} 所在分类。`); + stage = 4; // 进入下一阶段 + } else { + log.warn("未识别到材料分类图标,重新尝试"); + stage = 2; // 回退到阶段2 + } + break; + + case 4: // 扫描材料 + log.info("芭芭拉,冲鸭!"); + await moveMouseTo(1288, 124); // 移动鼠标至滑条顶端 + await sleep(200); + leftButtonDown(); // 长按左键重置材料滑条 + await sleep(300); + leftButtonUp(); + await sleep(200); + + // 调用扫描材料的逻辑 + if (!await scanMaterials(materialsCategory)) { + // log.warn(`${pageScrollCount} 页扫描完。`); + } + // 扫描完成后,流程结束 + stage = maxStage + 1; // 确保退出循环 + break; + } + } + + // 返回主界面 + await genshin.returnMainUi(); + log.info("扫描流程结束,返回主界面。"); +} + + + // 执行主逻辑 + await MaterialPath(); +})(); diff --git a/repo/js/背包材料统计/manifest.json b/repo/js/背包材料统计/manifest.json new file mode 100644 index 000000000..abb53e329 --- /dev/null +++ b/repo/js/背包材料统计/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "name": "背包材料统计 ", + "version": "1.3", + "bgi_version": "0.44.8", + "description": "默认四行为一页;模板匹配材料,OCR识别数量。\n数字太小可能无法识别,用?代替。\n目前支持 养成道具 和 素材 的两个大类。\n材料种类数量或导入js本地\n图包文件夹images放入assets下\n链接:https://share.weiyun.com/DVBGMPzU 密码:sg7avi", + "authors": [ + { + "name": "吉吉喵" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} \ No newline at end of file diff --git a/repo/js/背包材料统计/settings.json b/repo/js/背包材料统计/settings.json new file mode 100644 index 000000000..720e35ef0 --- /dev/null +++ b/repo/js/背包材料统计/settings.json @@ -0,0 +1,30 @@ +[ + { + "name": "materials", + "type": "select", + "label": "选择材料范围(默认一般素材)", + "options": [ + "一般素材", + "怪物掉落素材", + "烹饪食材", + "周本素材", + "木材", + "角色突破素材", + "鱼饵鱼类", + "锻造素材", + "宝石", + "角色天赋素材", + "武器突破素材", + ] + }, + { + "name": "OcrDelay", + "type": "input-text", + "label": "OCR基准时间(默认:10 毫秒)" + }, + { + "name": "ascension", + "type": "checkbox", + "label": "更大更慢更准,推荐10~50\n\n=============\n数字太小可能无法识别,用?代替。\n支持 养成道具、素材 两个大类。\n材料种类、数量会导入该js目录。\n=============\n摩拉、树脂(待做)" + } +] \ No newline at end of file