diff --git a/sleeve.js b/sleeve.js index e61d4311..ae1f3e51 100644 --- a/sleeve.js +++ b/sleeve.js @@ -1,4 +1,4 @@ -import { getNsDataThroughFile, formatMoney, formatDuration, disableLogs } from './helpers.js' +import { getNsDataThroughFile, formatMoney, formatDuration, getActiveSourceFiles, disableLogs } from './helpers.js' const interval = 5000; // Uodate (tick) this often const minTaskWorkTime = 59000; // Sleeves assigned a new task should stick to it for at least this many milliseconds @@ -14,6 +14,9 @@ const argsSchema = [ ['aug-budget', 0.5], // Spend up to this much of current cash on augs per tick (Default is high, because these are permanent for the rest of the BN) ['buy-cooldown', 60 * 1000], // Must wait this may milliseconds before buying more augs for a sleeve ['min-aug-batch', 20], // Must be able to afford at least this many augs before we pull the trigger (or fewer if buying all remaining augs) + ['mimic-player', true], // Whether sleeve 0 always attempts work instead of doing crime, prioritizing the same faction/company as the player + ['work-chance', 0.5], // General chance of working instead of doing crime, if we still need more karma for a gang, or money + ['money-threshold', 10000000], // Above this amount of money, don't worry about crime unless we're still trying to get a gang ]; export function autocomplete(data, _) { @@ -26,7 +29,20 @@ export async function main(ns) { options = ns.flags(argsSchema); disableLogs(ns, ['getServerMoneyAvailable']); if (!crimes.includes(options.crime)) crimes.push(options.crime); - let task = [], lastUpdate = [], lastPurchase = [], availableAugs = [], lastReassign = []; + let task = [], lastUpdate = [], lastPurchase = [], availableAugs = [], lastReassign = [], sleeveFactions = [], ownedSourceFiles = []; + let needGang, randomCrime, doWork, doFaction; + let crimeRoll, randomIndex; + + ownedSourceFiles = await getActiveSourceFiles(ns); + const sf2Level = ownedSourceFiles[2] || 0; + if (sf2Level == 0) { + needGang = false; + log(ns, "Gangs not unlocked."); + } else { + needGang = true; + } + + log(ns, `WARN: Do not go to Sleeves tab while sleeve.js is managing multiple sleeves, until https://github.com/danielyxie/bitburner/issues/2832 is resolved. It can trigger Recovery Mode, but is seemingly harmless.`, `warning`, true); // Collect info that won't change or that we can track ourselves going forward let numSleeves; @@ -35,17 +51,29 @@ export async function main(ns) { } catch { return ns.print("User does not appear to have access to sleeves. Exiting..."); } - for (let i = 0; i < numSleeves; i++) - availableAugs[i] = null; + for (let i = 0; i < numSleeves; i++) { + availableAugs[i] = null; + await getNsDataThroughFile(ns, `ns.sleeve.setToShockRecovery(${i})`, tempFile); // Default everyone to non-faction work to avoid trouble before sleeveFactions are populated + } while (true) { let cash = ns.getServerMoneyAvailable("home") - Number(ns.read("reserve.txt")); let budget = cash * options['aug-budget']; - let playerInfo = await getNsDataThroughFile(ns, 'ns.getPlayer()', '/Temp/player-info.txt') + let playerInfo = await getNsDataThroughFile(ns, 'ns.getPlayer()', '/Temp/player-info.txt'); + const gangInfo = await getNsDataThroughFile(ns, 'ns.gang.inGang() ? ns.gang.getGangInformation() : false', '/Temp/gang-stats.txt'); + let gangFaction = ' '; + if (gangInfo && gangInfo.faction) { + needGang = false; + gangFaction = gangInfo.faction; + } + randomCrime = (cash < options['money-threshold'] || needGang); + let numJoinedFactions = playerInfo.factions.length; + let factionIndex = 0; for (let i = 0; i < numSleeves; i++) { let sleeveStats = ns.sleeve.getSleeveStats(i); let shock = sleeveStats.shock; let sync = sleeveStats.sync; + let factionAssignedName = ' '; // Manage Augmentations if (shock == 0 && availableAugs[i] == null) // No augs are available augs until shock is 0 availableAugs[i] = (await getNsDataThroughFile(ns, `ns.sleeve.getSleevePurchasableAugs(${i})`, '/Temp/sleeve-augs.txt')).sort((a, b) => a.cost - b.cost); // list of { name, cost } @@ -67,6 +95,24 @@ export async function main(ns) { lastPurchase[i] = Date.now(); } } + + if (!randomCrime || Math.random() < options['work-chance']) { + doWork = true; + // Pick a valid faction that no one else is using + randomIndex = Math.floor(Math.random() * numJoinedFactions); // This will remove the bias for our earlier, less-useful factions + for (let j = 0; j < numJoinedFactions; j++) { + if ((!sleeveFactions.includes(playerInfo.factions[(j+randomIndex) % numJoinedFactions]) || sleeveFactions[i] == playerInfo.factions[(j+randomIndex) % numJoinedFactions]) && playerInfo.factions[(j+randomIndex) % numJoinedFactions] != gangFaction) { + factionIndex = (j+randomIndex) % numJoinedFactions; + doFaction = true + break; + } else if (j == numJoinedFactions - 1) { + doFaction = false + } + } + } else { + doWork = false; + doFaction = false; + } // Manage what this sleeve should be doing let command, designatedTask; if (sync < 100) { // Synchronize @@ -83,15 +129,21 @@ export async function main(ns) { log(ns, `INFO: Sleeve ${i} is recovering from shock... ${shock.toFixed(2)}%`); lastUpdate[i] = Date.now(); } - } else if (i == 0 && playerInfo.isWorking && playerInfo.workType == "Working for Faction") { // If player is currently working for faction rep, sleeves 0 shall help him out (only one sleeve can work for a faction) + } else if (i == 0 && playerInfo.isWorking && playerInfo.workType == "Working for Faction" && !sleeveFactions.includes(playerInfo.currentWorkFactionName) && (doWork || options['mimic-player'])) { // If player is currently working for faction rep, sleeves 0 shall help him out (only one sleeve can work for a faction) // TODO: We should be able to borrow logic from work-for-factions.js to have more sleeves work for useful factions / companies + factionAssignedName = playerInfo.currentWorkFactionName; let work = works[workByFaction[playerInfo.currentWorkFactionName] || 0]; - designatedTask = `work for faction '${playerInfo.currentWorkFactionName}' (${work})`; - command = `ns.sleeve.setToFactionWork(${i}, '${playerInfo.currentWorkFactionName}', '${work}')`; // TODO: Auto-determine the most productive faction work to do? - } else if (i == 0 && playerInfo.isWorking && playerInfo.workType == "Working for Company") { // If player is currently working for a company rep, sleeves 0 shall help him out (only one sleeve can work for a company) + designatedTask = `work for faction '${factionAssignedName}' (${work})`; + command = `ns.sleeve.setToFactionWork(${i}, '${factionAssignedName}', '${work}')`; // TODO: Auto-determine the most productive faction work to do? + } else if (i == 0 && playerInfo.isWorking && playerInfo.workType == "Working for Company" && (doWork || options['mimic-player'])) { // If player is currently working for a company rep, sleeves 0 shall help him out (only one sleeve can work for a company) designatedTask = `work for company '${playerInfo.companyName}'`; command = `ns.sleeve.setToCompanyWork(${i}, '${playerInfo.companyName}')`; - } else { // Do something productive + } else if (doWork && doFaction) { + factionAssignedName = playerInfo.factions[factionIndex]; + let work = works[workByFaction[playerInfo.factions[factionIndex]] || 0]; + designatedTask = `work for faction '${factionAssignedName}' (${work})`; + command = `ns.sleeve.setToFactionWork(${i}, '${factionAssignedName}', '${work}')`; + } else { // Do something productive let crime = options.crime || (sleeveStats.strength < 100 ? 'mug' : 'homicide'); designatedTask = `commit ${crime}`; command = `ns.sleeve.setToCommitCrime(${i}, '${crime}')`; @@ -102,13 +154,14 @@ export async function main(ns) { let strAction = `Set sleeve ${i} to ${designatedTask}`; if (await getNsDataThroughFile(ns, command, tempFile)) { task[i] = designatedTask; + sleeveFactions[i] = factionAssignedName; lastReassign[i] = Date.now(); log(ns, `SUCCESS: ${strAction}`); } else { // If working for faction / company, it's possible he current work isn't supported, so try the next one. if (designatedTask.startsWith('work for faction')) { log(ns, `WARN: Failed to ${strAction} - work type may not be supported.`, 'warning'); - workByFaction[playerInfo.currentWorkFactionName] = (workByFaction[playerInfo.currentWorkFactionName] || 0) + 1; + workByFaction[factionAssignedName] = (workByFaction[factionAssignedName] || 0) + 1; } else log(ns, `ERROR: Failed to ${strAction}`, 'error'); }