├── README.md ├── favTheme.json └── src ├── augPurchaser.js ├── backdoor.js ├── batchGrow.js ├── batchHack.js ├── batchWeaken.js ├── batching ├── builder.js ├── calculations.js ├── queue.js ├── shotgunBatcher.js └── shotgunBuilder.js ├── bestHack.js ├── botnet.js ├── breadwinner.js ├── cleanupStaleScripts.js ├── contracts ├── CodingContractWrapper.js ├── arrayJumpingSolver.js ├── compression1RLE.js ├── encryption1CaesarCipher.js ├── encryption2VigenereCipher.js ├── findValidExpressionsSolver.js ├── generateIpAddsSolver.js ├── hammingCodesBinToIntSolver.js ├── mergeIntervalsSolver.js ├── minimumPathSumSolver.js ├── primeFactorSolver.js ├── sanitizeParensSolver.js ├── shortestPathSolver.js ├── spiralizeMatrixSolver.js ├── squareRootSolver.js ├── stockTraderSolver.js ├── subarrayMaximumSolver.js ├── totalWaysToSumSolver.js ├── uniquePaths1Solver.js └── uniquePaths2Solver.js ├── crime.js ├── doProcess.js ├── gang ├── ascend.js ├── augments.js ├── clashRecorder.js ├── equipment.js ├── recruitment.js ├── tasks.js └── warRunner.js ├── hacknet ├── coreUpgrader.js ├── levelUpgrader.js ├── ramUpgrader.js └── startup.js ├── insight ├── analyze-hack.js ├── faction-manager.js ├── helpers.js ├── stockmaster.js └── work-for-factions.js ├── monitor.js ├── nuker.js ├── pServBuyer.js ├── programBuyer.js ├── rooter.js ├── satellites ├── activityObserver.js ├── backdoorObserver.js ├── batchMetaObserver.js ├── batchObserver.js ├── contractsObserver.js ├── controller.js ├── factionObserver.js ├── gangClashObserver.js ├── gangMetaObserver.js ├── homeRamObserver.js ├── networkObserver.js ├── playerObserver.js ├── programObserver.js ├── pservObserver.js ├── shareObserver.js └── stanekObserver.js ├── share.js ├── sleeves ├── manager.js └── metaObserver.js ├── startup ├── cleanup.js ├── initStartup.js └── run.js ├── stats.js ├── torBuyer.js ├── upgradeHomeRam.js ├── usr ├── find.js ├── lsClear.js ├── lsGet.js └── lsSet.js ├── utils ├── constants.js ├── formulas.js ├── helpers.js └── network.js └── workForFactions.js /README.md: -------------------------------------------------------------------------------- 1 | # Bitburner 2 | Scripts for [Bitburner](https://danielyxie.github.io/bitburner/). 3 | 4 | ## If you're starting BitBurner and want all the Codez 5 | 6 | Hi! Welcome, please look around. You are absolutely welcome and free to copy/paste, fork, splat, or whatever. The initial startup script and copy-paste instructions below I borrowed from https://github.com/moriakaice/bitburner/tree/master. That repo (at the time of this edit) doesn't work out of the box, but they're trying to be pretty new-player friendly. I recommend checking it out. 7 | 8 | This repo.... is not especially new-player-friendly. If you want to take my repo whole-hog and run it in your own game (following the instructions below), it is unlikely to work unless you've been playing for a while. I started this repo primarily for myself, and the code makes some assumptions about certain features that are not available to new players. I also assume that there is at least 32GB of ram on your home computer, which is not immediately available to new players. 9 | 10 | Also, be aware, I am not primarily a Javascript programmer, though it is something I enjoy playing with. I don't follow best practices in the industry for this language. If somebody says that the way I did something was *not good*, it probably is *not good* and trust other resources before you trust my code for `the best way` if you're learning. 11 | 12 | Good luck, and have fun out there. 13 | 14 | ### WARNINGS 15 | 16 | * This is an unstable repo. I regularly push untested code. I'm a rebel, I know. 17 | * **This is an unstable repo.** I change method signatures for the hell of it. Depend on nothing. 18 | * I'm a flake. If I haven't commited in a few weeks, it probably means I'm bored or got stuck and found something else to play with. 19 | * This **does not work for new players**. This repo uses methods that are locked behind game mechanics. 20 | * This contains spoilers. If you don't like spoilers, well... I warned you. 21 | 22 | ## Installation 23 | 24 | Paste `wget https://raw.githubusercontent.com/jenheilemann/bitburner-scripts/main/src/startup/initStartup.js startup/initStartup.js; run startup/initStartup.js` 25 | 26 | ## My aliases 27 | 28 | ```js 29 | // startup everything. If you've run this at least once and want to skip 30 | // downloading/overwriting files, do `run startup/run.js` instead 31 | alias start="run start.js" 32 | 33 | // run arbitrary ns processes and helper functions, see doProcess for examples 34 | alias do="run doProcess.js" 35 | 36 | // connect to any server by name 37 | alias find="run usr/find.js" 38 | 39 | // connect to any server by name 40 | alias monitor="run monitor.js" 41 | 42 | // get server data about what the best server to hack might be right now 43 | alias best="run bestHack.js" 44 | 45 | // manipulate localStorage 46 | alias get="run usr/lsGet.js" 47 | alias set="run usr/lsSet.js" 48 | 49 | // force crime.js/workForFactions.js to stop, so you can play in-game 50 | alias working="run usr/lsSet.js working" 51 | alias done="run usr/lsClear.js working" 52 | 53 | // set a reserve amount manually, above reseved money for buying programs 54 | alias reserve="run usr/lsSet.js reserve" 55 | 56 | // prints out the hours since your last BN reset (or since start of game) 57 | alias time="do formatDuration(Date.now()-ns.getResetInfo().lastNodeReset)" 58 | 59 | // copy-paste-able lines 60 | alias start="run startup/run.js"; 61 | alias init="wget https://raw.githubusercontent.com/jenheilemann/bitburner-scripts/main/src/startup/initStartup.js startup/initStartup.js; run startup/initStartup.js"; 62 | alias do="run doProcess.js";alias find="run usr/find.js"; 63 | alias best="run bestHack.js --tail";alias get="run usr/lsGet.js"; 64 | alias set="run usr/lsSet.js";alias working="run usr/lsSet.js working"; 65 | alias done="run usr/lsClear.js working";alias reserve="run usr/lsSet.js reserve"; 66 | alias monitor="run monitor.js"; alias hoem="home"; 67 | alias bqueue="run usr/lsGet.js BATCHES"; 68 | alias time="do formatDuration(Date.now()-ns.getResetInfo().lastNodeReset)"; 69 | ``` 70 | -------------------------------------------------------------------------------- /favTheme.json: -------------------------------------------------------------------------------- 1 | {"primarylight":"#AED1B5","primary":"#80AA89","primarydark":"#6C7E70","successlight":"#68D680","success":"#47AC5D","successdark":"#3A7145","errorlight":"#EF5757","error":"#CC3D3D","errordark":"#AA4B4B","secondarylight":"#AFAFAF","secondary":"#7C817E","secondarydark":"#5A5A5A","warninglight":"#DFDF5D","warning":"#C3C346","warningdark":"#9A9A42","infolight":"#ADF1FA","info":"#72C0CA","infodark":"#60878C","welllight":"#444","well":"#222","white":"#fff","black":"#000","hp":"#dd3434","money":"#FFE347","hack":"#adff2f","combat":"#faffdf","cha":"#a671d1","int":"#6495ed","rep":"#faffdf","disabled":"#605C5C","backgroundprimary":"#121217","backgroundsecondary":"#060607","button":"#333"} 2 | -------------------------------------------------------------------------------- /src/backdoor.js: -------------------------------------------------------------------------------- 1 | import { findPath } from 'utils/network.js' 2 | import { announce } from 'utils/helpers.js' 3 | 4 | /** @param {NS} ns **/ 5 | export async function main(ns) { 6 | let target = ns.args[0] 7 | ns.tprint(`Backdoor running on ${target}`) 8 | await backdoor(ns, target) 9 | } 10 | 11 | export function autocomplete(data, args) { 12 | return data.servers 13 | } 14 | 15 | /** 16 | * @param {NS} ns 17 | * @param {string} target 18 | **/ 19 | export async function backdoor(ns, target) { 20 | let path = findPath(target) 21 | 22 | path.forEach((step) => ns.singularity.connect(step)) 23 | await ns.singularity.installBackdoor() 24 | announce(ns, `Backdoor installed on ${target}`) 25 | ns.singularity.connect('home') 26 | if ( target == 'w0r1d_d43m0n'){ 27 | ns.killall('home') 28 | ns.tprint('SUCCESS: The w0r1d_d43m0n has been defeated. Time to move on.') 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/batchGrow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {NS} ns 3 | **/ 4 | export async function main(ns) { 5 | const portHandle = ns.getPortHandle(ns.pid) 6 | const job = JSON.parse(ns.args[0]) 7 | ns.print(`Grow job opened, batch ${job.id}`) 8 | 9 | const promise = ns.grow(job.target, { additionalMsec: job.delay }) 10 | portHandle.write('started') 11 | await promise 12 | 13 | const end = Date.now() 14 | ns.atExit(() => { 15 | ns.print(`Batch ${job.id}: Grow finished at ${end.toString()}`) 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/batchHack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {NS} ns 3 | **/ 4 | export async function main(ns) { 5 | const portHandle = ns.getPortHandle(ns.pid) 6 | const job = JSON.parse(ns.args[0]) 7 | ns.print(`Hack job opened, batch ${job.id}`) 8 | 9 | const promise = ns.hack(job.target, { additionalMsec: job.delay }) 10 | portHandle.write('started') 11 | await promise 12 | 13 | const end = Date.now() 14 | ns.atExit(() => { 15 | ns.print(`INFO: Batch ${job.id}: Hack finished at ${end.toString()}`) 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/batchWeaken.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {NS} ns 3 | **/ 4 | export async function main(ns) { 5 | const portHandle = ns.getPortHandle(ns.pid) 6 | const job = JSON.parse(ns.args[0]) 7 | ns.print(`Weaken job opened, batch ${job.id}`) 8 | 9 | const promise = ns.weaken(job.target, { additionalMsec: job.delay }) 10 | portHandle.write('started') 11 | await promise 12 | 13 | const end = Date.now() 14 | ns.atExit(() => { 15 | ns.print(`Batch ${job.id}: Weaken finished at ${end.toString()}`) 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/batching/builder.js: -------------------------------------------------------------------------------- 1 | import { 2 | calcThreadsToGrow, 3 | calcThreadsToHack, 4 | calcRam, ramSizes, 5 | hackTime, weakTime, growTime, 6 | calcHackAmount, 7 | calculatePercentMoneyHacked } from '/batching/calculations.js' 8 | 9 | // Game-set constants. Don't change these magic numbers. 10 | export const growsPerWeaken = 12.5 11 | export const hacksPerWeaken = 25 12 | 13 | /** @param {NS} ns */ 14 | export async function main(ns) { 15 | ns.tprint('ERROR: /batching/builder.js is not meant to be run independently.') 16 | } 17 | 18 | 19 | class BatchTask { 20 | /** 21 | * @param {string} type 22 | * @param {Integer} threads 23 | * @param {Float} ram 24 | * @param {Float} time 25 | */ 26 | constructor(type, threads, ram, time) { 27 | this.type = type 28 | this.threads = threads 29 | this.ram = ram 30 | this.time = time 31 | this.servers 32 | } 33 | } 34 | 35 | 36 | /** 37 | * @prop {Server} target 38 | * @prop {[BatchTask]} tasks 39 | */ 40 | class Builder { 41 | /** 42 | * @param {Server} target 43 | */ 44 | constructor(target) { 45 | this.target = target 46 | this.tasks = [] 47 | } 48 | 49 | /** 50 | * @param {NS} ns 51 | * @param {Server[]]} serversWithRam 52 | * @returns {BatchTask[]} The tasks with added chosen servers and # of threads 53 | **/ 54 | assignServers(serversWithRam) { 55 | this.tasks.forEach(task => { 56 | let servers = this.matchServers(task, serversWithRam) 57 | task.servers = servers 58 | }) 59 | return this.tasks 60 | } 61 | 62 | /** 63 | * @returns {boolean} true if all batches have matching servers 64 | */ 65 | isFulfilled() { 66 | return this.tasks.every(b => b.servers && b.servers.length > 0) 67 | } 68 | 69 | /** 70 | * @returns {boolean} true if all batches have matching servers 71 | */ 72 | isEmpty() { 73 | return this.tasks.every(b => !b.servers || b.servers.length == 0) 74 | } 75 | 76 | /** 77 | * @returns {float} GB of ram required to run a full batch 78 | */ 79 | calcTotalRamRequired() { 80 | if (this.tasks.length == 0) 81 | this.calcTasks() 82 | 83 | return this.tasks.reduce((a,b) => a + b.ram, 0) 84 | } 85 | } 86 | 87 | export class PrepBuilder extends Builder { 88 | type = 'Prepping' 89 | 90 | /** 91 | * @returns {BatchTask[]} The ram and threads for weaken, grow, weaken until 92 | * the target server is prepped 93 | * [ 94 | * weaken1: {type: weaken, threads: y, time: x} 95 | * grow: {type: grow, threads: y, time: z}, 96 | * weaken2: {type: weaken, threads: y, time: x} 97 | * ] 98 | **/ 99 | calcTasks() { 100 | let weakTh1 = Math.ceil(((this.target.hackDifficulty - this.target.minDifficulty) / 0.05)) 101 | let growTh = calcThreadsToGrow(this.target, this.target.moneyMax) + 1 102 | let weakTh2 = Math.ceil((growTh/growsPerWeaken)) 103 | this.tasks = [ 104 | new BatchTask('weak', weakTh1, calcRam('weak', weakTh1), weakTime(this.target)), 105 | new BatchTask('grow', growTh, calcRam('grow', growTh), growTime(this.target)), 106 | new BatchTask('weak', weakTh2, calcRam('weak', weakTh2), weakTime(this.target)), 107 | ].filter(t => t.threads > 0) 108 | return this.tasks 109 | } 110 | 111 | /** 112 | * @param {BatchTask} task 113 | * @param {Server[]} serversWithRam 114 | * @returns {[string, Int][]} Array where each entry is sub-array of Server 115 | * hostname and number of threads assigned 116 | **/ 117 | matchServers(task, serversWithRam) { 118 | serversWithRam.sort((a,b) => a.availableRam - b.availableRam) 119 | let server = serversWithRam.find(s => s.availableRam >= task.ram) 120 | if (server) { 121 | server.availableRam -= task.ram 122 | return [[server.hostname, task.threads]] 123 | } 124 | serversWithRam.sort((a,b) => b.availableRam - a.availableRam) 125 | let neededThreads = task.threads 126 | let scriptSize = ramSizes[task.type] 127 | let servers = [] 128 | serversWithRam.forEach(server => { 129 | if (neededThreads == 0 ) return 130 | 131 | if (server.availableRam > scriptSize) { 132 | let useThreads = Math.min(neededThreads, Math.floor(server.availableRam/scriptSize)) 133 | let useRam = useThreads*scriptSize 134 | server.availableRam -= useRam 135 | servers.push([server.hostname, useThreads]) 136 | neededThreads -= useThreads 137 | } 138 | }) 139 | return servers 140 | } 141 | } 142 | 143 | export class HackBuilder extends Builder { 144 | type = 'Hacking' 145 | 146 | /** 147 | * @params {float|undefined} decimal 148 | * @returns {BatchTask[]} The needed ram and threads for HWGW batch 149 | **/ 150 | calcTasks(decimal) { 151 | // zero out the server, assume prepping script goes well 152 | let mA = this.target.moneyAvailable 153 | let hD = this.target.hackDifficulty 154 | this.target.moneyAvailable = this.target.moneyMax 155 | this.target.hackDifficulty = this.target.minDifficulty 156 | 157 | let hackDecimal = decimal ?? calcHackAmount(this.target) 158 | let moneyToHack = this.target.moneyAvailable * hackDecimal 159 | let hackTh = calcThreadsToHack(this.target, moneyToHack) 160 | hackTh = Math.max(hackTh, 1) 161 | let weakTh1 = Math.ceil(hackTh/hacksPerWeaken) 162 | this.target.moneyAvailable -= this.target.moneyAvailable * 163 | calculatePercentMoneyHacked(this.target) * 164 | hackTh 165 | let growTh = calcThreadsToGrow(this.target, this.target.moneyMax) 166 | // to offset the loss during levelups or desyncs 167 | growTh += Math.ceil(growTh * 0.05) 168 | let weakTh2 = Math.ceil(growTh/growsPerWeaken) 169 | // reset to actual, especially hackDifficulty before calculating hackTime 170 | this.target.moneyAvailable = mA 171 | this.target.hackDifficulty = hD 172 | this.tasks = [ 173 | new BatchTask('hack', hackTh, calcRam('hack', hackTh), hackTime(this.target)), 174 | new BatchTask('weak', weakTh1, calcRam('weak', weakTh1), weakTime(this.target)), 175 | new BatchTask('grow', growTh, calcRam('grow', growTh), growTime(this.target)), 176 | new BatchTask('weak', weakTh2, calcRam('weak', weakTh2), weakTime(this.target)), 177 | ].filter(t => t.threads > 0) 178 | 179 | return this.tasks 180 | } 181 | 182 | /** 183 | * @param {BatchTask} task 184 | * @param {Server[]} serversWithRam 185 | * @returns {[string, Int][]} Array where each entry is sub-array of Server 186 | * hostname and number of threads assigned 187 | **/ 188 | matchServers(task, serversWithRam) { 189 | serversWithRam.sort((a,b) => a.availableRam - b.availableRam) 190 | let server = serversWithRam.find(s => s.availableRam >= task.ram) 191 | if (server) { 192 | server.availableRam -= task.ram 193 | return [[server.hostname, task.threads]] 194 | } 195 | 196 | if (task.type == "weak") { 197 | serversWithRam.sort((a,b) => b.availableRam - a.availableRam) 198 | let neededThreads = task.threads 199 | let scriptSize = ramSizes[task.type] 200 | let servers = [] 201 | let useThreads, useRam 202 | for (let server of serversWithRam) { 203 | if (server.availableRam < scriptSize) 204 | continue 205 | useThreads = Math.min(neededThreads, Math.floor(server.availableRam/scriptSize)) 206 | useRam = useThreads*scriptSize 207 | server.availableRam -= useRam 208 | servers.push([server.hostname, useThreads]) 209 | neededThreads -= useThreads 210 | if (neededThreads < 1 ) 211 | return servers 212 | } 213 | return servers 214 | } 215 | return [] 216 | } 217 | } 218 | 219 | -------------------------------------------------------------------------------- /src/batching/calculations.js: -------------------------------------------------------------------------------- 1 | import { fetchPlayer, getLSItem } from 'utils/helpers.js' 2 | import { reservedRam } from 'utils/constants.js' 3 | 4 | /** @param {NS} ns */ 5 | export async function main(ns) { 6 | ns.tprint('/batching/calculations.js Not meant to be run independently.') 7 | } 8 | 9 | 10 | /** 11 | * Returns time it takes to complete a hack on a server, in ms. 12 | * @param {Server} server - Server being hacked 13 | * @returns {number} Time to hack, in ms 14 | */ 15 | export function hackTime(server) { 16 | const player = fetchPlayer() 17 | const hackDifficulty = server.hackDifficulty 18 | const requiredHackingSkill = server.requiredHackingSkill 19 | if (typeof hackDifficulty !== "number" || 20 | typeof requiredHackingSkill !== "number") 21 | return Infinity; 22 | const difficultyMult = requiredHackingSkill * hackDifficulty; 23 | 24 | const baseDiff = 500; 25 | const baseSkill = 50; 26 | const diffFactor = 2.5; 27 | let skillFactor = diffFactor * difficultyMult + baseDiff; 28 | skillFactor /= player.skills.hacking + baseSkill; 29 | 30 | const speedMult = getLSItem('bitnode')['HackingSpeedMultiplier'] 31 | const hackTimeMultiplier = 5; 32 | const hackingTime = 33 | (hackTimeMultiplier * skillFactor) / 34 | (player.mults.hacking_speed * 35 | speedMult * 36 | calculateIntelligenceBonus(player.skills.intelligence, 1)); 37 | 38 | return hackingTime * 1000 39 | } 40 | 41 | // Game-set constants. Don't change these magic numbers. 42 | const growTimeMultiplier = 3.2 // Relative to hacking time. 16/5 = 3.2 43 | const weakenTimeMultiplier = 4 // Relative to hacking time 44 | 45 | /** 46 | * Returns time it takes to complete a grow on a server, in ms. 47 | * @param {Server} server - Server being grown 48 | * @returns {number} Time to grow, in ms 49 | */ 50 | export function growTime(server) { 51 | return hackTime(server) * growTimeMultiplier 52 | } 53 | 54 | /** 55 | * Returns time it takes to complete a weaken on a server, in ms. 56 | * @param {Server} server - Server being weakened 57 | * @returns {number} Time to weaken, in ms 58 | */ 59 | export function weakTime(server) { 60 | return hackTime(server) * weakenTimeMultiplier 61 | } 62 | 63 | export function calculateIntelligenceBonus(intelligence, weight = 1) { 64 | const bitNodeOptions = getLSItem('reset')['bitNodeOptions'] 65 | const effectiveIntelligence = 66 | bitNodeOptions.intelligenceOverride !== undefined 67 | ? Math.min(bitNodeOptions.intelligenceOverride, intelligence) 68 | : intelligence; 69 | return 1 + (weight * Math.pow(effectiveIntelligence, 0.8)) / 600; 70 | } 71 | 72 | 73 | // minimum ram required to run each file with 1 thread 74 | // avoid calling getScriptRam 75 | export const ramSizes = { 76 | 'hack' : 1.7, 77 | 'weak' : 1.75, 78 | 'grow' : 1.75, 79 | } 80 | 81 | /** 82 | * @param {string} type 83 | * @param {num} numThreads 84 | * @returns {num} Amount of ram needed to run that many of that type of action 85 | **/ 86 | export function calcRam(type, numThreads) { 87 | return ramSizes[type] * numThreads 88 | } 89 | 90 | /** 91 | * Returns the number of threads needed to grow the specified server by 92 | * the specified amount. 93 | * @param {Server} server - Server being grown 94 | * @param {num} targetMoney - - How much you want the server grown TO (not by), 95 | * for instance, to grow from 200 to 600, input 600 96 | * @returns {num} Number of threads needed 97 | */ 98 | export function calcThreadsToGrow(server, targetMoney) { 99 | let person = fetchPlayer() 100 | let startMoney = server.moneyAvailable 101 | 102 | const k = calculateServerGrowthLog(server, 1, person); 103 | const guess = (targetMoney - startMoney) / (1 + (targetMoney * (1 / 16) + startMoney * (15 / 16)) * k); 104 | let x = guess; 105 | let diff; 106 | do { 107 | const ox = startMoney + x; 108 | // Have to use division instead of multiplication by inverse, because 109 | // if targetMoney is MIN_VALUE then inverting gives Infinity 110 | const newx = (x - ox * Math.log(ox / targetMoney)) / (1 + ox * k); 111 | diff = newx - x; 112 | x = newx; 113 | } while (diff < -1 || diff > 1); 114 | /* If we see a diff of 1 or less we know all future diffs will be smaller, and the rate of 115 | * convergence means the *sum* of the diffs will be less than 1. 116 | 117 | * In most cases, our result here will be ceil(x). 118 | */ 119 | const ccycle = Math.ceil(x); 120 | if (ccycle - x > 0.999999) { 121 | // Rounding-error path: It's possible that we slightly overshot the integer value due to 122 | // rounding error, and more specifically precision issues with log and the size difference of 123 | // startMoney vs. x. See if a smaller integer works. Most of the time, x was not close enough 124 | // that we need to try. 125 | const fcycle = ccycle - 1; 126 | if (targetMoney <= (startMoney + fcycle) * Math.exp(k * fcycle)) { 127 | return fcycle; 128 | } 129 | } 130 | if (ccycle >= x + ((diff <= 0 ? -diff : diff) + 0.000001)) { 131 | // Fast-path: We know the true value is somewhere in the range [x, x + |diff|] but the next 132 | // greatest integer is past this. Since we have to round up grows anyway, we can return this 133 | // with no more calculation. We need some slop due to rounding errors - we can't fast-path 134 | // a value that is too small. 135 | return ccycle; 136 | } 137 | if (targetMoney <= (startMoney + ccycle) * Math.exp(k * ccycle)) { 138 | return ccycle; 139 | } 140 | return ccycle + 1 141 | } 142 | 143 | 144 | // Returns the log of the growth rate. When passing 1 for threads, this gives a useful constant. 145 | function calculateServerGrowthLog(server, threads, p, cores = 1) { 146 | if (!server.serverGrowth) return -Infinity; 147 | const hackDifficulty = server.hackDifficulty ?? 100; 148 | const numServerGrowthCycles = Math.max(threads, 0); 149 | 150 | const serverBaseGrowthIncr = 0.03 // Unadjusted growth increment (growth rate is this * adjustment + 1) 151 | const serverMaxGrowthLog = 0.00349388925425578 // Maximum possible growth rate accounting for server security, precomputed as log1p(.0035) 152 | 153 | //Get adjusted growth log, which accounts for server security 154 | //log1p computes log(1+p), it is far more accurate for small values. 155 | let adjGrowthLog = Math.log1p(serverBaseGrowthIncr / hackDifficulty); 156 | if (adjGrowthLog >= serverMaxGrowthLog) { 157 | adjGrowthLog = serverMaxGrowthLog; 158 | } 159 | 160 | //Calculate adjusted server growth rate based on parameters 161 | const serverGrowthPercentage = server.serverGrowth / 100; 162 | const serverGrowthPercentageAdjusted = serverGrowthPercentage * getLSItem('bitnode')['ServerGrowthRate']; 163 | 164 | //Apply serverGrowth for the calculated number of growth cycles 165 | const coreBonus = 1 + (cores - 1) * (1 / 16); 166 | // It is critical that numServerGrowthCycles (aka threads) is multiplied last, 167 | // so that it rounds the same way as numCycleForGrowth. 168 | return adjGrowthLog * serverGrowthPercentageAdjusted * p.mults.hacking_grow * coreBonus * numServerGrowthCycles; 169 | } 170 | 171 | /** 172 | * @params {Server} server 173 | * @params {num} hackAmount - how much money to get with this hack, as a dollar amount 174 | * @returns {num} the number of threads to hack with to get about this amount 175 | */ 176 | export function calcThreadsToHack(server, hackAmount) { 177 | if (hackAmount < 0 || hackAmount > server.moneyAvailable) { 178 | return -1; 179 | } 180 | 181 | const percentHacked = calculatePercentMoneyHacked(server) 182 | return Math.floor(hackAmount / (server.moneyAvailable * percentHacked)) 183 | } 184 | 185 | /** 186 | * Returns the percentage of money that will be stolen from a server if 187 | * it is successfully hacked with one thread. (returns the decimal form, not 188 | * the actual percent value) 189 | * @params {Server} server 190 | */ 191 | export function calculatePercentMoneyHacked(server) { 192 | // Adjust if needed for balancing. This is the divisor for the final calculation 193 | const balanceFactor = 240; 194 | const player = fetchPlayer() 195 | 196 | const difficultyMult = (100 - server.hackDifficulty) / 100; 197 | const skillMult = (player.skills.hacking - (server.requiredHackingSkill - 1)) / player.skills.hacking; 198 | const percentMoneyHacked = (difficultyMult * skillMult * player.mults.hacking_money) / balanceFactor; 199 | if (percentMoneyHacked < 0) { 200 | return 0; 201 | } 202 | if (percentMoneyHacked > 1) { 203 | return 1; 204 | } 205 | 206 | let scriptHackMoneyMult = getLSItem('bitnode')["ScriptHackMoney"] 207 | return percentMoneyHacked * scriptHackMoneyMult 208 | } 209 | 210 | /** 211 | * Returns the percentage (as a decimal) that this server should be hacked 212 | * based on the amount of growth it has. 213 | * @param {Server} server 214 | */ 215 | export function calcHackAmount(server) { 216 | // ~ 5% depending on the serverGrowth 217 | let base = Math.sqrt(Math.sqrt(server.serverGrowth))/50 218 | // multiply by the dynamic value of hackPercent 219 | // and cap at a maximum (can't hack more than 100%) 220 | return Math.min(0.99, base * getLSItem('hackPercent')) 221 | } 222 | 223 | /** 224 | * Returns the percentage (as a decimal) that all available ram is being used 225 | * Does not include amounts of ram smaller than the smallest file. 226 | * @param {Server} server 227 | */ 228 | export function getPercentUsedRam(nmap) { 229 | nmap = Object.values(nmap) 230 | let totRamServers = fetchServersWithUsableRam(nmap, ramSizes['hack']) 231 | let ramServers = fetchServersWithUnusedRam(totRamServers, ramSizes['hack']) 232 | let totRam = totRamServers.reduce((a, b) => { return a + b.maxRam }, 0) 233 | let availableRam = ramServers.reduce((a, b) => { return a + b.availableRam }, 0) 234 | return (totRam - availableRam)/totRam 235 | } 236 | 237 | /** 238 | * @param {Server[]} servers 239 | * @param {num} minRam - the smallest amount of ram we might use at once (for hacking) 240 | * @returns {Server[]} - All servers with usable ram, possibly being used 241 | **/ 242 | function fetchServersWithUsableRam(servers, minRam) { 243 | return servers.filter(server => 244 | server.maxRam > minRam && 245 | server.files.includes('batchWeaken.js') && 246 | getLSItem('decommissioned') != server.hostname 247 | ) 248 | } 249 | 250 | /** 251 | * @param {Server[]} servers 252 | * @param {num} minRam - the smallest amount of ram we might use at once (for hacking) 253 | * @returns {Server[]} - All servers with unused ram 254 | **/ 255 | function fetchServersWithUnusedRam(servers, minRam) { 256 | return servers.filter(server => 257 | serverHasAvailableRam(server, minRam) && 258 | server.files.includes('batchWeaken.js') && 259 | getLSItem('decommissioned') != server.hostname 260 | ) 261 | } 262 | 263 | /** 264 | * @param {Server} server 265 | * @param {num} minRam - the smallest amount of ram we might use at once (for hacking) 266 | * @returns {boolean} Whether the server's unused ram is enough to run one thread of the smallest file 267 | **/ 268 | function serverHasAvailableRam(server, minRam) { 269 | if (server.hostname == 'home') 270 | server.availableRam = server.availableRam - reservedRam 271 | return server.availableRam > minRam 272 | } 273 | 274 | -------------------------------------------------------------------------------- /src/batching/queue.js: -------------------------------------------------------------------------------- 1 | /** @param {NS} ns */ 2 | export async function main(ns) { 3 | ns.tprint("This is purely a helper class, don't run this file by itself.") 4 | } 5 | 6 | const JOB_TYPES = { 7 | PREPPING : 'prepping', 8 | HACKING: 'hacking' 9 | } 10 | 11 | export class BatchJob { 12 | constructor(data) { 13 | this.id = data.id 14 | this.time = data.time 15 | this.type = JOB_TYPES[data.type.toUpperCase()] 16 | this.target = data.target 17 | this.start = data.start 18 | this.end = data.end 19 | this.pids = data.pids 20 | } 21 | 22 | /** 23 | * @returns {boolean} has this batch fully completed? 24 | */ 25 | isExpired() { 26 | return this.end < Date.now() 27 | } 28 | 29 | /** 30 | * @returns {boolean} are we currently within the error window while 31 | * this batch is completing? 32 | */ 33 | isInsideErrorWindow(timestamp) { 34 | return this.start < timestamp && timestamp < this.end 35 | } 36 | 37 | /** 38 | * @returns {boolean} is this batch prepping a server for batching? 39 | */ 40 | isPrepping() { 41 | return this.type == JOB_TYPES.PREPPING 42 | } 43 | 44 | /** 45 | * @returns {boolean} is this batch hacking a server? 46 | */ 47 | isHacking() { 48 | return this.type == JOB_TYPES.HACKING 49 | } 50 | 51 | toObj() { 52 | return { 53 | id: this.id, 54 | time: this.time, 55 | type: this.type, 56 | target: this.target, 57 | start: this.start, 58 | end: this.end, 59 | pids: this.pids, 60 | } 61 | } 62 | } 63 | 64 | export class BatchDataQueue { 65 | /** 66 | * @param {array} batchList - list of batches that might be running 67 | */ 68 | constructor(batchList) { 69 | this.batchList = batchList.map(data => new BatchJob(data)) 70 | } 71 | 72 | /** 73 | * @returns {BatchJob} data object that handles what we know about the batch 74 | */ 75 | nextBatch() { 76 | return this.batchList[0] 77 | } 78 | 79 | /** 80 | * @param {string|undefined} target 81 | * @returns {boolean} are there any batches running right now? 82 | */ 83 | isEmpty(target) { 84 | if (this.batchList.length == 0 ) { return true } 85 | if ( target ) 86 | return !this.batchList.some(job => job.target == target) 87 | return false 88 | } 89 | 90 | /** 91 | * @returns {null} 92 | */ 93 | discardExpiredBatchData() { 94 | this.batchList = this.batchList.filter(job => !job.isExpired()) 95 | } 96 | 97 | /** 98 | * @returns {obj} 99 | */ 100 | toObj() { 101 | return this.batchList.map(job => job.toObj()) 102 | } 103 | 104 | /** 105 | * @param {string} target 106 | * @param {number} timestamp 107 | * @returns {string} 108 | */ 109 | anyInsideErrorWindow(target, timestamp = Date.now()) { 110 | return this.batchList.some(job => job.target == target && job.isInsideErrorWindow(timestamp)) 111 | } 112 | 113 | /** 114 | * @param {string} hostname 115 | * @returns {string} Is there a batch prepping this server? 116 | */ 117 | hasPreppingBatch(hostname) { 118 | return this.batchList.some(job => job.isPrepping() && job.target == hostname) 119 | } 120 | 121 | /** 122 | * @param {string} hostname 123 | * @returns {string} Is there a batch hacking this server? 124 | */ 125 | hasHackingBatch(hostname) { 126 | return this.batchList.some(job => job.isHacking() && job.target == hostname) 127 | } 128 | 129 | /** 130 | * Add a new batchJob to the list 131 | * @returns {null} 132 | */ 133 | addNewJob(start, end, id, time, type, target, pidsWithThreads) { 134 | this.batchList.push(new BatchJob({start: start, end: end, id: id, time: time, type: type, target: target, pids: pidsWithThreads})) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/batching/shotgunBatcher.js: -------------------------------------------------------------------------------- 1 | import { disableLogs, 2 | getLSItem, setLSItem, 3 | } from 'utils/helpers.js' 4 | import { networkMapFree } from 'utils/network.js' 5 | import { reservedRam } from 'utils/constants.js' 6 | import { BatchDataQueue } from '/batching/queue.js' 7 | import { PrepBuilder, HackBuilder } from '/batching/shotgunBuilder.js' 8 | import { weakTime, growTime, hackTime, 9 | ramSizes, 10 | } from '/batching/calculations.js' 11 | 12 | const argsSchema = [ 13 | ['target', 'joesguns'] 14 | ] 15 | 16 | export function autocomplete(data, args) { 17 | data.flags(argsSchema) 18 | const lastFlag = args.length > 1 ? args[args.length - 2] : null; 19 | if (lastFlag == "--target") 20 | return data.servers; 21 | return [] 22 | } 23 | 24 | /** @param {NS} ns */ 25 | export async function main(ns) { 26 | disableLogs(ns, ['sleep', 'getServerUsedRam', 'getServerMoneyAvailable']) 27 | const flags = ns.flags(argsSchema) 28 | ns.clearLog(); 29 | ns.print('Running shotgunBatcher...') 30 | 31 | const serversWithRam = fetchServersWithRam(ns, ramSizes['weak']) 32 | if ( serversWithRam.length == 0 ) { 33 | ns.print("No ram available") 34 | return 35 | } 36 | 37 | const target = networkMapFree()[flags.target] 38 | fetchTargetData(ns, target) 39 | const queue = fetchBatchQueue() 40 | prepTarget(ns, target, serversWithRam, queue) 41 | 42 | const builder = new HackBuilder(target) 43 | builder.calcTasks() 44 | for (let i = 0; i < 500; ++i) { 45 | hackTarget(ns, target, builder, serversWithRam, queue) 46 | builder.clearServerAssignments() 47 | } 48 | prepTarget(ns, target, serversWithRam, queue, true) 49 | saveBatches(queue) 50 | } 51 | 52 | /** 53 | * @param {NS} ns 54 | * @param {number} minRam - the smallest amount of ram we might use at once 55 | **/ 56 | export function fetchServersWithRam(ns, minRam) { 57 | return Object.values(networkMapFree()).filter(server => 58 | serverHasEnoughRam(ns, server, minRam) && 59 | server.files.includes('batchWeaken.js') 60 | ) 61 | } 62 | 63 | /** 64 | * @param {NS} ns 65 | * @param {Server} server 66 | * @param {number} minRam - the smallest amount of ram we might use at once 67 | * @returns {boolean} Whether the server's unused ram is enough to run one 68 | * thread of the smallest file 69 | **/ 70 | function serverHasEnoughRam(ns, server, minRam) { 71 | let reserved = server.hostname == 'home' ? reservedRam : 0 72 | server.availableRam = server.maxRam - ns.getServerUsedRam(server.hostname) - reserved 73 | return server.availableRam > minRam 74 | } 75 | 76 | /** 77 | * @param {NS} ns 78 | * @param {Server} target 79 | */ 80 | function fetchTargetData(ns, target) { 81 | target.hackDifficulty = ns.getServerSecurityLevel(target.hostname) 82 | target.moneyAvailable = ns.getServerMoneyAvailable(target.hostname) 83 | target.weakTime = weakTime(target) 84 | target.hackTime = hackTime(target) 85 | target.growTime = growTime(target) 86 | } 87 | 88 | 89 | /** 90 | * @param {NS} ns 91 | * @param {Server} target 92 | * @param {Server[]} serversWithRam 93 | * @param {BatchDataQueue} queue 94 | * @param {boolean} corrective 95 | */ 96 | function prepTarget(ns, target, serversWithRam, queue, corrective = false) { 97 | if (!corrective 98 | && isHealthy(ns, target) 99 | && queue.hasPreppingBatch(target.hostname)) { 100 | ns.print("No prep needed, continuing with hacking...") 101 | return 102 | } 103 | 104 | if ( corrective ) { 105 | target.hackDifficulty = target.hackDifficulty * 1.2 106 | target.moneyAvailable = target.moneyAvailable * 0.5 107 | } 108 | 109 | const builder = new PrepBuilder(target) 110 | builder.calcTasks() 111 | builder.assignServers(serversWithRam) 112 | if (builder.isEmpty()) { 113 | ns.print("Found zero ram to fulfill prepping, try again later....") 114 | saveBatches(queue) 115 | ns.exit() 116 | } 117 | ns.print(`Launching prepping batch!`) 118 | launch(ns,builder,queue) 119 | } 120 | 121 | /** 122 | * @param {NS} ns 123 | * @param {Server} server 124 | * @returns {boolean} Whether the server is grown to max and weakened enough 125 | **/ 126 | function isHealthy(ns, server) { 127 | return ns.getServerMoneyAvailable(server.hostname) >= (server.moneyMax*0.99) && server.hackDifficulty < (server.minDifficulty*1.005) 128 | } 129 | 130 | 131 | /** 132 | * @param {NS} ns 133 | * @param {Server} target 134 | * @param {Builder} builder 135 | * @param {Server[]} serversWithRam 136 | * @param {BatchDataQueue} queue 137 | */ 138 | function hackTarget(ns, target, builder, serversWithRam, queue) { 139 | builder.assignServers(serversWithRam) 140 | 141 | if (builder.isEmpty() || !builder.isFulfilled() ) { 142 | ns.print("Not enough ram to fulfill hacking batch, try again later....") 143 | saveBatches(queue) 144 | ns.exit() 145 | } 146 | 147 | ns.print(`Launching hacking batch, Target: ${target.hostname}`) 148 | launch(ns,builder, queue) 149 | } 150 | 151 | const fileNames = { 152 | 'hack' : 'batchHack.js', 153 | 'weak' : 'batchWeaken.js', 154 | 'grow' : 'batchGrow.js', 155 | } 156 | 157 | /** 158 | * @param {NS} ns 159 | * @param {PrepBuilder|HackBuilder} batcher 160 | * @param {BatchDataQueue} queue 161 | * Launches the batch 162 | */ 163 | function launch(ns, batcher, queue) { 164 | const target = batcher.target.hostname 165 | const batchID = fetchNextBatchID() 166 | const longestTime = Math.max(...batcher.tasks.map(t => t.time)) 167 | ns.print(`batch id: ${batchID}`) 168 | 169 | const errorWindowStartTime = Date.now() + longestTime 170 | for (const job of batcher.tasks) { 171 | ns.print(job) 172 | const delay = longestTime - job.time 173 | const args = JSON.stringify({id: batchID, delay: delay, target: target}) 174 | if (job.threads == 0) continue 175 | job.pids = [] 176 | for (const server of job.servers) { 177 | const pid = ns.exec(fileNames[job.type],server[0],{threads: server[1]}, args) 178 | job.pids.push(pid) 179 | server.push(pid) 180 | } 181 | } 182 | 183 | if ( batcher.tasks.some(t => t.pids.some(p => p == 0)) && batcher.type != 'Prepping') { 184 | ns.tprint(`ERROR: One or more pids was zero! Canceling other jobs in batch ${batchID}.`) 185 | for (let job of batcher.tasks) { 186 | job.pids.forEach(pid => pid == 0 ? null : ns.kill(pid)) 187 | } 188 | saveBatches(queue) 189 | ns.exit(); 190 | } 191 | 192 | const errorWindowEndTime = Date.now() + longestTime 193 | const pidsWithThreads = [] 194 | batcher.tasks.forEach((j) => { pidsWithThreads.push([j.type, ...j.servers]) }) 195 | ns.print(`recordBatch(start, end, id, longestTime, type, target, pids)`) 196 | ns.print(`recordBatch(${errorWindowStartTime}, ${errorWindowEndTime}, ${batchID}, ${longestTime}, ${batcher.type}, ${target}, ${pidsWithThreads})})`) 197 | queue.addNewJob(errorWindowStartTime, 198 | errorWindowEndTime, 199 | batchID, 200 | longestTime, 201 | batcher.type, 202 | target, 203 | pidsWithThreads) 204 | } 205 | 206 | /** 207 | * @returns {num} Next batch ID number, probably unique 208 | */ 209 | function fetchNextBatchID() { 210 | let lastID = parseInt( (getLSItem('batchJobId') ?? 0), 36 ) 211 | let nextID = lastID + 1 212 | if ( nextID > 9_999_999_999_999 ) { nextID = 1 } 213 | setLSItem('batchJobId', nextID.toString(36)) 214 | return nextID.toString(36) 215 | } 216 | 217 | /** 218 | * @returns {BatchDataQueue} For working with upcoming batches 219 | */ 220 | function fetchBatchQueue() { 221 | let rawData = getLSItem('batches') ?? [] 222 | let batchDataQueue = new BatchDataQueue(rawData) 223 | batchDataQueue.discardExpiredBatchData() 224 | return batchDataQueue 225 | } 226 | 227 | /** 228 | * Saves the batch to the list 229 | * @param {BatchDataQueue} queue 230 | * @returns {null} 231 | */ 232 | function saveBatches(queue) { 233 | setLSItem('batches', queue.toObj()) 234 | } 235 | -------------------------------------------------------------------------------- /src/batching/shotgunBuilder.js: -------------------------------------------------------------------------------- 1 | import { 2 | calcThreadsToGrow, 3 | calcThreadsToHack, 4 | calcRam, ramSizes, 5 | calcHackAmount, 6 | calculatePercentMoneyHacked } from '/batching/calculations.js' 7 | import { createCurrentFormulas } from 'utils/formulas.js' 8 | 9 | // Game-set constants. Don't change these magic numbers. 10 | export const growsPerWeaken = 12.5 11 | export const hacksPerWeaken = 25 12 | 13 | /** @param {NS} ns */ 14 | export async function main(ns) { 15 | ns.tprint('ERROR: /batching/shotgunBuilder.js is not meant to be run independently.') 16 | } 17 | 18 | 19 | class BatchTask { 20 | /** 21 | * @param {string} type 22 | * @param {Integer} threads 23 | * @param {Float} ram 24 | * @param {Float} time 25 | */ 26 | constructor(type, threads, ram, time) { 27 | this.type = type 28 | this.threads = threads 29 | this.ram = ram 30 | this.time = time 31 | this.servers 32 | } 33 | } 34 | 35 | 36 | /** 37 | * @prop {Server} target 38 | * @prop {[BatchTask]} tasks 39 | */ 40 | class Builder { 41 | /** 42 | * @param {Server} target 43 | */ 44 | constructor(target) { 45 | this.target = target 46 | this.tasks = [] 47 | this.formulas = createCurrentFormulas(); 48 | } 49 | 50 | /** 51 | * @param {NS} ns 52 | * @param {Server[]} serversWithRam 53 | * @returns {BatchTask[]} The tasks with added chosen servers and # of threads 54 | **/ 55 | assignServers(serversWithRam) { 56 | this.tasks.forEach(task => { 57 | let servers = this.matchServers(task, serversWithRam) 58 | task.servers = servers 59 | }) 60 | return this.tasks 61 | } 62 | 63 | /** 64 | * @returns {boolean} true if all batches have matching servers 65 | */ 66 | isFulfilled() { 67 | return this.tasks.every(b => b.servers && b.servers.length > 0) 68 | } 69 | 70 | /** 71 | * @returns {boolean} true if all batches have matching servers 72 | */ 73 | isEmpty() { 74 | return this.tasks.every(b => !b.servers || b.servers.length == 0) 75 | } 76 | 77 | /** 78 | * @returns {float} GB of ram required to run a full batch 79 | */ 80 | calcTotalRamRequired() { 81 | if (this.tasks.length == 0) 82 | this.calcTasks() 83 | 84 | return this.tasks.reduce((a,b) => a + b.ram, 0) 85 | } 86 | 87 | /** 88 | * Empties the task servers array so new servers can be assigned 89 | */ 90 | clearServerAssignments() { 91 | this.tasks.map(task => task.servers = []) 92 | } 93 | } 94 | 95 | export class PrepBuilder extends Builder { 96 | type = 'Prepping' 97 | 98 | /** 99 | * @returns {BatchTask[]} The ram and threads for weaken, grow, weaken until 100 | * the target server is prepped 101 | * [ 102 | * weaken1: {type: weaken, threads: y, time: x} 103 | * grow: {type: grow, threads: y, time: z}, 104 | * weaken2: {type: weaken, threads: y, time: x} 105 | * ] 106 | **/ 107 | calcTasks() { 108 | let weakTh1 = Math.ceil(((this.target.hackDifficulty - this.target.minDifficulty) / 0.05)) 109 | let growTh = calcThreadsToGrow(this.target, this.target.moneyMax) + 1 110 | let weakTh2 = Math.ceil((growTh/growsPerWeaken)) 111 | this.tasks = [ 112 | new BatchTask('weak', weakTh1, calcRam('weak', weakTh1), this.target.weakTime), 113 | new BatchTask('grow', growTh, calcRam('grow', growTh), this.target.growTime), 114 | new BatchTask('weak', weakTh2, calcRam('weak', weakTh2), this.target.weakTime), 115 | ].filter(t => t.threads > 0) 116 | return this.tasks 117 | } 118 | 119 | /** 120 | * @param {BatchTask} task 121 | * @param {Server[]} serversWithRam 122 | * @returns {[string, Int][]} Array where each entry is sub-array of Server 123 | * hostname and number of threads assigned 124 | **/ 125 | matchServers(task, serversWithRam) { 126 | serversWithRam.sort((a,b) => a.availableRam - b.availableRam) 127 | let server = serversWithRam.find(s => s.availableRam >= task.ram) 128 | if (server) { 129 | server.availableRam -= task.ram 130 | return [[server.hostname, task.threads]] 131 | } 132 | serversWithRam.sort((a,b) => b.availableRam - a.availableRam) 133 | let neededThreads = task.threads 134 | let scriptSize = ramSizes[task.type] 135 | let servers = [] 136 | serversWithRam.forEach(server => { 137 | if (neededThreads == 0 ) return 138 | 139 | if (server.availableRam > scriptSize) { 140 | let useThreads = Math.min(neededThreads, Math.floor(server.availableRam/scriptSize)) 141 | let useRam = useThreads*scriptSize 142 | server.availableRam -= useRam 143 | servers.push([server.hostname, useThreads]) 144 | neededThreads -= useThreads 145 | } 146 | }) 147 | return servers 148 | } 149 | } 150 | 151 | export class HackBuilder extends Builder { 152 | type = 'Hacking' 153 | 154 | /** 155 | * @returns {BatchTask[]} The needed ram and threads for HWGW batch 156 | **/ 157 | calcTasks() { 158 | // zero out the server, assume prepping script goes well 159 | let mA = this.target.moneyAvailable 160 | let hD = this.target.hackDifficulty 161 | this.target.moneyAvailable = this.target.moneyMax 162 | this.target.hackDifficulty = this.target.minDifficulty 163 | 164 | let hackDecimal = calcHackAmount(this.target) 165 | let moneyToHack = this.target.moneyAvailable * hackDecimal 166 | let hackTh = calcThreadsToHack(this.target, moneyToHack) 167 | hackTh = Math.max(hackTh, 1) 168 | let weakTh1 = Math.ceil(hackTh/hacksPerWeaken) 169 | this.target.moneyAvailable -= this.target.moneyAvailable * 170 | calculatePercentMoneyHacked(this.target) * 171 | hackTh 172 | let growTh = calcThreadsToGrow(this.target, this.target.moneyMax) 173 | // to offset the loss during levelups or desyncs 174 | // growTh += Math.ceil(growTh * 0.02) 175 | let weakTh2 = Math.ceil(growTh/growsPerWeaken) 176 | // reset to actual 177 | this.target.moneyAvailable = mA 178 | this.target.hackDifficulty = hD 179 | this.tasks = [ 180 | new BatchTask('hack', hackTh, calcRam('hack', hackTh), this.target.hackTime), 181 | new BatchTask('weak', weakTh1, calcRam('weak', weakTh1), this.target.weakTime), 182 | new BatchTask('grow', growTh, calcRam('grow', growTh), this.target.growTime), 183 | new BatchTask('weak', weakTh2, calcRam('weak', weakTh2), this.target.weakTime), 184 | ].filter(t => t.threads > 0) 185 | 186 | return this.tasks 187 | } 188 | 189 | /** 190 | * @param {BatchTask} task 191 | * @param {Server[]} serversWithRam 192 | * @returns {[string, Int][]} Array where each entry is sub-array of Server 193 | * hostname and number of threads assigned 194 | **/ 195 | matchServers(task, serversWithRam) { 196 | serversWithRam.sort((a,b) => a.availableRam - b.availableRam) 197 | let server = serversWithRam.find(s => s.availableRam >= task.ram) 198 | if (server) { 199 | server.availableRam -= task.ram 200 | return [[server.hostname, task.threads]] 201 | } 202 | 203 | if (task.type == "weak") { 204 | serversWithRam.sort((a,b) => b.availableRam - a.availableRam) 205 | let neededThreads = task.threads 206 | let scriptSize = ramSizes[task.type] 207 | let servers = [] 208 | let useThreads, useRam 209 | for (let server of serversWithRam) { 210 | if (server.availableRam < scriptSize) 211 | continue 212 | useThreads = Math.min(neededThreads, Math.floor(server.availableRam/scriptSize)) 213 | useRam = useThreads*scriptSize 214 | server.availableRam -= useRam 215 | servers.push([server.hostname, useThreads]) 216 | neededThreads -= useThreads 217 | if (neededThreads < 1 ) 218 | return servers 219 | } 220 | return servers 221 | } 222 | return [] 223 | } 224 | } 225 | 226 | -------------------------------------------------------------------------------- /src/bestHack.js: -------------------------------------------------------------------------------- 1 | import { 2 | fetchPlayer, 3 | getLSItem, 4 | formatDuration, 5 | formatRam 6 | } from 'utils/helpers.js' 7 | import { HackBuilder } from '/batching/builder.js' 8 | import { 9 | weakTime, 10 | calculatePercentMoneyHacked 11 | } from '/batching/calculations.js' 12 | import { createCurrentFormulas } from 'utils/formulas.js' 13 | 14 | 15 | /** 16 | * Calculate a score to decide how lucrative it is to hack a server 17 | * @param {Server} s 18 | */ 19 | export function calcScore(s) { 20 | let player = fetchPlayer() 21 | let formulas = createCurrentFormulas() 22 | // clone the server so these manipulations don't affect anything else 23 | let server = structuredClone(s) 24 | // Set up the calculation with everything min/maxed 25 | server.hackDifficulty = server.minDifficulty 26 | server.moneyAvailable = server.moneyMax 27 | 28 | let batcher = new HackBuilder(server) 29 | let totalRamRequired = batcher.calcTotalRamRequired() 30 | let percentPerHack = calculatePercentMoneyHacked(server) 31 | let hackChance = formulas.hacking.hackChance(server, player) 32 | let moneyPerHack = server.moneyMax * percentPerHack * hackChance 33 | let maxTime = weakTime(server) / 1000 34 | 35 | return moneyPerHack/totalRamRequired/maxTime 36 | } 37 | 38 | 39 | export class BestHack { 40 | constructor(serverData) { 41 | this.serverData = serverData 42 | } 43 | 44 | /** 45 | * @param {number} player_hacking 46 | */ 47 | findBestPerLevel(player_hacking) { 48 | let filtered = this.findTop(player_hacking) 49 | if (filtered.length == 0) { 50 | return false 51 | } 52 | return filtered[0] 53 | } 54 | 55 | /** 56 | * @param {number} player_hacking 57 | */ 58 | findTop(player_hacking) { 59 | let filtered = this.filterServers(player_hacking) 60 | filtered.map(s => s.hackableScore = calcScore(s)) 61 | return filtered.sort((a, b) => b.hackableScore - a.hackableScore) 62 | } 63 | 64 | /** 65 | * @param {number} player_hacking 66 | * @param {number} count 67 | */ 68 | findTopN(player_hacking, count) { 69 | let filtered = this.findTop(player_hacking) 70 | return filtered.slice(0, count) 71 | } 72 | 73 | /** 74 | * @param {number} player_hacking 75 | */ 76 | filterServers(player_hacking) { 77 | let filtered = Object.values(this.serverData) 78 | .filter((server) => server.requiredHackingSkill <= Math.max(Math.floor(player_hacking - 1), 1) && 79 | server.hasAdminRights && 80 | server.moneyMax > 0) 81 | return filtered 82 | } 83 | } 84 | 85 | export function findBestTarget() { 86 | let map = getLSItem('nmap') 87 | if (! map || map.length == 0 ) { 88 | throw new Error("No network map exists, BestHack can't work.") 89 | } 90 | 91 | let searcher = new BestHack(map) 92 | return searcher.findBestPerLevel(fetchPlayer().skills.hacking) 93 | } 94 | 95 | export function findTop() { 96 | let map = getLSItem('nmap') 97 | if (! map || map.length == 0 ) { 98 | throw new Error("No network map exists, BestHack can't work.") 99 | } 100 | 101 | let searcher = new BestHack(map) 102 | return searcher.findTop(fetchPlayer().skills.hacking) 103 | } 104 | 105 | export async function main(ns) { 106 | let map = getLSItem('nmap') 107 | if (! map ) { 108 | throw new Error("No network map exists, BestHack can't work.") 109 | } 110 | ns.clearLog() 111 | 112 | let searcher = new BestHack(map) 113 | let player = fetchPlayer() 114 | ns.print(player.skills.hacking) 115 | ns.print(` s.name , s.moneyMax, calcScore(s), weakTime(s), ramRequired ]`) 116 | let top = searcher.findTop(player.skills.hacking) 117 | for (let s of top) { 118 | ns.print(" | ", 119 | s.name.padEnd(18), 120 | `\$${ns.formatNumber(s.moneyMax,2).padStart(10)}`, 121 | ns.formatNumber(calcScore(s),2).padStart(14), 122 | formatDuration(weakTime(s)).padStart(13), 123 | formatRam((new HackBuilder(s).calcTotalRamRequired())).padStart(13) 124 | ) 125 | } 126 | ns.tprint( top[0] ) 127 | } 128 | -------------------------------------------------------------------------------- /src/botnet.js: -------------------------------------------------------------------------------- 1 | import { 2 | disableLogs, 3 | getLSItem, 4 | announce, 5 | } from 'utils/helpers.js' 6 | // magic number (Ram required to run hack.js) 7 | const hackingScriptSize = 1.7 8 | const scripts = [ 9 | 'breadwinner.js', 10 | 'batchGrow.js', 11 | 'batchHack.js', 12 | 'batchWeaken.js', 13 | 'share.js' 14 | ] 15 | const argsSchema = [['overwrite', false]] 16 | 17 | export function autocomplete(data, args) { 18 | data.flags(argsSchema) 19 | } 20 | 21 | /** 22 | * @param {NS} ns 23 | **/ 24 | export async function main(ns) { 25 | disableLogs(ns, ['sleep', 'scp']) 26 | let options = ns.flags(argsSchema) 27 | ns.print(options.overwrite) 28 | let servers = fetchZombifyableServers(ns, options.overwrite) 29 | if (!servers) return 30 | 31 | ns.tprint("Zombifying " + servers.length + " servers") 32 | var success = [] 33 | var failure = [] 34 | for (let server of servers) { 35 | var res = zombify(ns, server.name) 36 | res ? success.push(server) : failure.push(server) 37 | await ns.sleep(20) 38 | } 39 | if (success.length > 0 ) { 40 | let msg = `Zombified servers: ${success.map(s => s.name).join(', ')}` 41 | announce(ns, msg) 42 | ns.tprint(msg) 43 | } 44 | if (failure.length > 0 ) { 45 | let msg = `ERROR: failed to zombify servers: ${failure.map(s => s.name).join(', ')}` 46 | announce(ns, msg) 47 | ns.tprint(msg) 48 | } 49 | } 50 | 51 | /** 52 | * @param {array} files - files on a server 53 | * @returns {boolean} - whether those files include all the scripts we need to upload 54 | **/ 55 | function hasAllScripts(files) { 56 | return scripts.every((script) => files.includes(script)) 57 | } 58 | 59 | /** 60 | * fetchZombifyableServers 61 | * Find any servers in the network that haven't gotten the copied files yet. 62 | * @param {NS} NS 63 | * @param {boolean} overwrite - overwrite existing filess 64 | * @returns {boolean|array} - servers we can work on, or false if none available 65 | **/ 66 | function fetchZombifyableServers(ns, overwrite) { 67 | ns.print("Fetching servers from nmap") 68 | let nmap = getLSItem('nmap') 69 | if (! nmap ) { 70 | ns.print('NMAP is not populated, try again later.') 71 | return false 72 | } 73 | let servers = Object.values(nmap) 74 | .filter(s => s.hasAdminRights && 75 | s.name != 'home' && 76 | s.maxRam >= hackingScriptSize && 77 | (overwrite || !hasAllScripts(ns.ls(s.hostname))) 78 | ) 79 | ns.print(servers.map(s => [s.name, s.hasAdminRights, s.maxRam, s.ramUsed, s.files])) 80 | // early return, if there are no servers no need to do anything else 81 | if ( servers.length == 0 ) { 82 | ns.print("Everything we can zombify has been already.") 83 | return false 84 | } 85 | return servers 86 | } 87 | 88 | /** 89 | * @param {NS} ns 90 | * @param {string} server - Server host name 91 | * @returns {boolean} - whether the files successfully copied 92 | **/ 93 | function zombify(ns, server) { 94 | var res = ns.scp(scripts, server, "home") 95 | if ( res ) { 96 | ns.print(`Copied ${scripts} to ${server}`) 97 | } else { 98 | ns.print(`ERROR: Failed in copying ${scripts} to ${server}`) 99 | } 100 | return res 101 | } 102 | -------------------------------------------------------------------------------- /src/breadwinner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * breadwinner.js 3 | * 4 | * Built to be run independantly, this is the teenager version of the starter script. 5 | * Very cheap ram-wise because it calculates many things manually. 6 | * Works best if each instance of breadwinner.js is targeting a different server, 7 | * to reduce collisions. 8 | * 9 | * Called with `run breadwinner.js -t 17 n00dles 17` 10 | * 11 | * Threads are passed as the second argument to avoid ns.getRunningScript() & similar. 12 | **/ 13 | 14 | 15 | export function autocomplete(data, args) { 16 | return data.servers 17 | } 18 | 19 | /** 20 | * @param {NS} ns 21 | **/ 22 | export async function main(ns) { 23 | ns.disableLog('disableLog') 24 | ns.disableLog('sleep') 25 | 26 | let target = await fetchServer(ns.args[0]) 27 | let maxMoney = target.maxMoney 28 | let moneyThreshhold = maxMoney 29 | let securityThreshhold = target.minSecurity + 3 30 | let money, threads, growFactor, player; 31 | let scriptThreads = parseInt(ns.args[1]) 32 | 33 | while (true) { 34 | await ns.sleep(53) 35 | target = await fetchServer(ns.args[0]) 36 | ns.print(`Security: ${ns.formatNumber(target.security, 2)}`) 37 | if (target.security > securityThreshhold) { 38 | ns.print("------ Target security: " + securityThreshhold) 39 | await ns.weaken(target.name) 40 | continue 41 | } 42 | 43 | target = await fetchServer(ns.args[0]) 44 | money = target.moneyAvailable 45 | ns.print("Current money: $" + ns.formatNumber(money, 2) ) 46 | 47 | if (money < moneyThreshhold) { 48 | ns.print("------ Target money: " + ns.formatNumber(moneyThreshhold, 2)) 49 | threads = numCycleForGrowth(target, maxMoney, ns) 50 | ns.print(`Calculating ${threads} to grow \$${(moneyThreshhold-money)} from ${target.name} (vs ${scriptThreads})`) 51 | threads = Math.min(threads, scriptThreads) 52 | await ns.grow(target.name, { threads: threads }); 53 | continue 54 | } 55 | 56 | if (money <= 0) { 57 | ns.print("Not enough money to hack, continuing", target.name, money) 58 | continue 59 | } 60 | 61 | target = await fetchServer(ns.args[0]) 62 | let desiredHackAmount = money * 0.05 63 | threads = hackThreads(target, desiredHackAmount) 64 | ns.print(`Calculating ${threads} to hack \$${desiredHackAmount} from ${target.name} (vs ${scriptThreads})`) 65 | threads = Math.min(threads, scriptThreads) 66 | if (threads == -1) { 67 | ns.tprint("Threads negative!") 68 | continue 69 | } 70 | 71 | await ns.hack(target.name, { threads: threads }) 72 | } 73 | } 74 | 75 | function hackThreads(server, hackAmount) { 76 | if (hackAmount < 0 || hackAmount > server.moneyAvailable) { 77 | return -1; 78 | } 79 | 80 | const percentHacked = calculatePercentMoneyHacked(server) 81 | return Math.floor(hackAmount / (server.moneyAvailable * percentHacked)) 82 | } 83 | 84 | /** 85 | * Returns the percentage of money that will be stolen from a server if 86 | * it is successfully hacked (returns the decimal form, not the actual percent value) 87 | */ 88 | export function calculatePercentMoneyHacked(server) { 89 | // Adjust if needed for balancing. This is the divisor for the final calculation 90 | const balanceFactor = 240; 91 | const player = fetchPlayer() 92 | 93 | const difficultyMult = (100 - server.hackDifficulty) / 100; 94 | const skillMult = (player.skills.hacking - (server.requiredHackingSkill - 1)) / player.skills.hacking; 95 | const percentMoneyHacked = (difficultyMult * skillMult * player.mults.hacking_money) / balanceFactor; 96 | if (percentMoneyHacked < 0) { 97 | return 0; 98 | } 99 | if (percentMoneyHacked > 1) { 100 | return 1; 101 | } 102 | 103 | let scriptHackMoneyMult = getLSItem('bitnode')["ScriptHackMoney"] 104 | return percentMoneyHacked * scriptHackMoneyMult 105 | } 106 | 107 | /** 108 | * Returns the number of "growth cycles" needed to grow the specified server by 109 | * the specified amount. 110 | * @param server - Server being grown 111 | * @param targetMoney - - How much you want the server grown TO (not by), for instance, to grow from 200 to 600, input 600 112 | * @returns Number of "growth cycles" needed 113 | */ 114 | export function numCycleForGrowth(server, targetMoney) { 115 | let person = fetchPlayer() 116 | let startMoney = server.moneyAvailable 117 | 118 | const k = calculateServerGrowthLog(server, 1, person); 119 | const guess = (targetMoney - startMoney) / (1 + (targetMoney * (1 / 16) + startMoney * (15 / 16)) * k); 120 | let x = guess; 121 | let diff; 122 | do { 123 | const ox = startMoney + x; 124 | // Have to use division instead of multiplication by inverse, because 125 | // if targetMoney is MIN_VALUE then inverting gives Infinity 126 | const newx = (x - ox * Math.log(ox / targetMoney)) / (1 + ox * k); 127 | diff = newx - x; 128 | x = newx; 129 | } while (diff < -1 || diff > 1); 130 | /* If we see a diff of 1 or less we know all future diffs will be smaller, and the rate of 131 | * convergence means the *sum* of the diffs will be less than 1. 132 | 133 | * In most cases, our result here will be ceil(x). 134 | */ 135 | const ccycle = Math.ceil(x); 136 | if (ccycle - x > 0.999999) { 137 | // Rounding-error path: It's possible that we slightly overshot the integer value due to 138 | // rounding error, and more specifically precision issues with log and the size difference of 139 | // startMoney vs. x. See if a smaller integer works. Most of the time, x was not close enough 140 | // that we need to try. 141 | const fcycle = ccycle - 1; 142 | if (targetMoney <= (startMoney + fcycle) * Math.exp(k * fcycle)) { 143 | return fcycle; 144 | } 145 | } 146 | if (ccycle >= x + ((diff <= 0 ? -diff : diff) + 0.000001)) { 147 | // Fast-path: We know the true value is somewhere in the range [x, x + |diff|] but the next 148 | // greatest integer is past this. Since we have to round up grows anyway, we can return this 149 | // with no more calculation. We need some slop due to rounding errors - we can't fast-path 150 | // a value that is too small. 151 | return ccycle; 152 | } 153 | if (targetMoney <= (startMoney + ccycle) * Math.exp(k * ccycle)) { 154 | return ccycle; 155 | } 156 | return ccycle + 1 157 | } 158 | 159 | // Returns the log of the growth rate. When passing 1 for threads, this gives a useful constant. 160 | export function calculateServerGrowthLog(server, threads, p, cores = 1) { 161 | if (!server.serverGrowth) return -Infinity; 162 | const hackDifficulty = server.hackDifficulty ?? 100; 163 | const numServerGrowthCycles = Math.max(threads, 0); 164 | 165 | const serverBaseGrowthIncr = 0.03 // Unadjusted growth increment (growth rate is this * adjustment + 1) 166 | const serverMaxGrowthLog = 0.00349388925425578 // Maximum possible growth rate accounting for server security, precomputed as log1p(.0035) 167 | 168 | //Get adjusted growth log, which accounts for server security 169 | //log1p computes log(1+p), it is far more accurate for small values. 170 | let adjGrowthLog = Math.log1p(serverBaseGrowthIncr / hackDifficulty); 171 | if (adjGrowthLog >= serverMaxGrowthLog) { 172 | adjGrowthLog = serverMaxGrowthLog; 173 | } 174 | 175 | //Calculate adjusted server growth rate based on parameters 176 | const serverGrowthPercentage = server.serverGrowth / 100; 177 | const serverGrowthPercentageAdjusted = serverGrowthPercentage * getLSItem('bitnode')['ServerGrowthRate']; 178 | 179 | //Apply serverGrowth for the calculated number of growth cycles 180 | const coreBonus = 1 + (cores - 1) * (1 / 16); 181 | // It is critical that numServerGrowthCycles (aka threads) is multiplied last, 182 | // so that it rounds the same way as numCycleForGrowth. 183 | return adjGrowthLog * serverGrowthPercentageAdjusted * p.mults.hacking_grow * coreBonus * numServerGrowthCycles; 184 | } 185 | 186 | export const lsKeys = { 187 | NMAP : 'jh_network_map', 188 | PLAYER : 'jh_player', 189 | BITNODE : 'jh_bn_multipliers', 190 | } 191 | 192 | /** 193 | * @param {NS} ns 194 | * @param {string} serverName 195 | **/ 196 | async function fetchServer(serverName) { 197 | let map = await networkMap() 198 | return map[serverName] 199 | } 200 | 201 | function fetchPlayer() { 202 | return getLSItem('player') 203 | } 204 | 205 | /** 206 | * @params {string} key 207 | * from helpers.js because copying all the files everywhere is not my jam 208 | **/ 209 | function getLSItem(key) { 210 | let item = localStorage.getItem(lsKeys[key.toUpperCase()]) 211 | 212 | return item ? JSON.parse(item) : undefined 213 | } 214 | 215 | /** 216 | * @param {NS} ns 217 | **/ 218 | async function networkMap() { 219 | let map = getLSItem('NMAP') 220 | 221 | while ( map === undefined ) { 222 | await mySleep(5) 223 | map = getLSItem('NMAP') 224 | } 225 | 226 | return map; 227 | } 228 | 229 | /** 230 | * @param {integer} ms 231 | **/ 232 | function mySleep(ms){ 233 | return new Promise(resolve => setTimeout(resolve, ms)) 234 | } 235 | -------------------------------------------------------------------------------- /src/cleanupStaleScripts.js: -------------------------------------------------------------------------------- 1 | import { networkMapFree } from 'utils/network.js' 2 | 3 | const hangableFiles = [ 4 | 'batchGrow.js', 5 | 'batchHack.js', 6 | 'batchWeaken.js' 7 | ] 8 | const badLog = "Waiting for port write." 9 | 10 | /** @param {NS} ns */ 11 | export async function main(ns) { 12 | let map = networkMapFree() 13 | for (let server of Object.values(map)) { 14 | ns.print(server.hostname) 15 | let scripts = ns.ps(server.hostname) 16 | for (let s of scripts) { 17 | if (!hangableFiles.includes(s.filename)) 18 | continue 19 | let script = ns.getRunningScript(s.pid) 20 | if (!script.logs[script.logs.length-1].includes(badLog)) 21 | continue 22 | if (script.onlineRunningTime < 5) 23 | continue 24 | ns.print(`--- ${script.filename} ${script.onlineRunningTime}`) 25 | ns.kill(s.pid) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/contracts/CodingContractWrapper.js: -------------------------------------------------------------------------------- 1 | /** Example usage: 2 | 3 | export async function main(ns, file, type, server) { 4 | const codingContractor = new CodingContractWrapper(ns, {file: filename}) 5 | const answer = solve(await codingContractor.extractData()) 6 | codingContractor.sendSolution(answer) 7 | } 8 | 9 | function solve(data) { 10 | solve the puzzle here! 11 | return solution 12 | } 13 | 14 | **/ 15 | 16 | import { 17 | getNsDataThroughFile as fetch, 18 | announce, 19 | } from 'utils/helpers.js' 20 | 21 | export class CodingContractWrapper { 22 | /** @param {NS} ns **/ 23 | constructor(ns, file, type, server) { 24 | this.ns = ns 25 | this.file = file 26 | this.type = type 27 | this.server = server 28 | ns.tprint(`Found ${this.file} (${this.type}) on ${this.server}`) 29 | } 30 | 31 | // Get the coding contract puzzle data 32 | async extractData() { 33 | let cmd = `ns.codingcontract.getData('${this.file}', '${this.server}')` 34 | this.data = await fetch(this.ns, cmd, `/Temp/codingcontract.getData.txt`) 35 | return this.data 36 | } 37 | 38 | // attempt to send the solution for the coding contract 39 | async sendSolution(solution) { 40 | const result = await fetch(this.ns, `ns.codingcontract.attempt( 41 | ${JSON.stringify(solution)}, 42 | '${this.file}', 43 | '${this.server}', 44 | { returnReward: true })`, 45 | '/Temp/codingContract.attempt.txt') 46 | const msg = `${this.file} attempt result: ${result}` 47 | announce(this.ns, msg) 48 | 49 | if ( result === '' ) { 50 | this.ns.tprint(`**************** Failure detected! ********************`) 51 | this.ns.tprint(JSON.stringify({ file: this.file, type: this.type, server: this.server })) 52 | this.ns.tprint(JSON.stringify(this.data)) 53 | this.ns.tprint(solution) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/contracts/arrayJumpingSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Array Jumping Game 3 | * 4 | * You are given the following array of integers: 5 | * 6 | * 7,10,5,8,0,0,7,0,4,0,2,2,6,7 7 | * 8 | * Each element in the array represents your MAXIMUM jump length at that 9 | * position. This means that if you are at position i and your maximum jump 10 | * length is n, you can jump to any position from i to i+n. 11 | * 12 | * Assuming you are initially positioned at the start of the array, determine 13 | * whether you are able to reach the last index exactly. 14 | * 15 | * Your answer should be submitted as 1 or 0, representing true and false 16 | * respectively 17 | **/ 18 | 19 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 20 | 21 | /** @param {NS} ns **/ 22 | export async function main(ns, file, type, server) { 23 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 24 | const answer = solve(await codingContractor.extractData()) 25 | await codingContractor.sendSolution(answer) 26 | } 27 | 28 | function solve(arr) { 29 | let farthest = arr[0] 30 | 31 | for (let i = 0; i <= farthest; i++) { 32 | farthest = Math.max(farthest, i+arr[i]) 33 | if ( farthest >= arr.length-1 ) { return 1 } 34 | } 35 | 36 | return 0 37 | } 38 | -------------------------------------------------------------------------------- /src/contracts/compression1RLE.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compression I: RLE Compression 3 | * 4 | * You are attempting to solve a Coding Contract. You have 10 tries remaining, 5 | * after which the contract will self-destruct. 6 | * 7 | * Run-length encoding (RLE) is a data compression technique which encodes data 8 | * as a series of runs of a repeated single character. Runs are encoded as a 9 | * length, followed by the character itself. Lengths are encoded as a single 10 | * ASCII digit; runs of 10 characters or more are encoded by splitting them 11 | * into multiple runs. 12 | * 13 | * You are given the following input string: 14 | * sFFFFFFFFGerk0y3q00BBBBBBBBBBaaaaaaaaa4SSg62266QffffffffT111111111zllllllllllRR 15 | * Encode it using run-length encoding with the minimum possible output 16 | * length. 17 | **/ 18 | 19 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 20 | 21 | /** @param {NS} ns **/ 22 | export async function main(ns, file, type, server) { 23 | const testVal1 = 'aaaaabccc' 24 | if (solve(testVal1) !== '5a1b3c') { 25 | ns.print("ERROR: 'aaaaabccc' should solve to '5a1b3c'.") 26 | ns.print(`ERROR: 'aaaaabccc' solves to '${solve(testVal1)}'.`) 27 | ns.exit() 28 | } 29 | ns.print("SUCCESS: Test 1 passed. ") 30 | const testVal2 = 'aAaAaA' 31 | if (solve(testVal2) !== '1a1A1a1A1a1A') { 32 | ns.print("ERROR: 'aAaAaA' should solve to '1a1A1a1A1a1A'.") 33 | ns.print(`ERROR: 'aAaAaA' solves to '${solve(testVal2)}'.`) 34 | ns.exit() 35 | } 36 | ns.print("SUCCESS: Test 2 passed. ") 37 | const testVal3 = '111112333' 38 | if (solve(testVal3) !== '511233') { 39 | ns.print("ERROR: '111112333' should solve to '511233'.") 40 | ns.print(`ERROR: '111112333' solves to '${solve(testVal3)}'.`) 41 | ns.exit() 42 | } 43 | ns.print("SUCCESS: Test 3 passed. ") 44 | const testVal4 = 'zzzzzzzzzzzzzzzzzzz' 45 | if (solve(testVal4) !== '9z9z1z') { 46 | ns.print("ERROR: 'zzzzzzzzzzzzzzzzzzz' should solve to '9z9z1z'.") 47 | ns.print(`ERROR: 'zzzzzzzzzzzzzzzzzzz' solves to '${solve(testVal4)}'.`) 48 | ns.exit() 49 | } 50 | ns.print("SUCCESS: Test 4 passed. ") 51 | ns.print("SUCCESS: All tests passed.") 52 | 53 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 54 | const answer = solve(await codingContractor.extractData()) 55 | await codingContractor.sendSolution(answer) 56 | } 57 | 58 | /** 59 | * @param {string} data 60 | */ 61 | function solve(data) { 62 | const regex = /(.)\1*/g 63 | const matches = data.matchAll(regex) 64 | 65 | let res = "" 66 | for (const match of matches) { 67 | res += processSubstring(match[0]) 68 | } 69 | 70 | return res 71 | } 72 | 73 | /** 74 | * @param {string} substr 75 | */ 76 | function processSubstring(substr) { 77 | const remainder = substr.length % 9 78 | const divisor = Math.floor(substr.length / 9) 79 | let res = "" 80 | if (divisor > 0 ) 81 | res += `9${substr[0]}`.repeat(divisor) 82 | if ( remainder > 0 ) 83 | res += remainder + substr[0] 84 | return res 85 | } 86 | -------------------------------------------------------------------------------- /src/contracts/encryption1CaesarCipher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Encryption I: Caesar Cipher 3 | * 4 | * You are attempting to solve a Coding Contract. You have 10 tries remaining, 5 | * after which the contract will self-destruct. 6 | * 7 | * Caesar cipher is one of the simplest encryption technique. It is a type of 8 | * substitution cipher in which each letter in the plaintext is replaced by a 9 | * letter some fixed number of positions down the alphabet. For example, with a 10 | * left shift of 3, D would be replaced by A, E would become B, and A would 11 | * become X (because of rotation). 12 | * 13 | * You are given an array with two elements: 14 | * ["CACHE VIRUS CLOUD PASTE DEBUG", 13] 15 | * The first element is the plaintext, the second element is the left shift 16 | * value. 17 | * 18 | * Return the ciphertext as uppercase string. Spaces remain the same. 19 | **/ 20 | 21 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 22 | 23 | /** @param {NS} ns **/ 24 | export async function main(ns, file, type, server) { 25 | const testVal1 = ['abc', 3] 26 | if (solve(testVal1) !== 'XYZ') { 27 | ns.print("ERROR: 'abc' should solve to 'XYZ'.") 28 | ns.print(`ERROR: 'abc' solves to '${solve(testVal1)}'.`) 29 | ns.exit() 30 | } 31 | ns.print("SUCCESS: Test 1 passed. ") 32 | const testVal2 = ['def xyz', 4] 33 | if (solve(testVal2) !== 'ZAB TUV') { 34 | ns.print("ERROR: 'def xyz' should solve to 'ZAB TUV'.") 35 | ns.print(`ERROR: 'def xyz' solves to '${solve(testVal2)}'.`) 36 | ns.exit() 37 | } 38 | ns.print("SUCCESS: Test 2 passed. ") 39 | const testVal3 = ['CACHE VIRUS CLOUD PASTE DEBUG', 13] 40 | const sol3 = 'PNPUR IVEHF PYBHQ CNFGR QROHT' 41 | if (solve(testVal3) !== sol3) { 42 | ns.print(`ERROR: '${testVal3}' should solve to '${sol3}'.`) 43 | ns.print(`ERROR: '${testVal3}' solves to '${solve(testVal3)}'.`) 44 | ns.exit() 45 | } 46 | ns.print("SUCCESS: Test 3 passed. ") 47 | ns.print("SUCCESS: All tests passed.") 48 | 49 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 50 | const answer = solve(await codingContractor.extractData()) 51 | await codingContractor.sendSolution(answer) 52 | } 53 | 54 | /** 55 | * @param {string} data 56 | */ 57 | function solve(data) { 58 | const plaintext = data[0] 59 | const leftShift = data[1] 60 | if ( typeof plaintext != 'string' || typeof leftShift != "number") 61 | return `ERROR: data passed in is formatted incorrectly! ${data}` 62 | 63 | let result = "" 64 | for (const char of plaintext) { 65 | const charCode = char.charCodeAt() 66 | 67 | // magic numbers: charCodes 65-90 are A-Z, 97-122 are a-z 68 | if ((charCode < 65 || charCode > 122) || 69 | (charCode > 90 && charCode < 97)) { 70 | result += char 71 | continue; 72 | } 73 | 74 | let rotChar = charCode - leftShift 75 | if ((charCode <= 90 && rotChar < 65) || (charCode >= 97 && rotChar < 97)) 76 | rotChar += 26 77 | result += String.fromCharCode(rotChar) 78 | } 79 | return result.toUpperCase() 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/contracts/encryption2VigenereCipher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Encryption II: Vigenère Cipher 3 | * 4 | * You are attempting to solve a Coding Contract. You have 10 tries remaining, 5 | * after which the contract will self-destruct. 6 | * 7 | * Vigenère cipher is a type of polyalphabetic substitution. It uses the 8 | * Vigenère square to encrypt and decrypt plaintext with a keyword. 9 | * 10 | * For encryption each letter of the plaintext is paired with the corresponding 11 | * letter of a repeating keyword. For example, the plaintext DASHBOARD is 12 | * encrypted with the keyword LINUX: 13 | * Plaintext: DASHBOARD 14 | * Keyword: LINUXLINU 15 | * So, the first letter D is paired with the first letter of the key L. 16 | * Therefore, row D and column L of the Vigenère square are used to get the 17 | * first cipher letter O. This must be repeated for the whole ciphertext. 18 | * 19 | * You are given an array with two elements: 20 | * ["VIRUSPASTESHELLTABLEINBOX", "HYPERLINK"] 21 | * The first element is the plaintext, the second element is the keyword. 22 | * 23 | * Return the ciphertext as uppercase string. 24 | **/ 25 | 26 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 27 | 28 | /** @param {NS} ns **/ 29 | export async function main(ns, file, type, server) { 30 | runTest(ns, ['ABC', 'ABC'], 'ACE') 31 | runTest(ns, ['TUVW XYZ', 'BACK'], 'UUXG YYB') 32 | runTest(ns, ['DASH BOARD', 'LINUX'], 'OIFB YZIEX') 33 | runTest(ns, ['dashboard', 'LINUX'], 'OIFBYZIEX') 34 | runTest(ns, ['DASHBOARD', 'linux'], 'OIFBYZIEX') 35 | runTest(ns, ['dashboard', 'linux'], 'OIFBYZIEX') 36 | ns.print("SUCCESS: All tests passed.") 37 | 38 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 39 | const answer = solve(await codingContractor.extractData()) 40 | await codingContractor.sendSolution(answer) 41 | } 42 | 43 | /** 44 | * @param {string[]} data 45 | */ 46 | function solve(data) { 47 | const [text, key] = data.map((v) => v.toUpperCase()) 48 | const keyLength = key.length 49 | let nonLetterCount = 0 50 | return text.split("").map((char, i) => { 51 | const charCode = char.charCodeAt() 52 | // magic numbers: charCodes 65-90 are A-Z 53 | // otherwise punctuation/numbers/whitespace, etc 54 | if (charCode < 65 || charCode > 90){ 55 | ++nonLetterCount 56 | return char 57 | } 58 | const keyCode = key[(i - nonLetterCount) % keyLength].charCodeAt() - 65 59 | const shifted = (charCode - 65 + keyCode) % 26 + 65 60 | return String.fromCharCode(shifted) 61 | }).join("") 62 | } 63 | 64 | /** 65 | * @param {NS} ns 66 | * @param {any} input 67 | * @param {string|array} expectedOutput 68 | **/ 69 | function runTest(ns, input, expectedOutput) { 70 | const solution = solve(input) 71 | if ( solution != expectedOutput ) { 72 | ns.print(`ERROR: '${input}' should solve to '${expectedOutput}'`) 73 | ns.print(`ERROR: '${input}' solves to '${solution}'.`) 74 | ns.exit() 75 | } 76 | ns.print(`SUCCESS: Test ${input} passed.`) 77 | } 78 | -------------------------------------------------------------------------------- /src/contracts/findValidExpressionsSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Find All Valid Math Expressions 3 | * 4 | * You are given a string which contains only digits between 0 and 9: 5 | * 6 | * 27275 7 | * 8 | * You are also given a target number. Return all possible ways you can add 9 | * the +, -, and * operators to the string such that it evaluates to the target 10 | * number. 11 | * 12 | * The provided answer should be an array of strings containing the valid 13 | * expressions. The data provided by this problem is an array with two 14 | * elements. The first element is the string of digits, while the second 15 | * element is the target number: 16 | * 17 | * ["27275", 62] 18 | * 19 | * NOTE: Numbers in the expression cannot have leading 0's. In other words, 20 | * "1+01" is not a valid expression 21 | * 22 | * Examples: 23 | * 24 | * Input: digits = "123", target = 6 25 | * Output: ["1+2+3", "1*2*3"] 26 | * 27 | * Input: digits = "105", target = 5 28 | * Output: ["1*0+5", "10-5"] 29 | **/ 30 | 31 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 32 | 33 | /** @param {NS} ns **/ 34 | export async function main(ns, file, type, server) { 35 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 36 | const answer = solve(await codingContractor.extractData()) 37 | await codingContractor.sendSolution(answer) 38 | } 39 | 40 | 41 | function solve(data) { 42 | const [digits, target] = data 43 | let walker = new Walker(digits, target) 44 | walker.walk() 45 | return walker.result 46 | } 47 | 48 | class Walker { 49 | constructor(digits, target) { 50 | this.digits = digits 51 | this.target = target 52 | this.result = [] 53 | } 54 | 55 | walk(path="", position=0, value=0, prevTerm=0) { 56 | if ( position === this.digits.length ) { 57 | if (this.target === value) { 58 | this.result.push(path) 59 | } 60 | return 61 | } 62 | 63 | for ( let i = position; i < this.digits.length; ++i ) { 64 | if (i != position && this.digits[position] == '0') { 65 | break 66 | } 67 | const current = parseInt(this.digits.substring(position, i + 1)) 68 | 69 | if ( position == 0 ) { 70 | this.walk(`${path}${current}`, i+1, current, current) 71 | } else { 72 | this.walk(`${path}+${current}`, i+1, value + current, current) 73 | this.walk(`${path}-${current}`, i+1, value - current, -current) 74 | this.walk(`${path}*${current}`, i+1, value - prevTerm + prevTerm*current, prevTerm*current) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/contracts/generateIpAddsSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generage IP Addresses 3 | * 4 | * Given a string containing only digits, return an array with all possible 5 | * valid IP address combinations that can be created. 6 | * 7 | * Note that an octet cannot begin with a '0' unless the number itself is actually 8 | * 0. For example, '192.168.010.1' is not a valid IP. 9 | * 10 | * Examples: 11 | * 12 | * 25525511135 -> [255.255.11.135, 255.255.111.35] 13 | * 1938718066 -> [193.87.180.66] 14 | **/ 15 | 16 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 17 | 18 | /** @param {NS} ns **/ 19 | export async function main(ns, file, type, server) { 20 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 21 | const answer = solve(await codingContractor.extractData()) 22 | await codingContractor.sendSolution(answer) 23 | } 24 | 25 | /** 26 | * @param {string} digits 27 | * @returns {array string} 28 | **/ 29 | function solve(digits) { 30 | let permutations = splitPermutations(digits) 31 | let answer = permutations.filter(val => val.every(octet => validOctet(octet))) 32 | 33 | return answer.map(ip => ip.join('.')).join(',') 34 | } 35 | 36 | /** 37 | * @param {string} octet 38 | **/ 39 | function validOctet(octet) { 40 | if ( octet.length > 3 ) 41 | return false 42 | 43 | if ( octet.length == 0 ) 44 | return false 45 | 46 | if ( octet.length > 1 && octet[0] == '0' ) 47 | return false 48 | 49 | if ( octet > 255 ) 50 | return false 51 | 52 | return true 53 | } 54 | 55 | /** 56 | * @param {string} digits 57 | **/ 58 | function splitPermutations(digits) { 59 | let permutations = [] 60 | 61 | for (let length1 of [1,2,3]) { 62 | for (let length2 of [1,2,3]) { 63 | for (let length3 of [1,2,3]) { 64 | permutations.push([ 65 | digits.substr(0, length1), 66 | digits.substr(length1, length2), 67 | digits.substr(length1+length2, length3), 68 | digits.substr(length1+length2+length3) 69 | ]) 70 | } 71 | } 72 | } 73 | 74 | return permutations 75 | } 76 | -------------------------------------------------------------------------------- /src/contracts/hammingCodesBinToIntSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HammingCodes: Encoded Binary to Integer 3 | * 4 | * You are attempting to solve a Coding Contract. You have 10 tries remaining, 5 | * after which the contract will self-destruct. 6 | * 7 | * You are given the following encoded binary string: 8 | * '1000100011110100' 9 | * 10 | * Decode it as an 'extended Hamming code' and convert it to a decimal value. 11 | * The binary string may include leading zeroes. A parity bit is inserted at 12 | * position 0 and at every position N where N is a power of 2. Parity bits are 13 | * used to make the total number of '1' bits in a given set of data even. The 14 | * parity bit at position 0 considers all bits including parity bits. Each 15 | * parity bit at position 2^N alternately considers 2^N bits then ignores 2^N 16 | * bits, starting at position 2^N. The endianness of the parity bits is 17 | * reversed compared to the endianness of the data bits: 18 | * - Data bits are encoded most significant bit first and the parity bits 19 | * encoded least significant bit first. 20 | * The parity bit at position 0 is set last. There is a ~55% chance for an 21 | * altered bit at a random index. Find the possible altered bit, fix it and 22 | * extract the decimal value. 23 | * 24 | * Examples: 25 | * 26 | * '11110000' passes the parity checks and has data bits of 1000, which is 8 27 | * in binary. 28 | * '1001101010' fails the parity checks and needs the last bit to be 29 | * corrected to get '1001101011', after which the data bits are found to 30 | * be 10101, which is 21 in binary. 31 | * 32 | * For more information on the 'rule' of encoding, refer to Wikipedia 33 | * (https://wikipedia.org/wiki/Hamming_code) or the 3Blue1Brown videos on 34 | * Hamming Codes. (https://youtube.com/watch?v=X8jsijhllIA) 35 | */ 36 | 37 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 38 | 39 | /** @param {NS} ns **/ 40 | export async function main(ns, file, type, server) { 41 | const testVal1 = '11110000' 42 | if (solve(testVal1) !== 8) { 43 | ns.tprint("ERROR: '11110000' should solve to 8.") 44 | ns.exit() 45 | } 46 | const testVal2 = '1001101010' 47 | if (solve(testVal2) !== 21) { 48 | ns.tprint("ERROR: '1001101010' should solve to 21.") 49 | ns.exit() 50 | } 51 | const testVal3 = '1000100011110100' 52 | if (solve(testVal3) !== 52) { 53 | ns.tprint("ERROR: '1000100011110100' should solve to 52.") 54 | ns.exit() 55 | } 56 | ns.print("All tests passed.") 57 | 58 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 59 | const answer = solve(await codingContractor.extractData()) 60 | await codingContractor.sendSolution(answer) 61 | } 62 | 63 | /** 64 | * @param {string} data 65 | */ 66 | function solve(data) { 67 | const arr = data.split("") 68 | const map = arr.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map()) 69 | if ( map.get('1') % 2 == 1) { 70 | // there's an error, we should find it 71 | let pos = 0 72 | for (let i = 0; i < arr.length; i++) { if (arr[i] == '1') pos = pos ^ i } 73 | arr[pos] = arr[pos] == '1' ? '0' : '1' 74 | } 75 | let idx = 1 76 | const idxs = [0] 77 | while( idx < arr.length ) { 78 | idxs.push(idx) 79 | idx = idx * 2 80 | } 81 | // indexes of parity bits, removing them in reverse order so not to change 82 | // higher indicies 83 | idxs.reverse() 84 | for (let i of idxs) { 85 | arr.splice(i,1) 86 | } 87 | return parseInt(arr.join(""), 2) 88 | } 89 | -------------------------------------------------------------------------------- /src/contracts/mergeIntervalsSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Merge Overlapping Intervals 3 | * 4 | * Given an array of arrays of numbers representing a list of intervals, merge 5 | * all overlapping intervals. 6 | * 7 | * Example: 8 | * 9 | * [[1, 3], [8, 10], [2, 6], [10, 16]] 10 | * 11 | * would merge into [[1, 6], [8, 16]]. 12 | * 13 | * The intervals must be returned in ASCENDING order. You can assume that in an 14 | * interval, the first number will always be smaller than the second. 15 | * 16 | **/ 17 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 18 | 19 | /** @param {NS} ns **/ 20 | export async function main(ns, file, type, server) { 21 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 22 | const answer = solve(await codingContractor.extractData()) 23 | await codingContractor.sendSolution(answer) 24 | } 25 | 26 | /** @param {array[]} pairs **/ 27 | function solve(pairs) { 28 | let changed = true 29 | let focus; 30 | let intervals = [] 31 | 32 | while (pairs.length > 0) { 33 | changed = false 34 | focus = pairs.shift() 35 | for (let i = 0; i < pairs.length; i++) { 36 | if ( overlap(focus, pairs[i]) ) { 37 | changed = true 38 | focus = [Math.min(focus[0], pairs[i][0]), Math.max(focus[1], pairs[i][1])] 39 | pairs.splice(i, 1) 40 | } 41 | } 42 | if (!changed) { 43 | intervals.push(focus) 44 | } else { 45 | pairs.push(focus) 46 | } 47 | } 48 | intervals.sort((a,b) => a[0] - b[0]) 49 | 50 | return intervals 51 | } 52 | 53 | /** 54 | * @param {any} val 55 | * @param {number[]} range 56 | **/ 57 | function overlap(val, range) { 58 | if ( typeof val === 'object' ) { 59 | return ( overlap(val[0], range) || overlap(val[1], range) || overlap(range[1], val) || overlap(range[0], val)) 60 | } 61 | if ( val >= range[0] && val <= range[1] ){ 62 | return true 63 | } 64 | return false 65 | } 66 | -------------------------------------------------------------------------------- /src/contracts/minimumPathSumSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimum Path Sum in a Triangle 3 | * 4 | * Given a triangle, find the minimum path sum from top to bottom. In each step 5 | * of the path, you may only move to adjacent numbers in the row below. 6 | * 7 | * The triangle is represented as a 2D array of numbers: 8 | * 9 | [[3], 10 | [2,5], 11 | [6,2,0], 12 | [8,7,5,2]] 13 | * 14 | * Example: If you are given the following triangle: 15 | * 16 | [[2], 17 | [3,4], 18 | [6,5,7], 19 | [4,1,8,3]] 20 | * 21 | * The minimum path sum is 11 (2 -> 3 -> 5 -> 1). 22 | * 23 | **/ 24 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 25 | 26 | /** @param {NS} ns **/ 27 | export async function main(ns, file, type, server) { 28 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 29 | const answer = solve(await codingContractor.extractData()) 30 | await codingContractor.sendSolution(answer) 31 | } 32 | 33 | /** 34 | * @param {array[]} data 35 | */ 36 | function solve(data) { 37 | const pyramid = data.slice() 38 | pyramid.forEach((row, level) => { 39 | if ( level == 0 ) { 40 | // the top level is its own sum, doesn't need to change 41 | return 42 | } 43 | row.forEach((node, position) => row[position] = node + fetchCheapestPath(pyramid, level, position)) 44 | }) 45 | 46 | return Math.min(...pyramid.pop()) 47 | } 48 | 49 | /** 50 | * @param {array[]} pyramid - the whole pyramid array of arrays 51 | * @param {number} level - the height/row/subarray being evaluated 52 | * @param {number} position - the specific location being evaluated 53 | */ 54 | function fetchCheapestPath(pyramid, level, position) { 55 | let higher = pyramid[level-1] 56 | if ( position == 0 ) 57 | return higher[0] 58 | if ( position == higher.length ) 59 | return higher[position-1] 60 | 61 | let left = higher[position-1] 62 | let right = higher[position] 63 | return Math.min(left, right) 64 | } 65 | -------------------------------------------------------------------------------- /src/contracts/primeFactorSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Find Largest Prime Factor 3 | * 4 | * A prime factor is a factor that is a prime number. What is the largest 5 | * prime factor of 339654226? 6 | * 7 | **/ 8 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 9 | 10 | /** @param {NS} ns **/ 11 | export async function main(ns, file, type, server) { 12 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 13 | const answer = solve(await codingContractor.extractData()) 14 | await codingContractor.sendSolution(answer) 15 | } 16 | 17 | function solve(number) { 18 | let factor = 2 19 | while (number > (factor - 1)* (factor - 1)) { 20 | while (number % factor === 0) { 21 | number = Math.round(number/factor) 22 | } 23 | ++factor 24 | } 25 | 26 | return (number === 1 ? fac-1 : number) 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/contracts/sanitizeParensSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanitize Parentheses in Expression 3 | * 4 | * Given a string such as below: 5 | * 6 | * ()(()a 7 | * 8 | * Remove the minimum number of invalid parentheses in order to validate the 9 | * string. If there are multiple minimal ways to validate the string, provide 10 | * all of the possible results. The answer should be provided as an array 11 | * of strings. If it is impossible to validate the string the result should be 12 | * an array with only an empty string. 13 | * 14 | * IMPORTANT: The string may contain letters, not just parentheses. Examples: 15 | * "()())()" -> ["()()()", "(())()"] 16 | * "(a)())()" -> ["(a)()()", "(a())()"] 17 | * ")( -> [""] 18 | * 19 | **/ 20 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 21 | 22 | /** @param {NS} ns **/ 23 | export async function main(ns, file, type, server) { 24 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 25 | const answer = solve(await codingContractor.extractData()) 26 | await codingContractor.sendSolution(answer) 27 | } 28 | 29 | function solve(str) { 30 | let walker = new Walker(str) 31 | let [left, right] = walker.calcLeftRight() 32 | walker.walk(left, right) 33 | return walker.solutions.join(',') 34 | } 35 | 36 | class Walker { 37 | constructor(str) { 38 | this.str = str 39 | this.solutions = [] 40 | } 41 | 42 | calcLeftRight() { 43 | let left = 0 44 | let right = 0 45 | 46 | for (var i = 0; i < this.str.length; i++) { 47 | if (this.str[i] === "(") { 48 | ++left 49 | } else if (this.str[i] ===")") { 50 | left > 0 ? --left : ++right 51 | } 52 | } 53 | return [left, right] 54 | } 55 | 56 | walk(left, right, pair = 0, index = 0, solutionCandidate = "") { 57 | if ( this.str.length === index ) { 58 | if (left === 0 && right === 0 && pair == 0) { 59 | if (this.solutions.indexOf(solutionCandidate) > -1) { 60 | return 61 | } 62 | this.solutions.push(solutionCandidate) 63 | } 64 | return 65 | } 66 | 67 | if (this.str[index] === "(") { 68 | if ( left > 0 ) { 69 | this.walk(left-1, right, pair, index+1, solutionCandidate) 70 | } 71 | this.walk(left, right, pair+1, index+1, solutionCandidate+this.str[index]) 72 | } else if (this.str[index] === ")") { 73 | if (right > 0) 74 | this.walk(left, right-1, pair, index+1, solutionCandidate) 75 | if (pair > 0) 76 | this.walk(left, right, pair-1, index+1, solutionCandidate+this.str[index]) 77 | } else { 78 | this.walk(left, right, pair, index+1, solutionCandidate+this.str[index]) 79 | } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/contracts/shortestPathSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shortest Path in a Grid 3 | * 4 | * You are located in the top-left corner of the following grid: 5 | * 6 | * [[0,0,0,0,0,0,0,0,1,0,1,0], 7 | * [0,0,1,0,0,0,1,0,0,0,0,1], 8 | * [0,0,1,0,1,0,1,0,0,0,0,0], 9 | * [0,0,0,0,1,1,0,0,0,0,0,0], 10 | * [0,0,0,0,1,1,1,0,0,1,0,0], 11 | * [0,0,0,0,0,1,0,0,1,1,0,0], 12 | * [0,1,1,0,0,1,0,0,1,0,0,1], 13 | * [0,1,0,0,0,0,0,1,1,0,0,0]] 14 | * 15 | * You are trying to find the shortest path to the bottom-right corner of the 16 | * grid, but there are obstacles on the grid that you cannot move onto. These 17 | * obstacles are denoted by '1', while empty spaces are denoted by 0. 18 | * 19 | * Determine the shortest path from start to finish, if one exists. The answer 20 | * should be given as a string of UDLR characters, indicating the moves along 21 | * the path. 22 | * 23 | * NOTE: If there are multiple equally short paths, any of them is accepted as 24 | * answer. If there is no path, the answer should be an empty string. 25 | * NOTE: The data returned for this contract is an 2D array of numbers 26 | * representing the grid. 27 | * 28 | * Examples: 29 | * 30 | * [[0,1,0,0,0], 31 | * [0,0,0,1,0]] 32 | * 33 | * Answer: 'DRRURRD' 34 | * 35 | * [[0,1], 36 | * [1,0]] 37 | * 38 | * Answer: '' 39 | **/ 40 | 41 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 42 | 43 | /** @param {NS} ns **/ 44 | export async function main(ns, file, type, server) { 45 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 46 | const answer = solve(await codingContractor.extractData()) 47 | await codingContractor.sendSolution(answer) 48 | } 49 | 50 | function solve(data) { 51 | let grid = new Grid(data) 52 | return grid.walk() 53 | } 54 | 55 | class Grid { 56 | /** 57 | * @param {NS} ns 58 | * @param {[[int]]} map 59 | */ 60 | constructor(map) { 61 | this.map = map 62 | this.path = '' 63 | this.visited = [] 64 | this.height = map.length 65 | this.width = map[0].length 66 | 67 | this.start = new Point(0, 0) 68 | this.end = new Point(this.height - 1, this.width - 1) 69 | } 70 | 71 | walk() { 72 | let visited = [] 73 | let toVisit = [this.start] 74 | let visiting, neighbors 75 | while (toVisit.length > 0) { 76 | visiting = toVisit.shift() 77 | visited.push(visiting) 78 | if (visiting.equals(this.end)) 79 | return visiting.path 80 | 81 | neighbors = visiting.getNeighbors() 82 | for (let neighbor of neighbors) { 83 | if (this.outsideBounds(neighbor)) 84 | continue 85 | if (this.isWall(neighbor)) 86 | continue 87 | if (visited.some(v => v.equals(neighbor))) 88 | continue 89 | toVisit.push(neighbor) 90 | } 91 | } 92 | return '' 93 | } 94 | 95 | outsideBounds(point) { 96 | if ( point.row < 0 || point.col < 0) 97 | return true 98 | if ( point.row >= this.height || point.col >= this.width) 99 | return true 100 | return false 101 | } 102 | 103 | isWall(point) { 104 | return this.map[point.row][point.col] == 1 105 | } 106 | 107 | } 108 | 109 | class Point { 110 | constructor(row, col, path = "") { 111 | this.row = row 112 | this.col = col 113 | this.path = path 114 | } 115 | 116 | equals(other) { 117 | return this.row == other.row && this.col == other.col 118 | } 119 | 120 | getNeighbors() { 121 | return [ 122 | new Point(this.row - 1, this.col , this.path + "U"), 123 | new Point(this.row , this.col+1, this.path + "R"), 124 | new Point(this.row + 1, this.col , this.path + "D"), 125 | new Point(this.row , this.col-1, this.path + "L"), 126 | ] 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/contracts/spiralizeMatrixSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Spiralize Matrix 3 | * 4 | * Given an array of arrays of numbers representing a 2D matrix, 5 | * return the elements of the matrix as an array in spiral order. 6 | * 7 | * Here is an example of what spiral order should be: 8 | * [ 9 | * [1, 2, 3] 10 | * [4, 5, 6] 11 | * [7, 8, 9] 12 | * ] 13 | * Answer: [1, 2, 3, 6, 9, 8 ,7, 4, 5] 14 | * 15 | * Note that the matrix will not always be square. 16 | * 17 | **/ 18 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 19 | 20 | /** @param {NS} ns **/ 21 | export async function main(ns, file, type, server) { 22 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 23 | const data = await codingContractor.extractData() 24 | const answer = solve(data.slice()) 25 | await codingContractor.sendSolution(answer) 26 | } 27 | 28 | /** 29 | * @param {array []} matrix 30 | **/ 31 | function solve(matrix) { 32 | let answer = [] 33 | 34 | while ( matrix.length > 0 ) { 35 | // Add the top array, and remove it from the matrix 36 | answer.push( ...matrix.shift() ) 37 | // add the last element of each array 38 | matrix.forEach((arr) => answer.push(arr.pop())) 39 | // check if it's an odd number of rows, we might be done now 40 | if ( matrix.length == 0 ) { return answer.flat().filter(v => v != null) } 41 | // add the bottom array, reversed 42 | answer.push( ...matrix.pop().reverse() ) 43 | // add the first element of each array, reversed (bottom to top) 44 | answer.push( ...matrix.map(arr => arr.shift()).reverse() ) 45 | } 46 | 47 | return answer.flat().filter(v => v != null) 48 | } 49 | -------------------------------------------------------------------------------- /src/contracts/squareRootSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Square Root 3 | * 4 | * You are given a ~200 digit BigInt. Find the square root of this number, to 5 | * the nearest integer. 6 | * 7 | * The input is a BigInt value. The answer must be the string representing the 8 | * solution's BigInt value. The trailing "n" is not part of the string. 9 | * 10 | * Hint: If you are having trouble, you might consult 11 | * https://en.wikipedia.org/wiki/Methods_of_computing_square_roots 12 | * 13 | * Input number: 14 | * 90019831557767435513122200386199975874574068149007846220348315108226435074589880062424802852305997994790215553649502215952639945125184990676242381086650984321678904154798506312654134168732955973830233 15 | * 16 | * If your solution is an empty string, you must leave the text box empty. Do not use "", '', or ``. 17 | **/ 18 | 19 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 20 | 21 | /** @param {NS} ns **/ 22 | export async function main(ns, file, type, server) { 23 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 24 | const answer = solve(await codingContractor.extractData()) 25 | if ( typeof answer == 'string' ) { 26 | ns.ui.openTail() 27 | ns.print("ERROR: Something went wrong:") 28 | ns.print(answer) 29 | return 30 | } 31 | ns.print(answer) 32 | await codingContractor.sendSolution(answer.toString()) 33 | } 34 | 35 | /** 36 | * @param {str} data 37 | */ 38 | function solve(data) { 39 | let bigint = BigInt(data) 40 | let low = 4n 41 | let high = bigint/low 42 | return findSquareRootRecursive(bigint,high,low) 43 | } 44 | 45 | function findSquareRootRecursive(bigint, high, low, iter =0) { 46 | if (iter > 100) { 47 | return `Iter 101, high ${high}, low ${low}, guess ${average(high, low)}` 48 | } 49 | 50 | let avg = averageBigInt(high, low) 51 | let square = avg * avg 52 | if (square == bigint) 53 | return avg 54 | 55 | if (square > bigint) 56 | high = avg 57 | 58 | if ( square < bigint ) 59 | low = avg 60 | 61 | if ( high - low == 1n) { 62 | let highDiff = (high * high) - bigint 63 | let lowDiff = bigint - (low * low) 64 | if ( highDiff <= lowDiff) 65 | return high 66 | if (lowDiff < highDiff) 67 | return low 68 | } 69 | return findSquareRootRecursive(bigint, high, low, iter++) 70 | } 71 | 72 | /** 73 | * @param {BigInt} a 74 | * @param {BigInt} b 75 | * @returns {BigInt} 76 | */ 77 | function averageBigInt(a, b) { 78 | return (a + b)/2n 79 | } 80 | -------------------------------------------------------------------------------- /src/contracts/stockTraderSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Algorithmic Stock Trader IV 3 | * 4 | * You are given an array with two elements: 5 | * 6 | * [7, [106,42,117,140,162,101,135,181,110,9]] 7 | * 8 | * The first element is an integer k. The second element is an array of stock 9 | * prices (which are numbers) where the i-th element represents the stock price 10 | * on day i. 11 | * 12 | * Determine the maximum possible profit you can earn using at most k 13 | * transactions. A transaction is defined as buying and then selling one share 14 | * of the stock. Note that you cannot engage in multiple transactions at once. 15 | * In other words, you must sell the stock before you can buy it again. 16 | * 17 | * If no profit can be made, then the answer should be 0. 18 | * 19 | **/ 20 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 21 | 22 | /** @param {NS} ns **/ 23 | export async function main(ns, file, type, server) { 24 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 25 | const data = await codingContractor.extractData() 26 | const solveArgs = solveArgsByType(codingContractor.type, data) 27 | const answer = solve(...solveArgs) 28 | await codingContractor.sendSolution(answer) 29 | } 30 | 31 | function solveArgsByType(type, data) { 32 | switch (type) { 33 | case "Algorithmic Stock Trader I" : 34 | return [1, data] 35 | case "Algorithmic Stock Trader II" : 36 | return [data.length, data] 37 | case "Algorithmic Stock Trader III" : 38 | return [2, data] 39 | case "Algorithmic Stock Trader IV" : 40 | return [data[0], data[1]] 41 | } 42 | } 43 | 44 | export function solve(transactions, prices) { 45 | const length = prices.length 46 | 47 | if ( length < 2 ) { 48 | return 0 49 | } 50 | 51 | if (transactions > length/2) { 52 | let result = 0 53 | for (let i = 1; i < length; ++i) { 54 | result += Math.max(prices[i]-prices[i-1], 0) 55 | } 56 | return result 57 | } 58 | 59 | const rele = Array(transactions + 1).fill(0) 60 | const hold = Array(transactions + 1).fill(Number.MIN_SAFE_INTEGER) 61 | let current; 62 | 63 | for (let i = 0; i < length; ++i) { 64 | current = prices[i] 65 | for (let j = transactions; j > 0; --j) { 66 | rele[j] = Math.max(rele[j], hold[j] + current) 67 | hold[j] = Math.max(hold[j], rele[j - 1] - current) 68 | } 69 | } 70 | 71 | return rele[transactions] 72 | } 73 | -------------------------------------------------------------------------------- /src/contracts/subarrayMaximumSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Subarray with Maximum Sum 3 | * 4 | * Given an array of integers, find the contiguous subarray (containing at 5 | * least one number) which has the largest sum and return that sum. 6 | * 7 | * 'Sum' referst to the sum of all the numbers in the subarray. 8 | * 9 | * Example: 10 | * 11 | * [3,2,0,-3,-5,4] 12 | * 13 | * Answer: 5 (the sum of [3,2]) 14 | **/ 15 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 16 | 17 | /** @param {NS} ns **/ 18 | export async function main(ns, file, type, server) { 19 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 20 | const answer = solve(await codingContractor.extractData()) 21 | await codingContractor.sendSolution(answer) 22 | } 23 | 24 | function solve(numbers) { 25 | for (var i = 1; i < numbers.length; i++) { 26 | numbers[i] = Math.max(numbers[i], numbers[i] + numbers[i-1]) 27 | } 28 | 29 | return Math.max(...numbers) 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/contracts/totalWaysToSumSolver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Total Ways to Sum 3 | * 4 | * It is possible to write four as a sum in exactly four different ways: 5 | * 6 | * 3 + 1 7 | * 2 + 2 8 | * 2 + 1 + 1 9 | * 1 + 1 + 1 + 1 10 | * 11 | * How many different ways can the number 9 be written as a sum of at least two 12 | * positive integers? 13 | * 14 | **/ 15 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 16 | 17 | /** @param {NS} ns **/ 18 | export async function main(ns, file, type, server) { 19 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 20 | const answer = solve(await codingContractor.extractData()) 21 | await codingContractor.sendSolution(answer) 22 | } 23 | 24 | function solve(num) { 25 | const ways = [] 26 | ways.length = num + 1 27 | ways.fill(1) 28 | 29 | for (let i = 2; i < num; ++i) { 30 | for (let j = i; j <= num; ++j) { 31 | ways[j] += ways[j-i] 32 | } 33 | } 34 | 35 | return ways[num] 36 | } 37 | -------------------------------------------------------------------------------- /src/contracts/uniquePaths1Solver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unique Paths in a Grid I 3 | * 4 | * You are in a grid with 14 rows and 13 columns, and you are positioned in the 5 | * top-left corner of that grid. You are trying to reach the bottom-right 6 | * corner of the grid, but you can only move down or right on each step. 7 | * Determine how many unique paths there are from start to finish. 8 | * 9 | * NOTE: The data returned for this contract is an array with the number of 10 | * rows and columns: 11 | * 12 | * [14, 13] 13 | **/ 14 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 15 | import { solve } from '/contracts/uniquePaths2Solver.js' 16 | 17 | /** @param {NS} ns **/ 18 | export async function main(ns, file, type, server) { 19 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 20 | const data = await codingContractor.extractData() 21 | const answer = solve(expand(data[0], data[1])) 22 | await codingContractor.sendSolution(answer) 23 | } 24 | 25 | function expand(height, width) { 26 | return Array(height).fill(Array(width).fill(0)) 27 | } 28 | -------------------------------------------------------------------------------- /src/contracts/uniquePaths2Solver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unique Paths in a Grid II 3 | * 4 | * You are located in the top-left corner of the following grid: 5 | * 6 | * 0,0,0,0,0,0,1,1,0, 7 | * 0,1,0,0,0,0,0,1,0, 8 | * 0,0,0,0,0,1,1,0,0, 9 | * 0,0,0,0,0,0,0,0,0, 10 | * 1,0,0,0,0,0,0,0,0, 11 | * 1,0,0,0,0,1,0,0,0, 12 | * 1,0,0,1,0,0,0,0,0, 13 | * 14 | * You are trying reach the bottom-right corner of the grid, but you can only 15 | * move down or right on each step. Furthermore, there are obstacles on the 16 | * grid that you cannot move onto. These obstacles are denoted by '1', while 17 | * empty spaces are denoted by 0. 18 | * 19 | * Determine how many unique paths there are from start to finish. 20 | * 21 | * NOTE: The data returned for this contract is an 2D array of numbers 22 | * representing the grid. 23 | * 24 | **/ 25 | import { CodingContractWrapper } from '/contracts/CodingContractWrapper.js' 26 | 27 | /** @param {NS} ns **/ 28 | export async function main(ns, file, type, server) { 29 | const codingContractor = new CodingContractWrapper(ns, file, type, server) 30 | const data = await codingContractor.extractData() 31 | const answer = solve(data) 32 | await codingContractor.sendSolution(answer) 33 | } 34 | 35 | /** 36 | * @param {array} arr 37 | * @returns {int} 38 | **/ 39 | export function solve(arr) { 40 | let grid = new Grid(arr) 41 | return grid.bottomRight().findNumPaths(grid) 42 | } 43 | 44 | class Grid { 45 | constructor(arr) { 46 | this.grid = this.initGrid(arr) 47 | this.width = arr[0].length 48 | this.height = arr.length 49 | } 50 | 51 | bottomRight() { 52 | return this.at(this.width-1, this.height-1) 53 | } 54 | 55 | at(x, y) { 56 | return this.grid[`${x},${y}`] 57 | } 58 | 59 | initGrid(arr) { 60 | let grid = {} 61 | 62 | arr.forEach((row, y_index) => { 63 | row.forEach((cell, x_index) => { 64 | grid[`${x_index},${y_index}`] = new Cell(x_index, y_index, cell) 65 | }) 66 | }) 67 | return grid 68 | } 69 | 70 | } 71 | 72 | class Cell { 73 | constructor(x, y, isObstacle) { 74 | this.isObstacle = isObstacle ? true : false 75 | this.x = x 76 | this.y = y 77 | this.paths; 78 | } 79 | 80 | findNumPaths(grid) { 81 | if ( this.isObstacle ) { 82 | return 0 83 | } 84 | 85 | if ( this.x == 0 && this.y == 0 ) { 86 | return 1 87 | } 88 | 89 | let west = this.x == 0 ? 0 : grid.at(this.x-1, this.y).findNumPaths(grid) 90 | let north = this.y == 0 ? 0 : grid.at(this.x, this.y-1).findNumPaths(grid) 91 | 92 | return north + west 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/crime.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | fetchPlayer, 4 | disableLogs, 5 | } from 'utils/helpers.js' 6 | 7 | const crimes = [ 8 | "shoplift", 9 | "rob store", 10 | "mug", 11 | "larceny", 12 | "deal drugs", 13 | "bond forgery", 14 | "traffick arms", 15 | "homicide", 16 | "grand theft auto", 17 | "kidnap", 18 | "assassinate", 19 | "heist", 20 | ] 21 | 22 | const combatXPStats = [ 23 | 'strength_exp', 24 | 'defense_exp', 25 | 'dexterity_exp', 26 | 'agility_exp' 27 | ] 28 | 29 | const xpStats = [ 30 | 'hacking_exp', 31 | 'charisma_exp', 32 | 'intelligence_exp' 33 | ].concat(combatXPStats) 34 | 35 | const opts = [ 36 | ['focus', 'money'], 37 | ['fastCrimes', false], 38 | ] 39 | 40 | export function autocomplete(data, args) { 41 | data.flags(opts) 42 | return crimes.concat([ 43 | 'money', 44 | 'xp', 45 | 'karma', 46 | 'hacking', 47 | 'combat', 48 | 'charisma' 49 | ]) 50 | } 51 | 52 | /** 53 | * @param {NS} ns 54 | **/ 55 | export async function main(ns) { 56 | disableLogs(ns, ['sleep']) 57 | ns.tail() 58 | let args = ns.flags(opts) 59 | let time = 1, again = true, crime 60 | let karma = ns.heart.break() 61 | 62 | while (again) { 63 | crime = await chooseCrime(ns, args) 64 | time = await fetch(ns, `ns.commitCrime('${crime}')`, `/Temp/commitCrime.txt`) 65 | ns.print(`Attempting ${crime} in ${ns.tFormat(time)}...`) 66 | await ns.sleep(time * 0.75) 67 | 68 | again = fetchPlayer().busy 69 | while (fetchPlayer().busy) { 70 | await ns.sleep(50) 71 | } 72 | 73 | if ( ns.heart.break() < karma ){ 74 | ns.print(`SUCCESS: ${crime}`) 75 | } else { 76 | ns.print(`FAILURE: ${crime}`) 77 | } 78 | ns.print('karma: ', karma = ns.heart.break()) 79 | } 80 | } 81 | 82 | async function chooseCrime(ns, args) { 83 | if (args._.length > 0 && crimes.includes(args._[0].toLowerCase())) { 84 | return args._[0].toLowerCase() 85 | } 86 | const stats = [] 87 | let score = 0, data 88 | for ( let crime of crimes ) { 89 | data = await fetch(ns, `ns.getCrimeStats('${crime}')`, `/Temp/crimeStats.txt`) 90 | if ( args.fastCrimes && data.time > 60*1000 ) { 91 | continue 92 | } 93 | score = await calcScore(ns, args.focus, crime, data) 94 | stats.push({name: crime, score: score}) 95 | } 96 | let sorted = stats.sort((a, b) => b.score - a.score) 97 | return sorted[0].name 98 | } 99 | 100 | async function calcScore(ns, focus, crime, stats) { 101 | let value = focusValue(focus, stats) 102 | let chance = await fetch(ns, `ns.getCrimeChance('${crime}')`,`/Temp/crimeChance.txt`) 103 | 104 | return chance * value / stats.time 105 | } 106 | 107 | function focusValue(focus, stats) { 108 | switch (focus) { 109 | case 'karma': 110 | return stats.karma 111 | case 'xp': 112 | return xpStats.reduce((prev, name) => stats[name] + prev, 0) 113 | case 'hacking': 114 | return stats.hacking_exp 115 | case 'combat': 116 | return combatXPStats.reduce((p, name) => stats[name] + prev, 0) 117 | case 'charisma', 'cha': 118 | return stats.charisma_exp 119 | case 'intelligence', 'int': 120 | return stats.intelligence_exp 121 | default: 122 | return stats.money 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /src/doProcess.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a clever little trick, if I do say so myself. Absolutely stole the 3 | * idea and most of the implementation from Insight on Discord, saw him doing 4 | * this on a stream and immediately needed it for myself. 5 | * 6 | * This allows you to run any arbitrary ns/js code from the terminal. E.g.: 7 | * 8 | * run doProcess.js await ns.hack('n00dles'); ns.tprint('Hacked!') 9 | * 10 | * You can also alias it (alias do='run doProcess.js') for even more deliciousness: 11 | * 12 | * do ns.ps('foodnstuff') 13 | **/ 14 | 15 | import { 16 | runCommand, 17 | } from 'utils/helpers.js' 18 | 19 | /** @param {NS} ns **/ 20 | export async function main(ns) { 21 | // run the command in verbose mode, since that's usually what I want anyway 22 | await runCommand(ns, ns.args.join(" "), '/Temp/doThisProcess.js', true) 23 | } 24 | -------------------------------------------------------------------------------- /src/gang/ascend.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | announce, 4 | formatNumber, 5 | getLSItem, 6 | } from 'utils/helpers.js' 7 | // i like the fibonacci sequence 8 | const threshholds = [2,3,5,8,13,21,34,55,89] 9 | 10 | /** @param {NS} ns **/ 11 | export async function main(ns) { 12 | const gangInfo = getLSItem('gangMeta') 13 | if ( !gangInfo || !gangInfo.faction ) 14 | return ns.print('no gang') // can't ascend members for a gang that doesn't exist 15 | 16 | if ( gangInfo.members.length == 0 ) 17 | return ns.print('no members') // can't ascend no members 18 | 19 | const bestSkill = gangInfo.isHacker ? 'hack' : 'str' 20 | const memberData = gangInfo.members 21 | const names = memberData.map(m => m.name) 22 | const ascensionResults = await fetch(ns, 23 | `${JSON.stringify(names)}.map(m => ns.gang.getAscensionResult(m))`, 24 | '/Temp/gang.getAscensionResult.txt') 25 | ns.print(`best skill : ${bestSkill}`) 26 | ns.print(`members : ${names}`) 27 | 28 | const ascender = new Ascender(memberData, ascensionResults, bestSkill) 29 | await ascender.ascendGangMembers(ns) 30 | } 31 | 32 | 33 | class Ascender { 34 | /** 35 | * @param {object[]} memberData 36 | * @param {object[]} ascTheoretical 37 | * @param {string} skill 38 | **/ 39 | constructor(memberData, ascTheoretical, skill){ 40 | this.keySkill = skill 41 | this.multKey = `${skill}_asc_mult` 42 | this.members = memberData 43 | this.members.forEach((d, i) => { 44 | d.ascResult = ascTheoretical[i] 45 | }) 46 | } 47 | 48 | /** @param {NS} ns */ 49 | async ascendGangMembers(ns) { 50 | ns.print(this.members.map(m => m.name)) 51 | for ( const member of this.members) { 52 | ns.print(`Evaluating ${member.name}`) 53 | await this.ascendGangMember(ns, member) 54 | } 55 | } 56 | 57 | /** 58 | * @param {NS} ns 59 | * @param {object} member 60 | **/ 61 | async ascendGangMember(ns, member) { 62 | if ( member.ascResult === undefined || member.ascResult === null ) { 63 | return ns.print(`${member.name} cannot ascend at this time.`) 64 | } 65 | const currentMult = member[this.multKey] 66 | const nextThreshhold = threshholds.find(t => t > currentMult) 67 | const ascResult = member.ascResult[this.keySkill]*currentMult 68 | 69 | if ( nextThreshhold === undefined ) { 70 | return ns.print(`${member.name} has achieved mastery. ` + 71 | `(another ascension would be ${formatNumber(ascResult)}`) 72 | } 73 | 74 | ns.print(`${member.name}'s current ${this.keySkill} asc level is ` + 75 | `${currentMult}, next at ${nextThreshhold}`) 76 | ns.print(`Ascension result: ${this.keySkill} mult => ${ascResult}`) 77 | 78 | let result = false 79 | if ( ascResult > nextThreshhold ) { 80 | result = await fetch(ns, `ns.gang.ascendMember('${member.name}')`, 81 | '/Temp/gang.ascendMember.txt') 82 | } else { 83 | return ns.print(`${member.name} ascension multiplier too low to ascend`) 84 | } 85 | 86 | if (result) 87 | return announce(ns, `Gang member ${member.name} ascended, new multiplier ` + 88 | `is ${formatNumber(ascResult)}`, 'success') 89 | ns.tail() 90 | ns.print(`Member ${member} did not ascend?`) 91 | ns.print(ascensionResults[i]) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/gang/augments.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | runCommand, 4 | myMoney, 5 | reserve, 6 | getLSItem, 7 | } from 'utils/helpers.js' 8 | import { gangEquipment } from 'utils/constants.js' 9 | import { sortForHacking, sortForCombat } from '/gang/equipment.js' 10 | 11 | 12 | /** @param {NS} ns **/ 13 | export async function main(ns) { 14 | const gangInfo = getLSItem('gangMeta') 15 | if ( !gangInfo || !gangInfo.faction ) 16 | return ns.print('no gang') // can't buy augments for a gang that doesn't exist 17 | 18 | const members = gangInfo.members.map(m => m.name) 19 | const augData = await getAugData(ns, gangInfo.isHacker) 20 | 21 | for (const aug of augData) { 22 | if ( myMoney(ns) < aug.cost*members.length + reserve(ns) ) 23 | return // all done for now 24 | if ( gangInfo.members.every(m => m.augmentations.includes(aug.name) ) ) { 25 | ns.print(`All gang members have '${aug.name},' skipping for now...`) 26 | continue 27 | } 28 | ns.print(`Attempting to purchase ${aug.name} for gang members...`) 29 | const cmd = JSON.stringify(members) + 30 | `.forEach(m => ns.print( ns.gang.purchaseEquipment(m, ns.args[0])))` 31 | await runCommand(ns, cmd, '/Temp/gang.purchaseEquipment.js', false, 1, aug.name) 32 | } 33 | } 34 | 35 | /** 36 | * @param {NS} ns 37 | * @param {boolean} isHacker 38 | **/ 39 | async function getAugData(ns, isHacker) { 40 | const augTypes = isHacker ? gangEquipment.hackAugs : gangEquipment.combatAugs 41 | let desired = augTypes.map(obj => { return {name: obj} }) 42 | 43 | const command = (arr, prop, cmdString) => { 44 | return JSON.stringify(arr) + 45 | `.map(equip => { ` + 46 | `equip.${prop} = ns.gang.${cmdString}(equip.name); ` + 47 | `return equip; ` + 48 | `})` 49 | } 50 | desired = await fetch(ns, command(desired, 'cost', 'getEquipmentCost' ), 51 | '/Temp/gang.getEquipmentCost.txt' ) 52 | desired = await fetch(ns, command(desired, 'stats','getEquipmentStats'), 53 | '/Temp/gang.gangEquipStats.txt' ) 54 | desired.sort(isHacker ? sortForHacking : sortForCombat) 55 | 56 | return desired 57 | } 58 | -------------------------------------------------------------------------------- /src/gang/clashRecorder.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | announce, 4 | getLSItem, 5 | setLSItem, 6 | disableLogs 7 | } from 'utils/helpers.js' 8 | 9 | /** @param {NS} ns **/ 10 | export async function main(ns) { 11 | disableLogs(ns, ['sleep']) 12 | const gangInfo = getLSItem('gangMeta') 13 | if ( !gangInfo || !gangInfo.faction ) 14 | return ns.print('no gang') // can't clash a gang that doesn't exist 15 | 16 | const nextClashTime = getLSItem('clashtime') 17 | if ( nextClashTime && nextClashTime > Date.now()+1000 ) { 18 | await ns.sleep(nextClashTime - Date.now() - 1000) 19 | } 20 | 21 | const currentGangData = JSON.stringify(ns.gang.getOtherGangInformation()) 22 | while( gangDataIsTheSame(ns, currentGangData)) { 23 | await ns.sleep(10) 24 | } 25 | 26 | setLSItem('clashtime', Date.now()) 27 | announce(ns, `ClashTime set to ${Date.now()}`) 28 | } 29 | 30 | function gangDataIsTheSame(ns, prev) { 31 | const curr = JSON.stringify(ns.gang.getOtherGangInformation()) 32 | 33 | return prev == curr 34 | } 35 | -------------------------------------------------------------------------------- /src/gang/equipment.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | runCommand, 4 | myMoney, 5 | reserve, 6 | getLSItem, 7 | } from 'utils/helpers.js' 8 | import { gangEquipment } from 'utils/constants.js' 9 | 10 | const hackerEquipment = ['rootkits', 'vehicles'] 11 | const combatEquipment = [ 'weapons', 'armor', 'vehicles'] 12 | 13 | /** @param {NS} ns **/ 14 | export async function main(ns) { 15 | const gangInfo = getLSItem('gangMeta') 16 | if ( !gangInfo || !gangInfo.faction ) 17 | return ns.print('no gang') // can't buy equipment for a gang that doesn't exist 18 | 19 | const members = gangInfo.members.map(m => m.name) 20 | const equipData = await getEquipmentData(ns, gangInfo.isHacker) 21 | 22 | for (const equip of equipData) { 23 | if ( myMoney(ns) < equip.cost*members.length + reserve(ns) ) 24 | return ns.print('not enough money, try again later.') 25 | if ( gangInfo.members.every(m => m.upgrades.includes(equip.name))) { 26 | ns.print(`All members currently have a '${equip.name},' skipping for now...`) 27 | continue 28 | } 29 | ns.print(`Attempting to purchase ${equip.name} for gang members...`) 30 | const cmd = JSON.stringify(members) + 31 | `.forEach(m => ns.print( ns.gang.purchaseEquipment(m, ns.args[0])))` 32 | await runCommand(ns, cmd, '/Temp/gangEquipPurchase.js', false, 1, equip.name) 33 | } 34 | } 35 | 36 | /** 37 | * @param {NS} ns 38 | * @param {boolean} isHacker 39 | **/ 40 | async function getEquipmentData(ns, isHacker) { 41 | const equipTypes = isHacker ? hackerEquipment : combatEquipment 42 | let eq, desired = [] 43 | for (const type of equipTypes) { 44 | eq = gangEquipment[type].map(obj => { return {name: obj, type: type} }) 45 | desired.push(...eq) 46 | } 47 | const command = (arr, prop, cmdString) => { 48 | return JSON.stringify(arr) + 49 | `.map(equip => { ` + 50 | `equip.${prop} = ns.gang.${cmdString}(equip.name); ` + 51 | `return equip; ` + 52 | `})` 53 | } 54 | desired = await fetch(ns, command(desired, 'cost', 'getEquipmentCost' ), '/Temp/gangEquipCost.txt' ) 55 | desired = await fetch(ns, command(desired, 'stats','getEquipmentStats'), '/Temp/gangEquipStats.txt' ) 56 | desired.sort(isHacker ? sortForHacking : sortForCombat) 57 | 58 | return desired 59 | } 60 | 61 | /** @param {object} a, b **/ 62 | export function sortForHacking(a, b) { 63 | if ( a.stats.hack == b.stats.hack ) { 64 | if (b.stats.cha == a.stats.cha) 65 | return a.cost - b.cost 66 | return (b.stats.cha || 0) - (a.stats.cha || 0) 67 | } 68 | return (b.stats.hack || 0) - (a.stats.hack || 0) 69 | } 70 | 71 | /** @param {object} a, b **/ 72 | export function sortForCombat(a, b) { 73 | if ( a.stats.str == b.stats.str ) 74 | return a.cost - b.cost 75 | return (b.stats.str || 0) - (a.stats.str || 0) 76 | } 77 | -------------------------------------------------------------------------------- /src/gang/recruitment.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | announce, 4 | getLSItem, 5 | } from 'utils/helpers.js' 6 | 7 | /** @param {NS} ns **/ 8 | export async function main(ns) { 9 | const gangInfo = getLSItem('gangMeta') 10 | if ( !gangInfo || !gangInfo.faction ) 11 | return ns.print('no gang') // can't recruit for a gang that doesn't exist 12 | 13 | const canRecruit = await fetch(ns, `ns.gang.canRecruitMember()`, 14 | `/Temp/gang.CanRecruitMember.txt`) 15 | if (!canRecruit) 16 | return // this gang can't recruit right now. enough respect? full roster? 17 | 18 | const nextMember = await getNextMemberName(ns, gangInfo.members.map(m => m.name)) 19 | const result = await fetch(ns, `ns.gang.recruitMember('${nextMember}')`) 20 | if ( result === true ) 21 | return announce(ns, `Recruited new Gang Member ${nextMember}`, 'success') 22 | announce(ns, `Problem recruiting gang member ${nextMember}`, 'warning') 23 | } 24 | 25 | /** @param {NS} ns **/ 26 | async function getNextMemberName(ns, members) { 27 | const last = members.pop() 28 | if ( last == undefined ) { 29 | return '0-Jane' 30 | } 31 | const next = Number.parseInt(last) + 1 32 | return `${next}-Jane` 33 | } 34 | -------------------------------------------------------------------------------- /src/gang/tasks.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | getLSItem, 4 | } from 'utils/helpers.js' 5 | 6 | Object.defineProperty(Array.prototype, 'shuffle', { 7 | enumerable: false, configurable: true, 8 | value: function(b, c, d) { 9 | c=this.length;while(c)b=Math.random()*c--|0,d=this[c],this[c]=this[b],this[b]=d 10 | } 11 | }); 12 | 13 | /** @param {NS} ns **/ 14 | export async function main(ns) { 15 | const gangInfo = getLSItem('gangMeta') 16 | if ( !gangInfo || !gangInfo.faction ) 17 | return ns.print('no gang') // can't assign tasks to non-existant gang 18 | 19 | if ( gangInfo.members.length == 0 ) 20 | return ns.print('no gang members') 21 | 22 | if ( gangInfo.members[0].task == 'Territory Warfare' && gangInfo.warPhase != 'peace' ) 23 | return ns.print('gang currently defending territory, skip assigning tasks.') 24 | 25 | const activityOrganizer = gangInfo.isHacker ? new HackingActivityOrganizer() : 26 | new CombatActivityOrganizer() 27 | 28 | let result; 29 | const groupedMembers = membersBySkill(gangInfo.members, activityOrganizer.skill()) 30 | if ( groupedMembers.trainees.length > 0 ) { 31 | result = await setTask(ns, groupedMembers.trainees, activityOrganizer.learning()) 32 | ns.print(`Trainees assignment result: ${result}`) 33 | } 34 | 35 | const blackBelts = groupedMembers.blackBelts 36 | blackBelts.shuffle() // make sure the shit jobs get shared to everybody randomly 37 | const nWhiteHats = calculateHats(gangInfo, groupedMembers.blackBelts.length) 38 | const whiteHats = blackBelts.slice(0, nWhiteHats) 39 | const blackHats = blackBelts.slice(nWhiteHats) 40 | 41 | if ( whiteHats.length > 0 ) { 42 | result = await setTask(ns, whiteHats, activityOrganizer.whiteHat()) 43 | ns.print(`WhiteHats assignment result: ${result}`) 44 | } 45 | 46 | if ( blackHats.length > 0 ) { 47 | result = await setTask(ns, blackHats, activityOrganizer.blackHat(gangInfo.taskPhase)) 48 | ns.print(`BlackHats assignment result: ${result}`) 49 | } 50 | } 51 | 52 | class HackingActivityOrganizer { 53 | skill() {return 'hack'} 54 | learning() {return 'Train Hacking'} 55 | whiteHat() {return 'Ethical Hacking'} 56 | blackHat(phase) { 57 | switch (phase) { 58 | case 'money' : 59 | return 'Money Laundering' 60 | case 'respect' : 61 | return 'Cyberterrorism' 62 | } 63 | } 64 | } 65 | 66 | class CombatActivityOrganizer { 67 | skill() {return 'str'} 68 | learning() {return 'Train Combat'} 69 | whiteHat() {return 'Vigilante Justice'} 70 | blackHat(phase) { 71 | switch (phase) { 72 | case 'money' : 73 | return 'Human Trafficking' 74 | case 'respect' : 75 | return 'Terrorism' 76 | } 77 | } 78 | } 79 | 80 | async function setTask(ns, members, task) { 81 | const names = members.map(m => m.name) 82 | const cmd = `${JSON.stringify(names)}.map(m => ns.gang.setMemberTask(m, '${task}'))` 83 | return await fetch(ns, cmd, '/Temp/gang.setMemberTask.txt') 84 | } 85 | 86 | function membersBySkill(memberData, skill) { 87 | return memberData.reduce((groups, member) => { 88 | if (member[skill] > 300) 89 | groups.blackBelts.push(member) 90 | else 91 | groups.trainees.push(member) 92 | return groups 93 | }, { 'trainees': [], 'blackBelts': []}) 94 | } 95 | 96 | function calculateHats(gangInfo, numMembers) { 97 | if ( numMembers == 0 ) 98 | return 0 99 | 100 | if ( gangInfo.wantedLevel < 2 ) 101 | return 0 102 | 103 | const penalty = Math.abs(1 - gangInfo.wantedPenalty) 104 | 105 | // I decided that if wanted gain rate == 50% or more, I want all members 106 | // working to reduce it, and proportionate down to 0%. 107 | const percentWhite = Math.min(1, penalty * 2) 108 | return Math.round(percentWhite * numMembers) 109 | } 110 | -------------------------------------------------------------------------------- /src/gang/warRunner.js: -------------------------------------------------------------------------------- 1 | import { 2 | getLSItem, 3 | announce, 4 | disableLogs, 5 | } from 'utils/helpers.js' 6 | const buffer = 100 //ms 7 | 8 | /** @param {NS} ns **/ 9 | export async function main(ns) { 10 | disableLogs(ns, ['sleep']) 11 | const gangInfo = getLSItem('gangMeta') 12 | 13 | if ( !gangInfo || !gangInfo.faction ) 14 | return ns.print('no gang') // can't war without a gang 15 | 16 | if ( gangInfo.warPhase == 'peace' ) { 17 | if ( members[0].task == 'Territory Warfare' ) 18 | members.map(m => ns.gang.setMemberTask(m, 'Terrorism')) 19 | return ns.print('we have conquered all') 20 | } 21 | 22 | const bufferedClashTime = getLSItem('clashTime') + buffer 23 | if ( bufferedClashTime < Date.now() ) 24 | return ns.print('clash time has passed, whoops!') 25 | 26 | const members = gangInfo.members.map(m => m.name) 27 | ns.print('Attempting territory warfare with ' + JSON.stringify(members)) 28 | let workingMembers = members.filter(m => ns.gang.setMemberTask(m, 'Territory Warfare')) 29 | announce(ns, `${workingMembers.length} gang members assigned to territory warfare`) 30 | 31 | if ( ns.gang.getBonusTime() > 10 ) { 32 | return ns.print('gang in bonus time right now, just take territory for a while.') 33 | } 34 | 35 | ns.print('Waiting for clashtime to pass...') 36 | while(bufferedClashTime > Date.now()) { 37 | await ns.sleep(1) 38 | } 39 | 40 | workingMembers = gangInfo.members.filter(m => ns.gang.setMemberTask(m.name, m.task)) 41 | announce(ns, `${workingMembers.length} gang members assigned back to their tasks`) 42 | } 43 | -------------------------------------------------------------------------------- /src/hacknet/coreUpgrader.js: -------------------------------------------------------------------------------- 1 | import { haveEnoughMoney } from "helpers.js" 2 | const maxCores = 8 3 | 4 | function upgradeCores(ns, node, id, cores) { 5 | if (node.cores >= cores) { 6 | return; 7 | } 8 | let cost = ns.hacknet.getCoreUpgradeCost(id, 1) 9 | ns.print('Upgrading core, costs $' + ns.formatNumber(cost)) 10 | if (haveEnoughMoney(ns, cost)) { 11 | ns.hacknet.upgradeCore(id, 1) 12 | return true 13 | } else { 14 | return false 15 | } 16 | } 17 | 18 | async function upgradeTo(ns, totalCount) { 19 | let total, node, cost; 20 | while (true) { 21 | total = ns.hacknet.numNodes() 22 | for (let i = 0; i < total; i++) { 23 | node = ns.hacknet.getNodeStats(i) 24 | ns.print('Upgrading ' + node.name) 25 | upgradeCores(ns, node, i, maxCores) 26 | } 27 | if (total >= totalCount && node.cores >= maxCores) { 28 | ns.tprint("Hacknet Node Cores upgraded to max. Hasta la vista, baby.") 29 | return; 30 | } 31 | await ns.sleep(200) 32 | } 33 | } 34 | 35 | export async function main(ns) { 36 | ns.disableLog("getServerMoneyAvailable") 37 | ns.disableLog("sleep") 38 | 39 | await upgradeTo(ns, ns.args[0]) 40 | } 41 | -------------------------------------------------------------------------------- /src/hacknet/levelUpgrader.js: -------------------------------------------------------------------------------- 1 | import { haveEnoughMoney } from "helpers.js" 2 | const maxLevel = 100 3 | 4 | function upgradeLevel(ns, node, id, level) { 5 | if (node.level >= level) { 6 | return; 7 | } 8 | let cost = ns.hacknet.getLevelUpgradeCost(id, 2) 9 | ns.print('Upgrading level, costs ' + ns.nFormat(cost, "$0.000a")) 10 | if (haveEnoughMoney(ns, cost) { 11 | ns.hacknet.upgradeLevel(id, 2) 12 | } 13 | } 14 | 15 | async function upgradeTo(ns, totalCount) { 16 | let total, node; 17 | while (true) { 18 | total = ns.hacknet.numNodes() 19 | for (let i = 0; i < total; i++) { 20 | node = ns.hacknet.getNodeStats(i) 21 | ns.print('Upgrading ' + node.name) 22 | upgradeLevel(ns, node, i, maxLevel) 23 | } 24 | if (total >= totalCount && node.level >= maxLevel) { 25 | ns.tprint("Hacknet Node Levels upgraded to max. Godspeed, Runner.") 26 | return; 27 | } 28 | await ns.sleep(200) 29 | } 30 | } 31 | 32 | 33 | export async function main(ns) { 34 | ns.disableLog("getServerMoneyAvailable") 35 | ns.disableLog("sleep") 36 | 37 | await upgradeTo(ns, ns.args[0]) 38 | } 39 | -------------------------------------------------------------------------------- /src/hacknet/ramUpgrader.js: -------------------------------------------------------------------------------- 1 | import { haveEnoughMoney } from "helpers.js" 2 | const maxRam = 16 3 | 4 | function upgradeRam(ns, node, id, ram) { 5 | if (node.ram >= ram) { 6 | return; 7 | } 8 | let cost = ns.hacknet.getRamUpgradeCost(id, 1) 9 | ns.print('Upgrading ram, costs ' + ns.nFormat(cost, "$0.000a")) 10 | if ( haveEnoughMoney(ns, cost) ) { 11 | ns.hacknet.upgradeRam(id, 1) 12 | } 13 | } 14 | 15 | async function upgradeTo(ns, totalCount) { 16 | let total, node, cost; 17 | while (true) { 18 | total = ns.hacknet.numNodes() 19 | for (let i = 0; i < total; i++) { 20 | node = ns.hacknet.getNodeStats(i) 21 | ns.print('Upgrading ' + node.name) 22 | await upgradeRam(ns, node, i, maxRam) 23 | } 24 | if (total >= totalCount && node.ram >= maxRam) { 25 | ns.tprint("Hacknet Node Ram upgraded to max. I'm so cold.") 26 | return; 27 | } 28 | await ns.sleep(200) 29 | } 30 | } 31 | 32 | 33 | export async function main(ns) { 34 | ns.disableLog("getServerMoneyAvailable") 35 | ns.disableLog("sleep") 36 | 37 | await upgradeTo(ns, ns.args[0]) 38 | } 39 | -------------------------------------------------------------------------------- /src/hacknet/startup.js: -------------------------------------------------------------------------------- 1 | import { haveEnoughMoney } from "helpers.js" 2 | const hacknetGoal = 3 3 | 4 | function buyNodes(ns, goal) { 5 | let nodeId, cost; 6 | while (ns.hacknet.numNodes() < goal) { 7 | cost = ns.hacknet.getPurchaseNodeCost() 8 | ns.print('Buying next server, costs ' + ns.nFormat(cost, "$0.000a")) 9 | if ( haveEnoughMoney(ns, cost) ) { 10 | nodeId = ns.hacknet.purchaseNode() 11 | ns.print("Purchased node with id of " + nodeId) 12 | } else { 13 | return false 14 | } 15 | } 16 | return true 17 | } 18 | 19 | export async function main(ns) { 20 | ns.disableLog("getServerMoneyAvailable") 21 | ns.disableLog("sleep") 22 | 23 | let goal = typeof(ns.args[0]) !== 'number' ? hacknetGoal : ns.args[0] 24 | 25 | ns.run('/hacknet/levelUpgrader.js', 1, goal) 26 | ns.run('/hacknet/ramUpgrader.js', 1, goal) 27 | ns.run('/hacknet/coreUpgrader.js', 1, goal) 28 | 29 | if ( buyNodes(ns, goal) ) { 30 | ns.tprint("Purchased " + goal + " hacknet nodes. Adios, amigo.") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/insight/analyze-hack.js: -------------------------------------------------------------------------------- 1 | import { disableLogs } from './helpers.js' 2 | 3 | export async function main(ns) { 4 | ns.tprint('here') 5 | disableLogs(ns, ["scan", "scp", "sleep"]) 6 | 7 | var to_scan = ['home'].concat(ns.scan('home')); 8 | 9 | for (var i = 1; i < to_scan.length; i++) { 10 | var target = to_scan[i]; 11 | var add_to_scan = ns.scan(target); 12 | for (var j = 0; j < add_to_scan.length; j++) { 13 | var a = add_to_scan[j]; 14 | if (!to_scan.includes(a)) { 15 | to_scan.push(a); 16 | } 17 | } 18 | } 19 | 20 | var weaken_ram = 1.75; 21 | var grow_ram = 1.75; 22 | var hack_ram = 1.7; 23 | 24 | var flags = ns.flags([ 25 | ['all', false], 26 | ['at-hack-level', 0], 27 | ]); 28 | 29 | var player = ns.getPlayer(); 30 | if (flags['at-hack-level']) player.skills.hacking = flags['at-hack-level']; 31 | var servers = to_scan.map(ns.getServer); 32 | var ram_total = servers.reduce(function (total, server) { 33 | if (!(flags['all'] || server.hasAdminRights)) return total; 34 | return total + server.maxRam; 35 | }, 0); 36 | var server_eval = servers.filter(server => (flags['all'] || server.hasAdminRights && server.requiredHackingSkill <= player.skills.hacking) 37 | && !server.purchasedByPlayer && server.moneyMax > 0) 38 | .map(function (server) { 39 | server.hackDifficulty = server.minDifficulty; 40 | let real_player_hack_skill = player.skills.hacking; 41 | // If necessary, temporarily fake the hacking skill to get the numbers for when this server will first be unlocked 42 | if (server.requiredHackingSkill > real_player_hack_skill) 43 | player.skills.hacking = server.requiredHackingSkill; 44 | var growGain = Math.log(ns.formulas.hacking.growPercent(server, 1, player, 1)); 45 | var growCost = grow_ram * ns.formulas.hacking.growTime(server, player); 46 | var hackGain = Math.log(ns.formulas.hacking.hackPercent(server, player)) * ns.formulas.hacking.hackAnalyzeChance(server, player); 47 | var hackCost = hack_ram * ns.formulas.hacking.hackTime(server, player); 48 | var weakenCost = weaken_ram * ns.formulas.hacking.weakenTime(server, player); 49 | growCost += weakenCost * 0.004 / 0.05; 50 | hackCost += weakenCost * 0.002 / 0.05; 51 | server.gainRate = server.moneyMax / (growCost / growGain + hackCost / hackGain); 52 | server.expRate = ns.formulas.hacking.hackExp(server, player) * (1 + 0.002 / 0.05) / (hackCost); 53 | player.skills.hacking = real_player_hack_skill; // Restore the real hacking skill if we changed it temporarily 54 | ns.print(server.hostname, ": Theoretical $", server.gainRate, ", limit ", ns.nFormat(server.moneyMax * 0.1 / ram_total, "$0.000a") , ", exp ", server.expRate); 55 | server.gainRate = Math.min(server.gainRate, server.moneyMax * 0.1 / ram_total); 56 | return server; 57 | }); 58 | var best_server = server_eval.sort(function (a, b) { 59 | return b.gainRate - a.gainRate; 60 | })[0]; 61 | ns.tprint("Best server: ", best_server.hostname, " with ", ns.nFormat(best_server.gainRate, "$0.000a"), " per ram-second"); 62 | ns.print(`\nServers in order of best to worst hack money at Hack ${player.skills.hacking}:`); 63 | let order = 1; 64 | for (const server of server_eval) { 65 | ns.print(` ${order++} ${server.hostname}, with ${ns.nFormat(server.gainRate, "$0.000a")} per ram-second`); 66 | } 67 | 68 | var best_exp_server = server_eval.sort(function (a, b) { 69 | return b.expRate - a.expRate; 70 | })[0]; 71 | ns.tprint("Best exp server: ", best_exp_server.hostname, " with ", best_exp_server.expRate, " exp per ram-second"); 72 | ns.print("\nServers in order of best to worst hack XP:"); 73 | order = 1; 74 | for (let i = 0; i < 5; i++) { 75 | ns.tprint(` ${order++} ${server_eval[i].hostname}, with ${server_eval[i].expRate.toPrecision(3)} exp per ram-second`); 76 | } 77 | 78 | await ns.write('/Temp/analyze-hack.txt', JSON.stringify(server_eval.map(s => ({ 79 | hostname: s.hostname, 80 | gainRate: s.gainRate, 81 | expRate: s.expRate 82 | }))), "w"); 83 | // Below is stats for hacknet servers - uncomment at cost of 4 GB Ram 84 | /* 85 | var hacknet_nodes = [...(function* () { 86 | var n = ns.hacknet.numNodes(); 87 | for (var i = 0; i < n; i++) { 88 | var server = ns.hacknet.getNodeStats(i); 89 | server.gainRate = 1000000 / 4 * server.production / server.ram; 90 | yield server; 91 | } 92 | })()]; 93 | var best_hacknet_node = hacknet_nodes.sort(function (a, b) { 94 | return b.gainRate - a.gainRate; 95 | })[0]; 96 | if (best_hacknet_node) ns.tprint("Best hacknet node: ", best_hacknet_node.name, " with $", best_hacknet_node.gainRate, " per ram-second"); 97 | */ 98 | } 99 | -------------------------------------------------------------------------------- /src/monitor.js: -------------------------------------------------------------------------------- 1 | import { fetchServerFree, networkMapFree } from 'utils/network.js' 2 | import { disableLogs, getLSItem, formatDuration } from 'utils/helpers.js' 3 | import { findTop } from 'bestHack.js' 4 | import { calcThreadsToHack, 5 | calcHackAmount, 6 | calcThreadsToGrow, 7 | getPercentUsedRam, 8 | calculatePercentMoneyHacked, } from '/batching/calculations.js' 9 | import { BatchDataQueue } from '/batching/queue.js' 10 | 11 | export function autocomplete(data, args) { 12 | return data.servers 13 | } 14 | 15 | /** @param {NS} ns */ 16 | export async function main(ns) { 17 | disableLogs(ns, ['sleep', 'getServerMoneyAvailable']) 18 | ns.ui.openTail() 19 | 20 | while (true) { 21 | await ns.sleep(100) 22 | ns.clearLog(); 23 | let batches = fetchBatchQueue().batchList 24 | let nBatches = batches.length 25 | ns.print(`Batches: ${nBatches} --- ` + 26 | `Ram used: ${ns.formatPercent(getPercentUsedRam(networkMapFree()))} `) 27 | 28 | if (ns.args[0]) { 29 | printServer(ns, fetchServerFree(ns.args[0]), batches) 30 | continue 31 | } 32 | let top = findTop() 33 | for (let server of top) { 34 | let numBatches = batches.filter(b=>b.target == server.hostname).length 35 | if ( numBatches > 0 ) 36 | printServer(ns, server, batches) 37 | batches = batches.filter(b => b.target !== server.hostname) 38 | if (batches?.length == 0) 39 | break 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * @param {NS} ns 46 | * @param {Server} server 47 | * @param {BatchJob[]} batches 48 | **/ 49 | function printServer(ns, server, batches) { 50 | ns.print(` ----------- ${server.hostname}`) 51 | let percent = Math.round((server.moneyAvailable / server.moneyMax) * 100) 52 | ns.print(`*** Money : \$${ns.formatNumber(server.moneyAvailable,0)} / \$${ns.formatNumber(server.moneyMax,0)} (${(percent)}%)`) 53 | let weakTime = ns.getWeakenTime(server.hostname) 54 | ns.print(`*** Growth : ${server.serverGrowth.toString().padStart(3)} | ` + 55 | `Security : ${ns.formatNumber(server.hackDifficulty, 1)}/${ns.formatNumber(server.minDifficulty, 0)}`) 56 | let numBatches = batches.filter(b=>b.target == server.hostname).length 57 | ns.print(`*** Batches : ${numBatches.toString().padStart(3)} | ` + 58 | `Time : ${formatDuration(weakTime)}`) 59 | 60 | let available = server.moneyAvailable 61 | server.moneyAvailable = server.moneyMax 62 | let difficulty = server.hackDifficulty 63 | server.hackDifficulty = server.minDifficulty 64 | let hackThreads = Math.ceil(calcThreadsToHack(server, server.moneyAvailable * calcHackAmount(server))) 65 | ns.print(`* Hack : ${hackThreads.toString().padStart(3," ")}`) 66 | server.moneyAvailable = available 67 | server.hackDifficulty = difficulty 68 | 69 | let weakThreads = Math.ceil((server.hackDifficulty - server.minDifficulty)/0.05) 70 | let weakThForHack = Math.ceil(hackThreads/25) 71 | ns.print(`* Weaken : ${weakThreads.toString().padStart(3," ")} (${weakThForHack})`) 72 | 73 | server.hackDifficulty = server.minDifficulty 74 | let multiplier = server.moneyMax / Math.max(server.moneyAvailable, 1) 75 | let growThreads = Math.ceil(ns.growthAnalyze(server.hostname, multiplier)) 76 | multiplier = server.moneyMax / (Math.max(server.moneyMax, 1) * (1 - calcHackAmount(server))) 77 | 78 | server.moneyAvailable = server.moneyMax 79 | server.moneyAvailable -= server.moneyAvailable * 80 | calculatePercentMoneyHacked(server) * 81 | hackThreads 82 | let growThForHack = calcThreadsToGrow(server, server.moneyMax) 83 | ns.print(`* Grow : ${growThreads.toString().padStart(3," ")} (${growThForHack})`) 84 | server.hackDifficulty = difficulty 85 | server.moneyAvailable = available 86 | 87 | weakThreads = Math.ceil(growThreads/12.5) 88 | weakThForHack = Math.ceil(growThForHack/12.5) 89 | ns.print(`* Weaken2 : ${weakThreads.toString().padStart(3," ")} (${weakThForHack})`) 90 | } 91 | 92 | /** 93 | * @returns {BatchDataQueue} For working with upcoming batches 94 | */ 95 | function fetchBatchQueue() { 96 | let rawData = getLSItem('batches') ?? [] 97 | let batchDataQueue = new BatchDataQueue(rawData) 98 | batchDataQueue.discardExpiredBatchData() 99 | return batchDataQueue 100 | } 101 | -------------------------------------------------------------------------------- /src/nuker.js: -------------------------------------------------------------------------------- 1 | import { 2 | disableLogs, 3 | toolsCount, 4 | clearLSItem, 5 | getLSItem 6 | } from 'utils/helpers.js' 7 | import { root } from 'rooter.js' 8 | 9 | /** 10 | * @param {NS} ns 11 | **/ 12 | export async function main(ns) { 13 | disableLogs(ns, ['sleep']) 14 | 15 | let count = toolsCount() 16 | let nmap = getLSItem('nmap') 17 | if (!nmap) { 18 | ns.tprint(`Nuker.js: NMAP is not populated, can't nuke anything right now.`) 19 | return 20 | } 21 | let servers = Object.values(nmap) 22 | .filter((s) => !s.hasAdminRights && 23 | s.portsRequired <= count ) 24 | 25 | if ( servers.length == 0 ) 26 | return 27 | 28 | ns.tprint(`Nuking ${servers.length} servers with ${count} or less ports required.`) 29 | for ( const server of servers ) { 30 | root(ns, server) 31 | } 32 | ns.tprint(`SUCCESS: Nuked ${servers.map(s => s.name).join(", ")}`) 33 | } 34 | -------------------------------------------------------------------------------- /src/pServBuyer.js: -------------------------------------------------------------------------------- 1 | import { getNsDataThroughFile as fetch, 2 | disableLogs, 3 | announce, 4 | formatRam, 5 | } from 'utils/helpers.js' 6 | const argsSchema = [ 7 | ['size', 7], 8 | ] 9 | 10 | export function autocomplete(data, args) { 11 | data.flags(argsSchema) 12 | return data.servers 13 | } 14 | 15 | /** 16 | * @param {NS} ns 17 | */ 18 | export async function main(ns) { 19 | disableLogs(ns, ['getServerMoneyAvailable', 'sleep']) 20 | const args = ns.flags(argsSchema) 21 | // ns.ui.openTail() 22 | 23 | const ram = 2**args.size 24 | const limit = await fetch(ns, `ns.getPurchasedServerLimit()`, 25 | `/Temp/getPurchasedServerLimit.txt`) 26 | const cost = await fetch(ns, `ns.getPurchasedServerCost(${ram})`, 27 | `/Temp/getPurchasedServerCost.${args.size}.txt`) 28 | ns.print(`Buying ${limit} ${ram}GB servers for ${ns.formatNumber(cost)} each`) 29 | let count = 0 30 | 31 | let hostname 32 | for (let i = 0; i < limit; i++) { 33 | hostname = "pserv-" + i 34 | count += await buyNewOrReplaceServer(ns, hostname, cost, ram) 35 | } 36 | let msg = `PServBuyer.js is finished, purchased ${count} size ${args.size} servers.` 37 | announce(ns, msg, 'success') 38 | ns.tprint("SUCCESS: " + msg) 39 | } 40 | 41 | /** 42 | * @param {NS} ns 43 | * @param {string} hostname 44 | * @param {number} cost 45 | * @param {number} ram 46 | */ 47 | async function buyNewOrReplaceServer(ns, hostname, cost, ram) { 48 | if (!ns.serverExists(hostname)) { 49 | ns.print(`Buying a new server ${hostname} with ${ram} GB ram for ` + 50 | `${ns.formatNumber(cost)}`) 51 | return purchaseNewServer(ns, hostname, ram) 52 | } 53 | let host = await fetch(ns, `ns.getServer('${hostname}')`) 54 | 55 | if (host.maxRam >= ram) { 56 | ns.print(`${hostname} is large enough, with ${host.maxRam} GB ram`) 57 | return 0 58 | } 59 | 60 | ns.print(`Upgrading ${hostname} with ${host.maxRam} -> ${ram} GB ram` + 61 | ` for \$${ns.formatNumber(cost)}`) 62 | return await upgradeServer(ns, host, ram) 63 | } 64 | 65 | /** 66 | * @param {NS} ns 67 | * @param {string} hostname 68 | * @param {number} ram 69 | */ 70 | async function purchaseNewServer(ns, hostname, ram) { 71 | let result = await fetch(ns, `ns.purchaseServer('${hostname}', ${ram})`, 72 | `/Temp/purchaseServer.txt`) 73 | if (result) { 74 | announce(ns, `Purchased new server, ${hostname} with ${formatRam(ram)}`) 75 | return 1 76 | } 77 | return 0 78 | } 79 | 80 | /** 81 | * @param {NS} ns 82 | * @param {Server} server 83 | * @param {number} ram 84 | */ 85 | async function upgradeServer(ns, server, ram) { 86 | ns.print("Upgrading server: " + server.hostname) 87 | const result = ns.upgradePurchasedServer(server.hostname, ram) 88 | 89 | if (result) { 90 | announce(ns, `Upgraded server ${server.hostname} with ${formatRam(ram)}`) 91 | return 1 92 | } 93 | return 0 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/programBuyer.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | runCommand, 4 | fetchPlayer, 5 | } from 'utils/helpers.js' 6 | import { purchaseables } from 'utils/constants.js' 7 | 8 | /** 9 | * @param {NS} ns 10 | **/ 11 | export async function main(ns) { 12 | const player = fetchPlayer() 13 | const program = purchaseables.find(p => p.name === ns.args[0]) 14 | if ( program === undefined || !player.tor || player.money < program.cost) { 15 | ns.ui.openTail() 16 | ns.print('Program buyer quit unexpectedly') 17 | ns.print('Program: ', program) 18 | ns.print('player.tor: ', player.tor) 19 | ns.print('player.money: $', ns.formatNumber(player.money)," > ", 20 | ' program.cost $', ns.formatNumber(program.cost), " ? ", 21 | player.money >= program.cost) 22 | return 23 | } 24 | 25 | ns.tprint(`Buying ${program.name} for \$${ns.formatNumber(program.cost)}`) 26 | let result = await fetch(ns, `ns.singularity.purchaseProgram('${program.name}')`, 27 | '/Temp/purchaseProgram.txt') 28 | if ( result ) { 29 | ns.tprint(`SUCCESS: ${program.name} purchased.`) 30 | return 31 | } 32 | ns.tprint(`FAILURE: Purchasing ${program.name} was unsuccessfull. Trying again soon.`) 33 | } 34 | -------------------------------------------------------------------------------- /src/rooter.js: -------------------------------------------------------------------------------- 1 | import { fetchPlayer, toolsCount } from 'utils/helpers.js' 2 | import { fetchServerFree } from 'utils/network.js' 3 | 4 | export function autocomplete(data, args) { 5 | return data.servers 6 | } 7 | 8 | /** 9 | * @param {NS} ns 10 | * @param {string} target 11 | **/ 12 | export function root (ns, target) { 13 | let player = fetchPlayer() 14 | 15 | if (target.hasAdminRights) { 16 | ns.print("Have root access already: " + target.name) 17 | return 18 | } 19 | 20 | if ( target.portsRequired > toolsCount() ) { 21 | ns.print("Not enough tools to nuke this server.") 22 | return 23 | } 24 | 25 | if (player.programs.includes("BruteSSH.exe")) { 26 | ns.brutessh(target.name) 27 | } 28 | if (player.programs.includes("FTPCrack.exe")) { 29 | ns.ftpcrack(target.name) 30 | } 31 | if (player.programs.includes("HTTPWorm.exe")) { 32 | ns.httpworm(target.name) 33 | } 34 | if (player.programs.includes("relaySMTP.exe")) { 35 | ns.relaysmtp(target.name) 36 | } 37 | if (player.programs.includes("SQLInject.exe")) { 38 | ns.sqlinject(target.name) 39 | } 40 | 41 | var ret = ns.nuke(target.name) 42 | ns.print("Sudo aquired: " + ret) 43 | } 44 | 45 | /** 46 | * @param {NS} ns 47 | **/ 48 | export async function main(ns) { 49 | var targetName = ns.args[0] 50 | 51 | if (targetName === undefined) { 52 | ns.tprint("Must choose a target to root, `run rooter.js n00dles`") 53 | ns.exit() 54 | return; 55 | } 56 | let target = fetchServerFree(targetName) 57 | if (!target) { 58 | ns.tprint("NMAP is not populated, try again later.") 59 | return 60 | } 61 | ns.print("Target: " + target.hostname) 62 | root(ns, target) 63 | } 64 | -------------------------------------------------------------------------------- /src/satellites/activityObserver.js: -------------------------------------------------------------------------------- 1 | import { toolsCount, 2 | announce, 3 | getLSItem, 4 | fetchPlayer, 5 | getNsDataThroughFile as fetch, 6 | } from 'utils/helpers.js' 7 | import { earnFactionInvite } from 'workForFactions.js' 8 | const sec = 1000, min = 60*sec, hour = 60*min 9 | 10 | /** @param {NS} ns **/ 11 | export async function main(ns) { 12 | const processes = ns.ps('home') 13 | const crimePS = processes.find(p => p.filename === 'crime.js') 14 | const factionPS = processes.find(p => p.filename === 'workForFactions.js') 15 | 16 | // if I set the working key within the last hour, don't do anything 17 | if ( getLSItem('working') > Date.now() - hour ) { 18 | ns.print('currently working, killing processes and skipping.') 19 | if (crimePS) ns.kill(crimePS.pid, 'home') 20 | if (factionPS) ns.kill(factionPS.pid, 'home') 21 | return 22 | } 23 | 24 | const inAGang = await inAnyGang(ns) 25 | if ( !inAGang && canJoinGang(ns) ) { 26 | ns.print(`inAGang: ${inAGang}, canJoin: ${canJoinGang(ns)}`) 27 | ns.print('trying to join a gang') 28 | return await joinGang(ns) 29 | } 30 | 31 | if ( !inAGang && !canJoinGang(ns) ) { 32 | ns.print(`inAGang: ${inAGang}, canJoin: ${canJoinGang(ns)}`) 33 | return runGetKarma(ns, crimePS) 34 | } 35 | 36 | if ( toolsCount() >= 5 ) { 37 | if ( crimePS ) { 38 | announce(ns, 'Switching from crime to grinding faction rep') 39 | ns.kill(crimePS.pid, 'home') 40 | } 41 | if ( !factionPS ) { 42 | announce(ns, 'Starting work for factions...') 43 | ns.run('workForFactions.js') 44 | } 45 | return 46 | } 47 | 48 | if ( !crimePS ) { 49 | ns.run('crime.js', 1, '--fastCrimes') 50 | } 51 | } 52 | 53 | function runGetKarma(ns, crimePS) { 54 | if (!crimePS) { 55 | announce(ns, 'Reducing my karma for starting a gang') 56 | ns.run('crime.js', 1, '--focus', 'karma') 57 | } 58 | } 59 | 60 | /** @param {NS} ns */ 61 | async function joinGang(ns) { 62 | announce(ns, "Attempting to start Slum Snakes gang...") 63 | await earnFactionInvite(ns, 'Slum Snakes') 64 | let res = await fetch(ns, `ns.gang.createGang('Slum Snakes')`, '/Temp/gang.createGang.txt') 65 | if ( res ) 66 | return ns.print('SUCCESS: formed gang in Slum Snakes') 67 | ns.print('ERROR: attempted to form Slum Snakes gang, not successfull.') 68 | } 69 | 70 | async function inAnyGang(ns) { 71 | return await fetch(ns, `ns.gang.inGang()`, '/Temp/inGang.txt') 72 | } 73 | 74 | function canJoinGang(ns) { 75 | const player = fetchPlayer() 76 | if ( player.resetInfo.currentNode == 2 ) 77 | return true 78 | 79 | ns.print(`Current karma (${player.karma}) (${player.karma < -54000})`) 80 | return ( player.karma < -54000 ) 81 | } 82 | -------------------------------------------------------------------------------- /src/satellites/backdoorObserver.js: -------------------------------------------------------------------------------- 1 | import { getLSItem, tryRun, canUseSingularity } from 'utils/helpers.js' 2 | import { networkMapFree } from 'utils/network.js' 3 | import { factionServers, orgServers } from 'utils/constants.js' 4 | const worldDaemon = "w0r1d_d43m0n" 5 | 6 | /** 7 | * @param {NS} ns 8 | **/ 9 | export async function main(ns) { 10 | const nmap = networkMapFree() 11 | const player = getLSItem('PLAYER') 12 | 13 | let server = selectServer(nmap,player.skills.hacking) 14 | 15 | if (!server) { 16 | ns.print('No server found!') 17 | return 18 | } 19 | 20 | if (canUseSingularity()) { 21 | if ( ns.ps('home').some(proc => isBackdoorOf(proc, server.hostname)) ) { 22 | ns.print("Backdoor already running on this server.") 23 | return 24 | } 25 | ns.tprint('Attempting automatic backdoor of ' + server.hostname) 26 | await tryRun(() => { ns.spawn('backdoor.js', {spawnDelay:0}, server.hostname) }) 27 | } else { 28 | ns.tprint('Backdoor of ' + server.hostname + " available, finding path.") 29 | await tryRun(() => { ns.spawn('usr/find.js', {spawnDelay:0}, server.hostname, true) }) 30 | } 31 | } 32 | 33 | function isBackdoorOf(process, hostname) { 34 | return process.filename == 'backdoor.js' && process.args.includes(hostname) 35 | } 36 | 37 | function selectServer(nmap, playerHacking) { 38 | const daemon = nmap[worldDaemon] 39 | if (daemon && serverIsBackdoorable(daemon, playerHacking)) 40 | return daemon 41 | 42 | // shuffle the array semi-randomly so there are less collisions 43 | const shuffledServers = Object.values(nmap).sort(() => .5 - Math.random()) 44 | const factionServer = findServer(shuffledServers, 45 | playerHacking, 46 | Object.keys(factionServers)) 47 | if (factionServer) 48 | return factionServer 49 | 50 | const orgServer = findServer(shuffledServers, 51 | playerHacking, 52 | Object.keys(orgServers)) 53 | if (orgServer) 54 | return orgServer 55 | 56 | if (!canUseSingularity()) 57 | return false 58 | 59 | return shuffledServers.find(s => serverIsBackdoorable(s, playerHacking)) 60 | } 61 | 62 | function findServer(servers, playerHacking, preferred) { 63 | let server = servers.find(s => 64 | preferred.includes(s.hostname) && 65 | serverIsBackdoorable(s, playerHacking) 66 | ) 67 | if (server) 68 | return server 69 | return false 70 | } 71 | 72 | function serverIsBackdoorable(server, playerHacking) { 73 | return !server.backdoorInstalled && 74 | !server.purchasedByPlayer && 75 | playerHacking >= server.requiredHackingSkill && 76 | server.hasAdminRights 77 | } 78 | -------------------------------------------------------------------------------- /src/satellites/batchMetaObserver.js: -------------------------------------------------------------------------------- 1 | import { networkMapFree } from 'utils/network.js' 2 | import { getLSItem, setLSItem } from 'utils/helpers.js' 3 | import { getPercentUsedRam, calcHackAmount } from '/batching/calculations.js' 4 | 5 | /** @param {NS} ns */ 6 | export async function main(ns) { 7 | let nmap = networkMapFree() 8 | let utilizedRam = getPercentUsedRam(nmap) 9 | ns.ui.openTail(), ns.clearLog() 10 | ns.print(`calcHackAmount of b-and-a: ${calcHackAmount(nmap['b-and-a'])}`) 11 | 12 | ns.print(`Utilized Ram: ${ns.formatPercent(utilizedRam)} (${utilizedRam})`) 13 | let adjustment = calcHackPercentAdjustment(utilizedRam) 14 | ns.print(`Proposed hack percent adjustment: ${adjustment}`) 15 | setLSItem('hackPercent', adjustment) 16 | ns.print(`calcHackAmount of b-and-a: ${calcHackAmount(nmap['b-and-a'])}`) 17 | } 18 | 19 | function calcHackPercentAdjustment(ramPercent) { 20 | return capPercenAdjustment(percentAdjustBasedOnRam(ramPercent) * getLSItem('HackPercent')) 21 | } 22 | 23 | function percentAdjustBasedOnRam(ramPercent) { 24 | if (ramPercent < 0.1) 25 | return 2 26 | if (ramPercent < 0.5) 27 | return 1.05 28 | if (ramPercent < 0.95) 29 | return 1.01 30 | if (ramPercent > 0.99) 31 | return 0.99 32 | return 1 33 | } 34 | 35 | function capPercenAdjustment(val) { 36 | return Math.min(99, val) 37 | } 38 | -------------------------------------------------------------------------------- /src/satellites/batchObserver.js: -------------------------------------------------------------------------------- 1 | import { fetchPlayer, getLSItem } from 'utils/helpers.js' 2 | import { networkMapFree } from 'utils/network.js' 3 | import { BestHack } from 'bestHack.js' 4 | import { getPercentUsedRam, weakTime } from '/batching/calculations.js' 5 | import { BatchDataQueue } from '/batching/queue.js' 6 | import { createCurrentFormulas } from '/utils/formulas.js' 7 | 8 | /** @param {NS} ns */ 9 | export async function main(ns) { 10 | const nmap = networkMapFree() 11 | 12 | if ( getPercentUsedRam(nmap) > 0.9 ) { 13 | ns.print("Enough ram used, seems good.") 14 | return 15 | } 16 | 17 | if ( getRunningBatchesCount() > 10_000 ) { 18 | ns.print("Enough batches, don't run out of memory!") 19 | return 20 | } 21 | 22 | let target = findBestTarget(ns, nmap) 23 | 24 | ns.spawn("batching/shotgunBatcher.js", {spawnDelay: 0, threads: 1}, "--target", target ) 25 | } 26 | 27 | /** 28 | * Returns a server object that's probably the best one to send a batch against 29 | * right now, maybe? 30 | * @param {NS} ns 31 | * @param {Server[]} nmap 32 | **/ 33 | function findBestTarget(ns, nmap) { 34 | const player = fetchPlayer() 35 | const fs = createCurrentFormulas() 36 | const hackingLvl = player.skills.hacking 37 | const fiveMin = 5 * 60 * 1000 38 | 39 | if ( hackingLvl > 3500 ) { 40 | const searcher = new BestHack(nmap) 41 | const servers = searcher.findTop(hackingLvl) 42 | ns.print(`Servers that make some kinda sense to hack`) 43 | ns.print(servers.map(s => s.hostname)) 44 | return servers[0].hostname 45 | } 46 | 47 | if ( hackingLvl > 1200 && isServerWorthIt(nmap['rho-construction'], player, fs, fiveMin) ){ 48 | return 'rho-construction' 49 | } 50 | 51 | if ( hackingLvl > 500 && isServerWorthIt(nmap['phantasy'], player, fs, fiveMin) ){ 52 | return 'phantasy' 53 | } 54 | 55 | const home = nmap['home'] 56 | if ( hackingLvl > 20 && home.maxRam >= 64 && isServerWorthIt(nmap['joesguns'], player, fs, fiveMin) ){ 57 | return 'joesguns' 58 | } 59 | return 'n00dles' 60 | } 61 | 62 | /** 63 | * @param {Server} server 64 | * @param {Player} player 65 | * @param {Formulas} fs 66 | * @param {number} maxTime - max time we're willing to wait for weakes to run 67 | */ 68 | function isServerWorthIt(server, player, fs, maxTime) { 69 | const oldSecurity = server.security 70 | server.security = server.minSecurity 71 | const hChance = fs.hacking.hackChance(server, player) 72 | server.security = oldSecurity 73 | return server.hasAdminRights && weakTime(server) < maxTime && hChance > 0.9 74 | } 75 | 76 | /** 77 | * @returns {number} how many batches are currently running 78 | */ 79 | function getRunningBatchesCount() { 80 | const queue = fetchBatchQueue() 81 | return queue.batchList.length 82 | } 83 | 84 | /** 85 | * @returns {BatchDataQueue} For working with upcoming batches 86 | */ 87 | function fetchBatchQueue() { 88 | let rawData = getLSItem('batches') ?? [] 89 | let batchDataQueue = new BatchDataQueue(rawData) 90 | batchDataQueue.discardExpiredBatchData() 91 | return batchDataQueue 92 | } 93 | -------------------------------------------------------------------------------- /src/satellites/contractsObserver.js: -------------------------------------------------------------------------------- 1 | import { networkMapFree } from 'utils/network.js' 2 | import { 3 | disableLogs, 4 | getNsDataThroughFile as fetch, 5 | } from 'utils/helpers.js' 6 | 7 | import * as primeFactorSolver from "/contracts/primeFactorSolver.js" 8 | import * as subarrayMaximumSolver from "/contracts/subarrayMaximumSolver.js" 9 | import * as totalWaysToSumSolver from "/contracts/totalWaysToSumSolver.js" 10 | import * as spiralizeMatrixSolver from "/contracts/spiralizeMatrixSolver.js" 11 | import * as arrayJumpingSolver from "/contracts/arrayJumpingSolver.js" 12 | import * as mergeIntervalsSolver from "/contracts/mergeIntervalsSolver.js" 13 | import * as generateIpAddsSolver from "/contracts/generateIpAddsSolver.js" 14 | import * as stockTraderSolver from "/contracts/stockTraderSolver.js" 15 | import * as minimumPathSumSolver from "/contracts/minimumPathSumSolver.js" 16 | import * as uniquePaths1Solver from "/contracts/uniquePaths1Solver.js" 17 | import * as uniquePaths2Solver from "/contracts/uniquePaths2Solver.js" 18 | import * as sanitizeParensSolver from "/contracts/sanitizeParensSolver.js" 19 | import * as findValidExpressionsSolver from "/contracts/findValidExpressionsSolver.js" 20 | import * as squareRootSolver from "/contracts/squareRootSolver.js" 21 | import * as shortestPathSolver from "/contracts/shortestPathSolver.js" 22 | import * as hammingCodesBinToInt from "/contracts/hammingCodesBinToIntSolver.js" 23 | import * as compression1RLE from "/contracts/compression1RLE.js" 24 | import * as encryption1CC from "/contracts/encryption1CaesarCipher.js" 25 | import * as encryption1VC from "/contracts/encryption2VigenereCipher.js" 26 | 27 | const solvers = { 28 | "Find Largest Prime Factor" : primeFactorSolver, 29 | "Subarray with Maximum Sum" : subarrayMaximumSolver, 30 | "Total Ways to Sum" : totalWaysToSumSolver, 31 | "Spiralize Matrix" : spiralizeMatrixSolver, 32 | "Array Jumping Game" : arrayJumpingSolver, 33 | "Merge Overlapping Intervals" : mergeIntervalsSolver, 34 | "Generate IP Addresses" : generateIpAddsSolver, 35 | "Algorithmic Stock Trader I" : stockTraderSolver, 36 | "Algorithmic Stock Trader II" : stockTraderSolver, 37 | "Algorithmic Stock Trader III" : stockTraderSolver, 38 | "Algorithmic Stock Trader IV" : stockTraderSolver, 39 | "Minimum Path Sum in a Triangle" : minimumPathSumSolver, 40 | "Unique Paths in a Grid I" : uniquePaths1Solver, 41 | "Unique Paths in a Grid II" : uniquePaths2Solver, 42 | "Sanitize Parentheses in Expression" : sanitizeParensSolver, 43 | "Find All Valid Math Expressions" : findValidExpressionsSolver, 44 | "Square Root" : squareRootSolver, 45 | "Shortest Path in a Grid" : shortestPathSolver, 46 | "HammingCodes: Encoded Binary to Integer" : hammingCodesBinToInt, 47 | "Compression I: RLE Compression" : compression1RLE, 48 | "Encryption I: Caesar Cipher" : encryption1CC, 49 | "Encryption II: Vigenère Cipher" : encryption1VC, 50 | } 51 | 52 | /** 53 | * @param {NS} ns 54 | **/ 55 | export async function main(ns) { 56 | // disableLogs(ns, ['sleep']) 57 | let map = networkMapFree() 58 | if (map['home'].maxRam < 32){ 59 | ns.print('Not enough ram to read contracts, try later.') 60 | return 61 | } 62 | 63 | ns.print("Running contracts...") 64 | await runContracts(ns, map) 65 | } 66 | 67 | /** 68 | * @param {NS} ns 69 | * @param {Obj} map 70 | **/ 71 | async function runContracts(ns, map) { 72 | const servers = Object.values(map) 73 | let contracts = servers.map( 74 | s => s.files.filter(f => f.includes('.cct')).map(f => 75 | { return {name: f, server: s.hostname, type: '', }})).flat() 76 | let solver 77 | for (let file of contracts ) { 78 | let cmd = `ns.codingcontract.getContractType('${file.name}','${file.server}')` 79 | file.type = await fetch(ns, cmd, "/Temp/codingContract.getContractType.txt") 80 | ns.print(`${file.server} : ${file.name} (${file.type})`) 81 | solver = solvers[file.type] ?? "fail" 82 | if ( typeof solver == 'string') { 83 | continue 84 | } 85 | await solver.main(ns, file.name, file.type, file.server) 86 | return 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/satellites/controller.js: -------------------------------------------------------------------------------- 1 | import { disableLogs, formatDuration } from 'utils/helpers.js' 2 | 3 | const sec = 1000 4 | const min = 60 * sec 5 | const reportRefreshRate = min 6 | 7 | /** 8 | * how long to wait between running a satellite file 9 | * 'freq' is time in ms, selected as prime numbers to reduce clashes 10 | **/ 11 | const timers = [ 12 | // these are sorted by frequency, except playerObserver which must run first 13 | { file: 'satellites/playerObserver.js', freq: 83, last: 0 }, 14 | 15 | { file: 'satellites/networkObserver.js', freq: 73, last: 0 }, 16 | { file: 'satellites/batchObserver.js', freq: 499, last: Date.now() }, 17 | { file: 'stats.js', freq: 1003, last: 0 }, 18 | // { file: 'satellites/gangClashObserver.js', freq: 1.3*sec, last: 0 }, 19 | // { file: 'gang/equipment.js', freq: 5.2*sec, last: 0 }, 20 | // { file: 'gang/recruitment.js', freq: 5.3*sec, last: 0 }, 21 | // { file: 'satellites/gangMetaObserver.js', freq: 5.4*sec, last: 0 }, 22 | { file: 'nuker.js', freq: 7001, last: 0 }, 23 | { file: 'botnet.js', freq: 8009, last: 0 }, 24 | // { file: 'gang/ascend.js', freq: 8.1*sec, last: 0 }, 25 | // { file: 'gang/augments.js', freq: 12 *sec, last: 0 }, 26 | { file: 'satellites/backdoorObserver.js', freq: 20347, last: 0 }, 27 | // { file: 'gang/tasks.js', freq: 30 *sec, last: 0 }, 28 | // { file: 'sleeves/metaObserver.js', freq: 31 * sec, last: Date.now() }, 29 | // { file: 'sleeves/manager.js', freq: 31.1*sec, last: Date.now() }, 30 | { file: 'satellites/programObserver.js', freq: 33751, last: 0 }, 31 | // { file: 'satellites/activityObserver.js', freq: min, last: Date.now() }, 32 | { file: 'satellites/homeRamObserver.js', freq: 63577, last: Date.now() }, 33 | { file: 'satellites/pservObserver.js', freq: 63901, last: Date.now() }, 34 | { file: 'satellites/contractsObserver.js', freq: 119993, last: Date.now() }, 35 | 36 | ] 37 | 38 | /** 39 | * @param {NS} ns 40 | **/ 41 | export async function main(ns) { 42 | disableLogs(ns, ['sleep','run']) 43 | ns.ui.openTail() 44 | ns.ui.resizeTail(740,400) 45 | let report = new Report() 46 | let proc 47 | 48 | while(true) { 49 | ns.clearLog() 50 | for ( const timer of timers) { 51 | proc = ns.ps('home').some(p => p.filename == timer.file) 52 | if (!proc && Date.now() > timer.last + timer.freq ) { 53 | let res = ns.run(timer.file, 1) 54 | if (res > 0 ) { 55 | timer.last = Date.now() 56 | report.success(timer) 57 | } else { 58 | report.failure(timer) 59 | } 60 | } 61 | } 62 | report.refresh() 63 | report.print(ns) 64 | await ns.sleep(2) 65 | } 66 | } 67 | 68 | class Report { 69 | constructor() { 70 | this.happenings = {} 71 | } 72 | success(satellite) { 73 | if (!(satellite.file in this.happenings)) 74 | this.happenings[satellite.file] = { success: [], failure: []} 75 | this.happenings[satellite.file].success.push(Date.now()) 76 | } 77 | failure(satellite) { 78 | if (!(satellite.file in this.happenings)) 79 | this.happenings[satellite.file] = { success: [], failure: []} 80 | this.happenings[satellite.file].failure.push(Date.now()) 81 | } 82 | print(ns) { 83 | ns.print(`Rolling report of files run in the last ${formatDuration(reportRefreshRate)}`) 84 | ns.print('**************************************************************') 85 | ns.print('| Filename'.padEnd(38) + '| Successes | Failures |') 86 | ns.print('--------------------------------------------------------------') 87 | for (let filename in this.happenings) { 88 | let rep = this.happenings[filename] 89 | ns.print(`${filename.padEnd(37)} | ${rep.success.length.toString().padStart(9)} | ${rep.failure.length.toString().padStart(8)} |`) 90 | } 91 | ns.print('**************************************************************') 92 | } 93 | refresh() { 94 | let currentTime = Date.now() 95 | let cutOffTime = currentTime - reportRefreshRate 96 | for (let file in this.happenings) { 97 | let rep = this.happenings[file] 98 | rep.success = rep.success.filter(t => t > cutOffTime) 99 | rep.failure = rep.failure.filter(t => t > cutOffTime) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/satellites/factionObserver.js: -------------------------------------------------------------------------------- 1 | import { fetchPlayer } from 'utils/helpers.js' 2 | import { factionServers } from 'utils/constants.js' 3 | const autoAccept = Object.values(factionServers) 4 | 5 | /** @param {NS} ns **/ 6 | export async function main(ns) { 7 | const player = fetchPlayer() 8 | const canJoin = autoAccept.filter(f => !player.factions.includes(f)) 9 | canJoin.forEach(f => { 10 | let joined = ns.joinFaction(f) 11 | if ( joined ) { ns.tprint(`********* Joined ${f} **********`) } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/satellites/gangClashObserver.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | getLSItem, 4 | setLSItem, 5 | disableLogs, 6 | formatDuration, 7 | tryRun 8 | } from 'utils/helpers.js' 9 | const sec = 1000 10 | 11 | /** @param {NS} ns **/ 12 | export async function main(ns) { 13 | disableLogs(ns, ['sleep']) 14 | 15 | const gangInfo = getLSItem('gangMeta') 16 | if ( !gangInfo || !gangInfo.faction ) 17 | return ns.print('no gang') // can't clash a gang that doesn't exist 18 | 19 | if ( gangInfo.warPhase == 'peace' ) 20 | return ns.print('peace time, there is no longer need to fight.') 21 | 22 | let nextClashTime = getLSItem('clashtime') 23 | let diff = Date.now() - nextClashTime 24 | ns.print(`Next clash time: ${nextClashTime}`) 25 | ns.print(`Diff to now: ${formatDuration(diff)}`) 26 | 27 | if (nextClashTime && nextClashTime < Date.now()) { 28 | nextClashTime = findNextClashTime(nextClashTime) 29 | setLSItem('clashtime', nextClashTime) 30 | ns.print(`WARNING: Missed clashTime by ${formatDuration(diff)}, setting next ` + 31 | `to ${nextClashTime}`) 32 | } 33 | 34 | if ( nextClashTime && nextClashTime > Date.now() + 1*sec ) { 35 | ns.print(`Next clash time too far away, run again in ${ 36 | formatDuration(nextClashTime-Date.now() - 1*sec)}.`) 37 | return 38 | } 39 | 40 | ns.print(`Trying to run clashRecorder.js...`) 41 | ns.run('/gang/clashRecorder.js') 42 | 43 | ns.print(`Waiting for next clash time.. (${nextClashTime - Date.now()}).`) 44 | while(nextClashTime - Date.now() > 200 ) { 45 | await ns.sleep(5) 46 | } 47 | ns.print(`Clash time arrived! (${nextClashTime - Date.now()})`) 48 | 49 | ns.print('Trying to run gang/warRunner....') 50 | await ns.run('/gang/warRunner.js') 51 | } 52 | 53 | function findNextClashTime(curr) { 54 | if (curr < Date.now()) 55 | return findNextClashTime(curr + 20*sec) 56 | return curr 57 | } 58 | -------------------------------------------------------------------------------- /src/satellites/gangMetaObserver.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | runCommand, 4 | setLSItem, 5 | getLSItem, 6 | announce, 7 | } from 'utils/helpers.js' 8 | let gangInfo; 9 | 10 | /** @param {NS} ns **/ 11 | export async function main(ns) { 12 | const inAnyGang = await fetch(ns, `ns.gang.inGang()`, '/Temp/gang.inGang.txt') 13 | if ( !inAnyGang ) { 14 | setLSItem('gangMeta', { faction: false }) 15 | return ns.print('no gang') // can't gather data for no gang 16 | } 17 | 18 | gangInfo = await fetch(ns, `ns.gang.getGangInformation()`, 19 | '/Temp/gang.getGangInformation.txt') 20 | 21 | gangInfo.members = await fetch(ns, 22 | `ns.gang.getMemberNames().map(m => ns.gang.getMemberInformation(m))`, 23 | '/Temp/gang.getMemberInformation.txt') 24 | gangInfo.otherGangs = await fetch(ns, `ns.gang.getOtherGangInformation()`, 25 | '/Temp/gang.getOtherGangInformation.txt') 26 | delete gangInfo.otherGangs[gangInfo.faction] 27 | 28 | 29 | gangInfo.taskPhase = await findTaskPhase(ns, gangInfo) 30 | gangInfo.warPhase = await findWarPhase(ns, gangInfo) 31 | 32 | await adjustTerritoryWarfare(ns, gangInfo) 33 | 34 | setLSItem('gangMeta', gangInfo) 35 | ns.print(getLSItem('gangMeta')) 36 | } 37 | 38 | async function findWarPhase(ns, gangInfo) { 39 | // sometimes rounding errors means my gang.territory < 1 but all others == 0 40 | const allOthersAtZero = Object.values(gangInfo.otherGangs).every(g => g.territory == 0) 41 | if ( gangInfo.territory == 1 || allOthersAtZero ) 42 | return 'peace' 43 | 44 | const otherGangs = Object.keys(gangInfo.otherGangs) 45 | const chances = await fetch(ns, 46 | `Object.fromEntries(${JSON.stringify(otherGangs)}.map(g => [g, ns.gang.getChanceToWinClash(g)]))`, 47 | '/Temp/gang.getChanceToWinClash.txt') 48 | const minChance = Math.min(...otherGangs.map(g => chances[g])) 49 | if ( minChance > 0.7 ) 50 | return 'war' 51 | 52 | return 'posturing' 53 | } 54 | 55 | async function findTaskPhase(ns, gangInfo) { 56 | if ( await metFactionRepRequirements(ns, gangInfo.faction) ) { 57 | return 'money' 58 | } 59 | 60 | return 'respect' 61 | } 62 | 63 | async function metFactionRepRequirements(ns, faction) { 64 | const allAugs = await fetch(ns, `ns.getAugmentationsFromFaction('${faction}')`, 65 | '/Temp/faction-augs.txt') 66 | const owned = await fetch(ns, 'ns.getOwnedAugmentations(true)', 67 | '/Temp/getOwnedAugmentations.txt') 68 | const unownedAugs = allAugs.filter(a => !owned.includes(a)) 69 | const reputation = await fetch(ns, `ns.getFactionRep('${faction}')`, 70 | '/Temp/faction-rep.txt') 71 | 72 | const augRepReqs = await fetch(ns, 73 | `${JSON.stringify(unownedAugs)}.map(aug => ns.getAugmentationRepReq(aug))`, 74 | '/Temp/aug-repreqs.txt') 75 | const max = Math.max(0, ...augRepReqs) 76 | return reputation > max 77 | } 78 | 79 | async function adjustTerritoryWarfare(ns, gangInfo) { 80 | if ( gangInfo.warPhase == 'war' && !gangInfo.territoryWarfareEngaged ) { 81 | await runCommand(ns, 'ns.gang.setTerritoryWarfare(ns.args[0])', 82 | '/Temp/gang.setTerritoryWarfare.js', false, 1, true) 83 | gangInfo.territoryWarfareEngaged = true 84 | announce(ns, 'Engaging territory warfare!', 'error') 85 | } 86 | if ( gangInfo.warPhase != 'war' && gangInfo.territoryWarfareEngaged ) { 87 | await runCommand(ns, 'ns.gang.setTerritoryWarfare(ns.args[0])', 88 | '/Temp/gang.setTerritoryWarfare.js', false, 1, false) 89 | gangInfo.territoryWarfareEngaged = false 90 | announce(ns, 'Disengaging from territory war.', 'warning') 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/satellites/homeRamObserver.js: -------------------------------------------------------------------------------- 1 | import { 2 | fetchPlayer, 3 | reserve, 4 | announce, 5 | getLSItem, 6 | canUseSingularity 7 | } from 'utils/helpers.js' 8 | import { fetchServerFree } from 'utils/network.js' 9 | 10 | 11 | /** @param {NS} ns **/ 12 | export async function main(ns) { 13 | ns.print("------------------------") 14 | 15 | var mult = getLSItem("bitnode")["HomeComputerRamCost"] 16 | var serverData = fetchServerFree('home') 17 | var ram = serverData.maxRam 18 | var cost = Math.ceil(ram * 3.2 * mult * Math.pow(10,4) * Math.pow(1.58, Math.log2(ram))) 19 | ns.print("Cost: $" + ns.formatNumber(cost, 3)) 20 | var player = fetchPlayer() 21 | ns.print("Player money: $" + ns.formatNumber(player.money, 3)) 22 | let res = ram < 32 ? 0 : reserve(ns) 23 | 24 | if ((player.money - res) < cost) { 25 | ns.print("Too expensive to justify, maybe later.") 26 | return 27 | } 28 | 29 | ns.print("available ram: " + (ram - serverData.ramUsed)) 30 | ns.print("script ram cost: " + ns.getScriptRam('upgradeHomeRam.js')) 31 | 32 | if (canUseSingularity(2) && ns.getScriptRam('upgradeHomeRam.js') < (ram - serverData.ramUsed)) { 33 | announce(ns, 'Attempting to automatically upgrade home ram.', 'info') 34 | ns.spawn('upgradeHomeRam.js', {spawnDelay: 100}) 35 | } else { 36 | announce(ns, 'Upgrade home RAM manually, see Alpha Enterprises in Sector 12', 'warning') 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/satellites/networkObserver.js: -------------------------------------------------------------------------------- 1 | import { setLSItem } from 'utils/helpers.js' 2 | import { networkMap } from 'utils/network.js' 3 | 4 | /** 5 | * @param {NS} ns 6 | **/ 7 | export async function main(ns) { 8 | let map = networkMap(ns) 9 | setLSItem('nmap', map) 10 | } 11 | -------------------------------------------------------------------------------- /src/satellites/playerObserver.js: -------------------------------------------------------------------------------- 1 | import { getLSItem, setLSItem } from 'utils/helpers.js' 2 | import { purchaseables } from 'utils/constants.js' 3 | 4 | /** 5 | * @param {NS} ns 6 | **/ 7 | export async function main(ns) { 8 | let player = ns.getPlayer() 9 | player.karma = ns.heart.break() 10 | player.resetInfo = getLSItem('reset') 11 | player.tor = ns.hasTorRouter() 12 | 13 | if ( isFirstRun() ) { 14 | player.programs = [] 15 | player.boughtAllPrograms = false 16 | } else { 17 | const files = ns.ls('home') 18 | player.programs = files.filter(f => f.includes('.exe')) 19 | player.boughtAllPrograms = didPlayerBuyAllPrograms(player) 20 | } 21 | 22 | setLSItem('player', player) 23 | } 24 | 25 | function didPlayerBuyAllPrograms(player) { 26 | if ( !player.tor ) 27 | return false 28 | 29 | return purchaseables.every(f => player.programs.includes(f.name)) 30 | } 31 | 32 | function isFirstRun() { 33 | return getLSItem('player') === undefined 34 | } 35 | -------------------------------------------------------------------------------- /src/satellites/programObserver.js: -------------------------------------------------------------------------------- 1 | import { fetchPlayer, tryRun, canUseSingularity } from 'utils/helpers.js' 2 | import { purchaseables } from 'utils/constants.js' 3 | 4 | /** 5 | * @param {NS} ns 6 | **/ 7 | export async function main(ns) { 8 | const player = fetchPlayer() 9 | if ( player.tor && player.boughtAllPrograms ) { 10 | return 11 | } 12 | 13 | if ( !player.tor ) { 14 | if ( player.money > 2e5 ) { 15 | if (!canUseSingularity()) { 16 | ns.tprint("WARNING: We haven't yet reached the singularity; purchase Tor darkweb " + 17 | "access manually at Alpha Enterprises.") 18 | return 19 | } 20 | await tryRun(() => ns.run('torBuyer.js')) 21 | } 22 | return 23 | } 24 | 25 | for ( const file of purchaseables ) { 26 | if ( !player.programs.includes(file.name) ) { 27 | if ( player.money > file.cost) { 28 | 29 | if (!canUseSingularity()) { 30 | ns.tprint(`WARNING: We haven't yet reached the singularity; purchase ${file.name} ` + 31 | `manually: \`buy ${file.name}\``) 32 | return 33 | } 34 | 35 | await tryRun(() => ns.run('programBuyer.js', 1, file.name)) 36 | } 37 | return 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/satellites/pservObserver.js: -------------------------------------------------------------------------------- 1 | import { 2 | getLSItem, 3 | announce, 4 | myMoney, 5 | getReserve 6 | } from 'utils/helpers.js' 7 | import { getPercentUsedRam } from '/batching/calculations.js' 8 | 9 | /** 10 | * @param {NS} ns 11 | **/ 12 | export async function main(ns) { 13 | ns.clearLog() 14 | // if there's already a buyer running, let it finish before starting another 15 | const homePS = ns.ps('home') 16 | if ( homePS.some(proc => proc.filename === 'pServBuyer.js') ) { 17 | ns.print("pServBuyer.js running currently, try again later.") 18 | return 19 | } 20 | 21 | const nmap = getLSItem('nmap') 22 | if ( !nmap ) { 23 | ns.print("NMAP not available, waiting until it comes back.") 24 | return 25 | } 26 | 27 | if (nmap['home'].maxRam < 16) { 28 | ns.print("Not enough ram on home to handle the pServBuyer script.") 29 | return 30 | } 31 | 32 | if (getPercentUsedRam(nmap) < 0.25) { 33 | ns.print("Enough ram to run batches, spend money elsewhere.") 34 | return 35 | } 36 | 37 | const pservs = Object.values(nmap).filter(s => s.name != 'home' && s.purchasedByPlayer) 38 | const currRam = smallestCurrentServerSize(ns, pservs) 39 | const reserve = getReserve() 40 | const moneyAvailable = myMoney() - reserve 41 | const nextRam = nextRamSize(ns, currRam, moneyAvailable) 42 | ns.print(`Current: ${currRam} Next: ${2**nextRam} (2^${nextRam})`) 43 | 44 | 45 | if (nextRam == 0 ) { 46 | ns.print("INFO: NextRam is 0, cancelling.") 47 | return 48 | } 49 | if (2**nextRam == currRam) { 50 | ns.print("INFO: NextRam is currRam, cancelling.") 51 | return 52 | } 53 | if (nextRam >= 4 && nmap['home'].maxRam < 32) { 54 | ns.print("Pausing to allow upgrading home.") 55 | return 56 | } 57 | 58 | const cost = ns.getPurchasedServerCost(2**nextRam) 59 | if ( moneyAvailable < cost * 2 ){ 60 | ns.print(`INFO: Not enough money to afford the server * 2 + reserve: \$${ns.formatNumber(cost)} * 2 + \$${ns.formatNumber(reserve)} (\$${ns.formatNumber(cost*2 + reserve)})`) 61 | return 62 | } 63 | 64 | let msg = `Running pServBuyer.js to purchase ${ns.formatRam(2**nextRam)} (currently: ${ns.formatRam(currRam)}) for \$${ns.formatNumber(cost)}` 65 | announce(ns, msg) 66 | // ns.tprint(msg) 67 | // ns.tprint(` ns.spawn('pServBuyer.js', 1, '--size', ${nextRam})`) 68 | ns.spawn('pServBuyer.js', {spawnDelay: 0}, '--size', nextRam) 69 | } 70 | 71 | 72 | /** 73 | * @param {NS} ns 74 | * @param {array} pservs 75 | **/ 76 | function smallestCurrentServerSize(ns, pservs) { 77 | const limit = ns.getPurchasedServerLimit() 78 | if (pservs.length < limit) 79 | return 0 80 | 81 | return pservs.reduce(((prev, cur) => prev.maxRam < cur.maxRam ? prev : cur)).maxRam 82 | } 83 | 84 | /** 85 | * @param {NS} ns 86 | * @param {number} curRam 87 | * @param {number} money 88 | * @param {array} pservs 89 | **/ 90 | function nextRamSize(ns, currRam, money) { 91 | const limit = ns.getPurchasedServerLimit() 92 | const maxServerSize = ns.getPurchasedServerMaxRam() 93 | ns.print(`My money: \$${ns.formatNumber(money)}`) 94 | 95 | let cost, totalCost, i 96 | for (i = 20; (i > 0 && 2**i > currRam); i--) { 97 | // max server size can vary based on BN 98 | if ( 2**i > maxServerSize ) continue 99 | if (i < 0) { ns.ui.openTail(); throw `How is i less than 0? ${i}` } 100 | cost = ns.getPurchasedServerCost(2**i) 101 | totalCost = cost * limit 102 | 103 | ns.print(`Total cost for ${2**i}GB ram: ${ns.formatNumber(totalCost, 12)}`) 104 | if ( cost*2 < money ) { 105 | ns.print(`(${2**i}) totalCost*2 < myMoney`) 106 | ns.print(`Returning ${i}`) 107 | return i 108 | } 109 | } 110 | return i + 1 111 | } 112 | -------------------------------------------------------------------------------- /src/satellites/shareObserver.js: -------------------------------------------------------------------------------- 1 | import { networkMapFree } from 'utils/network.js' 2 | import { getPercentUsedRam } from '/batching/calculations.js' 3 | 4 | /** @param {NS} ns */ 5 | export async function main(ns) { 6 | const nmap = networkMapFree() 7 | ns.clearLog() 8 | 9 | if (!nmap) { 10 | ns.print("No network map, try again later") 11 | return 12 | } 13 | 14 | const percentUsed = getPercentUsedRam(nmap) 15 | if ( percentUsed > 0.7 ) { 16 | ns.print(`Using more than 70% ram, try when there's less used. (${ns.formatPercent(percentUsed)}%)`) 17 | return 18 | } 19 | ns.print(`Ram used less than 70%: ${ns.formatPercent(percentUsed)}%`) 20 | 21 | const sharePower = ns.getSharePower() 22 | if ( sharePower > 1.7 ) { 23 | ns.print(`Share power greater than 1.7: ${sharePower}`) 24 | return 25 | } 26 | ns.print(`Share power less than 1.7: ${sharePower}`) 27 | 28 | const pservs = Object.values(nmap) 29 | .filter(s => s.purchasedByPlayer && 30 | s.hostname !== 'home' && 31 | s.files.includes('share.js') ) 32 | .sort((a, b) => { return b.availableRam - a.availableRam }) 33 | ns.print(pservs.map(s => [s.hostname, s.availableRam])) 34 | const server = pservs[0] 35 | const threads = Math.floor((server.availableRam * 0.8)/4) 36 | 37 | ns.exec('share.js', server.hostname, { threads: threads }) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/satellites/stanekObserver.js: -------------------------------------------------------------------------------- 1 | /** @param {NS} ns */ 2 | export async function main(ns) { 3 | let rep = ns.singularity.getFactionRep("Church of the Machine God") 4 | while( ns.singularity.getFactionRep("Church of the Machine God") < rep + 100000) { 5 | await ns.stanek.chargeFragment(2,0) 6 | await ns.stanek.chargeFragment(1,2) 7 | await ns.stanek.chargeFragment(1,0) 8 | } 9 | ns.tprint("SUCCESS: CotMG has been fed.") 10 | } 11 | -------------------------------------------------------------------------------- /src/share.js: -------------------------------------------------------------------------------- 1 | /** @param {NS} ns */ 2 | export async function main(ns) { 3 | while (true) await ns.share() 4 | } 5 | -------------------------------------------------------------------------------- /src/sleeves/manager.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | getLSItem, 4 | haveSourceFile, 5 | fetchPlayer, 6 | formatNumberShort, 7 | } from 'utils/helpers.js' 8 | 9 | /** @param {NS} ns **/ 10 | export async function main(ns) { 11 | if ( !haveSourceFile(10) ) 12 | return ns.print('dont have source file 10') 13 | 14 | const sleeveData = getLSItem('sleeveMeta') 15 | if ( !sleeveData || sleeveData.length == 0 ) 16 | return ns.print('no sleeves yet, wyd?') 17 | 18 | for ( const sleeve of sleeveData ) { 19 | ns.print(`Managing activity for sleeve #${sleeve.number}:`) 20 | await manageSleeve(ns, sleeve) 21 | } 22 | } 23 | 24 | /** 25 | * @param {NS} ns 26 | * @param {object} sleeve 27 | **/ 28 | async function manageSleeve(ns, sleeve) { 29 | if ( sleeve.stats.sync < 95 ) { 30 | ns.print(`Sleeve sync at ${formatNumberShort(sleeve.stats.sync)}, syncronizing....`) 31 | return await syncronize(ns, sleeve) 32 | } 33 | 34 | const gangMeta = getLSItem('gangMeta') 35 | if ( !gangMeta || !gangMeta.faction ) { 36 | ns.print(`Not yet joined a gang, reducing karma`) 37 | return await assistJoiningGang(ns, sleeve) 38 | } 39 | 40 | // if ( sleeve.stats.shock > 0 ) 41 | // return await goToTherapy(ns, sleeve) 42 | 43 | const player = fetchPlayer() 44 | if ( ['strength', 'defense', 'dexterity', 'agility'].some(s => player[s] < 200 ) ) { 45 | ns.print(`some stat is less than 200, mugging.`) 46 | return await farmCombatStats(ns, sleeve) 47 | } 48 | 49 | ns.print(`farming int`) 50 | return await farmIntelligence(ns, sleeve) 51 | } 52 | 53 | /** 54 | * @param {NS} ns 55 | * @param {object} sleeve 56 | **/ 57 | async function farmIntelligence(ns, sleeve) { 58 | if ( bondForgeryChance(sleeve) > 0.75 ) { 59 | if ( sleeve.task.crime == 'Bond Forgery' ) 60 | return ns.print(`Sleeve #${sleeve.number} already forging bonds.`) 61 | await fetch(ns, `ns.sleeve.setToCommitCrime(${sleeve.number}, "Bond Forgery")`, 62 | '/Temp/sleeve.setToCommitCrime.txt') 63 | return ns.print(`Sleeve #${sleeve.number} set to forging bonds.`) 64 | } 65 | 66 | if ( larcenyChance(sleeve) > 0.75 ) { 67 | if ( sleeve.task.crime == 'Larceny' ) 68 | return ns.print(`Sleeve #${sleeve.number} already committing larceny.`) 69 | await fetch(ns, `ns.sleeve.setToCommitCrime(${sleeve.number}, "Larceny")`, 70 | '/Temp/sleeve.setToCommitCrime.txt') 71 | return ns.print(`Sleeve #${sleeve.number} set to larceny.`) 72 | } 73 | 74 | if ( sleeve.task.crime == "Rob Store" ) 75 | return ns.print(`Sleeve #${sleeve.number} already robbing store.`) 76 | 77 | ns.print(`set to commit crime rob store`) 78 | await fetch(ns, `ns.sleeve.setToCommitCrime(${sleeve.number}, "Rob Store")`, 79 | '/Temp/sleeve.setToCommitCrime.txt') 80 | } 81 | 82 | /** 83 | * @param {NS} ns 84 | * @param {object} sleeve 85 | **/ 86 | async function farmCombatStats(ns, sleeve) { 87 | if ( sleeve.task.crime == "Mug" ) 88 | return ns.print('already mugging') 89 | 90 | ns.print(`set to commit crime mug`) 91 | await fetch(ns, `ns.sleeve.setToCommitCrime(${sleeve.number}, "Mug")`, 92 | '/Temp/sleeve.setToCommitCrime.txt') 93 | } 94 | 95 | /** 96 | * @param {NS} ns 97 | * @param {object} sleeve 98 | **/ 99 | async function goToTherapy(ns, sleeve) { 100 | if ( sleeve.task.task == "Recovery" ) 101 | return 102 | 103 | await fetch(ns, `ns.sleeve.setToShockRecovery(${sleeve.number})`, 104 | '/Temp/sleeve.setToShockRecovery.txt') 105 | } 106 | 107 | /** 108 | * @param {NS} ns 109 | * @param {object} sleeve 110 | **/ 111 | async function assistJoiningGang(ns, sleeve) { 112 | if ( homicideChance(sleeve) < 0.75 ) { 113 | if ( sleeve.task.crime == "Mug") 114 | return 115 | return await fetch(ns, `ns.sleeve.setToCommitCrime(${sleeve.number}, "Mug")`, 116 | '/Temp/sleeve.setToCommitCrime.txt') 117 | } 118 | 119 | if ( sleeve.task.crime == "Homicide" ) 120 | return 121 | 122 | await fetch(ns, `ns.sleeve.setToCommitCrime(${sleeve.number}, "Homicide")`, 123 | '/Temp/sleeve.setToCommitCrime.txt') 124 | } 125 | 126 | /** 127 | * @param {NS} ns 128 | * @param {object} sleeve 129 | **/ 130 | async function syncronize(ns, sleeve) { 131 | if ( sleeve.task.task == 'Syncro' ) // already syncronizing! 132 | return 133 | 134 | return await fetch(ns, `ns.sleeve.setToSynchronize(${sleeve.number})`, 135 | '/Temp/sleeve.setToSynchronize.txt') 136 | } 137 | 138 | function homicideChance(sleeve) { 139 | const playerInt = fetchPlayer().intelligence 140 | const difficulty = 1 141 | return (((2*sleeve.stats.strength + 142 | 2*sleeve.stats.defense + 143 | 0.5*sleeve.stats.dexterity + 144 | 0.5*sleeve.stats.agility + 145 | 0.25*playerInt) / 975 ) / difficulty ) * 146 | (1+((1*(playerInt^0.8))/600)) 147 | } 148 | 149 | function bondForgeryChance(sleeve) { 150 | const playerInt = fetchPlayer().intelligence 151 | const difficulty = 0.5 152 | 153 | return (((0.05*sleeve.stats.hacking + 154 | 1.25*sleeve.stats.dexterity) / 975) / difficulty )* 155 | (1 + ((1*(playerInt^0.8))/600)) 156 | } 157 | 158 | function larcenyChance(sleeve) { 159 | const playerInt = fetchPlayer().intelligence 160 | const difficulty = 0.33 161 | 162 | return (((0.5*sleeve.stats.hacking + 163 | 1*sleeve.stats.dexterity + 164 | 1*sleeve.stats.agility) / 975) / difficulty )* 165 | (1 + ((1*(playerInt^0.8))/600)) 166 | } 167 | -------------------------------------------------------------------------------- /src/sleeves/metaObserver.js: -------------------------------------------------------------------------------- 1 | import { 2 | getNsDataThroughFile as fetch, 3 | setLSItem, 4 | getLSItem, 5 | haveSourceFile, 6 | } from 'utils/helpers.js' 7 | 8 | /** @param {NS} ns **/ 9 | export async function main(ns) { 10 | if ( !haveSourceFile(10) ) 11 | return ns.print('dont have source file 10') 12 | 13 | const numSleeves = await fetch(ns, `ns.sleeve.getNumSleeves()`, 14 | '/Temp/sleeve.getNumSleeves.txt') 15 | 16 | if ( numSleeves < 1 ) 17 | return ns.print('no sleeves to manage, probably should get some....') 18 | 19 | const counter = [...Array(numSleeves).keys()] // shortcut for [0,1,2...numSleeves] 20 | const sleeves = await fetchData(ns, 'sleeve.getInformation', counter) 21 | ns.print(`Found data for ${sleeves.length} sleeves`) 22 | 23 | mergeData(sleeves, 'number', counter) 24 | mergeData(sleeves, 'augs', await fetchData(ns, 'sleeve.getSleeveAugmentations', counter)) 25 | mergeData(sleeves, 'stats', await fetchData(ns, 'sleeve.getSleeveStats', counter)) 26 | mergeData(sleeves, 'task', await fetchData(ns, 'sleeve.getTask', counter)) 27 | 28 | setLSItem('sleeveMeta', sleeves) 29 | ns.print(getLSItem('sleeveMeta')) 30 | ns.print('Set augs, stats, and tasks for sleeve data.') 31 | } 32 | 33 | /** 34 | * @param {NS} ns 35 | * @param {string} command 36 | * @param {number[]} counter 37 | **/ 38 | async function fetchData(ns, command, counter) { 39 | return await fetch(ns, 40 | `${JSON.stringify(counter)}.map(n => ns.${command}(n))`, 41 | `/Temp/${command}.txt`) 42 | } 43 | 44 | /** 45 | * @param {object[]} arrSleeves - the array of sleeve data 46 | * @param {string} prop - the property to add to each sleeve object 47 | * @param {object[]} data - the array of data associated with each sleeve 48 | **/ 49 | function mergeData(arrSleeves, prop, data) { 50 | arrSleeves.forEach((sleeve, ndx) => sleeve[prop] = data[ndx]) 51 | } 52 | -------------------------------------------------------------------------------- /src/startup/cleanup.js: -------------------------------------------------------------------------------- 1 | import { networkMapFree } from 'utils/network.js' 2 | import { clearLSItem } from 'utils/helpers.js' 3 | 4 | const hangableFiles = [ 5 | 'batchGrow.js', 6 | 'batchHack.js', 7 | 'batchWeaken.js' 8 | ] 9 | const badLog = "Waiting for port write." 10 | 11 | 12 | /** @param {NS} ns */ 13 | export async function main(ns) { 14 | cleanOldBatchRecords() 15 | cleanHangingFiles(ns) 16 | } 17 | 18 | function cleanOldBatchRecords() { 19 | clearLSItem('batches') 20 | } 21 | 22 | /** @param {NS} ns */ 23 | function cleanHangingFiles(ns) { 24 | let map = networkMapFree() 25 | for (let server of Object.values(map)) { 26 | let scripts = ns.ps(server.hostname) 27 | for (let s of scripts) { 28 | if (!hangableFiles.includes(s.filename)) 29 | continue 30 | let script = ns.getRunningScript(s.pid) 31 | if (!script.logs[script.logs.length-1].includes(badLog)) 32 | continue 33 | if (script.onlineRunningTime < 5) 34 | continue 35 | ns.print(server.hostname) 36 | ns.print(`--- ${script.filename} ${script.onlineRunningTime}`) 37 | ns.kill(s.pid) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/startup/initStartup.js: -------------------------------------------------------------------------------- 1 | const filesToDownload = [ 2 | 'batching/builder.js', 3 | 'batching/calculations.js', 4 | 'batching/queue.js', 5 | 'batching/shotgunBatcher.js', 6 | 'batching/shotgunBuilder.js', 7 | 'contracts/arrayJumpingSolver.js', 8 | 'contracts/CodingContractWrapper.js', 9 | 'contracts/compression1RLE.js', 10 | 'contracts/encryption1CaesarCipher.js', 11 | 'contracts/encryption2VigenereCipher.js', 12 | 'contracts/findValidExpressionsSolver.js', 13 | 'contracts/generateIpAddsSolver.js', 14 | 'contracts/hammingCodesBinToIntSolver.js', 15 | 'contracts/mergeIntervalsSolver.js', 16 | 'contracts/minimumPathSumSolver.js', 17 | 'contracts/primeFactorSolver.js', 18 | 'contracts/sanitizeParensSolver.js', 19 | 'contracts/shortestPathSolver.js', 20 | 'contracts/spiralizeMatrixSolver.js', 21 | 'contracts/squareRootSolver.js', 22 | 'contracts/stockTraderSolver.js', 23 | 'contracts/subarrayMaximumSolver.js', 24 | 'contracts/totalWaysToSumSolver.js', 25 | 'contracts/uniquePaths1Solver.js', 26 | 'contracts/uniquePaths2Solver.js', 27 | 'gang/ascend.js', 28 | 'gang/augments.js', 29 | 'gang/clashRecorder.js', 30 | 'gang/equipment.js', 31 | 'gang/recruitment.js', 32 | 'gang/tasks.js', 33 | 'gang/warRunner.js', 34 | 'hacknet/coreUpgrader.js', 35 | 'hacknet/levelUpgrader.js', 36 | 'hacknet/ramUpgrader.js', 37 | 'hacknet/startup.js', 38 | 'satellites/activityObserver.js', 39 | 'satellites/backdoorObserver.js', 40 | 'satellites/batchMetaObserver.js', 41 | 'satellites/batchObserver.js', 42 | 'satellites/contractsObserver.js', 43 | 'satellites/controller.js', 44 | 'satellites/factionObserver.js', 45 | 'satellites/gangClashObserver.js', 46 | 'satellites/gangMetaObserver.js', 47 | 'satellites/homeRamObserver.js', 48 | 'satellites/networkObserver.js', 49 | 'satellites/playerObserver.js', 50 | 'satellites/programObserver.js', 51 | 'satellites/pservObserver.js', 52 | 'satellites/shareObserver.js', 53 | 'satellites/stanekObserver.js', 54 | 'sleeves/manager.js', 55 | 'sleeves/metaObserver.js', 56 | 'startup/cleanup.js', 57 | 'startup/run.js', 58 | 'usr/find.js', 59 | 'usr/lsClear.js', 60 | 'usr/lsGet.js', 61 | 'usr/lsSet.js', 62 | 'utils/constants.js', 63 | 'utils/formulas.js', 64 | 'utils/helpers.js', 65 | 'utils/network.js', 66 | 'augPurchaser.js', 67 | 'backdoor.js', 68 | 'batchGrow.js', 69 | 'batchHack.js', 70 | 'batchWeaken.js', 71 | 'bestHack.js', 72 | 'botnet.js', 73 | 'breadwinner.js', 74 | 'cleanupStaleScripts.js', 75 | 'crime.js', 76 | 'doProcess.js', 77 | 'monitor.js', 78 | 'nuker.js', 79 | 'pServBuyer.js', 80 | 'programBuyer.js', 81 | 'rooter.js', 82 | 'share.js', 83 | 'stats.js', 84 | 'torBuyer.js', 85 | 'upgradeHomeRam.js', 86 | 'workForFactions.js', 87 | ] 88 | 89 | const argsSchema = [ 90 | ['branch', 'main'] 91 | ]; 92 | 93 | export function autocomplete(data, args) { 94 | data.flags(argsSchema) 95 | return ['main'] // add any additional branches here if you are working on them 96 | } 97 | 98 | const baseUrl = 'https://raw.githubusercontent.com/jenheilemann/bitburner-scripts/' 99 | 100 | /** 101 | * @param {NS} ns 102 | **/ 103 | export async function main(ns) { 104 | ns.disableLog("sleep") 105 | const args = ns.flags(argsSchema) 106 | 107 | for ( let filename of ns.ls('home', '.js')) { 108 | if (filename == 'startup/initStartup.js') continue; 109 | ns.scriptKill(filename, 'home') 110 | ns.rm(filename) 111 | } 112 | for ( let filename of ns.ls('home', 'Temp')) { 113 | ns.rm(filename) 114 | } 115 | 116 | ns.tprint(`INFO: Attempting to download files from ${baseUrl}:`) 117 | for ( let filename of filesToDownload ) { 118 | await ns.sleep(20) 119 | await download(ns, filename, args.branch) 120 | } 121 | await ns.sleep(50) 122 | ns.tprint('Killed and deleted old scripts.') 123 | await ns.sleep(50) 124 | ns.tprint(`Files downloaded.`) 125 | 126 | await ns.sleep(50) 127 | ns.tprint(`Starting startup/run.js`) 128 | ns.spawn('/startup/run.js', {spawnDelay: 1_000}) 129 | } 130 | 131 | export async function download(ns, filename, branch) { 132 | const path = baseUrl + branch + '/src/' + filename 133 | ns.tprint(` -- Downloading ${branch + '/src/' + filename}`) 134 | const res = await ns.wget(path + '?ts=' + new Date().getTime(), filename) 135 | if (!res) ns.tprint(`ERROR: Download failed: ${path}`) 136 | } 137 | -------------------------------------------------------------------------------- /src/startup/run.js: -------------------------------------------------------------------------------- 1 | import { 2 | disableLogs, 3 | getNsDataThroughFile as fetch, 4 | setLSItem, 5 | clearLSItem, 6 | } from 'utils/helpers.js' 7 | import { myFavTheme } from 'utils/constants.js' 8 | import { getBitnodeMultipliers } from 'utils/formulas.js' 9 | 10 | const staleLocalStorageKeys = [ 11 | 'nmap', 12 | 'reserve', 13 | 'reset', 14 | 'batches', 15 | 'batchJobID', 16 | 'player', 17 | 'decommissioned', 18 | 'hackpercent', 19 | 'clashtime', 20 | 'gangmeta', 21 | 'sleevemeta', 22 | ] 23 | 24 | /** 25 | * @param {NS} ns 26 | **/ 27 | export async function main(ns) { 28 | disableLogs(ns, ["sleep"]) 29 | ns.tprint("--------------------------------------") 30 | 31 | ns.tprint(`Cleaning up localStorage.`) 32 | staleLocalStorageKeys.map((value) => clearLSItem(value)) 33 | await ns.sleep(20) 34 | 35 | ns.tprint(`Setting favorite theme`) 36 | ns.ui.setTheme(myFavTheme) 37 | await ns.sleep(20) 38 | 39 | ns.tprint(`Fetching reset info`) 40 | const reset = await fetch(ns, `ns.getResetInfo()`, '/Temp/reset-info.txt') 41 | setLSItem('reset', reset) 42 | await ns.sleep(100) 43 | 44 | ns.tprint(`Setting hackPercent to 1 (about 5% baseline)`) 45 | setLSItem('hackPercent', 1) 46 | await ns.sleep(100) 47 | 48 | const bn = getBitnodeMultipliers(reset) 49 | setLSItem('bitnode', bn) 50 | await ns.sleep(10) 51 | 52 | ns.tprint(`Setting source file information`) 53 | const sf = reset.ownedSF 54 | setLSItem('sourceFiles', sf) 55 | await ns.sleep(10) 56 | 57 | ns.tprint(`Initializing the Player data`) 58 | const player = await fetch(ns, `ns.getPlayer()`, '/Temp/getPlayer.txt') 59 | setLSItem('player', player) 60 | await ns.sleep(10) 61 | 62 | ns.tprint(`Initializing the Network Map`) 63 | ns.run('satellites/networkObserver.js') 64 | await ns.sleep(100) 65 | 66 | ns.tprint(`Starting controller.js`) 67 | ns.run('/satellites/controller.js') 68 | ns.tprint(`Startup completed. May your pillow always be cool.`) 69 | } 70 | -------------------------------------------------------------------------------- /src/stats.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @param {NS} ns 4 | **/ 5 | export async function main(ns) { 6 | const doc = eval('document') 7 | const hook0 = doc.getElementById('overview-extra-hook-0') 8 | const hook1 = doc.getElementById('overview-extra-hook-1') 9 | if ( !hook0 || !hook1 ) { 10 | return 11 | } 12 | hook0.innerText = "Income\nExper.\nKarma\nShare" 13 | 14 | hook1.innerText = 15 | `${ns.formatNumber(ns.getTotalScriptIncome()[0], 2)}/s` + 16 | `\n${ns.formatNumber(ns.getTotalScriptExpGain(), 2)}/s` + 17 | `\n${ns.formatNumber(ns.heart.break(), 2)}` + 18 | `\n${ns.formatNumber(ns.getSharePower(), 2)}` 19 | } 20 | -------------------------------------------------------------------------------- /src/torBuyer.js: -------------------------------------------------------------------------------- 1 | import { getLSItem, clearLSItem, canUseSingularity } from 'utils/helpers.js' 2 | 3 | /** 4 | * @param {NS} ns 5 | **/ 6 | export async function main(ns) { 7 | const player = getLSItem('PLAYER') 8 | if ( player.tor || player.money < 2e5){ 9 | ns.print("Tor already purchased, or not enough money.") 10 | return 11 | } 12 | if (!canUseSingularity()) { 13 | ns.tprint("Can't use the singularity yet, you have to go buy the Tor darkweb " + 14 | "server access manually at Alpha Enterprises.") 15 | return 16 | } 17 | ns.tprint(`Buying access to the Darkweb. ooOOOooo spoooooOOOkey`) 18 | ns.singularity.purchaseTor() 19 | } 20 | -------------------------------------------------------------------------------- /src/upgradeHomeRam.js: -------------------------------------------------------------------------------- 1 | import { announce } from 'utils/helpers.js' 2 | 3 | /** @param {NS} ns */ 4 | export async function main(ns) { 5 | let ret = ns.singularity.upgradeHomeRam() 6 | if (ret) return announce(ns, 'Upgraded home ram automatically', 'success') 7 | announce(ns, 'Failed to upgrade home ram', 'failure') 8 | } 9 | -------------------------------------------------------------------------------- /src/usr/find.js: -------------------------------------------------------------------------------- 1 | import { canUseSingularity, runCommand, getLSItem } from 'utils/helpers.js' 2 | import { factionServers as targets } from 'utils/constants.js' 3 | 4 | export function autocomplete(data, args) { 5 | return data.servers 6 | } 7 | 8 | export async function main(ns) { 9 | let path; 10 | 11 | if (ns.args[0] === undefined) { 12 | for (const server in targets) { 13 | ns.tprint("*********** " + server + " ( " + targets[server] + " faction)") 14 | path = mapPath(server) 15 | ns.tprint(printablePathToServer(path, true)) 16 | } 17 | } else { 18 | path = mapPath(ns.args[0]) 19 | ns.tprint(printablePathToServer(path,ns.args[1])) 20 | if ( canUseSingularity() ) { 21 | let cmd = path.map((step) => `ns.singularity.connect('${step}');`).join() 22 | await runCommand(ns, cmd, '/Temp/singularity.connect.js') 23 | } 24 | } 25 | } 26 | 27 | function printablePathToServer(path, backdoor = false) { 28 | let msg = path.join("; connect ") 29 | if (path[0] != "home") 30 | msg = "connect " + msg 31 | 32 | if (backdoor) 33 | msg += "; backdoor;" 34 | 35 | return msg 36 | } 37 | 38 | function mapPath(goal) { 39 | let nMap = getLSItem('NMAP') 40 | let path = [] 41 | 42 | // @ignore-infinite 43 | while (true) { 44 | path.unshift(goal) 45 | if (nMap[goal].parent == '' || nMap[goal].backdoorInstalled ) { 46 | return path 47 | } 48 | goal = nMap[goal].parent 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/usr/lsClear.js: -------------------------------------------------------------------------------- 1 | import { lsKeys } from 'utils/constants.js' 2 | import { clearLSItem,getLSItem } from 'utils/helpers.js' 3 | 4 | export function autocomplete() { 5 | return Object.keys(lsKeys) 6 | } 7 | 8 | /** 9 | * @param {NS} ns 10 | **/ 11 | export async function main(ns) { 12 | let key = ns.args[0] 13 | 14 | if ( !key ) { 15 | ns.tprint(`This script needs a recognized key!`) 16 | ns.tprint('like: `run usr/lsClear.js player`') 17 | return 18 | } 19 | 20 | let safeKey = lsKeys[key.toUpperCase()] 21 | if ( !safeKey ) { 22 | ns.tprint(`That is not a recognized key. Use one of: `) 23 | ns.tprint(Object.keys(lsKeys).join(", ")) 24 | return 25 | } 26 | 27 | clearLSItem(key) 28 | ns.tprint(`Cleared '${safeKey}' ${getLSItem(key)}`) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/usr/lsGet.js: -------------------------------------------------------------------------------- 1 | import { lsKeys } from 'utils/constants.js' 2 | import { getLSItem, formatMoney, formatNumberShort } from 'utils/helpers.js' 3 | 4 | export function autocomplete(data) { 5 | return Object.keys(lsKeys).concat(data.servers) 6 | } 7 | 8 | /** 9 | * @param {NS} ns 10 | **/ 11 | export async function main(ns) { 12 | let args = ns.flags([ 13 | ['pretty', false], 14 | ['p', false], 15 | ]) 16 | 17 | if ( args._.length === 0 ) { 18 | ns.tprint(`This script needs a recognized key!`) 19 | ns.tprint('like: `run usr/lsGet.js reserve`') 20 | return 21 | } 22 | 23 | let keys = args._.slice() 24 | let key = keys.shift() 25 | if ( !lsKeys[key.toUpperCase()] ) { 26 | ns.tprint(`That is not a recognized key. Use one of: `) 27 | ns.tprint(Object.keys(lsKeys).join(", ")) 28 | return 29 | } 30 | 31 | let value = getLSItem(key), nextKey 32 | while ( keys.length > 0 ) { 33 | key = keys.shift() 34 | value = value[key] 35 | } 36 | 37 | if (key == 'reserve') value = formatMoney(value) 38 | if (key.toLowerCase().includes('money') ) value = formatMoney(value) 39 | if (typeof value == 'number') value = formatNumberShort(value, 6, 3) 40 | 41 | if ( args.p || args.pretty ) { 42 | ns.tprint(`\n\r${JSON.stringify(value, null, 2)}`) 43 | } else { 44 | ns.tprint(value) 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/usr/lsSet.js: -------------------------------------------------------------------------------- 1 | import { lsKeys } from 'utils/constants.js' 2 | import { getLSItem, setLSItem } from 'utils/helpers.js' 3 | 4 | export function autocomplete(data) { 5 | return Object.keys(lsKeys).concat(data.servers) 6 | } 7 | 8 | /** 9 | * @param {NS} ns 10 | **/ 11 | export async function main(ns) { 12 | let args = ns.flags([ 13 | ['pretty', false], 14 | ['p', false], 15 | ]) 16 | 17 | if ( args._.length !== 2 ) { 18 | ns.tprint(`This script needs a recognized key and value!`) 19 | ns.tprint('like: `run usr/lsSet.js reserve 100`') 20 | return 21 | } 22 | 23 | let key = ns.args[0] 24 | let safeKey = lsKeys[key.toUpperCase()] 25 | if ( !safeKey ) { 26 | ns.tprint(`That is not a recognized key. Use one of: `) 27 | ns.tprint(Object.keys(lsKeys).join(", ")) 28 | return 29 | } 30 | 31 | setLSItem(key, ns.args[1]) 32 | ns.tprint(`Set '${safeKey}' to ${getLSItem(key)}`) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | 2 | // how much GB ram should be set aside on the home server for 3 | // running the controller etc 4 | export const reservedRam = 30 5 | 6 | export const factionServers = { 7 | "CSEC" : "CyberSec", 8 | "avmnite-02h" : "NiteSec", 9 | "I.I.I.I" : "The Black Hand", 10 | "run4theh111z" : "BitRunners", 11 | "." : "The Dark Army", 12 | "The-Cave" : "Daedalus" 13 | } 14 | 15 | export const orgServers = { 16 | "fulcrumassets" : "Fulcrum Secret Technologies", 17 | "rothman-uni" : "Rothman University", 18 | "summit-uni" : "Summit University", 19 | "powerhouse-fitness" : "Powerhouse Gym", 20 | "iron-gym" : "Iron Gym", 21 | "millenium-fitness" : "Millenium Fitness Gym", 22 | "crush-fitness" : "Crush Fitness Gym", 23 | "snap-fitness" : "Snap Fitness Gym", 24 | "aevum-police" : "Aevum Police Headquarters", 25 | } 26 | 27 | export const specialServers = {...factionServers, ...orgServers} 28 | 29 | export const rootFiles = [ 30 | { name: "BruteSSH.exe", cost: 500000, }, 31 | { name: "FTPCrack.exe", cost: 1500000, }, 32 | { name: "relaySMTP.exe", cost: 5000000, }, 33 | { name: "HTTPWorm.exe", cost: 30000000, }, 34 | { name: "SQLInject.exe", cost: 250000000, }, 35 | ] 36 | 37 | export const purchaseables = rootFiles.concat([ 38 | // { name: "Formulas.exe", cost: 5000000000, } 39 | ]) 40 | 41 | 42 | export const gangEquipment = { 43 | weapons : ["Baseball Bat","Katana","Glock 18C","P90C","Steyr AUG","AK-47","M15A10 Assault Rifle","AWM Sniper Rifle"], 44 | armor : ["Bulletproof Vest","Full Body Armor","Liquid Body Armor","Graphene Plating Armor"], 45 | vehicles : [ "Ford Flex V20","ATX1070 Superbike","Mercedes-Benz S9001","White Ferrari"], 46 | rootkits : ["NUKE Rootkit","Soulstealer Rootkit","Demon Rootkit","Hmap Node","Jack the Ripper"], 47 | hackAugs : ["BitWire","Neuralstimulator","DataJack"], 48 | combatAugs : ["Bionic Arms","Bionic Legs","Bionic Spine","BrachiBlades","Nanofiber Weave","Synthetic Heart","Synfibril Muscle","Graphene Bone Lacings"], 49 | } 50 | 51 | export const lsKeys = { 52 | NMAP : 'jh_network_map', 53 | PLAYER : 'jh_player', 54 | BATCHES : 'jh_batches', 55 | BATCHJOBID : 'jh_batchJobId', 56 | RESERVE : 'jh_reserve', 57 | RESET : 'jh_reset', 58 | BITNODE : 'jh_bn_multipliers', 59 | SOURCEFILES : 'jh_owned_sourcefiles', 60 | WORKING : 'jh_working', 61 | DECOMMISSIONED : 'jh_decommissioned', 62 | HACKPERCENT : 'jh_hack_percent', 63 | CLASHTIME : 'jh_next_territory_warefare', 64 | GANGMETA : 'jh_gang_information', 65 | SLEEVEMETA : 'jh_sleeve_information', 66 | } 67 | 68 | export const myFavTheme = { 69 | "primarylight": "#AED1B5", 70 | "primary": "#80AA89", 71 | "primarydark": "#6C7E70", 72 | "successlight": "#68D680", 73 | "success": "#47AC5D", 74 | "successdark": "#3A7145", 75 | "errorlight": "#EF5757", 76 | "error": "#CC3D3D", 77 | "errordark": "#AA4B4B", 78 | "secondarylight": "#AFAFAF", 79 | "secondary": "#7C817E", 80 | "secondarydark": "#5A5A5A", 81 | "warninglight": "#DFDF5D", 82 | "warning": "#C3C346", 83 | "warningdark": "#9A9A42", 84 | "infolight": "#69f", 85 | "info": "#36c", 86 | "infodark": "#039", 87 | "welllight": "#444", 88 | "well": "#222", 89 | "white": "#fff", 90 | "black": "#000", 91 | "hp": "#dd3434", 92 | "money": "#FFE347", 93 | "hack": "#adff2f", 94 | "combat": "#faffdf", 95 | "cha": "#a671d1", 96 | "int": "#6495ed", 97 | "rep": "#faffdf", 98 | "disabled": "#605C5C", 99 | "backgroundprimary": "#121217", 100 | "backgroundsecondary": "#060607", 101 | "button": "#333", 102 | "maplocation": "#ffffff", 103 | "bnlvl0": "#ffff00", 104 | "bnlvl1": "#ff0000", 105 | "bnlvl2": "#48d1cc", 106 | "bnlvl3": "#0000ff" 107 | } 108 | -------------------------------------------------------------------------------- /src/utils/network.js: -------------------------------------------------------------------------------- 1 | import { getLSItem, disableLogs } from 'utils/helpers.js' 2 | 3 | /** 4 | * @param {NS} ns 5 | **/ 6 | export async function main(ns) { 7 | ns.tprint("ERROR: network.js not meant to be run independently. \nUsage: " + 8 | "import { networkMap } from 'utils/network.js'\n" + 9 | "\tlet map = networkMap(ns)\n" + 10 | "// OR \n" + 11 | "\tlet map = networkMapFree(ns)\n" 12 | ) 13 | } 14 | 15 | /** 16 | * @param {NS} ns 17 | * @cost 2.4 GB 18 | **/ 19 | export function networkMap(ns) { 20 | disableLogs(ns, ['scan']) 21 | let map = walkServers(ns) 22 | return map 23 | } 24 | 25 | /** 26 | * @param {NS} ns 27 | * @param {string} serverName 28 | * @cost 2.4 GB 29 | **/ 30 | export function fetchServer(ns, serverName) { 31 | let map = networkMap(ns) 32 | return map[serverName] 33 | } 34 | 35 | /** 36 | * @param {string} serverName 37 | * @cost 0 GB 38 | **/ 39 | export function fetchServerFree(serverName) { 40 | let map = networkMapFree() 41 | 42 | if (!map) { 43 | return false 44 | } 45 | 46 | return map[serverName] 47 | } 48 | 49 | /** 50 | * @param {NS} ns 51 | * @cost 0 GB 52 | **/ 53 | export function networkMapFree() { 54 | let map = getLSItem('nmap') 55 | 56 | if ( !map ) { 57 | return false 58 | } 59 | 60 | return map; 61 | } 62 | 63 | /** 64 | * @param {NS} ns 65 | * @param {string} goal 66 | **/ 67 | export function findPath(goal) { 68 | let nMap = networkMapFree() 69 | 70 | let path = [] 71 | // @ignore-infinite 72 | while (true) { 73 | path.unshift(goal) 74 | goal = nMap[goal].parent 75 | if (goal == '') { 76 | return path 77 | } 78 | } 79 | } 80 | 81 | 82 | /** 83 | * @param {NS} ns 84 | **/ 85 | function walkServers(ns) { 86 | let serverData = {}; 87 | let serverList = ['home']; 88 | serverData['home'] = updateData(ns, 'home', ''); 89 | for (var i = 0; i < serverList.length; i++) { 90 | ns.scan(serverList[i]).forEach(function (host) { 91 | if (!serverList.includes(host)) { 92 | serverData[host] = updateData(ns, host, serverList[i]); 93 | serverList.push(host); 94 | } 95 | }); 96 | } 97 | return serverData; 98 | } 99 | 100 | export function updateData(ns, server_name, parent) { 101 | let server 102 | try { 103 | server = ns.getServer(server_name) 104 | server.name = server_name 105 | server.portsRequired = server.numOpenPortsRequired 106 | server.hackingLvl = server.requiredHackingSkill 107 | server.maxMoney = server.moneyMax 108 | server.minSecurity = server.minDifficulty 109 | server.growth = server.serverGrowth 110 | server.parent = parent 111 | server.files = ns.ls(server.name) 112 | server.security = server.hackDifficulty 113 | server.availableRam = server.maxRam - server.ramUsed 114 | } catch(e) { ns.print(e.message) } 115 | return server 116 | } 117 | 118 | 119 | --------------------------------------------------------------------------------