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"); }