Bitburner Scripting Tutorial + Basic Operation

Bitburner Scripting Tutorial + Basic Operation 1 - steamsplay.com
Bitburner Scripting Tutorial + Basic Operation 1 - steamsplay.com

Do you want to learn to write scripts yourself, but don’t know where to start? I’ll (try to) help you on your way!
 
 
By the end of this guide you should be able to:
 

  • Write a simple hacking script
  • Write a deployer script
  • Write a script to buy (and upgrade) servers
  • Write a startup script
  • Read from and write to files
  • Think of ways to change/improve scripts by yourself

 
 

Getting started

So you probably just started playing this game (and hopefully did the tutorial) and now you can start writing some actual code, but before we get to writing any code, let’s set up a couple of things.
 
 
I’m assuming you have the ‘early-hack-template.script’ from the tutorial, but if you don’t you can find both the NS1 and NS2 versions of this script here in the documentation – [readthedocs.io] . This script is far from optimal, but it will help you out a lot the first couple of days.
 
 
Run the script from your home server to hack n00dles. You can do this by using the following command from the terminal:
 
 

run early-hack-template.script n00dles -t 3

 
 
There might some things there that you don’t understand, but that’s okay, we’ll get around to it in due time.
 
 
Now you have to make a choice (not forever, just for now) between NS1 and NS2. NS1 is the simplified scripting the game offers, you can do pretty much everything with it that you can do with NS2, but the cost of simplifying the scripting is that the code runs slower (in real life, so in game there is no difference). NS2 is pretty much just javascript with an additional layer on top. I’d advise going with NS2 as it isn’t that daunting to write some basic javascript (and by the time you’ll have gotten used to writing scripts you can quite naturally start using more complex javascript (because you won’t have to rewrite all of your existing scripts)).
 
 

A small side-note on NS2 being faster in real life

 
You are going to run into some bottlenecks while playing this game, and one of them is real life resources; all the code you write is actually being run by the game, which means that the more efficient your scripts are the lower the chances that your game starts freezing up/framedropping and the happier your CPU is; something to keep in mind.
 
 
 

Before writing any code

Before you start writing any code, you may want to consider diving into the options of both the script editor and the game. Lets start with the game options, because they are easily found.
 
 
The last button in the sidebar leads to the options menu, there there are quite a few options, but just click on the ‘Theme editor’ button and try some of the presets (or go crazy and just try some color combinations yourself, just realize that you are editing that preset, so if you want the preset back you’re going to put in some effort (probably not a lot, but still)).
 
 
Got something to your liking? Good! Lets go to the editor options, to open these you first need to open a file in the script editor, so go ahead and open a file. You can do so with:
 
 

nano deployer.js

 
 
Yes, that’s a small preview for what’s to come. Now that you have a file open, you can see an options button in the lower right of the screen, click it. Use the dropdown menu to try some color schemes and adjust the other things to your liking (I only really care about the color scheme).
 
 
You may be wondering why this was the first step to writing code, the answer to that is: you’ll be looking at your screen a lot, better make it comfortable on your eyes.
 
 
 

Basic operations

If you know about basic programming operations like add (+), substract (-), multiply(*), and so on, you can skip this section and head straight into the next one.
 
 
If however this is new to you, or you don’t feel completely comfortable with them yet, stick around for a bit. There are five operators I would like you to know of, and one more that could be usefull in the future, these are: ‘=’, ‘+’, ‘-‘, ‘*’, ‘/’, ‘%’.
 
 
The assignment operator ‘=’ assigns a value to a variable.
 
 

a = 5;

 
 
The code above sets the variable ‘a’ to the value 5. You can assign pretty much any value to a variable, it is not limited to numbers, you could set it to a string, an array, or even an object (if you don’t know any of these that’s okay, just know that assigning values is not limited to numbers).
 
 
The addition operator ‘+’ adds two values together.
 
 

a = 5 + 2;

 
 
The code above sets the variable ‘a’ to the value (5 + 2), which is 7, so if you access ‘a’ later on, it will give you the value 7. Addition is also not limited to numbers, but when using it for anything other than numbers you need to know what you’re doing: you can concatenate a string onto another string with the ‘+’ operator for example. One more thing to know is that the ‘+’ operator can be combined with the ‘=’ operator.
 
 

a = 5;
a += 2;

 
 
The code above first assigns the value 5 to ‘a’ with the ‘=’ operator and then adds 2 to ‘a’ with the ‘+=’ combined operator. The end result is that the value of ‘a’ is 7.
 
 
The substraction operator ‘-‘ substracts a value from another value.
 
 

a = 5 - 2;

 
 
The code above sets the value of ‘a’ to (5 – 2), which is 3. The ‘-‘ operator only works for numbers. The ‘-‘ operator can be combined with the ‘=’ operator in the same way the ‘+’ operator can be. The code below results in ‘a’ having the value 3.
 
 

a = 5;
a -= 2;

 
 
The multiplication operator ‘*’ multiplies two values.
 
 

a = 2 * 3;

 
 
The code above sets the value of ‘a’ to (2 * 3), which is 6. The ‘*’ operator can only be used for numbers. The ‘*’ operator can be combined with the ‘=’ operator as well. The code below results in ‘a’ having the value 6.
 
 

a = 2;
a *= 3;

 
 
The divisor operator ‘/’ divides one value by another.
 
 

a = 6 / 3;

 
 
The code above sets the value of ‘a’ to (6 / 3), which is 2. The ‘/’ operator can only be used with numbers. The ‘/’ operator can be combined with the ‘=’ operator as well. The code below results in ‘a’ having the value 2.
 
 

a = 6;
a /= 3;

 
 
The modulo operator ‘%’ gives the remainder when dividing one value by another.
 
 

a = 5 % 2;

 
 
The code above sets the value of ‘a’ to (5 % 2), which is 1. The ‘%’ operator can only be used with numbers.
 
 
 

Conditional statements

If you know about conditional statements you can head straight into the next section. If you don’t, stick around for a bit.
 
 
A conditional statement is an expression that evaluates to a boolean value (a lot of fancy words to say: a statement that is either true of false). There are couple of operators you’ll frequently see when writing conditional statements, these are: ‘==’, ‘===’, ‘>’, ‘<‘, ‘<=’, ‘>=’, ‘!’, ‘!=’, ‘!==’ and ‘&&’, ‘||’.
 
 
Lets start with the reason I split up that list with the word ‘and’ towards the end. The reason for this is that ‘&&’ and ‘||’ can link multiple smaller conditional statements together to get a single bigger conditional statement and that’s why they are different from the other conditional operators.
 
 
The ‘&&’ operator is the logical and operator (a conjunction), while the ‘||’ operator is the logical or operator (a disjunction). If you don’t know what logical operators are, you can easily find some good information about them online (you could check this wikipedia link – [wikipedia.org]  for example).
 
 
The ‘==’ and ‘===’ operators compare two values, the difference is that ‘===’ is strict. Being strict means that the two values need to be exactly the same, this is easiest shown in an example:
 
 

5 == 5 // This evaluates to true
5 === 5 // This evaluates to true
5 == "5" // This evaluates to true
5 === "5" // This evaluates to false!

 
 
