MISC: Print logs when ns.hacknet.spendHashes fails and update param type of APIs using hash upgrade (#2145)

This commit is contained in:
catloversg
2025-05-21 22:38:39 +07:00
committed by GitHub
parent 90555a64e6
commit 47153bd31c
19 changed files with 252 additions and 197 deletions

View File

@@ -9,14 +9,14 @@ Get the level of a hash upgrade.
**Signature:** **Signature:**
```typescript ```typescript
getHashUpgradeLevel(upgName: string): number; getHashUpgradeLevel(upgName: HacknetServerHashUpgrade): number;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| upgName | string | | | upgName | [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md) | |
**Returns:** **Returns:**

View File

@@ -9,11 +9,11 @@ Get the list of hash upgrades
**Signature:** **Signature:**
```typescript ```typescript
getHashUpgrades(): string[]; getHashUpgrades(): HacknetServerHashUpgrade[];
``` ```
**Returns:** **Returns:**
string\[\] [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md)<!-- -->\[\]
An array containing the available upgrades An array containing the available upgrades

View File

@@ -9,14 +9,14 @@ Get the cost of a hash upgrade.
**Signature:** **Signature:**
```typescript ```typescript
hashCost(upgName: string, count?: number): number; hashCost(upgName: HacknetServerHashUpgrade, count?: number): number;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| upgName | string | Name of the upgrade of Hacknet Node. | | upgName | [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md) | Name of the upgrade using hash of Hacknet Server. |
| count | number | _(Optional)_ Number of upgrades to buy at once. Defaults to 1 if not specified. | | count | number | _(Optional)_ Number of upgrades to buy at once. Defaults to 1 if not specified. |
**Returns:** **Returns:**

View File

@@ -9,14 +9,14 @@ Purchase a hash upgrade.
**Signature:** **Signature:**
```typescript ```typescript
spendHashes(upgName: string, upgTarget?: string, count?: number): boolean; spendHashes(upgName: HacknetServerHashUpgrade, upgTarget?: string, count?: number): boolean;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| upgName | string | Name of the upgrade of Hacknet Node. | | upgName | [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md) | Name of the upgrade using hash of Hacknet Server. |
| upgTarget | string | _(Optional)_ Object to which upgrade applies. Required for certain upgrades. | | upgTarget | string | _(Optional)_ Object to which upgrade applies. Required for certain upgrades. |
| count | number | _(Optional)_ Number of upgrades to buy at once. Must be a non-negative integer. Defaults to 1 if not specified. For compatibility reasons, upgTarget must be specified, even if it is not used, in order to specify count. | | count | number | _(Optional)_ Number of upgrades to buy at once. Must be a non-negative integer. Defaults to 1 if not specified. For compatibility reasons, upgTarget must be specified, even if it is not used, in order to specify count. |

View File

@@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md)
## HacknetServerHashUpgrade type
**Signature:**
```typescript
type HacknetServerHashUpgrade =
| "Sell for Money"
| "Sell for Corporation Funds"
| "Reduce Minimum Security"
| "Increase Maximum Money"
| "Improve Studying"
| "Improve Gym Training"
| "Exchange for Corporation Research"
| "Exchange for Bladeburner Rank"
| "Exchange for Bladeburner SP"
| "Generate Coding Contract"
| "Company Favor";
```

View File

@@ -9,14 +9,14 @@ Calculate hash cost of an upgrade.
**Signature:** **Signature:**
```typescript ```typescript
hashUpgradeCost(upgName: string, level: number): number; hashUpgradeCost(upgName: HacknetServerHashUpgrade, level: number): number;
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| upgName | string | name of the upgrade | | upgName | [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md) | name of the upgrade |
| level | number | level of the upgrade | | level | number | level of the upgrade |
**Returns:** **Returns:**

View File

@@ -182,6 +182,7 @@
| [GymLocationName](./bitburner.gymlocationname.md) | | | [GymLocationName](./bitburner.gymlocationname.md) | |
| [GymLocationNameEnumType](./bitburner.gymlocationnameenumtype.md) | Locations of gym | | [GymLocationNameEnumType](./bitburner.gymlocationnameenumtype.md) | Locations of gym |
| [GymType](./bitburner.gymtype.md) | | | [GymType](./bitburner.gymtype.md) | |
| [HacknetServerHashUpgrade](./bitburner.hacknetserverhashupgrade.md) | |
| [JobField](./bitburner.jobfield.md) | | | [JobField](./bitburner.jobfield.md) | |
| [JobFieldEnumType](./bitburner.jobfieldenumtype.md) | | | [JobFieldEnumType](./bitburner.jobfieldenumtype.md) | |
| [JobName](./bitburner.jobname.md) | | | [JobName](./bitburner.jobname.md) | |

