mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-16 03:33:25 +08:00
* feat: 新增自动配置圣遗物锁定方案 * fix: clean * fix: 截图资源释放 * chore: library 清理 * chore: README 删除冒号 * fix: 区分套装配置失败与跳过状态,修正结果汇总误报 * fix: 以已完成套装数提前结束,避免跳过后继续滚动列表 * feat: 分辨率检测,自动返回主界面打开背包,README 修改 * chore: 默认延迟系数调整为 1.2
622 lines
23 KiB
JavaScript
622 lines
23 KiB
JavaScript
import { findTextInRegion, smoothDragVertical } from './lib.js';
|
||
|
||
// 延迟时间默认值(单位:毫秒)
|
||
const DEFAULT_DELAY_SHORT = 75;
|
||
const DEFAULT_DELAY_MEDIUM = 200;
|
||
const DEFAULT_DELAY_LONG = 1000;
|
||
const DEFAULT_DELAY_EXTRA_LONG = 3000;
|
||
|
||
// 从 settings 读取延迟系数,不填则使用默认值 1.2
|
||
const rawMultiplier = settings.delayMultiplier ? parseFloat(settings.delayMultiplier) : 1.2;
|
||
if (isNaN(rawMultiplier) || rawMultiplier <= 0) {
|
||
log.error("延迟系数配置错误,必须是大于 0 的数字,当前值: {value}", settings.delayMultiplier);
|
||
throw new Error("延迟系数配置错误,脚本已终止");
|
||
}
|
||
const DELAY_MULTIPLIER = rawMultiplier;
|
||
|
||
// 计算实际延迟值(默认值 × 延迟系数)
|
||
const DELAY_SHORT = Math.round(DEFAULT_DELAY_SHORT * DELAY_MULTIPLIER);
|
||
const DELAY_MEDIUM = Math.round(DEFAULT_DELAY_MEDIUM * DELAY_MULTIPLIER);
|
||
const DELAY_LONG = Math.round(DEFAULT_DELAY_LONG * DELAY_MULTIPLIER);
|
||
const DELAY_EXTRA_LONG = Math.round(DEFAULT_DELAY_EXTRA_LONG * DELAY_MULTIPLIER);
|
||
|
||
// 标记是否已检查过必须条件提示框
|
||
let hasCheckedNecessaryTip = false;
|
||
|
||
/**
|
||
* 检查 OCR 识别到的套装名称是否匹配配置中的套装
|
||
* @param {string} recognizedName - OCR 识别到的套装名称
|
||
* @param {Object} setConfig - 套装配置对象(包含 set_name 和可选的 alias 数组)
|
||
* @returns {boolean} - 是否匹配
|
||
*/
|
||
function matchSetName(recognizedName, setConfig) {
|
||
// 先精确匹配套装名称
|
||
if (recognizedName === setConfig.set_name) {
|
||
return true;
|
||
}
|
||
// 如果有别名,尝试模糊匹配(识别到的名称包含别名)
|
||
if (setConfig.alias && Array.isArray(setConfig.alias)) {
|
||
for (let alias of setConfig.alias) {
|
||
if (recognizedName.includes(alias)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 根据 OCR 识别到的套装名称查找对应的配置
|
||
* @param {string} recognizedName - OCR 识别到的套装名称
|
||
* @param {Array} config - 所有套装配置数组
|
||
* @returns {Object|null} - 匹配的套装配置,未找到返回 null
|
||
*/
|
||
function findSetConfig(recognizedName, config) {
|
||
for (let setConfig of config) {
|
||
if (matchSetName(recognizedName, setConfig)) {
|
||
return setConfig;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 检查用户选择的套装名称是否匹配配置(支持别名)
|
||
* @param {string} selectedName - 用户选择的套装名称
|
||
* @param {string} recognizedName - OCR 识别到的套装名称
|
||
* @param {Array} config - 所有套装配置数组
|
||
* @returns {boolean} - 是否匹配
|
||
*/
|
||
function isSelectedSetMatch(selectedName, recognizedName, config) {
|
||
// 精确匹配
|
||
if (selectedName === recognizedName) {
|
||
return true;
|
||
}
|
||
// 查找用户选择的套装对应的配置
|
||
let setConfig = config.find(s => s.set_name === selectedName);
|
||
if (setConfig) {
|
||
return matchSetName(recognizedName, setConfig);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 配置单个套装的所有方案
|
||
* @param {Object} setConfig - 套装配置对象
|
||
* @param {Object} uiCoords - UI坐标映射对象
|
||
* @param {boolean} overwriteExisting - 是否覆盖已有方案
|
||
* @returns {boolean|null} - true: 成功配置, false: 跳过(已有方案且未勾选覆盖), null: 错误(找不到按钮/无启用方案等)
|
||
*/
|
||
async function configureArtifactSet(setConfig, uiCoords, overwriteExisting) {
|
||
// 根据配置处理推荐方案开关
|
||
let shouldEnable = setConfig.enable_recommended_scheme;
|
||
log.debug("套装「{name}」的推荐方案配置: {value}", setConfig.set_name, shouldEnable);
|
||
|
||
// 检查当前界面状态:如果不包含"未生效",说明推荐方案是打开的
|
||
let statusResult = findTextInRegion(838, 294, 1136, 351, "未生效");
|
||
let isCurrentlyEnabled = (statusResult === null); // 不包含"未生效"说明是打开的
|
||
|
||
log.debug("当前推荐方案状态: {status}", isCurrentlyEnabled ? "已开启" : "未开启");
|
||
|
||
// 判断是否需要点击开关
|
||
if (shouldEnable !== isCurrentlyEnabled) {
|
||
log.info("步骤:切换推荐方案开关");
|
||
click(1786, 323);
|
||
await sleep(DELAY_MEDIUM);
|
||
} else {
|
||
log.debug("步骤:推荐方案状态已符合配置,无需操作");
|
||
}
|
||
|
||
// 查找并点击"编辑"按钮
|
||
let editResult = findTextInRegion(1686, 208, 1850, 256, "编辑");
|
||
if (editResult) {
|
||
log.debug("找到「编辑」按钮,坐标: ({x}, {y})", editResult.x, editResult.y);
|
||
log.info("步骤:点击编辑按钮");
|
||
click(editResult.x, editResult.y);
|
||
await sleep(DELAY_MEDIUM);
|
||
} else {
|
||
log.error("未找到「编辑」按钮");
|
||
return null;
|
||
}
|
||
|
||
// 获取所有启用的方案
|
||
let enabledPlans = setConfig.plans.filter(p => p.enabled);
|
||
if (enabledPlans.length === 0) {
|
||
log.warn("套装「{name}」未找到启用的方案,跳过", setConfig.set_name);
|
||
// 点击返回按钮
|
||
click(1840, 44);
|
||
await sleep(DELAY_MEDIUM);
|
||
return null;
|
||
}
|
||
|
||
log.debug("找到 {count} 个启用的方案", enabledPlans.length);
|
||
|
||
// 遍历所有启用的方案
|
||
for (let planIndex = 0; planIndex < enabledPlans.length; planIndex++) {
|
||
let plan = enabledPlans[planIndex];
|
||
|
||
log.debug("开始配置第 {index} 个方案", planIndex + 1);
|
||
|
||
// 如果不是第一个方案,需要切换到方案2
|
||
if (planIndex > 0) {
|
||
log.info("步骤:切换到方案 {index}", planIndex + 1);
|
||
click(129, 265);
|
||
await sleep(DELAY_LONG);
|
||
}
|
||
|
||
// 检查是否存在旧方案
|
||
let noConfigResult = findTextInRegion(1004, 499, 1253, 549, "暂无方案配置");
|
||
|
||
if (noConfigResult === null) {
|
||
// 不包含"暂无方案配置",说明已经有旧方案
|
||
if (!overwriteExisting) {
|
||
// 未勾选覆盖选项,跳过该套装
|
||
log.info("套装「{name}」已有方案配置,且未勾选覆盖选项,跳过", setConfig.set_name);
|
||
// 点击返回按钮
|
||
click(1840, 44);
|
||
await sleep(DELAY_LONG);
|
||
return false;
|
||
}
|
||
|
||
// 点击删除按钮
|
||
log.info("步骤:点击删除按钮,坐标: (1147, 1022)");
|
||
click(1147, 1022);
|
||
|
||
// 等待一下让删除确认对话框出现
|
||
await sleep(DELAY_MEDIUM);
|
||
|
||
// 点击确认按钮
|
||
log.info("步骤:点击确认按钮,坐标: (1171, 758)");
|
||
click(1171, 758);
|
||
await sleep(DELAY_MEDIUM);
|
||
} else {
|
||
log.debug("未检测到旧方案(显示「暂无方案配置」),无需删除");
|
||
}
|
||
|
||
// 点击"新建锁定方案"按钮
|
||
let newPlanResult = findTextInRegion(1563, 986, 1876, 1051, "新建锁定方案");
|
||
|
||
if (newPlanResult) {
|
||
log.debug("找到「新建锁定方案」按钮,坐标: ({x}, {y})", newPlanResult.x, newPlanResult.y);
|
||
log.info("步骤:点击「新建锁定方案」按钮");
|
||
click(newPlanResult.x, newPlanResult.y);
|
||
await sleep(DELAY_MEDIUM);
|
||
} else {
|
||
log.error("未找到「新建锁定方案」按钮");
|
||
continue;
|
||
}
|
||
|
||
// 配置当前方案
|
||
await configurePlan(plan, uiCoords);
|
||
|
||
log.debug("第 {index} 个方案配置完成", planIndex + 1);
|
||
}
|
||
|
||
// 点击返回按钮
|
||
log.info("步骤:点击返回按钮,坐标: (1840, 44)");
|
||
click(1840, 44);
|
||
await sleep(DELAY_LONG);
|
||
|
||
log.debug("套装「{name}」配置完成", setConfig.set_name);
|
||
return true; // 返回 true 表示成功配置
|
||
}
|
||
|
||
/**
|
||
* 配置单个锁定方案
|
||
* @param {Object} plan - 方案配置对象
|
||
* @param {Object} uiCoords - UI坐标映射对象
|
||
*/
|
||
async function configurePlan(plan, uiCoords) {
|
||
log.debug("开始配置方案: {name}", plan.plan_name);
|
||
|
||
// 遍历配置中的所有部位(不一定是5个)
|
||
for (let positionConfig of plan.positions) {
|
||
// 根据部位名称找到对应的UI坐标
|
||
let positionUI = uiCoords.positions.find(p => p.name === positionConfig.position_name);
|
||
|
||
if (!positionUI) {
|
||
log.error("未找到部位「{name}」的UI坐标", positionConfig.position_name);
|
||
continue;
|
||
}
|
||
|
||
log.info("配置部位: {name}", positionConfig.position_name);
|
||
|
||
// 点击部位按钮
|
||
click(positionUI.x, positionUI.y);
|
||
await sleep(DELAY_SHORT);
|
||
|
||
// 如果部位未启用,跳过配置
|
||
if (!positionConfig.enabled) {
|
||
log.info("部位 {name} 未启用,跳过", positionConfig.position_name);
|
||
continue;
|
||
}
|
||
|
||
// 配置主属性
|
||
log.debug("点击主要属性按钮");
|
||
click(uiCoords.buttons.main_attr.x, uiCoords.buttons.main_attr.y);
|
||
await sleep(DELAY_SHORT);
|
||
|
||
// 点击所有需要的主属性
|
||
let positionMainAttrs = uiCoords.main_attrs_by_position[positionConfig.position_name];
|
||
if (!positionMainAttrs) {
|
||
log.error("未找到部位 {name} 的主属性坐标映射", positionConfig.position_name);
|
||
continue;
|
||
}
|
||
|
||
// 检查是否包含"元素伤害加成"
|
||
let hasElementalDamage = positionConfig.main_attrs.includes("元素伤害加成");
|
||
|
||
for (let mainAttr of positionConfig.main_attrs) {
|
||
// 特殊处理:元素伤害加成 = 使用"全选伤害加成"按钮(包含物理+所有7种元素伤害)
|
||
if (mainAttr === "元素伤害加成") {
|
||
let coord = positionMainAttrs["全选伤害加成"];
|
||
log.debug("检测到「元素伤害加成」,使用全选按钮一次性选择所有伤害加成");
|
||
log.debug("点击全选伤害加成按钮,坐标: ({x}, {y})", coord.x, coord.y);
|
||
click(coord.x, coord.y);
|
||
await sleep(DELAY_SHORT);
|
||
} else if (mainAttr === "物理伤害加成" && hasElementalDamage) {
|
||
// 如果已经点击了"元素伤害加成"(全选),则跳过单独的"物理伤害加成"
|
||
log.debug("「物理伤害加成」已被全选包含,跳过");
|
||
} else {
|
||
// 普通主属性
|
||
if (positionMainAttrs[mainAttr]) {
|
||
let coord = positionMainAttrs[mainAttr];
|
||
log.debug("选择主属性: {attr}, 坐标: ({x}, {y})", mainAttr, coord.x, coord.y);
|
||
click(coord.x, coord.y);
|
||
await sleep(DELAY_SHORT);
|
||
} else {
|
||
log.warn("未找到主属性坐标: {attr} (部位: {pos})", mainAttr, positionConfig.position_name);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 配置副属性
|
||
log.debug("点击追加属性按钮");
|
||
click(uiCoords.buttons.sub_attr.x, uiCoords.buttons.sub_attr.y);
|
||
await sleep(DELAY_SHORT);
|
||
|
||
// 点击所有需要的副属性
|
||
for (let subAttr of positionConfig.sub_attrs) {
|
||
if (uiCoords.sub_attrs[subAttr]) {
|
||
let coord = uiCoords.sub_attrs[subAttr];
|
||
log.debug("选择副属性: {attr}, 坐标: ({x}, {y})", subAttr, coord.x, coord.y);
|
||
click(coord.x, coord.y);
|
||
await sleep(DELAY_SHORT);
|
||
|
||
// 如果该副属性是必须条件,点击右侧的必须条件按钮
|
||
if (positionConfig.necessary_sub_attrs && positionConfig.necessary_sub_attrs.includes(subAttr)) {
|
||
// 必须条件按钮在副属性右侧,X 坐标固定:第一列 1090,第二列 1810
|
||
let necessaryX = coord.x < 800 ? 1090 : 1810;
|
||
log.debug("设置必须条件: {attr}, 坐标: ({x}, {y})", subAttr, necessaryX, coord.y);
|
||
click(necessaryX, coord.y);
|
||
await sleep(DELAY_SHORT);
|
||
|
||
// 第一次点击必须条件按钮后,检查是否弹出提示框
|
||
if (!hasCheckedNecessaryTip) {
|
||
hasCheckedNecessaryTip = true;
|
||
await sleep(DELAY_MEDIUM);
|
||
let tipResult = findTextInRegion(870, 261, 1069, 338, "提示");
|
||
if (tipResult) {
|
||
log.debug("检测到提示框,点击确认关闭");
|
||
click(964, 745);
|
||
await sleep(DELAY_MEDIUM);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
log.warn("未找到副属性坐标: {attr}", subAttr);
|
||
}
|
||
}
|
||
|
||
// 配置副属性最小命中数
|
||
let minCount = positionConfig.min_count;
|
||
let countKey = `任意${minCount}条`;
|
||
|
||
if (uiCoords.sub_attr_count[countKey]) {
|
||
let coord = uiCoords.sub_attr_count[countKey];
|
||
log.debug("选择副属性命中数: {count}, 坐标: ({x}, {y})", countKey, coord.x, coord.y);
|
||
click(coord.x, coord.y);
|
||
await sleep(DELAY_SHORT);
|
||
} else {
|
||
log.warn("未找到副属性命中数坐标: {count}", countKey);
|
||
}
|
||
|
||
log.debug("部位 {name} 配置完成", positionConfig.position_name);
|
||
}
|
||
|
||
// 点击保存按钮
|
||
log.info("步骤:点击保存按钮,坐标: (1733, 1017)");
|
||
click(1733, 1017);
|
||
await sleep(DELAY_LONG);
|
||
// 再点一次关闭弹窗
|
||
log.info("步骤:关闭保存按钮的弹窗");
|
||
click(1733, 1017);
|
||
await sleep(DELAY_LONG);
|
||
|
||
}
|
||
|
||
async function main() {
|
||
// 运行前检查游戏窗口信息,并验证分辨率是否为 16:9
|
||
const width = genshin.width;
|
||
const height = genshin.height;
|
||
if (!width || !height) {
|
||
log.error("无法获取游戏窗口信息,请确保原神正在运行,脚本已终止");
|
||
return;
|
||
}
|
||
|
||
const aspectRatio = width / height;
|
||
const targetRatio = 16 / 9;
|
||
const ratioTolerance = 0.01; // 允许极小的浮点误差
|
||
|
||
log.debug("环境检测:当前游戏分辨率 {w}x{h}", width, height);
|
||
|
||
if (Math.abs(aspectRatio - targetRatio) > ratioTolerance) {
|
||
log.error("当前分辨率不是 16:9(检测到: {w}x{h}),脚本已终止", width, height);
|
||
return;
|
||
}
|
||
|
||
// 读取配置文件
|
||
const configText = await file.readText("圣遗物锁定方案信息.json");
|
||
const config = JSON.parse(configText);
|
||
|
||
// 读取UI坐标映射文件
|
||
const uiCoordsText = await file.readText("ui_coordinates.json");
|
||
const uiCoords = JSON.parse(uiCoordsText);
|
||
|
||
// 读取用户设置
|
||
let selectedSets = settings.artifactSets ? Array.from(settings.artifactSets) : []; // C# List<string> 对象,需要转换为 JS 数组
|
||
let overwriteExisting = settings.overwriteExisting; // 是否覆盖已有方案
|
||
|
||
log.debug("用户选择的套装数量: {count}", selectedSets.length);
|
||
log.debug("选择的套装: {sets}", JSON.stringify(selectedSets));
|
||
log.debug("是否覆盖已有方案: {value}", overwriteExisting);
|
||
|
||
// 如果选择了"全选",需要读取 settings.json 获取所有套装列表
|
||
if (selectedSets.includes("全选")) {
|
||
const settingsText = await file.readText("settings.json");
|
||
const settingsConfig = JSON.parse(settingsText);
|
||
const artifactSetsConfig = settingsConfig.find(s => s.name === "artifactSets");
|
||
|
||
if (artifactSetsConfig && artifactSetsConfig.options) {
|
||
// 包含所有套装(除了"全选"本身)
|
||
selectedSets = artifactSetsConfig.options.filter(name => name !== "全选");
|
||
log.debug("检测到「全选」,已展开为所有套装,共 {count} 个", selectedSets.length);
|
||
}
|
||
}
|
||
|
||
// 检查用户是否选择了至少一个套装
|
||
if (selectedSets.length === 0) {
|
||
log.error("未选择任何套装,请在设置中勾选需要配置的圣遗物套装");
|
||
return;
|
||
}
|
||
|
||
// 返回主界面
|
||
await genshin.returnMainUi();
|
||
|
||
// 打开背包
|
||
keyPress("B")
|
||
await sleep(DELAY_LONG);
|
||
|
||
log.info("即将配置 {count} 个套装", selectedSets.length);
|
||
|
||
// 第一步:点击圣遗物背包页面
|
||
log.info("步骤:点击圣遗物背包页面");
|
||
click(671, 46);
|
||
await sleep(DELAY_MEDIUM);
|
||
|
||
// 第二步:验证是否进入圣遗物页面
|
||
let verifyResult = findTextInRegion(131, 21, 231, 70, "圣遗物");
|
||
if (!verifyResult) {
|
||
log.error("未检测到「圣遗物」页面");
|
||
return;
|
||
}
|
||
|
||
// 第三步:点击进入圣遗物锁定方案入口
|
||
log.info("步骤:点击「锁定辅助」");
|
||
click(394, 1017);
|
||
await sleep(DELAY_LONG);
|
||
|
||
// 第四步:查找并点击 "套装锁定方案",来到套装选择界面
|
||
log.info("步骤:点击「套装锁定方案」");
|
||
let result = findTextInRegion(148, 696, 322, 760, "套装锁定方案");
|
||
if (!result) {
|
||
// 第一个位置没找到,尝试第二个位置
|
||
result = findTextInRegion(146, 290, 332, 366, "套装锁定方案");
|
||
}
|
||
if (result) {
|
||
log.debug("点击「套装锁定方案」,坐标: ({x}, {y})", result.x, result.y);
|
||
click(result.x, result.y);
|
||
await sleep(DELAY_LONG);
|
||
} else {
|
||
log.error("未找到「套装锁定方案」按钮");
|
||
return;
|
||
}
|
||
|
||
// 第五步:把滚动条拉到顶部
|
||
log.info("步骤:点击滚动条顶部");
|
||
click(795, 212);
|
||
await sleep(DELAY_MEDIUM);
|
||
|
||
// 第六步:遍历所有套装
|
||
let processedSets = new Set(); // 记录已处理的套装名称,避免重复
|
||
let configuredSets = []; // 记录成功配置的套装名称
|
||
let skippedSets = []; // 记录跳过的套装名称(已有方案且未勾选覆盖)
|
||
let notFoundSets = []; // 记录未找到的套装名称(OCR 未识别到)
|
||
let failedSets = []; // 记录处理失败的套装名称(执行过程中出错)
|
||
let finishedSetNames = new Set(); // 记录已完成处理的套装名称(成功/跳过/失败)
|
||
|
||
while (true) {
|
||
log.info("步骤:开始识别当前屏幕的套装");
|
||
|
||
// 获取游戏画面截图
|
||
let screen = captureGameRegion();
|
||
let listRegion = null;
|
||
|
||
try {
|
||
// 裁剪套装列表区域 (142,209) 到 (384,954)
|
||
const cropStartX = 142;
|
||
const cropStartY = 209;
|
||
const cropEndX = 384;
|
||
const cropEndY = 954;
|
||
listRegion = screen.deriveCrop(cropStartX, cropStartY, cropEndX - cropStartX, cropEndY - cropStartY);
|
||
|
||
// 使用 findMulti 进行 OCR 识别
|
||
let ocrResultList = listRegion.findMulti(RecognitionObject.ocrThis);
|
||
|
||
log.debug("识别到 {count} 个文本", ocrResultList.count);
|
||
|
||
if (ocrResultList.count === 0) {
|
||
log.warn("未识别到任何文本,列表遍历完成");
|
||
break;
|
||
}
|
||
|
||
// 记录本轮识别到的套装名称
|
||
let currentScreenSets = [];
|
||
for (let i = 0; i < ocrResultList.count; i++) {
|
||
currentScreenSets.push(ocrResultList[i].text.trim());
|
||
}
|
||
|
||
// 检查是否所有套装都已处理过(说明列表已经到底了)
|
||
let allProcessed = currentScreenSets.every(name => processedSets.has(name));
|
||
if (allProcessed) {
|
||
log.info("当前屏幕所有套装都已处理过,列表遍历完成");
|
||
break;
|
||
}
|
||
|
||
// 遍历识别到的所有文本,处理套装
|
||
for (let i = 0; i < ocrResultList.count; i++) {
|
||
let ocrResult = ocrResultList[i];
|
||
let setName = ocrResult.text.trim();
|
||
|
||
// 跳过已处理的套装
|
||
if (processedSets.has(setName)) {
|
||
log.debug("套装「{name}」已处理过,跳过", setName);
|
||
continue;
|
||
}
|
||
|
||
log.debug("识别到套装名称: {name}", setName);
|
||
|
||
// 检查该套装是否在用户选择的列表中(支持别名匹配)
|
||
let matchedSelectedName = selectedSets.find(selected => isSelectedSetMatch(selected, setName, config));
|
||
if (!matchedSelectedName) {
|
||
log.debug("套装「{name}」不在用户选择列表中,跳过", setName);
|
||
processedSets.add(setName);
|
||
continue;
|
||
}
|
||
|
||
// 在配置中查找对应的套装(支持别名匹配)
|
||
let setConfig = findSetConfig(setName, config);
|
||
|
||
// 标记为已处理(无论是否有配置)
|
||
processedSets.add(setName);
|
||
|
||
if (setConfig) {
|
||
log.info(`开始处理套装: 「${setConfig.set_name}」`);
|
||
|
||
// 计算文本左上角的屏幕绝对坐标
|
||
let absoluteX = cropStartX + ocrResult.x;
|
||
let absoluteY = cropStartY + ocrResult.y;
|
||
|
||
// 点击套装名称进入详情页
|
||
log.debug("点击套装,坐标: ({x}, {y})", absoluteX, absoluteY);
|
||
click(absoluteX, absoluteY);
|
||
await sleep(DELAY_MEDIUM);
|
||
|
||
// 配置该套装
|
||
let configured = await configureArtifactSet(setConfig, uiCoords, overwriteExisting);
|
||
|
||
if (configured === true) {
|
||
configuredSets.push(setConfig.set_name);
|
||
finishedSetNames.add(setConfig.set_name);
|
||
log.info("套装「{name}」处理完成,进度: {current}/{total}", setConfig.set_name, configuredSets.length, selectedSets.length);
|
||
|
||
// 快速结算:如果已完成数量达到用户选择的数量,提前结束
|
||
if (finishedSetNames.size >= selectedSets.length) {
|
||
log.info("已处理完所有选择的套装(含跳过/失败)");
|
||
break;
|
||
}
|
||
} else if (configured === false) {
|
||
skippedSets.push(setConfig.set_name);
|
||
finishedSetNames.add(setConfig.set_name);
|
||
log.info("套装「{name}」已跳过(已有方案且未勾选覆盖)", setConfig.set_name);
|
||
} else {
|
||
failedSets.push(setConfig.set_name);
|
||
finishedSetNames.add(setConfig.set_name);
|
||
log.error("套装「{name}」处理失败(执行过程中出现错误)", setConfig.set_name);
|
||
}
|
||
} else {
|
||
log.info("配置文件中未找到套装「{name}」,跳过", setName);
|
||
}
|
||
}
|
||
|
||
// 快速结算:如果已完成数量达到用户选择的数量,跳出外层循环
|
||
if (finishedSetNames.size >= selectedSets.length) {
|
||
break;
|
||
}
|
||
} finally {
|
||
// 释放图像资源
|
||
if (listRegion) {
|
||
listRegion.dispose();
|
||
}
|
||
screen.dispose();
|
||
}
|
||
|
||
await sleep(DELAY_LONG);
|
||
// 处理完当前屏幕的所有套装后,平滑拖动列表显示下一批
|
||
log.info("当前屏幕处理完成,拖动列表显示下一批套装");
|
||
// 从 (771,856) 移动到 (770,244)
|
||
await smoothDragVertical(771, 856, 770, 244);
|
||
}
|
||
|
||
// 检查哪些用户选择的套装未被识别到
|
||
for (let selectedName of selectedSets) {
|
||
// 检查是否已成功配置或已跳过
|
||
if (configuredSets.includes(selectedName) || skippedSets.includes(selectedName) || failedSets.includes(selectedName)) {
|
||
continue;
|
||
}
|
||
// 检查是否通过别名匹配到了
|
||
let foundByAlias = configuredSets.some(name => {
|
||
let cfg = config.find(c => c.set_name === name);
|
||
return cfg && cfg.set_name !== selectedName && matchSetName(selectedName, cfg);
|
||
}) || skippedSets.some(name => {
|
||
let cfg = config.find(c => c.set_name === name);
|
||
return cfg && cfg.set_name !== selectedName && matchSetName(selectedName, cfg);
|
||
}) || failedSets.some(name => {
|
||
let cfg = config.find(c => c.set_name === name);
|
||
return cfg && cfg.set_name !== selectedName && matchSetName(selectedName, cfg);
|
||
});
|
||
if (!foundByAlias) {
|
||
notFoundSets.push(selectedName);
|
||
}
|
||
}
|
||
|
||
// 输出结果汇总
|
||
log.info("========== 配置结果汇总 ==========");
|
||
log.info("成功配置: {count} 个套装", configuredSets.length);
|
||
|
||
if (skippedSets.length > 0) {
|
||
log.info("跳过(已有方案): {count} 个套装", skippedSets.length);
|
||
for (let name of skippedSets) {
|
||
log.info(" - {name}", name);
|
||
}
|
||
}
|
||
|
||
if (failedSets.length > 0) {
|
||
log.error("处理失败: {count} 个套装", failedSets.length);
|
||
for (let name of failedSets) {
|
||
log.error(" ✗ {name}", name);
|
||
}
|
||
}
|
||
|
||
if (notFoundSets.length > 0) {
|
||
log.warn("未能设置: {count} 个套装(未在列表中识别到)", notFoundSets.length);
|
||
for (let name of notFoundSets) {
|
||
log.warn(" ✗ {name}", name);
|
||
}
|
||
}
|
||
|
||
log.info("所有套装配置完成!");
|
||
}
|
||
|
||
main();
|