I will always be using the strict comparison operator ‘===’ over the non-strict ‘==’ operator in this guide.
 
 
The rest I will go through a bit faster, because they are quite straightforward.
 
 
The ‘<‘ operator is the ‘less than’ operator.
 
The ‘>’ operator is the ‘greater than’ operator.
 
The ‘<=’ operator is the ‘less than or equals’ operator.
 
The ‘>=’ operator is the ‘greater than or equals’ operator.
 
The ‘!’ operator is the ‘not’ operator, this flips the value from true to false and
 
the other way around.
 
The ‘!=’ operator is the ‘not equals’ operator.
 
The ‘!==’ operator is the strict ‘not equals’ operator.
 
 
Some examples would be:
 
 

5 < 6 // true
5 > 6 // false
6 <= 5 // false
5 >= 5 // true
5 != 4 // true
5 != 5 // false
5 !== "5" // true
!true // false
!false // true

 
 
In case you were wondering about those double slashes ‘//’, they denote that everything behind them on that line is a comment (so the things prefaced with a ‘//’ don’t do anything code-wise).
 
 
 

Deployer script

So now that you have a script running to hack n00dles, you can open up the script editor to start writing a script of your own. The thing you’ll need/want most at this point is a script that copies the ‘early-hack-template’ script to other servers and runs it there to hack n00dles. You can name your script whatever you want, but I named it ‘deployer.js’, because that’s what it does, deploy things.
 
 
You can open a file (or make a new one) by writing the following in the terminal:
 
 

nano deployer.js

 
 
If you chose to use NS1, you can’t use the ‘.js’ file extension and instead need to use the ‘.script’ file extension.
 
 
You should now be in the script editor and seeing the following code stub:
 
 

/** @param {NS} ns **/
export async function main(ns) {
}

 
 
To explain a few things:
 

  • The comment with the @param remark enables you to autocomplete functions in the netscript namespace (these are the in-game functions such as finding out how much money a server has available or running other scripts).
  • An NS2 script needs to export an asyncronous main function, so every script you’ll be running will need the line ‘export async function main(ns)’.
  • To access the functions in the netscript namespace a function needs to be passed the ‘ns’ argument.

 
All of the above is done for you behind the scenes if you use NS1 (and when using NS1 you can access the functions in the netscript namespace without the use of the ‘ns.’ reference).
 
 

Finding servers

 
The first thing you’ll want is a list of servers to deploy the ‘early-hack-template’ script to. You can get all servers neighboring your home server by using:
 
 

let servers = ns.scan("home");

 
 
In this line you first declare your new variable ‘servers’ with the keyword ‘let’ (you can also use ‘var’ and in some cases ‘const’, you can find more about the differences here – [mozilla.org] , but I’ll be using ‘let’ for this guide). After declaring the ‘servers’ variable it gets set to the value returned by ‘ns.scan(“home”)’, which is an array of string values (the names of the neighboring servers). I’ll tell you more about arrays later, for now just know that it is an iterable collection of items.
 
 
You can check what the array returned by ‘ns.scan(“home”)’ looks like by printing it to the terminal by adding the line below after the line of code above:
 
 

ns.tprint(servers);

 
 
It should print something like this to your terminal when you run the script (remember you can run a script by writing ‘run scriptname.js’ in the terminal):
 
 

["n00dles","foodnstuff","sigma-cosmetics","joesguns","hong-fang-tea","harakiri-sushi","iron-gym"]

 
 

Copying files to other servers

 
Now that you know how to get a list of servers and what form that data is presented in, you can do something with that list. Lets start by copying the ‘early-hack-template’ script to these servers.
 
 
To do that you’ll need ‘ns.scp()’, this function takes three arguments: the first is either the name of the file or an array with file names, the stuff to copy; the second is optional and is the server to copy the file(s) from, if you give the scp function only two arguments, it will set this one to “home” for you by default; the third is the name of the server to copy the file(s) to. The scp function needs an ‘await’, I’ll tell you more about this later. This leads to the following line of code in this case:
 
 

await ns.scp("early-hack-template.script", serverName);

 
 
If you were proactive in an earlier part of this guide and already copied the NS2 version of the ‘early-hack-template’ to a ‘.js’ file, you can instead run the following line:
 
 

await ns.scp("early-hack-template.js", serverName);

 
 

Iterating over the servers list

 
But wait, there is no variable named ‘serverName’, you only have a list of server names, but the scp function does not take multiple servers to copy to at once, now what? Now you need to iterate over the list of server names, you can do this in different ways, but you’ll be using a ‘for..of’ loop for this one (other ways of iterating over lists include (but are not limited to): ‘while’, ‘for’, ‘for..in’). This loop will look something like this:
 
 

for (let serverName of servers) {
}

 
 
In the code above you declare a new variable ‘serverName’, this variable will be set to every value in the ‘servers’ array exactly once. The code that you place in between the curly braces (these things: {}) will execute exactly once per value of the ‘servers’ array, so if the ‘servers’ array has the values [“n00dles”, “joesguns”] the code in between the curly braces will execute two times, the first time the value of the variable ‘serverName’ will be “n00dles” and the second time the value will be “joesguns”. This means that if you put the call to the scp function in between the curly braces the ‘early-hack-template’ script will get copied to every server in the ‘servers’ array.
 
 

Running scripts remote

 
Now that the script can copy the ‘early-hack-template’ script to all neighboring servers, you’ll want to run that script on those servers. You can run a script on another server from your script by using the ‘ns.exec()’ function, this function has dynamic parameters, which means you can pass it as many arguments as you’d like.
 
 
There are still some rules to the arguments though: the first argument is the name of the script to run; the second argument is the name of the server to run it on; the third and optional argument is the amount of threads to run the script with, this defaults to zero; from the fourth argument and onwards are the free arguments, these arguments are passed into the script you’re trying to run remote, and these arguments can only be single values (string, boolean, number, anything not an array or an object, more on this later).
 
 
This means you can add the following line of code inside the loop you just wrote:
 
 

ns.exec("early-hack-template.script", serverName, 1, "n00dles");

 
 
This will run the script ‘early-hack-template’ on every server with one thread and give it the argument “n00dles”, which it will then use to start hacking n00dles.
 
 
Lets give the script a test run. Go to the terminal and type ‘killall’ and hit enter, this kills all scripts currently running on your current server (which should be your home server unless you connected to some other server, in which case you should first type ‘home’ in the terminal and hit enter, this returns you to your home server). This is nessecary because at the start of this guide I made you run the ‘early-hack-template’ script with all available ram. Now you can run the ‘deployer’ script:
 
 

run deployer.js

 
 
 

Getting root access

Before you can run a script on a server, you’ll need to have root access on that server. You’ll have done this with the terminal, if you’ve gone through the tutorial, by using:
 
 

run NUKE.exe

 
 
You can however also do this with your script, the related functions for this are:
 
 

ns.nuke();
ns.brutessh();
ns.ftpcrack();
ns.relaysmtp();
ns.httpworm();
ns.sqlinject();

 
 
You’ll need the corresponding programs for all of them. The ‘nuke’ program gives you root access, if enough ports have been opened using the other programs. But how do you know whether you have the corresponding program or not, and how do you know whether you have enough programs to open the required number of ports? There are a couple of functions that are going to help with this:
 
 