View File

@@ -15,3 +15,4 @@ export * from "./StockMarket/Enums";
export * from "./ui/Enums"; export * from "./ui/Enums";
export * from "./Work/Enums"; export * from "./Work/Enums";
export * from "./CodingContract/Enums"; export * from "./CodingContract/Enums";
export * from "./Hacknet/Enums";

13
src/Hacknet/Enums.ts Normal file
View File

@@ -0,0 +1,13 @@
export enum HashUpgradeEnum {
SellForMoney = "Sell for Money",
SellForCorporationFunds = "Sell for Corporation Funds",
ReduceMinimumSecurity = "Reduce Minimum Security",
IncreaseMaximumMoney = "Increase Maximum Money",
ImproveStudying = "Improve Studying",
ImproveGymTraining = "Improve Gym Training",
ExchangeForCorporationResearch = "Exchange for Corporation Research",
ExchangeForBladeburnerRank = "Exchange for Bladeburner Rank",
ExchangeForBladeburnerSP = "Exchange for Bladeburner SP",
GenerateCodingContract = "Generate Coding Contract",
CompanyFavor = "Company Favor",
}

View File

@@ -25,6 +25,9 @@ import { Companies } from "../Company/Companies";
import { isMember } from "../utils/EnumHelper"; import { isMember } from "../utils/EnumHelper";
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils"; import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
import { checkServerOwnership, ServerOwnershipType } from "../Server/ServerHelpers"; import { checkServerOwnership, ServerOwnershipType } from "../Server/ServerHelpers";
import { Result } from "../types";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { HashUpgradeEnum } from "./Enums";
// Returns a boolean indicating whether the player has Hacknet Servers // Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes) // (the upgraded form of Hacknet Nodes)
@@ -415,7 +418,7 @@ function processAllHacknetServerEarnings(numCycles: number): number {
const wastedHashes = Player.hashManager.storeHashes(hashes); const wastedHashes = Player.hashManager.storeHashes(hashes);
if (wastedHashes > 0) { if (wastedHashes > 0) {
const upgrade = HashUpgrades["Sell for Money"]; const upgrade = HashUpgrades[HashUpgradeEnum.SellForMoney];
if (upgrade === null) throw new Error("Could not get the hash upgrade"); if (upgrade === null) throw new Error("Could not get the hash upgrade");
if (!upgrade.cost) throw new Error("Upgrade is not properly configured"); if (!upgrade.cost) throw new Error("Upgrade is not properly configured");
@@ -460,139 +463,137 @@ export function updateHashManagerCapacity(): void {
Player.hashManager.updateCapacity(total); Player.hashManager.updateCapacity(total);
} }
export function purchaseHashUpgrade(upgName: string, upgTarget: string, count = 1): boolean { function applyEffectOfHashUpgrade(upgName: HashUpgradeEnum, upgTarget: string, count = 1): Result {
if (!(Player.hashManager instanceof HashManager)) { const upg = HashUpgrades[upgName];
console.error(`Player does not have a HashManager`);
return false;
}
// HashManager handles the transaction. This just needs to actually implement switch (upgName) {
// the effects of the upgrade case HashUpgradeEnum.SellForMoney: {
if (Player.hashManager.upgrade(upgName, count)) { Player.gainMoney(upg.value * count, "hacknet");
const upg = HashUpgrades[upgName]; break;
switch (upgName) {
case "Sell for Money": {
Player.gainMoney(upg.value * count, "hacknet");
break;
}
case "Sell for Corporation Funds": {
const corp = Player.corporation;
if (corp === null) {
Player.hashManager.refundUpgrade(upgName, count);
return false;
}
corp.gainFunds(upg.value * count, "hacknet");
break;
}
case "Reduce Minimum Security": {
try {
const target = GetServer(upgTarget);
if (target == null) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
throw new Error(`'${upgTarget}' is not a server.`);
}
if (!(target instanceof Server)) {
throw new Error(`'${upgTarget}' is not a normal server.`);
}
if (!checkServerOwnership(target, ServerOwnershipType.Foreign)) {
throw new Error(
`'${upgTarget}' is not a valid target. You can only perform this action on servers that you do not own.`,
);
}
target.changeMinimumSecurity(upg.value ** count, true);
} catch (e) {
Player.hashManager.refundUpgrade(upgName, count);
return false;
}
break;
}
case "Increase Maximum Money": {
try {
const target = GetServer(upgTarget);
if (target == null) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
throw new Error(`'${upgTarget}' is not a server.`);
}
if (!(target instanceof Server)) {
throw new Error(`'${upgTarget}' is not a normal server.`);
}
if (!checkServerOwnership(target, ServerOwnershipType.Foreign)) {
throw new Error(
`'${upgTarget}' is not a valid target. You can only perform this action on servers that you do not own.`,
);
}
//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, count);
return false;
}
break;
}
case "Improve Studying": {
// Multiplier is handled by HashManager
break;
}
case "Improve Gym Training": {
// Multiplier is handled by HashManager
break;
}
case "Exchange for Corporation Research": {
const corp = Player.corporation;
if (corp === null) {
Player.hashManager.refundUpgrade(upgName, count);
return false;
}
for (const division of corp.divisions.values()) {
division.researchPoints += upg.value * count;
}
break;
}
case "Exchange for Bladeburner Rank": {
const bladeburner = Player.bladeburner;
if (bladeburner === null) {
Player.hashManager.refundUpgrade(upgName, count);
return false;
}
bladeburner.changeRank(Player, upg.value * count);
break;
}
case "Exchange for Bladeburner SP": {
const bladeburner = Player.bladeburner;
if (bladeburner === null) {
Player.hashManager.refundUpgrade(upgName, count);
return false;
}
bladeburner.skillPoints += upg.value * count;
break;
}
case "Generate Coding Contract": {
for (let i = 0; i < count; i++) {
generateRandomContract();
}
break;
}
case "Company Favor": {
if (!isMember("CompanyName", upgTarget)) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
throw new Error(`'${upgTarget}' is not a company.`);
}
Companies[upgTarget].setFavor(Companies[upgTarget].favor + 5 * count);
break;
}
default:
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`);
return false;
} }
case HashUpgradeEnum.SellForCorporationFunds: {
const corp = Player.corporation;
if (corp === null) {
return { success: false, message: "You have not created a corporation." };
}
corp.gainFunds(upg.value * count, "hacknet");
break;
}
case HashUpgradeEnum.ReduceMinimumSecurity: {
const target = GetServer(upgTarget);
if (target == null) {
return { success: false, message: `'${upgTarget}' is not a server.` };
}
if (!(target instanceof Server)) {
return { success: false, message: `'${upgTarget}' is not a normal server.` };
}
if (!checkServerOwnership(target, ServerOwnershipType.Foreign)) {
return {
success: false,
message: `'${upgTarget}' is not a valid target. You can only perform this action on servers that you do not own.`,
};
}
return true; target.changeMinimumSecurity(upg.value ** count, true);
break;
}
case HashUpgradeEnum.IncreaseMaximumMoney: {
const target = GetServer(upgTarget);
if (target == null) {
return { success: false, message: `'${upgTarget}' is not a server.` };
}
if (!(target instanceof Server)) {
return { success: false, message: `'${upgTarget}' is not a normal server.` };
}
if (!checkServerOwnership(target, ServerOwnershipType.Foreign)) {
return {
success: false,
message: `'${upgTarget}' is not a valid target. You can only perform this action on servers that you do not own.`,
};
}
//Manually loop the change so as to properly handle the softcap
for (let i = 0; i < count; i++) {
target.changeMaximumMoney(upg.value);
}
break;
}
case HashUpgradeEnum.ImproveStudying: {
// Multiplier is handled by HashManager
break;
}
case HashUpgradeEnum.ImproveGymTraining: {
// Multiplier is handled by HashManager
break;
}
case HashUpgradeEnum.ExchangeForCorporationResearch: {
const corp = Player.corporation;
if (corp === null) {
return { success: false, message: "You have not created a corporation." };
}
for (const division of corp.divisions.values()) {
division.researchPoints += upg.value * count;
}
break;
}
case HashUpgradeEnum.ExchangeForBladeburnerRank: {
const bladeburner = Player.bladeburner;
if (bladeburner === null) {
return { success: false, message: "You have not joined Bladeburner." };
}
bladeburner.changeRank(Player, upg.value * count);
break;
}
case HashUpgradeEnum.ExchangeForBladeburnerSP: {
const bladeburner = Player.bladeburner;
if (bladeburner === null) {
return { success: false, message: "You have not joined Bladeburner." };
}
bladeburner.skillPoints += upg.value * count;
break;
}
case HashUpgradeEnum.GenerateCodingContract: {
for (let i = 0; i < count; i++) {
generateRandomContract();
}
break;
}
case HashUpgradeEnum.CompanyFavor: {
if (!isMember("CompanyName", upgTarget)) {
return { success: false, message: `'${upgTarget}' is not a company.` };
}
Companies[upgTarget].setFavor(Companies[upgTarget].favor + 5 * count);
break;
}
default: {
// Verify that the switch statement is exhaustive.
const __a: never = upgName;
}
} }
return false; return { success: true };
}
export function purchaseHashUpgrade(upgName: HashUpgradeEnum, upgTarget: string, count = 1): Result {
if (!(Player.hashManager instanceof HashManager)) {
exceptionAlert(new Error("Player does not have a HashManager"));
return { success: false, message: "Player does not have a HashManager" };
}
/**
* Spend hashes to buy the upgrade. The hashManager validates and handles the transaction (e.g., checks the upgrade
* name, deducts the hash amount, increases the count of the upgrade).
*/
const upgradeResult = Player.hashManager.upgrade(upgName, count);
if (!upgradeResult.success) {
return upgradeResult;
}
// Apply the effect. If we cannot apply it, the hashManager will roll back the transaction.
const result = applyEffectOfHashUpgrade(upgName, upgTarget, count);
if (!result.success) {
Player.hashManager.refundUpgrade(upgName, count);
}
return result;
} }

View File

@@ -10,6 +10,8 @@ import { HashUpgrades } from "./HashUpgrades";
import { HashUpgrade } from "./HashUpgrade"; import { HashUpgrade } from "./HashUpgrade";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { Result } from "../types";
import { HashUpgradeEnum } from "./Enums";
export class HashManager { export class HashManager {
// Max number of hashes this can hold. Equal to the sum of capacities of // Max number of hashes this can hold. Equal to the sum of capacities of
@@ -29,7 +31,7 @@ export class HashManager {
} }
/** Generic helper function for getting a multiplier from a HashUpgrade */ /** Generic helper function for getting a multiplier from a HashUpgrade */
getMult(upgName: string): number { getMult(upgName: HashUpgradeEnum): number {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
const currLevel = this.upgrades[upgName]; const currLevel = this.upgrades[upgName];
if (upg == null || currLevel == null) { if (upg == null || currLevel == null) {
@@ -42,19 +44,19 @@ export class HashManager {
/** One of the Hash upgrades improves studying. This returns that multiplier */ /** One of the Hash upgrades improves studying. This returns that multiplier */
getStudyMult(): number { getStudyMult(): number {
const upgName = "Improve Studying"; const upgName = HashUpgradeEnum.ImproveStudying;
return this.getMult(upgName); return this.getMult(upgName);
} }
/** One of the Hash upgrades improves gym training. This returns that multiplier */ /** One of the Hash upgrades improves gym training. This returns that multiplier */
getTrainingMult(): number { getTrainingMult(): number {
const upgName = "Improve Gym Training"; const upgName = HashUpgradeEnum.ImproveGymTraining;
return this.getMult(upgName); return this.getMult(upgName);
} }
getUpgrade(upgName: string): HashUpgrade | null { getUpgrade(upgName: HashUpgradeEnum): HashUpgrade | null {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
if (!upg) { if (!upg) {
console.error(`Invalid Upgrade Name given to HashManager.getUpgrade(): ${upgName}`); console.error(`Invalid Upgrade Name given to HashManager.getUpgrade(): ${upgName}`);
@@ -64,7 +66,7 @@ export class HashManager {
} }
/** Get the cost (in hashes) of an upgrade */ /** Get the cost (in hashes) of an upgrade */
getUpgradeCost(upgName: string, count = 1): number { getUpgradeCost(upgName: HashUpgradeEnum, 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) {
@@ -86,7 +88,7 @@ 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, count = 1): void { refundUpgrade(upgName: HashUpgradeEnum, 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
@@ -126,23 +128,22 @@ 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 function does NOT actually implement the effect. * Note that this function does NOT actually implement the effect.
*/ */
upgrade(upgName: string, count = 1): boolean { upgrade(upgName: HashUpgradeEnum, count = 1): Result {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
if (upg == null) { if (upg == null) {
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`); return { success: false, message: `Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}` };
return false;
} }
const cost = this.getUpgradeCost(upgName, count); const cost = this.getUpgradeCost(upgName, count);
if (this.hashes < cost) { if (this.hashes < cost) {
return false; return { success: false, message: "Not enough hashes" };
} }
this.hashes -= cost; this.hashes -= cost;
this.upgrades[upgName] += count; this.upgrades[upgName] += count;
return true; return { success: true };
} }
//Serialize the current object to a JSON save state. //Serialize the current object to a JSON save state.

