diff --git a/repo/js/WeeklyThousandStarRealm/README.md b/repo/js/WeeklyThousandStarRealm/README.md new file mode 100644 index 000000000..c39928ae3 --- /dev/null +++ b/repo/js/WeeklyThousandStarRealm/README.md @@ -0,0 +1,56 @@ +# 🌌 千星奇域 · 成就经验自动慢刷脚本 + +**⚠️ 提醒:由于奇域地图经常下架,默认地图也可能无法使用。若默认地图被下架,请使用 MiliastraExperienceAutomation 脚本刷取固定 20 经验的关卡,并将房间号填写到本脚本设置中。** + +> 与 MiliastraExperienceAutomation 脚本存在部分逻辑重复,这里就不再重新撰写啦😇 + +--- + +## ❗ 使用前重要说明(免责声明) + +本脚本依赖“**删除奇域地图存档**”来实现重复获取成就经验。 + +这意味着: + +- 当脚本执行删除存档时,如果 **第一个存档不是你正在刷的地图**,可能会误删你正常游玩的关卡数据。 +- 若你完全理解并接受此风险,请在脚本的 *自定义 JS 配置* 中手动勾选 `我已阅读说明中的免责声明` 才可继续执行脚本。 + +请务必确认你能承担潜在损失后再继续使用。 + +--- + +## 🌟 功能特点 + +- 🔁 自动重复通关指定奇域地图 +- 🧹 自动删除存档,实现成就经验可重复获取 +- 📅 自动监测每周经验上限,到达后自动停止 +- 🏞️ 完成后自动返回提瓦特大陆,不影响其他自动化脚本 + +--- + +## 🛠️ 脚本设置项说明 + +| 配置项 | 描述 | 默认值 | +|--------------|---------------------------------------------------------------------|--------------| +| **runJS** | 我已阅读免责声明(未勾选无法执行) | `false` | +| **room** | 奇域关卡关键词或 GUID(仅支持单个;空则使用默认地图) | `37135473336` | +| **thisAttempts** | 指定通关次数(`0` = 无限,直到达到每周上限) | `0` | +| **weekMaxExp** | 每周可获取的经验值上限 | `4000` | +| **singleExp** | 每次通关可获得的经验值 | `270` | + +--- + +## ❗ 注意事项 + +- 游戏窗口需保持 **16:9** 的宽高比例,否则可能影响图像识别。 +- `singleExp` 请填写正确,否则脚本的剩余次数计算会不准确。 +- 若默认地图下架,请手动输入可游玩的房间号。 + +--- + +## 📅 更新计划 + +未来可能会支持运行“收藏页面”中的奇域地图。 +但由于收藏地图状态不稳定(部分下架 → 能进;彻底取消发布 → 无法进入),**暂不确定是否正式加入**。 + +--- diff --git a/repo/js/WeeklyThousandStarRealm/assets/check_box.png b/repo/js/WeeklyThousandStarRealm/assets/check_box.png new file mode 100644 index 000000000..4cc5b5c33 Binary files /dev/null and b/repo/js/WeeklyThousandStarRealm/assets/check_box.png differ diff --git a/repo/js/WeeklyThousandStarRealm/assets/close_manage.png b/repo/js/WeeklyThousandStarRealm/assets/close_manage.png new file mode 100644 index 000000000..7b83d3226 Binary files /dev/null and b/repo/js/WeeklyThousandStarRealm/assets/close_manage.png differ diff --git a/repo/js/WeeklyThousandStarRealm/assets/close_star_manage.png b/repo/js/WeeklyThousandStarRealm/assets/close_star_manage.png new file mode 100644 index 000000000..1a8ba7641 Binary files /dev/null and b/repo/js/WeeklyThousandStarRealm/assets/close_star_manage.png differ diff --git a/repo/js/WeeklyThousandStarRealm/assets/exit_room.png b/repo/js/WeeklyThousandStarRealm/assets/exit_room.png new file mode 100644 index 000000000..af8da7451 Binary files /dev/null and b/repo/js/WeeklyThousandStarRealm/assets/exit_room.png differ diff --git a/repo/js/WeeklyThousandStarRealm/data/store.json b/repo/js/WeeklyThousandStarRealm/data/store.json new file mode 100644 index 000000000..9aed3ad2a --- /dev/null +++ b/repo/js/WeeklyThousandStarRealm/data/store.json @@ -0,0 +1,5 @@ +{ + "weekMaxExp": 4000, + "singleExp": 270, + "weekTotal": 15 +} \ No newline at end of file diff --git a/repo/js/WeeklyThousandStarRealm/main.js b/repo/js/WeeklyThousandStarRealm/main.js new file mode 100644 index 000000000..362870c65 --- /dev/null +++ b/repo/js/WeeklyThousandStarRealm/main.js @@ -0,0 +1,334 @@ +const attempts = 20; // 最大尝试次数 +const interval = 1000; // 每次间隔 ms +const duration = 1000; // 默认点击等待延时 + +const storePath = "data/store.json" +const runJS = settings.runJS || false; +const roomID = settings.room || "37135473336"; +const userAttempts = Number(settings.thisAttempts || "0"); +const useFixedAttempts = userAttempts > 0; +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 { + const t = file.readTextSync(storePath); + return JSON.parse(t); + } catch (_) { + return null; + } +} + +// 保存存档 +function saveWeekData(data) { + file.writeTextSync(storePath, JSON.stringify(data)); +} + +// 初始化或更新 weekTotal +function initWeekTotal() { + const stored = loadWeekData(); + const calculated = Math.ceil(weekMaxExp / singleExp); + + // 首次 OR 配置变化 → 重写 + if ( + !stored || + stored.weekMaxExp !== weekMaxExp || + stored.singleExp !== singleExp + ) { + const newData = { + weekMaxExp, + singleExp, + weekTotal: calculated + }; + saveWeekData(newData); + return calculated; + } + + return stored.weekTotal; +} + +// 刷完一次 → 计数 -1 +function decreaseWeekTotal() { + const stored = loadWeekData(); + if (!stored) return; + + stored.weekTotal = Math.max(stored.weekTotal - 1, 0); + saveWeekData(stored); + 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 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) { + 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); + } +} + +// 清除游玩数据 +async function deleteSource() { + await sleep(duration); + await genshin.returnMainUi(); + log.info("开始清除地图数据"); + await sleep(duration); + keyPress("VK_B"); + await sleep(duration); + await findTextAndClick("管理关卡", 960, 0, 960, 100); + await findTextAndClick("管理", 960, 980, 960, 100); + await findImageAndClick("check_box", 0, 0, 1480, 340); + await findTextAndClick("删除",960, 980, 960, 100); + await findTextAndClick("确认", 960, 600, 960, 400); + await findTextAndClick("确认", 960, 600, 960, 400); + log.info("数据清除完成"); + await sleep(duration); + await genshin.returnMainUi(); +} + +// 进入千星奇域的全部奇域页面 +async function enterSourcePage() { + // 1. 检测是否在房间内,在则退出 + const inRoom = await findText("房间", 1500, 0, 420, 500, 5); + if (inRoom) { + keyPress("VK_P"); + await sleep(duration); + await findImageAndClick("exit_room", 960, 0, 960, 540); + await findTextAndClick("确认", 960, 600, 960, 400); + await genshin.returnMainUi(); + keyPress("VK_F6"); + } else { + keyPress("VK_F6"); + } + + await sleep(duration); +} + +// 创建关卡 +async function createMap() { + await findTextAndClick("全部", 1320, 0, 600, 95); + await findTextAndClick("搜索", 0, 120, 1920, 60); + inputText(roomID); + await sleep(1000); + await findTextAndClick("搜索", 0, 120, 1920, 60); + 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); + } + } + } + await findText("开始游戏", 960, 540, 960, 540); + click(770, 275); + await sleep(duration); +} + +// 游玩关卡 +async function playMap() { + const stored = loadWeekData(); + const leave = stored ? stored.weekTotal : weekTotal; + const total = useFixedAttempts ? userAttempts : leave; + 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; + } + } + let firstOutputCount = 0; + while (true) { + 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 (firstOutputCount % 16 === 0) { + log.info("等待本次关卡结束..."); + } + firstOutputCount++; + await sleep(interval); + } + } + + await deleteSource(); + + for (let i = 1; i < total; i++) { + const inRoom = await findText("房间", 1500, 0, 420, 500); + if (inRoom) { + 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); + } + } + let outputCount = 0; + while (true) { + 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) { + log.info("等待本次关卡结束..."); + } + outputCount++; + await sleep(interval); + } + } + await deleteSource(); + } + } +} + +// 退出到提瓦特 +async function exitToTeyvat() { + await genshin.returnMainUi(); + await sleep(duration); + keyPress("VK_F2"); + await sleep(duration); + await findTextAndClick("返回", 960, 0, 960, 100); + await sleep(duration); + await findTextAndClick("确认", 960, 600, 960, 400); +} + +(async function () { + if (!runJS) { + log.error("您未同意此脚本的免责声明,请先同意后重新运行此脚本!"); + return; + } + + await genshin.returnMainUi(); + + if (useFixedAttempts) { + // 手动模式:忽略本周计数 + log.info( + "已进入指定次数模式,本次将执行{count}次挑战(不计入周进度)", + userAttempts + ); + } else { + // 每周模式 + const stored = loadWeekData(); + const leave = stored.weekTotal; + const done = Math.ceil(weekMaxExp / singleExp) - leave; + + log.info( + "本周共需刷取 {total} 次,已刷 {done} 次,剩余 {leave} 次", + Math.ceil(weekMaxExp / singleExp), + done, + leave + ); + } + + await enterSourcePage(); + await playMap(); + await exitToTeyvat(); +})(); diff --git a/repo/js/WeeklyThousandStarRealm/manifest.json b/repo/js/WeeklyThousandStarRealm/manifest.json new file mode 100644 index 000000000..990fd15f7 --- /dev/null +++ b/repo/js/WeeklyThousandStarRealm/manifest.json @@ -0,0 +1,18 @@ +{ + "manifest_version": 1, + "name": "千星奇域每周成就经验刷取", + "version": "1.0", + "bgi_version": "0.53.0", + "description": "可用于利用成就高经验值刷取经验", + "authors": [ + { + "name": "躁动的氨气", + "link": "https://github.com/zaodonganqi" + } + ], + "main": "main.js", + "settings_ui": "settings.json", + "saved_files": [ + "store/*.json" + ] +} diff --git a/repo/js/WeeklyThousandStarRealm/settings.json b/repo/js/WeeklyThousandStarRealm/settings.json new file mode 100644 index 000000000..99198f0e7 --- /dev/null +++ b/repo/js/WeeklyThousandStarRealm/settings.json @@ -0,0 +1,31 @@ +[ + { + "name": "runJS", + "type": "checkbox", + "label": "我已阅读说明中的免责声明" + }, + { + "type": "input-text", + "name": "room", + "label": "奇域关卡关键词或关卡GUID\n(仅支持单个,默认为作者特供地图)", + "default": "37135473336" + }, + { + "type": "input-text", + "name": "thisAttempts", + "label": "指定通关次数", + "default": "0" + }, + { + "type": "input-text", + "name": "weekMaxExp", + "label": "每周可获取的经验值上限", + "default": "4000" + }, + { + "type": "input-text", + "name": "singleExp", + "label": "每次通关获取的经验值数量", + "default": "270" + } +]