mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-18 21:53:50 +01:00
BitNode-9 initial implementation
This commit is contained in:
parent
2ce4af2498
commit
34d749809a
@ -38,6 +38,7 @@ button {
|
||||
}
|
||||
|
||||
.a-link-button-inactive,
|
||||
.std-button-disabled,
|
||||
.std-button:disabled {
|
||||
text-decoration: none;
|
||||
background-color: #333;
|
||||
|
@ -2,7 +2,7 @@
|
||||
@import "theme";
|
||||
|
||||
/**
|
||||
* Styling for the Character Overview Panel (top-right)
|
||||
* Styling for the Character Overview Panel (top-right panel)
|
||||
*/
|
||||
|
||||
#character-overview-wrapper {
|
||||
|
75
css/hacknetnodes.scss
Normal file
75
css/hacknetnodes.scss
Normal file
@ -0,0 +1,75 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
|
||||
/**
|
||||
* Styling for the Hacknet Nodes UI Page
|
||||
*/
|
||||
|
||||
#hacknet-nodes-container {
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.hacknet-general-info {
|
||||
margin: 10px;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
#hacknet-nodes-container li {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&.hacknet-node {
|
||||
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
|
||||
@include boxShadow($boxShadowArgs);
|
||||
|
||||
margin: 6px;
|
||||
padding: 7px;
|
||||
width: 35vw;
|
||||
border: 2px solid var(--my-highlight-color);
|
||||
}
|
||||
}
|
||||
|
||||
#hacknet-nodes-list {
|
||||
list-style: none;
|
||||
width: 82vw;
|
||||
}
|
||||
|
||||
#hacknet-nodes-money {
|
||||
margin: 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#hacknet-nodes-money-multipliers-div {
|
||||
display: inline-block;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
#hacknet-nodes-multipliers {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#hacknet-nodes-purchase-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hacknet-node-container {
|
||||
display: inline-table;
|
||||
|
||||
.row {
|
||||
display: table-row;
|
||||
height: 30px;
|
||||
|
||||
p {
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
|
||||
.upgradable-info {
|
||||
display: inline-block;
|
||||
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
|
||||
padding: 0 4px;
|
||||
width: $defaultFontSize * 4;
|
||||
}
|
||||
}
|
@ -138,81 +138,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Hacknet Nodes */
|
||||
#hacknet-nodes-container {
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#hacknet-nodes-text,
|
||||
#hacknet-nodes-container li {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#hacknet-nodes-container li {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&.hacknet-node {
|
||||
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
|
||||
@include boxShadow($boxShadowArgs);
|
||||
|
||||
margin: 6px;
|
||||
padding: 7px;
|
||||
width: 35vw;
|
||||
border: 2px solid var(--my-highlight-color);
|
||||
}
|
||||
}
|
||||
|
||||
#hacknet-nodes-list {
|
||||
list-style: none;
|
||||
width: 82vw;
|
||||
}
|
||||
|
||||
#hacknet-nodes-money {
|
||||
margin: 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#hacknet-nodes-money-multipliers-div {
|
||||
display: inline-block;
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
#hacknet-nodes-multipliers {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#hacknet-nodes-purchase-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hacknet-node-container {
|
||||
display: inline-table;
|
||||
|
||||
.row {
|
||||
display: table-row;
|
||||
height: 30px;
|
||||
|
||||
p {
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
|
||||
.upgradable-info {
|
||||
display: inline-block;
|
||||
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
|
||||
padding: 0 4px;
|
||||
width: $defaultFontSize * 4;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-page-text {
|
||||
width: 70vw;
|
||||
}
|
||||
|
||||
/* World */
|
||||
#world-container {
|
||||
position: fixed;
|
||||
|
@ -2069,7 +2069,7 @@ function installAugmentations(cbScript=null) {
|
||||
}
|
||||
var runningScriptObj = new RunningScript(script, []); //No args
|
||||
runningScriptObj.threads = 1; //Only 1 thread
|
||||
home.runningScripts.push(runningScriptObj);
|
||||
home.runScript(runningScriptObj, Player);
|
||||
addWorkerScript(runningScriptObj, home);
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,10 @@ export function initBitNodes() {
|
||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||
"This Source-File also increases your hacking growth multipliers by: " +
|
||||
"<br>Level 1: 12%<br>Level 2: 18%<br>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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<any> = {
|
||||
@ -17,24 +22,9 @@ export let CONSTANTS: IMap<any> = {
|
||||
/* 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,
|
||||
|
431
src/Hacknet/HacknetHelpers.jsx
Normal file
431
src/Hacknet/HacknetHelpers.jsx
Normal file
@ -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(<HacknetRoot />, 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;
|
||||
}
|
285
src/Hacknet/HacknetNode.ts
Normal file
285
src/Hacknet/HacknetNode.ts
Normal file
@ -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;
|
348
src/Hacknet/HacknetServer.ts
Normal file
348
src/Hacknet/HacknetServer.ts
Normal file
@ -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;
|
151
src/Hacknet/HashManager.ts
Normal file
151
src/Hacknet/HashManager.ts
Normal file
@ -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<number> = {};
|
||||
|
||||
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 = <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;
|
48
src/Hacknet/HashUpgrade.ts
Normal file
48
src/Hacknet/HashUpgrade.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
18
src/Hacknet/HashUpgrades.ts
Normal file
18
src/Hacknet/HashUpgrades.ts
Normal file
@ -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<HashUpgrade> = {};
|
||||
|
||||
function createHashUpgrade(p: IConstructorParams) {
|
||||
HashUpgrades[p.name] = new HashUpgrade(p);
|
||||
}
|
||||
|
||||
for (const metadata of HashUpgradesMetadata) {
|
||||
createHashUpgrade(metadata);
|
||||
}
|
16
src/Hacknet/IHacknetNode.ts
Normal file
16
src/Hacknet/IHacknetNode.ts
Normal file
@ -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;
|
||||
}
|
64
src/Hacknet/data/HashUpgradesMetadata.ts
Normal file
64
src/Hacknet/data/HashUpgradesMetadata.ts
Normal file
@ -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,
|
||||
},
|
||||
]
|
56
src/Hacknet/ui/GeneralInfo.jsx
Normal file
56
src/Hacknet/ui/GeneralInfo.jsx
Normal file
@ -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 (
|
||||
<div>
|
||||
<p className={"hacknet-general-info"}>
|
||||
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.
|
||||
</p>
|
||||
<br />
|
||||
<p className={"hacknet-general-info"}>
|
||||
{this.getSecondParagraph()}
|
||||
</p>
|
||||
<br />
|
||||
<p className={"hacknet-general-info"}>
|
||||
{this.getThirdParagraph()}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
153
src/Hacknet/ui/HacknetNode.jsx
Normal file
153
src/Hacknet/ui/HacknetNode.jsx
Normal file
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* React Component for the Hacknet Node UI.
|
||||
* This Component displays the panel for a single Hacknet Node
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { HacknetNodeMaxLevel,
|
||||
HacknetNodeMaxRam,
|
||||
HacknetNodeMaxCores } from "../HacknetNode";
|
||||
import { getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
export class HacknetNode extends React.Component {
|
||||
render() {
|
||||
const node = this.props.node;
|
||||
const purchaseMult = this.props.purchaseMultiplier;
|
||||
const recalculate = this.props.recalculate;
|
||||
|
||||
// Upgrade Level Button
|
||||
let upgradeLevelText, upgradeLevelClass;
|
||||
if (node.level >= HacknetNodeMaxLevel) {
|
||||
upgradeLevelText = "MAX LEVEL";
|
||||
upgradeLevelClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||
} else {
|
||||
const levelsToMax = HacknetNodeMaxLevel - node.level;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||
if (Player.money.lt(upgradeLevelCost)) {
|
||||
upgradeLevelClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeLevelClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeLevelOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||
}
|
||||
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
||||
let upgradeRamText, upgradeRamClass;
|
||||
if (node.ram >= HacknetNodeMaxRam) {
|
||||
upgradeRamText = "MAX RAM";
|
||||
upgradeRamClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||
} else {
|
||||
const levelsToMax = Math.round(Math.log2(HacknetNodeMaxRam / node.ram));
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||
if (Player.money.lt(upgradeRamCost)) {
|
||||
upgradeRamClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeRamClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeRamOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||
}
|
||||
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
||||
let upgradeCoresText, upgradeCoresClass;
|
||||
if (node.cores >= HacknetNodeMaxCores) {
|
||||
upgradeCoresText = "MAX CORES";
|
||||
upgradeCoresClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||
} else {
|
||||
const levelsToMax = HacknetNodeMaxCores - node.cores;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||
if (Player.money.lt(upgradeCoreCost)) {
|
||||
upgradeCoresClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeCoresClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeCoresOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||
}
|
||||
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={"hacknet-node"}>
|
||||
<div className={"hacknet-node-container"}>
|
||||
<div className={"row"}>
|
||||
<p>Node name:</p>
|
||||
<span className={"text"}>{node.name}</span>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Production:</p>
|
||||
<span className={"text money-gold"}>
|
||||
{numeralWrapper.formatMoney(node.totalMoneyGenerated)} ({numeralWrapper.formatMoney(node.moneyGainRatePerSecond)} / sec)
|
||||
</span>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span>
|
||||
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
|
||||
{upgradeLevelText}
|
||||
</button>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>RAM:</p><span className={"text upgradable-info"}>{node.ram}GB</span>
|
||||
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
|
||||
{upgradeRamText}
|
||||
</button>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span>
|
||||
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
|
||||
{upgradeCoresText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
}
|
200
src/Hacknet/ui/HacknetServer.jsx
Normal file
200
src/Hacknet/ui/HacknetServer.jsx
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* React Component for the Hacknet Node UI.
|
||||
* This Component displays the panel for a single Hacknet Node
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { HacknetServerMaxLevel,
|
||||
HacknetServerMaxRam,
|
||||
HacknetServerMaxCores,
|
||||
HacknetServerMaxCache } from "../HacknetServer";
|
||||
import { getMaxNumberLevelUpgrades,
|
||||
getMaxNumberRamUpgrades,
|
||||
getMaxNumberCoreUpgrades,
|
||||
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
export class HacknetServer extends React.Component {
|
||||
render() {
|
||||
const node = this.props.node;
|
||||
const purchaseMult = this.props.purchaseMultiplier;
|
||||
const recalculate = this.props.recalculate;
|
||||
|
||||
// Upgrade Level Button
|
||||
let upgradeLevelText, upgradeLevelClass;
|
||||
if (node.level >= HacknetServerMaxLevel) {
|
||||
upgradeLevelText = "MAX LEVEL";
|
||||
upgradeLevelClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||
} else {
|
||||
const levelsToMax = HacknetServerMaxLevel - node.level;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||
if (Player.money.lt(upgradeLevelCost)) {
|
||||
upgradeLevelClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeLevelClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeLevelOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||
}
|
||||
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upgrade RAM Button
|
||||
let upgradeRamText, upgradeRamClass;
|
||||
if (node.maxRam >= HacknetServerMaxRam) {
|
||||
upgradeRamText = "MAX RAM";
|
||||
upgradeRamClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||
} else {
|
||||
const levelsToMax = Math.round(Math.log2(HacknetServerMaxRam / node.maxRam));
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||
if (Player.money.lt(upgradeRamCost)) {
|
||||
upgradeRamClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeRamClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeRamOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||
}
|
||||
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upgrade Cores Button
|
||||
let upgradeCoresText, upgradeCoresClass;
|
||||
if (node.cores >= HacknetServerMaxCores) {
|
||||
upgradeCoresText = "MAX CORES";
|
||||
upgradeCoresClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||
} else {
|
||||
const levelsToMax = HacknetServerMaxCores - node.cores;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||
if (Player.money.lt(upgradeCoreCost)) {
|
||||
upgradeCoresClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeCoresClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeCoresOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||
}
|
||||
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upgrade Cache button
|
||||
let upgradeCacheText, upgradeCacheClass;
|
||||
if (node.cache >= HacknetServerMaxCache) {
|
||||
upgradeCacheText = "MAX CACHE";
|
||||
upgradeCacheClass = "std-button-disabled";
|
||||
} else {
|
||||
let multiplier = 0;
|
||||
if (purchaseMult === "MAX") {
|
||||
multiplier = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||
} else {
|
||||
const levelsToMax = HacknetServerMaxCache - node.cache;
|
||||
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||
}
|
||||
|
||||
const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier);
|
||||
upgradeCacheText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCacheCost)}`;
|
||||
if (Player.money.lt(upgradeCacheCost)) {
|
||||
upgradeCacheClass = "std-button-disabled";
|
||||
} else {
|
||||
upgradeCacheClass = "std-button";
|
||||
}
|
||||
}
|
||||
const upgradeCacheOnClick = () => {
|
||||
let numUpgrades = purchaseMult;
|
||||
if (purchaseMult === "MAX") {
|
||||
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||
}
|
||||
node.purchaseCacheUpgrade(numUpgrades, Player);
|
||||
recalculate();
|
||||
Player.hashManager.updateCapacity(Player);
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={"hacknet-node"}>
|
||||
<div className={"hacknet-node-container"}>
|
||||
<div className={"row"}>
|
||||
<p>Node name:</p>
|
||||
<span className={"text"}>{node.hostname}</span>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Production:</p>
|
||||
<span className={"text money-gold"}>
|
||||
{numeralWrapper.formatBigNumber(node.totalHashesGenerated)} ({numeralWrapper.formatBigNumber(node.hashRate)} / sec)
|
||||
</span>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Hash Capacity:</p>
|
||||
<span className={"text"}>{node.hashCapacity}</span>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span>
|
||||
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
|
||||
{upgradeLevelText}
|
||||
</button>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>RAM:</p><span className={"text upgradable-info"}>{node.maxRam}GB</span>
|
||||
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
|
||||
{upgradeRamText}
|
||||
</button>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span>
|
||||
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
|
||||
{upgradeCoresText}
|
||||
</button>
|
||||
</div>
|
||||
<div className={"row"}>
|
||||
<p>Cache Level:</p><span className={"text upgradable-info"}>{node.cache}</span>
|
||||
<button className={upgradeCacheClass} onClick={upgradeCacheOnClick}>
|
||||
{upgradeCacheText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
}
|
137
src/Hacknet/ui/HashUpgradePopup.jsx
Normal file
137
src/Hacknet/ui/HashUpgradePopup.jsx
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Create the pop-up for purchasing upgrades with hashes
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { purchaseHashUpgrade } from "../HacknetHelpers";
|
||||
import { HashManager } from "../HashManager";
|
||||
import { HashUpgrades } from "../HashUpgrades";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
import { AllServers } from "../../Server/AllServers";
|
||||
import { Server } from "../../Server/Server";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { ServerDropdown,
|
||||
ServerType } from "../../ui/React/ServerDropdown"
|
||||
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
class HashUpgrade extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedServer: "foodnstuff",
|
||||
}
|
||||
|
||||
this.changeTargetServer = this.changeTargetServer.bind(this);
|
||||
this.purchase = this.purchase.bind(this, this.props.hashManager, this.props.upg);
|
||||
}
|
||||
|
||||
changeTargetServer(e) {
|
||||
this.setState({
|
||||
selectedServer: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
purchase(hashManager, upg) {
|
||||
const canPurchase = hashManager.hashes >= hashManager.getUpgradeCost(upg.name);
|
||||
if (canPurchase) {
|
||||
const res = purchaseHashUpgrade(upg.name, this.state.selectedServer);
|
||||
if (res) {
|
||||
this.props.rerender();
|
||||
} else {
|
||||
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 this upgrade affects.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const hashManager = this.props.hashManager;
|
||||
const upg = this.props.upg;
|
||||
const cost = hashManager.getUpgradeCost(upg.name);
|
||||
|
||||
// Purchase button
|
||||
const canPurchase = hashManager.hashes >= cost;
|
||||
const btnClass = canPurchase ? "std-button" : "std-button-disabled";
|
||||
|
||||
// We'll reuse a Bladeburner css class
|
||||
return (
|
||||
<div className={"bladeburner-action"}>
|
||||
<h2>{upg.name}</h2>
|
||||
<p>Cost: {numeralWrapper.format(cost, "0.000a")}</p>
|
||||
<p>{upg.desc}</p>
|
||||
<button className={btnClass} onClick={this.purchase}>
|
||||
Purchase
|
||||
</button>
|
||||
{
|
||||
upg.hasTargetServer &&
|
||||
<ServerDropdown
|
||||
serverType={ServerType.Foreign}
|
||||
onChange={this.changeTargetServer}
|
||||
style={{margin: "5px"}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class HashUpgradePopup extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.closePopup = this.closePopup.bind(this);
|
||||
|
||||
this.state = {
|
||||
totalHashes: Player.hashManager.hashes,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.interval = setInterval(() => this.tick(), 1e3);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
closePopup() {
|
||||
removePopup(this.props.popupId);
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.setState({
|
||||
totalHashes: Player.hashManager.hashes,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const rerender = this.props.rerender;
|
||||
|
||||
const hashManager = Player.hashManager;
|
||||
if (!(hashManager instanceof HashManager)) {
|
||||
throw new Error(`Player does not have a HashManager)`);
|
||||
}
|
||||
|
||||
const upgradeElems = Object.keys(HashUpgrades).map((upgName) => {
|
||||
const upg = HashUpgrades[upgName];
|
||||
return <HashUpgrade upg={upg} hashManager={hashManager} key={upg.name} rerender={rerender} />
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button className={"std-button"} onClick={this.closePopup}>
|
||||
Close
|
||||
</button>
|
||||
<p>Spend your hashes on a variety of different upgrades</p>
|
||||
<p>Hashes: {numeralWrapper.formatBigNumber(this.state.totalHashes)}</p>
|
||||
{upgradeElems}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
41
src/Hacknet/ui/MultiplierButtons.jsx
Normal file
41
src/Hacknet/ui/MultiplierButtons.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* React Component for the Multiplier buttons on the Hacknet page.
|
||||
* These buttons let the player control how many Nodes/Upgrades they're
|
||||
* purchasing when using the UI (x1, x5, x10, MAX)
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { PurchaseMultipliers } from "./Root";
|
||||
|
||||
function MultiplierButton(props) {
|
||||
return (
|
||||
<button className={props.className} onClick={props.onClick}>{props.text}</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function MultiplierButtons(props) {
|
||||
if (props.purchaseMultiplier == null) {
|
||||
throw new Error(`MultiplierButtons constructed without required props`);
|
||||
}
|
||||
|
||||
const mults = ["x1", "x5", "x10", "MAX"];
|
||||
const onClicks = props.onClicks;
|
||||
const buttons = [];
|
||||
for (let i = 0; i < mults.length; ++i) {
|
||||
const mult = mults[i];
|
||||
const btnProps = {
|
||||
className: props.purchaseMultiplier === PurchaseMultipliers[mult] ? "std-button-disabled" : "std-button",
|
||||
key: mult,
|
||||
onClick: onClicks[i],
|
||||
text: mult,
|
||||
}
|
||||
|
||||
buttons.push(<MultiplierButton {...btnProps} />)
|
||||
}
|
||||
|
||||
return (
|
||||
<span id={"hacknet-nodes-multipliers"}>
|
||||
{buttons}
|
||||
</span>
|
||||
)
|
||||
}
|
51
src/Hacknet/ui/PlayerInfo.jsx
Normal file
51
src/Hacknet/ui/PlayerInfo.jsx
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* React Component for displaying Player info and stats on the Hacknet Node UI.
|
||||
* This includes:
|
||||
* - Player's money
|
||||
* - Player's production from Hacknet Nodes
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { hasHacknetServers } from "../HacknetHelpers";
|
||||
import { Player } from "../../Player";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
export function PlayerInfo(props) {
|
||||
const hasServers = hasHacknetServers();
|
||||
|
||||
let prod;
|
||||
if (hasServers) {
|
||||
prod = numeralWrapper.format(props.totalProduction, "0.000a") + " hashes / sec";
|
||||
} else {
|
||||
prod = numeralWrapper.formatMoney(props.totalProduction) + " / sec";
|
||||
}
|
||||
|
||||
let hashInfo;
|
||||
if (hasServers) {
|
||||
hashInfo = numeralWrapper.format(Player.hashManager.hashes, "0.000a") + " / " +
|
||||
numeralWrapper.format(Player.hashManager.capacity, "0.000a");
|
||||
}
|
||||
|
||||
return (
|
||||
<p id={"hacknet-nodes-money"}>
|
||||
<span>Money:</span>
|
||||
<span className={"money-gold"}>{numeralWrapper.formatMoney(Player.money.toNumber())}</span><br />
|
||||
|
||||
{
|
||||
hasServers &&
|
||||
<span>Hashes:</span>
|
||||
}
|
||||
{
|
||||
hasServers &&
|
||||
<span className={"money-gold"}>{hashInfo}</span>
|
||||
}
|
||||
{
|
||||
hasServers &&
|
||||
<br />
|
||||
}
|
||||
|
||||
<span>Total Hacknet Node Production:</span>
|
||||
<span className={"money-gold"}>{prod}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
40
src/Hacknet/ui/PurchaseButton.jsx
Normal file
40
src/Hacknet/ui/PurchaseButton.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* React Component for the button that is used to purchase new Hacknet Nodes
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { hasHacknetServers,
|
||||
hasMaxNumberHacknetServers } from "../HacknetHelpers";
|
||||
import { Player } from "../../Player";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
export function PurchaseButton(props) {
|
||||
if (props.multiplier == null || props.onClick == null) {
|
||||
throw new Error(`PurchaseButton constructed without required props`);
|
||||
}
|
||||
|
||||
const cost = props.cost;
|
||||
let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled";
|
||||
let text;
|
||||
let style = null;
|
||||
if (hasHacknetServers()) {
|
||||
if (hasMaxNumberHacknetServers()) {
|
||||
className = "std-button-disabled";
|
||||
text = "Hacknet Server limit reached";
|
||||
style = {color: "red"};
|
||||
} else {
|
||||
text = `Purchase Hacknet Server - ${numeralWrapper.formatMoney(cost)}`;
|
||||
}
|
||||
} else {
|
||||
text = `Purchase Hacknet Node - ${numeralWrapper.formatMoney(cost)}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className}
|
||||
onClick={props.onClick}
|
||||
style={style}>
|
||||
{text}
|
||||
</button>
|
||||
|
||||
)
|
||||
}
|
144
src/Hacknet/ui/Root.jsx
Normal file
144
src/Hacknet/ui/Root.jsx
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Root React Component for the Hacknet Node UI
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
import { GeneralInfo } from "./GeneralInfo";
|
||||
import { HacknetNode } from "./HacknetNode";
|
||||
import { HacknetServer } from "./HacknetServer";
|
||||
import { HashUpgradePopup } from "./HashUpgradePopup";
|
||||
import { MultiplierButtons } from "./MultiplierButtons";
|
||||
import { PlayerInfo } from "./PlayerInfo";
|
||||
import { PurchaseButton } from "./PurchaseButton";
|
||||
|
||||
import { getCostOfNextHacknetNode,
|
||||
getCostOfNextHacknetServer,
|
||||
hasHacknetServers,
|
||||
purchaseHacknet } from "../HacknetHelpers";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
|
||||
export const PurchaseMultipliers = Object.freeze({
|
||||
"x1": 1,
|
||||
"x5": 5,
|
||||
"x10": 10,
|
||||
"MAX": "MAX",
|
||||
});
|
||||
|
||||
export class HacknetRoot extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
purchaseMultiplier: PurchaseMultipliers.x1,
|
||||
totalProduction: 0, // Total production ($ / s) of Hacknet Nodes
|
||||
}
|
||||
|
||||
this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.recalculateTotalProduction();
|
||||
}
|
||||
|
||||
createHashUpgradesPopup() {
|
||||
const id = "hacknet-server-hash-upgrades-popup";
|
||||
createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup });
|
||||
}
|
||||
|
||||
recalculateTotalProduction() {
|
||||
let total = 0;
|
||||
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
if (hasHacknetServers()) {
|
||||
total += Player.hacknetNodes[i].hashRate;
|
||||
} else {
|
||||
total += Player.hacknetNodes[i].moneyGainRatePerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
totalProduction: total,
|
||||
});
|
||||
}
|
||||
|
||||
setPurchaseMultiplier(mult) {
|
||||
this.setState({
|
||||
purchaseMultiplier: mult,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// Cost to purchase a new Hacknet Node
|
||||
let purchaseCost;
|
||||
if (hasHacknetServers()) {
|
||||
purchaseCost = getCostOfNextHacknetServer();
|
||||
} else {
|
||||
purchaseCost = getCostOfNextHacknetNode();
|
||||
}
|
||||
|
||||
// onClick event handler for purchase button
|
||||
const purchaseOnClick = () => {
|
||||
if (purchaseHacknet() >= 0) {
|
||||
this.recalculateTotalProduction();
|
||||
Player.hashManager.updateCapacity(Player);
|
||||
}
|
||||
}
|
||||
|
||||
// onClick event handlers for purchase multiplier buttons
|
||||
const purchaseMultiplierOnClicks = [
|
||||
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1),
|
||||
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x5),
|
||||
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x10),
|
||||
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.MAX),
|
||||
];
|
||||
|
||||
// HacknetNode components
|
||||
const nodes = Player.hacknetNodes.map((node) => {
|
||||
if (hasHacknetServers()) {
|
||||
return (
|
||||
<HacknetServer
|
||||
key={node.hostname}
|
||||
node={node}
|
||||
purchaseMultiplier={this.state.purchaseMultiplier}
|
||||
recalculate={this.recalculateTotalProduction.bind(this)}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<HacknetNode
|
||||
key={node.name}
|
||||
node={node}
|
||||
purchaseMultiplier={this.state.purchaseMultiplier}
|
||||
recalculate={this.recalculateTotalProduction.bind(this)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Hacknet Nodes</h1>
|
||||
<GeneralInfo />
|
||||
|
||||
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={purchaseOnClick} />
|
||||
|
||||
<br />
|
||||
<div id={"hacknet-nodes-money-multipliers-div"}>
|
||||
<PlayerInfo totalProduction={this.state.totalProduction} />
|
||||
<MultiplierButtons onClicks={purchaseMultiplierOnClicks} purchaseMultiplier={this.state.purchaseMultiplier} />
|
||||
</div>
|
||||
|
||||
{
|
||||
hasHacknetServers() &&
|
||||
<button className={"std-button"} onClick={this.createHashUpgradesPopup} style={{display: "block"}}>
|
||||
{"Spend Hashes on Upgrades"}
|
||||
</button>
|
||||
}
|
||||
|
||||
<ul id={"hacknet-nodes-list"}>{nodes}</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,694 +0,0 @@
|
||||
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { Engine } from "./engine";
|
||||
import {iTutorialSteps, iTutorialNextStep,
|
||||
ITutorial} from "./InteractiveTutorial";
|
||||
import {Player} from "./Player";
|
||||
import {Page, routing} from "./ui/navigationTracking";
|
||||
import { numeralWrapper } from "./ui/numeralFormat";
|
||||
|
||||
import {dialogBoxCreate} from "../utils/DialogBox";
|
||||
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
|
||||
import {Reviver, Generic_toJSON,
|
||||
Generic_fromJSON} from "../utils/JSONReviver";
|
||||
import {createElement} from "../utils/uiHelpers/createElement";
|
||||
import {getElementById} from "../utils/uiHelpers/getElementById";
|
||||
|
||||
// Stores total money gain rate from all of the player's Hacknet Nodes
|
||||
let TotalHacknetNodeProduction = 0;
|
||||
|
||||
/**
|
||||
* Overwrites the inner text of the specified HTML element if it is different from what currently exists.
|
||||
* @param {string} elementId The HTML ID to find the first instance of.
|
||||
* @param {string} text The inner text that should be set.
|
||||
*/
|
||||
function updateText(elementId, text) {
|
||||
var el = getElementById(elementId);
|
||||
if (el.innerText != text) {
|
||||
el.innerText = text;
|
||||
}
|
||||
};
|
||||
|
||||
/* HacknetNode.js */
|
||||
function hacknetNodesInit() {
|
||||
var performMapping = function(x) {
|
||||
getElementById("hacknet-nodes-" + x.id + "-multiplier")
|
||||
.addEventListener("click", function() {
|
||||
hacknetNodePurchaseMultiplier = x.multiplier;
|
||||
updateHacknetNodesMultiplierButtons();
|
||||
updateHacknetNodesContent();
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
var mappings = [
|
||||
{ id: "1x", multiplier: 1 },
|
||||
{ id: "5x", multiplier: 5 },
|
||||
{ id: "10x", multiplier: 10 },
|
||||
{ id: "max", multiplier: 0 }
|
||||
];
|
||||
for (var elem of mappings) {
|
||||
// Encapsulate in a function so that the appropriate scope is kept in the click handler.
|
||||
performMapping(elem);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", hacknetNodesInit, false);
|
||||
|
||||
function HacknetNode(name) {
|
||||
this.level = 1;
|
||||
this.ram = 1; //GB
|
||||
this.cores = 1;
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.totalMoneyGenerated = 0;
|
||||
this.onlineTimeSeconds = 0;
|
||||
|
||||
this.moneyGainRatePerSecond = 0;
|
||||
}
|
||||
|
||||
HacknetNode.prototype.updateMoneyGainRate = function() {
|
||||
//How much extra $/s is gained per level
|
||||
var gainPerLevel = CONSTANTS.HacknetNodeMoneyGainPerLevel;
|
||||
|
||||
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
||||
Math.pow(1.035, this.ram-1) *
|
||||
((this.cores + 5) / 6) *
|
||||
Player.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");
|
||||
}
|
||||
|
||||
updateTotalHacknetProduction();
|
||||
}
|
||||
|
||||
HacknetNode.prototype.calculateLevelUpgradeCost = function(levels=1) {
|
||||
levels = Math.round(levels);
|
||||
if (isNaN(levels) || levels < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.level >= CONSTANTS.HacknetNodeMaxLevel) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
var mult = CONSTANTS.HacknetNodeUpgradeLevelMult;
|
||||
var totalMultiplier = 0; //Summed
|
||||
var currLevel = this.level;
|
||||
for (var i = 0; i < levels; ++i) {
|
||||
totalMultiplier += Math.pow(mult, currLevel);
|
||||
++currLevel;
|
||||
}
|
||||
|
||||
return CONSTANTS.BaseCostForHacknetNode / 2 * totalMultiplier * Player.hacknet_node_level_cost_mult;
|
||||
}
|
||||
|
||||
HacknetNode.prototype.purchaseLevelUpgrade = function(levels=1) {
|
||||
levels = Math.round(levels);
|
||||
var cost = this.calculateLevelUpgradeCost(levels);
|
||||
if (isNaN(cost) || levels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//If we're at max level, return false
|
||||
if (this.level >= CONSTANTS.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 + levels > CONSTANTS.HacknetNodeMaxLevel) {
|
||||
var diff = Math.max(0, CONSTANTS.HacknetNodeMaxLevel - this.level);
|
||||
return this.purchaseLevelUpgrade(diff);
|
||||
}
|
||||
|
||||
if (Player.money.lt(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
this.level = Math.round(this.level + levels); //Just in case of floating point imprecision
|
||||
this.updateMoneyGainRate();
|
||||
return true;
|
||||
}
|
||||
|
||||
HacknetNode.prototype.calculateRamUpgradeCost = function(levels=1) {
|
||||
levels = Math.round(levels);
|
||||
if (isNaN(levels) || levels < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.ram >= CONSTANTS.HacknetNodeMaxRam) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
let totalCost = 0;
|
||||
let numUpgrades = Math.round(Math.log2(this.ram));
|
||||
let currentRam = this.ram;
|
||||
|
||||
for (let i = 0; i < levels; ++i) {
|
||||
let baseCost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHacknetNode;
|
||||
let mult = Math.pow(CONSTANTS.HacknetNodeUpgradeRamMult, numUpgrades);
|
||||
|
||||
totalCost += (baseCost * mult);
|
||||
|
||||
currentRam *= 2;
|
||||
++numUpgrades;
|
||||
}
|
||||
|
||||
totalCost *= Player.hacknet_node_ram_cost_mult
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
HacknetNode.prototype.purchaseRamUpgrade = function(levels=1) {
|
||||
levels = Math.round(levels);
|
||||
var cost = this.calculateRamUpgradeCost(levels);
|
||||
if (isNaN(cost) || levels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if we're already at max
|
||||
if (this.ram >= CONSTANTS.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, levels) > CONSTANTS.HacknetNodeMaxRam) {
|
||||
var diff = Math.max(0, Math.log2(Math.round(CONSTANTS.HacknetNodeMaxRam / this.ram)));
|
||||
return this.purchaseRamUpgrade(diff);
|
||||
}
|
||||
|
||||
if (Player.money.lt(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
for (let i = 0; i < levels; ++i) {
|
||||
this.ram *= 2; //Ram is always doubled
|
||||
}
|
||||
this.ram = Math.round(this.ram); //Handle any floating point precision issues
|
||||
this.updateMoneyGainRate();
|
||||
return true;
|
||||
}
|
||||
|
||||
HacknetNode.prototype.calculateCoreUpgradeCost = function(levels=1) {
|
||||
levels = Math.round(levels);
|
||||
if (isNaN(levels) || levels < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.cores >= CONSTANTS.HacknetNodeMaxCores) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
const coreBaseCost = CONSTANTS.BaseCostForHacknetNodeCore;
|
||||
const mult = CONSTANTS.HacknetNodeUpgradeCoreMult;
|
||||
let totalCost = 0;
|
||||
let currentCores = this.cores;
|
||||
for (let i = 0; i < levels; ++i) {
|
||||
totalCost += (coreBaseCost * Math.pow(mult, currentCores-1));
|
||||
++currentCores;
|
||||
}
|
||||
|
||||
totalCost *= Player.hacknet_node_core_cost_mult;
|
||||
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
HacknetNode.prototype.purchaseCoreUpgrade = function(levels=1) {
|
||||
levels = Math.round(levels);
|
||||
var cost = this.calculateCoreUpgradeCost(levels);
|
||||
if (isNaN(cost) || levels < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Fail if we're already at max
|
||||
if (this.cores >= CONSTANTS.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 + levels > CONSTANTS.HacknetNodeMaxCores) {
|
||||
var diff = Math.max(0, CONSTANTS.HacknetNodeMaxCores - this.cores);
|
||||
return this.purchaseCoreUpgrade(diff);
|
||||
}
|
||||
|
||||
if (Player.money.lt(cost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player.loseMoney(cost);
|
||||
this.cores = Math.round(this.cores + levels); //Just in case of floating point imprecision
|
||||
this.updateMoneyGainRate();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Saving and loading HackNets */
|
||||
HacknetNode.prototype.toJSON = function() {
|
||||
return Generic_toJSON("HacknetNode", this);
|
||||
}
|
||||
|
||||
HacknetNode.fromJSON = function(value) {
|
||||
return Generic_fromJSON(HacknetNode, value.data);
|
||||
}
|
||||
|
||||
Reviver.constructors.HacknetNode = HacknetNode;
|
||||
|
||||
function purchaseHacknet() {
|
||||
/* INTERACTIVE TUTORIAL */
|
||||
if (ITutorial.isRunning) {
|
||||
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
|
||||
iTutorialNextStep();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* END INTERACTIVE TUTORIAL */
|
||||
|
||||
var cost = getCostOfNextHacknetNode();
|
||||
if (isNaN(cost)) {
|
||||
throw new Error("Cost is NaN");
|
||||
}
|
||||
|
||||
if (Player.money.lt(cost)) {
|
||||
//dialogBoxCreate("You cannot afford to purchase a Hacknet Node!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Auto generate a name for the node for now...TODO
|
||||
var numOwned = Player.hacknetNodes.length;
|
||||
var name = "hacknet-node-" + numOwned;
|
||||
var node = new HacknetNode(name);
|
||||
node.updateMoneyGainRate();
|
||||
|
||||
Player.loseMoney(cost);
|
||||
Player.hacknetNodes.push(node);
|
||||
|
||||
if (routing.isOn(Page.HacknetNodes)) {
|
||||
displayHacknetNodesContent();
|
||||
}
|
||||
updateTotalHacknetProduction();
|
||||
return numOwned;
|
||||
}
|
||||
|
||||
//Calculates the total production from all HacknetNodes
|
||||
function updateTotalHacknetProduction() {
|
||||
var total = 0;
|
||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
total += Player.hacknetNodes[i].moneyGainRatePerSecond;
|
||||
}
|
||||
TotalHacknetNodeProduction = total;
|
||||
}
|
||||
|
||||
function getCostOfNextHacknetNode() {
|
||||
//Cost increases exponentially based on how many you own
|
||||
var numOwned = Player.hacknetNodes.length;
|
||||
var mult = CONSTANTS.HacknetNodePurchaseNextMult;
|
||||
return CONSTANTS.BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
||||
}
|
||||
|
||||
var hacknetNodePurchaseMultiplier = 1;
|
||||
function updateHacknetNodesMultiplierButtons() {
|
||||
var mult1x = document.getElementById("hacknet-nodes-1x-multiplier");
|
||||
var mult5x = document.getElementById("hacknet-nodes-5x-multiplier");
|
||||
var mult10x = document.getElementById("hacknet-nodes-10x-multiplier");
|
||||
var multMax = document.getElementById("hacknet-nodes-max-multiplier");
|
||||
mult1x.setAttribute("class", "a-link-button");
|
||||
mult5x.setAttribute("class", "a-link-button");
|
||||
mult10x.setAttribute("class", "a-link-button");
|
||||
multMax.setAttribute("class", "a-link-button");
|
||||
if (Player.hacknetNodes.length == 0) {
|
||||
mult1x.setAttribute("class", "a-link-button-inactive");
|
||||
mult5x.setAttribute("class", "a-link-button-inactive");
|
||||
mult10x.setAttribute("class", "a-link-button-inactive");
|
||||
multMax.setAttribute("class", "a-link-button-inactive");
|
||||
} else if (hacknetNodePurchaseMultiplier == 1) {
|
||||
mult1x.setAttribute("class", "a-link-button-inactive");
|
||||
} else if (hacknetNodePurchaseMultiplier == 5) {
|
||||
mult5x.setAttribute("class", "a-link-button-inactive");
|
||||
} else if (hacknetNodePurchaseMultiplier == 10) {
|
||||
mult10x.setAttribute("class", "a-link-button-inactive");
|
||||
} else {
|
||||
multMax.setAttribute("class", "a-link-button-inactive");
|
||||
}
|
||||
}
|
||||
|
||||
//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
|
||||
function getMaxNumberLevelUpgrades(nodeObj) {
|
||||
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var min = 1;
|
||||
var max = CONSTANTS.HacknetNodeMaxLevel - 1;
|
||||
var levelsToMax = CONSTANTS.HacknetNodeMaxLevel - nodeObj.level;
|
||||
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax))) {
|
||||
return levelsToMax;
|
||||
}
|
||||
|
||||
while (min <= max) {
|
||||
var curr = (min + max) / 2 | 0;
|
||||
if (curr != CONSTANTS.HacknetNodeMaxLevel &&
|
||||
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr)) &&
|
||||
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1))) {
|
||||
return Math.min(levelsToMax, curr);
|
||||
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr))) {
|
||||
max = curr - 1;
|
||||
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr))) {
|
||||
min = curr + 1;
|
||||
} else {
|
||||
return Math.min(levelsToMax, curr);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getMaxNumberRamUpgrades(nodeObj) {
|
||||
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const levelsToMax = Math.round(Math.log2(CONSTANTS.HacknetNodeMaxRam / nodeObj.ram));
|
||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax))) {
|
||||
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))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getMaxNumberCoreUpgrades(nodeObj) {
|
||||
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var min = 1;
|
||||
var max = CONSTANTS.HacknetNodeMaxCores - 1;
|
||||
const levelsToMax = CONSTANTS.HacknetNodeMaxCores - nodeObj.cores;
|
||||
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(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 != CONSTANTS.HacknetNodeMaxCores &&
|
||||
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr)) &&
|
||||
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1))) {
|
||||
return Math.min(levelsToMax, curr);
|
||||
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr))) {
|
||||
max = curr - 1;
|
||||
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr))) {
|
||||
min = curr + 1;
|
||||
} else {
|
||||
return Math.min(levelsToMax, curr);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Creates Hacknet Node DOM elements when the page is opened
|
||||
function displayHacknetNodesContent() {
|
||||
//Update Hacknet Nodes button
|
||||
var newPurchaseButton = clearEventListeners("hacknet-nodes-purchase-button");
|
||||
|
||||
newPurchaseButton.addEventListener("click", function() {
|
||||
purchaseHacknet();
|
||||
return false;
|
||||
});
|
||||
|
||||
//Handle Purchase multiplier buttons
|
||||
updateHacknetNodesMultiplierButtons();
|
||||
|
||||
//Remove all old hacknet Node DOM elements
|
||||
var hacknetNodesList = document.getElementById("hacknet-nodes-list");
|
||||
while (hacknetNodesList.firstChild) {
|
||||
hacknetNodesList.removeChild(hacknetNodesList.firstChild);
|
||||
}
|
||||
|
||||
//Then re-create them
|
||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
createHacknetNodeDomElement(Player.hacknetNodes[i]);
|
||||
}
|
||||
|
||||
updateTotalHacknetProduction();
|
||||
updateHacknetNodesContent();
|
||||
}
|
||||
|
||||
//Update information on all Hacknet Node DOM elements
|
||||
function updateHacknetNodesContent() {
|
||||
//Set purchase button to inactive if not enough money, and update its price display
|
||||
var cost = getCostOfNextHacknetNode();
|
||||
var purchaseButton = getElementById("hacknet-nodes-purchase-button");
|
||||
var formattedCost = numeralWrapper.formatMoney(cost);
|
||||
|
||||
updateText("hacknet-nodes-purchase-button", `Purchase Hacknet Node - ${formattedCost}`);
|
||||
|
||||
if (Player.money.lt(cost)) {
|
||||
purchaseButton.setAttribute("class", "a-link-button-inactive");
|
||||
} else {
|
||||
purchaseButton.setAttribute("class", "a-link-button");
|
||||
}
|
||||
|
||||
//Update player's money
|
||||
updateText("hacknet-nodes-player-money", numeralWrapper.formatMoney(Player.money.toNumber()));
|
||||
updateText("hacknet-nodes-total-production", numeralWrapper.formatMoney(TotalHacknetNodeProduction) + " / sec");
|
||||
|
||||
//Update information in each owned hacknet node
|
||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||
updateHacknetNodeDomElement(Player.hacknetNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//Creates a single Hacknet Node DOM element
|
||||
function createHacknetNodeDomElement(nodeObj) {
|
||||
var nodeName = nodeObj.name;
|
||||
|
||||
var nodeLevelContainer = createElement("div", {
|
||||
class: "hacknet-node-level-container row",
|
||||
innerHTML: "<p>Level:</p><span class=\"text upgradable-info\" id=\"hacknet-node-level-" + nodeName + "\"></span>"
|
||||
});
|
||||
|
||||
var nodeRamContainer = createElement("div", {
|
||||
class: "hacknet-node-ram-container row",
|
||||
innerHTML: "<p>RAM:</p><span class=\"text upgradable-info\" id=\"hacknet-node-ram-" + nodeName + "\"></span>"
|
||||
});
|
||||
|
||||
var nodeCoresContainer = createElement("div", {
|
||||
class: "hacknet-node-cores-container row",
|
||||
innerHTML: "<p>Cores:</p><span class=\"text upgradable-info\" id=\"hacknet-node-cores-" + nodeName + "\"><span>"
|
||||
})
|
||||
var containingDiv = createElement("div", {
|
||||
class: "hacknet-node-container",
|
||||
innerHTML: "<div class=\"hacknet-node-name-container row\">" +
|
||||
"<p>Node name:</p>" +
|
||||
"<span class=\"text\" id=\"hacknet-node-name-" + nodeName + "\"></span>" +
|
||||
"</div>" +
|
||||
"<div class=\"hacknet-node-production-container row\">" +
|
||||
"<p>Production:</p>" +
|
||||
"<span class=\"text\" id=\"hacknet-node-total-production-" + nodeName + "\"></span>" +
|
||||
"<span class=\"text\" id=\"hacknet-node-production-rate-" + nodeName + "\"></span>" +
|
||||
"</div>"
|
||||
});
|
||||
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
|
||||
};
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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<string>;
|
||||
karma: number;
|
||||
|
@ -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");
|
||||
|
@ -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];
|
||||
|
@ -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 <span class='money-gold'>" +
|
||||
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> and your Hacknet Nodes generated <span class='money-gold'>" +
|
||||
numeralWrapper.formatMoney(offlineProductionFromHacknetNodes) + "</span>");
|
||||
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> " +
|
||||
"and your Hacknet Nodes generated <span class='money-gold'>" + hacknetProdInfo + "</span>");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
206
src/Server/BaseServer.ts
Normal file
206
src/Server/BaseServer.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 <span class='money-gold'>" +
|
||||
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> and your Hacknet Nodes generated <span class='money-gold'>" +
|
||||
numeralWrapper.formatMoney(offlineProductionFromHacknetNodes) + "</span>");
|
||||
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> " +
|
||||
"and your Hacknet Nodes generated <span class='money-gold'>" + hacknetProdInfo + "</span>");
|
||||
//Close main menu accordions for loaded game
|
||||
var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
|
||||
hacknetnodes, city, tutorial, options, dev];
|
||||
|
@ -203,35 +203,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
||||
|
||||
<!-- Hacknet Nodes -->
|
||||
<div id="hacknet-nodes-container" class="generic-menupage-container">
|
||||
<h1 id="hacknet-nodes-title"> Hacknet Nodes </h1>
|
||||
<p id="hacknet-nodes-text" class="menu-page-text">
|
||||
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.
|
||||
<br /><br />
|
||||
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.
|
||||
<br /><br />
|
||||
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.
|
||||
</p>
|
||||
<a id="hacknet-nodes-purchase-button" class="a-link-button"> Purchase Hacknet Node </a>
|
||||
<br />
|
||||
<div id="hacknet-nodes-money-multipliers-div">
|
||||
<p id="hacknet-nodes-money">
|
||||
<span>Money:</span><span id="hacknet-nodes-player-money" class="money-gold"></span><br />
|
||||
<span>Total Hacknet Node Production:</span><span id="hacknet-nodes-total-production" class="money-gold"></span>
|
||||
</p>
|
||||
<span id="hacknet-nodes-multipliers">
|
||||
<a id="hacknet-nodes-1x-multiplier" class="a-link-button-inactive"> x1 </a>
|
||||
<a id="hacknet-nodes-5x-multiplier" class="a-link-button"> x5 </a>
|
||||
<a id="hacknet-nodes-10x-multiplier" class="a-link-button"> x10 </a>
|
||||
<a id="hacknet-nodes-max-multiplier" class="a-link-button"> MAX </a>
|
||||
</span>
|
||||
</div>
|
||||
<ul id="hacknet-nodes-list">
|
||||
</ul>
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- World -->
|
||||
|
65
src/ui/React/ServerDropdown.jsx
Normal file
65
src/ui/React/ServerDropdown.jsx
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Creates a dropdown (select HTML element) with server hostnames as options
|
||||
*
|
||||
* Configurable to only contain certain types of servers
|
||||
*/
|
||||
import React from "react";
|
||||
import { AllServers } from "../../Server/AllServers";
|
||||
|
||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
|
||||
// TODO make this an enum when this gets converted to TypeScript
|
||||
export const ServerType = {
|
||||
All: 0,
|
||||
Foreign: 1, // Hackable, non-owned servers
|
||||
Owned: 2, // Home Computer, Purchased Servers, and Hacknet Servers
|
||||
Purchased: 3, // Everything from Owned except home computer
|
||||
}
|
||||
|
||||
export class ServerDropdown extends React.Component {
|
||||
/**
|
||||
* Checks if the server should be shown in the dropdown menu, based on the
|
||||
* 'serverType' property
|
||||
*/
|
||||
isValidServer(s) {
|
||||
const type = this.props.serverType;
|
||||
switch (type) {
|
||||
case ServerType.All:
|
||||
return true;
|
||||
case ServerType.Foreign:
|
||||
return (s.hostname !== "home" && !s.purchasedByPlayer);
|
||||
case ServerType.Owned:
|
||||
return s.purchasedByPlayer || (s instanceof HacknetServer) || s.hostname === "home";
|
||||
case ServerType.Purchased:
|
||||
return s.purchasedByPlayer || (s instanceof HacknetServer);
|
||||
default:
|
||||
console.warn(`Invalid ServerType specified for ServerDropdown component: ${type}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a Server object, creates a Option element
|
||||
*/
|
||||
renderOption(s) {
|
||||
return (
|
||||
<option key={s.hostname} value={s.hostname}>{s.hostname}</option>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const servers = [];
|
||||
for (const serverName in AllServers) {
|
||||
const server = AllServers[serverName];
|
||||
if (this.isValidServer(server)) {
|
||||
servers.push(this.renderOption(server));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<select className={"dropdown"} onChange={this.props.onChange} style={this.props.style}>
|
||||
{servers}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
}
|
56
src/ui/React/createPopup.ts
Normal file
56
src/ui/React/createPopup.ts
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Create a pop-up dialog box using React.
|
||||
*
|
||||
* Calling this function with the same ID and React Root Component will trigger a re-render
|
||||
*
|
||||
* @param id The (hopefully) unique identifier for the popup container
|
||||
* @param rootComponent Root React Component
|
||||
*/
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import { createElement } from "../../../utils/uiHelpers/createElement";
|
||||
import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
|
||||
|
||||
type ReactComponent = new(...args: any[]) => React.Component<any, any>;
|
||||
|
||||
export function createPopup(id: string, rootComponent: ReactComponent, props: object): HTMLElement {
|
||||
let container = document.getElementById(id);
|
||||
let content = document.getElementById(`${id}-content`);
|
||||
if (container == null || content == null) {
|
||||
container = createElement("div", {
|
||||
class: "popup-box-container",
|
||||
display: "flex",
|
||||
id: id,
|
||||
});
|
||||
|
||||
content = createElement("div", {
|
||||
class: "popup-box-content",
|
||||
id: `${id}-content`,
|
||||
});
|
||||
|
||||
container.appendChild(content);
|
||||
|
||||
try {
|
||||
document.getElementById("entire-game-container")!.appendChild(container);
|
||||
} catch(e) {
|
||||
console.error(`Exception caught when creating popup: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(React.createElement(rootComponent, props), content);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a popup created with the createPopup() function above
|
||||
*/
|
||||
export function removePopup(id: string): void {
|
||||
let content = document.getElementById(`${id}-content`);
|
||||
if (content == null) { return; }
|
||||
|
||||
ReactDOM.unmountComponentAtNode(content);
|
||||
|
||||
removeElementById(id);
|
||||
}
|
12
src/utils/createRandomString.ts
Normal file
12
src/utils/createRandomString.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// Function that generates a random gibberish string of length n
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
export function createRandomString(n: number): string {
|
||||
let str: string = "";
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
str += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
Loading…
Reference in New Issue
Block a user