// Checks if a file exists, takes one argument: the filename to check
ns.fileExists(file);

// Gives the amount of ports that need to be opened to gain root access on a server
// Takes one argument: the name of the server to check
ns.getServerNumPortsRequired(serverName);

 
 
You can add the following code to the ‘deployer’ script inside the for-loop:
 
 

let openPorts = 0;
if (ns.fileExists("BruteSSH.exe")) {
 ns.brutessh(server);
 openPorts++;
}
if (ns.fileExists("FTPCrack.exe")) {
 ns.ftpcrack(server);
 openPorts++;
}
if (ns.fileExists("RelaySMTP.exe")) {
 ns.relaysmtp(server);
 openPorts++;
}
if (ns.fileExists("HTTPWorm.exe")) {
 ns.httpworm(server);
 openPorts++;
}
if (ns.fileExists("SQLInject.exe")) {
 ns.sqlinject(server);
 openPorts++;
}
if (ns.getServerNumPortsRequired(server) <= openPorts) {
 ns.nuke(server);
}

 
 
Once you have gotten root access on al servers you could, you still need to do one more thing: you’ll have to check whether you have root access on a server before attempting to run a script on it. You can check this with:
 
 

// Checks if you have root access on a server
// takes one argument: the name of the server
ns.hasRootAccess(serverName)

 
 
 

Threading scripts