View File

@@ -1,4 +1,5 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { HashUpgradeEnum } from "./Enums";
/** Object representing an upgrade that can be purchased with hashes */ /** Object representing an upgrade that can be purchased with hashes */
export interface HashUpgradeParams { export interface HashUpgradeParams {
@@ -7,7 +8,7 @@ export interface HashUpgradeParams {
desc: ReactNode; desc: ReactNode;
hasTargetServer?: boolean; hasTargetServer?: boolean;
hasTargetCompany?: boolean; hasTargetCompany?: boolean;
name: string; name: HashUpgradeEnum;
value: number; value: number;
effectText?: (level: number) => JSX.Element | null; effectText?: (level: number) => JSX.Element | null;
} }
@@ -43,7 +44,7 @@ export class HashUpgrade {
hasTargetCompany = false; hasTargetCompany = false;
/** Name of upgrade */ /** Name of upgrade */
name = ""; name: HashUpgradeEnum;
// Generic value used to indicate the potency/amount of this upgrade's effect // Generic value used to indicate the potency/amount of this upgrade's effect
// The meaning varies between different upgrades // The meaning varies between different upgrades

View File

@@ -2,15 +2,12 @@
* Map of all Hash Upgrades * Map of all Hash Upgrades
* Key = Hash name, Value = HashUpgrade object * Key = Hash name, Value = HashUpgrade object
*/ */
import { HashUpgrade, HashUpgradeParams } from "./HashUpgrade"; import { HashUpgradeEnum } from "./Enums";
import { HashUpgrade } from "./HashUpgrade";
import { HashUpgradesMetadata } from "./data/HashUpgradesMetadata"; import { HashUpgradesMetadata } from "./data/HashUpgradesMetadata";
export const HashUpgrades: Record<string, HashUpgrade> = {}; export const HashUpgrades = {} as Record<HashUpgradeEnum, HashUpgrade>;
function createHashUpgrade(p: HashUpgradeParams): void {
HashUpgrades[p.name] = new HashUpgrade(p);
}
for (const metadata of HashUpgradesMetadata) { for (const metadata of HashUpgradesMetadata) {
createHashUpgrade(metadata); HashUpgrades[metadata.name] = new HashUpgrade(metadata);
} }

View File

@@ -3,6 +3,7 @@ import React from "react";
import { HashUpgradeParams } from "../HashUpgrade"; import { HashUpgradeParams } from "../HashUpgrade";
import { formatInt } from "../../ui/formatNumber"; import { formatInt } from "../../ui/formatNumber";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { HashUpgradeEnum } from "../Enums";
export const HashUpgradesMetadata: HashUpgradeParams[] = [ export const HashUpgradesMetadata: HashUpgradeParams[] = [
{ {
@@ -13,7 +14,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
Sell hashes for <Money money={1e6} /> Sell hashes for <Money money={1e6} />
</> </>
), ),
name: "Sell for Money", name: HashUpgradeEnum.SellForMoney,
effectText: (level: number): JSX.Element | null => ( effectText: (level: number): JSX.Element | null => (
<> <>
Sold for <Money money={1e6 * level} /> Sold for <Money money={1e6 * level} />
@@ -28,7 +29,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
Sell hashes for <Money money={1e9} /> in Corporation funds Sell hashes for <Money money={1e9} /> in Corporation funds
</> </>
), ),
name: "Sell for Corporation Funds", name: HashUpgradeEnum.SellForCorporationFunds,
effectText: (level: number): JSX.Element | null => ( effectText: (level: number): JSX.Element | null => (
<> <>
Sold for <Money money={1e9 * level} /> Corporation funds. Sold for <Money money={1e9 * level} /> Corporation funds.
@@ -43,7 +44,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
"Note that a server's minimum security cannot go below 1. This effect persists " + "Note that a server's minimum security cannot go below 1. This effect persists " +
"until you install augmentations (since servers are reset at that time).", "until you install augmentations (since servers are reset at that time).",
hasTargetServer: true, hasTargetServer: true,
name: "Reduce Minimum Security", name: HashUpgradeEnum.ReduceMinimumSecurity,
value: 0.98, value: 0.98,
}, },
{ {
@@ -56,7 +57,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
</> </>
), ),
hasTargetServer: true, hasTargetServer: true,
name: "Increase Maximum Money", name: HashUpgradeEnum.IncreaseMaximumMoney,
value: 1.02, value: 1.02,
}, },
{ {
@@ -64,7 +65,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
desc: desc:
"Use hashes to improve the experience earned when studying at a university by 20%. " + "Use hashes to improve the experience earned when studying at a university by 20%. " +
"This effect persists until you install augmentations.", "This effect persists until you install augmentations.",
name: "Improve Studying", name: HashUpgradeEnum.ImproveStudying,
effectText: (level: number): JSX.Element | null => <>Improves studying by {level * 20}%</>, effectText: (level: number): JSX.Element | null => <>Improves studying by {level * 20}%</>,
value: 20, // Improves studying by value% value: 20, // Improves studying by value%
}, },
@@ -73,14 +74,14 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
desc: desc:
"Use hashes to improve the experience earned when training at the gym by 20%. This effect " + "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
"persists until you install augmentations.", "persists until you install augmentations.",
name: "Improve Gym Training", name: HashUpgradeEnum.ImproveGymTraining,
effectText: (level: number): JSX.Element | null => <>Improves training by {level * 20}%</>, effectText: (level: number): JSX.Element | null => <>Improves training by {level * 20}%</>,
value: 20, // Improves training by value% value: 20, // Improves training by value%
}, },
{ {
costPerLevel: 200, costPerLevel: 200,
desc: "Exchange hashes for 1k Scientific Research in all of your corporation's divisions", desc: "Exchange hashes for 1k Scientific Research in all of your corporation's divisions",
name: "Exchange for Corporation Research", name: HashUpgradeEnum.ExchangeForCorporationResearch,
effectText: (level: number): JSX.Element | null => ( effectText: (level: number): JSX.Element | null => (
<>Acquired a total of {formatInt(level * 1000)} Scientific Research in your divisions.</> <>Acquired a total of {formatInt(level * 1000)} Scientific Research in your divisions.</>
), ),
@@ -89,7 +90,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
{ {
costPerLevel: 250, costPerLevel: 250,
desc: "Exchange hashes for 100 Bladeburner Rank", desc: "Exchange hashes for 100 Bladeburner Rank",
name: "Exchange for Bladeburner Rank", name: HashUpgradeEnum.ExchangeForBladeburnerRank,
effectText: (level: number): JSX.Element | null => ( effectText: (level: number): JSX.Element | null => (
<>Acquired a total of {formatInt(100 * level)} Bladeburner rank</> <>Acquired a total of {formatInt(100 * level)} Bladeburner rank</>
), ),
@@ -98,7 +99,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
{ {
costPerLevel: 250, costPerLevel: 250,
desc: "Exchanges hashes for 10 Bladeburner Skill Points", desc: "Exchanges hashes for 10 Bladeburner Skill Points",
name: "Exchange for Bladeburner SP", name: HashUpgradeEnum.ExchangeForBladeburnerSP,
effectText: (level: number): JSX.Element | null => ( effectText: (level: number): JSX.Element | null => (
<>Acquired a total of {formatInt(10 * level)} Bladeburner Skill Points</> <>Acquired a total of {formatInt(10 * level)} Bladeburner Skill Points</>
), ),
@@ -107,7 +108,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
{ {
costPerLevel: 200, costPerLevel: 200,
desc: "Generate a random Coding Contract somewhere on the network", desc: "Generate a random Coding Contract somewhere on the network",
name: "Generate Coding Contract", name: HashUpgradeEnum.GenerateCodingContract,
effectText: (level: number): JSX.Element | null => <>Generated {level} contracts.</>, effectText: (level: number): JSX.Element | null => <>Generated {level} contracts.</>,
value: 1, value: 1,
}, },
@@ -115,7 +116,7 @@ export const HashUpgradesMetadata: HashUpgradeParams[] = [
costPerLevel: 200, costPerLevel: 200,
desc: "Use hashes to increase the favor with a company by 5. This effect persists until you enter a new BitNode.", desc: "Use hashes to increase the favor with a company by 5. This effect persists until you enter a new BitNode.",
hasTargetCompany: true, hasTargetCompany: true,
name: "Company Favor", name: HashUpgradeEnum.CompanyFavor,
value: 5, value: 5,
}, },
]; ];

View File

@@ -15,7 +15,7 @@ import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { SelectChangeEvent } from "@mui/material/Select"; import { SelectChangeEvent } from "@mui/material/Select";
import { CompanyName, FactionName } from "@enums"; import { CompanyName, FactionName, HashUpgradeEnum } from "@enums";
import { PartialRecord } from "../../Types/Record"; import { PartialRecord } from "../../Types/Record";
import { isMember } from "../../utils/EnumHelper"; import { isMember } from "../../utils/EnumHelper";
import { ServerOwnershipType } from "../../Server/ServerHelpers"; import { ServerOwnershipType } from "../../Server/ServerHelpers";
@@ -46,19 +46,17 @@ export function HacknetUpgradeElem(props: IProps): React.ReactElement {
} }
function purchase(): void { function purchase(): void {
const canPurchase = props.hashManager.hashes >= props.hashManager.getUpgradeCost(props.upg.name); const canPurchase = props.hashManager.hashes >= props.hashManager.getUpgradeCost(props.upg.name);
if (canPurchase) { if (!canPurchase) {
const res = purchaseHashUpgrade( return;
props.upg.name,
props.upg.name === "Company Favor" ? selectedCompany : selectedServer,
);
if (!res) {
dialogBoxCreate(
"Failed to purchase upgrade. This may be because you do not have enough hashes, " +
"or because you do not have access to the feature upgrade affects.",
);
}
props.rerender();
} }
const result = purchaseHashUpgrade(
props.upg.name,
props.upg.name === HashUpgradeEnum.CompanyFavor ? selectedCompany : selectedServer,
);
if (!result.success) {
dialogBoxCreate(`Failed to purchase upgrade. Reason: ${result.message} `);
}
props.rerender();
} }
const hashManager = props.hashManager; const hashManager = props.hashManager;

View File

@@ -8,6 +8,7 @@ import { Modal } from "../../ui/React/Modal";
import { Player } from "@player"; import { Player } from "@player";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { useCycleRerender } from "../../ui/React/hooks"; import { useCycleRerender } from "../../ui/React/hooks";
import { getRecordKeys } from "../../Types/Record";
interface IProps { interface IProps {
open: boolean; open: boolean;
@@ -30,7 +31,7 @@ export function HashUpgradeModal(props: IProps): React.ReactElement {
<Typography> <Typography>
Hashes: <Hashes hashes={Player.hashManager.hashes} /> Hashes: <Hashes hashes={Player.hashManager.hashes} />
</Typography> </Typography>
{Object.keys(HashUpgrades).map((upgName) => { {getRecordKeys(HashUpgrades).map((upgName) => {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
return <HacknetUpgradeElem upg={upg} hashManager={hashManager} key={upg.name} rerender={rerender} />; return <HacknetUpgradeElem upg={upg} hashManager={hashManager} key={upg.name} rerender={rerender} />;
})} })}

View File

@@ -330,7 +330,7 @@ export function NetscriptFormulas(): InternalAPI<IFormulas> {
return HScalculateCacheUpgradeCost(startingCache, extraCache); return HScalculateCacheUpgradeCost(startingCache, extraCache);
}, },
hashUpgradeCost: (ctx) => (_upgName, _level) => { hashUpgradeCost: (ctx) => (_upgName, _level) => {
const upgName = helpers.string(ctx, "upgName", _upgName); const upgName = getEnumHelper("HashUpgradeEnum").nsGetMember(ctx, _upgName);
const level = helpers.number(ctx, "level", _level); const level = helpers.number(ctx, "level", _level);
checkFormulasAccess(ctx); checkFormulasAccess(ctx);
const upg = Player.hashManager.getUpgrade(upgName); const upg = Player.hashManager.getUpgrade(upgName);

View File

@@ -21,6 +21,7 @@ import { GetServer } from "../Server/AllServers";
import { Hacknet as IHacknet, NodeStats } from "@nsdefs"; import { Hacknet as IHacknet, NodeStats } from "@nsdefs";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper"; import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers"; import { helpers } from "../Netscript/NetscriptHelpers";
import { getEnumHelper } from "../utils/EnumHelper";
export function NetscriptHacknet(): InternalAPI<IHacknet> { export function NetscriptHacknet(): InternalAPI<IHacknet> {
// Utility function to get Hacknet Node object // Utility function to get Hacknet Node object
@@ -188,7 +189,7 @@ export function NetscriptHacknet(): InternalAPI<IHacknet> {
hashCost: hashCost:
(ctx) => (ctx) =>
(_upgName, _count = 1) => { (_upgName, _count = 1) => {
const upgName = helpers.string(ctx, "upgName", _upgName); const upgName = getEnumHelper("HashUpgradeEnum").nsGetMember(ctx, _upgName);
const count = helpers.number(ctx, "count", _count); const count = helpers.number(ctx, "count", _count);
if (!hasHacknetServers()) { if (!hasHacknetServers()) {
return Infinity; return Infinity;
@@ -199,7 +200,7 @@ export function NetscriptHacknet(): InternalAPI<IHacknet> {
spendHashes: spendHashes:
(ctx) => (ctx) =>
(_upgName, _upgTarget = "", _count = 1) => { (_upgName, _upgTarget = "", _count = 1) => {
const upgName = helpers.string(ctx, "upgName", _upgName); const upgName = getEnumHelper("HashUpgradeEnum").nsGetMember(ctx, _upgName);
const upgTarget = helpers.string(ctx, "upgTarget", _upgTarget); const upgTarget = helpers.string(ctx, "upgTarget", _upgTarget);
const count = helpers.integer(ctx, "count", _count); const count = helpers.integer(ctx, "count", _count);
if (count < 0) { if (count < 0) {
@@ -208,7 +209,11 @@ export function NetscriptHacknet(): InternalAPI<IHacknet> {
if (!hasHacknetServers()) { if (!hasHacknetServers()) {
return false; return false;
} }
return purchaseHashUpgrade(upgName, upgTarget, count); const result = purchaseHashUpgrade(upgName, upgTarget, count);
if (!result.success) {
helpers.log(ctx, () => result.message);
}
return result.success;
}, },
getHashUpgrades: () => () => { getHashUpgrades: () => () => {
if (!hasHacknetServers()) { if (!hasHacknetServers()) {

View File

@@ -2821,6 +2821,19 @@ export interface CompanyPositionInfo {
requiredSkills: Skills; requiredSkills: Skills;
} }
type HacknetServerHashUpgrade =
| "Sell for Money"
| "Sell for Corporation Funds"
| "Reduce Minimum Security"
| "Increase Maximum Money"
| "Improve Studying"
| "Improve Gym Training"
| "Exchange for Corporation Research"
| "Exchange for Bladeburner Rank"
| "Exchange for Bladeburner SP"
| "Generate Coding Contract"
| "Company Favor";
/** /**
* Hacknet API * Hacknet API
* @remarks * @remarks
@@ -3075,11 +3088,11 @@ export interface Hacknet {
* ns.hacknet.spendHashes(upgradeName); * ns.hacknet.spendHashes(upgradeName);
* } * }
* ``` * ```
* @param upgName - Name of the upgrade of Hacknet Node. * @param upgName - Name of the upgrade using hash of Hacknet Server.
* @param count - Number of upgrades to buy at once. Defaults to 1 if not specified. * @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, count?: number): number; hashCost(upgName: HacknetServerHashUpgrade, count?: number): number;
/** /**
* Purchase a hash upgrade. * Purchase a hash upgrade.
@@ -3102,13 +3115,13 @@ export interface Hacknet {
* // For upgrades requiring a target * // For upgrades requiring a target
* ns.hacknet.spendHashes("Increase Maximum Money", "foodnstuff"); * ns.hacknet.spendHashes("Increase Maximum Money", "foodnstuff");
* ``` * ```
* @param upgName - Name of the upgrade of Hacknet Node. * @param upgName - Name of the upgrade using hash of Hacknet Server.
* @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. Must be a non-negative integer. Defaults to 1 if not specified. * @param count - Number of upgrades to buy at once. Must be a non-negative integer. Defaults to 1 if not specified.
* For compatibility reasons, upgTarget must be specified, even if it is not used, in order to specify count. * For compatibility 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, count?: number): boolean; spendHashes(upgName: HacknetServerHashUpgrade, upgTarget?: string, count?: number): boolean;
/** /**
* Get the list of hash upgrades * Get the list of hash upgrades
@@ -3124,7 +3137,7 @@ export interface Hacknet {
* ``` * ```
* @returns An array containing the available upgrades * @returns An array containing the available upgrades
*/ */
getHashUpgrades(): string[]; getHashUpgrades(): HacknetServerHashUpgrade[];
/** /**
* Get the level of a hash upgrade. * Get the level of a hash upgrade.
@@ -3135,7 +3148,7 @@ export interface Hacknet {
* *
* @returns Level of the upgrade. * @returns Level of the upgrade.
*/ */
getHashUpgradeLevel(upgName: string): number; getHashUpgradeLevel(upgName: HacknetServerHashUpgrade): number;
/** /**
* Get the multiplier to study. * Get the multiplier to study.
@@ -5579,7 +5592,7 @@ interface HacknetServersFormulas {
* @param level - level of the upgrade * @param level - level of the upgrade
* @returns The calculated hash cost. * @returns The calculated hash cost.
*/ */
hashUpgradeCost(upgName: string, level: number): number; hashUpgradeCost(upgName: HacknetServerHashUpgrade, level: number): number;
/** /**
* Calculate the cost of a hacknet server. * Calculate the cost of a hacknet server.
* @param n - number of the hacknet server * @param n - number of the hacknet server