Files
起个名字好难 be4166d2cd rename JS
2025-05-08 01:32:15 +08:00

412 lines
16 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.

(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(); // 返回主界面
}
})();