diff --git a/work-for-factions.js b/work-for-factions.js index a54aa290..fb7b8cde 100644 --- a/work-for-factions.js +++ b/work-for-factions.js @@ -547,11 +547,13 @@ async function goToCity(ns, cityName) { /** @param {NS} ns */ export async function crimeForKillsKarmaStats(ns, reqKills, reqKarma, reqStats, doFastCrimesOnly = false) { const bestCrimesByDifficulty = ["heist", "assassinate", "homicide", "mug"]; // Will change crimes as our success rate improves + const bestGym = "powerhouse gym"; const chanceThresholds = [0.75, 0.9, 0.5, 0]; // Will change crimes once we reach this probability of success for better all-round gains doFastCrimesOnly = doFastCrimesOnly || (options ? options['fast-crimes-only'] : false); let player = await getPlayerInfo(ns); let strRequirements = []; - let forever = reqKills >= Number.MAX_SAFE_INTEGER || reqKarma >= Number.MAX_SAFE_INTEGER || reqStats >= Number.MAX_SAFE_INTEGER; + let forever = reqKills >= Number.MAX_SAFE_INTEGER || reqKarma >= Number.MAX_SAFE_INTEGER; + let statForever = reqStats >= Number.MAX_SAFE_INTEGER; if (reqKills) strRequirements.push(() => `${reqKills} kills (Have ${player.numPeopleKilled})`); if (reqKarma) strRequirements.push(() => `-${reqKarma} Karma (Have ${Math.round(ns.heart.break()).toLocaleString('en')})`); if (reqStats) strRequirements.push(() => `${reqStats} of each combat stat (Have ` + @@ -559,7 +561,7 @@ export async function crimeForKillsKarmaStats(ns, reqKills, reqKarma, reqStats, let anyStatsDeficient = (p) => p.skills.strength < reqStats || p.skills.defense < reqStats || /* */ p.skills.dexterity < reqStats || p.skills.agility < reqStats; let crime, lastCrime, crimeTime, lastStatusUpdateTime, needStats; - while (forever || (needStats = anyStatsDeficient(player)) || player.numPeopleKilled < reqKills || -ns.heart.break() < reqKarma) { + while (forever || player.numPeopleKilled < reqKills || -ns.heart.break() < reqKarma) { if (!forever && breakToMainLoop()) return ns.print('INFO: Interrupting crime to check on high-level priorities.'); let crimeChances = await getNsDataThroughFile(ns, `Object.fromEntries(ns.args.map(c => [c, ns.singularity.getCrimeChance(c)]))`, '/Temp/crime-chances.txt', bestCrimesByDifficulty); let karma = -ns.heart.break(); @@ -592,10 +594,123 @@ export async function crimeForKillsKarmaStats(ns, reqKills, reqKarma, reqStats, crimeCount++; player = await getPlayerInfo(ns); } + + // Travels to Sector-12 since ns.singularity.gymWorkout(); requires that the location of the player is the same as the gym + await goToCity(ns, "Sector-12"); + let isWorking = false; + let currentStat = 1; + + while(statForever || (needStats = anyStatsDeficient(player))) { + + player = await getPlayerInfo(ns); + + let pStr = player.skills.strength; + let pDef = player.skills.defense; + let pDex = player.skills.dexterity; + let pAgi = player.skills.agility; + + if (!statForever && breakToMainLoop()) return ns.print('INFO: Interrupting training to check on high-level priorities.'); + switch (currentStat) { + case 1: + if (!isWorking) { + ns.singularity.gymWorkout(bestGym, "strength") + isWorking = true; + } + ns.print(`Currently at ${pStr} strength, out of ${reqStats}` + ` (ETA: ${formatDuration(await getSkillEta(ns, "strength", reqStats))})`); + if (pStr >= reqStats) { + currentStat = 2; + ns.singularity.stopAction(); + isWorking = false; + ns.print('SUCCESS: Strength stat requirement completed.'); + } + break; + case 2: + if (!isWorking) { + ns.singularity.gymWorkout(bestGym, "defense") + isWorking = true; + } + ns.print(`Currently at ${pDef} defense, out of ${reqStats}` + ` (ETA: ${formatDuration(await getSkillEta(ns, "defense", reqStats))})`); + if (pDef >= reqStats) { + currentStat = 3; + ns.singularity.stopAction(); + isWorking = false; + ns.print('SUCCESS: Defense stat requirement completed.'); + } + break; + case 3: + if (!isWorking) { + ns.singularity.gymWorkout(bestGym, "dexterity") + isWorking = true; + } + ns.print(`Currently at ${pDex} dexterity, out of ${reqStats}` + ` (ETA: ${formatDuration(await getSkillEta(ns, "dexterity", reqStats))})`); + if (pDex >= reqStats) { + currentStat = 4; + ns.singularity.stopAction(); + isWorking = false; + ns.print('SUCCESS: Dexterity stat requirement completed.'); + } + break; + case 4: + if (!isWorking) { + ns.singularity.gymWorkout(bestGym, "agility") + isWorking = true; + } + ns.print(`Currently at ${pAgi} agility, out of ${reqStats}` + ` (ETA: ${formatDuration(await getSkillEta(ns, "agility", reqStats))})`); + if (pAgi >= reqStats) { + currentStat = 1; + ns.singularity.stopAction(); + isWorking = false; + ns.print('SUCCESS: Agility stat requirement completed.'); + } + break; + + } + + // TODO: Maybe configure the loop update to match the ETA for the stat to not over train stat (it probably doesn't matter since it's a matter of seconds). + await ns.sleep(loopSleepInterval + 5000); + } ns.print(`Done committing crimes. Reached ${strRequirements.map(r => r()).join(', ')}`); return true; } +// TODO: Finish cost check before working at the gym. +/** @param {NS} ns */ +async function getGymCost(ns, etaMilli) { + let baseGymCost = 120; + let powerHouseGymCostMult = 20; + // TODO: Apparently, if the gym gets backdoored, a 10% discount will be applied. + return baseGymCost * powerHouseGymCostMult * (etaMilli / 1000); +} + +// Gets the time to fill Exp requirements. +/** @param {NS} ns */ +async function getSkillEta(ns, skill, reqStats) { + let player = await getPlayerInfo(ns); + let skill1 = skill + "_exp"; + let statExpMult = player.mults[skill1]; + let powerHouseGymSkillMult = 10; + + let expPerMilli = ((Math.ceil(statExpMult * 10000) / 10000) * powerHouseGymSkillMult) / 1000; + // ^ While this seems unneccessairy, it actually makes the ETA more precise. + + return (await getReqExp(ns, skill, reqStats) / expPerMilli); +} +// Gets the required exp to bring a stat level to a reuqired stat level. +/** @param {NS} ns */ +async function getReqExp(ns, _wStat, reqStats) { + let player = await getPlayerInfo(ns); + + let actExp = player.exp[_wStat]; + let mult = player.mults[_wStat]; + let reqExp = calculateExp(reqStats, mult); + + return reqExp - actExp; +} +// Calculates the exp for a given stat level (game accurate). +export function calculateExp(skill, mult = 1) { + return Math.exp((skill / mult + 200) / 32) - 534.6; +} + /** @param {NS} ns */ async function studyForCharisma(ns, focus) { await goToCity(ns, 'Volhaven'); @@ -1123,4 +1238,4 @@ export async function workForMegacorpFactionInvite(ns, factionName, waitForInvit ns.print(`Stopped working for "${companyName}" repRequiredForFaction: ${repRequiredForFaction.toLocaleString('en')} ` + `currentReputation: ${Math.round(currentReputation).toLocaleString('en')} inFaction: ${player.factions.includes(factionName)}`); return false; -} \ No newline at end of file +}