mizzajl sync

This commit is contained in:
2024-10-05 19:32:54 +02:00
parent 53e9822d01
commit 80b3ca8873
83 changed files with 3639 additions and 3234 deletions

View File

@@ -1,97 +0,0 @@
/** @param {NS} ns */
export async function main(ns, allServers) {
ns.tail();
await scanRecursiveWrapper(ns);
let currentHackingLevel = ns.getHackingLevel();
let currentArray = [];
let currentHop = "";
let serverRoutes = JSON.parse(ns.read("ServerRouteList.txt"));
let allPaths = getPaths(serverRoutes);
let checkAll = ns.args[0];
for (const entry of allPaths) {
for (const name of entry) {
if (ns.singularity.connect(name) === false) {
ns.tprint("Error when trying to connect to: " + currentHop);
return
}
if (ns.getServer(name).hostname === "CSEC" || ns.getServer(name).hostname === "avmnite-02h" || ns.getServer(name).hostname === "I.I.I.I" || ns.getServer(name).hostname === "run4theh111z" || ns.getServer(name).hostname === "The-Cave" || checkAll === true ) {
if (!ns.getServer(name).backdoorInstalled) {
if (ns.getServerRequiredHackingLevel(name) < currentHackingLevel && ns.hasRootAccess(name) === true) {
ns.print("Trying to backdoor " + name)
await ns.singularity.installBackdoor(name);
ns.print("Success on " + name)
}
} else { continue }
}
}
}
ns.singularity.connect("home");
}
function getPaths(obj, path = []) {
const paths = [];
for (const key in obj) {
const newPath = [...path, key];
paths.push(newPath);
if (typeof obj[key] === 'object' && obj[key] !== null) {
paths.push(...getPaths(obj[key], newPath));
}
}
return paths;
}
/** @param {NS} ns */
async function scanRecursiveWrapper(ns) {
ns.rm("ServerRouteList.txt");
const home = "home";
let serverRouteList = { home: {} };
let knownServers = [];
let unscanned = [];
unscanned.push(home);
knownServers.push(home);
while (unscanned.length > 0) {
let currentServer = unscanned.pop();
let currentChildren = ns.scan(currentServer).filter(element => !knownServers.includes(element));
knownServers = knownServers.concat(currentChildren);
let keyPath = findKeyPath(serverRouteList, currentServer);
let childrenObject = currentChildren.reduce((a, v) => ({ ...a, [v]: {} }), {});
writeValueToPath(serverRouteList, keyPath, childrenObject);
for (let i = 0; i < currentChildren.length; i++) {
let child = currentChildren[i];
unscanned.push(child);
}
}
ns.write("ServerRouteList.txt", JSON.stringify(serverRouteList), "w");
}
function findKeyPath(json, key) {
if (typeof json !== 'object' || json === null) {
return null;
}
if (key in json) {
return key;
}
for (const property in json) {
if (json.hasOwnProperty(property)) {
const path = findKeyPath(json[property], key);
if (path !== null) {
return property + '*' + path;
}
}
}
return null;
}
function writeValueToPath(json, path, value) {
const parts = path.split('*');
let currentObject = json;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (currentObject[part] === undefined) {
currentObject[part] = {};
}
currentObject = currentObject[part];
}
currentObject[parts[parts.length - 1]] = value;
}

View File

@@ -1,14 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let cities = ["Sector-12", "Aevum", "Volhaven", "Chongqing", "New Tokyo", "Ishima"];
let corpName = "AgraNeo";
//ns.tprint(ns.corporation.getConstants())
//ns.corporation.getMaterial();
//ns.corporation.buyMaterial();
for (let city of cities) {
await ns.run("/corp/Smart.js",1,corpName,city);
await ns.tprint(ns.run("/corp/UpgradeOffice.js",1,corpName,city));
await ns.sleep(1000)
}
}

View File

@@ -1,8 +0,0 @@
import { getCracks, scanServerList, findBestTarget, crackingAndRooting, copyAndRunScript } from "/RamsesUtils.js";
/** @param {NS} ns */
export async function main(ns) {
let cracks = {};
cracks = getCracks(ns);
crackingAndRooting(ns, cracks, "", false);
}

View File

@@ -0,0 +1,88 @@
import { getGrowThreads } from "myLibrary.js";
/** @param {NS} ns */
export async function main(ns) {
//ns.tail();
const sOverrideTarget = ns.args[0]; // run on this target instead of best
let serverList = JSON.parse(ns.read("serverList.txt"));
let bestTarget = JSON.parse(ns.read("bestTarget.txt"));
let sTarget;
if (sOverrideTarget !== undefined) {
sTarget = sOverrideTarget;
}
else {
sTarget = bestTarget.serverName;
}
let sScript = "batch.js";
const sShareScript = "factionboost.js";
const sWeakenScript = "RMweaken.js";
const sGrowScript = "RMgrow.js";
const sHackScript = "RMhack.js";
const nWeakenThreads = 1; //5
const nHackThreads = 1; //1
const nGrowThreads = getGrowThreads(ns, sTarget, nHackThreads);
//ns.tprint("nGrowThreads = " + nGrowThreads);
const nWeakenScriptRAM = 1.75 * nWeakenThreads;
const nHackScriptRAM = 1.75 * nHackThreads;
const nGrowScriptRAM = 1.7 * nGrowThreads;
//ns.tprint("nGrowScriptRAM = " + nGrowScriptRAM);
const nFrequency = 50;
let nScriptsRAM;
let nMaxRam;
let nScripts;
for (const [name, entry] of Object.entries(serverList)) {
//let rootAccess = ns.hasRootAccess(currentServer);
//ns.tprint(entry.serverName + " sTarget = " + sTarget);
nMaxRam = ns.getServerMaxRam(entry.serverName);
//ns.tprint(entry.serverName + " nMaxRam = " + nMaxRam);
//nScriptsRAM = ns.getScriptRam(sScript, "home") + Math.max(nWeakenScriptRAM, nGrowScriptRAM, nHackScriptRAM);
//ns.tprint("nScriptsRAM = " + nScriptsRAM);
if (entry.maxRam >= 64 && serverList[name].rootAccess === true) {
if (sOverrideTarget !== undefined) {
sTarget = sOverrideTarget;
}
else {
sTarget = bestTarget.serverName;
}
//ns.tprint("sTarget = " + sTarget);
//ns.tprint("entry.serverName = " + entry.serverName);
ns.scriptKill(sScript, entry.serverName);
ns.scriptKill(sShareScript, entry.serverName);
ns.scp("myLibrary.js", entry.serverName, "home");
ns.scp(sScript, entry.serverName, "home");
ns.scp(sWeakenScript, entry.serverName, "home");
ns.scp(sGrowScript, entry.serverName, "home");
ns.scp(sHackScript, entry.serverName, "home");
ns.exec(sScript, entry.serverName, 1, sTarget, nFrequency, true, false);
await ns.sleep(1);
}
else if (entry.maxRam < 64 && entry.maxRam > 0 && serverList[name].rootAccess === true) {
ns.scriptKill(sScript, entry.serverName);
ns.scriptKill(sShareScript, entry.serverName);
ns.scriptKill(sWeakenScript, entry.serverName);
ns.scriptKill(sGrowScript, entry.serverName);
ns.scriptKill(sHackScript, entry.serverName);
ns.scp(sShareScript, entry.serverName);
let nShare = Math.floor(((ns.getServerMaxRam(entry.serverName) - ns.getServerUsedRam(entry.serverName)) / ns.getScriptRam(sShareScript)));
ns.tprint(entry.serverName + " nShare = " + nShare * ns.getScriptRam(sShareScript));
ns.exec(sShareScript, entry.serverName, nShare);
}
}
//ns.exec("serverlister.js", "home", 1);
}

View File

@@ -1,6 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
while (true) {
await ns.share();
}
}

View File

@@ -1,16 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let serverList = JSON.parse(ns.read("serverList.txt"));
for (const [name, entry] of Object.entries(serverList)) {
if (entry.rootAccess === true) {
ns.print("killed threads on: " + name + " " +ns.killall(name, true));
//move script and run
let maxRam = ns.getServerMaxRam(name);
if (maxRam > 0) {
ns.scp("FactionBoost.js", name, "home");
let maxProcesses = 1;
maxProcesses = Math.floor(maxRam / 4);
if (name === "home") maxProcesses = maxProcesses - 50;
ns.exec("FactionBoost.js", name, maxProcesses);
}}}}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
/** @param {NS} ns */
export async function main(ns) {
//8,5,1,1,2,6,7,2,5,4
//const sContractData = ns.codingcontract.getData(sContract, sTarget);
const sContractData = "8,5,1,1,2,6,7,2,5,4";
/*
8
5,1
1,2,6
7,2,5,4
*/
//nArray[0[2]]
const sArray = sContractData.split(',');
ns.tprint(sArray);
let nArray = [];
//Number
for (let i = 0; i < sArray.length; i++) {
nArray[i] = Number(sArray[i]);
}
ns.tprint(nArray);
let nLayers = [];
for (let l = 0; l < nArray.length; l++) {
}
}

View File