If you go to one of the servers to check how much ram there is available you’ll notice that you’re not taking full advantage of these servers (you can see this by either going to the terminal and typing ‘connect joesguns’ and then ‘free’ or going to the ‘active scripts’ tab and checking the amount of bars (|) and dashes (-), where the bars indicate ram being used (this is a relative representation, so an x out of y amount).
 
 
But how do you make full use of the ram available then? The game offers a solution: threading. Threading is an in-game concept and has nothing to do with actual threading of code (you can google that if you’re interested though), but instead scales the result of actions taken by a script by the amount of threads it is running with, so if a script would hack $1k from a server with one thread, it would hack $3k with three threads.
 
 

Calculating threads

 
But how do you find out how many threads a script can run with on a server? You’ll need to know two things:
 

  1. The amount of ram a server has available
  2. The amount of ram a script needs to run (with one thread)

 
You can get the amount of ram available with:
 
 

let ramAvailable = ns.getServerMaxRam(serverName) - ns.getServerUsedRam(serverName);

 
 
The netscript function ‘getServerMaxRam()’ returns the amount of ram a server has and ‘getServerUsedRam()’ returns the amount of ram currently in use on a server. They both take in the name of the server as an argument.
 
 
You can get the amount of ram the script needs with:
 
 

let ramPerThread = ns.getScriptRam("early-hack-template.script");

 
 
The netscript function ‘getScriptRam()’ returns the amount of ram a script needs to run and it takes the name of the script as an argument.
 
 
Now all you need to do is divide the ram available by the ram needed per thread, and round the result down to the closest integer, like so:
 
 

let threads = Math.floor(ramAvailable / ramPerThread);

 
 
The function ‘Math.floor()’ is a function from the built in javascript Math library (which holds many such functions, like ceil(), round(), min(), max(), etc.), and this function rounds a decimal number down to the closest integer. You want to do this because you can only use real numbers for the amount of threads (i.e.: 1, 2, 3, … , 1037, etc.) and rounding up means you try to use more ram than available.
 
 
You can now apply this new knowledge to the deployer script to make full use of the ram available on servers. (Hint: the function call I gave you for running scripts remotely had a ‘1’ where the amount of threads was set)
 
 
 

Setting an alias

By now you might have gotten tired of typing ‘run deployer.js’ in the terminal, if not then great, if you have I might have something to make your life a little bit easier: aliases.
 
 
You can define an alias in the terminal with the ‘alias’ command, these commands only work in the terminal, so they won’t do you any good in your scripts. Lets add an alias for running the ‘deployer’ script, type in the following in your terminal and hit enter afterwards:
 
 

alias deploy="run deployer.js"

 
 
You can now type in ‘deploy’ in the terminal to run the deployer script. This might not feel like it makes your life easier, but if you try to run scripts or chain different commands quite often, needing to type in only one command in the terminal will save you some time. Right now it’s only goind to be the ‘deploy’ command but as you keep expanding your codebase and unlock more functions in the game, you’ll be making more aliases.
 
 
 

Buying servers

Apart from the servers that you can hack, there are other servers you can use to write scripts from: purchased servers.
 
 
You can buy a new server from a shop in the city manually, but you can also write a script to buy servers for you. Which is what you’ll be doing now. I’m going to speed up a bit compared to writing the ‘deployer’ script, by going over larger chunks of code.
 
 
You, of course, need to open the script in the script editor by typing the following into the terminal:
 
 

nano purchase-servers.js

 
 
Again you can name your script anything you want, just make sure you know what a script does when you read its name. I named the script ‘purchase-servers.js’, because it purchases servers (so simple, I know).
 
 

Functions you’ll be using

 
 

// Takes no arguments and returns a list of all servers purchased this run.
ns.getPurchasedServers();

// Takes no argument and returns the amount of purchased servers you can own.
ns.getPurchasedServerLimit();

// Takes one argument: the amount of ram the server should have; 
// returns the cost of purchasing a server with the given amount of ram.
// Ram should be an exponential of 2, so 2, 4, 8, 16, 32, and so on.
ns.getPurchasedServerCost(ram); // Takes one argument: the server to check the money of;
// returns the amount of money available on the server.
// Using this on your home server gives you the money you own
ns.getServerMoneyAvailable("home"); // Takes two arguments: the name to give the server to purchase;
// the amount of ram the server to purchase should have;
// returns the name of the server purchased,
// it changes the name by itself if a server with the given name already exists
ns.purchaseServer(name, ram);

 
 

The flow of the script

 
This script is not just meant to buy new servers, but to buy all servers you can buy. So how do you go about doing that?
 
 
First lets start by thinking about the situations where you can run this script:
 

  1. You haven’t bought any servers yet
  2. You have bought some servers but can still buy more
  3. You have bought all servers you can buy

 
Why is it important to think about this? Well, what would happen if you simply try to buy 25 new servers when you already have 18 out of the 25 you can have: you would get 18 error messages about the fact that you can’t buy any more servers (you’ll probably get one error message and then your script stops running, but I’m ignoring this for the sake of argument).
 
 
Fortunately there is a function that gives you a list of all the servers you have purchased: ‘ns.getPurchasedServers();’.
 
 

Intermezzo: arrays

 
By now you may have noticed that I am using the terms ‘list’ and ‘array’ interchangeably, but the name of the data-structure in javascript is an array and a list is not actually a thing in javascript (it is in some other languages), so I’m using it in the sense of ‘making a todo list’ or ‘making a shopping list’.
 
 
That may have seemed like a rather strange intermezzo, but I’ll be talking about arrays for a bit now, so I felt that had to be clarified.
 
 
There are several things you can do with an array, one you’va already used: iteration. Arrays provide a lot more utility than just iteration though, if you want some in depth information I suggest you take a look at this link – [mozilla.org] . Other uses of an array you will be using in this script are the ‘length’ property and the ‘includes’ function, optionally you could use things like ‘push’ and ‘pop’ as well.
 
 
So lets start with the ‘length’ property, using this on an array will give you the amount of elements in an array. Next up is ‘includes’, this gives you a boolean value (true or false) depending on wheter a value is present in an array. With ‘push’ you can add a value to the end of an array and with ‘pop’ you can remove an element from the end of an array (while return the value).
 
 

The flow of the script — part 2

 
Now that you know some things you can do with an array, you can use the data that ‘ns.getPurchasedServers()’ gives you:
 
 

let servers = ns.getPurchasedServers();
for (let i = servers.length; i < ns.getPurchasedServerLimit(); i++) {
}

 
 
This is a different form of the for-loop I showed you earlier, the format of this for-loop is as follows:
 
the statement inside the round brackets is divided into three parts divided by two semicolons (;), these three parts are: intialization; condition; update.
 
 
The initialization sets the inital state of things for the for-loop, here that would be declaring the variable ‘i’ and setting it to ‘servers.length’. The condition is what determines if the loop continues or stops, this part needs to evaluate to a boolean, in this case the loop continues while ‘i’ is less than ‘ns.getPurchasedServerLimit()’. The update is what happens at the end of every iteration of the loop (usually to make sure the loop will end after a certain amount of iterations), in this case that would be incrementing ‘i’.
 
 
By starting the loop at ‘servers.length’ you prevent the script from trying to purchase more servers than available. Now lets add some meat to that loop:
 
 

let name = "pserv-" + i;
if (ns.getPurchasedServerCost(ram) < ns.getServerMoneyAvailable("home")) {
 ns.purchaseServer(name, ram);
}

 
 
First the name gets set to ‘pserv-4’ for example, some side info that is important here is that javascript is (pretty much) typeless, so if you try to add a number to a string, the number is treated as a string and it is concatenated onto the other string.
 
 
After getting a (hopefully) unique name, you check if you have enough money with the if-statement (read more about if-statements – [mozilla.org] ). If you have enough money, you buy a new server.
 
 
Now all you need to do is make sure the script keeps running until all servers were bought, because right now servers get skipped if you don’t have enough money. So lets change that if-statement to something else:
 
 

while (ns.getPurchasedServerCost(ram) > ns.getServerMoneyAvailable("home")) {
 await ns.sleep(3000);
}
ns.purchaseServer(name, ram);

 
 
Now you may wonder what this ‘ns.sleep(3000)’ is all about, not to mention the ‘await’. Lets start with ‘ns.sleep()’, this function just waits for a given amount of time (3000 ms in this case, that’s 3 seconds if you were wondering).
 
 

Intermezzo: hunters, dogs and ducks

 
Now on to the ‘await’, but first: javascript is asyncronous, which pretty much comes down to ‘it’ll whatever it wants, whenever it wants’; so if you do a function call to an ‘async’ (that keyword the main function needs) function, javascript sends that function call somewhere to be executed, but just continues executing code that comes after the function call. Lets compare it to a hunter that has some dogs, if the hunter shoots a duck, he sends a dog to go fetch it, so that he can shoot the next duck, which he can keep doing while he has dogs. Javascript does the same, exept with function calls and schedulers instead of the shooting, dogs and ducks.
 
 
Anyway, the ‘await’ makes the script wait until the virtual hunting dog returns with the virtual duck.
 
 

The flow of the script — part 3

 
The if-statement got changed to a while loop (read more about while loops – [mozilla.org] ), and intead of the less than sign (<), you’re now using a greater than sign (>) and the purchasing of the new server was moved to after the while loop. So now the script will wait three seconds for every iteration of the while loop, which will keep looping while there is not enough money to buy that new server.
 
 
 

In between scripting

This game is not just about writing script, there is also a game to play (Shocking, I know). So a few things to check out and/or take care of before you go on with writing/improving scripts.
 
 

Buying new servers and upgrading your home server

 
Before you go and fire up that new ‘purchase-servers’ script, you might want to go to the city (you can do so via the sidebar-menu) and check out the shop (assuming you’re still in sector-12, that would be the T symbol with ‘[alpha ent.]’ next to it).
 
 
In the shop you can upgrade your home server, you’ll want to get a few upgrades for your home server before you let your new script splurge on new servers, because you lose your purchased servers when you install augments but you keep upgrades for your home server. Just upgrade your home server to 16GB or 32GB, that’s fine for now.
 
 
You can now fire up that ‘purchase-servers’ script with:
 
 

run purchase-servers.js

 
 

Factions

 
By now you may have gotten a message from ‘Jump3r’ about your first faction: ‘CSEC’. Once your hacking level gets up to about 60 you can go look for it. If you want to do this on your own, go and get them, if not then this is how to do it, in the terminal enter:
 
 

scan-analyze 2

 
 
Try to find a server named ‘CSEC’ in the list and connect to the server before it (with less indentation) and from there connect to ‘CSEC’. Once there, type the following into the terminal:
 
 

backdoor

 
 
You’ll now get to see a progress bar and once it is done, you can go back to your home server (you can just type ‘home’ into the terminal and hit enter).
 
 
Before long you’ll get a faction invite, click join and go to the faction through the menu and start working on hacking contracts to earn reputation with the faction (you’ll need lots of reputation, so it’s best to get started on hacking contracts when you can).
 
 

Installing augments

 
After you’ve gotten enough reputation with a faction to buy some augments (and have the money to do so), buy some augments. Remember to use all of your money on either augments or home server upgrades before installing your augments and buy the more expensive augments first because of the cost multiplier.
 
 
It is important to realize that any progress that you made gets reset, with the exception of the augments you purchased and the upgrades you bought for your home server. This does not mean you should not install augments, it just means that you should pay attention to where you invest your money.
 
 
Another thing to take note of is: you can stop scripts from the ‘active scripts’ menu, accessible from the sidebar. That means you can stop your ‘purchase-servers’ script if you feel your income is at a level where you earn enough money to buy some augments (new servers can be quite expensive).
 
 
Now you can install your augments.
 
 
 

Startup script

Now that you are aware of the progress reset, you’ll want to start working on a ‘startup’ script. So that you only need to run one script, after installing augments, to get your hacking setup back up, because, lets be honest, you just want to write more scripts and manually running scripts takes away from the time you could be writing them.
 
 
This one is going to be a quicky, because you only have two scripts so, ‘deployer.js’ and ‘purchase-servers.js’. The only thing this script has to do is start those two scripts.
 
 
You’ve already used ‘ns.exec()’ to start another script, but for scripts that run on the same server there is a simpler option:
 
 

// Only the first argument is required, the name of the script to run;
// the seconds argument is the number of threads to run the script with;
// from the third argument onwards are arguments to be passed to the script.
// Returns the pid appointed to the script (not really usefull right now)
ns.run(script, threads, ...args);

 
 
So you just need to use ‘ns.run()’ for both the ‘deployer’ script and the ‘purchase-servers’ script, both can be run with one thread. After saving the script you can add an alias, to make running it even simpler.
 
 
 

Version control

It may sound silly to be doing version control inside of a game, but I really would advise you to use it. There is no need to crazy on it, just add a version number to your scripts, you could easily take advantage of the folder structure in this game and call your script ‘/v2/deployer.js’ or something like that.
 
 
Now, you may wonder what the benefits of this are. The two biggest benefits are:
 

  • You can run older version scripts while you work on new ones, that way your game progress doesn’t stall.
  • You have a backup (the older version of your script) in case you botch a new version of your script and can easily fall back on that older version (both for running scripts and as a codebase for newer versions of a script).

 
Other than that you also automatically sort your scripts into folders (assuming you use the ‘/v2/deployer.js’ approach), which will make using ‘ls’ from the terminal a nicer experience (‘ls’ lists the files on a server).
 
 
 

Improving the deployer script

By now you may have noticed that there are a lot more servers than just the servers neighboring your home server, so it’s time to improve your ‘deployer’ script. As mentioned in the version control part, you’ll be making a new file for the ‘deployer’ script, but because it will be improving on the existing code, you’ll copy the existing ‘deployer’ script, you can do this from the terminal like so:
 
 

cp deployer.js /v2/deployer.js

 
 
This copies the ‘deployer.js’ file and names the copy ‘/v2/deployer.js’. You can now open ‘/v2/deployer.js’ in the script editor with the ‘nano’ command through the terminal.
 
 

Scanning deeper

 
If you want to scan deeper you’ll have to use the function ‘ns.scan()’ on the servers neighboring your home server. The easiest way to do this would be to loop over the result of ‘ns.scan(“home”)’. Doing this would result in something like:
 
 

let servers = ns.scan("home");
for (let serverName of servers) {
 let newServers = ns.scan(serverName);
 for (let newServerName of newServers) {
 // Do the deploying of the 'early-hack-template' script to the 'newServerName' server
 }
}

 
 
But this will lead to a couple of issues:
 

  1. This way you will add a new for-loop every time you want to go a step deeper (and let the fact that you can upgrade scan-analyze to go 10 nodes deep be a hint that that’s going to be a lot of for-loops).
  2. With this approach you will try to deploy scripts to your home server (though that could be nice, it’s not exactly intended behaviour).
  3. It’s not exactly a nice read with the ‘servers’ and ‘newServers’ variable names, especially with the fact that when you go deeper you’ll end up with ‘newNewServers’, ‘newNewNewServers’, and so on.

 
So then what? There are several solutions to the problem, but I’ll talk about one that doesn’t involve anything I haven’t discussed in this guide. First you’ll need to think about what data you’ll need to accomplish what you want to with this script, which is deploying. All you want is a list of all servers, with no duplicate entries.
 
 
To get this done you’ll need an array to hold the result of all the servers you have found so far, and manage the servers you want to scan using a different array.
 
 

Intermezzo: stacks and queues

 
Although javascript does not have data-structures for stacks and queues, I’ll talk about them because I think they are an important concept. Stacks and queues are basically just lists, but different to an array you can only access one element in them. In the case of a stack, you can only access the last element in it, this is called a LIFO (Last In First Out) data-structure (or FILO: First In Last Out; potatoes, potahtoes). A queue on the other hand only allows you to access the first element in it, this is called a FIFO (First In First Out) data-structure.
 
 
The supporting data-structure you’ll be using is a queue. You can just use an array for this purpose, since you can add elements to the end of an array with ‘push()’ and remove (while getting the value of) the first element of the array with ‘shift()’. You could of course write your own queue class and use that instead and if you want to you should (I’ve included a full example of a stack class and an unfinished example of a queue class in the bonus section; why an unfinished queue class? because I’m a meanie and you should try making things yourself).
 
 

Scanning deeper — part 2

 
Let’s grab a code snippet to see what using a queue to help reduce the amount of loops looks like:
 
 

let servers = [];
let serversToScan = ns.scan("home");
while (serversToScan.length > 0) {
 let server = serversToScan.shift();
 if (!servers.includes(server)) {
 servers.push(server);
 serversToScan.concat(ns.scan(server));
 }
}

 
 
There is only one while loop left and this code snippet keeps going untill the maximum depth has been reached (it crawls the entire network). There are a couple of functions I haven’t shown you yet in code and those are ‘includes()’ (though I have explained this one in the arrays intermezzo), ‘shift()’ which removes an item from the front of an array while fetching its value and ‘concat()’ which is pretty much a mass ‘push()’, as it pushes all elements of an array onto the end of another array.
 
 
Take a good look at the code snippet and think about how it works, why it works and maybe even test it out a bit (you can print things to the terminal with ‘ns.tprint()’).
 
 

To modularize or not to modularize

 
You’ve reached a crossroad and need to make a choice about modularization (if that is even a word…).
 
 
The code snippet that uses a queue produces a clean list that you can iterate, which could be used in your ‘deployer’ script, by doing this you could move the scanning part to a different script which you could import into a plethora of other scripts that also want to know which servers there are. You could however also get a small efficiency gain by adding the deploying part into the while loop of the queue code snippet.
 
 
The choice is yours, there is no right or wrong, both have their advantages and disadvantages.
 
 
 

Improving the purchase servers script

Right now you have a script that tries to buy all available servers and then stops, but what if I told you you can replace those servers with better servers.
 
 
Lets start by making a new script and call it ‘/v2/purchase-servers.js’ (you can call it whatever you want, but I’m keeping the name in line with the advice I’ve given so far).
 
 

while(true)

 
To do this you are going to have to write your first ‘while(true)’ loop. For safety reasons, this loop will need an ‘await ns.sleep()’ call, because without it your game will crash. So the base of your new script will look like this:
 
 

/** @param {NS} ns **/
export async function main(ns) {
 while (true) {
 await ns.sleep(1000);
 }
}

 
 
Which does absolutely nothing apart from not stopping. So lets start by thinking about what needs to go inside the ‘while(true)’ loop and what needs to happen outside of it.
 
 

Outside the ‘while(true)’ loop

 
Lets start with the outside part: same as with the previous ‘purchase-servers’ script, you’ll have to anticipate the situation where you have already bought servers before, so lets make a variable ‘servers’ to hold the result of ‘ns.getPurchasedServers()’.
 
 
Something that is new for this script, compared to the previous ‘purchase-servers’ script, is that you will be buying servers with increasingly more ram. This means you should make a variable to hold the amount of ram you want for your servers and set it to an initial value (I would suggest an initial value of 8).
 
 

Inside the ‘while(true)’ loop

 
Then on to the inside part: you will want to do what you did in the previous ‘purchase-servers’ script, check if you have enough money and buy a server if you do; but in addition to that, you’ll have to check if the server you want to buy already exists and if it has less ram than what you are trying to buy, because it determines wether you buy a servers or skip buying it for this amount of ram.
 
 
Then how do you go about doing that? Lets start by rewriting the for-loop to start from ‘i = 0’, because you’ll want to be checking things for all servers, not just buying new ones.
 
 
Now lets think about where to check if you want to buy a server or skip it. It should be before buying a new server, but it can happen after checking wheter you have enough money to even buy a new server. So now that you know where to put the if-statement, you can start writing it. I’ll help you with a code snippet of my own this time, because there are a couple of new things to use:
 
 

if (servers.includes(name)) {
 if (ns.getServerMaxRam(name) < ram) {
 ns.killall(name);
 ns.deleteServer(name);
 } else {
 continue;
 }
}

 
 
This snippet first checks if the servers list includes the name of the server you want to buy and if it is not included it does not interfere with the rest of the script at all. If the name is included in the servers list however, it checks if the server has less ram than you want to buy and if it doesn’t, it forces the surrounding for-loop (which is not depicted in the snippet) into the next iteration without allowing it to finish the current iteration, with the ‘continue’ keyword.
 
 
If the ram of the server is less than what you want to buy, you’ll have to delete the server (because you can’t buy that server if it already exists). You can do this by first killing all running scripts on that server with the ‘ns.killall()’ function, which takes the name of the server as an argument, and once the scripts have been killed on that server, you can delete it with the ‘ns.deleteServer()’ function, which takes the name of the server as an argument. After deleting the server, the code after the snippet is executed and there you can buy a new server.
 
 
So then you just need to update the servers list and the ram variable after you finish the for-loop. You can fill the servers array the same way you did in the previous ‘purchase-servers’ script (or you can update the list within the for loop, with the ‘push’ and ‘pop’ functions) and you can multiply the ram value by two (valid amounts of ram are exponentials of 2 (2, 4, 8, 16, 32, and so on)).
 
 
If this is too much text and not enough code, you can take a look at the ‘script solutions’ section for reference (don’t just copy-paste from there, you won’t learn from that).
 
 
 

Centralized hack script

Yeah, I know, you’ve written quite a few scripts by now, but I’m still making you use the prefab ‘early-hack-template’ script. But not anymore! It’s time to start paying attention to the ram cost of your scripts, because the ‘early-hack-template’ isn’t the most efficient in that regard.
 
 
First off, you’ll need three minimum ram scripts. One for hacking, one for growing and one for weakening. So lets name these scripts: ‘/shared/hack.js’, ‘/shared/grow.js’ and ‘/shared/weaken.js’. I put the ‘/shared/’ part in front of the script names because any hacking script you’ll be writing from now on will probably be making use of these scripts, and putting them in a folder together keeps your home server organized.
 
 

/shared/hack.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
 await ns.hack(ns.args[0]);
}

 
 

