(async function () { // 初始化游戏窗口大小和返回主界面 setGameMetrics(1920, 1080, 1); await genshin.returnMainUi(); // 获取角色、元素、武器等设置信息 const Character = settings.Character || "纳西妲"; // 默认角色 const Element = settings.Element || "物"; // 默认元素 const Weapon = settings.Weapon || "试作金珀"; // 默认武器 const pageScrollCount = Math.min(99, Math.max(0, Math.floor(Number(settings.pageScrollCount) || 2))); // 页面滚动次数 const ocrRegion = { x: 1463, y: 135, width: 256, height: 32 }; // OCR识别区域 const replacementMap = { "卵": "卯", "姐": "妲", "去": "云", "日": "甘", "螨": "螭", "知": "矢", "钱": "钺", "础": "咄", "厘": "匣", "排": "绯", "朦": "曚", "矿": "斫", "镰": "簾", "廉": "簾", "救": "赦", "塑": "槊", "雍": "薙" }; // OCR替换映射表 const elements = [ "火", "水", "草", "雷", "风", "冰", "岩", "物"]; // 元素列表 const weaponTypeMap = { "1": "单手剑", "11": "双手剑", "12": "弓箭", "10": "法器", "13": "长枪" }; // 武器类型映射表 // 加载角色数据 const filePath = "assets/combat_avatar.json"; const { aliasToNameMap, nameToWeaponMap } = await loadCombatAvatarData(filePath); if (!aliasToNameMap || !nameToWeaponMap) { log.error("无法加载角色数据,OCR 识别无法进行。"); return; } // 加载武器名称数据 const weaponNamesMap = await loadWeaponNames("assets/weaponName.json"); if (!weaponNamesMap) { log.error("无法加载武器名称数据"); return; } // 开始执行角色路径 await CharacterPath(); // 加载角色数据 async function loadCombatAvatarData(filePath) { try { const jsonData = file.readTextSync(filePath); const combatAvatarData = JSON.parse(jsonData); const aliasToNameMap = {}; // 用于存储别名到正式名称的映射 const nameToWeaponMap = {}; // 用于存储正式名称到武器属性的映射 combatAvatarData.forEach(character => { aliasToNameMap[character.name] = character.name; // 存储正式名称 nameToWeaponMap[character.name] = character.weapon; // 存储武器属性 character.alias.forEach(alias => { aliasToNameMap[alias] = character.name; // 存储别名到正式名称的映射 }); }); return { aliasToNameMap, nameToWeaponMap }; } catch (error) { log.error(`加载或解析 JSON 文件失败: ${error}`); return null; } } // 加载武器名称数据 async function loadWeaponNames(filePath) { try { const jsonData = file.readTextSync(filePath); const weaponNamesData = JSON.parse(jsonData); const weaponNamesMap = {}; // 将武器名称数据存储到一个对象中,键为武器类型,值为武器名称数组 weaponNamesData.forEach(item => { for (const [weaponType, weaponNames] of Object.entries(item)) { weaponNamesMap[weaponType] = weaponNames; } }); // log.info(`角色别名映射表: ${JSON.stringify(aliasToNameMap)}`); // log.info(`角色武器映射表: ${JSON.stringify(nameToWeaponMap)}`); return weaponNamesMap; } catch (error) { log.error(`加载武器名称文件失败: ${error}`); return null; } } // OCR识别文本 async function recognizeText(targetText, ocrRegion, aliasToNameMap, timeout = 10000, retryInterval = 20, maxAttempts = 5) { let startTime = Date.now(); let retryCount = 0; const targetFormalName = aliasToNameMap ? aliasToNameMap[targetText] || targetText : targetText; while (Date.now() - startTime < timeout && retryCount < maxAttempts) { try { let captureRegion = captureGameRegion(); let ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); ocrObject.threshold = 0.8; let resList = captureRegion.findMulti(ocrObject); for (let res of resList) { let correctedText = res.text; for (let [wrongChar, correctChar] of Object.entries(replacementMap)) { correctedText = correctedText.replace(new RegExp(wrongChar, 'g'), correctChar); } // log.info(`识别结果: ${correctedText}, 原始坐标: x=${res.x}, y=${res.y}`); let recognizedFormalName = aliasToNameMap ? aliasToNameMap[correctedText] || correctedText : correctedText; recognizedFormalName = fuzzyMatch(correctedText, Object.values(aliasToNameMap)) || recognizedFormalName; if (recognizedFormalName === targetFormalName) { return { success: true, text: recognizedFormalName, x: res.x, y: res.y }; } } } catch (error) { retryCount++; log.warn(`OCR 识别失败,正在进行第 ${retryCount} 次重试...`); } await sleep(retryInterval); } return { success: false }; } // 模糊匹配文本 function fuzzyMatch(target, candidates, weightThreshold = 0.6) { function levenshteinDistance(a, b) { const m = a.length + 1; const n = b.length + 1; const d = Array(m).fill(null).map(() => Array(n).fill(0)); for (let i = 0; i < m; i++) d[i][0] = i; for (let j = 0; j < n; j++) d[0][j] = j; for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { const cost = a[i - 1] === b[j - 1] ? 0 : 1; d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost); } } return d[m - 1][n - 1]; } let bestMatch = null; let bestWeight = 0; for (const candidate of candidates) { const distance = levenshteinDistance(target, candidate); const keywordWeight = 0.8; const lengthWeight = 0.2; const keywordMatch = candidate.includes(target); const weight = (keywordMatch ? keywordWeight : 0) + (1 - distance / Math.max(target.length, candidate.length)) * lengthWeight; if (weight >= weightThreshold) { return candidate; } if (weight > bestWeight) { bestWeight = weight; bestMatch = candidate; } } return bestMatch; } // 合并OCR识别结果 function combineResults(results) { const frequencyMap = {}; results.forEach(result => { if (!frequencyMap[result]) { frequencyMap[result] = 0; } frequencyMap[result]++; }); const sortedResults = Object.keys(frequencyMap).sort((a, b) => frequencyMap[b] - frequencyMap[a]); for (let result of sortedResults) { if (result.length === 2) { return result; } } if (sortedResults.length >= 2) { return sortedResults[0] + sortedResults[1]; } return sortedResults[0] || ""; } // 滚动页面 async function scrollPage(totalDistance, stepDistance = 10, delayMs = 10) { moveMouseTo(525, 920); await sleep(500); leftButtonDown(); const steps = Math.ceil(totalDistance / stepDistance); for (let j = 0; j < steps; j++) { const remainingDistance = totalDistance - j * stepDistance; const moveDistance = remainingDistance < stepDistance ? remainingDistance : stepDistance; moveMouseBy(0, -moveDistance); await sleep(delayMs); } await sleep(700); leftButtonUp(); await sleep(100); } // 选择角色 async function selectCharacter(characterName) { const SwitchingSteps = 99; // 最大切换次数 for (let i = 0; i < SwitchingSteps; i++) { let result = await recognizeText(characterName, ocrRegion, aliasToNameMap, 200); if (result.success) { // log.info(`找到 ${characterName},识别结果: ${result.text},坐标: x=${result.x}, y=${result.y}`); return true; } await click(1840, 540); // 点击切换角色 await sleep(200); } log.warn(`扫描完成,未找到 ${characterName}`); return false; } // 选择元素 async function selectElement(element) { if (element === "物") return; // 如果是物理属性,无需切换 const ElementClickX = Math.round(787 + elements.indexOf(element) * 57.5); // 计算点击位置 await click(960, 45); // 点击元素切换按钮 await sleep(100); leftButtonDown(); const steps = 10; const stepDistance = 15; for (let j = 0; j < steps; j++) { moveMouseBy(stepDistance, 0); // 拖动鼠标选择元素 await sleep(10); } await sleep(500); leftButtonUp(); await sleep(500); await click(ElementClickX, 130); // 点击选择元素 await sleep(500); await click(540, 45); // 点击确认 await sleep(200); } // 识别并组合武器名称 async function recognizeAndCombineWeaponName(ocrRegion, maxAttempts = 5) { const allResults = []; for (let i = 0; i < maxAttempts; i++) { try { let captureRegion = captureGameRegion(); let ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); ocrObject.threshold = 0.8; let resList = captureRegion.findMulti(ocrObject); for (let res of resList) { let correctedText = res.text; for (let [wrongChar, correctChar] of Object.entries(replacementMap)) { correctedText = correctedText.replace(new RegExp(wrongChar, 'g'), correctChar); } // log.info(`OCR 识别结果: ${correctedText}, 原始坐标: x=${res.x}, y=${res.y}`); allResults.push(correctedText); } } catch (error) { log.warn(`OCR 识别失败,正在进行第 ${i + 1} 次重试...`); } await sleep(20); } const combinedResult = combineResults(allResults); // log.info(`组合后的识别结果: ${combinedResult}`); return combinedResult; } // 扫描武器 async function scanWeapons(settingsWeapon) { // 获取角色的正式名称 const characterName = aliasToNameMap[Character] || Character; log.info(`寻找到角色 ${Character} 正式名称 ${characterName}`); if (!characterName) { log.error(`未找到角色 ${Character} 的正式名称`); return false; } // 获取角色的武器类型 const characterWeaponType = nameToWeaponMap[characterName]; log.info(`寻找到角色 ${Character} 的武器类型为 ${characterWeaponType}`); if (!characterWeaponType) { log.warn(`未找到角色 ${characterName} 的武器类型,将直接匹配 目标武器名 和 当前武器名 `); } // 获取对应的武器名称列表 const weaponType = weaponTypeMap[characterWeaponType]; const weaponNames = weaponNamesMap[weaponType] || []; // 如果武器名称列表为空,则将 settingsWeapon 添加到列表中,以便后续匹配 if (!weaponNames.length) { log.warn(`未找到武器类型 ${weaponType} 的武器名称列表,将使用原始武器名 ${settingsWeapon}`); weaponNames.push(settingsWeapon); } let weaponName1 = fuzzyMatch(settingsWeapon, weaponNames, 0.9); log.info(`寻找到 目标武器 正式名称 ${weaponName1}`); if (!weaponName1) { log.warn(`未找到与 ${settingsWeapon} 匹配的武器名,使用原始名称作为 目标武器名`); weaponName1 = settingsWeapon; } const startX = 99.5; const startY = 213.5; const rowHeight = 167; const columnWidth = 141; const maxRows = 4; const maxColumns = 4; for (let scroll = 0; scroll <= pageScrollCount; scroll++) { for (let row = 0; row < maxRows; row++) { for (let column = 0; column < maxColumns; column++) { const clickX = Math.round(startX + column * columnWidth); const clickY = Math.round(startY + row * rowHeight); await click(clickX, clickY); // 点击武器 await sleep(50); const combinedWeaponName2 = await recognizeAndCombineWeaponName(ocrRegion); if (!combinedWeaponName2) { log.warn("OCR 识别失败,未找到任何武器名"); continue; } // 尝试模糊匹配武器名称 let weaponName2 = fuzzyMatch(combinedWeaponName2, weaponNames, 1); // 如果未匹配到已知武器名称,则将 OCR 识别结果直接作为武器名称使用 if (!weaponName2) { log.warn(`未找到与 ${combinedWeaponName2} 匹配的已知武器名,将使用 OCR 识别结果作为 当前武器名`); weaponName2 = combinedWeaponName2; } // 计算匹配占比,排除干扰词 const matchRatio = calculateMatchRatio(weaponName1, weaponName2); if (matchRatio >= 0.8) { // 如果匹配占比大于等于 80%,则认为匹配成功 log.info(`成功匹配武器:${weaponName1},匹配占比 ${matchRatio.toFixed(2)}`); await click(1600, 1005); // 点击确认 await sleep(1000); await click(1320, 755); // 点击确认 await sleep(1000); return true; } else { log.warn(` 目标武器名 (${weaponName1}) 和 当前武器名 (${weaponName2}) 不匹配,匹配占比 ${matchRatio.toFixed(2)}`); } } } if (scroll < pageScrollCount) { await scrollPage(673, 10, 10); // 滚动页面 } } log.warn(`扫描完成,未找到 ${settingsWeapon}`); return false; } // 计算匹配占比 function calculateMatchRatio(target, candidate) { const ignoreWords = ["剑", "之", "弓", "枪", "长", "大", "典", "章"]; // 需要排除的干扰词 const targetClean = target.split('').filter(char => !ignoreWords.includes(char)).join(''); const candidateClean = candidate.split('').filter(char => !ignoreWords.includes(char)).join(''); const commonChars = targetClean.split('').filter(char => candidateClean.includes(char)).length; const totalChars = targetClean.length; return commonChars / totalChars; } // 执行角色路径 async function CharacterPath() { log.info("开始寻找"); await genshin.returnMainUi(); // 返回主界面 keyPress("1"); // 按键操作 await sleep(500); keyPress("C"); // 打开角色界面 await sleep(1000); await selectElement(Element); // 选择元素属性 if (!await selectCharacter(Character)) { // 选择角色 log.error("角色筛选失败,退出脚本"); return; } await click(125, 225); // 点击武器详情 await sleep(1000); await click(1600, 1005); // 点击替换武器 await sleep(1000); await click(500, 1005); // 点击武器排序 await sleep(200); await click(500, 905); // 点击排序类型 await sleep(200); await click(605, 137); // 点击武器排序 await moveMouseTo(605, 140); // 移动鼠标至滑条顶端 await sleep(200); leftButtonDown(); // 长按左键重置武器滑条 await sleep(300); leftButtonUp(); await sleep(200); if (!await scanWeapons(Weapon)) { // 未找到指定武器 log.warn(` ${pageScrollCount+1} 页扫描完,未找到 ${Weapon}`); } await genshin.returnMainUi(); // 返回主界面 } })();