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,
|
.a-link-button-inactive,
|
||||||
|
.std-button-disabled,
|
||||||
.std-button:disabled {
|
.std-button:disabled {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
@import "theme";
|
@import "theme";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Styling for the Character Overview Panel (top-right)
|
* Styling for the Character Overview Panel (top-right panel)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#character-overview-wrapper {
|
#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 */
|
||||||
#world-container {
|
#world-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -2069,7 +2069,7 @@ function installAugmentations(cbScript=null) {
|
|||||||
}
|
}
|
||||||
var runningScriptObj = new RunningScript(script, []); //No args
|
var runningScriptObj = new RunningScript(script, []); //No args
|
||||||
runningScriptObj.threads = 1; //Only 1 thread
|
runningScriptObj.threads = 1; //Only 1 thread
|
||||||
home.runningScripts.push(runningScriptObj);
|
home.runScript(runningScriptObj, Player);
|
||||||
addWorkerScript(runningScriptObj, home);
|
addWorkerScript(runningScriptObj, home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,10 @@ export function initBitNodes() {
|
|||||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||||
"This Source-File also increases your hacking growth multipliers by: " +
|
"This Source-File also increases your hacking growth multipliers by: " +
|
||||||
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
"<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",
|
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 " +
|
"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 " +
|
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
|
||||||
@ -245,9 +248,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (p.bitNodeN) {
|
switch (p.bitNodeN) {
|
||||||
case 1: //Source Genesis (every multiplier is 1)
|
case 1: // Source Genesis (every multiplier is 1)
|
||||||
break;
|
break;
|
||||||
case 2: //Rise of the Underworld
|
case 2: // Rise of the Underworld
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||||
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
||||||
@ -257,7 +260,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
||||||
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
||||||
break;
|
break;
|
||||||
case 3: //Corporatocracy
|
case 3: // Corporatocracy
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||||
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
||||||
BitNodeMultipliers.AugmentationRepCost = 3;
|
BitNodeMultipliers.AugmentationRepCost = 3;
|
||||||
@ -272,7 +275,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HomeComputerRamCost = 1.5;
|
BitNodeMultipliers.HomeComputerRamCost = 1.5;
|
||||||
BitNodeMultipliers.PurchasedServerCost = 2;
|
BitNodeMultipliers.PurchasedServerCost = 2;
|
||||||
break;
|
break;
|
||||||
case 4: //The Singularity
|
case 4: // The Singularity
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
||||||
@ -286,7 +289,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.CrimeExpGain = 0.5;
|
BitNodeMultipliers.CrimeExpGain = 0.5;
|
||||||
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
||||||
break;
|
break;
|
||||||
case 5: //Artificial intelligence
|
case 5: // Artificial intelligence
|
||||||
BitNodeMultipliers.ServerMaxMoney = 2;
|
BitNodeMultipliers.ServerMaxMoney = 2;
|
||||||
BitNodeMultipliers.ServerStartingSecurity = 2;
|
BitNodeMultipliers.ServerStartingSecurity = 2;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||||
@ -299,7 +302,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HackExpGain = 0.5;
|
BitNodeMultipliers.HackExpGain = 0.5;
|
||||||
BitNodeMultipliers.CorporationValuation = 0.5;
|
BitNodeMultipliers.CorporationValuation = 0.5;
|
||||||
break;
|
break;
|
||||||
case 6: //Bladeburner
|
case 6: // Bladeburner
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||||
@ -314,7 +317,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HackExpGain = 0.25;
|
BitNodeMultipliers.HackExpGain = 0.25;
|
||||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||||
break;
|
break;
|
||||||
case 7: //Bladeburner 2079
|
case 7: // Bladeburner 2079
|
||||||
BitNodeMultipliers.BladeburnerRank = 0.6;
|
BitNodeMultipliers.BladeburnerRank = 0.6;
|
||||||
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
||||||
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
||||||
@ -334,7 +337,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
||||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||||
break;
|
break;
|
||||||
case 8: //Ghost of Wall Street
|
case 8: // Ghost of Wall Street
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0;
|
BitNodeMultipliers.ScriptHackMoney = 0;
|
||||||
BitNodeMultipliers.ManualHackMoney = 0;
|
BitNodeMultipliers.ManualHackMoney = 0;
|
||||||
BitNodeMultipliers.CompanyWorkMoney = 0;
|
BitNodeMultipliers.CompanyWorkMoney = 0;
|
||||||
@ -345,6 +348,19 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.CorporationValuation = 0;
|
BitNodeMultipliers.CorporationValuation = 0;
|
||||||
BitNodeMultipliers.CodingContractMoney = 0;
|
BitNodeMultipliers.CodingContractMoney = 0;
|
||||||
break;
|
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
|
case 10: // Digital Carbon
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
|
||||||
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
|
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
|
||||||
|
@ -120,7 +120,8 @@ interface IBitNodeMultipliers {
|
|||||||
HackingLevelMultiplier: number;
|
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;
|
HacknetNodeMoney: number;
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Generic Game Constants
|
||||||
|
*
|
||||||
|
* Constants for specific mechanics or features will NOT be here.
|
||||||
|
*/
|
||||||
import {IMap} from "./types";
|
import {IMap} from "./types";
|
||||||
|
|
||||||
export let CONSTANTS: IMap<any> = {
|
export let CONSTANTS: IMap<any> = {
|
||||||
@ -17,24 +22,9 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
/* Base costs */
|
/* Base costs */
|
||||||
BaseCostFor1GBOfRamHome: 32000,
|
BaseCostFor1GBOfRamHome: 32000,
|
||||||
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
|
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
|
||||||
BaseCostFor1GBOfRamHacknetNode: 30000,
|
|
||||||
|
|
||||||
TravelCost: 200e3,
|
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 */
|
/* Faction and Company favor */
|
||||||
BaseFavorToDonate: 150,
|
BaseFavorToDonate: 150,
|
||||||
DonateMoneyToRepDivisor: 1e6,
|
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 {NetscriptFunctions} from "./NetscriptFunctions";
|
||||||
import {NetscriptPort} from "./NetscriptPort";
|
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
|
//Creates (or overwrites) a variable in the current scope
|
||||||
def: function(name, value) {
|
def: function(name, value) {
|
||||||
return this.vars[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);
|
var runningScriptObj = new RunningScript(script, args);
|
||||||
runningScriptObj.threads = threads;
|
runningScriptObj.threads = threads;
|
||||||
server.runningScripts.push(runningScriptObj); //Push onto runningScripts
|
server.runScript(runningScriptObj, Player); // Push onto runningScripts
|
||||||
addWorkerScript(runningScriptObj, server);
|
addWorkerScript(runningScriptObj, server);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import { joinFaction,
|
|||||||
purchaseAugmentation } from "./Faction/FactionHelpers";
|
purchaseAugmentation } from "./Faction/FactionHelpers";
|
||||||
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
|
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
|
||||||
import { getCostOfNextHacknetNode,
|
import { getCostOfNextHacknetNode,
|
||||||
purchaseHacknet } from "./HacknetNode";
|
purchaseHacknet } from "./Hacknet/HacknetNode";
|
||||||
import {Locations} from "./Locations";
|
import {Locations} from "./Locations";
|
||||||
import { Message } from "./Message/Message";
|
import { Message } from "./Message/Message";
|
||||||
import { Messages } from "./Message/MessageHelpers";
|
import { Messages } from "./Message/MessageHelpers";
|
||||||
@ -278,27 +278,27 @@ function NetscriptFunctions(workerScript) {
|
|||||||
},
|
},
|
||||||
upgradeLevel : function(i, n) {
|
upgradeLevel : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.purchaseLevelUpgrade(n);
|
return node.purchaseLevelUpgrade(n, Player);
|
||||||
},
|
},
|
||||||
upgradeRam : function(i, n) {
|
upgradeRam : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.purchaseRamUpgrade(n);
|
return node.purchaseRamUpgrade(n, Player);
|
||||||
},
|
},
|
||||||
upgradeCore : function(i, n) {
|
upgradeCore : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.purchaseCoreUpgrade(n);
|
return node.purchaseCoreUpgrade(n, Player);
|
||||||
},
|
},
|
||||||
getLevelUpgradeCost : function(i, n) {
|
getLevelUpgradeCost : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.calculateLevelUpgradeCost(n);
|
return node.calculateLevelUpgradeCost(n, Player);
|
||||||
},
|
},
|
||||||
getRamUpgradeCost : function(i, n) {
|
getRamUpgradeCost : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.calculateRamUpgradeCost(n);
|
return node.calculateRamUpgradeCost(n, Player);
|
||||||
},
|
},
|
||||||
getCoreUpgradeCost : function(i, n) {
|
getCoreUpgradeCost : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.calculateCoreUpgradeCost(n);
|
return node.calculateCoreUpgradeCost(n, Player);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sprintf : sprintf,
|
sprintf : sprintf,
|
||||||
|
@ -9,6 +9,8 @@ import { Sleeve } from "./Sleeve/Sleeve";
|
|||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
|
|
||||||
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
|
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
|
||||||
|
import { HacknetNode } from "../Hacknet/HacknetNode";
|
||||||
|
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||||
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
||||||
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ export interface IPlayer {
|
|||||||
corporation: any;
|
corporation: any;
|
||||||
currentServer: string;
|
currentServer: string;
|
||||||
factions: string[];
|
factions: string[];
|
||||||
hacknetNodes: any[];
|
hacknetNodes: (HacknetNode | HacknetServer)[];
|
||||||
hasWseAccount: boolean;
|
hasWseAccount: boolean;
|
||||||
jobs: IMap<string>;
|
jobs: IMap<string>;
|
||||||
karma: number;
|
karma: number;
|
||||||
|
@ -21,6 +21,7 @@ import { Faction } from "./Faction/Faction";
|
|||||||
import { Factions } from "./Faction/Factions";
|
import { Factions } from "./Faction/Factions";
|
||||||
import { displayFactionContent } from "./Faction/FactionHelpers";
|
import { displayFactionContent } from "./Faction/FactionHelpers";
|
||||||
import {Gang, resetGangs} from "./Gang";
|
import {Gang, resetGangs} from "./Gang";
|
||||||
|
import { HashManager } from "./Hacknet/HashManager";
|
||||||
import {Locations} from "./Locations";
|
import {Locations} from "./Locations";
|
||||||
import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions";
|
import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions";
|
||||||
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
|
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)
|
// 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
|
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.currentServer = ""; //IP address of Server currently being accessed through terminal
|
||||||
this.purchasedServers = []; //IP Addresses of purchased servers
|
this.purchasedServers = []; //IP Addresses of purchased servers
|
||||||
|
|
||||||
|
// Hacknet Nodes/Servers
|
||||||
this.hacknetNodes = [];
|
this.hacknetNodes = [];
|
||||||
|
this.hashManager = new HashManager();
|
||||||
|
|
||||||
//Factions
|
//Factions
|
||||||
this.factions = []; //Names of all factions player has joined
|
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
|
//Find cost and exp gain per game cycle
|
||||||
var cost = 0;
|
var cost = 0;
|
||||||
var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0;
|
var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0;
|
||||||
|
const hashManager = this.hashManager;
|
||||||
switch (className) {
|
switch (className) {
|
||||||
case CONSTANTS.ClassStudyComputerScience:
|
case CONSTANTS.ClassStudyComputerScience:
|
||||||
hackExp = baseStudyComputerScienceExp * expMult / gameCPS;
|
hackExp = baseStudyComputerScienceExp * expMult / gameCPS * hashManager.getStudyMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassDataStructures:
|
case CONSTANTS.ClassDataStructures:
|
||||||
cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS;
|
||||||
hackExp = baseDataStructuresExp * expMult / gameCPS;
|
hackExp = baseDataStructuresExp * expMult / gameCPS * hashManager.getStudyMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassNetworks:
|
case CONSTANTS.ClassNetworks:
|
||||||
cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS;
|
||||||
hackExp = baseNetworksExp * expMult / gameCPS;
|
hackExp = baseNetworksExp * expMult / gameCPS * hashManager.getStudyMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassAlgorithms:
|
case CONSTANTS.ClassAlgorithms:
|
||||||
cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS;
|
||||||
hackExp = baseAlgorithmsExp * expMult / gameCPS;
|
hackExp = baseAlgorithmsExp * expMult / gameCPS * hashManager.getStudyMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassManagement:
|
case CONSTANTS.ClassManagement:
|
||||||
cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS;
|
||||||
chaExp = baseManagementExp * expMult / gameCPS;
|
chaExp = baseManagementExp * expMult / gameCPS * hashManager.getStudyMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassLeadership:
|
case CONSTANTS.ClassLeadership:
|
||||||
cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS;
|
||||||
chaExp = baseLeadershipExp * expMult / gameCPS;
|
chaExp = baseLeadershipExp * expMult / gameCPS * hashManager.getStudyMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassGymStrength:
|
case CONSTANTS.ClassGymStrength:
|
||||||
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
||||||
strExp = baseGymExp * expMult / gameCPS;
|
strExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassGymDefense:
|
case CONSTANTS.ClassGymDefense:
|
||||||
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
||||||
defExp = baseGymExp * expMult / gameCPS;
|
defExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassGymDexterity:
|
case CONSTANTS.ClassGymDexterity:
|
||||||
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
||||||
dexExp = baseGymExp * expMult / gameCPS;
|
dexExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
|
||||||
break;
|
break;
|
||||||
case CONSTANTS.ClassGymAgility:
|
case CONSTANTS.ClassGymAgility:
|
||||||
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;
|
||||||
agiExp = baseGymExp * expMult / gameCPS;
|
agiExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("ERR: Invalid/unrecognized class name");
|
throw new Error("ERR: Invalid/unrecognized class name");
|
||||||
|
@ -213,9 +213,7 @@ function loadBitVerse(destroyedBitNodeNum, flume=false) {
|
|||||||
var elemId = "bitnode-" + i.toString();
|
var elemId = "bitnode-" + i.toString();
|
||||||
var elem = clearEventListeners(elemId);
|
var elem = clearEventListeners(elemId);
|
||||||
if (elem == null) {return;}
|
if (elem == null) {return;}
|
||||||
if (i === 1 || i === 2 || i === 3 || i === 4 || i === 5 ||
|
if (i >= 1 && i <= 12) {
|
||||||
i === 6 || i === 7 || i === 8 || i === 10 || i === 11 ||
|
|
||||||
i === 12) {
|
|
||||||
elem.addEventListener("click", function() {
|
elem.addEventListener("click", function() {
|
||||||
var bitNodeKey = "BitNode" + i;
|
var bitNodeKey = "BitNode" + i;
|
||||||
var bitNode = BitNodes[bitNodeKey];
|
var bitNode = BitNodes[bitNodeKey];
|
||||||
|
@ -10,7 +10,8 @@ import { processPassiveFactionRepGain } from "./Faction/FactionHelpers";
|
|||||||
import { loadFconf } from "./Fconf/Fconf";
|
import { loadFconf } from "./Fconf/Fconf";
|
||||||
import { FconfSettings } from "./Fconf/FconfSettings";
|
import { FconfSettings } from "./Fconf/FconfSettings";
|
||||||
import {loadAllGangs, AllGangs} from "./Gang";
|
import {loadAllGangs, AllGangs} from "./Gang";
|
||||||
import {processAllHacknetNodeEarnings} from "./HacknetNode";
|
import { hasHacknetServers,
|
||||||
|
processHacknetEarnings } from "./Hacknet/HacknetHelpers";
|
||||||
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
|
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
|
||||||
import {Player, loadPlayer} from "./Player";
|
import {Player, loadPlayer} from "./Player";
|
||||||
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
|
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
|
||||||
@ -490,7 +491,10 @@ function loadImportedGame(saveObj, saveString) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Hacknet Nodes offline progress
|
//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
|
//Passive faction rep gain offline
|
||||||
processPassiveFactionRepGain(numCyclesOffline);
|
processPassiveFactionRepGain(numCyclesOffline);
|
||||||
@ -515,8 +519,8 @@ function loadImportedGame(saveObj, saveString) {
|
|||||||
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
|
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
|
||||||
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
|
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
|
||||||
"generated <span class='money-gold'>" +
|
"generated <span class='money-gold'>" +
|
||||||
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> and your Hacknet Nodes generated <span class='money-gold'>" +
|
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> " +
|
||||||
numeralWrapper.formatMoney(offlineProductionFromHacknetNodes) + "</span>");
|
"and your Hacknet Nodes generated <span class='money-gold'>" + hacknetProdInfo + "</span>");
|
||||||
return true;
|
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
|
// TODO This import is a circular import. Try to fix it in the future
|
||||||
import { GetServerByHostname } from "./ServerHelpers";
|
import { GetServerByHostname } from "./ServerHelpers";
|
||||||
|
|
||||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
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 { createRandomIp } from "../../utils/IPAddress";
|
||||||
import { Generic_fromJSON,
|
import { Generic_fromJSON,
|
||||||
Generic_toJSON,
|
Generic_toJSON,
|
||||||
@ -31,7 +27,7 @@ interface IConstructorParams {
|
|||||||
serverGrowth?: number;
|
serverGrowth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Server {
|
export class Server extends BaseServer {
|
||||||
// Initializes a Server Object from a JSON save state
|
// Initializes a Server Object from a JSON save state
|
||||||
static fromJSON(value: any): Server {
|
static fromJSON(value: any): Server {
|
||||||
return Generic_fromJSON(Server, value.data);
|
return Generic_fromJSON(Server, value.data);
|
||||||
@ -41,47 +37,13 @@ export class Server {
|
|||||||
// (i.e. security level when the server was created)
|
// (i.e. security level when the server was created)
|
||||||
baseDifficulty: number = 1;
|
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
|
// Server Security Level
|
||||||
hackDifficulty: number = 1;
|
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.
|
// Flag indicating whether this server has been manually hacked (ie.
|
||||||
// hacked through Terminal) by the player
|
// hacked through Terminal) by the player
|
||||||
manuallyHacked: boolean = false;
|
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
|
// Minimum server security level that this server can be weakened to
|
||||||
minDifficulty: number = 1;
|
minDifficulty: number = 1;
|
||||||
|
|
||||||
@ -97,67 +59,35 @@ export class Server {
|
|||||||
// How many ports are currently opened on the server
|
// How many ports are currently opened on the server
|
||||||
openPortCount: number = 0;
|
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
|
// Flag indicating wehther this is a purchased server
|
||||||
purchasedByPlayer: boolean = false;
|
purchasedByPlayer: boolean = false;
|
||||||
|
|
||||||
// RAM (GB) used. i.e. unavailable RAM
|
|
||||||
ramUsed: number = 0;
|
|
||||||
|
|
||||||
// Hacking level required to hack this server
|
// Hacking level required to hack this server
|
||||||
requiredHackingSkill: number = 1;
|
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
|
// Parameter that affects how effectively this server's money can
|
||||||
// be increased using the grow() Netscript function
|
// be increased using the grow() Netscript function
|
||||||
serverGrowth: number = 1;
|
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() }) {
|
constructor(params: IConstructorParams={hostname: "", ip: createRandomIp() }) {
|
||||||
/* Properties */
|
super(params);
|
||||||
//Connection information
|
|
||||||
this.ip = params.ip ? params.ip : createRandomIp();
|
|
||||||
|
|
||||||
var hostname = params.hostname;
|
// "hacknet-node-X" hostnames are reserved for Hacknet Servers
|
||||||
var i = 0;
|
if (this.hostname.startsWith("hacknet-node-")) {
|
||||||
var suffix = "";
|
this.hostname = createRandomString(10);
|
||||||
while (GetServerByHostname(hostname+suffix) != null) {
|
}
|
||||||
//Server already exists
|
|
||||||
suffix = "-" + i;
|
// Validate hostname by ensuring there are no repeats
|
||||||
++i;
|
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;
|
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
|
||||||
|
|
||||||
//RAM, CPU speed and Scripts
|
//RAM, CPU speed and Scripts
|
||||||
@ -178,23 +108,9 @@ export class Server {
|
|||||||
this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5;
|
this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
setMaxRam(ram: number): void {
|
/**
|
||||||
this.maxRam = ram;
|
* Ensures that the server's difficulty (server security) doesn't get too high
|
||||||
}
|
*/
|
||||||
|
|
||||||
// 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
|
|
||||||
capDifficulty(): void {
|
capDifficulty(): void {
|
||||||
if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;}
|
if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;}
|
||||||
if (this.hackDifficulty < 1) {this.hackDifficulty = 1;}
|
if (this.hackDifficulty < 1) {this.hackDifficulty = 1;}
|
||||||
@ -204,97 +120,54 @@ export class Server {
|
|||||||
if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;}
|
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 {
|
fortify(amt: number): void {
|
||||||
this.hackDifficulty += amt;
|
this.hackDifficulty += amt;
|
||||||
this.capDifficulty();
|
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 {
|
weaken(amt: number): void {
|
||||||
this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate);
|
this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate);
|
||||||
this.capDifficulty();
|
this.capDifficulty();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to a script file
|
/**
|
||||||
// Overwrites existing files. Creates new files if the script does not eixst
|
* Serialize the current object to a JSON save state
|
||||||
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
|
|
||||||
toJSON(): any {
|
toJSON(): any {
|
||||||
return Generic_toJSON("Server", this);
|
return Generic_toJSON("Server", this);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import {calculateHackingChance,
|
|||||||
calculateHackingTime,
|
calculateHackingTime,
|
||||||
calculateGrowTime,
|
calculateGrowTime,
|
||||||
calculateWeakenTime} from "./Hacking";
|
calculateWeakenTime} from "./Hacking";
|
||||||
|
import { HacknetServer } from "./Hacknet/HacknetServer";
|
||||||
import {TerminalHelpText, HelpTexts} from "./HelpText";
|
import {TerminalHelpText, HelpTexts} from "./HelpText";
|
||||||
import {iTutorialNextStep, iTutorialSteps,
|
import {iTutorialNextStep, iTutorialSteps,
|
||||||
ITutorial} from "./InteractiveTutorial";
|
ITutorial} from "./InteractiveTutorial";
|
||||||
@ -760,18 +761,19 @@ let Terminal = {
|
|||||||
finishAnalyze: function(cancelled = false) {
|
finishAnalyze: function(cancelled = false) {
|
||||||
if (cancelled == false) {
|
if (cancelled == false) {
|
||||||
let currServ = Player.getCurrentServer();
|
let currServ = Player.getCurrentServer();
|
||||||
|
const isHacknet = currServ instanceof HacknetServer;
|
||||||
post(currServ.hostname + ": ");
|
post(currServ.hostname + ": ");
|
||||||
post("Organization name: " + currServ.organizationName);
|
post("Organization name: " + currServ.organizationName);
|
||||||
var rootAccess = "";
|
var rootAccess = "";
|
||||||
if (currServ.hasAdminRights) {rootAccess = "YES";}
|
if (currServ.hasAdminRights) {rootAccess = "YES";}
|
||||||
else {rootAccess = "NO";}
|
else {rootAccess = "NO";}
|
||||||
post("Root Access: " + rootAccess);
|
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("Server security level: " + numeralWrapper.format(currServ.hackDifficulty, '0.000a'));
|
||||||
post("Chance to hack: " + numeralWrapper.format(calculateHackingChance(currServ), '0.00%'));
|
post("Chance to hack: " + numeralWrapper.format(calculateHackingChance(currServ), '0.00%'));
|
||||||
post("Time to hack: " + numeralWrapper.format(calculateHackingTime(currServ), '0.000') + " seconds");
|
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("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) {
|
if (currServ.sshPortOpen) {
|
||||||
post("SSH port: Open")
|
post("SSH port: Open")
|
||||||
@ -2240,7 +2242,7 @@ let Terminal = {
|
|||||||
post("May take a few seconds to start up the process...");
|
post("May take a few seconds to start up the process...");
|
||||||
var runningScriptObj = new RunningScript(script, args);
|
var runningScriptObj = new RunningScript(script, args);
|
||||||
runningScriptObj.threads = numThreads;
|
runningScriptObj.threads = numThreads;
|
||||||
server.runningScripts.push(runningScriptObj);
|
server.runScript(runningScriptObj, Player);
|
||||||
|
|
||||||
addWorkerScript(runningScriptObj, server);
|
addWorkerScript(runningScriptObj, server);
|
||||||
return;
|
return;
|
||||||
|
@ -30,8 +30,10 @@ import { FconfSettings } from "./Fconf/FconfSetti
|
|||||||
import {displayLocationContent,
|
import {displayLocationContent,
|
||||||
initLocationButtons} from "./Location";
|
initLocationButtons} from "./Location";
|
||||||
import {Locations} from "./Locations";
|
import {Locations} from "./Locations";
|
||||||
import {displayHacknetNodesContent, processAllHacknetNodeEarnings,
|
import { hasHacknetServers,
|
||||||
updateHacknetNodesContent} from "./HacknetNode";
|
renderHacknetNodesUI,
|
||||||
|
clearHacknetNodesUI,
|
||||||
|
processHacknetEarnings } from "./Hacknet/HacknetHelpers";
|
||||||
import {iTutorialStart} from "./InteractiveTutorial";
|
import {iTutorialStart} from "./InteractiveTutorial";
|
||||||
import {initLiterature} from "./Literature";
|
import {initLiterature} from "./Literature";
|
||||||
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
||||||
@ -109,6 +111,7 @@ import "../css/mainmenu.scss";
|
|||||||
import "../css/characteroverview.scss";
|
import "../css/characteroverview.scss";
|
||||||
import "../css/terminal.scss";
|
import "../css/terminal.scss";
|
||||||
import "../css/scripteditor.scss";
|
import "../css/scripteditor.scss";
|
||||||
|
import "../css/hacknetnodes.scss";
|
||||||
import "../css/menupages.scss";
|
import "../css/menupages.scss";
|
||||||
import "../css/redpill.scss";
|
import "../css/redpill.scss";
|
||||||
import "../css/stockmarket.scss";
|
import "../css/stockmarket.scss";
|
||||||
@ -294,8 +297,8 @@ const Engine = {
|
|||||||
loadHacknetNodesContent: function() {
|
loadHacknetNodesContent: function() {
|
||||||
Engine.hideAllContent();
|
Engine.hideAllContent();
|
||||||
Engine.Display.hacknetNodesContent.style.display = "block";
|
Engine.Display.hacknetNodesContent.style.display = "block";
|
||||||
displayHacknetNodesContent();
|
|
||||||
routing.navigateTo(Page.HacknetNodes);
|
routing.navigateTo(Page.HacknetNodes);
|
||||||
|
renderHacknetNodesUI();
|
||||||
MainMenuLinks.HacknetNodes.classList.add("active");
|
MainMenuLinks.HacknetNodes.classList.add("active");
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -506,7 +509,7 @@ const Engine = {
|
|||||||
Engine.Display.characterContent.style.display = "none";
|
Engine.Display.characterContent.style.display = "none";
|
||||||
Engine.Display.scriptEditorContent.style.display = "none";
|
Engine.Display.scriptEditorContent.style.display = "none";
|
||||||
Engine.Display.activeScriptsContent.style.display = "none";
|
Engine.Display.activeScriptsContent.style.display = "none";
|
||||||
Engine.Display.hacknetNodesContent.style.display = "none";
|
clearHacknetNodesUI();
|
||||||
Engine.Display.worldContent.style.display = "none";
|
Engine.Display.worldContent.style.display = "none";
|
||||||
Engine.Display.createProgramContent.style.display = "none";
|
Engine.Display.createProgramContent.style.display = "none";
|
||||||
Engine.Display.factionsContent.style.display = "none";
|
Engine.Display.factionsContent.style.display = "none";
|
||||||
@ -870,7 +873,7 @@ const Engine = {
|
|||||||
updateOnlineScriptTimes(numCycles);
|
updateOnlineScriptTimes(numCycles);
|
||||||
|
|
||||||
//Hacknet Nodes
|
//Hacknet Nodes
|
||||||
processAllHacknetNodeEarnings(numCycles);
|
processHacknetEarnings(numCycles);
|
||||||
},
|
},
|
||||||
|
|
||||||
//Counters for the main event loop. Represent the number of game cycles are required
|
//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) {
|
if (Engine.Counters.updateDisplays <= 0) {
|
||||||
Engine.displayCharacterOverviewInfo();
|
Engine.displayCharacterOverviewInfo();
|
||||||
if (routing.isOn(Page.HacknetNodes)) {
|
if (routing.isOn(Page.HacknetNodes)) {
|
||||||
updateHacknetNodesContent();
|
renderHacknetNodesUI();
|
||||||
} else if (routing.isOn(Page.CreateProgram)) {
|
} else if (routing.isOn(Page.CreateProgram)) {
|
||||||
displayCreateProgramContent();
|
displayCreateProgramContent();
|
||||||
} else if (routing.isOn(Page.Sleeves)) {
|
} else if (routing.isOn(Page.Sleeves)) {
|
||||||
@ -1183,7 +1186,10 @@ const Engine = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Hacknet Nodes offline progress
|
//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
|
//Passive faction rep gain offline
|
||||||
processPassiveFactionRepGain(numCyclesOffline);
|
processPassiveFactionRepGain(numCyclesOffline);
|
||||||
@ -1237,8 +1243,8 @@ const Engine = {
|
|||||||
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
|
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
|
||||||
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
|
dialogBoxCreate(`Offline for ${timeOfflineString}. While you were offline, your scripts ` +
|
||||||
"generated <span class='money-gold'>" +
|
"generated <span class='money-gold'>" +
|
||||||
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> and your Hacknet Nodes generated <span class='money-gold'>" +
|
numeralWrapper.formatMoney(offlineProductionFromScripts) + "</span> " +
|
||||||
numeralWrapper.formatMoney(offlineProductionFromHacknetNodes) + "</span>");
|
"and your Hacknet Nodes generated <span class='money-gold'>" + hacknetProdInfo + "</span>");
|
||||||
//Close main menu accordions for loaded game
|
//Close main menu accordions for loaded game
|
||||||
var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
|
var visibleMenuTabs = [terminal, createScript, activeScripts, stats,
|
||||||
hacknetnodes, city, tutorial, options, dev];
|
hacknetnodes, city, tutorial, options, dev];
|
||||||
|
@ -203,35 +203,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
|||||||
|
|
||||||
<!-- Hacknet Nodes -->
|
<!-- Hacknet Nodes -->
|
||||||
<div id="hacknet-nodes-container" class="generic-menupage-container">
|
<div id="hacknet-nodes-container" class="generic-menupage-container">
|
||||||
<h1 id="hacknet-nodes-title"> Hacknet Nodes </h1>
|
<!-- React Component -->
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- World -->
|
<!-- 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