JS脚本:禽肉好感 (#3155)

* 提交新的好感任务脚本

* 完善JS脚本:禽肉好感

* 完善JS脚本:禽肉好感

* 完善JS脚本:禽肉好感

* JS脚本:禽肉好感,增加预估剩余时间显示

* 替换JS脚本:鸡腿好感
This commit is contained in:
Gedley
2026-04-27 22:20:51 +08:00
committed by GitHub
parent 6515f2700b
commit 1772354e60
24 changed files with 582 additions and 212 deletions

View File

@@ -0,0 +1,21 @@
**简介:**
---
- 通过完成“动物密友”突发事件,获取角色好感、摩拉、锻造矿。
- 完成突发事件每天最多可获得10次奖励。
- 脚本会确保游戏模式为单人模式。
- 当完成突发事件10次或事件完成后未获得奖励时脚本将结束运行。
- 测试正常任务时间在8分钟左右
---
**使用说明:**
1. 添加脚本
- 在 BGI 配置组中添加“禽肉好感”脚本。请不要在调度器外直接运行。
2. 配置组设置
- 无需设置
3. JS脚本自定义配置右键点击“禽肉好感”脚本 → 修改JS脚本自定义配置
- 填写好感队的队伍名称
- 确保选择的好感队为4人队
- 确保背包里“禽肉”充足

View File

@@ -1,20 +0,0 @@
{
"info": {
"name": "到狗盆",
"type": "collect",
"author": "花火",
"version": "1.2",
"description": "",
"bgi_version": "0.35.1"
},
"positions": [
{
"id": 1,
"x": -4500.28125,
"y": -3116.0146484375,
"type": "target",
"move_mode": "walk",
"action": ""
}
]
}

View File

@@ -1,20 +0,0 @@
{
"info": {
"name": "到甜甜花",
"type": "collect",
"author": "花火",
"version": "1.2",
"description": "",
"bgi_version": "0.43.5"
},
"positions": [
{
"id": 1,
"action": "",
"move_mode": "walk",
"type": "target",
"x": -4529.2578125,
"y": -3084.009765625
}
]
}

View File

@@ -1,52 +0,0 @@
{
"info": {
"name": "到达甜甜花位置",
"type": "collect",
"author": "花火",
"version": "1.2",
"description": "",
"bgi_version": "0.43.5"
},
"positions": [
{
"id": 1,
"action": "",
"move_mode": "walk",
"type": "teleport",
"x": -4402.525390625,
"y": -3052.986328125
},
{
"id": 2,
"x": -4407.537109375,
"y": -3055.0751953125,
"type": "path",
"move_mode": "walk",
"action": ""
},
{
"id": 3,
"x": -4480.993107617283,
"y": -2996,
"type": "path",
"move_mode": "fly",
"action": ""
},
{
"id": 4,
"x": -4500.1015625,
"y": -3004.046875,
"type": "path",
"move_mode": "walk",
"action": ""
},
{
"id": 5,
"x": -4529.2578125,
"y": -3084.009765625,
"type": "target",
"move_mode": "walk",
"action": ""
}
]
}

View File

@@ -0,0 +1,85 @@
{
"info": {
"authors": [
{
"links": "https://github.com/Gedley",
"name": "Gedley"
}
],
"bgi_version": "0.45.0",
"description": "",
"enable_monster_loot_split": false,
"last_modified_time": 1776789201832,
"map_match_method": "",
"map_name": "Teyvat",
"name": "禽肉好感_初始化",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "walk",
"type": "teleport",
"x": -4492,
"y": -3208
},
{
"action": "",
"action_params": "",
"id": 2,
"move_mode": "fly",
"type": "path",
"x": -4477.25,
"y": -3188.93
},
{
"action": "",
"action_params": "",
"id": 3,
"move_mode": "dash",
"type": "path",
"x": -4495.12,
"y": -3124.78
},
{
"action": "",
"action_params": "",
"id": 4,
"move_mode": "fly",
"type": "path",
"x": -4510.1,
"y": -3108.09
},
{
"action": "",
"action_params": "",
"id": 5,
"move_mode": "jump",
"type": "path",
"x": -4512.96,
"y": -3104.62
},
{
"action": "",
"action_params": "",
"id": 6,
"move_mode": "dash",
"type": "path",
"x": -4526.72,
"y": -3086.67
},
{
"action": "",
"action_params": "",
"id": 7,
"move_mode": "walk",
"type": "path",
"x": -4529.26,
"y": -3084.01
}
]
}

