diff --git a/repo/js/YNF-AutoEat/README.md b/repo/js/YNF-AutoEat/README.md new file mode 100644 index 000000000..6c6df6d3b --- /dev/null +++ b/repo/js/YNF-AutoEat/README.md @@ -0,0 +1,28 @@ +# 伊涅芙の自助餐 + +## 食用方法: +1. 将脚本添加至调度器。 +2. 右键点击脚本以修改 JS 自定义配置。 +3. 选择队伍名称、食物种类数量和运行次数之后点击运行即可 + + +## 注意事项: +1. 队伍名称有一定长度并且清晰,避免系统识别失败 +2. 使用前确保食材充足 + + +## 致谢 +1. 感谢古又老师的悉心指导,没有古又老师就没有现在的~~伊涅芙灭绝计划~~调味品批量生产计划 +2. 感谢氨气老师和秋秋云老师,在审核时提供了宝贵的意见和建议。 + + +##作者留言: +欢迎使用该脚本,毕竟使用的过程也是帮作者发现bug的过程 +那么当你在使用时发现有bug存在,请反馈到频道并艾特作者 +当然,作者会保证尽力去修但是不一定能修好(书到用时方恨少了属于是) +希望发现问题积极反馈,来频道尽情蹂虐作者吧! +--------------------------------------------------------------------------------------------------------------------------------- +## 更新日志 +### 0.1(2025.09.15) +酱酱!“伊涅芙の自助餐(YNF-AutoEat) V0.1”闪亮登场!你还在为徒手喂料理而痛苦吗?你的苹果又没处用了吗?你做菜时的调味品又不够用了吗?快来用伊涅芙の自助餐,包你满意! + \ No newline at end of file diff --git a/repo/js/YNF-AutoEat/assets/zhengjianzhao.png b/repo/js/YNF-AutoEat/assets/zhengjianzhao.png new file mode 100644 index 000000000..0b7d98cd1 Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/zhengjianzhao.png differ diff --git a/repo/js/YNF-AutoEat/assets/日落果.png b/repo/js/YNF-AutoEat/assets/日落果.png new file mode 100644 index 000000000..726ca0003 Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/日落果.png differ diff --git a/repo/js/YNF-AutoEat/assets/星蕈.png b/repo/js/YNF-AutoEat/assets/星蕈.png new file mode 100644 index 000000000..875f30f4f Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/星蕈.png differ diff --git a/repo/js/YNF-AutoEat/assets/枯焦的星蕈.png b/repo/js/YNF-AutoEat/assets/枯焦的星蕈.png new file mode 100644 index 000000000..26406be5c Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/枯焦的星蕈.png differ diff --git a/repo/js/YNF-AutoEat/assets/泡泡桔.png b/repo/js/YNF-AutoEat/assets/泡泡桔.png new file mode 100644 index 000000000..02cea4681 Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/泡泡桔.png differ diff --git a/repo/js/YNF-AutoEat/assets/活化的星蕈.png b/repo/js/YNF-AutoEat/assets/活化的星蕈.png new file mode 100644 index 000000000..fec5a5e92 Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/活化的星蕈.png differ diff --git a/repo/js/YNF-AutoEat/assets/烛伞蘑菇.png b/repo/js/YNF-AutoEat/assets/烛伞蘑菇.png new file mode 100644 index 000000000..70249c5a9 Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/烛伞蘑菇.png differ diff --git a/repo/js/YNF-AutoEat/assets/苹果.png b/repo/js/YNF-AutoEat/assets/苹果.png new file mode 100644 index 000000000..b374bdde2 Binary files /dev/null and b/repo/js/YNF-AutoEat/assets/苹果.png differ diff --git a/repo/js/YNF-AutoEat/main.js b/repo/js/YNF-AutoEat/main.js new file mode 100644 index 000000000..7cb892921 --- /dev/null +++ b/repo/js/YNF-AutoEat/main.js @@ -0,0 +1,366 @@ +(async function () { + /** + * 封装函数,执行图片识别及点击操作(测试中,未封装完成,后续会优化逻辑) + * @param {string} imagefilePath - 模板图片路径 + * @param {number} timeout - 超时时间(秒) + * @param {number} afterBehavior - 识别后行为(0:无,1:点击,2:按F键) + * @param {number} debugmodel - 调试模式(0:关闭,1:详细日志) + * @param {number} xa - 识别区域X坐标 + * @param {number} ya - 识别区域Y坐标 + * @param {number} wa - 识别区域宽度 + * @param {number} ha - 识别区域高度 + * @param {boolean} clickCenter - 是否点击目标中心 + * @param {number} clickOffsetX - 点击位置X轴偏移量 + * @param {number} clickOffsetY - 点击位置Y轴偏移量 + * @param {number} tt - 匹配阈值(0-1) + */ + async function imageRecognitionEnhanced( + imagefilePath = "空参数", + timeout = 10, + afterBehavior = 0, + debugmodel = 0, + xa = 0, + ya = 0, + wa = 1920, + ha = 1080, + clickCenter = false, // 新增:是否点击中心 + clickOffsetX = 0, // 新增:X轴偏移量 + clickOffsetY = 0, // 新增:Y轴偏移量 + tt = 0.8 + ) { + // 参数验证 + if (xa + wa > 1920 || ya + ha > 1080) { + log.info("图片区域超出屏幕范围"); + return { found: false, error: "区域超出屏幕范围" }; + } + + const startTime = Date.now(); + let captureRegion = null; + let result = { found: false }; + + try { + // 读取模板图像 + const templateImage = file.ReadImageMatSync(imagefilePath); + if (!templateImage) { + throw new Error("无法读取模板图像"); + } + + const Imagidentify = RecognitionObject.TemplateMatch(templateImage, true); + if (tt !== 0.8) { + Imagidentify.Threshold = tt; + Imagidentify.InitTemplate(); + } + + // 循环尝试识别 + for (let attempt = 0; attempt < 10; attempt++) { + if (Date.now() - startTime > timeout * 1000) { + if (debugmodel === 1) { + log.info(`${timeout}秒超时退出,未找到图片`); + } + break; + } + + captureRegion = captureGameRegion(); + if (!captureRegion) { + await sleep(200); + continue; + } + + try { + const croppedRegion = captureRegion.DeriveCrop(xa, ya, wa, ha); + const res = croppedRegion.Find(Imagidentify); + + if (res.isEmpty()) { + if (debugmodel === 1) { + log.info("识别图片中..."); + } + } else { + // 计算基准点击位置(目标的左上角) + let clickX = res.x + xa; + let clickY = res.y + ya; + + // 如果要求点击中心,计算中心点坐标 + if (clickCenter) { + clickX += Math.floor(res.width / 2); + clickY += Math.floor(res.height / 2); + } + + // 应用自定义偏移量 + clickX += clickOffsetX; + clickY += clickOffsetY; + + if (debugmodel === 1) { + log.info("计算后点击位置:({x},{y})", clickX, clickY); + } + + // 执行识别后行为 + if (afterBehavior === 1) { + await sleep(1000); + click(clickX, clickY); + } else if (afterBehavior === 2) { + await sleep(1000); + keyPress("F"); + } + + result = { + x: clickX, + y: clickY, + w: res.width, + h: res.height, + found: true + }; + break; + } + } finally { + if (captureRegion) { + captureRegion.dispose(); + captureRegion = null; + } + } + + await sleep(200); + } + } catch (error) { + log.info(`图像识别错误: ${error.message}`); + result.error = error.message; + } + + return result; + } + + /** + * 文字OCR识别封装函数 + * @param {string} text 要识别的文字,默认为"空参数" + * @param {number} timeout 超时时间,单位为秒,默认为10秒 + * @param {number} afterBehavior 点击模式,0表示不点击,1表示点击识别到文字的位置,2表示输出模式,默认为0 + * @param {number} debugmodel 调试模式,0表示输入判断模式,1表示输出位置信息,2表示输出判断模式,默认为0 + * @param {number} x OCR识别区域的起始X坐标,默认为0 + * @param {number} y OCR识别区域的起始Y坐标,默认为0 + * @param {number} w OCR识别区域的宽度,默认为1920 + * @param {number} h OCR识别区域的高度,默认为1080 + * @returns {Promise} 包含识别结果的对象,包括识别的文字、坐标和是否找到的结果 + */ + async function textOCR(text = "空参数", timeout = 10, afterBehavior = 0, debugmodel = 0, x = 0, y = 0, w = 1920, h = 1080) { + const startTime = Date.now(); + const timeoutMs = timeout * 1000; + let foundResult = null; + + try { + while (Date.now() - startTime < timeoutMs) { + // 获取截图 + const captureRegion = captureGameRegion(); + + try { + // 对整个区域进行 OCR + const resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, w, h)); + + if (resList && resList.count > 0) { + for (let i = 0; i < resList.count; i++) { + const res = resList[i]; + + if (debugmodel === 3 && res.text.includes(text)) { + return { text: res.text, x: res.x, y: res.y, found: true }; + } + + if (res.text.includes(text)) { + if (debugmodel !== 2) { + if (debugmodel === 1) { log.info(`"${res.text}"找到`); } + + if (debugmodel === 1 && x === 0 && y === 0) { + log.info(`全图代码位置:(${res.x - 10},${res.y - 10},${res.width + 10},${res.height + 10})`); + } + + if (afterBehavior === 1) { + if (debugmodel === 1) { log.info("点击模式:开"); } + await sleep(1000); + click(res.x, res.y); + } + + if (afterBehavior === 2) { + if (debugmodel === 1) { log.info("F模式:开"); } + await sleep(100); + keyPress("F"); + } + } + + foundResult = { text: res.text, x: res.x, y: res.y, found: true }; + break; + } + } + + if (foundResult) { + return foundResult; + } + } + + // 释放OCR结果资源 + if (resList && typeof resList.dispose === 'function') { + resList.dispose(); + } + } finally { + // 确保释放截图资源 + if (captureRegion && typeof captureRegion.dispose === 'function') { + captureRegion.dispose(); + } + } + + if (debugmodel === 1 && x === 0 && y === 0) { + log.info(`"${text}"识别中……`); + } + + await sleep(100); + } + + log.info(`${timeout}秒超时退出,"${text}"未找到`); + + return { found: false }; + } catch (error) { + log.error("OCR识别过程中发生错误:", error); + return { found: false }; + } + } + + //切换队伍 + async function switchPartyIfNeeded(partyName) { + if (!partyName) { + await genshin.returnMainUi(); + return; + } + try { + log.info("正在尝试切换至" + partyName); + if (!await genshin.switchParty(partyName)) { + log.info("切换队伍失败,前往七天神像重试"); + await genshin.tpToStatueOfTheSeven(); + await genshin.switchParty(partyName); + } + } catch { + log.error("队伍切换失败,可能处于联机模式或其他不可切换状态"); + notification.error(`队伍切换失败,可能处于联机模式或其他不可切换状态`); + await genshin.returnMainUi(); + } + } + + // 传送并进入副本 + async function fuben() { + await genshin.tp(-887.193359375, 1679.44287109375); + + await switchPartyIfNeeded(party); + + let ifynf = await textOCR("伊涅芙", 3, 0, 0, 1670, 200, 120, 400); + if (!ifynf.found) { log.error("未识别到伊涅芙,请在设置中输入包含伊涅芙的队伍!"); return; } + + keyDown("w"); + await sleep(2500); + keyUp("w"); + keyPress("F"); + await sleep(4000); + await click(1600, 1015); + await sleep(1500); + await click(1600, 1015); + await sleep(9000); + leftButtonClick(); + await sleep(1500); + + log.info("古又老师说让你先die一下"); + keyDown("A"); + await sleep(3000); + keyUp("A"); + await sleep(3000); + } + + // 伊涅芙跳楼机 + async function doit() { + await sleep(3000); + await keyPress("B"); + await sleep(1000); + + //连续点击三次防止过期道具卡背包 + await click(970, 760); + await sleep(300); + await click(970, 760); + await sleep(300); + await click(970, 760); + + log.info("猜猜为什么要连续点击这个位置呢~"); + + await sleep(1000); + await click(860, 50);//点开背包,可做图像识别优化 + + const ifshiwu = await textOCR("食物", 3, 0, 0, 126, 17, 99, 53); + if (!ifshiwu.found) { + await genshin.returnMainUi(); + log.warn("未打开'食物'页面"); + return; + }//确认在食物界面 + await sleep(500); + const ifpingguo = await imageRecognitionEnhanced(pingguo, 1, 1, 0, 115, 120, 1150, 155);//识别"苹果"图片 + if (!ifpingguo.found) { + await genshin.returnMainUi(); + log.warn("没有找到指定的食物"); + return; + } + await sleep(500); + + await textOCR("使用", 5, 1, 0, 1620, 987, 225, 50); + await sleep(1000); + + const ifzjz = await imageRecognitionEnhanced(zjz, 5, 1, 0, 625, 290, 700, 360, true); + await sleep(300); + leftButtonClick(); + await sleep(300); + leftButtonClick(); + await sleep(300); + + if (!ifzjz.found) { log.error("未识别到伊涅芙"); return; } + + for (let i = 0; i < foodCount; i++) { + click(1251, 630); + await sleep(300); + } + + await textOCR("确认", 5, 1, 0, 1100, 740, 225, 50); + await sleep(500); + + await sleep(1000); + await keyPress("ESCAPE"); + await sleep(1000); + await keyPress("ESCAPE"); + + log.info("再见了伊涅芙,希望你喜欢这几分钟的戏份"); + + await sleep(1000); + keyDown("A"); + await sleep(3000); + keyUp("A"); + await sleep(3000); + } + + log.warn("使用前请仔细阅读readme并进行相关设置!"); + log.warn("请确保食材充足!"); + + //设置分辨率和缩放 + setGameMetrics(1920, 1080, 1); + await genshin.returnMainUi(); + + const food = settings.food; + const party = settings.n; + const n = settings.runNumber; + const foodCount = settings.foodNumber - 1; + const pingguo = `assets/${food}.png`; + const zjz = `assets/zhengjianzhao.png`; + + await fuben(); + + try { + for (let i = 0; i < n; i++) { + await doit(); + } + } catch (error) { + log.error(`识别图像时发生异常: ${error.message}`); + } + + await genshin.tpToStatueOfTheSeven(); + +})(); + + diff --git a/repo/js/YNF-AutoEat/manifest.json b/repo/js/YNF-AutoEat/manifest.json new file mode 100644 index 000000000..b6d47bba4 --- /dev/null +++ b/repo/js/YNF-AutoEat/manifest.json @@ -0,0 +1,22 @@ +{ + "manifest_version": 1, + "name": "伊涅芙の自助餐", + "version": "0.1", + "tags": [ + "伊涅芙", + "调味品" + ], + "description": "伊涅芙天赋吃食物,30%概率产生调味品", + "authors": [ + { + "name": "江紫烟owo", + "links": "https://github.com/jiangziyanowo" + }, + { + "name": "古又", + "links": "https://github.com/guyou45" + } + ], + "settings_ui": "settings.json", + "main": "main.js" +} diff --git a/repo/js/YNF-AutoEat/settings.json b/repo/js/YNF-AutoEat/settings.json new file mode 100644 index 000000000..dc16198ab --- /dev/null +++ b/repo/js/YNF-AutoEat/settings.json @@ -0,0 +1,35 @@ +[ + { + "name": "n", + "type": "input-text", + "label": "队伍名称(包含伊涅芙的队伍)", + "default": "" + }, + { + "name": "food", + "type": "select", + "label": "伊涅芙今天吃什么", + "options": [ + "苹果", + "日落果", + "泡泡桔", + "烛伞蘑菇", + "星蕈", + "活化的星蕈", + "枯焦的星蕈" + ], + "default": "" + }, + { + "name": "foodNumber", + "type": "input-text", + "label": "食材数量\n想让伊涅芙吃多少个(单次)", + "default": "99" + }, + { + "name": "runNumber", + "type": "input-text", + "label": "运行次数(想嘎伊涅芙多少次)", + "default": "1" + } +] \ No newline at end of file