Merge pull request #3810 from Undeemiss/multi-hash-sell

BLADEBURNER: Add batch functionality to NS spendHashes API
This commit is contained in:
hydroflame 2022-07-21 01:39:02 -04:00 committed by GitHub
commit 0d95533470
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 49 additions and 36 deletions

@ -467,7 +467,7 @@ export function updateHashManagerCapacity(player: IPlayer): void {
player.hashManager.updateCapacity(total); 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)) { if (!(player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`); console.error(`Player does not have a HashManager`);
return false; return false;
@ -475,21 +475,21 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
// HashManager handles the transaction. This just needs to actually implement // HashManager handles the transaction. This just needs to actually implement
// the effects of the upgrade // the effects of the upgrade
if (player.hashManager.upgrade(upgName)) { if (player.hashManager.upgrade(upgName, count)) {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
switch (upgName) { switch (upgName) {
case "Sell for Money": { case "Sell for Money": {
player.gainMoney(upg.value, "hacknet"); player.gainMoney(upg.value * count, "hacknet");
break; break;
} }
case "Sell for Corporation Funds": { case "Sell for Corporation Funds": {
const corp = player.corporation; const corp = player.corporation;
if (corp === null) { if (corp === null) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName, count);
return false; return false;
} }
corp.funds = corp.funds + upg.value; corp.funds = corp.funds + upg.value * count;
break; break;
} }
case "Reduce Minimum Security": { case "Reduce Minimum Security": {
@ -497,13 +497,13 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
const target = GetServer(upgTarget); const target = GetServer(upgTarget);
if (target == null) { if (target == null) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); 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.`); 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) { } catch (e) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName, count);
return false; return false;
} }
break; break;
@ -513,13 +513,16 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
const target = GetServer(upgTarget); const target = GetServer(upgTarget);
if (target == null) { if (target == null) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); 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.`); if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`);
//Manually loop the change so as to properly handle the softcap
for (let i = 0; i < count; i++) {
target.changeMaximumMoney(upg.value); target.changeMaximumMoney(upg.value);
}
} catch (e) { } catch (e) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName, count);
return false; return false;
} }
break; break;
@ -536,11 +539,11 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
// This will throw if player doesn't have a corporation // This will throw if player doesn't have a corporation
const corp = player.corporation; const corp = player.corporation;
if (corp === null) { if (corp === null) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName, count);
return false; return false;
} }
for (const division of corp.divisions) { for (const division of corp.divisions) {
division.sciResearch.qty += upg.value; division.sciResearch.qty += upg.value * count;
} }
break; break;
} }
@ -548,30 +551,31 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
// This will throw if player isnt in Bladeburner // This will throw if player isnt in Bladeburner
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) { if (bladeburner === null) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName, count);
return false; return false;
} }
bladeburner.changeRank(player, upg.value); bladeburner.changeRank(player, upg.value * count);
break; break;
} }
case "Exchange for Bladeburner SP": { case "Exchange for Bladeburner SP": {
// This will throw if player isnt in Bladeburner // This will throw if player isnt in Bladeburner
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) { if (bladeburner === null) {
player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName, count);
return false; return false;
} }
bladeburner.skillPoints += upg.value; bladeburner.skillPoints += upg.value * count;
break; break;
} }
case "Generate Coding Contract": { case "Generate Coding Contract": {
for (let i = 0; i < count; i++) {
generateRandomContract(); generateRandomContract();
}
break; break;
} }
default: default:
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`); console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`);
player.hashManager.refundUpgrade(upgName);
return false; return false;
} }