/shared/grow.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
 await ns.grow(ns.args[0]);
}

 
 

/shared/weaken.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
 await ns.weaken(ns.args[0]);
}

 
 
These scripts only use 1.75GB ram (which is as small as it’s going to get) (technically the hack script only uses 1.7GB, but taking that into account is only going to make things harder). These scripts are one-shot scripts, they perform one action of ‘hack’, ‘grow’ or ‘weaken’ each.
 
 

Managing the timing and choice of action centralized

 
So lets take a closer look at the ‘early-hack-template’, because it’s doing what you would want your centralized hack script to do, only it’s doing it for only one server.
 
 

/** @param {NS} ns **/
export async function main(ns) {
let target = ns.args[0];
let moneyThresh = ns.getServerMaxMoney(target) * 0.75;
let securityThresh = ns.getServerMinSecurityLevel(target) + 5;
if (ns.fileExists("BruteSSH.exe", "home")) {
ns.brutessh(target);
}
ns.nuke(target);
while(true) {
if (ns.getServerSecurityLevel(target) > securityThresh) {
await ns.weaken(target);
} else if (ns.getServerMoneyAvailable(target) < moneyThresh) {
await ns.grow(target);
} else {
await ns.hack(target);
}
}
}

 
 
Lets start at the top of the main function: the variable ‘target’ is set to ‘ns.args[0]’, ‘ns.args’ is an array with the arguments that you used to run the script (that’s the reason I made you run this script with the argument “n00dles”) and ‘ns.args[0]’ is the first element of that array (because in javascript the first index of an array is 0). (also, yes, I did replace all of the ‘var’ keywords with ‘let’ for this example)
 
 
Next the script determines the amount of money the server should have before hacking it and the security level it can have at maximum before growing or hacking it. Then the script tries to get root access on the target server (because you can’t use ‘hack’, ‘grow’ or ‘weaken’ on it otherwise) by running the ‘BruteSSH.exe’ program if you have it (to open one port) and then running ‘Nuke.exe’ to get root access (this means this script can’t be used to target any server that needs more than one port opened).
 
 
Then comes the ‘while(true)’ loop, meaning the script will do this forever (or at least untill you tell it to stop or it crashes for some reason). Inside the loop it checks what action it should perform based on the actual security and money values and the threshold values.
 
 
What you need change/add in this script are the ‘await ns.weaken(target);’, ‘await ns.grow(target);’ and ‘await ns.hack(target);’ lines. Because instead of that, you’ll be wanting to run those one-shot scripts I showed you.
 
 

