From a2499df9e74f9d9669244b8ab54f6ba07b301073 Mon Sep 17 00:00:00 2001
From: Alain Bryden <2285037+alainbryden@users.noreply.github.com>
Date: Fri, 25 Oct 2024 14:15:34 -0300
Subject: [PATCH] Fix #402
- Fixed hacknet servers having undefined properties
- Fixed formatNumber not returning a string in the case of exactly zero
- Fixed scan.js having imperfect alignment
---
helpers.js | 2 +-
scan.js | 172 +++++++++++++++++++++++++++--------------------------
2 files changed, 90 insertions(+), 84 deletions(-)
diff --git a/helpers.js b/helpers.js
index 64453e92..a451bb57 100644
--- a/helpers.js
+++ b/helpers.js
@@ -42,7 +42,7 @@ export function parseShortNumber(text = "0") {
* @param {number=} minDecimalPlaces - (default: 3) The minimum decimal places you wish to see, regardless of significant figures. (e.g. 12.3, 1.2, 0.1 all have 1 decimal)
**/
export function formatNumber(num, minSignificantFigures = 3, minDecimalPlaces = 1) {
- return num == 0.0 ? num : num.toFixed(Math.max(minDecimalPlaces, Math.max(0, minSignificantFigures - Math.ceil(Math.log10(num)))));
+ return num == 0.0 ? "0" : num.toFixed(Math.max(minDecimalPlaces, Math.max(0, minSignificantFigures - Math.ceil(Math.log10(num)))));
}
const memorySuffixes = ["GB", "TB", "PB", "EB"];
diff --git a/scan.js b/scan.js
index 4219d4ac..2aab8533 100644
--- a/scan.js
+++ b/scan.js
@@ -16,98 +16,104 @@ export function autocomplete(data, _) {
export function main(ns) {
const options = getConfiguration(ns, argsSchema);
const showStats = !options['hide-stats'];
- const factionServers = ["CSEC", "avmnite-02h", "I.I.I.I", "run4theh111z", "w0r1d_d43m0n", "fulcrumassets"],
- css = ` `,
- doc = eval("document"),
- terminalInsert = html => doc.getElementById("terminal").insertAdjacentHTML('beforeend', `
${html}`),
- terminalInput = doc.getElementById("terminal-input"),
- terminalEventHandlerKey = Object.keys(terminalInput)[1],
- setNavCommand = async inputValue => {
- terminalInput.value = inputValue
- terminalInput[terminalEventHandlerKey].onChange({ target: terminalInput })
- terminalInput.focus()
- await terminalInput[terminalEventHandlerKey].onKeyDown({ key: 'Enter', preventDefault: () => 0 })
- },
- myHackLevel = ns.getHackingLevel(),
- serverInfo = (serverName) => {
- // Costs 2 GB. If you can't don't need backdoor links, uncomment and use the alternate implementations below
- return ns.getServer(serverName)
- /* return {
+ `;
+ const doc = eval("document");
+ const terminalInput = doc.getElementById("terminal-input");
+ if (!terminalInput) throw new Error("This script must be run while the terminal is visible.");
+ const terminalEventHandlerKey = Object.keys(terminalInput)[1];
+
+ function terminalInsert(html) {
+ const term = doc.getElementById("terminal");
+ if (!term) throw new Error("This script must be run while the terminal is visible.");
+ term.insertAdjacentHTML('beforeend', `${html}`);
+ }
+ async function setNavCommand(inputValue) {
+ terminalInput.value = inputValue
+ terminalInput[terminalEventHandlerKey].onChange({ target: terminalInput })
+ terminalInput.focus()
+ await terminalInput[terminalEventHandlerKey].onKeyDown({ key: 'Enter', preventDefault: () => 0 })
+ }
+
+ const myHackLevel = ns.getHackingLevel();
+
+ function getServerInfo(serverName) {
+ // Costs 2 GB. If you can't don't need backdoor links, uncomment and use the alternate implementations below
+ return ns.getServer(serverName)
+ /* return {
requiredHackingSkill: ns.getServerRequiredHackingLevel(serverName),
hasAdminRights: ns.hasRootAccess(serverName),
purchasedByPlayer: serverName.includes('daemon') || serverName.includes('hacknet'),
backdoorInstalled: true // No way of knowing without ns.getServer
// TODO: Other things needed if showStats is true
- } */
- },
- createServerEntry = serverName => {
- let server = serverInfo(serverName),
- requiredHackLevel = server.requiredHackingSkill,
- rooted = server.hasAdminRights,
- canHack = requiredHackLevel <= myHackLevel,
- shouldBackdoor = !server.backdoorInstalled && canHack && serverName != 'home' && rooted && !server.purchasedByPlayer,
- contracts = ns.ls(serverName, ".cct")
-
- return ``
- + `${serverName}`
- + (server.purchasedByPlayer ? '' : ` (${requiredHackLevel})`)
- + `${(shouldBackdoor ? ` [backdoor]` : '')}`
- + ` ${contracts.map(c => `@`)}`
- + (showStats ? ` ... ` +
- `Money: ` + ((server.moneyMax > 0 ? `${formatMoney(server.moneyAvailable, 4, 1).padStart(7)} / ` : '') + `${formatMoney(server.moneyMax, 4, 1).padStart(7)} `).padEnd(18) +
- `Sec: ${formatNumber(server.hackDifficulty, 0, 0).padStart(2)}/${formatNumber(server.minDifficulty, 0, 0)} `.padEnd(11) +
- `RAM: ${formatRam(server.maxRam).replace(' ', '').padStart(5)}` + (server.maxRam > 0 ? ` (${formatNumber(server.ramUsed * 100.0 / server.maxRam, 0, 1)}% used)` : '') +
- `` : '')
- + ""
- },
- buildOutput = (parent = servers[0], prefix = ["\n"]) => {
- let output = prefix.join("") + createServerEntry(parent)
- if (showStats) { // Roughly right-align server stats if enabled
- const expectedLength = parent.length + (2 * prefix.length) + (output.includes('backdoor') ? 10 : 0) +
- (output.match(/@/g) || []).length + (((output.match(/\(\d+\)/g) || [{ length: 0 }])[0].length) + 1);
- output = output.replace('...', '.'.repeat(Math.max(1, 60 - expectedLength)));
- }
- for (let i = 0; i < servers.length; i++) {
- if (parentByIndex[i] != parent) continue
- let newPrefix = prefix.slice()
- const appearsAgain = parentByIndex.slice(i + 1).includes(parentByIndex[i]),
- lastElementIndex = newPrefix.length - 1
-
- newPrefix.push(appearsAgain ? "├╴" : "└╴")
-
- newPrefix[lastElementIndex] = newPrefix[lastElementIndex].replace("├╴", "│ ").replace("└╴", " ")
- output += buildOutput(servers[i], newPrefix)
- }
-
- return output
- },
- ordering = (serverA, serverB) => {
- // Sort servers with fewer connections towards the top.
- let orderNumber = ns.scan(serverA).length - ns.scan(serverB).length
- // Purchased servers to the very top
- orderNumber = orderNumber != 0 ? orderNumber
- : serverInfo(serverB).purchasedByPlayer - serverInfo(serverA).purchasedByPlayer
- // Hack: compare just the first 2 chars to keep purchased servers in order purchased
- orderNumber = orderNumber != 0 ? orderNumber
- : serverA.slice(0, 2).toLowerCase().localeCompare(serverB.slice(0, 2).toLowerCase())
+ } */
+ }
+ function createServerEntry(serverName) {
+ const server = getServerInfo(serverName);
+ const requiredHackLevel = server.requiredHackingSkill;
+ const rooted = server.hasAdminRights;
+ const canHack = requiredHackLevel <= myHackLevel;
+ const shouldBackdoor = !server.backdoorInstalled && canHack && serverName != 'home' && rooted && !server.purchasedByPlayer;
+ const contracts = ns.ls(serverName, ".cct");
+ return ``
+ + `${serverName}`
+ + (server.purchasedByPlayer ? '' : ` (${requiredHackLevel})`)
+ + `${(shouldBackdoor ? ` [backdoor]` : '')}`
+ + ` ${contracts.map(c => `@`).join('')}`
+ + (showStats ? ` ... ` +
+ `Money: ` + ((server.moneyMax ?? 0 > 0 ? `${formatMoney(server.moneyAvailable ?? 0, 4, 1).padStart(7)} / ` : '') +
+ `${formatMoney(server.moneyMax ?? 0, 4, 1).padStart(7)} `).padEnd(18) +
+ `Sec: ${formatNumber(server.hackDifficulty ?? 0, 0, 0).padStart(3)}/${formatNumber(server.minDifficulty ?? 0, 0, 0)} `.padEnd(13) +
+ `RAM: ${formatRam(server.maxRam ?? 0).replace(' ', '').padStart(6)}` + (
+ server.maxRam ?? 0 > 0 ? ` (${formatNumber(server.ramUsed * 100.0 / server.maxRam, 0, 1)}% used)` : '') +
+ `` : '')
+ + ""
+ }
+ function buildOutput(parent = servers[0], prefix = ["\n"]) {
+ let output = prefix.join("") + createServerEntry(parent);
+ if (showStats) { // Roughly right-align server stats if enabled
+ const expectedLength = parent.length + (2 * prefix.length) + (output.includes('backdoor') ? 11 : 0) +
+ (output.match(/@/g) || []).length + (((output.match(/\(\d+\)/g) || [{ length: -1 }])[0].length) + 1);
+ output = output.replace('...', '.'.repeat(Math.max(1, 60 - expectedLength)));
+ }
+ for (let i = 0; i < servers.length; i++) {
+ if (parentByIndex[i] != parent) continue;
+ const newPrefix = prefix.slice();
+ const appearsAgain = parentByIndex.slice(i + 1).includes(parentByIndex[i]);
+ const lastElementIndex = newPrefix.length - 1;
- return orderNumber
+ newPrefix.push(appearsAgain ? "├╴" : "└╴");
+ newPrefix[lastElementIndex] = newPrefix[lastElementIndex].replace("├╴", "│ ").replace("└╴", " ");
+ output += buildOutput(servers[i], newPrefix);
}
+ return output;
+ }
+ function ordering(serverA, serverB) {
+ // Sort servers with fewer connections towards the top.
+ let orderNumber = ns.scan(serverA).length - ns.scan(serverB).length;
+ // Purchased servers to the very top
+ orderNumber = orderNumber != 0 ? orderNumber :
+ getServerInfo(serverB).purchasedByPlayer - getServerInfo(serverA).purchasedByPlayer;
+ // Hack: compare just the first 2 chars to keep purchased servers in order purchased
+ orderNumber = orderNumber != 0 ? orderNumber :
+ serverA.slice(0, 2).toLowerCase().localeCompare(serverB.slice(0, 2).toLowerCase());
+ return orderNumber;
+ }
// refresh css (in case it changed)
doc.getElementById("scanCSS")?.remove()
@@ -118,16 +124,16 @@ export function main(ns) {
for (let server of servers)
for (let oneScanResult of ns.scan(server).sort(ordering))
if (!servers.includes(oneScanResult)) {
- const backdoored = serverInfo(oneScanResult)?.backdoorInstalled
- servers.push(oneScanResult)
- parentByIndex.push(server)
- routes[oneScanResult] = backdoored ? "connect " + oneScanResult : routes[server] + ";connect " + oneScanResult
+ const backdoored = getServerInfo(oneScanResult)?.backdoorInstalled;
+ servers.push(oneScanResult);
+ parentByIndex.push(server);
+ routes[oneScanResult] = backdoored ? "connect " + oneScanResult : routes[server] + ";connect " + oneScanResult;
}
- terminalInsert(`${buildOutput()}
`)
+ terminalInsert(`${buildOutput()}
`);
doc.querySelectorAll(".serverscan.new .server").forEach(serverEntry => serverEntry
- .addEventListener('click', setNavCommand.bind(null, routes[serverEntry.childNodes[0].nodeValue])))
+ .addEventListener('click', setNavCommand.bind(null, routes[serverEntry.childNodes[0].nodeValue])));
doc.querySelectorAll(".serverscan.new .backdoor").forEach(backdoorButton => backdoorButton
- .addEventListener('click', setNavCommand.bind(null, routes[backdoorButton.parentNode.childNodes[0].childNodes[0].nodeValue] + ";backdoor")))
- doc.querySelector(".serverscan.new").classList.remove("new")
+ .addEventListener('click', setNavCommand.bind(null, routes[backdoorButton.parentNode.childNodes[0].childNodes[0].nodeValue] + ";backdoor")));
+ doc.querySelector(".serverscan.new").classList.remove("new");
}