@@ -9,37 +9,41 @@ export async function main(ns) {
//declare variables
const sWeakenScript = "RMweaken.js";
const sGrowScript = "RMgrow.js";
const sBatch = "RMcontroller.js";
const nCores = oHome.cpuCores;
let nSecurity = ns.getServerSecurityLevel(sTarget);
const nMinSecurity = ns.getServerMinSecurityLevel(sTarget);
let nMoney = ns.getServerMoneyAvailable(sTarget);
const nMaxMoney = ns.getServerMaxMoney(sTarget);
const nWeakenSTR = ns.weakenAnalyze(1, nCores);
let nThreads = Math.ceil((nSecurity - nMinSecurity) / nWeakenSTR);
let nWeakenThreads = Math.ceil((nSecurity - nMinSecurity) / nWeakenSTR);
let nFreeRam = ns.getServerMaxRam("home") - ns.getServerUsedRam("home");
ns.tail("RMbreach.js", "home", sTarget);
ns.tail(ns.pid, oHome.hostname, sTarget);
//ns.resizeTail(815, 395);
//ns.moveTail(1925, 0);
// crack target
//ns.run(sCrack, 1, sTarget);
if (nThreads > 0 && nSecurity > nMinSecurity) {
const nDelay = ns.getWeakenTime(sTarget);
if (nWeakenThreads > 0 && nSecurity > nMinSecurity) {
ns.tprint("current security is: " + nSecurity);
ns.tprint("minimum security is: " + nMinSecurity);
ns.tprint("threads needed for weaken: " + nThreads);
ns.tprint(nThreads + " will reduce Security by " + ns.weakenAnalyze(nThreads, nCores));
ns.tprint("threads needed for weaken: " + nWeakenThreads);
ns.tprint(nWeakenThreads + " will reduce Security by " + ns.weakenAnalyze(nWeakenThreads, nCores));
let nScriptRAM = ns.getScriptRam(sWeakenScript, "home");
let nRequiredRAM = nScriptRAM * nThreads;
ns.tprint(nThreads + " of " + sWeakenScript + " requires " + nRequiredRAM + " GB of RAM");
let nRequiredRAM = nScriptRAM * nWeakenThreads;
ns.tprint(nWeakenThreads + " of " + sWeakenScript + " requires " + nRequiredRAM + " GB of RAM");
ns.tprint("weakening will take " + (nDelay / 1000 / 60) + " minutes");
if (nFreeRam > nRequiredRAM) {
ns.run(sWeakenScript, nThreads, sTarget);
ns.run(sWeakenScript, nWeakenThreads, sTarget);
await ns.sleep(Math.ceil(nDelay));
//await ns.sleep(Math.ceil(nDelay));
nSecurity = ns.getServerSecurityLevel(sTarget);
ns.tprint("Breach complete, security level is now at: " + nSecurity);
@@ -47,19 +51,53 @@ export async function main(ns) {
}
else {
ns.print("not enough RAM to run all threads at once, splitting into smaller chunks...");
while (nSecurity > nMinSecurity) {
nThreads /= (1+(nRequiredRAM / nFreeRam));
//nWeakenThreads /= (1+(nRequiredRAM / nFreeRam));
nMaxThreads = Math.min( );
ns.print(Math.ceil(nRequiredRAM / nFreeRam));
ns.print(nThreads);
ns.print(nThreads * nScriptRAM);
ns.run(sWeakenScript, Math.ceil(nThreads), sTarget);
ns.print(nWeakenThreads);
ns.print(nWeakenThreads * nScriptRAM);
ns.run(sWeakenScript, Math.ceil(nWeakenThreads), sTarget);
await ns.sleep(Math.ceil(nDelay));
nSecurity = ns.getServerSecurityLevel(sTarget);
}
}
}
/*
let nGrowMulti = 1;
ns.print("nGrowMulti = " + nGrowMulti);
let nGrowth = ns.growthAnalyze(sTarget, nGrowMulti, nCores);
ns.print("nGrowth = " + nGrowth);
*/
//ns.growthAnalyzeSecurity();
const startValue = nMoney; // First argument: starting value
const target = nMaxMoney; // Second argument: target value
let result = startValue; // Initialize result with the starting value
let n = 1;
// For loop that continues until the result exceeds or matches the target
for (; result < target; n++) {
result += 1; // Add 1 before multiplication
result *= n; // Multiply by the current step value
}
let nGrowThreadsNeeded = n * ns.growthAnalyze(sTarget, n, 1);
ns.print("nGrowThreadsNeeded = " + nGrowThreadsNeeded);
while (nMoney < nMaxMoney) {
let nFreeRam = ns.getServerMaxRam("home") - ns.getServerUsedRam("home");
let nGrowPID = ns.run(sGrowScript, Math.min([Math.ceil(nGrowThreadsNeeded)], [Math.ceil(nFreeRam / ns.getScriptRam(sGrowScript, "home"))]), sTarget);
await ns.nextPortWrite(nGrowPID);
await ns.sleep(1000);
nMoney = ns.getServerMoneyAvailable(sTarget);
}
await ns.sleep(Math.ceil(nDelay));
//run batch
const nBatchPID = ns.run(sBatch, 1, sTarget);

View File

@@ -20,7 +20,7 @@ export async function main(ns) {
const nHomeMaxRAM = ns.getServerMaxRam("home");
let nHomeFreeRAM = nHomeMaxRAM - nHomeUsedRAM;
const nDelays = [0, 20, 40, 60];
const nDelays = [0, 100, 200, 300];
//abort script if sTarget is undefined
@@ -34,7 +34,7 @@ export async function main(ns) {
const nMaxMoney = ns.getServerMaxMoney(sTarget);
let nWeakenTime1 = ns.getWeakenTime(sTarget);
let nWeakenTime2 = nWeakenTime1;
let nWeakenTime2 = nWeakenTime1 + nDelays[3];
let nGrowTime = nWeakenTime1 * 0.8;
let nHackTime = nWeakenTime1 / 4;
@@ -51,7 +51,7 @@ export async function main(ns) {
//let nGrowThreadsEstimate = Math.max(Math.floor(1 / nGrowSecurityGain),1);
//ns.tprint("nGrowSecurityGain = " + nGrowSecurityGain);
//ns.tprint("nGrowThreadsEstimate = " + nGrowThreadsEstimate);
const nGrowTotalRAM = nGrowRAM * 13;
const nGrowTotalRAM = nGrowRAM * 12.5;
//let nWeakenSecurity = ns.weakenAnalyze(1, nCores);
//let nWeakenSecurity = 0.05;
@@ -65,7 +65,7 @@ export async function main(ns) {
let nHackThreadsEstimate = nTotalBatches * 25;
let nWeakenThreadsEstimate1 = nTotalBatches * 1;
let nGrowThreadsEstimate = nTotalBatches * 13;
let nGrowThreadsEstimate = nTotalBatches * 12.5;
let nWeakenThreadsEstimate2 = nTotalBatches * 1;
ns.tprint("RAM per Cycle = " + nTotalRAM);
@@ -75,6 +75,7 @@ export async function main(ns) {
let nGrowDelay = nWeakenTime1 - nGrowTime;
let nHackDelay = nWeakenTime1 - nHackTime;
const nCycleDuration = nWeakenTime2 + nDelays[3];
ns.tprint("nCycleDuration = " + nCycleDuration);
@@ -108,7 +109,7 @@ export async function main(ns) {
//ns.tail(nHackPID, "home", "home", nHackThreadsEstimate, sTarget, 0, nHackDelay + nDelays[0]);
//weaken 1
const nWeakenPID = ns.exec(sWeaken, "home", nWeakenThreadsEstimate1, sTarget, false, nDelays[1]);
const nWeakenPID1 = ns.exec(sWeaken, "home", nWeakenThreadsEstimate1, sTarget, false, nDelays[1]);
//ns.tail(nWeakenPID, "home", "home", nWeakenThreadsEstimate, sTarget, 0, nDelays[1]);
//grow
@@ -119,8 +120,47 @@ export async function main(ns) {
const nWeakenPID2 = ns.exec(sWeaken, "home", nWeakenThreadsEstimate2, sTarget, false, nDelays[3]);
//ns.tail(nWeakenPID2, "home", "home", nWeakenThreadsEstimate, sTarget, 0, nDelays[3]);
await ns.sleep(nSafeTime);
//await ns.sleep(nSafeTime);
/*
await ns.nextPortWrite(nHackPID);
await ns.nextPortWrite(nWeakenPID1);
await ns.nextPortWrite(nGrowPID);
await ns.nextPortWrite(nWeakenPID2);
ns.print("nHackPeek = " + nHackPeek);
ns.print("nWeaken1Peek = " + nWeaken1Peek);
ns.print("nGrowPeek = " + nGrowPeek);
ns.print("nWeaken2Peek = " + nWeaken2Peek);
*/
let nHackPeek = ns.peek(nHackPID);
let nWeaken1Peek = ns.peek(nWeakenPID1);
let nGrowPeek = ns.peek(nGrowPID);
let nWeaken2Peek = ns.peek(nWeakenPID2);
if (nHackPeek === true || nWeaken1Peek === true || nGrowPeek === true || nWeaken2Peek === true) {
ns.print("some of the ports are already written to...");
ns.print("nHackPeek = " + nHackPeek);
ns.print("nWeaken1Peek = " + nWeaken1Peek);
ns.print("nGrowPeek = " + nGrowPeek);
ns.print("nWeaken2Peek = " + nWeaken2Peek);
}
await Promise.all([
ns.nextPortWrite(nHackPID),
ns.nextPortWrite(nWeakenPID1),
ns.nextPortWrite(nGrowPID),
ns.nextPortWrite(nWeakenPID2)
])
ns.print("hack: " + nHackPID + " completed at " + ns.readPort(nHackPID));
ns.print("weaken1: " + nWeakenPID1 + " completed at " + ns.readPort(nWeakenPID1));
ns.print("grow: " + nGrowPID + " completed at " + ns.readPort(nGrowPID));
ns.print("weaken2: " + nWeakenPID2 + " completed at " + ns.readPort(nWeakenPID2));
await ns.sleep(100);
}
}

View File

@@ -11,4 +11,5 @@ export async function main(ns) {
await ns.grow(sTarget, { additionalMsec: nMsecDelay });
ns.print(Date.now());
ns.writePort(ns.pid, Date.now());
}

View File

@@ -11,4 +11,5 @@ export async function main(ns) {
await ns.hack(sTarget, { additionalMsec: nMsecDelay });
ns.print(Date.now());
ns.writePort(ns.pid, Date.now());
}

View File

@@ -11,4 +11,5 @@ export async function main(ns) {
await ns.weaken(sTarget, { additionalMsec: nMsecDelay });
ns.print(Date.now());
ns.writePort(ns.pid, Date.now());
}

View File

@@ -1,5 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
const sServer = ns.args[0];
await ns.grow(sServer);
}

View File

@@ -1,5 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
const sServer = ns.args[0];
await ns.hack(sServer);
}

View File

@@ -1,5 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
const sServer = ns.args[0];
await ns.weaken(sServer);
}

View File

@@ -1,297 +0,0 @@
/*
Welcome to part 2. I'll only be commenting on things that have changed from the previous part, so if there's something
confusing, be sure to go back and look at part 1 for more detailed explanations.
For part 2, we'll be making a protobatcher. Essentially that means we'll be running our previous version in a constant loop.
To facilitate this, and because otherwise there wouldn't really be much to this part, we're going to refine the way our
scripts communicate with each other using ports.
*/
import { getServers, copyScripts, checkTarget, isPrepped, prep } from "/S2utils.js";
const TYPES = ["hack", "weaken1", "grow", "weaken2"];
const WORKERS = ["S2tHack.js", "S2tWeaken.js", "S2tGrow.js"];
const SCRIPTS = { hack: "S2tHack.js", weaken1: "S2tWeaken.js", grow: "S2tGrow.js", weaken2: "S2tWeaken.js" };
const COSTS = { hack: 1.7, weaken1: 1.75, grow: 1.75, weaken2: 1.75 };
const OFFSETS = { hack: 0, weaken1: 1, grow: 2, weaken2: 3 };
/*
Most of the changes are in the main function, so I've moved it up top. I generally prefer having the main function at the
top of the file anyway.
*/
/** @param {NS} ns */
export async function main(ns) {
await ns.sleep(500);
// Moving most of our active feeback to the tail window so that batches finishing don't get swept away.
ns.disableLog("ALL");
ns.tail();
// Stick the whole script in a loop. That's it, see you in part 3.
// Just kidding, there's a bit more to it.
let batchCount = 0;
while (true) {
// Register a port using the script's unique handle.
// I like to keep ports strictly coupled to a specific script, but you can use whatever number you like.
const dataPort = ns.getPortHandle(ns.pid);
dataPort.clear() // Make sure there's no random data left in the port.
let target = "n00dles";
const servers = getServers(ns, (server) => {
// Don't worry if you don't have Formulas, it's not needed at all here.
target = checkTarget(ns, server, target, ns.fileExists("Formulas.exe", "home"));
copyScripts(ns, server, WORKERS, true);
return ns.hasRootAccess(server);
});
//target = "n00dles";
const ramNet = new RamNet(ns, servers);
const metrics = new Metrics(ns, target);
if (!isPrepped(ns, target)) await prep(ns, metrics, ramNet);
optimizeBatch(ns, metrics, ramNet); // The same optimization algorithm works just fine for protobatching.
metrics.calculate(ns);
const batch = [];
batchCount++;
for (const type of TYPES) {
// We've removed the buffer. You'll see why later.
metrics.ends[type] = Date.now() + metrics.wTime + metrics.spacer * OFFSETS[type];
const job = new Job(type, metrics);
job.batch = batchCount; // This is a bit of a hack. We'll do it better in the next part.
if (!ramNet.assign(job)) {
ns.print(`ERROR: Unable to assign ${type}. Dumping debug info:`);
ns.print(job);
ns.print(metrics);
ramNet.printBlocks(ns);
return;
}
batch.push(job);
}
// We do a bit more during deployment now.
for (const job of batch) {
job.end += metrics.delay;
const jobPid = ns.exec(SCRIPTS[job.type], job.server, { threads: job.threads, temporary: true }, JSON.stringify(job));
if (!jobPid) throw new Error(`Unable to deploy ${job.type}`); // If the exec fails for any reason, error out.
/*
If a worker deploys late, it will communicate back how late it was, so that the other scripts can adjust.
Note that for this we use the *worker's* port instead of our controller's port. It's good practice to make
sure your ports have a very narrow focus.
*/
const tPort = ns.getPortHandle(jobPid);
await tPort.nextWrite();
metrics.delay += tPort.read();
}
const timer = setInterval(() => {
ns.clearLog();
ns.print(`Hacking \$${ns.formatNumber(metrics.maxMoney * metrics.greed)} from ${metrics.target}`)
ns.print(`Running batch: ETA ${ns.tFormat(metrics.ends.weaken2 - Date.now())}`);
}, 1000);
ns.atExit(() => {
clearInterval(timer);
});
// Wait for the weaken2 worker to report back. For now I've just hardcoded the Job class to tell only
// weaken2 to report. This behavior will change later.
await dataPort.nextWrite();
dataPort.clear(); // For now we don't actually need the information here, we're just using it for timing.
clearInterval(timer);
}
}
class Job {
constructor(type, metrics, server = "none") {
this.type = type;
this.end = metrics.ends[type];
this.time = metrics.times[type];
this.target = metrics.target;
this.threads = metrics.threads[type];
this.cost = this.threads * COSTS[type];
this.server = server;
this.report = this.type === "weaken2"; // For now, only w2 jobs report.
this.port = metrics.port; // This lets the workers know which port to write to.
this.batch = 0; // We'll keep track of how many we've run, just because we can.
}
}
/** @param {NS} ns */
class Metrics {
constructor(ns, server) {
this.target = server;
this.maxMoney = ns.getServerMaxMoney(server);
this.money = Math.max(ns.getServerMoneyAvailable(server), 1);
this.minSec = ns.getServerMinSecurityLevel(server);
this.sec = ns.getServerSecurityLevel(server);
this.prepped = isPrepped(ns, server);
this.chance = 0;
this.wTime = 0;
this.delay = 0; // The cumulative delays caused by late jobs.
this.spacer = 5;
this.greed = 0.1;
this.depth = 0; // Still not using this.
this.times = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.ends = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.threads = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.port = ns.pid;
}
calculate(ns, greed = this.greed) {
const server = this.target;
const maxMoney = this.maxMoney;
this.money = ns.getServerMoneyAvailable(server);
this.sec = ns.getServerSecurityLevel(server);
this.wTime = ns.getWeakenTime(server);
this.times.weaken1 = this.wTime;
this.times.weaken2 = this.wTime;
this.times.hack = this.wTime / 4;
this.times.grow = this.wTime * 0.8;
this.depth = this.wTime / this.spacer * 4;
const hPercent = ns.hackAnalyze(server);
const amount = maxMoney * greed;
const hThreads = Math.max(Math.floor(ns.hackAnalyzeThreads(server, amount)), 1);
const tGreed = hPercent * hThreads;
const gThreads = Math.ceil(ns.growthAnalyze(server, maxMoney / (maxMoney - maxMoney * tGreed)));
this.threads.weaken1 = Math.max(Math.ceil(hThreads * 0.002 / 0.05), 1);
this.threads.weaken2 = Math.max(Math.ceil(gThreads * 0.004 / 0.05), 1);
this.threads.hack = hThreads;
this.threads.grow = gThreads;
this.chance = ns.hackAnalyzeChance(server);
}
}
/** @param {NS} ns */
class RamNet {
#blocks = [];
#minBlockSize = Infinity;
#maxBlockSize = 0;
#totalRam = 0;
#maxRam = 0;
#prepThreads = 0;
#index = new Map();
constructor(ns, servers) {
for (const server of servers) {
if (ns.hasRootAccess(server)) {
const maxRam = ns.getServerMaxRam(server);
const ram = maxRam - ns.getServerUsedRam(server);
if (ram >= 1.60) {
const block = { server: server, ram: ram };
this.#blocks.push(block);
if (ram < this.#minBlockSize) this.#minBlockSize = ram;
if (ram > this.#maxBlockSize) this.#maxBlockSize = ram;
this.#totalRam += ram;
this.#maxRam += maxRam;
this.#prepThreads += Math.floor(ram / 1.75);
}
}
}
this.#sort();
this.#blocks.forEach((block, index) => this.#index.set(block.server, index));
}
#sort() {
this.#blocks.sort((x, y) => {
if (x.server === "home") return 1;
if (y.server === "home") return -1;
return x.ram - y.ram;
});
}
getBlock(server) {
if (this.#index.has(server)) {
return this.#blocks[this.#index.get(server)];
} else {
throw new Error(`Server ${server} not found in RamNet.`);
}
}
get totalRam() {
return this.#totalRam;
}
get maxRam() {
return this.#maxRam;
}
get maxBlockSize() {
return this.#maxBlockSize;
}
get prepThreads() {
return this.#prepThreads;
}
assign(job) {
const block = this.#blocks.find(block => block.ram >= job.cost);
if (block) {
job.server = block.server;
block.ram -= job.cost;
this.#totalRam -= job.cost;
return true;
} else return false;
}
finish(job) {
const block = this.getBlock(job.server);
block.ram += job.cost;
this.#totalRam += job.cost;
}
cloneBlocks() {
return this.#blocks.map(block => ({ ...block }));
}
printBlocks(ns) {
for (const block of this.#blocks) ns.print(block);
}
}
/**
* @param {NS} ns
* @param {Metrics} metrics
* @param {RamNet} ramNet
*/
export function optimizeBatch(ns, metrics, ramNet) {
const maxThreads = ramNet.maxBlockSize / 1.75;
const maxMoney = metrics.maxMoney;
const hPercent = ns.hackAnalyze(metrics.target);
const minGreed = 0.001;
const stepValue = 0.001;
let greed = 0.99;
while (greed > minGreed) {
const amount = maxMoney * greed;
const hThreads = Math.max(Math.floor(ns.hackAnalyzeThreads(metrics.target, amount)), 1);
const tGreed = hPercent * hThreads;
const gThreads = Math.ceil(ns.growthAnalyze(metrics.target, maxMoney / (maxMoney - maxMoney * tGreed)));
if (Math.max(hThreads, gThreads) <= maxThreads) {
const wThreads1 = Math.max(Math.ceil(hThreads * 0.002 / 0.05), 1);
const wThreads2 = Math.max(Math.ceil(gThreads * 0.004 / 0.05), 1);
const threadCosts = [hThreads * 1.7, wThreads1 * 1.75, gThreads * 1.75, wThreads2 * 1.75];
const pRam = ramNet.cloneBlocks();
let found;
for (const cost of threadCosts) {
found = false;
for (const block of pRam) {
if (block.ram < cost) continue;
found = true;
block.ram -= cost;
break;
}
if (found) continue;
break;
}
if (found) {
metrics.greed = greed;
metrics.threads = { hack: hThreads, weaken1: wThreads1, grow: gThreads, weaken2: wThreads2 };
return true;
}
}
greed -= stepValue;
}
throw new Error("Not enough ram to run even a single batch. Something has gone seriously wrong.");
}

View File

@@ -1,23 +0,0 @@
/*
Very little has changed in the workers. We uncommented a couple of parts to allow for the ping-pong deployment.
See the tWeaken.js for full comments.
*/
/** @param {NS} ns */
export async function main(ns) {
const job = JSON.parse(ns.args[0]);
let delay = job.end - job.time - Date.now();
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
ns.writePort(ns.pid, -delay);
delay = 0;
} else {
ns.writePort(ns.pid, 0);
}
await ns.grow(job.target, { additionalMsec: delay });
const end = Date.now();
ns.atExit(() => {
// if (job.report) ns.writePort(job.port, job.type + job.server);
ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
});
}

View File

@@ -1,23 +0,0 @@
/*
Very little has changed in the workers. We uncommented a couple of parts to allow for the ping-pong deployment.
See the tWeaken.js for full comments.
*/
/** @param {NS} ns */
export async function main(ns) {
const job = JSON.parse(ns.args[0]);
let delay = job.end - job.time - Date.now();
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
ns.writePort(ns.pid, -delay);
delay = 0;
} else {
ns.writePort(ns.pid, 0);
}
await ns.hack(job.target, { additionalMsec: delay });
const end = Date.now();
ns.atExit(() => {
// if (job.report) ns.writePort(job.port, job.type + job.server);
ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
});
}

View File

@@ -1,27 +0,0 @@
/*
Very little has changed in the workers. We uncommented a couple of parts to allow for the ping-pong deployment.
See the tWeaken.js for full comments.
*/
/** @param {NS} ns */
export async function main(ns) {
const job = JSON.parse(ns.args[0]);
let delay = job.end - job.time - Date.now();
if (delay < 0) {
// We now write back to the controller if jobs are delayed so that it can adjust the other jobs to match.
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
ns.writePort(ns.pid, -delay);
delay = 0;
} else {
ns.writePort(ns.pid, 0);
}
await ns.weaken(job.target, { additionalMsec: delay });
const end = Date.now();
// Write back to let the controller know that we're done. The actual data is currently only used by the prep function.
ns.atExit(() => {
if (job.report) ns.writePort(job.port, job.type + job.server);
ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
});
}

View File

@@ -1,218 +0,0 @@
/*
The utility function library. This is almost entirely unchanged from part 1, aside from the prep function
printing to the log console instead of the terminal.
*/
/** @param {NS} ns */
export async function main(ns) {
ns.tprint("This is just a function library, it doesn't do anything.");
}
// The recursive server navigation algorithm. The lambda predicate determines which servers to add to the final list.
// You can also plug other functions into the lambda to perform other tasks that check all servers at the same time.
/** @param {NS} ns */
export function getServers(ns, lambdaCondition = () => true, hostname = "home", servers = [], visited = []) {
if (visited.includes(hostname)) return;
visited.push(hostname);
if (lambdaCondition(hostname)) servers.push(hostname);
const connectedNodes = ns.scan(hostname);
if (hostname !== "home") connectedNodes.shift();
for (const node of connectedNodes) getServers(ns, lambdaCondition, node, servers, visited);
return servers;
}
// Here are a couple of my own getServers modules.
// This one finds the best target for hacking. It tries to balance expected return with time taken.
/** @param {NS} ns */
export function checkTarget(ns, server, target = "n00dles", forms = false) {
if (!ns.hasRootAccess(server)) return target;
const player = ns.getPlayer();
const serverSim = ns.getServer(server);
const pSim = ns.getServer(target);
let previousScore;
let currentScore;
if (serverSim.requiredHackingSkill <= player.skills.hacking / (forms ? 1 : 2)) {
if (forms) {
serverSim.hackDifficulty = serverSim.minDifficulty;
pSim.hackDifficulty = pSim.minDifficulty;
previousScore = pSim.moneyMax / ns.formulas.hacking.weakenTime(pSim, player) * ns.formulas.hacking.hackChance(pSim, player);
currentScore = serverSim.moneyMax / ns.formulas.hacking.weakenTime(serverSim, player) * ns.formulas.hacking.hackChance(serverSim, player);
} else {
previousScore = pSim.moneyMax / pSim.minDifficulty;
currentScore = serverSim.moneyMax / serverSim.minDifficulty;
}
if (currentScore > previousScore) target = server;
}
return target;
}
// A simple function for copying a list of scripts to a server.
/** @param {NS} ns */
export function copyScripts(ns, server, scripts, overwrite = false) {
for (const script of scripts) {
if ((!ns.fileExists(script, server) || overwrite) && ns.hasRootAccess(server)) {
ns.scp(script, server);
}
}
}
// A generic function to check that a given server is prepped. Mostly just a convenience.
export function isPrepped(ns, server) {
const tolerance = 0.0001;
const maxMoney = ns.getServerMaxMoney(server);
const money = ns.getServerMoneyAvailable(server);
const minSec = ns.getServerMinSecurityLevel(server);
const sec = ns.getServerSecurityLevel(server);
const secFix = Math.abs(sec - minSec) < tolerance;
return (money === maxMoney && secFix) ? true : false;
}
/*
This prep function isn't part of the tutorial, but the rest of the code wouldn't work without it.
I don't make any guarantees, but I've been using it and it's worked well enough. I'll comment it anyway.
The prep strategy uses a modified proto-batching technique, which will be covered in part 2.
*/
/** @param {NS} ns */
export async function prep(ns, values, ramNet) {
const maxMoney = values.maxMoney;
const minSec = values.minSec;
let money = values.money;
let sec = values.sec;
while (!isPrepped(ns, values.target)) {
const wTime = ns.getWeakenTime(values.target);
const gTime = wTime * 0.8;
const dataPort = ns.getPortHandle(ns.pid);
dataPort.clear();
const pRam = ramNet.cloneBlocks();
const maxThreads = Math.floor(ramNet.maxBlockSize / 1.75);
const totalThreads = ramNet.prepThreads;
let wThreads1 = 0;
let wThreads2 = 0;
let gThreads = 0;
let batchCount = 1;
let script, mode;
/*
Modes:
0: Security only
1: Money only
2: One shot
*/
if (money < maxMoney) {
gThreads = Math.ceil(ns.growthAnalyze(values.target, maxMoney / money));
wThreads2 = Math.ceil(ns.growthAnalyzeSecurity(gThreads) / 0.05);
}
if (sec > minSec) {
wThreads1 = Math.ceil((sec - minSec) * 20);
if (!(wThreads1 + wThreads2 + gThreads <= totalThreads && gThreads <= maxThreads)) {
gThreads = 0;
wThreads2 = 0;
batchCount = Math.ceil(wThreads1 / totalThreads);
if (batchCount > 1) wThreads1 = totalThreads;
mode = 0;
} else mode = 2;
} else if (gThreads > maxThreads || gThreads + wThreads2 > totalThreads) {
mode = 1;
const oldG = gThreads;
wThreads2 = Math.max(Math.floor(totalThreads / 13.5), 1);
gThreads = Math.floor(wThreads2 * 12.5);
batchCount = Math.ceil(oldG / gThreads);
} else mode = 2;
// Big buffer here, since all the previous calculations can take a while. One second should be more than enough.
const wEnd1 = Date.now() + wTime + 1000;
const gEnd = wEnd1 + values.spacer;
const wEnd2 = gEnd + values.spacer;
// "metrics" here is basically a mock Job object. Again, this is just an artifact of repurposed old code.
const metrics = {
batch: "prep",
target: values.target,
type: "none",
time: 0,
end: 0,
port: ns.pid,
log: values.log,
report: false
};
// Actually assigning threads. We actually allow grow threads to be spread out in mode 1.
// This is because we don't mind if the effect is a bit reduced from higher security unlike a normal batcher.
// We're not trying to grow a specific amount, we're trying to grow as much as possible.
for (const block of pRam) {
while (block.ram >= 1.75) {
const bMax = Math.floor(block.ram / 1.75)
let threads = 0;
if (wThreads1 > 0) {
script = "S2tWeaken.js";
metrics.type = "pWeaken1";
metrics.time = wTime;
metrics.end = wEnd1;
threads = Math.min(wThreads1, bMax);
if (wThreads2 === 0 && wThreads1 - threads <= 0) metrics.report = true;
wThreads1 -= threads;
} else if (wThreads2 > 0) {
script = "S2tWeaken.js";
metrics.type = "pWeaken2";
metrics.time = wTime;
metrics.end = wEnd2;
threads = Math.min(wThreads2, bMax);
if (wThreads2 - threads === 0) metrics.report = true;
wThreads2 -= threads;
} else if (gThreads > 0 && mode === 1) {
script = "S2tGrow.js";
metrics.type = "pGrow";
metrics.time = gTime;
metrics.end = gEnd;
threads = Math.min(gThreads, bMax);
metrics.report = false;
gThreads -= threads;
} else if (gThreads > 0 && bMax >= gThreads) {
script = "S2tGrow.js";
metrics.type = "pGrow";
metrics.time = gTime;
metrics.end = gEnd;
threads = gThreads;
metrics.report = false;
gThreads = 0;
} else break;
metrics.server = block.server;
const pid = ns.exec(script, block.server, { threads: threads, temporary: true }, JSON.stringify(metrics));
if (!pid) throw new Error("Unable to assign all jobs.");
block.ram -= 1.75 * threads;
}
}
// Fancy UI stuff to update you on progress.
const tEnd = ((mode === 0 ? wEnd1 : wEnd2) - Date.now()) * batchCount + Date.now();
const timer = setInterval(() => {
ns.clearLog();
switch (mode) {
case 0:
ns.print(`Weakening security on ${values.target}...`);
break;
case 1:
ns.print(`Maximizing money on ${values.target}...`);
break;
case 2:
ns.print(`Finalizing preparation on ${values.target}...`);
}
ns.print(`Security: +${ns.formatNumber(sec - minSec, 3)}`);
ns.print(`Money: \$${ns.formatNumber(money, 2)}/${ns.formatNumber(maxMoney, 2)}`);
const time = tEnd - Date.now();
ns.print(`Estimated time remaining: ${ns.tFormat(time)}`);
ns.print(`~${batchCount} ${(batchCount === 1) ? "batch" : "batches"}.`);
}, 200);
ns.atExit(() => clearInterval(timer));
// Wait for the last weaken to finish.
do await dataPort.nextWrite(); while (!dataPort.read().startsWith("pWeaken"));
clearInterval(timer);
await ns.sleep(100);
money = ns.getServerMoneyAvailable(values.target);
sec = ns.getServerSecurityLevel(values.target);
}
return true;
}

View File

@@ -1,491 +0,0 @@
/*
Welcome to part 4. A continuous batcher is a major hurdle compared to everything we've done so far. The number
and complexity of the challenges increases drastically when trying to keep everything running indefinitely.
With luck, the overengineering we've done so far will have well prepared us for the challenges of a periodic
batcher.
Technically, I use quite a few JIT techniques in this batcher, but I don't consider it a true JIT batcher
as it doesn't take full advantage of the potential RAM efficiency. Instead, I favor simpler logic, while still
allowing the batcher to make certain adjustments if it needs to.
When it comes to continuous batchers, performance is king. We're going to aim for 5ms spacing as we have
throughout this guide so far, but there's a lot we need to do in those 5ms. As such, we need to make sure that
we choose which operations to do carefully, as well as when to do them and how to make sure they are as fast
as we can make them.
*/
// One new utility. A custom data structure for managing our schedule. You can see the details in utils.js
import { getServers, copyScripts, checkTarget, isPrepped, prep, Deque } from "/S4utils.js";
const TYPES = ["hack", "weaken1", "grow", "weaken2"];
const WORKERS = ["S4tHack.js", "S4tWeaken.js", "S4tGrow.js"];
const SCRIPTS = { hack: "S4tHack.js", weaken1: "S4tWeaken.js", grow: "S4tGrow.js", weaken2: "S4tWeaken.js" };
const COSTS = { hack: 1.7, weaken1: 1.75, grow: 1.75, weaken2: 1.75 };
// const OFFSETS = { hack: 0, weaken1: 1, grow: 2, weaken2: 3 };
// A new optional constant. The RamNet will attempt to reserve this much ram at home.
// You can set it to 0 if you don't want to reserve anything, and setting it too high will just reserve as much as possible.
const RESERVED_HOME_RAM = 0;
// A brand new class to help keep our increasingly complex logic organized.
class ContinuousBatcher {
#ns; // The ns object. Stored as a class variable to save me the trouble of passing it all the time.
// The usual data we've grown familiar with by now.
#metrics;
#ramNet;
#target;
#schedule;
#dataPort;
#batchCount = 0;
#desyncs = 0; // This is mostly used for logging purposes, since the batcher is self-correcting.
// A capital M Map. We'll use this to keep track of active jobs.
#running = new Map();
constructor(ns, metrics, ramNet) {
this.#ns = ns;
this.#metrics = metrics;
this.#ramNet = ramNet;
this.#target = metrics.target;
this.#dataPort = ns.getPortHandle(ns.pid);
// Seeding the first ending time.
this.#metrics.end = Date.now() + metrics.wTime - metrics.spacer;
// The new schedule I promised. It's a double-ended queue, but we'll mostly just be using it as a normal queue.
// It has a static size, so we make sure it can accomodate all of our jobs.
this.#schedule = new Deque(metrics.depth * 4);
}
// This is a function that can schedule a given number of batches.
// With no arguments, it just fills up the queue.
scheduleBatches(batches = this.#metrics.depth) {
while (this.#schedule.size < batches * 4) {
++this.#batchCount;
for (const type of TYPES) {
this.#metrics.end += this.#metrics.spacer;
const job = new Job(type, this.#metrics, this.#batchCount);
/*
We don't actually error out if a job can't be assigned anymore. Instead, we just assign as much
as we can. If it desyncs, the logic will correct it, and if a weaken2 gets cancelled then the actual
depth will naturally decrease below the target depth. Not a perfect fix, but better than breaking.
*/
if (!this.#ramNet.assign(job)) {
this.#ns.tprint(`WARN: Insufficient RAM to assign ${job.type}: ${job.batch}.`);
continue;
}
this.#schedule.push(job);
}
}
}
// The function for deploying jobs. Very similar to the code from our shotgun batcher with some minor changes.
async deploy() {
// The for loop is replaced by a while loop, since our Deque isn't iterable.
while (!this.#schedule.isEmpty()) {
const job = this.#schedule.shift();
job.end += this.#metrics.delay;
const jobPid = this.#ns.exec(SCRIPTS[job.type], job.server, { threads: job.threads, temporary: true }, JSON.stringify(job));
if (!jobPid) throw new Error(`Unable to deploy ${job.type}`);
const tPort = this.#ns.getPortHandle(jobPid);
// We save the pid for later.
job.pid = jobPid;
await tPort.nextWrite();
// Jobs can be late as long as the delay won't cause collisions.
this.#metrics.delay += Math.max(Math.ceil(tPort.read()) - this.#metrics.spacer, 0);
this.#running.set(job.id, job);
}
// After the loop, we adjust future job ends to account for the delay, then discard it.
this.#metrics.end += this.#metrics.delay;
this.#metrics.delay = 0;
}
// Our old timeout function is now a proper function of its own. A few extra baubles in the log, but nothing exciting.
/** @param {NS} ns */
log() {
const ns = this.#ns;
const metrics = this.#metrics;
const ramNet = this.#ramNet;
ns.clearLog();
ns.print(`Hacking ~\$${ns.formatNumber(metrics.maxMoney * metrics.greed * metrics.chance / (4 * metrics.spacer) * 1000)}/s from ${metrics.target}`);
ns.print(`Status: ${isPrepped(ns, this.#target) ? "Prepped" : "Desynced"}`);
ns.print(`Security: +${metrics.minSec - metrics.sec}`);
ns.print(`Money: \$${ns.formatNumber(metrics.money, 2)}/${ns.formatNumber(metrics.maxMoney, 2)}`);
ns.print(`Greed: ${Math.floor(metrics.greed * 1000) / 10}%`);
ns.print(`Ram available: ${ns.formatRam(ramNet.totalRam)}/${ns.formatRam(ramNet.maxRam)}`);
ns.print(`Active jobs: ${this.#running.size}/${metrics.depth * 4}`);
// You'll see what this line's about in a moment.
if (this.#desyncs) ns.print(`Hacks cancelled by desync: ${this.#desyncs}`);
}
// The core loop of our batcher logic. Quite lean with everything neatly divided into functions, but there's still
// plenty going on here.
async run() {
// First we do some initial setup, this is essentially firing off a shotgun blast to get us started.
const dataPort = this.#dataPort;
this.scheduleBatches();
await this.deploy();
await this.#ns.sleep(0); // This is probably pointless. I forget why I put it here.
this.log();
while (true) {
// Wait for the nextWrite, as usual.
await dataPort.nextWrite();
// Sometimes there's a delay and more than one job writes to the port at once.
// We make sure to handle it all before we move on.
while (!dataPort.empty()) {
// Workers now report unique identifiers (type + batchnumber) used to find them on the map.
const data = dataPort.read();
// Free up the ram, them remove them from the active list.
// The check handles a corner case where a hack gets "cancelled" after it's already finished.
if (this.#running.has(data)) {
this.#ramNet.finish(this.#running.get(data));
this.#running.delete(data);
}
// If it's a W2, we've got an opening to do some work.
if (data.startsWith("weaken2")) {
// Recalculate times. Threads too, but only if prepped (the logic is in the function itself).
this.#metrics.calculate(this.#ns);
/*
This is probably the most JIT-like aspect of the entire batcher. If the server isn't prepped, then
we cancel the next hack to let the server fix itself. Between this and the extra 1% grow threads, level
ups are completely handled. Rapid level ups can lead to a lot of lost jobs, but eventually the program
stabilizes.
There are probably more efficient ways to do this. Heck, even this solution could be optimized better,
but for now, this is an adequate demonstration of a reasonable non-formulas solution to the level up
problem. It also lets us dip our toes into JIT logic in preparation for the final part.
*/
if (!isPrepped(this.#ns, this.#target)) {
const id = "hack" + (parseInt(data.slice(7)) + 1);
const cancel = this.#running.get(id);
// Just in case the hack was already aborted somehow.
if (cancel) {
this.#ramNet.finish(cancel);
this.#ns.kill(cancel.pid);
this.#running.delete(id);
++this.#desyncs; // Just to keep track of how much we've lost keeping things prepped.
}
}
// Then of course we just schedule and deploy a new batch.
this.scheduleBatches(1);
await this.deploy();
this.log();
}
}
}
}
}
/*
Our poor "main" function isn't much more than a kickstart for our new batcher object. It's a bit weird having
it wedged between objects like this, but I wanted to have the new functionality up at the top since most of the
remaining code hasn't changed much. I'll comment the changes anyway.
*/
/** @param {NS} ns */
export async function main(ns) {
ns.disableLog("ALL");
ns.tail();
/*
This commented out code is for a debugging tool that centralizes logs from the worker scripts into one place.
It's main advantage is the ability to write txt logs to file, which can be perused later to track down errors.
You can uncomment it if you'd like to see a live stream of workers finishing without flooding the terminal.
If you do, make sure to search the file for -LOGGING and uncomment all relevant lines.
*/
// if (ns.isRunning("S4logHelper.js", "home")) ns.kill("S4logHelper.js", "home");
// const logPort = ns.exec("S4logHelper.js", "home");
// ns.atExit(() => ns.closeTail(logPort));
// Setup is mostly the same.
const dataPort = ns.getPortHandle(ns.pid);
dataPort.clear();
let target = ns.args[0] ? ns.args[0] : "n00dles";
while (true) {
const servers = getServers(ns, (server) => {
if (!ns.args[0]) target = checkTarget(ns, server, target, ns.fileExists("Formulas.exe", "home"));
copyScripts(ns, server, WORKERS, true);
return ns.hasRootAccess(server);
});
const ramNet = new RamNet(ns, servers);
const metrics = new Metrics(ns, target);
// metrics.log = logPort; // Uncomment for -LOGGING.
if (!isPrepped(ns, target)) await prep(ns, metrics, ramNet);
ns.clearLog();
ns.print("Optimizing. This may take a few seconds...");
// Optimizer has changed again. Back to being synchronous, since the performance is much better.
optimizePeriodic(ns, metrics, ramNet);
metrics.calculate(ns);
// Create and run our batcher.
const batcher = new ContinuousBatcher(ns, metrics, ramNet);
await batcher.run();
/*
You might be wondering why I put this in a while loop and then just return here. The simple answer is that
it's because this is meant to be run in a loop, but I didn't implement the logic for it. This version of the
batcher is completely static once created. It sticks to a single greed value, and doesn't update if more
RAM becomes available.
In a future version, you'd want some logic to allow the batcher to choose new targets, update its available RAM,
and create new batchers during runtime. For now, that's outside the scope of this guide, but consider this loop
as a sign of what could be.
*/
return;
}
}
// The Job class, lean as it is, remains mostly unchanged. I got rid of the server argument since I wasn't using it
// and added a batch number instead.
class Job {
constructor(type, metrics, batch) {
this.type = type;
// this.end = metrics.ends[type];
this.end = metrics.end;
this.time = metrics.times[type];
this.target = metrics.target;
this.threads = metrics.threads[type];
this.cost = this.threads * COSTS[type];
this.server = "none";
this.report = true;
this.port = metrics.port;
this.batch = batch;
// The future is now. The status and id are used for interacting with the Deque and Map in our batcher class.
this.status = "active";
this.id = type + batch;
// this.log = metrics.log; // -LOGGING
}
}
// The only change to the metrics class is the calculate function skipping threadcounts if the server isn't prepped.
/** @param {NS} ns */
class Metrics {
constructor(ns, server) {
this.target = server;
this.maxMoney = ns.getServerMaxMoney(server);
this.money = Math.max(ns.getServerMoneyAvailable(server), 1);
this.minSec = ns.getServerMinSecurityLevel(server);
this.sec = ns.getServerSecurityLevel(server);
this.prepped = isPrepped(ns, server);
this.chance = 0;
this.wTime = 0;
this.delay = 0;
this.spacer = 5;
this.greed = 0.01;
this.depth = 0; // The number of concurrent batches to run. Set by the optimizer.
this.times = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.end = 0; // Slight change for the new timing. The old way in commented out in case I switch back later.
// this.ends = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.threads = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.port = ns.pid;
}
calculate(ns, greed = this.greed) {
const server = this.target;
const maxMoney = this.maxMoney;
this.money = ns.getServerMoneyAvailable(server);
this.sec = ns.getServerSecurityLevel(server);
this.wTime = ns.getWeakenTime(server);
this.times.weaken1 = this.wTime;
this.times.weaken2 = this.wTime;
this.times.hack = this.wTime / 4;
this.times.grow = this.wTime * 0.8;
// this.depth = this.wTime / this.spacer * 4;
if (isPrepped(ns, server)) { // The only change.
const hPercent = ns.hackAnalyze(server);
const amount = maxMoney * greed;
const hThreads = Math.max(Math.floor(ns.hackAnalyzeThreads(server, amount)), 1);
const tGreed = hPercent * hThreads;
const gThreads = Math.ceil(ns.growthAnalyze(server, maxMoney / (maxMoney - maxMoney * tGreed)) * 1.01);
this.threads.weaken1 = Math.max(Math.ceil(hThreads * 0.002 / 0.05), 1);
this.threads.weaken2 = Math.max(Math.ceil(gThreads * 0.004 / 0.05), 1);
this.threads.hack = hThreads;
this.threads.grow = gThreads;
this.chance = ns.hackAnalyzeChance(server);
}
}
}
// A few minor edits here. An unused "simulation" mode, and reserved RAM on home.
/** @param {NS} ns */
class RamNet {
#blocks = [];
#minBlockSize = Infinity;
#maxBlockSize = 0;
#totalRam = 0;
#prepThreads = 0;
#maxRam = 0;
#index = new Map();
// Simulate mode ignores running scripts. Can be used to make calculations while the batcher is operating.
constructor(ns, servers, simulate = false) {
for (const server of servers) {
if (ns.hasRootAccess(server)) {
const maxRam = ns.getServerMaxRam(server);
// Save some extra ram on home. Clamp used ram to maxRam to prevent negative numbers.
const reserved = (server === "home") ? RESERVED_HOME_RAM : 0;
const used = Math.min((simulate ? 0 : ns.getServerUsedRam(server)) + reserved, maxRam);
const ram = maxRam - used;
if (maxRam > 0) {
const block = { server: server, ram: ram };
this.#blocks.push(block);
if (ram < this.#minBlockSize) this.#minBlockSize = ram;
if (ram > this.#maxBlockSize) this.#maxBlockSize = ram;
this.#totalRam += ram;
this.#maxRam += maxRam;
this.#prepThreads += Math.floor(ram / 1.75);
}
}
}
this.#sort();
this.#blocks.forEach((block, index) => this.#index.set(block.server, index));
}
#sort() {
this.#blocks.sort((x, y) => {
if (x.server === "home") return 1;
if (y.server === "home") return -1;
return x.ram - y.ram;
});
}
get totalRam() {
return this.#totalRam;
}
get maxRam() {
return this.#maxRam;
}
get maxBlockSize() {
return this.#maxBlockSize;
}
get prepThreads() {
return this.#prepThreads;
}
getBlock(server) {
if (this.#index.has(server)) {
return this.#blocks[this.#index.get(server)];
} else {
throw new Error(`Server ${server} not found in RamNet.`);
}
}
assign(job) {
const block = this.#blocks.find(block => block.ram >= job.cost);
if (block) {
job.server = block.server;
block.ram -= job.cost;
this.#totalRam -= job.cost;
return true;
} else return false;
}
finish(job) {
const block = this.getBlock(job.server);
block.ram += job.cost;
this.#totalRam += job.cost;
}
cloneBlocks() {
return this.#blocks.map(block => ({ ...block }));
}
printBlocks(ns) {
for (const block of this.#blocks) ns.print(block);
}
testThreads(threadCosts) {
const pRam = this.cloneBlocks();
let batches = 0;
let found = true;
while (found) {
for (const cost of threadCosts) {
found = false;
const block = pRam.find(block => block.ram >= cost);
if (block) {
block.ram -= cost;
found = true;
} else break;
}
if (found) batches++;
}
return batches;
}
}
// Quite a bit has changed in this one. It's back to being synchronous, though it can still take a while.
/**
* @param {NS} ns
* @param {Metrics} metrics
* @param {RamNet} ramNet
*/
function optimizePeriodic(ns, metrics, ramNet) {
const maxThreads = ramNet.maxBlockSize / 1.75;
const maxMoney = metrics.maxMoney;
const hPercent = ns.hackAnalyze(metrics.target);
const wTime = ns.getWeakenTime(metrics.target);
const minGreed = 0.001;
const maxSpacer = wTime; // This is more of an infinite loop safety net than anything.
const stepValue = 0.01;
let greed = 0.95; // Capping greed a bit lower. I don't have a compelling reason for this.
let spacer = metrics.spacer; // We'll be adjusting the spacer in low ram conditions to allow smaller depths.
while (greed > minGreed && spacer < maxSpacer) {
// We calculate a max depth based on the spacer, then add one as a buffer.
const depth = Math.ceil(wTime / (4 * spacer)) + 1;
const amount = maxMoney * greed;
const hThreads = Math.max(Math.floor(ns.hackAnalyzeThreads(metrics.target, amount)), 1);
const tGreed = hPercent * hThreads;
const gThreads = Math.ceil(ns.growthAnalyze(metrics.target, maxMoney / (maxMoney - maxMoney * tGreed)) * 1.01);
if (Math.max(hThreads, gThreads) <= maxThreads) {
const wThreads1 = Math.max(Math.ceil(hThreads * 0.002 / 0.05), 1);
const wThreads2 = Math.max(Math.ceil(gThreads * 0.004 / 0.05), 1);
const threadCosts = [hThreads * 1.7, wThreads1 * 1.75, gThreads * 1.75, wThreads2 * 1.75];
// Glad I kept these, they turned out to be useful after all. When trying to hit target depth,
// checking that there's actually enough theoretical ram to fit them is a massive boost to performance.
const totalCost = threadCosts.reduce((t, c) => t + c) * depth;
if (totalCost < ramNet.totalRam) {
// Double check that we can actually fit our threads into ram, then set our metrics and return.
const batchCount = ramNet.testThreads(threadCosts);
if (batchCount >= depth) {
metrics.spacer = spacer;
metrics.greed = greed;
metrics.depth = depth;
return
}
}
}
// await ns.sleep(0); // Uncomment and make the function async if you don't like the freeze on startup.
// Decrement greed until we hit the minimum, then reset and increment spacer. We'll find a valid configuration eventually.
greed -= stepValue;
if (greed < minGreed && spacer < maxSpacer) {
greed = 0.99;
++spacer;
}
}
throw new Error("Not enough ram to run even a single batch. Something has gone seriously wrong.");
}

View File

@@ -1,42 +0,0 @@
/*
This script is completely unchanged from the last part. As a note, if you find that saves are taking a very long time
it may help to disable txt logging when you aren't actively debugging. The log files generated by this script
are quite big even when it's erasing the data on each new instance.
*/
/** @param {NS} ns */
export async function main(ns) {
const logFile = "S4log.txt";
ns.clear(logFile); // Clear the previous log for each instance.
ns.disableLog("ALL");
ns.tail();
ns.moveTail(200, 200); // Move it out of the way so it doesn't cover up the controller.
const logPort = ns.getPortHandle(ns.pid);
logPort.clear();
// Pretty simple. Just wait until something writes to the log and save the info.
// Writes to its own console as well as a text file.
let max = 0;
let count = 0;
let total = 0;
let errors = 0;
while (true) {
await logPort.nextWrite();
do {
const data = logPort.read();
// if (data > max) max = data;
// if (data > 5) ++errors;
// total += data;
// ++count;
// ns.clearLog();
// ns.print(`Max desync: ${max}`);
// ns.print(`Average desync: ${total / count}`);
// ns.print(`Errors: ${errors}`);
// if (data.startsWith("WARN")) ns.print(data);
ns.print(data);
// ns.write(logFile, data); // Comment this line out to disable txt logging.
} while (!logPort.empty());
}
}

View File

@@ -1,30 +0,0 @@
/*
A pretty big change this time. Well, big for workers anyway. I've tightened up the delay calculations
to be as perfect as I can get them. Full comments in weaken.js as usual.
*/
/** @param {NS} ns */
export async function main(ns) {
const start = performance.now();
const port = ns.getPortHandle(ns.pid);
const job = JSON.parse(ns.args[0]);
let tDelay = 0;
let delay = job.end - job.time - Date.now();
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
tDelay = -delay
delay = 0;
}
const promise = ns.grow(job.target, { additionalMsec: delay });
tDelay += performance.now() - start;
port.write(tDelay);
await promise;
ns.atExit(() => {
const end = Date.now();
if (job.report) ns.writePort(job.port, job.type + job.batch);
// Uncomment one of these if you want to log completed jobs. Make sure to uncomment the appropriate lines in the controller as well.
// ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
// ns.writePort(job.log, `Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end + tDelay).toString().slice(-6)}\n`);
});
}

View File

@@ -1,30 +0,0 @@
/*
A pretty big change this time. Well, big for workers anyway. I've tightened up the delay calculations
to be as perfect as I can get them. Full comments in weaken.js as usual.
*/
/** @param {NS} ns */
export async function main(ns) {
const start = performance.now();
const port = ns.getPortHandle(ns.pid);
const job = JSON.parse(ns.args[0]);
let tDelay = 0;
let delay = job.end - job.time - Date.now();
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
tDelay = -delay
delay = 0;
}
const promise = ns.hack(job.target, { additionalMsec: delay });
tDelay += performance.now() - start;
port.write(tDelay);
await promise;
ns.atExit(() => {
const end = Date.now();
if (job.report) ns.writePort(job.port, job.type + job.batch);
// Uncomment one of these if you want to log completed jobs. Make sure to uncomment the appropriate lines in the controller as well.
// ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
// ns.writePort(job.log, `Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end + tDelay).toString().slice(-6)}\n`);
});
}

View File

@@ -1,41 +0,0 @@
/*
A pretty big change this time. Well, big for workers anyway. I've tightened up the delay calculations
to be as perfect as I can get them. Full comments in weaken.js as usual.
*/
/** @param {NS} ns */
export async function main(ns) {
const start = performance.now();
const port = ns.getPortHandle(ns.pid); // We have to define this here. You'll see why in a moment.
const job = JSON.parse(ns.args[0]);
let tDelay = 0;
let delay = job.end - job.time - Date.now();
// Don't report delay right away.
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
tDelay = -delay
delay = 0;
}
// The actual function call can take some time, so instead of awaiting on it right away, we save the promise for later.
const promise = ns.weaken(job.target, { additionalMsec: delay });
// Then after calling the hack function, we calculate our final delay and report it to the controller.
tDelay += performance.now() - start;
// The ns object is tied up by the promise, so invoking it now would cause a concurrency error.
// That's why we fetched this handle earlier.
port.write(tDelay);
// Then we finally await the promise. This should give millisecond-accurate predictions for the end time of a job.
await promise;
ns.atExit(() => {
const end = Date.now();
if (job.report) ns.writePort(job.port, job.type + job.batch);
// Uncomment one of these if you want to log completed jobs. Make sure to uncomment the appropriate lines in the controller as well.
// ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
// ns.writePort(job.log, `Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end + tDelay).toString().slice(-6)}\n`);
});
}

View File

@@ -1,420 +0,0 @@
/*
We've got a brand new class to look at, but the rest of the file remains unchanged.
*/
/** @param {NS} ns */
export async function main(ns) {
ns.tprint("This is just a function library, it doesn't do anything.");
}
/*
This is an overengineered abomination of a custom data structure. It is essentially a double-ended queue,
but also has a Map stapled to it, just in case we need to access items by id (we don't.)
The idea is that it can fetch/peek items from the front or back with O(1) timing. This gets around the issue of
dynamic arrays taking O(n) time to shift, which is terrible behavior for very long queues like the one we're using.
*/
export class Deque {
#capacity = 0; // The maximum length.
#length = 0; // The actual number of items in the queue
#front = 0; // The index of the "head" where data is read from the queue.
#deleted = 0; // The number of "dead" items in the queue. These occur when items are deleted by index. They are bad.
#elements; // An inner array to store the data.
#index = new Map(); // A hash table to track items by ID. Try not to delete items using this, it's bad.
// Create a new queue with a specific capacity.
constructor(capacity) {
this.#capacity = capacity;
this.#elements = new Array(capacity);
}
// You can also convert arrays.
static fromArray(array, overallocation = 0) {
const result = new Deque(array.length + overallocation);
array.forEach(item => result.push(item));
return result;
}
// Deleted items don't count towards length, but they still take up space in the array until they can be cleared.
// Seriously, don't use the delete function unless it's absolutely necessary.
get size() {
return this.#length - this.#deleted;
}
isEmpty() {
return this.#length - this.#deleted === 0;
}
// Again, "deleted" items still count towards this. Use caution.
isFull() {
return this.#length === this.#capacity;
}
// The "tail" where data is typically written to.
// Unlike the front, which points at the first piece of data, this point at the first empty slot.
get #back() {
return (this.#front + this.#length) % this.#capacity;
}
// Push a new element into the queue.
push(value) {
if (this.isFull()) {
throw new Error("The deque is full. You cannot add more items.");
}
this.#elements[this.#back] = value;
this.#index.set(value.id, this.#back);
++this.#length;
}
// Pop an item off the back of the queue.
pop() {
while (!this.isEmpty()) {
--this.#length;
const item = this.#elements[this.#back];
this.#elements[this.#back] = undefined; // Free up the item for garbage collection.
this.#index.delete(item.id); // Don't confuse index.delete() with this.delete()
if (item.status !== "deleted") return item; // Clear any "deleted" items we encounter.
else --this.#deleted; // If you needed another reason to avoid deleting by ID, this breaks the O(1) time complexity.
}
throw new Error("The deque is empty. You cannot delete any items.");
}
// Shift an item off the front of the queue. This is the main method for accessing data.
shift() {
while (!this.isEmpty()) {
// Our pointer already knows exactly where the front of the queue is. This is much faster than the array equivalent.
const item = this.#elements[this.#front];
this.#elements[this.#front] = undefined;
this.#index.delete(item.id);
// Move the head up and wrap around if we reach the end of the array. This is essentially a circular buffer.
this.#front = (this.#front + 1) % this.#capacity;
--this.#length;
if (item.status !== "deleted") return item;
else --this.#deleted;
}
throw new Error("The deque is empty. You cannot delete any items.");
}
// Place an item at the front of the queue. Slightly slower than pushing, but still faster than doing it on an array.
unshift(value) {
if (this.isFull()) {
throw new Error("The deque is full. You cannot add more items.");
}
this.#front = (this.#front - 1 + this.#capacity) % this.#capacity;
this.#elements[this.#front] = value;
this.#index.set(value.id, this.#front);
++this.#length;
}
// Peeking at the front is pretty quick, since the head is already looking at it. We just have to clear those pesky "deleted" items first.
peekFront() {
if (this.isEmpty()) {
throw new Error("The deque is empty. You cannot peek.");
}
while (this.#elements[this.#front].status === "deleted") {
this.#index.delete(this.#elements[this.#front]?.id);
this.#elements[this.#front] = undefined;
this.#front = (this.#front + 1) % this.#capacity;
--this.#deleted;
--this.#length;
if (this.isEmpty()) {
throw new Error("The deque is empty. You cannot peek.");
}
}
return this.#elements[this.#front];
}
// Peeking at the back is ever so slightly slower, since we need to recalculate the pointer.
// It's a tradeoff for the faster push function, and it's a very slight difference either way.
peekBack() {
if (this.isEmpty()) {
throw new Error("The deque is empty. You cannot peek.");
}
let back = (this.#front + this.#length - 1) % this.#capacity;
while (this.#elements[back].status === "deleted") {
this.#index.delete(this.#elements[back].id);
this.#elements[back] = undefined;
back = (back - 1 + this.#capacity) % this.#capacity;
--this.#deleted;
--this.#length;
if (this.isEmpty()) {
throw new Error("The deque is empty. You cannot peek.");
}
}
return this.#elements[back];
}
// Fill the queue with a single value.
fill(value) {
while (!this.isFull()) {
this.push(value);
}
}
// Empty the whole queue.
clear() {
while (!this.isEmpty()) {
this.pop();
}
}
// Check if an ID exists.
exists(id) {
return this.#index.has(id);
}
// Fetch an item by ID
get(id) {
let pos = this.#index.get(id);
return pos !== undefined ? this.#elements[pos] : undefined;
}
// DON'T
delete(id) {
let item = this.get(id);
if (item !== undefined) {
item.status = "deleted";
++this.#deleted;
return item;
} else {
throw new Error("Item not found in the deque.");
}
}
}
// The recursive server navigation algorithm. The lambda predicate determines which servers to add to the final list.
// You can also plug other functions into the lambda to perform other tasks that check all servers at the same time.
/** @param {NS} ns */
export function getServers(ns, lambdaCondition = () => true, hostname = "home", servers = [], visited = []) {
if (visited.includes(hostname)) return;
visited.push(hostname);
if (lambdaCondition(hostname)) servers.push(hostname);
const connectedNodes = ns.scan(hostname);
if (hostname !== "home") connectedNodes.shift();
for (const node of connectedNodes) getServers(ns, lambdaCondition, node, servers, visited);
return servers;
}
// Here are a couple of my own getServers modules.
// This one finds the best target for hacking. It tries to balance expected return with time taken.
/** @param {NS} ns */
export function checkTarget(ns, server, target = "n00dles", forms = false) {
if (!ns.hasRootAccess(server)) return target;
const player = ns.getPlayer();
const serverSim = ns.getServer(server);
const pSim = ns.getServer(target);
let previousScore;
let currentScore;
if (serverSim.requiredHackingSkill <= player.skills.hacking / (forms ? 1 : 2)) {
if (forms) {
serverSim.hackDifficulty = serverSim.minDifficulty;
pSim.hackDifficulty = pSim.minDifficulty;
previousScore = pSim.moneyMax / ns.formulas.hacking.weakenTime(pSim, player) * ns.formulas.hacking.hackChance(pSim, player);
currentScore = serverSim.moneyMax / ns.formulas.hacking.weakenTime(serverSim, player) * ns.formulas.hacking.hackChance(serverSim, player);
} else {
const weight = (serv) => {
// Calculate the difference between max and available money
let diff = serv.moneyMax - serv.moneyAvailable;
// Calculate the scaling factor as the ratio of the difference to the max money
// The constant here is just an adjustment to fine tune the influence of the scaling factor
let scalingFactor = diff / serv.moneyMax * 0.95;
// Adjust the weight based on the difference, applying the scaling penalty
return (serv.moneyMax / serv.minDifficulty) * (1 - scalingFactor);
}
previousScore = weight(pSim)
currentScore = weight(serverSim)
}
if (currentScore > previousScore) target = server;
}
return target;
}
// A simple function for copying a list of scripts to a server.
/** @param {NS} ns */
export function copyScripts(ns, server, scripts, overwrite = false) {
for (const script of scripts) {
if ((!ns.fileExists(script, server) || overwrite) && ns.hasRootAccess(server)) {
ns.scp(script, server);
}
}
}
// A generic function to check that a given server is prepped. Mostly just a convenience.
export function isPrepped(ns, server) {
const tolerance = 0.0001;
const maxMoney = ns.getServerMaxMoney(server);
const money = ns.getServerMoneyAvailable(server);
const minSec = ns.getServerMinSecurityLevel(server);
const sec = ns.getServerSecurityLevel(server);
const secFix = Math.abs(sec - minSec) < tolerance;
return (money === maxMoney && secFix) ? true : false;
}
/*
This prep function isn't part of the tutorial, but the rest of the code wouldn't work without it.
I don't make any guarantees, but I've been using it and it's worked well enough. I'll comment it anyway.
The prep strategy uses a modified proto-batching technique, which will be covered in part 2.
*/
/** @param {NS} ns */
export async function prep(ns, values, ramNet) {
const maxMoney = values.maxMoney;
const minSec = values.minSec;
let money = values.money;
let sec = values.sec;
while (!isPrepped(ns, values.target)) {
const wTime = ns.getWeakenTime(values.target);
const gTime = wTime * 0.8;
const dataPort = ns.getPortHandle(ns.pid);
dataPort.clear();
const pRam = ramNet.cloneBlocks();
const maxThreads = Math.floor(ramNet.maxBlockSize / 1.75);
const totalThreads = ramNet.prepThreads;
let wThreads1 = 0;
let wThreads2 = 0;
let gThreads = 0;
let batchCount = 1;
let script, mode;
/*
Modes:
0: Security only
1: Money only
2: One shot
*/
if (money < maxMoney) {
gThreads = Math.ceil(ns.growthAnalyze(values.target, maxMoney / money));
wThreads2 = Math.ceil(ns.growthAnalyzeSecurity(gThreads) / 0.05);
}
if (sec > minSec) {
wThreads1 = Math.ceil((sec - minSec) * 20);
if (!(wThreads1 + wThreads2 + gThreads <= totalThreads && gThreads <= maxThreads)) {
gThreads = 0;
wThreads2 = 0;
batchCount = Math.ceil(wThreads1 / totalThreads);
if (batchCount > 1) wThreads1 = totalThreads;
mode = 0;
} else mode = 2;
} else if (gThreads > maxThreads || gThreads + wThreads2 > totalThreads) {
mode = 1;
const oldG = gThreads;
wThreads2 = Math.max(Math.floor(totalThreads / 13.5), 1);
gThreads = Math.floor(wThreads2 * 12.5);
batchCount = Math.ceil(oldG / gThreads);
} else mode = 2;
// Big buffer here, since all the previous calculations can take a while. One second should be more than enough.
const wEnd1 = Date.now() + wTime + 1000;
const gEnd = wEnd1 + values.spacer;
const wEnd2 = gEnd + values.spacer;
// "metrics" here is basically a mock Job object. Again, this is just an artifact of repurposed old code.
const metrics = {
batch: "prep",
target: values.target,
type: "none",
time: 0,
end: 0,
port: ns.pid,
log: values.log,
report: false
};
// Actually assigning threads. We actually allow grow threads to be spread out in mode 1.
// This is because we don't mind if the effect is a bit reduced from higher security unlike a normal batcher.
// We're not trying to grow a specific amount, we're trying to grow as much as possible.
for (const block of pRam) {
while (block.ram >= 1.75) {
const bMax = Math.floor(block.ram / 1.75)
let threads = 0;
if (wThreads1 > 0) {
script = "S4tWeaken.js";
metrics.type = "pWeaken1";
metrics.time = wTime;
metrics.end = wEnd1;
threads = Math.min(wThreads1, bMax);
if (wThreads2 === 0 && wThreads1 - threads <= 0) metrics.report = true;
wThreads1 -= threads;
} else if (wThreads2 > 0) {
script = "S4tWeaken.js";
metrics.type = "pWeaken2";
metrics.time = wTime;
metrics.end = wEnd2;
threads = Math.min(wThreads2, bMax);
if (wThreads2 - threads === 0) metrics.report = true;
wThreads2 -= threads;
} else if (gThreads > 0 && mode === 1) {
script = "S4tGrow.js";
metrics.type = "pGrow";
metrics.time = gTime;
metrics.end = gEnd;
threads = Math.min(gThreads, bMax);
metrics.report = false;
gThreads -= threads;
} else if (gThreads > 0 && bMax >= gThreads) {
script = "S4tGrow.js";
metrics.type = "pGrow";
metrics.time = gTime;
metrics.end = gEnd;
threads = gThreads;
metrics.report = false;
gThreads = 0;
} else break;
metrics.server = block.server;
const pid = ns.exec(script, block.server, { threads: threads, temporary: true }, JSON.stringify(metrics));
if (!pid) throw new Error("Unable to assign all jobs.");
block.ram -= 1.75 * threads;
}
}
// Fancy UI stuff to update you on progress.
const tEnd = ((mode === 0 ? wEnd1 : wEnd2) - Date.now()) * batchCount + Date.now();
const timer = setInterval(() => {
ns.clearLog();
switch (mode) {
case 0:
ns.print(`Weakening security on ${values.target}...`);
break;
case 1:
ns.print(`Maximizing money on ${values.target}...`);
break;
case 2:
ns.print(`Finalizing preparation on ${values.target}...`);
}
ns.print(`Security: +${ns.formatNumber(sec - minSec, 3)}`);
ns.print(`Money: \$${ns.formatNumber(money, 2)}/${ns.formatNumber(maxMoney, 2)}`);
const time = tEnd - Date.now();
ns.print(`Estimated time remaining: ${ns.tFormat(time)}`);
ns.print(`~${batchCount} ${(batchCount === 1) ? "batch" : "batches"}.`);
}, 200);
ns.atExit(() => clearInterval(timer));
// Wait for the last weaken to finish.
do await dataPort.nextWrite(); while (!dataPort.read().startsWith("pWeaken"));
clearInterval(timer);
await ns.sleep(100);
money = ns.getServerMoneyAvailable(values.target);
sec = ns.getServerSecurityLevel(values.target);
}
return true;
}
// I don't actually use this anywhere it the code. It's a debugging tool that I use to test the runtimes of functions.
export function benchmark(lambda) {
let result = 0;
for (let i = 0; i <= 1000; ++i) {
const start = performance.now();
lambda(i);
result += performance.now() - start;
}
return result / 1000;
}

View File

@@ -1 +1 @@
{"home":{"n00dles":{},"foodnstuff":{},"sigma-cosmetics":{"zer0":{"omega-net":{"the-hub":{},"netlink":{"rothman-uni":{"rho-construction":{"aerocorp":{"unitalife":{"icarus":{"infocomm":{"titan-labs":{"fulcrumtech":{"omnitek":{},"4sigma":{"powerhouse-fitness":{}}}}}},"solaris":{"nova-med":{"run4theh111z":{}}}}}}},"catalyst":{}}}},"max-hardware":{}},"joesguns":{},"hong-fang-tea":{},"harakiri-sushi":{"nectar-net":{"neo-net":{"avmnite-02h":{}},"phantasy":{}},"CSEC":{"silver-helix":{"computek":{"zb-institute":{"lexo-corp":{"global-pharm":{"omnia":{"defcomm":{"zb-def":{"microdyne":{"vitalife":{"kuai-gong":{},".":{"b-and-a":{},"blade":{"fulcrumassets":{}},"nwo":{"The-Cave":{}},"clarkinc":{"ecorp":{},"megacorp":{}}}}}}}},"deltaone":{"univ-energy":{"taiyang-digital":{"applied-energetics":{"stormtech":{},"helios":{}}}},"zeus-med":{}}}},"alpha-ent":{"galactic-cyber":{}}}},"johnson-ortho":{"summit-uni":{"millenium-fitness":{}},"I.I.I.I":{"aevum-police":{"snap-fitness":{}}}},"crush-fitness":{"syscore":{}}}}},"iron-gym":{},"darkweb":{},"pserv-0":{},"pserv-1":{},"pserv-2":{},"pserv-3":{},"pserv-4":{},"pserv-5":{},"pserv-6":{},"pserv-7":{},"pserv-8":{},"pserv-9":{},"pserv-10":{},"pserv-11":{},"pserv-12":{},"pserv-13":{},"pserv-14":{},"pserv-15":{},"pserv-16":{},"pserv-17":{},"pserv-18":{},"pserv-19":{},"pserv-20":{},"pserv-21":{},"pserv-22":{},"pserv-23":{},"pserv-24":{}}}
{"home":{"n00dles":{"nectar-net":{"neo-net":{"computek":{"summit-uni":{}}}}},"foodnstuff":{"zer0":{"silver-helix":{"the-hub":{"rothman-uni":{"millenium-fitness":{}}},"netlink":{"zb-institute":{"alpha-ent":{"galactic-cyber":{}}}},"johnson-ortho":{},"crush-fitness":{}}},"max-hardware":{"phantasy":{},"omega-net":{"avmnite-02h":{"syscore":{"rho-construction":{},"aevum-police":{"global-pharm":{"omnia":{"defcomm":{},"solaris":{}}}}},"catalyst":{},"I.I.I.I":{"lexo-corp":{"aerocorp":{"deltaone":{"icarus":{"infocomm":{"run4theh111z":{"helios":{}}},"taiyang-digital":{"titan-labs":{"vitalife":{"omnitek":{"nwo":{},"powerhouse-fitness":{"fulcrumassets":{},"The-Cave":{}}}}}},"zb-def":{"microdyne":{}}},"zeus-med":{}},"unitalife":{"univ-energy":{"nova-med":{"applied-energetics":{"fulcrumtech":{"4sigma":{},".":{"b-and-a":{"ecorp":{}}}},"stormtech":{"kuai-gong":{"blade":{},"clarkinc":{"megacorp":{}}}}}}}}},"snap-fitness":{}}}}}}},"sigma-cosmetics":{"CSEC":{}},"joesguns":{},"hong-fang-tea":{},"harakiri-sushi":{},"iron-gym":{},"darkweb":{},"pserv-01":{},"pserv-02":{},"pserv-03":{},"pserv-04":{},"pserv-05":{},"pserv-06":{},"pserv-07":{},"pserv-08":{},"pserv-09":{},"pserv-10":{},"pserv-11":{},"pserv-12":{},"pserv-13":{},"pserv-14":{},"pserv-15":{},"pserv-16":{},"pserv-17":{},"pserv-18":{},"pserv-19":{},"pserv-20":{},"pserv-21":{},"pserv-22":{},"pserv-23":{},"pserv-24":{},"pserv-25":{}}}

View File

@@ -1,42 +0,0 @@
import { getCracks, scanServerList, findBestTarget, crackingAndRooting, copyAndRunScript, purchaseAndUpgradeServers } from "/RamsesUtils.js";
/** @param {NS} ns */
export async function main(ns) {
const funnyScript = ["batch.js", "Ramses-grow.js", "Ramses-weaken.js", "Ramses-hack.js"];
//write function to purchase scripts from tor network and rerun getCracks() then recrack and reroot
let cracks = {};
cracks = getCracks(ns);
let maxPorts = Object.keys(cracks).length;
scanServerList(ns);
let manualTargetOverride = "";
if (ns.getHackingLevel() < 200) {
manualTargetOverride = "n00dles";
};
findBestTarget(ns, 999, maxPorts, ns.getHackingLevel(), manualTargetOverride);
let bestTarget = ns.read("bestTarget.txt")
ns.tprint("Best Target: " + bestTarget);
ns.tprint(Object.keys(JSON.parse(ns.read("serverList.txt"))).length);
crackingAndRooting(ns, cracks, funnyScript, true);
ns.exec(funnyScript[0], "home", 1, JSON.parse(bestTarget).serverName, 500, true);
let reset = ns.args[0];
ns.print(reset);
if (reset === true) {
ns.tprint("reset === true")
findBestTarget(ns, 999, maxPorts, ns.getHackingLevel(), manualTargetOverride);
let serverList = JSON.parse(ns.read("serverList.txt"));
for (const [name, entry] of Object.entries(serverList)) {
copyAndRunScript(ns, funnyScript, name);
}
}
/*let serverListForFiles = JSON.parse(ns.read("serverList.txt"));
for (const [name2, entry2] of Object.entries(serverListForFiles)) {
ns.tprint(name2 + " Files: " + entry2.serverFiles)
}*/
//await ns.sleep(500000);
await purchaseAndUpgradeServers(ns);
}

View File

@@ -1,5 +0,0 @@
import { getCracks, scanServerList, findBestTarget, crackingAndRooting, copyAndRunScript, runControllerOnPserv } from "/RamsesUtils.js";
/** @param {NS} ns */
export async function main(ns) {
await runControllerOnPserv(ns)
}

View File

@@ -1,14 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let player = null;
player = ns.getPlayer();
ns.tprint(player)
/*
while (true) {
ns.singularity.
}
*/
}

View File

@@ -0,0 +1,72 @@
/** @param {NS} ns */
export async function main(ns) {
const sNode = ns.args[0]; // target server
//logs
ns.disableLog("getServerSecurityLevel");
ns.disableLog("getServerMoneyAvailable");
//server info
const nMinSec = ns.getServerMinSecurityLevel(sNode);
const nMaxCash = ns.getServerMaxMoney(sNode);
while (true) {
//timestamp
let currentDate = new Date();
let ntimeStamp = currentDate.toLocaleTimeString('sw-SV') + " -";
const nCurrSec = ns.getServerSecurityLevel(sNode);
const nCurrCash = ns.getServerMoneyAvailable(sNode);
ns.print(ntimeStamp + "---------------------------------------------------------------");
ns.print("Cash: " + (Math.floor(nCurrCash * 1000) / 1000) + " / " + nMaxCash);
ns.print("Security: " + (Math.floor(nCurrSec * 1000) / 1000) + " / " + nMinSec);
if (nCurrSec > (nMinSec + 1)) {
//Calculate estimate time of completion
let nOffset = ns.getWeakenTime(sNode);
let nWeakTime = new Date(currentDate.getTime() + nOffset);
let sWeakTime = nWeakTime.toLocaleTimeString('sw-SV');
//Print estimated time of completion
ns.print("Weakening " + sNode + " Estimated complete at " + sWeakTime);
//run weaken
await ns.weaken(sNode);
//ns.spawn(sWeakenScript, {threads: 1, spawnDelay: 0});
ns.print("-------------------------------------------------------------------------");
}
else if (nCurrCash < (nMaxCash * 0.9)) {
//Calculate estimate time of completion
let nOffset = ns.getGrowTime(sNode);
let nGrowTime = new Date(currentDate.getTime() + nOffset);
let sGrowTime = nGrowTime.toLocaleTimeString('sw-SV');
//Print estimated time of completion
ns.print("Growing " + sNode + " Estimated complete at " + sGrowTime);
//run grow
await ns.grow(sNode);
ns.print("-------------------------------------------------------------------------");
}
else {
//Calculate estimate time of completion
let nOffset = ns.getHackTime(sNode);
let nHackTime = new Date(currentDate.getTime() + nOffset);
let sHackTime = nHackTime.toLocaleTimeString('sw-SV');
//Print estimated time of completion
ns.print("Hacking " + sNode + " Estimated complete at " + sHackTime);
//run hack
await ns.hack(sNode);
//await ns.singularity.manualHack();
ns.print("-------------------------------------------------------------------------");
}
}
}

View File

@@ -0,0 +1,82 @@
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$XX$$XXXXXXX$XXXXXXxxxxXXXXXxxxxXxXXXXXxxxxxXX$XXXXXXXXXXXXXX$$$$$$$$$$$XX$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$X$$$$$
$$$$$$$$$$$$$$$$$$$$XXXXX$$XX$XXXXXXXXX$$XXXXXX$XXXXXXXxxxxXXxXXxxxxxxxxxXXxxxxxxXXXXXXXXXXXXXXXXXXXX$XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXX$$$$$$$$xx$$$$$
$$$XXXX$XX$$X$$$$$$$$$$XX$XXX$$XXXXXXXXX$XXXXXXXXXXXXXXXxxxXXxXXxxxxxxxxxXXxxxx+xxxXXXXXxXXXXXXXXXXXXXXXXXXXXXXXXXX$XX$$$$$X$$XXX$$$XX$$$$$$$$$xxX$$$$
XXX..;xX$XX$XX$$$$$$$$$XXXXXX$$XX$XXXXXXXXXXXXXXXXXXXxXxxxxxxxXXxxxxxxxxXXXxxxx+xxxxXXXXxXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$X$XXX$$XXX$$$$$$$$$$$$$$xXX$$$$
X$X:..xXXXX$XX$XX$$$$$$XXXXXX$$XXXXXXXXXXXXXXXXXXXXXXXXxxxxxx+xxxxxxxxxxxxxxxxx;x+xXXXXXxXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$XXXXXXXXXX$$$XXXXX$$$$$$XxX$$$$
$$XX$XXXXXXXXXXXXX$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxx++xxxxxxxxxxxxxxxxx;x+++XXXXxXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$XXXXXXXX$$$$$$XXX$X$$
$$$X$XX$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxxxxxxxxxx;x;++XXXXxXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxXXXXXXXXXX+XXXXXX$$$$$$xXX$X$$
XX$X$$X$XXX$XXX$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxx+xxxxxXxxxxx;x+;;XXXXxXXXXXXXXXxXXXX+;++xXXXXXXXXXXXxXXXXXXXXXX+xxxXXX$$X$$XXxXXxX$
$$$$$$X$XXX$$XX$XXXX$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxXXxxx+xx+XXXXXx+x++;;;XXxxxXXXXXXXXXxXXXXx++++XxXXXXXXXXXxXXXXXXXXXXx;xxXXX$$XX$$X+xxXxX
$X$X$$X$XXX$$XX$XXXXXXXXXXXXXXXXXXXXXXXXXXX+;:xXXXXXXXXxxxxxxxxXx+++xx+XXXXXx++++;;+XXxxxXXXXxXXxX+xXXX+;;;+xxxxXXXxxXXxXXXxXXXXXXx;xxXXXX$XX$$XxxXXxX
$$$$$$$$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxX$x;+++++xxxxxxxxxx++++x++xXXXX++++++;xXxxxxXXxxXxxxxxxXXX+;;;;xxxxxxxXxXXxxxxxXXXxXXx+xxxxXX$XX$$XxxXXxX
$$$$$$$$$XXXXXXXXXXXXXXXXXXXXXXXXxXXXXXXXXxX$$$$$$xxx++;+;;;;xxx++++xx+xXXXXx++++;;;xxxx+XXxxxxxxxxxXXXx;;:;xx+xxxxxxXXxxxxxXXXxXXxxxxXXXX$XX$XxxxxxXX
$$$$$$$$$XXXXXXXXXXXXXXXXXXXXXXXXxXXXXXXXxXX$$$$$$$$Xxx;:..:X+xx+xxxxx;+XXXXx+++;;::+xxx+XXxxxxxxxxxxxxxxx;xxx++xxxx+xxx+xxxXXXxXX+;+xxxXX$XXXXXxxxXXx
X$XXXX$$$XXXXXXXXXXXXXXXXXXXXXXXXxXxxXXX+xX$$$$$$$$$$$$Xx;;X+xxx+++xxx;xXXXxx+++;;;;+xxxxxxxxxxxx+xxxxx+++;xx++;++xx+xxx+xxxxXXxXX+:xxxXxX$XXx++xXXXX+
XXXXXX$$$XXXXXXXXXXXXXXXXXXXXXXXXxxxxXXxXXX$$$$$$$$$$$$$$XX+$xxx+x+xxx+xxXxxx++++++++xxxxxxxxxx++x+xxx+++;;+++;;+++x+xxx+xxxxXX+Xxx+Xxxxxxx+++xXXXxxx+
$$$$$$$$$XXXXXXXXXXXXXXXXXXXXxX++xxxxxxXxX$$$$$$$$$$$$$$$$XX$xxxxxxxxx+xxxxxxxxx+xxx+xxx+xxxxxx+++++x+++;;::;++:;;++++xxx+xxxXXx+x+XXXxx+;+xxXXx++;++;
$$$$$$$$$$XXXXXXXXXXXXXXXXXXXxXX+xx+++xxX$$$$$$$$$$$$$$$$$$xXxXxxxxxxxxxxxxxxxxx+xxx+xxx+xxx+++++++++++: :;+++xxx+xxxXXxxx+Xx+++xxXXx+;;;;;++x
$$$$$$$$$$$$XXXXXXXXXXXXXXXXXxXXXXx++;;;XX$$$$$$$$$$$$$$$$$XXxxxxxxxxxxxxxxxx+x++xx++xx+;++++++;;;;+++;: .;+++x+x+xxxxXx+++XxxXXXx+;;:;::;xXx+
$$$$XX$$XXX$XXXXXXXXXXXXXXXXXxXXXXxXX+++x+xX$$$$$$$$$$$$$$$$x+xxxxxxxxx+xxxxx+++++++;+++;+++++;;;;;;+;;; ..;;+;x++++xxxxxxXx+XXx++;;;::+Xx+x$x+
$$$$$X$$X$$$XXXXXXXXXXXXXXXXXxXXXXXXXX$xxx+xxX$$$$$$$$$$$$$$xXxx++xxxxx+xxxxx+++++++;+++;+;+++;;;;;;;;;:: ::;++;x++++xxxxxxxx;xX+;;;+xXXXX+;x$xx
XXX$$XX$X$$$XXXXxxXXXXXXXXXXXXXXXXXXXX$X$XxxxxxX$$$$$$$$$$$$$$$Xxx+;x+x+xxxxx;++; :+++;;;;;;;;::;;;;;;;. :::;++;+++xxxxxxx+;x+xX+xXXXXXXXXXxxXx+
XXXX$XXXX$$XXXXXxxXXXXXXXXXXXXXXXx;$$X$$$$$$XXxXxX$$$$$$$$$$$$$$XXXxx+x+xxxxx;+++. :;+;:;;;;:::::;;;;;;;;;;+;:;;:++++xxxxx++;xX+;Xxx+;+xXXXXXx++x++X;
XXXXXXXXXXXXXXXXXxxXXXXxXXXXXXXXx;xXX$X$$$XXXX$$$XxXXX$$$$$$$$$$Xxx+x+x+xxxxx++++ .;;;:::::...::;;;;;;;;:+++;;+;++++x+;;;+x++xxXxx:;++xxXXXx+;;x:;X;
XXXXXXx+XXXXXXXXXx+XXXxxXXXXXxx;+:++$X$XXX$XXXXX$$$$$$$$$$$$$XXxxxx+xxx+xxxxx++;; :;:..:.. ..::;;;;;;++++x++++;++++++++++xXXxxXXx;+xxxxxXXx;+XX;:X:
XXXXX+;::;xXXXXXXxxXXXxxXXXXX:;++:x+$X$XXXXXXXXXXX$XX$$$Xxxxxxxxxxxxxxxxx++++++;;;;:::::.. ..:;;;;;;++++xx+++;+++++xxxxxxxXxxxXX;+x+;.:xXxx++X+;X;
XXXXXx+x;+xXXXXXXX+XXXXxXXx+:;++x;x$$X$XXXXXXXXXXXXXXX$xx+x+xxxxxxxxx+xxx++++++;;;;:;:. ..::;;;+;+;++xx++x;++xxxxxxxxxxXXXXXX;.;+; :x+. .;X;
XXXXXXX+xxxXXXXXXX+XXxx;;x+ ;+x+X$X$$$XXXXXXXXXXXXXX$Xx+xxxxx+x++++;+xx++;.++;;;;::.. ..::;;;;+++++Xx++x;xxxxxxxxXxXXXXXXXx;.:+: ;:: :X+
$XXXXxx;+xxXXXXXXx+;+++;++;++;+xXxX$$X$$$$XXXXXXX$XXX$$$Xx+xxx+++++++;+xx++++++;;;:::::. .::;;;;++++++Xx++++xxxxxxxxXXXXXXXXXx;..+; . ;Xx
XXXXXxx;++xXXXx++++:;::;:++xxxxxXXX$$XX$$$$$XXXXXXXX$$$$$XXx+xxx+++++++xx++++;::;;;;;:::. .. ..::;;;+;+;++++XXx++xxxxxxxxxXXXXxXx;;:::.+; .;: :+Xx
XXXXXxx+++xX+;++;;::::;;xxxXxXXXXXX$$$XXXX$$$XXXXXXX$$$$$$XxX+x+++++xxxxx+;;;;++;;;;;:;:::::.:::;;;;;;;+++++XXx++xxxxxxxxxx+;::++:;++: ++ ;++xXXx+XX
xxXXXxXXXXxxXx++x+xx+++xXxXXXxx++;x$$$$XXXXX$$$$$$XX$$$$$$$$xX+++++++xxxx+;;+++++;;;;;;;;;;;:;:;;+;+++++++++XXx++xxxxxx;. ++:+xXxxxxx;;++ ;XXXXXXxXX
;+:;++XXXxxXXXXxxXxXxXXXXxxx;;+xX$$$$$$$XXXXXX$$$XXX$$$$$$$$XX$Xx;+++xxx++;+++++++;+;;;+;;;+;;;;;++++;+;++++XXxx+xxxxx+;++xx+xXXXxxxx+;xx; +XXXXxxxXX
:; +XXxXXXXXXxXXXXXXXXXX+x$$$X$XxX$X$$XXXXXX$$$$XX$$$$$$$$$xxXxX+++++++++++++x+;;;;;+;++++;+++;;+++;++++;;xXxxxxxxxxxxXXxX+++xx+++x++xx+.:+XXXXXXxXX
:;+++;xXXXXXX$$XXX$$$$XXX$XX$$Xx;;xX$$$$$XXXX$$$$X$X$$$$$$$$$X$xXxx++++++x++++xx+;;;;;+;+++x;;;+;+++++++++;;xx+:;xxxxXxxxxXx+++xx++++x+xxx;;xXXXXXXXXX
:+x+xxxxXXXXXX$XXX$$$$$XXX$$$$XXxXxXX$$$$$XX$$$$$$X$$$$$$$$$$$$X$xX++++++x++++++;;;;;;+++++x+++++++x++++:: .X;.;xxxXXxxxxXxx++xx++;+xxXxx;;xXXXXXXXXX
:+xx++xXXX$XX$$$XX$$$$$XXx$$$X$$$Xx+x$$$$$$XX$$$$$X$$$$$$$$$$$$XXXX+++++++++++;;;;;;;;+++++x++++++xxxxx; . :X+.;xxxXXxxxxxx+++xx;;;+xxXXX++xXXXXXXXXX
xxXxx+XXX$$$$$$$$X$$$$$$$X$$$$$XXX$$XX$$$$$$XX$$$$XX$$$$$$$$$$XXXXX;;+;;;+x+xx;;;;;;;;;++++xx++++xxxxxXX+;: ;X+:;xxxxXxxxxxxx++xx;;;+xxXXXx+xXXXXXXXXX
xxxxxxXX$$$$$$$$$X$$$$$$$$$$$$$XX$X$$$$$$$$$$$$$$$$X$$$$$$$$$$XXX$$;:;;;;+x+xx;:::::;;;;;;+xx+++++xxxxXXXx+:+Xx:;xxxxXXxxxx+;::;x;;;;+xXXXxxxXXXXXXXXX
xxXxxXXXXX$$$$$$$XX$$$$$$$$$$$$XXXX$$$$$$$$$$$$$$$$X$$$$$$$$$XXXXXXX;;;;;+xxxx;..::::;;::;+xx++++++x+xXXx++;xXX;+xxxxXXxxxxx+; :x;;x++xXXXXxXXXXXXXXXX
xxXXXXXXXX$$$$$$$X$$$$$$$$$$$$$$XX$$$$$$$$$$$$$$$$$$$$$$$$$$$XXxXXX$+;:::;xxxx;.. ..:;;;;:;xx++++++xXXXXXXx+XXXx+xxxxxXxxxxx;; ;x+xxxxXXXXXxXXXXXXXXX$
.$XXXxX$XXX$$$$$$X$$$$$$X$$$$X$XXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$X$XX+ :::;xxxx;....:;: ::xx+;;+xXXXXXXXXXXXXXxxxxxXXXxxxxx++++xxxxx+xxXXXxXX$XXXXXXX
xX$XXXXXXXxX$$$$$X$$$$$$$$$$$$X$XXX$$$$$$$$$$$$$$$$$$$$$$$$$$XXXX$XXx .: +xxx; ++::. ;;xx+;;+xXXXXXXXXXXXXXXXXxxXXXXx+xx++++xxxx++xXXXXXXX$$XXXXXX
:XXXXXX$XxX$$$$$XX$$$$$X$$$$$$$XX$X$$$$$$$$$$$$$$$$$$$$$$$$$$$XXX$XXx: +xxx+. +x;:: ;;xx++:xXXXXXXXXXXXXXXXxxXXXXXxxxxXxxxxxx++++xXXXXXXX$$XXXXXX
;:XXXX$X$$XxX$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$X$XXXXXXX. +xxx+;+xxx+:. +xxxx+;xXXXXXXXXXXXXXXXxxxxxXxxxxxXxxxXxxxxxxxXXXXXXX$$XXXXXX
$+XXX$$$$$$XxX$$$$$$$$$$$$$$$$$$$XXX$$$$$$$$$$$$$$$$$$$$$$$$$$$XXX$$X$;...+xxx+;xxx+x;: ;+xxx+;XXxXXXXXXXXXXXXXXxxxxxx+;+xX$XXXXXXXXXxXXXXXX$XX$X$$X$
$XXX$$$$$$$XXXX$$$$$$$$$$$$$$$$$$X$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$XX$$XX+:..+xxx+;xxx+x+. ;+xxx+;XXxXXXXXXXXXXXXXXxxXXXXXX$XXXXXXXXXXXXxXXXXXX$XXXXXX$X
$$X$$XXXXxXX$$X$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$XX$$$$$$$$$$$$$$X$X$$$$$Xx;.+xxxx+xxxxxx;;;++xxx+;XxxXXXXXXXXXXXXXXXXXXXxxXXX$X$XXXXXXXXxXXXXXXxXX+xxxxX
$xXXxxxxXX$$$X$X$$$$$$$$$$$$$$$$$$$$$X$$$$X$$$X;X$$$$$$$$$$$$$$$X$$$$$$X$+;xxxx+xxxxxx+;++xxxx++xxXXXXXXXXXXXXXXXXXXXXXXXX$XXX$$$$X$XXxxxxxxXXXXxX$$$$
$XxxXX$$x++xXX++xxx+X$$$$$$$$$$$$XX$$XX$$$XX$$$$x$$$$$$$$$$$$$$$X$$$XXXxxX+xxxxx+xx+xxx+++xxxxxxxxxXXXXXXXXXXXXXX$X$$XXXXX$$$$$XXXXXX$xXXXXXXX$$$$$$$$
$XxX$X+++;;xXXXXx++xXX;xxXX;+;;:+;x++x+++X$::;:;;;;::x$$$$$$$$$$$$$XX$X+$X+xxxxxxxx;+xx+;+xxxxxxxxxXXXXX$XXXXXXXXX$$$XXXXXx$$$$$$$$$XXxXXXX$$XXXXX$$$$
XX$$$Xx+X$X$XXx+xXXXxx++xX+x+.:::;;;::;+x. :xxXXXx+$$$$$$$$$$$$Xxx+XXXXxxxxxxx+;;+x+;;xXxXx++xxXXXXX$$XXXXXX$$$$$$$XXXX$$$$$$$X$xXxXXXXXXXXXXX$$$$
X$$$$$$$$$XxxxXXXX$xXXXXx+xx+;:;;+;;;;x;:+;++xxXxx+++XXx$$$$$$x+++xxXXxxXXx+x+++xx++xxxxxxxXXXXxxxxXXXXXX$XXXXXXXX$$$$$$$$$$$$$$$XXXXXxXXXX$$XXXXX$$$$
$$$$X$$$$$XX$$$$$XxX$$XXxxxxxxxX$XxxxXx+;;x+;;;++++XXxXx$$$$Xx++;:::::::+xxXXXXXxxxxxXXXx;+xxxXXXXXXXXXXX$XXXXXXX$$$$$$$XXX$$$$$$XXXXXXX$$$$$$$$$X$$$$
$$XX$$$$$$$$$$$$xx$$$$XXXXXxXXX$XXXXXXXxxx;++xXXXXXX$$XXXx+;;;:.. :xXXXXxxxxx+xxxxxxXXXXXXXx++: ;XX+xX$XXXXXXXXXXXXXXXXX$$$$$$$XxxXXXxxXXXX$$$$$$$$$
$X$$$$$$$$$$$$$XX$X$$$$$$$$XXXX$$XXXXX$XXXXxxxxxxXXx+:...::;;++xxxxXXXXXXX++xxxxxxXxXxxxx++++++xxXXXXXXXXXx+..:;;xxXX+:.+++XX$$$$XXxxxxXXxxXXXxXXXXXXX
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XX$XXXXx+;;;+xxxXXXXXXX$XXXXXXxXXXXXXxxxxxxxx++;:... .:;+xXX++XXXXXXX$$XXXxx++:+xxXxXXXXXXXXXXxXXxxxXxxXXXXXX
$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$X$$X+xXXX$$$$$$$$$$$$$$$XxXXxXXXXXXx::::;;:;;;;. . :xXXXxxx+xx++xXxXX$$$$$$$$XxxXX$$XXX$$$$$$$$$$$$$
$$$$X$$$$$$$$$$$$$$$$$$$$X$$$$XX$$$$$X$$$$X$$$$$$X$$$$$$$$$$$$$XxXXXXX$$xxxxXxx+x;+++xxx;x +:: ... ;XXXXXX$$XXxXXXXXXXxxXXXX$$XXXX$$XX$$$$$$$$$$XX
$$$$xxXXXX:xXX$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$X$xx+.:. ;;;;;;::. . .. :;::xxxx;++XXX$X$$$$$$$$XX+xX+xXXXX$$$$$$$$$$$Xx$X$x$
$$$$xXXXXX;+XXX$$$$$$$$$$$$$$$$$$$$$$$$$$$XX$$X$$$$X$$$$$$$$$$$$$Xx;;+;:::;;+xXXXxXXXXxxxxXXXXXX+x;+x+;+;;;;;..+xxxxXXXXXXXX$$$XXXXxXXXXX$X$$$$$$X+x+:
$$$$XXXXXXXXXXX$$$$$$$$$$$$$$$$$$$$$$$$$$$XX$$$$$$X$$$$$$$$$$$$$$X$x+++xx+++++; +;xxx+:xx;:::..xxxxxxXXXXXXXX$X$$$$$$$$$$$xxXXXXXXXXXXxX$XX$$$$$$$$$XX
$$$$$$XXXXXXXXX$$$$$$$$$$$$$$$$$$$X$$$$$$$XX$$$$$$$$$$$$$$$$$$$$$X$::::;++xxXX; ;+XxXXxXx; .+xxxx;xxXXXXXXXXXXxXXXxxxx++xXXxXXXXX$XXX$$$$$$$$$$Xxx+::
$X$$$XXXXXXXXXX$$$$$$$$$$$$$$$$$$XX$$$$$$$XX$$$$$$$$$$$$$$$$$$$$$XX+;;:...:;:xx:+xxxXXXXXXXXxxxxxXXxXXXXXXXXXx::;x;.::;x+xXXXXXX$XxxXXXXXX$$X$$$XX+;xX
$XXXxXxXX$$$xxX$$$$$$$$$$$$$$$$$$$X$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$X$xxxXXxx;;;XXXxxXXXXXXXXXXXXXXXXXXXXXXXXXX$$$$XXx+:+++xXXX:XXXXXXX;xXX$$$$$$XX$$$xxX
$$$$X$XXxxX$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$X$XXXXXXXxxXXxXXXXx+;+XXXXXXXXXXXXXXXXXXXXX$$$$$$$$$$X: +XXXX$x.+X$$$X$xx$$$$$$$x;;xx
XXxxX$x+++Xxxx$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$X$XXXXxxXXXXxxXxxXxxxxx+:.;xXxXXXXXXXXXXXXXX$$$$$$$$$$$$$xX$$XXX$X$Xx$$$$$$$$$$$+Xx;x
Xx+;X$$XX$XxxX$$$$$$$$$$$$$$$$$$$$$$$XXX$$$XXX$$$$$$$$$$$$$$$$$$$XXXXXXXXXXXXXxxxXXxxXX : .x+XXXXXXXXXXX$X$X$$$$$$$$$$$$$XX$$$$$$$$$$$$$$$$$$$$$x
XXxx$$XXXX$XxX$$$$$X$$$$$$$$$$$$$$$X$XXXX$$XXXX$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXX;:+xxxx+++++++ ; .:;+ :x;;;+xXX$$$$$$$$$$$$$$$$$$$$X+$$$$$$$$$$$$$$$$$$
Xxx$$XXXXXX$xX$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXXx+XX++:..:::;;:. . ..:. ..+ :.;xXX$$$$$$X$$$$$$$$$XXX$$$$$$$$$$$$$$$$$$
$X$$Xx++xXXXXX$$$$$$$$$$$$$$$$$$$$$$$$$XXX$$X$X$$$$$$$$$$$$$$$$$$$$$XxXXxxXXXXx;X+;Xx++: ;+:: ::;;::;;::.:++xXXXX+X$$$$$XXXXXX$$XXX$XXX$$XX$XX$XXX
xx$$$X$X$X$XX$$$$$$$X$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXxxxxxxxXXXxXXX+;xx+: ;+xx+.;:++;+++;;;;:;X;.;::.++;+XXX+xX$$$$XXXXxxxxxxxx
X$X$XXXxX$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$X$$$$$$$$$$$$$$$$$$$$$$$$xxxxxXxXXxXxXXXXxxxx . .:::++ ++xX:;XX+ ;;;;;;;x$x++X$XxxXXXXXxxxxxxxxXxXXX
$$xx++;+x$X$X$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXxxXXxXXxXXXXXXXx;:: .;$XxXXXXXx;;:::.:::::::::;;;++++xxXXXXX$$$$$$$$$$$$$
$XXXXXxxX$$x$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$XxXXXXxXXXXXXXxx+x:+. : . . :..;Xx;::;++xx+ ::. ...:;x$$$$$$$$$$$$$$$$$$$
$$$$$XxX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$xXXxxxxxXXx.XXXXx++x ;x++xx:xXXxX+. :+xX$XX$$$$$$$$$$$$$$$X$XXXXxXX$$$$$$$$
$$$$XXXX$$$XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$X$$$$$$$$$$$$$$$$$$$$$$$$$xXxXxxx:++ +XXXXXXXX+. .+XXXXXXXX$$$$X:x+++;xxXx +;xxXXX$$x+XxX$X$$$$$$$$$$$X
$$$$$xX$X$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XX$X$$$$$$$$$$$$$$$$$$$$$$$$X$$$X$$XXXXXXXXXXXXX: . .+XXXXX$XXXXXX$X$$X;;;+XXxXXXXXXXXXXx++xXXXXXXXXXXXXX$
$$X$XXXXX$XXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXX$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXXXXXXXXXXX+: .+xXXX$XXx+++;;+;x::+ . .+:X;+xXXX$$$$$$$XXXXXX$$
$$$$$$$$X$XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXXXXXX$$XXXXx: ;xXxXX$$X+;: ;;;;+. .:+xXXxXXX$$$XXXXX$XXXXXXXXX
XXXX$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$$$$$XXXXX$$$X$$XX$$$$$$X$+:.:;. ;xx+XX$$Xx++;; .;:x++.+x .+xXXXXXXXX$$$$$$$$$$$$$$
$$$$$$$$XXXXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXXxXX$XXxx+;:+.;; +Xxx:.:+$$$$X+. .;+xXxXXXxXX$$$XXXXxxxxXXXXXXXXXXX$
XXXX$X$$X$XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXxx; XXXxXXXxx..++XX$$+;x .x+XXX$$X+. ..:;;+;.; .;;++xX$X$$$$$$$$$$XX

View File

@@ -0,0 +1,55 @@
$$$$$$$$$$$$$$$$$$$$X$$X$X$XXXXXXXXXXxxXXXXxxxxXXXxxxxXXXXXXXXXXXXX$$$$$XX$$$$$$$$$$$$$$$X$$$$$$X$$$
$$X$X$$$$$$$$$$XXXXXXXXXXXXXXXXXXXXxXxxXXXxxxxxxxXxxxxxXXXXXXXXXXXXXXXXXXXXXXX$$$$$$$X$$XX$$$$$xx$$$
Xx..xXXXX$$$$$$XXXX$$XXXXXXXXXXXXXXxXxxxxxxxxxxxxXxx+xxxXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$$X$$$$$XX$$$
$XXXXXX$XXXX$XXXXXXXXXXXXXXXXXXXXXXXXxxx+xxxxxxxxxxx+x+xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX$$$$XXX$$
$$$$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxx++++XXXXXXXXXXXXXxXXXXXXXXXXXXXXXXXxXXX$$$$XXX$$
$$$$$$X$XXXXX$XXXXXXXXXXXXXXXXXXXXXXXxxxXXxxxx+xXXXx++;+XxxXXXXXXxXXX+++XXXXXXXXXXXXXXX+xxXX$X$XxxxX
$X$$$$XX$XXXXXXXXXXXXXXXXXXXx++xXXXXXxxxxxx++x+xXXX+++;+xxxXXXxXxxxXX;;;xxxXxxXXxXxXXXX+xxXX$X$XxXXX
$$$$$$XXXXXXXXXXXXXXXXXXXXXXxX$$Xx+++;;++xx++x+xXXX++;++xxxxxxxxxxxXx;::x+xxxxXxxxxXXXXxxxXX$XXXxxxX
$$$$$$XXXXXXXXXXXXXXXXXXXXXxX$$$$$$Xx;::x++xxx++XXx++;;:xxxxxxxxxxxxxx++x++xxxxxxxxXXXx++xxX$XXXxxXx
XXXX$$XXXXXXXXXXXXXXXXxXxXxxX$$$$$$$$Xxxxx+x+xx+xxxx+;++xxxxxxx+xxxxx+;++;;+++xxxxxXxXx+xxxXx++xXXX+
$$$$$$XXXXXXXXXXXXXXx+xxxxxX$$$$$$$$$$$XXxxxxxxxxxxxx+xx+xxxxxx++++++;;:::;;++xxxxxxx++xXx++xxxx++++
$$$$$$$XXXXXXXXXXXXXXXx;;;xX$$$$$$$$$$$XXxxxxxxxxxxxx+xx+x+++++;;+++; :;++x+xxxX++xxxxxx;;;;;xx
$$XX$$X$XXXXXXXXXXXXXXXXXx++XX$$$$$$$$$$xxxxxxx+xxx++++++++++++;;;+;;. :;+++++xxxxxxXx+;;;+x+xX+
XX$XX$$$XXXxXXXXXXXXXXXXX$$XxxxX$$$$$$$$XXx+++x+xxx+++..;++;;+;;;;;;;: .:;++++xxxx++xx++xXXXXxxX+
XXX$XX$XXXXxXXXXXXXXXX+x$X$$$XXXxX$$$$$$$$$XXx++xxx+++ :;;;;;:::;;;;;;;+;;;+++xxx++x++x+++XXXX+x+x+
XXXXXXXXXXXxXXXxXXXXx+;xX$$$XXXX$XX$$$$$$XXxx++xxxx++; .;:::...:;;;;;;++++++++++;+xxxxx;+xxxX+;x+++
XXXx+;+XXXXxXXXxXXX+++;xX$XXXXXXXXXX$Xxxxxxxxxxxx++++;;;::. ..:;;;+++x++++++xxxxxxxXx+++:;x+:;x+;
XXXXXXxXXXXXxXXx+;..;x+X$$XXXXXXXXXXXxxxxxxxxx+xx++++;;;:. .:;;;+++x+++xxxxxxxXXXXX:.+ ;;. ++
XXXx+;xxXXXx+++++++++xXX$$$$XXXXXXX$$Xxx++++++;xx++++;;::: .::;;++++xx+xxxxxxxXXXXXx;.+ . +x
XXXx+++xXx++;::;+xxxXXXX$XXX$XXXXXX$$$Xxx+x+++xxx+;;;;;;;::.:..:;;;;;+++Xx+xxxxxxxxxx+::;.; .;.;xxx
xxxxXXxxX+xxx++xXXXxx++X$$XXXX$$$XX$$$$$Xx++++xxx;;+++;;;;;;;:;;;;++++++Xxxxxxx;:+;;xXxx+;+: :XXXxxX
: .+XXxXXXxXXXXXx++xX$XX$$$XXXX$$XX$$$$$XXxx+++x++++++;;;+;;+;;;;++;++++xxxxxxxxxxxxxXxx++x+ ;XXXXxX
:;;+xXXXX$XXX$$XX$X$x++XX$$XXX$$$X$$$$$$$XXX++++++++x+;;;+;+++;++++;++;.++;xxxxxxxx+xx++x+xx:+XXXXXX
:++xXXXX$$$X$$$$XX$$$XxxX$$$$X$$$X$$$$$$$XXXx+++++++;;;;;++++++++xxx+. :+:xxXXxxxx++x;;+xXx+xXXXXXX
xxx+XX$$$$$X$$$$X$$$XX$$X$$$$$$$$$$$$$$$$$XXx;+;+x++;;;;;;++xx++xxxxX+;.++:xxxXxxxx++x;;+xXXxxXXXXXX
xxxxXX$$$$$XX$$$$$$$$XX$$$$$$$$$$$$$$$$$$$XXX+;;;xx+;:::;;::+x++++xxXXx;xx;xxxXxxx+;:x;;+xXXxxXXXXXX
xXXXXXX$$$$$X$$$$$$$$XX$$$$$$$$$$$$$$$$$$XXXX+:::+xx;. .:;;:;x++++xXXXX+XXxxxxXXxx+;.x+xxxXXxXXXXXXX
;XXXXXxX$$$XX$$$$$$$$XX$$$$$$$$$$$$$$$$$$XXXXx ::+xx:.::: .;x+;+XXXXXXXXXxxxxXXxxx++xx+++XXXXX$XXXX
:+XXX$Xx$$$$X$$$$$$$$$X$$$$$$$$$$$$$$$$$$$XXXX; ;xx;.++:. :+x+;xXXXXXXXXXXxxXXxxxXxxxx+++XXXXX$XXXX
x+XX$$$XX$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$XX$Xx..;xx+xxx+. :xxx;XXXXXXXXXXXxxxx++xXXXXXXXxXXXXX$XXXX
$XX$$$$XXX$$$$$$$$$$$$XXX$$$$$$$$$$$$$$$$$XX$$X;.;xx+xxxx:.;xxx;XxXXXXXXXXXxxXXXXXXXXXXXXxXXXXXXXxXX
$XXxxxXX$X$$$$$$$$$$$$$$$$$$$$X+$$$$$$$$$X$X$$$X+;xxxxxxx+;+xxx+XxXXXXXXXXXXXXxXXX$$$XXX$X++++xxxXXX
XXxXXXxxXXxxxXX$$$$XX$$$$X$$XXXX+XX$$$$$$$$$$XXxx+xxx+xxxx;+xxxxxxXXXXXXXXXX$XXXXXX$$X$$XXXXXXX$X$$$
XX$Xx+xXXXxxxXx++x+;::;+;;+x:.:+xx+xX$$$$$$$XXxxXXxxxxx++x+;xxx++xXXX$XXXXXX$$XXXX$$$$$$XXXXX$XXX$$$
X$$$$$$XxxXXXXXXx+x;:;+;;+;;;;+xx++xXX$$$x++xXxxxx+x+xx++xxxxxXxxXXXX$$XXXXX$$$$$X$$$$XXXXXXXXX$$$$$
$$$$$$$$$$$XX$$XXxxX$$XXXxxxx+xx++XXXX$X+;:::::;XXXXxxxxxXX++x++xxXXXXXXXXXX$$$$XX$$$$XXXXX$$$$$$$$$
$X$$$$$$$$XX$$$$$$XX$XXXXXXXx+xx+x++;;;;;;++xXXXXx+xxxxxxxxxxxxxxXXxxXXx++++xx++x+xXX$XxxxxxXXXXXXXX
$$$$$$$$$$$$$$$$$$$$$$$$X$XXxxxxxXXXX$$$XXXXXXXXx+++++;;;. ::;;;+xxXX+x+xXXXXXXXXxXXXX$X+xXXXXXXXX
$$$$$$$$$$$$$$$$$$$XX$$$X$$$X$$$$$$$$$$$$XXXXXXXxxxx;;:;;;++ ;; .. :xXXX$XXXXXXXxXX$$XXX$XX$$$$$$XX
$$XxXXx:XX$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$Xxx;;:;+++;;:;:..:. ::;;+;+xxXXXXXXXX+XxXXXXX$$$$$XXXxx
$$XXXXXXXX$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$XX++x+++x;;+xx:+;:::+xxxXXXXXXXXXXX$$$XxXXXXXXxX$$$$$$$$X
$$$XXXXXXX$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$Xx::.:;+x:.+xXXXx++xxxxxXXXXXxxxx;+XXxxxXXXX$X$$$$$$$x;x;
$XXXXX$XXX$$$$$$$$$$$$X$$$$$XX$$$$$$$$$$$$$XX++xx+;xxxxxXXXXXXXXXXXXXXXX$Xxxx++++XxxXXXXxXX$$$$$$X;;
$XXXXxx$XX$$$$$$$$$$$$$$$$$$XX$$$$$$$$$$$$$XXXXXXXxXxXX+::x+:+XXXXXXXXXX$$$$$$$xxX$$XxxXXXXX$$$$x++.
Xxx$$$$xX$$$$$$$$$$$$$$$$X$$XXX$$$$$$$$$$$$$XXXXXXXXxxXxXX::.. :xXXXXXXXXX$$$$$$$$$$X$$$$$$$$$$$$$$X
XXXXXXXXX$$$$$$$$$$$$$$$XXX$$XX$$$$$$$$$$$$$$XXXXXXX++xxx++++: :::;;;+xX$$$$$$$$$$$XXXX$$$$$$$$$$$
$$$X+xXXX$$$$$$$$$$$$$$$$$XX$XX$$$$$$$$$$$$$$XxXxXXXxxx++:. +;: .:;::::.;+XX$x$$$$$XXXXXX$$$$$$X$XX
XXX+xxX$X$$$$$$$$$$$$$$$$$XX$$$$$$$$$$$$$$$$$$xXXXXxXXxXXxx+: +x+ :+:;;.:..x+++;xXXXXX$$$XXXxxxxx
XXX++x$$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXXxx+;: :++:;;;+;.;:;;;;;;+++xxxxxxXXXX$$$$$
$XXXXX$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXXx+;::. ::;;++++++xx+xx++xxxxX$$$$$$$$$$$$$
$$XXXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXxXxx+xXXXXx; .xXXXXXXXx+;;;+X;+;xXX$XXXX$XX$$$$XX
$$$XXX$$$$$$$$$$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$X$XXXXXXXXXXX:. :xXXXXXXXXXX+;:xxxxxXXxXxxXXXXXXXXXX
$$$$$$XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXXXXXXX$XXx: ;xXX$x;. .:;::. :+xxxXXXXX$$$XX$$X
XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXXXX$$XX$$$$Xx;.: :+;X$$X+; :;;+++: :xXX$$XX$$$$$$$$
$XXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$XXxxXX++XXXXx;.xxx;::.xxX$Xx;::;:x+;::;+xXXXXX$$$$XX

View File

@@ -0,0 +1,54 @@
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▓▓▓▒▒▒▒▒▒▒▓▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓
▓▒ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▒▒▒▒▒▒▓▓▓▒▒▒░░▓▒▒▓▓▓▓▓▓▒▓▓▓▒░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓▓▓▒▒▒▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒░░░▒▒▒▓▓▓▒▓▒▒▒▓▓░░░▒▒▒▓▒▒▓▓▒▓▒▓▓▓▓▒▒▒▓▓▓▓▓▓▒▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▒▒░░░░░▒▒▒▒▒▒▒▒▓▓▓▒░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▒░ ▒▒▒▒▒▒▓▒▒▒▒▓▓▓▓▒▒▒▓▓▓▓▓▓▒▒▒▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒░ ▒▒▒▒▒▒▒░▓▓▒▒▒░░ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▒░▒▒▒▓▓▓▓▓▒▒▓▒
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▓▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒░░░▒▒▒▒▒▒▒▓▒▓▒░▒▒▒▓▒▒▒▒▓▓▓▒
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░ ░░░░▒▒▒▒▒▒▒▒▒▒▓▒▒░▒▒▒▒░░▒░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░ ░░▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒░░░░░▒▒
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒░▒▒░▒░░░░░░░░░░░ ░░░▒▒▒▒▒▒▒▒▒▓▒▒░░░░▒▒▒▓▒
▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒░▒▒▒▒▒▒▒░░ ░▒░░░░░░░░░░░ ░░░▒▒▒▒▒▒▒▒▒▒▒░▒▓▓▓▓▒▒▓▒
▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░ ░░░░░ ░░░░░░░░░░░░░░▒▒▒▒░▒▒░▒▒░▒▓▓▓▓▒▒▒▒░
▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▒▓▓▓▓▒░░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒░░ ░ ░░░░░░░▒░░░▒▒▒░░░░▒▒▒▒▒░▒▒▒▒▓▒░▒▒░░
▓▓▓▒░░░▓▓▓▓▒▓▓▓▒▓▓▓░░▒░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░ ░░░░░░▒▒░░▒░▒▒▒▒▒▒▒▒▓▒░▒▒ ░▒░ ░▒▒░
▓▓▓▓▓▓▒▓▓▓▓▓▒▓▓▒▒░ ░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒░▒▒░░░░░░░ ░░░░░▒▒▒▒░▒▒▒▒▒▒▒▓▓▓▓▓ ░ ░░ ░▒
▓▓▓▒▒░▒▒▓▓▓▒▒░░░░░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒░▒▒▒░░░░░ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▒░ ░ ░▒
▓▓▓▒▒░▒▒▓▒▒░░ ░░▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒░░░░░░░░ ░░░░░░▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒ ░ ░ ░ ░▒▒▒
▒▒▒▒▓▓▒▒▓▒▒▒▒▒▒▒▓▓▓▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░▒▒▒▒░░░▒▒░░░░░░░ ░░░░░░░▒▒▒▓▒▒▒▒▒▒░ ░░░▒▓▒▒▒░▒ ▓▓▓▒▒▓
░▓▓▒▓▓▓▒▓▓▓▓▓▒░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒░▒▒▒░░▒▒▒░░░░░░░░░░░░░░░▒▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒░▒░ ░▓▓▓▓▒▓
░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░▒▒▒▒▒▒░░░░░░▒░░░░░▒░░▒░ ▒▒░▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒ ▒▓▓▓▓▓▓
▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░░░▒▒░░░░░░░░░▒▒░▒▒▒▒▒▒ ░ ▒▒▓▓▒▒▒▒░▒▒░░▒▒▓▒░▒▓▓▓▓▓▓
▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░░░▒▒▒░░░░░░░▒▒▒▒▒▒▒▒▒▓▒░ ░▒ ▒▒▒▓▒▒▒▒▒▒▒░░▒▒▓▓▒▒▓▓▓▓▓▓
▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░▒▒▒░ ░░ ▒▒▒▒▒▒▒▒▓▓▒░▒▒░▒▒▒▓▒▒▒▒░ ▒░░░▒▓▓▒▒▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▒▒▒░ ░░ ░▒▒▒▒▒▒▓▓▓▓▒▓▓▒▒▒▒▓▓▒▒▒░ ▒░▒▒▒▓▓▒▓▓▓▓▓▓▓
░▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ░▒▒ ░▒░░▒▓▓▓▓▓▓▓▓▓▒▒▒▒▓▓▒▒▒▒░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░▒▒░ ░░ ▒▒▒░▒▓▓▓▓▓▓▓▓▓▓▒▒▓▓▒▒▒▓▒▒▒▒▒▒░▓▓▓▓▓▓▓▓▓▓
▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ░▒▒░▒▒▒▒ ▒▒▒░▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░ ░▒▒░▒▒▒▒ ░▒▒▒░▓▒▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▒▓▓
▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░▒▒▒▒▒▒▒░░░▒▒▒░▓▒▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓▓
▓▓▒▓▓▓▒▒▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒░▒▒▒▒░░▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▒▒▒▓▓▓▒▒▒▓▒▒▒▒▒░ ░░░░▒▒ ░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▒▒▓▓▒▒▒▒▒░░▒░░▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▒▒▒░ ░░░░▒░░░░▒▒▒░▒▒▓▓▓▓▓▒▒▒▒▓▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒░▓▓▓▓▓▓▒░ ░▓▓▓▓▒▒▒▒▒▓▓▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░░░░░░░▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▒▓▓▒░░░▒▒▒▒░▒▒▒▓▓▓▓▒▒▒▒▒▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░▒░░░░░ ░░░▒▒▒▓▓▒▒▒▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░ ░░░░░ ░░ ▒▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▒▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ░░▒▒░░ ░ ░░░░░▒▒▓▓▓▓▓▓▓▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░░▒▒ ░░ ▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ░▒▒ ▒▒▓▓▓▒░░▒▒▒▒▒▓▓▓▓▓▒▒▒▒░░▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒░▒░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░▓▒▒▓▓▓▓▒▓▓▓▓▓▓▓▓▓░░
▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▓▓▒ ▒░ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▒▒░
▓▒▒▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▒▓▓ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒ ░░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓
▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▓▓▓▒▒▒▒░ ░░ ░ ░▒▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▒▓▓▒▓▓▒▒▒ ▒▒░ ░ ░░ ▒░▒▒░▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒
▓▓▓▒░▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░ ░▒ ░░░▒░ ░ ░░░░░░▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓█
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░ ░░░░▒▒▒▒▒▒▒▒▒░▒▒▒▒▒▓▓█▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▒▓▒▒░▒▓▓▓▓▒░ ▒▓▓▓▓▓▓▓▒░░░░▒▓░░░▒▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓▓▓▓▓▓▓▒░ ▒▒▒▒▒▓▓▒▓▒▒▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▓▓▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ░▒▓▓▓▒░ ░ ▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓█▓▓▓▓▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░ ░░▓▓▓▓▒░ ░░▒░░ ▒▓▓▓▓▓▓██████▓▓
▓▓▓▓▓▓▓▓▓▓███▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓█▓███▓▓▓▓███▓▓▓▓▓▓▓▒▒▓▓▒░▓▓▓▓▒░ ▒▒▒░ ▒▒▓▓▓▒░ ░ ▒▒░ ░▒▒▓▓▓▓▓▓▓▓▓▓▓

View File

@@ -0,0 +1,33 @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-%@%: :%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ *@@@= *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ %@@@@@: =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# #@@@@#- =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%.-@@#: *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ =#%*+=-. #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%. +%@@@@@@@%. :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# *@@@@@@@@@@@@@@@* %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@% @@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #@@@@@@@@@@@@@@@@@@ .%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@% :@@@@@@@@@@@@@@@+ =%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+ @@@@@@@@@@@@% :@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@= #@* #@@@@@@@@@% -+-. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@- @@@@@@@*:#@@@@@@@@=+@@@@@+ * .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@# @%@@@@@@@@@@@@@@@@@@@@@@@@@=%@@ =@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@% @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@% -@@@@@+====-----::::.... %@@% @@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@ :==@@@@ =##%%%%%%%%%%%%%%%%%%%%* %@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@= -@@@@@@ *@@@@@@@@@@@@@@@@@@@@@@# %@@@@@* @@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@ #@@@@@:*@@@@@@@@@@@@@@@@@@@@@@#.%@@@@@== *@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@*:=#%#-. :*%*+@@@@@@@@*: :#@@@@@@@@#=@@@=. ...:%@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#%@@@@@@+@@@@@@@@*. .*@@@@@@@@##%*+=. %@@#@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@@@@@@@@%##@@@@@@@@@@#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

View File

@@ -0,0 +1,21 @@
______
,-~ _ ^^~-.,
,^ -,____ ^, ,/\/\/\,
/ (____) | S~ ~7
; .---._ | | || _| S I AM THE Z
| | ~-.,\ | |!/ | /_ LAW! _\
( | ~<-.,_^\|_7^ ,| _//_ _\
| | ", 77> (T/| _/' \/\/\/
| \_ )/<,/^\)i(|
( ^~-, |________||
^!,_ / /, ,'^~^',!!_,..---.
\_ "-./ / (-~^~-))' =,__,..>-,
^-,__/#w,_ '^' /~-,_/^\ )
/\ ( <_ ^~~--T^ ~=, \ \_,-=~^\
.-==, _,=^_,.-"_ ^~*.(_ /_) \ \,=\ )
/-~; \,-~ .-~ _,/ \ ___[8]_ \ T_),--~^^)
_/ \,,..==~^_,.=,\ _.-~O ~ \_\_\_,.-=}
,{ _,.-<~^\ \ \\ () .=~^^~=. \_\_,./
,{ ^T^ _ / \ \ \ \ \) [| \oDREDD >
^T~ ^ { \ \ _\.-|=-T~\\ () ()\<||>,' )
+ \ |=~T ! Y [|() \ ,' / -naughty

View File

@@ -0,0 +1,58 @@
@@@@@@@@@@@@@%@@%%@%###(>{=*<[{%@@%@@@@%{#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@%%#%@%#(^ ~ :<}@@@@@@@@@%@##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@%#{#%%@@@% (#%%@@@@@@@@@@{{%%@@%%%%%%%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@%{{%%#@@@@@( . <}@@@@@@@@@%%%%%%%%%%%%%%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@%%}#%#@@@@@@@@. {@@@@@@@%%%%%%%%%%%%%%%%%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@%%#{##%@@@@@@@%{ -] [%@@@@@@@%%%###%%%%%%%%%%%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@%###@@@@@@@@@@@%%{ + @%%@@@@@@%%##############%%%%%%%@%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@%#{#%@@@@@@@@@@@@%{({[~+@%%@@@@@%################%%%%%%%@%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@%#{%@@@@@@@@@@@@@@@@%}%#(#%@@@@@@%%##{{{#{{{{{{{{#######%%%%%%%%%%%%%%%%%%%%%%@@@@@@@@@@@@@@@@@@
@@%#{{%@@@@@@@@@@@@@@@@@@%%%#[%@@@@%%%##{{{{{{{{{{{{{#############%%%%%%%%%%%%%%%@@@@@@@@@@@@@@@@@@@
@%#}[%@@@@@@@@@@@@@@@@@@@@@@#%%@@@@%%##{{}}}{{{{{{{{{{{{#######%##%%%%%%%%%%%%%%%%%%%@@@@@@@@@@@@@@@
%#{}#@@@@@@@@@@@@@@@@@@@@@%%#%%@@@%%%{{{}[[}}{{{{{{{{{{{{{########[#%%%%%%%%%%%%%%%%%@@@@@@@@@@@@@@@
{{}%@@@@@@@@@@@@@@@@@@@@@@@%%%%%@@%%#{}}[]][[[[[[[}}{{{{{{{{{{{{{}*}#%%%%%######%%%%%%%%%@@@@@@@@@@@
][%%%@@@@@@@@@@@@@@@@@@@@@@%%%@@%%%##}}[[]]][[[[[[[[}}{{{{{{{{{{{{###############%%%%%%%%@@@@@@@@@@@
#%%%%%%@@@@@@@@@@@@@@@@@@@@%##%%%##{{[[]]((]]]]][]]][}}}}}}{{{{{{{{{{{{{{{########%%%%%%%%%%@@@@@@@@
@%%%%#%%%@@@@@@@@@@@@@@@@@@%##%###{{}]]]())((]]] (][[[[}}}}}{{{{{{{{{{{########%%%%%%%%%%@@@@@@@@
#######%%%%%%%%%%%@@@@@@@@%#%{##%#{[])))))<)))((( ([[][[[[}}}}}}}{{{{{{{{{{{{{{##%%%%%%%%%@@@@@@@@
{{{#{######%%%%%%%%@@@@%@(##)}{###{[(<<<<<><))))])><]]]]]]][[[[[[[}}}}{}}[{{{{{{{##%%%%%%%%%@@@@@@@@
{{{{{{#############%%%%@@@@%{}{{{#}])>>>>>>>><<<))((((( >]]]]]]][[[}}}}}{{{{{####%%%%%%%%@@%%%@@@@
{{{{{{{{{{{{{{{}{{####%%%@@%{}{{{}])>^^^^^^^><<<<))))))* =(((]]]][[[[}}}{{{{{####%%%%%%%%@@%%%@@@@
{{{{{{{{{{}}}[[[[][}}}}{%%@%}[[[[[(>*++++***^>>>>>>><<<<)( >)([[[[[} >( [[{{{{######%%%%%%%@@@@
}}}}}}}}}[[]]]]((((]]()]{%@%[]((](>+~=++++++**^^^^>>>>>><> =->- ))[]][[}]>]{}}{{}{{######%%%%%%%@@@@
[[[}[[[[[]]]((()<>^<>+~^({%#<)(())*----~====+++++***^^^>><<<<<)))((]]][[[}}}}{{{{{{######%%%%%%%%%@@
]]]]]]]]]]]()>^*+*^*~~=.*[%#- ]( :..:----~====+++**^^>>>><<<))(((]]][[[}}}}}{{{{######%%%%%%%%%@@
)(]((((](<<<<<>==+=~-~+ >#{=%@@@. .::::--~~=+++**^^>>><<)))(((]]]][[[}}}{{{{{#####%%%%%%%%%@@
)))(((()<>>=--=~~ .*%@%%@%}- .:-~=+++**^^>>><<)))(((]((]][[}}}{{{{{#####%%%%%%%%%@@
)<<<)))<<>*+*=- ..~}#%@@{+ ==++**^^^>><<)))(((]]]]][[}}}{{{{######%%%%%%%%%%%
))<>><<<>*-. . : #%%%@@# -~=+++*^^^>><<)))(((]]]]][[}}}{{{{######%%%%%%%%%%%
)>>>>>>>>>*+~=~:: : }@@@%%@%= *] -++**^^^>><<))((((]][[[[[}}}{{{{{#####%%%%%%%%%@@
)^^><<<>>>^~++~ . }@@@@@@%%{{# .**^^^>><<))((((][[[[[[}}}{{{{{#####%%%%%%%%%@@
)>>>>><>^^*++*~: . . }%@@@@@@@@@% +*^^>>><<))(((]]][[[[[}}}{{{{{{####%%%%%%%%%%%
)<>>>>>>^^**=**++~::-. {#){%%%%%%%* ~+***^^>>><<))(((]]][[[[[}}}{{{{{{####%%%%%%%%%%%
()<)<<<>>^^^^^^*+~ ~- . #[ <@@@@@%) =++*^^^>>><<))((]]]][[[[[}}}{{{{{{{{{#%%%%%%%%%%@
(((()<<<>>^*^^^^*++~ -:-::{[ (@@@@@@# :---~~==++*^^^>>><<))((((]][[[[[}}}{{{{{{{{{#%%%%%%%%%%@
((]))>^>>>^**><^+*+++--=>^%%)]@@@@@@( - .:-~~===+++**^^>><<<))((]]]][[[[[}}}{{{{{{{{{####%%%%%%%%
](>^><)>++*+*^^^+=+++=~++)@%(<%@@@@@@]+:::----~=++**+**^^>><<<)))((]]]][[[[}}}}{{{{{{{{{####%%%%%%%%
{()^==***-==^~ - :-=**^*}%%(*)}#%@@@@@(-~~~-~===+++*^^^>><<)))))((]]][[[[}}}}{{{{{{{{{{####%%%%%%%%
([[(][)^+-: ::~.:=^>>*=[%%(++*)}{{#%%@#[+=++++++**^>>>><<<)))((((]]][[[[}}}}{{{{{{{{###%%%%%%%%%@@
<][}[[]((^ -.=++~+*^>><><{@%[^*+>[##%%@@@#(*++****^^>><<<<)))))((((]]][[[}}}}}}{{{{{{{###%%%%%%%%%@@
(]{}][})<>=-^)^>)<<<*^<)({@%[^**>[##%%%#}])>***^^^>>><<<)))))((((]]]]][[}}}{{{{{{{{{{{###%%%%%%%%@@@
[-}{{}}]*=>)>++]]())<<^)]#@%[+>}{%%#[<<.(())<^*^>>><<<<<)))((((]][[[[}}}}}{{{{{{{##{{{#%%%%%%%%%%%%%
<+(^[{[}[>^<^([])(]]]((][#%#()}%##%%{))>(]]((<>*><<<<<<)))))(((]][[[[[[[[}}}}}}}}}}[(]][}}}}}}}}{{{{
)*){])}[](+^^]]]>]}[[}]}[#%{)([]]{%%<=><>*+=~. ----~==++++***^>><<<))((((]]]][[[}}]**^><<<))(((]]][
+~](})>[[(~*(>)())[[}}[[[#%}<^+~.^#%( >+++++**^^^^***^^^>>^^^>>>>>>>>^^^^+*= -~+*******^>>>
+[~<]}]<(]*^*^>>>*<>**+*=]{[>. ]%^ .:---= ....::-:-~~
(^*=+==**+:- ~: =}(+ *%> *]]]])<<<<<<<)((((((]]]]](][[][[}[]][}}]]
===~- . [(~ (%%
=::. [): >} .-~=+*><)((]}}}[}}}}{{{{{{##{{#{#%>
. ])= }^ ))[#{#{{{}}}}[[[[]((<<<<<>^^>>><<))<))<<>^^*~:
<[] [{(
=[]= ]{}
^###[ +{{{] .-=*^<<)))(][}}}{%#%%%%%@%%%%%%%@%)
[{{}~ <#%%# %%%@@@@@@@@@@@%%%%%%##%%%%%%%%%%%#@%@%%%%%%%%%%%%%#{(
~++*+ + -^^*^*- =^^^^^^^^^^*===~~~~~--. :-:::--~:..

View File

@@ -0,0 +1,56 @@
/** @param {NS} ns */
export async function main(ns) {
ns.tprint(" ...,?77??!~~~~!???77?<~.... ");
ns.tprint(" ..?7` `7!.. ");
ns.tprint(" .,=` ..~7^` I ?1. ");
ns.tprint(" ........ ..^ ?` ..?7!1 . ...??7 ");
ns.tprint(" . .7` .,777.. .I. . .! .,7! ");
ns.tprint(" .. .? .^ .l ?i. . .` .,^ ");
ns.tprint(" b .! .= .?7???7~. .>r . .= ");
ns.tprint(" .,.?4 , .^ 1 ` 4... ");
ns.tprint(" J ^ , 5 ` ?<. ");
ns.tprint(" .%.7; .` ., .; .=. ");
ns.tprint(" .+^ ., .% MML F ., ?, ");
ns.tprint(" P ,, J .MMN F 6 4. ");
ns.tprint(" l d, , .MMM! .t .. ,, ");
ns.tprint(" , JMa..` MMM` . .! .; ");
ns.tprint(" r .M# .M# .% . .~ ., ");
ns.tprint(" dMMMNJ..! .P7! .> . . ,, ");
ns.tprint(" .WMMMMMm ?^.. ..,?! .. .. , Z7` `?^.. ,, ");
ns.tprint(" ?THB3 ?77?! .Yr . .! ?, ?^C ");
ns.tprint(" ?, .,^.` .% .^ 5. ");
ns.tprint(" 7, .....?7 .^ ,` ?. ");
ns.tprint(" `<. .= .`' 1 ");
ns.tprint(" ....dn... ... ...,7..J=!7, ., ");
ns.tprint(" ..= G.,7 ..,o.. .? J. F ");
ns.tprint(" .J. .^ ,,,t ,^ ?^. .^ `?~. F ");
ns.tprint(" r %J. $ 5r J ,r.1 .=. .% ");
ns.tprint(" r .77=?4. ``, l ., 1 .. <. 4., ");
ns.tprint(" .$.. .X.. .n.. ., J. r .` J. `' ");
ns.tprint(" .?` .5 `` .% .% .' L.' t ");
ns.tprint(" ,. ..1JL ., J .$.?` . ");
ns.tprint(" 1. .=` ` .J7??7<.. .; ");
ns.tprint(" JS.. ..^ L 7.: ");
ns.tprint(" `> .. J. 4. ");
ns.tprint(" + r `t r ~=..G. ");
ns.tprint(" = $ ,. J ");
ns.tprint(" 2 r t .; ");
ns.tprint(" .,7! r t`7~.. j.. ");
ns.tprint(" j 7~L...$=.?7r r ;?1. ");
ns.tprint(" 8. .= j ..,^ .. ");
ns.tprint(" r G . ");
ns.tprint(" .,7, j, .>=. ");
ns.tprint(" .J??, `T....... % .. ");
ns.tprint(" ..^ <. ~. ,. .D ");
ns.tprint(" .?` 1 L .7.........?Ti..l ");
ns.tprint(" ,` L . .% .`! `j, ");
ns.tprint(" .^ . .. .` .^ .?7!?7+. 1 ");
ns.tprint(".` . .`..`7. .^ ,` .i.; ");
ns.tprint(".7<..........~<<3?7!` 4. r ` G% ");
ns.tprint(" J.` .! % ");
ns.tprint(" JiJ .` ");
ns.tprint(" .1. J ");
ns.tprint(" ?1. .' ");
ns.tprint(" 7<..% ");
}

298
local/path/home/autoexec.js Normal file
View File

@@ -0,0 +1,298 @@
import { purchaseServers, listPurchasedServers, getSetting } from "myLibrary.js";
/** @param {NS} ns */
export async function main(ns) {
ns.disableLog("ALL");
//host and script info
const sThisRunner = ns.getHostname();
const sThisScript = ns.getScriptName();
const nThisPID = ns.pid;
// Custom color coding.
const cCyan = "\u001b[36m";
const cGreen = "\u001b[32m";
const cRed = "\u001b[31m";
const cReset = "\u001b[0m";
//open log for this script
ns.tail(nThisPID, sThisRunner, ns.args);
//scripts
const sGangScript = "gang/auto-gang2.js";
const sServerLister = "serverlister.js";
const sServerListFile = "serverList.txt";
const sBestServerFile = "bestTarget.txt";
const sBackdoorScript = "backdoor.js";
//settings
const nRefreshFrequency = 1000; //time in ms
ns.exec(sServerLister, sThisRunner, 1, false, true);
await ns.sleep(100);
let oBestTarget = JSON.parse(ns.read("bestTarget.txt"));
let sBestTarget;
while (true) {
ns.clearLog();
//Home Computer
let nHomeMaxRAM = ns.getServerMaxRam("home");
let nHomeUsedRAM = ns.getServerUsedRam("home");
let nHomeFreeRAM = nHomeMaxRAM - nHomeUsedRAM;
ns.print("Home Free RAM = " + cCyan + Math.floor(nHomeFreeRAM).toLocaleString() + cReset + " / " + cCyan + nHomeMaxRAM.toLocaleString() + cReset + " GB");
let oHome = ns.getServer("home");
let nCores = oHome.cpuCores;
ns.print("Home Cores = " + cCyan + Math.floor(nCores).toLocaleString() + cReset);
//Gang
ns.print("Karma = " + cCyan + Math.round(ns.heart.break()).toLocaleString() + cReset);
if (ns.gang.inGang()) {
if (!ns.getRunningScript(sGangScript)) {
ns.print("Gang found... running " + sGangScript);
ns.exec(sGangScript, sThisRunner, 1);
}
}
else {
ns.print("Gang: " + cRed + "none" + cReset);
}
//Best Target
oBestTarget = JSON.parse(ns.read("bestTarget.txt"));
sBestTarget = oBestTarget.serverName;
ns.print("Best Target: " + cCyan + sBestTarget + cReset);
ns.print("Target Weakness: " + cCyan + Math.floor((ns.getServerMinSecurityLevel(sBestTarget) / ns.getServerSecurityLevel(sBestTarget)) * 100) + "%" + cReset);
ns.print("Target Money: " + cCyan + Math.floor((ns.getServerMoneyAvailable(sBestTarget) / ns.getServerMaxMoney(sBestTarget)) * 100) + "%" + cReset);
let nPServs = listPurchasedServers(ns).length;
let aPserv;
const oEXE = {
ssh: { name: "BruteSSH.exe", nHackingLevel: 50, cost: 500000 },
ftp: { name: "FTPCrack.exe", nHackingLevel: 100, cost: 1500000 },
smtp: { name: "relaySMTP.exe", nHackingLevel: 250, cost: 5000000 },
http: { name: "HTTPWorm.exe", nHackingLevel: 500, cost: 30000000 },
sql: { name: "SQLInject.exe", nHackingLevel: 750, cost: 250000000 },
ds1: { name: "DeepscanV1.exe", nHackingLevel: 75, cost: 500000 },
ds2: { name: "DeepscanV2.exe", nHackingLevel: 400, cost: 25000000 },
sp: { name: "ServerProfiler.exe", nHackingLevel: 75, cost: 500000 },
al: { name: "AutoLink.exe", nHackingLevel: 25, cost: 1000000 },
form: { name: "Formulas.exe", nHackingLevel: 25, cost: 1000000000 }
};
let bBusy = ns.singularity.isBusy();
let bBruteSSH = ns.fileExists(oEXE.ssh.name, "home");
let bFTPCrack = ns.fileExists(oEXE.ftp.name, "home");
let bRelaySMTP = ns.fileExists(oEXE.smtp.name, "home");
let bHttpWorm = ns.fileExists(oEXE.http.name, "home");
let bSqlInject = ns.fileExists(oEXE.sql.name, "home");
let bDeepscanV1 = ns.fileExists(oEXE.ds1.name, "home");
let bDeepscanV2 = ns.fileExists(oEXE.ds2.name, "home");
let bServerProfiler = ns.fileExists(oEXE.sp.name, "home");
let bAutoLink = ns.fileExists(oEXE.al.name, "home");
let bFormulas = ns.fileExists(oEXE.form.name, "home");
let nHackingLevel = ns.getHackingLevel();
let nMoney = ns.getPlayer().money;
//Home Upgrades
if (nMoney > ns.singularity.getUpgradeHomeRamCost()) {
ns.singularity.upgradeHomeRam();
ns.tprint(cCyan + "Upgraded home RAM" + cReset);
}
else if (nCores < 8 && nMoney > ns.singularity.getUpgradeHomeCoresCost()) {
ns.singularity.upgradeHomeCores();
ns.tprint(cCyan + "Upgraded home Cores" + cReset);
}
ns.print("Number of pservers = " + cCyan + nPServs + cReset);
let sLowestPserv;
aPserv = listPurchasedServers(ns);
let nLowestRAM = 2 ** 21;
let nCurrentRAM = 0;
for (let i = 0; i < aPserv.length; i++) {
nCurrentRAM = ns.getServerMaxRam(aPserv[i]);
if (nCurrentRAM < nLowestRAM) {
nLowestRAM = nCurrentRAM
sLowestPserv = aPserv[i];
}
}
if (nLowestRAM < 2 ** 21) {
ns.print("Smallest psserv = " + cCyan + sLowestPserv + " " + nLowestRAM.toLocaleString() + " GB" + cReset);
}
else {
ns.print("Smallest psserv = " + cRed + "none" + cReset);
}
//TOR
let bHasTOR = ns.hasTorRouter();
if (bHasTOR === false && nMoney >= 2e+5) {
ns.singularity.purchaseTor();
ns.tprint(cCyan + "Purchased TOR" + cReset);
ns.toast("Purchased " + "TOR Router", "info", 10000);
}
await ns.sleep(1);
if (bBruteSSH === false && bHasTOR && nMoney >= oEXE.ssh.cost) {
ns.singularity.purchaseProgram(oEXE.ssh.name);
ns.tprint(cCyan + "Purchased " + oEXE.ssh.name + cReset);
ns.toast("Purchased " + oEXE.ssh.name, "info", 10000);
ns.exec(sServerLister, sThisRunner, 1, false, true);
ns.exec(sBackdoorScript, sThisRunner, 1);
}
else if (bAutoLink === false && bHasTOR && nMoney >= oEXE.al.cost) {
ns.singularity.purchaseProgram(oEXE.al.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.al.name + cReset);
ns.toast("Purchased " + oEXE.al.name, "info", 10000);
}
else if (bFTPCrack === false && bHasTOR && nMoney >= oEXE.ftp.cost) {
ns.singularity.purchaseProgram(oEXE.ftp.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.ftp.name + cReset);
ns.toast("Purchased " + oEXE.ftp.name, "info", 10000);
ns.exec(sServerLister, sThisRunner, 1, false, true);
ns.exec(sBackdoorScript, sThisRunner, 1);
}
else if (bDeepscanV1 === false && bHasTOR && nMoney >= oEXE.ds1.cost) {
ns.singularity.purchaseProgram(oEXE.ds1.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.ds1.name + cReset);
ns.toast("Purchased " + oEXE.ds1.name, "info", 10000);
}
else if (bRelaySMTP === false && bHasTOR && nMoney >= oEXE.smtp.cost) {
ns.singularity.purchaseProgram(oEXE.smtp.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.smtp.name + cReset);
ns.toast("Purchased " + oEXE.smtp.name, "info", 10000);
ns.exec(sServerLister, sThisRunner, 1, false, true);
ns.exec(sBackdoorScript, sThisRunner, 1);
}
else if (bHttpWorm === false && bHasTOR && nMoney >= oEXE.http.cost) {
ns.singularity.purchaseProgram(oEXE.http.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.http.name + cReset);
ns.toast("Purchased " + oEXE.http.name, "info", 10000);
ns.exec(sServerLister, sThisRunner, 1, false, true);
ns.exec(sBackdoorScript, sThisRunner, 1);
}
else if (bSqlInject === false && bHasTOR && nMoney >= oEXE.sql.cost) {
ns.singularity.purchaseProgram(oEXE.sql.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.sql.name + cReset);
ns.toast("Purchased " + oEXE.sql.name, "info", 10000);
ns.exec(sServerLister, sThisRunner, 1, false, true);
ns.exec(sBackdoorScript, sThisRunner, 1);
}
else if (bDeepscanV2 === false && bHasTOR && nMoney >= oEXE.ds2.cost) {
ns.singularity.purchaseProgram(oEXE.ds2.name, false);
ns.tprint(cCyan + "Purchased " + oEXE.ds2.name + cReset);
ns.toast("Purchased " + oEXE.ds2.name, "info", 10000);
}
else if (getSetting(ns, "autoPurchaseServers") && nPServs < 25 && nMoney >= 5e+6) { //Purchased Servers
ns.tprint("purchasing servers ");
await purchaseServers(ns);
ns.exec(sServerLister, sThisRunner, 1, false, true);
}
else if (getSetting(ns, "autoUpgrades") && nLowestRAM < (2 ** 20) && !ns.scriptRunning("upgrade.js", sThisRunner) && nPServs >= 25 && nMoney >= 1e+6) {
ns.tprint("upgrading servers ");
ns.exec("upgrade.js", sThisRunner, 1);
}
//ns.tprint("autoPurchaseServers = " + getSetting(ns, "autoPurchaseServers"));
//ns.tprint("autoUpgrades = " + getSetting(ns, "autoUpgrades"));
//work on something
if (bBusy === false) {
if (bHasTOR === false) {
if (bBruteSSH === false && nHackingLevel >= 50) {
ns.singularity.createProgram("BruteSSH.exe", false);
}
else if (bFTPCrack === false && nHackingLevel >= 100) {
ns.singularity.createProgram("FTPCrack.exe", false);
}
else if (bRelaySMTP === false && nHackingLevel >= 250) {
ns.singularity.createProgram("relaySMTP.exe", false);
}
else if (bDeepscanV1 === false && nHackingLevel >= 75) {
ns.singularity.createProgram("DeepscanV1.exe", false);
}
}
if (nHackingLevel < 500) {
ns.singularity.universityCourse("rothman university", "study computer science", false);
}
}
let nSharePower = Math.floor(ns.getSharePower() * 10000) / 100;
ns.print("Faction Reputation = " + cCyan + nSharePower + " %" + cReset);
if (bBusy) {
let oCurrentWork = ns.singularity.getCurrentWork();
//ns.print("oCurrentWork = " + JSON.stringify(oCurrentWork));
let bCanResume = false;
let nWorkCycles;
switch (oCurrentWork.type) {
case "CLASS": //Task = StudyTask
nWorkCycles = oCurrentWork.cyclesWorked;
ns.print("Working at: " + cCyan + oCurrentWork.location + cReset);
ns.print("Working with: " + cCyan + oCurrentWork.classType + cReset);
ns.print("Task Cycle: " + cCyan + nWorkCycles.toLocaleString() + cReset)
bCanResume = true;
break;
case "CREATE_PROGRAM": //Task = CreateProgramWorkTask
nWorkCycles = oCurrentWork.cyclesWorked;
ns.print("Creating Program: " + cCyan + oCurrentWork.programName + cReset);
ns.print("Task Cycle: " + cCyan + nWorkCycles.toLocaleString() + cReset)
bCanResume = true;
break;
case "FACTION": //Task = FactionWorkTask
nWorkCycles = oCurrentWork.cyclesWorked;
ns.print("Working at: " + cCyan + oCurrentWork.factionName + cReset);
ns.print("Working with: " + cCyan + oCurrentWork.factionWorkType + cReset);
ns.print("Task Cycle: " + cCyan + nWorkCycles.toLocaleString() + cReset)
bCanResume = true;
break;
case "CRIME": //Task = CrimeTask
nWorkCycles = oCurrentWork.cyclesWorked;
ns.print("Comitting crime: " + cCyan + oCurrentWork.crimeType + cReset);
ns.print("Crime Chance: " + cCyan + Math.floor(10000 * ns.singularity.getCrimeChance(oCurrentWork.crimeType)) / 100 + "%" + cReset);
ns.print("Task Cycle: " + cCyan + nWorkCycles.toLocaleString() + cReset)
bCanResume = true;
break;
case "COMPANY": //Task = CompanyWorkTask
nWorkCycles = oCurrentWork.cyclesWorked;
ns.print("Working at: " + cCyan + oCurrentWork.companyName + cReset);
ns.print("Task Cycle: " + cCyan + nWorkCycles.toLocaleString() + cReset)
bCanResume = true;
break;
case "GRAFTING": //Task = GraftingTask
nWorkCycles = oCurrentWork.cyclesWorked;
ns.print("Grafting: " + cCyan + oCurrentWork.augmentation + cReset);
ns.print("Task Cycle: " + cCyan + nWorkCycles.toLocaleString() + cReset)
bCanResume = false;
break;
}
/*
if (bBusy && bCanResume === true && nWorkCycles >= 100) {
ns.singularity.stopAction();
}
*/
}
await ns.sleep(nRefreshFrequency);
}
}

View File

@@ -0,0 +1,120 @@
/** @param {NS} ns */
export async function main(ns) {
//Arguments
const sTarget = ns.args[0]; // target server
const oRunner = ns.getServer();
const sRunner = oRunner.hostname;
ns.tail("batch2.js", sRunner, sTarget);
//Settings
//const oHome = ns.getServer("home");
//const nCores = oHome.cpuCores;
const sScript = ns.getScriptName();
const sWeaken = "RMweaken.js";
const sGrow = "RMgrow.js";
const sHack = "RMhack.js";
const nScriptRAM = ns.getScriptRam(sScript, sRunner);
const nWeakenRAM = ns.getScriptRam(sWeaken, sRunner);
const nGrowRAM = ns.getScriptRam(sGrow, sRunner);
const nHackRAM = ns.getScriptRam(sHack, sRunner);
const nHomeUsedRAM = ns.getServerUsedRam(sRunner);
const nHomeMaxRAM = ns.getServerMaxRam(sRunner);
let nHomeFreeRAM = nHomeMaxRAM - nHomeUsedRAM;
const nDelays = [0, 20, 40, 60];
//abort script if sTarget is undefined
if (sTarget === undefined) {
ns.tprint("1st arg sTarget is undefined");
return false;
}
//target server info
const nMinSecurity = ns.getServerMinSecurityLevel(sTarget);
const nMaxMoney = ns.getServerMaxMoney(sTarget);
let nWeakenTime1 = ns.getWeakenTime(sTarget);
let nWeakenTime2 = nWeakenTime1;
let nGrowTime = nWeakenTime1 * 0.8;
let nHackTime = nWeakenTime1 / 4;
//let nHackSecurityGain = ns.hackAnalyzeSecurity(1, sTarget);
let nHackSecurityGain = 0.002;
//let nHackThreadsEstimate = Math.floor(1 / nHackSecurityGain);
let nHackThreadsEstimate = 10;
ns.tprint("nHackSecurityGain = " + nHackSecurityGain);
ns.tprint("nHackThreadsEstimate = " + nHackThreadsEstimate);
const nHackTotalRAM = nHackRAM * nHackThreadsEstimate;
//let nGrowSecurityGain = ns.growthAnalyzeSecurity(1, sTarget, nCores);
let nGrowSecurityGain = 0.004;
let nGrowThreadsEstimate = Math.floor(1 / nGrowSecurityGain);
ns.tprint("nGrowSecurityGain = " + nGrowSecurityGain);
ns.tprint("nGrowThreadsEstimate = " + nGrowThreadsEstimate);
const nGrowTotalRAM = nGrowRAM * nGrowThreadsEstimate;
//let nWeakenSecurity = ns.weakenAnalyze(1, nCores);
let nWeakenSecurity = 0.05;
let nWeakenThreadsEstimate = Math.ceil(1 / nWeakenSecurity);
ns.tprint("nWeakenSecurity = " + nWeakenSecurity);
ns.tprint("nWeakenThreadsEstimate = " + nWeakenThreadsEstimate);
const nWeakenTotalRAM = nWeakenRAM * nWeakenThreadsEstimate;
const nTotalRAM = nHackTotalRAM + nGrowTotalRAM + (nWeakenTotalRAM * 2)
const nTotalBatches = Math.floor(nHomeFreeRAM / (nTotalRAM - nScriptRAM));
ns.tprint("RAM per Cycle = " + nTotalRAM);
ns.tprint("how many batches can i run at the same time? = " + nTotalBatches);
let nGrowDelay = nWeakenTime1 - nGrowTime;
let nHackDelay = nWeakenTime1 - nHackTime;
const nCycleDuration = nWeakenTime2 + nDelays[3];
ns.tprint("nCycleDuration = " + nCycleDuration);
const nBatchFrequency = Math.ceil(nCycleDuration / nTotalBatches);
ns.tprint("nBatchFrequency = " + nBatchFrequency);
while (true) {
//server stats
let nCurrentSecurity = ns.getServerSecurityLevel(sTarget);
let nCurrentMoney = ns.getServerMoneyAvailable(sTarget);
//timestamp
let currentDate = new Date();
let nOffset;
ns.print("Cash: " + (Math.floor(nCurrentMoney * 1000) / 1000) + " / " + nMaxMoney);
ns.print("Security: " + (Math.floor(nCurrentSecurity * 1000) / 1000) + " / " + nMinSecurity);
//Calculate estimate time of completion
nOffset = ns.getWeakenTime(sTarget);
let nWeakTime = new Date(currentDate.getTime() + nOffset);
let sWeakTime = nWeakTime.toLocaleTimeString('sw-SV'); //swedish time
//Print estimated time of completion
ns.print("Weakening " + sTarget + " Estimated complete at " + sWeakTime);
//hack
const nHackPID = ns.exec(sHack, sRunner, 10, sTarget, false, nHackDelay + nDelays[0]);
//ns.tail(nHackPID, "home", "home", nHackThreadsEstimate, sTarget, 0, nHackDelay + nDelays[0]);
//weaken 1
const nWeakenPID = ns.exec(sWeaken, sRunner, nWeakenThreadsEstimate, sTarget, false, nDelays[1]);
//ns.tail(nWeakenPID, "home", "home", nWeakenThreadsEstimate, sTarget, 0, nDelays[1]);
//grow
const nGrowPID = ns.exec(sGrow, sRunner, nGrowThreadsEstimate, sTarget, false, nGrowDelay + nDelays[2]);
//ns.tail(nGrowPID, "home", "home", nGrowThreadsEstimate, sTarget, 0, nGrowDelay + nDelays[2]);
//weaken 2
const nWeakenPID2 = ns.exec(sWeaken, sRunner, nWeakenThreadsEstimate, sTarget, false, nDelays[3]);
//ns.tail(nWeakenPID2, "home", "home", nWeakenThreadsEstimate, sTarget, 0, nDelays[3]);
await ns.sleep(nBatchFrequency);
}
}

View File

@@ -0,0 +1,18 @@
/** @param {NS} ns */
export async function main(ns) {
/*
goals
get money
upgrade home ram
/*
let nPID = ns.run("serverlister.js");
while (ns.getRunningScript(nPID)){
ns.tprint("waiting for script to finish");
await ns.sleep(100);
}
}

View File

@@ -0,0 +1,5 @@
/** @param {NS} ns */
export async function main(ns) {
const sTarget = ns.args[0]; // target server
while (true) { await ns.grow(sTarget); }
}

View File

@@ -0,0 +1,28 @@
/** @param {NS} ns */
export async function main(ns) {
//args
const sTarget = ns.args[0]; // target server
ns.tail(ns.pid,"home",ns.args);
let nMoney = ns.getServerMoneyAvailable(sTarget);
const nMaxMoney = ns.getServerMaxMoney(sTarget);
const startValue = nMoney; // First argument: starting value
const target = nMaxMoney; // Second argument: target value
let result = startValue; // Initialize result with the starting value
let n = 1;
// For loop that continues until the result exceeds or matches the target
for (; result < target; n++) {
result += 1; // Add 1 before multiplication
result *= n; // Multiply by the current step value
ns.print("result = "+result.toLocaleString() + " / "+ nMaxMoney.toLocaleString());
ns.print("n = "+n);
}
let nGrowThreadsNeeded = n;
// * ns.growthAnalyze(sTarget, n, 1);
ns.print("nGrowThreadsNeeded = " + nGrowThreadsNeeded);
}

View File

@@ -1,93 +1,117 @@
import { getGrowThreads } from "myLibrary.js";
/** @param {NS} ns */
export async function main(ns) {
//Arguments
const nArgs = ns.args.length
const sTarget = ns.args[0]; // target server
let nFrequency = ns.args[1]; // frequency to run the Hack / Grow / Weaken
const bIgnoreRAM = ns.args[2]; //if true the script will run even if estimated RAM is too low
const bTail = ns.args[3]; //if true the script will run even if estimated RAM is too low
//Settings
const sWeakenScript = "Ramses-weaken.js";
const sGrowScript = "Ramses-grow.js";
const sHackScript = "Ramses-hack.js";
const nWeakenThreads = 5;
const nGrowThreads = 10;
const nHackThreads = 1;
//host and script info
//const sThisRunner = ns.getHostname();
//const sThisScript = ns.getScriptName();
if (bTail === true) { ns.tail(); }
//logs
ns.disableLog("getServerMaxRam");
ns.disableLog("getServerUsedRam");
ns.disableLog("getServerMinSecurityLevel");
ns.disableLog("getServerMaxMoney");
ns.disableLog("getServerSecurityLevel");
ns.disableLog("getServerMoneyAvailable");
ns.disableLog("ALL");
//Settings
const sHackScript = "RMhack.js";
const sGrowScript = "RMgrow.js";
const sWeakenScript = "RMweaken.js";
const nHackThreads = 5; //1
const nHackScriptRAM = 1.75;
const nGrowScriptRAM = 1.7;
const nWeakenScriptRAM = 1.75;
//abort script if sTarget is undefined
if (sTarget === undefined) {
ns.tprint("1st arg sTarget is undefined");
return false;
}
if (sTarget === undefined) { ns.tprint("1st arg sTarget is undefined"); return false; }
//how often do we run script in milliseconds
if (nFrequency === undefined) {
nFrequency = 20000; //run every 20 seconds unless defined as the 2nd argument when calling the script
}
if (nFrequency === undefined) { nFrequency = 1000; }
//target server info
const nMinSecurity = ns.getServerMinSecurityLevel(sTarget);
const nMaxMoney = ns.getServerMaxMoney(sTarget);
//abort script if sTarget cant have money
if (nMaxMoney <= 0) {
ns.tprint("sTarget (" + sTarget + ") has no nMaxMoney");
return false;
if (nMaxMoney <= 0) { ns.tprint("sTarget (" + sTarget + ") has no nMaxMoney"); return false; }
let nCurrentSecurity = ns.getServerSecurityLevel(sTarget);
let nCurrentMoney = ns.getServerMoneyAvailable(sTarget);
let nBonusWeaken = 0;
let nBonusGrow = 0;
/*if (nCurrentSecurity > nMinSecurity + 3) {
nBonusWeaken = 4;
}
if (nCurrentMoney <= nMaxMoney * 0.8) {
nBonusGrow = 9;
}*/
let nGrowThreads = Math.max(12 + nBonusGrow, getGrowThreads(ns, sTarget, nHackThreads));
let nGrowThreadsINT = Math.ceil(nGrowThreads);
//25 hacks or 12.5 grows
let nWeakenThreads = Math.max(1 + nBonusWeaken, (nHackThreads / 25) + (nGrowThreads / 12.5));
let nWeakenThreadsINT = Math.ceil(nWeakenThreads);
//main variables
const oRunner = ns.getServer(); //which server object is running this script
const sRunner = oRunner.hostname; //hostname string of the server running the script
const nMaxRAM = ns.getServerMaxRam(sRunner);
const nUsedRAM = ns.getServerUsedRam(sRunner);
let nFreeRam = nMaxRAM - nUsedRAM;
const sScriptName = ns.getScriptName();
const nScriptSize = ns.getScriptRam(sScriptName, sRunner);
const nCores = oRunner.cpuCores;
const nWeakenScriptRAM = ns.getScriptRam(sWeakenScript, sRunner) * nWeakenThreads;
const nGrowScriptRAM = ns.getScriptRam(sGrowScript, sRunner) * nGrowThreads;
const nHackScriptRAM = ns.getScriptRam(sHackScript, sRunner) * nHackThreads;
//const sRunner = ns.getHostname(); //hostname string of the server running the script
let nMaxRAM = ns.getServerMaxRam(sRunner);
let nUsedRAM = ns.getServerUsedRam(sRunner);
let nFreeRam = nMaxRAM - nUsedRAM;
//const sScriptName = ns.getScriptName();
//const nScriptSize = ns.getScriptRam(sScriptName, sRunner);
//let nMoney = ns.getServerMoneyAvailable(sTarget);
let nHomeServerReserve = Math.min(nMaxRAM / 8, 64);
const nWeakenTime = ns.getWeakenTime(sTarget);
const nGrowTime = ns.getGrowTime(sTarget);
const nHackTime = ns.getHackTime(sTarget);
if (sRunner !== "home") {
nHomeServerReserve = 0;
}
ns.tprint(sScriptName + " nScriptSize = " + nScriptSize+"GB");
/*
let nWeakenPower = ns.weakenAnalyze(1, nCores); // default 0.05 (always?)
ns.tprint("nWeakenPower = " + nWeakenPower + " with " + nCores + " nCores");
const nMaxWeakenRAM = Math.ceil(nWeakenScriptRAM * ((nWeakenTime / 1000) / (nFrequency / 1000)));
ns.tprint("nWeakenTime = " + nWeakenTime / 1000);
ns.tprint("nFrequency = " + nFrequency / 1000);
ns.tprint("nMaxWeakenRAM = " + nMaxWeakenRAM);
let nHackSecurity = ns.hackAnalyzeSecurity(1, sTarget); // default 0.002
ns.tprint("nHackSecurity = " + nHackSecurity + " on " + sTarget + " sTarget");
const nMaxGrowRAM = Math.ceil(nGrowScriptRAM * ((nGrowTime / 1000) / (nFrequency / 1000)));
ns.tprint("nGrowTime = " + nGrowTime / 1000);
ns.tprint("nFrequency = " + nFrequency / 1000);
ns.tprint("nMaxGrowRAM = " + nMaxGrowRAM);
const nMaxHackRAM = Math.ceil(nHackScriptRAM * ((nHackTime / 1000) / (nFrequency / 1000)));
ns.tprint("nHackTime = " + nHackTime / 1000);
ns.tprint("nFrequency = " + nFrequency / 1000);
ns.tprint("nMaxHackRAM = " + nMaxHackRAM);
const nTotalRAM = (nScriptSize + nMaxWeakenRAM + nMaxGrowRAM + nMaxHackRAM) * 1.07;
ns.tprint("Total estimated required RAM = " + nTotalRAM+"GB");
ns.tprint("Available RAM: " + nFreeRam+" / "+nMaxRAM+"GB");
if (nTotalRAM < nFreeRam || bIgnoreRAM === true) {
let nGrowSecurity = ns.growthAnalyzeSecurity(1, "", nCores);// default 0.004 (always?)
ns.tprint("nGrowSecurity = " + nGrowSecurity + " on " + "_" + " sTarget" + " with " + nCores + " nCores");
*/
while (true) {
//server stats
let nCurrentSecurity = ns.getServerSecurityLevel(sTarget);
let nCurrentMoney = ns.getServerMoneyAvailable(sTarget);
nCurrentSecurity = ns.getServerSecurityLevel(sTarget);
nCurrentMoney = ns.getServerMoneyAvailable(sTarget);
nBonusWeaken = 0;
if (nCurrentSecurity > nMinSecurity + 3) {
nBonusWeaken = 4;
}
nBonusGrow = 0;
if (nCurrentMoney <= nMaxMoney * 0.8) {
nBonusGrow = 11;
}
nGrowThreads = Math.max(1 + nBonusGrow, getGrowThreads(ns, sTarget, nHackThreads));
nGrowThreadsINT = Math.ceil(nGrowThreads);
//25 hacks or 12.5 grows
nWeakenThreads = Math.max(1 + nBonusWeaken, (nHackThreads / 25) + (nGrowThreads / 12.5));
nWeakenThreadsINT = Math.ceil(nWeakenThreads);
//timestamp
let currentDate = new Date();
@@ -96,6 +120,18 @@ export async function main(ns) {
ns.print("Cash: " + (Math.floor(nCurrentMoney * 1000) / 1000) + " / " + nMaxMoney);
ns.print("Security: " + (Math.floor(nCurrentSecurity * 1000) / 1000) + " / " + nMinSecurity);
nMaxRAM = ns.getServerMaxRam(sRunner);
nUsedRAM = ns.getServerUsedRam(sRunner);
nFreeRam = nMaxRAM - nUsedRAM;
while (nFreeRam <= (nWeakenScriptRAM * nWeakenThreads) + nHomeServerReserve + 6) {
await ns.sleep(100);
nMaxRAM = ns.getServerMaxRam(sRunner);
nUsedRAM = ns.getServerUsedRam(sRunner);
nFreeRam = nMaxRAM - nUsedRAM;
}
if (nFreeRam > (nWeakenScriptRAM * nWeakenThreads) + nHomeServerReserve + 6) {
//Calculate estimate time of completion
nOffset = ns.getWeakenTime(sTarget);
let nWeakTime = new Date(currentDate.getTime() + nOffset);
@@ -103,11 +139,24 @@ export async function main(ns) {
//Print estimated time of completion
ns.print("Weakening " + sTarget + " Estimated complete at " + sWeakTime);
ns.print("Weakening with " + nWeakenThreadsINT + " threads on " + sTarget);
let nWeakenPID = ns.run(sWeakenScript, nWeakenThreadsINT, sTarget);
if (nWeakenPID === 0) { ns.print("Weaken Failed..."); }
}
nMaxRAM = ns.getServerMaxRam(sRunner);
nUsedRAM = ns.getServerUsedRam(sRunner);
nFreeRam = nMaxRAM - nUsedRAM;
while (nFreeRam <= (nGrowScriptRAM * nGrowThreads) + nHomeServerReserve + 6) {
await ns.sleep(100);
nMaxRAM = ns.getServerMaxRam(sRunner);
nUsedRAM = ns.getServerUsedRam(sRunner);
nFreeRam = nMaxRAM - nUsedRAM;
}
if (nCurrentSecurity <= (nMinSecurity + 5)) {
if (nCurrentSecurity <= (nMinSecurity + 1) && nFreeRam > (nGrowScriptRAM * nGrowThreads) + nHomeServerReserve + 6) {
//Calculate estimate time of completion
nOffset = ns.getGrowTime(sTarget);
@@ -116,10 +165,24 @@ export async function main(ns) {
//Print estimated time of completion
ns.print("Growing " + sTarget + " Estimated complete at " + sGrowTime);
ns.print("Growing with " + nGrowThreadsINT + " threads on " + sTarget);
let nGrowPID = ns.run(sGrowScript, nGrowThreadsINT, sTarget);
if (nGrowPID === 0) { ns.print("Grow Failed..."); }
nMaxRAM = ns.getServerMaxRam(sRunner);
nUsedRAM = ns.getServerUsedRam(sRunner);
nFreeRam = nMaxRAM - nUsedRAM;
if (nCurrentMoney >= nMaxMoney * 0.8) {
while (nFreeRam <= (nHackScriptRAM * nHackThreads) + nHomeServerReserve + 6) {
await ns.sleep(100);
nMaxRAM = ns.getServerMaxRam(sRunner);
nUsedRAM = ns.getServerUsedRam(sRunner);
nFreeRam = nMaxRAM - nUsedRAM;
}
if (nCurrentMoney >= nMaxMoney * 0.8 && nFreeRam > (nHackScriptRAM * nHackThreads) + nHomeServerReserve + 6) {
//Calculate estimate time of completion
nOffset = ns.getHackTime(sTarget);
@@ -128,20 +191,15 @@ export async function main(ns) {
//Print estimated time of completion
ns.print("Hacking " + sTarget + " Estimated complete at " + sHackTime);
ns.print("Hacking with " + nHackThreads + " threads on " + sTarget);
let mHackPID = ns.run(sHackScript, nHackThreads, sTarget);
if (mHackPID === 0) { ns.print("Hack Failed..."); }
ns.run(sHackScript, 1, sTarget);
}
ns.run(sGrowScript, 10, sTarget);
}
ns.run(sWeakenScript, 5, sTarget);
nFreeRam = ns.getServerMaxRam(sRunner) - ns.getServerUsedRam(sRunner);
}
await ns.sleep(nFrequency);
ns.print("-------------------------------------------------------------------------");
}
}
else {
ns.tprint("Insufficient estimated required RAM... no scripts were started...");
}
}

View File

@@ -1 +1 @@
{"serverName":"omega-net","maxRam":32,"maxMoney":69174578,"minSec":10,"minPorts":2,"minHackLvl":202,"rootAccess":true,"factorMoneyPerTime":0.0319075530954955,"openPorts":0,"serverFiles":["S4tGrow.js","S4tHack.js","S4tWeaken.js","the-new-god.lit"]}
{"serverName":"the-hub","maxRam":32,"maxMoney":483168296.25,"minSec":13,"minPorts":2,"minHackLvl":318,"rootAccess":true,"factorMoneyPerTime":0.1841702873569451,"openPorts":0,"serverFiles":["factionboost.js"],"hackingChance":0.40919188586028893}

63
local/path/home/breach.js Normal file
View File

@@ -0,0 +1,63 @@
/** @param {NS} ns */
export async function main(ns) {
//args
const sTarget = ns.args[0]; // target server
// declare objects
const oHome = ns.getServer("home");
//const oTarget = ns.getServer(sTarget);
//declare variables
const sScriptName = ns.getScriptName();
const sWeakenScript = "RMweaken.js";
const nScriptRAM = ns.getScriptRam(sScriptName);
const sBatch = "EveryServerRun.js";
const nCores = oHome.cpuCores;
let nSecurity = ns.getServerSecurityLevel(sTarget);
const nMinSecurity = ns.getServerMinSecurityLevel(sTarget);
const nWeakenSTR = ns.weakenAnalyze(1, nCores);
const nThreads = Math.ceil((nSecurity - nMinSecurity) / nWeakenSTR);
ns.tprint("nThreads = " + nThreads);
ns.tail(ns.pid, oHome.hostname, sTarget);
//ns.resizeTail(815, 395);
//ns.moveTail(1925, 0);
if (nThreads > 0) {
const nDelay = ns.getWeakenTime(sTarget);
ns.tprint("current security is: " + nSecurity);
ns.tprint("minimum security is: " + nMinSecurity);
ns.tprint("threads needed for weaken: " + nThreads);
ns.tprint(nThreads + " will reduce Security by " + ns.weakenAnalyze(nThreads, nCores));
ns.tprint(nThreads + " of " + sWeakenScript + " requires " + (ns.getScriptRam(sWeakenScript, "home") * nThreads) + " GB of RAM");
ns.tprint("weakening will take " + (nDelay / 1000 / 60) + " minutes");
if ((ns.getServerMaxRam(oHome.hostname) - ns.getServerUsedRam(oHome.hostname)) >= (ns.getScriptRam(sWeakenScript, "home") * nThreads)) {
ns.run(sWeakenScript, nThreads, sTarget);
await ns.sleep(Math.ceil(nDelay));
nSecurity = ns.getServerSecurityLevel(sTarget);
ns.tprint("Breach complete, security level is now at: " + nSecurity);
//run batch
const nBatchPID = ns.run(sBatch, 1, sTarget);
ns.tail(nBatchPID, "home", sBatch, 1, sTarget);
ns.resizeTail(815, 395, nBatchPID);
ns.moveTail(1925, 0, nBatchPID);
}
else {
ns.tprint("try to loop untill finished");
let nScripts = Math.ceil((ns.getServerMaxRam("home") - ns.getServerUsedRam("home")) / ns.getScriptRam(sWeakenScript, "home")-nScriptRAM);
ns.tprint("nScripts = " + nScripts);
ns.run(sWeakenScript, nScripts, sTarget);
await ns.sleep(Math.ceil(nDelay));
}
}
}

View File

@@ -1,384 +0,0 @@
/*
Welcome to part 3. I'll only be commenting on things that have changed from the previous parts, so if there's something
confusing, be sure to go back and look at parts 1 and 2 for more detailed explanations.
This time we're going to make a shotgun batcher. In some ways this is really just a protobatcher that makes a
much larger batch. We're going to fill up ram with as many batches as we can manage, wait for them to finish, then
fire off another blast.
Note that this is mainly written with the fact that I intend to adapt this into a continuous batcher later in mind.
There are far more optimal ways to run a shotgun-style batcher, but rather than make the best shotgun I could,
I aimed to make this an ideal stepping stone on the quest for a continuous batcher.
*/
import { getServers, copyScripts, checkTarget, isPrepped, prep } from "/utils.js";
const TYPES = ["hack", "weaken1", "grow", "weaken2"];
const WORKERS = ["tHack.js", "tWeaken.js", "tGrow.js"];
const SCRIPTS = { hack: "tHack.js", weaken1: "tWeaken.js", grow: "tGrow.js", weaken2: "tWeaken.js" };
const COSTS = { hack: 1.7, weaken1: 1.75, grow: 1.75, weaken2: 1.75 };
// We won't be using the offsets anymore, but I've left them here in case we bring them back for a later part.
// const OFFSETS = { hack: 0, weaken1: 1, grow: 2, weaken2: 3 };
/** @param {NS} ns */
export async function main(ns) {
ns.disableLog("ALL");
ns.tail();
while (true) {
// Setup is mostly the same.
const dataPort = ns.getPortHandle(ns.pid);
dataPort.clear();
let target = "n00dles";
const servers = getServers(ns, (server) => {
target = checkTarget(ns, server, target, ns.fileExists("Formulas.exe", "home"));
copyScripts(ns, server, WORKERS, true);
return ns.hasRootAccess(server);
});
/* manual override */
target = "max-hardware";
const ramNet = new RamNet(ns, servers);
const metrics = new Metrics(ns, target);
if (!isPrepped(ns, target)) await prep(ns, metrics, ramNet);
ns.clearLog();
ns.print("Optimizing. This may take a few seconds...")
/*
New optimizer is async because it can take upwards of 5 seconds to run. We can afford the heavy
computations because shotgun batchers are very front-loaded. In a "real" shotgun batcher, you'll want
to modify the ramnet so that you can do this during the downtime between mega-batches.
*/
await optimizeShotgun(ns, metrics, ramNet); // See the function below for details.
metrics.calculate(ns);
// I've renamed the schedule array from "batch" to "jobs" just for clarity purposes.
// The batchCount declaration has also been moved down here because we use it for scheduling.
const jobs = [];
let batchCount = 0;
// Another change. Instead of tracking the end times by type, I'm now using a unified end time.
// This makes the scheduling a bit simpler as long as we're always going in chronological order.
metrics.end = Date.now() + metrics.wTime - metrics.spacer;
// Instead of one batch, we repeat the scheduling based on the depth calculated by the optimizer.
while (batchCount++ < metrics.depth) {
for (const type of TYPES) {
// As you can see, calculating the end time for each new job is much simpler this way.
// The rest of the scheduling is mostly unchanged.
metrics.end += metrics.spacer;
// Batchcount is part of the constructor now. Yes I was that lazy in the last part.
const job = new Job(type, metrics, batchCount);
if (!ramNet.assign(job)) {
ns.print(`ERROR: Unable to assign ${type}. Dumping debug info:`);
ns.print(job);
ns.print(metrics);
ramNet.printBlocks(ns);
return;
}
jobs.push(job);
}
}
/*
Deployment is completely unchanged. However, with the much larger batch sizes, you may find that
this can potentially freeze the game for minutes at a time. If it's too disruptive or triggers the
infinite loop failsafe, you can uncomment the sleep line.
There's really no need to do this synchronously for our batcher, but in a "real" shotgun batcher, you wouldn't
use any spacers at all, and try to keep deployment time and execution time down to as little as possible in order
to minimize downtime.
*/
for (const job of jobs) {
job.end += metrics.delay;
const jobPid = ns.exec(SCRIPTS[job.type], job.server, { threads: job.threads, temporary: true }, JSON.stringify(job));
if (!jobPid) throw new Error(`Unable to deploy ${job.type}`);
const tPort = ns.getPortHandle(jobPid);
await tPort.nextWrite();
metrics.delay += tPort.read();
}
/*
This is a silly hack. Due to the way arrays work in JS, pop() is much faster than shift() and we're
going to be accessing these jobs in FIFO order in a moment (ie. a queue). Since we've got lots of downtime
and the jobs array can get really huge, I just reverse them now to save time later.
We'll be implementing a more sophisticated schedule in the next part.
*/
jobs.reverse();
// I've stepped up the logging/feedback a bit here, but it's otherwise pretty much the same.
const timer = setInterval(() => {
ns.clearLog();
ns.print(`Hacking ~\$${ns.formatNumber(metrics.maxMoney * metrics.greed * batchCount * metrics.chance)} from ${metrics.target}`);
ns.print(`Greed: ${Math.floor(metrics.greed * 1000) / 10}%`);
ns.print(`Ram available: ${ns.formatRam(ramNet.totalRam)}/${ns.formatRam(ramNet.maxRam)}`);
ns.print(`Total delay: ${metrics.delay}ms`);
ns.print(`Active jobs remaining: ${jobs.length}`);
ns.print(`ETA ${ns.tFormat(metrics.end - Date.now())}`);
}, 1000);
ns.atExit(() => {
clearInterval(timer);
});
/*
As each job finishes, we update the ramnet to reflect it. Once the queue is empty, we start over.
Updating the ramnet like this isn't really necessary since we're just going to rebuild it entirely in
the next iteration, but I wanted to demonstrate what it will look like in preparation for the next part.
*/
do {
await dataPort.nextWrite();
dataPort.clear();
// It's technically possible that some of these might finish out of order due to lag or something.
// But it doesn't actually matter since we're not doing anything with this data yet.
ramNet.finish(jobs.pop());
} while (jobs.length > 0);
clearInterval(timer);
}
}
// The Job class, lean as it is, remains mostly unchanged. I got rid of the server argument since I wasn't using it
// and added a batch number instead.
class Job {
constructor(type, metrics, batch) {
this.type = type;
// this.end = metrics.ends[type]; // Left in for now, in case I decided to use it again later.
this.end = metrics.end; // Using the unified end time now.
this.time = metrics.times[type];
this.target = metrics.target;
this.threads = metrics.threads[type];
this.cost = this.threads * COSTS[type];
this.server = "none";
this.report = true; // All workers now report when they finish.
this.port = metrics.port;
this.batch = batch;
// Future stuff. Ignore these.
// this.status = "active";
// this.id = type + batch;
}
}
// Almost entirely the same, aside from the changes to end time.
/** @param {NS} ns */
class Metrics {
constructor(ns, server) {
this.target = server;
this.maxMoney = ns.getServerMaxMoney(server);
this.money = Math.max(ns.getServerMoneyAvailable(server), 1);
this.minSec = ns.getServerMinSecurityLevel(server);
this.sec = ns.getServerSecurityLevel(server);
this.prepped = isPrepped(ns, server);
this.chance = 0;
this.wTime = 0;
this.delay = 0;
this.spacer = 5;
this.greed = 0.1;
this.depth = 0; // The number of concurrent batches to run. Set by the optimizer.
this.times = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.end = 0; // Slight change for the new timing. The old way in commented out in case I switch back later.
// this.ends = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.threads = { hack: 0, weaken1: 0, grow: 0, weaken2: 0 };
this.port = ns.pid;
}
// Almost totally unchanged, except that I've commented out the default depth calculation, since it's done elsewhere.
calculate(ns, greed = this.greed) {
const server = this.target;
const maxMoney = this.maxMoney;
this.money = ns.getServerMoneyAvailable(server);
this.sec = ns.getServerSecurityLevel(server);
this.wTime = ns.getWeakenTime(server);
this.times.weaken1 = this.wTime;
this.times.weaken2 = this.wTime;
this.times.hack = this.wTime / 4;
this.times.grow = this.wTime * 0.8;
// this.depth = this.wTime / this.spacer * 4;
const hPercent = ns.hackAnalyze(server);
const amount = maxMoney * greed;
const hThreads = Math.max(Math.floor(ns.hackAnalyzeThreads(server, amount)), 1);
const tGreed = hPercent * hThreads;
// Okay I lied. We now overestimate grow threads by 1%. This helps prevent level ups from causing desyncs.
// Only a little, though. If you gain too many levels per shotgun blast, it will still have to re-prep the server.
const gThreads = Math.ceil(ns.growthAnalyze(server, maxMoney / (maxMoney - maxMoney * tGreed)) * 1.01);
this.threads.weaken1 = Math.max(Math.ceil(hThreads * 0.002 / 0.05), 1);
this.threads.weaken2 = Math.max(Math.ceil(gThreads * 0.004 / 0.05), 1);
this.threads.hack = hThreads;
this.threads.grow = gThreads;
this.chance = ns.hackAnalyzeChance(server);
}
}
// Once again, not a whole lot of changes. I've added a new function in support of the optimizer. Details below.
/** @param {NS} ns */
class RamNet {
#blocks = [];
#minBlockSize = Infinity;
#maxBlockSize = 0;
#totalRam = 0;
#maxRam = 0;
#prepThreads = 0;
#index = new Map();
constructor(ns, servers) {
for (const server of servers) {
if (ns.hasRootAccess(server)) {
const maxRam = ns.getServerMaxRam(server);
const ram = maxRam - ns.getServerUsedRam(server);
if (ram >= 1.60) {
const block = { server: server, ram: ram };
this.#blocks.push(block);
if (ram < this.#minBlockSize) this.#minBlockSize = ram;
if (ram > this.#maxBlockSize) this.#maxBlockSize = ram;
this.#totalRam += ram;
this.#maxRam += maxRam;
this.#prepThreads += Math.floor(ram / 1.75);
}
}
}
this.#sort();
this.#blocks.forEach((block, index) => this.#index.set(block.server, index));
}
#sort() {
this.#blocks.sort((x, y) => {
if (x.server === "home") return 1;
if (y.server === "home") return -1;
return x.ram - y.ram;
});
}
get totalRam() {
return this.#totalRam;
}
get maxRam() {
return this.#maxRam;
}
get maxBlockSize() {
return this.#maxBlockSize;
}
get prepThreads() {
return this.#prepThreads;
}
getBlock(server) {
if (this.#index.has(server)) {
return this.#blocks[this.#index.get(server)];
} else {
throw new Error(`Server ${server} not found in RamNet.`);
}
}
assign(job) {
const block = this.#blocks.find(block => block.ram >= job.cost);
if (block) {
job.server = block.server;
block.ram -= job.cost;
this.#totalRam -= job.cost;
return true;
} else return false;
}
finish(job) {
const block = this.getBlock(job.server);
block.ram += job.cost;
this.#totalRam += job.cost;
}
cloneBlocks() {
return this.#blocks.map(block => ({ ...block }));
}
printBlocks(ns) {
for (const block of this.#blocks) ns.print(block);
}
// This function takes an array of job costs and simulates assigning them to see how many batches it can fit.
testThreads(threadCosts) {
// Clone the blocks, since we don't want to actually change the ramnet.
const pRam = this.cloneBlocks();
let batches = 0;
let found = true;
while (found) {
// Pretty much just a copy of assign(). Repeat until a batch fails to assign all it's jobs.
for (const cost of threadCosts) {
found = false;
const block = pRam.find(block => block.ram >= cost);
if (block) {
block.ram -= cost;
found = true;
} else break;
}
if (found) batches++; // If all of the jobs were assigned successfully, +1 batch and loop.
}
return batches; // Otherwise, we've found our number.
}
}
// This one's got some pretty big changes, even if it doesn't look like it. For one, it's now async, and you'll see why.
/**
* @param {NS} ns
* @param {Metrics} metrics
* @param {RamNet} ramNet
*/
async function optimizeShotgun(ns, metrics, ramNet) {
// Setup is mostly the same.
const maxThreads = ramNet.maxBlockSize / 1.75;
const maxMoney = metrics.maxMoney;
const hPercent = ns.hackAnalyze(metrics.target);
const wTime = ns.getWeakenTime(metrics.target); // We'll need this for one of our calculations.
const minGreed = 0.001;
const stepValue = 0.01; // Step value is now 10x higher. If you think that's overkill, it's not.
let greed = 0.99;
let best = 0; // Initializing the best value found.
// This algorithm starts out pretty much the same. We begin by weeding out the obviously way too huge greed levels.
while (greed > minGreed) {
const amount = maxMoney * greed;
const hThreads = Math.max(Math.floor(ns.hackAnalyzeThreads(metrics.target, amount)), 1);
const tGreed = hPercent * hThreads;
// 1% overestimation here too. Always make sure your calculations match.
const gThreads = Math.ceil(ns.growthAnalyze(metrics.target, maxMoney / (maxMoney - maxMoney * tGreed)) * 1.01);
if (Math.max(hThreads, gThreads) <= maxThreads) {
const wThreads1 = Math.max(Math.ceil(hThreads * 0.002 / 0.05), 1);
const wThreads2 = Math.max(Math.ceil(gThreads * 0.004 / 0.05), 1);
const threadCosts = [hThreads * 1.7, wThreads1 * 1.75, gThreads * 1.75, wThreads2 * 1.75];
// These lines were supposed to help weed out a few more too-high values, but in my unit tests they never
// actually did anything. Uncomment them if you want.
// const totalCost = threadCosts.reduce((t, c) => t + c);
// if (totalCost > ramNet.totalRam) continue;
/*
Here's where it all changes. First we calculate the number of batches we can fit into ram at the current
greed level. Then we calculate how much money that nets and how long it will take. If that income/time is
better than what we've found before, we update the metrics and then continue.
Unlike the previous version, this one checks every value. Between that and the loop to simulate assigning
jobs, this is a very heavy algorithm that can take seconds to execute if done synchronously. To prevent it
from freezing the game, we run it asynchronously and sleep after checking each value.
*/
const batchCount = ramNet.testThreads(threadCosts);
const income = tGreed * maxMoney * batchCount / (metrics.spacer * 4 * batchCount + wTime);
if (income > best) {
best = income;
metrics.greed = tGreed;
metrics.depth = batchCount;
}
}
await ns.sleep(0);
greed -= stepValue;
}
// Added the check here to only throw an error if we failed to find any valid configurations.
if (best === 0) throw new Error("Not enough ram to run even a single batch. Something has gone seriously wrong.");
}

View File

@@ -1,7 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let cities = ["Sector-12", "Aevum", "Volhaven", "Chongqing", "New Tokyo", "Ishima"];
let corpName = "AgraNeo";
ns.tprint(ns.corporation.getMaterial(corpName,cities[0],"Plants"))
ns.corporation.sellMaterial()
}

View File

@@ -1,16 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let cities = ["Sector-12", "Aevum", "Volhaven", "Chongqing", "New Tokyo", "Ishima"];
let corpName = "ChemNeo";
let currentSize = 0;
for (let city of cities) {
let currentOffice=(ns.corporation.getOffice(corpName, city));
if (currentOffice.numEmployees < currentOffice.size) {
(currentOffice.employeeJobs.Operations < 1) ? ns.corporation.hireEmployee(corpName, city,"Operations") : "";
(currentOffice.employeeJobs.Engineer < 1) ? ns.corporation.hireEmployee(corpName, city,"Engineer") : "";
(currentOffice.employeeJobs.Business < 1) ? ns.corporation.hireEmployee(corpName, city,"Business") : "";
(currentOffice.employeeJobs.Management < 1) ? ns.corporation.hireEmployee(corpName, city,"Management") : "";
};
}
}

View File

@@ -1,13 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let cities = ["Sector-12", "Aevum", "Volhaven", "Chongqing", "New Tokyo", "Ishima"];
let corpName1 = ["AgraNeo","Plants"];
let corpName2 = ["ChemNeo","Chemicals"];
let exportString = "IPROD*-1"
for (let city of cities) {
ns.corporation.cancelExportMaterial(corpName1[0],city,corpName2[0],city,corpName1[1]);
ns.corporation.cancelExportMaterial(corpName2[0],city,corpName1[0],city,corpName2[1]);
ns.corporation.exportMaterial(corpName1[0],city,corpName2[0],city,corpName1[1],exportString);
ns.corporation.exportMaterial(corpName2[0],city,corpName1[0],city,corpName2[1],exportString);
}
}

View File

@@ -1,6 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let [corpName, city] = ns.args;
//ns.corporation.setSmartSupply(corpName, city, true);
return true;
}

View File

@@ -1,12 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let cities = ["Sector-12", "Aevum", "Volhaven", "Chongqing", "New Tokyo", "Ishima"];
let corpName = "ChemNeo";
let currentSize = 0;
for (let city of cities) {
currentSize = ns.corporation.getOffice(corpName, city).size;
if (currentSize < 4) {
ns.corporation.upgradeOfficeSize(corpName, city, 4 - currentSize);
};
}
}

View File

@@ -0,0 +1,46 @@
/** @param {NS} ns */
export async function main(ns) {
/*
ns.renamePurchasedServer("pserv-23", "pserv-24");
ns.renamePurchasedServer("pserv-22", "pserv-23");
ns.renamePurchasedServer("pserv-21", "pserv-22");
ns.renamePurchasedServer("pserv-20", "pserv-21");
ns.renamePurchasedServer("pserv-19", "pserv-20");
ns.renamePurchasedServer("pserv-18", "pserv-19");
ns.renamePurchasedServer("pserv-17", "pserv-18");
ns.renamePurchasedServer("pserv-16", "pserv-17");
ns.renamePurchasedServer("pserv-15", "pserv-16");
ns.renamePurchasedServer("pserv-14", "pserv-15");
ns.renamePurchasedServer("pserv-13", "pserv-14");
ns.renamePurchasedServer("pserv-12", "pserv-13");
ns.renamePurchasedServer("pserv-11", "pserv-12");
ns.renamePurchasedServer("pserv-10", "pserv-11");
ns.renamePurchasedServer("pserv-9", "pserv-10");
ns.renamePurchasedServer("pserv-8", "pserv-9");
ns.renamePurchasedServer("pserv-7", "pserv-8");
ns.renamePurchasedServer("pserv-6", "pserv-7");
ns.renamePurchasedServer("pserv-5", "pserv-6");
ns.renamePurchasedServer("pserv-4", "pserv-5");
ns.renamePurchasedServer("pserv-3", "pserv-4");
ns.renamePurchasedServer("pserv-2", "pserv-3");
ns.renamePurchasedServer("pserv-1-0", "pserv-2");
*/
ns.renamePurchasedServer("pserv-1", "pserv-01");
ns.renamePurchasedServer("pserv-2", "pserv-02");
ns.renamePurchasedServer("pserv-3", "pserv-03");
ns.renamePurchasedServer("pserv-4", "pserv-04");
ns.renamePurchasedServer("pserv-5", "pserv-05");
ns.renamePurchasedServer("pserv-6", "pserv-06");
ns.renamePurchasedServer("pserv-7", "pserv-07");
ns.renamePurchasedServer("pserv-8", "pserv-08");
ns.renamePurchasedServer("pserv-9", "pserv-09");
//const nServers = ns.getPurchasedServers();
//for (let i = 0; i < nServers; i++)
//let hostname = ns.purchaseServer("pserv-" + i, ram);
// ns.renamePurchasedServer(([i]), "pserv-" + i);
}

View File

@@ -0,0 +1,30 @@
export function GetFlag(ns, flag)
{
return ns.args.includes(flag);
}
export function GetArg(ns, arg, def = null)
{
for(var i = 0; i < ns.args.length - 1; i++)
{
if(ns.args[i] == arg)
{
return ns.args[i+1];
}
}
return def;
}
export function GetIndex(ns, arg)
{
for(var i = 0; i < ns.args.length; i++)
{
if(ns.args[i] == arg)
{
return i;
}
}
return -1;
}

View File

@@ -0,0 +1,114 @@
/*
A 'cycle' lasts 5 minutes and the current task for any given member on a cycle
is set by taking the cycle and their member index and modding by the number
of tasks in your list, so it should spread out tasks evenly among your members,
and each one should progress through the list of tasks, although when you start
only the first member will start on task 0.
Set 'upgradeEquipmentNames' to a list of the gear you want to purchase,
currently it gets a list of all equipment by type and includes all types.
Jobs are:
[
"Unassigned",
"Mug People",
"Deal Drugs",
"Strongarm Civilians",
"Run a Con",
"Armed Robbery",
"Traffick Illegal Arms",
"Threaten & Blackmail",
"Human Trafficking",
"Terrorism",
"Vigilante Justice",
"Train Combat",
"Train Hacking",
"Train Charisma",
"Territory Warfare"
]
*/
const DURATION = 5 * 60 * 1000 // 5 minutes
const newMemberNames = ('General Kenobi,General Zod,Admiral Akbar,Admiral Thrawn' +
',Colonel Duke,Colonel Nick Fury,Major Tom,Major Paine' +
',Corporal Klinger,Corporal Barnes,Sergeant Slaughter,Sergeant Smith').split(',')
/** @param {NS} ns */
export async function main(ns) {
// // show list of job names
// ns.tprint(JSON.stringify(ns.gang.getTaskNames(), null, 2))
// return
ns.disableLog('ALL')
// what gear will we buy after ascension?
let equipmentNames = ns.gang.getEquipmentNames()
let upgradeEquipmentNames = equipmentNames.filter(x => ['Weapon', 'Armor', 'Vehicle', 'Rootkit', 'Augmentation'].find(y => ns.gang.getEquipmentType(x) === y))
let cycle = 0
// with 6 jobs and 12 members, we should have two members on each
let cycleTasks = ['Train Combat', 'Train Combat',
'Vigilante Justice', 'Terrorism',
'Human Trafficking', 'Territory Warfare']
while (true) {
let memberNames = ns.gang.getMemberNames()
ns.print(`Cycle ${cycle} activating for ${memberNames.length} gang members`)
memberNames.forEach((name, index) => {
let taskIndex = (cycle + index) % cycleTasks.length
if (taskIndex === 0) {
let result = ns.gang.ascendMember(name)
if (result) ns.print(`INFO: Ascended gang member ${name}:\n Hack:${sf(result.hack)}, Str:${sf(result.str)}, Def:${sf(result.def)}, Dex:${sf(result.dex)}, Agi:${sf(result.agi)}`)
// if (result) ns.print(`INFO: Ascended gang member ${name}:\n ${JSON.stringify(result)}`)
purchaseEquipment(name)
}
ns.gang.setMemberTask(name, cycleTasks[taskIndex])
})
// hire new members if possible and set them to first job for this cycle,
// should be training probably
if (ns.gang.canRecruitMember()) {
newMemberNames.forEach(name => {
if (ns.gang.recruitMember(name)) {
ns.print(`INFO: Recruited new gang member '${name}`)
ns.gang.setMemberTask(name, cycleTasks[0])
purchaseEquipment()
}
})
}
let cycleEnd = new Date(new Date().valueOf() + DURATION).toLocaleTimeString()
ns.print(`Next cycle at ${cycleEnd}`)
await ns.sleep(DURATION) // wait 5 minutes
cycle++
}
/**
* Purchase equipment for a gang member if we have the money
*
* @param {string} memberName Name of the gang member to purchase equipment for
*/
function purchaseEquipment(memberName) {
let { money } = ns.getPlayer()
upgradeEquipmentNames.forEach(equipName => {
let cost = ns.gang.getEquipmentCost(equipName)
if (money >= cost && ns.gang.purchaseEquipment(memberName, equipName)) money -= cost
})
}
/**
* Simple format for stats
*
* @param {number} value
*/
function sf(value) {
if (typeof(value) !== 'number') return '???'
return Math.trunc(value * 1000) / 1000
}
}

View File

@@ -0,0 +1,144 @@
const TASK_TRAIN = "Train Combat";
const TASK_VIGI = "Vigilante Justice";
const TASK_NOOB = String.fromCharCode(77) + "ug People";
const TASK_RESPECT = String.fromCharCode(84) + "errorism";
const TASK_MONEY = "Human " + String.fromCharCode(84) + "rafficking";
const TASK_WARFARE = "Territory Warfare";
const TASK_NULL = "Unassigned";
const TASK_MANUAL = "Manual/NotReallyTaskName";
const ASCEND_ON_MPL = 10;
const EQUIP_AFFORD_COEFF = 100;
const STATS_TRESHOLD = 0.7;
const STATS_MIN = 4000;
const STATS_HARD_MIN = 200;
const TRAIN_CHANCE = 0.2;
const RESPECT_MIN = 2e+7;
const WANTED_PENALTY_TRESHOLD = 0.99;
const WARFARE_TRESHOLD = 2;
const MEMBERS_MIN = 6;
const MEMBERS_MAX = 12;
const SLEEP_TIME = 10000;
/** @param {NS} ns **/
export async function main(ns) {
ns.tail();
const gang = ns.gang;
// Get weighted stats sum (at this moment, sum of combat stats in eq proportions)
function getStatsSum(member) {
const info = gang.getMemberInformation(member);
return info.str + info.def + info.dex + info.agi;
}
// Find the best gang power except our gang
function maxEnemyPower(myGang) {
const others = ns.gang.getOtherGangInformation();
let maxPower = 0;
for (let name in others) {
if (name === myGang.faction) continue;
maxPower = Math.max(maxPower, others[name].power);
}
return maxPower;
}
// Set a task or not to set (if manually overridden)
const autoTasks = {}
function setAutoTask(member, task) {
const info = gang.getMemberInformation(member);
const lastTask = info.task;
// Manual task: stored task mismatches real task and not unassigned
if (lastTask !== TASK_NULL && autoTasks.hasOwnProperty(member) && autoTasks[member] !== lastTask) {
autoTasks[member] = TASK_MANUAL;
return;
}
// Automatic task: set it if differs from real one
autoTasks[member] = task;
if (lastTask !== task) {
gang.setMemberTask(member, task);
}
}
// The script accepts argument for default task override (optional)
let defaultTask = null;
if (ns.args[0] && gang.getTaskNames().includes(ns.args[0])) {
defaultTask = ns.args[0];
}
// Main loop
for (; ;) {
// Recruit any member possible
while (gang.canRecruitMember()) {
gang.recruitMember('member' + Math.random().toString().substr(2, 3));
}
let bestStats = STATS_MIN / STATS_TRESHOLD; // minimum
const members = gang.getMemberNames();
const info = gang.getGangInformation();
// Ascend if good enough
for (let member of members) {
const r = gang.getAscensionResult(member);
if (!r) continue;
const mpl = r.agi * r.def * r.dex * r.str;
if (mpl > ASCEND_ON_MPL) {
gang.ascendMember(member);
ns.tprint(`Member ${member} ascended!`);
}
}
// Purchase equipment
const allEquip = gang.getEquipmentNames();
let money = ns.getServerMoneyAvailable('home');
for (let equip of allEquip) {
const cost = gang.getEquipmentCost(equip);
const amount = money / cost;
if (amount < EQUIP_AFFORD_COEFF) continue;
for (let member of members) {
const info = gang.getMemberInformation(member);
if (info.upgrades.includes(equip) || info.augmentations.includes(equip)) continue;
if (gang.purchaseEquipment(member, equip)) {
money -= cost;
}
}
}
// Find best stats
for (let member of members) {
let sum = getStatsSum(member);
if (sum > bestStats) bestStats = sum;
}
// Check if we are powerful enough
let powerfulEnough = info.power >= maxEnemyPower(info) * WARFARE_TRESHOLD;
gang.setTerritoryWarfare(powerfulEnough);
// Choose the default task for members
let task = defaultTask;
if (!defaultTask) {
// If gang isn't full - gain respect
if (members.length < MEMBERS_MAX) {
task = (members.length < MEMBERS_MIN) ? TASK_NOOB : TASK_RESPECT;
} else {
// if respect too low - gain it first, power second, money last
if (info.respect < RESPECT_MIN) {
task = TASK_RESPECT;
} else if (!powerfulEnough) {
task = TASK_WARFARE;
} else {
task = TASK_MONEY;
}
}
}
// Assign tasks
for (let member of members) {
let sum = getStatsSum(member);
// Train members, not acceptable in 'noob mode'
if (sum < STATS_HARD_MIN || (members.length >= MEMBERS_MIN && sum < bestStats * STATS_TRESHOLD)) {
setAutoTask(member, TASK_TRAIN);
continue;
}
// Vigi if wanted penalty too large
if (info.wantedLevel > 2 && info.wantedPenalty < WANTED_PENALTY_TRESHOLD) {
setAutoTask(member, TASK_VIGI);
continue;
}
// Do the default task (autoselected or called with args[0])
setAutoTask(member, Math.random() < TRAIN_CHANCE ? TASK_TRAIN : task);
}
await ns.sleep(SLEEP_TIME);
}
}

View File

@@ -0,0 +1,219 @@
import { GetFlag, GetArg } from "gang/argFunctions.js";
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
// 30 possible gang members
// create list of names
let memberNamePool = [
"Thor", // 1
"Iron Man", // 2
"Starlord", // 3
"Thanos", // 4
"Groot", // 5
"Ant-Man", // 6
"Wasp", // 7
"Spiderman", // 8
"Loki", // 9
"Gamora", // 10
"Rocket Raccoon", // 11
"T'Challa", // 12
"Vision", // 13
"Scarlet Witch", // 14
"Winter Soldier", // 15
"Black Widow", // 16
"Hulk", // 17
"Bruce Banner", // 18
"Hawkeye", // 19
"Captain Marvel", // 20
"War Machine", // 21
"Nick Fury", // 22
"Nebula", // 23
"Drax", // 24
"Deadpool", // 25
"Cable", // 26
"Quicksilver", // 27
"Wolverine", // 28
"Adam Warlock", // 29
"Yondu", // 30
];
export async function main(ns) {
var buyAll = GetFlag(ns, "--buyAll");
var buyEquip = buyAll || GetFlag(ns, "--buyEquip");
var buyWeapon = buyAll || buyEquip || GetFlag(ns, "--buyWeapon");
var buyArmor = buyAll || buyEquip || GetFlag(ns, "--buyArmor");
var buyVehicle = buyAll || buyEquip || GetFlag(ns, "--buyVehicle");
var buyRoot = buyAll || buyEquip || GetFlag(ns, "--buyRoot");
var buyAug = buyAll || GetFlag(ns, "--buyAug");
var myGang = ns.gang.getGangInformation();
var possibleTasks = ns.gang.getTaskNames();
var unassignedTask = possibleTasks.shift();
var territoryTask = possibleTasks.pop();
var trainingTasks = possibleTasks.splice(possibleTasks.length - 3, 3);
var wantedLevelLowerTask = possibleTasks.pop();
var desirableAugs = [];
if (myGang.isHacking) {
wantedLevelLowerTask = possibleTasks.pop();
// replace combat with hacking
trainingTasks.splice(0, 1, trainingTasks[1]);
desirableAugs.push("BitWire");
desirableAugs.push("Neuralstimulator");
desirableAugs.push("DataJack");
}
else {
// replace hacking with combat
trainingTasks.splice(1, 1, trainingTasks[0]);
desirableAugs.push("Bionic Arms");
desirableAugs.push("Bionic Legs");
desirableAugs.push("Bionic Spine");
desirableAugs.push("BrachiBlades");
desirableAugs.push("Nanofiber Weave");
desirableAugs.push("Synthetic Heart");
desirableAugs.push("Synfibril Muscle");
desirableAugs.push("Graphene Bone Lacings");
}
var ascensionCycles = GetArg(ns, "--asc", 600000);
var nextAscensionAttempt = 0;
var cycleMs = 1100;
var ascensionMultLimit = GetArg(ns, "--alim", 2);
while (true) {
myGang = ns.gang.getGangInformation();
var otherGangs = ns.gang.getOtherGangInformation();
var buyableEquipment = ns.gang.getEquipmentNames().filter(e => {
return ns.gang.getEquipmentType(e) != "Augmentation" || desirableAugs.includes(e);
});
var members = ns.gang.getMemberNames();
while (ns.gang.canRecruitMember()) {
var possibleNames = memberNamePool.filter(name => !members.includes(name));
var toRecruit = possibleNames[getRandomInt(possibleNames.length)];
ns.gang.recruitMember(toRecruit);
await ns.sleep(1);
}
members = ns.gang.getMemberNames();
var memInfo = null;
members.sort((a, b) => { return Math.random() * 2 - 1; });
members.forEach((m) => {
var didBuy = false;
var hadAll = true;
memInfo = ns.gang.getMemberInformation(m);
ns.gang.setMemberTask(m, unassignedTask);
buyableEquipment.forEach((e) => {
if (memInfo.equipment.includes(e)) return;
if (memInfo.augmentations.includes(e)) return;
hadAll = false;
var type = ns.gang.getEquipmentType(e);
switch (type) {
case "Weapon":
if (buyWeapon) {
didBuy |= ns.gang.purchaseEquipment(m, e);
}
break;
case "Armor":
if (buyArmor) {
didBuy |= ns.gang.purchaseEquipment(m, e);
}
break;
case "Vehicle":
if (buyVehicle) {
didBuy |= ns.gang.purchaseEquipment(m, e);
}
break;
case "Rootkit":
if (buyRoot) {
didBuy |= ns.gang.purchaseEquipment(m, e);
}
break;
case "Augmentation":
if (buyAug) {
didBuy |= ns.gang.purchaseEquipment(m, e);
}
break;
default:
break;
}
});
var wantsToAscend = hadAll;
if (myGang.isHacking) {
wantsToAscend &= memInfo.hackingAscensionMult < ascensionMultLimit;
}
else {
wantsToAscend &= memInfo.hackingAscensionMult < ascensionMultLimit;
wantsToAscend &= memInfo.strengthAscensionMult < ascensionMultLimit;
wantsToAscend &= memInfo.agilityAscensionMult < ascensionMultLimit;
wantsToAscend &= memInfo.dexterityAscensionMult < ascensionMultLimit;
}
if (wantsToAscend && nextAscensionAttempt <= 0) {
ns.gang.ascendMember(m);
}
});
if (nextAscensionAttempt <= 0) {
nextAscensionAttempt = ascensionCycles;
}
var member = "";
if (!myGang.isHacking) {
var memCount = members.length;
while (members.length > (memCount / 2)) {
member = members.pop();
ns.gang.setMemberTask(member, territoryTask);
}
}
while (members.length > 0) {
var task = "";
member = members.pop();
memInfo = ns.gang.getMemberInformation(member);
var statsTarget = 50;
if ((myGang.isHacking && memInfo.hacking < statsTarget) ||
(!myGang.isHacking && memInfo.strength < statsTarget && memInfo.agility < statsTarget && memInfo.charisma < statsTarget && memInfo.defense < statsTarget)) {
task = trainingTasks[getRandomInt(trainingTasks.length)];
}
else if (myGang.wantedLevel > 1) {
task = wantedLevelLowerTask;
}
else {
if (Math.random() > 0.25) {
task = possibleTasks[possibleTasks.length - getRandomInt(2) - 1];
}
else {
task = trainingTasks[getRandomInt(trainingTasks.length)];
}
}
ns.gang.setMemberTask(member, task);
}
await ns.sleep(cycleMs);
nextAscensionAttempt -= cycleMs;
}
}

View File

@@ -0,0 +1,41 @@
/** @param {NS} ns **/
export async function main(ns) {
function myMoney() {
return ns.getServerMoneyAvailable("home");
}
//this script is designed to manage the hacknet nodes
//to prevent excess spending i've limited it from spending
//more than half the players money
var nodes = 0;
var ref = 0;
ns.disableLog("ALL");
while (true) {
//sleep for second to prevent the loop from crashing the game
await ns.sleep(50);
//buy a node if we have more than twice the money needed
if (ns.hacknet.getPurchaseNodeCost() < myMoney() / 10) {
ref = ns.hacknet.purchaseNode();
ns.print("bought node hn-" + ref);
}
nodes = ns.hacknet.numNodes()
for (var i = 0; i < nodes; i++) {
//check if nodes level is a multiple of 10
var mod = ns.hacknet.getNodeStats(i).level % 10;
//buy level node to the nearest multiple of 10 if we have double the money needed
if (ns.hacknet.getLevelUpgradeCost(i, 10 - mod) < myMoney() / 10) {
ns.hacknet.upgradeLevel(i, 10 - mod);
ns.print("node hn-" + i + " leveled up");
}
//same for ram
if (ns.hacknet.getRamUpgradeCost(i) < myMoney() / 10) {
ns.hacknet.upgradeRam(i);
ns.print("node hn-" + i + " ram upgraded");
}
//and cores
if (ns.hacknet.getCoreUpgradeCost(i) < myMoney() / 10) {
ns.hacknet.upgradeCore(i);
ns.print("node hn-" + i + " core upgraded");
}
}
}
}

View File

@@ -0,0 +1,77 @@
// Custom color coding.
const cCyan = "\u001b[36m";
const cGreen = "\u001b[32m";
const cRed = "\u001b[31m";
const cReset = "\u001b[0m";
/** @param {NS} ns */
export async function main(ns) {
ns.print("This script does nothing on its own, it contains functions for other scripts to use");
}
/** @param {NS} ns */
export async function purchaseServers(ns) {
const ram = 64;
let aServers = listPurchasedServers(ns);
let nServers = aServers.length;
if (nServers >= ns.getPurchasedServerLimit()) {
return nServers;
}
if (ns.getServerMoneyAvailable("home") > ns.getPurchasedServerCost(ram)) {
let sFrontZero = "0";
if (nServers >= 9) {
sFrontZero = "";
}
let hostname = ns.purchaseServer("pserv-" + sFrontZero + (aServers.length + 1), ram);
ns.tprint("Purchased " + cCyan + hostname + cReset);
ns.toast("Purchased " + hostname, "info", 10000);
}
aServers = listPurchasedServers(ns);
return aServers.length;
}
/** @param {NS} ns */
export function listPurchasedServers(ns) {
return ns.getPurchasedServers();
}
/** @param {NS} ns */
export function getSetting(ns, sSetting) {
let oSettings = JSON.parse(ns.read("settings.txt"));
let settingEntry = oSettings.setting[sSetting];
//ns.tprint(oSettings.setting[sSetting])
//ns.tprint("settingEntry = "+settingEntry);
return settingEntry;
}
/** @param {NS} ns */
export function getGrowThreads(ns, sTarget, nHackThreads, nCores) {
let nHackAmountPercent = ns.hackAnalyze(sTarget) * nHackThreads;
let nGrowthThreads = ns.growthAnalyze(sTarget, 1 + nHackAmountPercent, nCores);
return nGrowthThreads;
}

View File

@@ -1,4 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
ns.tprint('success')
}

23
local/path/home/notes.txt Normal file
View File

@@ -0,0 +1,23 @@
Karma loss per second by crime:
0.0500 Shoplifting
0.0083 Robbery
0.0625 Mugging
0.0167 Larceny
0.0500 Drug dealing
0.0003 Bond forgery
0.0250 Trafficking arms
1.0000 Homicide
0.0625 Grand theft auto
0.0500 Kidnapping
0.0333 Assassination
0.0250 The ultimate heist
cool functions
ns.singularity.getOwnedSourceFiles();
ns.asleep();
ns.atExit();
ns.spawn();
ns.toast();

View File

@@ -0,0 +1,39 @@
/** @param {NS} ns */
export async function main(ns) {
const sMessage = ns.args[0]; //
const nCurrentProgress = ns.args[1]; //
const stest = ns.args[2];
const nPID = ns.pid;
const sFileName = "progress_" + nPID;
//ns.print("nPID = "+nPID);
const sProgress = [
"░░░░░░░░░░",
"█░░░░░░░░░",
"██░░░░░░░░",
"███░░░░░░░",
"████░░░░░░",
"█████░░░░░",
"██████░░░░",
"███████░░░",
"████████░░",
"█████████░",
"██████████"
];
ns.tail(nPID);
while (nCurrentProgress < 100) {
ns.read(""filename"");
ns.print(sProgress[Math.floor(nCurrentProgress / (sProgress.length - 1))]);
ns.write("filename", "text", "w");
}
if (nCurrentProgress >= 100)
ns.rm(sFileName);
return nPID;
}

52
local/path/home/pserv.js Normal file
View File

@@ -0,0 +1,52 @@
/** @param {NS} ns */
export async function main(ns) {
let aPservers = ns.getPurchasedServers();
ns.tprint("aPservers = " + aPservers);
let nPservers = aPservers.length;
ns.tprint("nPservers = " + nPservers);
let nCurrentRAM;
let nLowestRAM = 2 ** 21;
let nHighestRAM = 0;
let sLowestPserv;
let sHighestPserv;
let nTotalPServRAM = 0;
const oPservers = new Object();
/*
const oPservers = {
pserv-01 : { name: "pserv-01" , ram: nRAM}
};
*/
for (let i = 0; i < nPservers; i++) {
nCurrentRAM = ns.getServerMaxRam(aPservers[i]);
//ns.tprint("nTotalPServRAM = " + nTotalPServRAM + " + " + "nCurrentRAM = " + nCurrentRAM);
nTotalPServRAM += nCurrentRAM;
if (nCurrentRAM < nLowestRAM) {
nLowestRAM = nCurrentRAM
sLowestPserv = aPservers[i];
}
if (nCurrentRAM > nHighestRAM) {
nHighestRAM = nCurrentRAM
sHighestPserv = aPservers[i];
}
}
ns.tprint("sLowestPserv = " + sLowestPserv);
ns.tprint("nLowestRAM = " + nLowestRAM);
ns.tprint("sHighestPserv = " + sHighestPserv);
ns.tprint("nHighestRAM = " + nHighestRAM);
ns.tprint("nTotalPServRAM = " + nTotalPServRAM);
}

View File

@@ -1,5 +0,0 @@
import { getCracks, scanServerList, findBestTarget, crackingAndRooting, copyAndRunScript, purchaseAndUpgradeServers } from "/RamsesUtils.js";
/** @param {NS} ns */
export async function main(ns) {
await purchaseAndUpgradeServers(ns);
}

View File

@@ -1,164 +0,0 @@
/** @param {NS} ns */
export async function main(ns) {
let files = ["contract-115802.cct",
"contract-121862.cct",
"contract-124253.cct",
"contract-130050.cct",
"contract-132458.cct",
"contract-133951.cct",
"contract-137578.cct",
"contract-140971.cct",
"contract-141455.cct",
"contract-143455.cct",
"contract-160840.cct",
"contract-16178.cct",
"contract-166840.cct",
"contract-171215.cct",
"contract-173050.cct",
"contract-17770.cct",
"contract-18028.cct",
"contract-183510.cct",
"contract-195657.cct",
"contract-202801.cct",
"contract-204367.cct",
"contract-217301.cct",
"contract-221818.cct",
"contract-230038.cct",
"contract-236291.cct",
"contract-241097.cct",
"contract-242406.cct",
"contract-253624.cct",
"contract-25923.cct",
"contract-265811.cct",
"contract-267921.cct",
"contract-275503.cct",
"contract-278600.cct",
"contract-279485.cct",
"contract-280015.cct",
"contract-280601.cct",
"contract-286276.cct",
"contract-286508.cct",
"contract-298018.cct",
"contract-30149.cct",
"contract-302603.cct",
"contract-305569.cct",
"contract-322138.cct",
"contract-323283.cct",
"contract-328409.cct",
"contract-328979.cct",
"contract-334003.cct",
"contract-36206.cct",
"contract-375990.cct",
"contract-376805.cct",
"contract-410024.cct",
"contract-413055.cct",
"contract-423941.cct",
"contract-427686.cct",
"contract-441619.cct",
"contract-446103.cct",
"contract-448094.cct",
"contract-467871.cct",
"contract-480431.cct",
"contract-505241.cct",
"contract-516679.cct",
"contract-519369.cct",
"contract-529643.cct",
"contract-535021.cct",
"contract-535336.cct",
"contract-547419.cct",
"contract-560001.cct",
"contract-564079.cct",
"contract-570111.cct",
"contract-570844.cct",
"contract-573534.cct",
"contract-576739.cct",
"contract-580202.cct",
"contract-584555.cct",
"contract-586489.cct",
"contract-592906.cct",
"contract-599940.cct",
"contract-600802.cct",
"contract-603840.cct",
"contract-605640.cct",
"contract-6060.cct",
"contract-606205.cct",
"contract-610194.cct",
"contract-619856.cct",
"contract-631275.cct",
"contract-6317.cct",
"contract-653136.cct",
"contract-655415.cct",
"contract-658731.cct",
"contract-662427.cct",
"contract-663124.cct",
"contract-663518.cct",
"contract-669853.cct",
"contract-671683.cct",
"contract-676164.cct",
"contract-677643.cct",
"contract-681060.cct",
"contract-683911.cct",
"contract-685393.cct",
"contract-695727.cct",
"contract-696156.cct",
"contract-703758.cct",
"contract-720460.cct",
"contract-722083.cct",
"contract-727788.cct",
"contract-735210.cct",
"contract-736394.cct",
"contract-736483.cct",
"contract-748113.cct",
"contract-751169.cct",
"contract-752502.cct",
"contract-765155.cct",
"contract-772173.cct",
"contract-773439.cct",
"contract-77492.cct",
"contract-778492.cct",
"contract-784712.cct",
"contract-785014.cct",
"contract-786215.cct",
"contract-789483.cct",
"contract-7918.cct",
"contract-796855.cct",
"contract-800839.cct",
"contract-801748.cct",
"contract-81208.cct",
"contract-817514.cct",
"contract-82882.cct",
"contract-843473.cct",
"contract-843884.cct",
"contract-847170.cct",
"contract-847956.cct",
"contract-848049.cct",
"contract-856399.cct",
"contract-862326.cct",
"contract-866043.cct",
"contract-866539.cct",
"contract-870914.cct",
"contract-887241.cct",
"contract-893688.cct",
"contract-89945.cct",
"contract-900580.cct",
"contract-915646.cct",
"contract-918325.cct",
"contract-9193.cct",
"contract-921551.cct",
"contract-942582.cct",
"contract-945836.cct",
"contract-947944.cct",
"contract-954121.cct",
"contract-957901.cct",
"contract-960362.cct",
"contract-963099.cct",
"contract-965221.cct",
"contract-979556.cct",
"contract-985969.cct",
"contract-992733.cct",
"contract-996245.cct",
"contract-997464.cct"];
for (let file of files) {
ns.rm(file)
}
}

View File

@@ -0,0 +1,24 @@
/** @param {NS} ns */
export async function main(ns) {
let aFactions = ns.getPlayer().factions;
ns.tprint("aFactions = " + aFactions);
let aAugmentations;
let sStats;
for (let f = 0; f < aFactions.length; f++) {
//ns.tprint("f = " + f);
//sFaction = ns.singularity.faction
aAugmentations = ns.singularity.getAugmentationsFromFaction(aFactions[f]);
ns.tprint("aAugmentations = " + aAugmentations);
ns.tprint("----------------------------------------------------------------------------------------------------------------------------------------------------------");
for (let a = 0; a < aFactions.length; a++) {
sStats = JSON.stringify(ns.singularity.getAugmentationStats(aAugmentations[a]));
ns.tprint(aAugmentations[f] + "sStats = " + sStats);
ns.tprint("----------------------------------------------------------------------------------------------------------------------------------------------------------");
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,97 +1,83 @@
// Custom color coding.
const cCyan = "\u001b[36m";
const cGreen = "\u001b[32m";
const cRed = "\u001b[31m";
const cReset = "\u001b[0m";
/** @param {NS} ns */
export async function main(ns) {
ns.tprint("This is just a function library, it doesn't do anything.");
}
let reset = ns.args[0];
const bSilent = ns.args[1] === true ? true : false; // if true supress feedback
//ns.tprint("bSilent = " + bSilent);
//host and script info
const sThisRunner = ns.getHostname();
const sThisScript = ns.getScriptName();
const nThisPID = ns.pid;
/** @param {NS} ns */
export function getCracks(ns) {
//open log for this script
if (!bSilent) ns.tail(nThisPID, sThisRunner, ns.args);
const funnyScript = ["breach.js", "batch.js", "grow.js", "weaken.js", "hack.js"];
const sScript = "breach.js";
//write function to purchase scripts from tor network and rerun getCracks() then recrack and reroot
let cracks = {};
if (ns.fileExists("BruteSSH.exe", "home")) {
cracks["BruteSSH.exe"] = ns.brutessh;
};
if (ns.fileExists("FTPCrack.exe", "home")) {
cracks["FTPCrack.exe"] = ns.ftpcrack;
};
if (ns.fileExists("relaySMTP.exe", "home")) {
cracks["relaySMTP.exe"] = ns.relaysmtp;
};
if (ns.fileExists("HTTPWorm.exe", "home")) {
cracks["HTTPWorm.exe"] = ns.httpworm;
};
if (ns.fileExists("SQLInject.exe", "home")) {
cracks["SQLInject.exe"] = ns.sqlinject;
};
return cracks;
cracks = getCracks(ns);
let maxPorts = Object.keys(cracks).length;
scanServerList(ns, bSilent);
let manualTargetOverride = "";
let nHackingLevel = ns.getHackingLevel();
let nServerMaxSecurity = 100;
if (nHackingLevel < 10) {
manualTargetOverride = "n00dles";
}
/** @param {NS} ns */
export function scanServerList(ns) {
const home = "home";
let serverList = {};
let unscanned = [];
unscanned.push(home);
while (unscanned.length > 0) {
let currentServer = unscanned.pop();
if (!serverList[currentServer]) {
let maxRam = ns.getServerMaxRam(currentServer);
let minPorts = ns.getServerNumPortsRequired(currentServer);
let minSecLevel = ns.getServerMinSecurityLevel(currentServer);
let minHackLevel = ns.getServerRequiredHackingLevel(currentServer);
let rootAccess = ns.hasRootAccess(currentServer);
let serverMoney = ns.getServerMaxMoney(currentServer);
let serverFiles = ns.ls(currentServer);
let skillFactor = (2.5 * minHackLevel * minSecLevel + 500) / (ns.getHackingLevel() + 50);
let compareTimeFactor = serverMoney / skillFactor / 10e7;
serverList[currentServer] =
{
serverName: currentServer,
maxRam: maxRam,
maxMoney: serverMoney,
minSec: minSecLevel,
minPorts: minPorts,
minHackLvl: minHackLevel,
rootAccess: rootAccess,
factorMoneyPerTime: compareTimeFactor,
openPorts: 0,
serverFiles: serverFiles,
};
let neighbours = ns.scan(currentServer);
for (let i = 0; i < neighbours.length; i++) {
let neighbour = neighbours[i];
if (serverList[neighbour]) {
continue
}
unscanned.push(neighbour);
}
}
}
ns.write("serverList.txt", JSON.stringify(serverList), "w");
}
/** @param {NS} ns */
export function findBestTarget(ns, maxSec, maxPorts, currentHackLevel, manualTargetOverride) {
if (!ns.fileExists("serverList.txt", "home")) scanServerList();
//ns.tprint("find best target");
findBestTarget(ns, nServerMaxSecurity, maxPorts, ns.getHackingLevel(), manualTargetOverride);
//ns.tprint("Best Target found? ");
//let bestTarget = ns.read("bestTarget.txt");
//ns.tprint("Best Target: " + bestTarget);
//ns.tprint(Object.keys(JSON.parse(ns.read("serverList.txt"))).length);
crackingAndRooting(ns, cracks, funnyScript);
//ns.exec(funnyScript[0], "home", 1, JSON.parse(bestTarget).serverName);
//ns.print(reset);
if (reset === true) {
//ns.tprint("reset === true")
findBestTarget(ns, nServerMaxSecurity, maxPorts, ns.getHackingLevel(), manualTargetOverride);
let serverList = JSON.parse(ns.read("serverList.txt"));
let bestEntry = null;
let compareTime = 0;
for (const [name, entry] of Object.entries(serverList)) {
if (entry.minSec <= maxSec && entry.minPorts <= maxPorts && entry.minHackLvl < currentHackLevel) {
if (entry.factorMoneyPerTime > compareTime) {
compareTime = entry.factorMoneyPerTime;
bestEntry = name;
//copyAndRunSript(ns, funnyScript, name);
}
}
}
if (manualTargetOverride.length > 0) {
bestEntry = manualTargetOverride;
}
ns.write("bestTarget.txt", JSON.stringify(serverList[bestEntry]), "w");
}
/*let serverListForFiles = JSON.parse(ns.read("serverList.txt"));
for (const [name2, entry2] of Object.entries(serverListForFiles)) {
ns.tprint(name2 + " Files: " + entry2.serverFiles)
}*/
//await ns.sleep(500000);
//await purchaseAndUpgradeServers(ns, funnyScript);
//await runScriptsFromServers(ns, sScript, JSON.parse(bestTarget).serverName);
let oBestTarget = JSON.parse(ns.read("bestTarget.txt"));
let sBestTarget = oBestTarget.serverName;
//ns.exec(sScript, "home", 1, sBestTarget);
/*
if(){
ns.exec(sScript,"home",1);
}
*/
}
/** @param {NS} ns */
export function crackingAndRooting(ns, cracks, funnyScript, copy) {
if (!ns.fileExists("serverList.txt", "home")) scanServerList();
function crackingAndRooting(ns, cracks, funnyScript) {
let serverList = JSON.parse(ns.read("serverList.txt"));
for (const [name, entry] of Object.entries(serverList)) {
let cracked = false;
@@ -107,8 +93,8 @@ export function crackingAndRooting(ns, cracks, funnyScript, copy) {
ns.nuke(name);
if (ns.hasRootAccess(name)) {
serverList[name].rootAccess = true;
if (serverList[name].maxRam > 0 && copy === true) {
copyAndRunScript(ns, funnyScript, name);
if (serverList[name].maxRam > 0) {
copyAndRunSript(ns, funnyScript, name);
}
}
}
@@ -117,13 +103,73 @@ export function crackingAndRooting(ns, cracks, funnyScript, copy) {
ns.tprint("Cracking and rooting done");
}
/** @param {NS} ns */
async function purchaseAndUpgradeServers(ns, funnyScript) {
let maxPurchasedServers = ns.getPurchasedServerLimit();
let purchasedServers = [];
let count = listPurchasedServers(ns).length;
let currentMoney = 0;
let serverList = {};
while (count < maxPurchasedServers) {
purchasedServers = listPurchasedServers(ns);
currentMoney = ns.getServerMoneyAvailable("home");
let targetRamInitial = 16;
if (ns.getPurchasedServerCost(targetRamInitial) < currentMoney) {
let hostname = ns.purchaseServer("pserv-" + purchasedServers.length, 16);
count = listPurchasedServers(ns).length;
serverList = JSON.parse(ns.read("serverList.txt"));
serverList[hostname] = {
serverName: hostname,
maxRam: 16,
maxMoney: 0,
minSec: 0,
minPorts: 5,
minHackLvl: 1,
rootAccess: true,
factorMoneyPerTime: 99999999,
openPorts: 0,
};
ns.write("serverList.txt", JSON.stringify(serverList), "w");
copyAndRunSript(ns, funnyScript, hostname);
continue
} else {
await ns.sleep(5000);
}
}
let i = 5;
while (i < 21) {
let targetRam = 2 ** i;
purchasedServers = listPurchasedServers(ns);
for (let currentServer of purchasedServers) {
currentMoney = ns.getServerMoneyAvailable("home");
if (ns.getPurchasedServerUpgradeCost(currentServer, targetRam) < currentMoney) {
if (ns.upgradePurchasedServer(currentServer, targetRam)) {
ns.print(currentServer + " upgraded to " + targetRam + " GB RAM");
copyAndRunSript(ns, funnyScript, currentServer);
serverList = JSON.parse(ns.read("serverList.txt"));
serverList[currentServer].maxRam = targetRam;
ns.write("serverList.txt", JSON.stringify(serverList), "w");
}
} else {
await ns.sleep(5000);
continue
};
}
++i;
}
ns.tprint("Extiting purchaseServers script!")
}
/** @param {NS} ns */
export function copyAndRunScript(ns, funnyScript, currentServer) {
function listPurchasedServers(ns) {
return ns.getPurchasedServers();
}
/** @param {NS} ns */
function copyAndRunSript(ns, funnyScript, currentServer) {
// change to run for one specific server with bestTarget from file
//let minRam = ns.getScriptRam(funnyScript);
let bestTarget = JSON.parse(ns.read("bestTarget.txt"));
let name = currentServer;
let serverList = JSON.parse(ns.read("serverList.txt"));
ns.print(name);
@@ -137,16 +183,15 @@ export function copyAndRunScript(ns, funnyScript, currentServer) {
if (serverList[name].maxRam > 0) {
ns.scp(funnyScript, name, "home");
let maxProcesses = 1;
if (serverList[name].maxRam >= 8) {
maxProcesses = Math.max(Math.floor((serverList[name].maxRam) / 8), 1);
if (serverList[name].maxRam - 64 > 512) {
maxProcesses = Math.max(Math.floor((serverList[name].maxRam - 64) / 512), 1);
} else {
maxProcesses = 1
};
for (let n = 1; n <= maxProcesses; n++) {
ns.exec(funnyScript[0], name, 1, bestTarget.serverName);
}
/*let maxThreads = 0;
if (name === "home") {
maxThreads = Math.floor((serverList[name].maxRam - ns.getServerUsedRam(name) - 32) / minRam);
@@ -171,108 +216,156 @@ export function copyAndRunScript(ns, funnyScript, currentServer) {
}
/** @param {NS} ns */
export async function purchaseAndUpgradeServers(ns) {
ns.disableLog("sleep");
ns.disableLog("getServerMoneyAvailable");
ns.disableLog("getServerMaxRam");
let maxPurchasedServers = ns.getPurchasedServerLimit();
let purchasedServers = [];
let count = listPurchasedServers(ns).length;
let currentMoney = 0;
function getCracks(ns) {
let cracks = {};
if (ns.fileExists("BruteSSH.exe", "home")) {
cracks["BruteSSH.exe"] = ns.brutessh;
};
if (ns.fileExists("FTPCrack.exe", "home")) {
cracks["FTPCrack.exe"] = ns.ftpcrack;
};
if (ns.fileExists("relaySMTP.exe", "home")) {
cracks["relaySMTP.exe"] = ns.relaysmtp;
};
if (ns.fileExists("HTTPWorm.exe", "home")) {
cracks["HTTPWorm.exe"] = ns.httpworm;
};
if (ns.fileExists("SQLInject.exe", "home")) {
cracks["SQLInject.exe"] = ns.sqlinject;
};
return cracks;
}
/** @param {NS} ns */
function scanServerList(ns, bSilent) {
const home = "home";
let serverList = {};
while (count < maxPurchasedServers) {
purchasedServers = listPurchasedServers(ns);
currentMoney = ns.getServerMoneyAvailable("home");
let targetRamInitial = 16;
if (ns.getPurchasedServerCost(targetRamInitial) < currentMoney) {
let hostname = ns.purchaseServer("pserv-" + purchasedServers.length, 16);
count = listPurchasedServers(ns).length;
serverList = JSON.parse(ns.read("serverList.txt"));
serverList[hostname] = {
serverName: hostname,
maxRam: 16,
maxMoney: 0,
minSec: 0,
minPorts: 5,
minHackLvl: 1,
rootAccess: true,
factorMoneyPerTime: 99999999,
let unscanned = [];
let nHackingLevel = ns.getHackingLevel();
let oServer;
unscanned.push(home);
while (unscanned.length > 0) {
let currentServer = unscanned.pop();
if (!serverList[currentServer]) {
let maxRam = ns.getServerMaxRam(currentServer);
let minPorts = ns.getServerNumPortsRequired(currentServer);
let minSecLevel = ns.getServerMinSecurityLevel(currentServer);
let minHackLevel = ns.getServerRequiredHackingLevel(currentServer);
let rootAccess = ns.hasRootAccess(currentServer);
let serverMoney = ns.getServerMaxMoney(currentServer);
let serverFiles = ns.ls(currentServer);
let skillFactor = (2.5 * minHackLevel * minSecLevel + 500) / (nHackingLevel + 50);
let compareTimeFactor = serverMoney / skillFactor / 10e7;
let nHackingChance = ns.hackAnalyzeChance(currentServer);
oServer = ns.getServer(currentServer);
let sOrg = oServer.organizationName;
let nCores = oServer.cpuCores;
if (minHackLevel <= nHackingLevel && !currentServer.includes("home") && !currentServer.includes("pserv") && serverMoney > 0 && !bSilent) {
//if (!currentServer.includes("home") && !currentServer.includes("pserv") && !bSilent) {
ns.tprint("Server : " + currentServer);
ns.tprint("sOrg : " + sOrg);
ns.tprint("Hacking : " + minHackLevel);
ns.tprint("RAM : " + maxRam);
ns.tprint("Cores : " + nCores);
ns.tprint("Money : " + Math.floor(serverMoney).toLocaleString());
ns.tprint("Hack chance : " + Math.floor(nHackingChance * 10000) / 100 + " %");
ns.tprint("Security : " + minSecLevel);
ns.tprint("--------------------------------------------------------------");
}
serverList[currentServer] =
{
serverName: currentServer,
maxRam: maxRam,
maxMoney: serverMoney,
minSec: minSecLevel,
minPorts: minPorts,
minHackLvl: minHackLevel,
rootAccess: rootAccess,
factorMoneyPerTime: compareTimeFactor,
openPorts: 0,
serverFiles: serverFiles,
hackingChance: nHackingChance
};
ns.write("serverList.txt", JSON.stringify(serverList), "w");
let neighbours = ns.scan(currentServer);
for (let i = 0; i < neighbours.length; i++) {
let neighbour = neighbours[i];
if (serverList[neighbour]) {
continue
} else {
await ns.sleep(5000);
}
unscanned.push(neighbour);
}
}
}
let i = 5;
while (i < 21) {
let targetRam = 2 ** i;
purchasedServers = listPurchasedServers(ns);
for (let currentServer of purchasedServers) {
currentMoney = ns.getServerMoneyAvailable("home");
if (ns.getServerMaxRam(currentServer) < targetRam && ns.getPurchasedServerUpgradeCost(currentServer, targetRam) < currentMoney) {
if (ns.upgradePurchasedServer(currentServer, targetRam)) {
ns.print(currentServer + " upgraded to " + targetRam + " GB RAM");
serverList = JSON.parse(ns.read("serverList.txt"));
serverList[currentServer].maxRam = targetRam;
ns.write("serverList.txt", JSON.stringify(serverList), "w");
}
} else {
await ns.sleep(5000);
continue
};
}
++i;
}
ns.tprint("Extiting purchaseServers script!")
}
/** @param {NS} ns */
function listPurchasedServers(ns) {
return ns.getPurchasedServers();
}
/** @param {NS} ns */
export async function runControllerOnPserv(ns) {
let purchasedServers = listPurchasedServers(ns);
let nPID = 0;
nPID = ns.exec("S2controller.js", "home");
ns.tprint("Started S2controller.js on " + "home" + " with PID " + nPID)
for (let currentServer of purchasedServers) {
ns.scp(["S2tGrow.js", "S2tWeaken.js", "S2tHack.js", "S2controller.js", "S2utils.js"], currentServer, "home");
nPID = ns.exec("S2controller.js", currentServer);
if (nPID > 0) {
ns.tprint("Started S2controller.js on " + currentServer + " with PID " + nPID)
}
}
}
/** @param {NS} ns */
export async function backdoor(ns) {
function findBestTarget(ns, maxSec, maxPorts, currentHackLevel, manualTargetOverride) {
//ns.tprint("start of findBestTarget ");
const bSilent = ns.args[1] === true ? true : false; // if true supress feedback
//ns.tprint("bSilent = " +bSilent);
let sCurrentBestTarget = JSON.parse(ns.read("bestTarget.txt"));
//let sCurrentBestTarget = "n00dles";
//ns.tprint("sCurrentBestTarget = " +JSON.stringify(sCurrentBestTarget));
//if (!bSilent) ns.tprint("sCurrentBestTarget = " + sCurrentBestTarget.serverName);
let serverList = JSON.parse(ns.read("serverList.txt"));
let lasthackingLevel = 0;
let currentHackingLevel = 0;
while (true) {
currentHackingLevel = ns.getHackingLevel();
if (currentHackingLevel > lasthackingLevel) {
lasthackingLevel = currentHackingLevel;
let bestEntry = null;
let compareTime = 0;
let nMaxMoneyPerChance = 0;
let nBestMoneyPerChance = 0;
//ns.tprint("before for loop ");
for (const [name, entry] of Object.entries(serverList)) {
if (entry.minHackLvl <= lasthackingLevel && entry.hasBackdoor !== true) {
ns.singularity.connect(name);
await ns.singularity.installBackdoor();
ns.singularity.connect("home");
serverList[name].hasBackdoor = true;
ns.tprint("Backdoor on: " + name);
if (entry.minSec <= maxSec && entry.minPorts <= maxPorts && entry.minHackLvl < currentHackLevel) {
nMaxMoneyPerChance = (entry.maxMoney * entry.hackingChance) / entry.minSec;
//ns.tprint("nMaxMoneyPerChance " + nMaxMoneyPerChance);
/*
if (entry.factorMoneyPerTime > compareTime) {
compareTime = entry.factorMoneyPerTime;
bestEntry = name;
}
}
ns.write("serverList.txt", JSON.stringify(serverList), "w");
} else {
await ns.sleep(30000)
};
*/
if (nMaxMoneyPerChance > nBestMoneyPerChance) {
nBestMoneyPerChance = nMaxMoneyPerChance;
bestEntry = name;
if (!bSilent) ns.tprint("bestEntry " + bestEntry);
if (!bSilent) ns.tprint("╚ value = " + Math.floor(nMaxMoneyPerChance));
}
}
}
if (manualTargetOverride.length > 0) {
bestEntry = manualTargetOverride;
}
if (sCurrentBestTarget.serverName === bestEntry) {
if (!bSilent) ns.tprint("Same target = " + bestEntry);
}
else {
ns.tprint("New best target = " + cCyan + bestEntry + cReset);
}
ns.write("bestTarget.txt", JSON.stringify(serverList[bestEntry]), "w");
}
/** @param {NS} ns */
async function runScriptsFromServers(ns, sScript, sTarget) {
let serverList = JSON.parse(ns.read("serverList.txt"));
let nScripts;
let nMaxRam = 0;
const nScriptRAM = ns.getScriptRam(sScript);
for (const [name, entry] of Object.entries(serverList)) {
//let rootAccess = ns.hasRootAccess(currentServer);
nMaxRam = ns.getServerMaxRam(entry.serverName);
nScripts = Math.floor(nMaxRam / nScriptRAM);
if (entry.maxRam >= 8 && serverList[name].rootAccess === true && !entry.serverName.includes("home") && !entry.serverName.includes("pserv")) {
//ns.killall(entry.serverName);
ns.scriptKill(sScript, entry.serverName);
ns.scp(sScript, entry.serverName);
while (nScripts > 0) {
ns.exec(sScript, entry.serverName, 1, sTarget);
nScripts--;
await ns.sleep(10);
}
}
}
}

View File

@@ -0,0 +1,4 @@
{"setting":{
"autoUpgrades":true,
"autoPurchaseServers":true
}}

View File

@@ -0,0 +1,34 @@
/** @param {NS} ns */
export async function main(ns) {
const bKill = ns.args[0]; // if true kill all existing processess
//host and script info
const sThisRunner = ns.getHostname();
//const sThisScript = ns.getScriptName();
const nThisPID = ns.pid;
//open log for this script
ns.tail(nThisPID, sThisRunner, ns.args);
//my servers
const nServ = ns.getPurchasedServers();
const sShareScript = "factionboost.js";
let nScripts = 0;
//kill all scripts
for (let i = 0; i < nServ.length; i++) {
nScripts = Math.floor(ns.getServerMaxRam(nServ[i]) / ns.getScriptRam(sShareScript));
if (bKill) { ns.killall(nServ[i]); }
else { ns.scriptKill(sShareScript, nServ[i]); }
ns.scp(sShareScript, nServ[i], "home");
let nShare = Math.floor(((ns.getServerMaxRam(nServ[i]) - ns.getServerUsedRam(nServ[i])) / ns.getScriptRam(sShareScript)));
if (nShare > 0) {
ns.exec(sShareScript, nServ[i], nShare);
}
}
}

View File

@@ -0,0 +1,52 @@
/** @param {NS} ns */
export async function main(ns) {
ns.tail();
let serverRoutes = await scanRecursiveWrapper(ns);
let currentHackingLevel = ns.getHackingLevel();
let currentArray = [];
let currentHop = "";
for (const [name, entry] of Object.entries(serverRoutes)) {
if (ns.getServerRequiredHackingLevel(name) < currentHackingLevel && ns.hasRootAccess(name) === true) {
currentArray = entry;
if (entry.length > 0) {
while (currentArray.length > 0) {
currentHop = currentArray.shift();
if (ns.singularity.connect(currentHop) === false) ns.tprint("Error when trying to connect to: " + currentHop);
}
ns.tprint("Trying to backdoor " + currentHop)
await ns.singularity.installBackdoor(currentHop);
ns.tprint("Success on " + currentHop)
ns.singularity.connect("home");
}
}
}
}
/** @param {NS} ns */
async function scanRecursiveWrapper(ns) {
let startTime = Date.now();
ns.rm("ServerRouteList.txt");
await scanRecursive(ns, [], "home", startTime);
let result = JSON.parse("[" + ns.read("ServerRouteList.txt") + "{}]").reduce((acc, obj) => ({ ...acc, ...obj }), {});
ns.write("ServerRouteList.txt", JSON.stringify(result, "w"));
return result;
}
/** @param {NS} ns */
async function scanRecursive(ns, parents, currentServer, startTime) {
let currentTime = Date.now();
if (currentTime > startTime + 5) {
return
} else {
let currentChildren = ns.scan(currentServer);
let parentsSet = new Set(parents);
let cleanedChildren = currentChildren.filter(element => !parentsSet.has(element));
let tempServername = String(currentServer);
ns.write("ServerRouteList.txt", JSON.stringify({ [tempServername]: parents }) + ",", "a");
parents.push(currentServer);
for (let child in cleanedChildren) {
await scanRecursive(ns, parents, cleanedChildren[child], startTime)
}
}
}

View File

@@ -0,0 +1,71 @@
/** @param {NS} ns */
export async function main(ns) {
const sNode = ns.args[0]; // target server
//logs
ns.disableLog("getServerSecurityLevel");
ns.disableLog("getServerMoneyAvailable");
//server info
const nMinSec = ns.getServerMinSecurityLevel(sNode);
const nMaxCash = ns.getServerMaxMoney(sNode);
while (true) {
//timestamp
let currentDate = new Date();
let ntimeStamp = currentDate.toLocaleTimeString('sw-SV') + " -";
const nCurrSec = ns.getServerSecurityLevel(sNode);
const nCurrCash = ns.getServerMoneyAvailable(sNode);
ns.print(ntimeStamp + "---------------------------------------------------------------");
ns.print("Cash: " + (Math.floor(nCurrCash * 1000) / 1000) + " / " + nMaxCash);
ns.print("Security: " + (Math.floor(nCurrSec * 1000) / 1000) + " / " + nMinSec);
if (nCurrSec > (nMinSec + 1)) {
//Calculate estimate time of completion
let nOffset = ns.getWeakenTime(sNode);
let nWeakTime = new Date(currentDate.getTime() + nOffset);
let sWeakTime = nWeakTime.toLocaleTimeString('sw-SV');
//Print estimated time of completion
ns.print("Weakening " + sNode + " Estimated complete at " + sWeakTime);
//run weaken
await ns.weaken(sNode);
ns.print("-------------------------------------------------------------------------");
}
else if (nCurrCash < (nMaxCash * 0.9)) {
//Calculate estimate time of completion
let nOffset = ns.getGrowTime(sNode);
let nGrowTime = new Date(currentDate.getTime() + nOffset);
let sGrowTime = nGrowTime.toLocaleTimeString('sw-SV');
//Print estimated time of completion
ns.print("Growing " + sNode + " Estimated complete at " + sGrowTime);
//run grow
await ns.grow(sNode);
ns.print("-------------------------------------------------------------------------");
}
else {
//Calculate estimate time of completion
let nOffset = ns.getHackTime(sNode);
let nHackTime = new Date(currentDate.getTime() + nOffset);
let sHackTime = nHackTime.toLocaleTimeString('sw-SV');
//Print estimated time of completion
ns.print("Hacking " + sNode + " Estimated complete at " + sHackTime);
//run hack
//await ns.hack(sNode);
await ns.singularity.manualHack();
ns.print("-------------------------------------------------------------------------");
}
}
}

View File

@@ -1,24 +0,0 @@
/*
Workers are mostly the same, aside from uncommented portions allowing the hack and grow workers to report.
I've also generally commented out the terminal logging, as it gets rather laggy when there's a lot of scripts
writing to terminal.
*/
/** @param {NS} ns */
export async function main(ns) {
const job = JSON.parse(ns.args[0]);
let delay = job.end - job.time - Date.now();
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
ns.writePort(ns.pid, -delay);
delay = 0;
} else {
ns.writePort(ns.pid, 0);
}
await ns.grow(job.target, { additionalMsec: delay });
const end = Date.now();
ns.atExit(() => {
if (job.report) ns.writePort(job.port, job.type + job.server);
// ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
});
}

View File

@@ -1,24 +0,0 @@
/*
Workers are mostly the same, aside from uncommented portions allowing the hack and grow workers to report.
I've also generally commented out the terminal logging, as it gets rather laggy when there's a lot of scripts
writing to terminal.
*/
/** @param {NS} ns */
export async function main(ns) {
const job = JSON.parse(ns.args[0]);
let delay = job.end - job.time - Date.now();
if (delay < 0) {
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
ns.writePort(ns.pid, -delay);
delay = 0;
} else {
ns.writePort(ns.pid, 0);
}
await ns.hack(job.target, { additionalMsec: delay });
const end = Date.now();
ns.atExit(() => {
if (job.report) ns.writePort(job.port, job.type + job.server);
// ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
});
}

View File

@@ -1,28 +0,0 @@
/*
Workers are mostly the same, aside from uncommented portions allowing the hack and grow workers to report.
I've also generally commented out the terminal logging, as it gets rather laggy when there's a lot of scripts
writing to terminal.
*/
/** @param {NS} ns */
export async function main(ns) {
const job = JSON.parse(ns.args[0]);
let delay = job.end - job.time - Date.now();
if (delay < 0) {
// We write back to the controller if jobs are delayed so that it can adjust the other jobs to match.
ns.tprint(`WARN: Batch ${job.batch} ${job.type} was ${-delay}ms late. (${job.end})\n`);
ns.writePort(ns.pid, -delay);
delay = 0;
} else {
ns.writePort(ns.pid, 0);
}
await ns.weaken(job.target, { additionalMsec: delay });
const end = Date.now();
// Write back to let the controller know that we're done.
ns.atExit(() => {
if (job.report) ns.writePort(job.port, job.type + job.server);
// ns.tprint(`Batch ${job.batch}: ${job.type} finished at ${end.toString().slice(-6)}/${Math.round(job.end).toString().slice(-6)}\n`);
});
}

View File

@@ -1,4 +0,0 @@
export async function main(ns) {
ns.tprint("Hello World!");
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkM6L2dhbWVzL0JpdEJ1cm5lckdpdC92aXRlYnVybmVyLXRlbXBsYXRlL3NyYy90ZW1wbGF0ZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBOUyB9IGZyb20gJ0Bucyc7XHJcblxyXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbWFpbihuczogTlMpIHtcclxuICBucy50cHJpbnQoJ0hlbGxvIFdvcmxkIScpO1xyXG59XHJcbiJdLCJtYXBwaW5ncyI6IkFBRUEsc0JBQXNCLEtBQUssSUFBUTtBQUNqQyxLQUFHLE9BQU8sY0FBYztBQUMxQjsiLCJuYW1lcyI6W119

View File

@@ -1,161 +1,16 @@
/** @param {NS} ns */
export async function main(ns) {
ns.tprint(ns.codingcontract.getContractTypes())
let testcontract = ns.codingcontract.createDummyContract("Total Ways to Sum")
let contractType = ns.codingcontract.getContractType(testcontract);
ns.tprint(ns.codingcontract.getDescription(testcontract))
let n = ns.codingcontract.getData(testcontract);
ns.tprint("Data: " + n);
let answer = "";
if (contractType === "Find Largest Prime Factor") {
answer = largestPrimeFactor(n);
}
if (contractType === "Subarray with Maximum Sum") {
answer = SubarrayWithMaximumSum(ns, n)
}
if (contractType === "Total Ways to Sum") {
answer = TotalWaysToSum(ns, n)
}
let oSettings = JSON.parse(ns.read("settings.txt"));
// let variable = someJSON.missingKey || ""
//let tempVar = oSettings.settings.missingKey || "";
//ns.tprint(tempVar)
ns.tprint(answer);
ns.tprint(oSettings);
ns.tprint(JSON.stringify(oSettings));
ns.tprint(oSettings.setting.autoUpgrades);
//let tempVar = oSettings.settings.missingKey || "";
//ns.tprint(tempVar)
ns.tprint(ns.codingcontract.attempt(answer, testcontract));
}
/*
5:
4 1
3 2
3 1 1
2 2 1
2 1 1 1
1 1 1 1 1
6:
5 1
4 2
4 1 1
3 3
3 2 1
3 1 1 1
2 2 2
2 2 1 1
2 1 1 1 1
1 1 1 1 1 1
# Start with one position m filling it with the integers between 1 and target
# For each m, fill the next position n with integers between 1 and m
# Repeat as long as the sum is smaller than target.
# append all iterations to the Array and count
*/
function TotalWaysToSum(ns, target) {
let sumArray = [];
let inputArray = [];
let unfinishedArray = [];
let rollingSum = 0;
for (let i = 1; i < target; i++) {
inputArray.push([i]);
}
let z = 1
while (inputArray.length > 0) {
z++
inputArray.forEach((element) => {
rollingSum = element.reduce((a, b) => a + b, 0);
if (rollingSum === target) {
sumArray.push(element)
} else {
for (let k = 1; k <= element[element.length-1] && k <= target - rollingSum; k++) {
unfinishedArray.push(element.concat([k]))
}
}
}
)
inputArray = unfinishedArray;
}
ns.tprint("Target: " +target)
ns.tprint("Length: " + sumArray.length)
return sumArray.length
}
function SubarrayWithMaximumSum(ns, givenArray) {
let arrayLength = givenArray.length;
let maxSum = -10000;
let runningSum = 0;
for (let i = 1; i <= arrayLength; i++) {
for (let j = 0; j <= arrayLength - i; j++) {
runningSum = eval(givenArray.slice(j, i + j).join('+'));
//ns.tprint("i: "+i+ " j: "+ j + " Array: "+givenArray.slice(j,i+j)+ " eval: "+ givenArray.slice(j,i+j).join('+')+"runningSum: "+runningSum);
if (maxSum < runningSum) { maxSum = runningSum };
}
}
return maxSum
}
function FindLargestPrimeFactor(number) {
let factor = 2;
while (factor * factor <= number) {
if (number % factor === 0) {
number /= factor;
} else {
factor++
}
}
return number;
}
/*
function FindLargestPrimeFactor(n) {
let x = Math.ceil(Math.random()*10);
let y = x;
let d = 1;
while (d === 1) {
x = g(x, n);
y = g(g(y, n), n)
d = gcd(n, Math.abs(x - y))
//ns.tprint("x:" + x + " y: " + y + " d: " + d)
}
if (d === n) {
return ("failure")
}
else {
return (d)
}
}
function g(x, n) {
return (x * x) % n
}
function gcd(a,b) {
a = Math.abs(a);
b = Math.abs(b);
if (b > a) {var temp = a; a = b; b = temp;}
while (true) {
if (b == 0) return a;
a %= b;
if (a == 0) return b;
b %= a;
}
}
function gcd(a, b) {
if (!b) {
return a;
}
return gcd(b, a % b);
}
*/

View File

@@ -0,0 +1,41 @@
/** @param {NS} ns */
export async function main(ns) {
ns.tail();
const sTarget = ns.args[0]; // run on this target instead of best
const nHackThreads = ns.args[1] ? ns.args[1] : 1;
ns.disableLog("sleep");
ns.print("nHackThreads = " + nHackThreads);
//ns.tprint(ns.getHackingMultipliers());
let nHackAmountPercent = ns.hackAnalyze(sTarget) * nHackThreads;
let nTargetMoney = ns.getServerMoneyAvailable(sTarget);
let nHackAmount = nTargetMoney * nHackAmountPercent;
ns.print("nHackAmountPercent = " + nHackAmountPercent);
ns.print("nTargetMoney = " + nTargetMoney);
ns.print("nHackAmount = " + nHackAmount);
let nGrowthThreads = ns.growthAnalyze(sTarget, 1 + nHackAmountPercent, 1);
ns.print("nGrowthThreads = " + nGrowthThreads);
let nGrowThreadsINT = Math.ceil(nGrowthThreads);
ns.print("Hack() : Grow() Ratio = " + nHackThreads + ":" + nGrowThreadsINT);
//test hack() and grow()
/*
let nHackPID = ns.exec("RMhack.js", "home", nHackThreads, sTarget);
ns.tail(nHackPID);
let nGrowPID = ns.exec("RMgrow.js", "home", nGrowThreadsINT, sTarget);
ns.tail(nGrowPID);
ns.print("Wait for Hack and Grow...");
while (ns.isRunning(nHackPID) || ns.isRunning(nGrowPID)) {
await ns.sleep(1000);
}
ns.print("Wait complete");
nTargetMoney = ns.getServerMoneyAvailable(sTarget);
ns.print("nTargetMoney = " + nTargetMoney);
*/
}

View File

@@ -0,0 +1,66 @@
import { listPurchasedServers, getSetting } from "myLibrary.js";
// Custom color coding.
const cCyan = "\u001b[36m";
const cGreen = "\u001b[32m";
const cRed = "\u001b[31m";
const cReset = "\u001b[0m";
/** @param {NS} ns */
export async function main(ns) {
ns.disableLog("ALL");
//host and script info
const sThisRunner = ns.getHostname();
//open log for this script
ns.tail(ns.pid, sThisRunner);
//const npServ = ["pserv-0", "pserv-1", "pserv-2", "pserv-3", "pserv-4", "pserv-5", "pserv-6", "pserv-7", "pserv-8", "pserv-9", "pserv-10", "pserv-11", "pserv-12", "pserv-13", "pserv-14", "pserv-15", "pserv-16", "pserv-17", "pserv-18", "pserv-19", "pserv-20", "pserv-21", "pserv-22", "pserv-23", "pserv-24"];
const npServ = listPurchasedServers(ns);
let nCurrentRAM = 4;
let nMyMoney = ns.getServerMoneyAvailable("home");
let bAutoUpgrade = getSetting(ns, "autoUpgrades");
while (bAutoUpgrade && nCurrentRAM < 2 ** 20) {
//for (let i = 0; i < npServ.length; i++) {
for (let i = 0; i < npServ.length && bAutoUpgrade; i++) {
ns.print("----------------------------------------------------------------------");
nMyMoney = ns.getServerMoneyAvailable("home");
let nNextRAM = nCurrentRAM * 2;
let nUpgradeCost = ns.getPurchasedServerUpgradeCost(npServ[i], nNextRAM);
ns.print("npServ = " + npServ[i]);
ns.print("nMyMoney = " + Math.ceil(nMyMoney).toLocaleString());
ns.print("nCurrentRAM = " + nCurrentRAM.toLocaleString());
ns.print("nNextRAM = " + nNextRAM);
ns.print("nUpgradeCost = " + Math.ceil(nUpgradeCost).toLocaleString());
while (bAutoUpgrade && nMyMoney < nUpgradeCost) {
ns.print("Money Treshold = " + Math.ceil(nMyMoney).toLocaleString() + " / " + Math.ceil(nUpgradeCost).toLocaleString());
await ns.sleep(1000);
bAutoUpgrade = getSetting(ns, "autoUpgrades");
if (bAutoUpgrade === false) {
return;
}
nMyMoney = ns.getServerMoneyAvailable("home");
}
if (ns.upgradePurchasedServer(npServ[i], nNextRAM)) {
ns.print("Server: " + npServ[i] + " upgraded to: " + nNextRAM + "GB");
ns.tprint(cCyan + "Server: " + npServ[i] + " upgraded to: " + nNextRAM.toLocaleString() + " GB" + cReset);
ns.toast("Server: " + npServ[i] + " upgraded to: " + nNextRAM.toLocaleString() + " GB", "info", 10000);
}
else {
ns.print("Server: " + npServ[i] + " could not be upgraded to : " + nNextRAM + "GB it is currently at: " + ns.getServerMaxRam(npServ[i]) + "GB");
}
bAutoUpgrade = getSetting(ns, "autoUpgrades");
if (bAutoUpgrade === false) {
return;
}
}
nCurrentRAM *= 2;
}
ns.print("----------------------------------------------------------------------");
}

View File

@@ -1,218 +0,0 @@
/*
This file remains unchanged from the previous part, aside from updating the file paths.
I didn't even bother removing the old comments.
*/
/** @param {NS} ns */
export async function main(ns) {
ns.tprint("This is just a function library, it doesn't do anything.");
}
// The recursive server navigation algorithm. The lambda predicate determines which servers to add to the final list.
// You can also plug other functions into the lambda to perform other tasks that check all servers at the same time.
/** @param {NS} ns */
export function getServers(ns, lambdaCondition = () => true, hostname = "home", servers = [], visited = []) {
if (visited.includes(hostname)) return;
visited.push(hostname);
if (lambdaCondition(hostname)) servers.push(hostname);
const connectedNodes = ns.scan(hostname);
if (hostname !== "home") connectedNodes.shift();
for (const node of connectedNodes) getServers(ns, lambdaCondition, node, servers, visited);
return servers;
}
// Here are a couple of my own getServers modules.
// This one finds the best target for hacking. It tries to balance expected return with time taken.
/** @param {NS} ns */
export function checkTarget(ns, server, target = "n00dles", forms = false) {
if (!ns.hasRootAccess(server)) return target;
const player = ns.getPlayer();
const serverSim = ns.getServer(server);
const pSim = ns.getServer(target);
let previousScore;
let currentScore;
if (serverSim.requiredHackingSkill <= player.skills.hacking / (forms ? 1 : 2)) {
if (forms) {
serverSim.hackDifficulty = serverSim.minDifficulty;
pSim.hackDifficulty = pSim.minDifficulty;
previousScore = pSim.moneyMax / ns.formulas.hacking.weakenTime(pSim, player) * ns.formulas.hacking.hackChance(pSim, player);
currentScore = serverSim.moneyMax / ns.formulas.hacking.weakenTime(serverSim, player) * ns.formulas.hacking.hackChance(serverSim, player);
} else {
previousScore = pSim.moneyMax / pSim.minDifficulty / ns.getWeakenTime(pSim.hostname);
currentScore = serverSim.moneyMax / serverSim.minDifficulty / ns.getWeakenTime(serverSim.hostname);
}
if (currentScore > previousScore) target = server;
}
return target;
}
// A simple function for copying a list of scripts to a server.
/** @param {NS} ns */
export function copyScripts(ns, server, scripts, overwrite = false) {
for (const script of scripts) {
if ((!ns.fileExists(script, server) || overwrite) && ns.hasRootAccess(server)) {
ns.scp(script, server);
}
}
}
// A generic function to check that a given server is prepped. Mostly just a convenience.
export function isPrepped(ns, server) {
const tolerance = 0.0001;
const maxMoney = ns.getServerMaxMoney(server);
const money = ns.getServerMoneyAvailable(server);
const minSec = ns.getServerMinSecurityLevel(server);
const sec = ns.getServerSecurityLevel(server);
const secFix = Math.abs(sec - minSec) < tolerance;
return (money === maxMoney && secFix) ? true : false;
}
/*
This prep function isn't part of the tutorial, but the rest of the code wouldn't work without it.
I don't make any guarantees, but I've been using it and it's worked well enough. I'll comment it anyway.
The prep strategy uses a modified proto-batching technique, which will be covered in part 2.
*/
/** @param {NS} ns */
export async function prep(ns, values, ramNet) {
const maxMoney = values.maxMoney;
const minSec = values.minSec;
let money = values.money;
let sec = values.sec;
while (!isPrepped(ns, values.target)) {
const wTime = ns.getWeakenTime(values.target);
const gTime = wTime * 0.8;
const dataPort = ns.getPortHandle(ns.pid);
dataPort.clear();
const pRam = ramNet.cloneBlocks();
const maxThreads = Math.floor(ramNet.maxBlockSize / 1.75);
const totalThreads = ramNet.prepThreads;
let wThreads1 = 0;
let wThreads2 = 0;
let gThreads = 0;
let batchCount = 1;
let script, mode;
/*
Modes:
0: Security only
1: Money only
2: One shot
*/
if (money < maxMoney) {
gThreads = Math.ceil(ns.growthAnalyze(values.target, maxMoney / money));
wThreads2 = Math.ceil(ns.growthAnalyzeSecurity(gThreads) / 0.05);
}
if (sec > minSec) {
wThreads1 = Math.ceil((sec - minSec) * 20);
if (!(wThreads1 + wThreads2 + gThreads <= totalThreads && gThreads <= maxThreads)) {
gThreads = 0;
wThreads2 = 0;
batchCount = Math.ceil(wThreads1 / totalThreads);
if (batchCount > 1) wThreads1 = totalThreads;
mode = 0;
} else mode = 2;
} else if (gThreads > maxThreads || gThreads + wThreads2 > totalThreads) {
mode = 1;
const oldG = gThreads;
wThreads2 = Math.max(Math.floor(totalThreads / 13.5), 1);
gThreads = Math.floor(wThreads2 * 12.5);
batchCount = Math.ceil(oldG / gThreads);
} else mode = 2;
// Big buffer here, since all the previous calculations can take a while. One second should be more than enough.
const wEnd1 = Date.now() + wTime + 1000;
const gEnd = wEnd1 + values.spacer;
const wEnd2 = gEnd + values.spacer;
// "metrics" here is basically a mock Job object. Again, this is just an artifact of repurposed old code.
const metrics = {
batch: "prep",
target: values.target,
type: "none",
time: 0,
end: 0,
port: ns.pid,
log: values.log,
report: false
};
// Actually assigning threads. We actually allow grow threads to be spread out in mode 1.
// This is because we don't mind if the effect is a bit reduced from higher security unlike a normal batcher.
// We're not trying to grow a specific amount, we're trying to grow as much as possible.
for (const block of pRam) {
while (block.ram >= 1.75) {
const bMax = Math.floor(block.ram / 1.75)
let threads = 0;
if (wThreads1 > 0) {
script = "tWeaken.js";
metrics.type = "pWeaken1";
metrics.time = wTime;
metrics.end = wEnd1;
threads = Math.min(wThreads1, bMax);
if (wThreads2 === 0 && wThreads1 - threads <= 0) metrics.report = true;
wThreads1 -= threads;
} else if (wThreads2 > 0) {
script = "tWeaken.js";
metrics.type = "pWeaken2";
metrics.time = wTime;
metrics.end = wEnd2;
threads = Math.min(wThreads2, bMax);
if (wThreads2 - threads === 0) metrics.report = true;
wThreads2 -= threads;
} else if (gThreads > 0 && mode === 1) {
script = "tGrow.js";
metrics.type = "pGrow";
metrics.time = gTime;
metrics.end = gEnd;
threads = Math.min(gThreads, bMax);
metrics.report = false;
gThreads -= threads;
} else if (gThreads > 0 && bMax >= gThreads) {
script = "tGrow.js";
metrics.type = "pGrow";
metrics.time = gTime;
metrics.end = gEnd;
threads = gThreads;
metrics.report = false;
gThreads = 0;
} else break;
metrics.server = block.server;
const pid = ns.exec(script, block.server, { threads: threads, temporary: true }, JSON.stringify(metrics));
if (!pid) throw new Error("Unable to assign all jobs.");
block.ram -= 1.75 * threads;
}
}
// Fancy UI stuff to update you on progress.
const tEnd = ((mode === 0 ? wEnd1 : wEnd2) - Date.now()) * batchCount + Date.now();
const timer = setInterval(() => {
ns.clearLog();
switch (mode) {
case 0:
ns.print(`Weakening security on ${values.target}...`);
break;
case 1:
ns.print(`Maximizing money on ${values.target}...`);
break;
case 2:
ns.print(`Finalizing preparation on ${values.target}...`);
}
ns.print(`Security: +${ns.formatNumber(sec - minSec, 3)}`);
ns.print(`Money: \$${ns.formatNumber(money, 2)}/${ns.formatNumber(maxMoney, 2)}`);
const time = tEnd - Date.now();
ns.print(`Estimated time remaining: ${ns.tFormat(time)}`);
ns.print(`~${batchCount} ${(batchCount === 1) ? "batch" : "batches"}.`);
}, 200);
ns.atExit(() => clearInterval(timer));
// Wait for the last weaken to finish.
do await dataPort.nextWrite(); while (!dataPort.read().startsWith("pWeaken"));
clearInterval(timer);
await ns.sleep(100);
money = ns.getServerMoneyAvailable(values.target);
sec = ns.getServerSecurityLevel(values.target);
}
return true;
}