diff --git a/src/Hacknet/HacknetHelpers.tsx b/src/Hacknet/HacknetHelpers.tsx index ecd91719f..3a7cf288c 100644 --- a/src/Hacknet/HacknetHelpers.tsx +++ b/src/Hacknet/HacknetHelpers.tsx @@ -467,7 +467,7 @@ export function updateHashManagerCapacity(player: IPlayer): void { player.hashManager.updateCapacity(total); } -export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: string): boolean { +export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: string, count = 1): boolean { if (!(player.hashManager instanceof HashManager)) { console.error(`Player does not have a HashManager`); return false; @@ -475,21 +475,21 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: // HashManager handles the transaction. This just needs to actually implement // the effects of the upgrade - if (player.hashManager.upgrade(upgName)) { + if (player.hashManager.upgrade(upgName, count)) { const upg = HashUpgrades[upgName]; switch (upgName) { case "Sell for Money": { - player.gainMoney(upg.value, "hacknet"); + player.gainMoney(upg.value * count, "hacknet"); break; } case "Sell for Corporation Funds": { const corp = player.corporation; if (corp === null) { - player.hashManager.refundUpgrade(upgName); + player.hashManager.refundUpgrade(upgName, count); return false; } - corp.funds = corp.funds + upg.value; + corp.funds = corp.funds + upg.value * count; break; } case "Reduce Minimum Security": { @@ -497,13 +497,13 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: const target = GetServer(upgTarget); if (target == null) { console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); - return false; + throw new Error(`'${upgTarget}' is not a server.`); } if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`); - target.changeMinimumSecurity(upg.value, true); + target.changeMinimumSecurity(upg.value ** count, true); } catch (e) { - player.hashManager.refundUpgrade(upgName); + player.hashManager.refundUpgrade(upgName, count); return false; } break; @@ -513,13 +513,16 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: const target = GetServer(upgTarget); if (target == null) { console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); - return false; + throw new Error(`'${upgTarget}' is not a server.`); } if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`); - target.changeMaximumMoney(upg.value); + //Manually loop the change so as to properly handle the softcap + for (let i = 0; i < count; i++) { + target.changeMaximumMoney(upg.value); + } } catch (e) { - player.hashManager.refundUpgrade(upgName); + player.hashManager.refundUpgrade(upgName, count); return false; } break; @@ -536,11 +539,11 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: // This will throw if player doesn't have a corporation const corp = player.corporation; if (corp === null) { - player.hashManager.refundUpgrade(upgName); + player.hashManager.refundUpgrade(upgName, count); return false; } for (const division of corp.divisions) { - division.sciResearch.qty += upg.value; + division.sciResearch.qty += upg.value * count; } break; } @@ -548,30 +551,31 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: // This will throw if player isnt in Bladeburner const bladeburner = player.bladeburner; if (bladeburner === null) { - player.hashManager.refundUpgrade(upgName); + player.hashManager.refundUpgrade(upgName, count); return false; } - bladeburner.changeRank(player, upg.value); + bladeburner.changeRank(player, upg.value * count); break; } case "Exchange for Bladeburner SP": { // This will throw if player isnt in Bladeburner const bladeburner = player.bladeburner; if (bladeburner === null) { - player.hashManager.refundUpgrade(upgName); + player.hashManager.refundUpgrade(upgName, count); return false; } - bladeburner.skillPoints += upg.value; + bladeburner.skillPoints += upg.value * count; break; } case "Generate Coding Contract": { - generateRandomContract(); + for (let i = 0; i < count; i++) { + generateRandomContract(); + } break; } default: console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`); - player.hashManager.refundUpgrade(upgName); return false; } diff --git a/src/Hacknet/HashManager.ts b/src/Hacknet/HashManager.ts index 45560d08b..8d12051d4 100644 --- a/src/Hacknet/HashManager.ts +++ b/src/Hacknet/HashManager.ts @@ -73,7 +73,7 @@ export class HashManager { /** * Get the cost (in hashes) of an upgrade */ - getUpgradeCost(upgName: string): number { + getUpgradeCost(upgName: string, count = 1): number { const upg = this.getUpgrade(upgName); const currLevel = this.upgrades[upgName]; if (upg == null || currLevel == null) { @@ -81,7 +81,7 @@ export class HashManager { return Infinity; } - return upg.getCost(currLevel); + return upg.getCost(currLevel, count); } prestige(): void { @@ -97,11 +97,11 @@ export class HashManager { /** * Reverts an upgrade and refunds the hashes used to buy it */ - refundUpgrade(upgName: string): void { + refundUpgrade(upgName: string, count = 1): void { const upg = HashUpgrades[upgName]; // Reduce the level first, so we get the right cost - --this.upgrades[upgName]; + this.upgrades[upgName] -= count; const currLevel = this.upgrades[upgName]; if (upg == null || currLevel == null || currLevel < 0) { @@ -109,7 +109,7 @@ export class HashManager { return; } - const cost = upg.getCost(currLevel); + const cost = upg.getCost(currLevel, count); this.hashes += cost; } @@ -137,21 +137,21 @@ export class HashManager { * Returns boolean indicating whether or not the upgrade was successfully purchased * Note that this does NOT actually implement the effect */ - upgrade(upgName: string): boolean { + upgrade(upgName: string, count = 1): boolean { const upg = HashUpgrades[upgName]; if (upg == null) { console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`); return false; } - const cost = this.getUpgradeCost(upgName); + const cost = this.getUpgradeCost(upgName, count); if (this.hashes < cost) { return false; } this.hashes -= cost; - ++this.upgrades[upgName]; + this.upgrades[upgName] += count; return true; } diff --git a/src/Hacknet/HashUpgrade.ts b/src/Hacknet/HashUpgrade.ts index 4dfd4742d..9f53f333a 100644 --- a/src/Hacknet/HashUpgrade.ts +++ b/src/Hacknet/HashUpgrade.ts @@ -62,11 +62,15 @@ export class HashUpgrade { // Functions that returns the UI element to display the effect of this upgrade. effectText: (level: number) => JSX.Element | null = () => null; - getCost(levels: number): number { + getCost(currentLevel: number, count = 1): number { if (typeof this.cost === "number") { - return this.cost; + return this.cost * count; } - return Math.round((levels + 1) * this.costPerLevel); + //This formula is equivalent to + //(currentLevel + 1) * this.costPerLevel + //being performed repeatedly + const collapsedSum = 0.5 * count * (count + 2 * currentLevel + 1); + return this.costPerLevel * collapsedSum; } } diff --git a/src/NetscriptFunctions/Hacknet.ts b/src/NetscriptFunctions/Hacknet.ts index 877b61d31..8454c4789 100644 --- a/src/NetscriptFunctions/Hacknet.ts +++ b/src/NetscriptFunctions/Hacknet.ts @@ -188,23 +188,25 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript): I }, hashCost: (ctx: NetscriptContext) => - (_upgName: unknown): number => { + (_upgName: unknown, _count: unknown = 1): number => { const upgName = ctx.helper.string("upgName", _upgName); + const count = ctx.helper.number("count", _count); if (!hasHacknetServers(player)) { return Infinity; } - return player.hashManager.getUpgradeCost(upgName); + return player.hashManager.getUpgradeCost(upgName, count); }, spendHashes: (ctx: NetscriptContext) => - (_upgName: unknown, _upgTarget: unknown = ""): boolean => { + (_upgName: unknown, _upgTarget: unknown = "", _count: unknown = 1): boolean => { const upgName = ctx.helper.string("upgName", _upgName); const upgTarget = ctx.helper.string("upgTarget", _upgTarget); + const count = ctx.helper.number("count", _count); if (!hasHacknetServers(player)) { return false; } - return purchaseHashUpgrade(player, upgName, upgTarget); + return purchaseHashUpgrade(player, upgName, upgTarget, count); }, getHashUpgrades: () => (): string[] => { if (!hasHacknetServers(player)) { diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 3aa3ea1d1..4358f82f8 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -2675,9 +2675,10 @@ export interface Hacknet { * } * ``` * @param upgName - Name of the upgrade of Hacknet Node. + * @param count - Number of upgrades to buy at once. Defaults to 1 if not specified. * @returns Number of hashes required for the specified upgrade. */ - hashCost(upgName: string): number; + hashCost(upgName: string, count?: number): number; /** * Purchase a hash upgrade. @@ -2707,9 +2708,11 @@ export interface Hacknet { * ``` * @param upgName - Name of the upgrade of Hacknet Node. * @param upgTarget - Object to which upgrade applies. Required for certain upgrades. + * @param count - Number of upgrades to buy at once. Defaults to 1 if not specified. + * For compatability reasons, upgTarget must be specified, even if it is not used, in order to specify count. * @returns True if the upgrade is successfully purchased, and false otherwise.. */ - spendHashes(upgName: string, upgTarget?: string): boolean; + spendHashes(upgName: string, upgTarget?: string, count?: number): boolean; /** * Get the list of hash upgrades