Files
bettergi-scripts-list/repo/js/AutoCommission/lib/commission-recognition.js
DarkFlameMaster 10d06c3487 同步AutoCommission更新 (#2746)
Co-authored-by: DarkFlameMaster <actions@github.com>
2026-01-20 14:21:48 +08:00

717 lines
24 KiB
JavaScript
Raw 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.
// 原神每日委托自动执行脚本 - 委托识别模块
// 编辑距离算法,用于计算字符串相似度
function levenshteinDistance(str1, str2) {
const m = str1.length;
const n = str2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
dp[i][j] = Math.min(
dp[i - 1][j] + 1,
dp[i][j - 1] + 1,
dp[i - 1][j - 1] + cost
);
}
}
return dp[m][n];
}
// 获取最接近的匹配项(带阈值)
function getClosestMatch(target, candidates, threshold = Constants.MATCH_THRESHOLD.DEFAULT) {
if (!candidates || candidates.length === 0) return null;
let closest = candidates[0];
let minDistance = levenshteinDistance(target, closest);
let maxSimilarity = calculateSimilarity(target, closest);
for (let i = 1; i < candidates.length; i++) {
const distance = levenshteinDistance(target, candidates[i]);
const similarity = calculateSimilarity(target, candidates[i]);
if (similarity > maxSimilarity) {
maxSimilarity = similarity;
minDistance = distance;
closest = candidates[i];
}
}
// 检查相似度是否达到阈值
if (maxSimilarity < threshold) {
return null;
}
return closest;
}
// 计算字符串相似度
function calculateSimilarity(str1, str2) {
const distance = levenshteinDistance(str1, str2);
const maxLength = Math.max(str1.length, str2.length);
return maxLength === 0 ? 1 : 1 - (distance / maxLength);
}
var CommissionStandardizer = {
// 标准化列表
standardizationLists: {
fight: {},
talk: {}
},
// 初始化标准化列表
initialize: function() {
log.info("初始化委托标准化列表...");
try {
// 初始化战斗委托标准化列表
this.standardizationLists.fight = this.buildFightStandardizationList();
// 初始化对话委托标准化列表
this.standardizationLists.talk = this.buildTalkStandardizationList();
log.info("委托标准化列表初始化完成");
log.debug("战斗委托标准化列表: {count} 个委托", Object.keys(this.standardizationLists.fight).length);
log.debug("对话委托标准化列表: {count} 个委托", Object.keys(this.standardizationLists.talk).length);
log.debug("standardizationLists的值:{x}",JSON.stringify(this.standardizationLists))
} catch (error) {
log.error("初始化标准化列表时出错: {error}", error.message);
}
},
// 构建战斗委托标准化列表
buildFightStandardizationList: function() {
const fightList = {};
try {
// 读取assets目录下除process外的所有文件夹
const assetsPath = "assets";
const items = Array.from(file.readPathSync(assetsPath));
// 过滤出文件夹且不是process
const folders = items.filter(item => {
return file.isFolder(item) && !item.includes("process");
});
// 遍历每个战斗委托文件夹
for (const folderPath of folders) {
// 从完整路径中提取文件夹名称
const folderName = folderPath.replace(assetsPath + "/", "").replace(assetsPath + "\\", "");
const files = Array.from(file.readPathSync(folderPath));
// 过滤出json文件
const jsonFiles = files.filter(file => file.endsWith(".json"));
// 提取文件名(不包含路径和.json后缀并去除-1 -2等数字后缀
const cleanFileNames = jsonFiles.map(filePath => {
// 从完整路径中提取文件名
const fileName = filePath.split("/").pop().split("\\").pop();
// 去除-1 -2等数字后缀和.json后缀
return fileName.replace(/-(\d+)?\.json$/, "");
});
fightList[folderName] = cleanFileNames;
}
} catch (error) {
log.error("构建战斗委托标准化列表时出错: {error}", error.message);
}
return fightList;
},
// 构建对话委托标准化列表
buildTalkStandardizationList: function() {
const talkList = {};
try {
// 读取assets/process目录下的所有文件夹
const processPath = "assets/process";
const items = Array.from(file.readPathSync(processPath));
// 过滤出文件夹
const folders = items.filter(item => {
return file.isFolder(item);
});
// 遍历每个对话委托文件夹
for (const folderPath of folders) {
// 从完整路径中提取文件夹名称
const folderName = folderPath.split("/").pop().split("\\").pop();
const subItems = Array.from(file.readPathSync(folderPath));
// 过滤出子文件夹
const subFolders = subItems.filter(subItem => {
return file.isFolder(subItem);
});
// 从完整路径中提取子文件夹名称
const cleanSubFolders = subFolders.map(subFolderPath => {
// 从完整路径中提取最后一级文件夹名称
return subFolderPath.split("/").pop().split("\\").pop();
});
talkList[folderName] = cleanSubFolders;
}
} catch (error) {
log.error("构建对话委托标准化列表时出错: {error}", error.message);
}
return talkList;
},
// 标准化委托名称
standardizeCommissionName: function(rawName) {
// 从所有标准化列表中查找最接近的名称
const allNames = [
...Object.keys(this.standardizationLists.fight),
...Object.keys(this.standardizationLists.talk)
];
return getClosestMatch(rawName, allNames, Constants.MATCH_THRESHOLD.COMISSIONS_NAME);
},
// 标准化委托地点
standardizeCommissionLocation: function(commissionName, rawLocation) {
// 根据委托类型选择对应的标准化列表
let candidates = [];
if (this.standardizationLists.fight[commissionName]) {
// 战斗委托
candidates = this.standardizationLists.fight[commissionName];
} else if (this.standardizationLists.talk[commissionName]) {
// 对话委托
candidates = this.standardizationLists.talk[commissionName];
}
if (candidates.length === 0) {
log.warn("没有找到委托 {name} 的标准化地点列表", commissionName);
return rawLocation;
}
// 获取最接近的地点
const closestLocation = getClosestMatch(rawLocation, candidates, Constants.MATCH_THRESHOLD.LOCATION);
if (closestLocation) {
log.info("标准化地点: {raw} -> {standard}", rawLocation, closestLocation);
return closestLocation;
} else {
log.info("地点相似度未达阈值,保持原地点: {raw}", rawLocation);
return rawLocation;
}
}
};
var CommissionRecognition = {
// 识别委托地点
recognizeCommissionLocation: async function() {
try {
log.info(
"识别委托地点 ({x}, {y}) ({width}, {height})...",
Constants.OCR_REGIONS.LOCATION.X,
Constants.OCR_REGIONS.LOCATION.Y,
Constants.OCR_REGIONS.LOCATION.X + Constants.OCR_REGIONS.LOCATION.WIDTH,
Constants.OCR_REGIONS.LOCATION.Y + Constants.OCR_REGIONS.LOCATION.HEIGHT
);
// 使用Utils.easyOCROne进行识别
var location = await Utils.easyOCROne(Constants.OCR_REGIONS.LOCATION);
if (location && location.trim()) {
return location.trim();
}
return "未知地点";
} catch (error) {
log.error("识别委托地点时出错: {error}", error.message);
return "识别失败";
}
},
// 检测是否进入委托详情界面
checkDetailPageEntered: async function() {
try {
// 尝试3次OCR识别
for (var i = 0; i < 3; i++) {
// 使用Utils.easyOCR进行识别
var results = await Utils.easyOCR(Constants.OCR_REGIONS.DETAIL_COUNTRY);
if (results.count > 0) {
// 检查OCR结果
for (var j = 0; j < results.count; j++) {
var text = results[j].text.trim();
// 如果有"蒙德",表示进入了详情界面
if (text.includes("蒙德")) {
log.info("检测到蒙德委托,成功进入详情界面");
return "蒙德";
}
// 如果没有文字,可能是已完成委托
else if (text === "") {
log.info("未检测到地区文本,可能是已完成委托");
return "已完成";
}
// 其他地区委托
else if (text.length >= 2) {
log.info("检测到其他地区委托: {text}", text);
return text;
}
}
}
// 如果没有检测到,等待一会再试
await sleep(500);
}
log.info("三次OCR检测后仍未确认委托国家");
return "未知";
} catch (error) {
log.error("检测委托详情界面时出错: {error}", error.message);
return "错误";
}
},
// 识别委托列表 - 进行4个单独识别
recognizeCommissions: async function(supportedCommissions) {
try {
log.info("开始执行委托识别");
// 初始化当前委托位置变量
var currentCommissionPosition = null;
// 步骤1: 识别前3个委托索引0-2
log.info("步骤1: 使用识别前3个委托");
var allCommissions = [];
// 先识别前3个区域索引0-2
for (var regionIndex = 0; regionIndex < 3; regionIndex++) {
var region = Constants.OCR_REGIONS.Main_Dev[regionIndex];
log.info(
"识别第{index}个委托区域 ({x}, {y}) ({width}, {height})",
regionIndex + 1,
region.X,
region.Y,
region.X + region.WIDTH,
region.Y + region.HEIGHT
);
try {
var results = await Utils.easyOCR(region);
log.info(
"第{index}个区域OCR识别结果数量: {count}",
regionIndex + 1,
results.count
);
// 处理识别结果,取第一个有效结果
for (var i = 0; i < results.count; i++) {
try {
var result = results[i];
var text = Utils.cleanText(result.text);
if (text && text.length >= Constants.MIN_TEXT_LENGTH) {
log.info('第{index}个委托: "{text}"', regionIndex + 1, text);
// 标准化委托名称
var standardizedName = CommissionStandardizer.standardizeCommissionName(text);
if (standardizedName && standardizedName !== text) {
log.info('委托名称标准化: "{raw}" -> "{standard}"', text, standardizedName);
text = standardizedName;
} else {
log.debug('委托名称相似度未达阈值,保持原名称: "{raw}"', text);
}
// 检查委托类型
var isFightCommission = supportedCommissions.fight.includes(text);
var isTalkCommission = supportedCommissions.talk.includes(text);
var isSupported = isFightCommission || isTalkCommission;
var commissionType = isFightCommission
? Constants.COMMISSION_TYPE.FIGHT
: isTalkCommission
? Constants.COMMISSION_TYPE.TALK
: "";
allCommissions.push({
id: regionIndex + 1,
name: text,
supported: isSupported,
type: commissionType,
location: "",
});
// 找到有效结果后跳出循环
break;
}
} catch (ocrError) {
log.error(
"处理第{regionIndex}个区域第{resultIndex}个OCR识别结果时出错: {error},跳过该结果",
regionIndex + 1,
i + 1,
ocrError
);
// 跳过该结果,继续处理下一个
continue;
}
}
} catch (regionError) {
log.error(
"识别第{index}个委托区域时出错: {error},跳过该区域",
regionIndex + 1,
regionError
);
continue;
}
}
// 步骤2: 使用图像识别检测所有委托的完成状态
log.info("步骤2: 检测所有委托的完成状态");
// 处理前3个委托
for (var i = 0; i < Math.min(3, allCommissions.length); i++) {
var commission = allCommissions[i];
try {
// 使用图像识别检测完成状态
var status = await CommissionBasic.detectCommissionStatusByImage(i);
if (status === "completed") {
log.info(
"委托{id} {name} 已完成,跳过详情查看",
commission.id,
commission.name
);
commission.location = "已完成";
continue;
} else if (status === "uncompleted") {
log.info(
"委托{id} {name} 未完成,查看详情",
commission.id,
commission.name
);
} else {
log.warn(
"委托{id} {name} 状态未知,尝试查看详情",
commission.id,
commission.name
);
}
// 只有未完成或状态未知的委托才点击查看详情
log.info(
"查看第{id}个委托详情: {name}",
commission.id,
commission.name
);
// 点击详情按钮
var detailButton = Constants.COMMISSION_DETAIL_BUTTONS[commission.id - 1];
log.info(
"点击委托详情按钮 ({x}, {y})",
detailButton.x,
detailButton.y
);
click(detailButton.x, detailButton.y);
await sleep(700);
// 检测委托国家
var detailStatus = await CommissionRecognition.checkDetailPageEntered();
log.info("委托国家: {status}", detailStatus);
commission.country = detailStatus;
var location = await CommissionRecognition.recognizeCommissionLocation();
// 标准化委托地点
var standardizedLocation = CommissionStandardizer.standardizeCommissionLocation(commission.name, location);
if (standardizedLocation && standardizedLocation !== location) {
log.info('委托地点标准化: "{raw}" -> "{standard}"', location, standardizedLocation);
location = standardizedLocation;
}
commission.location = location;
log.info(
"委托 {name} 的地点: {location}",
commission.name,
location
);
// 退出详情页面并获取地图坐标
if (commission.location !== "已完成") {
log.info("退出详情页面 - 按ESC");
keyDown("VK_ESCAPE");
await sleep(300);
keyUp("VK_ESCAPE");
await sleep(1200);
let scale = 2.0
let bigMapPosition
while (scale <= 5.0) {
try {
await genshin.setBigMapZoomLevel(scale);
bigMapPosition = genshin.getPositionFromBigMap();
break;
} catch {
scale += 0.1;
}
await sleep(100);
}
if (bigMapPosition) {
commission.CommissionPosition = bigMapPosition;
log.info(
"当前委托位置: ({x}, {y})",
bigMapPosition.x,
bigMapPosition.y
);
}
keyDown("VK_ESCAPE");
await sleep(300);
keyUp("VK_ESCAPE");
await sleep(1200);
}
} catch (commissionError) {
log.error(
"处理委托{id} {name} 时出错: {error},跳过该委托",
commission.id,
commission.name,
commissionError
);
// 设置错误状态,但不中断整个流程
commission.location = "处理失败";
commission.country = "未知";
// 尝试退出可能打开的详情页面
try {
keyDown("VK_ESCAPE");
await sleep(300);
keyUp("VK_ESCAPE");
await sleep(1200);
} catch (escapeError) {
log.warn("尝试退出详情页面时出错: {error}", escapeError);
}
}
}
// 步骤3: 翻页后识别第4个委托
log.info("步骤3: 翻页后识别第4个委托");
await UI.pageScroll(1);
// 识别第4个区域索引3
var region = Constants.OCR_REGIONS.Main_Dev[3];
var fourthCommission = null;
log.info(
"识别第4个委托区域 ({x}, {y}) ({width}, {height})",
region.X,
region.Y,
region.X + region.WIDTH,
region.Y + region.HEIGHT
);
try {
var results = await Utils.easyOCR(region);
log.info("第4个区域OCR识别结果数量: {count}", results.count);
// 处理识别结果,取第一个有效结果
for (var i = 0; i < results.count; i++) {
try {
var result = results[i];
var text = Utils.cleanText(result.text);
if (text && text.length >= Constants.MIN_TEXT_LENGTH) {
log.info('第4个委托: "{text}"', text);
// 标准化委托名称
var standardizedName = CommissionStandardizer.standardizeCommissionName(text);
if (standardizedName && standardizedName !== text) {
log.info('委托名称标准化: "{raw}" -> "{standard}"', text, standardizedName);
text = standardizedName;
} else {
log.info('委托名称相似度未达阈值,保持原名称: "{raw}"', text);
}
// 检查委托类型
var isFightCommission = supportedCommissions.fight.includes(text);
var isTalkCommission = supportedCommissions.talk.includes(text);
var isSupported = isFightCommission || isTalkCommission;
var commissionType = isFightCommission
? Constants.COMMISSION_TYPE.FIGHT
: isTalkCommission
? Constants.COMMISSION_TYPE.TALK
: "";
fourthCommission = {
id: 4,
name: text,
supported: isSupported,
type: commissionType,
location: "",
};
// 找到有效结果后跳出循环
break;
}
} catch (ocrError) {
log.error(
"处理第4个区域第{resultIndex}个OCR识别结果时出错: {error},跳过该结果",
i + 1,
ocrError
);
// 跳过该结果,继续处理下一个
continue;
}
}
} catch (regionError) {
log.error("识别第4个委托区域时出错: {error}", regionError);
}
// 如果识别到第4个委托添加到列表中
if (fourthCommission) {
allCommissions.push(fourthCommission);
}
// 步骤4: 处理第4个委托的完成状态
if (fourthCommission) {
try {
// 使用图像识别检测第4个委托的完成状态
var status = await CommissionBasic.detectCommissionStatusByImage(3); // 第4个委托索引为3
if (status === "completed") {
log.info(
"委托{id} {name} 已完成,跳过详情查看",
fourthCommission.id,
fourthCommission.name
);
fourthCommission.location = "已完成";
} else if (status === "uncompleted") {
log.info(
"委托{id} {name} 未完成,查看详情",
fourthCommission.id,
fourthCommission.name
);
} else {
log.warn(
"委托{id} {name} 状态未知,尝试查看详情",
fourthCommission.id,
fourthCommission.name
);
}
// 只有未完成或状态未知的委托才点击查看详情
if (status !== "completed") {
log.info("查看第4个委托详情: {name}", fourthCommission.name);
// 点击详情按钮
var detailButton = Constants.COMMISSION_DETAIL_BUTTONS[3]; // 第4个按钮索引为3
log.info(
"点击委托详情按钮 ({x}, {y})",
detailButton.x,
detailButton.y
);
click(detailButton.x, detailButton.y);
await sleep(700);
// 检测是否成功进入详情界面并获取委托国家
var detailStatus = await CommissionRecognition.checkDetailPageEntered();
log.info("委托国家: {status}", detailStatus);
fourthCommission.country = detailStatus;
// 识别委托地点
var location = await CommissionRecognition.recognizeCommissionLocation();
// 标准化委托地点
var standardizedLocation = CommissionStandardizer.standardizeCommissionLocation(fourthCommission.name, location);
if (standardizedLocation && standardizedLocation !== location) {
log.info('委托地点标准化: "{raw}" -> "{standard}"', location, standardizedLocation);
location = standardizedLocation;
}
fourthCommission.location = location;
log.info(
"委托 {name} 的地点: {location}",
fourthCommission.name,
location
);
// 退出详情页面并获取地图坐标
if (fourthCommission.location !== "已完成") {
log.info("退出详情页面 - 按ESC");
keyDown("VK_ESCAPE");
await sleep(300);
keyUp("VK_ESCAPE");
await sleep(1200);
let scale = 2.0
let bigMapPosition
while (scale <= 5.0) {
try {
await genshin.setBigMapZoomLevel(scale);
bigMapPosition = genshin.getPositionFromBigMap();
break;
} catch {
scale += 0.1;
}
await sleep(100);
}
if (bigMapPosition) {
fourthCommission.CommissionPosition = bigMapPosition;
log.info(
"当前委托位置: ({x}, {y})",
bigMapPosition.x,
bigMapPosition.y
);
}
keyDown("VK_ESCAPE");
await sleep(300);
keyUp("VK_ESCAPE");
await sleep(1200);
}
}
} catch (fourthCommissionError) {
log.error(
"处理第4个委托{name} 时出错: {error},跳过该委托",
fourthCommission.name,
fourthCommissionError
);
// 设置错误状态,但不中断整个流程
fourthCommission.location = "处理失败";
fourthCommission.country = "未知";
// 尝试退出可能打开的详情页面
try {
keyDown("VK_ESCAPE");
await sleep(300);
keyUp("VK_ESCAPE");
await sleep(1200);
} catch (escapeError) {
log.warn("尝试退出详情页面时出错: {error}", escapeError);
}
}
}
// 输出完整委托列表
log.info("完整委托列表:");
for (var i = 0; i < allCommissions.length; i++) {
var commission = allCommissions[i];
var supportStatus = commission.supported ? "✅ 支持" : "❌ 不支持";
var typeInfo = commission.type ? "[" + commission.type + "]" : "";
var locationInfo = commission.location ? "(" + commission.location + ")" : "";
var countryInfo = commission.country ? "{" + commission.country + "}" : "";
log.info(
"{id}. {name} {location} {country} {type} - {status}",
commission.id,
commission.name,
locationInfo,
countryInfo,
typeInfo,
supportStatus
);
}
return allCommissions;
} catch (error) {
log.error("识别委托时出错: {error}", error.message);
return [];
}
},
};