js脚本:YNF-AutoEat-demo (#1911)
* YNF-AutoEat-demo 自动喂伊涅芙吃苹果(等食材)以获得调味品 * js脚本:YNF-AutoEat-demo 自动喂伊涅芙吃苹果(等食材)以获得调味品 * 修改了部分文字描述错误的地方 第一次pr太激动,有点手忙脚乱,抱歉 * 最终改动 * 抱歉佬,添麻烦了
28
repo/js/YNF-AutoEat/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 伊涅芙の自助餐
|
||||
|
||||
## 食用方法:
|
||||
1. 将脚本添加至调度器。
|
||||
2. 右键点击脚本以修改 JS 自定义配置。
|
||||
3. 选择队伍名称、食物种类数量和运行次数之后点击运行即可
|
||||
|
||||
|
||||
## 注意事项:
|
||||
1. 队伍名称有一定长度并且清晰,避免系统识别失败
|
||||
2. 使用前确保食材充足
|
||||
|
||||
|
||||
## 致谢
|
||||
1. 感谢古又老师的悉心指导,没有古又老师就没有现在的~~伊涅芙灭绝计划~~调味品批量生产计划
|
||||
2. 感谢氨气老师和秋秋云老师,在审核时提供了宝贵的意见和建议。
|
||||
|
||||
|
||||
##作者留言:
|
||||
欢迎使用该脚本,毕竟使用的过程也是帮作者发现bug的过程
|
||||
那么当你在使用时发现有bug存在,请反馈到频道并艾特作者
|
||||
当然,作者会保证尽力去修但是不一定能修好(书到用时方恨少了属于是)
|
||||
希望发现问题积极反馈,来频道尽情蹂虐作者吧!
|
||||
---------------------------------------------------------------------------------------------------------------------------------
|
||||
## 更新日志
|
||||
### 0.1(2025.09.15)
|
||||
酱酱!“伊涅芙の自助餐(YNF-AutoEat) V0.1”闪亮登场!你还在为徒手喂料理而痛苦吗?你的苹果又没处用了吗?你做菜时的调味品又不够用了吗?快来用伊涅芙の自助餐,包你满意!
|
||||
|
||||
BIN
repo/js/YNF-AutoEat/assets/zhengjianzhao.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
repo/js/YNF-AutoEat/assets/日落果.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
repo/js/YNF-AutoEat/assets/星蕈.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
repo/js/YNF-AutoEat/assets/枯焦的星蕈.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
repo/js/YNF-AutoEat/assets/泡泡桔.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
repo/js/YNF-AutoEat/assets/活化的星蕈.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
repo/js/YNF-AutoEat/assets/烛伞蘑菇.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
repo/js/YNF-AutoEat/assets/苹果.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
366
repo/js/YNF-AutoEat/main.js
Normal 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();
|
||||
|
||||
})();
|
||||
|
||||
|
||||
22
repo/js/YNF-AutoEat/manifest.json
Normal 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"
|
||||
}
|
||||
35
repo/js/YNF-AutoEat/settings.json
Normal 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"
|
||||
}
|
||||
]
|
||||