Skip to content

Commit

Permalink
Fix #402
Browse files Browse the repository at this point in the history
- Fixed hacknet servers having undefined properties
- Fixed formatNumber not returning a string in the case of exactly zero
- Fixed scan.js having imperfect alignment
  • Loading branch information
alainbryden committed Oct 25, 2024
1 parent b599f2e commit a2499df
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 84 deletions.
2 changes: 1 addition & 1 deletion helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down
172 changes: 89 additions & 83 deletions scan.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ` <style id="scanCSS">
.serverscan {white-space:pre; color:#ccc; font:14px monospace; line-height: 16px; }
const factionServers = ["CSEC", "avmnite-02h", "I.I.I.I", "run4theh111z", "w0r1d_d43m0n", "fulcrumassets"];
const css = ` <style id="scanCSS">
.serverscan {white-space:pre; color:#ccc; font:14px consolas,monospace; line-height: 16px; }
.serverscan .server {color:#080;cursor:pointer;text-decoration:underline}
.serverscan .faction {color:#088}
.serverscan .rooted {color:#6f3}
.serverscan .rooted.faction {color:#0ff}
.serverscan .rooted::before {color:#6f3}
.serverscan .hack {display:inline-block; font:12px monospace}
.serverscan .hack {display:inline-block;}
.serverscan .red {color:red;}
.serverscan .green {color:green;}
.serverscan .backdoor {color:#6f3; font:12px monospace}
.serverscan .backdoor {color:#6f3;}
.serverscan .backdoor > a {cursor:pointer; text-decoration:underline;}
.serverscan .cct {color:#0ff;}
.serverscan .serverStats {color:#8AA;}
</style>`,
doc = eval("document"),
terminalInsert = html => doc.getElementById("terminal").insertAdjacentHTML('beforeend', `<li>${html}</li>`),
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 {
</style>`;
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', `<li>${html}</li>`);
}
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 `<span id="${serverName}">`
+ `<a class="server${factionServers.includes(serverName) ? " faction" : ""}`
+ `${rooted ? " rooted" : ""}">${serverName}</a>`
+ (server.purchasedByPlayer ? '' : ` <span class="hack ${(canHack ? 'green' : 'red')}">(${requiredHackLevel})</span>`)
+ `${(shouldBackdoor ? ` <span class="backdoor${factionServers.includes(serverName) ? " faction" : ""}">[<a>backdoor</a>]</span>` : '')}`
+ ` ${contracts.map(c => `<span class="cct" title="${c}">@</span>`)}`
+ (showStats ? ` <span class="serverStats">... ` +
`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)` : '') +
`</span>` : '')
+ "</span>"
},
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 `<span id="${serverName}">`
+ `<a class="server${factionServers.includes(serverName) ? " faction" : ""}`
+ `${rooted ? " rooted" : ""}">${serverName}</a>`
+ (server.purchasedByPlayer ? '' : ` <span class="hack ${(canHack ? 'green' : 'red')}">(${requiredHackLevel})</span>`)
+ `${(shouldBackdoor ? ` <span class="backdoor${factionServers.includes(serverName) ? " faction" : ""}">[<a>backdoor</a>]</span>` : '')}`
+ ` ${contracts.map(c => `<span class="cct" title="${c}">@</span>`).join('')}`
+ (showStats ? ` <span class="serverStats">... ` +
`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)` : '') +
`</span>` : '')
+ "</span>"
}
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()
Expand All @@ -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(`<div class="serverscan new">${buildOutput()}</div>`)
terminalInsert(`<div class="serverscan new">${buildOutput()}</div>`);
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");
}

0 comments on commit a2499df

Please sign in to comment.