pull existing scripts from bitburner
This commit is contained in:
parent
ad0171f878
commit
d851d84c05
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,166 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const moneyLowPercent = 0.75
|
||||
const securityHighOffset = 5
|
||||
|
||||
const moneyFormat = new Intl.NumberFormat({ style: "currency" })
|
||||
|
||||
const pwnMethods = []
|
||||
if (ns.fileExists("BruteSSH.exe")) {
|
||||
pwnMethods.push(ns.brutessh)
|
||||
}
|
||||
if (ns.fileExists("FTPCrack.exe")) {
|
||||
pwnMethods.push(ns.ftpcrack)
|
||||
}
|
||||
if (ns.fileExists("HTTPWorm.exe")) {
|
||||
pwnMethods.push(ns.httpworm)
|
||||
}
|
||||
if (ns.fileExists("relaySMTP.exe")) {
|
||||
pwnMethods.push(ns.relaysmtp)
|
||||
}
|
||||
if (ns.fileExists("SQLInject.exe")) {
|
||||
pwnMethods.push(ns.sqlinject)
|
||||
}
|
||||
|
||||
// ns.clearLog()
|
||||
// ns.tail()
|
||||
// ns.disableLog("getServerMaxRam")
|
||||
// ns.disableLog("scp")
|
||||
// ns.disableLog("killall")
|
||||
ns.print(`${pwnMethods.length} pwning method(s) available!`)
|
||||
|
||||
class Server {
|
||||
constructor(hostname) {
|
||||
this.hostname = hostname
|
||||
this.maxMoney = ns.getServerMaxMoney(hostname)
|
||||
this.minSecurityLevel = ns.getServerMinSecurityLevel(hostname)
|
||||
this.maxRam = ns.getServerMaxRam(hostname)
|
||||
this.requiredHackLevel = ns.getServerRequiredHackingLevel(hostname)
|
||||
this.hasRootAccess = ns.hasRootAccess(hostname)
|
||||
this.numPortRequired = ns.getServerNumPortsRequired(hostname)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.hostname
|
||||
}
|
||||
|
||||
get usedRam() {
|
||||
return ns.getServerUsedRam(this.hostname)
|
||||
}
|
||||
|
||||
get availRam() {
|
||||
return this.maxRam - this.usedRam
|
||||
}
|
||||
|
||||
get availMoney() {
|
||||
return ns.getServerMoneyAvailable(this.hostname)
|
||||
}
|
||||
|
||||
get currSecurityLevel() {
|
||||
return ns.getServerSecurityLevel(this.hostname)
|
||||
}
|
||||
|
||||
get weakenTime() {
|
||||
return ns.getWeakenTime(this.hostname)
|
||||
}
|
||||
|
||||
get hackTime() {
|
||||
return ns.getHackTime(this.hostname)
|
||||
}
|
||||
|
||||
get growTime() {
|
||||
return ns.getGrowTime(this.hostname)
|
||||
}
|
||||
|
||||
get score() {
|
||||
if (this.requiredHackLevel > ns.getHackingLevel() || this.maxMoney == 0) {
|
||||
return Infinity
|
||||
}
|
||||
else {
|
||||
const percentMinSecurityLevel = (this.minSecurityLevel / 100)
|
||||
const targetRequiredHackLevel = ns.getHackingLevel() * 0.33
|
||||
const percentLevelDeviation = Math.abs((this.requiredHackLevel - targetRequiredHackLevel) / targetRequiredHackLevel)
|
||||
// return Math.round(this.maxMoney * percentMinSecurityLevel * percentDeviation)
|
||||
return percentMinSecurityLevel + percentLevelDeviation
|
||||
}
|
||||
}
|
||||
|
||||
computeScriptCapacity(script) {
|
||||
return Math.floor(this.availRam / ns.getScriptRam(script))
|
||||
}
|
||||
|
||||
pwn() {
|
||||
if (nodeNumPortsRequired <= pwnMethods.length) {
|
||||
ns.tprint(`pwning ${this.hostname}`)
|
||||
for (let i = 0; i < this.numPortRequired; i++) {
|
||||
pwnMethods[i](this.hostname)
|
||||
}
|
||||
ns.nuke(this.hostname)
|
||||
return true
|
||||
}
|
||||
else {
|
||||
ns.print(`missing requirements to pwn ${this.hostname}. (${pwnMethods.length} < ${this.numPortRequired})`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
exploit(target) {
|
||||
const moneyLowThresh = target.maxMoney * moneyLowPercent
|
||||
const securityHighThresh = target.minSecurityLevel + securityHighOffset
|
||||
|
||||
ns.print(`setup ${this}`)
|
||||
const capacity = this.computeScriptCapacity("exploit.js")
|
||||
if (capacity > 0) {
|
||||
ns.scp("exploit.js", this.hostname)
|
||||
ns.killall(this.hostname)
|
||||
ns.exec("exploit.js", this.hostname, capacity, target.hostname, moneyLowThresh, securityHighThresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively list all available worker nodes
|
||||
*/
|
||||
const getAvailableNodes = (hostnameToAnalyze = "home", availableNodes = []) => {
|
||||
for (const parentHostname of ns.scan(hostnameToAnalyze)) {
|
||||
const parentServer = new Server(parentHostname)
|
||||
// check if we alreay have added this node to the list
|
||||
if (availableNodes.map((n) => n.toString()).includes(parentServer.toString())) {
|
||||
continue //skip this node
|
||||
}
|
||||
// if we are not root on the server yet
|
||||
if (!parentServer.hasRootAccess) {
|
||||
// gain access
|
||||
if (!parentServer.pwn()) {
|
||||
// failed, skip this node
|
||||
}
|
||||
}
|
||||
ns.print(`adding ${parentServer} to worker list`)
|
||||
availableNodes.push(parentServer)
|
||||
availableNodes = getAvailableNodes(parentServer.hostname, availableNodes)
|
||||
}
|
||||
return availableNodes
|
||||
}
|
||||
|
||||
const printNodeTab = (nodes) => {
|
||||
const format = "%-15ss %10s %10s %10s %10s %10s %10s"
|
||||
ns.tprintf(format, "host", "score", "maxMoney", "minSecurity", "root?", "hackLevel", "maxRam")
|
||||
nodes.forEach((n) => {
|
||||
ns.tprintf(format, n.hostname, ns.formatNumber(n.score), ns.formatNumber(n.maxMoney), ns.formatNumber(n.minSecurityLevel), n.hasRootAccess, n.requiredHackLevel, ns.formatRam(n.maxRam))
|
||||
})
|
||||
ns.tprint(`total nodes: ${ns.formatNumber(nodes.length)}`)
|
||||
ns.tprint(`total RAM: ${ns.formatRam(nodes.reduce((x, y) => x + y.maxRam, 0))}`)
|
||||
}
|
||||
|
||||
// find optimal target
|
||||
const availableNodes = getAvailableNodes()
|
||||
availableNodes.sort((a, b) => a.score - b.score)
|
||||
const target = availableNodes.at(0)
|
||||
|
||||
// print status
|
||||
printNodeTab(availableNodes)
|
||||
ns.tprint(`targeting ${target}`)
|
||||
|
||||
//execute
|
||||
availableNodes.forEach((n) => { n.exploit(target) })
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const moneyLowPercent = 0.75
|
||||
const securityHighOffset = 5
|
||||
|
||||
const moneyFormat = new Intl.NumberFormat({ style: "currency" })
|
||||
|
||||
const pwnMethods = []
|
||||
if (ns.fileExists("BruteSSH.exe")) {
|
||||
pwnMethods.push(ns.brutessh)
|
||||
}
|
||||
if (ns.fileExists("FTPCrack.exe")) {
|
||||
pwnMethods.push(ns.ftpcrack)
|
||||
}
|
||||
if (ns.fileExists("HTTPWorm.exe")) {
|
||||
pwnMethods.push(ns.httpworm)
|
||||
}
|
||||
if (ns.fileExists("relaySMTP.exe")) {
|
||||
pwnMethods.push(ns.relaysmtp)
|
||||
}
|
||||
if (ns.fileExists("SQLInject.exe")) {
|
||||
pwnMethods.push(ns.sqlinject)
|
||||
}
|
||||
|
||||
// ns.clearLog()
|
||||
// ns.tail()
|
||||
// ns.disableLog("getServerMaxRam")
|
||||
// ns.disableLog("scp")
|
||||
// ns.disableLog("killall")
|
||||
ns.print(`${pwnMethods.length} pwning method(s) available!`)
|
||||
|
||||
class Server {
|
||||
constructor(hostname) {
|
||||
this.hostname = hostname
|
||||
this.maxMoney = ns.getServerMaxMoney(hostname)
|
||||
this.minSecurityLevel = ns.getServerMinSecurityLevel(hostname)
|
||||
this.maxRam = ns.getServerMaxRam(hostname)
|
||||
this.requiredHackLevel = ns.getServerRequiredHackingLevel(hostname)
|
||||
this.hasRootAccess = ns.hasRootAccess(hostname)
|
||||
this.numPortRequired = ns.getServerNumPortsRequired(hostname)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.hostname
|
||||
}
|
||||
|
||||
get usedRam() {
|
||||
return ns.getServerUsedRam(this.hostname)
|
||||
}
|
||||
|
||||
get availRam() {
|
||||
return this.maxRam - this.usedRam
|
||||
}
|
||||
|
||||
get availMoney() {
|
||||
return ns.getServerMoneyAvailable(this.hostname)
|
||||
}
|
||||
|
||||
get currSecurityLevel() {
|
||||
return ns.getServerSecurityLevel(this.hostname)
|
||||
}
|
||||
|
||||
get weakenTime() {
|
||||
return ns.getWeakenTime(this.hostname)
|
||||
}
|
||||
|
||||
get hackTime() {
|
||||
return ns.getHackTime(this.hostname)
|
||||
}
|
||||
|
||||
get growTime() {
|
||||
return ns.getGrowTime(this.hostname)
|
||||
}
|
||||
|
||||
get score() {
|
||||
if (this.requiredHackLevel > ns.getHackingLevel() || this.maxMoney == 0) {
|
||||
return Infinity
|
||||
}
|
||||
else {
|
||||
const percentMinSecurityLevel = (this.minSecurityLevel / 100)
|
||||
const targetRequiredHackLevel = ns.getHackingLevel() * 0.33
|
||||
const percentLevelDeviation = Math.abs((this.requiredHackLevel - targetRequiredHackLevel) / targetRequiredHackLevel)
|
||||
// return Math.round(this.maxMoney * percentMinSecurityLevel * percentDeviation)
|
||||
return percentMinSecurityLevel + percentLevelDeviation
|
||||
}
|
||||
}
|
||||
|
||||
computeScriptCapacity(script) {
|
||||
return Math.floor(this.maxRam / ns.getScriptRam(script))
|
||||
}
|
||||
|
||||
pwn() {
|
||||
if (nodeNumPortsRequired <= pwnMethods.length) {
|
||||
ns.tprint(`pwning ${this.hostname}`)
|
||||
for (let i = 0; i < this.numPortRequired; i++) {
|
||||
pwnMethods[i](this.hostname)
|
||||
}
|
||||
ns.nuke(this.hostname)
|
||||
return true
|
||||
}
|
||||
else {
|
||||
ns.print(`missing requirements to pwn ${this.hostname}. (${pwnMethods.length} < ${this.numPortRequired})`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
exploit(target) {
|
||||
const moneyLowThresh = target.maxMoney * moneyLowPercent
|
||||
const securityHighThresh = target.minSecurityLevel + securityHighOffset
|
||||
|
||||
ns.print(`setup ${this}`)
|
||||
const capacity = this.computeScriptCapacity("exploit.js")
|
||||
if (capacity > 0) {
|
||||
ns.scp("exploit.js", this.hostname)
|
||||
ns.killall(this.hostname)
|
||||
ns.exec("exploit.js", this.hostname, capacity, target.hostname, moneyLowThresh, securityHighThresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively list all available worker nodes
|
||||
*/
|
||||
const getAvailableNodes = (hostnameToAnalyze = "home", availableNodes = []) => {
|
||||
for (const parentHostname of ns.scan(hostnameToAnalyze)) {
|
||||
const parentServer = new Server(parentHostname)
|
||||
// check if we alreay have added this node to the list
|
||||
if (availableNodes.map((n) => n.toString()).includes(parentServer.toString())) {
|
||||
continue //skip this node
|
||||
}
|
||||
// if we are not root on the server yet
|
||||
if (!parentServer.hasRootAccess) {
|
||||
// gain access
|
||||
if (!parentServer.pwn()) {
|
||||
// failed, skip this node
|
||||
}
|
||||
}
|
||||
ns.print(`adding ${parentServer} to worker list`)
|
||||
availableNodes.push(parentServer)
|
||||
availableNodes = getAvailableNodes(parentServer.hostname, availableNodes)
|
||||
}
|
||||
return availableNodes
|
||||
}
|
||||
|
||||
const printNodeTab = (nodes) => {
|
||||
const format = "%-15ss %10s %10s %10s %10s %10s %10s"
|
||||
ns.tprintf(format, "host", "score", "maxMoney", "minSecurity", "root?", "hackLevel", "maxRam")
|
||||
nodes.forEach((n) => {
|
||||
ns.tprintf(format, n.hostname, ns.formatNumber(n.score), ns.formatNumber(n.maxMoney), ns.formatNumber(n.minSecurityLevel), n.hasRootAccess, n.requiredHackLevel, ns.formatRam(n.maxRam))
|
||||
})
|
||||
ns.tprint(`total nodes: ${ns.formatNumber(nodes.length)}`)
|
||||
ns.tprint(`total RAM: ${ns.formatRam(nodes.reduce((x, y) => x + y.maxRam, 0))}`)
|
||||
}
|
||||
|
||||
// find optimal target
|
||||
const availableNodes = getAvailableNodes()
|
||||
availableNodes.sort((a, b) => a.score - b.score)
|
||||
const target = availableNodes.at(0)
|
||||
|
||||
// print status
|
||||
printNodeTab(availableNodes)
|
||||
ns.tprint(`targeting ${target}`)
|
||||
|
||||
//execute
|
||||
availableNodes.forEach((n) => { n.exploit(target) })
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const target = ns.args[0]
|
||||
const moneyLowThresh = ns.args[1]
|
||||
const securityHighThresh = ns.args[2]
|
||||
|
||||
while (true) {
|
||||
if (ns.getServerSecurityLevel(target) > securityHighThresh) {
|
||||
await ns.weaken(target)
|
||||
}
|
||||
else if (ns.getServerMoneyAvailable(target) < moneyLowThresh) {
|
||||
await ns.grow(target)
|
||||
}
|
||||
else {
|
||||
await ns.hack(target)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { getServerTree } from "/lib/ServerTree.js"
|
||||
|
||||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const serverTree = getServerTree(ns)
|
||||
|
||||
// 1st pass: recursively find servers to backdoor
|
||||
const getTreeOfServerToBackdoor = (subtree) => {
|
||||
const childrenToBackdoor = []
|
||||
for (const child of subtree["children"]) {
|
||||
const backdoorSubtreeOfChild = getTreeOfServerToBackdoor(child)
|
||||
if (backdoorSubtreeOfChild != null) {
|
||||
childrenToBackdoor.push(backdoorSubtreeOfChild)
|
||||
}
|
||||
}
|
||||
if (childrenToBackdoor.length != 0 || (!subtree.info.backdoorInstalled && subtree.info.hasAdminRights && subtree.info.requiredHackingSkill <= ns.getHackingLevel())) {
|
||||
return {
|
||||
"info": subtree.info,
|
||||
"children": childrenToBackdoor,
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 2nd pass: walk the tree and backdoor servers
|
||||
const backdoor = async (subtree) => {
|
||||
if (!subtree.info.backdoorInstalled) {
|
||||
ns.tprintf("backdoor %s, do not change server", subtree.info.hostname)
|
||||
await ns.singularity.installBackdoor()
|
||||
ns.tprint("backdoor complete")
|
||||
}
|
||||
for (const child of subtree.children) {
|
||||
// connect to child
|
||||
if (!ns.singularity.connect(child.info.hostname)) {
|
||||
ns.tprintf("failed to connect: %s", child.info.hostname)
|
||||
ns.exit()
|
||||
}
|
||||
// backdoor the child
|
||||
await backdoor(child)
|
||||
// connect back to current host
|
||||
if (!ns.singularity.connect(subtree.info.hostname)) {
|
||||
ns.tprintf("failed to connect: %s", subtree.info.hostname)
|
||||
ns.exit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// execute
|
||||
const serverTreeToBackdoor = getTreeOfServerToBackdoor(serverTree)
|
||||
if (serverTreeToBackdoor == null) {
|
||||
ns.tprint("nothing to do")
|
||||
}
|
||||
else {
|
||||
await backdoor(serverTreeToBackdoor)
|
||||
}
|
||||
ns.tprint("script complete")
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const maxServerCount = ns.getPurchasedServerLimit()
|
||||
let serverCount = ns.getPurchasedServers().length
|
||||
|
||||
ns.tprint(`we have ${serverCount} servers`)
|
||||
ns.tprint(`we can have ${maxServerCount} servers`)
|
||||
|
||||
if (serverCount < maxServerCount) {
|
||||
// create missing servers
|
||||
const requestedRAM = 4
|
||||
const serverCost = ns.getPurchasedServerCost(requestedRAM)
|
||||
ns.tprint(`server cost is ${ns.formatNumber(serverCost)}`)
|
||||
|
||||
while (serverCount < maxServerCount) {
|
||||
const serverName = `worker-${serverCount}`
|
||||
if (ns.getServerMoneyAvailable("home") >= serverCost) {
|
||||
ns.tprint(`racking server ${serverName}`)
|
||||
ns.purchaseServer(serverName, requestedRAM)
|
||||
serverCount++
|
||||
}
|
||||
else {
|
||||
await ns.sleep(60000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ns.tprint("done! spawning auto-upgrade")
|
||||
ns.spawn("auto/upgrade-workers.js")
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
|
||||
const programs = [
|
||||
"BruteSSH.exe",
|
||||
"FTPCrack.exe",
|
||||
"relaySMTP.exe",
|
||||
"HTTPWorm.exe",
|
||||
"SQLInject.exe",
|
||||
"ServerProfiler.exe",
|
||||
"DeepscanV1.exe",
|
||||
"DeepscanV2.exe",
|
||||
"AutoLink.exe",
|
||||
"Formulas.exe",
|
||||
]
|
||||
|
||||
// attempt to buy tor every second
|
||||
while (!ns.singularity.purchaseTor()) {
|
||||
await ns.sleep(1000)
|
||||
}
|
||||
|
||||
// build a list to track which program we have in our possession
|
||||
let programsPurchased = new Array(programs.length)
|
||||
for (let i = 0; i < programs.length; i++) {
|
||||
programsPurchased[i] = ns.ls("home").includes(programs[i])
|
||||
}
|
||||
|
||||
// attempt to buy the programs every seconds
|
||||
do {
|
||||
await ns.sleep(1000)
|
||||
for (let i = 0; i < programs.length; i++) {
|
||||
if (!programsPurchased[i]) {
|
||||
const purchaseResult = ns.singularity.purchaseProgram(programs[i])
|
||||
if (purchaseResult) {
|
||||
ns.tprintf("auto-tor: purchased %s", programs[i])
|
||||
}
|
||||
else {
|
||||
// purchase the programs in the order listed
|
||||
// if we are unable to buy a program, break and retry later
|
||||
break
|
||||
}
|
||||
programsPurchased[i] = purchaseResult
|
||||
}
|
||||
}
|
||||
} while (!programsPurchased.reduce((x, y) => x && y, true))
|
||||
|
||||
ns.tprint("auto-tor: complete")
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const servers = ns.getPurchasedServers()
|
||||
|
||||
ns.clearLog()
|
||||
ns.tail()
|
||||
ns.disableLog('ALL')
|
||||
|
||||
const getLowestServerRAM = () => {
|
||||
return Math.min.apply(Math, servers.map(ns.getServerMaxRam))
|
||||
}
|
||||
|
||||
while (getLowestServerRAM() < ns.getPurchasedServerMaxRam()) {
|
||||
const requestedRAM = getLowestServerRAM()*2
|
||||
for (const serverName of servers) {
|
||||
const serverRAM = ns.getServerMaxRam(serverName)
|
||||
if (serverRAM < requestedRAM) {
|
||||
const serverCost = ns.getPurchasedServerUpgradeCost(serverName, requestedRAM)
|
||||
ns.print(`selected server ${serverName} to be upgraded (current RAM: ${ns.formatRam(serverRAM)}, upgraded RAM: ${ns.formatRam(requestedRAM)}, upgrade cost: ${ns.formatNumber(serverCost)})`)
|
||||
while (true) {
|
||||
if (ns.getServerMoneyAvailable("home") >= serverCost) {
|
||||
ns.killall(serverName) //TODO: make it more graceful
|
||||
ns.upgradePurchasedServer(serverName, requestedRAM)
|
||||
break
|
||||
}
|
||||
else {
|
||||
await ns.sleep(60000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,668 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const hackMinMoneyLimitPercent = 0.75
|
||||
const hackMaxSecurityIncreaseLimit = 5
|
||||
const hackMinLevelThreshPercent = 1
|
||||
const maxTargets = Infinity
|
||||
const loopSettings = {
|
||||
"poolBlacklist": [],
|
||||
"poolBlacklist": ["home"],
|
||||
|
||||
"moneyTargetsWhitelist": [],
|
||||
// "moneyTargetsWhitelist": ["n00dles", "harakiri-sushi", "phantasy"], // early game list
|
||||
// "moneyTargetsBlacklist": [],
|
||||
"moneyTargetsBlacklist": ["joesguns"], // blacklist joesguns to avoid conflict with exp farm
|
||||
|
||||
"expTargetHostname": "joesguns",
|
||||
"expMaxAvailRamUsePercent": 0.99,
|
||||
}
|
||||
|
||||
const hackRamUsage = ns.getScriptRam("/payloads/hack.js")
|
||||
const growRamUsage = ns.getScriptRam("/payloads/grow.js")
|
||||
const weakenRamUsage = ns.getScriptRam("/payloads/weaken.js")
|
||||
const autoRackRamUsage = ns.getScriptRam("/payloads/autorack.js")
|
||||
const autoRackPwnUsage = ns.getScriptRam("/payloads/autopwn.js")
|
||||
|
||||
const moneyFormat = new Intl.NumberFormat({ style: "currency" })
|
||||
|
||||
ns.clearLog()
|
||||
ns.tail()
|
||||
ns.disableLog('ALL')
|
||||
// ns.disableLog('getServerUsedRam')
|
||||
// ns.disableLog('getServerMaxRam')
|
||||
// ns.print(`${pwnMethods.length} pwning method(s) available!`)
|
||||
|
||||
class PoolException {
|
||||
constructor(message) {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `WARNING: ${this.message}`
|
||||
}
|
||||
}
|
||||
|
||||
class Server {
|
||||
constructor(hostname) {
|
||||
this.hostname = hostname
|
||||
ns.scp(["/payloads/grow.js", "/payloads/hack.js", "/payloads/weaken.js", "/payloads/autorack.js", "/payloads/autopwn.js"], this.hostname)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.hostname
|
||||
}
|
||||
|
||||
get hasRootAccess() {
|
||||
return ns.hasRootAccess(this.hostname)
|
||||
}
|
||||
|
||||
get hackable() {
|
||||
return this.hasRootAccess && this.maxMoney > 0 && (this.requiredHackLevel <= ns.getHackingLevel() * hackMinLevelThreshPercent || this.requiredHackLevel == 1)
|
||||
}
|
||||
|
||||
get requiredHackLevel() {
|
||||
return ns.getServerRequiredHackingLevel(this.hostname)
|
||||
}
|
||||
|
||||
get maxRam() {
|
||||
return ns.getServerMaxRam(this.hostname)
|
||||
}
|
||||
|
||||
get usedRam() {
|
||||
return ns.getServerUsedRam(this.hostname)
|
||||
}
|
||||
|
||||
get availRam() {
|
||||
return this.maxRam - this.usedRam
|
||||
}
|
||||
|
||||
get maxMoney() {
|
||||
return ns.getServerMaxMoney(this.hostname)
|
||||
}
|
||||
|
||||
get availMoney() {
|
||||
return ns.getServerMoneyAvailable(this.hostname)
|
||||
}
|
||||
|
||||
get minSecurityLevel() {
|
||||
return ns.getServerMinSecurityLevel(this.hostname)
|
||||
}
|
||||
|
||||
get currSecurityLevel() {
|
||||
return ns.getServerSecurityLevel(this.hostname)
|
||||
}
|
||||
|
||||
get hackTime() {
|
||||
return Math.ceil(ns.getHackTime(this.hostname))
|
||||
}
|
||||
|
||||
get growTime() {
|
||||
return Math.ceil(ns.getGrowTime(this.hostname))
|
||||
}
|
||||
|
||||
get weakenTime() {
|
||||
return Math.ceil(ns.getWeakenTime(this.hostname))
|
||||
}
|
||||
|
||||
get hackChance() {
|
||||
return ns.hackAnalyzeChance(this.hostname)
|
||||
}
|
||||
|
||||
get pids() {
|
||||
return ns.ps(this.hostname).map(process => process.pid)
|
||||
}
|
||||
|
||||
getScriptCapacity(script) {
|
||||
return Math.floor(this.availRam / ns.getScriptRam(script))
|
||||
}
|
||||
|
||||
getHackCapacity() {
|
||||
return Math.floor(this.availRam / hackRamUsage)
|
||||
}
|
||||
|
||||
getGrowCapacity() {
|
||||
return Math.floor(this.availRam / growRamUsage)
|
||||
}
|
||||
|
||||
getWeakenCapacity() {
|
||||
return Math.floor(this.availRam / weakenRamUsage)
|
||||
}
|
||||
|
||||
computeOptimalHackThreadCount() {
|
||||
const requiredThreads = Math.ceil(((this.availMoney - (this.maxMoney * hackMinMoneyLimitPercent)) / this.availMoney) / ns.hackAnalyze(this.hostname))
|
||||
let securityLimitedThreads = 0
|
||||
let predictedSecurityIncrease = 0
|
||||
while ((predictedSecurityIncrease + this.currSecurityLevel) < (this.minSecurityLevel + hackMaxSecurityIncreaseLimit)) {
|
||||
predictedSecurityIncrease = ns.hackAnalyzeSecurity(++securityLimitedThreads)
|
||||
}
|
||||
return Math.min(requiredThreads, securityLimitedThreads)
|
||||
}
|
||||
|
||||
computeOptimalGrowThreadCount() {
|
||||
const requiredThreads = Math.ceil(ns.growthAnalyze(this.hostname, this.maxMoney / this.availMoney))
|
||||
// let securityLimitedThreads = 0
|
||||
// let predictedSecurityIncrease = 0
|
||||
// while ((predictedSecurityIncrease + this.currSecurityLevel) < (this.minSecurityLevel + hackMaxSecurityIncreaseLimit)) {
|
||||
// predictedSecurityIncrease = ns.growthAnalyzeSecurity(++securityLimitedThreads)
|
||||
// }
|
||||
// return Math.min(requiredThreads, securityLimitedThreads)
|
||||
return requiredThreads
|
||||
}
|
||||
|
||||
computeOptimalWeakenThreadCount() {
|
||||
let requiredThreads = 0
|
||||
let prediction = this.currSecurityLevel
|
||||
while (prediction > this.minSecurityLevel) {
|
||||
prediction = this.currSecurityLevel - ns.weakenAnalyze(++requiredThreads)
|
||||
}
|
||||
return requiredThreads
|
||||
}
|
||||
|
||||
execHack(target, numThreads = 1) {
|
||||
const pid = ns.exec("/payloads/hack.js", this.hostname, numThreads, crypto.randomUUID(), numThreads, target.hostname)
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run hack() from ${this.hostname}, target = ${target}, numThreads = ${numThreads}, capacity = ${this.getHackCapacity()}`
|
||||
}
|
||||
return new Task(this, pid, "hack", Date.now() + target.hackTime, target)
|
||||
}
|
||||
|
||||
execGrow(target, numThreads = 1) {
|
||||
const pid = ns.exec("/payloads/grow.js", this.hostname, numThreads, crypto.randomUUID(), numThreads, target.hostname)
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run grow() from ${this.hostname}, target = ${target}, numThreads = ${numThreads}, capacity = ${this.getGrowCapacity()}`
|
||||
}
|
||||
return new Task(this, pid, "grow", Date.now() + target.growTime, target)
|
||||
}
|
||||
|
||||
execWeaken(target, numThreads = 1) {
|
||||
const pid = ns.exec("/payloads/weaken.js", this.hostname, numThreads, crypto.randomUUID(), numThreads, target.hostname)
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run weaken() from ${this.hostname}, target = ${target}, numThreads = ${numThreads}, capacity = ${this.getHackCapacity()}`
|
||||
}
|
||||
return new Task(this, pid, "weaken", Date.now() + target.weakenTime, target)
|
||||
}
|
||||
|
||||
execAutoRack() {
|
||||
const pid = ns.exec("/payloads/autorack.js", this.hostname, 1, crypto.randomUUID())
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run autorack on ${this.hostname}, availRAM = ${this.availRam}`
|
||||
}
|
||||
return new Task(this, pid, "autorack", Date.now())
|
||||
}
|
||||
|
||||
execAutoPwn() {
|
||||
const pid = ns.exec("/payloads/autopwn.js", this.hostname, 1, crypto.randomUUID())
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run autopwn on ${this.hostname}, availRAM = ${this.availRam}`
|
||||
}
|
||||
return new Task(this, pid, "autorack", Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
class ServerPool {
|
||||
constructor() {
|
||||
this.pool = []
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.pool.length
|
||||
}
|
||||
|
||||
get maxRam() {
|
||||
return this.pool.reduce((x, y) => x + y.maxRam, 0)
|
||||
}
|
||||
|
||||
get usedRam() {
|
||||
return this.pool.reduce((x, y) => x + y.usedRam, 0)
|
||||
}
|
||||
|
||||
get availRam() {
|
||||
return this.pool.reduce((x, y) => x + y.availRam, 0)
|
||||
}
|
||||
|
||||
getHackCapacity() {
|
||||
return this.pool.reduce((x, y) => x + y.getHackCapacity(), 0)
|
||||
}
|
||||
|
||||
getGrowCapacity() {
|
||||
return this.pool.reduce((x, y) => x + y.getGrowCapacity(), 0)
|
||||
}
|
||||
|
||||
getWeakenCapacity() {
|
||||
return this.pool.reduce((x, y) => x + y.getWeakenCapacity(), 0)
|
||||
}
|
||||
|
||||
killAll() {
|
||||
this.pool.forEach(server => ns.killall(server.hostname))
|
||||
}
|
||||
|
||||
execHack(target, numThreads) {
|
||||
let tasks = []
|
||||
|
||||
if (numThreads == 0) {
|
||||
throw new PoolException(`attempted to schedule task with threads = 0 (operation = hack, target = ${target})`)
|
||||
}
|
||||
|
||||
for (const server of this.pool) {
|
||||
const capacity = server.getHackCapacity()
|
||||
if (capacity == 0) {
|
||||
// this server is full, so we skip it
|
||||
continue
|
||||
}
|
||||
const newThreads = numThreads > capacity ? capacity : numThreads
|
||||
const task = server.execHack(target, newThreads)
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
// reduce the number of remaining threads by the number of thread started by the new task
|
||||
numThreads -= newThreads
|
||||
if (numThreads <= 0) {
|
||||
// all threads scheduled, we can stop assigning them now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.length == 0) {
|
||||
throw new PoolException(`attempted to schedule task, put pool has no capacity (operation = hack, target = ${target}, threads = ${numThreads})`)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execGrow(target, numThreads) {
|
||||
let tasks = []
|
||||
|
||||
if (numThreads == 0) {
|
||||
throw new PoolException(`attempted to schedule task with threads = 0 (operation = grow, target = ${target})`)
|
||||
}
|
||||
|
||||
for (const server of this.pool) {
|
||||
const capacity = server.getGrowCapacity()
|
||||
if (capacity == 0) {
|
||||
// this server is full, so we skip it
|
||||
continue
|
||||
}
|
||||
const newThreads = numThreads > capacity ? capacity : numThreads
|
||||
const task = server.execGrow(target, newThreads)
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
// reduce the number of remaining threads by the number of thread started by the new task
|
||||
numThreads -= newThreads
|
||||
if (numThreads <= 0) {
|
||||
// all threads scheduled, we can stop assigning them now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.length == 0) {
|
||||
throw new PoolException(`attempted to schedule task, put pool has no capacity (task = grow, target = ${target}, threads = ${numThreads})`)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execWeaken(target, numThreads) {
|
||||
let tasks = []
|
||||
|
||||
if (numThreads == 0) {
|
||||
throw new PoolException(`attempted to schedule task with threads = 0 (tasks = weaken, target = ${target} `)
|
||||
}
|
||||
|
||||
for (const server of this.pool) {
|
||||
const capacity = server.getWeakenCapacity()
|
||||
if (capacity == 0) {
|
||||
// this server is full, so we skip it
|
||||
continue
|
||||
}
|
||||
const newThreads = numThreads > capacity ? capacity : numThreads
|
||||
const task = server.execWeaken(target, newThreads)
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
// reduce the number of remaining threads by the number of thread started by the new task
|
||||
numThreads -= newThreads
|
||||
if (numThreads <= 0) {
|
||||
// all threads scheduled, we can stop assigning them now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.length == 0) {
|
||||
throw new PoolException(`attempted to schedule task, put pool has no capacity (task = weaken, target = ${target}, threads = ${numThreads})`)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execAutoRack() {
|
||||
// TODO: skip if fully upgraded
|
||||
let tasks = []
|
||||
for (const server of this.pool) {
|
||||
if (server.availRam >= autoRackRamUsage) {
|
||||
const task = server.execAutoRack()
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execAutoPwn() {
|
||||
// TODO: skip if fully upgraded
|
||||
let tasks = []
|
||||
for (const server of this.pool) {
|
||||
if (server.availRam >= autoRackPwnUsage) {
|
||||
const task = server.execAutoPwn()
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execOptimalHack(target, factor = 1) {
|
||||
const numThreads = Math.min(this.getHackCapacity(), target.computeOptimalHackThreadCount() * factor)
|
||||
ns.print(`INFO: hacking ${target} with ${numThreads} threads`)
|
||||
return this.execHack(target, numThreads)
|
||||
}
|
||||
|
||||
execOptimalGrow(target, factor = 1) {
|
||||
const numThreads = Math.min(this.getGrowCapacity(), target.computeOptimalGrowThreadCount() * factor)
|
||||
ns.print(`INFO: growing ${target} with ${numThreads} threads`)
|
||||
return this.execGrow(target, numThreads)
|
||||
}
|
||||
|
||||
execOptimalWeaken(target, factor = 1) {
|
||||
const numThreads = Math.min(this.getWeakenCapacity(), target.computeOptimalWeakenThreadCount() * factor)
|
||||
ns.print(`INFO: weakening ${target} with ${numThreads} threads`)
|
||||
return this.execWeaken(target, numThreads)
|
||||
}
|
||||
|
||||
refresh() {
|
||||
// recursively list all available servers on network
|
||||
const getAvailableServers = (hostnameToAnalyze = null, availableServers = []) => {
|
||||
for (const parentHostname of ns.scan(hostnameToAnalyze)) {
|
||||
const parentServer = new Server(parentHostname)
|
||||
// check if we alreay have added this server to the list
|
||||
if (!availableServers.map((n) => n.toString()).includes(parentServer.toString())) {
|
||||
// add to the list
|
||||
availableServers.push(parentServer)
|
||||
// add parent servers to the list
|
||||
availableServers = getAvailableServers(parentServer.hostname, availableServers)
|
||||
}
|
||||
}
|
||||
return availableServers
|
||||
}
|
||||
|
||||
const availableServers = getAvailableServers()
|
||||
ns.print(`INFO: found ${availableServers.length} servers in network`)
|
||||
|
||||
let serversToAdd = []
|
||||
availableServers
|
||||
.filter(server => server.hasRootAccess && server.maxRam > 0) // is able to run tasks
|
||||
.filter(server => !this.pool.find(poolServer => poolServer.hostname == server.hostname)) // we don't already have this server in the pool
|
||||
.filter(server => !loopSettings.poolBlacklist.find(blacklistedHostname => blacklistedHostname == server.hostname)) // hostname is blacklisted
|
||||
.forEach(server => serversToAdd.push(server))
|
||||
this.pool = this.pool.concat(serversToAdd)
|
||||
ns.print(`INFO: added ${serversToAdd.length} servers to pool`)
|
||||
|
||||
return serversToAdd
|
||||
}
|
||||
|
||||
prepare(target) {
|
||||
let tasks = []
|
||||
if (target.availMoney != target.maxMoney) {
|
||||
// schedule as many grow() as able or necessary
|
||||
const growThreads = Math.min(this.getGrowCapacity(), Math.ceil(ns.growthAnalyze(target.hostname, target.maxMoney / target.availMoney)))
|
||||
tasks = tasks.concat(this.execGrow(target, growThreads))
|
||||
// try to offset the security growth with some parallel weaken() as able
|
||||
const weakenThreads = Math.min(this.getWeakenCapacity(), Math.ceil(((target.currSecurityLevel - target.minSecurityLevel) + (growThreads * 0.004)) / 0.04)) // 0.05 is more optimal, but t doesn't leave a lot of margin
|
||||
if (weakenThreads > 0) {
|
||||
// it's okay to skip weaken() if we have no capacity, we will do it next cycle
|
||||
tasks = tasks.concat(this.execWeaken(target, weakenThreads))
|
||||
}
|
||||
}
|
||||
return new Batch(tasks, target)
|
||||
}
|
||||
|
||||
attack(target) {
|
||||
if (target.availMoney != target.maxMoney) {
|
||||
// if money is not at maximum, grow()
|
||||
return this.execOptimalGrow(target)
|
||||
}
|
||||
else if (target.currSecurityLevel != target.minSecurityLevel) {
|
||||
// if security is not at minimum, weaken()
|
||||
return this.execOptimalWeaken(target)
|
||||
}
|
||||
else {
|
||||
// if money is at maximum and security is at minimum, hack()
|
||||
return this.execOptimalHack(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Task {
|
||||
constructor(server, pid, operation, completionTime, target = null) {
|
||||
this.server = server
|
||||
this.pid = pid
|
||||
this.operation = operation
|
||||
this.completionTime = completionTime
|
||||
this.target = target
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.server.pids.includes(this.pid)
|
||||
}
|
||||
|
||||
getTimeUntilComplete() {
|
||||
return Math.max(0, this.completionTime - Date.now())
|
||||
}
|
||||
|
||||
async sleepUntilCompleted() {
|
||||
while (this.isRunning()) {
|
||||
const sleepTime = this.getTimeUntilComplete() + 200
|
||||
ns.print(`INFO: sleeping ${sleepTime} milliseconds for pid ${this.pid} on server ${this.server} to complete (op = ${this.operation}, target = ${this.target})`)
|
||||
await ns.sleep(sleepTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Batch {
|
||||
constructor(tasks, target = null) {
|
||||
this.tasks = tasks
|
||||
this.target = target
|
||||
}
|
||||
|
||||
get completionTime() {
|
||||
return Math.max(...this.tasks.map(t => t.completionTime))
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.tasks.reduce((r, t) => r || t.isRunning(), false)
|
||||
}
|
||||
|
||||
getTimeUntilComplete() {
|
||||
return Math.max(...this.tasks.map(t => t.getTimeUntilComplete()))
|
||||
}
|
||||
|
||||
async sleepUntilCompleted() {
|
||||
while (this.isRunning()) {
|
||||
const sleepTime = this.getTimeUntilComplete() + 200
|
||||
ns.print(`INFO: sleeping ${sleepTime} milliseconds for batch to complete (tasks = [${this.tasks.map(t => t.operation).join(",")}], target = ${this.target})`)
|
||||
await ns.sleep(sleepTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ns.exec("map.js", "home")
|
||||
|
||||
const loop = async () => {
|
||||
const pool = new ServerPool()
|
||||
let tasks = []
|
||||
let preparingTargets = []
|
||||
let targets = []
|
||||
let lastRefreshLevel = ns.getHackingLevel()
|
||||
let refresh = true
|
||||
let reScore = false
|
||||
|
||||
const expFarmTarget = new Server(loopSettings.expTargetHostname)
|
||||
expFarmTarget.targetOf = 0
|
||||
|
||||
ns.atExit(() => pool.killAll())
|
||||
|
||||
// run autopwn once on startup to enable new servers
|
||||
ns.exec("/payloads/autopwn.js", "home")
|
||||
await ns.sleep(500)
|
||||
|
||||
while (true) {
|
||||
// every 10 level-up refresh the list of targets and recalculate the target priorities
|
||||
if (ns.getHackingLevel() - lastRefreshLevel >= 10) {
|
||||
ns.print(`INFO: triggering auto-refresh of targets`)
|
||||
refresh = true
|
||||
reScore = true
|
||||
lastRefreshLevel = ns.getHackingLevel()
|
||||
}
|
||||
|
||||
// refresh targets
|
||||
if (refresh) {
|
||||
pool.refresh()
|
||||
tasks.concat(pool.execAutoPwn())
|
||||
pool.pool
|
||||
.filter(target => !preparingTargets.includes(target))
|
||||
.filter(target => !targets.includes(target))
|
||||
.filter(target => target.hackable) // server is hackable
|
||||
.filter(target => loopSettings.moneyTargetsWhitelist.length != 0 ? loopSettings.moneyTargetsWhitelist.includes(target.hostname) : true)
|
||||
.filter(target => !loopSettings.moneyTargetsBlacklist.includes(target.hostname))
|
||||
.forEach(target => {
|
||||
target.targetOf = 0
|
||||
ns.print(`INFO: added ${target} as potential target`)
|
||||
preparingTargets.push(target)
|
||||
})
|
||||
refresh = false
|
||||
await ns.sleep(500)
|
||||
}
|
||||
|
||||
// prepare targets
|
||||
// TODO: this is currently very expensive because we need to place all target in ideal conditions before computing the score
|
||||
// in the future, calculate score and prepare only relevant targets
|
||||
let tmpPreparingTargets = [] // to avoid concurrent modification
|
||||
for (const target of preparingTargets) {
|
||||
try {
|
||||
if (target.targetOf == 0) {
|
||||
const prepareBatch = pool.prepare(target)
|
||||
if (prepareBatch.tasks.length == 0) {
|
||||
// target is ready to go
|
||||
// target.targetData.operation = "ready"
|
||||
targets.push(target)
|
||||
reScore = true // trigger a reScore
|
||||
}
|
||||
else {
|
||||
// track the prepare batch
|
||||
tasks.push(prepareBatch)
|
||||
target.targetOf += 1
|
||||
tmpPreparingTargets.push(target)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we can't schedule more tasks, but keep track of the target
|
||||
tmpPreparingTargets.push(target)
|
||||
}
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof PoolException) {
|
||||
ns.print(e.toString())
|
||||
tmpPreparingTargets.push(target) // TODO: this is very jank
|
||||
if (e.fatal) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
preparingTargets = tmpPreparingTargets
|
||||
|
||||
// score targets
|
||||
if (reScore) {
|
||||
ns.print(`INFO: sorting ${targets.length} targets`)
|
||||
const score = (target) => target.maxMoney * target.hackChance / target.growTime
|
||||
targets.sort((t1, t2) => score(t2) - score(t1))
|
||||
reScore = false
|
||||
}
|
||||
|
||||
// extract money
|
||||
if (targets.length != 0) {
|
||||
for (let i = 0; i < Math.min(maxTargets, targets.length); i++) {
|
||||
try {
|
||||
const target = targets[i]
|
||||
if (target.targetOf == 0) {
|
||||
// ns.print(`hacking ${target}`)
|
||||
// ns.print(`maxMoney: ${target.maxMoney}`)
|
||||
// ns.print(`availMoney: ${target.availMoney}`)
|
||||
// ns.print(`minSecurity: ${target.minSecurityLevel}`)
|
||||
// ns.print(`currSecurity: ${target.currSecurityLevel}`)
|
||||
const newTasks = pool.attack(target)
|
||||
tasks = tasks.concat(newTasks)
|
||||
target.targetOf += newTasks.length
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof PoolException) {
|
||||
ns.print(e.toString())
|
||||
}
|
||||
else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// farm exp
|
||||
if (expFarmTarget.targetOf == 0 && loopSettings.expMaxAvailRamUsePercent != 0) {
|
||||
// we want to avoid filling up the ram to not starve other tasks
|
||||
const usableRam = pool.availRam * loopSettings.expMaxAvailRamUsePercent
|
||||
let newTasks = []
|
||||
try {
|
||||
if (expFarmTarget.currSecurityLevel > expFarmTarget.minSecurityLevel) {
|
||||
// minimize security to speed up hack()
|
||||
newTasks = pool.execWeaken(expFarmTarget, Math.floor(usableRam / weakenRamUsage))
|
||||
}
|
||||
else {
|
||||
// hack() nonstop
|
||||
newTasks = pool.execHack(expFarmTarget, Math.floor(usableRam / hackRamUsage))
|
||||
}
|
||||
expFarmTarget.targetOf += newTasks.length
|
||||
tasks = tasks.concat(newTasks)
|
||||
}
|
||||
catch (e) {
|
||||
// ns.print(e.toString())
|
||||
if (!e instanceof PoolException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sleep until new tick
|
||||
// ns.print(tasks)
|
||||
// ns.print(targets)
|
||||
// ns.print(preparingTargets)
|
||||
ns.print(`pool = ${pool.size}, RAM = ${ns.formatRam(pool.usedRam)}/${ns.formatRam(pool.maxRam)} (${ns.formatPercent(pool.usedRam / pool.maxRam)}), targets = ${targets.length}/${targets.length + preparingTargets.length}, tasks = ${tasks.length}, refreshCountDown = ${10 - (ns.getHackingLevel() - lastRefreshLevel)}`)
|
||||
if (tasks.length > 0) {
|
||||
tasks.sort((t1, t2) => t1.completionTime - t2.completionTime)
|
||||
await tasks[0].sleepUntilCompleted()
|
||||
while (tasks.length != 0 && !tasks[0].isRunning()) {
|
||||
const task = tasks.shift()
|
||||
task.target.targetOf--
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "idle"
|
||||
}
|
||||
}
|
||||
}
|
||||
await loop()
|
||||
}
|
|
@ -0,0 +1,680 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const hackMinMoneyLimitPercent = 0.75
|
||||
const hackMaxSecurityIncreaseLimit = 5
|
||||
const hackMinLevelThreshPercent = 1
|
||||
const maxTargets = Infinity
|
||||
const loopSettings = {
|
||||
"poolBlacklist": [],
|
||||
// "poolBlacklist": ["home"],
|
||||
|
||||
"moneyTargetsWhitelist": [],
|
||||
"moneyTargetsWhitelist": ["n00dles", "harakiri-sushi", "phantasy"],
|
||||
// "moneyTargetsBlacklist": [],
|
||||
"moneyTargetsBlacklist": ["joesguns"], // blacklist joesguns to avoid conflict with exp farm
|
||||
|
||||
"expTargetHostname": "joesguns",
|
||||
"expMaxAvailRamUsePercent": 0.95,
|
||||
}
|
||||
|
||||
const hackRamUsage = ns.getScriptRam("/payloads/hack.js")
|
||||
const growRamUsage = ns.getScriptRam("/payloads/grow.js")
|
||||
const weakenRamUsage = ns.getScriptRam("/payloads/weaken.js")
|
||||
const autoRackRamUsage = ns.getScriptRam("/payloads/autorack.js")
|
||||
const autoRackPwnUsage = ns.getScriptRam("/payloads/autopwn.js")
|
||||
|
||||
const moneyFormat = new Intl.NumberFormat({ style: "currency" })
|
||||
|
||||
ns.clearLog()
|
||||
ns.tail()
|
||||
ns.disableLog('ALL')
|
||||
// ns.disableLog('getServerUsedRam')
|
||||
// ns.disableLog('getServerMaxRam')
|
||||
// ns.print(`${pwnMethods.length} pwning method(s) available!`)
|
||||
|
||||
class PoolException {
|
||||
constructor(message) {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `WARNING: ${this.message}`
|
||||
}
|
||||
}
|
||||
|
||||
class Server {
|
||||
constructor(hostname) {
|
||||
this.hostname = hostname
|
||||
ns.scp(["/payloads/grow.js", "/payloads/hack.js", "/payloads/weaken.js", "/payloads/autorack.js", "/payloads/autopwn.js"], this.hostname)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.hostname
|
||||
}
|
||||
|
||||
get hasRootAccess() {
|
||||
return ns.hasRootAccess(this.hostname)
|
||||
}
|
||||
|
||||
get hackable() {
|
||||
return this.hasRootAccess && this.maxMoney > 0 && (this.requiredHackLevel <= ns.getHackingLevel() * hackMinLevelThreshPercent || this.requiredHackLevel == 1)
|
||||
}
|
||||
|
||||
get requiredHackLevel() {
|
||||
return ns.getServerRequiredHackingLevel(this.hostname)
|
||||
}
|
||||
|
||||
get maxRam() {
|
||||
return ns.getServerMaxRam(this.hostname)
|
||||
}
|
||||
|
||||
get usedRam() {
|
||||
return ns.getServerUsedRam(this.hostname)
|
||||
}
|
||||
|
||||
get availRam() {
|
||||
return this.maxRam - this.usedRam
|
||||
}
|
||||
|
||||
get maxMoney() {
|
||||
return ns.getServerMaxMoney(this.hostname)
|
||||
}
|
||||
|
||||
get availMoney() {
|
||||
return ns.getServerMoneyAvailable(this.hostname)
|
||||
}
|
||||
|
||||
get minSecurityLevel() {
|
||||
return ns.getServerMinSecurityLevel(this.hostname)
|
||||
}
|
||||
|
||||
get currSecurityLevel() {
|
||||
return ns.getServerSecurityLevel(this.hostname)
|
||||
}
|
||||
|
||||
get hackTime() {
|
||||
return Math.ceil(ns.getHackTime(this.hostname))
|
||||
}
|
||||
|
||||
get growTime() {
|
||||
return Math.ceil(ns.getGrowTime(this.hostname))
|
||||
}
|
||||
|
||||
get weakenTime() {
|
||||
return Math.ceil(ns.getWeakenTime(this.hostname))
|
||||
}
|
||||
|
||||
get hackChance() {
|
||||
return ns.hackAnalyzeChance(this.hostname)
|
||||
}
|
||||
|
||||
get pids() {
|
||||
return ns.ps(this.hostname).map(process => process.pid)
|
||||
}
|
||||
|
||||
getScriptCapacity(script) {
|
||||
return Math.floor(this.availRam / ns.getScriptRam(script))
|
||||
}
|
||||
|
||||
getHackCapacity() {
|
||||
return Math.floor(this.availRam / hackRamUsage)
|
||||
}
|
||||
|
||||
getGrowCapacity() {
|
||||
return Math.floor(this.availRam / growRamUsage)
|
||||
}
|
||||
|
||||
getWeakenCapacity() {
|
||||
return Math.floor(this.availRam / weakenRamUsage)
|
||||
}
|
||||
|
||||
computeOptimalHackThreadCount() {
|
||||
const requiredThreads = Math.ceil(((this.availMoney - (this.maxMoney * hackMinMoneyLimitPercent)) / this.availMoney) / ns.hackAnalyze(this.hostname))
|
||||
let securityLimitedThreads = 0
|
||||
let predictedSecurityIncrease = 0
|
||||
while ((predictedSecurityIncrease + this.currSecurityLevel) < (this.minSecurityLevel + hackMaxSecurityIncreaseLimit)) {
|
||||
predictedSecurityIncrease = ns.hackAnalyzeSecurity(++securityLimitedThreads)
|
||||
}
|
||||
return Math.min(requiredThreads, securityLimitedThreads)
|
||||
}
|
||||
|
||||
computeOptimalGrowThreadCount() {
|
||||
const requiredThreads = Math.ceil(ns.growthAnalyze(this.hostname, this.maxMoney / this.availMoney))
|
||||
// let securityLimitedThreads = 0
|
||||
// let predictedSecurityIncrease = 0
|
||||
// while ((predictedSecurityIncrease + this.currSecurityLevel) < (this.minSecurityLevel + hackMaxSecurityIncreaseLimit)) {
|
||||
// predictedSecurityIncrease = ns.growthAnalyzeSecurity(++securityLimitedThreads)
|
||||
// }
|
||||
// return Math.min(requiredThreads, securityLimitedThreads)
|
||||
return requiredThreads
|
||||
}
|
||||
|
||||
computeOptimalWeakenThreadCount() {
|
||||
let requiredThreads = 0
|
||||
let prediction = this.currSecurityLevel
|
||||
while (prediction > this.minSecurityLevel) {
|
||||
prediction = this.currSecurityLevel - ns.weakenAnalyze(++requiredThreads)
|
||||
}
|
||||
return requiredThreads
|
||||
}
|
||||
|
||||
execHack(target, numThreads = 1) {
|
||||
const pid = ns.exec("/payloads/hack.js", this.hostname, numThreads, crypto.randomUUID(), numThreads, target.hostname)
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run hack() from ${this.hostname}, target = ${target}, numThreads = ${numThreads}, capacity = ${this.getHackCapacity()}`
|
||||
}
|
||||
return new Task(this, pid, "hack", Date.now() + target.hackTime, target)
|
||||
}
|
||||
|
||||
execGrow(target, numThreads = 1) {
|
||||
const pid = ns.exec("/payloads/grow.js", this.hostname, numThreads, crypto.randomUUID(), numThreads, target.hostname)
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run grow() from ${this.hostname}, target = ${target}, numThreads = ${numThreads}, capacity = ${this.getGrowCapacity()}`
|
||||
}
|
||||
return new Task(this, pid, "grow", Date.now() + target.growTime, target)
|
||||
}
|
||||
|
||||
execWeaken(target, numThreads = 1) {
|
||||
const pid = ns.exec("/payloads/weaken.js", this.hostname, numThreads, crypto.randomUUID(), numThreads, target.hostname)
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run weaken() from ${this.hostname}, target = ${target}, numThreads = ${numThreads}, capacity = ${this.getHackCapacity()}`
|
||||
}
|
||||
return new Task(this, pid, "weaken", Date.now() + target.weakenTime, target)
|
||||
}
|
||||
|
||||
execAutoRack() {
|
||||
const pid = ns.exec("/payloads/autorack.js", this.hostname, 1, crypto.randomUUID())
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run autorack on ${this.hostname}, availRAM = ${this.availRam}`
|
||||
}
|
||||
return new Task(this, pid, "autorack", Date.now())
|
||||
}
|
||||
|
||||
execAutoPwn() {
|
||||
const pid = ns.exec("/payloads/autopwn.js", this.hostname, 1, crypto.randomUUID())
|
||||
if (pid == 0) {
|
||||
throw `ERROR: failed to run autopwn on ${this.hostname}, availRAM = ${this.availRam}`
|
||||
}
|
||||
return new Task(this, pid, "autorack", Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
class ServerPool {
|
||||
constructor() {
|
||||
this.pool = []
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.pool.length
|
||||
}
|
||||
|
||||
get maxRam() {
|
||||
return this.pool.reduce((x, y) => x + y.maxRam, 0)
|
||||
}
|
||||
|
||||
get usedRam() {
|
||||
return this.pool.reduce((x, y) => x + y.usedRam, 0)
|
||||
}
|
||||
|
||||
get availRam() {
|
||||
return this.pool.reduce((x, y) => x + y.availRam, 0)
|
||||
}
|
||||
|
||||
getHackCapacity() {
|
||||
return this.pool.reduce((x, y) => x + y.getHackCapacity(), 0)
|
||||
}
|
||||
|
||||
getGrowCapacity() {
|
||||
return this.pool.reduce((x, y) => x + y.getGrowCapacity(), 0)
|
||||
}
|
||||
|
||||
getWeakenCapacity() {
|
||||
return this.pool.reduce((x, y) => x + y.getWeakenCapacity(), 0)
|
||||
}
|
||||
|
||||
killAll() {
|
||||
this.pool.forEach(server => ns.killall(server.hostname))
|
||||
}
|
||||
|
||||
execHack(target, numThreads, split=true) {
|
||||
let tasks = []
|
||||
|
||||
if (numThreads == 0) {
|
||||
throw new PoolException(`attempted to schedule task with threads = 0 (operation = hack, target = ${target})`)
|
||||
}
|
||||
|
||||
for (const server of this.pool) {
|
||||
const capacity = server.getHackCapacity()
|
||||
if (capacity == 0) {
|
||||
// this server is full, so we skip it
|
||||
continue
|
||||
}
|
||||
else if(!split && capacity < numThreads) {
|
||||
// we don't want to split into multiple threads and this server doesn't have enough capacity, so we skip it
|
||||
continue
|
||||
}
|
||||
const newThreads = numThreads > capacity ? capacity : numThreads
|
||||
const task = server.execHack(target, newThreads)
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
// reduce the number of remaining threads by the number of thread started by the new task
|
||||
numThreads -= newThreads
|
||||
if (numThreads <= 0) {
|
||||
// all threads scheduled, we can stop assigning them now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.length == 0) {
|
||||
throw new PoolException(`attempted to schedule task, put pool has no capacity (operation = hack, target = ${target}, threads = ${numThreads})`)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execGrow(target, numThreads, split=true) {
|
||||
let tasks = []
|
||||
|
||||
if (numThreads == 0) {
|
||||
throw new PoolException(`attempted to schedule task with threads = 0 (operation = grow, target = ${target})`)
|
||||
}
|
||||
|
||||
for (const server of this.pool) {
|
||||
const capacity = server.getGrowCapacity()
|
||||
if (capacity == 0) {
|
||||
// this server is full, so we skip it
|
||||
continue
|
||||
}
|
||||
else if(!split && capacity < numThreads) {
|
||||
// we don't want to split into multiple threads and this server doesn't have enough capacity, so we skip it
|
||||
continue
|
||||
}
|
||||
const newThreads = numThreads > capacity ? capacity : numThreads
|
||||
const task = server.execGrow(target, newThreads)
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
// reduce the number of remaining threads by the number of thread started by the new task
|
||||
numThreads -= newThreads
|
||||
if (numThreads <= 0) {
|
||||
// all threads scheduled, we can stop assigning them now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.length == 0) {
|
||||
throw new PoolException(`attempted to schedule task, put pool has no capacity (task = grow, target = ${target}, threads = ${numThreads})`)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execWeaken(target, numThreads, split=true) {
|
||||
let tasks = []
|
||||
|
||||
if (numThreads == 0) {
|
||||
throw new PoolException(`attempted to schedule task with threads = 0 (tasks = weaken, target = ${target} `)
|
||||
}
|
||||
|
||||
for (const server of this.pool) {
|
||||
const capacity = server.getWeakenCapacity()
|
||||
if (capacity == 0) {
|
||||
// this server is full, so we skip it
|
||||
continue
|
||||
}
|
||||
else if(!split && capacity < numThreads) {
|
||||
// we don't want to split into multiple threads and this server doesn't have enough capacity, so we skip it
|
||||
continue
|
||||
}
|
||||
const newThreads = numThreads > capacity ? capacity : numThreads
|
||||
const task = server.execWeaken(target, newThreads)
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
// reduce the number of remaining threads by the number of thread started by the new task
|
||||
numThreads -= newThreads
|
||||
if (numThreads <= 0) {
|
||||
// all threads scheduled, we can stop assigning them now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tasks.length == 0) {
|
||||
throw new PoolException(`attempted to schedule task, put pool has no capacity (task = weaken, target = ${target}, threads = ${numThreads})`)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execAutoRack() {
|
||||
// TODO: skip if fully upgraded
|
||||
let tasks = []
|
||||
for (const server of this.pool) {
|
||||
if (server.availRam >= autoRackRamUsage) {
|
||||
const task = server.execAutoRack()
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execAutoPwn() {
|
||||
// TODO: skip if fully upgraded
|
||||
let tasks = []
|
||||
for (const server of this.pool) {
|
||||
if (server.availRam >= autoRackPwnUsage) {
|
||||
const task = server.execAutoPwn()
|
||||
if (task.pid != 0) {
|
||||
tasks.push(task)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
execOptimalHack(target, factor = 1) {
|
||||
const numThreads = Math.min(this.getHackCapacity(), target.computeOptimalHackThreadCount() * factor)
|
||||
ns.print(`INFO: hacking ${target} with ${numThreads} threads`)
|
||||
return this.execHack(target, numThreads)
|
||||
}
|
||||
|
||||
execOptimalGrow(target, factor = 1) {
|
||||
const numThreads = Math.min(this.getGrowCapacity(), target.computeOptimalGrowThreadCount() * factor)
|
||||
ns.print(`INFO: growing ${target} with ${numThreads} threads`)
|
||||
return this.execGrow(target, numThreads)
|
||||
}
|
||||
|
||||
execOptimalWeaken(target, factor = 1) {
|
||||
const numThreads = Math.min(this.getWeakenCapacity(), target.computeOptimalWeakenThreadCount() * factor)
|
||||
ns.print(`INFO: weakening ${target} with ${numThreads} threads`)
|
||||
return this.execWeaken(target, numThreads)
|
||||
}
|
||||
|
||||
refresh() {
|
||||
// recursively list all available servers on network
|
||||
const getAvailableServers = (hostnameToAnalyze = null, availableServers = []) => {
|
||||
for (const parentHostname of ns.scan(hostnameToAnalyze)) {
|
||||
const parentServer = new Server(parentHostname)
|
||||
// check if we alreay have added this server to the list
|
||||
if (!availableServers.map((n) => n.toString()).includes(parentServer.toString())) {
|
||||
// add to the list
|
||||
availableServers.push(parentServer)
|
||||
// add parent servers to the list
|
||||
availableServers = getAvailableServers(parentServer.hostname, availableServers)
|
||||
}
|
||||
}
|
||||
return availableServers
|
||||
}
|
||||
|
||||
const availableServers = getAvailableServers()
|
||||
ns.print(`INFO: found ${availableServers.length} servers in network`)
|
||||
|
||||
let serversToAdd = []
|
||||
availableServers
|
||||
.filter(server => server.hasRootAccess && server.maxRam > 0) // is able to run tasks
|
||||
.filter(server => !this.pool.find(poolServer => poolServer.hostname == server.hostname)) // we don't already have this server in the pool
|
||||
.filter(server => !loopSettings.poolBlacklist.find(blacklistedHostname => blacklistedHostname == server.hostname)) // hostname is blacklisted
|
||||
.forEach(server => serversToAdd.push(server))
|
||||
this.pool = this.pool.concat(serversToAdd)
|
||||
ns.print(`INFO: added ${serversToAdd.length} servers to pool`)
|
||||
|
||||
return serversToAdd
|
||||
}
|
||||
|
||||
prepare(target) {
|
||||
let tasks = []
|
||||
if (target.availMoney != target.maxMoney) {
|
||||
// schedule as many grow() as able or necessary
|
||||
const growThreads = Math.min(this.getGrowCapacity(), Math.ceil(ns.growthAnalyze(target.hostname, target.maxMoney / target.availMoney)))
|
||||
tasks = tasks.concat(this.execGrow(target, growThreads, true))
|
||||
// try to offset the security growth with some parallel weaken() as able
|
||||
const weakenThreads = Math.min(this.getWeakenCapacity(), Math.ceil(((target.currSecurityLevel - target.minSecurityLevel) + (growThreads * 0.004)) / 0.04)) // 0.05 is more optimal, but t doesn't leave a lot of margin
|
||||
if (weakenThreads > 0) {
|
||||
// it's okay to skip weaken() if we have no capacity, we will do it next cycle
|
||||
tasks = tasks.concat(this.execWeaken(target, weakenThreads, true))
|
||||
}
|
||||
}
|
||||
return new Batch(tasks, target)
|
||||
}
|
||||
|
||||
attack(target) {
|
||||
if (target.availMoney != target.maxMoney) {
|
||||
// if money is not at maximum, grow()
|
||||
return this.execOptimalGrow(target)
|
||||
}
|
||||
else if (target.currSecurityLevel != target.minSecurityLevel) {
|
||||
// if security is not at minimum, weaken()
|
||||
return this.execOptimalWeaken(target)
|
||||
}
|
||||
else {
|
||||
// if money is at maximum and security is at minimum, hack()
|
||||
return this.execOptimalHack(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Task {
|
||||
constructor(server, pid, operation, completionTime, target = null) {
|
||||
this.server = server
|
||||
this.pid = pid
|
||||
this.operation = operation
|
||||
this.completionTime = completionTime
|
||||
this.target = target
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.server.pids.includes(this.pid)
|
||||
}
|
||||
|
||||
getTimeUntilComplete() {
|
||||
return Math.max(0, this.completionTime - Date.now())
|
||||
}
|
||||
|
||||
async sleepUntilCompleted() {
|
||||
while (this.isRunning()) {
|
||||
const sleepTime = this.getTimeUntilComplete() + 200
|
||||
ns.print(`INFO: sleeping ${sleepTime} milliseconds for pid ${this.pid} on server ${this.server} to complete (op = ${this.operation}, target = ${this.target})`)
|
||||
await ns.sleep(sleepTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Batch {
|
||||
constructor(tasks, target = null) {
|
||||
this.tasks = tasks
|
||||
this.target = target
|
||||
}
|
||||
|
||||
get completionTime() {
|
||||
return Math.max(...this.tasks.map(t => t.completionTime))
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.tasks.reduce((r, t) => r || t.isRunning(), false)
|
||||
}
|
||||
|
||||
getTimeUntilComplete() {
|
||||
return Math.max(...this.tasks.map(t => t.getTimeUntilComplete()))
|
||||
}
|
||||
|
||||
async sleepUntilCompleted() {
|
||||
while (this.isRunning()) {
|
||||
const sleepTime = this.getTimeUntilComplete() + 200
|
||||
ns.print(`INFO: sleeping ${sleepTime} milliseconds for batch to complete (tasks = [${this.tasks.map(t => t.operation).join(",")}], target = ${this.target})`)
|
||||
await ns.sleep(sleepTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ns.exec("map.js", "home")
|
||||
|
||||
const loop = async () => {
|
||||
const pool = new ServerPool()
|
||||
let tasks = []
|
||||
let preparingTargets = []
|
||||
let targets = []
|
||||
let lastRefreshLevel = ns.getHackingLevel()
|
||||
let refresh = true
|
||||
let reScore = false
|
||||
|
||||
const expFarmTarget = new Server(loopSettings.expTargetHostname)
|
||||
expFarmTarget.targetOf = 0
|
||||
|
||||
ns.atExit(() => pool.killAll())
|
||||
|
||||
// run autopwn once on startup to enable new servers
|
||||
ns.exec("/payloads/autopwn.js", "home")
|
||||
await ns.sleep(500)
|
||||
|
||||
while (true) {
|
||||
// every 10 level-up refresh the list of targets and recalculate the target priorities
|
||||
if (ns.getHackingLevel() - lastRefreshLevel >= 10) {
|
||||
ns.print(`INFO: triggering auto-refresh of targets`)
|
||||
refresh = true
|
||||
reScore = true
|
||||
lastRefreshLevel = ns.getHackingLevel()
|
||||
}
|
||||
|
||||
// refresh targets
|
||||
if (refresh) {
|
||||
pool.refresh()
|
||||
tasks.concat(pool.execAutoPwn())
|
||||
pool.pool
|
||||
.filter(target => !preparingTargets.includes(target))
|
||||
.filter(target => !targets.includes(target))
|
||||
.filter(target => target.hackable) // server is hackable
|
||||
.filter(target => loopSettings.moneyTargetsWhitelist.length != 0 ? loopSettings.moneyTargetsWhitelist.includes(target.hostname) : true)
|
||||
.filter(target => !loopSettings.moneyTargetsBlacklist.includes(target.hostname))
|
||||
.forEach(target => {
|
||||
target.targetOf = 0
|
||||
ns.print(`INFO: added ${target} as potential target`)
|
||||
preparingTargets.push(target)
|
||||
})
|
||||
refresh = false
|
||||
await ns.sleep(500)
|
||||
}
|
||||
|
||||
// prepare targets
|
||||
// TODO: this is currently very expensive because we need to place all target in ideal conditions before computing the score
|
||||
// in the future, calculate score and prepare only relevant targets
|
||||
let tmpPreparingTargets = [] // to avoid concurrent modification
|
||||
for (const target of preparingTargets) {
|
||||
try {
|
||||
if (target.targetOf == 0) {
|
||||
const prepareBatch = pool.prepare(target)
|
||||
if (prepareBatch.tasks.length == 0) {
|
||||
// target is ready to go
|
||||
// target.targetData.operation = "ready"
|
||||
targets.push(target)
|
||||
reScore = true // trigger a reScore
|
||||
}
|
||||
else {
|
||||
// track the prepare batch
|
||||
tasks.push(prepareBatch)
|
||||
target.targetOf += 1
|
||||
tmpPreparingTargets.push(target)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we can't schedule more tasks, but keep track of the target
|
||||
tmpPreparingTargets.push(target)
|
||||
}
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof PoolException) {
|
||||
ns.print(e.toString())
|
||||
tmpPreparingTargets.push(target) // TODO: this is very jank
|
||||
if (e.fatal) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
preparingTargets = tmpPreparingTargets
|
||||
|
||||
// score targets
|
||||
if (reScore) {
|
||||
ns.print(`INFO: sorting ${targets.length} targets`)
|
||||
const score = (target) => target.maxMoney * target.hackChance / target.growTime
|
||||
targets.sort((t1, t2) => score(t2) - score(t1))
|
||||
reScore = false
|
||||
}
|
||||
|
||||
// extract money
|
||||
if (targets.length != 0) {
|
||||
for (let i = 0; i < Math.min(maxTargets, targets.length); i++) {
|
||||
try {
|
||||
const target = targets[i]
|
||||
if (target.targetOf == 0) {
|
||||
// ns.print(`hacking ${target}`)
|
||||
// ns.print(`maxMoney: ${target.maxMoney}`)
|
||||
// ns.print(`availMoney: ${target.availMoney}`)
|
||||
// ns.print(`minSecurity: ${target.minSecurityLevel}`)
|
||||
// ns.print(`currSecurity: ${target.currSecurityLevel}`)
|
||||
const newTasks = pool.attack(target)
|
||||
tasks = tasks.concat(newTasks)
|
||||
target.targetOf += newTasks.length
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof PoolException) {
|
||||
ns.print(e.toString())
|
||||
}
|
||||
else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// farm exp
|
||||
if (expFarmTarget.targetOf == 0 && loopSettings.expMaxAvailRamUsePercent != 0) {
|
||||
// we want to avoid filling up the ram to not starve other tasks
|
||||
const usableRam = pool.availRam * loopSettings.expMaxAvailRamUsePercent
|
||||
let newTasks = []
|
||||
try {
|
||||
if (expFarmTarget.currSecurityLevel > expFarmTarget.minSecurityLevel) {
|
||||
// minimize security to speed up hack()
|
||||
newTasks = pool.execWeaken(expFarmTarget, Math.floor(usableRam / weakenRamUsage))
|
||||
}
|
||||
else {
|
||||
// hack() nonstop
|
||||
newTasks = pool.execHack(expFarmTarget, Math.floor(usableRam / hackRamUsage))
|
||||
}
|
||||
expFarmTarget.targetOf += newTasks.length
|
||||
tasks = tasks.concat(newTasks)
|
||||
}
|
||||
catch (e) {
|
||||
// ns.print(e.toString())
|
||||
if (!e instanceof PoolException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sleep until new tick
|
||||
// ns.print(tasks)
|
||||
// ns.print(targets)
|
||||
// ns.print(preparingTargets)
|
||||
ns.print(`pool = ${pool.size}, RAM = ${ns.formatRam(pool.usedRam)}/${ns.formatRam(pool.maxRam)} (${ns.formatPercent(pool.usedRam / pool.maxRam)}), targets = ${targets.length}/${targets.length + preparingTargets.length}, tasks = ${tasks.length}, refreshCountDown = ${10 - (ns.getHackingLevel() - lastRefreshLevel)}`)
|
||||
if (tasks.length > 0) {
|
||||
tasks.sort((t1, t2) => t1.completionTime - t2.completionTime)
|
||||
await tasks[0].sleepUntilCompleted()
|
||||
while (tasks.length != 0 && !tasks[0].isRunning()) {
|
||||
const task = tasks.shift()
|
||||
task.target.targetOf--
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "idle"
|
||||
}
|
||||
}
|
||||
}
|
||||
await loop()
|
||||
}
|
|
@ -0,0 +1,411 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
|
||||
ns.clearLog()
|
||||
ns.tail()
|
||||
ns.disableLog('ALL')
|
||||
|
||||
const getServers = (hostnameToAnalyze = "home", servers = ["home"]) => {
|
||||
for (const hostname of ns.scan(hostnameToAnalyze)) {
|
||||
// check if we alreay have added this server to the list
|
||||
if (!servers.includes(hostname)) {
|
||||
// add to the list
|
||||
servers.push(hostname)
|
||||
// map the host
|
||||
servers = getServers(hostname, servers)
|
||||
}
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
const solvers = new Map([
|
||||
["Find Largest Prime Factor", (data) => {
|
||||
const factorize = (n) => {
|
||||
let results = [];
|
||||
for (let i = 2; i <= n; i++) {
|
||||
// check if divisible
|
||||
if (n % i == 0) {
|
||||
// divide the number to test
|
||||
n = n / i;
|
||||
// store the divisor
|
||||
results.push(i);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return factorize(data).pop()
|
||||
}],
|
||||
["Subarray with Maximum Sum", (data) => {
|
||||
let result = 0
|
||||
// loop 1: walk starting index
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let sum = 0
|
||||
// loop 2: sum everything from the stating index
|
||||
for (let j = i; j < data.length; j++) {
|
||||
sum += data[j]
|
||||
// if at any point during computing the sum we find a greater sum
|
||||
if (sum > result) {
|
||||
result = sum
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}],
|
||||
["Total Ways to Sum", (data) => {
|
||||
// This can be solved with using the Partition function P
|
||||
// solution = P(n) - 1
|
||||
// https://en.wikipedia.org/wiki/Partition_function_(number_theory)
|
||||
// https://rosettacode.org/wiki/Partition_function_P
|
||||
const p = n => {
|
||||
var a = new Array(n + 1)
|
||||
a[0] = 1n
|
||||
|
||||
for (let i = 1; i <= n; i++) {
|
||||
a[i] = 0n
|
||||
for (let k = 1, s = 1; s <= i;) {
|
||||
a[i] += (k & 1 ? a[i - s] : -a[i - s])
|
||||
k > 0 ? (s += k, k = -k) : (k = -k + 1, s = k * (3 * k - 1) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
return a[n]
|
||||
}
|
||||
return Number(p(data) - 1n)
|
||||
}],
|
||||
["Total Ways to Sum II", (data) => {
|
||||
// https://www.geeksforgeeks.org/coin-change-dp-7/
|
||||
// https://www.geeksforgeeks.org/understanding-the-coin-change-problem-with-dynamic-programming/
|
||||
const target = data[0]
|
||||
const numberPool = data[1]
|
||||
let memoizationTable = new Array(target + 1).fill(0)
|
||||
|
||||
// there is always 1 way of returning 0 sum; with 0 number
|
||||
memoizationTable[0] = 1
|
||||
|
||||
// loop through available number in the pool
|
||||
for (let i = 0; i < numberPool.length; i++) {
|
||||
// compute each entry in the memoization table
|
||||
for (let j = 0; j < memoizationTable.length; j++) {
|
||||
// ensure we do not lookup negative indices
|
||||
if (numberPool[i] <= j) {
|
||||
// compute the value at this index and store in the table to be referenced next iteration
|
||||
memoizationTable[j] += memoizationTable[j - numberPool[i]]
|
||||
}
|
||||
}
|
||||
}
|
||||
// last entry in the table is our awnser
|
||||
return memoizationTable.pop()
|
||||
}],
|
||||
["Spiralize Matrix", (data) => {
|
||||
const matrix = data
|
||||
|
||||
let resultSize = matrix.length * matrix[0].length
|
||||
let result = []
|
||||
|
||||
let topStop = 1
|
||||
let bottomStop = matrix.length - 1
|
||||
let leftStop = 0
|
||||
let rightStop = matrix[0].length - 1
|
||||
|
||||
let pos = [0, 0]
|
||||
let vector = [1, 0]
|
||||
|
||||
while (result.length < resultSize) {
|
||||
if (vector[0] == 1 && vector[1] == 0) {
|
||||
// going right
|
||||
while (pos[1] <= rightStop) {
|
||||
result.push(matrix[pos[0]][pos[1]])
|
||||
pos[1]++
|
||||
}
|
||||
rightStop--
|
||||
pos[0]++
|
||||
pos[1]--
|
||||
// go down next
|
||||
vector = [0, 1]
|
||||
}
|
||||
else if (vector[0] == -1 && vector[1] == 0) {
|
||||
// going left
|
||||
while (pos[1] >= leftStop) {
|
||||
result.push(matrix[pos[0]][pos[1]])
|
||||
pos[1]--
|
||||
}
|
||||
leftStop++;
|
||||
// go up next
|
||||
pos[0]--
|
||||
pos[1]++
|
||||
vector = [0, -1]
|
||||
}
|
||||
else if (vector[0] == 0 && vector[1] == 1) {
|
||||
// going down
|
||||
while (pos[0] <= bottomStop) {
|
||||
result.push(matrix[pos[0]][pos[1]])
|
||||
pos[0]++
|
||||
}
|
||||
bottomStop--
|
||||
// go right next
|
||||
pos[0]--
|
||||
pos[1]--
|
||||
vector = [-1, 0]
|
||||
}
|
||||
else if (vector[0] == 0 && vector[1] == -1) {
|
||||
// going up
|
||||
while (pos[0] >= topStop) {
|
||||
result.push(matrix[pos[0]][pos[1]])
|
||||
pos[0]--
|
||||
}
|
||||
topStop++
|
||||
// go left next
|
||||
pos[0]++
|
||||
pos[1]++
|
||||
vector = [1, 0]
|
||||
}
|
||||
else {
|
||||
ns.print("Invalid state")
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}],
|
||||
["Array Jumping Game", (data) => {
|
||||
return solvers.get("Array Jumping Game II")(data) == 0 ? 0 : 1
|
||||
}],
|
||||
["Array Jumping Game II", (data) => {
|
||||
let indexJumpSums = [] // contain an array of to index this index can jump
|
||||
let position = 0
|
||||
let moves = [position]
|
||||
let goal = data.length - 1
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
indexJumpSums[i] = data[i] + i
|
||||
}
|
||||
|
||||
do {
|
||||
position = moves[moves.length - 1]
|
||||
const jumpValue = data[position]
|
||||
|
||||
if (goal - position <= jumpValue) { // check if the goal is within reach
|
||||
moves.push(goal) // reached the goal
|
||||
break
|
||||
}
|
||||
else {
|
||||
// find the largest jump possible at our current position
|
||||
let maxJump = position
|
||||
for (let i = position; i <= position + jumpValue; i++) {
|
||||
if (indexJumpSums[i] > indexJumpSums[maxJump]) {
|
||||
maxJump = i // this index allows us to jump the furthest
|
||||
}
|
||||
}
|
||||
if (maxJump == position) { // we haven't moved, we are stuck at this index
|
||||
moves = [0] // impossible!
|
||||
break
|
||||
}
|
||||
moves.push(maxJump)
|
||||
}
|
||||
} while (position != goal)
|
||||
|
||||
return moves.length - 1
|
||||
}],
|
||||
["Merge Overlapping Intervals", (data) => {
|
||||
let result = []
|
||||
let newInterval = []
|
||||
// for this algo to work, we need to sort the input by the min of the intervals
|
||||
data.sort((a, b) => a[0] - b[0])
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
// first interval
|
||||
if (newInterval.length == 0) {
|
||||
newInterval = data[i]
|
||||
}
|
||||
// if the min of the current inverval is less than the max of the newInterval
|
||||
if (data[i][0] <= newInterval[1]) {
|
||||
newInterval[1] = Math.max(data[i][1], newInterval[1]) // set the largest of the max to be the max of the newInterval
|
||||
}
|
||||
else {
|
||||
result.push(newInterval) // we are out of the new interval so push it to the result
|
||||
newInterval = data[i] // start a new newInterval
|
||||
}
|
||||
// last interval
|
||||
if (i == data.length - 1) {
|
||||
newInterval[1] = Math.max(data[i][1], newInterval[1])
|
||||
result.push(newInterval)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}],
|
||||
// ["Generate IP Addresses", (data) => {
|
||||
// }],
|
||||
["HammingCodes: Integer to Encoded Binary", (data) => {
|
||||
const dataBits = data.toString(2).split("").map(x => parseInt(x))
|
||||
|
||||
// compute the number of parity bits
|
||||
let parityBitsCount = 0
|
||||
while ((1 << parityBitsCount) < (dataBits.length + parityBitsCount + 1)) {
|
||||
parityBitsCount++
|
||||
}
|
||||
|
||||
// initialize encodedData and setup the data at the correct indices
|
||||
let encodedData = new Array(dataBits.length + parityBitsCount)
|
||||
let dataIndex = 0
|
||||
for (let i = 0; i < encodedData.length; i++) {
|
||||
// TODO: why this works?
|
||||
if (Math.log2(i + 1) % 1 == 0) { // check if i is power of 2
|
||||
// initialize parity bit
|
||||
encodedData[i] = 0
|
||||
}
|
||||
else {
|
||||
// set data bit
|
||||
encodedData[i] = dataBits[dataIndex++]
|
||||
}
|
||||
}
|
||||
|
||||
// set the parity bits
|
||||
for (let i = 0; i < parityBitsCount; i++) { // loop every parity bits
|
||||
const parityIndex = (1 << i) - 1;
|
||||
let parity = 0;
|
||||
|
||||
// xor together every bits this parity bit covers
|
||||
for (let j = parityIndex; j < encodedData.length; j += (parityIndex + 1) * 2) {
|
||||
for (let k = j; k < j + parityIndex + 1; k++) {
|
||||
parity ^= encodedData[k];
|
||||
}
|
||||
}
|
||||
|
||||
// set parity bit
|
||||
encodedData[parityIndex] = parity;
|
||||
}
|
||||
|
||||
// insert the overall parity bit at index 0
|
||||
encodedData.unshift(encodedData.reduce((x, y) => x ^ y))
|
||||
|
||||
// reduce encodedData to a string of 1s and 0s
|
||||
return encodedData.reduce((x, y) => x + y, "")
|
||||
}],
|
||||
["HammingCodes: Encoded Binary to Integer", (data) => {
|
||||
// https://www.youtube.com/watch?v=b3NxrZOu_CE
|
||||
|
||||
let encodedData = data.split("").map(x => parseInt(x))
|
||||
|
||||
// NOTE: bit at index 0 is the overall parity bit
|
||||
// we will not get a string with 2 errors, so can ignore it
|
||||
|
||||
// check for flipped bit and correct it
|
||||
let errorBitIndex = 0
|
||||
for (let i = 0; i < encodedData.length; i++) {
|
||||
if (encodedData[i] == 1) {
|
||||
errorBitIndex ^= i
|
||||
}
|
||||
}
|
||||
if (errorBitIndex != 0) {
|
||||
encodedData[errorBitIndex] ^= 1
|
||||
}
|
||||
|
||||
// extract data
|
||||
let dataBits = []
|
||||
for (let i = 0; i < encodedData.length; i++) {
|
||||
if ((i & (i - 1)) != 0 && i != 0) { // check if i is not power of 2
|
||||
// extract data bit
|
||||
dataBits.push(encodedData[i])
|
||||
}
|
||||
}
|
||||
|
||||
return parseInt(dataBits.reduce((x, y) => x + y, ""), 2).toString(10)
|
||||
}],
|
||||
["Encryption I: Caesar Cipher", (data) => {
|
||||
const charList = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
|
||||
const plaintext = data[0].split("")
|
||||
const key = data[1]
|
||||
|
||||
return plaintext.map(c => {
|
||||
const i = charList.indexOf(c)
|
||||
if (i >= 0) {
|
||||
return charList[(i + (26 - key)) % 26]
|
||||
}
|
||||
else {
|
||||
return ' '
|
||||
}
|
||||
}).reduce((x, y) => x + y, "")
|
||||
|
||||
}],
|
||||
["Encryption II: Vigenère Cipher", (data) => {
|
||||
const charList = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
|
||||
const cypher = (a, b) => charList[(charList.indexOf(a) + charList.indexOf(b)) % 26]
|
||||
const plaintext = data[0].split("")
|
||||
const key = data[1].split("")
|
||||
|
||||
let anwser = ""
|
||||
let keyIndex = 0
|
||||
for (const char of plaintext) {
|
||||
anwser += cypher(char, key[keyIndex++ % key.length])
|
||||
}
|
||||
return anwser
|
||||
}]
|
||||
])
|
||||
|
||||
// ns.codingcontract.createDummyContract("Find Largest Prime Factor") // ok
|
||||
// ns.codingcontract.createDummyContract("Subarray with Maximum Sum") // ok
|
||||
// ns.codingcontract.createDummyContract("Total Ways to Sum") // ok
|
||||
// ns.codingcontract.createDummyContract("Total Ways to Sum II") // ok
|
||||
// ns.codingcontract.createDummyContract("Spiralize Matrix") // ok
|
||||
// ns.codingcontract.createDummyContract("Array Jumping Game") // ok
|
||||
// ns.codingcontract.createDummyContract("Array Jumping Game II") //ok
|
||||
// ns.codingcontract.createDummyContract("Merge Overlapping Intervals") // ok
|
||||
// ns.codingcontract.createDummyContract("Generate IP Addresses")
|
||||
// ns.codingcontract.createDummyContract("Algorithmic Stock Trader I")
|
||||
// ns.codingcontract.createDummyContract("Algorithmic Stock Trader II")
|
||||
// ns.codingcontract.createDummyContract("Algorithmic Stock Trader III")
|
||||
// ns.codingcontract.createDummyContract("Algorithmic Stock Trader IV")
|
||||
// ns.codingcontract.createDummyContract("Minimum Path Sum in a Triangle")
|
||||
// ns.codingcontract.createDummyContract("Unique Paths in a Grid I")
|
||||
// ns.codingcontract.createDummyContract("Unique Paths in a Grid II")
|
||||
// ns.codingcontract.createDummyContract("Shortest Path in a Grid")
|
||||
// ns.codingcontract.createDummyContract("Sanitize Parentheses in Expression")
|
||||
// ns.codingcontract.createDummyContract("Find All Valid Math Expressions")
|
||||
// ns.codingcontract.createDummyContract("HammingCodes: Integer to Encoded Binary") // ok
|
||||
// ns.codingcontract.createDummyContract("HammingCodes: Encoded Binary to Integer") // ok
|
||||
// ns.codingcontract.createDummyContract("Proper 2-Coloring of a Graph")
|
||||
// ns.codingcontract.createDummyContract("Compression I: RLE Compression")
|
||||
// ns.codingcontract.createDummyContract("Compression II: LZ Decompression")
|
||||
// ns.codingcontract.createDummyContract("Compression III: LZ Compression")
|
||||
// ns.codingcontract.createDummyContract("Encryption I: Caesar Cipher") // ok
|
||||
// ns.codingcontract.createDummyContract("Encryption II: Vigenère Cipher") // ok
|
||||
|
||||
// const server = "home"
|
||||
// for (const contract of ns.ls(server, '.cct')) {
|
||||
// const contractType = ns.codingcontract.getContractType(contract, server)
|
||||
// ns.print(`found contract ${contract} of type "${contractType}" on server ${server}`)
|
||||
// if (solvers.has(contractType)) {
|
||||
// ns.print("attempting to solve")
|
||||
// const anwser = solvers.get(contractType)(ns.codingcontract.getData(contract, server))
|
||||
// const reward = ns.codingcontract.attempt(anwser, contract, server)
|
||||
// if (reward == "") {
|
||||
// ns.print(`ERROR: failed to solve contract! anwser = ${anwser}`)
|
||||
// break
|
||||
// }
|
||||
// else {
|
||||
// ns.print(reward)
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// ns.print(`WARNING: unknown contract type: ${contractType}`)
|
||||
// }
|
||||
// }
|
||||
|
||||
for (const server of getServers()) {
|
||||
for (const contract of ns.ls(server, '.cct')) {
|
||||
const contractType = ns.codingcontract.getContractType(contract, server)
|
||||
ns.print(`found contract ${contract} of type "${contractType}" on server ${server}`)
|
||||
if (solvers.has(contractType)) {
|
||||
const anwser = solvers.get(contractType)(ns.codingcontract.getData(contract, server))
|
||||
const reward = ns.codingcontract.attempt(anwser, contract, server)
|
||||
if (anwser == "") {
|
||||
ns.print(`ERROR: failed to solve contract! anwser = ${anwser}`)
|
||||
break
|
||||
}
|
||||
else {
|
||||
ns.print(reward)
|
||||
}
|
||||
}
|
||||
else {
|
||||
ns.print(`WARNING: unknown contract type: ${contractType}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,186 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
ns.clearLog()
|
||||
ns.tail()
|
||||
ns.disableLog('ALL')
|
||||
|
||||
const MAKE_MONEY = "Human Trafficking"
|
||||
const VIGILANTE_JUSTICE = "Vigilante Justice"
|
||||
const TERRORISM = "Terrorism"
|
||||
const TRAIN_COMBAT = "Train Combat"
|
||||
const TERRITORY_WARFARE = "Territory Warfare"
|
||||
const maxMemberCount = 12
|
||||
const memberNamePool = [
|
||||
"CJ",
|
||||
"Big Smoke",
|
||||
"Sweet",
|
||||
"Ryder",
|
||||
"OG Loc",
|
||||
"Big Bear",
|
||||
"Emmet",
|
||||
"Big Devil",
|
||||
"Little Devil",
|
||||
"Tony",
|
||||
"LB",
|
||||
"Madd Dogg",
|
||||
"Little Weasel",
|
||||
]
|
||||
|
||||
function estimateMemberPower(memberName) {
|
||||
const memberInfo = ns.gang.getMemberInformation(memberName)
|
||||
return Math.floor(Math.min(
|
||||
memberInfo.str_asc_mult,
|
||||
memberInfo.def_asc_mult,
|
||||
memberInfo.dex_asc_mult,
|
||||
memberInfo.agi_asc_mult,
|
||||
) / 1.5)
|
||||
}
|
||||
|
||||
// Credit: Mysteyes. https://discord.com/channels/415207508303544321/415207923506216971/940379724214075442
|
||||
function getAscendTreshold(memberName) {
|
||||
const memberInfo = ns.gang.getMemberInformation(memberName)
|
||||
const mult = Math.min(
|
||||
memberInfo.str_asc_mult,
|
||||
memberInfo.def_asc_mult,
|
||||
memberInfo.dex_asc_mult,
|
||||
memberInfo.agi_asc_mult,
|
||||
)
|
||||
if (mult < 1.632) return 1.6326;
|
||||
if (mult < 2.336) return 1.4315;
|
||||
if (mult < 2.999) return 1.284;
|
||||
if (mult < 3.363) return 1.2125;
|
||||
if (mult < 4.253) return 1.1698;
|
||||
if (mult < 4.860) return 1.1428;
|
||||
if (mult < 5.455) return 1.1225;
|
||||
if (mult < 5.977) return 1.0957;
|
||||
if (mult < 6.496) return 1.0869;
|
||||
if (mult < 7.008) return 1.0789;
|
||||
if (mult < 7.519) return 1.073;
|
||||
if (mult < 8.025) return 1.0673;
|
||||
if (mult < 8.513) return 1.0631;
|
||||
return 1.0591;
|
||||
}
|
||||
|
||||
function getMinAscensionResult(memberName) {
|
||||
const memberAscensionResult = ns.gang.getAscensionResult(memberName)
|
||||
return memberAscensionResult === undefined ? undefined : Math.min(
|
||||
memberAscensionResult.str,
|
||||
memberAscensionResult.def,
|
||||
memberAscensionResult.dex,
|
||||
memberAscensionResult.agi
|
||||
)
|
||||
}
|
||||
|
||||
function isAscensionReady(memberName) {
|
||||
return getMinAscensionResult(memberName) >= getAscendTreshold(memberName)
|
||||
}
|
||||
|
||||
function isRecruitementReady(relativePower, memberCount) {
|
||||
const res = relativePower / 3 > memberCount - 3 // TODO
|
||||
// ns.print(ascentionCount / 3)
|
||||
return res
|
||||
}
|
||||
|
||||
function getGangWarWinChances() {
|
||||
const gangInfo = ns.gang.getGangInformation()
|
||||
const otherGangNames = Object.keys(ns.gang.getOtherGangInformation()).filter(x => gangInfo.faction != x && x.territory > 0)
|
||||
const chancesToWin = otherGangNames.map(x => ns.gang.getChanceToWinClash(x))
|
||||
return Math.min(...chancesToWin)
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const gangInfo = ns.gang.getGangInformation()
|
||||
|
||||
ns.print("running loop")
|
||||
|
||||
// check for and recruit new members if available
|
||||
while (ns.gang.canRecruitMember()) {
|
||||
const availableNames = memberNamePool.filter(x => memberNamePool.includes(x))
|
||||
const memberName = availableNames[Math.floor(Math.random() * availableNames.length)]
|
||||
ns.gang.recruitMember(memberName)
|
||||
ns.gang.setMemberTask(memberName, TRAIN_COMBAT)
|
||||
}
|
||||
|
||||
const gangMembers = ns.gang.getMemberNames()
|
||||
|
||||
// check for and ascend members who are ready
|
||||
for (const memberName of gangMembers) {
|
||||
if (isAscensionReady(memberName)) {
|
||||
// ns.print("ascending " + memberName)
|
||||
ns.gang.ascendMember(memberName)
|
||||
}
|
||||
}
|
||||
|
||||
for (const memberName of gangMembers) {
|
||||
if (isAscensionReady(memberName)) {
|
||||
// ns.print("ascending " + memberName)
|
||||
ns.gang.ascendMember(memberName)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: equipement
|
||||
|
||||
|
||||
// gang war
|
||||
if (getGangWarWinChances() >= 0.6) {
|
||||
// go to war
|
||||
ns.gang.setTerritoryWarfare(true)
|
||||
}
|
||||
else if (getGangWarWinChances() <= 0.55) {
|
||||
// disengage
|
||||
ns.gang.setTerritoryWarfare(false)
|
||||
}
|
||||
|
||||
// score each member by their estimated power level
|
||||
let memberDatas = gangMembers.map(x => [x, estimateMemberPower(x)])
|
||||
memberDatas.sort((a, b) => b[1] - a[1])
|
||||
const targetRelativePower = memberDatas[0][1] - 1
|
||||
for (const memberData of memberDatas) {
|
||||
const memberName = memberData[0]
|
||||
const relativePower = memberData[1]
|
||||
|
||||
ns.print(memberName)
|
||||
// ns.print(getMinAscensionResult(memberName) / getAscendTreshold(memberName))
|
||||
// ns.print(isAscensionReady(memberName))
|
||||
|
||||
if (relativePower < targetRelativePower && relativePower < 40) {
|
||||
ns.print("assignment: catchup")
|
||||
// bring up newcomers to the same level than established members
|
||||
ns.gang.setMemberTask(memberName, TRAIN_COMBAT)
|
||||
}
|
||||
else if (getMinAscensionResult(memberName) / getAscendTreshold(memberName) < 0.9) { // TODO: arbitrary constant to be ajusted
|
||||
ns.print("assignment: level up after ascention")
|
||||
// train a member back up after ascention
|
||||
ns.gang.setMemberTask(memberName, TRAIN_COMBAT)
|
||||
}
|
||||
else if (gangInfo.wantedPenalty < 0) {
|
||||
ns.print("assignment: reduce wanted level")
|
||||
// reduce wanted level
|
||||
ns.print(gangInfo.wantedPenalty)
|
||||
ns.gang.setMemberTask(memberName, VIGILANTE_JUSTICE)
|
||||
}
|
||||
else if (gangMembers.length < maxMemberCount && isRecruitementReady(relativePower, gangMembers.length)) {
|
||||
ns.print("assignment: gain respect")
|
||||
// gain respect to recruit new member
|
||||
ns.gang.setMemberTask(memberName, TERRORISM)
|
||||
}
|
||||
else if (relativePower < 20) { // TODO
|
||||
ns.print("assignment: train to next ascension")
|
||||
// train up to next ascention
|
||||
ns.gang.setMemberTask(memberName, TRAIN_COMBAT)
|
||||
}
|
||||
else if (gangInfo.territory < 1 && getGangWarWinChances() < 0.95) {
|
||||
ns.print("assignment: gang war")
|
||||
// prepare for gang war
|
||||
ns.gang.setMemberTask(memberName, TERRITORY_WARFARE)
|
||||
}
|
||||
else {
|
||||
ns.print("assignment: make money")
|
||||
// go for the money
|
||||
ns.gang.setMemberTask(memberName, MAKE_MONEY) // TODO
|
||||
}
|
||||
}
|
||||
|
||||
await ns.sleep(5000)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
ns.tprint(ns.heart.break())
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/** @param {NS} ns */
|
||||
const importantHosts = ["CSEC", "avmnite-02h", "I.I.I.I", "run4theh111z", "w0r1d_d43m0n"]
|
||||
|
||||
class ServerTreeNode {
|
||||
constructor(ns, serverName) {
|
||||
this.ns = ns
|
||||
this.serverName = serverName
|
||||
this.children = []
|
||||
this.info = ns.getServer(serverName)
|
||||
}
|
||||
|
||||
toObject() {
|
||||
return {
|
||||
[this.serverName]: {
|
||||
"info": this.info,
|
||||
"children": this.children.map(x => x.toObject()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return JSON.stringify(this.toObject())
|
||||
}
|
||||
|
||||
printTable(padding = "", pointer = "", lastNodeOfParent = true) {
|
||||
if (this.serverName == "home") {
|
||||
this.ns.tprintf("--R-P--LVL---")
|
||||
}
|
||||
this.ns.tprintf(
|
||||
"%s %s %s %4s %s",
|
||||
importantHosts.includes(this.serverName) ? "!" : " ",
|
||||
this.info.hasAdminRights ? "Y" : "N",
|
||||
this.info.openPortCount,
|
||||
this.info.requiredHackingSkill,
|
||||
padding + pointer + this.serverName
|
||||
)
|
||||
|
||||
// prepare padding for children
|
||||
let newPadding = padding
|
||||
if (this.serverName != "home") {
|
||||
if (lastNodeOfParent) {
|
||||
newPadding += " "
|
||||
}
|
||||
else {
|
||||
newPadding += "| "
|
||||
}
|
||||
}
|
||||
|
||||
// print children
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
if (i == this.children.length - 1) {
|
||||
this.children[i].printTable(newPadding, "└─", true)
|
||||
}
|
||||
else {
|
||||
this.children[i].printTable(newPadding, "├─", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getServerTree(ns, serverNameToScan = "home", scannedServerList = ["home"]) {
|
||||
const currentNode = new ServerTreeNode(ns, serverNameToScan)
|
||||
for (const serverName of ns.scan(serverNameToScan)) {
|
||||
// check if we have already scanned this server
|
||||
if (!scannedServerList.includes(serverName)) {
|
||||
// add to the list to avoid duplicates
|
||||
scannedServerList.push(serverName)
|
||||
// scan the node and add as a child to the current node
|
||||
currentNode.children.push(getServerTree(ns, serverName, scannedServerList))
|
||||
}
|
||||
}
|
||||
return currentNode
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import {getServerTree} from '/lib/ServerTree.js'
|
||||
|
||||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const serverTree = getServerTree(ns)
|
||||
ns.write("dump/map.txt", serverTree.toJson(), 'w')
|
||||
if (ns.args[0] != "noout") {
|
||||
ns.tprintf("")
|
||||
serverTree.printTable()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
// ns.clearLog()
|
||||
// ns.tail()
|
||||
ns.disableLog('ALL')
|
||||
|
||||
const pwnMethods = []
|
||||
if (ns.fileExists("BruteSSH.exe")) {
|
||||
pwnMethods.push(ns.brutessh)
|
||||
}
|
||||
if (ns.fileExists("FTPCrack.exe")) {
|
||||
pwnMethods.push(ns.ftpcrack)
|
||||
}
|
||||
if (ns.fileExists("HTTPWorm.exe")) {
|
||||
pwnMethods.push(ns.httpworm)
|
||||
}
|
||||
if (ns.fileExists("relaySMTP.exe")) {
|
||||
pwnMethods.push(ns.relaysmtp)
|
||||
}
|
||||
if (ns.fileExists("SQLInject.exe")) {
|
||||
pwnMethods.push(ns.sqlinject)
|
||||
}
|
||||
|
||||
const autopwn = (hostnameToAnalyze = "home", serverList = ["home"]) => {
|
||||
for (const hostname of ns.scan(hostnameToAnalyze)) {
|
||||
// check if we alreay have added this server to the list
|
||||
if (!serverList.includes(hostname)) {
|
||||
// nuke if able
|
||||
if (ns.hasRootAccess(hostname)) {
|
||||
ns.print(`already pwned ${hostname}`)
|
||||
}
|
||||
else if (ns.getServerNumPortsRequired(hostname) <= pwnMethods.length) {
|
||||
ns.print(`pwning ${hostname}`)
|
||||
for (let i = 0; i < ns.getServerNumPortsRequired(hostname); i++) {
|
||||
pwnMethods[i](hostname)
|
||||
}
|
||||
ns.nuke(hostname)
|
||||
}
|
||||
else {
|
||||
ns.print(`missing requirements to pwn ${hostname}. (${pwnMethods.length} < ${ns.getServerNumPortsRequired(hostname)})`)
|
||||
}
|
||||
|
||||
// add to the list
|
||||
serverList.push(hostname)
|
||||
// pwn childen
|
||||
autopwn(hostname, serverList)
|
||||
}
|
||||
}
|
||||
}
|
||||
autopwn()
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const maxServerCount = ns.getPurchasedServerLimit()
|
||||
let serverCount = ns.getPurchasedServers().length
|
||||
|
||||
ns.print(`we have ${serverCount} servers`)
|
||||
ns.print(`we can have ${maxServerCount} servers`)
|
||||
|
||||
const getLowestServerRAM = (servers) => {
|
||||
return Math.min.apply(Math, servers.map(ns.getServerMaxRam))
|
||||
}
|
||||
|
||||
if (serverCount < maxServerCount) {
|
||||
// create missing servers
|
||||
const requestedRAM = 2
|
||||
const serverCost = ns.getPurchasedServerCost(requestedRAM)
|
||||
ns.print(`server cost is ${ns.formatNumber(serverCost)}`)
|
||||
|
||||
while (serverCount < maxServerCount) {
|
||||
const serverName = `worker-${serverCount}`
|
||||
if (ns.getServerMoneyAvailable("home") >= serverCost) {
|
||||
ns.tprint(`racking server ${serverName}`)
|
||||
ns.purchaseServer(serverName, requestedRAM)
|
||||
serverCount++
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (serverCount == maxServerCount) {
|
||||
const servers = ns.getPurchasedServers()
|
||||
let success = true
|
||||
do {
|
||||
// upgrade servers
|
||||
const requestedRAM = getLowestServerRAM(servers) * 2
|
||||
for (const serverName of servers) {
|
||||
const serverRAM = ns.getServerMaxRam(serverName)
|
||||
if (serverRAM < requestedRAM) {
|
||||
const serverCost = ns.getPurchasedServerUpgradeCost(serverName, requestedRAM)
|
||||
ns.print(`selected server ${serverName} to be upgraded (current RAM: ${ns.formatRam(serverRAM)}, upgraded RAM: ${ns.formatRam(requestedRAM)}, upgrade cost: ${ns.formatNumber(serverCost)})`)
|
||||
if (ns.getServerMoneyAvailable("home") >= serverCost) {
|
||||
ns.tprint(`upgrading server ${serverName}`)
|
||||
ns.killall(serverName) //TODO: make it more graceful
|
||||
ns.upgradePurchasedServer(serverName, requestedRAM)
|
||||
}
|
||||
else {
|
||||
success = false
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (success)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
await ns.grow(ns.args[2], { "threads": ns.args[1] })
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
await ns.hack(ns.args[2], { "threads": ns.args[1] })
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
await ns.weaken(ns.args[2], { "threads": ns.args[1] })
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
const styles = ns.ui.getStyles();
|
||||
styles.fontFamily = 'Hack';
|
||||
ns.ui.setStyles(styles);
|
||||
}
|
Loading…
Reference in New Issue