Making a new deployer script

 
So lets start by making a new script and name it ‘/v3/deployer.js’, because yes, this will be the third generation ‘deployer’ script.
 
 
First you can copy the contents of your ‘/v2/deployer.js’ script into this new script. Then you can add the contents of the ‘early-hack-template’ script into your new script, but after the deployer part (if you feel like you know what you’re doing by now, feel free to change the order of things around a bit, just make sure to allow the deployer part to make a full list of servers before going into the ‘while(true)’ loop of the ‘early-hack-template’ script).
 
 
Now I’ll give you an example of how to do the inside of the while-loop (the example only includes ‘weaken’, so you can add ‘grow’ and ‘hack’ yourself):
 
 

let sleepTime = 3000;
for (let server of servers) {
 let ramAvailable = ns.getServerMaxRam(server) 
 - ns.getServerUsedRam(server);
 let threads = Math.floor(ramAvailable / ramPerThread);
 if (ns.getServerSecurityLevel(target) > securityThresh) {
 sleepTime = ns.getWeakenTime(target)
 ns.exec("/shared/weaken.js", server, threads, target);
 }
}
await ns.sleep(sleepTime + 100);

 
 
The ‘+ 100’ inside the last ‘ns.sleep()’ call is there to give you some leeway to make sure all one-shot scripts have finished running, so that you don’t lose a round of hack/weaken/grow because there was no room available.
 
 
After you’re done with the inside of the while-loop, you only need to clean up some parts you copied from the other scripts that don’t make sense in this script (e.g.: ‘ns.exec(“early-hack-template.js”, server, threads, target);’) and change the files to copy to the servers (which should now be the ‘/shared/weaken.js’, ‘/shared/grow.js’ and ‘/shared/hack.js’ files instead of the ‘early-hack-template.js’ file.
 
 
And you’re done! I will put the code for this script in the ‘script solutions’ section as well (for reference, not copying).
 
 
 

Go and create

So you might have noticed a fair spike in difficulty towards the end of this guide and that is mostly because I’m trying to prepare you for writing code without anyone helping you (though it is no shame to still ask others for help if you’re stuck on something!). Because I believe you should now have the tools to start writing your own scripts, you know about operators, conditions, arrays, loops and a fair amount of netscript functions.
 
 
If you read the ‘resources’ section further on in this guide, you’ll get an extra push in where to look if you’re stuck on something, though I think you should have enough knowledge by now to know what to search for (which is solving half the problem already).
 
 
You can continue reading this guide to find out about reading from and writing to files (which will be usefull if you keep evolving your scripts) and I’ve included the code for the scripts I talked you through as well, those are in the ‘script solutions (cheat sheet)’ section.
 
 
One last thing I’d like to give you advice on is documentation. I know I didn’t add a lot, if any, comments in the example code I’ve given you, but it is very usefull to add comments to your own code, so that you can easily see which part of the code does what (comments are lines prefaced with ‘//’).
 
 
I wish you all the best and I hope this guide was helpfull in getting you started with writing scripts, now go and create more scripts of your own!
 
 
Let me know if you missed anything, found things to be unclear, found something wrong with my thinking, or if just want to leave a message, in the comments down below.
 
 
 

Debugging

Although debugging is not an exact sience and more something you just need to get a feeling for (in my opinion at least), I’ll include some basic patterns for debugging you scripts in this game.
 
 
There are going to be two scenarios in which you’ll want to debug your code:
 

  1. The script crashed with an error message.
  2. The script does run, but seems to not be doing what it should be doing.

 
Lets start with the scenario of the script crashing. If you get an error message shoved in your face, always read it! An error message tends to have usefull information on both what went wrong and where it went wrong, so if you get a message saying ‘did you forget to await a hack/grow/weaken?’ you should start looking for all netscript functions you used in your script and double check (a function that returns a ‘Promise’ needs to an ‘await’ in front of it) which functions need an ‘await’. Sometimes you’ll even get a stacktrace telling you on what line in which file something went wrong.
 
 
Okay, so you’ve read the error message but it was not clear enough on the issue, now what? Now you’ll have to manually find out what code caused the error and what code is ‘safe’, you can do so by either commenting out chunks of code untill the script starts working or by copying code over to a new script and keep doing so untill the new script stops working.
 
 
Now you’ll have a decent idea of which part of the code is causing the issue, read the error message again to see if it makes more sense with the smaller code sample to debug. If you still havent found it, it could be usefull to take the small chunk of code and put it in a new script and set the variables used in the code to a value it might (or should) have when this chunk of code is run and start adding in some ‘ns.tprintf()’ statements to check if variables have the same values you’d expect them to have at that point in the code.
 
 
Unfortunately the rest of the process is just trial and error.
 
 
For the case where the script does run, but is not doing what it should be doing, the approach is quite similar, except you don’t have any error messages to help you. First you start off identifying the issue and take a look at your code to see if perhaps any loops of if-statements might have any glaring issues.
 
 
If you didn’t find any issues that way, you’ll to start isolating your code again, but since you should have an idea of what is going wrong, you should have a decent idea of where it is going wrong, so start by isolating the parts you’re suspicious of and from here on it is the same as the situation where you did have an error message, but couldn’t make heads or tails of it.
 
 
I hope this gives you an idea of where to start when debugging. I can only tell you that the more you do it, the easier it is going to get (on average at least), because you’ll come across a bug more than once and, once you can recognize a bug, fixing it is that much easier.
 
 
 

Resources

Whenever I linked to anything javascript related, I linked to the MDN Web Docs – [mozilla.org] , but there are plenty other resources to check if you feel stuck. Just google (or use whatever search engine you’re using) for what you’re stuck on and go for links to either MDN, w3schools or stackoverflow, to name some well known sources.
 
 
If you get stuck on the netscript functions, which are a part of this game, you should go check out the documentation for this game – [readthedocs.io] , or dive straight into the full documentation of this game on github – [github.com] 
 
 
 

Reading from and writing to files

Although I won’t give you a concrete example of what script(s) you could be using this in, I do feel it’s important to know that you can read from and write to files. A simple application of using files would be to centralize data for your scripts, essentially creating a database of sorts.
 
 
I’ll give you a simple ‘FileHandler’ wrapper class for handling files and I’ll try to point out some things, so you can either make your own file handler or work with or implement the one I’m giving you. So here comes the code:
 
 

export class FileHandler {
#file;
#ns;

constructor (ns, file) {
this.#ns = ns;
this.#file = file;
}

async newFile() {
await this.#ns.write(this.#file, "", "w");
}

async write(data, mode="a") {
await this.#ns.write(this.#file, JSON.stringify(data), mode);
}

async read() {
let dataString = await this.#ns.read(this.#file);
if (dataString.length > 1) {
return JSON.parse(dataString);
} else {
return [];
}
}
}

 
 
So lets start with classes, classes can be compared to being a template for object. A class needs a constructor (which can do nothing, if you want), to tell javascript what to do when a new object of this class is made. Which brings me to how to make a new object, so can do so with the ‘new’ keyword, an example of making a new object with the ‘FileHandler’ class is:
 
 

let fileHandler = new FileHandler(ns, "filename.txt");

 
 
The code above makes a new instance of the ‘FileHandler’ class and sets the filename to work with to ‘filename.txt’. The ‘ns’ argument is passed so that the class can access the netscript functions, but you could pass it to the functions (‘read’, ‘write’, ‘newFile’) instead of the instructor if you don’t want the class to have a reference to the ‘ns’ namespace internally (because this can lead to complications when you implement it the wrong way).
 
 
The functions ‘read’, ‘write’ and ‘newFile’ are all ‘async’, because you should ‘await’ them in whenever you use them in you code, an example would be:
 
 

await fileHandler.write(["test", "test2"]);

 
 
So you may have noticed me passing an array to the ‘write’ function, which brings me to the ‘JSON’ namespace. With the ‘stringify’ function in the ‘JSON’ namespace you can turn any primitive javascript object (a.k.a. anything not one of you own classes) into a string, which can the be written to the file. You can then turn the string back into a primitive javascript object with the ‘parse’ function in the ‘JSON’ namespace, which is done in this snippet within the ‘read’ function.
 
 
Which just leaves me with the netscript functions ‘ns.read()’ and ‘ns.write()’, and these just get a string value from a file (read) and write a string value to a file (write). The ‘ns.write()’ function has two modes: append (“a”) and write (“w”); append leaves the current contents of the file as is and concatenates the new string to the end of the file; write overwrites the entire file with the new string.
 
 
If you put this code in a seperate file (e.g.: ‘/data/file-handler.js’) you can import it in other scripts using:
 
 

import { FileHandler } from "/data/file-handler.js";

 
 
 

Script solutions (cheat sheet)

I decided to include the full scripts I’ve been going through in this guide, you’re not supposed to copy-paste these (though I can’t stop you…), but you’re supposed to use these as a reference if you get stuck on anything (because I didn’t mention every line of code of every script, because I want you to fill in the blanks yourself). Anyway, here they are:
 
 

deployer.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
let servers = ns.scan("home");
let ramPerThread = ns.getScriptRam("early-hack-template.js");
for (let serverName of servers) {
await ns.scp("early-hack-template.js", serverName);

let openPorts = 0;
if (ns.fileExists("BruteSSH.exe")) {
ns.brutessh(serverName);
openPorts++;
}
if (ns.fileExists("FTPCrack.exe")) {
ns.ftpcrack(serverName);
openPorts++;
}
if (ns.fileExists("RelaySMTP.exe")) {
ns.relaysmtp(serverName);
openPorts++;
}
if (ns.fileExists("HTTPWorm.exe")) {
ns.httpworm(serverName);
openPorts++;
}
if (ns.fileExists("SQLInject.exe")) {
ns.sqlinject(serverName);
openPorts++;
}
if (ns.getServerNumPortsRequired(serverName) <= openPorts) {
ns.nuke(serverName);
}

if (ns.hasRootAccess(serverName)) {
let ramAvailable = ns.getServerMaxRam(serverName)
- ns.getServerUsedRam(serverName);
let threads = Math.floor(ramAvailable / ramPerThread);
if (threads > 0) {
ns.exec("early-hack-template.js", serverName, threads, "n00dles");
}
}
}
}

 
 

purchase-servers.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
let servers = ns.getPurchasedServers();
let ram = 8;
for (let i = servers.length; i < ns.getPurchasedServerLimit(); i++) {
let name = "pserv-" + i;
while (ns.getPurchasedServerCost(ram) > ns.getServerMoneyAvailable("home")) {
await ns.sleep(3000);
}
ns.purchaseServer(name, ram);
}
}

 
 

