js:AutoStoryLoader 1.3 (#1958)

This commit is contained in:
xoipz
2025-09-20 22:41:58 +08:00
committed by GitHub
parent eedd6d80af
commit 732f3d89e8
17 changed files with 412 additions and 987 deletions

View File

@@ -1,14 +1,15 @@
# 自动剧情加载器 - 使用说明
## 项目宗旨
自动剧情加载器是专为委托开发者设计的**脚本调试与编辑工具**,旨在提升委托开发效率,简化测试流程
自动剧情加载器是加载流程文件的工具
> **重要声明**
> 1. 本项目**不会将任何非委托任务流程脚本上传至脚本仓库**
> 1. 本项目**不会将任何会影响到本体的任务流程脚本上传至脚本仓库**
> 2. **禁止任何人用于商业用途**,禁止在任何场合展示或宣传自动任务效果
> 3. **不提倡**上传任何任务流程脚本到公开平台
> 4. 工具仅提供技术框架,**不包含任何自动游戏剧情内容**
> 5. 请勿在任何官方平台米游社、B站、微博等讨论本工具
> 6. 严禁在官方直播、动态或社区提及本工具相关内容
> 7. 本文档的最终解释权归星野所有
**免责声明**
1. **使用风险**

View File

@@ -1,13 +1,29 @@
(async function () {
// 版本和编译信息
const VERSION = "1.1";
const BUILD_TIME = "2025.08.26";
const VERSION = "1.3";
const BUILD_TIME = "2025.09.20";
// 定义识别对象
const paimonMenuRo = RecognitionObject.TemplateMatch(
file.ReadImageMatSync("Data/RecognitionObject/paimon_menu.png"),
0,
0,
genshin.width / 3.0,
genshin.width / 5.0
);
// 读取设置
const team = settings.team || "";
const elementTeam = settings.elementTeam || "";
const selectedProcess = settings.process_selector || "刷新剧情列表";
// 判断是否在主界面的函数
const isInMainUI = () => {
let captureRegion = captureGameRegion();
let res = captureRegion.Find(paimonMenuRo);
return !res.isEmpty();
};
async function errorlog() {
// 输出版本和编译时间信息
log.info("=".repeat(20));
@@ -253,7 +269,7 @@
},
// 处理等待主界面步骤
processWaitMainUI: async (isInMainUI) => {
processWaitMainUI: async () => {
for (let i = 0; i < 120; i++) {
if (isInMainUI()) {
log.info("检测到已返回主界面,结束等待");
@@ -387,171 +403,6 @@
}
},
// 处理角色切换步骤
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 {
@@ -613,16 +464,6 @@
);
},
等待: 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,
@@ -644,33 +485,10 @@
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);
log.info("地址检测当前版本用不了,请联请联系作者获取最新版");
},
委托描述检测: 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);
},
@@ -748,62 +566,129 @@
// 对话处理模块 - 处理自动对话相关功能
const DialogProcessor = {
// 执行优化的自动对话
executeOptimizedAutoTalk: async (
extractedName = null,
skipCount = 5,
customPriorityOptions = null,
customNpcWhiteList = null,
isInMainUI
) => {
// 使用传入的参数,不再加载默认配置
const effectivePriorityOptions = customPriorityOptions || [];
const effectiveNpcWhiteList = customNpcWhiteList || [];
/**
* 执行自动对话处理
* @param {string|string[]} data - NPC名称
* @returns {Promise<void>}
*/
executeAutoTalk: async (data) => {
// 使用传入的参数,不再加载默认配置
// 确保 effectiveNpcWhiteList 始终是一个数组,并过滤掉空字符串
let effectiveNpcWhiteList = [];
if (Array.isArray(data)) {
effectiveNpcWhiteList = data.filter(name => name && name.trim() !== "");
} else if (data && data.trim() !== "") {
effectiveNpcWhiteList = [data];
}
log.info(`白名单NPC: ${JSON.stringify(effectiveNpcWhiteList)}`);
await genshin.returnMainUi();
// 初始化
keyPress("V");
await sleep(1000);
// 初始触发剧情 - 识别人名并点击
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 extractedName = null;
let retryCount = 0;
const maxRetries = 3;
// 尝试提取任务人名
let name = UIUtils.extractName(text);
if (name) {
extractedName = name;
log.info(`提取到人名: ${extractedName}`);
break;
// 处理任务提示重试逻辑
while (retryCount < maxRetries) {
// 人名区域OCR识别
const nameRegion = { X: 75, Y: 240, WIDTH: 225, HEIGHT: 60 };
let nameResults = await Utils.easyOCR(nameRegion);
// 检查是否有"任务提示"
let hasTaskHint = false;
for (let i = 0; i < nameResults.count; i++) {
let text = nameResults[i].text;
log.info(`任务区域识别文本: ${text}`);
if (text.includes("任务提示")) {
log.info("检测到任务提示等待4秒后重试");
hasTaskHint = true;
break;
}
// 尝试提取任务人名(排除空字符串)
let name = UIUtils.extractName(text);
if (name && name.trim() !== "") {
extractedName = name;
log.info(`提取到人名: ${extractedName}`);
break;
}
}
// 如果有任务提示,等待后重试
if (hasTaskHint) {
retryCount++;
if (retryCount >= maxRetries) {
log.warn("已达到最大重试次数,跳过任务提示");
break;
}
await sleep(4000);
continue;
}
// 如果没有任务提示,跳出循环
break;
}
// 对话选项区域OCR识别
const dialogRegion = { X: 1150, Y: 300, WIDTH: 350, HEIGHT: 400 };
nameResults = await Utils.easyOCR(dialogRegion);
let dialogResults = await Utils.easyOCR(dialogRegion);
let clickedWhitelistNPC = false;
let clickedExtractedName = false;
// 如果对话区域没有人名,尝试按键操作
if (dialogResults.count === 0) {
log.info("对话区域未识别到文本,尝试按键操作");
// 尝试按下W键并重新OCR
keyPress("W");
await sleep(500);
dialogResults = await Utils.easyOCR(dialogRegion);
// 如果仍然没有人名尝试按下A键
if (dialogResults.count === 0) {
keyPress("A");
await sleep(500);
dialogResults = await Utils.easyOCR(dialogRegion);
}
// 如果仍然没有人名尝试按下S键
if (dialogResults.count === 0) {
keyPress("S");
await sleep(500);
dialogResults = await Utils.easyOCR(dialogRegion);
}
// 如果仍然没有人名尝试按下D键
if (dialogResults.count === 0) {
keyPress("D");
await sleep(500);
dialogResults = await Utils.easyOCR(dialogRegion);
}
}
// 处理人名区域的OCR结果
if (nameResults.count > 0) {
log.info(`人名区域识别到 ${nameResults.count} 个文本`);
if (dialogResults.count > 0) {
log.info(`对话区域识别到 ${dialogResults.count} 个文本`);
// 首先尝试点击白名单中的NPC
for (let i = 0; i < nameResults.count; i++) {
let text = nameResults[i].text;
let res = nameResults[i];
for (let i = 0; i < dialogResults.count; i++) {
let text = dialogResults[i].text;
let res = dialogResults[i];
log.info(
"人名区域识别到{text}:位置({x},{y},{h},{w})",
"对话区域识别到{text}:位置({x},{y},{h},{w})",
res.text,
res.x,
res.y,
res.width,
res.Height
res.height
);
// 检查是否包含白名单中的NPC名称
for (let j = 0; j < effectiveNpcWhiteList.length; j++) {
if (text.includes(effectiveNpcWhiteList[j])) {
@@ -822,9 +707,9 @@
// 如果没有点击白名单NPC尝试点击包含提取到的人名的选项
if (!clickedWhitelistNPC && extractedName) {
for (let i = 0; i < nameResults.count; i++) {
let text = nameResults[i].text;
let res = nameResults[i];
for (let i = 0; i < dialogResults.count; i++) {
let text = dialogResults[i].text;
let res = dialogResults[i];
if (text.includes(extractedName)) {
log.info(`点击包含提取到任务人名的选项: ${text}`);
keyDown("VK_MENU");
@@ -846,96 +731,10 @@
await sleep(500);
}
// 重复执行自动剧情,直到返回主界面
let maxAttempts = 100; // 设置最大尝试次数,防止无限循环
let attempts = 0;
let repetition = 0;
let oldcount = 1;
await sleep(1000);
log.info("开始执行自动剧情");
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");
keyPress("VK_ESCAPE");
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},但未检测到返回主界面`);
}
await StepProcessor.processWaitMainUI();
},
};
@@ -1128,12 +927,7 @@
return false;
}
} catch (jsonError) {
// 如果不是JSON格式按简单格式处理
const lines = processContent
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0);
processSteps = lines;
throw jsonError;
}
return processSteps;
},
@@ -1159,96 +953,6 @@
}
},
// 自动导航到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,
@@ -1346,37 +1050,12 @@
// 处理单个步骤
processStep: async (step, context) => {
if (typeof step === "string") {
// 简单格式处理
await Execute.processStringStep(step, context);
} else if (typeof step === "object") {
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) {
@@ -1387,69 +1066,14 @@
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
step
) => {
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
);
await DialogProcessor.executeAutoTalk(step.data);
},
};
@@ -1703,53 +1327,87 @@
// await StepProcessor.processSwitchRole(switchRoleStep);
};
//Main
const Main = async () => {
log.debug("版本: {version}", VERSION);
try {
if (selectedProcess === "刷新剧情列表") {
// 刷新操作扫描所有process.json并更新设置
await refreshProcessList();
log.info("委托列表已刷新,请重新选择并运行");
} else {
// 解析选中的委托路径
const pathParts = selectedProcess.split('-');
// 免责声明内容(分批显示)
const DISCLAIMER_PARTS = [
"1. 没有自动过任务的功能,一切后果由使用者自行承担",
"2. 禁止任何人用于商业用途,禁止在任何场合展示或宣传自动任务效果",
"3. 不提倡上传任何任务流程脚本到公开平台",
"4. 用户应自行承担使用风险,并严格遵守《原神》用户协议。因使用本脚本导致的损失,开发者概不负责",
"5. 严禁在任何官方平台米游社、B站、微博等讨论本工具",
"6. 严禁在官方直播、动态或社区提及本工具相关内容"
];
// 确保至少有两个文件夹层级
if (pathParts.length < 2) {
throw new Error("无效的委托路径格式");
}
// 提取最后两个文件夹名
const folder1 = pathParts[pathParts.length - 2];
const folder2 = pathParts[pathParts.length - 1];
// 设置动态基础路径(倒数第二个文件夹之前的所有部分)
Datas.TALK_PROCESS_BASE_PATH = "process/" + pathParts.slice(0, pathParts.length - 2).join('/');
log.info("执行任务: {path}", selectedProcess);
log.debug("基础路径: {basePath}", Datas.TALK_PROCESS_BASE_PATH);
log.debug("文件夹1: {folder1}, 文件夹2: {folder2}", folder1, folder2);
log.info("启用自动剧情");
dispatcher.AddTrigger(new RealtimeTimer("AutoSkip"));
if (!settings.noSkip) {
log.info("启用自动拾取");
dispatcher.AddTrigger(new RealtimeTimer("AutoPick"));
}
if (!settings.noEat) {
log.info("启用自动吃药");
dispatcher.AddTrigger(new RealtimeTimer("AutoEat"));
}
await switchPartyIfNeeded(team);
await Execute.executeTalkCommission(folder1, folder2);
dispatcher.ClearAllTriggers();
// 检查免责声明
async function checkDisclaimer() {
if (!settings.disclaimer_accepted || settings.disclaimer_confirm_text !== "同意") {
log.warn("请先阅读并同意免责声明");
// 分批显示免责声明
for (let j = 0; j < 5; j++) {
for (let i = 0; i < DISCLAIMER_PARTS.length; i++) {
log.info(DISCLAIMER_PARTS[i]);
await sleep(200); // 每条信息间隔0.5秒
}
} catch (error) {
log.error("执行出错: {error}", error.message);
errorlog();
}
};
log.error("请勾选'我同意不传播、不跳脸'并在输入框中输入'同意'");
return false;
}
return true;
}
const Main = async () => {
log.debug("版本: {version}", VERSION);
try {
// 检查免责声明(除了刷新列表操作)
if (selectedProcess !== "刷新剧情列表" && !await checkDisclaimer()) {
return;
}
if (selectedProcess === "刷新剧情列表") {
// 刷新操作扫描所有process.json并更新设置
await refreshProcessList();
log.info("委托列表已刷新,请重新选择并运行");
} else {
// 解析选中的委托路径
const pathParts = selectedProcess.split('-');
// 确保至少有两个文件夹层级
if (pathParts.length < 2) {
throw new Error("无效的委托路径格式");
}
// 提取最后两个文件夹名
const folder1 = pathParts[pathParts.length - 2];
const folder2 = pathParts[pathParts.length - 1];
// 设置动态基础路径(倒数第二个文件夹之前的所有部分)
Datas.TALK_PROCESS_BASE_PATH = "process/" + pathParts.slice(0, pathParts.length - 2).join('/');
log.info("执行任务: {path}", selectedProcess);
log.debug("基础路径: {basePath}", Datas.TALK_PROCESS_BASE_PATH);
log.debug("文件夹1: {folder1}, 文件夹2: {folder2}", folder1, folder2);
log.info("启用自动剧情");
dispatcher.AddTrigger(new RealtimeTimer("AutoSkip"));
if (!settings.noSkip) {
log.info("启用自动拾取");
dispatcher.AddTrigger(new RealtimeTimer("AutoPick"));
}
if (!settings.noEat) {
log.info("启用自动吃药");
dispatcher.AddTrigger(new RealtimeTimer("AutoEat"));
}
await switchPartyIfNeeded(team);
await Execute.executeTalkCommission(folder1, folder2);
dispatcher.ClearAllTriggers();
}
} catch (error) {
log.error("执行出错: {error}", error.message);
errorlog();
}
};
// 刷新委托列表(保留完整路径结构)
async function refreshProcessList() {

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "自动剧情加载器",
"version": "1.1",
"version": "1.3",
"tags": ["剧情","任务"],
"description": "剧情自动加载器一键挂机过剧情:自动寻路、对话、战斗(需任务脚本支持),脚本拖进调度器就能跑,仅提供技术框架,完全体须搭配需任务脚本使用,详情见说明文档",
"authors": [

View File

@@ -1 +0,0 @@
1-1.json

View File

@@ -1,11 +0,0 @@
1.json
2.json
F
3.json
F
4.json
F
5.json
F

View File

@@ -1,23 +0,0 @@
--1.json
F
:
--2.json
F
:
--3.json
F
50000

View File

@@ -1,112 +0,0 @@
{
"info": {
"name": "父亲能做的事-晨曦酒庄",
"type": "collect",
"author": "11",
"version": "1.0",
"description": "父亲能做的事-晨曦酒庄-1",
"map_name": "Teyvat",
"bgi_version": "0.45.0"
},
"positions": [
{
"id": 1,
"action": "",
"move_mode": "walk",
"type": "teleport",
"x": -578.81640625,
"y": 1853.37255859375,
"action_params": ""
},
{
"id": 2,
"x": -491.6826171875,
"y": 1825.15087890625,
"type": "path",
"move_mode": "dash",
"action": "",
"action_params": ""
},
{
"id": 3,
"x": -485.435546875,
"y": 1822.61767578125,
"type": "path",
"move_mode": "fly",
"action": "",
"action_params": ""
},
{
"id": 4,
"x": -445.2705078125,
"y": 1805.80078125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 5,
"x": -439.673828125,
"y": 1793.24267578125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 6,
"x": -420.3369140625,
"y": 1789.75439453125,
"type": "path",
"move_mode": "run",
"action": "",
"action_params": ""
},
{
"id": 7,
"x": -407.6767578125,
"y": 1793.21533203125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 8,
"x": -392.833984375,
"y": 1797.24267578125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 9,
"x": -380.150390625,
"y": 1801.69287109375,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 10,
"x": -374.2587890625,
"y": 1793.12841796875,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 11,
"x": -376.1328125,
"y": 1789.58935546875,
"type": "target",
"move_mode": "walk",
"action": "",
"action_params": ""
}
]
}

View File

@@ -1,67 +0,0 @@
{
"info": {
"name": "父亲能做的事-晨曦酒庄-2",
"type": "collect",
"author": "11",
"version": "1.0",
"description": "父亲能做的事-晨曦酒庄-2",
"map_name": "Teyvat",
"bgi_version": "0.45.0"
},
"positions": [
{
"id": 1,
"x": -749.4365234375,
"y": 2263.11474609375,
"action": "",
"move_mode": "walk",
"action_params": "",
"type": "teleport"
},
{
"id": 2,
"x": -748.0517578125,
"y": 2264.512451171875,
"action": "",
"move_mode": "fly",
"action_params": "",
"type": "path"
},
{
"id": 3,
"x": -719.00390625,
"y": 2301.57080078125,
"action": "stop_flying",
"move_mode": "fly",
"action_params": "",
"type": "path"
},
{
"id": 4,
"x": -705.7412109375,
"y": 2353.3037109375,
"action": "",
"move_mode": "dash",
"action_params": "",
"type": "path"
},
{
"id": 5,
"x": -697.658203125,
"y": 2357.437255859375,
"action": "",
"move_mode": "walk",
"action_params": "",
"type": "path"
},
{
"id": 6,
"x": -684.8525390625,
"y": 2355.17578125,
"action": "combat_script",
"move_mode": "walk",
"action_params": "attack,wait(1),walk(w,0.2),wait(2),keypress(f),wait(14),walk(w,1.6),wait(2),walk(a,0.6),wait(1),moveby(286,0),walk(w,6.3),wait(2),walk(d,0.2),wait(2),keypress(s),",
"type": "path"
}
]
}

View File

@@ -1,112 +0,0 @@
{
"info": {
"name": "父亲能做的事-晨曦酒庄",
"type": "collect",
"author": "11",
"version": "1.0",
"description": "父亲能做的事-晨曦酒庄-1",
"map_name": "Teyvat",
"bgi_version": "0.45.0"
},
"positions": [
{
"id": 1,
"action": "",
"move_mode": "walk",
"type": "teleport",
"x": -578.81640625,
"y": 1853.37255859375,
"action_params": ""
},
{
"id": 2,
"x": -491.6826171875,
"y": 1825.15087890625,
"type": "path",
"move_mode": "dash",
"action": "",
"action_params": ""
},
{
"id": 3,
"x": -485.435546875,
"y": 1822.61767578125,
"type": "path",
"move_mode": "fly",
"action": "",
"action_params": ""
},
{
"id": 4,
"x": -445.2705078125,
"y": 1805.80078125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 5,
"x": -439.673828125,
"y": 1793.24267578125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 6,
"x": -420.3369140625,
"y": 1789.75439453125,
"type": "path",
"move_mode": "run",
"action": "",
"action_params": ""
},
{
"id": 7,
"x": -407.6767578125,
"y": 1793.21533203125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 8,
"x": -392.833984375,
"y": 1797.24267578125,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 9,
"x": -380.150390625,
"y": 1801.69287109375,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 10,
"x": -374.2587890625,
"y": 1793.12841796875,
"type": "path",
"move_mode": "walk",
"action": "",
"action_params": ""
},
{
"id": 11,
"x": -376.1328125,
"y": 1789.58935546875,
"type": "target",
"move_mode": "walk",
"action": "",
"action_params": ""
}
]
}

View File

@@ -1,14 +0,0 @@
1.json
F
2.json
3.json
F
4.json
5.json
F
6.json
72.json
F

View File

@@ -1,33 +0,0 @@
[
{
"type": "地图追踪",
"data": "接取任务.json",
"note": "执行第一个地图追踪步骤"
},
{
"type": "自动任务",
"data": {
"action": "enable",
"taskType": "AutoSkip"
},
"note": "开启自动剧情"
},
{
"type": "按键",
"data": "F",
"note": "按下F键"
},
{
"type": "等待",
"data": 20
},
{
"type": "按键",
"data": "F",
"note": "按下F键"
},
{
"type": "等待返回主界面",
"note": "等待对话完成"
}
]

View File

@@ -1,58 +0,0 @@
{
"info": {
"authors": [
{
"links": "",
"name": "chairui"
}
],
"bgi_version": "0.45.0",
"description": "蒙德世界任务《致自由之风》",
"enable_monster_loot_split": false,
"last_modified_time": 1756120148497,
"map_match_method": "",
"map_name": "Teyvat",
"name": "致自由之风",
"tags": [],
"type": "collect",
"version": "1.0"
},
"positions": [
{
"action": "",
"action_params": "",
"id": 1,
"move_mode": "walk",
"type": "teleport",
"x": -749.4375,
"y": 2263.06494140625
},
{
"action": "",
"action_params": "",
"id": 2,
"move_mode": "fly",
"type": "path",
"x": -750.76171875,
"y": 2263.576904296875
},
{
"action": "",
"action_params": "",
"id": 3,
"move_mode": "fly",
"type": "path",
"x": -753.986328125,
"y": 2264.576171875
},
{
"action": "stop_flying",
"action_params": "",
"id": 4,
"move_mode": "fly",
"type": "path",
"x": -817.0703125,
"y": 2286.75927734375
}
]
}

View File

@@ -0,0 +1,52 @@
.json
F
F
.json
F
F
.json
F
F
.json
F
F
.json
F
F
.json
F
F
.json
F
F
.json
F
F
.json
F
F
.json
.json
2.json
.json
.json
.json
.json

View File

@@ -0,0 +1,85 @@
-1.json
-2.json
-3.json
-4.json
-5.json
-6.json
-7.json
F
-8.json
-9.json
-10.json
-11.json
.json
-12.json
-13.json
-14.json
-15.json
-16.json
-17.json
-18.json
-19.json
-20.json
-21.json
-22.json
-23.json
-20.json
-24.json
-25.json
-26.json
-27.json
.json
-28.json
-29.json
-30.json
.json

View File

@@ -0,0 +1,47 @@
-31.json
-32.json
-33.json
-1.json
-2.json
-3.json
-4.json
-5.json
-6.json
-7.json
-8.json
-9.json
-10.json
-11.json
E
-12.json
-13.json
-14.json
-15.json
-16.json
-17.json
-18.json
-19.json

View File

@@ -1,15 +1,27 @@
[
{
"name": "disclaimer_accepted",
"type": "checkbox",
"label": "我同意不传播、不跳脸",
"default": false
},
{
"name": "disclaimer_confirm_text",
"type": "input-text",
"label": "请输入'同意'以确认免责声明",
"default": "纳西妲世界第一"
},
{
"name": "process_selector",
"type": "select",
"label": "可执行剧情列表",
"options": [
"刷新剧情列表",
"示例-度假村欢聚总在悠闲第1天",
"示例-度假村欢聚总在悠闲第2天",
"示例-父亲能做的事-晨曦酒庄",
"示例-皮皮潘大赛",
"致自由之风-蒙德城"
"新建文件夹-铛铛铛,大危机(风马牛)",
"馋馋你们-5.x主线 第五章 第五幕",
"馋馋你们-6.0主线 第一章 雪浪与苍林之舞",
"馋馋你们-6.0主线 第二章 尘与灯的挽歌",
"馋馋你们-用不了馋馋你们,这个星野怎么这么坏"
],
"default": "刷新剧情列表"
},