@ -73,7 +73,7 @@ export class HashManager {
/** /**
* Get the cost (in hashes) of an upgrade * Get the cost (in hashes) of an upgrade
*/ */
getUpgradeCost(upgName: string): number { getUpgradeCost(upgName: string, count = 1): number {
const upg = this.getUpgrade(upgName); const upg = this.getUpgrade(upgName);
const currLevel = this.upgrades[upgName]; const currLevel = this.upgrades[upgName];
if (upg == null || currLevel == null) { if (upg == null || currLevel == null) {
@ -81,7 +81,7 @@ export class HashManager {
return Infinity; return Infinity;
} }
return upg.getCost(currLevel); return upg.getCost(currLevel, count);
} }
prestige(): void { prestige(): void {
@ -97,11 +97,11 @@ export class HashManager {
/** /**
* Reverts an upgrade and refunds the hashes used to buy it * 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]; const upg = HashUpgrades[upgName];
// Reduce the level first, so we get the right cost // Reduce the level first, so we get the right cost
--this.upgrades[upgName]; this.upgrades[upgName] -= count;
const currLevel = this.upgrades[upgName]; const currLevel = this.upgrades[upgName];
if (upg == null || currLevel == null || currLevel < 0) { if (upg == null || currLevel == null || currLevel < 0) {
@ -109,7 +109,7 @@ export class HashManager {
return; return;
} }
const cost = upg.getCost(currLevel); const cost = upg.getCost(currLevel, count);
this.hashes += cost; this.hashes += cost;
} }
@ -137,21 +137,21 @@ export class HashManager {
* Returns boolean indicating whether or not the upgrade was successfully purchased * Returns boolean indicating whether or not the upgrade was successfully purchased
* Note that this does NOT actually implement the effect * Note that this does NOT actually implement the effect
*/ */
upgrade(upgName: string): boolean { upgrade(upgName: string, count = 1): boolean {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
if (upg == null) { if (upg == null) {
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`); console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
return false; return false;
} }
const cost = this.getUpgradeCost(upgName); const cost = this.getUpgradeCost(upgName, count);
if (this.hashes < cost) { if (this.hashes < cost) {
return false; return false;
} }
this.hashes -= cost; this.hashes -= cost;
++this.upgrades[upgName]; this.upgrades[upgName] += count;
return true; return true;
} }

@ -62,11 +62,15 @@ export class HashUpgrade {
// Functions that returns the UI element to display the effect of this upgrade. // Functions that returns the UI element to display the effect of this upgrade.
effectText: (level: number) => JSX.Element | null = () => null; effectText: (level: number) => JSX.Element | null = () => null;
getCost(levels: number): number { getCost(currentLevel: number, count = 1): number {
if (typeof this.cost === "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;
} }
} }

@ -188,23 +188,25 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript): I
}, },
hashCost: hashCost:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
(_upgName: unknown): number => { (_upgName: unknown, _count: unknown = 1): number => {
const upgName = ctx.helper.string("upgName", _upgName); const upgName = ctx.helper.string("upgName", _upgName);
const count = ctx.helper.number("count", _count);
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return Infinity; return Infinity;
} }
return player.hashManager.getUpgradeCost(upgName); return player.hashManager.getUpgradeCost(upgName, count);
}, },
spendHashes: spendHashes:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
(_upgName: unknown, _upgTarget: unknown = ""): boolean => { (_upgName: unknown, _upgTarget: unknown = "", _count: unknown = 1): boolean => {
const upgName = ctx.helper.string("upgName", _upgName); const upgName = ctx.helper.string("upgName", _upgName);
const upgTarget = ctx.helper.string("upgTarget", _upgTarget); const upgTarget = ctx.helper.string("upgTarget", _upgTarget);
const count = ctx.helper.number("count", _count);
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return false; return false;
} }
return purchaseHashUpgrade(player, upgName, upgTarget); return purchaseHashUpgrade(player, upgName, upgTarget, count);
}, },
getHashUpgrades: () => (): string[] => { getHashUpgrades: () => (): string[] => {
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {

@ -2675,9 +2675,10 @@ export interface Hacknet {
* } * }
* ``` * ```
* @param upgName - Name of the upgrade of Hacknet Node. * @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. * @returns Number of hashes required for the specified upgrade.
*/ */
hashCost(upgName: string): number; hashCost(upgName: string, count?: number): number;
/** /**
* Purchase a hash upgrade. * Purchase a hash upgrade.
@ -2707,9 +2708,11 @@ export interface Hacknet {
* ``` * ```
* @param upgName - Name of the upgrade of Hacknet Node. * @param upgName - Name of the upgrade of Hacknet Node.
* @param upgTarget - Object to which upgrade applies. Required for certain upgrades. * @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.. * @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 * Get the list of hash upgrades