Skip to content

Commit

Permalink
Improvements to spending hashes
Browse files Browse the repository at this point in the history
- Autopilot can switch servers to boost as better servers become unlocked
- Autopilot will kill and restart spend-hacknet-hashes to stop spending on Reduce_Minimum_Security once min security drops below 2.
- Fixed spend-hacknet-hashes shutting down if it can't afford any new upgrades and the current nodes are at max hash capacity, but new notes could still be purchased.
- Improved toast message when we cannot afford to upgrade hashes, and encourages the user to violate the budget / reserve if they deem it worthwhile.
  • Loading branch information
alainbryden committed Dec 1, 2024
1 parent 7600c4c commit bca94e1
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 25 deletions.
38 changes: 29 additions & 9 deletions autopilot.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export async function main(ns) {
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 = (/**@returns{{[k: number]: number;}}*/() => [])();
let dictOwnedSourceFiles = (/**@returns{{[k: number]: number;}}*/() => [])(); // Player owned source files
let dictServerHackReqs = (/**@returns{{[serverName: string]: number;}}*/() => undefined)(); // Hacking requirement for each server
let unlockedSFs = [], nextBn = 0; // Info for the current bitnode
let resetInfo = (/**@returns{ResetInfo}*/() => undefined)(); // Information about the current bitnode
let bitNodeMults = (/**@returns{BitNodeMultipliers}*/() => undefined)(); // bitNode multipliers that can be automatically determined after SF-5
Expand Down Expand Up @@ -432,6 +433,7 @@ export async function main(ns) {

/** Helper to get the first instance of a running script by name.
* @param {NS} ns
* @param {string} baseScriptName The name of a script (before applying getFilePath)
* @param {ProcessInfo[]} runningScripts - (optional) Cached list of running scripts to avoid repeating this expensive request
* @param {(value: ProcessInfo, index: number, array: ProcessInfo[]) => unknown} filter - (optional) Filter the list of processes beyond just matching on the script name */
function findScriptHelper(baseScriptName, runningScripts, filter = null) {
Expand Down Expand Up @@ -459,7 +461,8 @@ export async function main(ns) {
if (lastScriptsCheck > Date.now() - options['interval-check-scripts']) return;
lastScriptsCheck = Date.now();
const runningScripts = await getRunningScripts(ns); // Cache the list of running scripts for the duration
const findScript = (baseScriptName, filter = null) => findScriptHelper(baseScriptName, runningScripts, filter);
const findScript = /** @param {(value: ProcessInfo, index: number, array: ProcessInfo[]) => unknown} filter @returns {ProcessInfo} */
(baseScriptName, filter = null) => findScriptHelper(baseScriptName, runningScripts, filter);

// Kill any scripts that were flagged for restart
while (killScripts.length > 0)
Expand All @@ -486,21 +489,38 @@ export async function main(ns) {
}

// Spend hacknet hashes on our boosting best hack-income server once established
const spendingHashesOnHacking = findScript('spend-hacknet-hashes.js', s => s.args.includes("--spend-on-server"))
if ((9 in unlockedSFs) && !spendingHashesOnHacking && getTimeInAug() >= options['time-before-boosting-best-hack-server']
const existingSpendHashesProc = findScript('spend-hacknet-hashes.js', s => s.args.includes("--spend-on-server"))
if ((9 in unlockedSFs) && getTimeInAug() >= options['time-before-boosting-best-hack-server']
&& 0 != bitNodeMults.ScriptHackMoney * bitNodeMults.ScriptHackMoneyGain) // No point in boosting hack income if it's scaled to 0 in the current BN
{
const strServerIncomeInfo = ns.read('/Temp/analyze-hack.txt'); // HACK: Steal this file that Daemon also relies on
if (strServerIncomeInfo) {
const incomeByServer = JSON.parse(strServerIncomeInfo);
const dictServerHackReqs = await getNsDataThroughFile(ns, 'Object.fromEntries(ns.args.map(server => [server, ns.getServerRequiredHackingLevel(server)]))',
'/Temp/servers-hack-req.txt', incomeByServer.map(s => s.hostname));
dictServerHackReqs ??= await getNsDataThroughFile(ns, 'Object.fromEntries(ns.args.map(server => [server, ns.getServerRequiredHackingLevel(server)]))',
'/Temp/getServerRequiredHackingLevel-all.txt', incomeByServer.map(s => s.hostname));
const [bestServer, gain] = incomeByServer.filter(s => dictServerHackReqs[s.hostname] <= player.skills.hacking)
.reduce(([bestServer, bestIncome], target) => target.gainRate > bestIncome ? [target.hostname, target.gainRate] : [bestServer, bestIncome], [null, -1]);
if (bestServer && gain > 1) {
log(ns, `Identified that the best hack income server is ${bestServer} worth ${formatMoney(gain)}/sec.`)
launchScriptHelper(ns, 'spend-hacknet-hashes.js',
["--liquidate", "--spend-on", "Increase_Maximum_Money", "--spend-on", "Reduce_Minimum_Security", "--spend-on-server", bestServer]);
// Check whether we should be spending hashes to reduce minimum security
const serverMinSecurity = await getNsDataThroughFile(ns, 'ns.getServerMinSecurityLevel(ns.args[0])', null, [bestServer]);
const shouldReduceMinSecurity = serverMinSecurity > 2; // Each purchase reduces by 2%. Can't go below 1, but not worth the cost to keep going below 2.
// If we were already spending hashes to boost a server, check to see if things have changed
if (existingSpendHashesProc) {
const currentBoostTarget = existingSpendHashesProc.args[1 + existingSpendHashesProc.args.indexOf("--spend-on-server")];
const isReducingSecurity = existingSpendHashesProc.args.includes("Reduce_Minimum_Security");
if (currentBoostTarget != bestServer || isReducingSecurity != shouldReduceMinSecurity) {
log(ns, `Killing a prior spend-hacknet-hashes.js process targetting ${currentBoostTarget} because ` +
(currentBoostTarget != bestServer ? `The new best income server is ${bestServer}.` : 'We no longer need to reduce minimum security.'), true);
await killScript(ns, 'spend-hacknet-hashes.js', null, existingSpendHashesProc);
existingSpendHashesProc = false;
}
}
if (!existingSpendHashesProc) { //
log(ns, `Identified that the best hack income server is ${bestServer} worth ${formatMoney(gain)}/sec.`);
const spendHashesArgs = ["--liquidate", "--spend-on-server", bestServer, "--spend-on", "Increase_Maximum_Money"];
if (shouldReduceMinSecurity) spendHashesArgs.push("--spend-on", "Reduce_Minimum_Security");
launchScriptHelper(ns, 'spend-hacknet-hashes.js', spendHashesArgs);
}
} else if (gain <= 1)
log_once(ns, `INFO: Hack income is currently too severely penalized to merit launching spend-hacknet-hashes.js to boost servers.`);
else
Expand Down
42 changes: 26 additions & 16 deletions spend-hacknet-hashes.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export async function main(ns) {


let lastHashBalance = -1; // Balance of hashes last time we woke up. If unchanged, we go back to sleep quickly (game hasn't ticked)
let capacityMaxed = false; // Flag indicating we've maxed our hash capacity, to avoid repeatedly logging this fact.
let notifiedMaxCapacity = false; // Flag indicating we've maxed our hash capacity, to avoid repeatedly logging this fact.
// Function determines the current cheapest upgrade of all the upgrades we wish to keep purchasing
const getMinCost = spendActions => Math.min(...spendActions.map(p => ns.hacknet.hashCost(p)));
// Helper to format hashes in log message
Expand All @@ -83,7 +83,7 @@ export async function main(ns) {
let currentHashes = ns.hacknet.numHashes();
// Go back to sleep if the game hasn't ticket yet (given us more hashes) since our last loop.
if (lastHashBalance != capacity && lastHashBalance == currentHashes) continue;
log(ns, `INFO: Waking up, last hash balance has changed from ${lastHashBalance} to ${currentHashes}`);
//log(ns, `INFO: Waking up, last hash balance has changed from ${lastHashBalance} to ${currentHashes}`);
// Compute the total income rate of all hacknet nodes. We have to spend faster than this when near capacity.
const nodes = ns.hacknet.numNodes();
if (nodes == 0) {
Expand Down Expand Up @@ -158,7 +158,7 @@ export async function main(ns) {
`so we cannot increase our hash capacity. ${capacityMessage}`, false,
remaining < hashesEarnedNextTick ? 'warning' : undefined); // Only warn via toast if we are running out of capacity
} else { // Otherwise, try to upgrade hacknet capacity so we can save up for more upgrades
if (!capacityMaxed) // Log that we want to increase hash capacity (unless we've previously seen that we are maxed out)
if (!notifiedMaxCapacity) // Log that we want to increase hash capacity (unless we've previously seen that we are maxed out)
log(ns, `INFO: ${capacityMessage}`);
let lowestLevel = Number.MAX_SAFE_INTEGER, lowestIndex = null;
for (let i = 0; i < nodes; i++)
Expand All @@ -167,7 +167,8 @@ export async function main(ns) {
const nextCacheUpgradeCost = lowestIndex == null ? Number.POSITIVE_INFINITY : ns.hacknet.getCacheUpgradeCost(lowestIndex, 1);
const nextNodeCost = ns.hacknet.getPurchaseNodeCost();
const reservedMoney = options['reserve'] ?? Number(ns.read("reserve.txt") || 0);
const spendableMoney = Math.max(0, ns.getServerMoneyAvailable('home') - reservedMoney);
const playerMoney = ns.getServerMoneyAvailable('home');
const spendableMoney = Math.max(0, playerMoney - reservedMoney);
// If it's cheaper to buy a new hacknet node than to upgrade the cache of an existing one, do so
if (nextNodeCost < nextCacheUpgradeCost && nextNodeCost < spendableMoney) {
if (ns.hacknet.purchaseNode())
Expand All @@ -185,23 +186,32 @@ export async function main(ns) {
log(ns, `WARNING: spend-hacknet-hashes.js attempted to spend ${formatMoney(nextCacheUpgradeCost)} to upgrade hacknet node ${lowestIndex} hash capacity, `
`but the purchase failed for an unknown reason (despite appearing to have ${formatMoney(spendableMoney)} to spend after reserves.)`, false, 'warning');
} else if (nodes > 0) {
// Prepare a message about our inability to upgrade hash capacity
let message = `Cannot upgrade hash capacity (currently ${formatHashes(capacity)} hashes max). `;
const nextCheapestCacheIncreaseCost = Math.min(nextCacheUpgradeCost, nextNodeCost);
const nextCheapestCacheIncrease = nextNodeCost < nextCacheUpgradeCost ? `buy hacknet node ${nodes + 1}` : `upgrade hacknet node ${lowestIndex} hash capacity`;
if (!Number.isFinite(nextCheapestCacheIncreaseCost))
message += `Hash Capacity is at its maximum and hacknet server limit is reached.`;
else
message += ` We cannot afford to increase our hash capacity (${formatMoney(nextCheapestCacheIncreaseCost)} to ${nextCheapestCacheIncrease}).` +
(playerMoney < nextCheapestCacheIncreaseCost ? '' : // Don't bother mentioning budget if the cost exceeds all player money
`on our budget of ${formatMoney(spendableMoney)}` + (reservedMoney > 0 ? ` (after respecting reserve of ${formatMoney(reservedMoney)}).` : '.'));
// Include in the message information about what we are trying to spend hashes on
const nextPurchaseCost = getMinCost(toBuy);
let message = `Failed to upgrade hash capacity (currently ${formatHashes(capacity)} hashes max) on budget of ${formatMoney(spendableMoney)}.`;
message += (Number.isFinite(nextCacheUpgradeCost) ? ` We cannot afford to increase our hash capacity at this time` :
` Hash Capacity is at its maximum`) + ` (next cost is ${formatMoney(nextCacheUpgradeCost)}).`;
if (nextPurchaseCost > capacity)
message += ` We have insufficient hashes to buy any of the desired upgrades (${toBuy.join(", ")}) at our current hash capacity. ` +
`The next cheapest purchase costs ${formatHashes(nextPurchaseCost)} hashes.`;
// Log a slightly different message about our failure to upgrade hash capacity depending on the root cause.
if (nextPurchaseCost > capacity && !Number.isFinite(nextCacheUpgradeCost))
return log(ns, `SUCCESS: We've maxed all purchases. ${message}`); // Shut down, because we won't be able to buy anything further.
else if (!Number.isFinite(nextCacheUpgradeCost)) {
if (!capacityMaxed) // Only inform the user of this the first time it happens.
log(ns, `INFO: ${message}`, true, 'info');
capacityMaxed = true; // Set a flag to no longer check or inform the user about this
// If we don't have the budget for the upgrade, toast a warning so the user can decide whether they think it worth manually intervening
if (Number.isFinite(nextCheapestCacheIncreaseCost)) {
if (playerMoney > nextCheapestCacheIncreaseCost)
message += ' Feel free to manually purchase this upgrade (despite the reserve/budget) if you deem it worthwhile.'
log(ns, `WARNING: spend-hacknet-hashes.js ${message}`, false, 'warning');
} else if (nextPurchaseCost > capacity) // If we can't afford anything, and have maxed our hash capacity, we may as well shut down.
return log(ns, `SUCCESS: We've maxed all purchases. ${message}`); // Shut down, because we will never be able to buy anything further.
else if (!notifiedMaxCapacity) { // The first time we discover we are at max hash capacity (infinite cost) notify the user
log(ns, `INFO: spend-hacknet-hashes.js ${message}`, true, 'info'); // Only inform the user of this the first time it happens.
notifiedMaxCapacity = true; // Set the flag to avoid repeated notifications
}
else
log(ns, `WARNING: ${message} `, false, 'warning');
}
}
// If for any of the above reasons, we weren't able to upgrade capacity, calling 'SpendHashes' once more
Expand Down

0 comments on commit bca94e1

Please sign in to comment.