mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-15 03:23:22 +08:00
给OCR一点时间 (#2597)
* 给OCR一点时间 OCR识别前增加等待时间,增加两模板匹配 * Update README.md * 增加重购逻辑 * 调用setTime函数 * 换回原来的识图函数
This commit is contained in:
@@ -19,208 +19,75 @@ let userName = settings.userName || "默认账户";
|
||||
return userName;
|
||||
}
|
||||
|
||||
// 设置游戏时间
|
||||
async function setTime(hour, minute) {
|
||||
// 关于setTime
|
||||
// 原作者: Tim
|
||||
// 脚本名称: SetTimeMinute - 精确调整游戏时间到分钟
|
||||
// 脚本版本: 1.0
|
||||
// Hash: f5c2547dfc286fc643c733d630f775e8fbf12971
|
||||
|
||||
// 设置游戏分辨率和DPI缩放
|
||||
setGameMetrics(1920, 1080, 1);
|
||||
// 圆心坐标
|
||||
const centerX = 1441;
|
||||
const centerY = 501.6;
|
||||
// 半径
|
||||
const r1 = 30;
|
||||
const r2 = 150;
|
||||
const r3 = 300;
|
||||
const stepDuration = 50;
|
||||
|
||||
function getPosition(r, index) {
|
||||
let angle = index * Math.PI / 720;
|
||||
return [Math.round(centerX + r * Math.cos(angle)), Math.round(centerY + r * Math.sin(angle))];
|
||||
}
|
||||
async function mouseClick(x, y) {
|
||||
moveMouseTo(x, y);
|
||||
await sleep(50);
|
||||
leftButtonDown();
|
||||
await sleep(50);
|
||||
leftButtonUp();
|
||||
await sleep(stepDuration);
|
||||
}
|
||||
async function mouseClickAndMove(x1, y1, x2, y2) {
|
||||
moveMouseTo(x1, y1);
|
||||
await sleep(50);
|
||||
leftButtonDown();
|
||||
await sleep(50);
|
||||
moveMouseTo(x2, y2);
|
||||
await sleep(50);
|
||||
leftButtonUp();
|
||||
await sleep(stepDuration);
|
||||
}
|
||||
async function setTime(hour, minute) {
|
||||
const end = (hour + 6) * 60 + minute - 20;
|
||||
const n = 3;
|
||||
for (let i = - n + 1; i < 1; i++) {
|
||||
let [x, y] = getPosition(r1, end + i * 1440 / n);
|
||||
await mouseClick(x, y);
|
||||
}
|
||||
let [x1, y1] = getPosition(r2, end + 5);
|
||||
let [x2, y2] = getPosition(r3, end + 20 + 0.5);
|
||||
await mouseClickAndMove(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
let h = Math.floor(hour + minute / 60);
|
||||
const m = Math.floor(hour * 60 + minute) - h * 60;
|
||||
h = ((h % 24) + 24) % 24;
|
||||
log.info(`设置时间到 ${h} 点 ${m} 分`);
|
||||
await keyPress("Escape");
|
||||
await sleep(1000);
|
||||
await click(50, 700);
|
||||
await sleep(2000);
|
||||
await setTime(h, m);
|
||||
await sleep(1000);
|
||||
await click(1500, 1000);//确认
|
||||
await sleep(2000);
|
||||
await genshin.returnMainUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断任务是否已刷新
|
||||
* 判断任务是否已刷新(固定为每周四4点刷新)
|
||||
* @param {string} filePath - 存储最后完成时间的文件路径
|
||||
* @param {object} options - 配置选项
|
||||
* @param {string} [options.refreshType] - 刷新类型: 'hourly'|'daily'|'weekly'|'monthly'|'custom'
|
||||
* @param {number} [options.customHours] - 自定义小时数(用于'custom'类型)
|
||||
* @param {number} [options.dailyHour=4] - 每日刷新的小时(0-23)
|
||||
* @param {number} [options.weeklyDay=1] - 每周刷新的星期(0-6, 0是周日)
|
||||
* @param {number} [options.weeklyHour=4] - 每周刷新的小时(0-23)
|
||||
* @param {number} [options.monthlyDay=1] - 每月刷新的日期(1-31)
|
||||
* @param {number} [options.monthlyHour=4] - 每月刷新的小时(0-23)
|
||||
* @returns {Promise<boolean>} - 是否已刷新
|
||||
*/
|
||||
async function isTaskRefreshed(filePath, options = {}) {
|
||||
const {
|
||||
refreshType = 'hourly', // 默认每小时刷新
|
||||
customHours = 24, // 自定义刷新小时数默认24
|
||||
dailyHour = 4, // 每日刷新默认凌晨4点
|
||||
weeklyDay = 1, // 每周刷新默认周一(0是周日)
|
||||
weeklyHour = 4, // 每周刷新默认凌晨4点
|
||||
monthlyDay = 1, // 每月刷新默认第1天
|
||||
monthlyHour = 4 // 每月刷新默认凌晨4点
|
||||
} = options;
|
||||
async function isTaskRefreshed(filePath) {
|
||||
const WEEKLY_DAY = 4; // 周四(0是周日,1是周一,4是周四)
|
||||
const WEEKLY_HOUR = 4; // 凌晨4点
|
||||
|
||||
try {
|
||||
// 读取文件内容
|
||||
let content = await file.readText(filePath);
|
||||
const lastTime = new Date(content);
|
||||
const nowTime = new Date();
|
||||
|
||||
|
||||
let shouldRefresh = false;
|
||||
|
||||
|
||||
switch (refreshType) {
|
||||
case 'hourly': // 每小时刷新
|
||||
shouldRefresh = (nowTime - lastTime) >= 3600 * 1000;
|
||||
break;
|
||||
|
||||
case 'daily': // 每天固定时间刷新
|
||||
// 检查是否已经过了当天的刷新时间
|
||||
const todayRefresh = new Date(nowTime);
|
||||
todayRefresh.setHours(dailyHour, 0, 0, 0);
|
||||
|
||||
// 如果当前时间已经过了今天的刷新时间,检查上次完成时间是否在今天刷新之前
|
||||
if (nowTime >= todayRefresh) {
|
||||
shouldRefresh = lastTime < todayRefresh;
|
||||
} else {
|
||||
// 否则检查上次完成时间是否在昨天刷新之前
|
||||
const yesterdayRefresh = new Date(todayRefresh);
|
||||
yesterdayRefresh.setDate(yesterdayRefresh.getDate() - 1);
|
||||
shouldRefresh = lastTime < yesterdayRefresh;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'weekly': // 每周固定时间刷新
|
||||
// 获取本周的刷新时间
|
||||
const thisWeekRefresh = new Date(nowTime);
|
||||
// 计算与本周指定星期几的差值
|
||||
const dayDiff = (thisWeekRefresh.getDay() - weeklyDay + 7) % 7;
|
||||
thisWeekRefresh.setDate(thisWeekRefresh.getDate() - dayDiff);
|
||||
thisWeekRefresh.setHours(weeklyHour, 0, 0, 0);
|
||||
|
||||
// 如果当前时间已经过了本周的刷新时间
|
||||
if (nowTime >= thisWeekRefresh) {
|
||||
shouldRefresh = lastTime < thisWeekRefresh;
|
||||
} else {
|
||||
// 否则检查上次完成时间是否在上周刷新之前
|
||||
const lastWeekRefresh = new Date(thisWeekRefresh);
|
||||
lastWeekRefresh.setDate(lastWeekRefresh.getDate() - 7);
|
||||
shouldRefresh = lastTime < lastWeekRefresh;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'monthly': // 每月固定时间刷新
|
||||
// 获取本月的刷新时间
|
||||
const thisMonthRefresh = new Date(nowTime);
|
||||
// 设置为本月指定日期的凌晨
|
||||
thisMonthRefresh.setDate(monthlyDay);
|
||||
thisMonthRefresh.setHours(monthlyHour, 0, 0, 0);
|
||||
|
||||
// 如果当前时间已经过了本月的刷新时间
|
||||
if (nowTime >= thisMonthRefresh) {
|
||||
shouldRefresh = lastTime < thisMonthRefresh;
|
||||
} else {
|
||||
// 否则检查上次完成时间是否在上月刷新之前
|
||||
const lastMonthRefresh = new Date(thisMonthRefresh);
|
||||
lastMonthRefresh.setMonth(lastMonthRefresh.getMonth() - 1);
|
||||
shouldRefresh = lastTime < lastMonthRefresh;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'custom': // 自定义小时数刷新
|
||||
shouldRefresh = (nowTime - lastTime) >= customHours * 3600 * 1000;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`未知的刷新类型: ${refreshType}`);
|
||||
}
|
||||
|
||||
// 如果文件内容无效或不存在,视为需要刷新
|
||||
if (!content || isNaN(lastTime.getTime())) {
|
||||
// 如果文件内容为空或无效,视为需要刷新
|
||||
if (!content) {
|
||||
await file.writeText(filePath, '');
|
||||
shouldRefresh = true;
|
||||
}
|
||||
|
||||
if (shouldRefresh) {
|
||||
notification.send(`购买狗粮已经刷新,执行脚本`);
|
||||
|
||||
|
||||
return true;
|
||||
} else {
|
||||
log.info(`购买狗粮未刷新`);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// 如果文件不存在,创建新文件并返回true(视为需要刷新)
|
||||
const createResult = await file.writeText(filePath, '');
|
||||
if (createResult) {
|
||||
log.info("创建新时间记录文件成功,执行脚本");
|
||||
return true;
|
||||
}
|
||||
else throw new Error(`创建新文件失败`);
|
||||
|
||||
const lastTime = new Date(content);
|
||||
const nowTime = new Date();
|
||||
|
||||
// 检查上次记录时间是否有效
|
||||
if (isNaN(lastTime.getTime())) {
|
||||
log.info("时间记录文件内容无效,执行脚本");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取本周的刷新时间
|
||||
const thisWeekRefresh = new Date(nowTime);
|
||||
|
||||
// 计算与本周周四的差值
|
||||
const dayDiff = (thisWeekRefresh.getDay() - WEEKLY_DAY + 7) % 7;
|
||||
thisWeekRefresh.setDate(thisWeekRefresh.getDate() - dayDiff);
|
||||
thisWeekRefresh.setHours(WEEKLY_HOUR, 0, 0, 0);
|
||||
|
||||
// 如果当前时间已经过了本周的刷新时间
|
||||
if (nowTime >= thisWeekRefresh) {
|
||||
// 检查上次完成时间是否在本周刷新之前
|
||||
if (lastTime < thisWeekRefresh) {
|
||||
notification.send("购买狗粮已经刷新,执行脚本");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 否则检查上次完成时间是否在上周刷新之前
|
||||
const lastWeekRefresh = new Date(thisWeekRefresh);
|
||||
lastWeekRefresh.setDate(lastWeekRefresh.getDate() - 7);
|
||||
|
||||
if (lastTime < lastWeekRefresh) {
|
||||
notification.send("购买狗粮已经刷新,执行脚本");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("购买狗粮未刷新");
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
// 如果文件不存在或读取失败,创建新文件并返回true
|
||||
log.info(`文件读取失败: ${error.message},创建新文件`);
|
||||
await file.writeText(filePath, '');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 购买圣遗物
|
||||
async function purChase(locationName) {
|
||||
// 寻路
|
||||
log.info(`加载路径文件: ${locationName}`);
|
||||
let filePath = `assets/Pathing/${locationName}.json`;
|
||||
await pathingScript.runFile(filePath);
|
||||
await sleep(1000);
|
||||
// 购买圣遗物 - 只执行购买部分(不包含寻路)
|
||||
async function purchaseOnly(locationName, isRetry = false) {
|
||||
log.info(`开始购买流程: ${locationName}${isRetry ? ' (重试)' : ''}`);
|
||||
|
||||
// 定义模板
|
||||
let fDialogueRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/Picture/F_Dialogue.png"), 1050, 400, 100, 400);
|
||||
@@ -234,7 +101,7 @@ let userName = settings.userName || "默认账户";
|
||||
let fRes = ra.find(fDialogueRo);
|
||||
ra.dispose();
|
||||
if (!fRes.isExist()) {
|
||||
let f_attempts = null; // 初始化尝试次数
|
||||
let f_attempts = 0; // 初始化尝试次数
|
||||
while (f_attempts < 6) { // 最多尝试 5 次
|
||||
f_attempts++;
|
||||
log.info(`当前尝试次数:${f_attempts}`);
|
||||
@@ -246,9 +113,8 @@ let userName = settings.userName || "默认账户";
|
||||
await sleep(500);
|
||||
} else if (f_attempts <= 5) {
|
||||
// 第 4-5 次尝试
|
||||
log.info("重新加载路径文件");
|
||||
await pathingScript.runFile(filePath);
|
||||
await sleep(500);
|
||||
log.warn("无法找到NPC,退出购买流程");
|
||||
return { success: false, reason: "npc_not_found" };
|
||||
} else {
|
||||
// 第 6 次尝试,尝试次数已达上限
|
||||
log.warn("尝试次数已达上限");
|
||||
@@ -269,116 +135,218 @@ let userName = settings.userName || "默认账户";
|
||||
// 如果尝试次数用完仍未找到 F 图标,返回 false
|
||||
if (!fRes.isExist()) {
|
||||
log.warn("经过多次尝试后仍未找到 F 图标");
|
||||
return false;
|
||||
return { success: false, reason: "npc_not_found" };
|
||||
}
|
||||
}
|
||||
return true
|
||||
return { success: true, reason: "" };
|
||||
}
|
||||
let isAligned = await checkFAlignment(fDialogueRo);
|
||||
if(isAligned){
|
||||
// 进入对话选项
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// 最多 F 5次
|
||||
let captureRegion = captureGameRegion(); // 获取一张截图
|
||||
let res = captureRegion.Find(shopDialogueRo);
|
||||
captureRegion.dispose();
|
||||
if (res.isEmpty()) {
|
||||
keyPress("F");
|
||||
await sleep(1000);
|
||||
} else {
|
||||
res.click();
|
||||
log.info("已到达对话选项界面,点击商店图标({x},{y},{h},{w})", res.x, res.y, res.width, res.Height);
|
||||
break;
|
||||
|
||||
let alignmentResult = await checkFAlignment(fDialogueRo);
|
||||
if(!alignmentResult.success){
|
||||
// 返回一个对象,包含购买数量和失败原因
|
||||
return { purchasedCount: 0, failureReason: alignmentResult.reason, aligned: false };
|
||||
}
|
||||
|
||||
// 进入对话选项
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// 最多 F 5次
|
||||
let captureRegion = captureGameRegion(); // 获取一张截图
|
||||
let res = captureRegion.Find(shopDialogueRo);
|
||||
captureRegion.dispose();
|
||||
if (res.isEmpty()) {
|
||||
keyPress("F");
|
||||
await sleep(1000);
|
||||
} else {
|
||||
res.click();
|
||||
log.info("已到达对话选项界面,点击商店图标({x},{y},{h},{w})", res.x, res.y, res.width, res.Height);
|
||||
break;
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
// 进入商店界面
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// 最多 F 5次
|
||||
let captureRegion = captureGameRegion(); // 获取一张截图
|
||||
let res = captureRegion.Find(shopDialogueRo2);
|
||||
captureRegion.dispose();
|
||||
if (res.isEmpty()) {
|
||||
keyPress("F");
|
||||
await sleep(1000);
|
||||
} else {
|
||||
log.info("已到达商店界面");
|
||||
break;
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
if (locationName=='稻妻购买狗粮'){
|
||||
click(200, 400); await sleep(500); // 选择狗粮
|
||||
}
|
||||
// 购买狗粮
|
||||
let purchasedCount = 0; // 记录购买次数
|
||||
for (let i = 0; i < 6; i++) {
|
||||
// 最多购买6次
|
||||
let captureRegion = captureGameRegion(); // 获取一张截图
|
||||
let res = captureRegion.Find(conFirmRo);
|
||||
captureRegion.dispose();
|
||||
if (res.isEmpty()) {
|
||||
log.info('圣遗物已售罄');
|
||||
break;
|
||||
}else{
|
||||
// 识别到购买标识,模拟购买操作的后续点击
|
||||
await click(1600, 1020);
|
||||
await sleep(1000); // 购买
|
||||
await click(1320, 780);
|
||||
await sleep(1000); // 最终确认
|
||||
await click(1320, 780);
|
||||
await sleep(1000); // 点击空白
|
||||
purchasedCount++; // 购买成功,计数加1
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
// 返回购买结果对象
|
||||
return { purchasedCount, failureReason: purchasedCount === 0 ? "sold_out" : "", aligned: true };
|
||||
}
|
||||
|
||||
// 完整的购买流程(包含寻路)
|
||||
async function purChase(locationName) {
|
||||
// 寻路
|
||||
log.info(`加载路径文件: ${locationName}`);
|
||||
let filePath = `assets/Pathing/${locationName}.json`;
|
||||
await pathingScript.runFile(filePath);
|
||||
await sleep(1000);
|
||||
|
||||
// 执行购买
|
||||
return await purchaseOnly(locationName);
|
||||
}
|
||||
|
||||
// 检查函数,如果未买完则重新对话购买
|
||||
async function checkAndPurchase(locationName) {
|
||||
let maxRetries = 2; // 最大重试次数(加上第一次共3次)
|
||||
let retryCount = 0;
|
||||
let totalPurchased = 0;
|
||||
|
||||
// 第一次执行完整的购买流程(包含寻路)
|
||||
log.info(`开始执行 ${locationName} 的完整购买流程`);
|
||||
let purchaseResult = await purChase(locationName);
|
||||
let purchasedCount = purchaseResult.purchasedCount;
|
||||
let failureReason = purchaseResult.failureReason;
|
||||
let aligned = purchaseResult.aligned;
|
||||
|
||||
totalPurchased += purchasedCount;
|
||||
|
||||
// 如果购买数量为0,检查失败原因
|
||||
if (purchasedCount === 0) {
|
||||
if (failureReason === "npc_not_found" || !aligned) {
|
||||
// NPC对齐失败,重新执行一次完整购买流程
|
||||
log.info(`${locationName} NPC对齐失败,重新执行完整购买流程`);
|
||||
await genshin.returnMainUi();
|
||||
await sleep(2000);
|
||||
|
||||
purchaseResult = await purChase(locationName);
|
||||
purchasedCount = purchaseResult.purchasedCount;
|
||||
failureReason = purchaseResult.failureReason;
|
||||
aligned = purchaseResult.aligned;
|
||||
totalPurchased = purchasedCount; // 重置总购买数
|
||||
|
||||
if (purchasedCount === 0 && (failureReason === "npc_not_found" || !aligned)) {
|
||||
log.warn(`${locationName} 第二次完整购买仍然NPC对齐失败,跳过此地点`);
|
||||
return 0;
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
// 进入商店界面
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// 最多 F 5次
|
||||
let captureRegion = captureGameRegion(); // 获取一张截图
|
||||
let res = captureRegion.Find(shopDialogueRo2);
|
||||
captureRegion.dispose();
|
||||
if (res.isEmpty()) {
|
||||
keyPress("F");
|
||||
await sleep(1000);
|
||||
} else {
|
||||
log.info("已到达商店界面");
|
||||
break;
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
if (locationName=='稻妻购买狗粮'){
|
||||
click(200, 400); await sleep(500); // 选择狗粮
|
||||
}
|
||||
// 购买狗粮
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// 最多购买5次
|
||||
let captureRegion = captureGameRegion(); // 获取一张截图
|
||||
let res = captureRegion.Find(conFirmRo);
|
||||
captureRegion.dispose();
|
||||
if (res.isEmpty()) {
|
||||
log.info('圣遗物已售罄');
|
||||
break;
|
||||
}else{
|
||||
// 识别到购买标识,模拟购买操作的后续点击
|
||||
await click(1600, 1020);
|
||||
await sleep(1000); // 购买
|
||||
await click(1320, 780);
|
||||
await sleep(1000); // 最终确认
|
||||
await click(1320, 780);
|
||||
await sleep(1000); // 点击空白
|
||||
}
|
||||
await sleep(500);
|
||||
} else if (failureReason === "sold_out") {
|
||||
// 商店已售罄,说明之前已经买过了
|
||||
log.info(`${locationName} 商店已售罄,之前已完整购买过`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果第一次没买完(且数量大于0),尝试重新对话购买
|
||||
while (totalPurchased < 5 && retryCount < maxRetries) {
|
||||
retryCount++;
|
||||
log.info(`第 ${retryCount} 次重试购买 ${locationName},已购买 ${totalPurchased} 个`);
|
||||
|
||||
// 返回主界面
|
||||
await genshin.returnMainUi();
|
||||
await sleep(2000);
|
||||
|
||||
// 重新执行购买(不包含寻路)
|
||||
log.info(`重新执行 ${locationName} 的购买流程(不包含寻路)`);
|
||||
purchaseResult = await purchaseOnly(locationName, true);
|
||||
purchasedCount = purchaseResult.purchasedCount;
|
||||
failureReason = purchaseResult.failureReason;
|
||||
|
||||
// 如果重新购买时数量为0,检查失败原因
|
||||
if (purchasedCount === 0) {
|
||||
if (failureReason === "npc_not_found") {
|
||||
log.info(`${locationName} 重试时NPC对齐失败,停止重试`);
|
||||
break;
|
||||
} else if (failureReason === "sold_out") {
|
||||
log.info(`${locationName} 重试时已无圣遗物可购买,停止重试`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
totalPurchased += purchasedCount;
|
||||
|
||||
if (totalPurchased >= 5) {
|
||||
log.info(`成功购买 ${locationName} 的所有圣遗物`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalPurchased < 5 && totalPurchased > 0) {
|
||||
log.warn(`购买 ${locationName} 未完成,只购买了 ${totalPurchased} 个圣遗物`);
|
||||
} else if (totalPurchased === 0) {
|
||||
log.info(`${locationName} 无圣遗物可购买`);
|
||||
}
|
||||
|
||||
return totalPurchased;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await genshin.returnMainUi();
|
||||
if(settings.select1){
|
||||
await purChase('蒙德购买狗粮');
|
||||
}
|
||||
await genshin.returnMainUi();
|
||||
// 使用数组存储要执行的地点
|
||||
const purchaseTasks = [
|
||||
{ enabled: settings.select1, name: '蒙德购买狗粮' },
|
||||
{ enabled: settings.select2, name: '璃月购买狗粮1' },
|
||||
{ enabled: settings.select3, name: '璃月购买狗粮2', time: { hour: 19, minute: 0 } },
|
||||
{ enabled: settings.select4, name: '稻妻购买狗粮' },
|
||||
{ enabled: settings.select5, name: '须弥购买狗粮' },
|
||||
{ enabled: settings.select6, name: '枫丹购买狗粮' },
|
||||
{ enabled: settings.select7, name: '纳塔购买狗粮' },
|
||||
{ enabled: settings.select8, name: '挪德卡莱购买狗粮' }
|
||||
];
|
||||
|
||||
if(settings.select2){
|
||||
await purChase('璃月购买狗粮1');
|
||||
}
|
||||
let totalPurchased = 0;
|
||||
|
||||
if(settings.select3){
|
||||
await setTime(19,00)
|
||||
await purChase('璃月购买狗粮2');
|
||||
}
|
||||
for (const task of purchaseTasks) {
|
||||
if (task.enabled) {
|
||||
// 如果有时间设置,先设置时间
|
||||
if (task.time) {
|
||||
await genshin.setTime(task.time.hour, task.time.minute)
|
||||
}
|
||||
|
||||
if(settings.select4){
|
||||
await purChase('稻妻购买狗粮');
|
||||
}
|
||||
if(settings.select5){
|
||||
await purChase('须弥购买狗粮');
|
||||
}
|
||||
// 执行检查并购买
|
||||
let count = await checkAndPurchase(task.name);
|
||||
totalPurchased += count;
|
||||
|
||||
if(settings.select6){
|
||||
await purChase('枫丹购买狗粮');
|
||||
}
|
||||
log.info(`${task.name} 完成,购买了 ${count} 个圣遗物`);
|
||||
|
||||
if(settings.select7){
|
||||
await purChase('纳塔购买狗粮');
|
||||
}
|
||||
// 返回主界面准备下一个任务
|
||||
await genshin.returnMainUi();
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if(settings.select8){
|
||||
await purChase('挪德卡莱购买狗粮');
|
||||
}
|
||||
notification.send(`所有任务完成,总共购买了 ${totalPurchased} 个圣遗物`);
|
||||
|
||||
await file.writeText(recordPath, new Date().toISOString());
|
||||
}
|
||||
await file.writeText(recordPath, new Date().toISOString());
|
||||
}
|
||||
|
||||
userName = await getUserName();
|
||||
const recordPath = `assets/${userName}.txt`;
|
||||
//每周四4点刷新
|
||||
if( await isTaskRefreshed(recordPath, {
|
||||
refreshType: 'weekly',
|
||||
weeklyDay: 4, // 周四
|
||||
weeklyHour: 4 // 凌晨4点
|
||||
})|| settings.select9){
|
||||
if( await isTaskRefreshed(recordPath)|| settings.select9){
|
||||
await main();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "自动购买狗粮(8点位)",
|
||||
"version": "2.3.1",
|
||||
"version": "2.4",
|
||||
"bgi_version": "0.52",
|
||||
"description": "自动购买狗粮",
|
||||
"authors": [
|
||||
|
||||
@@ -1,28 +1,128 @@
|
||||
公版的bgi在执行js脚本的时候,默认是关闭自动吃药的,除非脚本作者在代码中开启。
|
||||
# 药品消耗统计脚本使用指南
|
||||
|
||||
所以目前本脚本推荐搭配茶包版bgi使用。
|
||||
## 📋 一、脚本概述
|
||||
|
||||
在搜索框搜索茶包,可以找到作者,点击作者名字,进入作者主页就可下载。
|
||||
本脚本是一款专为《原神》设计的自动化工具,主要功能如下:
|
||||
|
||||
找不到下载地址的,可以阅读锄地js的readme,群号在readme的最后,加群来测试茶包。
|
||||
- **自动识别**:通过OCR技术读取背包中指定药品的数量
|
||||
- **智能统计**:记录每日药品消耗/新增变化,统计周期为当日4点至次日4点
|
||||
- **数据管理**:自动保存30天历史记录,过期数据自动清理
|
||||
- **智能提醒**:通过系统通知推送药品变化信息
|
||||
|
||||
原理是将每天4点后第一次运行的数据记为初始值,后续再次运行得出数量变化。
|
||||
## 🛠️ 二、环境与工具要求
|
||||
|
||||
记录保存30天的数据,每次运行保存一次数据。
|
||||
### 必备工具:茶包版BGI
|
||||
|
||||
如果当天运行过后,想初始化新料理,可以直接修改料理名称,修改后如果没有本地数据,运行会初始化。
|
||||
**为什么不推荐公版BGI?**
|
||||
|
||||
如果新料理今日已初始化,需要手动初始化,可以在选项设置中勾选,勾选后会删除当天的同名记录。
|
||||
公版BGI执行JS脚本时默认关闭自动吃药功能,且无法通过脚本代码开启。本脚本的设计逻辑完全依赖自动吃药功能的正常运行:
|
||||
|
||||
手动运行初始化一次后记得取消勾选,不然运行就一直初始化。
|
||||
- 药品消耗主要发生在自动吃药场景中
|
||||
- 脚本未内置开启自动吃药的适配代码
|
||||
|
||||
脚本运行前需要在自定义设置里填写准确的食物名称。
|
||||
### 系统配置要求
|
||||
|
||||
使用前请确认已装备便携营养袋,并且已打开bgi的自动吃药功能。
|
||||
1. **游戏分辨率**:1920×1080(固定不可改)
|
||||
2. **游戏内设置**:
|
||||
- 装备「便携营养袋」
|
||||
- 确保游戏界面无遮挡、无缩放
|
||||
3. **模板文件**:确认`assets/RecognitionObject/`文件夹包含所有必要的模板图片
|
||||
|
||||
脚本会记录每次运行读取到的数量,所以如果你打算做菜,建议第一次运行前或最后一次运行后做菜。
|
||||
## 📥 三、获取茶包版BGI
|
||||
|
||||
账户名只允许使用数字,中英文,同时长度在20个字符以内。
|
||||
1. **直接获取**:在脚本仓库搜索关键词「茶包」,进入作者主页下载
|
||||
2. **备用方案**:查阅「锄地一条龙js」的README文档,通过文档末尾的群号加入测测莫酱交流群获取
|
||||
|
||||
## ⚙️ 四、使用前配置
|
||||
|
||||
### 1. 基础检查
|
||||
|
||||
- [ ] 游戏分辨率为1920×1080
|
||||
- [ ] 已装备「便携营养袋」
|
||||
- [ ] 茶包版BGI中已开启「自动吃药」功能
|
||||
|
||||
### 2. 参数设置(关键步骤)
|
||||
|
||||
| 参数项 | 填写要求 | 示例 |
|
||||
|--------|----------|------------|
|
||||
| **userName** | 1-20字符,仅支持中英文、数字 | "旅行者001" |
|
||||
| **recoveryFoodName** | 与游戏内名称完全一致 | "美味的甜甜花酿鸡" |
|
||||
| **resurrectionFoodName** | 与游戏内名称完全一致 | "美味的提瓦特煎蛋" |
|
||||
| **initSelect** | 默认关闭,仅初始化时临时开启 | 取消勾选 |
|
||||
|
||||
**重要**:药品名称必须完全匹配。
|
||||
|
||||
## 🔄 五、操作流程
|
||||
|
||||
### 场景一:首次使用 / 新账户
|
||||
|
||||
1. 完成上述配置后运行脚本
|
||||
2. 脚本自动执行:
|
||||
- 校验账户名合法性
|
||||
- 领取月卡(如可领取)
|
||||
- 打开背包识别药品数量
|
||||
- 创建记录文件(路径:`assets/账户名.txt`)
|
||||
3. 收到「今日初始化完成」通知
|
||||
|
||||
### 场景二:日常使用
|
||||
|
||||
- **每日首次运行**(4点后):记录为当日初始值
|
||||
- **后续运行**:对比最新数量,计算消耗/新增
|
||||
- **自动记录**:每次运行都会保存一次记录数据
|
||||
- **智能清理**:仅保留30天内数据
|
||||
|
||||
### 场景三:更换药品或重置数据
|
||||
|
||||
1. 在设置中**勾选`initSelect`选项**
|
||||
2. 运行脚本一次
|
||||
3. **务必取消勾选`initSelect`**
|
||||
|
||||
**效果**:删除当日同名药品记录,以当前数量重新初始化。
|
||||
|
||||
## 📊 六、核心机制说明
|
||||
|
||||
### 统计周期规则
|
||||
|
||||
- 以**凌晨4点**为分界点
|
||||
- 举例:当前时间3点,统计昨天4点至今的数据
|
||||
- 当前时间5点,统计今天4点起的数据
|
||||
|
||||
### 记录格式示例
|
||||
|
||||
```
|
||||
时间:2023/10/15 14:30:45-美味的甜甜花酿鸡-18
|
||||
时间:2023/10/15 14:30:45-美味的提瓦特煎蛋-6
|
||||
```
|
||||
|
||||
### 异常处理机制
|
||||
|
||||
| 异常情况 | 自动处理方式 |
|
||||
|----------|-------------|
|
||||
| 药品未识别 | 数量设为0,发送通知提醒 |
|
||||
| 记录文件格式错误 | 自动重置文件 |
|
||||
|
||||
## ⚠️ 七、重要注意事项
|
||||
|
||||
### 每日首次使用时机建议
|
||||
|
||||
- **在锄地一条龙前**
|
||||
- **在药品制作后**
|
||||
- **避免**在两次脚本运行之间制作大量药品
|
||||
|
||||
### 常见误区
|
||||
|
||||
1. **`initSelect`常开**:导致每次运行都重新初始化
|
||||
2. **名称不准确**:最常导致识别失败的原因
|
||||
3. **窗口遮挡**:影响OCR识别准确率
|
||||
4. **多账户混淆**:每个账户有独立记录文件
|
||||
|
||||
## 🔧 八、故障排查指南
|
||||
|
||||
### 快速自查表
|
||||
|
||||
| 问题现象 | 优先检查项 |
|
||||
|----------|------------|
|
||||
| 药品数量识别为0 | 1. 药品名称准确性<br>2. 背包中是否有该药品<br>3. 分辨率是否为1920×1080 |
|
||||
| 统计数据异常 | 1. 是否在脚本运行期间操作药品<br>2. 是否每日4点后首次运行<br>3. `initSelect`是否误开启 |
|
||||
| 自动吃药无效 | 1. 是否使用茶包版BGI<br>2. BGI中自动吃药功能是否开启 |
|
||||
|
||||
|
||||
BIN
repo/js/营养袋吃药统计/assets/RecognitionObject/interface.png
Normal file
BIN
repo/js/营养袋吃药统计/assets/RecognitionObject/interface.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -10,6 +10,7 @@ const ocrRegion = {
|
||||
const filterButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/filterButton.png"));
|
||||
const resetButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/resetButton.png"));
|
||||
const researchRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/research.png"));
|
||||
const searchInterfaceRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/interface.png"));
|
||||
const confirmButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/RecognitionObject/confirmButton.png"), 350, 1000, 50, 50);
|
||||
(async function () {
|
||||
// 检验账户名
|
||||
@@ -415,104 +416,100 @@ const confirmButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("a
|
||||
}
|
||||
|
||||
async function recognizeNumberByOCR(ocrRegion, pattern) {
|
||||
let captureRegion = null;
|
||||
try {
|
||||
const ocrRo = RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height);
|
||||
captureRegion = captureGameRegion();
|
||||
const resList = captureRegion.findMulti(ocrRo);
|
||||
let captureRegion = null;
|
||||
try {
|
||||
const ocrRo = RecognitionObject.ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height);
|
||||
captureRegion = captureGameRegion();
|
||||
const resList = captureRegion.findMulti(ocrRo);
|
||||
|
||||
if (!resList || resList.length === 0) {
|
||||
log.warn("OCR未识别到任何文本");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const res of resList) {
|
||||
if (!res || !res.text) {
|
||||
continue;
|
||||
if (!resList || resList.length === 0) {
|
||||
log.warn("OCR未识别到任何文本");
|
||||
return null;
|
||||
}
|
||||
|
||||
const numberMatch = res.text.match(pattern);
|
||||
if (numberMatch) {
|
||||
const number = parseInt(numberMatch[1] || numberMatch[0]);
|
||||
if (!isNaN(number)) {
|
||||
return number;
|
||||
for (const res of resList) {
|
||||
if (!res || !res.text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const numberMatch = res.text.match(pattern);
|
||||
if (numberMatch) {
|
||||
const number = parseInt(numberMatch[1] || numberMatch[0]);
|
||||
if (!isNaN(number)) {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
log.error(`OCR识别时发生异常: ${error.message}`);
|
||||
}
|
||||
finally {
|
||||
if (captureRegion) {
|
||||
captureRegion.dispose();
|
||||
catch (error) {
|
||||
log.error(`OCR识别时发生异常: ${error.message}`);
|
||||
}
|
||||
finally {
|
||||
if (captureRegion) {
|
||||
captureRegion.dispose();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function findAndClick(target, maxAttempts = 20) {
|
||||
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
||||
const gameRegion = captureGameRegion();
|
||||
async function findAndClick(target, maxAttempts = 50, clicks=true) {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const rg = captureGameRegion();
|
||||
try {
|
||||
const result = gameRegion.find(target);
|
||||
if (result.isExist) {
|
||||
result.click();
|
||||
return true; // 成功立刻返回
|
||||
const res = rg.find(target);
|
||||
if (res.isExist()) {
|
||||
if(clicks)
|
||||
{await sleep(50); res.click()}
|
||||
return true;
|
||||
}
|
||||
log.warn(`识别失败,第 ${attempts + 1} 次重试`);
|
||||
} catch (err) {
|
||||
} finally {
|
||||
gameRegion.dispose();
|
||||
}
|
||||
if (attempts < maxAttempts - 1) { // 最后一次不再 sleep
|
||||
await sleep(250);
|
||||
}
|
||||
} finally { rg.dispose(); }
|
||||
if (i < maxAttempts - 1) await sleep(50);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async function getFoodNum(){
|
||||
keyPress("B");//打开背包
|
||||
await handleExpiredItems(); //处理过期物品弹窗
|
||||
await sleep(1000);
|
||||
click(863, 51);//选择食物
|
||||
await sleep(500);
|
||||
findAndClick(filterButtonRo);//筛选
|
||||
await sleep(500);
|
||||
findAndClick(resetButtonRo);//重置
|
||||
await sleep(500);
|
||||
findAndClick(researchRo);//点击搜索,输入名字
|
||||
await findAndClick(filterButtonRo);//筛选
|
||||
await findAndClick(searchInterfaceRo,50,false);//搜索界面
|
||||
await findAndClick(resetButtonRo);//重置按钮
|
||||
await findAndClick(researchRo);//搜索输入框
|
||||
inputText(recoveryFoodName);
|
||||
await sleep(500);
|
||||
findAndClick(confirmButtonRo)
|
||||
await sleep(500);
|
||||
await findAndClick(confirmButtonRo);//确认按钮
|
||||
await sleep(1000);
|
||||
var recoveryNumber=await recognizeNumberByOCR(ocrRegion,/\d+/) //识别回血药数量
|
||||
// 处理回血药识别结果
|
||||
if (recoveryNumber === null) {
|
||||
recoveryNumber = 0;
|
||||
notification.send(`未识别到回血药数量,设置数量为0,药品名:${recoveryFoodName}`)
|
||||
await sleep(5000);
|
||||
click(863, 51);//选择食物
|
||||
await sleep(1000);
|
||||
}
|
||||
findAndClick(filterButtonRo);//筛选
|
||||
await sleep(500);
|
||||
findAndClick(resetButtonRo);//重置
|
||||
await sleep(500);
|
||||
findAndClick(researchRo);//点击搜索,输入名字
|
||||
await findAndClick(filterButtonRo);//筛选
|
||||
await findAndClick(searchInterfaceRo,50,false);//搜索界面
|
||||
await findAndClick(resetButtonRo);//重置按钮
|
||||
await findAndClick(researchRo);//搜索输入框
|
||||
inputText(resurrectionFoodName);
|
||||
await sleep(500);
|
||||
findAndClick(confirmButtonRo);//确认筛选
|
||||
await sleep(500);
|
||||
await findAndClick(confirmButtonRo);//确认按钮
|
||||
await sleep(1000); // 增加等待时间
|
||||
var resurrectionNumber=await recognizeNumberByOCR(ocrRegion,/\d+/) //识别复活药数量
|
||||
// 处理复活药识别结果
|
||||
if (resurrectionNumber === null) {
|
||||
resurrectionNumber = 0;
|
||||
notification.send(`未识别到复活药数量,设置数量为0,药品名:${resurrectionFoodName}`)
|
||||
await sleep(5000);
|
||||
click(863, 51);//选择食物
|
||||
await sleep(1000);
|
||||
}
|
||||
findAndClick(filterButtonRo);//筛选
|
||||
await sleep(500);
|
||||
findAndClick(resetButtonRo);//重置
|
||||
await sleep(500);
|
||||
findAndClick(confirmButtonRo);//确认筛选
|
||||
await findAndClick(filterButtonRo);//筛选
|
||||
await findAndClick(searchInterfaceRo,50,false);//搜索界面
|
||||
await findAndClick(resetButtonRo);//重置
|
||||
await findAndClick(confirmButtonRo);//确认按钮
|
||||
await genshin.returnMainUi();
|
||||
return { recoveryNumber, resurrectionNumber };
|
||||
}
|
||||
@@ -522,9 +519,9 @@ const confirmButtonRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("a
|
||||
setGameMetrics(1920, 1080, 1);
|
||||
// 点击领月卡
|
||||
await genshin.blessingOfTheWelkinMoon();
|
||||
await sleep(1000);
|
||||
await sleep(500);
|
||||
await genshin.returnMainUi();
|
||||
await sleep(1000);
|
||||
await sleep(500);
|
||||
// 获取食物数量
|
||||
return await getFoodNum();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "吃药统计",
|
||||
"version": "1.3",
|
||||
"version": "1.4",
|
||||
"bgi_version": "0.51",
|
||||
"description": "用于统计指定两个食物的消耗,推荐锄地前后使用",
|
||||
"authors": [
|
||||
|
||||
Reference in New Issue
Block a user