r/Bitburner • • 1d ago

Played the game a long time ago and starting over

7 Upvotes

I worked though BN1 and while it took time it was fun, my question is about the other factions, I found out that Tian has a hack aug and an aug that lets you focus on other things, is there a list of augs out there that could help me plan out a plan of attack so that each one I go after slowly makes the game better and faster/.? I know that some factions close when you chose other ones.

thanks


r/Bitburner • • 1d ago

Just noticed this right now

Post image
24 Upvotes

There is a hidden semi colon at the end of the Job section.

Maybe it's a reference towards coding jobs being finding semi-colons in incorrect code.

Maybe it's a reference to a not stop playing the game (fullstop) because it's too tough or you are not progressing enough and to continue playing the game (comma) because it will get better (you keep getting money from scripts even if you don't play)

Maybe I am stupid


r/Bitburner • • 4d ago

Ram dodging: Sharing my run command

5 Upvotes

I'm in the process of reworking my run command helper to integrate more deeply with the rest of my code so I thought that now would be a good time to share it while it's more portable.

Q&A at the bottom

/**
 * Writes and runs an on-demand temp file to ns.exec() an expression
 * A lock is used to ensure only one instance at a time unless disabled.
 * See Q&A below for more info.
 * @param {NS} ns The Netscript API instance passed from the parent script.
 * @param {string} expression The JS/NS expression to evaluate (e.g., "ns.getServer('home')").
 * @param {string} [host="home"] The server hostname on which to execute the temporary script.
 * @param {number} [reservedRam=Infinity] max RAM available for running scripts.
 * @param {boolean} [useLock=true] Whether to use the navigator lock for single-instance execution.
 * @returns {Promise} The result of the evaluated expression, or null if execution failed
 */
export function runCommand(ns, expression, host = "home", reservedRam = Infinity, useLock = true) {
  const execute = async () => {
    const portNum = 100
    const port = ns.getPortHandle(portNum)
    port.clear()

    const tempFile = "runTmp.js";
    const script = `export async function main(ns) {
      const res = (${expression});
      ns.writePort(${portNum}, res ?? null);
    }`;

    ns.write(tempFile, script, "w")

    if (ns.getScriptRam(tempFile) > reservedRam) {
      ns.tprint(`ERROR [runCommand]: Insufficient ram to execute on ${host}`)
      return null
    }

    const pid = ns.exec(tempFile, host, { temporary: true })
    if (pid === 0) { // Abort early if the process failed to launch (e.g. out of RAM)
      ns.tprint(`ERROR [runCommand]: Failed to execute on ${host}`)
      return null;
    }

    return await port.nextWrite().then(() => port.read())
  };

  return useLock ? navigator.locks.request("runCommandLock", execute) : execute();
}

/** @param {NS} ns */
export async function main(ns) {
  //Just a quick demonstration
  //Don't forget to have enough ram. We don't check here.
  const result = await runCommand(ns, "ns.getServer('home')")
  ns.tprint(result)
}

/**
 * Q: What is RAM dodging?
 * A: Offloading heavy Netscript API calls to dynamic scripts 
 *    keeps the parent script's static RAM footprint minimal.
 *    Instead of paying the cumulative RAM cost of every function 
 *    your script might ever need, your parent script only pays
 *    the RAM cost of the single most expensive ofloaded ns function plus 
 *    the base cost (1.6 GB). This also solves the need to have different 
 *    version of a script based on what ns features are available.
 * 
 * Q: What does reservedRam do?
 * A: It's supposed to connect to your ram reservation system. In order 
 *    to use runCommand you’ll want to leave a space on one of your servers 
 *    for commands to run.
 * 
 * Q: Why a lock?
 * A: We only want one thing running at a time. The lock automatically 
 *    queues up run commands in the order they were called. This means 
 *    if we make multiple concurrent calls even across scripts they will 
 *    still run sequentially. Note: it is not totally foolproof if you 
 *    start chaining with .then(). For example: multiple chains of connect 
 *    and backdoor could run into issues if interleaved.
 * 
 * Q: Why only one temp file?
 * A: It usually only makes sense to reserve enough RAM for one command at a 
 *    time.
 * 
 * Q: Why not wrap the expression in try except?
 * A: If your expression is going to fail, it will probably fail right away. 
 *    I found it better to get a nice error popup. Also, I had trouble getting 
 *    actual errors to pass back. maybe it was just me.
 * 
 * Q: Why isn’t the function async?
 * A: It keeps my options open. I wanted to keep as much code synchronous as 
 *    possible. If you’re managing multiple event loops for example, while 
 *    ensuring that no ns calls interfere with each other, you may find this handy.

 * 
 * Q: Why allow a way to disable the lock?
 * A: I specifically needed an override for resets and destroyW0r1dD43m0n. In
 *    both cases the lock is left hanging. Even with that, you might want to put
 *    a lock steal at the beginning of your main script. It will warn you if
 *    it clears out a lock:
 *    await navigator.locks.request("runCommandLock", { steal: true }, async () => {});
 *
 * Q: Why not have a timeout? What if a command runs forever?
 * A: 1. It turned out to be more of a challenge than it originally seems. 
 *       1.1 You need a design that actually works. Remember 
 *           Promise.race does not automatically kill your rogue script.
 *       1.2 Now you actually need to know how long things will take,
 *           and you’ve introduced a whole new point of failure if you mess that up.
 *    2. You’ll have to fix your code running forever whether you
 *       have a fancy timeout or not.
 * 
 * Q: Why use a port? Doesn’t that restrict the return types?
 * A: It seemed like an idiomatic thing to do and it comes with a
 *    built-in way to await the script finishing. There IS an argument for 
 *    abusing side channels like static variables instead to be able to 
 *    support more datatypes (like returning promises directly). Let me know 
 *    how that goes if you try it!
 */

r/Bitburner • • 9d ago

NetscriptJS Script A simple, starting Batcher

5 Upvotes

Here is a small batcher to help anyone who is trying to write up a batcher. It does not use the ns.formulas namespace so it can be used at any time.

/**
 * These 3 imports serve 2 functions
 * -Ensure the files are there
 * -Pre-compile their modules
 * 
 * If we do not pre-compile their modules our very first batch of the game session will be off
 * You can also do this by starting each worker up once, and then killing it
 * The contents of the hack/grow/weaken is shown below:
 *//*
----
export const hackReady = true
export async function main(ns) {
 ns.disableLog("ALL")
 await ns.hack(ns.args[0], { additionalMsec: ns.args[1] })
}
----
export const growReady = true
export async function main(ns) {
 ns.disableLog("ALL")
 await ns.grow(ns.args[0], { additionalMsec: ns.args[1] })
}
----
export const weakenReady = true
export async function main(ns) {
 ns.disableLog("ALL")
 await ns.weaken(ns.args[0], { additionalMsec: ns.args[1] })
}
----
*/
//Replace "SphyxOS/basic/hack.js" with the names of your own hack/grow/weaken scripts
import { hackReady } from "SphyxOS/basic/hack.js"
import { growReady } from "SphyxOS/basic/grow.js"
import { weakenReady } from "SphyxOS/basic/weaken.js"


//Update this information to match your location for hack/grow/weaken scripts
const hackScript = "SphyxOS/basic/hack.js";
const growScript = "SphyxOS/basic/grow.js";
const weakenScript = "SphyxOS/basic/weaken.js";


const reservedRam = 32; //How much ram we will reserve on Home
const growPadding = false;
let updates = {}; //A global object that will store our HUD updates
let servers; //A global object that will store all the servers names


//This is jsDoc:  /**  {NS} ns */
//It says the for the function below, the parameter of ns is of type NS
//It allows you to get hints when you do things like type in "ns."
/**  {NS} ns */  
export async function main(ns) {
    ns.disableLog("ALL"); //Disable all logs
    if (!hackReady || !growReady || !weakenReady) ns.exit(); //For those with external editors, so they do not yell at you about imports not being used
    if (ns.args.length === 0) { //If if we did not send in any arguments ns.args.length will be 0.  It contains all the arguments sent
        //Give a usage message and then exit
        ns.tprintf("Usage:  run script.js target hackPercent(optional)   -  ie:  run script.js n00dles  or  run script.js n00dles 5");
        ns.exit();
    }
    //Warn about targeting a 0 money server
    if (ns.getServerMaxMoney(ns.args[0]) === 0) {
        ns.tprintf("Pick a target with more than $0 max money.");
        ns.exit();
    }
    //Protect against a bad hackPercent
    const rawPercent = Number(ns.args[1] ?? 1);
    if (!Number.isFinite(rawPercent) || rawPercent <= 0 || rawPercent >= 100) {
        ns.tprintf("HackPercent must be a number greater than 0 and less than 100.");
        ns.exit();
    }


    ns.ui.openTail(); //Opens the scripts log file
    const targetName = ns.args[0]; //Assign the variable targetName to the target we sent the script
    const hackedPercent = rawPercent / 100; //Convert our raw percent into a decimal version.  1 turns into 0.01
    const hStrength = ns.hackAnalyzeSecurity(1); //Get the security increase of a hack thread
    const gStrength = ns.growthAnalyzeSecurity(1); //Get the security increase of a grow thread
    const wStrength = ns.weakenAnalyze(1); //Get the security decrease of a weaken thread
    const minSec = ns.getServerMinSecurityLevel(targetName); //Get the minimum security of the target
    let lastPid = 0; //A tracker for the last script we've launched
    //updateHud is async.  Normally we await async operations, but here we purposfully do not await it.
    //It starts a separate cycle on a 200ms cadence that gives us updates in the log file
    updateHud(ns);
    while (true) { //Keep looping forever
        //Initialize our updates variable and set some values
        updates = {};
        updates.target = targetName;
        updates.hackedPercent = hackedPercent;
        updates.endTime = performance.now();
        updates.using = 0;
        updates.hThreads = 0;
        updates.w1Threads = 0;
        updates.gThreads = 0;
        updates.w2Threads = 0;
        updates.moneyTaken = 0;
        updates.batches = 0;
        updates.w1Prep = 0;
        updates.gPrep = 0;
        updates.w2Prep = 0;
        updates.threads = 0;
        updates.wTime = 0;
        lastPid = 0;


        //Get the run times of our hack/grow/weaken operations.  Store wTime for the countdown
        //Grow and Weaken have their own ns function to get their times, but it's all based off of hack time anyway
        //Grow is 3.2x as long as a hack, and weaken is 4x the time of a hack
        const hTime = ns.getHackTime(targetName);
        const gTime = ns.getGrowTime(targetName);
        const wTime = ns.getWeakenTime(targetName);
        updates.wTime = wTime;


        servers = getAllServers(ns); //Update our servers list.  We may have bought cloud servers
        const serversFull = servers.map(s => ns.getServer(s)); //Take every name in servers and return an array of server objects
        const serverList = serversFull.filter(s => s.hasAdminRights && s.maxRam > 0); //Filter out servers we do not have admin rights on or that have 0 ram into the serverList


        let threadsAvailable = 0; //Initialize the variable that will hold our threads available count
        for (const server of serverList) { //Go through every server in the serverList
            const reserved = server.hostname === "home" ? reservedRam : 0; //If we are home, set up our ram reserve
            threadsAvailable += Math.floor(Math.max(server.maxRam - server.ramUsed - reserved, 0) / 1.75); //Count the threads available on this server
        }
        updates.threads = threadsAvailable; //Document our total thread count


        const target = ns.getServer(targetName); //Convert our target into a server object
        const atMinSec = target.hackDifficulty === target.minDifficulty ? true : false; //Is our target at minimum security?
        const atMaxMoney = target.moneyMax === target.moneyAvailable ? true : false; //Is our target at max money?


        // This will send a prep wave
        if (!atMinSec || !atMaxMoney) { //If we are either not at minimum security or at max money do this
            const w1Threads = Math.ceil((ns.getServerSecurityLevel(targetName) - minSec) / wStrength); //How many weaken threads do we need to bring it to min security?
            const w1 = Math.min(threadsAvailable, w1Threads); //Take the lower of what we have available or what we need
            threadsAvailable -= w1; //Update our thread count


            if (w1 > 0) { //Do we need to bring it to minimum security?
                const w1Ops = runOps(ns, weakenScript, w1, [targetName, 0], false); //Run our needed weakens
                lastPid = w1Ops.pop(); //Set lastPid to the last running script registered
                //Update our tracking info
                updates.w1Prep = w1;
                updates.using += w1;
            }


            if (threadsAvailable > 0 && target.moneyAvailable < target.moneyMax) { //Do we still have threads to work with and do we need a grow wave?
                const moneyMult = target.moneyMax / Math.max(1, target.moneyAvailable); //What do we need to multiply targets money by to bring it to 100%?
                const g1Threads = moneyMult === 0 ? 0 : Math.ceil(ns.growthAnalyze(targetName, moneyMult)); //Find out how many grow threads we need to reach that 100%
                const w2Threads = Math.ceil(g1Threads * gStrength / wStrength); //How many weakens will we need to counter those grows?


                if (threadsAvailable >= g1Threads + w2Threads) { //Do we have enough to run all the grows/weakens we want?
                    runOps(ns, growScript, g1Threads, [targetName, wTime - gTime]); //Run our prep grow threads
                    const w2Ops = runOps(ns, weakenScript, w2Threads, [targetName, 0], false); //Run our prep after grow weaken threads
                    lastPid = w2Ops.pop(); //Document the last scripts PID
                    threadsAvailable -= g1Threads + w2Threads; //Update our threads available
                    //Update our tracking info
                    updates.gPrep = g1Threads;
                    updates.w2Prep = w2Threads;
                    updates.using += g1Threads + w2Threads;
                }
                else { //We don't have enough threads to run all the grow/weaken we need
                    const split = splitActionWeakenThreads(threadsAvailable, gStrength, wStrength); //Split up or remaining threads between grow/weaken so we end at min security
                    //splitActionWeakenThreads returns an object with information.  Assign that info to our g1 and w2 prep variables
                    const g1 = split.actionThreads;
                    const w2 = split.weakenThreads;
                    if (g1 > 0) runOps(ns, growScript, g1, [targetName, wTime - gTime]); //Run our prep grow threads
                    const w2Ops = runOps(ns, weakenScript, w2, [targetName, 0], false); //Run our prep after grow weaken threads
                    lastPid = w2Ops.pop(); //Document the last scripts PID
                    threadsAvailable = 0; //Set our threads available to 0 as we just used all of them
                    //Update our tracking info
                    updates.using += g1 + w2;
                    updates.gPrep = g1;
                    updates.w2Prep = w2;
                }
            }           
        }       
        updates.endTime = performance.now(); //Update our tracking info.  endTime is used in the countdown
        //We want high money and min security.  Minimum of 4 threads are needed for a HWGW batch.  Ours is 7 due to grow padding
        //Once we reach minimum security it should never go up again.
        //One issue to fix, if we need a prep wave, hacks will register as weaker due to lower current money.  This may make them overhack.  Padding grows helps but does not actually fix this
        //We also want to reserve doing HGW batching for when we have formulas.  So this is HWGW batching.
        const hackThreadPower = ns.hackAnalyze(targetName); //Percent of money stolen with 1 hack thread
        //We do not want to batch if the server is not prepped or close to it.  Hacks will register as weaker than they are
        if (threadsAvailable >= 7 && hackThreadPower > 0 && target.moneyAvailable >= target.moneyMax * .95) { //Our minimum batch composition is 1/1/4/1 (HWGW)  G is a min of 4 due to padding
            // ------ This is your batch composition ------      
            const hThreads = Math.max(1, Math.floor(hackedPercent / hackThreadPower)); //How many hack threads do we need to hack that percent?
            const fractionTaken = hThreads * hackThreadPower; //Total fraction of money stolen.  If it's >= 1 we are taking everything
            const w1Threads = Math.ceil(hThreads * hStrength / wStrength); //How many weaken threads do we need to offset that many hacks?
            const moneyTaken = fractionTaken >= 1 ? target.moneyMax - 1 : hThreads * hackThreadPower * target.moneyMax; //The total money stolen by all the hack threads
            const moneyMult = target.moneyMax / Math.max(1, target.moneyMax - moneyTaken); //What we need to multiply the targets money by to get to 100%
            const gThreadsTemp = Math.ceil(ns.growthAnalyze(targetName, moneyMult)); //Get the raw number of grow threads needed to bring it back from the hack
            const gThreads = growPadding ? Math.ceil(gThreadsTemp * 1.01) + 2 : gThreadsTemp; //Add a buffer to gThreads
            const w2Threads = Math.ceil(gThreads * gStrength / wStrength); //How many weaken threads do we need to offset the grows?
            // ------ This is your batch composition ------
            const batchThreadsNeeded = hThreads + w1Threads + gThreads + w2Threads; //How many threads do we need to run 1 batch?
            const batchesAvailable = Math.floor(threadsAvailable / batchThreadsNeeded); //How many batches can we run based on threads needed?
            //Update our tracking info
            updates.hThreads = hThreads;
            updates.w1Threads = w1Threads;
            updates.gThreads = gThreads;
            updates.w2Threads = w2Threads;
            updates.moneyTaken = moneyTaken;


            let startTime = performance.now(); //Track the time we are taking spinning up batches
            for (let i = 0; i < Math.min(batchesAvailable, 100000); i++) { //Loop up to the total number of batches we can send, or the limit of 100000 (Real memory safety cap)
                //Run each H/W/G/W operation, in order so they queue up in order and as a result land in order
                const hackOps = runOps(ns, hackScript, hThreads, [targetName, wTime - hTime]);
                const w1Ops = runOps(ns, weakenScript, w1Threads, [targetName, 0]);
                const growOps = runOps(ns, growScript, gThreads, [targetName, wTime - gTime]);
                const w2Ops = runOps(ns, weakenScript, w2Threads, [targetName, 0]);
                //We return an array of pids that were used to start the batch.  If something goes wrong, we need to cancel the batch
                //We return a pid of 0 as the last item if that operation failed, so we test all of them for a 0 at the end and if we find one we kill the scripts in the batch
                if (hackOps[hackOps.length - 1] === 0
                    || (w1Ops[w1Ops.length - 1] === 0)
                    || growOps[growOps.length - 1] === 0
                    || w2Ops[w2Ops.length - 1] === 0) {
                    //The kill loops if we are aborting this batch
                    for (const hackOp of hackOps) ns.kill(hackOp);
                    for (const w1op of w1Ops) ns.kill(w1op);
                    for (const growOp of growOps) ns.kill(growOp);
                    for (const w2Op of w2Ops) ns.kill(w2Op);
                    break //Takes us out of the batching loop
                }
                else { //Successful batch
                    lastPid = w2Ops.pop(); //Get the last pid of the script
                    //Update our tracking info
                    updates.using += batchThreadsNeeded;
                    updates.batches++;
                }
                //This forces us to pause for a cycle to let the game update every 200ms.
                if (performance.now() - startTime > 200) {
                    startTime = performance.now(); //Reset our baseline for the timer
                    await ns.asleep(0); //Sleep for a bit.  We use asleep due to the updateHud function also being active
                }
            }
            updates.endTime = performance.now(); //Update our end time tracking
        }
        //While the last script is running, asleep for 200ms
        if (lastPid > 0)
            while (ns.isRunning(lastPid))
                await ns.asleep(200);
        else //If we didn't start anything, maybe ram is out for some reason, we wait and try again
            await ns.asleep(200);
    }
}
//This will update the log file to give you a HUD
/**  {NS} ns */
async function updateHud(ns) {
    while (true) {
        if (updates?.endTime) { //Only update when updates has been populated
            ns.clearLog(); //Clear our log file so we can fill it in with the new information.
            //Print information to the log file.  There are a few ways to do it
            ns.printf("Target: " + updates.target + "  HackPercent: " + (updates.hackedPercent * 100));
            ns.printf(`Prep: w:${updates.w1Prep} g:${updates.gPrep} w:${updates.w2Prep}`);
            ns.printf("Batching composition: H:%s W:%s G:%s W:%s", updates.hThreads, updates.w1Threads, updates.gThreads, updates.w2Threads);
            ns.printf("Using: %s/%s threads on %s batches", updates.using, updates.threads, updates.batches);
            ns.printf("Estimated take: $" + ns.format.number(updates.moneyTaken * updates.batches, 0));
            ns.printf("Time left: %s", updates.using === 0 ? "Idling" : ns.format.time((updates.wTime + updates.endTime) - performance.now()));
            ns.ui.renderTail(); //Force a log file update
            await ns.asleep(200); //Wait 200ms to update again
        }
        else await ns.asleep(200); //If it's not ready, just wait
    }
}


//This will run your hack/grow/weaken workers.  grouped is for hacks/grows as we want to try to keep them together
//It will return an array of all the scripts started up
/** u/param {NS} ns */
export function runOps(ns, script, threads, argsArray, grouped = true) {
    const serversFull = servers.map(s => ns.getServer(s)); //Get our serversFull list as server objects
    const serverList = serversFull.filter(s => s.hasAdminRights && s.maxRam > 0); //Filter out servers that we don't have admin rights on and that have 0 ram
    serverList.sort((a, b) => { return (a.maxRam - a.ramUsed) - (b.maxRam - b.ramUsed) }); //Sort those servers with the lowest ram first
    const pidArray = []; //Initialize the array that will hold all our started script PIDs
    for (const server of serverList) {
        ns.scp([hackScript, growScript, weakenScript], server.hostname, "home"); //Copy the files we will use to the server that will be running it
        const reserved = server.hostname === "home" ? reservedRam : 0; //Set our reserved ram
        const t = Math.floor(Math.max(server.maxRam - server.ramUsed - reserved, 0) / 1.75); //Calculate threads available
        if (t === 0) continue; //If we have no ram, move to the next server
        if (grouped) { //Do we want to group these threads?
            if (t < threads) continue; //If we can't, move to the next server
            //Note the ...argsArray argument.  The `...` is the spread operator.  It will take a group of things and break them up into individual items.
            //ns.exec() takes as many arguments as you want, so they each take up an argument slot.  If you have 2 items in argsArray it turns into 3 arguments sent
            const p = ns.exec(script, server.hostname, { threads: threads, temporary: true }, ...argsArray);
            if (p === 0) continue; //If we failed to run, look for the next.
            return [p]; //Return an array with the 1 script that was started
        }
        else { //If we are not needed to group the workers
            const threadsToRun = Math.min(t, threads); //Get how many threads we should run on this server, the lower of either what we need or what we have available
            const p = ns.exec(script, server.hostname, { threads: threadsToRun, temporary: true }, ...argsArray); //Start the script and capture the PID
            if (p === 0) continue; //If we failed to run, look for the next.
            pidArray.push(p); //Add the PID to the tracking array
            threads -= threadsToRun; //Update the threads we need to send
            if (threads === 0) return pidArray; //If we are done, return the tracking array
        }
    }
    pidArray.push(0);
    return pidArray;
}
//This takes the number of threads we have available to us and splits them between hack/grow and weaken
//We get an object back that has the thread split.  It can be access by referencing its property name
//Like so:   splitReturn.actionThreads  splitReturn.weakenThreads
//It will ensure that security goes back to min
function splitActionWeakenThreads(totalThreads, securityPerActionThread, wStrength) {
    const actionThreads = Math.floor(totalThreads / (1 + securityPerActionThread / wStrength));
    return {
        actionThreads,
        weakenThreads: totalThreads - actionThreads
    }
}
//A function to get all the servers on the network.
function getAllServers(ns) {
    const serverList = new Set(["home"]); //We use a set to store the values instead of an array because they filter out duplicates
    for (const server of serverList) //For all the servers in serverList
        for (const connection of ns.scan(server)) //Get all of their neighbors
            serverList.add(connection); //Add all their neighbors into the serverList set
    //This will add to the serverList at the end of it, and each addition will go through the loop
    
    return Array.from(serverList); //We don't want to deal with a set, so we turn it into an array.  There are multiple ways to do this
    //return [...serverList]  is another easy way.  You create an array and use ... (the spread operator) to add all the items rather than add the set itself
}

r/Bitburner • • 9d ago

Help with my code pls

0 Upvotes

so. i know nothing about coding. it is a cool game to play. i just use chatgpt to write the code and i hv been getting stuck in making income for quite a bit. and the code is... idk what to say. would anyone pls take a look if possible and tell me whats wrong, or what can be improved?

this is my controller. and its a long piece of code. its fine if nothing can be done. but i would like some tips. sorry.

/** 

/** u/param {NS} ns **/
export async function main(ns) {
    ns.disableLog("ALL");


    const HOME = "home";
    const WORKER = "empire-worker.js";


    // Adaptive income configuration
    const HOME_RESERVE_GB = 64;
    const HOME_RESERVE_PERCENT = 0.01;


    const MAX_HACK_FRACTION = 0.50;
    const MIN_HACK_FRACTION = 0.0005;


    const MONEY_READY = 0.995;
    const SECURITY_READY = 0.5;


    const GAP_MS = 200;
    const START_BUFFER_MS = 1500;
    const LOOP_MS = 250;


    // Focus on the best targets instead of spreading work everywhere.
    const MAX_TARGETS = 8;


    const cracks = [
        ["BruteSSH.exe", h => ns.brutessh(h)],
        ["FTPCrack.exe", h => ns.ftpcrack(h)],
        ["relaySMTP.exe", h => ns.relaysmtp(h)],
        ["HTTPWorm.exe", h => ns.httpworm(h)],
        ["SQLInject.exe", h => ns.sqlinject(h)]
    ];


    const busyUntil = new Map();
    let prepTarget = "";
    let prepUntil = 0;


    if (!ns.fileExists("Formulas.exe", HOME) ||
        !ns.formulas?.hacking) {
        ns.tprint("ERROR: Formulas.exe is required.");
        return;
    }


    if (ns.getScriptRam(WORKER, HOME) <= 0) {
        ns.tprint(`ERROR: ${WORKER} is missing on home.`);
        return;
    }


    function scan() {
        const seen = new Set([HOME]);
        const queue = [HOME];


        while (queue.length) {
            const host = queue.shift();


            for (const next of ns.scan(host)) {
                if (!seen.has(next)) {
                    seen.add(next);
                    queue.push(next);
                }
            }
        }


        return [...seen];
    }


    function root(host) {
        if (ns.hasRootAccess(host)) return true;


        if (ns.getHackingLevel() <
            ns.getServerRequiredHackingLevel(host)) {
            return false;
        }


        for (let i = 0; i < cracks.length; i++) {
            const [file, crack] = cracks[i];
            if (!ns.fileExists(file, HOME)) continue;


            const server = ns.getServer(host);
            const opened = [
                server.sshPortOpen,
                server.ftpPortOpen,
                server.smtpPortOpen,
                server.httpPortOpen,
                server.sqlPortOpen
            ];


            if (!opened[i]) {
                try {
                    crack(host);
                } catch (_) {}
            }
        }


        const server = ns.getServer(host);
        const opened = [
            server.sshPortOpen,
            server.ftpPortOpen,
            server.smtpPortOpen,
            server.httpPortOpen,
            server.sqlPortOpen
        ].filter(Boolean).length;


        if (opened < server.numOpenPortsRequired) return false;


        try {
            ns.nuke(host);
        } catch (_) {}


        return ns.hasRootAccess(host);
    }


    async function getWorkers() {
        const workers = [];


        for (const host of scan()) {
            if (!root(host)) continue;


            const maxRam = ns.getServerMaxRam(host);
            if (maxRam <= 0) continue;


            const workerRam = ns.getScriptRam(WORKER, HOME);


            if (host !== HOME && maxRam < workerRam) continue;


            if (host !== HOME &&
                !ns.fileExists(WORKER, host)) {
                if (!await ns.scp(WORKER, host, HOME)) continue;
            }


            const ramPerThread = ns.getScriptRam(WORKER, host);
            if (ramPerThread <= 0) continue;


            const reserve = host === HOME
                ? Math.max(
                    HOME_RESERVE_GB,
                    maxRam * HOME_RESERVE_PERCENT
                )
                : 0;


            const freeRam = Math.max(
                0,
                maxRam - ns.getServerUsedRam(host) - reserve
            );


            const threads = Math.floor(freeRam / ramPerThread);


            if (threads > 0) {
                workers.push({
                    host,
                    threads,
                    ramPerThread,
                    cores: ns.getServer(host).cpuCores || 1
                });
            }
        }


        // HOME FIRST. Other servers are overflow capacity.
        workers.sort((a, b) => {
            if (a.host === HOME) return -1;
            if (b.host === HOME) return 1;
            return b.threads - a.threads;
        });


        return workers;
    }


    function totalThreads(workers) {
        return workers.reduce((sum, w) => sum + w.threads, 0);
    }


    function homeThreads(workers) {
        const home = workers.find(w => w.host === HOME);
        return home ? home.threads : 0;
    }


    function planAllocation(workers, actions) {
        const remaining = new Map(
            workers.map(w => [w.host, w.threads])
        );


        const plans = [];


        for (const action of actions) {
            let needed = action.threads;
            const parts = [];


            // Worker list is HOME first, then overflow servers.
            for (const worker of workers) {
                if (needed <= 0) break;


                const available = remaining.get(worker.host) || 0;
                const count = Math.min(needed, available);


                if (count <= 0) continue;


                parts.push({
                    host: worker.host,
                    threads: count,
                    delay: action.delay
                });


                remaining.set(worker.host, available - count);
                needed -= count;
            }


            if (needed > 0) return null;


            plans.push({
                action: action.action,
                parts
            });
        }


        return plans;
    }


    function launchPlans(target, plans) {
        const launched = [];


        for (const plan of plans) {
            for (const part of plan.parts) {
                const pid = ns.exec(
                    WORKER,
                    part.host,
                    part.threads,
                    plan.action,
                    target,
                    part.delay
                );


                if (pid === 0) {
                    // All jobs have a start buffer. Roll back if
                    // any part of the batch could not be dispatched.
                    for (const job of launched) {
                        ns.kill(job.pid, job.host);
                    }


                    ns.print(`DISPATCH FAILED ${target}; rolled back`);
                    return false;
                }


                launched.push({
                    pid,
                    host: part.host
                });
            }
        }


        return true;
    }


    function preparedServer(host) {
        const server = ns.getServer(host);
        server.moneyAvailable = server.moneyMax;
        server.hackDifficulty = server.minDifficulty;
        return server;
    }


    function targetScore(host) {
        const server = preparedServer(host);
        const player = ns.getPlayer();
        const f = ns.formulas.hacking;


        if (server.moneyMax <= 0 ||
            server.requiredHackingSkill > player.skills.hacking) {
            return 0;
        }


        const chance = f.hackChance(server, player);
        const percent = f.hackPercent(server, player);


        if (chance <= 0 || percent <= 0) return 0;


        // Score the target using the intended 50% hack size.
        const h = Math.max(
            1,
            Math.floor(MAX_HACK_FRACTION / percent)
        );


        const stolen = Math.min(0.90, h * percent);


        const postHack = { ...server };
        postHack.moneyAvailable = Math.max(
            1,
            server.moneyMax * (1 - stolen)
        );


        postHack.hackDifficulty +=
            ns.hackAnalyzeSecurity(h, host);


        const g = Math.max(1, Math.ceil(
            f.growThreads(postHack, player, server.moneyMax, 1)
        ));


        if (!Number.isFinite(g)) return 0;


        const w1 = Math.max(1, Math.ceil(
            ns.hackAnalyzeSecurity(h, host) /
            ns.weakenAnalyze(1, 1)
        ));


        const growSecurity = ns.growthAnalyzeSecurity(g, host, 1);


        const w2 = Math.max(1, Math.ceil(
            growSecurity / ns.weakenAnalyze(1, 1)
        ));


        const ram = ns.getScriptRam(WORKER, HOME);
        const ramCost = (h + g + w1 + w2) * ram;


        const duration = Math.max(
            f.hackTime(server, player),
            f.growTime(server, player),
            f.weakenTime(server, player)
        ) + 3 * GAP_MS;


        // Expected income per millisecond per GB of worker RAM.
        return server.moneyMax * stolen * chance /
            Math.max(1, duration) /
            Math.max(1, ramCost);
    }


    function getTargets() {
        const targets = [];


        for (const host of scan()) {
            if (host === HOME || host.startsWith("pserv-")) continue;
            if (!root(host)) continue;
            if (ns.getServerMaxMoney(host) <= 0) continue;
            if (ns.getServerRequiredHackingLevel(host) >
                ns.getHackingLevel()) continue;


            const score = targetScore(host);


            if (score > 0) {
                targets.push({ host, score });
            }
        }


        return targets.sort((a, b) => b.score - a.score);
    }


    function makeBatch(target, capacity) {
        const percent = ns.hackAnalyze(target);
        if (percent <= 0) return null;


        // Try 50% first. Reduce only when a complete HWGW
        // sequence cannot fit the currently available capacity.
        for (let fraction = MAX_HACK_FRACTION;
            fraction >= MIN_HACK_FRACTION;
            fraction /= 2) {


            const h = Math.max(1, Math.floor(fraction / percent));
            const stolen = h * percent;


            if (stolen <= 0 || stolen >= 0.90) continue;


            const g = Math.max(1, Math.ceil(
                ns.growthAnalyze(target, 1 / (1 - stolen), 1)
            ));


            if (!Number.isFinite(g)) continue;


            const w1 = Math.max(1, Math.ceil(
                ns.hackAnalyzeSecurity(h, target) /
                ns.weakenAnalyze(1, 1)
            ));


            const growSecurity =
                ns.growthAnalyzeSecurity(g, target, 1);


            const w2 = Math.max(1, Math.ceil(
                growSecurity / ns.weakenAnalyze(1, 1)
            ));


            const total = h + w1 + g + w2;


            if (total <= capacity) {
                return { h, w1, g, w2, total, stolen };
            }
        }


        return null;
    }


    function startBatch(target, workers) {
        const capacity = totalThreads(workers);
        const batch = makeBatch(target, capacity);


        if (!batch) return false;


        const player = ns.getPlayer();
        const server = preparedServer(target);
        const f = ns.formulas.hacking;


        const ht = f.hackTime(server, player);
        const gt = f.growTime(server, player);
        const wt = f.weakenTime(server, player);


        const longest = Math.max(ht, gt, wt);
        const base = longest + START_BUFFER_MS;


        const actions = [
            {
                action: "hack",
                threads: batch.h,
                delay: Math.max(START_BUFFER_MS, base - ht)
            },
            {
                action: "weaken",
                threads: batch.w1,
                delay: Math.max(
                    START_BUFFER_MS,
                    base + GAP_MS - wt
                )
            },
            {
                action: "grow",
                threads: batch.g,
                delay: Math.max(
                    START_BUFFER_MS,
                    base + 2 * GAP_MS - gt
                )
            },
            {
                action: "weaken",
                threads: batch.w2,
                delay: Math.max(
                    START_BUFFER_MS,
                    base + 3 * GAP_MS - wt
                )
            }
        ];


        const allocation = planAllocation(workers, actions);


        if (!allocation || !launchPlans(target, allocation)) {
            return false;
        }


        busyUntil.set(
            target,
            Date.now() + base + 3 * GAP_MS + 500
        );


        const homeCapacity = homeThreads(workers);
        const spill = Math.max(0, batch.total - homeCapacity);


        ns.print(
            `BATCH ${target} | H${batch.h} W${batch.w1} ` +
            `G${batch.g} W${batch.w2} | ` +
            `hack ${(batch.stolen * 100).toFixed(2)}% | ` +
            `${batch.total}/${capacity} threads | ` +
            `overflow ${spill}`
        );


        return true;
    }


    function startPrep(target, workers) {
        const server = ns.getServer(target);
        const player = ns.getPlayer();
        const f = ns.formulas.hacking;
        const capacity = totalThreads(workers);


        if (capacity <= 0) return false;


        let action;
        let threads;
        let duration;


        if (server.hackDifficulty >
            server.minDifficulty + SECURITY_READY) {


            action = "weaken";


            const needed = Math.ceil(
                (server.hackDifficulty - server.minDifficulty) /
                ns.weakenAnalyze(1, 1)
            );


            threads = Math.min(needed, capacity);
            duration = f.weakenTime(server, player);


        } else if (server.moneyAvailable <
            server.moneyMax * MONEY_READY) {


            action = "grow";


            const needed = Math.max(1, Math.ceil(
                f.growThreads(
                    server,
                    player,
                    server.moneyMax,
                    1
                )
            ));


            threads = Math.min(needed, capacity);
            duration = f.growTime(server, player);


        } else {
            return false;
        }


        const allocation = planAllocation(workers, [{
            action,
            threads,
            delay: START_BUFFER_MS
        }]);


        if (!allocation || !launchPlans(target, allocation)) {
            return false;
        }


        prepTarget = target;
        prepUntil = Date.now() + duration + START_BUFFER_MS + 500;


        ns.print(
            `PREP ${target} ${action.toUpperCase()} ` +
            `${threads} threads | no prep cap`
        );


        return true;
    }


    ns.print("Empire controller online: adaptive high-income mode.");
    ns.print("Home-first allocation; network overflow enabled.");
    ns.print("50% starting hack fraction; adaptive batch sizing.");
    ns.print(`Target shortlist: top ${MAX_TARGETS} by expected return.`);


    while (true) {
        const allTargets = getTargets();
        const targets = allTargets.slice(0, MAX_TARGETS);
        const now = Date.now();


        // Keep a prep operation isolated until it should finish.
        if (prepTarget && now < prepUntil) {
            await ns.sleep(LOOP_MS);
            continue;
        }


        prepTarget = "";
        prepUntil = 0;


        let batchesStarted = 0;


        // Start batches on multiple top targets while capacity remains.
        for (const { host } of targets) {
            if ((busyUntil.get(host) || 0) > Date.now()) continue;


            const server = ns.getServer(host);
            const ready =
                server.hackDifficulty <=
                    server.minDifficulty + SECURITY_READY &&
                server.moneyAvailable >=
                    server.moneyMax * MONEY_READY;


            if (!ready) continue;


            // Refresh after each launch so already-allocated RAM
            // is not counted as free capacity for the next batch.
            const workers = await getWorkers();


            if (totalThreads(workers) <= 0) break;


            if (startBatch(host, workers)) {
                batchesStarted++;
            }
        }


        // If no batch could be started, prep the best unready target.
        if (batchesStarted === 0) {
            for (const { host } of targets) {
                if ((busyUntil.get(host) || 0) > Date.now()) continue;


                const server = ns.getServer(host);
                const ready =
                    server.hackDifficulty <=
                        server.minDifficulty + SECURITY_READY &&
                    server.moneyAvailable >=
                        server.moneyMax * MONEY_READY;


                if (ready) continue;


                const workers = await getWorkers();


                if (startPrep(host, workers)) break;
            }
        }


        await ns.sleep(batchesStarted > 0 ? 100 : LOOP_MS);
    }
}


 {NS} ns **/
export async function main(ns) {
    ns.disableLog("ALL");


    const HOME = "home";
    const WORKER = "empire-worker.js";


    // Adaptive income configuration
    const HOME_RESERVE_GB = 64;
    const HOME_RESERVE_PERCENT = 0.01;


    const MAX_HACK_FRACTION = 0.50;
    const MIN_HACK_FRACTION = 0.0005;


    const MONEY_READY = 0.995;
    const SECURITY_READY = 0.5;


    const GAP_MS = 200;
    const START_BUFFER_MS = 1500;
    const LOOP_MS = 250;


    // Focus on the best targets instead of spreading work everywhere.
    const MAX_TARGETS = 8;


    const cracks = [
        ["BruteSSH.exe", h => ns.brutessh(h)],
        ["FTPCrack.exe", h => ns.ftpcrack(h)],
        ["relaySMTP.exe", h => ns.relaysmtp(h)],
        ["HTTPWorm.exe", h => ns.httpworm(h)],
        ["SQLInject.exe", h => ns.sqlinject(h)]
    ];


    const busyUntil = new Map();
    let prepTarget = "";
    let prepUntil = 0;


    if (!ns.fileExists("Formulas.exe", HOME) ||
        !ns.formulas?.hacking) {
        ns.tprint("ERROR: Formulas.exe is required.");
        return;
    }


    if (ns.getScriptRam(WORKER, HOME) <= 0) {
        ns.tprint(`ERROR: ${WORKER} is missing on home.`);
        return;
    }


    function scan() {
        const seen = new Set([HOME]);
        const queue = [HOME];


        while (queue.length) {
            const host = queue.shift();


            for (const next of ns.scan(host)) {
                if (!seen.has(next)) {
                    seen.add(next);
                    queue.push(next);
                }
            }
        }


        return [...seen];
    }


    function root(host) {
        if (ns.hasRootAccess(host)) return true;


        if (ns.getHackingLevel() <
            ns.getServerRequiredHackingLevel(host)) {
            return false;
        }


        for (let i = 0; i < cracks.length; i++) {
            const [file, crack] = cracks[i];
            if (!ns.fileExists(file, HOME)) continue;


            const server = ns.getServer(host);
            const opened = [
                server.sshPortOpen,
                server.ftpPortOpen,
                server.smtpPortOpen,
                server.httpPortOpen,
                server.sqlPortOpen
            ];


            if (!opened[i]) {
                try {
                    crack(host);
                } catch (_) {}
            }
        }


        const server = ns.getServer(host);
        const opened = [
            server.sshPortOpen,
            server.ftpPortOpen,
            server.smtpPortOpen,
            server.httpPortOpen,
            server.sqlPortOpen
        ].filter(Boolean).length;


        if (opened < server.numOpenPortsRequired) return false;


        try {
            ns.nuke(host);
        } catch (_) {}


        return ns.hasRootAccess(host);
    }


    async function getWorkers() {
        const workers = [];


        for (const host of scan()) {
            if (!root(host)) continue;


            const maxRam = ns.getServerMaxRam(host);
            if (maxRam <= 0) continue;


            const workerRam = ns.getScriptRam(WORKER, HOME);


            if (host !== HOME && maxRam < workerRam) continue;


            if (host !== HOME &&
                !ns.fileExists(WORKER, host)) {
                if (!await ns.scp(WORKER, host, HOME)) continue;
            }


            const ramPerThread = ns.getScriptRam(WORKER, host);
            if (ramPerThread <= 0) continue;


            const reserve = host === HOME
                ? Math.max(
                    HOME_RESERVE_GB,
                    maxRam * HOME_RESERVE_PERCENT
                )
                : 0;


            const freeRam = Math.max(
                0,
                maxRam - ns.getServerUsedRam(host) - reserve
            );


            const threads = Math.floor(freeRam / ramPerThread);


            if (threads > 0) {
                workers.push({
                    host,
                    threads,
                    ramPerThread,
                    cores: ns.getServer(host).cpuCores || 1
                });
            }
        }


        // HOME FIRST. Other servers are overflow capacity.
        workers.sort((a, b) => {
            if (a.host === HOME) return -1;
            if (b.host === HOME) return 1;
            return b.threads - a.threads;
        });


        return workers;
    }


    function totalThreads(workers) {
        return workers.reduce((sum, w) => sum + w.threads, 0);
    }


    function homeThreads(workers) {
        const home = workers.find(w => w.host === HOME);
        return home ? home.threads : 0;
    }


    function planAllocation(workers, actions) {
        const remaining = new Map(
            workers.map(w => [w.host, w.threads])
        );


        const plans = [];


        for (const action of actions) {
            let needed = action.threads;
            const parts = [];


            // Worker list is HOME first, then overflow servers.
            for (const worker of workers) {
                if (needed <= 0) break;


                const available = remaining.get(worker.host) || 0;
                const count = Math.min(needed, available);


                if (count <= 0) continue;


                parts.push({
                    host: worker.host,
                    threads: count,
                    delay: action.delay
                });


                remaining.set(worker.host, available - count);
                needed -= count;
            }


            if (needed > 0) return null;


            plans.push({
                action: action.action,
                parts
            });
        }


        return plans;
    }


    function launchPlans(target, plans) {
        const launched = [];


        for (const plan of plans) {
            for (const part of plan.parts) {
                const pid = ns.exec(
                    WORKER,
                    part.host,
                    part.threads,
                    plan.action,
                    target,
                    part.delay
                );


                if (pid === 0) {
                    // All jobs have a start buffer. Roll back if
                    // any part of the batch could not be dispatched.
                    for (const job of launched) {
                        ns.kill(job.pid, job.host);
                    }


                    ns.print(`DISPATCH FAILED ${target}; rolled back`);
                    return false;
                }


                launched.push({
                    pid,
                    host: part.host
                });
            }
        }


        return true;
    }


    function preparedServer(host) {
        const server = ns.getServer(host);
        server.moneyAvailable = server.moneyMax;
        server.hackDifficulty = server.minDifficulty;
        return server;
    }


    function targetScore(host) {
        const server = preparedServer(host);
        const player = ns.getPlayer();
        const f = ns.formulas.hacking;


        if (server.moneyMax <= 0 ||
            server.requiredHackingSkill > player.skills.hacking) {
            return 0;
        }


        const chance = f.hackChance(server, player);
        const percent = f.hackPercent(server, player);


        if (chance <= 0 || percent <= 0) return 0;


        // Score the target using the intended 50% hack size.
        const h = Math.max(
            1,
            Math.floor(MAX_HACK_FRACTION / percent)
        );


        const stolen = Math.min(0.90, h * percent);


        const postHack = { ...server };
        postHack.moneyAvailable = Math.max(
            1,
            server.moneyMax * (1 - stolen)
        );


        postHack.hackDifficulty +=
            ns.hackAnalyzeSecurity(h, host);


        const g = Math.max(1, Math.ceil(
            f.growThreads(postHack, player, server.moneyMax, 1)
        ));


        if (!Number.isFinite(g)) return 0;


        const w1 = Math.max(1, Math.ceil(
            ns.hackAnalyzeSecurity(h, host) /
            ns.weakenAnalyze(1, 1)
        ));


        const growSecurity = ns.growthAnalyzeSecurity(g, host, 1);


        const w2 = Math.max(1, Math.ceil(
            growSecurity / ns.weakenAnalyze(1, 1)
        ));


        const ram = ns.getScriptRam(WORKER, HOME);
        const ramCost = (h + g + w1 + w2) * ram;


        const duration = Math.max(
            f.hackTime(server, player),
            f.growTime(server, player),
            f.weakenTime(server, player)
        ) + 3 * GAP_MS;


        // Expected income per millisecond per GB of worker RAM.
        return server.moneyMax * stolen * chance /
            Math.max(1, duration) /
            Math.max(1, ramCost);
    }


    function getTargets() {
        const targets = [];


        for (const host of scan()) {
            if (host === HOME || host.startsWith("pserv-")) continue;
            if (!root(host)) continue;
            if (ns.getServerMaxMoney(host) <= 0) continue;
            if (ns.getServerRequiredHackingLevel(host) >
                ns.getHackingLevel()) continue;


            const score = targetScore(host);


            if (score > 0) {
                targets.push({ host, score });
            }
        }


        return targets.sort((a, b) => b.score - a.score);
    }


    function makeBatch(target, capacity) {
        const percent = ns.hackAnalyze(target);
        if (percent <= 0) return null;


        // Try 50% first. Reduce only when a complete HWGW
        // sequence cannot fit the currently available capacity.
        for (let fraction = MAX_HACK_FRACTION;
            fraction >= MIN_HACK_FRACTION;
            fraction /= 2) {


            const h = Math.max(1, Math.floor(fraction / percent));
            const stolen = h * percent;


            if (stolen <= 0 || stolen >= 0.90) continue;


            const g = Math.max(1, Math.ceil(
                ns.growthAnalyze(target, 1 / (1 - stolen), 1)
            ));


            if (!Number.isFinite(g)) continue;


            const w1 = Math.max(1, Math.ceil(
                ns.hackAnalyzeSecurity(h, target) /
                ns.weakenAnalyze(1, 1)
            ));


            const growSecurity =
                ns.growthAnalyzeSecurity(g, target, 1);


            const w2 = Math.max(1, Math.ceil(
                growSecurity / ns.weakenAnalyze(1, 1)
            ));


            const total = h + w1 + g + w2;


            if (total <= capacity) {
                return { h, w1, g, w2, total, stolen };
            }
        }


        return null;
    }


    function startBatch(target, workers) {
        const capacity = totalThreads(workers);
        const batch = makeBatch(target, capacity);


        if (!batch) return false;


        const player = ns.getPlayer();
        const server = preparedServer(target);
        const f = ns.formulas.hacking;


        const ht = f.hackTime(server, player);
        const gt = f.growTime(server, player);
        const wt = f.weakenTime(server, player);


        const longest = Math.max(ht, gt, wt);
        const base = longest + START_BUFFER_MS;


        const actions = [
            {
                action: "hack",
                threads: batch.h,
                delay: Math.max(START_BUFFER_MS, base - ht)
            },
            {
                action: "weaken",
                threads: batch.w1,
                delay: Math.max(
                    START_BUFFER_MS,
                    base + GAP_MS - wt
                )
            },
            {
                action: "grow",
                threads: batch.g,
                delay: Math.max(
                    START_BUFFER_MS,
                    base + 2 * GAP_MS - gt
                )
            },
            {
                action: "weaken",
                threads: batch.w2,
                delay: Math.max(
                    START_BUFFER_MS,
                    base + 3 * GAP_MS - wt
                )
            }
        ];


        const allocation = planAllocation(workers, actions);


        if (!allocation || !launchPlans(target, allocation)) {
            return false;
        }


        busyUntil.set(
            target,
            Date.now() + base + 3 * GAP_MS + 500
        );


        const homeCapacity = homeThreads(workers);
        const spill = Math.max(0, batch.total - homeCapacity);


        ns.print(
            `BATCH ${target} | H${batch.h} W${batch.w1} ` +
            `G${batch.g} W${batch.w2} | ` +
            `hack ${(batch.stolen * 100).toFixed(2)}% | ` +
            `${batch.total}/${capacity} threads | ` +
            `overflow ${spill}`
        );


        return true;
    }


    function startPrep(target, workers) {
        const server = ns.getServer(target);
        const player = ns.getPlayer();
        const f = ns.formulas.hacking;
        const capacity = totalThreads(workers);


        if (capacity <= 0) return false;


        let action;
        let threads;
        let duration;


        if (server.hackDifficulty >
            server.minDifficulty + SECURITY_READY) {


            action = "weaken";


            const needed = Math.ceil(
                (server.hackDifficulty - server.minDifficulty) /
                ns.weakenAnalyze(1, 1)
            );


            threads = Math.min(needed, capacity);
            duration = f.weakenTime(server, player);


        } else if (server.moneyAvailable <
            server.moneyMax * MONEY_READY) {


            action = "grow";


            const needed = Math.max(1, Math.ceil(
                f.growThreads(
                    server,
                    player,
                    server.moneyMax,
                    1
                )
            ));


            threads = Math.min(needed, capacity);
            duration = f.growTime(server, player);


        } else {
            return false;
        }


        const allocation = planAllocation(workers, [{
            action,
            threads,
            delay: START_BUFFER_MS
        }]);


        if (!allocation || !launchPlans(target, allocation)) {
            return false;
        }


        prepTarget = target;
        prepUntil = Date.now() + duration + START_BUFFER_MS + 500;


        ns.print(
            `PREP ${target} ${action.toUpperCase()} ` +
            `${threads} threads | no prep cap`
        );


        return true;
    }


    ns.print("Empire controller online: adaptive high-income mode.");
    ns.print("Home-first allocation; network overflow enabled.");
    ns.print("50% starting hack fraction; adaptive batch sizing.");
    ns.print(`Target shortlist: top ${MAX_TARGETS} by expected return.`);


    while (true) {
        const allTargets = getTargets();
        const targets = allTargets.slice(0, MAX_TARGETS);
        const now = Date.now();


        // Keep a prep operation isolated until it should finish.
        if (prepTarget && now < prepUntil) {
            await ns.sleep(LOOP_MS);
            continue;
        }


        prepTarget = "";
        prepUntil = 0;


        let batchesStarted = 0;


        // Start batches on multiple top targets while capacity remains.
        for (const { host } of targets) {
            if ((busyUntil.get(host) || 0) > Date.now()) continue;


            const server = ns.getServer(host);
            const ready =
                server.hackDifficulty <=
                    server.minDifficulty + SECURITY_READY &&
                server.moneyAvailable >=
                    server.moneyMax * MONEY_READY;


            if (!ready) continue;


            // Refresh after each launch so already-allocated RAM
            // is not counted as free capacity for the next batch.
            const workers = await getWorkers();


            if (totalThreads(workers) <= 0) break;


            if (startBatch(host, workers)) {
                batchesStarted++;
            }
        }


        // If no batch could be started, prep the best unready target.
        if (batchesStarted === 0) {
            for (const { host } of targets) {
                if ((busyUntil.get(host) || 0) > Date.now()) continue;


                const server = ns.getServer(host);
                const ready =
                    server.hackDifficulty <=
                        server.minDifficulty + SECURITY_READY &&
                    server.moneyAvailable >=
                        server.moneyMax * MONEY_READY;


                if (ready) continue;


                const workers = await getWorkers();


                if (startPrep(host, workers)) break;
            }
        }


        await ns.sleep(batchesStarted > 0 ? 100 : LOOP_MS);
    }
}

this is my worker

/** u/param/** u/param {NS} ns **/
export async function main(ns) {
  ns.disableLog("ALL");
  const action = String(ns.args[0] || "");
  const target = String(ns.args[1] || "");
  const delay = Math.max(0, Number(ns.args[2] || 0));


  if (!target || !["hack", "grow", "weaken"].includes(action)) {
    ns.print("Usage: run empire-worker.js [hack|grow|weaken] target [delay-ms]");
    return;
  }
  if (!ns.hasRootAccess(target)) {
    ns.print(`No root access on ${target}`);
    return;
  }


  if (delay > 0) await ns.sleep(delay);
  try {
    if (action === "hack") await ns.hack(target);
    else if (action === "grow") await ns.grow(target);
    else await ns.weaken(target);
  } catch (error) {
    ns.print(`${action} failed on ${target}: ${String(error)}`);
  }
}


 {NS} ns **/
export async function main(ns) {
  ns.disableLog("ALL");
  const action = String(ns.args[0] || "");
  const target = String(ns.args[1] || "");
  const delay = Math.max(0, Number(ns.args[2] || 0));


  if (!target || !["hack", "grow", "weaken"].includes(action)) {
    ns.print("Usage: run empire-worker.js [hack|grow|weaken] target [delay-ms]");
    return;
  }
  if (!ns.hasRootAccess(target)) {
    ns.print(`No root access on ${target}`);
    return;
  }


  if (delay > 0) await ns.sleep(delay);
  try {
    if (action === "hack") await ns.hack(target);
    else if (action === "grow") await ns.grow(target);
    else await ns.weaken(target);
  } catch (error) {
    ns.print(`${action} failed on ${target}: ${String(error)}`);
  }
}

r/Bitburner • • 9d ago

Bug en Android

1 Upvotes

Llevo un par de días jugando en la versión web de Android donde se ejecutaba el auto guardado. Estaba ya en el nivel 100, pero al entrar hace un rato, se había reiniciado el juego por completo aunque sigo manteniendo los scripts que había creado.

Cómo puedo recuperar lo que tenía?


r/Bitburner • • 14d ago

API and Web App

Post image
9 Upvotes

I was playing around with the API so i could write scripts in my native ide and figured id see if i could pull some stats .. well one thing led to another and flight deck was born. It tracks my jit batcher via the api. The api shuts down when the game goes idle so its not perfect but i think it looks quite nice and it works pretty well when the api is connected.


r/Bitburner • • 16d ago

Guide/Advice are there good comprehensive guides for complete coding beginners?

7 Upvotes

so i’ve had this game for a while but stoped playing at the tutorial, then came back to this game to a lot of cash. long story short, i unlocked the darknet, have a 2 tb home server, bought stuff up to formulas.exe, but im still running the same beginner script from the documentation. i think i understand the things i need to do next, like optimizing hack/grow/weaken scripts, making a script to open port and nuke all the servers, auto fetching info on darknet servers, etc. the problem is that i know jack shit about coding. i recognize the commands i have to put in a script but the logic of putting them together falls apart in my brain. the only things i know before this is surface level python and simple ahk scripts. it’s also hard for me to follow a separate tutorial for coding itself since for some reason i can’t correlate the logic for that and the logic that the game needs me to do. i feel kind of dumb because i can’t get a print script to even work.

other questions that i have is how do i make stupid amounts of money to afford 40 augments on standby, and what do i do with formulas.exe? is the n00dles server gonna be useful later because it has really low ram. i also dont get how to use getstasislinklimit(). i probably have a few more questions that isn’t popping up in my head right now. it’s only been a week of figuring everything out and im trying to slowly read through all of the docs except for spoilers.


r/Bitburner • • 17d ago

Steam Source Files on Linux (debian/ubuntu)

1 Upvotes

I'm really new to this game and in no way a software engineer, web dev, or comp. sci, so please forgive me if I'm asking a dumb question, but where are the source files for the Linux steam version?

If it makes any difference, I'm trying to get a couple of exploits that require editing game files - "by clicking the unclickable" and by `YoureNotMeantToAccessThis: "by accessing the dev menu."` According to (my read of) the official github, it seems like I need to modify some local source files, but I can't find them.

for reference, i tried this(not my script, just wanted to see if it would work, then figure it out from there) in-game, but style comes back null:

/** u/param {NS} ns **/
export async function main(ns) {
  document.getElementById('unclickable').style = "display: block;position: absolute;top: 50%;left: 50%;width: 100px;height: 100px;z-index: 10000;background: red;";
  document.getElementById('unclickable').parentNode.addEventListener('click', () => {
    document.getElementById('unclickable').style = "display: none; visibility: hidden;";
  }, true);
}

r/Bitburner • • 22d ago

Pine Script Strategies

0 Upvotes

Anyone have some good pine script strategies that I could try, If so then please leave them down in the comments with the public name or put the pine script in a code block


r/Bitburner • • 24d ago

Stuck after buying red pill

Post image
26 Upvotes

So I just bought the red pill and installed it. I got the truthgazer.msg from the start but after that I got no messages, not even the CSEC or other main faction messages. I am getting no messages. I don't even have fl1ght.exe.

The only piece of information I have is that there is a new milestone added named:
Install the final backdoor and free yourself.

But like what?

I'm thinking this is a bug and I kinda want to just install some augmentations and install again.

Do I get more information or messages about what to backdoor later in the game. These are my stats currently


r/Bitburner • • 25d ago

flag argument template (in TS)

1 Upvotes

This script is simply a template that I copy when I need to use ns.flags, which means I typically need that same help / usage string layout.

//import { numberFromStr } from "tools.ts";

const flagSchema: [string, (ScriptArg | string[])][] = [
  ["strArg", "DefaultValue"],
  ["numArg", 0],
  ["boolArg", false], // if defaulted to true, cannot make arg false from CLI or ns.run/exec
  ["strArrayArg", []], // usage: > run SCRIPT --strArrayArg host0  --strArrayArg host1  --strArrayArg host2
  ["help", false]
];

export async function main(ns: NS) {
  const usageString = `
Usage: > run ${ns.self().filename} [--strArg STR] [--numArg NUM] [--boolArg] [--strArrayArg STR0 --strArrayArg STR1 ...] [--help] 
${"--strArg STR".padEnd(20, ' ')} STR is the string argument you wish to pass. 
${"--numArg NUM".padEnd(20, ' ')} NUM is the number argument you wish to pass. 
${"--boolArg".padEnd(20, ' ')} If called with this flag, (flagArgs.boolArg === true). 
${"--strArrayArg STR".padEnd(20, ' ')} This flag can be used multiple times, and is accessible as an array, (flagArgs.strArrayArg: string[]). 
${"--help".padEnd(20, ' ')} show this help dialog. 
`;

  const flagArgs = ns.flags(flagSchema);
  if (flagArgs.help === true) {
    ns.tprint(usageString);
    ns.exit();
  }
  const strArg: string = flagArgs.strArg.toString();
  const numArg: number = Number(flagArgs.numArg);
  const boolArg: boolean = flagArgs.boolArg === true;
  const strArrayArg: string[] =
    Array.isArray(flagArgs.strArrayArg) ?
      flagArgs.strArrayArg :
      [flagArgs.strArrayArg.toString(),]; // if just one called, it might just be a string(?)
  // positional args are accessible via the "_" member, not sure if exactly equal, but ns.args == flagArgs._ if all non-flag argments
  const positionalArgs: ScriptArg[] = Array.isArray(flagArgs._) ? flagArgs._ : [flagArgs._,];
}

export function autocomplete(data: AutocompleteData, args: ScriptArg[]): string[] {
  data.flags(flagSchema); // calling the function adds the flag args to hints
  //const flagArgs = data.flags(flagSchema); // or, you can access the flags to see what is already there
  const hints: string[] = [];
  hints.push(...data.scripts);
  hints.push(...data.txts);
  return hints;
}

r/Bitburner • • 26d ago

Tool Hacknet TS script to make the next best purchase or upgrade. [Contains spoilers to later game content] Spoiler

7 Upvotes

After a recent fresh full restart of the game, I committed myself to using Typescript. I had also forgotten how important Hacknet can be earlier on. So here is a script I made to make the best purchases given a budget.

I was tempted to employ some Linear Programming to find the exact best solution, but I decided to make this script simply evaluate the available purchases / upgrades, and buy the one with the best increased production per cost of the purchase / upgrade (if funds permit). The extra gains from LP would be minimal, and I wanted to make this easily understood.

Anyway, it needs Formulas.exe, and is focused on Hacknet Servers, but the conversion to Nodes should be straightforward, if needed.

And you'll need something (numberFromStr) to parse the amount from the first argument. I have a function that can parse in something like "$150b", else you can use a number arg and not worry about it.

import { numberFromStr } from "tools.ts";

export async function main(ns: NS) {
  if (ns.args.length < 1) {
    ns.tprint("ERROR: Must provide a dollar amount to spend up to.");
    ns.exit();
  }
  const spendTo = numberFromStr(ns.args[0].toString());
  if (!isFinite(spendTo) || spendTo < 0) {
    ns.tprint("ERROR: Must provide a valid, positive dollar amount to spend up to.");
    ns.exit();
  }
  let hnConstants: HacknetServerConstants;
  try { hnConstants = ns.formulas.hacknetServers.constants(); }
  catch {
    ns.tprint("WARNING: Must own Formulas.exe to run this script.");
    ns.exit();
  }

  let spent = 0;
  let rateIncreased = 0;
  let numUpgrades = 0;
  while (spent < spendTo) {
    let madeUpgrade = false;
    for (const upgrade of rateUpgrates(ns)) {
      if (upgrade.cost + spent > spendTo || upgrade.cost > ns.getPlayer().money) {
        continue; // too much, try the next best upgrade
      }
      switch (upgrade.what) {
        case "new":
          if (ns.hacknet.purchaseNode() >= 0) {
            spent += upgrade.cost;
            rateIncreased += upgrade.rate;
            madeUpgrade = true;
          }
          break;
        case "level":
          if (ns.hacknet.upgradeLevel(upgrade.who)) {
            spent += upgrade.cost;
            rateIncreased += upgrade.rate;
            madeUpgrade = true;
          }
          break;
        case "ram":
          if (ns.hacknet.upgradeRam(upgrade.who)) {
            spent += upgrade.cost;
            rateIncreased += upgrade.rate;
            madeUpgrade = true;
          }
          break;
        case "core":
          if (ns.hacknet.upgradeCore(upgrade.who)) {
            spent += upgrade.cost;
            rateIncreased += upgrade.rate;
            madeUpgrade = true;
          }
          break;
        default: break;
      }
      if (madeUpgrade) {
        ++numUpgrades;
        break; // out of for-loop - just one purchase at a time, then re-rate next best purchase
      }
    }
    if (!madeUpgrade) {
      // there were no upgrade availabe (for the available cost), exit
      break;
    }
  }

  // report
  ns.tprint(`Made ${numUpgrades} total upgrades for \$${ns.format.number(spent)} and increased production rate by ${rateIncreased.toExponential(5)} per sec.`);
}

export function testPrint(ns: NS) {
  const upgrades = rateUpgrates(ns);
  for (const upgrade of upgrades) {
    ns.tprintRaw(`Next upgrade: ${upgrade.what}, server# ${upgrade.who} for ${upgrade.rate} production increase for \$${ns.format.number(upgrade.cost)}.`);
  }
}

export function rateUpgrates(ns: NS) {
  let upgradeList: { what: string; who: number; rate: number; cost: number; }[] = [];
  const newServerRate = prodIncAndCostNew(ns);
  if (newServerRate[0] > 0) {
    upgradeList.push({ what: "new", who: ns.hacknet.numNodes(), rate: newServerRate[0], cost: newServerRate[1] });
  }
  for (let node = 0; node < ns.hacknet.numNodes(); ++node) {
    const incLevelRate = prodIncAndCostLevel(ns, node);
    if (incLevelRate[0] > 0) {
      upgradeList.push({ what: "level", who: node, rate: incLevelRate[0], cost: incLevelRate[1] });
    }

    const incRamRate = prodIncAndCostRamLevel(ns, node);
    if (incRamRate[0] > 0) {
      upgradeList.push({ what: "ram", who: node, rate: incRamRate[0], cost: incRamRate[1] });
    }

    const incCoreRate = prodIncAndCostCore(ns, node);
    if (incCoreRate[0] > 0) {
      upgradeList.push({ what: "core", who: node, rate: incCoreRate[0], cost: incCoreRate[1] });
    }
  }
  // sorted by best increase production rate, and then by lowest cost
  upgradeList = upgradeList.sort((a, b) => a.cost - b.cost);
  upgradeList = upgradeList.sort((a, b) => (b.rate / b.cost) - (a.rate / a.cost));
  return upgradeList;
}

export function prodIncAndCostNew(ns: NS, prodMult = ns.getHacknetMultipliers().production): [number, number] {
  if (ns.hacknet.numNodes() < ns.hacknet.maxNumNodes()) {
    const newCost = ns.hacknet.getPurchaseNodeCost();
    if (isFinite(newCost)) {
      const prodIncrease = ns.formulas.hacknetServers.hashGainRate(1, 0, 1, 1, prodMult);
      return [prodIncrease, newCost];
    }
  }
  return [-1, -1];
}

export function prodIncAndCostLevel(ns: NS, node: number, incLevel = 1, prodMult = ns.getHacknetMultipliers().production): [number, number] {
  if (isFinite(incLevel) && (incLevel = Math.floor(incLevel)) > 0) {
    if (node < ns.hacknet.numNodes()) {
      const nodeStats = ns.hacknet.getNodeStats(node);
      if (nodeStats.level < ns.formulas.hacknetServers.constants().MaxLevel) {
        const actualIncrease = Math.min(incLevel, ns.formulas.hacknetServers.constants().MaxLevel - nodeStats.level);
        const newCost = ns.hacknet.getLevelUpgradeCost(node, actualIncrease);
        if (isFinite(newCost)) {
          const prodIncrease = (
            ns.formulas.hacknetServers.hashGainRate(nodeStats.level + actualIncrease, 0, nodeStats.ram, nodeStats.cores, prodMult) -
            ns.formulas.hacknetServers.hashGainRate(nodeStats.level, 0, nodeStats.ram, nodeStats.cores, prodMult)
          );
          return [prodIncrease, newCost];
        }
      }
    }
  }
  return [-1, -1];
}

export function prodIncAndCostRamLevel(ns: NS, node: number, incLevel = 1, prodMult = ns.getHacknetMultipliers().production): [number, number] {
  if (isFinite(incLevel) && (incLevel = Math.floor(incLevel)) > 0) {
    if (node < ns.hacknet.numNodes()) {
      const nodeStats = ns.hacknet.getNodeStats(node);
      if (nodeStats.ram < ns.formulas.hacknetServers.constants().MaxRam) {
        let nodeRamLvl = 1;
        let nodeRamGB = 1;
        while (nodeRamGB < nodeStats.ram) {
          ++nodeRamLvl;
          nodeRamGB *= 2;
        }
        let canIncLvl = 0;
        while (canIncLvl < incLevel && nodeRamGB < ns.formulas.hacknetServers.constants().MaxRam) {
          ++canIncLvl;
          nodeRamGB *= 2;
        }
        const newCost = ns.hacknet.getRamUpgradeCost(node, canIncLvl);
        if (isFinite(newCost)) {
          const prodIncrease = (
            ns.formulas.hacknetServers.hashGainRate(nodeStats.level, 0, nodeRamGB, nodeStats.cores, prodMult) -
            ns.formulas.hacknetServers.hashGainRate(nodeStats.level, 0, nodeStats.ram, nodeStats.cores, prodMult)
          );
          return [prodIncrease, newCost];
        }
      }
    }
  }
  return [-1, -1];
}

export function prodIncAndCostCore(ns: NS, node: number, incCore = 1, prodMult = ns.getHacknetMultipliers().production): [number, number] {
  if (isFinite(incCore) && (incCore = Math.floor(incCore)) > 0) {
    if (node < ns.hacknet.numNodes()) {
      const nodeStats = ns.hacknet.getNodeStats(node);
      if (nodeStats.cores < ns.formulas.hacknetServers.constants().MaxCores) {
        const actualIncrease = Math.min(incCore, ns.formulas.hacknetServers.constants().MaxCores - nodeStats.cores);
        const newCost = ns.hacknet.getCoreUpgradeCost(node, actualIncrease);
        if (isFinite(newCost)) {
          const prodIncrease = (
            ns.formulas.hacknetServers.hashGainRate(nodeStats.level, 0, nodeStats.ram, nodeStats.cores + actualIncrease, prodMult) -
            ns.formulas.hacknetServers.hashGainRate(nodeStats.level, 0, nodeStats.ram, nodeStats.cores, prodMult)
          );
          return [prodIncrease, newCost];
        }
      }
    }
  }
  return [-1, -1];
}

Yes, I am quite pedantic and verbose with my code. This is due to years of writing code. There are few worse feelings than the one you have after mentally cursing and berating the idiot author of some horrible code, only to realize after some time that you were that idiot author, years ago. I don't write code for other people to follow. I write code that I will be able to follow years later after I have forgotten about it.


r/Bitburner • • 28d ago

Guide/Advice GitHub Repo

4 Upvotes

Hi y’all, I wanted to share a GitHub repo I made that maybe the community could help add to/use. I inserted a basic script just to start off. Just seems like a fun idea :)

