Files
bettergi-scripts-list/repo/js/AutoMine/main.js
2025-08-27 21:32:50 +08:00

447 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function get_exclude_tags() {
var tags = [];
if (settings.exclude_fights) {
tags.push("fight");
}
if (settings.exclude_natlan) {
tags.push("natlan");
}
if (settings.exclude_fontaine_underwater) {
tags.push("fontaine underwater");
}
if (settings.exclude_fontaine_terrestrial) {
tags.push("fontaine terrestrial");
}
if (settings.exclude_sumeru) {
tags.push("sumeru");
}
if (settings.exclude_inazuma) {
tags.push("inazuma");
}
if (settings.exclude_liyue) {
tags.push("liyue");
}
if (settings.exclude_chasm_underground) {
tags.push("chasm underground");
}
if (settings.exclude_mondstadt) {
tags.push("mondstadt");
}
if (settings.exclude_crystal_chunks) {
tags.push("crystal chunk");
}
if (settings.exclude_condessence_crystals) {
tags.push("condessence crystal");
}
if (settings.exclude_amethyst_lumps) {
tags.push("amethyst lump");
}
return tags;
}
function get_profile_name() {
if (!settings.profile_id) {
return null;
}
return settings.profile_id;
}
const filename_to_path_map = {};
function load_filename_to_path_map() {
var all_paths = [];
const read_dir = (path) => {
for (const i of file.readPathSync(path)) {
if (file.isFolder(i)) {
read_dir(i);
} else {
all_paths.push(i);
}
}
};
read_dir("assets/矿物");
for (const i of all_paths) {
const filename = i.replace(/^.*[\\/]/, "");
filename_to_path_map[filename] = i;
}
}
var persistent_data = {};
function load_persistent_data() {
var file_content = "";
try {
file_content = file.readTextSync("records/persistent_data.json");
} catch (error) {}
if (file_content.length !== 0) {
persistent_data = JSON.parse(file_content);
}
}
const disabled_paths = new Set();
function load_disabled_paths() {
const file_content = file.readTextSync("assets/disabled_paths.txt");
for (var l of file_content.split("\n")) {
l = l.trim();
if (l.length === 0) {
continue;
}
if (l.startsWith("//") || l.startsWith("#")) {
continue;
}
disabled_paths.add(l);
}
}
async function flush_persistent_data() {
await file.writeText("records/persistent_data.json", JSON.stringify(persistent_data, null, " "));
}
async function mark_task_finished(task_name) {
const profile_name = get_profile_name();
const profile_key = !profile_name ? "default-profile" : ("profile-" + profile_name);
if (!persistent_data.hasOwnProperty(profile_key)) {
persistent_data[profile_key] = {};
}
persistent_data[profile_key][task_name] = {
"last_run_time": Date.now(),
};
await flush_persistent_data();
}
function get_task_last_run_time(task_name) {
const profile_name = get_profile_name();
const profile_key = !profile_name ? "default-profile" : ("profile-" + profile_name);
return persistent_data[profile_key]?.[task_name]?.last_run_time || 0;
}
function is_ore_respawned(t) {
t /= 1000;
var t0 = Math.floor(t / 86400) * 86400 + 57600;
if (t0 > t) {
t0 -= 86400;
}
const respawn_time = t0 + 86400 * 3;
return respawn_time < Date.now() / 1000;
}
function get_some_tasks() {
const statistics = JSON.parse(file.readTextSync("assets/statistics.json")).data;
const exclude_tags = new Set(get_exclude_tags());
var filtered_statistics = [];
for (const [key, value] of Object.entries(statistics)) {
if (disabled_paths.has(key)) {
continue;
}
if (value.tags.some(i => exclude_tags.has(i))) {
continue;
}
if (value.statistics.avg_num_defeats > 0) {
continue;
}
if (value.statistics.avg_abnormal_exits > 0) {
continue;
}
if (!filename_to_path_map.hasOwnProperty(key)) {
continue;
}
if (!is_ore_respawned(get_task_last_run_time(key))) {
log.debug("{name} not respawned, skip", key);
continue;
}
value.statistics.avg_yield_per_min = value.statistics.avg_yield / value.statistics.avg_time_consumed * 60;
filtered_statistics.push([key, value]);
}
filtered_statistics.sort((a, b) =>
b[1].statistics.avg_yield_per_min - a[1].statistics.avg_yield_per_min
);
// We don't want to teleport around all the time. So add some spacial affinity here.
const look_ahead_num = 20;
var sorted_filtered_statistics = [];
for (var i = 0; i < filtered_statistics.length; ++i) {
if (!filtered_statistics[i]) {
continue;
}
const filename = filtered_statistics[i][0];
const value = filtered_statistics[i][1];
const group_name = value.group;
sorted_filtered_statistics.push([filename, value]);
filtered_statistics[i] = null;
for (var j = i + 1; j <= i + look_ahead_num && j < filtered_statistics.length; ++j) {
if (!filtered_statistics[j]) {
continue;
}
const filename2 = filtered_statistics[j][0];
const value2 = filtered_statistics[j][1];
const group_name2 = value2.group;
if (group_name2 === group_name) {
sorted_filtered_statistics.push([filename2, value2]);
filtered_statistics[j] = null;
}
}
}
if (sorted_filtered_statistics.length === 0) {
return [];
}
const first_out_of_group_index = sorted_filtered_statistics.findIndex(i => i[1].group !== sorted_filtered_statistics[0][1].group);
const first_group = sorted_filtered_statistics.slice(0, first_out_of_group_index);
first_group.sort((a, b) => a[0].localeCompare(b[0]));
return first_group;
}
async function get_inventory() {
const ore_image_map = {
amethyst_lumps: "assets/images/amethyst_lump.png",
crystal_chunks: "assets/images/crystal_chunk.png",
condessence_crystals: "assets/images/condessence_crystal.png",
};
await genshin.returnMainUi();
keyPress("b")
await sleep(1000);
click(964, 53);
await sleep(500);
const game_region = captureGameRegion();
const inventory_result = {
crystal_chunks: 0,
condessence_crystals: 0,
amethyst_lumps: 0
};
for (const [name, path] of Object.entries(ore_image_map)) {
let match_obj = RecognitionObject.TemplateMatch(file.ReadImageMatSync(path));
match_obj.threshold = 0.85;
match_obj.Use3Channels = true;
const match_res = game_region.Find(match_obj);
if (match_res.isExist()) {
log.debug(`Found ${name} image at (${match_res.x}, ${match_res.y})`);
const text_x = match_res.x - 0;
const text_y = match_res.y + 120;
const text_w = 120;
const text_h = 40;
const ocr_res = game_region.find(RecognitionObject.ocr(text_x, text_y, text_w, text_h));
if (ocr_res) {
inventory_result[name] = Number(ocr_res.text);
}
}
}
await genshin.returnMainUi();
return inventory_result;
}
async function run_pathing_script(name, path_state_change, current_states) {
path_state_change ||= {};
path_state_change.require ||= [];
path_state_change.add ||= [];
path_state_change.sustain ||= [];
const statistics = JSON.parse(file.readTextSync("assets/statistics.json")).data;
for (const s of path_state_change.require) {
if (!current_states.has(s)) {
log.debug("Trying to get {s}", s);
for (const [name, data] of Object.entries(statistics)) {
const add_states = data.state_change?.add || [];
if (add_states.includes(s)) {
await run_pathing_script(name, data.state_change, current_states);
break;
}
}
}
}
log.info("运行 {name}", name);
var json_content = await file.readText(filename_to_path_map[name]);
{
// set Noelle mining action
const json_obj = JSON.parse(json_content);
var modified = false;
for (const i of json_obj.positions) {
if (i.action === "mining" && !i.action_params) {
i.action = "combat_script";
i.action_params = "诺艾尔 attack(2.0)";
modified = true;
}
}
// scale underwater mining actions
if (statistics[name].tags.includes("fontaine underwater") && genshin.screenDpiScale !== 1.0) {
for (const i of json_obj.positions) {
if (i.action_params) {
const new_actions = [];
for (const a of i.action_params.split(";")) {
if (a.startsWith("moveby(")) {
const [x, y] = a.slice(7, -1).split(",");
const new_val = "moveby(" + String(Math.round(x * genshin.screenDpiScale)) + "," + String(Math.round(y * genshin.screenDpiScale)) + ")";
new_actions.push(new_val);
} else {
new_actions.push(a);
}
}
const new_action_params = new_actions.join(";");
if (new_action_params !== i.action_params) {
i.action_params = new_action_params;
modified = true;
}
}
}
}
if (modified) {
log.debug("Patched mining action");
json_content = JSON.stringify(json_obj);
}
}
const cancellation_token = dispatcher.getLinkedCancellationToken();
await pathingScript.run(json_content);
if (!cancellation_token.isCancellationRequested) {
await mark_task_finished(name);
} else {
throw new Error("Cancelled");
}
const new_states = current_states.intersection(new Set(path_state_change.sustain)).union(new Set(path_state_change.add));
current_states.clear();
for (const s of new_states) {
current_states.add(s);
}
}
async function main() {
await genshin.returnMainUi();
load_filename_to_path_map();
load_persistent_data();
load_disabled_paths();
dispatcher.addTimer(new RealtimeTimer("AutoPick"));
if (["natlan", "fontaine terrestrial", "sumeru", "inazuma", "liyue", "chasm underground", "mondstadt"].filter(i => !get_exclude_tags().includes(i)).length > 0) {
if (!Array.from(getAvatars()).includes("诺艾尔")) {
log.error("地面挖矿必须带诺艾尔");
return;
}
}
const original_inventory = await get_inventory();
const start_time = Date.now();
var target_yield = null;
var target_running_minutes = null;
var run_until_hour = null;
var run_until_minute = null;
var run_until_unix_time = null;
switch (settings.arg_mode) {
case "最速480矿":
target_yield = 480;
break;
case "挖指定数目的矿(手动填写)":
target_yield = Number(settings.arg_amount);
break;
case "挖一段时间(手动填写)":
target_running_minutes = Number(settings.arg_amount);
break;
case "挖,直到某个时间(手动填写)":
[run_until_hour, run_until_minute] = settings.arg_amount.replace("", ":").split(":").map(Number);
const current_unix_time = Date.now();
const current_cst_time = (current_unix_time / 1000 + 8 * 3600) % 86400 / 3600;
var diff_hours = run_until_hour + run_until_minute / 60 - current_cst_time;
if (diff_hours < 0) {
diff_hours += 24;
}
run_until_unix_time = current_unix_time + diff_hours * 3600 * 1000;
break;
case "挖所有矿":
break;
default:
log.error("Unknown running mode");
return;
}
log.info("已有水晶块{a}个,紫晶块{b}个,萃凝晶{c}个", original_inventory.crystal_chunks, original_inventory.amethyst_lumps, original_inventory.condessence_crystals);
if (target_yield !== null) {
log.info("将挖矿{a}个", target_yield);
} else if (target_running_minutes !== null) {
log.info("将挖矿{a}分钟", target_running_minutes);
} else if (run_until_hour !== null && run_until_minute !== null && run_until_unix_time !== null) {
const num_hours = (run_until_unix_time - Date.now()) / 1000 / 3600;
log.info("将挖矿到{h}:{m}{nh}小时后)", String(run_until_hour).padStart(2, "0"), String(run_until_minute).padStart(2, "0"), num_hours.toFixed(2));
} else {
log.info("将标挖所有矿");
}
var accurate_yield = 0;
var estimated_yield = 0;
var cached_inventory_data = null;
var finished = false;
const current_states = new Set();
while (!finished) {
const tasks = get_some_tasks();
if (tasks.length === 0) {
log.info("没有更多任务可运行,退出");
finished = true;
} else {
log.debug("Running {num} tasks as a group", tasks.length);
}
for (const [name, data] of tasks) {
cached_inventory_data = null;
try {
await run_pathing_script(name, data.state_change, current_states);
} catch (e) {
finished = true;
break;
}
estimated_yield += data.statistics.avg_yield;
if (target_yield !== null && estimated_yield >= target_yield + 5) {
const current_inventory = await get_inventory();
cached_inventory_data = current_inventory;
accurate_yield += current_inventory.crystal_chunks - original_inventory.crystal_chunks;
accurate_yield += current_inventory.condessence_crystals - original_inventory.condessence_crystals;
accurate_yield += current_inventory.amethyst_lumps - original_inventory.amethyst_lumps;
estimated_yield = accurate_yield;
}
if (target_yield !== null && accurate_yield >= target_yield) {
finished = true;
break;
}
if (target_running_minutes !== null) {
const duration_mins = (Date.now() - start_time) / 1000 / 60;
if (duration_mins > target_running_minutes) {
finished = true;
break;
}
}
if (run_until_unix_time !== null && Date.now() >= run_until_unix_time) {
finished = true;
break;
}
}
}
const end_time = Date.now();
const running_minutes = (end_time - start_time) / 1000 / 60;
var total_yield_str = [];
const latest_inventory = cached_inventory_data ? cached_inventory_data : (await get_inventory());
if (latest_inventory.crystal_chunks - original_inventory.crystal_chunks) {
total_yield_str.push(`${latest_inventory.crystal_chunks - original_inventory.crystal_chunks}水晶块`);
}
if (latest_inventory.condessence_crystals - original_inventory.condessence_crystals) {
total_yield_str.push(`${latest_inventory.condessence_crystals - original_inventory.condessence_crystals}萃凝晶`);
}
if (latest_inventory.amethyst_lumps - original_inventory.amethyst_lumps) {
total_yield_str.push(`${latest_inventory.amethyst_lumps - original_inventory.amethyst_lumps}紫晶块`);
}
if (total_yield_str.length > 0) {
total_yield_str = "收获" + total_yield_str.join("");
} else {
total_yield_str = "无收获";
}
log.info("现有水晶块{a}个,紫晶块{b}个,萃凝晶{c}个", latest_inventory.crystal_chunks, latest_inventory.amethyst_lumps, latest_inventory.condessence_crystals);
log.info("运行{m}分钟,{y}", running_minutes.toFixed(2), total_yield_str);
}
(async function() {
await main();
})();