// 全局变量声明 let data, config; let characterX, characterY; let avatar; let failed = false; (async function () { setGameMetrics(1920, 1080, 1); await genshin.returnMainUi(); dispatcher.addTimer(new RealtimeTimer("AutoSkip")); // 开启自动剧情 await loadData(); // 角色点位信息(提前定义以便全局使用) const characterPositions = [ // 角色点位信息 { name: "柯莱-1", x: 2843, y: -384, hasKeyMouse: true }, { name: "迪希雅-1", x: 3771, y: 3608 }, { name: "迪希雅-2", x: 4436, y: 3539 }, { name: "赛诺-1", x: 3062, y: -268 }, { name: "赛诺-2", x: 3059, y: -268 }, { name: "赛诺-3", x: 2863, y: -380 }, { name: "林尼-1", x: 4197, y: 4805 }, { name: "夏沃蕾-1", x: 4356, y: 3707 }, { name: "菲米尼-1", x: 4202, y: 3037, hasKeyMouse: true }, { name: "夏洛蒂-1", x: 4618, y: 3518 }, { name: "夏洛蒂-2", x: 4642, y: 3495 }, { name: "夏洛蒂-3", x: 4443, y: 3538 }, { name: "绮良良-1", x: 231, y: -672 }, { name: "绮良良-2", x: 231, y: -672 }, { name: "绮良良-3", x: -4473, y: -2655 }, { name: "鹿野院平藏-1", x: -4459, y: -3141 }, { name: "鹿野院平藏-2", x: -4467, y: -3127 }, { name: "鹿野院平藏-3", x: -4417, y: -3037 }, { name: "鹿野院平藏-4", x: -4232, y: -2999 }, { name: "鹿野院平藏-5", x: -4232, y: -2999 }, { name: "托马-1", x: -4399, y: -3130 }, { name: "托马-2", x: -929, y: 2301, hasKeyMouse: true }, { name: "梦见月瑞希-1", x: -4458, y: -3111, hasKeyMouse: true }, { name: "梦见月瑞希-2", x: -4458, y: -3111, hasKeyMouse: true }, { name: "八重神子-1", x: -4424, y: -2475 }, { name: "那维莱特-1", x: 3600, y: 3804 }, { name: "那维莱特-2", x: 4472, y: 3553 }, { name: "那维莱特-3", x: 4797, y: 2660 }, { name: "神里绫人-1", x: -4473, y: -3132 }, { name: "早柚-1", x: -4327, y: -3141 }, ]; // 检查进度并显示统计信息 const progressInfo = checkProgress(characterPositions); log.info(`=== 对话进度统计 ===`); log.info(`总角色数量: ${progressInfo.total}`); log.info(`已完成: ${progressInfo.completed} (${progressInfo.completionRate}%)`); log.info(`待完成: ${progressInfo.remaining}`); // 处理重置设置 await handleResetSettings(); // 重新检查进度(可能已重置) let currentProgress = checkProgress(characterPositions); if (currentProgress.remaining === 0) { log.info("所有角色都已完成对话!"); log.info("如需在其他账号使用,请在设置中启用自动重置选项"); return; } if (currentProgress.remaining > 0) { log.info(`未完成的角色: ${currentProgress.remainingCharacters.join(", ")}`); } // 前往七天神像 // await genshin.tpToStatueOfTheSeven(); let runCount = 0; const maxRuns = parseInt(settings.maxRuns) || 50; log.info(`最大运行次数: ${maxRuns}`); showCurrentSettings(); let failCount = 0; let lastProgress = -1; let skipList = []; while (runCount < maxRuns) { log.info(`开始第${runCount + 1}次运行`); // 在每次运行前检查是否还有未完成的角色 currentProgress = checkProgress(characterPositions); if (currentProgress.remaining === lastProgress) { log.warn("与上轮循环剩余数量一致,可能所有点位都不可用或运行失败"); failCount++; } else { failCount = 0; } lastProgress = currentProgress.remaining; if (failCount >= 4) { log.error("连续五轮循环剩余数量没有变化,结束循环,请手动清理现在在地图上的旅闻后重新启动js") break; } if (currentProgress.remaining === 0) { log.info("🎉 所有角色都已完成对话!任务结束。"); break; } log.info(`当前进度: ${currentProgress.completed}/${currentProgress.total} (剩余${currentProgress.remaining}个)`); const detectedCharacters = await find(); let pathingName = null; let hasKeyMouse = false; let found = false; let matchedNames = []; for (const pos of characterPositions) { //如果启用了跳过已完成角色的设置,则跳过已完成的角色 if (settings.skipCompletedCharacters && config[pos.name]) { skipList.push(pos.name); } // 使用 Set 去除重复项 skipList = [...new Set(skipList)]; if (isNearPosition(characterX, characterY, pos.x, pos.y)) { matchedNames.push(pos.name); /* pathingName = pos.name; hasKeyMouse = !!pos.hasKeyMouse; found = true; log.info(`找到角色,执行路线:${pathingName}`); break; */ } } if (matchedNames.length === 1) { pathingName = matchedNames[0]; const pos = characterPositions.find(p => p.name === pathingName); hasKeyMouse = !!(pos && pos.hasKeyMouse); found = true; log.info(`找到角色,执行路线:${pathingName}`); } else if (matchedNames.length > 1) { log.info(`找到多个路线:${matchedNames.join(", ")}`); // 首先尝试通过检测到的角色名字进行匹配 let foundByName = false; for (const name of matchedNames) { if (skipList.includes(name)) { continue; } const avatarName = name.split("-")[0]; if (detectedCharacters.includes(avatarName)) { pathingName = name; const pos = characterPositions.find(p => p.name === pathingName); hasKeyMouse = !!(pos && pos.hasKeyMouse); found = true; log.info(`通过角色名字匹配到路线:${pathingName} (角色:${avatarName})`); foundByName = true; break; } } // 如果通过角色名字没有找到,则回退到原来的图像识别方法 if (!foundByName) { for (const name of matchedNames) { if (skipList.includes(name)) { continue; } const avatarName = name.split("-")[0]; const isChar = captureGameRegion().findMulti(RecognitionObject.TemplateMatch(file.ReadImageMatSync(`assets/avatars/${avatarName}.png`))); if (isChar && isChar.count > 0) { pathingName = name; const pos = characterPositions.find(p => p.name === pathingName); hasKeyMouse = !!(pos && pos.hasKeyMouse); found = true; log.info(`通过图像识别匹配到路线:${pathingName}`); break; } } } } if (!found) { currentProgress = checkProgress(characterPositions); log.error("未找到角色,或者角色未被收录"); log.error(`当前位置可能没有未完成的角色对话`); log.error(`剩余未完成角色: ${currentProgress.remainingCharacters.join(", ")}`); log.info("继续寻找下一个角色..."); continue; // 继续下一次循环而不是直接返回 } log.info(`正在前往 ${pathingName} 所在位置...`); if (pathingName == "赛诺-3") { // 特殊处理 await genshin.moveMapTo(2871, -377, "须弥"); await genshin.setBigMapZoomLevel(1.0); await genshin.tp(2871, -377); } await pathingScript.runFile(`assets/pathing/${pathingName}.json`) await sleep(500); keyPress("F"); await sleep(500); keyPress("F"); if (!hasKeyMouse) { log.info("开始对话..."); } await sleep(3000); await waitToMain(pathingName, hasKeyMouse); if (hasKeyMouse) { log.info("执行对应键鼠脚本"); await keyMouseScript.runFile(`assets/keymouse/${pathingName}.json`) await sleep(500); keyPress("F"); await sleep(500); keyPress("F"); log.info("开始对话..."); await sleep(3000); await waitToMain(pathingName, hasKeyMouse); } if (failed) { log.info("本次运行结果不会被保存"); // 将 pathingName 加入 skipList skipList.push(pathingName); } else { config[pathingName] = true; await file.writeText("config.json", JSON.stringify(config, null, 4)); log.info(`对话完成,已保存本次运行结果`); } runCount++; if (runCount < maxRuns) { log.info(`第${runCount}次运行完成`); // 根据设置决定进度更新间隔 const updateInterval = parseInt(settings.progressUpdateInterval) || 3; if (runCount % updateInterval === 0) { currentProgress = checkProgress(characterPositions); log.info(`=== 进度更新 (第${runCount}次运行后) ===`); log.info(`完成进度: ${currentProgress.completed}/${currentProgress.total} (${currentProgress.completionRate}%)`); if (currentProgress.remaining > 0) { log.info(`剩余角色: ${currentProgress.remainingCharacters.slice(0, 5).join(", ")}${currentProgress.remaining > 5 ? '...' : ''}`); } } } } // 最终统计 const finalProgress = checkProgress(characterPositions); log.info(`=== 任务完成统计 ===`); log.info(`总运行次数: ${runCount}`); log.info(`最终完成进度: ${finalProgress.completed}/${finalProgress.total} (${finalProgress.completionRate}%)`); if (finalProgress.remaining === 0) { log.info("🎉 恭喜!所有角色对话已完成!"); } else { log.info(`还有 ${finalProgress.remaining} 个角色未完成:`); log.info(finalProgress.remainingCharacters.join(", ")); } log.info(`程序结束`); })(); /** * 加载数据 * @returns {Promise} */ async function loadData() { try { data = JSON.parse(await file.readText("data.json")); config = JSON.parse(await file.readText("config.json")); } catch (error) { log.error(`加载配置文件失败: ${error.message}`); } } /** * 寻找角色 * @async * @returns {Promise} 找到的角色名字数组,如果没有找到则返回空数组 */ async function find() { log.info(`开始寻找角色...`); const positions = data.mapPositions; // 读取data.json中的点位数据 for (let retryCount = 0; retryCount < positions.length; retryCount++) { const position = positions[retryCount]; log.info(`第 ${retryCount + 1} 次尝试定位...`); log.info(`移动到位置:(${position.x}, ${position.y}), ${position.name || '未命名位置'}`); await genshin.moveMapTo(position.x, position.y, position.country); log.info(`缩放等级为${(position.zoom && typeof position.zoom === "number") ? position.zoom : 6.0}`); await genshin.setBigMapZoomLevel((position.zoom && typeof position.zoom === "number") ? position.zoom : 6.0); await sleep(1000); // 确保画面稳定 try { const detectedNames = await locate(); if (detectedNames && detectedNames.length > 0) { return detectedNames; // 保持兼容性,设置全局变量 } } catch (error) { await genshin.setBigMapZoomLevel(3.0); continue; } } log.error("寻找所有角色可能存在的位置都没有找到角色"); throw new Error("在所有可能的位置都没有找到角色,结束任务,请手动清理出现在地图上的旅闻后重新启动js"); } /** * 定位角色并获取坐标 * @async * @returns {Promise} 检测到的角色名字数组 */ async function locate() { let character = await captureGameRegion().findMulti(RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/三个点.png"))); await sleep(500); if (character && character.count > 0) { avatar = character[0]; const center = genshin.getPositionFromBigMap(); const mapZoomLevel = genshin.getBigMapZoomLevel(); const mapScaleFactor = 2.361; characterX = (960 - avatar.x - 13) * mapZoomLevel / mapScaleFactor + center.x + 20; characterY = (540 - avatar.y - 13) * mapZoomLevel / mapScaleFactor + center.y + 20; log.info(`找到角色的大致坐标:(${characterX}, ${characterY})`); await sleep(200); click(avatar.x + 20, avatar.y + 20); await sleep(2000); let resList = captureGameRegion().findMulti(RecognitionObject.ocrThis); // 识别text中的角色名字 const characterNames = [ "柯莱", "迪希雅", "赛诺", "林尼", "夏沃蕾", "菲米尼", "夏洛蒂", "绮良良", "鹿野院平藏", "托马", "梦见月瑞希", "八重神子", "那维莱特", "神里绫人", "早柚" ]; let foundNames = []; for (let i = 0; i < resList.count; i++) { let res = resList[i]; for (let j = 0; j < characterNames.length; j++) { if (res.text.includes(characterNames[j])) { log.info("识别到角色:{} ({x},{y})", characterNames[j]); foundNames.push(characterNames[j]); } } } log.info(`识别到的角色名字:${foundNames.length > 0 ? foundNames.join(", ") : "未知"}`); await sleep(200); keyPress("VK_ESCAPE"); // 关闭菜单 await sleep(1000); // 等待菜单关闭 return foundNames; } log.warn("未找到角色"); throw new Error("未找到角色,当前位置没有角色"); } /** * 检查是否存在派蒙菜单图标,等待游戏返回主菜单 * * @param {boolean} hasKeyMouse - 是否需要执行键鼠操作 * @returns {Promise} - 如果检测到主菜单,则返回 true,否则在超时时返回 false。 */ async function waitToMain(pathingName, hasKeyMouse = false) { log.info("等待返回主界面..."); const paimonMenuRo = RecognitionObject.TemplateMatch(file.ReadImageMatSync("assets/icon/paimon_menu.png"), 0, 0, genshin.width / 3.0, genshin.width / 5.0); const maxRetries = 60; // 设置最大重试次数以防止无限循环 let retries = 0; let enteredLoop = false; while (captureGameRegion().Find(paimonMenuRo).isEmpty()) { enteredLoop = true; if (retries >= maxRetries) { log.error("返回主界面超时"); return false; } if (pathingName === "八重神子-1") { await click(960, 540); // 点击解签 } await sleep(3000); retries++; } if (!enteredLoop && !hasKeyMouse) { log.error("进入对话失败"); failed = true; return false; } failed = false; return true; } /** * 判断坐标是否在指定位置附近(误差范围内) * @param {number} x - 当前X坐标 * @param {number} y - 当前Y坐标 * @param {number} targetX - 目标X坐标 * @param {number} targetY - 目标Y坐标 * @returns {boolean} 是否在指定范围内 */ function isNearPosition(x, y, targetX, targetY) { // 使用配置中的阈值或默认值100 const errorThreshold = 150; return Math.abs(x - targetX) <= errorThreshold && Math.abs(y - targetY) <= errorThreshold; } /** * 检查对话进度并返回统计信息 * @param {Object[]} characterPositions - 角色点位信息数组 * @returns {Object} 包含进度统计的对象 */ function checkProgress(characterPositions) { const total = characterPositions.length; let completed = 0; const remainingCharacters = []; const completedCharacters = []; for (const pos of characterPositions) { if (config[pos.name]) { completed++; completedCharacters.push(pos.name); } else { remainingCharacters.push(pos.name); } } const remaining = total - completed; const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0; return { total, completed, remaining, completionRate, remainingCharacters, completedCharacters }; } /** * 重置所有对话配置 * @returns {Promise} */ async function resetAllProgress() { log.info("重置所有对话进度..."); config = {}; await file.writeText("config.json", JSON.stringify(config, null, 4)); log.info("✅ 所有对话进度已重置,将重新开始所有角色的对话"); } /** * 检查是否有未完成的角色,如果没有则提前结束 * @param {Object[]} characterPositions - 角色点位信息数组 * @returns {boolean} 是否还有未完成的角色 */ function hasRemainingCharacters(characterPositions) { for (const pos of characterPositions) { if (!config[pos.name]) { return true; } } return false; } // === 设置管理相关函数 (BetterGI 自动处理设置) === /** * 显示当前设置 */ function showCurrentSettings() { log.info("=== 当前设置 ==="); log.info(`启动时重置进度: ${settings.resetOnStart ? "✅ 启用" : "❌ 禁用"}`); log.info(`进度更新间隔: ${settings.progressUpdateInterval} 次`); log.info(`最大运行次数: ${settings.maxRuns}`); log.info(`跳过已完成角色: ${settings.skipCompletedCharacters ? "✅ 启用" : "❌ 禁用"}`); log.info("==============="); } /** * 处理重置相关设置 * @param {Object[]} characterPositions - 角色点位信息数组 * @returns {Promise} 是否继续执行程序 */ async function handleResetSettings() { // 启动时重置 if (settings.resetOnStart) { log.info("⚠️ 检测到启动时重置设置已启用"); log.info("将在 5 秒后重置所有进度,如需取消请立即停止程序"); await sleep(5000); await resetAllProgress(); log.info("🔄 已根据设置重置所有进度"); } }