" +
"This Source-File also increases your hacking growth multipliers by: " +
" Level 1: 12% Level 2: 18% Level 3: 21%");
- BitNodes["BitNode9"] = new BitNode(9, "Do Androids Dream?", "COMING SOON");
+ BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
+ "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
+ "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
+ "powering the Hacknet, ");
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
@@ -245,9 +248,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
}
switch (p.bitNodeN) {
- case 1: //Source Genesis (every multiplier is 1)
+ case 1: // Source Genesis (every multiplier is 1)
break;
- case 2: //Rise of the Underworld
+ case 2: // Rise of the Underworld
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.ServerGrowthRate = 0.8;
BitNodeMultipliers.ServerMaxMoney = 0.2;
@@ -257,7 +260,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FactionWorkRepGain = 0.5;
BitNodeMultipliers.FactionPassiveRepGain = 0;
break;
- case 3: //Corporatocracy
+ case 3: // Corporatocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.RepToDonateToFaction = 0.5;
BitNodeMultipliers.AugmentationRepCost = 3;
@@ -272,7 +275,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 2;
break;
- case 4: //The Singularity
+ case 4: // The Singularity
BitNodeMultipliers.ServerMaxMoney = 0.15;
BitNodeMultipliers.ServerStartingMoney = 0.75;
BitNodeMultipliers.ScriptHackMoney = 0.2;
@@ -286,7 +289,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.75;
break;
- case 5: //Artificial intelligence
+ case 5: // Artificial intelligence
BitNodeMultipliers.ServerMaxMoney = 2;
BitNodeMultipliers.ServerStartingSecurity = 2;
BitNodeMultipliers.ServerStartingMoney = 0.5;
@@ -299,7 +302,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5;
break;
- case 6: //Bladeburner
+ case 6: // Bladeburner
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
BitNodeMultipliers.ServerMaxMoney = 0.4;
BitNodeMultipliers.ServerStartingMoney = 0.5;
@@ -314,7 +317,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
break;
- case 7: //Bladeburner 2079
+ case 7: // Bladeburner 2079
BitNodeMultipliers.BladeburnerRank = 0.6;
BitNodeMultipliers.BladeburnerSkillCost = 2;
BitNodeMultipliers.AugmentationMoneyCost = 3;
@@ -334,7 +337,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
break;
- case 8: //Ghost of Wall Street
+ case 8: // Ghost of Wall Street
BitNodeMultipliers.ScriptHackMoney = 0;
BitNodeMultipliers.ManualHackMoney = 0;
BitNodeMultipliers.CompanyWorkMoney = 0;
@@ -345,6 +348,19 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0;
break;
+ case 9: // Hacktocracy
+ BitNodeMultipliers.HackingLevelMultiplier = 0.3;
+ BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
+ BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
+ BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
+ BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
+ BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
+ BitNodeMultipliers.PurchasedServerLimit = 0;
+ BitNodeMultipliers.HomeComputerRamCost = 3;
+ BitNodeMultipliers.CrimeMoney = 0.5;
+ BitNodeMultipliers.ScriptHackMoney = 0.1;
+ BitNodeMultipliers.HackExpGain = 0.1;
+ break;
case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
diff --git a/src/BitNode/BitNodeMultipliers.ts b/src/BitNode/BitNodeMultipliers.ts
index e6d66062c..0a9334d59 100644
--- a/src/BitNode/BitNodeMultipliers.ts
+++ b/src/BitNode/BitNodeMultipliers.ts
@@ -120,7 +120,8 @@ interface IBitNodeMultipliers {
HackingLevelMultiplier: number;
/**
- * Influences how much money each Hacknet node can generate.
+ * Influences how much money is produced by Hacknet Nodes.
+ * Influeces the hash rate of Hacknet Servers (unlocked in BitNode-9)
*/
HacknetNodeMoney: number;
diff --git a/src/Constants.ts b/src/Constants.ts
index a34343427..cb56cb3d2 100644
--- a/src/Constants.ts
+++ b/src/Constants.ts
@@ -1,3 +1,8 @@
+/**
+ * Generic Game Constants
+ *
+ * Constants for specific mechanics or features will NOT be here.
+ */
import {IMap} from "./types";
export let CONSTANTS: IMap = {
@@ -17,24 +22,9 @@ export let CONSTANTS: IMap = {
/* Base costs */
BaseCostFor1GBOfRamHome: 32000,
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
- BaseCostFor1GBOfRamHacknetNode: 30000,
TravelCost: 200e3,
- BaseCostForHacknetNode: 1000,
- BaseCostForHacknetNodeCore: 500000,
-
- /* Hacknet Node constants */
- HacknetNodeMoneyGainPerLevel: 1.6,
- HacknetNodePurchaseNextMult: 1.85, //Multiplier when purchasing an additional hacknet node
- HacknetNodeUpgradeLevelMult: 1.04, //Multiplier for cost when upgrading level
- HacknetNodeUpgradeRamMult: 1.28, //Multiplier for cost when upgrading RAM
- HacknetNodeUpgradeCoreMult: 1.48, //Multiplier for cost when buying another core
-
- HacknetNodeMaxLevel: 200,
- HacknetNodeMaxRam: 64,
- HacknetNodeMaxCores: 16,
-
/* Faction and Company favor */
BaseFavorToDonate: 150,
DonateMoneyToRepDivisor: 1e6,
diff --git a/src/Hacknet/HacknetHelpers.jsx b/src/Hacknet/HacknetHelpers.jsx
new file mode 100644
index 000000000..38246f75a
--- /dev/null
+++ b/src/Hacknet/HacknetHelpers.jsx
@@ -0,0 +1,431 @@
+import { HacknetNode,
+ BaseCostForHacknetNode,
+ HacknetNodePurchaseNextMult,
+ HacknetNodeMaxLevel,
+ HacknetNodeMaxRam,
+ HacknetNodeMaxCores } from "./HacknetNode";
+import { HacknetServer,
+ BaseCostForHacknetServer,
+ HacknetServerPurchaseMult,
+ HacknetServerMaxLevel,
+ HacknetServerMaxRam,
+ HacknetServerMaxCores,
+ HacknetServerMaxCache,
+ MaxNumberHacknetServers } from "./HacknetServer";
+import { HashManager } from "./HashManager";
+import { HashUpgrades } from "./HashUpgrades";
+
+import { generateRandomContractOnHome } from "../CodingContractGenerator";
+import { iTutorialSteps, iTutorialNextStep,
+ ITutorial} from "../InteractiveTutorial";
+import { Player } from "../Player";
+import { AddToAllServers } from "../Server/AllServers";
+import { GetServerByHostname } from "../Server/ServerHelpers";
+import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
+import { Page, routing } from "../ui/navigationTracking";
+
+import {getElementById} from "../../utils/uiHelpers/getElementById";
+
+import React from "react";
+import ReactDOM from "react-dom";
+import { HacknetRoot } from "./ui/Root";
+
+let hacknetNodesDiv;
+function hacknetNodesInit() {
+ hacknetNodesDiv = document.getElementById("hacknet-nodes-container");
+}
+
+document.addEventListener("DOMContentLoaded", hacknetNodesInit, false);
+
+// Returns a boolean indicating whether the player has Hacknet Servers
+// (the upgraded form of Hacknet Nodes)
+export function hasHacknetServers() {
+ return (Player.bitNodeN === 9 || SourceFileFlags[9] > 0);
+}
+
+export function purchaseHacknet() {
+ /* INTERACTIVE TUTORIAL */
+ if (ITutorial.isRunning) {
+ if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
+ iTutorialNextStep();
+ } else {
+ return;
+ }
+ }
+
+ /* END INTERACTIVE TUTORIAL */
+
+ if (hasHacknetServers()) {
+ const cost = getCostOfNextHacknetServer();
+ if (isNaN(cost)) {
+ throw new Error(`Calculated cost of purchasing HacknetServer is NaN`)
+ }
+
+ if (!Player.canAfford(cost)) { return -1; }
+
+ // Auto generate a hostname for this Server
+ const numOwned = Player.hacknetNodes.length;
+ const name = `hacknet-node-${numOwned}`;
+ const server = new HacknetServer({
+ adminRights: true,
+ hostname: name,
+ player: Player,
+ });
+
+ Player.loseMoney(cost);
+ Player.hacknetNodes.push(server);
+
+ // Configure the HacknetServer to actually act as a Server
+ AddToAllServers(server);
+ const homeComputer = Player.getHomeComputer();
+ homeComputer.serversOnNetwork.push(server.ip);
+ server.serversOnNetwork.push(homeComputer.ip);
+
+ return numOwned;
+ } else {
+ const cost = getCostOfNextHacknetNode();
+ if (isNaN(cost)) {
+ throw new Error(`Calculated cost of purchasing HacknetNode is NaN`);
+ }
+
+ if (!Player.canAfford(cost)) { return -1; }
+
+ // Auto generate a name for the Node
+ const numOwned = Player.hacknetNodes.length;
+ const name = "hacknet-node-" + numOwned;
+ const node = new HacknetNode(name);
+ node.updateMoneyGainRate(Player);
+
+ Player.loseMoney(cost);
+ Player.hacknetNodes.push(node);
+
+ return numOwned;
+ }
+}
+
+export function hasMaxNumberHacknetServers() {
+ return hasHacknetServers() && Player.hacknetNodes.length >= MaxNumberHacknetServers;
+}
+
+export function getCostOfNextHacknetNode() {
+ // Cost increases exponentially based on how many you own
+ const numOwned = Player.hacknetNodes.length;
+ const mult = HacknetNodePurchaseNextMult;
+
+ return BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
+}
+
+export function getCostOfNextHacknetServer() {
+ const numOwned = Player.hacknetNodes.length;
+ const mult = HacknetServerPurchaseMult;
+
+ if (numOwned > MaxNumberHacknetServers) { return Infinity; }
+
+ return BaseCostForHacknetServer * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
+}
+
+//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
+export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
+ if (maxLevel == null) {
+ throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
+ }
+
+ if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player))) {
+ return 0;
+ }
+
+ let min = 1;
+ let max = maxLevel - 1;
+ let levelsToMax = maxLevel - nodeObj.level;
+ if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player))) {
+ return levelsToMax;
+ }
+
+ while (min <= max) {
+ var curr = (min + max) / 2 | 0;
+ if (curr !== maxLevel &&
+ Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
+ Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
+ return Math.min(levelsToMax, curr);
+ } else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
+ max = curr - 1;
+ } else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
+ min = curr + 1;
+ } else {
+ return Math.min(levelsToMax, curr);
+ }
+ }
+ return 0;
+}
+
+export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
+ if (maxLevel == null) {
+ throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
+ }
+
+ if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player))) {
+ return 0;
+ }
+
+ let levelsToMax;
+ if (nodeObj instanceof HacknetServer) {
+ levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.maxRam));
+ } else {
+ levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
+ }
+ if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player))) {
+ return levelsToMax;
+ }
+
+ //We'll just loop until we find the max
+ for (let i = levelsToMax-1; i >= 0; --i) {
+ if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player))) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
+ if (maxLevel == null) {
+ throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
+ }
+
+ if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player))) {
+ return 0;
+ }
+
+ let min = 1;
+ let max = maxLevel - 1;
+ const levelsToMax = maxLevel - nodeObj.cores;
+ if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player))) {
+ return levelsToMax;
+ }
+
+ //Use a binary search to find the max possible number of upgrades
+ while (min <= max) {
+ let curr = (min + max) / 2 | 0;
+ if (curr != maxLevel &&
+ Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
+ Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
+ return Math.min(levelsToMax, curr);
+ } else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
+ max = curr - 1;
+ } else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
+ min = curr + 1;
+ } else {
+ return Math.min(levelsToMax, curr);
+ }
+ }
+
+ return 0;
+}
+
+export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
+ if (maxLevel == null) {
+ throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`);
+ }
+
+ if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) {
+ return 0;
+ }
+
+ let min = 1;
+ let max = maxLevel - 1;
+ const levelsToMax = maxLevel - nodeObj.cache;
+ if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) {
+ return levelsToMax;
+ }
+
+ // Use a binary search to find the max possible number of upgrades
+ while (min <= max) {
+ let curr = (min + max) / 2 | 0;
+ if (curr != maxLevel &&
+ Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) &&
+ !Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1))) {
+ return Math.min(levelsToMax, curr);
+ } else if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
+ max = curr -1 ;
+ } else if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
+ min = curr + 1;
+ } else {
+ return Math.min(levelsToMax, curr);
+ }
+ }
+
+ return 0;
+}
+
+// Initial construction of Hacknet Nodes UI
+export function renderHacknetNodesUI() {
+ if (!routing.isOn(Page.HacknetNodes)) { return; }
+
+ ReactDOM.render(, hacknetNodesDiv);
+}
+
+export function clearHacknetNodesUI() {
+ if (hacknetNodesDiv instanceof HTMLElement) {
+ ReactDOM.unmountComponentAtNode(hacknetNodesDiv);
+ }
+
+ hacknetNodesDiv.style.display = "none";
+}
+
+export function processHacknetEarnings(numCycles) {
+ // Determine if player has Hacknet Nodes or Hacknet Servers, then
+ // call the appropriate function
+ if (Player.hacknetNodes.length === 0) { return 0; }
+ if (hasHacknetServers()) {
+ return processAllHacknetServerEarnings();
+ } else if (Player.hacknetNodes[0] instanceof HacknetNode) {
+ return processAllHacknetNodeEarnings();
+ } else {
+ return 0;
+ }
+}
+
+function processAllHacknetNodeEarnings(numCycles) {
+ let total = 0;
+ for (let i = 0; i < Player.hacknetNodes.length; ++i) {
+ total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
+ }
+
+ return total;
+}
+
+function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
+ const totalEarnings = nodeObj.process(numCycles);
+ Player.gainMoney(totalEarnings);
+ Player.recordMoneySource(totalEarnings, "hacknetnode");
+
+ return totalEarnings;
+}
+
+function processAllHacknetServerEarnings(numCycles) {
+ if (!(Player.hashManager instanceof HashManager)) {
+ throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`)
+ }
+
+ let hashes = 0;
+ for (let i = 0; i < Player.hacknetNodes.length; ++i) {
+ hashes += Player.hacknetNodes[i].process(numCycles);
+ }
+
+ Player.hashManager.storeHashes(hashes);
+
+ return hashes;
+}
+
+export function getHacknetNode(name) {
+ for (var i = 0; i < Player.hacknetNodes.length; ++i) {
+ if (Player.hacknetNodes[i].name == name) {
+ return Player.hacknetNodes[i];
+ }
+ }
+
+ return null;
+}
+
+export function purchaseHashUpgrade(upgName, upgTarget) {
+ if (!(Player.hashManager instanceof HashManager)) {
+ console.error(`Player does not have a HashManager`);
+ return false;
+ }
+
+ // HashManager handles the transaction. This just needs to actually implement
+ // the effects of the upgrade
+ if (Player.hashManager.upgrade(upgName)) {
+ const upg = HashUpgrades[upgName];
+
+ switch (upgName) {
+ case "Sell for Money": {
+ Player.gainMoney(upg.value);
+ break;
+ }
+ case "Sell for Corporation Funds": {
+ // This will throw if player doesn't have a corporation
+ try {
+ Player.corporation.funds = Player.corporation.funds.plus(upg.value);
+ } catch(e) {
+ Player.hashManager.refundUpgrade(upgName);
+ return false;
+ }
+ break;
+ }
+ case "Reduce Minimum Security": {
+ try {
+ const target = GetServerByHostname(upgTarget);
+ if (target == null) {
+ console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
+ return false;
+ }
+
+ target.changeMinimumSecurity(upg.value, true);
+ } catch(e) {
+ Player.hashManager.refundUpgrade(upgName);
+ return false;
+ }
+ break;
+ }
+ case "Increase Maximum Money": {
+ try {
+ const target = GetServerByHostname(upgTarget);
+ if (target == null) {
+ console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
+ return false;
+ }
+
+ target.changeMaximumMoney(upg.value, true);
+ } catch(e) {
+ Player.hashManager.refundUpgrade(upgName);
+ return false;
+ }
+ break;
+ }
+ case "Improve Studying": {
+ // Multiplier handled by HashManager
+ break;
+ }
+ case "Improve Gym Training": {
+ // Multiplier handled by HashManager
+ break;
+ }
+ case "Exchange for Corporation Research": {
+ // This will throw if player doesn't have a corporation
+ try {
+ for (const division of Player.corporation.divisions) {
+ division.sciResearch.qty += upg.value;
+ }
+ } catch(e) {
+ Player.hashManager.refundUpgrade(upgName);
+ return false;
+ }
+ break;
+ }
+ case "Exchange for Bladeburner Rank": {
+ // This will throw if player doesn't have a corporation
+ try {
+ for (const division of Player.corporation.divisions) {
+ division.sciResearch.qty += upg.value;
+ }
+ } catch(e) {
+ Player.hashManager.refundUpgrade(upgName);
+ return false;
+ }
+ break;
+ }
+ case "Generate Coding Contract": {
+ generateRandomContractOnHome();
+ break;
+ }
+ default:
+ console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`)
+ return false;
+ }
+
+ console.log("Hash Upgrade successfully purchased");
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/Hacknet/HacknetNode.ts b/src/Hacknet/HacknetNode.ts
new file mode 100644
index 000000000..51b9b2025
--- /dev/null
+++ b/src/Hacknet/HacknetNode.ts
@@ -0,0 +1,285 @@
+/**
+ * Hacknet Node Class
+ *
+ * Hacknet Nodes are specialized machines that passively earn the player money over time.
+ * They can be upgraded to increase their production
+ */
+import { IHacknetNode } from "./IHacknetNode";
+
+import { CONSTANTS } from "../Constants";
+
+import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
+import { IPlayer } from "../PersonObjects/IPlayer";
+
+import { dialogBoxCreate } from "../../utils/DialogBox";
+import { Generic_fromJSON,
+ Generic_toJSON,
+ Reviver } from "../../utils/JSONReviver";
+
+// Constants for Hacknet Node production
+export const HacknetNodeMoneyGainPerLevel: number = 1.6; // Base production per level
+
+// Constants for Hacknet Node purchase/upgrade costs
+export const BaseCostForHacknetNode: number = 1000;
+export const BaseCostFor1GBOfRamHacknetNode: number = 30e3;
+export const BaseCostForHacknetNodeCore: number = 500e3;
+export const HacknetNodePurchaseNextMult: number = 1.85; // Multiplier when purchasing an additional hacknet node
+export const HacknetNodeUpgradeLevelMult: number = 1.04; // Multiplier for cost when upgrading level
+export const HacknetNodeUpgradeRamMult: number = 1.28; // Multiplier for cost when upgrading RAM
+export const HacknetNodeUpgradeCoreMult: number = 1.48; // Multiplier for cost when buying another core
+
+// Constants for max upgrade levels for Hacknet Nodes
+export const HacknetNodeMaxLevel: number = 200;
+export const HacknetNodeMaxRam: number = 64;
+export const HacknetNodeMaxCores: number = 16;
+
+export class HacknetNode implements IHacknetNode {
+ /**
+ * Initiatizes a HacknetNode object from a JSON save state.
+ */
+ static fromJSON(value: any): HacknetNode {
+ return Generic_fromJSON(HacknetNode, value.data);
+ }
+
+ // Node's number of cores
+ cores: number = 1;
+
+ // Node's Level
+ level: number = 1;
+
+ // Node's production per second
+ moneyGainRatePerSecond: number = 0;
+
+ // Identifier for Node. Includes the full "name" (hacknet-node-N)
+ name: string;
+
+ // How long this Node has existed, in seconds
+ onlineTimeSeconds: number = 0;
+
+ // Node's RAM (GB)
+ ram: number = 1;
+
+ // Total money earned by this Node
+ totalMoneyGenerated: number = 0;
+
+ constructor(name: string="") {
+ this.name = name;
+ }
+
+ // Get the cost to upgrade this Node's number of cores
+ calculateCoreUpgradeCost(levels: number=1, p: IPlayer): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.cores >= HacknetNodeMaxCores) {
+ return Infinity;
+ }
+
+ const coreBaseCost = BaseCostForHacknetNodeCore;
+ const mult = HacknetNodeUpgradeCoreMult;
+ let totalCost = 0;
+ let currentCores = this.cores;
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ totalCost += (coreBaseCost * Math.pow(mult, currentCores-1));
+ ++currentCores;
+ }
+
+ totalCost *= p.hacknet_node_core_cost_mult;
+
+ return totalCost;
+ }
+
+ // Get the cost to upgrade this Node's level
+ calculateLevelUpgradeCost(levels: number=1, p: IPlayer): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.level >= HacknetNodeMaxLevel) {
+ return Infinity;
+ }
+
+ const mult = HacknetNodeUpgradeLevelMult;
+ let totalMultiplier = 0;
+ let currLevel = this.level;
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ totalMultiplier += Math.pow(mult, currLevel);
+ ++currLevel;
+ }
+
+ return BaseCostForHacknetNode / 2 * totalMultiplier * p.hacknet_node_level_cost_mult;
+ }
+
+ // Get the cost to upgrade this Node's RAM
+ calculateRamUpgradeCost(levels: number=1, p: IPlayer): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.ram >= HacknetNodeMaxRam) {
+ return Infinity;
+ }
+
+ let totalCost = 0;
+ let numUpgrades = Math.round(Math.log2(this.ram));
+ let currentRam = this.ram;
+
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ let baseCost = currentRam * BaseCostFor1GBOfRamHacknetNode;
+ let mult = Math.pow(HacknetNodeUpgradeRamMult, numUpgrades);
+
+ totalCost += (baseCost * mult);
+
+ currentRam *= 2;
+ ++numUpgrades;
+ }
+
+ totalCost *= p.hacknet_node_ram_cost_mult;
+
+ return totalCost;
+ }
+
+ // Process this Hacknet Node in the game loop.
+ // Returns the amount of money generated
+ process(numCycles: number=1): number {
+ const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000;
+ let gain = this.moneyGainRatePerSecond * seconds;
+ if (isNaN(gain)) {
+ console.error(`Hacknet Node ${this.name} calculated earnings of NaN`);
+ gain = 0;
+ }
+
+ this.totalMoneyGenerated += gain;
+ this.onlineTimeSeconds += seconds;
+
+ return gain;
+ }
+
+ // Upgrade this Node's number of cores, if possible
+ // Returns a boolean indicating whether new cores were successfully bought
+ purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
+ if (isNaN(cost) || sanitizedLevels < 0) {
+ return false;
+ }
+
+ // Fail if we're already at max
+ if (this.cores >= HacknetNodeMaxCores) {
+ return false;
+ }
+
+ // If the specified number of upgrades would exceed the max Cores, calculate
+ // the max possible number of upgrades and use that
+ if (this.cores + sanitizedLevels > HacknetNodeMaxCores) {
+ const diff = Math.max(0, HacknetNodeMaxCores - this.cores);
+ return this.purchaseCoreUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ this.cores = Math.round(this.cores + sanitizedLevels); // Just in case of floating point imprecision
+ this.updateMoneyGainRate(p);
+
+ return true;
+ }
+
+ // Upgrade this Node's level, if possible
+ // Returns a boolean indicating whether the level was successfully updated
+ purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
+ if (isNaN(cost) || sanitizedLevels < 0) {
+ return false;
+ }
+
+ // If we're at max level, return false
+ if (this.level >= HacknetNodeMaxLevel) {
+ return false;
+ }
+
+ // If the number of specified upgrades would exceed the max level, calculate
+ // the maximum number of upgrades and use that
+ if (this.level + sanitizedLevels > HacknetNodeMaxLevel) {
+ var diff = Math.max(0, HacknetNodeMaxLevel - this.level);
+ return this.purchaseLevelUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ this.level = Math.round(this.level + sanitizedLevels); // Just in case of floating point imprecision
+ this.updateMoneyGainRate(p);
+
+ return true;
+ }
+
+ // Upgrade this Node's RAM, if possible
+ // Returns a boolean indicating whether the RAM was successfully upgraded
+ purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
+ if (isNaN(cost) || sanitizedLevels < 0) {
+ return false;
+ }
+
+ // Fail if we're already at max
+ if (this.ram >= HacknetNodeMaxRam) {
+ return false;
+ }
+
+ // If the number of specified upgrades would exceed the max RAM, calculate the
+ // max possible number of upgrades and use that
+ if (this.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
+ var diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / this.ram)));
+ return this.purchaseRamUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ this.ram *= 2; // Ram is always doubled
+ }
+ this.ram = Math.round(this.ram); // Handle any floating point precision issues
+ this.updateMoneyGainRate(p);
+
+ return true;
+ }
+
+ // Re-calculate this Node's production and update the moneyGainRatePerSecond prop
+ updateMoneyGainRate(p: IPlayer): void {
+ //How much extra $/s is gained per level
+ var gainPerLevel = HacknetNodeMoneyGainPerLevel;
+
+ this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
+ Math.pow(1.035, this.ram - 1) *
+ ((this.cores + 5) / 6) *
+ p.hacknet_node_money_mult *
+ BitNodeMultipliers.HacknetNodeMoney;
+ if (isNaN(this.moneyGainRatePerSecond)) {
+ this.moneyGainRatePerSecond = 0;
+ dialogBoxCreate("Error in calculating Hacknet Node production. Please report to game developer", false);
+ }
+ }
+
+ /**
+ * Serialize the current object to a JSON save state.
+ */
+ toJSON(): any {
+ return Generic_toJSON("HacknetNode", this);
+ }
+}
+
+Reviver.constructors.HacknetNode = HacknetNode;
diff --git a/src/Hacknet/HacknetServer.ts b/src/Hacknet/HacknetServer.ts
new file mode 100644
index 000000000..15022cd1b
--- /dev/null
+++ b/src/Hacknet/HacknetServer.ts
@@ -0,0 +1,348 @@
+/**
+ * Hacknet Servers - Reworked Hacknet Node mechanic for BitNode-9
+ */
+import { CONSTANTS } from "../Constants";
+
+import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
+import { IHacknetNode } from "../Hacknet/IHacknetNode";
+import { IPlayer } from "../PersonObjects/IPlayer";
+import { BaseServer } from "../Server/BaseServer";
+import { RunningScript } from "../Script/RunningScript";
+
+import { dialogBoxCreate } from "../../utils/DialogBox";
+import { createRandomIp } from "../../utils/IPAddress";
+
+import { Generic_fromJSON,
+ Generic_toJSON,
+ Reviver } from "../../utils/JSONReviver";
+
+// Constants for Hacknet Server stats/production
+export const HacknetServerHashesPerLevel: number = 0.001;
+
+// Constants for Hacknet Server purchase/upgrade costs
+export const BaseCostForHacknetServer: number = 10e3;
+export const BaseCostFor1GBHacknetServerRam: number = 200e3;
+export const BaseCostForHacknetServerCore: number = 1e6;
+export const BaseCostForHacknetServerCache: number = 10e6;
+
+export const HacknetServerPurchaseMult: number = 3.2; // Multiplier for puchasing an additional Hacknet Server
+export const HacknetServerUpgradeLevelMult: number = 1.1; // Multiplier for cost when upgrading level
+export const HacknetServerUpgradeRamMult: number = 1.4; // Multiplier for cost when upgrading RAM
+export const HacknetServerUpgradeCoreMult: number = 1.55; // Multiplier for cost when buying another core
+export const HacknetServerUpgradeCacheMult: number = 1.85; // Multiplier for cost when upgrading cache
+
+export const MaxNumberHacknetServers: number = 25; // Max number of Hacknet Servers you can own
+
+// Constants for max upgrade levels for Hacknet Server
+export const HacknetServerMaxLevel: number = 300;
+export const HacknetServerMaxRam: number = 8192;
+export const HacknetServerMaxCores: number = 128;
+export const HacknetServerMaxCache: number = 15; // Max cache level. So max capacity is 2 ^ 12
+
+interface IConstructorParams {
+ adminRights?: boolean;
+ hostname: string;
+ ip?: string;
+ isConnectedTo?: boolean;
+ maxRam?: number;
+ organizationName?: string;
+ player?: IPlayer;
+}
+
+export class HacknetServer extends BaseServer implements IHacknetNode {
+ // Initializes a HacknetServer Object from a JSON save state
+ static fromJSON(value: any): HacknetServer {
+ return Generic_fromJSON(HacknetServer, value.data);
+ }
+
+ // Cache level. Affects hash Capacity
+ cache: number = 1;
+
+ // Number of cores. Improves hash production
+ cores: number = 1;
+
+ // Number of hashes that can be stored by this Hacknet Server
+ hashCapacity: number = 0;
+
+ // Hashes produced per second
+ hashRate: number = 0;
+
+ // Similar to Node level. Improves hash production
+ level: number = 1;
+
+ // How long this HacknetServer has existed, in seconds
+ onlineTimeSeconds: number = 0;
+
+ // Total number of hashes earned by this
+ totalHashesGenerated: number = 0;
+
+ constructor(params: IConstructorParams={ hostname: "", ip: createRandomIp() }) {
+ super(params);
+
+ this.maxRam = 1;
+ this.updateHashCapacity();
+
+ if (params.player) {
+ this.updateHashRate(params.player);
+ }
+ }
+
+ calculateCacheUpgradeCost(levels: number): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.cache >= HacknetServerMaxCache) {
+ return Infinity;
+ }
+
+ const mult = HacknetServerUpgradeCacheMult;
+ let totalCost = 0;
+ let currentCache = this.cache;
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ totalCost += Math.pow(mult, currentCache - 1);
+ ++currentCache;
+ }
+ totalCost *= BaseCostForHacknetServerCache;
+
+ return totalCost;
+ }
+
+ calculateCoreUpgradeCost(levels: number, p: IPlayer): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.cores >= HacknetServerMaxCores) {
+ return Infinity;
+ }
+
+ const mult = HacknetServerUpgradeCoreMult;
+ let totalCost = 0;
+ let currentCores = this.cores;
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ totalCost += Math.pow(mult, currentCores-1);
+ ++currentCores;
+ }
+ totalCost *= BaseCostForHacknetServerCore;
+ totalCost *= p.hacknet_node_core_cost_mult;
+
+ return totalCost;
+ }
+
+ calculateLevelUpgradeCost(levels: number, p: IPlayer): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.level >= HacknetServerMaxLevel) {
+ return Infinity;
+ }
+
+ const mult = HacknetServerUpgradeLevelMult;
+ let totalMultiplier = 0;
+ let currLevel = this.level;
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ totalMultiplier += Math.pow(mult, currLevel);
+ ++currLevel;
+ }
+
+ return 10 * BaseCostForHacknetServer * totalMultiplier * p.hacknet_node_level_cost_mult;
+ }
+
+ calculateRamUpgradeCost(levels: number, p: IPlayer): number {
+ const sanitizedLevels = Math.round(levels);
+ if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
+ return 0;
+ }
+
+ if (this.maxRam >= HacknetServerMaxRam) {
+ return Infinity;
+ }
+
+ let totalCost = 0;
+ let numUpgrades = Math.round(Math.log2(this.maxRam));
+ let currentRam = this.maxRam;
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ let baseCost = currentRam * BaseCostFor1GBHacknetServerRam;
+ let mult = Math.pow(HacknetServerUpgradeRamMult, numUpgrades);
+
+ totalCost += (baseCost * mult);
+
+ currentRam *= 2;
+ ++numUpgrades;
+ }
+ totalCost *= p.hacknet_node_ram_cost_mult;
+
+ return totalCost;
+ }
+
+ // Process this Hacknet Server in the game loop.
+ // Returns the number of hashes generated
+ process(numCycles: number=1): number {
+ const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000;
+
+ return this.hashRate * seconds;
+ }
+
+ // Returns a boolean indicating whether the cache was successfully upgraded
+ purchaseCacheUpgrade(levels: number, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateCacheUpgradeCost(levels);
+ if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
+ return false;
+ }
+
+ if (this.cache >= HacknetServerMaxCache) {
+ return false;
+ }
+
+ // If the specified number of upgrades would exceed the max, try to purchase
+ // the maximum possible
+ if (this.cache + levels > HacknetServerMaxCache) {
+ const diff = Math.max(0, HacknetServerMaxCache - this.cache);
+ return this.purchaseCacheUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ this.cache = Math.round(this.cache + sanitizedLevels);
+ this.updateHashCapacity();
+
+ return true;
+ }
+
+ // Returns a boolean indicating whether the number of cores was successfully upgraded
+ purchaseCoreUpgrade(levels: number, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
+ if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
+ return false;
+ }
+
+ if (this.cores >= HacknetServerMaxCores) {
+ return false;
+ }
+
+ // If the specified number of upgrades would exceed the max, try to purchase
+ // the maximum possible
+ if (this.cores + sanitizedLevels > HacknetServerMaxCores) {
+ const diff = Math.max(0, HacknetServerMaxCores - this.cores);
+ return this.purchaseCoreUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ this.cores = Math.round(this.cores + sanitizedLevels);
+ this.updateHashRate(p);
+
+ return true;
+ }
+
+ // Returns a boolean indicating whether the level was successfully upgraded
+ purchaseLevelUpgrade(levels: number, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
+ if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
+ return false;
+ }
+
+ if (this.level >= HacknetServerMaxLevel) {
+ return false;
+ }
+
+ // If the specified number of upgrades would exceed the max, try to purchase the
+ // maximum possible
+ if (this.level + sanitizedLevels > HacknetServerMaxLevel) {
+ const diff = Math.max(0, HacknetServerMaxLevel - this.level);
+ return this.purchaseLevelUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ this.level = Math.round(this.level + sanitizedLevels);
+ this.updateHashRate(p);
+
+ return true;
+ }
+
+ // Returns a boolean indicating whether the RAM was successfully upgraded
+ purchaseRamUpgrade(levels: number, p: IPlayer): boolean {
+ const sanitizedLevels = Math.round(levels);
+ const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
+ if(isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
+ return false;
+ }
+
+ if (this.maxRam >= HacknetServerMaxRam) {
+ return false;
+ }
+
+ // If the specified number of upgrades would exceed the max, try to purchase
+ // just the maximum possible
+ if (this.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
+ const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / this.maxRam)));
+ return this.purchaseRamUpgrade(diff, p);
+ }
+
+ if (!p.canAfford(cost)) {
+ return false;
+ }
+
+ p.loseMoney(cost);
+ for (let i = 0; i < sanitizedLevels; ++i) {
+ this.maxRam *= 2;
+ }
+ this.maxRam = Math.round(this.maxRam);
+
+ return true;
+ }
+
+ /**
+ * Whenever a script is run, we must update this server's hash rate
+ */
+ runScript(script: RunningScript, p?: IPlayer): void {
+ super.runScript(script);
+ if (p) {
+ this.updateHashRate(p);
+ }
+ }
+
+ updateHashCapacity(): void {
+ this.hashCapacity = 16 * Math.pow(2, this.cache);
+ }
+
+ updateHashRate(p: IPlayer): void {
+ const baseGain = HacknetServerHashesPerLevel * this.level;
+ const coreMultiplier = Math.pow(1.1, this.cores - 1);
+ const ramRatio = (1 - this.ramUsed / this.maxRam);
+
+ const hashRate = baseGain * coreMultiplier * ramRatio;
+
+ this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
+
+ if (isNaN(this.hashRate)) {
+ this.hashRate = 0;
+ dialogBoxCreate(`Error calculating Hacknet Server hash production. This is a bug. Please report to game dev`, false);
+ }
+ }
+
+ // Serialize the current object to a JSON save state
+ toJSON(): any {
+ return Generic_toJSON("HacknetServer", this);
+ }
+}
+
+Reviver.constructors.HacknetServer = HacknetServer;
diff --git a/src/Hacknet/HashManager.ts b/src/Hacknet/HashManager.ts
new file mode 100644
index 000000000..4aa9fa5f8
--- /dev/null
+++ b/src/Hacknet/HashManager.ts
@@ -0,0 +1,151 @@
+/**
+ * This is a central class for storing and managing the player's hashes,
+ * which are generated by Hacknet Servers
+ *
+ * It is also used to keep track of what upgrades the player has bought with
+ * his hashes, and contains method for grabbing the data/multipliers from
+ * those upgrades
+ */
+import { HacknetServer } from "./HacknetServer";
+import { HashUpgrades } from "./HashUpgrades";
+
+import { IMap } from "../types";
+import { IPlayer } from "../PersonObjects/IPlayer";
+import { Generic_fromJSON,
+ Generic_toJSON,
+ Reviver } from "../../utils/JSONReviver";
+
+export class HashManager {
+ // Initiatizes a HashManager object from a JSON save state.
+ static fromJSON(value: any): HashManager {
+ return Generic_fromJSON(HashManager, value.data);
+ }
+
+ // Max number of hashes this can hold. Equal to the sum of capacities of
+ // all Hacknet Servers
+ capacity: number = 0;
+
+ // Number of hashes currently in storage
+ hashes: number = 0;
+
+ // Map of Hash Upgrade Name -> levels in that upgrade
+ upgrades: IMap = {};
+
+ constructor() {
+ for (const name in HashUpgrades) {
+ this.upgrades[name] = 0;
+ }
+ }
+
+ /**
+ * Generic helper function for getting a multiplier from a HashUpgrade
+ */
+ getMult(upgName: string): number {
+ const upg = HashUpgrades[upgName];
+ const currLevel = this.upgrades[upgName];
+ if (upg == null || currLevel == null) {
+ console.error(`Could not find Hash Study upgrade`);
+ return 1;
+ }
+
+ return 1 + ((upg.value * currLevel) / 100);
+ }
+
+ /**
+ * One of the Hash upgrades improves studying. This returns that multiplier
+ */
+ getStudyMult(): number {
+ const upgName = "Improve Studying";
+
+ return this.getMult(upgName);
+ }
+
+ /**
+ * One of the Hash upgrades improves gym training. This returns that multiplier
+ */
+ getTrainingMult(): number {
+ const upgName = "Improve Gym Training";
+
+ return this.getMult(upgName);
+ }
+
+ /**
+ * Get the cost (in hashes) of an upgrade
+ */
+ getUpgradeCost(upgName: string): number {
+ const upg = HashUpgrades[upgName];
+ const currLevel = this.upgrades[upgName];
+ if (upg == null || currLevel == null) {
+ console.error(`Invalid Upgrade Name given to HashManager.getUpgradeCost(): ${upgName}`);
+ return Infinity;
+ }
+
+ return upg.getCost(currLevel);
+ }
+
+ storeHashes(numHashes: number): void {
+ this.hashes += numHashes;
+ this.hashes = Math.min(this.hashes, this.capacity);
+ }
+
+ /**
+ * Reverts an upgrade and refunds the hashes used to buy it
+ */
+ refundUpgrade(upgName: string): void {
+ const upg = HashUpgrades[upgName];
+ const currLevel = this.upgrades[upgName];
+ if (upg == null || currLevel == null || currLevel === 0) {
+ console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
+ return;
+ }
+
+ // Reduce the level first, so we get the right cost
+ --this.upgrades[upgName];
+ const cost = upg.getCost(currLevel);
+ this.hashes += cost;
+ }
+
+ updateCapacity(p: IPlayer): void {
+ if (p.hacknetNodes.length <= 0) { this.capacity = 0; }
+ if (!(p.hacknetNodes[0] instanceof HacknetServer)) { this.capacity = 0; }
+
+ let total: number = 0;
+ for (let i = 0; i < p.hacknetNodes.length; ++i) {
+ const hacknetServer = (p.hacknetNodes[i]);
+ total += hacknetServer.hashCapacity;
+ }
+
+ this.capacity = total;
+ }
+
+ /**
+ * Returns boolean indicating whether or not the upgrade was successfully purchased
+ * Note that this does NOT actually implement the effect
+ */
+ upgrade(upgName: string): boolean {
+ const upg = HashUpgrades[upgName];
+ const currLevel = this.upgrades[upgName];
+ if (upg == null || currLevel == null) {
+ console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
+ return false;
+ }
+
+ const cost = upg.getCost(currLevel);
+
+ if (this.hashes < cost) {
+ return false;
+ }
+
+ this.hashes -= cost;
+ ++this.upgrades[upgName];
+
+ return true;
+ }
+
+ //Serialize the current object to a JSON save state.
+ toJSON(): any {
+ return Generic_toJSON("HashManager", this);
+ }
+}
+
+Reviver.constructors.HashManager = HashManager;
diff --git a/src/Hacknet/HashUpgrade.ts b/src/Hacknet/HashUpgrade.ts
new file mode 100644
index 000000000..9f76e995b
--- /dev/null
+++ b/src/Hacknet/HashUpgrade.ts
@@ -0,0 +1,48 @@
+/**
+ * Object representing an upgrade that can be purchased with hashes
+ */
+export interface IConstructorParams {
+ costPerLevel: number;
+ desc: string;
+ hasTargetServer?: boolean;
+ name: string;
+ value: number;
+}
+
+export class HashUpgrade {
+ /**
+ * Base cost for this upgrade. Every time the upgrade is purchased,
+ * its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.)
+ */
+ costPerLevel: number = 0;
+
+ /**
+ * Description of what the upgrade does
+ */
+ desc: string = "";
+
+ /**
+ * Boolean indicating that this upgrade's effect affects a single server,
+ * the "target" server
+ */
+ hasTargetServer: boolean = false;
+
+ // Name of upgrade
+ name: string = "";
+
+ // Generic value used to indicate the potency/amount of this upgrade's effect
+ // The meaning varies between different upgrades
+ value: number = 0;
+
+ constructor(p: IConstructorParams) {
+ this.costPerLevel = p.costPerLevel;
+ this.desc = p.desc;
+ this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false;
+ this.name = p.name;
+ this.value = p.value;
+ }
+
+ getCost(levels: number): number {
+ return Math.round((levels + 1) * this.costPerLevel);
+ }
+}
diff --git a/src/Hacknet/HashUpgrades.ts b/src/Hacknet/HashUpgrades.ts
new file mode 100644
index 000000000..0a89e2525
--- /dev/null
+++ b/src/Hacknet/HashUpgrades.ts
@@ -0,0 +1,18 @@
+/**
+ * Map of all Hash Upgrades
+ * Key = Hash name, Value = HashUpgrade object
+ */
+import { HashUpgrade,
+ IConstructorParams } from "./HashUpgrade";
+import { HashUpgradesMetadata } from "./data/HashUpgradesMetadata";
+import { IMap } from "../types";
+
+export const HashUpgrades: IMap = {};
+
+function createHashUpgrade(p: IConstructorParams) {
+ HashUpgrades[p.name] = new HashUpgrade(p);
+}
+
+for (const metadata of HashUpgradesMetadata) {
+ createHashUpgrade(metadata);
+}
diff --git a/src/Hacknet/IHacknetNode.ts b/src/Hacknet/IHacknetNode.ts
new file mode 100644
index 000000000..32ec81f53
--- /dev/null
+++ b/src/Hacknet/IHacknetNode.ts
@@ -0,0 +1,16 @@
+// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
+// and the upgraded Hacknet Server in BitNode-9
+import { IPlayer } from "../PersonObjects/IPlayer";
+
+export interface IHacknetNode {
+ cores: number;
+ level: number;
+ onlineTimeSeconds: number;
+
+ calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
+ calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
+ calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
+ purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
+ purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
+ purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
+}
diff --git a/src/Hacknet/data/HashUpgradesMetadata.ts b/src/Hacknet/data/HashUpgradesMetadata.ts
new file mode 100644
index 000000000..d1904ec35
--- /dev/null
+++ b/src/Hacknet/data/HashUpgradesMetadata.ts
@@ -0,0 +1,64 @@
+// Metadata used to construct all Hash Upgrades
+import { IConstructorParams } from "../HashUpgrade";
+
+export const HashUpgradesMetadata: IConstructorParams[] = [
+ {
+ costPerLevel: 2,
+ desc: "Sell hashes for $1m",
+ name: "Sell for Money",
+ value: 1e6,
+ },
+ {
+ costPerLevel: 100,
+ desc: "Sell hashes for $1b in Corporation funds",
+ name: "Sell for Corporation Funds",
+ value: 1e9,
+ },
+ {
+ costPerLevel: 100,
+ desc: "Use hashes to decrease the minimum security of a single server by 5%. " +
+ "Note that a server's minimum security cannot go below 1.",
+ hasTargetServer: true,
+ name: "Reduce Minimum Security",
+ value: 0.95,
+ },
+ {
+ costPerLevel: 100,
+ desc: "Use hashes to increase the maximum amount of money on a single server by 5%",
+ hasTargetServer: true,
+ name: "Increase Maximum Money",
+ value: 1.05,
+ },
+ {
+ costPerLevel: 100,
+ desc: "Use hashes to improve the experience earned when studying at a university. " +
+ "This effect persists until you install Augmentations",
+ name: "Improve Studying",
+ value: 20, // Improves studying by value%
+ },
+ {
+ costPerLevel: 100,
+ desc: "Use hashes to improve the experience earned when training at the gym. This effect " +
+ "persists until you install Augmentations",
+ name: "Improve Gym Training",
+ value: 20, // Improves training by value%
+ },
+ {
+ costPerLevel: 250,
+ desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries",
+ name: "Exchange for Corporation Research",
+ value: 1000,
+ },
+ {
+ costPerLevel: 250,
+ desc: "Exchange hashes for 100 Bladeburner Rank",
+ name: "Exchange for Bladeburner Rank",
+ value: 100,
+ },
+ {
+ costPerLevel: 200,
+ desc: "Generate a random Coding Contract on your home computer",
+ name: "Generate Coding Contract",
+ value: 1,
+ },
+]
diff --git a/src/Hacknet/ui/GeneralInfo.jsx b/src/Hacknet/ui/GeneralInfo.jsx
new file mode 100644
index 000000000..3e4b7cd1d
--- /dev/null
+++ b/src/Hacknet/ui/GeneralInfo.jsx
@@ -0,0 +1,56 @@
+/**
+ * React Component for the Hacknet Node UI
+ *
+ * Displays general information about Hacknet Nodes
+ */
+import React from "react";
+
+import { hasHacknetServers } from "../HacknetHelpers";
+
+export class GeneralInfo extends React.Component {
+ getSecondParagraph() {
+ if (hasHacknetServers()) {
+ return `Here, you can purchase a Hacknet Server, an upgraded version of the Hacknet Node. ` +
+ `Hacknet Servers will perform computations and operations on the network, earning ` +
+ `you hashes. Hashes can be spent on a variety of different upgrades.`;
+ } else {
+ return `Here, you can purchase a Hacknet Node, a specialized machine that can connect ` +
+ `and contribute its resources to the Hacknet networ. This allows you to take ` +
+ `a small percentage of profits from hacks performed on the network. Essentially, ` +
+ `you are renting out your Node's computing power.`;
+ }
+ }
+
+ getThirdParagraph() {
+ if (hasHacknetServers()) {
+ return `Hacknet Servers can also be used as servers to run scripts. However, running scripts ` +
+ `on a server will reduce its hash rate (hashes generated per second). A Hacknet Server's hash ` +
+ `rate will be reduced by the percentage of RAM that is being used by that Server to run ` +
+ `scripts.`
+ } else {
+ return `Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node ` +
+ `can be upgraded in order to increase its computing power and thereby increase ` +
+ `the profit you earn from it.`;
+ }
+ }
+
+ render() {
+ return (
+
+
+ The Hacknet is a global, decentralized network of machines. It is used by
+ hackers all around the world to anonymously share computing power and
+ perform distributed cyberattacks without the fear of being traced.
+
"
- });
- containingDiv.appendChild(nodeLevelContainer);
- containingDiv.appendChild(nodeRamContainer);
- containingDiv.appendChild(nodeCoresContainer);
-
- var listItem = createElement("li", {
- class: "hacknet-node"
- });
- listItem.appendChild(containingDiv);
-
- //Upgrade buttons
- nodeLevelContainer.appendChild(createElement("a", {
- id: "hacknet-node-upgrade-level-" + nodeName,
- class: "a-link-button-inactive",
- clickListener: function() {
- let numUpgrades = hacknetNodePurchaseMultiplier;
- if (hacknetNodePurchaseMultiplier == 0) {
- numUpgrades = getMaxNumberLevelUpgrades(nodeObj);
- }
- nodeObj.purchaseLevelUpgrade(numUpgrades);
- updateHacknetNodesContent();
- return false;
- }
- }));
-
- nodeRamContainer.appendChild(createElement("a", {
- id: "hacknet-node-upgrade-ram-" + nodeName,
- class: "a-link-button-inactive",
- clickListener: function() {
- let numUpgrades = hacknetNodePurchaseMultiplier;
- if (hacknetNodePurchaseMultiplier == 0) {
- numUpgrades = getMaxNumberRamUpgrades(nodeObj);
- }
- nodeObj.purchaseRamUpgrade(numUpgrades);
- updateHacknetNodesContent();
- return false;
- }
- }));
-
- nodeCoresContainer.appendChild(createElement("a", {
- id: "hacknet-node-upgrade-core-" + nodeName,
- class: "a-link-button-inactive",
- clickListener: function() {
- let numUpgrades = hacknetNodePurchaseMultiplier;
- if (hacknetNodePurchaseMultiplier == 0) {
- numUpgrades = getMaxNumberCoreUpgrades(nodeObj);
- }
- nodeObj.purchaseCoreUpgrade(numUpgrades);
- updateHacknetNodesContent();
- return false;
- }
- }));
-
- document.getElementById("hacknet-nodes-list").appendChild(listItem);
-
- //Set the text and stuff inside the DOM element
- updateHacknetNodeDomElement(nodeObj);
-}
-
-//Updates information on a single hacknet node DOM element
-function updateHacknetNodeDomElement(nodeObj) {
- var nodeName = nodeObj.name;
-
- updateText("hacknet-node-name-" + nodeName, nodeName);
- updateText("hacknet-node-total-production-" + nodeName, numeralWrapper.formatMoney(nodeObj.totalMoneyGenerated));
- updateText("hacknet-node-production-rate-" + nodeName, "(" + numeralWrapper.formatMoney(nodeObj.moneyGainRatePerSecond) + " / sec)");
- updateText("hacknet-node-level-" + nodeName, nodeObj.level);
- updateText("hacknet-node-ram-" + nodeName, nodeObj.ram + "GB");
- updateText("hacknet-node-cores-" + nodeName, nodeObj.cores);
-
- //Upgrade level
- var upgradeLevelButton = getElementById("hacknet-node-upgrade-level-" + nodeName);
-
- if (nodeObj.level >= CONSTANTS.HacknetNodeMaxLevel) {
- updateText("hacknet-node-upgrade-level-" + nodeName, "MAX LEVEL");
- upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
- } else {
- let multiplier = 0;
- if (hacknetNodePurchaseMultiplier == 0) {
- //Max
- multiplier = getMaxNumberLevelUpgrades(nodeObj);
- } else {
- var levelsToMax = CONSTANTS.HacknetNodeMaxLevel - nodeObj.level;
- multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
- }
-
- var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost(multiplier);
- updateText("hacknet-node-upgrade-level-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeLevelCost))
- if (Player.money.lt(upgradeLevelCost)) {
- upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
- } else {
- upgradeLevelButton.setAttribute("class", "a-link-button");
- }
- }
-
- //Upgrade RAM
- var upgradeRamButton = getElementById("hacknet-node-upgrade-ram-" + nodeName);
-
- if (nodeObj.ram >= CONSTANTS.HacknetNodeMaxRam) {
- updateText("hacknet-node-upgrade-ram-" + nodeName, "MAX RAM");
- upgradeRamButton.setAttribute("class", "a-link-button-inactive");
- } else {
- let multiplier = 0;
- if (hacknetNodePurchaseMultiplier == 0) {
- multiplier = getMaxNumberRamUpgrades(nodeObj);
- } else {
- var levelsToMax = Math.round(Math.log2(CONSTANTS.HacknetNodeMaxRam / nodeObj.ram));
- multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
- }
-
- var upgradeRamCost = nodeObj.calculateRamUpgradeCost(multiplier);
- updateText("hacknet-node-upgrade-ram-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeRamCost));
- if (Player.money.lt(upgradeRamCost)) {
- upgradeRamButton.setAttribute("class", "a-link-button-inactive");
- } else {
- upgradeRamButton.setAttribute("class", "a-link-button");
- }
- }
-
- //Upgrade Cores
- var upgradeCoreButton = getElementById("hacknet-node-upgrade-core-" + nodeName);
-
- if (nodeObj.cores >= CONSTANTS.HacknetNodeMaxCores) {
- updateText("hacknet-node-upgrade-core-" + nodeName, "MAX CORES");
- upgradeCoreButton.setAttribute("class", "a-link-button-inactive");
- } else {
- let multiplier = 0;
- if (hacknetNodePurchaseMultiplier == 0) {
- multiplier = getMaxNumberCoreUpgrades(nodeObj);
- } else {
- var levelsToMax = CONSTANTS.HacknetNodeMaxCores - nodeObj.cores;
- multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
- }
- var upgradeCoreCost = nodeObj.calculateCoreUpgradeCost(multiplier);
- updateText("hacknet-node-upgrade-core-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeCoreCost));
- if (Player.money.lt(upgradeCoreCost)) {
- upgradeCoreButton.setAttribute("class", "a-link-button-inactive");
- } else {
- upgradeCoreButton.setAttribute("class", "a-link-button");
- }
- }
-}
-
-
-function processAllHacknetNodeEarnings(numCycles) {
- var total = 0;
- for (var i = 0; i < Player.hacknetNodes.length; ++i) {
- total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
- }
-
- return total;
-}
-
-function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
- var cyclesPerSecond = 1000 / Engine._idleSpeed;
- var earningPerCycle = nodeObj.moneyGainRatePerSecond / cyclesPerSecond;
- if (isNaN(earningPerCycle)) {
- console.error("Hacknet Node '" + nodeObj.name + "' Calculated earnings is NaN");
- earningPerCycle = 0;
- }
-
- var totalEarnings = numCycles * earningPerCycle;
- nodeObj.totalMoneyGenerated += totalEarnings;
- nodeObj.onlineTimeSeconds += (numCycles * (Engine._idleSpeed / 1000));
- Player.gainMoney(totalEarnings);
- Player.recordMoneySource(totalEarnings, "hacknetnode");
- return totalEarnings;
-}
-
-function getHacknetNode(name) {
- for (var i = 0; i < Player.hacknetNodes.length; ++i) {
- if (Player.hacknetNodes[i].name == name) {
- return Player.hacknetNodes[i];
- }
- }
-
- return null;
-}
-
-export {
- HacknetNode,
- displayHacknetNodesContent,
- getCostOfNextHacknetNode,
- getHacknetNode,
- getMaxNumberLevelUpgrades,
- hacknetNodesInit,
- processAllHacknetNodeEarnings,
- purchaseHacknet,
- updateHacknetNodesContent,
- updateHacknetNodesMultiplierButtons,
- updateTotalHacknetProduction
-};
diff --git a/src/NetscriptEnvironment.js b/src/NetscriptEnvironment.js
index dcdacbb77..480e4b45f 100644
--- a/src/NetscriptEnvironment.js
+++ b/src/NetscriptEnvironment.js
@@ -1,4 +1,3 @@
-import {HacknetNode} from "./HacknetNode";
import {NetscriptFunctions} from "./NetscriptFunctions";
import {NetscriptPort} from "./NetscriptPort";
@@ -56,37 +55,6 @@ Environment.prototype = {
}
},
- setArrayElement: function(name, idx, value) {
- if (!(idx instanceof Array)) {
- throw new Error("idx parameter is not an Array");
- }
- var scope = this.lookup(name);
- if (!scope && this.parent) {
- throw new Error("Undefined variable " + name);
- }
- var arr = (scope || this).vars[name];
- if (!(arr.constructor === Array || arr instanceof Array)) {
- throw new Error("Variable is not an array: " + name);
- }
- var res = arr;
- for (var iterator = 0; iterator < idx.length-1; ++iterator) {
- var i = idx[iterator];
- if (!(res instanceof Array) || i >= res.length) {
- throw new Error("Out-of-bounds array access");
- }
- res = res[i];
- }
-
- //Cant assign to ports or HacknetNodes
- if (res[idx[idx.length-1]] instanceof HacknetNode) {
- throw new Error("Cannot assign a Hacknet Node handle to a new value");
- }
- if (res[idx[idx.length-1]] instanceof NetscriptPort) {
- throw new Error("Cannot assign a Netscript Port handle to a new value");
- }
- return res[idx[idx.length-1]] = value;
- },
-
//Creates (or overwrites) a variable in the current scope
def: function(name, value) {
return this.vars[name] = value;
diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js
index c301f2b76..97e030ab4 100644
--- a/src/NetscriptEvaluator.js
+++ b/src/NetscriptEvaluator.js
@@ -196,7 +196,7 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
}
var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
- server.runningScripts.push(runningScriptObj); //Push onto runningScripts
+ server.runScript(runningScriptObj, Player); // Push onto runningScripts
addWorkerScript(runningScriptObj, server);
return Promise.resolve(true);
}
diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js
index 669bce884..c9380c790 100644
--- a/src/NetscriptFunctions.js
+++ b/src/NetscriptFunctions.js
@@ -30,7 +30,7 @@ import { joinFaction,
purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { getCostOfNextHacknetNode,
- purchaseHacknet } from "./HacknetNode";
+ purchaseHacknet } from "./Hacknet/HacknetNode";
import {Locations} from "./Locations";
import { Message } from "./Message/Message";
import { Messages } from "./Message/MessageHelpers";
@@ -278,27 +278,27 @@ function NetscriptFunctions(workerScript) {
},
upgradeLevel : function(i, n) {
var node = getHacknetNode(i);
- return node.purchaseLevelUpgrade(n);
+ return node.purchaseLevelUpgrade(n, Player);
},
upgradeRam : function(i, n) {
var node = getHacknetNode(i);
- return node.purchaseRamUpgrade(n);
+ return node.purchaseRamUpgrade(n, Player);
},
upgradeCore : function(i, n) {
var node = getHacknetNode(i);
- return node.purchaseCoreUpgrade(n);
+ return node.purchaseCoreUpgrade(n, Player);
},
getLevelUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
- return node.calculateLevelUpgradeCost(n);
+ return node.calculateLevelUpgradeCost(n, Player);
},
getRamUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
- return node.calculateRamUpgradeCost(n);
+ return node.calculateRamUpgradeCost(n, Player);
},
getCoreUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
- return node.calculateCoreUpgradeCost(n);
+ return node.calculateCoreUpgradeCost(n, Player);
}
},
sprintf : sprintf,
diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts
index c41247a2e..c3406d3c7 100644
--- a/src/PersonObjects/IPlayer.ts
+++ b/src/PersonObjects/IPlayer.ts
@@ -9,6 +9,8 @@ import { Sleeve } from "./Sleeve/Sleeve";
import { IMap } from "../types";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
+import { HacknetNode } from "../Hacknet/HacknetNode";
+import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
@@ -22,7 +24,7 @@ export interface IPlayer {
corporation: any;
currentServer: string;
factions: string[];
- hacknetNodes: any[];
+ hacknetNodes: (HacknetNode | HacknetServer)[];
hasWseAccount: boolean;
jobs: IMap;
karma: number;
diff --git a/src/Player.js b/src/Player.js
index 01903d82c..c7ee6502e 100644
--- a/src/Player.js
+++ b/src/Player.js
@@ -21,6 +21,7 @@ import { Faction } from "./Faction/Faction";
import { Factions } from "./Faction/Factions";
import { displayFactionContent } from "./Faction/FactionHelpers";
import {Gang, resetGangs} from "./Gang";
+import { HashManager } from "./Hacknet/HashManager";
import {Locations} from "./Locations";
import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
@@ -111,10 +112,13 @@ function PlayerObject() {
// Company at which player is CURRENTLY working (only valid when the player is actively working)
this.companyName = ""; // Name of Company. Must match a key value in Companies map
- //Servers
+ // Servers
this.currentServer = ""; //IP address of Server currently being accessed through terminal
this.purchasedServers = []; //IP Addresses of purchased servers
+
+ // Hacknet Nodes/Servers
this.hacknetNodes = [];
+ this.hashManager = new HashManager();
//Factions
this.factions = []; //Names of all factions player has joined
@@ -1391,45 +1395,46 @@ PlayerObject.prototype.startClass = function(costMult, expMult, className) {
//Find cost and exp gain per game cycle
var cost = 0;
var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0;
+ const hashManager = this.hashManager;
switch (className) {
case CONSTANTS.ClassStudyComputerScience:
- hackExp = baseStudyComputerScienceExp * expMult / gameCPS;
+ hackExp = baseStudyComputerScienceExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassDataStructures:
cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS;
- hackExp = baseDataStructuresExp * expMult / gameCPS;
+ hackExp = baseDataStructuresExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassNetworks:
cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS;
- hackExp = baseNetworksExp * expMult / gameCPS;
+ hackExp = baseNetworksExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassAlgorithms:
cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS;
- hackExp = baseAlgorithmsExp * expMult / gameCPS;
+ hackExp = baseAlgorithmsExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassManagement:
cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS;
- chaExp = baseManagementExp * expMult / gameCPS;
+ chaExp = baseManagementExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassLeadership:
cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS;
- chaExp = baseLeadershipExp * expMult / gameCPS;
+ chaExp = baseLeadershipExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassGymStrength:
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
- strExp = baseGymExp * expMult / gameCPS;
+ strExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
break;
case CONSTANTS.ClassGymDefense:
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
- defExp = baseGymExp * expMult / gameCPS;
+ defExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
break;
case CONSTANTS.ClassGymDexterity:
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
- dexExp = baseGymExp * expMult / gameCPS;
+ dexExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
break;
case CONSTANTS.ClassGymAgility:
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
- agiExp = baseGymExp * expMult / gameCPS;
+ agiExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
break;
default:
throw new Error("ERR: Invalid/unrecognized class name");
diff --git a/src/RedPill.js b/src/RedPill.js
index 43a913c90..5420e43c5 100644
--- a/src/RedPill.js
+++ b/src/RedPill.js
@@ -213,9 +213,7 @@ function loadBitVerse(destroyedBitNodeNum, flume=false) {
var elemId = "bitnode-" + i.toString();
var elem = clearEventListeners(elemId);
if (elem == null) {return;}
- if (i === 1 || i === 2 || i === 3 || i === 4 || i === 5 ||
- i === 6 || i === 7 || i === 8 || i === 10 || i === 11 ||
- i === 12) {
+ if (i >= 1 && i <= 12) {
elem.addEventListener("click", function() {
var bitNodeKey = "BitNode" + i;
var bitNode = BitNodes[bitNodeKey];
diff --git a/src/SaveObject.js b/src/SaveObject.js
index 5edc37780..d382765c9 100755
--- a/src/SaveObject.js
+++ b/src/SaveObject.js
@@ -10,7 +10,8 @@ import { processPassiveFactionRepGain } from "./Faction/FactionHelpers";
import { loadFconf } from "./Fconf/Fconf";
import { FconfSettings } from "./Fconf/FconfSettings";
import {loadAllGangs, AllGangs} from "./Gang";
-import {processAllHacknetNodeEarnings} from "./HacknetNode";
+import { hasHacknetServers,
+ processHacknetEarnings } from "./Hacknet/HacknetHelpers";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import {Player, loadPlayer} from "./Player";
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
@@ -490,7 +491,10 @@ function loadImportedGame(saveObj, saveString) {
}
//Hacknet Nodes offline progress
- var offlineProductionFromHacknetNodes = processAllHacknetNodeEarnings(numCyclesOffline);
+ var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline);
+ const hacknetProdInfo = hasHacknetServers() ?
+ `${numeralWrapper.format(offlineProductionFromHacknetNodes, "0.000a")} hashes` :
+ `${numeralWrapper.formatMoney(offlineProductionFromHacknetNodes)}`;
//Passive faction rep gain offline
processPassiveFactionRepGain(numCyclesOffline);
@@ -515,8 +519,8 @@ function loadImportedGame(saveObj, saveString) {
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
"generated " +
- numeralWrapper.formatMoney(offlineProductionFromScripts) + " and your Hacknet Nodes generated " +
- numeralWrapper.formatMoney(offlineProductionFromHacknetNodes) + "");
+ numeralWrapper.formatMoney(offlineProductionFromScripts) + " " +
+ "and your Hacknet Nodes generated " + hacknetProdInfo + "");
return true;
}
diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts
new file mode 100644
index 000000000..b09ff9534
--- /dev/null
+++ b/src/Server/BaseServer.ts
@@ -0,0 +1,206 @@
+/**
+ * Abstract Base Class for any Server object
+ */
+import { CodingContract } from "../CodingContracts";
+import { Message } from "../Message/Message";
+import { RunningScript } from "../Script/RunningScript";
+import { Script } from "../Script/Script";
+import { TextFile } from "../TextFile";
+
+import { isScriptFilename } from "../Script/ScriptHelpersTS";
+
+import { createRandomIp } from "../../utils/IPAddress";
+
+interface IConstructorParams {
+ adminRights?: boolean;
+ hostname: string;
+ ip?: string;
+ isConnectedTo?: boolean;
+ maxRam?: number;
+ organizationName?: string;
+}
+
+export abstract class BaseServer {
+ // Coding Contract files on this server
+ contracts: CodingContract[] = [];
+
+ // How many CPU cores this server has. Maximum of 8.
+ // Currently, this only affects hacking missions
+ cpuCores: number = 1;
+
+ // Flag indicating whether the FTP port is open
+ ftpPortOpen: boolean = false;
+
+ // Flag indicating whether player has admin/root access to this server
+ hasAdminRights: boolean = false;
+
+ // Hostname. Must be unique
+ hostname: string = "";
+
+ // Flag indicating whether HTTP Port is open
+ httpPortOpen: boolean = false;
+
+ // IP Address. Must be unique
+ ip: string = "";
+
+ // Flag indicating whether player is curently connected to this server
+ isConnectedTo: boolean = false;
+
+ // RAM (GB) available on this server
+ maxRam: number = 0;
+
+ // Message files AND Literature files on this Server
+ // For Literature files, this array contains only the filename (string)
+ // For Messages, it contains the actual Message object
+ // TODO Separate literature files into its own property
+ messages: (Message | string)[] = [];
+
+ // Name of company/faction/etc. that this server belongs to.
+ // Optional, not applicable to all Servers
+ organizationName: string = "";
+
+ // Programs on this servers. Contains only the names of the programs
+ programs: string[] = [];
+
+ // RAM (GB) used. i.e. unavailable RAM
+ ramUsed: number = 0;
+
+ // RunningScript files on this server
+ runningScripts: RunningScript[] = [];
+
+ // Script files on this Server
+ scripts: Script[] = [];
+
+ // Contains the IP Addresses of all servers that are immediately
+ // reachable from this one
+ serversOnNetwork: string[] = [];
+
+ // Flag indicating whether SMTP Port is open
+ smtpPortOpen: boolean = false;
+
+ // Flag indicating whether SQL Port is open
+ sqlPortOpen: boolean = false;
+
+ // Flag indicating whether the SSH Port is open
+ sshPortOpen: boolean = false;
+
+ // Text files on this server
+ textFiles: TextFile[] = [];
+
+ constructor(params: IConstructorParams={ hostname: "", ip: createRandomIp() }) {
+ this.ip = params.ip ? params.ip : createRandomIp();
+
+ this.hostname = params.hostname;
+ this.organizationName = params.organizationName != null ? params.organizationName : "";
+ this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false;
+
+ //Access information
+ this.hasAdminRights = params.adminRights != null ? params.adminRights : false;
+ }
+
+ addContract(contract: CodingContract) {
+ this.contracts.push(contract);
+ }
+
+ getContract(contractName: string): CodingContract | null {
+ for (const contract of this.contracts) {
+ if (contract.fn === contractName) {
+ return contract;
+ }
+ }
+ return null;
+ }
+
+ // Given the name of the script, returns the corresponding
+ // script object on the server (if it exists)
+ getScript(scriptName: string): Script | null {
+ for (let i = 0; i < this.scripts.length; i++) {
+ if (this.scripts[i].filename === scriptName) {
+ return this.scripts[i];
+ }
+ }
+
+ return null;
+ }
+
+ removeContract(contract: CodingContract) {
+ if (contract instanceof CodingContract) {
+ this.contracts = this.contracts.filter((c) => {
+ return c.fn !== contract.fn;
+ });
+ } else {
+ this.contracts = this.contracts.filter((c) => {
+ return c.fn !== contract;
+ });
+ }
+ }
+
+ /**
+ * Called when a script is run on this server.
+ * All this function does is add a RunningScript object to the
+ * `runningScripts` array. It does NOT check whether the script actually can
+ * be run.
+ */
+ runScript(script: RunningScript): void {
+ this.runningScripts.push(script);
+ }
+
+ setMaxRam(ram: number): void {
+ this.maxRam = ram;
+ }
+
+ /**
+ * Write to a script file
+ * Overwrites existing files. Creates new files if the script does not eixst
+ */
+ writeToScriptFile(fn: string, code: string) {
+ var ret = {success: false, overwritten: false};
+ if (!isScriptFilename(fn)) { return ret; }
+
+ //Check if the script already exists, and overwrite it if it does
+ for (let i = 0; i < this.scripts.length; ++i) {
+ if (fn === this.scripts[i].filename) {
+ let script = this.scripts[i];
+ script.code = code;
+ script.updateRamUsage();
+ script.module = "";
+ ret.overwritten = true;
+ ret.success = true;
+ return ret;
+ }
+ }
+
+ //Otherwise, create a new script
+ const newScript = new Script();
+ newScript.filename = fn;
+ newScript.code = code;
+ newScript.updateRamUsage();
+ newScript.server = this.ip;
+ this.scripts.push(newScript);
+ ret.success = true;
+ return ret;
+ }
+
+ // Write to a text file
+ // Overwrites existing files. Creates new files if the text file does not exist
+ writeToTextFile(fn: string, txt: string) {
+ var ret = { success: false, overwritten: false };
+ if (!fn.endsWith("txt")) { return ret; }
+
+ //Check if the text file already exists, and overwrite if it does
+ for (let i = 0; i < this.textFiles.length; ++i) {
+ if (this.textFiles[i].fn === fn) {
+ ret.overwritten = true;
+ this.textFiles[i].text = txt;
+ ret.success = true;
+ return ret;
+ }
+ }
+
+ //Otherwise create a new text file
+ var newFile = new TextFile(fn, txt);
+ this.textFiles.push(newFile);
+ ret.success = true;
+ return ret;
+ }
+}
diff --git a/src/Server/Server.ts b/src/Server/Server.ts
index bc3056d14..f11398c37 100644
--- a/src/Server/Server.ts
+++ b/src/Server/Server.ts
@@ -1,16 +1,12 @@
-// Class representing a single generic Server
+// Class representing a single hackable Server
+import { BaseServer } from "./BaseServer";
// TODO This import is a circular import. Try to fix it in the future
import { GetServerByHostname } from "./ServerHelpers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
-import { CodingContract } from "../CodingContracts";
-import { Message } from "../Message/Message";
-import { RunningScript } from "../Script/RunningScript";
-import { Script } from "../Script/Script";
-import { isScriptFilename } from "../Script/ScriptHelpersTS";
-import { TextFile } from "../TextFile";
+import { createRandomString } from "../utils/createRandomString";
import { createRandomIp } from "../../utils/IPAddress";
import { Generic_fromJSON,
Generic_toJSON,
@@ -31,7 +27,7 @@ interface IConstructorParams {
serverGrowth?: number;
}
-export class Server {
+export class Server extends BaseServer {
// Initializes a Server Object from a JSON save state
static fromJSON(value: any): Server {
return Generic_fromJSON(Server, value.data);
@@ -41,47 +37,13 @@ export class Server {
// (i.e. security level when the server was created)
baseDifficulty: number = 1;
- // Coding Contract files on this server
- contracts: CodingContract[] = [];
-
- // How many CPU cores this server has. Maximum of 8.
- // Currently, this only affects hacking missions
- cpuCores: number = 1;
-
- // Flag indicating whether the FTP port is open
- ftpPortOpen: boolean = false;
-
// Server Security Level
hackDifficulty: number = 1;
- // Flag indicating whether player has admin/root access to this server
- hasAdminRights: boolean = false;
-
- // Hostname. Must be unique
- hostname: string = "";
-
- // Flag indicating whether HTTP Port is open
- httpPortOpen: boolean = false;
-
- // IP Address. Must be unique
- ip: string = "";
-
- // Flag indicating whether player is curently connected to this server
- isConnectedTo: boolean = false;
-
// Flag indicating whether this server has been manually hacked (ie.
// hacked through Terminal) by the player
manuallyHacked: boolean = false;
- // RAM (GB) available on this server
- maxRam: number = 0;
-
- // Message files AND Literature files on this Server
- // For Literature files, this array contains only the filename (string)
- // For Messages, it contains the actual Message object
- // TODO Separate literature files into its own property
- messages: (Message | string)[] = [];
-
// Minimum server security level that this server can be weakened to
minDifficulty: number = 1;
@@ -97,67 +59,35 @@ export class Server {
// How many ports are currently opened on the server
openPortCount: number = 0;
- // Name of company/faction/etc. that this server belongs to.
- // Optional, not applicable to all Servers
- organizationName: string = "";
-
- // Programs on this servers. Contains only the names of the programs
- programs: string[] = [];
-
// Flag indicating wehther this is a purchased server
purchasedByPlayer: boolean = false;
- // RAM (GB) used. i.e. unavailable RAM
- ramUsed: number = 0;
-
// Hacking level required to hack this server
requiredHackingSkill: number = 1;
- // RunningScript files on this server
- runningScripts: RunningScript[] = [];
-
- // Script files on this Server
- scripts: Script[] = [];
-
// Parameter that affects how effectively this server's money can
// be increased using the grow() Netscript function
serverGrowth: number = 1;
- // Contains the IP Addresses of all servers that are immediately
- // reachable from this one
- serversOnNetwork: string[] = [];
-
- // Flag indicating whether SMTP Port is open
- smtpPortOpen: boolean = false;
-
- // Flag indicating whether SQL Port is open
- sqlPortOpen: boolean = false;
-
- // Flag indicating whether the SSH Port is open
- sshPortOpen: boolean = false;
-
- // Text files on this server
- textFiles: TextFile[] = [];
-
constructor(params: IConstructorParams={hostname: "", ip: createRandomIp() }) {
- /* Properties */
- //Connection information
- this.ip = params.ip ? params.ip : createRandomIp();
+ super(params);
- var hostname = params.hostname;
- var i = 0;
- var suffix = "";
- while (GetServerByHostname(hostname+suffix) != null) {
- //Server already exists
- suffix = "-" + i;
- ++i;
+ // "hacknet-node-X" hostnames are reserved for Hacknet Servers
+ if (this.hostname.startsWith("hacknet-node-")) {
+ this.hostname = createRandomString(10);
+ }
+
+ // Validate hostname by ensuring there are no repeats
+ if (GetServerByHostname(this.hostname) != null) {
+ // Use a for loop to ensure that we don't get suck in an infinite loop somehow
+ let hostname: string = this.hostname;
+ for (let i = 0; i < 200; ++i) {
+ hostname = `${this.hostname}-${i}`;
+ if (GetServerByHostname(hostname) == null) { break; }
+ }
+ this.hostname = hostname;
}
- this.hostname = hostname + suffix;
- this.organizationName = params.organizationName != null ? params.organizationName : "";
- this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false;
- //Access information
- this.hasAdminRights = params.adminRights != null ? params.adminRights : false;
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
//RAM, CPU speed and Scripts
@@ -178,23 +108,9 @@ export class Server {
this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5;
};
- setMaxRam(ram: number): void {
- this.maxRam = ram;
- }
-
- // Given the name of the script, returns the corresponding
- // script object on the server (if it exists)
- getScript(scriptName: string): Script | null {
- for (let i = 0; i < this.scripts.length; i++) {
- if (this.scripts[i].filename === scriptName) {
- return this.scripts[i];
- }
- }
-
- return null;
- }
-
- // Ensures that the server's difficulty (server security) doesn't get too high
+ /**
+ * Ensures that the server's difficulty (server security) doesn't get too high
+ */
capDifficulty(): void {
if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;}
if (this.hackDifficulty < 1) {this.hackDifficulty = 1;}
@@ -204,97 +120,54 @@ export class Server {
if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;}
}
- // Strengthens a server's security level (difficulty) by the specified amount
+ /**
+ * Change this server's minimum security
+ * @param n - Value by which to increase/decrease the server's minimum security
+ * @param perc - Whether it should be changed by a percentage, or a flat value
+ */
+ changeMinimumSecurity(n: number, perc: boolean=false): void {
+ if (perc) {
+ this.minDifficulty *= n;
+ } else {
+ this.minDifficulty += n;
+ }
+
+ // Server security cannot go below 1
+ this.minDifficulty = Math.max(1, this.minDifficulty);
+ }
+
+ /**
+ * Strengthens a server's security level (difficulty) by the specified amount
+ */
fortify(amt: number): void {
this.hackDifficulty += amt;
this.capDifficulty();
}
- // Lowers the server's security level (difficulty) by the specified amount)
+ /**
+ * Change this server's maximum money
+ * @param n - Value by which to change the server's maximum money
+ * @param perc - Whether it should be changed by a percentage, or a flat value
+ */
+ changeMaximumMoney(n: number, perc: boolean=false): void {
+ if (perc) {
+ this.moneyMax *= n;
+ } else {
+ this.moneyMax += n;
+ }
+ }
+
+ /**
+ * Lowers the server's security level (difficulty) by the specified amount)
+ */
weaken(amt: number): void {
this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate);
this.capDifficulty();
}
- // Write to a script file
- // Overwrites existing files. Creates new files if the script does not eixst
- writeToScriptFile(fn: string, code: string) {
- var ret = {success: false, overwritten: false};
- if (!isScriptFilename(fn)) { return ret; }
-
- //Check if the script already exists, and overwrite it if it does
- for (let i = 0; i < this.scripts.length; ++i) {
- if (fn === this.scripts[i].filename) {
- let script = this.scripts[i];
- script.code = code;
- script.updateRamUsage();
- script.module = "";
- ret.overwritten = true;
- ret.success = true;
- return ret;
- }
- }
-
- //Otherwise, create a new script
- const newScript = new Script();
- newScript.filename = fn;
- newScript.code = code;
- newScript.updateRamUsage();
- newScript.server = this.ip;
- this.scripts.push(newScript);
- ret.success = true;
- return ret;
- }
-
- // Write to a text file
- // Overwrites existing files. Creates new files if the text file does not exist
- writeToTextFile(fn: string, txt: string) {
- var ret = { success: false, overwritten: false };
- if (!fn.endsWith("txt")) { return ret; }
-
- //Check if the text file already exists, and overwrite if it does
- for (let i = 0; i < this.textFiles.length; ++i) {
- if (this.textFiles[i].fn === fn) {
- ret.overwritten = true;
- this.textFiles[i].text = txt;
- ret.success = true;
- return ret;
- }
- }
-
- //Otherwise create a new text file
- var newFile = new TextFile(fn, txt);
- this.textFiles.push(newFile);
- ret.success = true;
- return ret;
- }
-
- addContract(contract: CodingContract) {
- this.contracts.push(contract);
- }
-
- removeContract(contract: CodingContract) {
- if (contract instanceof CodingContract) {
- this.contracts = this.contracts.filter((c) => {
- return c.fn !== contract.fn;
- });
- } else {
- this.contracts = this.contracts.filter((c) => {
- return c.fn !== contract;
- });
- }
- }
-
- getContract(contractName: string) {
- for (const contract of this.contracts) {
- if (contract.fn === contractName) {
- return contract;
- }
- }
- return null;
- }
-
- // Serialize the current object to a JSON save state
+ /**
+ * Serialize the current object to a JSON save state
+ */
toJSON(): any {
return Generic_toJSON("Server", this);
}
diff --git a/src/Terminal.js b/src/Terminal.js
index d2744b2c9..08595bc5a 100644
--- a/src/Terminal.js
+++ b/src/Terminal.js
@@ -19,6 +19,7 @@ import {calculateHackingChance,
calculateHackingTime,
calculateGrowTime,
calculateWeakenTime} from "./Hacking";
+import { HacknetServer } from "./Hacknet/HacknetServer";
import {TerminalHelpText, HelpTexts} from "./HelpText";
import {iTutorialNextStep, iTutorialSteps,
ITutorial} from "./InteractiveTutorial";
@@ -760,18 +761,19 @@ let Terminal = {
finishAnalyze: function(cancelled = false) {
if (cancelled == false) {
let currServ = Player.getCurrentServer();
+ const isHacknet = currServ instanceof HacknetServer;
post(currServ.hostname + ": ");
post("Organization name: " + currServ.organizationName);
var rootAccess = "";
if (currServ.hasAdminRights) {rootAccess = "YES";}
else {rootAccess = "NO";}
post("Root Access: " + rootAccess);
- post("Required hacking skill: " + currServ.requiredHackingSkill);
+ if (!isHacknet) { post("Required hacking skill: " + currServ.requiredHackingSkill); }
post("Server security level: " + numeralWrapper.format(currServ.hackDifficulty, '0.000a'));
post("Chance to hack: " + numeralWrapper.format(calculateHackingChance(currServ), '0.00%'));
post("Time to hack: " + numeralWrapper.format(calculateHackingTime(currServ), '0.000') + " seconds");
post("Total money available on server: " + numeralWrapper.format(currServ.moneyAvailable, '$0,0.00'));
- post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired);
+ if (!isHacknet) { post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired); }
if (currServ.sshPortOpen) {
post("SSH port: Open")
@@ -2240,7 +2242,7 @@ let Terminal = {
post("May take a few seconds to start up the process...");
var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads;
- server.runningScripts.push(runningScriptObj);
+ server.runScript(runningScriptObj, Player);
addWorkerScript(runningScriptObj, server);
return;
diff --git a/src/engine.jsx b/src/engine.jsx
index 87d8fffe4..b1e5064e2 100644
--- a/src/engine.jsx
+++ b/src/engine.jsx
@@ -30,8 +30,10 @@ import { FconfSettings } from "./Fconf/FconfSetti
import {displayLocationContent,
initLocationButtons} from "./Location";
import {Locations} from "./Locations";
-import {displayHacknetNodesContent, processAllHacknetNodeEarnings,
- updateHacknetNodesContent} from "./HacknetNode";
+import { hasHacknetServers,
+ renderHacknetNodesUI,
+ clearHacknetNodesUI,
+ processHacknetEarnings } from "./Hacknet/HacknetHelpers";
import {iTutorialStart} from "./InteractiveTutorial";
import {initLiterature} from "./Literature";
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
@@ -109,6 +111,7 @@ import "../css/mainmenu.scss";
import "../css/characteroverview.scss";
import "../css/terminal.scss";
import "../css/scripteditor.scss";
+import "../css/hacknetnodes.scss";
import "../css/menupages.scss";
import "../css/redpill.scss";
import "../css/stockmarket.scss";
@@ -294,8 +297,8 @@ const Engine = {
loadHacknetNodesContent: function() {
Engine.hideAllContent();
Engine.Display.hacknetNodesContent.style.display = "block";
- displayHacknetNodesContent();
routing.navigateTo(Page.HacknetNodes);
+ renderHacknetNodesUI();
MainMenuLinks.HacknetNodes.classList.add("active");
},
@@ -506,7 +509,7 @@ const Engine = {
Engine.Display.characterContent.style.display = "none";
Engine.Display.scriptEditorContent.style.display = "none";
Engine.Display.activeScriptsContent.style.display = "none";
- Engine.Display.hacknetNodesContent.style.display = "none";
+ clearHacknetNodesUI();
Engine.Display.worldContent.style.display = "none";
Engine.Display.createProgramContent.style.display = "none";
Engine.Display.factionsContent.style.display = "none";
@@ -870,7 +873,7 @@ const Engine = {
updateOnlineScriptTimes(numCycles);
//Hacknet Nodes
- processAllHacknetNodeEarnings(numCycles);
+ processHacknetEarnings(numCycles);
},
//Counters for the main event loop. Represent the number of game cycles are required
@@ -932,7 +935,7 @@ const Engine = {
if (Engine.Counters.updateDisplays <= 0) {
Engine.displayCharacterOverviewInfo();
if (routing.isOn(Page.HacknetNodes)) {
- updateHacknetNodesContent();
+ renderHacknetNodesUI();
} else if (routing.isOn(Page.CreateProgram)) {
displayCreateProgramContent();
} else if (routing.isOn(Page.Sleeves)) {
@@ -1183,7 +1186,10 @@ const Engine = {
}
//Hacknet Nodes offline progress
- var offlineProductionFromHacknetNodes = processAllHacknetNodeEarnings(numCyclesOffline);
+ var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline);
+ const hacknetProdInfo = hasHacknetServers() ?
+ `${numeralWrapper.format(offlineProductionFromHacknetNodes, "0.000a")} hashes` :
+ `${numeralWrapper.formatMoney(offlineProductionFromHacknetNodes)}`;
//Passive faction rep gain offline
processPassiveFactionRepGain(numCyclesOffline);
@@ -1237,8 +1243,8 @@ const Engine = {
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
"generated " +
- numeralWrapper.formatMoney(offlineProductionFromScripts) + " and your Hacknet Nodes generated " +
- numeralWrapper.formatMoney(offlineProductionFromHacknetNodes) + "");
+ numeralWrapper.formatMoney(offlineProductionFromScripts) + " " +
+ "and your Hacknet Nodes generated " + hacknetProdInfo + "");
//Close main menu accordions for loaded game
var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
hacknetnodes, city, tutorial, options, dev];
diff --git a/src/index.html b/src/index.html
index d07c437f7..885c2dce4 100644
--- a/src/index.html
+++ b/src/index.html
@@ -203,35 +203,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
-
Hacknet Nodes
-
- The Hacknet is a global, decentralized network of machines. It is used by hackers all around
- the world to anonymously share computing power and perform distributed cyberattacks without the
- fear of being traced.
-
- Here, you can purchase a Hacknet Node, a specialized machine that can connect and contribute its
- resources to the Hacknet network. This allows you to take a small percentage of profits
- from hacks performed on the network. Essentially, you are renting out your Node's computing power.
-
- Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node can be upgraded
- in order to increase its computing power and thereby increase the profit you earn from it.
-