2
repo/js/ArtifactExpCount/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
统计背包中的圣遗物经验和狗粮数量,建议搭配JS通知使用。
|
||||
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/三星.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/分解.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/分解未锁定.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/四星.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/快速选择.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/执行分解.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/时间顺序.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/未满级.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/未装备.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/未锁定.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/确认.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/祝圣之霜定义.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/筛选.png
Normal file
|
After Width: | Height: | Size: 882 B |
BIN
repo/js/ArtifactExpCount/assets/RecognitionObject/重置.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/0.png
Normal file
|
After Width: | Height: | Size: 703 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/1.png
Normal file
|
After Width: | Height: | Size: 423 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/2.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/3.png
Normal file
|
After Width: | Height: | Size: 683 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/4.png
Normal file
|
After Width: | Height: | Size: 557 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/5.png
Normal file
|
After Width: | Height: | Size: 569 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/6.png
Normal file
|
After Width: | Height: | Size: 652 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/7.png
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/8.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
repo/js/ArtifactExpCount/assets/已储存经验数字/9.png
Normal file
|
After Width: | Height: | Size: 654 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/0.png
Normal file
|
After Width: | Height: | Size: 564 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/1.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/2.png
Normal file
|
After Width: | Height: | Size: 539 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/3.png
Normal file
|
After Width: | Height: | Size: 555 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/4.png
Normal file
|
After Width: | Height: | Size: 454 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/5.png
Normal file
|
After Width: | Height: | Size: 490 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/6.png
Normal file
|
After Width: | Height: | Size: 608 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/7.png
Normal file
|
After Width: | Height: | Size: 399 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/8.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
repo/js/ArtifactExpCount/assets/背包数字/9.png
Normal file
|
After Width: | Height: | Size: 512 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/0.png
Normal file
|
After Width: | Height: | Size: 648 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/1.png
Normal file
|
After Width: | Height: | Size: 316 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/2.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/3.png
Normal file
|
After Width: | Height: | Size: 713 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/4.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/5.png
Normal file
|
After Width: | Height: | Size: 637 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/6.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/7.png
Normal file
|
After Width: | Height: | Size: 551 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/8.png
Normal file
|
After Width: | Height: | Size: 719 B |
BIN
repo/js/ArtifactExpCount/assets/选中狗粮数字/9.png
Normal file
|
After Width: | Height: | Size: 632 B |
610
repo/js/ArtifactExpCount/main.js
Normal file
@@ -0,0 +1,610 @@
|
||||
let notify = settings.notify
|
||||
let account = settings.account || "默认账户";
|
||||
(async function () {
|
||||
// 设置分辨率和缩放
|
||||
setGameMetrics(1920, 1080, 1);
|
||||
await genshin.returnMainUi();
|
||||
keyPress("B");//打开背包
|
||||
await sleep(1000);
|
||||
// 关闭弹窗
|
||||
await close_expired_stuff_popup_window();
|
||||
let enterAttempts = 0;
|
||||
while (enterAttempts < 10) {
|
||||
await click(642,36);
|
||||
const clicked = await clickPNG("分解",false);
|
||||
if (clicked) break; // 找到并点击成功就退出循环
|
||||
await sleep(750);
|
||||
enterAttempts++;
|
||||
await genshin.returnMainUi();
|
||||
await sleep(100);
|
||||
keyPress("B");
|
||||
await sleep(1000);
|
||||
}
|
||||
await clickPNG("时间顺序",true,1);
|
||||
await sleep(200);
|
||||
await clickPNG("筛选");
|
||||
await sleep(200);
|
||||
click(30, 30);
|
||||
await sleep(100);
|
||||
await clickPNG("重置");
|
||||
await sleep(200);
|
||||
await clickPNG("祝圣之霜定义");
|
||||
await sleep(200);
|
||||
await clickPNG("未装备");
|
||||
await sleep(200);
|
||||
await clickPNG("未锁定");
|
||||
await sleep(200);
|
||||
await clickPNG("确认");
|
||||
await sleep(200);
|
||||
click(30, 30);
|
||||
await sleep(100);
|
||||
const smallBottle = await getBottleCount('背包小瓶', 'assets/RecognitionObject/三星.png');
|
||||
const bigBottle = await getBottleCount('背包大瓶', 'assets/RecognitionObject/四星.png');
|
||||
await clickPNG("筛选");
|
||||
await sleep(200);
|
||||
click(30, 30);
|
||||
await sleep(100);
|
||||
await clickPNG("重置");
|
||||
await sleep(200);
|
||||
await clickPNG("确认");
|
||||
click(30, 30);
|
||||
await sleep(100);
|
||||
//点击分解
|
||||
await clickPNG("分解");
|
||||
await sleep(1000);
|
||||
// 识别已储存经验(1570-880-1650-930)
|
||||
const digits = await numberTemplateMatch("assets/已储存经验数字", 1573, 885, 74, 36);
|
||||
let initialValue = 0;
|
||||
if (digits >= 0) {
|
||||
initialValue = digits;
|
||||
log.info(`已储存经验识别成功: ${initialValue}`);
|
||||
} else {
|
||||
log.warn(`已储存经验值识别失败,使用默认值0`);
|
||||
}
|
||||
await clickPNG("快速选择");
|
||||
await sleep(500);
|
||||
// 识别不同星级狗粮数量
|
||||
const starPositions = [
|
||||
{ star: 1, y: 130 },
|
||||
{ star: 2, y: 200 },
|
||||
{ star: 3, y: 270 },
|
||||
{ star: 4, y: 340 }
|
||||
];
|
||||
const starCounts = {};
|
||||
for (const { star, y } of starPositions) {
|
||||
const count = await numberTemplateMatch("assets/选中狗粮数字", 570, y, 60, 50);
|
||||
if (count < 0) {
|
||||
log.warn(`在${star}星狗粮位置未识别到有效数字`);
|
||||
starCounts[`star${star}`] = 0; // 设置默认值为0
|
||||
}else{
|
||||
starCounts[`star${star}`] = count;
|
||||
log.info(`${star}星狗粮识别到${count}个`);
|
||||
}
|
||||
}
|
||||
// 计算狗粮经验值
|
||||
const expStar1 = starCounts.star1 * 420;
|
||||
const expStar2 = starCounts.star2 * 840;
|
||||
const expStar3 = starCounts.star3 * 1260;
|
||||
const expStar4 = starCounts.star4 * 2520;
|
||||
const expStars = expStar1 + expStar2 + expStar3 + expStar4;
|
||||
// 库存经验值
|
||||
const expSmall = parseInt(smallBottle || 0) * 2500;
|
||||
const expBig = parseInt(bigBottle || 0) * 10000;
|
||||
const expStock = expSmall + expBig + initialValue;
|
||||
// 合计
|
||||
const totalExp = expStars + expStock;
|
||||
const totalCount = starCounts.star1 + starCounts.star2 + starCounts.star3 + starCounts.star4;
|
||||
|
||||
// 预计算所有需要格式化的值
|
||||
const formattedExpStar1 = await formatExp(expStar1);
|
||||
const formattedExpStar2 = await formatExp(expStar2);
|
||||
const formattedExpStar3 = await formatExp(expStar3);
|
||||
const formattedExpStar4 = await formatExp(expStar4);
|
||||
const formattedExpStars = await formatExp(expStars);
|
||||
const formattedExpSmall = await formatExp(expSmall);
|
||||
const formattedExpBig = await formatExp(expBig);
|
||||
const formattedExpStock = await formatExp(expStock);
|
||||
const formattedTotalExp = await formatExp(totalExp);
|
||||
|
||||
// 构建通知消息
|
||||
let message = `📦 圣遗物经验统计\n`;
|
||||
message += `\n`;
|
||||
message += `💾 已储存经验:${initialValue} 点\n`;
|
||||
message += `\n`;
|
||||
message += `📊 [狗粮数量统计]\n`;
|
||||
message += `📦 总数量:${totalCount} 个\n`;
|
||||
message += `⭐ 1星:${starCounts.star1} 个${expStar1 > 0 ? `(${formattedExpStar1})` : ''}\n`;
|
||||
message += `⭐ 2星:${starCounts.star2} 个${expStar2 > 0 ? `(${formattedExpStar2})` : ''}\n`;
|
||||
message += `⭐ 3星:${starCounts.star3} 个${expStar3 > 0 ? `(${formattedExpStar3})` : ''}\n`;
|
||||
message += `⭐ 4星:${starCounts.star4} 个${expStar4 > 0 ? `(${formattedExpStar4})` : ''}\n`;
|
||||
message += `💰 狗粮经验合计:${formattedExpStars}\n`;
|
||||
message += `\n`;
|
||||
message += `🧪 [经验瓶数量]\n`;
|
||||
message += `🧪 小经验瓶:${smallBottle || 0} 个${expSmall > 0 ? `(${formattedExpSmall})` : ''}\n`;
|
||||
message += `🧪 大经验瓶:${bigBottle || 0} 个${expBig > 0 ? `(${formattedExpBig})` : ''}\n`;
|
||||
message += `💰 库存经验合计:${formattedExpStock}(含储存${initialValue}点)\n`;
|
||||
message += `\n`;
|
||||
message += `✨ 总经验:${formattedTotalExp}\n`;
|
||||
// 记录保存功能
|
||||
const userName = await getUserName();
|
||||
const recordPath = `assets/${userName}.txt`;
|
||||
|
||||
// 获取本地保存的数据
|
||||
const localData = await getLocalData(recordPath);
|
||||
|
||||
// 更新记录
|
||||
await updateRecord(recordPath, initialValue, parseInt(smallBottle || 0), parseInt(bigBottle || 0), starCounts, totalExp);
|
||||
|
||||
// 构建基础日志信息
|
||||
const formattedExpStarsLog = await formatExp(expStars);
|
||||
const formattedExpSmallLog = await formatExp(expSmall);
|
||||
const formattedExpBigLog = await formatExp(expBig);
|
||||
const formattedExpStockLog = await formatExp(expStock);
|
||||
const formattedTotalExpLog = await formatExp(totalExp);
|
||||
const baseLog = `狗粮:${totalCount}个(1★${starCounts.star1} 2★${starCounts.star2} 3★${starCounts.star3} 4★${starCounts.star4}) | 狗粮经验:${formattedExpStarsLog} | 小瓶:${formattedExpSmallLog} 大瓶:${formattedExpBigLog} 储存:${initialValue} | 库存经验:${formattedExpStockLog} | 总计:${formattedTotalExpLog}`;
|
||||
|
||||
// 计算变化量并添加到通知消息
|
||||
let logMessage = baseLog;
|
||||
if (localData.initialized.initialValue && localData.initialized.smallBottle && localData.initialized.bigBottle &&
|
||||
localData.initialized.star1 && localData.initialized.star2 && localData.initialized.star3 && localData.initialized.star4 &&
|
||||
localData.initialized.totalExp) {
|
||||
// 计算本地记录中的狗粮经验合计
|
||||
const localExpStar1 = localData.starCounts.star1 * 420;
|
||||
const localExpStar2 = localData.starCounts.star2 * 840;
|
||||
const localExpStar3 = localData.starCounts.star3 * 1260;
|
||||
const localExpStar4 = localData.starCounts.star4 * 2520;
|
||||
const localExpStars = localExpStar1 + localExpStar2 + localExpStar3 + localExpStar4;
|
||||
|
||||
// 计算总经验和狗粮经验合计的变化
|
||||
const diffTotalExp = totalExp - localData.totalExp;
|
||||
const diffExpStars = expStars - localExpStars;
|
||||
|
||||
// 添加变化量到通知消息
|
||||
message += `\n`;
|
||||
if (diffTotalExp !== 0 || diffExpStars !== 0) {
|
||||
if (diffTotalExp !== 0) {
|
||||
const totalChangeDesc = diffTotalExp > 0 ? '增加' : '减少';
|
||||
const formattedDiffTotalExp = await formatExp(Math.abs(diffTotalExp));
|
||||
message += `📈 总经验${totalChangeDesc}:${formattedDiffTotalExp}\n`;
|
||||
}
|
||||
if (diffExpStars !== 0) {
|
||||
const expStarsChangeDesc = diffExpStars > 0 ? '增加' : '减少';
|
||||
const formattedDiffExpStars = await formatExp(Math.abs(diffExpStars));
|
||||
message += `🐶 未分解的狗粮经验${expStarsChangeDesc}:${formattedDiffExpStars}`;
|
||||
}
|
||||
} else {
|
||||
// 如果没有变化,输出不变
|
||||
message += `📊 经验数据无变化`;
|
||||
}
|
||||
|
||||
// 构建完整日志(包含变化信息)
|
||||
if (diffTotalExp !== 0 || diffExpStars !== 0) {
|
||||
const totalChangeDesc = diffTotalExp > 0 ? '增加' : '减少';
|
||||
const expStarsChangeDesc = diffExpStars > 0 ? '增加' : '减少';
|
||||
const formattedDiffTotalExpLog = await formatExp(Math.abs(diffTotalExp));
|
||||
const formattedDiffExpStarsLog = await formatExp(Math.abs(diffExpStars));
|
||||
logMessage = `总经验${totalChangeDesc}:${formattedDiffTotalExpLog} | 狗粮经验${expStarsChangeDesc}:${formattedDiffExpStarsLog} | ${baseLog}`;
|
||||
} else {
|
||||
// 如果没有变化,日志也显示不变
|
||||
logMessage = `经验数据无变化 | ${baseLog}`;
|
||||
}
|
||||
}
|
||||
if (settings.notify) {
|
||||
notification.send(message);
|
||||
}
|
||||
log.info(logMessage);
|
||||
await genshin.returnMainUi();
|
||||
|
||||
// 格式化经验值显示
|
||||
async function formatExp(num) {
|
||||
if (num >= 10000) {
|
||||
// 直接除法并转换为字符串,保留所有有效小数
|
||||
return `${(num / 10000).toString()}万`;
|
||||
} else {
|
||||
return `${num}`;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 在指定区域内,用 0-9 的 PNG 模板做「多阈值 + 非极大抑制」数字识别,
|
||||
* 最终把检测到的数字按左右顺序拼成一个整数返回。
|
||||
*
|
||||
* @param {string} numberPngFilePath - 存放 0.png ~ 9.png 的文件夹路径(不含文件名)
|
||||
* @param {number} x - 待识别区域的左上角 x 坐标,默认 0
|
||||
* @param {number} y - 待识别区域的左上角 y 坐标,默认 0
|
||||
* @param {number} w - 待识别区域的宽度,默认 1920
|
||||
* @param {number} h - 待识别区域的高度,默认 1080
|
||||
* @param {number} maxThreshold - 模板匹配起始阈值,默认 0.95(最高可信度)
|
||||
* @param {number} minThreshold - 模板匹配最低阈值,默认 0.8(最低可信度)
|
||||
* @param {number} splitCount - 在 maxThreshold 与 minThreshold 之间做几次等间隔阈值递减,默认 3
|
||||
* @param {number} maxOverlap - 非极大抑制时允许的最大重叠像素,默认 2;只要 x 或 y 方向重叠大于该值即视为重复框
|
||||
*
|
||||
* @returns {number} 识别出的整数;若没有任何有效数字框则返回 -1
|
||||
*
|
||||
* @example
|
||||
* const mora = await numberTemplateMatch('摩拉数字', 860, 70, 200, 40);
|
||||
* if (mora >= 0) console.log(`当前摩拉:${mora}`);
|
||||
*/
|
||||
async function numberTemplateMatch(
|
||||
numberPngFilePath,
|
||||
x = 0, y = 0, w = 1920, h = 1080,
|
||||
maxThreshold = 0.95,
|
||||
minThreshold = 0.8,
|
||||
splitCount = 3,
|
||||
maxOverlap = 2
|
||||
) {
|
||||
let ros = [];
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
ros[i] = RecognitionObject.TemplateMatch(
|
||||
file.ReadImageMatSync(`${numberPngFilePath}/${i}.png`), x, y, w, h);
|
||||
}
|
||||
|
||||
function setThreshold(roArr, newThreshold) {
|
||||
for (let i = 0; i < roArr.length; i++) {
|
||||
roArr[i].Threshold = newThreshold;
|
||||
roArr[i].InitTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
const gameRegion = captureGameRegion();
|
||||
const allCandidates = [];
|
||||
|
||||
/* 1. splitCount 次等间隔阈值递减 */
|
||||
for (let k = 0; k < splitCount; k++) {
|
||||
const curThr = maxThreshold - (maxThreshold - minThreshold) * k / Math.max(splitCount - 1, 1);
|
||||
setThreshold(ros, curThr);
|
||||
|
||||
/* 2. 0-9 每个模板跑一遍,所有框都收 */
|
||||
for (let digit = 0; digit <= 9; digit++) {
|
||||
const res = gameRegion.findMulti(ros[digit]);
|
||||
if (res.count === 0) continue;
|
||||
|
||||
for (let i = 0; i < res.count; i++) {
|
||||
const box = res[i];
|
||||
allCandidates.push({
|
||||
digit: digit,
|
||||
x: box.x,
|
||||
y: box.y,
|
||||
w: box.width,
|
||||
h: box.height,
|
||||
thr: curThr
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
gameRegion.dispose();
|
||||
|
||||
/* 3. 无结果提前返回 -1 */
|
||||
if (allCandidates.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 4. 非极大抑制(必须 x、y 两个方向重叠都 > maxOverlap 才视为重复) */
|
||||
const adopted = [];
|
||||
for (const c of allCandidates) {
|
||||
let overlap = false;
|
||||
for (const a of adopted) {
|
||||
const xOverlap = Math.max(0, Math.min(c.x + c.w, a.x + a.w) - Math.max(c.x, a.x));
|
||||
const yOverlap = Math.max(0, Math.min(c.y + c.h, a.y + a.h) - Math.max(c.y, a.y));
|
||||
if (xOverlap > maxOverlap && yOverlap > maxOverlap) {
|
||||
overlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!overlap) {
|
||||
adopted.push(c);
|
||||
//log.info(`在 [${c.x},${c.y},${c.w},${c.h}] 找到数字 ${c.digit},匹配阈值=${c.thr}`);
|
||||
}
|
||||
}
|
||||
|
||||
/* 5. 按 x 排序,拼整数;仍无有效框时返回 -1 */
|
||||
if (adopted.length === 0) return -1;
|
||||
adopted.sort((a, b) => a.x - b.x);
|
||||
|
||||
return adopted.reduce((num, item) => num * 10 + item.digit, 0);
|
||||
}
|
||||
|
||||
async function clickPNG(png, doClick = true, maxAttempts = 40, Threshold = 0.9) {
|
||||
const pngRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/RecognitionObject/${png}.png`));
|
||||
pngRo.Threshold = Threshold;
|
||||
pngRo.InitTemplate();
|
||||
return await findAndClick(pngRo, maxAttempts, doClick);
|
||||
}
|
||||
|
||||
async function findAndClick(target, maxAttempts = 20, doClick) {
|
||||
//log.info("调试-开始检查");
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
//log.info("调试-检查一次");
|
||||
const rg = captureGameRegion();
|
||||
try {
|
||||
const res = rg.find(target);
|
||||
if (res.isExist()) { if (doClick) await sleep(16), res.click(), await sleep(50); return true; }
|
||||
} finally { rg.dispose(); }
|
||||
if (i < maxAttempts - 1) await sleep(50);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检验账户名
|
||||
async function getUserName() {
|
||||
userName = userName.trim();
|
||||
// 账户名规则:数字、中英文,长度1-20字符
|
||||
if (!userName || !/^[\u4e00-\u9fa5A-Za-z0-9]{1,20}$/.test(userName)) {
|
||||
log.error(`账户名${userName}违规,暂时使用默认账户名,请查看readme后修改`)
|
||||
userName = "默认账户";
|
||||
}
|
||||
return userName;
|
||||
}
|
||||
|
||||
async function close_expired_stuff_popup_window() {
|
||||
const game_region = captureGameRegion();
|
||||
const text_x = 850;
|
||||
const text_y = 273;
|
||||
const text_w = 225;
|
||||
const text_h = 51;
|
||||
const ocr_res = game_region.find(RecognitionObject.ocr(text_x, text_y, text_w, text_h));
|
||||
if (ocr_res) {
|
||||
if (ocr_res.text.includes("物品过期")) {
|
||||
log.info("检测到物品过期");
|
||||
click(1000, 750);
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
game_region.dispose();
|
||||
}
|
||||
/**
|
||||
* 识别背包中指定物品的数量
|
||||
* @param {string} itemName - 物品名称(仅用于日志)
|
||||
* @param {string} templatePath - 模板图片路径
|
||||
* @returns {Promise<string>} 识别到的数字字符串(可能为空)
|
||||
*/
|
||||
async function getBottleCount(itemName, templatePath) {
|
||||
const ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(templatePath));
|
||||
ro.InitTemplate();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const rg = captureGameRegion();
|
||||
try {
|
||||
const res = rg.find(ro);
|
||||
if (res.isExist()) {
|
||||
const regionToCheck = { x: res.x, y: res.y + 20, width: 70, height: 20 };
|
||||
// 使用numberTemplateMatch函数识别数字
|
||||
const count = await numberTemplateMatch(
|
||||
'assets/背包数字', // 数字模板文件夹路径
|
||||
regionToCheck.x, regionToCheck.y, regionToCheck.width, regionToCheck.height
|
||||
);
|
||||
const digits = count === -1 ? '' : count.toString();
|
||||
log.info(`识别到${itemName}数量为${digits}`);
|
||||
//log.info(`识别到${itemName}识别区域为${regionToCheck.x}, ${regionToCheck.y}, ${regionToCheck.width}, ${regionToCheck.height}`)
|
||||
return digits; // 成功识别即返回
|
||||
}
|
||||
} finally {
|
||||
rg.dispose();
|
||||
}
|
||||
if (i < 5 - 1) await sleep(50);
|
||||
}
|
||||
return ''; // 未找到时返回空字符串
|
||||
}
|
||||
|
||||
// 检验账户名
|
||||
async function getUserName() {
|
||||
account = account.trim();
|
||||
// 账户名规则:数字、中英文,长度1-20字符
|
||||
if (!account || !/^[\u4e00-\u9fa5A-Za-z0-9]{1,20}$/.test(account)) {
|
||||
log.error(`账户名${account}违规,暂时使用默认账户名,请查看readme后修改`)
|
||||
account = "默认账户";
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地记录中最新的一组数据
|
||||
* @param {string} filePath - 记录文件路径
|
||||
* @returns {Promise<object>} 包含经验数据的对象
|
||||
*/
|
||||
async function getLocalData(filePath) {
|
||||
// 初始化返回结果
|
||||
const result = {
|
||||
initialValue: null,
|
||||
smallBottle: null,
|
||||
bigBottle: null,
|
||||
starCounts: {
|
||||
star1: null,
|
||||
star2: null,
|
||||
star3: null,
|
||||
star4: null
|
||||
},
|
||||
totalExp: null,
|
||||
initialized: {
|
||||
initialValue: false,
|
||||
smallBottle: false,
|
||||
bigBottle: false,
|
||||
star1: false,
|
||||
star2: false,
|
||||
star3: false,
|
||||
star4: false,
|
||||
totalExp: false
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// 尝试读取文件,不存在则直接返回空结果
|
||||
const content = await file.readText(filePath);
|
||||
const lines = content.split('\n').filter(line => line.trim());
|
||||
|
||||
if (lines.length === 0) return result;
|
||||
|
||||
// 数据匹配正则
|
||||
const initialValueRegex = /已储存经验-(\d+)/;
|
||||
const smallBottleRegex = /小经验瓶-(\d+)/;
|
||||
const bigBottleRegex = /大经验瓶-(\d+)/;
|
||||
const star1Regex = /1星狗粮-(\d+)/;
|
||||
const star2Regex = /2星狗粮-(\d+)/;
|
||||
const star3Regex = /3星狗粮-(\d+)/;
|
||||
const star4Regex = /4星狗粮-(\d+)/;
|
||||
const totalExpRegex = /总经验-(\d+)/;
|
||||
|
||||
// 遍历前几条记录,寻找最新的一组完整数据
|
||||
for (const line of lines) {
|
||||
// 匹配已储存经验
|
||||
if (!result.initialized.initialValue) {
|
||||
const match = line.match(initialValueRegex);
|
||||
if (match) {
|
||||
result.initialValue = parseInt(match[1]);
|
||||
result.initialized.initialValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配小经验瓶
|
||||
if (!result.initialized.smallBottle) {
|
||||
const match = line.match(smallBottleRegex);
|
||||
if (match) {
|
||||
result.smallBottle = parseInt(match[1]);
|
||||
result.initialized.smallBottle = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配大经验瓶
|
||||
if (!result.initialized.bigBottle) {
|
||||
const match = line.match(bigBottleRegex);
|
||||
if (match) {
|
||||
result.bigBottle = parseInt(match[1]);
|
||||
result.initialized.bigBottle = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配1星狗粮
|
||||
if (!result.initialized.star1) {
|
||||
const match = line.match(star1Regex);
|
||||
if (match) {
|
||||
result.starCounts.star1 = parseInt(match[1]);
|
||||
result.initialized.star1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配2星狗粮
|
||||
if (!result.initialized.star2) {
|
||||
const match = line.match(star2Regex);
|
||||
if (match) {
|
||||
result.starCounts.star2 = parseInt(match[1]);
|
||||
result.initialized.star2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配3星狗粮
|
||||
if (!result.initialized.star3) {
|
||||
const match = line.match(star3Regex);
|
||||
if (match) {
|
||||
result.starCounts.star3 = parseInt(match[1]);
|
||||
result.initialized.star3 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配4星狗粮
|
||||
if (!result.initialized.star4) {
|
||||
const match = line.match(star4Regex);
|
||||
if (match) {
|
||||
result.starCounts.star4 = parseInt(match[1]);
|
||||
result.initialized.star4 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配总经验
|
||||
if (!result.initialized.totalExp) {
|
||||
const match = line.match(totalExpRegex);
|
||||
if (match) {
|
||||
result.totalExp = parseInt(match[1]);
|
||||
result.initialized.totalExp = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有数据都找到,提前终止遍历
|
||||
if (result.initialized.initialValue &&
|
||||
result.initialized.smallBottle &&
|
||||
result.initialized.bigBottle &&
|
||||
result.initialized.star1 &&
|
||||
result.initialized.star2 &&
|
||||
result.initialized.star3 &&
|
||||
result.initialized.star4 &&
|
||||
result.initialized.totalExp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 文件不存在或读取错误时返回空结果
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新记录文件
|
||||
* @param {string} filePath - 记录文件路径
|
||||
* @param {number} initialValue - 已储存经验
|
||||
* @param {number} smallBottle - 小经验瓶数量
|
||||
* @param {number} bigBottle - 大经验瓶数量
|
||||
* @param {object} starCounts - 狗粮数量
|
||||
* @param {number} totalExp - 总经验值
|
||||
*/
|
||||
async function updateRecord(filePath, initialValue, smallBottle, bigBottle, starCounts, totalExp) {
|
||||
// 生成当前时间字符串
|
||||
const now = new Date();
|
||||
const timeStr = `${now.getFullYear()}/${
|
||||
String(now.getMonth() + 1).padStart(2, '0')
|
||||
}/${
|
||||
String(now.getDate()).padStart(2, '0')
|
||||
} ${
|
||||
String(now.getHours()).padStart(2, '0')
|
||||
}:${
|
||||
String(now.getMinutes()).padStart(2, '0')
|
||||
}:${
|
||||
String(now.getSeconds()).padStart(2, '0')
|
||||
}`;
|
||||
|
||||
// 生成记录
|
||||
const records = [
|
||||
`时间:${timeStr}-已储存经验-${initialValue}`,
|
||||
`时间:${timeStr}-小经验瓶-${smallBottle}`,
|
||||
`时间:${timeStr}-大经验瓶-${bigBottle}`,
|
||||
`时间:${timeStr}-1星狗粮-${starCounts.star1}`,
|
||||
`时间:${timeStr}-2星狗粮-${starCounts.star2}`,
|
||||
`时间:${timeStr}-3星狗粮-${starCounts.star3}`,
|
||||
`时间:${timeStr}-4星狗粮-${starCounts.star4}`,
|
||||
`时间:${timeStr}-总经验-${totalExp}`
|
||||
];
|
||||
|
||||
try {
|
||||
let content = await file.readText(filePath);
|
||||
let lines = content.split('\n').filter(line => line.trim());
|
||||
|
||||
if (lines.length === 0) {
|
||||
// 文件为空,直接写入新记录
|
||||
await file.writeText(filePath, records.join('\n'));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 添加新记录到最前面
|
||||
lines.unshift(...records);
|
||||
|
||||
// 只保留1年内的记录
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
|
||||
const recentLines = lines.filter(line => {
|
||||
const timeMatch = line.match(/时间:(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})/);
|
||||
if (!timeMatch) return false;
|
||||
const lineTime = new Date(timeMatch[1]);
|
||||
return lineTime >= oneYearAgo;
|
||||
});
|
||||
|
||||
// 写入文件
|
||||
await file.writeText(filePath, recentLines.join('\n'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
// 文件不存在时创建新文件
|
||||
await file.writeText(filePath, records.join('\n'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})();
|
||||
18
repo/js/ArtifactExpCount/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "圣遗物经验统计",
|
||||
"version": "1.0",
|
||||
"bgi_version": "0.55",
|
||||
"description": "识别可分解的1-4星圣遗物和经验瓶数量",
|
||||
"authors": [
|
||||
{
|
||||
"name": "勺子",
|
||||
"links": "https://github.com/skyflag2022"
|
||||
}
|
||||
],
|
||||
"settings_ui": "settings.json",
|
||||
"main": "main.js",
|
||||
"saved_files": [
|
||||
"assets/*.txt"
|
||||
]
|
||||
}
|
||||
14
repo/js/ArtifactExpCount/settings.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"name": "userName",
|
||||
"type": "input-text",
|
||||
"label": "账户名称\n用于多账户运行时区分不同账户",
|
||||
"default": "默认账户"
|
||||
},
|
||||
{
|
||||
"name": "notify",
|
||||
"type": "checkbox",
|
||||
"label": "是否通知信息",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||