startup.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
ns.run("deployer.js", 1);
ns.run("purchase-servers.js", 1);
}

 
 

/v2/deployer.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
let servers = [];
let ramPerThread = ns.getScriptRam("early-hack-template.js");
let serversToScan = ns.scan("home");
while (serversToScan.length > 0) {
let server = serversToScan.shift();
if (!servers.includes(server) && server !== "home") {
servers.push(server);
serversToScan = serversToScan.concat(ns.scan(server));

// Get root access
let openPorts = 0;
if (ns.fileExists("BruteSSH.exe")) {
ns.brutessh(server);
openPorts++;
}
if (ns.fileExists("FTPCrack.exe")) {
ns.ftpcrack(server);
openPorts++;
}
if (ns.fileExists("RelaySMTP.exe")) {
ns.relaysmtp(server);
openPorts++;
}
if (ns.fileExists("HTTPWorm.exe")) {
ns.httpworm(server);
openPorts++;
}
if (ns.fileExists("SQLInject.exe")) {
ns.sqlinject(server);
openPorts++;
}
if (ns.getServerNumPortsRequired(server) <= openPorts) {
ns.nuke(server);
}

if (ns.hasRootAccess(server)) {
// Deploy the 'early-hack-template' script
await ns.scp("early-hack-template.js", server);
let ramAvailable = ns.getServerMaxRam(server)
- ns.getServerUsedRam(server);
let threads = Math.floor(ramAvailable / ramPerThread);
if (threads > 0) {
ns.exec("early-hack-template.js", server, threads, "n00dles");
}
}
}
}
}

 
 

