Files
2025-06-28 16:01:10 +08:00

484 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 全局变量声明
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<void>}
*/
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<string[]>} 找到的角色名字数组,如果没有找到则返回空数组
*/
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<string[]>} 检测到的角色名字数组
*/
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<boolean>} - 如果检测到主菜单,则返回 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<void>}
*/
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<boolean>} 是否继续执行程序
*/
async function handleResetSettings() {
// 启动时重置
if (settings.resetOnStart) {
log.info("⚠️ 检测到启动时重置设置已启用");
log.info("将在 5 秒后重置所有进度,如需取消请立即停止程序");
await sleep(5000);
await resetAllProgress();
log.info("🔄 已根据设置重置所有进度");
}
}