View File

@@ -0,0 +1,58 @@
{
"info": {
"authors": [
{
"links": "https://github.com/Gedley",
"name": "Gedley"
}
],
"bgi_version": "0.45.0",
"description": "",
"enable_monster_loot_split": false,
"last_modified_time": 1777125351967,
"map_match_method": "",
"map_name": "Teyvat",
"name": "禽肉好感_喂狗",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "dash",
"type": "path",
"x": -4512.81,
"y": -3105.59
},
{
"action": "",
"action_params": "",
"id": 2,
"move_mode": "jump",
"type": "path",
"x": -4510.76,
"y": -3106.63
},
{
"action": "",
"action_params": "",
"id": 3,
"move_mode": "dash",
"type": "path",
"x": -4502.67,
"y": -3113.98
},
{
"action": "",
"action_params": "",
"id": 4,
"move_mode": "walk",
"type": "path",
"x": -4500.11,
"y": -3116.63
}
]
}

View File

@@ -0,0 +1,31 @@
{
"info": {
"authors": [
{
"links": "https://github.com/Gedley",
"name": "Gedley"
}
],
"bgi_version": "0.45.0",
"description": "突发事件“动物密友”中狗盆的位置",
"enable_monster_loot_split": false,
"last_modified_time": 1776787867681,
"map_match_method": "",
"map_name": "Teyvat",
"name": "禽肉好感_狗盆点位",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "walk",
"type": "target",
"x": -4500.26,
"y": -3116.57
}
]
}

View File

@@ -0,0 +1,31 @@
{
"info": {
"authors": [
{
"links": "https://github.com/Gedley",
"name": "Gedley"
}
],
"bgi_version": "0.45.0",
"description": "突发事件“动物密友”中甜甜花的位置",
"enable_monster_loot_split": false,
"last_modified_time": 1776787867681,
"map_match_method": "",
"map_name": "Teyvat",
"name": "禽肉好感_甜甜花点位",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "walk",
"type": "target",
"x": -4529.25,
"y": -3084.01
}
]
}

View File

