js脚本:YNF-AutoEat-demo (#1911)

* YNF-AutoEat-demo

自动喂伊涅芙吃苹果(等食材)以获得调味品

* js脚本:YNF-AutoEat-demo

自动喂伊涅芙吃苹果(等食材)以获得调味品

* 修改了部分文字描述错误的地方

第一次pr太激动,有点手忙脚乱,抱歉

* 最终改动

* 抱歉佬,添麻烦了
This commit is contained in:
江紫烟owo
2025-09-15 17:37:41 +08:00
committed by GitHub
parent 41b7423cea
commit c2dd6fdc2d
12 changed files with 451 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
# 伊涅芙の自助餐
## 食用方法:
1. 将脚本添加至调度器。
2. 右键点击脚本以修改 JS 自定义配置。
3. 选择队伍名称、食物种类数量和运行次数之后点击运行即可
## 注意事项:
1. 队伍名称有一定长度并且清晰,避免系统识别失败
2. 使用前确保食材充足
## 致谢
1. 感谢古又老师的悉心指导,没有古又老师就没有现在的~~伊涅芙灭绝计划~~调味品批量生产计划
2. 感谢氨气老师和秋秋云老师,在审核时提供了宝贵的意见和建议。
##作者留言:
欢迎使用该脚本毕竟使用的过程也是帮作者发现bug的过程
那么当你在使用时发现有bug存在请反馈到频道并艾特作者
当然,作者会保证尽力去修但是不一定能修好(书到用时方恨少了属于是)
希望发现问题积极反馈,来频道尽情蹂虐作者吧!
---------------------------------------------------------------------------------------------------------------------------------
## 更新日志
### 0.12025.09.15
酱酱“伊涅芙の自助餐YNF-AutoEat V0.1”闪亮登场!你还在为徒手喂料理而痛苦吗?你的苹果又没处用了吗?你做菜时的调味品又不够用了吗?快来用伊涅芙の自助餐,包你满意!

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

366
repo/js/YNF-AutoEat/main.js Normal file
View File

@@ -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<Object>} 包含识别结果的对象,包括识别的文字、坐标和是否找到的结果
*/
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();
})();

View File

@@ -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"
}

View File

@@ -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"
}
]