feat: 千星刷经验重构 (#2897)

This commit is contained in:
躁动的氨气
2026-02-20 21:20:43 +08:00
committed by GitHub
parent be482b014b
commit 51a32854b5
4 changed files with 110 additions and 214 deletions

View File

@@ -8,24 +8,31 @@
## ❗ 使用前重要说明(免责声明)
本脚本依赖“**删除第一个奇域地图存档**”来实现重复获取成就经验。
本脚本依赖“**删除奇域地图存档**”来实现重复获取成就经验。
这意味着:
- 当脚本执行删除存档时,如果 **第一个存档不是你正在刷的地图**可能会误删你正常游玩的关卡数据。
- 当脚本执行删除存档时,可能会误删你正常游玩的关卡数据。
若你完全理解并接受此风险,请在脚本的 *自定义 JS 配置* 中手动勾选 `我已阅读说明中的免责声明` 才可继续执行脚本。
请务必确认你能承担潜在损失后再继续使用。
---
## 🔥 焚诀
自己发布的千星奇域的地图也可以用来增长经验,但是并不需要发布成功,仅需要点击一下发布再预览详情,即可在弹窗中进行收藏。
这就意味着可以自行制作秒刷图,然后“发布”进行收藏,在收藏中使用自己实际上并未真正发布的地图进行秒刷。
最新版脚本进行了重构优化了点击速度秒刷图一周刷完仅需8分45秒。
地图制作请自行学习。
## 🌟 功能特点
- 🔁 自动重复通关指定奇域地图
- 🧹 自动删除存档,实现成就经验可重复获取
- 📅 自动监测每周经验上限,到达后自动停止
- 🏞️ 完成后自动返回提瓦特大陆,不影响其他自动化脚本
- 可刷收藏地图,支持自定义地图名称
* 自动重复通关指定奇域地图
* 自动删除存档,实现成就经验可重复获取
* 自动监测每周经验上限,到达后自动停止
* 完成后自动返回提瓦特大陆,不影响其他自动化脚本
* 可刷收藏地图,支持自定义地图名称
---
@@ -47,9 +54,10 @@
## ❗ 注意事项
- 游戏窗口需保持 **16:9** 的宽高比例,否则可能影响图像识别。
- `singleExp` 请填写正确,否则脚本的剩余次数计算会不准确。
- `每次通关可获得的经验值` 请填写正确,否则脚本的剩余次数计算会不准确。
- 若默认地图下架,请手动输入可游玩的地图号。
- 勾选收藏模式后,请确保 `starRoomName` 填写正确地图名称。
- 勾选收藏模式后,请确保 `收藏模式所使用的地图名称` 填写正确地图名称。
- 成就模式和收藏模式可以同时开启,但请确保地图符合对应要求。
- 若出现红色报错,提示文件不存在,无需在意,不会造成问题。
---

View File

@@ -1,5 +0,0 @@
{
"weekMaxExp": 4000,
"singleExp": 270,
"weekTotal": 15
}

View File

@@ -1,5 +1,11 @@
const attempts = 20; // 最大尝试次数
const interval = 1000; // 每次间隔 ms
import {
getImgMat,
findText,
findTextAndClick,
findImgAndClick,
waitUntilTextAppear
} from "../../../packages/utils/tool";
const duration = 1000; // 默认点击等待延时
const storePath = "data/store.json"
@@ -14,11 +20,6 @@ const weekMaxExp = Number(settings.weekMaxExp || "4000");
const singleExp = Number(settings.singleExp || "270");
let weekTotal = initWeekTotal();
// 获取图片资源
function getImgMat(path) {
return file.readImageMatSync('assets/' + path + '.png');
}
// 读取存档
function loadWeekData() {
try {
@@ -106,87 +107,12 @@ function decreaseWeekTotal() {
weekTotal = stored.weekTotal;
}
// 查找文本
async function findText(text, x, y, w, h, textAttempts = attempts) {
const searchText = text.toLowerCase();
for (let i = 0; i < textAttempts; i++) {
const captureRegion = captureGameRegion();
const ro = RecognitionObject.ocr(x, y, w, h);
const results = captureRegion.findMulti(ro);
captureRegion.dispose();
for (let j = 0; j < results.count; j++) {
const region = results[j];
if (region.isExist() && region.text.toLowerCase().includes(searchText)) {
return region;
}
}
await sleep(interval);
}
return null;
}
// 升级特殊界面点击
async function findAndClickWhiteSpaceNext() {
const captureRegion = captureGameRegion();
const ro = RecognitionObject.ocr(610, 950, 700, 60);
const results = captureRegion.findMulti(ro);
captureRegion.dispose();
for (let j = 0; j < results.count; j++) {
const region = results[j];
if (region.isExist()) {
const text = region.text.toLowerCase();
if (text.includes("点击") && text.includes("继续")) {
await sleep(duration);
click(610, 950);
}
}
}
}
// 查找图片
async function findImage(imgMat, x, y, w, h, imgAttempts = attempts) {
const searchImg = RecognitionObject.TemplateMatch(imgMat, x, y, w, h);
for (let i = 0; i < imgAttempts; i++) {
const captureRegion = captureGameRegion();
const result = captureRegion.find(searchImg);
captureRegion.dispose();
if (result.isExist()) {
return result;
}
await sleep(interval);
}
return null;
}
// 查找文本并点击
async function findTextAndClick(text, x, y, w, h, textAttempts = attempts) {
const target = await findText(text, x, y, w, h, textAttempts);
if (target) {
const result = await findText(["点击", "继续"],610, 950, 700, 60);
if (result) {
await sleep(duration);
target.click();
} else {
log.error("文本{text}查找失败", text);
}
}
// 查找图片并点击
async function findImageAndClick(path, x, y, w, h, imgAttempts = attempts) {
const imgMat = getImgMat(path);
const target = await findImage(imgMat, x, y, w, h, imgAttempts);
if (target) {
await sleep(duration);
target.click();
} else {
log.error("图标{path}查找失败", path);
click(610, 950);
}
}
@@ -198,7 +124,7 @@ async function findSaveInList(keyword) {
const region = await findText(
keyword,
200, 250, 1500, 700,
1
3
);
if (region) {
@@ -227,8 +153,8 @@ async function deleteSource() {
keyPress("VK_B");
await sleep(duration);
await findTextAndClick("管理关卡", 960, 0, 960, 100);
await findTextAndClick("管理", 960, 980, 960, 100);
await findTextAndClick("管理关卡", 960, 0, 960, 100, 50, 50);
await findTextAndClick("管理", 960, 980, 960, 100, 50, 50);
// 查找目标存档
const saveRegion = await findSaveInList(starRoomName);
@@ -242,12 +168,13 @@ async function deleteSource() {
const sy = saveRegion.y - 30;
await sleep(300);
await findImageAndClick("check_box", 0, sy, 1480, saveRegion.height + 70);
const check_box = getImgMat("assets/check_box.png");
await findImgAndClick(check_box, 0, sy, 1480, saveRegion.height + 70, 2000);
// 删除
await sleep(duration);
await findTextAndClick("删除", 960, 980, 960, 100);
await findTextAndClick("确认", 960, 600, 960, 400);
await findTextAndClick("确认", 960, 600, 960, 400);
await findTextAndClick("删除", 960, 980, 960, 100, 50, 100);
await findTextAndClick("确认", 960, 600, 960, 400, 50, 100);
await findTextAndClick("确认", 960, 600, 960, 400, 50, 100);
log.info("关卡存档删除完成");
await sleep(duration);
@@ -257,29 +184,48 @@ async function deleteSource() {
// 进入千星奇域的全部奇域页面
async function enterSourcePage() {
// 1. 检测是否在房间内,在则退出
const inRoom = await findText("房间", 1500, 0, 420, 500, 5);
const inRoom = await findText("房间", 1500, 0, 420, 500, 5, 100);
if (inRoom) {
keyPress("VK_P");
await sleep(duration);
await findImageAndClick("exit_room", 960, 0, 960, 540);
const exit_room = getImgMat("assets/exit_room.png");
await waitUntilTextAppear(
"确认",
async () => {
await findImgAndClick(exit_room, 960, 0, 960, 540, 500);
},
960,
600,
960,
400,
10
);
await findTextAndClick("确认", 960, 600, 960, 400);
await genshin.returnMainUi();
keyPress("VK_F6");
} else {
keyPress("VK_F6");
}
await sleep(duration);
}
// 进入千星奇域的收藏奇域页面
async function enterStarSourcePage() {
// 1. 检测是否在房间内,在则退出
const inRoom = await findText("房间", 1500, 0, 420, 500, 5);
const inRoom = await findText("房间", 1500, 0, 420, 500, 5, 100);
if (inRoom) {
keyPress("VK_P");
await sleep(duration);
await findImageAndClick("exit_room", 960, 0, 960, 540);
const exit_room = getImgMat("assets/exit_room.png");
await waitUntilTextAppear(
"确认",
async () => {
await findImgAndClick(exit_room, 960, 0, 960, 540, 500);
},
960,
600,
960,
400,
100
);
await findTextAndClick("确认", 960, 600, 960, 400);
await genshin.returnMainUi();
keyPress("VK_B");
@@ -302,20 +248,9 @@ async function createMap() {
await sleep(duration);
click(355, 365);
await sleep(duration);
while (true) {
const result = await findText("房间", 960, 100, 960, 200, 2);
if (result) {
await sleep(duration);
result.click();
await sleep(duration);
break;
} else {
const result2 = await findText("大厅", 960, 600, 960, 400, 2);
if (result2) {
result2.click();
await sleep(duration);
}
}
const result = await findTextAndClick("房间",960, 100, 960, 200, 2);
if (!result) {
await findTextAndClick("大厅", 960, 600, 960, 400, 2);
}
await findText("开始游戏", 960, 540, 960, 540);
click(770, 275);
@@ -326,25 +261,16 @@ async function createMap() {
async function createStarMap() {
await findTextAndClick("搜索", 0, 0, 1920, 120);
inputText(roomID);
await sleep(1000);
await sleep(500);
await findTextAndClick("搜索", 0, 0, 1920, 120);
await sleep(duration);
click(420, 830);
await sleep(duration);
while (true) {
const result = await findText("房间", 960, 100, 960, 200, 2);
if (result) {
await sleep(duration);
result.click();
await sleep(duration);
break;
} else {
const result2 = await findText("大厅", 960, 600, 960, 400, 2);
if (result2) {
result2.click();
await sleep(duration);
}
}
const result = await findTextAndClick("房间",960, 100, 960, 200, 2);
if (!result) {
await findTextAndClick("大厅", 960, 600, 960, 400, 2);
await waitUntilTextAppear("房间", () => {},960, 100, 960, 200, 50, 1000);
await findTextAndClick("房间",960, 100, 960, 200);
}
await findText("开始游戏", 960, 540, 960, 540);
click(770, 275);
@@ -363,49 +289,30 @@ async function playMap() {
await createMap();
}
while (true) {
await sleep(duration);
const result = await findText("开始游戏", 960, 540, 960, 540, 5);
if (result) {
await sleep(duration);
result.click();
log.info("开始执行第{i}/{total}次奇域挑战", 1, total);
await sleep(duration);
} else {
await sleep(duration);
break;
}
}
await findTextAndClick("开始游戏", 960, 540, 960, 540, 5, 50, 50);
log.info("开始执行第{i}/{total}次奇域挑战", 1, total);
let firstOutputCount = 0;
while (true) {
const whiteText = await findText("空白", 610, 900, 700, 100, 1);
if (whiteText) {
await sleep(duration);
click(610, 950);
}
const result = await findText("返回大厅", 960, 540, 960, 540, 1);
if (result) {
await sleep(duration);
result.click();
await sleep(duration);
if (!useFixedAttempts) {
decreaseWeekTotal();
}
log.info("本次关卡结束");
break;
} else {
const inRoom = await findText("房间", 1500, 0, 420, 500, 1);
if (inRoom) {
break;
}
await waitUntilTextAppear(
"返回大厅",
async () => {
await findAndClickWhiteSpaceNext();
if (firstOutputCount % 16 === 0) {
log.info("等待本次关卡结束...");
}
firstOutputCount++;
await sleep(interval);
}
},
960,
540,
960,
540,
500,
2000
);
await findTextAndClick("返回大厅", 960, 540, 960, 540);
if (!useFixedAttempts) {
decreaseWeekTotal();
}
log.info("本次关卡结束");
await deleteSource();
@@ -415,42 +322,28 @@ async function playMap() {
await sleep(duration);
keyPress("VK_P");
await sleep(duration);
while (true) {
const result = await findText("开始游戏", 960, 540, 960, 540, 1);
if (result) {
await sleep(duration);
result.click();
log.info("开始执行第{i}/{total}次奇域挑战", i + 1, total);
await sleep(duration);
break;
} else {
await sleep(duration);
}
}
await findTextAndClick("开始游戏", 960, 540, 960, 540, 20, 50, 50);
log.info("开始执行第{i}/{total}次奇域挑战", i + 1, total);
let outputCount = 0;
while (true) {
await findAndClickWhiteSpaceNext();
const result = await findText("返回大厅", 960, 540, 960, 540, 1);
if (result) {
await sleep(duration);
result.click();
await sleep(duration);
if (!useFixedAttempts) {
decreaseWeekTotal();
}
log.info("本次关卡结束");
break;
} else {
const inRoom = await findText("房间", 1500, 0, 420, 500, 1);
if (inRoom) {
break;
}
if (outputCount % 10 === 0) {
await waitUntilTextAppear(
"返回大厅",
async () => {
await findAndClickWhiteSpaceNext();
if (outputCount % 16 === 0) {
log.info("等待本次关卡结束...");
}
outputCount++;
await sleep(interval);
}
},
960,
540,
960,
540,
500,
2000
);
await findTextAndClick("返回大厅", 960, 540, 960, 540);
if (!useFixedAttempts) {
decreaseWeekTotal();
}
await deleteSource();
}

View File

@@ -1,9 +1,9 @@
{
"manifest_version": 1,
"name": "千星奇域每周成就经验刷取",
"version": "2.7",
"bgi_version": "0.54.0",
"description": "无需自己找图可用于利用成就高经验值刷取经验默认配置每周刷满需要24分钟",
"version": "3.0",
"bgi_version": "0.57.0",
"description": "无需自己找图可用于利用成就高经验值刷取经验默认配置每周刷满需要22分钟秒刷图仅需9分钟",
"authors": [
{
"name": "躁动的氨气",