Skip to content

Instantly share code, notes, and snippets.

@grimley517
Last active November 30, 2023 15:33
Show Gist options
  • Save grimley517/c2d531976db057cede4ac8e367418971 to your computer and use it in GitHub Desktop.
Save grimley517/c2d531976db057cede4ac8e367418971 to your computer and use it in GitHub Desktop.
Bitburner Hacknet Build automation
/** @param {NS} ns **/
export async function main(ns) {
var paybackLimit = 24 * 60 * 60;
var sleepMilliseconds = 5 * 1000;
var budgetPercentage = 0.5;
while (true) {
var budget = ns.getServerMoneyAvailable('home');
budget *= budgetPercentage; //Don't want to spend all money on hacknet, allow 33% but change this if wanted
var nodeNumber = ns.hacknet.numNodes();
var actionList = [];
if (nodeNumber > 0) {
for (var i = 0; i < nodeNumber; i++) {
var nodeActions = [];
nodeActions.push(new RamAction(ns, i));
nodeActions.push(new CoreAction(ns, i));
nodeActions.push(new LevelAction(ns, i));
nodeActions.forEach(action => actionList.push(action));
}
}
actionList.push(new NewNodeAction(ns, 1));
actionList = actionList.filter(action => action.payBackTime() < paybackLimit);
if (actionList.length > 0) {
actionList = actionList.filter(action => action.cost < budget);
if (actionList.length > 0) {// Second check, we don't want to escape the script if there are actions we will be able to afford later
actionList.sort(function (x, y) { x.payBackTime() - y.payBackTime() });
actionList[0].doAction();
}
await ns.sleep(sleepMilliseconds);
}
else {
ns.tprint(`All Hacknet Nodes are fully productive.`);
break;
}
}
}
class Action {
constants = {
MaxLevel: 200,
MaxRam: 64,
MaxCores: 16,
MoneyGainPerLevel: 1.5,
HackNetNodeMoneyBitNode: 0.25
}
sys;
nodeIndex;
cost;
originalProd;
prodIncrease;
name;
multProd;
ram;
level;
cores;
ns;
doAction = () => { return null; };
constructor(ns, nodeIndex) {
this.ns = ns;
this.sys = ns.hacknet;
this.nodeIndex = nodeIndex;
this.multProd = ns.getHacknetMultipliers().production;
var stats = this.sys.getNodeStats(nodeIndex);
this.name = stats.name;
this.ram = stats.ram;
this.level = parseFloat(stats.level);
this.cores = parseFloat(stats.cores);
this.originalProd = parseFloat(stats.production);
}
payBackTime() {
return this.cost / this.prodIncrease;
}
upgradedProdRate(ns) {
var updatedRate = this.calculateMoneyGainRate(this.level, this.ram, this.cores, this.multProd);
return updatedRate;
}
calculateMoneyGainRate(level, ram, cores, mult) {
const gainPerLevel = this.constants.MoneyGainPerLevel;
const levelMult = level * gainPerLevel;
const ramMult = Math.pow(1.035, ram - 1);
const coresMult = (cores + 5) / 6.0;
var result = levelMult;
result *= ramMult;
result *= coresMult;
result *= mult;
result *= this.constants.HackNetNodeMoneyBitNode;
return result;
}
}
class RamAction extends Action {
constructor(ns, nodeIndex) {
super(ns, nodeIndex);
this.cost = this.sys.getRamUpgradeCost(this.nodeIndex, 1);
if (isFinite(this.cost) && this.cost > 0) {
++this.ram;
this.prodIncrease = this.upgradedProdRate(ns);
this.prodIncrease -= this.originalProd;
this.doAction = () => {
this.sys.upgradeRam(nodeIndex, 1);
ns.print(`upgrading Ram on node ${this.nodeIndex}, payback time is ${Math.ceil(this.payBackTime() / 3600)} hours`);
}
}
else {
this.prodIncrease = 0;
}
}
}
class LevelAction extends Action {
constructor(ns, nodeIndex) {
super(ns, nodeIndex);
this.cost = this.sys.getLevelUpgradeCost(this.nodeIndex, 1);
if (isFinite(this.cost) && this.cost > 0) {
++this.level;
this.prodIncrease = this.upgradedProdRate(ns);
this.prodIncrease -= this.originalProd;
this.doAction = () => {
this.sys.upgradeLevel(nodeIndex, 1);
ns.print(`upgrading Level on node ${this.nodeIndex}, payback time is ${Math.ceil(this.payBackTime() / 3600)} hours`);
}
}
else {
this.prodIncrease = 0;
}
}
}
class CoreAction extends Action {
constructor(ns, nodeIndex) {
super(ns, nodeIndex);
this.cost = this.sys.getCoreUpgradeCost(this.nodeIndex, 1);
if (isFinite(this.cost) && this.cost > 0) {
++this.cores;
this.prodIncrease = this.upgradedProdRate(ns);
this.prodIncrease -= this.originalProd;
this.doAction = () => {
this.sys.upgradeCore(nodeIndex, 1);
ns.print(`upgrading Core on node ${this.nodeIndex}, payback time is ${Math.ceil(this.payBackTime() / 3600)} hours`);
}
}
else {
this.prodIncrease = 0;
}
}
}
class NewNodeAction extends Action {
constructor(ns, nodeIndex) {
super(ns, 0);
this.ram = 1;
this.level = 1;
this.cores = 1;
this.originalProd = 0;
this.cost = this.sys.getPurchaseNodeCost();
if (isFinite(this.cost) && this.cost > 0) {
this.prodIncrease = this.upgradedProdRate(ns);
this.prodIncrease -= this.originalProd;
this.doAction = () => {
this.sys.purchaseNode();
ns.print(`Purchasing a new node. payback time is ${Math.ceil(this.payBackTime() / 3600)} hours`);
}
}
else {
this.prodIncrease = 0;
}
}
}
@Youssef-Beltagy
Copy link

Youssef-Beltagy commented Jun 11, 2023

For this line:

actionList.push(new NewNodeAction(ns, 1));

There is a big problem with treating new node purchases as regular Action. New nodes at the beginning only produce $8/s (actual value) which is trivial. But what matters is their protentional to get $23.596k/s (actual value).

With a price of $4.125m (actual value) and profit of $8/s, a node will only pay for itself in 5+ days so it will not be purchased. But let's say we pay $500m (guesstimate--likely overestimate) to buy and fully upgrade the node but can get $23.596k/s. The node will make up for its total price in less than 6 hours.

Pretty much all the payback filtering logic is for this new node cost and it is just a bug.

@Youssef-Beltagy
Copy link

Youssef-Beltagy commented Jun 11, 2023

Furthermore, the budget percentage is not really useful because the home server money will exponentially decay to trivial numbers even if you set the percentage as a low value.

@pauleHeisster
Copy link

I have also tried your script and can only agree that buying a new node is a very rare action because of the minimal/initial production increase rate.
I would try to estimate the potential production increasement by upgrading a new node with the cost of the first item within the filtered actionList.
This should lead to the real potential of investing into a new node.

@pauleHeisster
Copy link

payBackTime() {
    return this.cost / this.prodIncrease;
}

is the relative payBackTime of each Action
If changed to the absolute payBackTime (using this.multProd) this script behaves as expected (new nodes will be purchased and upgraded).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment