Files
bettergi-scripts-list/repo/js/MiliastraExperiencePlayback/main.js

1148 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Better Genshin Impact JavaScript
* Bundled with BetterGI CLI (https://www.npmjs.com/package/@bettergi/cli)
*
* This file is automatically generated and should not be edited.
*/
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/workflow.js
var defaultMaxAttempts = 5;
var defaultRetryInterval = 1e3;
var waitForAction = async (condition, retryAction, options) => {
const { maxAttempts = defaultMaxAttempts, retryInterval = defaultRetryInterval } = options || {};
for (let i = 0; i < maxAttempts; i++) {
if (i === 0 && condition())
return true;
await retryAction?.();
await sleep(retryInterval);
if (condition())
return true;
}
return false;
};
var waitForRegionAppear = async (regionProvider, retryAction, options) => {
return waitForAction(() => {
const region = regionProvider();
return region != null && region.isExist();
}, retryAction, options);
};
var waitForRegionDisappear = async (regionProvider, retryAction, options) => {
return waitForAction(() => {
const region = regionProvider();
return !region || !region.isExist();
}, retryAction, options);
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/asserts.js
var assertRegionAppearing = async (regionProvider, message, retryAction, options) => {
const isAppeared = await waitForRegionAppear(regionProvider, retryAction, options);
if (!isAppeared) {
throw new Error(message);
}
};
var assertRegionDisappearing = async (regionProvider, message, retryAction, options) => {
const isDisappeared = await waitForRegionDisappear(regionProvider, retryAction, options);
if (!isDisappeared) {
throw new Error(message);
}
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/exception.js
var getErrorMessage = (err) => {
if (err && "message" in err && typeof err.message === "string")
return err.message;
return err && typeof err === "object" ? JSON.stringify(err) : "Unknown error";
};
var isHostException = (err) => {
return err && "hostException" in err;
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/mouse.js
var simulateScroll = async (scrollAmountInClicks, times) => {
const script = {
macroEvents: Array(times).fill({ type: 6, mouseX: 0, mouseY: scrollAmountInClicks, time: 0 }),
info: { name: "", description: "", x: 0, y: 0, width: 1920, height: 1080, recordDpi: 1.5 }
};
await keyMouseScript.run(JSON.stringify(script));
};
var mouseScrollDown = (height, algorithm = (h) => Math.floor(h / 18)) => {
return simulateScroll(-120, algorithm(height));
};
var mouseScrollDownLines = (lines, lineHeight = 175) => {
return mouseScrollDown(lines * lineHeight);
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/ocr.js
var findImageWithinBounds = (image, x, y, w, h, config = {}) => {
const ir = captureGameRegion();
try {
const mat = typeof image === "string" ? file.readImageMatSync(image) : image;
const ro = RecognitionObject.templateMatch(mat, x, y, w, h);
if (Object.keys(config).length > 0) {
Object.assign(ro, config) && ro.initTemplate();
}
const region = ir.find(ro);
return region.isExist() ? region : void 0;
} catch (err) {
log.warn(`${err.message || err}`);
} finally {
ir.dispose();
}
};
var findFirst = (ir, ro, predicate) => {
const candidates = ir.findMulti(ro);
for (let i = 0; i < candidates.count; i++) {
if (predicate(candidates[i]))
return candidates[i];
}
return void 0;
};
var findTextWithinBounds = (text, x, y, w, h, options, config = {}) => {
const { ignoreCase = true, contains = false } = options || {};
const searchText = ignoreCase ? text.toLowerCase() : text;
const ir = captureGameRegion();
try {
const ro = RecognitionObject.ocr(x, y, w, h);
if (Object.keys(config).length > 0) {
Object.assign(ro, config) && ro.initTemplate();
}
return findFirst(ir, ro, (region) => {
const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
return isMatch && region.isExist();
});
} catch (err) {
log.warn(`${err.message || err}`);
} finally {
ir.dispose();
}
};
var findTextWithinListView = async (text, listView, matchOptions, retryOptions, config = {}) => {
const { x, y, w, h, lineHeight, scrollLines = 1, paddingX = 10, paddingY = 10 } = listView;
const { maxAttempts = 30, retryInterval = 1e3 } = retryOptions || {};
const findTargetText = () => findTextWithinBounds(text, x, y, w, h, matchOptions, config);
let lastTextRegion;
const isReachedBottom = () => {
const textRegion = findFirst(captureGameRegion(), RecognitionObject.ocr(x, y, w, h), (region) => {
return region.isExist() && region.text.trim().length > 0;
});
if (textRegion) {
if (lastTextRegion?.text === textRegion.text && Math.abs(textRegion.y - lastTextRegion.y) < lineHeight) {
return true;
} else {
lastTextRegion = textRegion;
return false;
}
}
return true;
};
const isTextFoundOrBottomReached = await waitForAction(() => findTargetText() != void 0 || isReachedBottom(), async () => {
moveMouseTo(x + w - paddingX, y + paddingY);
await sleep(50);
await mouseScrollDownLines(scrollLines, lineHeight);
}, { maxAttempts, retryInterval });
return isTextFoundOrBottomReached ? findTargetText() : void 0;
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/misc.js
var deepMerge = (...objects) => {
const isPlainObject = (input) => input?.constructor === Object;
return objects.reduce((result, obj) => {
return Object.entries(obj).reduce((acc, [key, value]) => {
const recursive = isPlainObject(acc[key]) && isPlainObject(value);
acc[key] = recursive ? deepMerge(acc[key], value) : value;
return acc;
}, result);
}, {});
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/time.js
var getNextDay4AM = () => {
const now = /* @__PURE__ */ new Date();
const result = new Date(now);
result.setHours(4, 0, 0, 0);
const daysUntilNextDay = now.getHours() < 4 ? 0 : 1;
result.setDate(now.getDate() + daysUntilNextDay);
return result;
};
var getNextMonday4AM = () => {
const now = /* @__PURE__ */ new Date();
const result = new Date(now);
result.setHours(4, 0, 0, 0);
const currentDay = now.getDay();
const daysUntilNextMonday = currentDay === 1 && now.getHours() < 4 ? 0 : 8 - currentDay;
result.setDate(now.getDate() + daysUntilNextMonday);
return result;
};
var parseDuration = (duration) => {
return {
h: Math.floor(duration / 36e5),
m: Math.floor(duration % 36e5 / 6e4),
s: Math.floor(duration % 6e4 / 1e3),
ms: Math.floor(duration % 1e3)
};
};
var formatDurationAsClock = (duration) => {
return Object.values(parseDuration(duration)).slice(0, 3).map((num) => String(num).padStart(2, "0")).join(":");
};
var formatDurationAsReadable = (duration) => {
return Object.entries(parseDuration(duration)).filter(([, value]) => value > 0).map(([unit, value]) => `${value}${unit}`).join(" ");
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/progress.js
var ProgressTracker = class {
total = 0;
current = 0;
startTime = Date.now();
formatter;
interval;
lastPrintTime = 0;
constructor(total, config) {
const { formatter, interval = 3e3 } = config || {};
this.total = total;
this.formatter = formatter || this.defaultFormatter;
this.interval = interval;
}
defaultFormatter = (logger, message, progress) => {
logger("[🚧 {pct} ⏳ {eta}]: {msg}", progress.formatted.percentage.padStart(6), progress.current > 0 && progress.elapsed > 0 ? progress.formatted.remaining : "--:--:--", message);
};
tick(options) {
const { increment = 1, message, force = false } = options || {};
this.current = Math.min(this.current + increment, this.total);
if (message)
this.print(message, force);
return this.current === this.total;
}
complete(message) {
this.current = this.total;
this.print(message, true);
}
reset() {
this.current = 0;
this.startTime = Date.now();
this.lastPrintTime = 0;
}
print(message, force = false, logger = log.info) {
if (force || this.shouldPrint()) {
this.formatter(logger, message, this.getProgress());
this.printed();
}
}
shouldPrint() {
return Date.now() - this.lastPrintTime >= this.interval;
}
printed() {
this.lastPrintTime = Date.now();
}
getProgress() {
const percentage = this.current / this.total;
const elapsed = Date.now() - this.startTime;
const average = this.current > 0 ? elapsed / this.current : 0;
const remaining = (this.total - this.current) * average;
return {
current: this.current,
total: this.total,
percentage,
elapsed,
average,
remaining,
formatted: {
percentage: `${(percentage * 100).toFixed(1)}%`,
elapsed: formatDurationAsReadable(elapsed),
average: formatDurationAsReadable(average),
remaining: formatDurationAsClock(remaining)
}
};
}
};
// node_modules/.pnpm/@bettergi+utils@0.1.19/node_modules/@bettergi/utils/dist/store.js
var useStore = (name) => {
const filePath = `store/${name}.json`;
const obj = (() => {
try {
const storeFiles = [...file.readPathSync("store")].map((path) => path.replace(/\\/g, "/"));
if (!storeFiles.includes(filePath))
throw new Error("File does not exist");
const text = file.readTextSync(filePath);
return JSON.parse(text);
} catch {
return {};
}
})();
const createProxy = (target, parentPath = []) => {
if (typeof target !== "object" || target === null) {
return target;
}
return new Proxy(target, {
get: (target2, key) => {
const value = Reflect.get(target2, key);
return typeof value === "object" && value !== null ? createProxy(value, [...parentPath, key]) : value;
},
set: (target2, key, value) => {
const success = Reflect.set(target2, key, value);
if (success) {
Promise.resolve().then(() => {
file.writeTextSync(filePath, JSON.stringify(obj, null, 2));
});
}
return success;
},
deleteProperty: (target2, key) => {
const success = Reflect.deleteProperty(target2, key);
if (success) {
Promise.resolve().then(() => {
file.writeTextSync(filePath, JSON.stringify(obj, null, 2));
});
}
return success;
}
});
};
return createProxy(obj);
};
var useStoreWithDefaults = (name, defaults) => {
const store2 = useStore(name);
Object.assign(store2, deepMerge(defaults, store2));
return store2;
};
// src/config.ts
//! 用户脚本设置
var userConfig = {
//! 每周任务相关设置
room: settings.room || "20134075027",
playbacks: (settings.playbacks || "通关回放1.json,通关回放2.json").replace(//g, ",").split(",").map((str) => str.trim()).filter(Boolean),
expPerAttempt: Math.max(1, Number(settings.expPerAttempt || "20")),
deleteStageSave: settings.deleteStageSave ?? false,
deleteStageSaveKeyword: settings.deleteStageSaveKeyword || "深渊100层",
expWeeklyLimit: Math.max(1, Number(settings.expWeeklyLimit || "4000")),
force: settings.force ?? false,
thisAttempts: Math.max(0, Number(settings.thisAttempts || "0")),
//! 每日任务相关设置
dailyEnabled: settings.dailyEnabled ?? false,
dailyRooms: (settings.dailyRooms || "24429042323,28644538672").replace(//g, ",").split(",").map((str) => str.trim()).filter(Boolean),
dailyPlaybacks: (settings.dailyPlaybacks || "通关回放1.json,通关回放2.json;60秒按1通关.json").replace(//g, ",").replace(//g, ";").split(";").map((str) => str.trim()).filter(Boolean).reduce((arr, room) => {
const files = room.split(",").map((str) => str.trim()).filter(Boolean);
if (files.length > 0) arr.push(files);
return arr;
}, []),
dailyLimit: Math.max(1, Number(settings.dailyLimit || "1")),
dailyForce: settings.dailyForce ?? false,
goToTeyvat: settings.goToTeyvat ?? true
};
//! 脚本数据存储
var store = useStoreWithDefaults("data", {
weekly: { expGained: 0, attempts: 0 },
daily: { attempts: 0 },
nextWeek: getNextMonday4AM().getTime(),
nextDay: getNextDay4AM().getTime()
});
// src/modules/regions.ts
//! 通用:查找确认按钮
var findConfirmBtn = () => {
return findTextWithinBounds("确认", 480, 720, 960, 145);
};
//! 通用:查找标题文字
var findHeaderTitle = (title, contains) => {
return findTextWithinBounds(title, 0, 0, 300, 95, { contains });
};
//! 通用:查找底部按钮文字
var findBottomBtnText = (text, contains) => {
return findTextWithinBounds(text, 0, 980, 1920, 100, { contains });
};
//! 通用:查找关闭对话框按钮
var findCloseDialog = () => {
const img = "assets/UI_BtnIcon_Close.png";
const iro = findImageWithinBounds(img, 480, 216, 960, 648, { useMask: true, threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
//! 通用:点击空白处区域继续位置
var clickToContinue = () => {
click(900, 1050);
};
//! 查找抽卡按钮(判断处于大世界条件一)
var findGachaBtn = () => {
const img = "assets/UI_BtnIcon_Gacha.png";
const iro = findImageWithinBounds(img, 960, 0, 960, 80, { useMask: true, threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
//! 查找推荐奇域按钮(判断处于大世界条件二)
var findBeyondRecommendBtn = () => {
const img = "assets/UI_BtnIcon_Beyond_Recommend.png";
const iro = findImageWithinBounds(img, 960, 0, 960, 80, { useMask: true, threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
//! 查找奇域大厅按钮(判断处于奇域大厅)
var findBeyondHallBtn = () => {
const img = "assets/UI_BtnIcon_Beyond_Hall.png";
const iro = findImageWithinBounds(img, 200, 0, 150, 100, { useMask: true, threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
//! 房间:查找搜索奇域按钮
var findAllWonderlandsBtn = () => {
return findTextWithinBounds("搜索", 1320, 0, 600, 95, { contains: true });
};
//! 房间:查找奇域搜索输入框
var findSearchWonderlandInput = () => {
return findTextWithinBounds("搜索", 0, 120, 1920, 60, { contains: true });
};
//! 房间:查找奇域搜索输入框清除按钮
var findClearInputBtn = () => {
return findTextWithinBounds("清除", 0, 120, 1920, 60);
};
//! 房间:查找搜索奇域按钮
var findSearchWonderlandBtn = () => {
return findTextWithinBounds("搜索", 0, 120, 1920, 60, { contains: true });
};
//! 房间:查找搜索过于频繁提示
var findSearchWonderlandThrottleMsg = () => {
return findTextWithinBounds("过于频繁", 0, 0, 1920, 300, { contains: true });
};
//! 房间:查找第一个奇域搜索结果名称
var findFirstSearchResultText = () => {
const ir = captureGameRegion();
const ro = RecognitionObject.ocr(240, 390, 300, 50);
return (() => {
const list = ir.findMulti(ro);
for (let i = 0; i < list.count; i++) {
if (list[i] && list[i].isExist()) {
return list[i].text;
}
}
})();
};
//! 房间:点击选择第一个搜索结果位置
var clickToChooseFirstSearchResult = () => {
click(330, 365);
};
//! 房间:查找进入房间快捷键按钮
var findEnterRoomShortcut = () => {
return findTextWithinBounds("房间", 1580, 110, 320, 390, { contains: true });
};
//! 房间:查找退出房间按钮
var findLeaveRoomBtn = () => {
const img = "assets/UI_Icon_Leave_Right.png";
const iro = findImageWithinBounds(img, 1570, 0, 350, 100);
iro?.drawSelf("group_img");
return iro;
};
//! 房间:查找跳转大厅按钮
var findGoToLobbyBtn = () => {
return findTextWithinBounds("大厅", 880, 840, 1040, 110, {
contains: true
});
};
//! 房间:查找创建房间按钮
var findCreateRoomBtn = () => {
return findTextWithinBounds("房间", 960, 140, 960, 70, { contains: true });
};
//! 房间:点击加入准备区位置
var clickToPrepare = () => {
click(770, 275);
};
//! 房间:查找加入准备区提示
var findPrepareMsg = () => {
return findTextWithinBounds("加入准备", 576, 432, 768, 216, {
contains: true
});
};
//! 存档:查找奇域收藏
var findBeyondFavoritesBtn = () => {
return findTextWithinBounds("收藏", 0, 880, 200, 200, {
contains: true
});
};
//! 存档:查找管理关卡按钮
var findManageStagesBtn = () => {
return findTextWithinBounds("管理", 1320, 0, 600, 95, { contains: true });
};
//! 存档:查找编辑关卡存档按钮
var findEditStageSaveBtn = () => {
return findTextWithinBounds("管理", 1220, 980, 700, 100);
};
//! 存档:查找要删除的存档位置
var findSaveToDeletePos = (keyword) => findTextWithinListView(
keyword,
{
x: 210,
y: 250,
w: 1650,
h: 710,
scrollLines: 7,
lineHeight: 95
},
{ contains: true }
);
//! 存档:查找局外存档列头
var findExternalSaveColumnPos = () => {
return findTextWithinBounds("局外", 55, 190, 1810, 50, { contains: true });
};
//! 存档:查找删除局外存档复选框已选中状态
var findDeleteExternalSaveChecked = (colPos) => {
const img = "assets/Checkbox_Checked.png";
const iro = findImageWithinBounds(img, colPos, 250, 290, 710, {
threshold: 0.6,
use3Channels: false
});
iro?.drawSelf("group_img");
return iro;
};
//! 存档:查找删除关卡存档按钮
var findDeleteStageSaveBtn = () => {
return findTextWithinBounds("删除所选", 1220, 980, 700, 100);
};
//! 关卡:查找关卡退出按钮
var findStageEscBtn = () => {
const img = "assets/UI_Icon_Leave.png";
const iro = findImageWithinBounds(img, 0, 0, 100, 100, { threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
//! 关卡:查找中断挑战按钮
var findExitStageBtn = () => {
return findTextWithinBounds("中断挑战", 576, 324, 768, 432);
};
//! 关卡:查找奇域等级提升页面
var findSkipLevelUpMsg = () => {
return findTextWithinBounds("空白处", 610, 950, 700, 60, { contains: true });
};
//! 退出:查找返回提瓦特按钮
var findGotTeyvatBtn = () => {
return findTextWithinBounds("返回", 1500, 0, 300, 95, { contains: true });
};
//! 纪游:查找诸界纪游按钮
var findBeyondBattlepassBtn = () => {
const img = "assets/UI_BtnIcon_Beyond_Battlepass.png";
const iro = findImageWithinBounds(img, 960, 0, 960, 80, { useMask: true, threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
//! 纪游:查找纪游开屏动画
var findBeyondBattlepassPopup = () => {
return findTextWithinBounds("奖励一览", 0, 0, 960, 1080, { contains: true });
};
//! 纪游:查找领取奖励按钮
var findFetchRewardBtn = () => {
const img = "assets/UI_Img_UGCCultivateReward_FetchHint.png";
const iro = findImageWithinBounds(img, 1670, 100, 250, 880, { useMask: true, threshold: 0.75 });
iro?.drawSelf("group_img");
return iro;
};
// src/modules/lobby.ts
//! 判断是否处于奇域大厅
var isInLobby = () => findBeyondHallBtn() !== void 0;
//! 判断是否处于提瓦特大陆
var isInTeyvat = () => {
return findGachaBtn() !== void 0 && findBeyondRecommendBtn() !== void 0;
};
//! 从提瓦特前往公共大厅
//! 退出大厅返回提瓦特大陆
var exitLobbyToTeyvat = async () => {
if (!userConfig.goToTeyvat) return;
if (isInTeyvat()) {
log.warn("已处于提瓦特大陆,跳过");
return;
}
log.info("打开当前大厅...");
await assertRegionAppearing(
() => findHeaderTitle("大厅", true),
"打开当前大厅超时",
() => {
keyPress("VK_F2");
},
{ maxAttempts: 10, retryInterval: 2e3 }
);
log.info("返回提瓦特大陆...");
const done = await waitForAction(
isInTeyvat,
() => {
findGotTeyvatBtn()?.click();
findConfirmBtn()?.click();
},
{ maxAttempts: 120 }
);
if (!done) throw new Error("返回提瓦特大陆超时");
};
// src/modules/reawrd.ts
//! 领取诸界纪游经验
var fetchBattlepassExp = async () => {
//! 确保处于大厅内
if (!isInLobby()) {
log.warn("不在奇域大厅内,跳过领取诸界纪游经验");
return;
}
if (!findBeyondBattlepassBtn()) {
log.warn("诸界纪游已结束,跳过领取诸界纪游经验");
return;
}
//! 打开诸界纪游界面
await assertRegionAppearing(
() => findHeaderTitle("纪游", true),
"打开诸界纪游界面超时",
() => {
keyPress("VK_F4");
//! 关闭纪游开屏动画(如果弹出)
if (findBeyondBattlepassPopup()) {
keyPress("VK_ESCAPE");
}
},
{ maxAttempts: 5, retryInterval: 2e3 }
);
//! 跳转到任务界面
await assertRegionAppearing(
() => findHeaderTitle("任务", true),
"打开诸界纪游任务界面超时",
() => {
keyPress("VK_E");
},
{ maxAttempts: 5, retryInterval: 2e3 }
);
//! 点击一键领取
await assertRegionDisappearing(
() => findBottomBtnText("领取", true),
"领取诸界纪游经验超时",
async () => {
//! 重复确认,防止误领纪游奖励(部件礼箱会卡流程)而不是经验
if (findHeaderTitle("任务", true)) {
findBottomBtnText("领取", true)?.click();
clickToContinue();
await sleep(1e3);
clickToContinue();
}
},
{ maxAttempts: 5, retryInterval: 3e3 }
);
await genshin.returnMainUi();
};
//! 领取日活奖励
var fetchCultivateReward = async () => {
//! 确保处于大厅内
if (!isInLobby()) {
log.warn("不在奇域大厅内,跳过领取日活奖励");
return;
}
//! 打开奇趣盛邀
await assertRegionAppearing(
() => findHeaderTitle("盛邀", true),
"打开任务书超时",
async () => {
keyPress("VK_F1");
await sleep(2e3);
if (findHeaderTitle("盛邀", true) === void 0) {
keyPress("VK_E");
}
},
{ maxAttempts: 10, retryInterval: 1e3 }
);
//! 仅领取妙思觅索奖励(巧趣醒转奖励里有部件礼箱会卡流程)
await assertRegionDisappearing(
findFetchRewardBtn,
"领取妙思觅索奖励超时",
async () => {
const reward = findFetchRewardBtn();
if (reward) {
reward.click();
clickToContinue();
await sleep(1e3);
clickToContinue();
}
},
{ maxAttempts: 5, retryInterval: 2e3 }
);
await genshin.returnMainUi();
};
// src/modules/room.ts
var isInRoom = () => findHeaderTitle("房间", true) !== void 0;
//! 打开人气奇域
var goToRecommendedWonderlands = async () => {
log.info("打开人气奇域界面...");
await assertRegionAppearing(
() => findHeaderTitle("人气", true),
"打开人气奇域界面超时",
() => {
keyPress("VK_F6");
}
);
};
//! 创建并进入奇域房间
var createRoom = async (room) => {
await goToRecommendedWonderlands();
log.info("打开全部奇域界面...");
await assertRegionAppearing(
() => findHeaderTitle("搜索", true),
"打开全部奇域界面超时",
() => {
findAllWonderlandsBtn()?.click();
}
);
await sleep(1500);
//! 记录搜索前的第一个奇域名称
let iwnt;
let wi = 0;
while (iwnt === void 0) {
if (wi > 20) break;
iwnt = findFirstSearchResultText();
await sleep(500);
wi += 1;
}
if (iwnt === void 0) throw new Error("加载全部奇域列表超时");
log.info("搜索前的第一个奇域名称: {iwnt}", iwnt);
log.info("粘贴奇域关卡文本: {room}", room);
await assertRegionAppearing(findClearInputBtn, "粘贴关卡文本超时", () => {
const input = findSearchWonderlandInput();
if (input) {
input.click();
inputText(room);
}
});
//! 等待搜索结果变化
let fswnt;
log.info("搜索奇域关卡: {room}", room);
await waitForAction(
() => {
if (fswnt === void 0) return false;
//! 检测搜索过于频繁提示
if (findSearchWonderlandThrottleMsg()) return true;
//! 检测搜索结果是否变化
return fswnt.toLocaleLowerCase().trim() !== iwnt.toLocaleLowerCase().trim();
},
async () => {
const searchBtn = findSearchWonderlandBtn();
if (searchBtn) {
searchBtn.click();
await sleep(200);
searchBtn.click();
}
await sleep(500);
fswnt = findFirstSearchResultText();
},
{ maxAttempts: 30, retryInterval: 200 }
);
log.info("打开奇域介绍...");
await assertRegionAppearing(
findCreateRoomBtn,
"打开奇域介绍超时",
() => {
const goToLobbyButton = findGoToLobbyBtn();
if (goToLobbyButton) {
log.info("当前不在大厅,前往大厅...");
goToLobbyButton.click();
} else {
log.info("选择第一个奇域关卡...");
clickToChooseFirstSearchResult();
}
},
{ maxAttempts: 60 }
);
log.info("创建并进入房间...");
await assertRegionAppearing(
() => findHeaderTitle("房间", true),
"创建并进入房间超时",
() => {
findCreateRoomBtn()?.click();
},
{ maxAttempts: 60 }
);
};
//! 进入奇域房间
var enterRoom = async (room) => {
const inLobby = isInLobby();
if (inLobby) {
const enterButton = findEnterRoomShortcut();
if (enterButton) {
log.info("当前已存在房间,进入房间...", room);
await assertRegionAppearing(
() => findHeaderTitle("房间", true),
"进入房间超时",
() => {
keyPress("VK_P");
}
);
return;
}
}
log.info("当前不在房间内,创建房间...", room);
await createRoom(room);
};
//! 离开房间
var leaveRoom = async () => {
//! 当前在大厅,且存在房间
if (isInLobby() && findEnterRoomShortcut() !== void 0 || isInRoom()) {
log.info("当前存在房间,离开房间...");
//! 先进入房间
await assertRegionAppearing(
() => findHeaderTitle("房间", true),
"进入房间超时",
() => {
keyPress("VK_P");
}
);
//! 离开房间
await assertRegionAppearing(
findBeyondHallBtn,
"离开房间超时",
async () => {
findLeaveRoomBtn()?.click();
await sleep(1e3);
findConfirmBtn()?.click();
},
{ maxAttempts: 5 }
);
}
};
// src/modules/stage.ts
//! 已有的执行通关回放文件列表
var availablePlaybackFiles = () => {
return [...file.readPathSync("assets/playbacks")].map((path) => path.replace(/\\/g, "/"));
};
var playStage = async (playbacks) => {
//! 等待进入关卡
const ok = await waitForAction(
() => findStageEscBtn() !== void 0 || findBottomBtnText("返回大厅") !== void 0,
async () => {
findBottomBtnText("开始游戏")?.click();
findBottomBtnText("准备", true)?.click();
//! 判断是否已经加入准备区
if (findPrepareMsg()) {
log.info("加入准备区...");
await assertRegionDisappearing(findPrepareMsg, "等待加入准备区提示消失超时");
clickToPrepare();
}
},
{ maxAttempts: 60 }
);
if (!ok) throw new Error("进入关卡超时");
//! 直接通关结算的关卡(不会进入关卡)
if (findBottomBtnText("返回大厅")) {
await exitStageToLobby();
return;
}
//! 关闭游戏说明对话框
await assertRegionDisappearing(
findCloseDialog,
"关闭游戏说明对话框超时",
() => {
findCloseDialog()?.click();
},
{ maxAttempts: 10, retryInterval: 500 }
);
//! 执行随机通关回放文件
await execStagePlayback(playbacks);
await sleep(3e3);
//! 退出关卡返回大厅
await exitStageToLobby();
};
//! 执行通关回放文件(随机抽取)
var execStagePlayback = async (playbacks) => {
const file2 = playbacks[Math.floor(Math.random() * playbacks.length)];
log.info("执行通关回放文件: {file}", file2);
await keyMouseScript.runFile(file2);
};
//! 退出关卡
var exitStage = async () => {
if (findStageEscBtn() === void 0) return;
log.warn("关卡超时,尝试退出关卡...");
await assertRegionAppearing(
findExitStageBtn,
"等待中断挑战按钮出现超时",
() => {
keyPress("VK_ESCAPE");
},
{ maxAttempts: 10, retryInterval: 1e3 }
);
await assertRegionAppearing(
findBeyondHallBtn,
"返回大厅超时",
async () => {
//! 点击 “中断挑战” 按钮
findExitStageBtn()?.click();
//! 点击底部 “返回大厅” 按钮
findBottomBtnText("返回大厅")?.click();
},
{ maxAttempts: 60 }
);
await genshin.returnMainUi();
};
//! 退出关卡返回大厅
var exitStageToLobby = async () => {
if (isInLobby()) {
log.warn("已处于奇域大厅,跳过");
return;
}
log.info("退出关卡返回大厅...");
const done = await waitForAction(
isInLobby,
async () => {
//! 跳过奇域等级提升页面奇域等级每逢11、21、31、41级时出现加星页面
if (findSkipLevelUpMsg()) {
clickToContinue();
}
//! 点击底部 “返回大厅” 按钮
findBottomBtnText("返回大厅")?.click();
},
{ maxAttempts: 60 }
);
if (!done) {
await exitStage();
throw new Error("退出关卡返回大厅超时");
}
};
// src/workflows/daily.ts
var execDailyTask = async () => {
if (!userConfig.dailyEnabled) {
log.warn("未启用执行每日通关任务,跳过");
return;
}
//! 确保通关回放文件存在
if (userConfig.dailyRooms.length !== userConfig.dailyPlaybacks.length) {
log.warn("每日奇域关卡数量与通关回放文件池数量不匹配,跳过");
return;
}
const files = availablePlaybackFiles();
const mappings = {};
for (let i = 0; i < userConfig.dailyRooms.length; i++) {
const room = userConfig.dailyRooms[i];
const playbacks = userConfig.dailyPlaybacks[i].map((file2) => `assets/playbacks/${file2}`).filter((path) => files.includes(path));
if (playbacks.length === 0) {
log.warn(
"房间 {room} 未找到任何通关回放文件,请确保已录制回放并拷贝到 assets/playbacks 目录下",
room
);
return;
}
mappings[room] = playbacks;
}
//! 新的一天开始,重置经验值数据
if (Date.now() >= store.nextDay) {
store.daily = { attempts: 0 };
store.nextDay = getNextDay4AM().getTime();
}
//! 检查当日通关次数是否已达上限
if (store.daily.attempts >= userConfig.dailyLimit) {
if (userConfig.dailyForce) {
log.warn("当日通关次数已达上限,强制执行");
} else {
log.warn("当日通关次数已达上限,跳过执行");
return;
}
}
//! 计算需要进行的尝试次数
let attempts = userConfig.dailyLimit - store.daily.attempts;
attempts = attempts > 0 ? attempts : 1;
//! 创建进度追踪器
const tracker = new ProgressTracker(attempts * userConfig.dailyRooms.length);
//! 迭代奇域关卡列表
try {
for (let i = 0; i < attempts; i++) {
//! 迭代尝试
try {
for (const room of userConfig.dailyRooms) {
//! 离开当前所在房间(如果存在)
await leaveRoom();
tracker.print(`开始当日第 ${store.daily.attempts + 1} 次奇域挑战...`);
//! 进入房间
await enterRoom(room);
//! 游玩关卡
await playStage(mappings[room]);
//! 更新进度
tracker.tick({ increment: 1 });
}
} catch (err) {
//! 发生主机异常(如:任务取消异常等),无法再继续执行
if (isHostException(err)) throw err;
//! 发生脚本流程异常,尝试退出关卡(如果在关卡中)
await exitStage();
log.error("脚本执行出错: {error}", getErrorMessage(err));
}
//! 一轮关卡执行结束,更新数据存储
store.daily.attempts += 1;
}
//! 领取诸界纪游经验
await fetchBattlepassExp();
//! 领取日活奖励
await fetchCultivateReward();
} catch (err) {
//! 发生主机异常(如:任务取消异常等),无法再继续执行
if (isHostException(err)) throw err;
log.error("脚本执行出错: {error}", getErrorMessage(err));
}
await genshin.returnMainUi();
};
// src/modules/save.ts
//! 进入管理关卡存档界面
var goToManageStageSave = async () => {
//! 打开人气奇域
await goToRecommendedWonderlands();
//! 打开奇域收藏->管理关卡
await assertRegionAppearing(
findEditStageSaveBtn,
"打开编辑关卡存档按钮超时",
async () => {
//! 点击奇域收藏
findBeyondFavoritesBtn()?.click();
await sleep(300);
//! 点击管理关卡
findManageStagesBtn()?.click();
await sleep(300);
},
{ maxAttempts: 5 }
);
};
//! 删除关卡存档
var deleteStageSave = async () => {
if (!userConfig.deleteStageSave || userConfig.deleteStageSaveKeyword.trim() === "") {
log.info("未启用删除关卡存档,跳过");
return;
}
try {
//! 进入管理关卡存档界面
await goToManageStageSave();
//! 选中要删除的关卡的局外存档
const stagePos = await findSaveToDeletePos(userConfig.deleteStageSaveKeyword);
if (stagePos === void 0) {
log.warn("未找到要删除的关卡存档,跳过");
return;
}
stagePos?.drawSelf("group_text");
const colPos = findExternalSaveColumnPos();
if (colPos === void 0) {
log.warn("无法确定关卡的局外存档列位置,跳过");
return;
}
//! 进入编辑模式
await assertRegionDisappearing(
findEditStageSaveBtn,
"进入编辑模式超时",
() => {
keyPress("VK_F");
},
{ maxAttempts: 5 }
);
//! 计算勾选框位置并点击
const [cx, cy] = [(colPos.x * 2 + colPos.width) / 2, stagePos.y + 40];
await assertRegionAppearing(
() => findDeleteExternalSaveChecked(colPos.x),
"勾选要删除的局外存档超时",
() => {
click(Math.ceil(cx), Math.ceil(cy));
},
{ maxAttempts: 5, retryInterval: 1500 }
);
//! 点击删除所选按钮
await assertRegionDisappearing(
() => findDeleteExternalSaveChecked(colPos.x),
"删除关卡存档超时",
async () => {
//! 特征较为脆弱,多次确认,确保成功删除
findConfirmBtn()?.click();
await sleep(500);
findConfirmBtn()?.click();
findDeleteStageSaveBtn()?.click();
await sleep(1e3);
findConfirmBtn()?.click();
await sleep(500);
findConfirmBtn()?.click();
},
{
maxAttempts: 5
}
);
} catch (err) {
if (isHostException(err)) throw err;
log.warn("删除关卡存档失败: {error}", getErrorMessage(err));
} finally {
//! 返回大厅
await genshin.returnMainUi();
}
};
// src/workflows/weekly.ts
var execWeeklyTask = async () => {
//! 确保通关回放文件存在
const files = availablePlaybackFiles();
const playbacks = userConfig.playbacks.map((file2) => `assets/playbacks/${file2}`).filter((path) => files.includes(path));
if (playbacks.length === 0) {
log.warn("未找到任何通关回放文件,请确保已录制回放并拷贝到 assets/playbacks 目录下");
return;
}
//! 新的一周开始,重置经验值数据
if (Date.now() >= store.nextWeek) {
store.weekly = { expGained: 0, attempts: 0 };
store.nextWeek = getNextMonday4AM().getTime();
}
//! 检查本周经验值是否已达上限
if (store.weekly.expGained >= userConfig.expWeeklyLimit) {
if (userConfig.force) {
log.warn("本周获取经验值已达上限,强制执行");
} else {
log.warn("本周获取经验值已达上限,跳过执行");
return;
}
}
//! 计算本次本周剩余可获取经验值
let expRemaining = userConfig.expWeeklyLimit - store.weekly.expGained;
expRemaining = expRemaining > 0 ? expRemaining : userConfig.expWeeklyLimit;
//! 计算需要进行的尝试次数
let attempts = Math.ceil(expRemaining / userConfig.expPerAttempt);
attempts = userConfig.thisAttempts > 0 ? userConfig.thisAttempts : attempts;
//! 离开当前所在房间(如果存在)
await leaveRoom();
//! 创建进度追踪器
const tracker = new ProgressTracker(attempts);
//! 迭代尝试
try {
for (let i = 0; i < attempts; i++) {
tracker.print(`开始本周第 ${store.weekly.attempts + 1} 次奇域挑战...`);
//! 删除关卡存档
await deleteStageSave();
//! 进入房间
await enterRoom(userConfig.room);
//! 游玩关卡
await playStage(playbacks);
//! 关卡结束,更新数据存储
store.weekly.attempts += 1;
store.weekly.expGained += userConfig.expPerAttempt;
tracker.tick({ increment: 1 });
//! 本周已获取经验值达到上限,跳出循环
if (store.weekly.expGained >= userConfig.expWeeklyLimit) {
if (!userConfig.force) {
log.warn("本周已获取经验值达到上限,停止执行");
break;
}
}
}
} catch (err) {
//! 发生主机异常(如:任务取消异常等),无法再继续执行
if (isHostException(err)) throw err;
//! 发生脚本流程异常,尝试退出关卡(如果在关卡中)
await exitStage();
log.error("脚本执行出错: {error}", getErrorMessage(err));
}
await genshin.returnMainUi();
};
// main.ts
(async function() {
//! 初始化游戏环境
setGameMetrics(1920, 1080, 1.5);
await genshin.returnMainUi();
//! 执行每周任务
await execWeeklyTask();
//! 执行每日任务
await execDailyTask();
//! 返回提瓦特大陆
await exitLobbyToTeyvat();
})();