/v2/purchase-servers.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
let ram = 8;
let servers = ns.getPurchasedServers();

while (true) {
for (let i = 0; i < ns.getPurchasedServerLimit(); i++) {
let name = "pserv-" + i;
while (ns.getPurchasedServerCost(ram) > ns.getServerMoneyAvailable("home")) {
await ns.sleep(3000);
}
if (servers.includes(name)) {
if (ns.getServerMaxRam(name) < ram) {
ns.killall(name);
ns.deleteServer(name);
} else {
continue;
}
}
ns.purchaseServer(name, ram);
}

ram *= 2;
servers = ns.getPurchasedServers();
await ns.sleep(1000);
}
}

 
 

/v3/deployer.js

 
 

/** @param {NS} ns **/
export async function main(ns) {
let target = ns.args[0];
let moneyThresh = ns.getServerMaxMoney(target) * 0.75;
let securityThresh = ns.getServerMinSecurityLevel(target) + 5;
let servers = [];
let ramPerThread = ns.getScriptRam("/shared/weaken.js");

let serversToScan = ns.scan("home");
while (serversToScan.length > 0) {
let server = serversToScan.shift();
if (!servers.includes(server) && server !== "home") {
servers.push(server);
serversToScan = serversToScan.concat(ns.scan(server));
await ns.scp([
"/shared/weaken.js",
"/shared/grow.js",
"/shared/hack.js"
], server);

let openPorts = 0;
if (ns.fileExists("BruteSSH.exe")) {
ns.brutessh(server);
openPorts++;
}
if (ns.fileExists("FTPCrack.exe")) {
ns.ftpcrack(server);
openPorts++;
}
if (ns.fileExists("RelaySMTP.exe")) {
ns.relaysmtp(server);
openPorts++;
}
if (ns.fileExists("HTTPWorm.exe")) {
ns.httpworm(server);
openPorts++;
}
if (ns.fileExists("SQLInject.exe")) {
ns.sqlinject(server);
openPorts++;
}
if (ns.getServerNumPortsRequired(server) <= openPorts) {
ns.nuke(server);
}
}
}

if (ns.hasRootAccess(target)) {
while (true) {
let sleepTime = 3000;
for (let server of servers) {
if (ns.hasRootAccess(server)) {
let ramAvailable = ns.getServerMaxRam(server)
- ns.getServerUsedRam(server);
let threads = Math.floor(ramAvailable / ramPerThread);

if (threads > 0) {
if (ns.getServerSecurityLevel(target) > securityThresh) {
sleepTime = ns.getWeakenTime(target)
ns.exec("/shared/weaken.js", server, threads, target);
} else if (ns.getServerMoneyAvailable(target) < moneyThresh) {
sleepTime = ns.getGrowTime(target)
ns.exec("/shared/grow.js", server, threads, target);
} else {
sleepTime = ns.getHackTime(target)
ns.exec("/shared/hack.js", server, threads, target);
}
}
}
}
await ns.sleep(sleepTime + 100);
}
}
}

 
 
 

Bonus: data-structures

I have added a full example of a stack class below and an incomplete example of a queue (which you can finish by yourself). This is a bit of a difficulty spike compared to the rest of the guide, so don’t worry if you don’t understand a thing of what’s going on.
 
 

Stack

 
 

export class Stack {
#data;

constructor() {
this.#data = [];
}

get size() {
return this.#data.length;
}

push(item) {
this.#data.push(item);
}

pop() {
this.#data.pop();
}

peek() {
if (this.size > 0) {
return this.#data[this.size - 1];
} else {
return null;
}
}
}

 
 

Queue

 
 

export class Queue {
#data;

constructor() {
}

get length() {
}

enqueue(item) {
}

dequeue() {
}

peek() {
}
}

 
 
 

Changelog

Because I have been adding/changing/fixing more than I was expecting to, I thought I’d add a bit of a changelog here, so that you would know what changes I made. (btw, where I live we use: dd-mm-yyyy as a format for the date, which is what i’ll be using here)
 
 
14-01-2022:
 

  • Initial version.

15-01-2022:
 

  • Fixed some bugs in the deployer scripts: added a check to see if threads was larger than zero; updated serversToScan array now properly gets assigned; added an ‘await’ to ns.scp() calls.
  • Updated text related to the aforementioned bugs.
  • Added a section on debugging.

16-01-2022:
 

  • Fixed the deployer scripts so they try to get root access on all servers and check for root access before running scripts remotely.
  • Added a section on getting root access on a server.

 
 

Written by Suikoudan

 
 
Here we come to an end for Bitburner Scripting Tutorial + Basic Operation hope you enjoy it. If you think we forget something to include or we should make an update to the post let us know via comment, and we will fix it asap! Thanks and have a great day!
 


Be the first to comment

Leave a Reply

Your email address will not be published.


*