mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-27 05:19:51 +08:00
447 lines
16 KiB
JavaScript
447 lines
16 KiB
JavaScript
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();
|
||
})();
|