diff --git a/repo/js/AEscoffier_chef/README.md b/repo/js/AEscoffier_chef/README.md
index 81a33b536..7a2bb2a94 100644
--- a/repo/js/AEscoffier_chef/README.md
+++ b/repo/js/AEscoffier_chef/README.md
@@ -1,7 +1,9 @@
# 🍽️ 一只爱可菲脚本功能说明
基于 **地图追踪 · OCR · 模板匹配** 的自动烹饪脚本,可实现 **一键刷满所有料理熟练度**。
+
若料理已解锁自动烹饪,则不会使用手动烹饪。
-待做功能:特殊料理(支持根据相关概率尽可能制作出指定数目的特殊料理)
+
+待做功能:不可制作的料理获取、食材加工相关
---
@@ -72,13 +74,39 @@
---
+## ✨ 特殊料理
+
+### **选择烹饪数量的应用对象**
+
+```烹饪次数```:根据填入的```烹饪数量```,填入多少次就烹饪多少次
+
+```预期的特殊料理数```:根据填入的```烹饪数量```,计算出“必出”概率
+
+ - 假设10%的概率出特殊料理,那么```烹饪数量```为5的情况下就烹饪 10 * 5 = 50次
+ - 内置的特殊料理爆率如下(游戏内具体爆率未知,以下表格仅供参考)
+
+ 点击展开
+
+ | 星级 | 奇怪 | 普通 | 美味 |
+ |-----|-------|-------|------|
+ | 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%
+
+
+
+---
+
## 🛠️ 其他说明
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 @@
|
| 篝火边的欢腾 | 3 | 提升暴击 | 正常料理 |
队伍中所有角色暴击率提升15%,持续300秒。多人游戏时,仅对自己的角色生效。
| 烹饪获得
【食谱】在挪德卡莱·皮拉米达城正西方的一处哨站与NPC别蕾娜对话并完成她的委托后,开启精致的宝箱获得该食谱。
| 挪德卡莱 | 兽肉(2),火腿(1),香肠(1),胡椒(1) |
|
| 花果草糖 | 3 | 生命上限提升 | 正常料理 | 队伍中所有角色生命值上限提升22%,持续300秒。多人游戏时,仅对自己的角色生效。
| 烹饪获得
【食谱】那夏镇「斯佩兰扎」购买
| 挪德卡莱 | 宿影花(2),冬凌草(2),夏槲果(2),白灵果(2) |
|
| 皎月渺渺 | 3 | 生命上限提升 | 特殊料理 | 队伍中所有角色生命值上限提升30%,持续300秒。多人游戏时,仅对自己的角色生效。
| 烹饪获得
【角色】 哥伦比娅
【食谱】 花果草糖
| 挪德卡莱 | 宿影花(2),冬凌草(2),夏槲果(2),白灵果(2) |
+|
| 糖雕·哥伦比娅 | 2 | 其他,不可制作 | 探索获取,限时 | 好看又好吃,但是几乎没什么用。
| 队伍中有 哥伦比娅 时与那夏镇乌娜亚塔对话获得(每日仅能获得一种糖雕)
| 挪德卡莱 | |
|
| 香辛炸鸡块 | 4 | 持续恢复 | 正常料理 | 立即为选中的角色恢复生命值上限的32%,并在之后的30秒内,每5秒恢复730点生命值。
| 烹饪获得
【食谱】「原神×Duolingo」联动活动第二期连续完成3天任务获取(国际服限定)
| 其他 | 禽肉(4),面粉(3),香辛料(2),薄荷(2) |
|
| 苹果焖肉(初试版) | 3 | 恢复血量 | 特殊料理 | 为选中的角色恢复生命值上限的40%,并额外恢复2350点生命值。
| 烹饪获得
【角色】 杜林
【食谱】 北地苹果焖肉
| 蒙德 | 兽肉(3),苹果(3),黄油(1),胡椒(1) |
|
| 「课后作业」 | 3 | 恢复血量 | 特殊料理 | 为选中的角色恢复生命值上限的40%,并额外恢复2350点生命值。
| 烹饪获得
【角色】 雅珂达
【食谱】 双果卷
| 挪德卡莱 | 面粉(3),夏槲果(2),白灵果(2),糖(1) |
@@ -497,6 +526,7 @@
|
| 绝境求生烤鱼 | 1 | 恢复血量 | 特殊料理 | 为选中的角色恢复生命值上限的16%,并额外恢复1350点生命值。
| 烹饪获得
【角色】 刻晴
【食谱】 烤吃虎鱼
| 璃月 | 鱼肉(1),胡椒(1) |
|
| 果香串烤 | 1 | 恢复血量 | 特殊料理 | 为选中的角色恢复生命值上限的16%,并额外恢复1350点生命值。
| 烹饪获得
【角色】 凯亚
【食谱】 野菇鸡肉串
| 蒙德 | 蘑菇(1),禽肉(1) |
|
| 大碗茶 | 1 | 持续恢复,不可制作 | 饮品 | 立即为选中的角色恢复生命值上限的14%,并在之后的30秒内,每5秒恢复350点生命值。
| <茶水摊摊主>凯叔 处购买
<大碗茶摊主>老周叔 处购买
朱老板处购买
| 璃月 | |
+
---
diff --git a/repo/js/AEscoffier_chef/assets/icons/糖雕·哥伦比娅.png b/repo/js/AEscoffier_chef/assets/icons/糖雕·哥伦比娅.png
new file mode 100644
index 000000000..f3bab674d
Binary files /dev/null and b/repo/js/AEscoffier_chef/assets/icons/糖雕·哥伦比娅.png differ
diff --git a/repo/js/AEscoffier_chef/main.js b/repo/js/AEscoffier_chef/main.js
index e9b374bbf..30dd83d46 100644
--- a/repo/js/AEscoffier_chef/main.js
+++ b/repo/js/AEscoffier_chef/main.js
@@ -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}
* @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}
+ * @param spl 设定为选择特殊料理
+ * @returns {Promise}
*/
- 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}
*/
- 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} 如果成功读取,返回料理名称和料理数量的字典
*/
- 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();
}
}
}
diff --git a/repo/js/AEscoffier_chef/manifest.json b/repo/js/AEscoffier_chef/manifest.json
index 8879c1a39..3ed189de5 100644
--- a/repo/js/AEscoffier_chef/manifest.json
+++ b/repo/js/AEscoffier_chef/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "一只爱可菲",
- "version": "2.0",
+ "version": "2.1",
"bgi_version": "0.55.0",
"description": "专精料理制作的爱可菲(自动烹饪及解锁、特殊料理)",
"tags": [
diff --git a/repo/js/AEscoffier_chef/settings.json b/repo/js/AEscoffier_chef/settings.json
index 83ec2376c..e59a66cc0 100644
--- a/repo/js/AEscoffier_chef/settings.json
+++ b/repo/js/AEscoffier_chef/settings.json
@@ -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": [
"跳过此料理",
"用尽食材",