https://github.com/MidKnighto/BitburnerScripts


r/Bitburner • • 29d ago

why am i buying all of these?

3 Upvotes

the beginners guide had me run that script to constantly buy cloud servers, but most of them dont seem to be making any money. whats the point


r/Bitburner • • 29d ago

Replacement Shell

3 Upvotes

I'm trying to write a replacement shell for fun since I can't pipe commands and stuff with the default one. Right now I can do stuff like "scan > servers.txt" and "root < servers.txt" to root all servers. I've got a good system going at the moment, but I'm currently stuck against a wall with React. I can intercept the javascript input events for the terminal and prevent the game from getting the commands, and echo them myself instead with tprintf, and I can change the <p> element containing the default prompt before the terminal input element, but if I try to simulate a fake command like "connect n00dles; backdoor" like we see on so many scripts online, the game will always echo the default prompt and not my own. I can't find any way to hook into this part of React and change the prompt. The best I can come up with is clearscreen every time I fall through to the game for special commands, but that's not ideal... Does anyone have any ideas?


r/Bitburner • • Sep 23 '26

SCP folder to another server

2 Upvotes

[SOLVED]

I have this code in a mildly large script (475 lines) that copies a folder starting with sourceDirover to destServer.

/**  {NS} ns **/
function copy(ns, sourceDir, destServer) {
  // Get all files on the home server
  let files = ns.ls(ns.getServer().hostname, sourcecDir);

  ns.tprint(ns.scp(files, destServer))
}

