Skip to content

Instantly share code, notes, and snippets.

@80sVectorz
Last active August 3, 2023 11:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save 80sVectorz/b76021b59316cd0ca93ad6944d238b1d to your computer and use it in GitHub Desktop.
Save 80sVectorz/b76021b59316cd0ca93ad6944d238b1d to your computer and use it in GitHub Desktop.
Recursive scan terminal command for outputing an ascii tree representation of the network.
/*
-- RSCAN --
VERSION 2.5
Bitburner script: https://github.com/bitburner-official/bitburner-src https://store.steampowered.com/app/1812820/Bitburner/
Recursive scan terminal command for displaying an ascii art tree representation of the full network:
rscan --detailed
-------------------Network Tree-------------------|-|Balance|-|Hack Chance|-|Security LVL|-|Hacking LVL|-|Root?|
home----------------------------------------------| $701.293m 100% 1% 1 [X]
├─n00dles | $2.984k 18% 83% 1 [X]
├─foodnstuff--------------------------------------| $69.937k 0% 100% 1 [X]
│ └─CSEC | $0.000 96% 1% 60 [X]
. . . . ... ... ... ...
Add as command: alias rscan="run {path to rscan}"
Optionaly install parse goblin for better argument parsing support: https://gist.github.com/80sVectorz/606c4afd91e06d852393d4a66392646a
For more info use: rscan --help
Created by 80sVectorz: https://gist.github.com/80sVectorz/b76021b59316cd0ca93ad6944d238b1d
*/
function find_greatest_depth(tree, depth, greatest_depth) {
greatest_depth = Math.max(depth, greatest_depth);
for (var i = 0; i < tree.length; i++) {
if (Array.isArray(tree[i])) {
greatest_depth = find_greatest_depth(tree[i], depth + 1, greatest_depth);
}
}
return greatest_depth;
}
function find_longest_name(tree, longest_name) {
for (var i = 0; i < tree.length; i++) {
if (Array.isArray(tree[i])) {
longest_name = find_longest_name(tree[i], longest_name);
} else {
longest_name = Math.max(tree[i].length, longest_name);
}
}
return longest_name;
}
/** @param {NS} ns */
function rscan(ns, prev_res, depth, passed_servers) {
var results = [];
for (var i = 0; i < prev_res.length; i++) {
if (Array.isArray(prev_res[i])) {
results.push(rscan(ns, prev_res[i], depth + 1, passed_servers));
} else {
if (passed_servers.includes(prev_res[i])) {
continue;
}
results.push(prev_res[i]);
passed_servers.push(prev_res[i]);
var res = ns.scan(prev_res[i]);
if (res.length > 1) {
results.push(rscan(ns, res, depth + 1, passed_servers));
}
}
}
return results;
}
/** @param {NS} ns */
function format_results(ns, results, depth, detailed, current_roots, detail_margin, n) {
var n = n;
let lines = [];
let last_node = 0;
for (var i = 0; i < results.length; i++) {
if (!Array.isArray(results[i])) {
last_node = i;
}
}
for (var i = 0; i < results.length; i++) {
if (Array.isArray(results[i])) {
var out = format_results(ns, results[i], depth + 1, detailed, current_roots, detail_margin, n);
n = out[1];
lines.push(out[0]);
} else {
n++;
var deco = `├─`;
deco = `${current_roots.slice(1, depth).join("").replaceAll("0", " ").replaceAll("1", "│ ")}${deco}`;
if (i == last_node) {
deco = deco.replace('├', '└');
current_roots[depth] = "0";
} else {
current_roots[depth] = "1";
}
if (depth == 0) {
deco = ``;
}
if (n == 1 && detailed) {
var balance = "|Balance|";
var hack_chance = "|Hack Chance|";
var security_lvl = "|Security LVL|";
var hacking_lvl = "|Hacking LVL|";
var root_access = "|Root?|";
var center = Math.round((current_roots.length * 2 + detail_margin) / 2);
var body = `${"-".repeat(center - 6)}Network Tree`.padEnd(current_roots.length * 2 + detail_margin, "-");
lines.push(`\n${body}|-${balance}-${hack_chance}-${security_lvl}-${hacking_lvl}-${root_access}`);
}
if (detailed) {
var balance = ns.formatNumber(ns.getServerMoneyAvailable(results[i]), 3).padEnd(6 + 1 + 1 + 6);//digits+ . +size symbol+margin
var hack_chance = ns.formatPercent(ns.hackAnalyzeChance(results[i]), 0).padEnd(3 + 1 + 10);//digits+ % + margin
var security_lvl = ns.formatPercent(ns.getServerSecurityLevel(results[i]) / 100, 0).padEnd(3 + 1 + 11);//digits+ % + margin
var hacking_lvl = ns.formatNumber(ns.getServerRequiredHackingLevel(results[i]),0,0,true).padEnd(6+5);//margin
var root_access = ns.hasRootAccess(results[i]) ? "[X]" : "[ ]";
var delimeter = n % 2 == 0 ? " " : "-";
var body = `${deco}${results[i]}`.padEnd(current_roots.length * 2 + detail_margin, delimeter);
lines.push(`\n${body}| \$${balance}${hack_chance}${security_lvl}${hacking_lvl}${root_access}`);
} else {
lines.push(`\n${deco}${results[i]}`);
}
}
}
return [lines.join(""), n];
}
/** @param {NS} ns */
export async function main(ns) {
let input_data = [];
try {
input_data = parse_input(ns);
} catch {
input_data = ns.flags([
['detailed', false],
['help', false],
]);
}
if (input_data.help) {
help_message(ns);
}
let passed_servers = ["home"];
let current_roots = [];
let greatest_depth = 0;
let longest_name = 0;
let detailed_mode = input_data.detailed;
let home_scan = ns.scan("home");
let results = rscan(ns, home_scan, 0, passed_servers);
greatest_depth = find_greatest_depth(results, 2, greatest_depth);
longest_name = find_longest_name(results, longest_name);
for (var i = 0; i < greatest_depth; i++) {
current_roots.push("1");
}
let output = format_results(ns, ["home"].concat([results]), 0, detailed_mode, current_roots, longest_name, 0)[0];
ns.tprint(`\nRscan Results:\n ${output}`);
ns.exit();
}
/** @param {NS} ns */
function help_message(ns,) {
ns.tprint(
`
--------------------------------------------------------------------
Help message for rscan.js script.
Assumes the use of an alias.
--------------------------------------------------------------------
Without parse_goblin:
Ussage: rscan 1: help[--help] detailed[--detailed].
Examples:
With detailed mode: rscan --detailed
Without detailed mode: rscan
Show this message: rscan --help
With parse_goblin:
Ussage: rscan 1: help[--help|-h] detailed[--detailed|-d].
Examples:
With detailed mode: rscan -d
Without detailed mode: rscan
Show this message: rscan --help
--------------------------------------------------------------------
Concepts:
Detailed mode: Show more info like balance, security lvl etc.
help flag --help : Print this message.
`);
ns.exit();
}
/*
| |
) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (​̲̅ ) (
| |
Feel free to break away if you don't plan on using parse_goblin. Which adds support for single letter flags like -d and -h.
*/
/** @param {NS} ns */
function parse_input(ns) {
if (ns.fileExists("/lib/parse_goblin.js")) {
let goblin_available = false;
try {
goblin.check(ns);
goblin_available = true;
} catch {}
if (goblin_available){
let input_data = goblin.parse(ns, new goblin.Schema([
new goblin.Param("detailed",false,{short:"d", isFlag:true}),
new goblin.Param("help",false,{short:"h", isFlag:true}),
]));
return input_data;
} else {
//Use base62 encoded hashes for temporary paths and files to make them look "important"
const TSH = s => { for (var i = 0, h = 9; i < s.length;)h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9); return Number((h ^ h >>> 9).toString().replaceAll("-","0")) } //Hash function by https://github.com/bryc from this post: https://stackoverflow.com/a/52171480.
const base62 = {
charset: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
encode: integer => {
if (integer === 0) { return 0; }
let s = [];
while (integer > 0) { s = [base62.charset[integer % 62], ...s]; integer = Math.floor(integer / 62); } return s.join('');
}
};
let current_script_path = ns.getScriptName();
ns.tprint(current_script_path);
let current_script_name =current_script_path.split("/").reverse()[0].replace(".js","");
let nugget_data = `import * as goblin from "/lib/parse_goblin.js";\n${ns.read(current_script_path)}`;
let nugget_file = `/temp/${base62.encode(TSH(current_script_name))}/${base62.encode(TSH(current_script_name.concat("goblin")))}.js`;
ns.write(nugget_file,nugget_data,"w");
let res = ns.run(nugget_file,1,...ns.args);
ns.exit();
}
}
let input_data = ns.flags([
['detailed', false],
['help', false],
]);
return input_data;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment