Last active
August 3, 2023 11:07
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
-- 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