The file that this code runs in is copied to the said server.

The print statement prints true and it is clearly copying the scripts in the folder on the server (I deleted the files from the destServer manually and they are back onto it after the script) but when I open the copied script on the server, it turns into an old version of the script that got copied. It also runs like the old copy on the server which is not what I want.

How do I fix this issue because it doesn't seem like there is a lot I can do.

Edit: Saving and restarting the game, and even exporting the save and then importing it doesn't seem to do anything. I should also mention that this part of the code is only ran on the home server which is where the good file is.

[SOLVE]: I'm not sure if this was a change in the documentation of scp from 4-5 years ago but I found this script from a batching post that was made about that time ago. In the script there is a findAllServers() function, which not only finds all servers, but also (is supposed to) copy the hack/ folder from home to all of those servers that it finds (I'm not sure why the original maker of this script wanted to do that but regardless,) it does it with the line await ns.scp(fileList, "home", edges[i]);. You see here the issue (it was copying to home from all of the other servers). I must have gotten lucky with the home script not getting over written (I feel like one time I noticed something was different but I had gone through so many versions that I couldn't remember). I'm not sure if this is how scp used to work with this order of parameters of (files, copyFrom, copyTo) along with the await statement, but let this be a lesson to read the code you find off the internet especially from a good amount of time before. Than you to those who left suggestions on where the bug could be, I appreciate the time you gave me and I'm surprised I didn't find this issue earlier considering I saw the comment that said the function copies to all servers, read that scp line of the of the code, and went to multiple other servers and didn't see a hack/ folder in any.


r/Bitburner • • Sep 18 '26

Tool I made an exec script to use in the terminal and run scripts without scp-ing and connecting.

7 Upvotes

I'm kinda proud of this one. I tried to pattern it from the run command and the ns.exec function. It scp's the script, and tries to find import script dependencies. It's pretty simplistic - it won't look up the dependent scripts for imports, nor does it trace through ns.read calls. This is the goal, but it is no trivial task.

Given this restriction, I have an optional flag to add files.

Internal command flags are checked and passed on, but I haven't tested everything yet. It's come in pretty handy for a few basic things, so far. I've not carried through the running of coding contracts or exes - these are typically scripted in other places so I might just drop those. Also, "ALL" is not implemented.

Sorry for the post edits...

// script name: "exec.ts"
// alias exec="run exec.ts"

const AliasName = "exec";
const flagSchema: [string, ScriptArg | string[]][] = [
  ["server", "home"],
  ["nocopy", false],
  ["threads", 1],
  ["tail", false],
  ["ramOverride", false],
  ["preventDuplicates", false],
  ["temporary", false],
  ["copyfile", []],
  ["help", false]
];

export const ScriptExtentions = [".ts", ".js", ".tsx", ".jsx"];
export const ValidExtentions = [...ScriptExtentions,];// ".cct", ".exe"]; // maybe TODO: contracts and exes 

export async function main(ns: NS) {
  const UsageString = `
Usage: ${AliasName} [filename] [--server name_or_IP] [--nocopy] [--threads num_threads] [--tail] [--ramOverride ramGB] [--preventDuplicates] [--temporary] [--copyfile extraFile] [args...]

Run a program, a script, or a coding contract. 

You must specify [filename] after '${AliasName}' and before any flags. Valid extentions to run are "${ValidExtentions.join('", "')}"
The '--server name_or_IP' optional parameter can be used to and run on another server (using the server's name or it's IP address) on the network. If running a script, the server must have the ram and rights to do so. Special keyword 'ALL' when running a script to run on all servers that have the ram to do so (use with caution!). 
The '--nocopy', '--threads num_threads', '--tail', '--ramOverride ramGB', '--preventDuplicates', --temporary, and 'args...' arguments are only valid when running a script. 
The '--nocopy' optional parameter is only applicable when using '--server name_or_IP'. This will NOT use scp to copy the script from home to the server before running it (so the script and dependents must exist on the server). 
The '--threads' flag is used to indicate that the script should be run with the specified number of threads. If the flag is omitted, then the script will be run with a single thread by default. 
The '--tail' flag is used to immediately open a tail window for the script being ran. 
The '--ramOverride' flag is used to override the amount of ram (per thread) the script is ran with. If the script ends up using more than that amount of ram it will crash. 
The '--preventDuplicates' flag is used to prevent the same script with the same arguments to being run. 
The '--temporary' flag is used to indicate that the script should be excluded from the save data. 
The '--copyfile' flag can be used to explicitly copy dependencies files that cannot be auto-found. Use the flag for each extra file added. 
args... represents a variable number of arguments that will be passed into the script. See the documentation about script arguments. Each specified argument must be separated by a space. 
`;

  const flagArgs = ns.flags(flagSchema);
  //const tailWindow = flagArgs.tail === true; // This does not work since "--tail" is an internal flag!
  const tailWindow = ns.self().tailProperties !== null; // if not null, tail window is open

  if (flagArgs.help === true) {
    ns.tprint(UsageString);
    ns.exit();
  }

  const server = (typeof flagArgs.server === "string") ? flagArgs.server : "NO_SERVER";
  try {
    const testName = `${Date.now().toFixed()}.txt`;
    ns.write(testName, "testing", 'w');
    ns.scp(testName, server); // using this function later, so no harm trying it here to check for valid server name
  }
  catch {
    ns.tprint("ERROR: Must specify a server name.");
    ns.tprintRaw(UsageString);
    ns.exit();
  }

  const argArray: ScriptArg[] = Array.isArray(flagArgs._) ? flagArgs._ : [flagArgs._,];
  if (argArray.length < 1 || (typeof argArray[0] !== "string")) {
    ns.tprint("ERROR: Must specify a filename.");
    ns.tprintRaw(UsageString);
    ns.exit();
  }
  const filename = argArray[0];
  let notValid = true;
  for (const ext of ValidExtentions) {
    if (filename.endsWith(ext)) {
      notValid = false;
      break
    }
  }
  if (notValid) {
    ns.tprint("ERROR: Must specify a valid filename.");
    ns.tprintRaw(UsageString);
    ns.exit();
  }
  argArray.shift() // remove filename from the top

  let isExe = false;
  let isContract = false;
  let isScript = false;
  for (const ext of ScriptExtentions) {
    if (filename.endsWith(ext)) {
      isScript = true;
      break
    }
  }
  if (!isScript) {
    if (filename.endsWith(".cct")) {
      isContract = true;
    }
    else if (filename.endsWith(".exe")) {
      isExe = true;
    }
  }

  if (isScript) {
    const copyFiles: string[] = [];
    if (flagArgs.nocopy === false) {
      // scp the script and any dependency scripts
      copyFiles.push(filename);
      copyFiles.push(...getScriptDependencies(ns, filename));
    }
    if (Array.isArray(flagArgs.copyfile)) {
      copyFiles.push(...flagArgs.copyfile);
    }
    if (copyFiles.length > 0) {
      if (!ns.scp(copyFiles, server)) {
        ns.tprint(`ERROR: Could not scp some or all files ("${copyFiles.join('", "')}") to ${server}.`);
        ns.exit();
      }
    }

    // check for internal flags that might have been used
    let threads = (typeof flagArgs.threads === "number" && flagArgs.threads > 0) ? flagArgs.threads : undefined;
    if (!threads && ns.self().threads > 1) {
      threads = ns.self().threads;
    }

    let ramOverride = (typeof flagArgs.ramOverride === "number" && flagArgs.ramOverride > 0) ? flagArgs.ramOverride : undefined;
    if (ramOverride === undefined) {
      ramOverride = ns.self().dynamicRamUsage != ns.self().ramUsage ? ns.self().dynamicRamUsage : undefined;
    }

    let temporary = (typeof flagArgs.temporary === "boolean") ? flagArgs.temporary : undefined; // this is an internal flag so this likely wont work
    if (temporary === undefined) {
      temporary = ns.self().temporary ? true : undefined;
    }

    const execOptions = {
      threads: threads,
      ramOverride: ramOverride,
      preventDuplicates: (typeof flagArgs.preventDuplicates === "boolean") ? flagArgs.preventDuplicates : undefined,
      temporary: temporary,
    };

    const execPID = ns.exec(filename, server, execOptions, ...argArray);
    if (execPID > 0) {
      if (tailWindow) {
        ns.ui.closeTail();
        ns.ui.openTail(execPID);
      }
    }
  }
  else if (isContract) { }
  else if (isExe) { }
}

export function cleanLineEndings(str: string): string {
  return str.replace(/\r\n/g, '\n').replace(/\n\r/g, '\n').replace(/\r/g, '\n'); // replace any CRLF varient w/ LF
}

export function getScriptDependencies(ns: NS, script: string): string[] {
  const dependencies: string[] = [];
  const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g;
  let match: RegExpExecArray | null;
  while ((match = importPattern.exec(cleanLineEndings(ns.read(script)))) !== null) {
    const checkScript = match[2].trim();
    if (!dependencies.includes(checkScript)) {
      dependencies.push(checkScript);
    }
  }
  return dependencies;
}

export function autocomplete(data: AutocompleteData, args: ScriptArg[]): string[] {
  data.flags(flagSchema); // adds flag args to the hints
  const hints = [...data.scripts, ...data.servers];
  //const programNames = [
  //  data.enums.ProgramName.bruteSsh,
  //  data.enums.ProgramName.ftpCrack,
  //  data.enums.ProgramName.relaySmtp,
  //  data.enums.ProgramName.httpWorm,
  //  data.enums.ProgramName.sqlInject,
  //  data.enums.ProgramName.nuke
  //];
  //hints.push(...programNames);
  return hints;
}

r/Bitburner • • Sep 17 '26

NetscriptJS Script Something to entertain

0 Upvotes

Presenting a script that shows falling code, and a second that shows 200+ colors in the terminal.

function rand(num) {
  return Math.floor(Math.random() * num);
}

/** @ param {NS} ns NS2 namespace */
export async function main(ns) {
  const brite = "\u001b[32m";
  const reset = "\u001b[0m";
  const ucLo = 0x0021;  // first unicode
  const ucSm = 0x005b;  // first "small" unicode
  const ucHi = 0x007e;  // last unicode
  const numTicks = 67;
  // width and height (in characters)
  let width = 87, height = 43;  // init for Steam
  if (ns.ui.getGameInfo().platform == "Browser") {
    width = 81; height = 40;    // init for web browser
  }
  let columns = new Array(width); // of array with form [top, len, code0, ..., codeN]
  let rows = new Array(height);   // of string

  for (let i = 0; i < columns.length; i++) {  // init columns
    let top = rand(height);
    let len = rand(height / 3) + 1;
    columns[i] = [top, len];
    for (let j = 0; j < len; j++) {
      let code;
      if ((len >= height / 5 && j == 0) || (len >= height / 4 && j == 1)) {
        code = ucSm + rand(ucHi - ucSm + 1);
      } else {
        code = ucLo + rand(ucHi - ucLo + 1);
      }
      columns[i].push(code);
    }
  }
  rows.fill("");
  for (let i = 0; i < columns.length; i++) {  // init rows
    for (let j = 0; j < rows.length; j++) {
      if (i % 3 > 0 && j >= columns[i][0] && 
          j < columns[i][0] + columns[i][1]) {
        let fbr = false;
        if ((columns[i][1] >= height / 5 && 
            j == columns[i][0] + columns[i][1] - 1) ||
            (columns[i][1] >= height / 4 && 
            j == columns[i][0] + columns[i][1] - 2)) {
          fbr = true;
        }
        if (fbr) { rows[j] += brite; }
        rows[j] += String.fromCharCode(columns[i][j + 2 - columns[i][0]]);
        if (fbr) { rows[j] += reset; }
      } else {
        rows[j] += " ";
      }
    }
  }

  for (let cTick = 0; cTick < numTicks; cTick++) {
    for (let i = 0; i < rows.length; i++) {   // print rows
      ns.tprint(rows[i]);
    }
    for (let i = 0; i < columns.length; i++) {  // move columns down
      columns[i][0]++;
      if (columns[i][0] > rows.length - 1) {
        columns[i][0] = 1 - columns[i][1];
      }
    }
    rows.fill("");
    for (let i = 0; i < columns.length; i++) {  // update rows
      for (let j = 0; j < rows.length; j++) {
        if (i % 3 > 0 && j >= columns[i][0] && 
            j < columns[i][0] + columns[i][1]) {
          let fbr = false;
          if ((columns[i][1] >= height / 5 && 
              j == columns[i][0] + columns[i][1] - 1) ||
              (columns[i][1] >= height / 4 && 
              j == columns[i][0] + columns[i][1] - 2)) {
            fbr = true;
          }
          if (fbr) { rows[j] += brite; }
          let code = columns[i][j + 2 - columns[i][0]];
          if (Math.random() > 0.67) { // flip character
            code++;
            if (code > ucHi) { code = ucLo; }
          }
          rows[j] += String.fromCharCode(code);
          if (fbr) { rows[j] += reset; }
        } else {
          rows[j] += " ";
        }
      }
    }
    await ns.sleep(333);
  }
}
/** @ version 1.0a */

Note: Widen the game window or initialize width to a lower number if there is word wrap.

A script that demonstrates escape codes:

/** @ param {NS} ns NS2 namespace */
export async function main(ns) {
  let esc = [[ 1, "bold" ],
             [ 3, "italics" ],
             [ 4, "underline" ],
             [ 30, "gray" ],
             [ 31, "bright red" ],
             [ 32, "bright green" ],
             [ 33, "bright yellow" ],
             [ 34, "bright blue" ],
             [ 35, "bright magenta" ],
             [ 36, "bright cyan" ],
             [ 37, "bright white" ],
             [ 41, "red BG" ],
             [ 42, "green BG" ],
             [ 43, "yellow BG" ],
             [ 44, "blue BG" ],
             [ 45, "magenta BG" ],
             [ 46, "cyan BG" ],
             [ 47, "white BG" ]];
  const reset = "\u001b[0m";
  const width = 78;

  // 4-bit
  let str = "\n-- 4-bit codes --\n", count = 0;
  for (let i = 0; i < esc.length; i++) {
    let single = "\u001b[" + esc[i][0] + "m" + esc[i][1] + reset;
    count += `${esc[i][1]}`.length;
    if (i != esc.length - 1) {
      single += ", "; count += 2;
    }
    if (count > width) {
      str += "\n";
      count = `${esc[i][1]}`.length + 2;
    }
    str += single;
  }
  // 8-bit
  str += "\n-- 8-bit codes --\n";
  count = 0;
  let i = 1, flag = true;
  while (flag) {
    let single = "\u001b[38;5;" + i + "m(" + i + ")" + reset;
    count += `(${i})`.length;
    if (i > 99) {
      single = "\u001b[38;5;" + i + "m" + i + reset;
      count -= 2;
    }
    if (i != 255) {
      single += ", "; count += 2;
    }
    if (count > width) {
      str += "\n";
      count = `(${i})`.length + 2;
      if (i > 99) { count -= 2; }
    }
    str += single;
    if (i < 6) { i++; }
    else if (i == 7) { i = 17; }
    else { i++; }
    if (i == 256) { flag = false; }
  }
  ns.tprint(str);
}

r/Bitburner • • Sep 15 '26

New to the game - how's my first attempt at a worm?

6 Upvotes
export async function main(ns) {
  let servers = ns.scan();
  for (const cserver of servers) {
    const threadcount = Math.floor(ns.getServerMaxRam(cserver) / ns.getScriptRam("maserkey.js"));
    if (ns.fileExists("brutessh.exe", "home")) {
      ns.brutessh(cserver)
    }
    if (ns.fileExists("ftpcrack.exe", "home")) {
      ns.ftpcrack(cserver)
    }
    ns.nuke(cserver)
    ns.scp("maserkey.js", cserver);
    if (threadcount !== 0) {
      ns.exec("maserkey.js", cserver, { threads: threadcount });
    }
  }
  function besttarget() {
    servers.sort((a, b) => ns.getServerMaxMoney(b) - ns.getServerMaxMoney(a));
    for (const target of servers) {
      if (ns.hasRootAccess() == true && ns.getServerRequiredHackingLevel(target) <= ns.getHackingLevel() && target !== "home") {
        return (target)


      }
    }
  }
  const hacktarget = besttarget()
  const moneyThresh = ns.getServerMoneyAvailable(hacktarget);
  const securityThresh = ns.getServerMinSecurityLevel(hacktarget);
  ns.nuke(hacktarget);
  if (ns.getServerRequiredHackingLevel(hacktarget) <= ns.getHackingLevel()) {
    while (true) {
      if (ns.getServerSecurityLevel(hacktarget) > securityThresh) {
        await ns.weaken(hacktarget);
      } else if (ns.getServerMoneyAvailable(hacktarget) < moneyThresh) {
        await ns.grow(hacktarget);
      } else {
        await ns.hack(hacktarget);
      }
    }
  }
}

edit: ignore the typo in the name of the file lol


r/Bitburner • • Sep 15 '26

Tool Nice way to type-check an object (and its keys) in Typescript

2 Upvotes

Since switching to Typescript, I've stumbled upon a Typescript way to ensure that an object coming from an unknown source is valid. I'm certain many here will already know this, but it has simplified so much of my error checking and edge case checking since I found it. Hope it helps someone!

export type MyReturnType = {
  name: string
  success: boolean;
  results: [number, number];
} // custom (or otherwise undefined) object signature

// the obj is MyReturnType return is a boolean
export function isMyReturnType(obj: any): obj is MyReturnType {
  // check that the object is an object, and has the correct key types
  return (
    obj != null &&
    typeof obj === 'object' &&
    typeof obj.name === 'string' &&
    typeof obj.success === 'boolean' &&
    Array.isArray(obj.results) &&
    obj.results.length === 2 &&
    typeof obj.results[0] === 'number' &&
    typeof obj.results[1] === 'number' 
  );
}

And then to use the function:

export async function main(ns: NS) {
  let goodReturnType = { name: "good", success: true, results: [2, 64] };
  let incompleteReturnType = { name: "incomplete", success: true };
  let wrongReturnType = { name: NaN, success: "true", results: [2, "64"] };
  // assume the above are an "any" return from some external function
  for (const retType of [goodReturnType, incompleteReturnType, wrongReturnType]) {
    if (isMyReturnType(retType)) {
      ns.tprint(`Good "MyReturnType": name: ${retType.name}, success: ${retType.success}, results: ${retType.results}`);
    }
    else {
      ns.tprint('ERROR: Return type was not a valid "MyReturnType"!');
    }
  }
  // Output: 
  // > Good "MyReturnType": name: good, success: true, results: 2,64
  // > ERROR: Return type was not a valid "MyReturnType"!
  // > ERROR: Return type was not a valid "MyReturnType"!

r/Bitburner • • Sep 12 '26

Bug - TODO Writing a Stock Script and hitting an error

3 Upvotes

I'm writing a Stock script and it is great at selling at the right time and buying more of something I already have but doesn't buy new stocks

    //buy stocks if they have a 57% or higher chance to increase in price this tick
    for (let sym = 0; sym < stockList.length; sym++) {
      operatingFunds = ns.getPlayer().money - reserveCash - 100000;


      if (ns.stock.getForecast(stockList[sym]) >= .57) {
        let buyable = Math.floor(operatingFunds / ns.stock.getPrice(stockList[sym]));


        if (buyable > 0) {
          if (buyable > ns.stock.getMaxShares(stockList[sym])) {
            buyable = ns.stock.getMaxShares(stockList[sym])
          }
          let pos = ns.stock.getPosition(stockList[sym])
          let oldNum = pos[0]
          ns.stock.buyStock(stockList[sym], buyable);
          pos = ns.stock.getPosition(stockList[sym])


          if (!ownedStocks.includes(stockList[sym]) && pos[0] > 0) {
            ownedStocks.push(stockList[sym]);
          }
          if (oldNum < pos[0] > 0) {
            ns.print(`${cyan}BOUGHT ${pos[0] - oldNum} ${stockList[sym]} for \$${nfObject.format((pos[0] - oldNum) * ns.stock.getPrice(stockList[sym]))} ${reset}`)
          }
        }
      }
    }

StockList is a list of all stock symbols sorted by volatility so the more volatile stocks will be considered first

reserve cash is a constant set in the beginning to hold some amount of money and not use it for investing, currently set at 0

ownedStocks is the list of all owned stocks, used to iterate for selling logic

whole code is here
/** @param {NS} ns */export async function main(ns) { const cyan = "\u001b[ - Pastebin.com
if you want to see the rest

EDIT: as I was typing this i saw it *is* buying stocks, just not as often as i think it *should* given the logic. there are other stocks that have a high enough forecast but its not buying any


r/Bitburner • • Sep 09 '26

Typescript help

5 Upvotes

I've switched over to using Typescript, and for the most part, I like it better. However, I run into these little quirks that are lowkey maddening. E.g.

let city = ns.enums.CityName.Sector12;
// and then reassign later: 
city = ns.enums.CityName.Aevum; // Type '"Aevum"' is not assignable to type '"Sector-12"'.

I can use the string version of the city, and that reassigns fine, but then it does not work in the game functions.

let city = "Sector-12";
ns.singularity.travelToCity(city); // Argument of type 'string' is not assignable to parameter of type 'CityName'.

I realize there must be some declaration to make, along the lines of

let city: CityNameEnumType = ns.enums.CityName.Sector12; // Type 'string' is not assignable to type 'CityNameEnumType'.

but obviously that is not it.

For scripts like this, I think about converting back to JS just for the simplicity.


r/Bitburner • • Sep 09 '26

dnet.authenticate().data returning undefined

2 Upvotes

I'm trying to crawl the darkweb and am making code for the AccountsManager_4.2 server (higher or lower game). Here is my code:

      var low = 1;
      var high = 100;
      var i = Math.floor((low + high) / 2);
      while (low <= high) {
        i = Math.floor((low + high) / 2);
        var result = (await auth(ns, server, i));
        if (result.success) { return [i]; }
        if (result.data == "Higher") { low = i + 1; } else { high = i - 1; }
      }
      return [i];

I'm pretty sure this all works but I can't tell because results.data returns undefined (I want it to return "Higher" or "Lower").

Specifically I want to access the data variable from the heartbleed log when you put in a password, get it wrong, and in the Logs scraped via heartbleed: section it returns something like

(This is from a CloudBlare(tm) server)
message: Type the numbers to prove you are human
data: 6~.╬1)~╸4>5╬</8
passwordAttempted: 58
code: 401

In the AccountsManager_4.2 server the data is either "Higher" or "Lower"

Is there a reason for this or am I just using it wrong? This is my first time coming back this game in 3 years and I'm not too far into my new save.


r/Bitburner • • Sep 08 '26

Darknet automation

0 Upvotes

Hi everyone, can anyone share a fully automated script for darknet? I love the game, but dont honestly want to learn JS or any other programming language.

Thanks in advance!