JS脚本:AEscoffier_chef【更新】 (#2786)

This commit is contained in:
提瓦特钓鱼玳师
2026-01-26 16:32:02 +08:00
committed by GitHub
parent 3ff507afb2
commit b536df7321
5 changed files with 187 additions and 60 deletions

View File

@@ -1,7 +1,9 @@
# 🍽️ 一只爱可菲脚本功能说明
基于 **地图追踪 · OCR · 模板匹配** 的自动烹饪脚本,可实现 **一键刷满所有料理熟练度**
若料理已解锁自动烹饪,则不会使用手动烹饪。
待做功能:特殊料理(支持根据相关概率尽可能制作出指定数目的特殊料理)
待做功能:不可制作的料理获取、食材加工相关
---
@@ -72,13 +74,39 @@
---
## ✨ 特殊料理
### **选择烹饪数量的应用对象**
```烹饪次数```:根据填入的```烹饪数量```,填入多少次就烹饪多少次
```预期的特殊料理数```:根据填入的```烹饪数量```,计算出“必出”概率
- 假设10%的概率出特殊料理,那么```烹饪数量```为5的情况下就烹饪 10 * 5 = 50次
- 内置的特殊料理爆率如下(游戏内具体爆率未知,以下表格仅供参考)
<details>
<summary> 点击展开 </summary>
| 星级 | 奇怪 | 普通 | 美味 |
|-----|-------|-------|------|
| 1 | 0.1 | 0.15 | 0.2 |
| 2 | 0.1 | 0.1 | 0.15 |
| 3 | 0.05 | 0.1 | 0.15 |
| 4 | 0.05 | 0.05 | 0.1 |
爱可菲美味15%
</details>
---
## 🛠️ 其他说明
1. 如果在```选择料理```处选择了重复的料理仍视作选择1次
2. ```烹饪数量```设置单个数值将应用到所有选择的料理,如果使用空格隔开数值设置多个料理的数量,需要确保数值的数量等于实际选择的料理数(若出现重复项,只计入出现的第一个重复项)
2. 对应的```烹饪数量```设置单个数值将应用到所有选择的料理,如果使用空格隔开数值设置多个料理的数量,需要确保数值的数量等于实际选择的料理数(若出现重复项,只计入出现的第一个重复项)
- 例:选择的料理 A B C A B D 设置的数值 1 2 3 4 分别代表A: 1, B: 2, C: 3, D: 4
- 注意:```烹饪数量```的顺序为```选择料理```处从左到右、从上到下
- 注意:```烹饪数量```的顺序为对应的```选择料理```处从左到右、从上到下
3. JS脚本配置的食谱顺序与游戏内基本一致可以使用游戏内的筛选功能快速找到料理
4. 运行前确保料理不会超过2000上限
---
@@ -94,6 +122,7 @@
| <img src='./assets/icons/篝火边的欢腾.png' alt='篝火边的欢腾' style='zoom: 1500%;' /> | 篝火边的欢腾 | 3 | 提升暴击 | 正常料理 | <div>队伍中所有角色暴击率提升15%持续300秒。多人游戏时仅对自己的角色生效。</div> | <div>烹饪获得</div><div></div><div>【食谱】在挪德卡莱·皮拉米达城正西方的一处哨站与NPC别蕾娜对话并完成她的委托后开启精致的宝箱获得该食谱。</div> | 挪德卡莱 | 兽肉(2),火腿(1),香肠(1),胡椒(1) |
| <img src='./assets/icons/花果草糖.png' alt='花果草糖' style='zoom: 1500%;' /> | 花果草糖 | 3 | 生命上限提升 | 正常料理 | <div>队伍中所有角色生命值上限提升22%持续300秒。多人游戏时仅对自己的角色生效。</div> | <div>烹饪获得</div><div></div><div>【食谱】那夏镇「斯佩兰扎」购买</div> | 挪德卡莱 | 宿影花(2),冬凌草(2),夏槲果(2),白灵果(2) |
| <img src='./assets/icons/皎月渺渺.png' alt='皎月渺渺' style='zoom: 1500%;' /> | 皎月渺渺 | 3 | 生命上限提升 | 特殊料理 | <div>队伍中所有角色生命值上限提升30%持续300秒。多人游戏时仅对自己的角色生效。</div> | <div>烹饪获得</div><div>【角色】 哥伦比娅 </div><div>【食谱】 花果草糖 </div> | 挪德卡莱 | 宿影花(2),冬凌草(2),夏槲果(2),白灵果(2) |
| <img src='./assets/icons/糖雕·哥伦比娅.png' alt='糖雕·哥伦比娅' style='zoom: 1500%;' /> | 糖雕·哥伦比娅 | 2 | 其他,不可制作 | 探索获取,限时 | <div>好看又好吃,但是几乎没什么用。</div> | <div>队伍中有 哥伦比娅 时与那夏镇乌娜亚塔对话获得(每日仅能获得一种糖雕)</div> | 挪德卡莱 | |
| <img src='./assets/icons/香辛炸鸡块.png' alt='香辛炸鸡块' style='zoom: 1500%;' /> | 香辛炸鸡块 | 4 | 持续恢复 | 正常料理 | <div>立即为选中的角色恢复生命值上限的32%并在之后的30秒内每5秒恢复730点生命值。</div> | <div>烹饪获得</div><div></div><div>【食谱】「原神×Duolingo」联动活动第二期连续完成3天任务获取国际服限定</div> | 其他 | 禽肉(4),面粉(3),香辛料(2),薄荷(2) |
| <img src='./assets/icons/苹果焖肉(初试版).png' alt='苹果焖肉(初试版)' style='zoom: 1500%;' /> | 苹果焖肉(初试版) | 3 | 恢复血量 | 特殊料理 | <div>为选中的角色恢复生命值上限的40%并额外恢复2350点生命值。</div> | <div>烹饪获得</div><div>【角色】 杜林 </div><div>【食谱】 北地苹果焖肉 </div> | 蒙德 | 兽肉(3),苹果(3),黄油(1),胡椒(1) |
| <img src='./assets/icons/「课后作业」.png' alt='「课后作业」' style='zoom: 1500%;' /> | 「课后作业」 | 3 | 恢复血量 | 特殊料理 | <div>为选中的角色恢复生命值上限的40%并额外恢复2350点生命值。</div> | <div>烹饪获得</div><div>【角色】 雅珂达 </div><div>【食谱】 双果卷 </div> | 挪德卡莱 | 面粉(3),夏槲果(2),白灵果(2),糖(1) |
@@ -497,6 +526,7 @@
| <img src='./assets/icons/绝境求生烤鱼.png' alt='绝境求生烤鱼' style='zoom: 1500%;' /> | 绝境求生烤鱼 | 1 | 恢复血量 | 特殊料理 | <div>为选中的角色恢复生命值上限的16%并额外恢复1350点生命值。</div> | <div>烹饪获得</div><div>【角色】 刻晴 </div><div>【食谱】 烤吃虎鱼 </div> | 璃月 | 鱼肉(1),胡椒(1) |
| <img src='./assets/icons/果香串烤.png' alt='果香串烤' style='zoom: 1500%;' /> | 果香串烤 | 1 | 恢复血量 | 特殊料理 | <div>为选中的角色恢复生命值上限的16%并额外恢复1350点生命值。</div> | <div>烹饪获得</div><div>【角色】 凯亚 </div><div>【食谱】 野菇鸡肉串 </div> | 蒙德 | 蘑菇(1),禽肉(1) |
| <img src='./assets/icons/大碗茶.png' alt='大碗茶' style='zoom: 1500%;' /> | 大碗茶 | 1 | 持续恢复,不可制作 | 饮品 | <div>立即为选中的角色恢复生命值上限的14%并在之后的30秒内每5秒恢复350点生命值。</div> | <div><茶水摊摊主>凯叔 处购买</div><div><大碗茶摊主>老周叔 处购买</div><div>朱老板处购买</div> | 璃月 | |
</details>
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,4 +1,4 @@
(async function () { // 超2000上限未适配template的dispose需要完善
(async function () { // 超2000上限条件未适配,OCR特殊字符可能失效滑块底部时左下角料理文本无法识别
const food_msg = JSON.parse(file.readTextSync("assets/foodMsg.json"));
const material_list = ['蘑菇', '黑麦粉', '洋葱', '夏槲果', '卷心菜', '胡萝卜', '土豆', '酸奶油', '兽肉', '火腿', '香肠', '胡椒', '宿影花', '冬凌草', '白灵果', '禽肉', '面粉', '香辛料', '薄荷', '苹果', '黄油', '糖', '鱼肉', '奶油', '秃秃豆', '鸟蛋', '盐', '番茄', '寒涌石', '奶酪', '青蜜莓', '苦种', '虾仁', '颗粒果', '咖啡豆', '墩墩桃', '日落果', '树莓', '牛奶', '汐藻', '泡泡桔', '海露花', '螃蟹', '绯樱绣球', '红果果菇', '堇瓜', '蟹黄', '清心', '烬芯花', '果酱', '澄晶实', '培根', '烛伞蘑菇', '肉龙掌', '发酵果实汁', '茉洁草', '稻米', '白萝卜', '松茸', '沉玉仙茗', '豆腐', '绝云椒椒', '竹笋', '金鱼草', '杏仁', '小麦', '松果', '海草', '琉璃袋', '帕蒂沙兰', '神秘的肉', '莲蓬', '枣椰', '鳗肉', '须弥蔷薇', '钩钩果', '树王圣体菇', '星蕈', '嘟嘟莲', '马尾', '甜甜花', '小灯草', '「冷鲜肉」', '熏禽肉'];
const food_category = {
@@ -9,11 +9,12 @@
"其他": ["其他", "不可制作"],
}
// const food_type = ["特殊料理", "正常料理", "活动料理", "饮品", "视觉效果", "购买料理", "探索获取", "角色技能获取", "限时"]
const special_food = { // 各星级下奇怪普通美味的特殊料理爆率优菈美味10%爱可菲15%[特殊料理用]
const special_food = { // 5星仅作占位用无实际作用 [特殊料理用]
"1": [0.1, 0.15, 0.2],
"2": [0.1, 0.1, 0.15],
"3": [0.05, 0.1, 0.15],
"4": [0.05, 0.05, 0.1]
"4": [0.05, 0.05, 0.1],
"5": [0.05, 0.05, 0.1]
}
/**
@@ -139,7 +140,7 @@
*
* @param target 目标字符串
* @param candidates 字符串数组
* @returns {null}
* @returns {Promise<String>}
* @see levenshteinDistance
*/
async function findClosestMatch(target, candidates) {
@@ -376,7 +377,7 @@
}
}
if (flag) {
log.error(`区域(${x}, ${y}, ${w}, ${h})内未找到文本:${text}`);
log.debug(`区域(${x}, ${y}, ${w}, ${h})内未找到文本:${text}`);
return false;
}
} else {
@@ -597,6 +598,7 @@
// 确保滑动到顶部
await scroll_bar_to_side(1282, 112, 13, 838, 131, 930, 124, 936, 1288, "Up"); // 料理制作界面
log.info(`在当前界面寻找 ${food_name} `);
let select_food_category = food_msg[food_name]["category"];
let search_keys = []; // 大类
for (const [c_name, c_detail] of Object.entries(food_category)) {
@@ -630,8 +632,9 @@
let ocrResult = await ocr_find_area(104, 108, 1172, 857, await deal_string(food_name));
if (!ocrResult) {
while (await scroll_page(1282, 112, 13, 838, 131, 930, 1288, "Down")) {
let ocrResult = await ocr_find_area(104, 108, 1172, 857, await deal_string(food_name));
ocrResult = await ocr_find_area(104, 108, 1172, 857, await deal_string(food_name));
if (ocrResult) break;
await sleep(300);
}
}
if (ocrResult) {
@@ -666,6 +669,8 @@
log.info(`开始获取 ${food_name} 的食材余量...`);
let materialList = Object.keys(food_msg[food_name]["formula"]); // OCR纠错用
for (let i = 0; i < m_count; i++) {
let flag = false;
// 点击食材(上)
@@ -687,12 +692,19 @@
}
}
if (flag) {
let ocrName = await Ocr(736, 254, 280, 73);
let ocrName = await Ocr(736, 254, 280, 73); // 文本较少
if (ocrName) {
ocrName = await findClosestMatch(ocrName.text, material_list); // [DEBUG] material_list 或许应该换成 Object.keys(food_msg[food_name]["formula"])
ocrName = await findClosestMatch(ocrName.text, materialList); // 最大限度避免OCR误差结合动态调整的materialList[当前料理的食材列表]
materialList = materialList.filter(item => item !== ocrName);
} else {
log.error("OCR错误");
return false;
ocrName = await Ocr(736, 174, 280, 153); // 文本较多
if (ocrName) {
ocrName = await findClosestMatch(ocrName.text, Object.keys(food_msg[food_name]["formula"]));
materialList = materialList.filter(item => item !== ocrName);
} else {
log.error("OCR错误");
return false;
}
}
let item_num = await get_current_item_num();
if (item_num) {
@@ -715,9 +727,10 @@
/**
* 根据JS脚本配置的全局设置在烹饪界面选择角色加成
* @returns {Promise<void>}
* @param spl 设定为选择特殊料理
* @returns {Promise<boolean>}
*/
async function check_character_bonus() {
async function check_character_bonus(spl = false) {
await sleep(200);
click(1779, 254);
while (true) {
@@ -728,10 +741,12 @@
await sleep(200);
let ocrResult = await ocr_find_area(148, 95, 773, 937, "产出");
let flag = false;
if (settings.characterBonus === "12%概率双倍") {
if (ocrResult) ocrResult.Click();
log.info("选择角色加成: 12%概率双倍");
} else if (settings.characterBonus === "特殊料理") {
if (settings.characterBonus === "12%概率双倍" && !spl) {
if (ocrResult) {
ocrResult.Click();
log.info("选择角色加成: 12%概率双倍");
}
} else if (settings.characterBonus === "特殊料理" || spl) {
let checkOcr = await ocr_find_area(148, 95, 773, 937, "特殊");
if (checkOcr) {
checkOcr.Click();
@@ -756,7 +771,7 @@
flag = true;
}
}
if (flag) {
if (flag && !spl) {
if (ocrResult) {
ocrResult.Click();
log.info("选择角色加成: 12%概率双倍");
@@ -767,6 +782,7 @@
await sleep(500);
click(1893, 889);
await sleep(500);
return !flag;
}
/**
@@ -778,9 +794,10 @@
* 角色加成选择,特殊料理 [DEBUG]左侧角色选择8人最多有6条加成先不加滑块逻辑了
* @param food_name 料理名
* @param food_num 料理数量
* @param spl 特殊料理
* @returns {Promise<void>}
*/
async function escoffier_cook_for_u(food_name, food_num) {
async function escoffier_cook_for_u(food_name, food_num, spl = false) {
if (settings.dealInsufficient !== "禁用") {
// 食材数量检测
let material_quantity = await get_material_num(food_name);
@@ -823,9 +840,23 @@
click(1681, 1019); // 点击 制作
await sleep(800);
// 检测角色加成
await check_character_bonus();
let resultFlag = await check_character_bonus(spl);
if (!resultFlag) {
let characterName = "未知";
for (const f_msg of Object.values(food_msg)) {
if (f_msg["belonging"] === food_name) {
characterName = f_msg["character"];
break;
}
}
log.error(`未找到 ${food_name} 对应的特殊料理角色(${characterName})...`);
return false;
}
let checkOcr = await Ocr(730, 993, 124, 40);
await sleep(200);
click(1893, 889);
await sleep(500);
let checkOcr = await Ocr(730, 993, 524, 40);
if (checkOcr) {
if (!(checkOcr.text.includes("自动"))) { // 未解锁自动烹饪
// 手动烹饪默认次数
@@ -892,32 +923,60 @@
/**
* 根据settings的选择确定料理名和对应的数量
* @param spl 特殊料理
* @returns {Promise<void>} 如果成功读取,返回料理名称和料理数量的字典
*/
async function calculate_food() {
let arrays = [
Array.from(settings.selectRecovery),
Array.from(settings.selectATKBoosting),
Array.from(settings.selectAdventure),
Array.from(settings.selectDEFBoosting),
Array.from(settings.selectOthers)
]
const uniqueArray = [...new Set(arrays.flat())]; // 合并去重
let foodNum = settings.foodNum.trim().split(" ");
async function calculate_food(spl = false) {
let foodDic = {};
let foodNum, foodList;
// 读取设置项
if (spl) {
foodList = Array.from(settings.selectCharacter);
foodNum = settings.characterFoodNum.trim().split(" ");
log.debug(`解析料理数据(spl)\n${foodList.join("|")}\n${foodNum.join("|")}`);
// 将特殊料理名转换为普通料理名 [DEBUG] 未测试
let tempList = [];
for (let i = 0; i < foodList.length; i++) {
let spl_name = foodList[i].split("(")[0];
tempList.push(food_msg[spl_name]["belonging"]);
}
foodList = tempList;
} else {
let arrays = [
Array.from(settings.selectRecovery),
Array.from(settings.selectATKBoosting),
Array.from(settings.selectAdventure),
Array.from(settings.selectDEFBoosting),
Array.from(settings.selectOthers)
]
foodList = [...new Set(arrays.flat())]; // 合并去重
foodNum = settings.foodNum.trim().split(" ");
log.debug(`解析料理数据\n${foodList.join("|")}\n${foodNum.join("|")}`);
}
// 检测并合并数量
if (foodNum.length === 1) {
for (let i = 0; i < uniqueArray.length; i++) {
foodDic[uniqueArray[i]] = parseInt(foodNum[0], 10);
for (let i = 0; i < foodList.length; i++) {
foodDic[foodList[i]] = parseInt(foodNum[0], 10);
}
} else {
if (uniqueArray.length !== foodNum.length) {
if (foodList.length !== foodNum.length) {
log.error("输入的料理数与选择的料理数不一致!");
return false;
}
for (let i = 0; i < uniqueArray.length; i++) {
foodDic[uniqueArray[i]] = parseInt(foodNum[i], 10);
for (let i = 0; i < foodList.length; i++) {
foodDic[foodList[i]] = parseInt(foodNum[i], 10);
}
}
// 根据概率计算大致次数(向上取整)(spl)
if (spl && settings.characterMode === "预期的特殊料理数") {
for (const[f_name, f_num] of Object.entries(foodDic)) {
let probability = special_food[food_msg[f_name]["price"]][2];
let base = Math.ceil(1 / probability);
foodDic[f_name] = base * f_num;
log.info(`料理(${f_name})的预期烹饪次数发生更改: ${f_num} -> ${base * f_num}`)
}
}
return foodDic;
@@ -930,6 +989,9 @@
return null;
}
// 返回主界面
await genshin.returnMainUi();
// 刷满熟练度
if (settings.unlockAutoCooking) {
log.info("当前模式为刷满熟练度...");
@@ -947,24 +1009,31 @@
log.debug("料理熟练度循环...");
}
log.info("刷满熟练度 任务结束...");
// 返回主界面
await genshin.returnMainUi();
return null;
}
// 制作料理
let food_dic = await calculate_food();
if (food_dic) {
// 前往锅
let flag = await go_and_interact("锅");
if (!flag) {
log.error("未找到锅...");
return null;
}
for (const [f_name, f_num] of Object.entries(food_dic)) {
// 找到料理
let findResult = await find_and_click_food(f_name);
if (findResult) {
await escoffier_cook_for_u(f_name, f_num);
// 制作料理和特殊料理
for (let i = 0; i < 2; i++) {
let food_dic = await calculate_food(i !== 0);
if (Object.keys(food_dic).length !== 0) {
// 前往锅
let flag = await go_and_interact("锅");
if (!flag) {
log.error("未找到锅...");
return null;
}
for (const [f_name, f_num] of Object.entries(food_dic)) {
// 找到料理
let findResult = await find_and_click_food(f_name);
if (findResult) {
await escoffier_cook_for_u(f_name, f_num, i !== 0);
}
}
log.info("全部料理制作完毕...");
// 返回主界面
await genshin.returnMainUi();
}
}
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "一只爱可菲",
"version": "2.0",
"version": "2.1",
"bgi_version": "0.55.0",
"description": "专精料理制作的爱可菲(自动烹饪及解锁、特殊料理)",
"tags": [

View File

@@ -334,10 +334,38 @@
"小小阿夏包"
]
},
{
"type": "separator"
},
{
"type": "separator"
},
{
"name": "title_2",
"type": "input-text",
"label": "----------------------------特殊料理----------------------------",
"default": "⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬"
},
{
"name": "characterMode",
"type": "select",
"label": "选择烹饪数量的应用对象\n注详见README",
"options": [
"烹饪次数",
"预期的特殊料理数"
],
"default": "烹饪次数"
},
{
"name": "characterFoodNum",
"type": "input-text",
"label": "烹饪数量(1-999)[仅用于特殊料理]\n注单数字应用到全部或用单个空格隔开设置每个的数量",
"default": "0"
},
{
"name": "selectCharacter",
"type": "multi-checkbox",
"label": "【暂不可用】>>> 特料理 ✨✨✨✨✨✨✨✨✨✨",
"label": "【选择料理】>>> 特料理 ✨✨✨✨✨✨✨✨✨✨",
"options": [
"果香串烤(凯亚)",
"绝境求生烤鱼(刻晴)",
@@ -455,7 +483,7 @@
"type": "separator"
},
{
"name": "title_2",
"name": "title_3",
"type": "input-text",
"label": "----------------------------食材加工----------------------------",
"default": "⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬"
@@ -467,7 +495,7 @@
"type": "separator"
},
{
"name": "title_3",
"name": "title_4",
"type": "input-text",
"label": "-----------------------料理获取(不可制作)-----------------------",
"default": "⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬"
@@ -479,7 +507,7 @@
"type": "separator"
},
{
"name": "title_4",
"name": "title_5",
"type": "input-text",
"label": "----------------------------全局设置----------------------------",
"default": "⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬⏬"
@@ -487,7 +515,7 @@
{
"name": "dealInsufficient",
"type": "select",
"label": "检测到食材不足时\n注::禁用则不检测食材",
"label": "检测到食材不足时\n注禁用则不检测食材数量",
"options": [
"跳过此料理",
"用尽食材",