From 434c32923253dcc715c0a45b19eb713f451511cc Mon Sep 17 00:00:00 2001 From: skyflag2022 <107539971+skyflag2022@users.noreply.github.com> Date: Thu, 19 Mar 2026 23:16:18 +0800 Subject: [PATCH] Ynf eat (#3012) --- repo/js/YNF-AutoEat/assets/背包数字/0.png | Bin 0 -> 564 bytes repo/js/YNF-AutoEat/assets/背包数字/1.png | Bin 0 -> 234 bytes repo/js/YNF-AutoEat/assets/背包数字/2.png | Bin 0 -> 539 bytes repo/js/YNF-AutoEat/assets/背包数字/3.png | Bin 0 -> 555 bytes repo/js/YNF-AutoEat/assets/背包数字/4.png | Bin 0 -> 454 bytes repo/js/YNF-AutoEat/assets/背包数字/5.png | Bin 0 -> 490 bytes repo/js/YNF-AutoEat/assets/背包数字/6.png | Bin 0 -> 608 bytes repo/js/YNF-AutoEat/assets/背包数字/7.png | Bin 0 -> 399 bytes repo/js/YNF-AutoEat/assets/背包数字/8.png | Bin 0 -> 567 bytes repo/js/YNF-AutoEat/assets/背包数字/9.png | Bin 0 -> 512 bytes repo/js/YNF-AutoEat/main.js | 204 +++++++++++++++++++++- repo/js/YNF-AutoEat/manifest.json | 2 +- repo/js/YNF-AutoEat/settings.json | 20 ++- 13 files changed, 217 insertions(+), 9 deletions(-) create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/0.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/1.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/2.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/3.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/4.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/5.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/6.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/7.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/8.png create mode 100644 repo/js/YNF-AutoEat/assets/背包数字/9.png diff --git a/repo/js/YNF-AutoEat/assets/背包数字/0.png b/repo/js/YNF-AutoEat/assets/背包数字/0.png new file mode 100644 index 0000000000000000000000000000000000000000..da4d074628bfb4356fb0864da7c10ed2169106ff GIT binary patch literal 564 zcmV-40?Yl0P)JP00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0m(^3K~yMHby90f zVo?-bp8L>SW@ef8(DXwyQq2?$4KqC;3c`>5+&;uWR8$l}Gi4|hcFXiinvX>9F}>}3 z%LoQ#$YpVsHcL3qZ0Gz5~7lzm4Ml>80dBfyL zB-3DeCQnII2m=104{;Y`qdutboULWgrK)({hPz)Awm!q{?r}xZGLtu9wJkv?iVWr; z!=IqYJoWYKwTpHwYF92cWrBK^RZZsMk%hCQ^aNTmdHMB92Z4!NN3I`>6EcQuzhRn|m}G zrVwO}0@8X`SoI9_W9G{gWS3LL>aTC2Wq0=m_KyCcO(nb}xJ=z4OG8y@pOzM95D5h! zme2NRkN)&L3H>22x+O^xwUJ|xrX8COiIwpWd+`PaOv8>}Uv20B0000O>_4AgKKgc*)gmcHf$9 zup#G8Vb8;N&FMVx`|6T^{GZRr@z2Uu7#J8EJMI){IMvLwi^Is!a3NPw(I(>$Ma51mGbJP?fBf7b+4D6-Qsjw> a1Viy8#eRXiPOE@UVeoYIb6Mw<&;$T|_)(|; literal 0 HcmV?d00001 diff --git a/repo/js/YNF-AutoEat/assets/背包数字/2.png b/repo/js/YNF-AutoEat/assets/背包数字/2.png new file mode 100644 index 0000000000000000000000000000000000000000..1b4ebe3639035583e45b56a02919df4e768ad5fc GIT binary patch literal 539 zcmV+$0_6RPP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0kBC#K~yMHb&$q2%U#tkmK0ThrJ5V30Vc-I zac6RZuH=Q(v-w<+NI1leYZpnUM$jW+wzl_JTd!g_90IHI;kqu9=}Syy(j1Q-V{hEV zt~=zGO01MN(02crghD|E2l^D?t0x{M9@Eh@4a;tz_4mdYKAq?QzUuOX<21p8E(r!6 zJ;)G}W`_n(D4^A9Q~puI*xaGjZjyn@BIrJVQj z?IH_{-*Kgf^pz3r%PP)FPza%HXm92Ve99L&IJ6lXO)_=wI>|Ew9l*Du6^a!Ot-AU= zmFQLJei^^j{FGDKs3E3jo_Ma?lEn%7dSd8$SZ(m-wLMIEK|Fl?v{S*q002ovPDHLkV1jL>_7eaA literal 0 HcmV?d00001 diff --git a/repo/js/YNF-AutoEat/assets/背包数字/3.png b/repo/js/YNF-AutoEat/assets/背包数字/3.png new file mode 100644 index 0000000000000000000000000000000000000000..b37488276c653aa0e9ff5758026bc16b59a9b6b1 GIT binary patch literal 555 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 98 || foodCount < 0) { log.error("食材数量请填写1-99之间的数字!"); return; }//确保食材数量1~99 - if (n <= 0) { log.error("不是哥们,运行次数还能小于0???"); return; }//确保运行次数合法 + if (mode === "固定数量模式") { + if (foodCount > 98 || foodCount < 0) { log.error("食材数量请填写1-99之间的数字!"); return; }//确保食材数量1~99 + } + if (mode === "剩余数量模式") { + if (remainingFood < 0 ) { log.error("最终剩余数量请填写大于0的数字!"); return; }//确保最终剩余数量合法 + autoCalculateRuns = true; + } + if (!autoCalculateRuns && n <= 0) { log.error("不是哥们,运行次数还能小于0???"); return; }//确保运行次数合法 // ===== 2. 子函数定义部分 ===== @@ -150,6 +162,140 @@ return result; } + /** + * 在指定区域内,用 0-9 的 PNG 模板做「多阈值 + 非极大抑制」数字识别, + * 最终把检测到的数字按左右顺序拼成一个整数返回。 + * + * @param {string} numberPngFilePath - 存放 0.png ~ 9.png 的文件夹路径(不含文件名) + * @param {number} x - 待识别区域的左上角 x 坐标,默认 0 + * @param {number} y - 待识别区域的左上角 y 坐标,默认 0 + * @param {number} w - 待识别区域的宽度,默认 1920 + * @param {number} h - 待识别区域的高度,默认 1080 + * @param {number} maxThreshold - 模板匹配起始阈值,默认 0.95(最高可信度) + * @param {number} minThreshold - 模板匹配最低阈值,默认 0.8(最低可信度) + * @param {number} splitCount - 在 maxThreshold 与 minThreshold 之间做几次等间隔阈值递减,默认 3 + * @param {number} maxOverlap - 非极大抑制时允许的最大重叠像素,默认 2;只要 x 或 y 方向重叠大于该值即视为重复框 + * + * @returns {number} 识别出的整数;若没有任何有效数字框则返回 -1 + * + * @example + * const mora = await numberTemplateMatch('摩拉数字', 860, 70, 200, 40); + * if (mora >= 0) console.log(`当前摩拉:${mora}`); + */ + async function numberTemplateMatch( + numberPngFilePath, + x = 0, y = 0, w = 1920, h = 1080, + maxThreshold = 0.95, + minThreshold = 0.8, + splitCount = 3, + maxOverlap = 2 + ) { + let ros = []; + for (let i = 0; i <= 9; i++) { + ros[i] = RecognitionObject.TemplateMatch( + file.ReadImageMatSync(`${numberPngFilePath}/${i}.png`), x, y, w, h); + } + + function setThreshold(roArr, newThreshold) { + for (let i = 0; i < roArr.length; i++) { + roArr[i].Threshold = newThreshold; + roArr[i].InitTemplate(); + } + } + + const gameRegion = captureGameRegion(); + const allCandidates = []; + + try{ + /* 1. splitCount 次等间隔阈值递减 */ + for (let k = 0; k < splitCount; k++) { + const curThr = maxThreshold - (maxThreshold - minThreshold) * k / Math.max(splitCount - 1, 1); + setThreshold(ros, curThr); + + /* 2. 0-9 每个模板跑一遍,所有框都收 */ + for (let digit = 0; digit <= 9; digit++) { + const res = gameRegion.findMulti(ros[digit]); + if (res.count === 0) continue; + + for (let i = 0; i < res.count; i++) { + const box = res[i]; + allCandidates.push({ + digit: digit, + x: box.x, + y: box.y, + w: box.width, + h: box.height, + thr: curThr + }); + } + } + } + }catch{ + gameRegion.dispose(); + } + /* 3. 无结果提前返回 -1 */ + if (allCandidates.length === 0) { + return -1; + } + + /* 4. 非极大抑制(必须 x、y 两个方向重叠都 > maxOverlap 才视为重复) */ + const adopted = []; + for (const c of allCandidates) { + let overlap = false; + for (const a of adopted) { + const xOverlap = Math.max(0, Math.min(c.x + c.w, a.x + a.w) - Math.max(c.x, a.x)); + const yOverlap = Math.max(0, Math.min(c.y + c.h, a.y + a.h) - Math.max(c.y, a.y)); + if (xOverlap > maxOverlap && yOverlap > maxOverlap) { + overlap = true; + break; + } + } + if (!overlap) { + adopted.push(c); + //log.info(`在 [${c.x},${c.y},${c.w},${c.h}] 找到数字 ${c.digit},匹配阈值=${c.thr}`); + } + } + + /* 5. 按 x 排序,拼整数;仍无有效框时返回 -1 */ + if (adopted.length === 0) return -1; + adopted.sort((a, b) => a.x - b.x); + + return adopted.reduce((num, item) => num * 10 + item.digit, 0); + } + + /** + * 识别背包中指定物品的数量 + * @param {string} itemName - 物品名称(仅用于日志) + * @param {string} templatePath - 模板图片路径 + * @returns {Promise} 识别到的数字字符串(可能为空) + */ + async function getFoodCount(itemName, templatePath) { + const ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(templatePath)); + ro.InitTemplate(); + for (let i = 0; i < 5; i++) { + const rg = captureGameRegion(); + try { + const res = rg.find(ro); + if (res.isExist()) { + const regionToCheck = { x: res.x, y: res.y + 95, width: 70, height: 20 }; + // 使用numberTemplateMatch函数识别数字 + const count = await numberTemplateMatch( + 'assets/背包数字', // 数字模板文件夹路径 + regionToCheck.x, regionToCheck.y, regionToCheck.width, regionToCheck.height + ); + const digits = count === -1 ? '' : count.toString(); + log.info(`识别到${itemName}数量为${digits}`); + //log.info(`识别到${itemName}识别区域为${regionToCheck.x}, ${regionToCheck.y}, ${regionToCheck.width}, ${regionToCheck.height}`) + return digits; // 成功识别即返回 + } + } finally { + rg.dispose(); + } + if (i < 5 - 1) await sleep(50); + } + return ''; // 未找到时返回空字符串 + } + /** * 文字OCR识别封装函数(支持空文本匹配任意文字) * @param {string} text - 要识别的文字,默认为"空参数",空字符串会匹配任意文字 @@ -432,7 +578,53 @@ await leftButtonDown(); await sleep(100); await moveMouseTo(1287, 161); + + let currentFoodCount = foodCount; + let currentFoodNumber = foodNumber; + // 只在第一次运行时获取当前食物数量并计算所有运行参数 + if (dieCount === 0 && autoCalculateRuns) { + const currentCountStr = await getFoodCount(food, pingguo); + if (currentCountStr) { + currentCount = Number(currentCountStr); + // 计算需要消耗的食物总数 + totalFoodToEat = currentCount - remainingFood; + + if (totalFoodToEat <= 0) { + log.info(`当前${food}数量为${currentCount},已经满足或低于剩余数量${remainingFood}的要求,不需要再吃了!`); + await returnMijingUi(); + n = 1; + return; + } + + // 计算每次要吃的数量,每次最多99个 + eatNumbers = []; + while (totalFoodToEat > 0) { + const eatNumber = Math.min(totalFoodToEat, 99); + eatNumbers.push(eatNumber); + totalFoodToEat -= eatNumber; + } + + // 设置运行次数 + n = eatNumbers.length; + + log.info(`当前${food}数量为${currentCount},目标剩余${remainingFood}个,需要消耗${currentCount - remainingFood}个,将分${n}次完成。`); + log.info(`计划分批次吃的数量列表为:${eatNumbers.join('、')}`); + + // 设置第一次要吃的数量 + currentFoodNumber = eatNumbers[dieCount]; + currentFoodCount = currentFoodNumber - 1; + } else { + // 如果无法获取数量,使用默认值 + log.info(`未获取到食物数量,使用固定数量模式的设置`); + autoCalculateRuns = false; + } + } else if (autoCalculateRuns) { + // 非第一次运行,使用预计算的数量 + currentFoodNumber = eatNumbers[dieCount]; + currentFoodCount = currentFoodNumber - 1; + } + while (retries < maxRetries) { const ifpingguo = await imageRecognitionEnhanced(pingguo, 1, 0, 0, 115, 120, 1150, 880);//识别"苹果"图片 if (ifpingguo.found) { @@ -446,7 +638,7 @@ await imageRecognitionEnhanced(zjz, 3, 1, 0, 625, 290, 700, 360, true);//点击伊涅芙证件照,确保吃食物的是伊涅芙 await sleep(500); - for (let i = 0; i < foodCount; i++) { + for (let i = 0; i < currentFoodCount; i++) { click(1251, 630); await sleep(150); } @@ -454,8 +646,8 @@ await click(1180, 770);//点击确认 await sleep(500); - log.info("看我一口气吃掉" + settings.foodNumber + "个" + food + "!"); - totalFoodEaten += foodNumber; + log.info("看我一口气吃掉" + currentFoodNumber + "个" + food + "!"); + totalFoodEaten += currentFoodNumber; await returnMijingUi(); await sleep(10); diff --git a/repo/js/YNF-AutoEat/manifest.json b/repo/js/YNF-AutoEat/manifest.json index 01a83d514..1ce664447 100644 --- a/repo/js/YNF-AutoEat/manifest.json +++ b/repo/js/YNF-AutoEat/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "伊涅芙の自助餐", - "version": "1.6.3", + "version": "1.6.4", "tags": [ "伊涅芙", "调味品" diff --git a/repo/js/YNF-AutoEat/settings.json b/repo/js/YNF-AutoEat/settings.json index e435d56ba..dc5cda0be 100644 --- a/repo/js/YNF-AutoEat/settings.json +++ b/repo/js/YNF-AutoEat/settings.json @@ -1,4 +1,14 @@ [ + { + "name": "mode", + "type": "select", + "label": "运行模式", + "options": [ + "固定数量模式", + "剩余数量模式" + ], + "default": "固定数量模式" + }, { "name": "n", "type": "input-text", @@ -20,16 +30,22 @@ ], "default": "苹果" }, + { + "name": "remainingFood", + "type": "input-text", + "label": "最终剩余数量(剩余数量模式下生效)", + "default": "100" + }, { "name": "foodNumber", "type": "input-text", - "label": "食材数量\n想让伊涅芙吃多少个(单次)", + "label": "食材数量\n想让伊涅芙吃多少个(固定数量模式下生效)", "default": "99" }, { "name": "runNumber", "type": "input-text", - "label": "运行次数(想嘎伊涅芙多少次)", + "label": "运行次数\n想嘎伊涅芙多少次(固定数量模式下生效)", "default": "1" } ] \ No newline at end of file