@@ -0,0 +1,58 @@
{
"info": {
"authors": [
{
"links": "https://github.com/Gedley",
"name": "Gedley"
}
],
"bgi_version": "0.45.0",
"description": "",
"enable_monster_loot_split": false,
"last_modified_time": 1776787861554,
"map_match_method": "",
"map_name": "Teyvat",
"name": "禽肉好感_返回",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "dash",
"type": "path",
"x": -4510.72,
"y": -3107.23
},
{
"action": "",
"action_params": "",
"id": 2,
"move_mode": "jump",
"type": "path",
"x": -4512.82,
"y": -3104.66
},
{
"action": "",
"action_params": "",
"id": 3,
"move_mode": "dash",
"type": "path",
"x": -4527.63,
"y": -3085.92
},
{
"action": "",
"action_params": "",
"id": 4,
"move_mode": "walk",
"type": "path",
"x": -4529.2,
"y": -3084.2
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,131 +1,310 @@
(async function () {
const TELEPORT_COORDS = { x: 2297.60, y: -824.45 };
async function switchPartyIfNeeded(partyName) {
if (!partyName) {
await genshin.returnMainUi();
return;
// 全局变量
// 标记任务是否结束
let finished = false;
// 投喂次数
let feedCount = 0;
//模板与识别对象预加载
// “联机”指示图标裁剪范围为346, 29, 30, 30
const coOpModeRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/联机.png'), 336, 19, 50, 50);
// “回到单人模式”按钮完整范围为1483, 986, 308, 63图像裁剪范围为1514, 997, 230, 42
const singlePlayer1Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/单人模式.png'), 1483, 986, 308, 63);
// 单人模式“确认”按钮完整区域为979, 726, 370, 63裁剪范围为1010, 736, 210, 42
const singlePlayer1ConfirmRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/单人模式确认.png'), 979, 726, 370, 63);
// “离开队伍”按钮完整范围为1483, 986, 308, 63图像裁剪范围为1514, 997, 230, 42
const singlePlayer2Ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/离开队伍.png'), 1483, 986, 308, 63);
//“动物密友”任务文字图像裁剪范围为83, 253, 84, 21。登陆完成后约500毫秒后可以检测到
const friendToAnimalsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/动物密友.png'), 30, 240, 150, 400);
friendToAnimalsRo.Threshold = 0.5;
friendToAnimalsRo.InitTemplate();
// 完成“动物密友”任务文字图像裁剪范围为83, 253, 84, 21。投喂完成几乎是可以立即检测到持续时间约3000毫秒。
const finishedFriendToAnimalsRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/动物密友完成.png'), 30, 240, 150, 400);
// “动物密友突发事件”图像的裁剪范围为892, 167, 136, 37该图像在登陆完成后的1500-5500毫秒的时间内存在
const randomEventRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/突发事件.png'), 832, 151, 256, 69);
// “动物密友突发事件”图像的裁剪范围为892, 167, 136, 37。投喂完成后约1500毫秒后可以检测到持续时间约3500毫秒
const finishedRandomEventRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/事件完成.png'), 832, 151, 256, 69);
// “投喂”图像的裁剪范围为1221, 525, 60, 28
const feedRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/投喂.png'), 1215, 523, 72, 32);
// “确认”按钮完整裁剪范围为978, 725, 372, 64图像裁剪范围为1012, 736, 200, 42
const confirmRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/确认.png'), 978, 725, 372, 64);
// “禽肉数量”裁剪范围为945, 540, 14, 18
const zeroFowlRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/禽肉数量.png'), 940, 535, 40, 28);
// 事件奖励的摩拉图标裁剪范围124, 602, 21, 21。确认投喂后约2500毫秒后可以检测到持续时间约2500毫秒
const moraRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync('assets/摩拉.png'), 61, 500, 300, 300);
// 监测是否联机,若联机则退出联机状态
async function ensureSinglePlayerMode() {
let gameRegion;
let result;
let exists;
gameRegion = captureGameRegion();
result = gameRegion.find(coOpModeRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (exists) {
log.info("单人模式");
return true;
} else {
log.info("多人游戏中,尝试回到单人模式");
await genshin.tp(-4492, -3208, "Teyvat", true);
await sleep(1500);
keyPress("F2");
await sleep(1500);
gameRegion = captureGameRegion();
let result1 = gameRegion.find(singlePlayer1Ro);
let result2 = gameRegion.find(singlePlayer2Ro);
gameRegion.dispose();
if (result1.isExist()) {
result1.click();
await sleep(1500);
gameRegion = captureGameRegion();
result1.dispose();
result1 = gameRegion.find(singlePlayer1ConfirmRo);
gameRegion.dispose();
if (result1.isExist()) {
result1.click();
log.info("回到单人模式");
}
} else if (result2.isExist()) {
result2.click();
log.info("回到单人模式");
}
try {
await genshin.tp(TELEPORT_COORDS.x, TELEPORT_COORDS.y);
await sleep(3000);
log.info(`正在尝试切换至:${partyName}`);
await genshin.switchParty(partyName);
log.info(`队伍切换成功,继续下一步任务`);
} catch (error) {
log.warn("队伍切换失败,可能处于联机模式或其他不可切换状态");
await genshin.returnMainUi();
}
}
async function Feed() {
await sleep(500);
keyPress("F");
await sleep(500);
keyPress("F");
await sleep(1000);
click(1010, 760);
await sleep(1000);
}
async function AutoPath(locationName) {
try {
const filePath = `assets/AutoPath/${locationName}.json`;
await pathingScript.runFile(filePath);
} catch (error) {
log.error(`执行 ${locationName} 路径时发生错误: ${error.message}`);
}
result1.dispose();
result2.dispose();
await sleep(2000);
}
await genshin.returnMainUi();
async function OcrF() {
let capture = await captureGameRegion();
let ocr = await capture.find(RecognitionObject.ocrThis);
capture.dispose();
if (ocr.text.includes('投喂')) {
return true;
}
return false;
}
}
async function AutoFriendshipDev(times) {
log.info(`导航至甜甜花位置`);
await AutoPath('导航至甜甜花位置');
await AutoPath('到甜甜花');
await genshin.relogin();
log.info(`自动好感开始...`);
const startFirstTime = Date.now();
for (let i = 0; i < times; i++) {
log.info(`自动好感当前次数:${i + 1}/${times}`);
await AutoPath('到狗盆');
for (let j = 0; j < 3 && !await OcrF(); j++) {
await AutoPath('到狗盆');
}
await Feed();
if (i != times - 1) {
await AutoPath('到甜甜花');
await AutoPath('到甜甜花');
await genshin.relogin();
} //最后一次不需要返回到甜甜花
const estimatedCompletion = CalculateEstimatedCompletion(startFirstTime, i + 1, times);
const currentTime = LogTimeTaken(startFirstTime);
log.info(`当前进度:${i + 1}/${times} (${((i + 1) / times * 100).toFixed(1)}%)`);
log.info(`当前运行总时长:${currentTime}`);
log.info(`预计完成时间:${estimatedCompletion}`);
//切换队伍
async function switchPartyIfNeeded(partyName) {
if (!partyName) {
await genshin.returnMainUi();
return;
}
try {
log.info(`正在尝试切换至: ${partyName}`);
if (!(await genshin.switchParty(partyName))) {
throw new Error("切换失败");
}
log.info('自动好感已完成');
log.info(`成功切换至: ${partyName}`);
} catch {
log.error("队伍切换失败,可能处于联机模式或其他不可切换状态");
await genshin.returnMainUi();
}
}
//判断好感突发事件是否触发
async function randomEventTriggered() {
let index;
let gameRegion;
let result;
let exists;
await sleep(300);
for (index = 0; index < 10; index++) {
gameRegion = captureGameRegion();
result = gameRegion.find(friendToAnimalsRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (!exists) { await sleep(100); }
else { return true; }
}
for (index = 0; index < 10; index++) {
gameRegion = captureGameRegion();
result = gameRegion.find(randomEventRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (!exists) { await sleep(400); }
else { return true; }
}
gameRegion = captureGameRegion();
result = gameRegion.find(friendToAnimalsRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (exists) { return true; }
return false;
}
// 判断投喂后是否完成事件
async function finishedEvent() {
let index;
let gameRegion;
let result;
let exists;
for (index = 0; index < 10; index++) {
gameRegion = captureGameRegion();
result = gameRegion.find(finishedFriendToAnimalsRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (!exists) { await sleep(100); }
else { return true; }
}
function LogTimeTaken(startTimeParam) {
const currentTime = Date.now();
const totalTimeInSeconds = (currentTime - startTimeParam) / 1000;
const minutes = Math.floor(totalTimeInSeconds / 60);
const seconds = totalTimeInSeconds % 60;
const formattedTime = `${minutes}${seconds.toFixed(0).padStart(2, '0')}`;
return formattedTime;
for (index = 0; index < 10; index++) {
gameRegion = captureGameRegion();
result = gameRegion.find(finishedRandomEventRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (!exists) { await sleep(100); }
else { return true; }
}
// 计算预估时间
function CalculateEstimatedCompletion(startTime, current, total) {
if (current === 0) return "计算中...";
return false;
}
const elapsedTime = Date.now() - startTime;
const timePerTask = elapsedTime / current;
const remainingTasks = total - current;
const remainingTime = timePerTask * remainingTasks;
const completionDate = new Date(Date.now() + remainingTime);
return `${completionDate.toLocaleTimeString()} (约 ${Math.round(remainingTime / 60000)} 分钟)`;
// 识别奖励
async function hasEventReward() {
let index;
let gameRegion;
let result;
let exists;
for (index = 0; index < 50; index++) {
gameRegion = captureGameRegion();
result = gameRegion.find(moraRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (!exists) { await sleep(100); }
else { return true; }
}
return false;
}
// 寻找“投喂”按下F点击“确认”按钮完成投喂
async function feedDog() {
let gameRegion;
let result;
let exists;
let retry;
retry = 0;
await sleep(200); // 等待角色站稳,避免二次调整位置
do {
gameRegion = captureGameRegion();
result = gameRegion.find(feedRo);
gameRegion.dispose();
exists = result.isExist();
result.dispose();
if (!exists) {
if (++retry > 5) {
log.error("多次尝试仍未识别到“投喂”,结束任务");
return false;
}
await pathingScript.runFile("assets/pathing/鸡腿好感_狗盆点位.json");
} else { keyPress("F"); }
} while (!exists);
await sleep(1000);
gameRegion = captureGameRegion();
result = gameRegion.find(confirmRo);
try {
if (result.isExist()) { result.click(); }
else {
log.error("未识别到可交互的确认按钮");
result.dispose();
result = gameRegion.find(zeroFowlRo);
keyPress("Escape");
await sleep(1000);
await genshin.returnMainUi();
if (result.isExist()) { log.error("禽肉数量为0无法完成任务"); }
return false;
}
} finally {
result.dispose();
gameRegion.dispose();
}
function isPositiveInteger(value) {
value = Number(value);
return Number.isInteger(value) && value > 0;
}
// 启用自动拾取的实时任务
const startTime = Date.now();
dispatcher.addTimer(new RealtimeTimer("AutoPick"));
return true;
}
const messages = [
'请确保有足够的鸡腿',
'请确保队伍满员',
];
for (let message of messages) {
log.info(message);
await sleep(500);
// 检查“投喂”的结果
async function checkFeed() {
if (await finishedEvent()) {
feedCount++;
log.info("投喂成功");
if (feedCount >= 10) {
log.info("10次投喂完成");
finished = true;
return true;
}
if (!(await hasEventReward())) {
log.info("投喂成功后未识别到奖励,当日突发事件奖励已达上限");
finished = true;
}
}
}
async function main() {
let retry;
// 等待返回主界面
await genshin.returnMainUi();
// 强制传送,避免在奇奇怪怪的地方,例如“银月之庭”
await genshin.tp(-4492, -3208, "Teyvat", true);
// 判断是否为联机模式,通过模拟点击回到单人模式
while (!(await ensureSinglePlayerMode())) { await sleep(500); }
// 移动到任务刷新点
await pathingScript.runFile("assets/pathing/鸡腿好感_初始化.json");
// 判断是否设置了好感队,切换队伍
await switchPartyIfNeeded(settings.partyName);
log.info('自动好感开始...');
//默认10次自动好感
if (isPositiveInteger(settings.times)) {
log.info(`自动好感任务开始,运行:${settings.times}`);
await AutoFriendshipDev(settings.times);
} else {
log.info(`运行次数输入不合法或者未输入,使用默认值`);
times = 10;
log.info(`自动好感任务开始,运行:${times}`);
await AutoFriendshipDev(10);
retry = 0;
const startTime = Date.now();
// 任务循环
task: while (!finished) {
// 重新登陆,刷新任务
await genshin.relogin();
// 检测“动物密友”任务是否触发,若未触发则重新登陆触发
if (!(await randomEventTriggered())) {
if (++retry > 5) {
log.error("多次尝试触发突发事件失败,结束任务");
break;
}
await pathingScript.runFile("assets/pathing/鸡腿好感_甜甜花点位.json");
continue;
} else { retry = 0; }
// 前往狗盆
await pathingScript.runFile("assets/pathing/鸡腿好感_喂狗.json");
// 等待投喂
if (!(await feedDog())) { break; }
// 异步检查投喂结果
const checkFeedPromise = checkFeed();
// 返回前判断任务是否已完成,减少时间
for (let index = 0; index < 10; index++) {
if (finished) { break task; }
await sleep(10);
}
// 返回甜甜花
await pathingScript.runFile("assets/pathing/鸡腿好感_返回.json");
// 计算剩余时间
if (feedCount > 0) {
const remainingTime = (Date.now() - startTime) / feedCount * (10 - feedCount) / 1000;
const remainingMinutes = Math.floor(remainingTime / 60);
const remainingSeconds = remainingTime % 60;
log.info(`当前进度:第 ${feedCount}/10 次已完成,预计剩余时间:${remainingMinutes}${remainingSeconds.toFixed(0)}`);
}
await checkFeedPromise;
}
log.info(`自动好感运行总时长:${LogTimeTaken(startTime)}`);
})();
log.info("好感任务结束");
}
(async function () {
await main();
})();

View File

@@ -1,13 +1,13 @@
{
"manifest_version": 1,
"name": "鸡腿好感",
"version": "1.4",
"version": "1.5",
"tags": [
"好感",
"突发事件"
],
"bgi_version": "0.44.0",
"description": "通过喂狗突发事件刷好感度确保队伍满员4个角色都能刷每天上限10次",
"bgi_version": "0.45.0",
"description": "消耗禽肉喂狗,完成“动物密友”好感任务,获取角色好感、摩拉、锻造矿。确保队伍满员,每天最多10次",
"authors": [
{
"name": "花火"
@@ -15,6 +15,10 @@
{
"name": "秋云",
"links": "https://github.com/physligl"
},
{
"name": "Gedley",
"link": "https://github.com/Gedley"
}
],
"settings_ui": "settings.json",

View File

@@ -1,12 +1,7 @@
[
{
"name": "times",
"type": "input-text",
"label": "(选填)运行次数【默认10为避免失败可以酌情添加次数】"
},
{
"name": "partyName",
"type": "input-text",
"label": "(选填)需要切换的队伍名称【前往设置的七天神像切换好感队】"
"label": "好感队的队伍名称,若未设置则使用当前队伍"
}
]