3067 lines
101 KiB
JavaScript
3067 lines
101 KiB
JavaScript
(async function () {
|
||
// 版本和编译信息
|
||
const VERSION = "0.98.3";
|
||
const BUILD_TIME = "2025.07.22";
|
||
|
||
async function errorlog() {
|
||
// 输出版本和编译时间信息
|
||
log.info("=".repeat(20));
|
||
log.info("版本: {version}", VERSION);
|
||
log.info("编译时间: {buildTime}", BUILD_TIME);
|
||
log.info("=".repeat(20));
|
||
}
|
||
|
||
// 统一常量定义
|
||
const Datas = {
|
||
// 文件路径常量
|
||
SUPPORT_LIST_PATH: "name.json",
|
||
OUTPUT_DIR: "Data",
|
||
TALK_PROCESS_BASE_PATH: "assets/process",
|
||
|
||
// 图像识别相关常量
|
||
COMPLETED_IMAGE_PATH: "Data/RecognitionObject/Completed.png",
|
||
UNCOMPLETED_IMAGE_PATH: "Data/RecognitionObject/UnCompleted.png",
|
||
|
||
// 基础配置常量
|
||
MIN_TEXT_LENGTH: 4, // 最小文本长度
|
||
MAX_COMMISSION_RETRY_COUNT: 1, // 默认重试机制,超过则跳过该委托
|
||
|
||
// 委托类型常量
|
||
COMMISSION_TYPE: {
|
||
FIGHT: "fight",
|
||
TALK: "talk",
|
||
},
|
||
|
||
// OCR识别区域常量
|
||
OCR_REGIONS: {
|
||
Main_Dev: [
|
||
// 第1个委托名字
|
||
{
|
||
X: 796,
|
||
Y: 293,
|
||
WIDTH: 440,
|
||
HEIGHT: 40,
|
||
},
|
||
// 第2个委托名字
|
||
{
|
||
X: 796,
|
||
Y: 401,
|
||
WIDTH: 440,
|
||
HEIGHT: 40,
|
||
},
|
||
// 第3个委托名字
|
||
{
|
||
X: 796,
|
||
Y: 509,
|
||
WIDTH: 440,
|
||
HEIGHT: 40,
|
||
},
|
||
// 第4个委托名字(滑动后)
|
||
{
|
||
X: 796,
|
||
Y: 544,
|
||
WIDTH: 440,
|
||
HEIGHT: 40,
|
||
},
|
||
],
|
||
|
||
// 主要委托识别区域
|
||
MAIN: {
|
||
X: 750,
|
||
Y: 250,
|
||
WIDTH: 450,
|
||
HEIGHT: 400,
|
||
},
|
||
// 委托地点OCR区域
|
||
LOCATION: {
|
||
X: 1530,
|
||
Y: 100,
|
||
WIDTH: 250, // 1630 - 1530
|
||
HEIGHT: 30, // 130 - 100
|
||
},
|
||
// 委托详情页面国家检测OCR区域
|
||
DETAIL_COUNTRY: {
|
||
X: 1480,
|
||
Y: 100,
|
||
WIDTH: 55, // 1535 - 1480
|
||
HEIGHT: 30, // 130 - 100
|
||
},
|
||
// 委托触发检测区域
|
||
COMMISSION_TRIGGER: {
|
||
X: 885,
|
||
Y: 200,
|
||
WIDTH: 165, // 1050 - 885
|
||
HEIGHT: 50, // 250 - 200
|
||
},
|
||
// 委托完成检测区域
|
||
COMMISSION_COMPLETE: {
|
||
X: 880,
|
||
Y: 165,
|
||
WIDTH: 170, // 1050 - 880
|
||
HEIGHT: 45, // 210 - 165
|
||
},
|
||
// 委托追踪检测区域
|
||
COMMISSION_TRACKING: {
|
||
X: 1622,
|
||
Y: 987,
|
||
WIDTH: 137,
|
||
HEIGHT: 35,
|
||
},
|
||
// 委托详情检测区域
|
||
COMMISSION_DETAIL: {
|
||
X: 76,
|
||
Y: 239,
|
||
WIDTH: 280, // 358 - 76
|
||
HEIGHT: 43, // 272-239
|
||
},
|
||
},
|
||
|
||
// 委托详情按钮位置常量
|
||
COMMISSION_DETAIL_BUTTONS: [
|
||
{ id: 1, x: 1550, y: 320, checkX: 1450, checkWidth: 150 }, // 第一个委托详情按钮
|
||
{ id: 2, x: 1550, y: 440, checkX: 1450, checkWidth: 150 }, // 第二个委托详情按钮
|
||
{ id: 3, x: 1550, y: 530, checkX: 1500, checkWidth: 100 }, // 第三个委托详情按钮
|
||
{ id: 4, x: 1550, y: 560, checkX: 1450, checkWidth: 150 }, // 第四个委托详情按钮(滑动后)
|
||
],
|
||
};
|
||
|
||
// 存储当前委托位置
|
||
let currentCommissionPosition = null;
|
||
|
||
// 委托地点OCR区域
|
||
const LOCATION_OCR_X = 1530;
|
||
const LOCATION_OCR_Y = 100;
|
||
const LOCATION_OCR_WIDTH = 250; // 1630 - 1530
|
||
const LOCATION_OCR_HEIGHT = 30; // 130 - 100
|
||
|
||
// 委托触发检测区域
|
||
const COMMISSION_TRIGGER_OCR_X = 885;
|
||
const COMMISSION_TRIGGER_OCR_Y = 200;
|
||
const COMMISSION_TRIGGER_OCR_WIDTH = 165; // 1050 - 885
|
||
const COMMISSION_TRIGGER_OCR_HEIGHT = 50; // 250 - 200
|
||
|
||
// 委托完成检测区域
|
||
const COMMISSION_COMPLETE_OCR_X = 880;
|
||
const COMMISSION_COMPLETE_OCR_Y = 165;
|
||
const COMMISSION_COMPLETE_OCR_WIDTH = 170; // 1050 - 880
|
||
const COMMISSION_COMPLETE_OCR_HEIGHT = 45; // 210 - 165
|
||
|
||
// 获取设置
|
||
const getSetting = async () => {
|
||
try {
|
||
const skipRecognition = settings.skipRecognition || false;
|
||
const prepare = settings.prepare || false;
|
||
const team = settings.team || "";
|
||
const skipCommissions = "";
|
||
|
||
const result = {
|
||
skipRecognition,
|
||
prepare,
|
||
team,
|
||
skipCommissions,
|
||
};
|
||
|
||
log.debug("setting:{index}", result);
|
||
|
||
return result;
|
||
} catch {
|
||
log.error("getSetting函数出现错误,将使用默认配置");
|
||
return {
|
||
skipRecognition: false,
|
||
prepare: true,
|
||
team: "",
|
||
skipCommissions: "",
|
||
};
|
||
}
|
||
};
|
||
|
||
const { skipRecognition, prepare, team, skipCommissions } =
|
||
await getSetting();
|
||
|
||
const Utils = {
|
||
iframe: async ({ X, Y, WIDTH, HEIGHT }) => {
|
||
try {
|
||
log.info("i{index}", { X, Y, WIDTH, HEIGHT });
|
||
|
||
// 最简单的方式创建OCR识别对象
|
||
const ro = RecognitionObject.Ocr(X, Y, WIDTH, HEIGHT);
|
||
ro.Name = "debug";
|
||
ro.DrawOnWindow = true;
|
||
|
||
// 捕获并识别
|
||
const region = captureGameRegion();
|
||
region.Find(ro);
|
||
|
||
// 2000毫秒后移除绘制的边框
|
||
setTimeout(() => {
|
||
// 使用相同的名称移除边框
|
||
const drawContent = VisionContext.Instance().DrawContent;
|
||
drawContent.RemoveRect("debug");
|
||
// 或者也可以使用 drawContent.Clear() 清除所有绘制的内容
|
||
|
||
log.info("已移除边框");
|
||
}, 2000);
|
||
} catch (error) {
|
||
// 记录完整错误信息
|
||
log.error("详细错误: " + JSON.stringify(error));
|
||
}
|
||
},
|
||
easyOCR: async ({ X, Y, WIDTH, HEIGHT }) => {
|
||
try {
|
||
// log.info("进行文字识别")
|
||
// 创建OCR识别对象
|
||
const locationOcrRo = RecognitionObject.Ocr(X, Y, WIDTH, HEIGHT);
|
||
|
||
// 截图识别
|
||
let captureRegion = captureGameRegion();
|
||
let OCRresults = await captureRegion.findMulti(locationOcrRo);
|
||
|
||
return OCRresults;
|
||
} catch (error) {
|
||
log.error("easyOCR识别出错: {error}", error.message);
|
||
return { count: 0 };
|
||
}
|
||
},
|
||
easyOCROne: async (ocrdata) => {
|
||
results = await Utils.easyOCR(ocrdata);
|
||
if (results.count > 0) {
|
||
// 取第一个结果作为地点
|
||
return results[0].text.trim();
|
||
}
|
||
return "";
|
||
},
|
||
// 清理文本(去除标点符号等)
|
||
cleanText: (text) => {
|
||
if (!text) return "";
|
||
// 去除标点符号和特殊字符
|
||
return text.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, "").trim();
|
||
},
|
||
// 解析跳过的委托列表
|
||
parseSkipCommissions: (skipCommissionsStr) => {
|
||
if (!skipCommissionsStr || typeof skipCommissionsStr !== "string") {
|
||
return [];
|
||
}
|
||
|
||
// 支持中文逗号和英文逗号分割
|
||
return skipCommissionsStr
|
||
.split(/[,,]/)
|
||
.map((name) => name.trim())
|
||
.filter((name) => name.length > 0);
|
||
},
|
||
|
||
// 读取角色别名文件
|
||
readAliases: () => {
|
||
try {
|
||
const combatText = file.ReadTextSync("Data/avatar/combat_avatar.json");
|
||
const combatData = JSON.parse(combatText);
|
||
const aliases = {};
|
||
for (const character of combatData) {
|
||
if (character.alias && character.name) {
|
||
for (const alias of character.alias) {
|
||
aliases[alias] = character.name;
|
||
}
|
||
}
|
||
}
|
||
return aliases;
|
||
} catch (error) {
|
||
log.error("读取角色别名文件失败: {error}", error.message);
|
||
return {};
|
||
}
|
||
},
|
||
};
|
||
|
||
const UI = {
|
||
// 进入委托界面
|
||
enterCommissionScreen: async () => {
|
||
//log.info("正在进入委托界面...");
|
||
|
||
try {
|
||
// 使用F1快捷键直接打开委托界面
|
||
log.info("尝试使用F1快捷键进入委托界面");
|
||
keyPress("VK_F1");
|
||
// 点击委托界面
|
||
log.debug("点击委托界面");
|
||
await sleep(1000);
|
||
click(300, 350);
|
||
await sleep(100);
|
||
log.debug("已进入委托界面");
|
||
return true;
|
||
} catch (error) {
|
||
log.error("进入委托界面失败: {error}", error);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 自动执行划页操作 - 新的滑动方法
|
||
pageScroll: async (scrollCount) => {
|
||
try {
|
||
const clickX = 950; // 假设点击的起始坐标
|
||
const clickY = 600;
|
||
const totalDistance = 200; // 假设每次滑动的总距离
|
||
const stepDistance = 10; // 每步移动的距离
|
||
|
||
for (let i = 0; i < scrollCount; ++i) {
|
||
log.info(`开始第 ${i + 1} 次滑动`);
|
||
|
||
// 如果点击坐标为 (0, 0),则跳过点击
|
||
if (clickX !== 0 || clickY !== 0) {
|
||
moveMouseTo(clickX, clickY); // 移动到指定坐标
|
||
await sleep(100);
|
||
}
|
||
|
||
// 按住鼠标左键
|
||
leftButtonDown();
|
||
|
||
// 将鼠标移动到目标位置,模拟更自然的拖动操作
|
||
const steps = totalDistance / stepDistance; // 分成若干步移动
|
||
|
||
for (let j = 0; j < steps; j++) {
|
||
moveMouseBy(0, -stepDistance); // 每次移动 stepDistance 像素
|
||
await sleep(10); // 每次移动后延迟10毫秒
|
||
}
|
||
|
||
// 释放鼠标左键
|
||
await sleep(100);
|
||
leftButtonUp();
|
||
await sleep(300); // 增加滑动后的等待时间,确保界面稳定
|
||
}
|
||
|
||
return true;
|
||
} catch (error) {
|
||
log.error(`执行滑动操作时发生错误:${error.message}`);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 角色选择界面滚动页面函数
|
||
scrollPage: async (totalDistance, stepDistance = 10, delayMs = 5) => {
|
||
try {
|
||
moveMouseTo(400, 750);
|
||
await sleep(50);
|
||
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);
|
||
return true;
|
||
} catch (error) {
|
||
log.error(`角色选择界面滚动操作时发生错误:${error.message}`);
|
||
return false;
|
||
}
|
||
},
|
||
};
|
||
|
||
const Core = {
|
||
PrepareForLeyLineRun: async () => {
|
||
log.info("开始执行委托前准备");
|
||
setGameMetrics(1920, 1080, 1);
|
||
try {
|
||
await genshin.returnMainUi();
|
||
if (!prepare) {
|
||
await genshin.tpToStatueOfTheSeven();
|
||
}
|
||
// 切换战斗队伍
|
||
if (team) {
|
||
log.info(`切换至队伍 ${team}`);
|
||
await genshin.switchParty(team);
|
||
}
|
||
} catch (error) {
|
||
log.error("PrepareForLeyLineRun函数出现错误: {error}", error.message);
|
||
}
|
||
},
|
||
|
||
Identification: async () => {
|
||
log.info("开始执行原神每日委托识别脚本");
|
||
try {
|
||
// 设置游戏参数
|
||
setGameMetrics(1920, 1080, 1);
|
||
await genshin.returnMainUi();
|
||
|
||
// 初始化跳过委托列表
|
||
CommissionsFunc.initSkipCommissionsList();
|
||
|
||
// 加载支持的委托列表
|
||
const supportedCommissions =
|
||
await CommissionsFunc.loadSupportedCommissions();
|
||
|
||
log.info(
|
||
"支持的战斗委托: {count} 个",
|
||
supportedCommissions.fight.length
|
||
);
|
||
log.info(
|
||
"支持的对话委托: {count} 个",
|
||
supportedCommissions.talk.length
|
||
);
|
||
|
||
// 确保所有委托的资源目录存在
|
||
for (const commission of supportedCommissions.fight) {
|
||
await CommissionsFunc.ensureDirectoryExists(`assets/${commission}`);
|
||
}
|
||
for (const commission of supportedCommissions.talk) {
|
||
await CommissionsFunc.ensureDirectoryExists(
|
||
`${Datas.TALK_PROCESS_BASE_PATH}/${commission}`
|
||
);
|
||
}
|
||
|
||
// 进入委托界面
|
||
const enterSuccess = await UI.enterCommissionScreen();
|
||
if (!enterSuccess) {
|
||
log.error("无法进入委托界面,脚本终止");
|
||
return [];
|
||
}
|
||
await sleep(1000);
|
||
|
||
// 识别委托
|
||
const commissions = await CommissionsFunc.recognizeCommissions(
|
||
supportedCommissions
|
||
);
|
||
|
||
// 检测委托是否为错误状态,只有在成功识别到委托时才保存数据
|
||
if (commissions && commissions.length > 0) {
|
||
log.info("委托识别成功,开始保存数据");
|
||
await CommissionsFunc.saveCommissionsData(commissions);
|
||
} else {
|
||
log.warn("委托识别失败或未识别到任何委托,跳过保存数据");
|
||
}
|
||
|
||
// 根据识别结果输出不同的日志信息
|
||
if (commissions && commissions.length > 0) {
|
||
log.info(
|
||
"委托识别完成,共识别到 {total} 个委托,其中 {supported} 个受支持",
|
||
commissions.length,
|
||
commissions.filter((c) => c.supported).length
|
||
);
|
||
} else {
|
||
log.warn("委托识别失败,未识别到任何委托");
|
||
}
|
||
|
||
return commissions;
|
||
} catch (error) {
|
||
log.error("Identification函数出现错误: ${error.message}");
|
||
errorlog();
|
||
return [];
|
||
}
|
||
},
|
||
|
||
// 执行委托追踪(优化版 - 按距离排序)
|
||
executeCommissionTracking: async () => {
|
||
try {
|
||
log.info("开始执行委托追踪 - 按距离排序模式");
|
||
|
||
// 确保回到主界面
|
||
await genshin.returnMainUi();
|
||
await sleep(1000);
|
||
|
||
// 获取已识别的委托列表
|
||
let commissions = [];
|
||
try {
|
||
const commissionsData = JSON.parse(
|
||
file.readTextSync(`${Datas.OUTPUT_DIR}/commissions_data.json`)
|
||
);
|
||
commissions = commissionsData.commissions.filter((c) => c.supported);
|
||
log.info("已加载支持的委托数据,共 {count} 个", commissions.length);
|
||
} catch (error) {
|
||
log.error("读取委托数据失败: {error}", error.message);
|
||
return false;
|
||
}
|
||
|
||
if (commissions.length === 0) {
|
||
log.warn("没有找到支持的委托,请先运行识别脚本");
|
||
return false;
|
||
}
|
||
|
||
// 按距离排序委托(如果有位置信息)
|
||
const commissionsWithPosition = commissions.filter(
|
||
(c) =>
|
||
c.CommissionPosition &&
|
||
c.CommissionPosition.X &&
|
||
c.CommissionPosition.Y
|
||
);
|
||
|
||
// 统计已完成委托
|
||
let completedCount = 0;
|
||
for (const commission of commissions) {
|
||
if (commission.location === "已完成") {
|
||
completedCount++;
|
||
continue;
|
||
}
|
||
}
|
||
// 执行每个委托
|
||
for (const commission of commissions) {
|
||
// 检查是否在跳过列表中
|
||
if (
|
||
CommissionsFunc.skipCommissionsList.length > 0 &&
|
||
CommissionsFunc.skipCommissionsList.includes(commission.name)
|
||
) {
|
||
log.info("委托 {name} 在跳过列表中,跳过执行", commission.name);
|
||
continue;
|
||
}
|
||
|
||
// 跳过已完成的委托
|
||
if (commission.location === "已完成") {
|
||
log.info("委托 {name} 已完成,跳过", commission.name);
|
||
continue;
|
||
}
|
||
|
||
// 跳过没有地点信息的委托
|
||
if (
|
||
!commission.location ||
|
||
commission.location === "未知地点" ||
|
||
commission.location === "识别失败"
|
||
) {
|
||
log.warn("委托 {name} 缺少地点信息,跳过", commission.name);
|
||
continue;
|
||
}
|
||
|
||
log.info(
|
||
"开始执行委托: {name} ({location}) [{type}]",
|
||
commission.name,
|
||
commission.location,
|
||
commission.type || "未知类型"
|
||
);
|
||
|
||
try {
|
||
if (commission.CommissionPosition) {
|
||
log.info(
|
||
"当前委托位置: ({x}, {y})",
|
||
commission.CommissionPosition.X,
|
||
commission.CommissionPosition.Y
|
||
);
|
||
}
|
||
} catch (error) {
|
||
log.warn("委托 {name} 缺少坐标信息,尝试全部执行", commission.name);
|
||
}
|
||
|
||
let success = false;
|
||
let retryCount = 0;
|
||
|
||
// 委托执行重试循环
|
||
while (retryCount <= Datas.MAX_COMMISSION_RETRY_COUNT && !success) {
|
||
if (retryCount > 0) {
|
||
log.info(
|
||
"委托 {name} 第 {retry} 次重试执行",
|
||
commission.name,
|
||
retryCount
|
||
);
|
||
}
|
||
|
||
// 根据委托类型执行不同的处理逻辑
|
||
if (commission.type === Datas.COMMISSION_TYPE.TALK) {
|
||
// 执行对话委托
|
||
const talkSuccess = await Execute.executeTalkCommission(
|
||
commission.name,
|
||
commission.location
|
||
);
|
||
dispatcher.ClearAllTriggers();
|
||
|
||
if (talkSuccess) {
|
||
const completed = await CommissionsFunc.iscompleted(
|
||
completedCount
|
||
);
|
||
if (completed) {
|
||
completedCount++;
|
||
success = true;
|
||
log.info("对话委托 {name} 执行完成", commission.name);
|
||
} else {
|
||
log.warn(
|
||
"对话委托 {name} 执行后检查未完成,重试次数: {retry}/{max}",
|
||
commission.name,
|
||
retryCount,
|
||
Datas.MAX_COMMISSION_RETRY_COUNT
|
||
);
|
||
}
|
||
} else {
|
||
log.warn(
|
||
"对话委托 {name} 执行失败,重试次数: {retry}/{max}",
|
||
commission.name,
|
||
retryCount,
|
||
Datas.MAX_COMMISSION_RETRY_COUNT
|
||
);
|
||
}
|
||
} else {
|
||
// 默认执行战斗委托
|
||
const location = commission.location.trim();
|
||
|
||
// 脚本路径
|
||
const scriptPaths = [
|
||
`assets/${commission.name}/${location}-1.json`,
|
||
`assets/${commission.name}/${location}-2.json`,
|
||
`assets/${commission.name}/${location}-3.json`,
|
||
];
|
||
|
||
// 获取每个脚本对应的目标位置和距离
|
||
const scriptInfo = [];
|
||
for (const scriptPath of scriptPaths) {
|
||
try {
|
||
await file.readText(scriptPath);
|
||
const targetPos =
|
||
await CommissionsFunc.getCommissionTargetPosition(
|
||
scriptPath
|
||
);
|
||
if (targetPos) {
|
||
const distance = CommissionsFunc.calculateDistance(
|
||
commission.CommissionPosition,
|
||
targetPos
|
||
);
|
||
scriptInfo.push({
|
||
path: scriptPath,
|
||
distance: distance,
|
||
valid: true,
|
||
});
|
||
log.info(
|
||
"委托 {name} 目标位置: ({x}, {y}),距离: {distance}",
|
||
scriptPath,
|
||
targetPos.x,
|
||
targetPos.y,
|
||
distance
|
||
);
|
||
} else {
|
||
log.warn("委托 {name} 无法获取距离", scriptPath);
|
||
scriptInfo.push({
|
||
path: scriptPath,
|
||
distance: Infinity,
|
||
valid: false,
|
||
});
|
||
}
|
||
} catch (readError) {
|
||
log.info("路径追踪脚本不存在: {path}", scriptPath);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// 按距离排序脚本
|
||
scriptInfo.sort((a, b) => a.distance - b.distance);
|
||
|
||
// 输出排序结果
|
||
log.info("排序后的脚本执行顺序:");
|
||
scriptInfo.forEach((info, index) => {
|
||
log.info(
|
||
"{index}. 脚本: {path}, 距离: {distance}",
|
||
index + 1,
|
||
info.path,
|
||
info.distance
|
||
);
|
||
});
|
||
|
||
// 尝试执行排序后的脚本路径
|
||
// TODO:只执行第一个
|
||
let scriptSuccess = false;
|
||
for (const info of scriptInfo) {
|
||
const scriptPath = info.path;
|
||
try {
|
||
// 执行路径追踪脚本
|
||
log.info("开始执行路径追踪脚本: {path}", scriptPath);
|
||
dispatcher.addTimer(
|
||
new RealtimeTimer("AutoPick", { forceInteraction: false })
|
||
);
|
||
dispatcher.addTimer(new RealtimeTimer("AutoEat"));
|
||
await pathingScript.runFile(scriptPath);
|
||
log.info("路径追踪脚本执行完成");
|
||
dispatcher.ClearAllTriggers();
|
||
// 检查委托是否完成
|
||
if (await CommissionsFunc.iscompleted(completedCount)) {
|
||
log.info("委托 {name} 已完成", commission.name);
|
||
completedCount++;
|
||
success = true;
|
||
scriptSuccess = true;
|
||
break;
|
||
} else {
|
||
log.info(
|
||
"委托 {name} 未完成,尝试下一个脚本",
|
||
commission.name
|
||
);
|
||
}
|
||
} catch (scriptError) {
|
||
log.error("执行路径追踪脚本时出错: {error}", scriptError);
|
||
continue; // 尝试下一个脚本
|
||
}
|
||
}
|
||
|
||
if (!scriptSuccess) {
|
||
log.warn(
|
||
"战斗委托 {name} 所有脚本执行失败,重试次数: {retry}/{max}",
|
||
commission.name,
|
||
retryCount,
|
||
Datas.MAX_COMMISSION_RETRY_COUNT
|
||
);
|
||
}
|
||
}
|
||
|
||
// 增加重试计数
|
||
retryCount++;
|
||
|
||
// 如果未成功且还有重试机会,等待一段时间再重试
|
||
if (!success && retryCount <= Datas.MAX_COMMISSION_RETRY_COUNT) {
|
||
log.info("等待1秒后重试委托 {name}...", commission.name);
|
||
await sleep(1000);
|
||
}
|
||
}
|
||
|
||
// 重试循环结束后的处理
|
||
if (!success) {
|
||
if (retryCount > Datas.MAX_COMMISSION_RETRY_COUNT) {
|
||
log.warn(
|
||
"委托 {name} 重试 {retry} 次后仍未完成,跳过该委托",
|
||
commission.name,
|
||
Datas.MAX_COMMISSION_RETRY_COUNT
|
||
);
|
||
} else {
|
||
log.warn("委托 {name} 执行失败", commission.name);
|
||
}
|
||
} else {
|
||
log.info("委托 {name} 执行成功", commission.name);
|
||
}
|
||
|
||
// 每个委托之间等待一段时间
|
||
log.info("立刻执行下一个委托");
|
||
//await sleep(5000);
|
||
}
|
||
|
||
log.info(
|
||
"委托追踪全部执行完成,共执行 {count}/{total} 个委托",
|
||
completedCount,
|
||
commissions.length
|
||
);
|
||
|
||
return completedCount > 0;
|
||
} catch (error) {
|
||
log.error("执行委托追踪时出错: {error}", error.message);
|
||
errorlog();
|
||
return false;
|
||
}
|
||
},
|
||
};
|
||
|
||
// 步骤处理器类 - 处理不同类型的委托执行步骤
|
||
// TAG:添加脚本功能点1
|
||
const StepProcessor = {
|
||
// 处理地图追踪步骤
|
||
processMapTracking: async (step, commissionName, location) => {
|
||
const fullPath = `${
|
||
Datas.TALK_PROCESS_BASE_PATH
|
||
}/${commissionName}/${location}/${step.data || step}`;
|
||
log.info("执行地图追踪: {path}", fullPath);
|
||
try {
|
||
await pathingScript.runFile(fullPath);
|
||
log.info("地图追踪执行完成");
|
||
} catch (error) {
|
||
log.error("执行地图追踪时出错: {error}", error.message);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 处理追踪委托步骤
|
||
processCommissionTracking: async (step) => {
|
||
try {
|
||
// 获取目标NPC名称和图标类型
|
||
let targetNpc = "";
|
||
let iconType = "bigmap";
|
||
|
||
if (typeof step.data === "string") {
|
||
targetNpc = step.data;
|
||
} else if (typeof step.data === "object") {
|
||
if (step.data.npc) targetNpc = step.data.npc;
|
||
if (step.data.iconType) iconType = step.data.iconType;
|
||
}
|
||
|
||
log.info(
|
||
"执行追踪委托,目标NPC: {target},图标类型: {type}",
|
||
targetNpc,
|
||
iconType
|
||
);
|
||
await Execute.autoNavigateToTalk(targetNpc, iconType);
|
||
log.info("追踪委托执行完成");
|
||
} catch (error) {
|
||
log.error("执行追踪委托时出错: {error}", error.message);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 处理键鼠脚本步骤
|
||
processKeyMouseScript: async (step, commissionName, location) => {
|
||
log.info("执行键鼠脚本: {path}", step.data);
|
||
try {
|
||
const fullPath = `${Datas.TALK_PROCESS_BASE_PATH}/${commissionName}/${location}/${step.data}`;
|
||
await keyMouseScript.runFile(fullPath);
|
||
log.info("键鼠脚本执行完成");
|
||
} catch (error) {
|
||
log.error("执行键鼠脚本时出错: {error}", error.message);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 处理按键步骤
|
||
processKeyPress: async (step) => {
|
||
if (typeof step.data === "string") {
|
||
log.info("执行按键: {key}", step.data);
|
||
keyPress(step.data);
|
||
} else if (typeof step.data === "object") {
|
||
if (step.data.action === "down") {
|
||
log.info("按下按键: {key}", step.data.key);
|
||
keyDown(step.data.key);
|
||
} else if (step.data.action === "up") {
|
||
log.info("释放按键: {key}", step.data.key);
|
||
keyUp(step.data.key);
|
||
} else if (step.data.action === "press") {
|
||
log.info("点击按键: {key}", step.data.key);
|
||
keyPress(step.data.key);
|
||
}
|
||
}
|
||
},
|
||
|
||
// 处理传送步骤
|
||
processTeleport: async (step) => {
|
||
if (Array.isArray(step.data) && step.data.length >= 2) {
|
||
log.info("执行传送: {x}, {y}", step.data[0], step.data[1]);
|
||
const force = step.data.length > 2 ? step.data[2] : false;
|
||
await genshin.tp(step.data[0], step.data[1], force);
|
||
log.info("传送完成");
|
||
} else {
|
||
log.error("传送参数格式错误");
|
||
throw new Error("传送参数格式错误");
|
||
}
|
||
},
|
||
|
||
// 处理等待主界面步骤
|
||
processWaitMainUI: async (isInMainUI) => {
|
||
for (let i = 0; i < 120; i++) {
|
||
if (isInMainUI()) {
|
||
log.info("检测到已返回主界面,结束等待");
|
||
break;
|
||
}
|
||
await sleep(1000);
|
||
}
|
||
if (!isInMainUI()) {
|
||
log.info("等待返回主界面超时,尝试继续执行后续步骤");
|
||
}
|
||
},
|
||
|
||
// 处理地址检测步骤
|
||
processLocationDetection: async (
|
||
step,
|
||
commissionName,
|
||
location,
|
||
processSteps,
|
||
currentIndex
|
||
) => {
|
||
if (Array.isArray(step.data) && step.data.length >= 2) {
|
||
log.info(
|
||
`地址检测: {${step.data[0]}},{${step.data[1]}},run:${step.run}`
|
||
);
|
||
|
||
try {
|
||
// 获取当前委托目标位置
|
||
let commissionTarget = await Execute.findCommissionTarget(
|
||
commissionName
|
||
);
|
||
|
||
if (commissionTarget) {
|
||
const distance2 = CommissionsFunc.calculateDistance(
|
||
commissionTarget,
|
||
{
|
||
x: step.data[0],
|
||
y: step.data[1],
|
||
}
|
||
);
|
||
|
||
log.info(
|
||
"地址检测 - 委托位置: ({x}, {y}), 目标位置: ({tx}, {ty}), 距离: {d}",
|
||
commissionTarget.x,
|
||
commissionTarget.y,
|
||
step.data[0],
|
||
step.data[1],
|
||
distance2
|
||
);
|
||
|
||
if (distance2 < 15) {
|
||
log.info("地址检测成功,执行后续步骤");
|
||
const nextSteps = await Execute.loadAndParseProcessFile(
|
||
commissionName,
|
||
location,
|
||
step.run
|
||
);
|
||
// 插入到processSteps的这一步后面
|
||
if (nextSteps && Array.isArray(nextSteps)) {
|
||
processSteps.splice(currentIndex + 1, 0, ...nextSteps);
|
||
log.info("已插入 {count} 个后续步骤", nextSteps.length);
|
||
}
|
||
} else {
|
||
log.info("地址检测失败,距离过远: {distance}", distance2);
|
||
}
|
||
} else {
|
||
log.warn("无法获取委托目标位置,跳过地址检测");
|
||
}
|
||
} catch (error) {
|
||
log.error("地址检测时出错: {error}", error.message);
|
||
throw error;
|
||
}
|
||
} else {
|
||
log.error("地址检测参数格式错误");
|
||
throw new Error("地址检测参数格式错误");
|
||
}
|
||
},
|
||
|
||
// 处理委托描述检测步骤
|
||
processCommissionDescriptionDetection: async (
|
||
step,
|
||
commissionName,
|
||
location,
|
||
processSteps,
|
||
currentIndex
|
||
) => {
|
||
// 按v键打开任务界面
|
||
keyPress("v");
|
||
await sleep(300);
|
||
|
||
if (step.data !== "") {
|
||
log.info(`委托描述检测: {${step.data}}`);
|
||
|
||
// 循环检测,直到稳定
|
||
for (let c = 0; c < 13; c++) {
|
||
try {
|
||
// 使用委托详情检测区域进行OCR
|
||
const ocrResult = await Utils.easyOCROne(
|
||
Datas.OCR_REGIONS.COMMISSION_DETAIL
|
||
);
|
||
|
||
if (ocrResult === commissionName || ocrResult === "") {
|
||
await sleep(1000);
|
||
// 没有延时13s的错误提示,继续检测
|
||
log.debug("检测到委托名称或空文本,继续等待...");
|
||
}
|
||
// 成功匹配,开始插入step
|
||
else if (ocrResult === step.data) {
|
||
log.info("委托描述检测成功,执行后续步骤");
|
||
const nextSteps = await Execute.loadAndParseProcessFile(
|
||
commissionName,
|
||
location,
|
||
step.run
|
||
);
|
||
// 插入到这一步后面
|
||
if (nextSteps && Array.isArray(nextSteps)) {
|
||
processSteps.splice(currentIndex + 1, 0, ...nextSteps);
|
||
log.info("已插入 {count} 个后续步骤", nextSteps.length);
|
||
}
|
||
break;
|
||
} else {
|
||
log.warn(`委托描述不匹配,识别:${ocrResult},期望:${step.data}`);
|
||
break;
|
||
}
|
||
} catch (ocrError) {
|
||
log.error("委托描述OCR识别出错: {error}", ocrError);
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
log.error("委托描述检测参数格式错误");
|
||
throw new Error("委托描述检测参数格式错误");
|
||
}
|
||
},
|
||
|
||
// 处理角色切换步骤
|
||
processSwitchRole: async (step) => {
|
||
try {
|
||
const { position, character } = step.data;
|
||
|
||
if (!position || !character) {
|
||
log.error("角色切换参数不完整,需要 position 和 character");
|
||
return false;
|
||
}
|
||
|
||
log.info(`开始切换角色:第${position}号位 -> ${character}`);
|
||
|
||
const positionCoordinates = [
|
||
[460, 538],
|
||
[792, 538],
|
||
[1130, 538],
|
||
[1462, 538],
|
||
];
|
||
|
||
// 读取别名
|
||
const aliases = Utils.readAliases();
|
||
const actualName = aliases[character] || character;
|
||
log.info(`设置对应号位为【${character}】,切换角色为【${actualName}】`);
|
||
|
||
// 识别对象定义
|
||
const roTeamConfig = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(`Data/RecognitionObject/队伍配置.png`),
|
||
0,
|
||
0,
|
||
1920,
|
||
1080
|
||
);
|
||
const roReplace = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(`Data/RecognitionObject/更换.png`),
|
||
0,
|
||
0,
|
||
1920,
|
||
1080
|
||
);
|
||
const roJoin = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(`Data/RecognitionObject/加入.png`),
|
||
0,
|
||
0,
|
||
1920,
|
||
1080
|
||
);
|
||
|
||
let openPairingTries = 0;
|
||
let totalOpenPairingTries = 0;
|
||
|
||
// 打开配对界面的内部函数
|
||
const openPairingInterface = async () => {
|
||
while (openPairingTries < 3) {
|
||
keyPress("l");
|
||
await sleep(3500);
|
||
const teamConfigResult = captureGameRegion().find(roTeamConfig);
|
||
if (teamConfigResult.isExist()) {
|
||
openPairingTries = 0;
|
||
return true;
|
||
}
|
||
openPairingTries++;
|
||
totalOpenPairingTries++;
|
||
}
|
||
if (totalOpenPairingTries < 6) {
|
||
await genshin.tp("2297.630859375", "-824.5517578125");
|
||
openPairingTries = 0;
|
||
return openPairingInterface();
|
||
} else {
|
||
log.error("无法打开配对界面,任务结束");
|
||
return false;
|
||
}
|
||
};
|
||
|
||
if (!(await openPairingInterface())) {
|
||
return false;
|
||
}
|
||
|
||
const rolenum = position;
|
||
const selectedCharacter = actualName;
|
||
const [x, y] = positionCoordinates[position - 1];
|
||
click(x, y);
|
||
log.info(`开始设置${rolenum}号位角色`);
|
||
await sleep(1000);
|
||
let characterFound = false;
|
||
let pageTries = 0;
|
||
|
||
// 最多尝试滚动页面20次
|
||
while (pageTries < 20) {
|
||
// 尝试识别所有可能的角色文件名
|
||
for (let num = 1; ; num++) {
|
||
const paddedNum = num.toString().padStart(2, "0");
|
||
const characterFileName = `${selectedCharacter}${paddedNum}`;
|
||
try {
|
||
const characterRo = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(
|
||
`Data/characterimage/${characterFileName}.png`
|
||
),
|
||
0,
|
||
0,
|
||
1920,
|
||
1080
|
||
);
|
||
const characterResult = captureGameRegion().find(characterRo);
|
||
if (characterResult.isExist()) {
|
||
log.info(`已找到角色${selectedCharacter}`);
|
||
// 计算向右偏移35像素、向下偏移35像素的位置
|
||
const targetX = characterResult.x + 35;
|
||
const targetY = characterResult.y + 35;
|
||
|
||
// 边界检查,确保坐标在屏幕范围内
|
||
const safeX = Math.min(Math.max(targetX, 0), 1920);
|
||
const safeY = Math.min(Math.max(targetY, 0), 1080);
|
||
|
||
click(safeX, safeY);
|
||
await sleep(500); // 点击角色后等待0.5秒
|
||
characterFound = true;
|
||
break;
|
||
}
|
||
} catch (error) {
|
||
// 如果文件不存在,跳出循环
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (characterFound) {
|
||
break;
|
||
}
|
||
|
||
// 如果不是最后一次尝试,尝试滚动页面
|
||
if (pageTries < 15) {
|
||
log.info("当前页面没有目标角色,滚动页面");
|
||
await UI.scrollPage(200); // 使用UI模块的scrollPage函数
|
||
}
|
||
pageTries++;
|
||
}
|
||
|
||
if (!characterFound) {
|
||
log.error(`未找到【${selectedCharacter}】`);
|
||
return false;
|
||
}
|
||
|
||
// 识别"更换"或"加入"按钮
|
||
const replaceResult = captureGameRegion().find(roReplace);
|
||
const joinResult = captureGameRegion().find(roJoin);
|
||
|
||
if (replaceResult.isExist() || joinResult.isExist()) {
|
||
await sleep(300);
|
||
click(68, 1020);
|
||
keyPress("VK_LBUTTON");
|
||
await sleep(500);
|
||
log.info(`角色切换完成:${character} -> ${actualName}`);
|
||
return true;
|
||
} else {
|
||
log.error(`该角色已在队伍中,无需切换`);
|
||
await sleep(300);
|
||
keyPress("VK_ESCAPE");
|
||
await sleep(500);
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
log.error("角色切换过程中出错: {error}", error.message);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 处理自动任务步骤
|
||
processAutoTask: async (step) => {
|
||
try {
|
||
const { action, taskType, config } = step.data;
|
||
|
||
if (!action) {
|
||
log.error("自动任务参数不完整,需要 action 参数");
|
||
return false;
|
||
}
|
||
|
||
log.info("执行自动任务操作: {action}", action);
|
||
|
||
switch (action) {
|
||
case "enable":
|
||
// 启用自动任务
|
||
if (!taskType) {
|
||
log.error("启用自动任务需要指定 taskType");
|
||
return false;
|
||
}
|
||
|
||
if (config && typeof config === "object") {
|
||
log.info(
|
||
"启用自动任务: {type},配置: {config}",
|
||
taskType,
|
||
JSON.stringify(config)
|
||
);
|
||
dispatcher.addTimer(new RealtimeTimer(taskType, config));
|
||
} else {
|
||
log.info("启用自动任务: {type}", taskType);
|
||
dispatcher.addTimer(new RealtimeTimer(taskType));
|
||
}
|
||
break;
|
||
|
||
case "disable":
|
||
// 取消所有自动任务
|
||
log.info("取消所有自动任务");
|
||
dispatcher.ClearAllTriggers();
|
||
break;
|
||
|
||
default:
|
||
log.error("未知的自动任务操作: {action}", action);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
} catch (error) {
|
||
log.error("处理自动任务步骤时出错: {error}", error.message);
|
||
return false;
|
||
}
|
||
},
|
||
};
|
||
|
||
// 步骤处理器工厂 - 更好的扩展性设计
|
||
// TAG:添加脚本功能点2
|
||
const StepProcessorFactory = {
|
||
// 步骤处理器映射表
|
||
processors: {
|
||
地图追踪: async (step, context) => {
|
||
await StepProcessor.processMapTracking(
|
||
step,
|
||
context.commissionName,
|
||
context.location
|
||
);
|
||
},
|
||
|
||
等待: async (step, context) => {
|
||
const waitTime = step.data || 5000;
|
||
log.info("等待 {time} 毫秒", waitTime);
|
||
await sleep(waitTime);
|
||
},
|
||
|
||
追踪委托: async (step, context) => {
|
||
await StepProcessor.processCommissionTracking(step);
|
||
},
|
||
|
||
键鼠脚本: async (step, context) => {
|
||
await StepProcessor.processKeyMouseScript(
|
||
step,
|
||
context.commissionName,
|
||
context.location
|
||
);
|
||
},
|
||
|
||
对话: async (step, context) => {
|
||
await Execute.processDialogStep(
|
||
step,
|
||
context.priorityOptions,
|
||
context.npcWhiteList,
|
||
context.isInMainUI
|
||
);
|
||
},
|
||
|
||
按键: async (step, context) => {
|
||
await StepProcessor.processKeyPress(step);
|
||
},
|
||
|
||
tp: async (step, context) => {
|
||
await StepProcessor.processTeleport(step);
|
||
},
|
||
|
||
等待返回主界面: async (step, context) => {
|
||
await StepProcessor.processWaitMainUI(context.isInMainUI);
|
||
},
|
||
|
||
地址检测: async (step, context) => {
|
||
await StepProcessor.processLocationDetection(
|
||
step,
|
||
context.commissionName,
|
||
context.location,
|
||
context.processSteps,
|
||
context.currentIndex
|
||
);
|
||
},
|
||
|
||
委托描述检测: async (step, context) => {
|
||
await StepProcessor.processCommissionDescriptionDetection(
|
||
step,
|
||
context.commissionName,
|
||
context.location,
|
||
context.processSteps,
|
||
context.currentIndex
|
||
);
|
||
},
|
||
|
||
切换角色: async (step, context) => {
|
||
await StepProcessor.processSwitchRole(step);
|
||
},
|
||
|
||
自动任务: async (step, context) => {
|
||
await StepProcessor.processAutoTask(step);
|
||
},
|
||
},
|
||
|
||
// 注册新的步骤处理器
|
||
register: (stepType, processor) => {
|
||
StepProcessorFactory.processors[stepType] = processor;
|
||
log.info("注册新的步骤处理器: {type}", stepType);
|
||
},
|
||
|
||
// 处理步骤
|
||
process: async (step, context) => {
|
||
const processor = StepProcessorFactory.processors[step.type];
|
||
if (processor) {
|
||
await processor(step, context);
|
||
} else {
|
||
log.warn("未知的流程类型: {type}", step.type);
|
||
}
|
||
},
|
||
};
|
||
|
||
// UI工具模块 - 处理UI检测和文本提取等工具函数
|
||
const UIUtils = {
|
||
// 创建主界面检测函数
|
||
createMainUIChecker: () => {
|
||
const paimonMenuRo = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync("Data/RecognitionObject/paimon_menu.png"),
|
||
0,
|
||
0,
|
||
genshin.width / 3.0,
|
||
genshin.width / 5.0
|
||
);
|
||
|
||
return () => {
|
||
let captureRegion = captureGameRegion();
|
||
let res = captureRegion.Find(paimonMenuRo);
|
||
return !res.isEmpty();
|
||
};
|
||
},
|
||
|
||
// 人名提取函数
|
||
extractName: (text) => {
|
||
const patterns = [
|
||
/与(.+?)对话/,
|
||
/与(.+?)一起/,
|
||
/同(.+?)交谈/,
|
||
/向(.+?)打听/,
|
||
/向(.+?)回报/,
|
||
/向(.+?)报告/,
|
||
/给(.+?)听/,
|
||
/陪同(.+?)\S+/,
|
||
/找到(.+?)\S+/,
|
||
/询问(.+?)\S+/,
|
||
/拜访(.+?)\S+/,
|
||
/寻找(.+?)\S+/,
|
||
/告诉(.+?)\S+/,
|
||
/带(.+?)去\S+/,
|
||
/跟随(.+?)\S+/,
|
||
/协助(.+?)\S+/,
|
||
/请教(.+?)\S+/,
|
||
/拜托(.+?)\S+/,
|
||
/委托(.+?)\S+/,
|
||
];
|
||
|
||
for (const pattern of patterns) {
|
||
const match = text.match(pattern);
|
||
if (match && match[1]) {
|
||
return match[1].trim();
|
||
}
|
||
}
|
||
return null;
|
||
},
|
||
};
|
||
|
||
// 对话处理模块 - 处理自动对话相关功能
|
||
const DialogProcessor = {
|
||
// 执行优化的自动对话
|
||
executeOptimizedAutoTalk: async (
|
||
extractedName = null,
|
||
skipCount = 5,
|
||
customPriorityOptions = null,
|
||
customNpcWhiteList = null,
|
||
isInMainUI
|
||
) => {
|
||
// 使用传入的参数,不再加载默认配置
|
||
const effectivePriorityOptions = customPriorityOptions || [];
|
||
const effectiveNpcWhiteList = customNpcWhiteList || [];
|
||
|
||
// 初始化
|
||
keyPress("V");
|
||
|
||
// 初始触发剧情 - 识别人名并点击
|
||
extractedName = [];
|
||
// 人名区域OCR识别
|
||
const nameRegion = { X: 75, Y: 240, WIDTH: 225, HEIGHT: 60 };
|
||
let nameResults = await Utils.easyOCR(nameRegion);
|
||
// 尝试提取任务人名
|
||
for (let i = 0; i < nameResults.count; i++) {
|
||
let text = nameResults[i].text;
|
||
log.info(`任务区域识别文本: ${text}`);
|
||
|
||
// 尝试提取任务人名
|
||
let name = UIUtils.extractName(text);
|
||
if (name) {
|
||
extractedName = name;
|
||
log.info(`提取到人名: ${extractedName}`);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 对话选项区域OCR识别
|
||
const dialogRegion = { X: 1150, Y: 300, WIDTH: 350, HEIGHT: 400 };
|
||
nameResults = await Utils.easyOCR(dialogRegion);
|
||
let clickedWhitelistNPC = false;
|
||
let clickedExtractedName = false;
|
||
|
||
// 处理人名区域的OCR结果
|
||
if (nameResults.count > 0) {
|
||
log.info(`人名区域识别到 ${nameResults.count} 个文本`);
|
||
|
||
// 首先尝试点击白名单中的NPC
|
||
for (let i = 0; i < nameResults.count; i++) {
|
||
let text = nameResults[i].text;
|
||
let res = nameResults[i];
|
||
log.info(
|
||
"人名区域识别到{text}:位置({x},{y},{h},{w})",
|
||
res.text,
|
||
res.x,
|
||
res.y,
|
||
res.width,
|
||
res.Height
|
||
);
|
||
// 检查是否包含白名单中的NPC名称
|
||
for (let j = 0; j < effectiveNpcWhiteList.length; j++) {
|
||
if (text.includes(effectiveNpcWhiteList[j])) {
|
||
log.info(`找到白名单NPC: ${effectiveNpcWhiteList[j]},点击该NPC`);
|
||
keyDown("VK_MENU");
|
||
await sleep(500);
|
||
click(res.x, res.y);
|
||
leftButtonClick();
|
||
keyUp("VK_MENU");
|
||
clickedWhitelistNPC = true;
|
||
break;
|
||
}
|
||
}
|
||
if (clickedWhitelistNPC) break;
|
||
}
|
||
|
||
// 如果没有点击白名单NPC,尝试点击包含提取到的人名的选项
|
||
if (!clickedWhitelistNPC && extractedName) {
|
||
for (let i = 0; i < nameResults.count; i++) {
|
||
let text = nameResults[i].text;
|
||
let res = nameResults[i];
|
||
if (text.includes(extractedName)) {
|
||
log.info(`点击包含提取到任务人名的选项: ${text}`);
|
||
keyDown("VK_MENU");
|
||
await sleep(500);
|
||
click(res.x, res.y);
|
||
leftButtonClick();
|
||
keyUp("VK_MENU");
|
||
clickedExtractedName = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有找到NPC,使用默认触发
|
||
if (!clickedWhitelistNPC && !clickedExtractedName) {
|
||
log.info("未找到匹配的NPC,使用默认触发方式");
|
||
keyPress("F"); // 默认触发剧情
|
||
await sleep(500);
|
||
}
|
||
|
||
// 重复执行自动剧情,直到返回主界面
|
||
let maxAttempts = 100; // 设置最大尝试次数,防止无限循环
|
||
let attempts = 0;
|
||
let repetition = 0;
|
||
let oldcount = 1;
|
||
await sleep(1000);
|
||
log.info("开始执行自动剧情");
|
||
|
||
while (!isInMainUI() && attempts < maxAttempts) {
|
||
attempts++;
|
||
|
||
// 正常跳过对话
|
||
await genshin.chooseTalkOption("纳西妲美貌举世无双", skipCount, false);
|
||
|
||
if (isInMainUI()) {
|
||
log.info("检测到已返回主界面,结束循环");
|
||
break;
|
||
}
|
||
|
||
//keyPress("VK_ESCAPE");//关弹窗
|
||
|
||
// 每skipCount次跳过后,进行OCR识别
|
||
if (true) {
|
||
// 检查是否有匹配的优先选项
|
||
let foundPriorityOption = false;
|
||
|
||
// 获取对话区域截图并进行OCR识别
|
||
const dialogOptionsRegion = {
|
||
X: 1250,
|
||
Y: 450,
|
||
WIDTH: 550,
|
||
HEIGHT: 400,
|
||
};
|
||
let ocrResults = await Utils.easyOCR(dialogOptionsRegion);
|
||
if (ocrResults.count > 0) {
|
||
log.info(`识别到 ${ocrResults.count} 个选项`);
|
||
|
||
if (ocrResults.count === oldcount) {
|
||
repetition++;
|
||
} else {
|
||
repetition = 0;
|
||
}
|
||
oldcount = ocrResults.count;
|
||
if (repetition >= 5) {
|
||
log.info("连续5次选项数量一样,执行F跳过");
|
||
keyPress("F");
|
||
repetition = 0;
|
||
}
|
||
for (let i = 0; i < ocrResults.count; i++) {
|
||
let ocrText = ocrResults[i].text;
|
||
|
||
// 检查是否在优先选项列表中
|
||
for (let j = 0; j < effectivePriorityOptions.length; j++) {
|
||
if (ocrText.includes(effectivePriorityOptions[j])) {
|
||
log.info(
|
||
`找到优先选项: ${effectivePriorityOptions[j]},点击该选项`
|
||
);
|
||
// 点击该选项
|
||
ocrResults[i].click();
|
||
await sleep(500);
|
||
foundPriorityOption = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (foundPriorityOption) break;
|
||
}
|
||
|
||
// 如果没有找到优先选项,则使用默认跳过
|
||
if (!foundPriorityOption) {
|
||
await genshin.chooseTalkOption("", 1, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查是否已返回主界面
|
||
if (isInMainUI()) {
|
||
log.info("检测到已返回主界面,结束循环");
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (isInMainUI()) {
|
||
log.info("已返回主界面,自动剧情执行完成");
|
||
keyPress("V");
|
||
} else {
|
||
log.warn(`已达到最大尝试次数 ${maxAttempts},但未检测到返回主界面`);
|
||
}
|
||
},
|
||
};
|
||
|
||
const Execute = {
|
||
// 寻找委托目的地址带追踪任务
|
||
findCommissionTarget: async (commissionName) => {
|
||
try {
|
||
log.info("开始寻找委托目标位置: {name}", commissionName);
|
||
|
||
// 确保回到主界面
|
||
await genshin.returnMainUi();
|
||
|
||
// 第一步,检测这个任务是否在1-3之中
|
||
let index = 4;
|
||
|
||
try {
|
||
// 进入委托界面
|
||
const enterSuccess = await UI.enterCommissionScreen();
|
||
if (!enterSuccess) {
|
||
log.error("无法进入委托界面");
|
||
return null;
|
||
}
|
||
|
||
await sleep(1000);
|
||
|
||
// 识别前3个委托
|
||
log.debug("findCommissionTarget识别前3个委托");
|
||
|
||
// 先识别前3个Main_Dev区域(索引0-2)
|
||
for (let regionIndex = 0; regionIndex < 3; regionIndex++) {
|
||
const region = Datas.OCR_REGIONS.Main_Dev[regionIndex];
|
||
|
||
try {
|
||
let results = await Utils.easyOCR(region);
|
||
|
||
// 处理识别结果,取第一个有效结果
|
||
for (let i = 0; i < results.count; i++) {
|
||
let result = results[i];
|
||
let text = Utils.cleanText(result.text);
|
||
if (text && text.length >= 2) {
|
||
log.info(
|
||
'第{regionIndex}个委托: "{text}"',
|
||
regionIndex + 1,
|
||
text
|
||
);
|
||
if (text === commissionName) {
|
||
index = regionIndex + 1;
|
||
log.info(
|
||
"找到委托 {name} 在位置 {index}",
|
||
commissionName,
|
||
index
|
||
);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果找到了委托,跳出外层循环
|
||
if (index !== 4) {
|
||
break;
|
||
}
|
||
} catch (regionError) {
|
||
log.error(
|
||
"识别第{index}个委托区域时出错: {error}",
|
||
regionIndex + 1,
|
||
regionError
|
||
);
|
||
continue;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
log.error("findCommissionTarget第一步失败: {error}", error.message);
|
||
}
|
||
|
||
// 如果前3个没找到,检查第4个委托(需要翻页)
|
||
if (index === 4) {
|
||
try {
|
||
log.info("前3个委托中未找到,检查第4个委托");
|
||
await UI.pageScroll(1);
|
||
|
||
const region = Datas.OCR_REGIONS.Main_Dev[3]; // 第4个区域
|
||
let results = await Utils.easyOCR(region);
|
||
|
||
for (let i = 0; i < results.count; i++) {
|
||
let result = results[i];
|
||
let text = Utils.cleanText(result.text);
|
||
if (text && text.length >= 2) {
|
||
log.info('第4个委托: "{text}"', text);
|
||
if (text === commissionName) {
|
||
index = 4;
|
||
log.info("找到委托 {name} 在第4个位置", commissionName);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} catch (fourthError) {
|
||
log.error("识别第4个委托时出错: {error}", fourthError);
|
||
}
|
||
}
|
||
|
||
// 第二步:进入对应的大地图,获取位置
|
||
let currentCommissionPosition = null;
|
||
try {
|
||
// 点击详情按钮
|
||
if (index === 4) {
|
||
// 第4个委托已经翻页了,使用索引3
|
||
index = 3;
|
||
}
|
||
|
||
const button = Datas.COMMISSION_DETAIL_BUTTONS[index - 1];
|
||
if (button) {
|
||
log.info("点击委托详情按钮: {id}", button.id);
|
||
click(button.x, button.y);
|
||
await sleep(2000);
|
||
|
||
// 检查是否有追踪按钮并点击
|
||
const trackingResult = await Utils.easyOCROne(
|
||
Datas.OCR_REGIONS.COMMISSION_TRACKING
|
||
);
|
||
if (trackingResult === "追踪") {
|
||
log.info("发现追踪按钮,点击追踪");
|
||
click(1693, 1000);
|
||
await sleep(1000);
|
||
}
|
||
|
||
// 退出详情页面
|
||
log.info("退出详情页面 - 按ESC");
|
||
keyDown("VK_ESCAPE");
|
||
await sleep(300);
|
||
keyUp("VK_ESCAPE");
|
||
await sleep(1200);
|
||
await genshin.setBigMapZoomLevel(2);
|
||
// 获取地图坐标并保存
|
||
const bigMapPosition = genshin.getPositionFromBigMap();
|
||
if (bigMapPosition) {
|
||
currentCommissionPosition = bigMapPosition;
|
||
log.info(
|
||
"当前委托位置: ({x}, {y})",
|
||
bigMapPosition.x,
|
||
bigMapPosition.y
|
||
);
|
||
}
|
||
|
||
await genshin.returnMainUi();
|
||
} else {
|
||
log.error("无效的委托按钮索引: {index}", index);
|
||
}
|
||
} catch (error) {
|
||
log.error("findCommissionTarget第2步失败: {error}", error.message);
|
||
}
|
||
|
||
return currentCommissionPosition;
|
||
} catch (error) {
|
||
log.error("寻找委托目标位置时出错: {error}", error.message);
|
||
return null;
|
||
}
|
||
},
|
||
|
||
// 读取并解析流程文件为步骤数组
|
||
loadAndParseProcessFile: async (
|
||
commissionName,
|
||
location,
|
||
locationprocessFilePath = "process.json"
|
||
) => {
|
||
const processFilePath = `${Datas.TALK_PROCESS_BASE_PATH}/${commissionName}/${location}/${locationprocessFilePath}`;
|
||
let processContent;
|
||
let processSteps;
|
||
try {
|
||
processContent = await file.readText(processFilePath);
|
||
log.info("找到对话委托流程文件: {path}", processFilePath);
|
||
} catch (error) {
|
||
log.warn(
|
||
"未找到对话委托 {name} 在 {location} 的流程文件: {path}",
|
||
commissionName,
|
||
location,
|
||
processFilePath
|
||
);
|
||
return false;
|
||
}
|
||
// 解析流程内容
|
||
|
||
try {
|
||
// 尝试解析为JSON格式
|
||
const jsonData = JSON.parse(processContent);
|
||
if (Array.isArray(jsonData)) {
|
||
processSteps = jsonData;
|
||
log.debug("JSON流程解析成功");
|
||
} else {
|
||
log.error("JSON流程格式错误,应为数组");
|
||
return false;
|
||
}
|
||
} catch (jsonError) {
|
||
// 如果不是JSON格式,按简单格式处理
|
||
const lines = processContent
|
||
.split("\n")
|
||
.map((line) => line.trim())
|
||
.filter((line) => line.length > 0);
|
||
processSteps = lines;
|
||
}
|
||
return processSteps;
|
||
},
|
||
|
||
// 执行对话委托流程(优化版)
|
||
executeTalkCommission: async (commissionName, location) => {
|
||
try {
|
||
const processSteps = await Execute.loadAndParseProcessFile(
|
||
commissionName,
|
||
location,
|
||
"process.json"
|
||
);
|
||
|
||
// 使用统一的处理器执行流程
|
||
return await Execute.executeUnifiedTalkProcess(
|
||
processSteps,
|
||
commissionName,
|
||
location
|
||
);
|
||
} catch (error) {
|
||
log.error("执行对话委托时出错: {error}", error.message);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 自动导航到NPC对话位置(从main_branch.js移植)
|
||
autoNavigateToTalk: async (npcName = "", iconType = "") => {
|
||
try {
|
||
// 设置目标NPC名称
|
||
const textArray = npcName;
|
||
|
||
// 根据图标类型选择不同的识别对象
|
||
let boxIconRo;
|
||
if (iconType === "Bigmap") {
|
||
boxIconRo = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(
|
||
"Data/RecognitionObject/IconBigmapCommission.jpg"
|
||
)
|
||
);
|
||
log.info("使用大地图图标");
|
||
} else if (iconType === "Question") {
|
||
boxIconRo = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(
|
||
"Data/RecognitionObject/IconQuestionCommission.png"
|
||
)
|
||
);
|
||
log.info("使用问号任务图标");
|
||
} else {
|
||
// 默认使用任务图标
|
||
boxIconRo = RecognitionObject.TemplateMatch(
|
||
file.ReadImageMatSync(
|
||
"Data/RecognitionObject/IconTaskCommission.png"
|
||
)
|
||
);
|
||
log.info("使用任务图标");
|
||
}
|
||
|
||
let advanceNum = 0; //前进次数
|
||
|
||
middleButtonClick();
|
||
await sleep(800);
|
||
|
||
while (true) {
|
||
// 1. 优先检查是否已到达
|
||
await sleep(500); // 等待0.5秒
|
||
let captureRegion = captureGameRegion();
|
||
let rewardTextArea = captureRegion.DeriveCrop(1210, 515, 200, 50);
|
||
let rewardResult = rewardTextArea.find(RecognitionObject.ocrThis);
|
||
log.debug("检测到文字: " + rewardResult.text);
|
||
// 检测到特点文字则结束!!!
|
||
if (rewardResult.text == textArray) {
|
||
log.info("已到达指定位置,检测到文字: " + rewardResult.text);
|
||
return;
|
||
} else if (advanceNum > 80) {
|
||
throw new Error("前进时间超时");
|
||
}
|
||
// 2. 未到达领奖点,则调整视野
|
||
for (let i = 0; i < 100; i++) {
|
||
captureRegion = captureGameRegion();
|
||
let iconRes = captureRegion.Find(boxIconRo);
|
||
log.info("检测到委托图标位置 ({x}, {y})", iconRes.x, iconRes.y);
|
||
if (iconRes.x >= 920 && iconRes.x <= 980 && iconRes.y <= 540) {
|
||
advanceNum++;
|
||
log.info(`视野已调正,前进第${advanceNum}次`);
|
||
break;
|
||
} else {
|
||
// 小幅度调整
|
||
if (iconRes.y >= 520) moveMouseBy(0, 920);
|
||
let adjustAmount = iconRes.x < 920 ? -20 : 20;
|
||
let distanceToCenter = Math.abs(iconRes.x - 920); // 计算与920的距离
|
||
let scaleFactor = Math.max(1, Math.floor(distanceToCenter / 50)); // 根据距离缩放,最小为1
|
||
let adjustAmount2 = iconRes.y < 540 ? scaleFactor : 10;
|
||
moveMouseBy(adjustAmount * adjustAmount2, 0);
|
||
await sleep(100);
|
||
}
|
||
if (i > 50) throw new Error("视野调整超时");
|
||
}
|
||
// 3. 前进一小步
|
||
keyDown("w");
|
||
await sleep(200);
|
||
keyPress("VK_SPACE");
|
||
await sleep(200);
|
||
keyPress("VK_SPACE");
|
||
await sleep(200);
|
||
keyUp("w");
|
||
await sleep(200); // 等待角色移动稳定
|
||
}
|
||
} catch (error) {
|
||
log.error("自动导航到NPC对话位置时出错: {error}", error.message);
|
||
throw error;
|
||
}
|
||
},
|
||
|
||
// 统一的对话委托流程处理器(重构版 - 更简洁的主控制函数)
|
||
executeUnifiedTalkProcess: async (
|
||
processSteps,
|
||
commissionName,
|
||
location
|
||
) => {
|
||
try {
|
||
log.info("执行统一对话委托流程: {name}", commissionName);
|
||
|
||
if (!processSteps || processSteps.length === 0) {
|
||
log.warn("没有找到有效的流程步骤");
|
||
return false;
|
||
}
|
||
|
||
// 初始化UI检测器和配置
|
||
const isInMainUI = UIUtils.createMainUIChecker();
|
||
let priorityOptions = [];
|
||
let npcWhiteList = [];
|
||
|
||
// 刚开始就追踪委托目标
|
||
await Execute.findCommissionTarget(commissionName);
|
||
|
||
// 执行处理步骤
|
||
for (let i = 0; i < processSteps.length; i++) {
|
||
const step = processSteps[i];
|
||
log.info("执行流程步骤 {step}: {type}", i + 1, step.type || step);
|
||
|
||
try {
|
||
// 重置为默认值并处理自定义配置
|
||
const stepConfig = Execute.processStepConfiguration(
|
||
step,
|
||
priorityOptions,
|
||
npcWhiteList
|
||
);
|
||
priorityOptions = stepConfig.priorityOptions;
|
||
npcWhiteList = stepConfig.npcWhiteList;
|
||
|
||
// TAG:添加脚本功能点3
|
||
const context = {
|
||
commissionName,
|
||
location,
|
||
processSteps,
|
||
currentIndex: i,
|
||
isInMainUI,
|
||
priorityOptions,
|
||
npcWhiteList,
|
||
};
|
||
|
||
// 处理步骤
|
||
await Execute.processStep(step, context);
|
||
} catch (stepError) {
|
||
log.error(
|
||
"执行步骤 {step} 时出错: {error}",
|
||
i + 1,
|
||
stepError.message
|
||
);
|
||
// 继续执行下一步,不中断整个流程
|
||
}
|
||
|
||
// 每个步骤之间等待一段时间
|
||
await sleep(2000);
|
||
}
|
||
|
||
log.info("统一对话委托流程执行完成: {name}", commissionName);
|
||
return true;
|
||
} catch (error) {
|
||
log.error("执行统一对话委托流程时出错: {error}", error.message);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 处理步骤配置(优先选项和NPC白名单)
|
||
processStepConfiguration: (
|
||
step,
|
||
defaultPriorityOptions,
|
||
defaultNpcWhiteList
|
||
) => {
|
||
let priorityOptions = [...defaultPriorityOptions];
|
||
let npcWhiteList = [...defaultNpcWhiteList];
|
||
|
||
// 如果步骤中包含自定义的优先选项和NPC白名单,则使用它们
|
||
if (step.data && typeof step.data === "object") {
|
||
if (Array.isArray(step.data.priorityOptions)) {
|
||
priorityOptions = step.data.priorityOptions;
|
||
log.info("使用自定义优先选项: {options}", priorityOptions.join(", "));
|
||
}
|
||
if (Array.isArray(step.data.npcWhiteList)) {
|
||
npcWhiteList = step.data.npcWhiteList;
|
||
log.info("使用自定义NPC白名单: {npcs}", npcWhiteList.join(", "));
|
||
}
|
||
}
|
||
|
||
return { priorityOptions, npcWhiteList };
|
||
},
|
||
|
||
// 处理单个步骤
|
||
processStep: async (step, context) => {
|
||
if (typeof step === "string") {
|
||
// 简单格式处理
|
||
await Execute.processStringStep(step, context);
|
||
} else if (typeof step === "object") {
|
||
// JSON格式处理
|
||
await Execute.processObjectStep(step, context);
|
||
}
|
||
},
|
||
|
||
// 处理字符串格式的步骤
|
||
processStringStep: async (step, context) => {
|
||
if (step.endsWith(".json")) {
|
||
// 地图追踪文件
|
||
await StepProcessor.processMapTracking(
|
||
step,
|
||
context.commissionName,
|
||
context.location
|
||
);
|
||
} else if (step === "F") {
|
||
// 按F键并执行优化的自动剧情
|
||
log.info("执行自动剧情");
|
||
await DialogProcessor.executeOptimizedAutoTalk(
|
||
null,
|
||
5,
|
||
context.priorityOptions,
|
||
context.npcWhiteList,
|
||
context.isInMainUI
|
||
);
|
||
}
|
||
},
|
||
|
||
// 处理对象格式的步骤
|
||
processObjectStep: async (step, context) => {
|
||
if (step.note) {
|
||
log.info("步骤说明: {note}", step.note);
|
||
}
|
||
|
||
// 使用步骤处理器工厂来处理步骤
|
||
await StepProcessorFactory.process(step, context);
|
||
},
|
||
|
||
// 示例:注册自定义步骤处理器
|
||
registerCustomStepProcessors: () => {
|
||
// 注册等待步骤处理器
|
||
StepProcessorFactory.register("等待", async (step, context) => {
|
||
const waitTime = step.data || 1000;
|
||
log.info("等待 {time} 毫秒", waitTime);
|
||
await sleep(waitTime);
|
||
});
|
||
|
||
// 注册截图步骤处理器
|
||
StepProcessorFactory.register("截图", async (step, context) => {
|
||
const filename = step.data || `screenshot_${Date.now()}.png`;
|
||
log.info("截图保存为: {filename}", filename);
|
||
// 这里可以添加实际的截图逻辑
|
||
});
|
||
|
||
// 注册条件检查步骤处理器
|
||
StepProcessorFactory.register("条件检查", async (step, context) => {
|
||
const condition = step.data.condition;
|
||
const trueSteps = step.data.trueSteps || [];
|
||
const falseSteps = step.data.falseSteps || [];
|
||
|
||
log.info("执行条件检查: {condition}", condition);
|
||
|
||
// 这里可以添加条件判断逻辑
|
||
const conditionResult = true; // 示例结果
|
||
|
||
const stepsToExecute = conditionResult ? trueSteps : falseSteps;
|
||
for (const subStep of stepsToExecute) {
|
||
await Execute.processStep(subStep, context);
|
||
}
|
||
});
|
||
|
||
log.info("自定义步骤处理器注册完成");
|
||
},
|
||
|
||
// 处理对话步骤
|
||
processDialogStep: async (
|
||
step,
|
||
priorityOptions,
|
||
npcWhiteList,
|
||
isInMainUI
|
||
) => {
|
||
log.info("执行对话");
|
||
let skipCount = 2; // 默认跳过2次
|
||
|
||
// 处理对话选项
|
||
if (typeof step.data === "number") {
|
||
// 兼容旧版本,如果data是数字,则视为skipCount
|
||
skipCount = step.data;
|
||
} else if (typeof step.data === "object" && step.data.skipCount) {
|
||
// 新版本,data是对象,包含skipCount
|
||
skipCount = step.data.skipCount;
|
||
}
|
||
|
||
// 执行对话,使用当前步骤的优先选项和NPC白名单
|
||
await DialogProcessor.executeOptimizedAutoTalk(
|
||
null,
|
||
skipCount,
|
||
priorityOptions,
|
||
npcWhiteList,
|
||
isInMainUI
|
||
);
|
||
},
|
||
};
|
||
|
||
const CommissionsFunc = {
|
||
// 跳过的委托列表
|
||
skipCommissionsList: [],
|
||
|
||
// 初始化跳过委托列表
|
||
initSkipCommissionsList: () => {
|
||
CommissionsFunc.skipCommissionsList =
|
||
Utils.parseSkipCommissions(skipCommissions);
|
||
if (CommissionsFunc.skipCommissionsList.length > 0) {
|
||
log.info(
|
||
"配置的跳过委托列表: {list}",
|
||
CommissionsFunc.skipCommissionsList.join(", ")
|
||
);
|
||
}
|
||
},
|
||
|
||
// 检测委托完成状态(使用图像识别)
|
||
detectCommissionStatusByImage: async (buttonIndex) => {
|
||
try {
|
||
const button = Datas.COMMISSION_DETAIL_BUTTONS[buttonIndex];
|
||
if (!button) {
|
||
log.error("无效的按钮索引: {index}", buttonIndex);
|
||
return "unknown";
|
||
}
|
||
|
||
log.debug("检测委托{id}的完成状态(图像识别)", button.id);
|
||
// 截图
|
||
let captureRegion = captureGameRegion();
|
||
|
||
// 检测区域:按钮位置左右各扩展更大范围
|
||
const checkRegion = captureRegion.deriveCrop(
|
||
button.checkX,
|
||
button.y - 30, // 稍微向上扩展检测区域
|
||
button.checkWidth,
|
||
60 // 增加高度以确保捕获状态图标
|
||
);
|
||
|
||
// 加载完成和未完成的模板图像
|
||
let completedTemplate, uncompletedTemplate;
|
||
|
||
try {
|
||
completedTemplate = file.ReadImageMatSync(Datas.COMPLETED_IMAGE_PATH);
|
||
uncompletedTemplate = file.ReadImageMatSync(
|
||
Datas.UNCOMPLETED_IMAGE_PATH
|
||
);
|
||
} catch (imageError) {
|
||
log.error("加载模板图像失败: {error}", imageError);
|
||
return "unknown";
|
||
}
|
||
|
||
// 创建识别对象,使用更灵活的参数
|
||
const completedRo = RecognitionObject.TemplateMatch(
|
||
completedTemplate,
|
||
0,
|
||
0,
|
||
button.checkWidth,
|
||
60
|
||
);
|
||
const uncompletedRo = RecognitionObject.TemplateMatch(
|
||
uncompletedTemplate,
|
||
0,
|
||
0,
|
||
button.checkWidth,
|
||
60
|
||
);
|
||
|
||
// 降低匹配阈值,提高识别灵活性
|
||
completedRo.threshold = 0.65;
|
||
uncompletedRo.threshold = 0.65;
|
||
|
||
// 检测完成状态
|
||
const completedResult = checkRegion.find(completedRo);
|
||
if (!completedResult.isEmpty()) {
|
||
return "completed";
|
||
}
|
||
|
||
// 检测未完成状态
|
||
const uncompletedResult = checkRegion.find(uncompletedRo);
|
||
if (!uncompletedResult.isEmpty()) {
|
||
return "uncompleted";
|
||
}
|
||
|
||
log.warn("委托{id}状态识别失败", button.id);
|
||
return "unknown";
|
||
} catch (error) {
|
||
log.error("检测委托完成状态时出错: {error}", error.message);
|
||
return "unknown";
|
||
}
|
||
},
|
||
|
||
// 识别委托地点
|
||
recognizeCommissionLocation: async () => {
|
||
try {
|
||
log.info(
|
||
"识别委托地点 ({x}, {y}) ({width}, {height})...",
|
||
Datas.OCR_REGIONS.LOCATION.X,
|
||
Datas.OCR_REGIONS.LOCATION.Y,
|
||
Datas.OCR_REGIONS.LOCATION.X + Datas.OCR_REGIONS.LOCATION.WIDTH,
|
||
Datas.OCR_REGIONS.LOCATION.Y + Datas.OCR_REGIONS.LOCATION.HEIGHT
|
||
);
|
||
|
||
// 使用Utils.easyOCROne进行识别
|
||
const location = await Utils.easyOCROne(Datas.OCR_REGIONS.LOCATION);
|
||
|
||
if (location && location.trim()) {
|
||
return location.trim();
|
||
}
|
||
|
||
return "未知地点";
|
||
} catch (error) {
|
||
log.error("识别委托地点时出错: {error}", error.message);
|
||
return "识别失败";
|
||
}
|
||
},
|
||
|
||
// 检测是否进入委托详情界面
|
||
checkDetailPageEntered: async () => {
|
||
try {
|
||
// 尝试3次OCR识别
|
||
for (let i = 0; i < 3; i++) {
|
||
// 使用Utils.easyOCR进行识别
|
||
const results = await Utils.easyOCR(Datas.OCR_REGIONS.DETAIL_COUNTRY);
|
||
|
||
if (results.count > 0) {
|
||
// 检查OCR结果
|
||
for (let j = 0; j < results.count; j++) {
|
||
const 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}`);
|
||
return text;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有检测到,等待一会再试
|
||
await sleep(500);
|
||
}
|
||
|
||
log.info("三次OCR检测后仍未确认委托国家");
|
||
return "未知";
|
||
} catch (error) {
|
||
log.error("检测委托详情界面时出错: {error}", error.message);
|
||
return "错误";
|
||
}
|
||
},
|
||
|
||
// 识别委托列表 - 进行4个单独识别
|
||
recognizeCommissions: async (supportedCommissions) => {
|
||
try {
|
||
log.info("开始执行委托识别");
|
||
|
||
// 初始化当前委托位置变量
|
||
let currentCommissionPosition = null;
|
||
|
||
// 步骤1: 识别前3个委托(索引0-2)
|
||
log.info("步骤1: 使用识别前3个委托");
|
||
|
||
let allCommissions = [];
|
||
|
||
// 先识别前3个区域(索引0-2)
|
||
for (let regionIndex = 0; regionIndex < 3; regionIndex++) {
|
||
const region = Datas.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 {
|
||
let results = await Utils.easyOCR(region);
|
||
log.info(
|
||
"第{index}个区域OCR识别结果数量: {count}",
|
||
regionIndex + 1,
|
||
results.count
|
||
);
|
||
|
||
// 处理识别结果,取第一个有效结果
|
||
for (let i = 0; i < results.count; i++) {
|
||
try {
|
||
let result = results[i];
|
||
let text = Utils.cleanText(result.text);
|
||
if (text && text.length >= Datas.MIN_TEXT_LENGTH) {
|
||
log.info('第{index}个委托: "{text}"', regionIndex + 1, text);
|
||
|
||
// 检查委托类型
|
||
const isFightCommission =
|
||
supportedCommissions.fight.includes(text);
|
||
const isTalkCommission =
|
||
supportedCommissions.talk.includes(text);
|
||
const isSupported = isFightCommission || isTalkCommission;
|
||
const commissionType = isFightCommission
|
||
? Datas.COMMISSION_TYPE.FIGHT
|
||
: isTalkCommission
|
||
? Datas.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 (let i = 0; i < Math.min(3, allCommissions.length); i++) {
|
||
const commission = allCommissions[i];
|
||
|
||
try {
|
||
// 使用图像识别检测完成状态
|
||
const status = await CommissionsFunc.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
|
||
);
|
||
|
||
// 点击详情按钮
|
||
const detailButton =
|
||
Datas.COMMISSION_DETAIL_BUTTONS[commission.id - 1];
|
||
log.info(
|
||
"点击委托详情按钮 ({x}, {y})",
|
||
detailButton.x,
|
||
detailButton.y
|
||
);
|
||
click(detailButton.x, detailButton.y);
|
||
await sleep(700);
|
||
|
||
// 检测委托国家
|
||
const detailStatus = await CommissionsFunc.checkDetailPageEntered();
|
||
log.info(`委托国家: ${detailStatus}`);
|
||
commission.country = detailStatus;
|
||
const location =
|
||
await CommissionsFunc.recognizeCommissionLocation();
|
||
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);
|
||
await genshin.setBigMapZoomLevel(2);
|
||
// 获取地图坐标并保存
|
||
const bigMapPosition = genshin.getPositionFromBigMap();
|
||
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)
|
||
const region = Datas.OCR_REGIONS.Main_Dev[3];
|
||
let fourthCommission = null;
|
||
|
||
log.info(
|
||
"识别第4个委托区域 ({x}, {y}) ({width}, {height})",
|
||
region.X,
|
||
region.Y,
|
||
region.X + region.WIDTH,
|
||
region.Y + region.HEIGHT
|
||
);
|
||
|
||
try {
|
||
let results = await Utils.easyOCR(region);
|
||
log.info("第4个区域OCR识别结果数量: {count}", results.count);
|
||
|
||
// 处理识别结果,取第一个有效结果
|
||
for (let i = 0; i < results.count; i++) {
|
||
try {
|
||
let result = results[i];
|
||
let text = Utils.cleanText(result.text);
|
||
if (text && text.length >= Datas.MIN_TEXT_LENGTH) {
|
||
log.info('第4个委托: "{text}"', text);
|
||
|
||
// 检查委托类型
|
||
const isFightCommission =
|
||
supportedCommissions.fight.includes(text);
|
||
const isTalkCommission =
|
||
supportedCommissions.talk.includes(text);
|
||
const isSupported = isFightCommission || isTalkCommission;
|
||
const commissionType = isFightCommission
|
||
? Datas.COMMISSION_TYPE.FIGHT
|
||
: isTalkCommission
|
||
? Datas.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个委托的完成状态
|
||
const status = await CommissionsFunc.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);
|
||
|
||
// 点击详情按钮
|
||
const detailButton = Datas.COMMISSION_DETAIL_BUTTONS[3]; // 第4个按钮索引为3
|
||
log.info(
|
||
"点击委托详情按钮 ({x}, {y})",
|
||
detailButton.x,
|
||
detailButton.y
|
||
);
|
||
click(detailButton.x, detailButton.y);
|
||
await sleep(700);
|
||
|
||
// 检测是否成功进入详情界面并获取委托国家
|
||
const detailStatus =
|
||
await CommissionsFunc.checkDetailPageEntered();
|
||
log.info(`委托国家: ${detailStatus}`);
|
||
fourthCommission.country = detailStatus;
|
||
|
||
// 识别委托地点
|
||
const location =
|
||
await CommissionsFunc.recognizeCommissionLocation();
|
||
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);
|
||
await genshin.setBigMapZoomLevel(2);
|
||
// 获取地图坐标并保存
|
||
const bigMapPosition = genshin.getPositionFromBigMap();
|
||
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 (const commission of allCommissions) {
|
||
const supportStatus = commission.supported ? "✅ 支持" : "❌ 不支持";
|
||
const typeInfo = commission.type ? `[${commission.type}]` : "";
|
||
const locationInfo = commission.location
|
||
? `(${commission.location})`
|
||
: "";
|
||
const 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 [];
|
||
}
|
||
},
|
||
|
||
// 读取支持的委托列表
|
||
loadSupportedCommissions: async () => {
|
||
let supportedCommissions = {
|
||
fight: [],
|
||
talk: [],
|
||
};
|
||
|
||
try {
|
||
// 使用正确的文件读取方法
|
||
log.info("开始读取支持的委托列表: {path}", Datas.SUPPORT_LIST_PATH);
|
||
|
||
// 尝试读取文件内容
|
||
try {
|
||
const supportListContent = file.readTextSync(Datas.SUPPORT_LIST_PATH);
|
||
|
||
if (supportListContent && supportListContent.trim()) {
|
||
try {
|
||
// 解析JSON格式
|
||
const commissionData = JSON.parse(supportListContent);
|
||
supportedCommissions.fight = commissionData.fight || [];
|
||
supportedCommissions.talk = commissionData.talk || [];
|
||
|
||
log.info(
|
||
"已加载支持的战斗委托列表,共 {count} 个",
|
||
supportedCommissions.fight.length
|
||
);
|
||
log.info(
|
||
"已加载支持的对话委托列表,共 {count} 个",
|
||
supportedCommissions.talk.length
|
||
);
|
||
} catch (jsonError) {
|
||
log.error("解析委托列表JSON失败: {error}", jsonError);
|
||
}
|
||
} else {
|
||
log.warn("支持的委托列表为空");
|
||
}
|
||
} catch (readError) {
|
||
// 如果读取失败,检查文件是否存在
|
||
log.error("读取委托列表失败: {error}", readError);
|
||
|
||
// 尝试创建文件
|
||
try {
|
||
// 创建默认的JSON结构
|
||
const defaultJson = JSON.stringify(
|
||
{
|
||
fight: [],
|
||
talk: [],
|
||
},
|
||
null,
|
||
2
|
||
);
|
||
|
||
const writeResult = file.writeTextSync(
|
||
Datas.SUPPORT_LIST_PATH,
|
||
defaultJson
|
||
);
|
||
if (writeResult) {
|
||
log.info("已创建空的委托列表文件");
|
||
} else {
|
||
log.error("创建委托列表文件失败");
|
||
}
|
||
} catch (writeError) {
|
||
log.error("创建委托列表文件失败: {error}", writeError);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
log.error("处理委托列表时出错: {error}", error.message);
|
||
}
|
||
|
||
return supportedCommissions;
|
||
},
|
||
|
||
ensureDirectoryExists: async (dirPath) => {
|
||
try {
|
||
// 尝试创建目录,如果目录已存在,writeTextSync不会报错
|
||
// 创建一个临时文件来确保目录存在
|
||
const tempFilePath = `${dirPath}/.temp`;
|
||
file.writeTextSync(tempFilePath, "");
|
||
// log.info(`已确保目录存在: ${dirPath}`);
|
||
return true;
|
||
} catch (error) {
|
||
log.error(`创建目录时出错: ${error}`);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 保存委托数据到文件
|
||
saveCommissionsData: async (commissionsTable) => {
|
||
try {
|
||
log.info("保存委托数据到文件...");
|
||
|
||
// 创建JSON格式的委托数据
|
||
const commissionsData = {
|
||
timestamp: new Date().toISOString(),
|
||
commissions: commissionsTable,
|
||
};
|
||
|
||
// 保存到文件
|
||
const outputPath = `${Datas.OUTPUT_DIR}/commissions_data.json`;
|
||
try {
|
||
const jsonResult = file.writeTextSync(
|
||
outputPath,
|
||
JSON.stringify(commissionsData, null, 2)
|
||
);
|
||
if (jsonResult) {
|
||
log.info("委托数据已保存到: {path}", outputPath);
|
||
} else {
|
||
log.error("保存委托数据失败");
|
||
}
|
||
} catch (jsonError) {
|
||
log.error("保存委托数据失败: {error}", jsonError);
|
||
}
|
||
|
||
// 创建可读的文本报告
|
||
let reportContent = "# 原神每日委托识别报告\r\n";
|
||
reportContent += `生成时间: ${new Date().toLocaleString()}\r\n\r\n`;
|
||
reportContent += "## 委托列表\r\n\r\n";
|
||
|
||
for (const commission of commissionsTable) {
|
||
const supportStatus = commission.supported ? "✅ 支持" : "❌ 不支持";
|
||
const typeInfo = commission.type ? `[${commission.type}]` : "";
|
||
reportContent += `${commission.id}. ${commission.name} ${typeInfo} - ${supportStatus}\r\n`;
|
||
}
|
||
|
||
// 保存报告
|
||
const reportPath = `${Datas.OUTPUT_DIR}/commissions_report.txt`;
|
||
try {
|
||
const reportResult = file.writeTextSync(reportPath, reportContent);
|
||
if (reportResult) {
|
||
log.info("委托报告已保存到: {path}", reportPath);
|
||
} else {
|
||
log.error("保存委托报告失败");
|
||
}
|
||
} catch (reportError) {
|
||
log.error("保存委托报告失败: {error}", reportError);
|
||
}
|
||
|
||
return commissionsTable.filter((c) => c.supported);
|
||
} catch (error) {
|
||
log.error("处理委托数据时出错: {error}", error.message);
|
||
return [];
|
||
}
|
||
},
|
||
|
||
// 计算两点之间的距离
|
||
calculateDistance: (point1, point2) => {
|
||
if (
|
||
!point1 ||
|
||
!point2 ||
|
||
!point1.X ||
|
||
!point1.Y ||
|
||
!point2.x ||
|
||
!point2.y
|
||
) {
|
||
log.warn("无效的位置数据");
|
||
return Infinity;
|
||
}
|
||
return Math.sqrt(
|
||
Math.pow(point1.X - point2.x, 2) + Math.pow(point1.Y - point2.y, 2)
|
||
);
|
||
},
|
||
|
||
// 获取委托的目标坐标(从路径追踪文件中获取最后一个坐标)
|
||
getCommissionTargetPosition: async (scriptPath) => {
|
||
try {
|
||
const scriptContent = await file.readText(scriptPath);
|
||
const pathData = JSON.parse(scriptContent);
|
||
|
||
if (!pathData.positions || pathData.positions.length === 0) {
|
||
log.warn("路径追踪文件 {path} 中没有有效的坐标数据", scriptPath);
|
||
return null;
|
||
}
|
||
|
||
const lastPosition = pathData.positions[pathData.positions.length - 1];
|
||
if (!lastPosition.x || !lastPosition.y) {
|
||
log.warn(
|
||
"路径追踪文件 {path} 的最后一个路径点缺少坐标数据",
|
||
scriptPath
|
||
);
|
||
return null;
|
||
}
|
||
|
||
log.debug(
|
||
"从脚本路径 {path} 获取到目标坐标: ({x}, {y})",
|
||
scriptPath,
|
||
lastPosition.x,
|
||
lastPosition.y
|
||
);
|
||
return {
|
||
x: lastPosition.x,
|
||
y: lastPosition.y,
|
||
};
|
||
} catch (error) {
|
||
log.error("获取委托目标坐标时出错: {error}", error.message);
|
||
return null;
|
||
}
|
||
},
|
||
|
||
// 检查委托状态(通过委托名称查找位置后检测)
|
||
iscompleted: async (completedCount) => {
|
||
try {
|
||
// 记录已完成的委托数量
|
||
log.info("已完成委托数量: {completedCount}", completedCount);
|
||
|
||
const enterSuccess = await UI.enterCommissionScreen();
|
||
if (!enterSuccess) {
|
||
log.error("无法进入委托界面");
|
||
return false;
|
||
}
|
||
await sleep(900);
|
||
if (completedCount === 0) {
|
||
await UI.pageScroll(1);
|
||
const status = await CommissionsFunc.detectCommissionStatusByImage(3);
|
||
if (status === "completed") {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
} else {
|
||
const status = await CommissionsFunc.detectCommissionStatusByImage(
|
||
3 - completedCount
|
||
);
|
||
if (status === "completed") {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
log.error("检查委托完成状态失败: {error}", error.message);
|
||
try {
|
||
await genshin.returnMainUi();
|
||
} catch (exitError) {
|
||
log.warn("退出委托界面失败: {error}", exitError);
|
||
}
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 执行带分支的对话委托流程(从main_branch.js移植)
|
||
executeTalkCommissionWithBranches: async (processPath) => {
|
||
try {
|
||
log.info("开始执行对话委托流程: {path}", processPath);
|
||
|
||
// 读取流程文件
|
||
const processContent = await file.readText(processPath);
|
||
|
||
// 解析流程内容
|
||
const branches = CommissionsFunc.parseProcessBranches(processContent);
|
||
|
||
// 确定要执行的分支
|
||
const branchToExecute = await CommissionsFunc.determineBranch(branches);
|
||
|
||
if (branchToExecute) {
|
||
log.info("执行分支: {id}", branchToExecute.id);
|
||
await Execute.executeUnifiedTalkProcess(branchToExecute.steps);
|
||
} else {
|
||
log.warn("没有找到匹配的分支,执行默认流程");
|
||
// 尝试解析整个内容作为单一流程
|
||
const steps = JSON.parse(processContent);
|
||
await Execute.executeUnifiedTalkProcess(steps);
|
||
}
|
||
} catch (error) {
|
||
log.error("执行对话委托流程出错: {error}", error.message);
|
||
}
|
||
},
|
||
|
||
// 解析流程文件中的分支(从main_branch.js移植)
|
||
parseProcessBranches: (content) => {
|
||
const branches = [];
|
||
const branchRegex =
|
||
/分支:(\d+)[\s\S]*?判断方法"([^"]+)"[\s\S]*?data:"([^"]+)"([\s\S]*?)(?=分支:|$)/g;
|
||
|
||
let match;
|
||
while ((match = branchRegex.exec(content)) !== null) {
|
||
const branchId = parseInt(match[1]);
|
||
const judgmentMethod = match[2];
|
||
const judgmentData = match[3];
|
||
const stepsContent = match[4].trim();
|
||
|
||
// 解析步骤
|
||
let steps = [];
|
||
try {
|
||
// 尝试解析JSON数组
|
||
const jsonContent = `[${stepsContent}]`;
|
||
steps = JSON.parse(jsonContent);
|
||
} catch (error) {
|
||
log.warn("解析分支{id}的步骤出错: {error}", branchId, error);
|
||
continue;
|
||
}
|
||
|
||
branches.push({
|
||
id: branchId,
|
||
method: judgmentMethod,
|
||
data: judgmentData,
|
||
steps: steps,
|
||
});
|
||
}
|
||
|
||
return branches;
|
||
},
|
||
|
||
// 确定要执行的分支(从main_branch.js移植)
|
||
determineBranch: async (branches) => {
|
||
for (const branch of branches) {
|
||
switch (branch.method) {
|
||
case "坐标":
|
||
if (await CommissionsFunc.checkCoordinateMatch(branch.data)) {
|
||
return branch;
|
||
}
|
||
break;
|
||
|
||
case "任务追踪":
|
||
if (await CommissionsFunc.checkTaskMatch(branch.data)) {
|
||
return branch;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
log.warn("未知的判断方法: {method}", branch.method);
|
||
}
|
||
}
|
||
|
||
return null;
|
||
},
|
||
|
||
// 检查当前坐标是否匹配(从main_branch.js移植)
|
||
checkCoordinateMatch: async (coordData) => {
|
||
try {
|
||
const [targetX, targetY] = coordData
|
||
.split(",")
|
||
.map((c) => parseFloat(c.trim()));
|
||
|
||
// 获取当前委托位置
|
||
const playerPos = await CommissionsFunc.getCurrentCommissionPosition();
|
||
if (!playerPos) return false;
|
||
|
||
// 计算距离
|
||
const distance = CommissionsFunc.calculateDistance(playerPos, {
|
||
x: targetX,
|
||
y: targetY,
|
||
});
|
||
log.info(
|
||
"当前位置: ({x}, {y}),目标位置: ({tx}, {ty}),距离: {d}",
|
||
playerPos.x,
|
||
playerPos.y,
|
||
targetX,
|
||
targetY,
|
||
distance
|
||
);
|
||
|
||
// 如果距离小于阈值,认为匹配
|
||
return distance < 100; // 可以调整阈值
|
||
} catch (error) {
|
||
log.error("检查坐标匹配出错: {error}", error.message);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 检查当前任务是否匹配(从main_branch.js移植)
|
||
checkTaskMatch: async (taskName) => {
|
||
try {
|
||
// 识别左上角任务区域文本
|
||
const taskRegion = { X: 75, Y: 240, WIDTH: 225, HEIGHT: 60 };
|
||
const taskResults = await Utils.easyOCR(taskRegion);
|
||
|
||
// 检查是否包含目标任务名称
|
||
for (let i = 0; i < taskResults.count; i++) {
|
||
const text = taskResults[i].text;
|
||
log.info(`任务区域识别文本: ${text}`);
|
||
|
||
if (text.includes(taskName)) {
|
||
log.info(`找到匹配任务: ${taskName}`);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
log.info(`未找到匹配任务: ${taskName}`);
|
||
return false;
|
||
} catch (error) {
|
||
log.error("检查任务匹配出错: {error}", error.message);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 获取当前委托位置(辅助函数)
|
||
getCurrentCommissionPosition: async () => {
|
||
try {
|
||
// 这里可以通过多种方式获取当前位置
|
||
// 1. 从大地图获取
|
||
await genshin.setBigMapZoomLevel(2);
|
||
const bigMapPosition = genshin.getPositionFromBigMap();
|
||
if (bigMapPosition) {
|
||
return bigMapPosition;
|
||
}
|
||
|
||
// 2. 从当前委托位置变量获取(如果有的话)
|
||
if (currentCommissionPosition) {
|
||
return currentCommissionPosition;
|
||
}
|
||
|
||
log.warn("无法获取当前委托位置");
|
||
return null;
|
||
} catch (error) {
|
||
log.error("获取当前委托位置时出错: {error}", error.message);
|
||
return null;
|
||
}
|
||
},
|
||
};
|
||
|
||
const Test = async () => {
|
||
Utils.iframe(Datas.OCR_REGIONS.Main_Dev[0]);
|
||
// 角色切换步骤使用示例:
|
||
// 在process.json文件中添加如下步骤:
|
||
// {
|
||
// "type": "切换角色",
|
||
// "data": {
|
||
// "position": 1,
|
||
// "character": "枫原万叶"
|
||
// },
|
||
// "note": "切换第1号位为枫原万叶"
|
||
// }
|
||
|
||
// // 切换角色示例(可选)
|
||
// // 如果需要切换角色,可以取消注释下面的代码
|
||
// const switchRoleStep = {
|
||
// type: "切换角色",
|
||
// data: { position: 1, character: "枫原万叶" },
|
||
// note: "切换第1号位为枫原万叶",
|
||
// };
|
||
// await StepProcessor.processSwitchRole(switchRoleStep);
|
||
};
|
||
|
||
// TAG:Main
|
||
const Main = async () => {
|
||
try {
|
||
//await Identification();
|
||
//await Execute.autoNavigateToTalk("汤米","task");
|
||
|
||
log.debug("版本: {version}", VERSION);
|
||
|
||
if (skipRecognition) {
|
||
log.info("跳过识别,直接加载数据");
|
||
} else {
|
||
log.info("开始执行委托识别");
|
||
await Core.Identification();
|
||
}
|
||
|
||
// 开局准备
|
||
await Core.PrepareForLeyLineRun();
|
||
|
||
// 执行自动委托
|
||
await Core.executeCommissionTracking();
|
||
if (!prepare) {
|
||
log.info("每日委托执行完成,前往安全地点");
|
||
await genshin.tpToStatueOfTheSeven();
|
||
} else {
|
||
log.info("每日委托执行完成");
|
||
}
|
||
} catch (error) {
|
||
log.error("执行出错: {error}", error.message);
|
||
errorlog();
|
||
}
|
||
};
|
||
await Main();
|
||
})();
|