Skip to content

Commit

Permalink
Fix #377 - Work from start with 8GB Ram
Browse files Browse the repository at this point in the history
Autopilot will now launch daemon and shut itself down in BN1.1 with 8GB ram
  • Loading branch information
alainbryden committed Oct 26, 2024
1 parent ecd6c73 commit c866eea
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 32 deletions.
58 changes: 46 additions & 12 deletions autopilot.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ let ranCasino = false; // Flag to indicate whether we've stolen 10b from the cas
let reservedPurchase = 0; // Flag to indicate whether we've reservedPurchase money and can still afford augmentations
let alreadyJoinedDaedalus = false, autoJoinDaedalusUnavailable = false, reservingMoneyForDaedalus = false, prioritizeHackForDaedalus = false; // Flags to indicate that we should be keeping 100b cash on hand to earn an invite to Daedalus
let lastScriptsCheck = 0; // Last time we got a listing of all running scripts
let homeRam = 0; // Amount of RAM on the home server, last we checked
let killScripts = []; // A list of scripts flagged to be restarted due to changes in priority
let dictOwnedSourceFiles = [], unlockedSFs = [], nextBn = 0; // Info for the current bitnode
let installedAugmentations = [], playerInstalledAugCount = 0, stanekLaunched = false; // Info for the current ascend
Expand All @@ -109,17 +110,18 @@ export async function main(ns) {
log(ns, `WARNING: You have previously enabled the flag "--${flag}". Because of the way this script saves its run settings, the ` +
`only way to now turn this back off will be to manually edit or delete the file ${ns.getScriptName()}.config.txt`, true);

let startUpRan = false;
while (true) {
let startUpRan = false, keepRunning = true;
while (keepRunning) {
try {
// Start-up actions, wrapped in error handling in case of temporary failures
if (!startUpRan) startUpRan = await startUp(ns);
// Main loop: Monitor progress in the current BN and automatically reset when we can afford TRP, or N augs.
await mainLoop(ns);
keepRunning = await mainLoop(ns);
}
catch (err) {
log(ns, `WARNING: autopilot.js Caught (and suppressed) an unexpected error:` +
`\n${getErrorInfo(err)}`, false, 'warning');
keepRunning = shouldWeKeepRunning(ns);
}
await ns.sleep(options['interval']);
}
Expand All @@ -134,7 +136,7 @@ async function startUp(ns) {
alreadyJoinedDaedalus = autoJoinDaedalusUnavailable = reservingMoneyForDaedalus = prioritizeHackForDaedalus =
bnCompletionSuppressed = stanekLaunched = false;
playerInstalledAugCount = wdHack = null;
installCountdown = daemonStartTime = lastScriptsCheck = reservedPurchase = 0;
installCountdown = daemonStartTime = lastScriptsCheck = homeRam = reservedPurchase = 0;
lastStatusLog = "";
installedAugmentations = killScripts = [];

Expand All @@ -143,6 +145,7 @@ async function startUp(ns) {
bitNodeMults = await tryGetBitNodeMultipliers(ns);
dictOwnedSourceFiles = await getActiveSourceFiles(ns, false);
unlockedSFs = await getActiveSourceFiles(ns, true);
homeRam = await getNsDataThroughFile(ns, `ns.getServerMaxRam(ns.args[0])`, null, ["home"]);
try {
installedAugmentations = !(4 in unlockedSFs) ? [] :
await getNsDataThroughFile(ns, 'ns.singularity.getOwnedAugmentations()', '/Temp/player-augs-installed.txt');
Expand Down Expand Up @@ -193,13 +196,12 @@ async function persistConfigChanges(ns) {
* @param {NS} ns */
async function initializeNewBitnode(ns) {
// Nothing to do here (yet)
//const player = await getNsDataThroughFile(ns, 'ns.getPlayer()');
}

/** Logic run periodically throughout the BN
* @param {NS} ns */
async function mainLoop(ns) {
const player = await getNsDataThroughFile(ns, 'ns.getPlayer()');
const player = await getPlayerInfo(ns);
let stocksValue = 0;
try { stocksValue = await getStocksValue(ns); } catch { /* Assume if this fails (insufficient ram) we also have no stocks */ }
manageReservedMoney(ns, player, stocksValue);
Expand All @@ -208,6 +210,14 @@ async function mainLoop(ns) {
await checkOnRunningScripts(ns, player);
await maybeDoCasino(ns, player);
await maybeInstallAugmentations(ns, player);
return shouldWeKeepRunning(ns); // Return false to shut down autopilot.js if we installed augs, or don't have enough home RAM
}

/** Ram-dodge getting player info.
* @param {NS} ns
* @returns {Promise<Player>} */
async function getPlayerInfo(ns) {
return await getNsDataThroughFile(ns, `ns.getPlayer()`);
}

/** Logic run periodically to if there is anything we can do to speed along earning a Daedalus invite
Expand Down Expand Up @@ -412,8 +422,8 @@ async function checkOnRunningScripts(ns, player) {
while (killScripts.length > 0)
await killScript(ns, killScripts.pop(), runningScripts);

// Hold back on launching certain scripts if we are low on home RAM
const homeRam = await getNsDataThroughFile(ns, `ns.getServerMaxRam(ns.args[0])`, null, ["home"]);
// See if home ram has improved. We hold back on launching certain scripts if we are low on home RAM
homeRam = await getNsDataThroughFile(ns, `ns.getServerMaxRam(ns.args[0])`, null, ["home"]);

// Launch stock-master in a way that emphasizes it as our main source of income early-on
if (!findScript('stockmaster.js') && !reservingMoneyForDaedalus && homeRam >= 32)
Expand Down Expand Up @@ -501,6 +511,9 @@ async function checkOnRunningScripts(ns, player) {
// If we have SF4, but not level 3, instruct daemon.js to reserve additional home RAM
if ((4 in unlockedSFs) && unlockedSFs[4] < 3)
daemonArgs.push('--reserved-ram', 32 * ((unlockedSFs[4] ?? 0) == 2 ? 4 : 16));
// Open the tail window if it's the start of a new BN. Especially useful to new players.
if(getTimeInBit)
daemonArgs.push('--tail');
}

// Once stanek's gift is accepted, launch it once per reset before we launch daemon (Note: stanek's gift is auto-purchased by faction-manager.js on your first install)
Expand Down Expand Up @@ -624,10 +637,9 @@ function getFactionManagerOutput(ns) {
* @param {NS} ns
* @param {Player} player */
async function maybeInstallAugmentations(ns, player) {
if (!(4 in unlockedSFs)) {
setStatus(ns, `No singularity access, so you're on your own. You should manually work for factions and install augmentations!`);
return false; // Cannot automate augmentations or installs without singularity
}
if (!(4 in unlockedSFs)) // Cannot automate augmentations or installs without singularity
return setStatus(ns, `No singularity access, so you're on your own. You should manually work for factions and install augmentations!`);

// If we previously attempted to reserve money for an augmentation purchase order, do a fresh facman run to ensure it's still available
if (reservedPurchase && installCountdown <= Date.now()) {
log(ns, "INFO: Manually running faction-manager.js to ensure previously reserved purchase is still obtainable.");
Expand Down Expand Up @@ -821,6 +833,28 @@ function manageReservedMoney(ns, player, stocksValue) {
*/
}

/** Logic to determine whether we should keep running, or shut down autopilot.js for some reason.
* @param {NS} ns
* @returns {boolean} true if we should keep running. False if we should shut down this script. */
function shouldWeKeepRunning(ns) {
if (4 in unlockedSFs)
return true; // If we have SF4 - run always
if (homeRam == 8) {
log(ns, `WARN: (not an actual warning, just trying to make this message stand out.)` +
`\n` + '-'.repeat(100) +
`\n\n Welcome to bitburner and thanks for using my scripts!` +
`\n\n Currently, your available RAM on home (8 GB) is too small to keep autopilot.js running.` +
`\n The priority should just be to run "daemon.js" for a while until you have enough money to` +
`\n purchase some home RAM (which you must do manually at a store like [alpha ent.] in the city),` +
`\n\n Once you have more home ram, feel free to 'run ${ns.getScriptName()}' again!` +
`\n\n` + '-'.repeat(100), true);
ns.tail(getFilePath('daemon.js'));
return false; // Daemon.js needs more room to breath
}
// Otherwise, keep running
return true;
}

/** Helper to launch a script and log whether if it succeeded or failed
* @param {NS} ns */
function launchScriptHelper(ns, baseScriptName, args = [], convertFileName = true) {
Expand Down
30 changes: 19 additions & 11 deletions daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ let highUtilizationIterations = 0;
let lastShareTime = 0; // Tracks when share was last invoked so we can respect the configured share-cooldown
let allTargetsPrepped = false;

/** Ram-dodge getting updated player info. Note that this is the only async routine called in the main loop.
* If latency or ram instability is an issue, you may wish to try uncommenting the direct request.
/** Ram-dodge getting updated player info.
* @param {NS} ns
* @returns {Promise<Player>} */
async function getPlayerInfo(ns) {
Expand Down Expand Up @@ -216,10 +215,19 @@ function reservedMoney(ns) {
// script entry point
/** @param {NS} ns **/
export async function main(ns) {
try {
await startup(ns);
} catch (err) {
log(ns, `ERROR: daemon.js Caught a fatal error during startup: ${getErrorInfo(err)}`, true, 'error');
let startupAttempts = 0;
while (startupAttempts++ <= 5) {
try {
await startup(ns);
} catch (err) {
if (startupAttempts == 5)
log(ns, `ERROR: daemon.js Keeps catching a fatal error during startup: ${getErrorInfo(err)}`, true, 'error');
else {
log(ns, `WARN: daemon.js Caught an error during startup: ${getErrorInfo(err)}` +
`\nWill try again (attempt ${startupAttempts} of 5)`, false, 'warning');
await ns.sleep(5000);
}
}
}
}

Expand Down Expand Up @@ -538,11 +546,6 @@ async function runPeriodicScripts(ns) {
await runCommand(ns, `0; if(ns.hacknet.spendHashes("Sell for Money")) ns.toast('Sold 4 hashes for \$1M', 'success')`, '/Temp/sell-hashes-for-money.js');
}
}
// For early players, provide a hint to buy more home RAM asap:
if (!(4 in dictSourceFiles) && !homeServer.totalRam(true) < 64)
log(ns, `INFO: Reminder: Daemon.js can do a lot more if you have more Home RAM. Right now, you must buy this yourself.` +
`\nHead to the "City", visit [alpha ent.] (or other Tech store), and purchase at least 64 GB as soon as possible!` +
`\nAlso be sure to purchase TOR and run "buy -a" from the terminal until you own all hack tools.`, true, 'info');
}

// Helper that gets the either invokes a function that returns a value, or returns the value as-is if it is not a function.
Expand Down Expand Up @@ -662,6 +665,11 @@ async function doTargetingLoop(ns) {
.join('\n  ');
log(ns, targetsLog);
ns.write("/Temp/targets.txt", targetsLog, "w");
// For early players, provide a hint to buy more home RAM asap:
if (!(4 in dictSourceFiles) && !homeServer.totalRam(true) < 64)
log(ns, `Reminder: Daemon.js can do a lot more if you have more Home RAM. Right now, you must buy this yourself.` +
`\nHead to the "City", visit [alpha ent.] (or other Tech store), and purchase at least 64 GB as soon as possible!` +
`\nAlso be sure to purchase TOR and run "buy -a" from the terminal until you own all hack tools.`, true, 'info');
}
}
// Processed servers will be split into various lists for generating a summary at the end
Expand Down
15 changes: 11 additions & 4 deletions faction-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export async function main(ns) {
desiredAugs = priorityAugs.concat(desiredAugs);

// Determine which source files are active, which, for one, lets us determine how the cost of augmentations will scale
playerData = await getNsDataThroughFile(ns, 'ns.getPlayer()');
playerData = await getPlayerInfo(ns);
bitNode = (await getNsDataThroughFile(ns, `ns.getResetInfo()`)).currentNode;
const ownedSourceFiles = await getActiveSourceFiles(ns, false);
effectiveSourceFiles = await getActiveSourceFiles(ns, true);
Expand Down Expand Up @@ -240,6 +240,13 @@ export async function main(ns) {
}
}

/** Ram-dodge getting updated player info.
* @param {NS} ns
* @returns {Promise<Player>} */
async function getPlayerInfo(ns) {
return await getNsDataThroughFile(ns, `ns.getPlayer()`);
}

/** @param {NS} ns
* @returns {Promise<GangGenInfo|boolean>} Gang information, if we're in a gang, or False */
async function getGangInfo(ns) {
Expand Down Expand Up @@ -687,7 +694,7 @@ async function manageFilteredSubset(ns, outputRows, subsetName, subset, printLis
* Note: Stores this info in global properties `purchaseableAugs` and `purchaseFactionDonations` so that a final action in the main method will do the purchase. */
async function managePurchaseableAugs(ns, outputRows, accessibleAugs) {
// Refresh player data to get an accurate read of current money
playerData = await getNsDataThroughFile(ns, 'ns.getPlayer()');
playerData = await getPlayerInfo(ns);
const budget = playerData.money + stockValue;
let totalRepCost, totalAugCost, dropped, restart;
// We will make every effort to keep "priority" augs in the purchase order, but start dropping them if we find we cannot afford them all
Expand Down Expand Up @@ -872,7 +879,7 @@ async function purchaseDesiredAugs(ns) {
let totalRepCost = Object.values(purchaseFactionDonations).reduce((t, r) => t + r, 0);
let totalAugCost = getTotalCost(purchaseableAugs);
// Refresh player data to get an accurate read of current money
playerData = await getNsDataThroughFile(ns, 'ns.getPlayer()');
playerData = await getPlayerInfo(ns);
if (stockValue > 0)
return log(ns, `ERROR: For your own protection, --purchase will not run while you are holding stocks (current stock value: ${formatMoney(stockValue)}). ` +
`Liquidate your shares before running (run stockmaster.js --liquidate) or run this script with --ignore-stocks to override this.`, printToTerminal, 'error')
Expand Down Expand Up @@ -902,7 +909,7 @@ async function purchaseDesiredAugs(ns) {
else
log(ns, `ERROR: We were only able to purchase ${purchased} of our ${purchaseableAugs.length} augmentations. ` +
`Expected cost was ${getCostString(totalAugCost, totalRepCost)}. Player money was ${formatMoney(playerData.money)} right before purchase, ` +
`is now ${formatMoney(await getNsDataThroughFile(ns, 'ns.getPlayer().money'))}`, printToTerminal, 'error');
`is now ${formatMoney((await getPlayerInfo(ns)).money)}`, printToTerminal, 'error');
}

/** @param {NS} ns **/
Expand Down
21 changes: 16 additions & 5 deletions helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -643,16 +643,27 @@ async function getHardCodedBitNodeMultipliers(ns, fnGetNsDataThroughFile) {
export async function instanceCount(ns, onHost = "home", warn = true, tailOtherInstances = true) {
checkNsInstance(ns, '"alreadyRunning"');
const scriptName = ns.getScriptName();
const others = await getNsDataThroughFile(ns, 'ns.ps(ns.args[0]).filter(p => p.filename == ns.args[1]).map(p => p.pid)',
'/Temp/ps-other-instances.txt', [onHost, scriptName]);
if (others.length >= 2) {
let otherInstances = (/**@returns{ProcessInfo[]}*/() => [])();
try {
otherInstances = await getNsDataThroughFile(ns, 'ns.ps(ns.args[0]).filter(p => p.filename == ns.args[1]).map(p => p.pid)',
'/Temp/ps-other-instances.txt', [onHost, scriptName]);
} catch (err) {
if (err.message?.includes("insufficient RAM") ?? false) {
log(ns, `ERROR: Not enough free RAM on ${onHost} to run ${scriptName}.` +
`\nBuy more RAM or kill some other scripts first.` +
`\nYou can run the 'top' command from the terminal to see what scripts are using RAM.`, true, 'error');
return 2;
}
else throw err;
}
if (otherInstances.length >= 2) {
if (warn)
log(ns, `WARNING: You cannot start multiple versions of this script (${scriptName}). Please shut down the other instance first.` +
(tailOtherInstances ? ' (To help with this, a tail window for the other instance will be opened)' : ''), true, 'warning');
if (tailOtherInstances) // Tail all but the last pid, since it will belong to the current instance (which will be shut down)
others.slice(0, others.length - 1).forEach(pid => ns.tail(pid));
otherInstances.slice(0, others.length - 1).forEach(pid => ns.tail(pid));
}
return others.length;
return otherInstances.length;
}

/** Helper function to get all stock symbols, or null if you do not have TIX api access.
Expand Down

0 comments on commit c866eea

Please sign in to comment.