diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index ae9b32ec9..782fd880e 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -565,6 +565,7 @@ export const RamCosts: RamCostTree = { hackExp: 0, hackPercent: 0, growPercent: 0, + growThreads: 0, hackTime: 0, growTime: 0, weakenTime: 0, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 44c7d123a..3933265e4 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -297,22 +297,27 @@ export const ns: InternalAPI = { }, growthAnalyze: (ctx) => - (_hostname, _growth, _cores = 1) => { - const hostname = helpers.string(ctx, "hostname", _hostname); - const growth = helpers.number(ctx, "growth", _growth); + (_host, _multiplier, _cores = 1) => { + const host = helpers.string(ctx, "hostname", _host); + const mult = helpers.number(ctx, "multiplier", _multiplier); const cores = helpers.number(ctx, "cores", _cores); // Check argument validity - const server = helpers.getServer(ctx, hostname); + const server = helpers.getServer(ctx, host); if (!(server instanceof Server)) { - helpers.log(ctx, () => "Cannot be executed on this server."); + // Todo 2.3: Make this throw instead of returning 0? + helpers.log(ctx, () => `${host} is not a hackable server. Returning 0.`); return 0; } - if (typeof growth !== "number" || isNaN(growth) || growth < 1 || !isFinite(growth)) { - throw helpers.makeRuntimeErrorMsg(ctx, `Invalid argument: growth must be numeric and >= 1, is ${growth}.`); + if (mult < 1 || !isFinite(mult)) { + throw helpers.makeRuntimeErrorMsg(ctx, `Invalid argument: multiplier must be finite and >= 1, is ${mult}.`); + } + // TODO 2.3: Add assertion function for positive integer, there are a lot of places everywhere that can use this + if (!Number.isInteger(cores) || cores < 1) { + throw helpers.makeRuntimeErrorMsg(ctx, `Cores should be a positive integer. Cores provided: ${cores}`); } - return numCycleForGrowth(server, Number(growth), cores); + return numCycleForGrowth(server, mult, cores); }, growthAnalyzeSecurity: (ctx) => diff --git a/src/NetscriptFunctions/Formulas.ts b/src/NetscriptFunctions/Formulas.ts index 75a283745..4b0261233 100644 --- a/src/NetscriptFunctions/Formulas.ts +++ b/src/NetscriptFunctions/Formulas.ts @@ -1,5 +1,6 @@ import { Player as player } from "../Player"; import { calculateServerGrowth } from "../Server/formulas/grow"; +import { numCycleForGrowthCorrected } from "../Server/ServerHelpers"; import { calculateMoneyGainRate, calculateLevelUpgradeCost, @@ -164,6 +165,9 @@ export function NetscriptFormulas(): InternalAPI { checkFormulasAccess(ctx); return calculatePercentMoneyHacked(server, person); }, + /* TODO 2.3: Remove growPercent, add growMultiplier function? + Much better name given the output. Not sure if removedFunction error dialog/editing script will be too annoying. + Changing the function name also allows reordering params as server, player, etc. like other formulas functions */ growPercent: (ctx) => (_server, _threads, _player, _cores = 1) => { @@ -174,6 +178,17 @@ export function NetscriptFormulas(): InternalAPI { checkFormulasAccess(ctx); return calculateServerGrowth(server, threads, person, cores); }, + growThreads: + (ctx) => + (_server, _player, _targetMoney, _cores = 1) => { + const server = helpers.server(ctx, _server); + const player = helpers.person(ctx, _player); + const targetMoney = helpers.number(ctx, "targetMoney", _targetMoney); + const startMoney = helpers.number(ctx, "server.moneyAvailable", server.moneyAvailable); + const cores = helpers.number(ctx, "cores", _cores); + checkFormulasAccess(ctx); + return numCycleForGrowthCorrected(server, targetMoney, startMoney, cores, player); + }, hackTime: (ctx) => (_server, _player) => { const server = helpers.server(ctx, _server); const person = helpers.person(ctx, _player); diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index f5091020b..6badd6a82 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -3913,7 +3913,7 @@ interface ReputationFormulas { /** * Calculate how much rep would be gained. * @param amount - Amount of money donated - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} */ repFromDonation(amount: number, player: Person): number; } @@ -3926,8 +3926,8 @@ interface HackingFormulas { /** * Calculate hack chance. * (Ex: 0.25 would indicate a 25% chance of success.) - * @param server - Server info from {@link NS.getServer | getServer} - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @returns The calculated hack chance. */ hackChance(server: Server, player: Person): number; @@ -3935,8 +3935,8 @@ interface HackingFormulas { * Calculate hack exp for one thread. * @remarks * Multiply by thread to get total exp - * @param server - Server info from {@link NS.getServer | getServer} - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @returns The calculated hack exp. */ hackExp(server: Server, player: Person): number; @@ -3945,8 +3945,8 @@ interface HackingFormulas { * (Ex: 0.25 would steal 25% of the server's current value.) * @remarks * Multiply by thread to get total percent hacked. - * @param server - Server info from {@link NS.getServer | getServer} - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @returns The calculated hack percent. */ hackPercent(server: Server, player: Person): number; @@ -3954,31 +3954,40 @@ interface HackingFormulas { * Calculate the percent a server would grow to. * Not exact due to limitations of mathematics. * (Ex: 3.0 would would grow the server to 300% of its current value.) - * @param server - Server info from {@link NS.getServer | getServer} + * @param server - Server info, typically from {@link NS.getServer | getServer} * @param threads - Amount of thread. - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @param cores - Number of cores on the computer that will execute grow. * @returns The calculated grow percent. */ growPercent(server: Server, threads: number, player: Person, cores?: number): number; + /** + * Calculate how many threads it will take to grow server to targetMoney. Starting money is server.moneyAvailable. + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} + * @param targetMoney - Desired final money, capped to server's moneyMax + * @param cores - Number of cores on the computer that will execute grow. + * @returns The calculated grow threads as an integer, rounded up. + */ + growThreads(server: Server, player: Person, targetMoney: number, cores?: number): number; /** * Calculate hack time. - * @param server - Server info from {@link NS.getServer | getServer} - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @returns The calculated hack time. */ hackTime(server: Server, player: Person): number; /** * Calculate grow time. - * @param server - Server info from {@link NS.getServer | getServer} - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @returns The calculated grow time. */ growTime(server: Server, player: Person): number; /** * Calculate weaken time. - * @param server - Server info from {@link NS.getServer | getServer} - * @param player - Player info from {@link NS.getPlayer | getPlayer} + * @param server - Server info, typically from {@link NS.getServer | getServer} + * @param player - Player info, typically from {@link NS.getPlayer | getPlayer} * @returns The calculated weaken time. */ weakenTime(server: Server, player: Person): number; @@ -4617,32 +4626,39 @@ export interface NS { * RAM cost: 0.15 GB * * Use your hacking skills to increase the amount of money available on a server. - * The runtime for this command depends on your hacking level and the target server’s - * security level. When `grow` completes, the money available on a target server will be increased - * by amount equal to the number of threads used and a certain, fixed percentage of current money on - * the server. This percentage is determined by the target server’s growth rate (which varies between servers) - * and security level. Generally, higher-level servers have higher growth rates. - * The {@link NS.getServerGrowth | getServerGrowth} function can be used to obtain a server’s growth rate. * - * Like {@link NS.hack | hack}, `grow` can be called on any server, regardless of where the script is running. + * Once the grow is complete, $1 is added to the server's available money for every script thread. This additive + * growth allows for rescuing a server even after it is emptied. + * + * After this addition, the thread count is also used to determine a multiplier, which the server's money is then + * multiplied by. + * + * The multiplier scales exponentially with thread count, and its base depends on the server's security + * level and in inherent "growth" statistic that varies between different servers. + * + * {@link NS.getServerGrowth | getServerGrowth} can be used to check the inherent growth statistic of a server. + * + * {@link NS.growthAnalyze | growthAnalyze} can be used to determine the number of threads needed for a specified + * multiplicative portion of server growth. + * + * To determine the effect of a single grow, obtain access to the Formulas API and use + * {@link HackingFormulas.growPercent | formulas.hacking.growPercent}, or invert {@link NS.growthAnalyze | growthAnalyze}. + * + * Like {@link NS.hack | hack}, `grow` can be called on any hackable server, regardless of where the script is + * running. Hackable servers are any servers not owned by the player. + * * The grow() command requires root access to the target server, but there is no required hacking - * level to run the command. It also raises the security level of the target server by 0.004. + * level to run the command. It also raises the security level of the target server based on the number of threads. + * The security increase can be determined using {@link NS.growthAnalyzeSecurity | growthAnalyzeSecurity}. * * @example - * ```ts - * // NS1: - * var currentMoney = getServerMoneyAvailable("foodnstuff"); - * currentMoney = currentMoney * grow("foodnstuff"); - * ``` - * @example - * ```ts - * // NS2: - * let currentMoney = ns.getServerMoneyAvailable("foodnstuff"); + * ```js + * let currentMoney = ns.getServerMoneyAvailable("n00dles"); * currentMoney *= await ns.grow("foodnstuff"); * ``` * @param host - Hostname of the target server to grow. * @param opts - Optional parameters for configuring function behavior. - * @returns The number by which the money on the server was multiplied for the growth. + * @returns The total effective multiplier that was applied to the server's money (after both additive and multiplicative growth). */ grow(host: string, opts?: BasicHGWOptions): Promise; @@ -4691,22 +4707,24 @@ export interface NS { weakenAnalyze(threads: number, cores?: number): number; /** - * Predict the effect of hack. + * Calculate the decimal number of threads needed to hack a specified amount of money from a target host. * @remarks * RAM cost: 1 GB * - * This function returns the number of script threads you need when running the hack command + * This function returns the decimal number of script threads you need when running the hack command * to steal the specified amount of money from the target server. * If hackAmount is less than zero or greater than the amount of money available on the server, * then this function returns -1. * - * Warning: The value returned by this function isn’t necessarily a whole number. * * @example * ```ts - * //For example, let’s say the foodnstuff server has $10m and you run: - * hackAnalyzeThreads("foodnstuff", 1e6); - * //If this function returns 50, this means that if your next hack call is run on a script with 50 threads, it will steal $1m from the foodnstuff server. + * // Calculate threadcount of a single hack that would take $100k from n00dles + * const hackThreads = hackAnalyzeThreads("n00dles", 1e5); + * + * // Launching a script requires an integer thread count. The below would take less than the targeted $100k. + * ns.run("noodleHack.js", Math.floor(hackThreads)) + * * ``` * @param host - Hostname of the target server to analyze. * @param hackAmount - Amount of money you want to hack from the server. @@ -4721,16 +4739,11 @@ export interface NS { * * Returns the part of the specified server’s money you will steal with a single thread hack. * + * Like other basic hacking analysis functions, this calculation uses the current status of the player and server. + * To calculate using hypothetical server or player status, obtain access to the Formulas API and use {@link HackingFormulas.hackPercent | formulas.hacking.hackPercent}. + * * @example - * ```ts - * // NS1: - * //For example, assume the following returns 0.01: - * var hackAmount = hackAnalyze("foodnstuff"); - * //This means that if hack the foodnstuff server using a single thread, then you will steal 1%, or 0.01 of its total money. If you hack using N threads, then you will steal N*0.01 times its total money. - * ``` - * @example - * ```ts - * // NS2: + * ```js * //For example, assume the following returns 0.01: * const hackAmount = ns.hackAnalyze("foodnstuff"); * //This means that if hack the foodnstuff server using a single thread, then you will steal 1%, or 0.01 of its total money. If you hack using N threads, then you will steal N*0.01 times its total money. @@ -4762,43 +4775,45 @@ export interface NS { * * This returned value is in decimal form, not percentage. * + * Like other basic hacking analysis functions, this calculation uses the current status of the player and server. + * To calculate using hypothetical server or player status, obtain access to the Formulas API and use {@link HackingFormulas.hackChance | formulas.hacking.hackChance}. + * * @param host - Hostname of the target server. * @returns The chance you have of successfully hacking the target server. */ hackAnalyzeChance(host: string): number; /** - * Calculate the number of grow threads needed to grow a server by a certain multiplier. + * Calculate the number of grow threads needed for a given multiplicative growth factor. * @remarks * RAM cost: 1 GB * - * This function returns the number of “growths” needed in order to increase - * the amount of money available on the specified server by the specified amount. - * The specified amount is multiplicative and is in decimal form, not percentage. + * This function returns the total decimal number of {@link NS.grow | grow} threads needed in order to multiply the + * money available on the specified server by a given multiplier, if all threads are executed at the server's current + * security level, regardless of how many threads are assigned to each call. * - * Due to limitations of mathematics, this function won't be the true value, but an approximation. + * Note that there is also an additive factor that is applied before the multiplier. Each {@link NS.grow | grow} call + * will add $1 to the host's money for each thread before applying the multiplier for its thread count. This means + * that at extremely low starting money, fewer threads would be needed to apply the same effective multiplier than + * what is calculated by growthAnalyze. * - * Warning: The value returned by this function isn’t necessarily a whole number. + * Like other basic hacking analysis functions, this calculation uses the current status of the player and server. + * To calculate using hypothetical server or player status, obtain access to the Formulas API and use {@link HackingFormulas.growThreads | formulas.hacking.growThreads}. * * @example - * ```ts - * // NS1: - * //For example, if you want to determine how many grow calls you need to double the amount of money on foodnstuff, you would use: - * var growTimes = growthAnalyze("foodnstuff", 2); - * //If this returns 100, then this means you need to call grow 100 times in order to double the money (or once with 100 threads). - * ``` - * @example - * ```ts - * // NS2: - * //For example, if you want to determine how many grow calls you need to double the amount of money on foodnstuff, you would use: - * const growTimes = ns.growthAnalyze("foodnstuff", 2); - * //If this returns 100, then this means you need to call grow 100 times in order to double the money (or once with 100 threads). + * ```js + * // calculate number of grow threads to apply 2x growth multiplier on n00dles (does not include the additive growth). + * const growThreads = ns.growthAnalyze("n00dles", 2); + * + * // When using the thread count to launch a script, it needs to be converted to an integer. + * ns.run("noodleGrow.js", Math.ceil(growThreads)); * ``` * @param host - Hostname of the target server. - * @param growthAmount - Multiplicative factor by which the server is grown. Decimal form. - * @returns The amount of grow calls needed to grow the specified server by the specified amount. + * @param multiplier - Multiplier that will be applied to a server's money after applying additive growth. Decimal form. + * @param cores - Number of cores on the host running the grow function. Optional, defaults to 1. + * @returns Decimal number of grow threads needed for the specified multiplicative growth factor (does not include additive growth). */ - growthAnalyze(host: string, growthAmount: number, cores?: number): number; + growthAnalyze(host: string, multiplier: number, cores?: number): number; /** * Calculate the security increase for a number of threads. diff --git a/src/Server/ServerHelpers.ts b/src/Server/ServerHelpers.ts index 3db62bbe2..d15f84d7d 100644 --- a/src/Server/ServerHelpers.ts +++ b/src/Server/ServerHelpers.ts @@ -8,7 +8,7 @@ import { CONSTANTS } from "../Constants"; import { Player } from "@player"; import { Programs } from "../Programs/Programs"; import { LiteratureNames } from "../Literature/data/LiteratureNames"; - +import { Person as IPerson } from "@nsdefs"; import { isValidNumber } from "../utils/helpers/isValidNumber"; /** @@ -41,8 +41,8 @@ export function safelyCreateUniqueServer(params: IConstructorParams): Server { } /** - * Returns the number of "growth cycles" needed to grow the specified server by the - * specified amount. + * Returns the number of "growth cycles" needed to grow the specified server by the specified amount, taking into + * account only the multiplicative factor. Does not account for the additive $1/thread. Only used for growthAnalyze. * @param server - Server being grown * @param growth - How much the server is being grown by, in DECIMAL form (e.g. 1.5 rather than 50) * @param p - Reference to Player object @@ -70,26 +70,24 @@ export function numCycleForGrowth(server: Server, growth: number, cores = 1): nu /** * This function calculates the number of threads needed to grow a server from one $amount to a higher $amount - * (ie, how many threads to grow this server from $200 to $600 for example). Used primarily for a formulas (or possibly growthAnalyze) - * type of application. It lets you "theorycraft" and easily ask what-if type questions. It's also the one that implements the - * main thread calculation algorithm, and so is the function all helper functions should call. + * (ie, how many threads to grow this server from $200 to $600 for example). * It protects the inputs (so putting in INFINITY for targetMoney will use moneyMax, putting in a negative for start will use 0, etc.) * @param server - Server being grown * @param targetMoney - How much you want the server grown TO (not by), for instance, to grow from 200 to 600, input 600 * @param startMoney - How much you are growing the server from, for instance, to grow from 200 to 600, input 200 * @param cores - Number of cores on the host performing grow - * @returns Number of "growth cycles" needed + * @returns Integer threads needed by a single ns.grow call to reach targetMoney from startMoney. */ -export function numCycleForGrowthCorrected(server: Server, targetMoney: number, startMoney: number, cores = 1): number { - if (startMoney < 0) { - startMoney = 0; - } // servers "can't" have less than 0 dollars on them - if (targetMoney > server.moneyMax) { - targetMoney = server.moneyMax; - } // can't grow a server to more than its moneyMax - if (targetMoney <= startMoney) { - return 0; - } // no growth --> no threads +export function numCycleForGrowthCorrected( + server: Server, + targetMoney: number, + startMoney: number, + cores = 1, + person: IPerson = Player, +): number { + if (startMoney < 0) startMoney = 0; // servers "can't" have less than 0 dollars on them + if (targetMoney > server.moneyMax) targetMoney = server.moneyMax; // can't grow a server to more than its moneyMax + if (targetMoney <= startMoney) return 0; // no growth --> no threads // exponential base adjusted by security const adjGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; @@ -99,7 +97,7 @@ export function numCycleForGrowthCorrected(server: Server, targetMoney: number, const serverGrowthPercentage = server.serverGrowth / 100.0; const coreMultiplier = 1 + (cores - 1) / 16; const threadMultiplier = - serverGrowthPercentage * Player.mults.hacking_grow * coreMultiplier * BitNodeMultipliers.ServerGrowthRate; + serverGrowthPercentage * person.mults.hacking_grow * coreMultiplier * BitNodeMultipliers.ServerGrowthRate; /* To understand what is done below we need to do some math. I hope the explanation is clear enough. * First of, the names will be shortened for ease of manipulation: