Merge pull request #590 from danielyxie/dev

v0.46.0
This commit is contained in:
danielyxie 2019-04-03 16:59:42 -07:00 committed by GitHub
commit 7f88ade30e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 4814 additions and 2039 deletions

@ -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

@ -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;

@ -3,7 +3,7 @@
/* Pop-up boxes */ /* Pop-up boxes */
.popup-box-container { .popup-box-container {
display: none; /* Hidden by default */ display: none; /* Initially hidden */
position: fixed; /* Stay in place */ position: fixed; /* Stay in place */
z-index: 10; /* Sit on top */ z-index: 10; /* Sit on top */
left: 0; left: 0;

File diff suppressed because one or more lines are too long

125
dist/engine.css vendored

@ -486,6 +486,7 @@ button {
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6); } box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6); }
.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;
@ -501,11 +502,15 @@ button {
.a-link-button-inactive:hover .tooltiptext, .a-link-button-inactive:hover .tooltiptext,
.a-link-button-inactive:hover .tooltiptexthigh, .a-link-button-inactive:hover .tooltiptexthigh,
.a-link-button-inactive:hover .tooltiptextleft, .a-link-button-inactive:hover .tooltiptextleft,
.std-button-disabled:hover .tooltiptext,
.std-button-disabled:hover .tooltiptexthigh,
.std-button-disabled:hover .tooltiptextleft,
.std-button:disabled:hover .tooltiptext, .std-button:disabled:hover .tooltiptext,
.std-button:disabled:hover .tooltiptexthigh, .std-button:disabled:hover .tooltiptexthigh,
.std-button:disabled:hover .tooltiptextleft { .std-button:disabled:hover .tooltiptextleft {
visibility: visible; } visibility: visible; }
.a-link-button-inactive:active, .a-link-button-inactive:active,
.std-button-disabled:active,
.std-button:disabled:active { .std-button:disabled:active {
pointer-events: none; } pointer-events: none; }
@ -671,7 +676,7 @@ button {
/* COLORS */ /* COLORS */
/* Attributes */ /* Attributes */
/** /**
* Styling for the Character Overview Panel (top-right) * Styling for the Character Overview Panel (top-right panel)
*/ */
#character-overview-wrapper { #character-overview-wrapper {
position: relative; } position: relative; }
@ -927,6 +932,64 @@ button {
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */ /* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
/* COLORS */
/* Attributes */
/**
* 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-nodes-container li.hacknet-node {
-webkit-box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
-moz-box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
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; }
.hacknet-node-container .row {
display: table-row;
height: 30px; }
.hacknet-node-container .row p {
display: table-cell; }
.hacknet-node-container .upgradable-info {
display: inline-block;
margin: 0 4px;
/* Don't want the vertical margin/padding, just left & right */
padding: 0 4px;
width: 64px; }
/* COLORS */ /* COLORS */
/* Attributes */ /* Attributes */
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding /* CSS for different main menu pages, such as character info, script editor, etc (but excluding
@ -1044,64 +1107,6 @@ button {
color: #fff; color: #fff;
margin-left: 5%; } margin-left: 5%; }
/* 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-nodes-container li.hacknet-node {
-webkit-box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
-moz-box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
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; }
.hacknet-node-container .row {
display: table-row;
height: 30px; }
.hacknet-node-container .row p {
display: table-cell; }
.hacknet-node-container .upgradable-info {
display: inline-block;
margin: 0 4px;
/* Don't want the vertical margin/padding, just left & right */
padding: 0 4px;
width: 64px; }
.menu-page-text {
width: 70vw; }
/* World */ /* World */
#world-container { #world-container {
position: fixed; position: fixed;
@ -1402,7 +1407,7 @@ button {
/* Pop-up boxes */ /* Pop-up boxes */
.popup-box-container { .popup-box-container {
display: none; display: none;
/* Hidden by default */ /* Initially hidden */
position: fixed; position: fixed;
/* Stay in place */ /* Stay in place */
z-index: 10; z-index: 10;

130
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -80,6 +80,16 @@ when you normally install Augmentations.
The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected
by how many Augmentations you have purchased for yourself, and vice versa. by how many Augmentations you have purchased for yourself, and vice versa.
Memory
~~~~~~
Sleeve memory dictates what a sleeve's synchronization will be when its reset by
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
switch BitNodes its synchronization will initially be set to 10, rather than 1.
Memory can only be increased by purchasing upgrades from The Covenant.
It is a persistent stat, meaning it never gets reset back to 1.
The maximum possible value for a sleeve's memory is 100.
Re-sleeving Re-sleeving
^^^^^^^^^^^ ^^^^^^^^^^^
Re-sleeving is the process of digitizing and transferring your consciousness into a Re-sleeving is the process of digitizing and transferring your consciousness into a

@ -3,6 +3,24 @@
Changelog Changelog
========= =========
v0.46.0 - 4/3/2019
------------------
* Added BitNode-9: Hacktocracy
* Changed BitNode-11's multipliers to make it slightly harder overall
* Source-File 11 is now slightly stronger
* Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame)
* Added a new stat for Duplicate Sleeves: Memory
* Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels
* In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina
* Corporation Changes:
* 'Demand' value of products decreases more slowly
* Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
* Bug Fix: Issuing New Shares now works properly
* Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
* Bug Fix: Fixed an issue with the job requirement tooltip for security jobs
v0.45.1 - 3/23/2019 v0.45.1 - 3/23/2019
------------------- -------------------
* Added two new Corporation Researches * Added two new Corporation Researches

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.45' version = '0.46'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.45.0' release = '0.46.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

@ -0,0 +1,17 @@
getCacheUpgradeCost() Netscript Function
========================================
.. warning:: This page contains spoilers for the game
.. js:function:: getCacheUpgradeCost(i, n)
:param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>`
:param number n: Number of times to upgrade cache. Must be positive. Rounded to nearest integer
.. note:: This function is only applicable for Hacknet Servers (the upgraded version of
a Hacknet Node).
Returns the cost of upgrading the cache level of the specified Hacknet Server by *n*.
If an invalid value for *n* is provided, then this function returns 0. If the
specified Hacknet Server is already at the max cache level, then Infinity is returned.

@ -1,6 +1,8 @@
getNodeStats() Netscript Function getNodeStats() Netscript Function
================================= =================================
.. warning:: This page contains spoilers for the game
.. js:function:: getNodeStats(i) .. js:function:: getNodeStats(i)
:param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>` :param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>`
@ -12,7 +14,12 @@ getNodeStats() Netscript Function
level: Node's level, level: Node's level,
ram: Node's RAM, ram: Node's RAM,
cores: Node's number of cores, cores: Node's number of cores,
production: Node's money earned per second, cache: Cache level. Only applicable for Hacknet Servers
production: Node's production per second
timeOnline: Number of seconds since Node has been purchased, timeOnline: Number of seconds since Node has been purchased,
totalProduction: Total number of money Node has produced totalProduction: Total amount that the Node has produced
} }
.. note:: Note that for Hacknet Nodes, production refers to the amount of money the node generates.
For Hacknet Servers (the upgraded version of Hacknet Nodes), production refers to the amount
of hashes the node generates.

@ -0,0 +1,19 @@
upgradeCache() Netscript Function
=================================
.. warning:: This page contains spoilers for the game
.. js:function:: upgradeCache(i, n)
:param number i: Index/Identifier of Hacknet Node. :ref:`See here for details <netscript_hacknetnodeapi_referencingahacknetnode>`
:param number n: Number of cache levels to purchase. Must be positive. Rounded to nearest integer
.. note:: This function is only applicable for Hacknet Servers (the upgraded version of
a Hacknet Node).
Tries to upgrade the specified Hacknet Server's cache *n* times.
Returns true if it successfully upgrades the Server's cache *n* times, or if
it purchases some positive amount and the Server reaches its max cache level.
Returns false otherwise.

@ -31,9 +31,11 @@ In :ref:`netscriptjs`::
upgradeLevel() <hacknetnodeapi/upgradeLevel> upgradeLevel() <hacknetnodeapi/upgradeLevel>
upgradeRam() <hacknetnodeapi/upgradeRam> upgradeRam() <hacknetnodeapi/upgradeRam>
upgradeCore() <hacknetnodeapi/upgradeCore> upgradeCore() <hacknetnodeapi/upgradeCore>
upgradeCache() <hacknetnodeapi/upgradeCache>
getLevelUpgradeCost() <hacknetnodeapi/getLevelUpgradeCost> getLevelUpgradeCost() <hacknetnodeapi/getLevelUpgradeCost>
getRamUpgradeCost() <hacknetnodeapi/getRamUpgradeCost> getRamUpgradeCost() <hacknetnodeapi/getRamUpgradeCost>
getCoreUpgradeCost() <hacknetnodeapi/getCoreUpgradeCost> getCoreUpgradeCost() <hacknetnodeapi/getCoreUpgradeCost>
getCacheUpgradeCost() <hacknetnodeapi/getCacheUpgradeCost>
.. _netscript_hacknetnodeapi_referencingahacknetnode: .. _netscript_hacknetnodeapi_referencingahacknetnode:
@ -68,23 +70,25 @@ The following is an example of one way a script can be used to automate the
purchasing and upgrading of Hacknet Nodes. purchasing and upgrading of Hacknet Nodes.
This script attempts to purchase Hacknet Nodes until the player has a total of 8. Then This script attempts to purchase Hacknet Nodes until the player has a total of 8. Then
it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 cores:: it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 cores
.. code:: javascript
function myMoney() { function myMoney() {
return getServerMoneyAvailable("home");() <hacknetnodeapi/ return getServerMoneyAvailable("home");> return getServerMoneyAvailable("home");
} }
}() <hacknetnodeapi/>
disableLog("getServerMoneyAvailable"); disableLog("getServerMoneyAvailable");
disableLog("sleep"); disableLog("sleep");
cnt = 8; var cnt = 8;
while(hacknet.numNodes() < cnt) { while(hacknet.numNodes() < cnt) {
res = hacknet.purchaseNode(); res = hacknet.purchaseNode();
print("Purchased hacknet Node with index " + res); print("Purchased hacknet Node with index " + res);
}; };
for (i = 0; i < cnt; i++) { for (var i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).level <= 80) { while (hacknet.getNodeStats(i).level <= 80) {
var cost = hacknet.getLevelUpgradeCost(i, 10); var cost = hacknet.getLevelUpgradeCost(i, 10);
while (myMoney() < cost) { while (myMoney() < cost) {
@ -95,9 +99,9 @@ it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 c
}; };
}; };
print("All nodes upgrade to level 80"); print("All nodes upgraded to level 80");
for (i = 0; i < cnt; i++) { for (var i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).ram < 16) { while (hacknet.getNodeStats(i).ram < 16) {
var cost = hacknet.getRamUpgradeCost(i, 2); var cost = hacknet.getRamUpgradeCost(i, 2);
while (myMoney() < cost) { while (myMoney() < cost) {
@ -108,43 +112,4 @@ it gradually upgrades those Node's to a minimum of level 140, 64 GB RAM, and 8 c
}; };
}; };
print("All nodes upgrade to 16GB RAM"); print("All nodes upgraded to 16GB RAM");
for (i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).level <= 140) {
var cost = hacknet.getLevelUpgradeCost(i, 5);
while (myMoney() < cost) {
print("Need $" + cost + " . Have $" + myMoney());
sleep(3000);
}
res = hacknet.upgradeLevel(i, 5);
};
};
print("All nodes upgrade to level 140");
for (i = 0; i < cnt; i++) {
while (hacknet.getNodeStats(i).ram < 64) {
var cost = hacknet.getRamUpgradeCost(i, 2);
while (myMoney() < cost) {
print("Need $" + cost + " . Have $" + myMoney());
sleep(3000);
}
res = hacknet.upgradeRam(i, 2);
};
};
print("All nodes upgrade to 64GB RAM (MAX)");
for (i = 0; i < cnt; i++) {
while (hacknetnodes.getNodeStatsi(i).cores < 8) {
var cost = hacknet.getCoreUpgradeCost(7);
while (myMoney() < cost) {
print("Need $" + cost + " . Have $" + myMoney());
sleep(3000);
}
res = hacknet.upgradeCore(i, 7);
};
};
print("All nodes upgrade to 8 cores");

@ -0,0 +1,8 @@
getSleeveAugmentations() Netscript Function
=======================================
.. js:function:: getSleeveAugmentations(sleeveNumber)
:param int sleeveNumber: Index of the sleeve to retrieve augmentations from. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
Return a list of augmentation names that this sleeve has installed.

@ -0,0 +1,17 @@
getSleevePurchasableAugs() Netscript Function
=======================================
.. js:function:: getSleevePurchasableAugs(sleeveNumber)
:param int sleeveNumber: Index of the sleeve to retrieve purchasable augmentations from. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
Return a list of augmentations that the player can buy for this sleeve.
.. code-block:: javascript
[
{
name: string, // augmentation name
cost: number, // augmentation cost
}
]

@ -10,8 +10,8 @@ getSleeveStats() Netscript Function
.. code-block:: javascript .. code-block:: javascript
{ {
shock: current shock of the sleeve [0-1], shock: current shock of the sleeve [0-100],
sync: current sync of the sleeve [0-1], sync: current sync of the sleeve [0-100],
hacking_skill: current hacking skill of the sleeve, hacking_skill: current hacking skill of the sleeve,
strength: current strength of the sleeve, strength: current strength of the sleeve,
defense: current defense of the sleeve, defense: current defense of the sleeve,

@ -0,0 +1,9 @@
purchaseSleeveAug() Netscript Function
=======================================
.. js:function:: purchaseSleeveAug(sleeveNumber, augName)
:param int sleeveNumber: Index of the sleeve to buy an aug for. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
:param string augName: Name of the aug to buy. Must be an exact match
Return true if the aug was purchased and installed on the sleeve.

@ -201,35 +201,7 @@
<!-- 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 -->

1090
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -60,7 +60,7 @@
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"js-beautify": "^1.5.10", "js-beautify": "^1.5.10",
"json5": "^1.0.1", "json5": "^1.0.1",
"less": "^3.0.4", "less": "^3.9.0",
"less-loader": "^4.1.0", "less-loader": "^4.1.0",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"mini-css-extract-plugin": "^0.4.1", "mini-css-extract-plugin": "^0.4.1",
@ -79,7 +79,7 @@
"stylelint": "^9.2.1", "stylelint": "^9.2.1",
"stylelint-declaration-use-variable": "^1.6.1", "stylelint-declaration-use-variable": "^1.6.1",
"stylelint-order": "^0.8.1", "stylelint-order": "^0.8.1",
"ts-loader": "^4.4.1", "ts-loader": "^4.5.0",
"tslint": "^5.10.0", "tslint": "^5.10.0",
"typescript": "^2.9.2", "typescript": "^2.9.2",
"uglify-es": "^3.3.9", "uglify-es": "^3.3.9",
@ -89,7 +89,7 @@
"webpack": "^4.12.0", "webpack": "^4.12.0",
"webpack-cli": "^3.0.4", "webpack-cli": "^3.0.4",
"webpack-dev-middleware": "^3.1.3", "webpack-dev-middleware": "^3.1.3",
"webpack-dev-server": "^3.1.4", "webpack-dev-server": "^3.2.1",
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"engines": { "engines": {

@ -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,24 @@ 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, a global, decentralized network used for nefarious purposes. Fulcrum quickly " +
"abandoned the project and dissociated themselves from it.<br><br>" +
"This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " +
"hashes, which can be spent on a variety of different upgrades.<br><br>" +
"In this BitNode:<br><br>" +
"Your stats are significantly decreased<br>" +
"You cannnot purchase additional servers<br>" +
"Hacking is significantly less profitable<br><br>" +
"Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File grants the following benefits:<br><br>" +
"Level 1: Permanently unlocks the Hacknet Server in other BitNodes<br>" +
"Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode<br>" +
"Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode<br><br>" +
"(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
"when installing Augmentations)");
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 " +
@ -183,7 +200,7 @@ export function initBitNodes() {
"1. Re-sleeve: Purchase and transfer your consciousness into a new body<br>" + "1. Re-sleeve: Purchase and transfer your consciousness into a new body<br>" +
"2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously<br><br>" + "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously<br><br>" +
"In this BitNode:<br><br>" + "In this BitNode:<br><br>" +
"Your stats are significantly decreased.<br>" + "Your stats are significantly decreased<br>" +
"All methods of gaining money are half as profitable (except Stock Market)<br>" + "All methods of gaining money are half as profitable (except Stock Market)<br>" +
"Purchased servers are more expensive, have less max RAM, and a lower maximum limit<br>" + "Purchased servers are more expensive, have less max RAM, and a lower maximum limit<br>" +
"Augmentations are 5x as expensive and require twice as much reputation<br><br>" + "Augmentations are 5x as expensive and require twice as much reputation<br><br>" +
@ -198,8 +215,9 @@ export function initBitNodes() {
"were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" + "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
"In this BitNode:<br><br>" + "In this BitNode:<br><br>" +
"Your hacking stat and experience gain are halved<br>" +
"The starting and maximum amount of money available on servers is significantly decreased<br>" + "The starting and maximum amount of money available on servers is significantly decreased<br>" +
"The growth rate of servers is halved<br>" + "The growth rate of servers is significantly reduced<br>" +
"Weakening a server is twice as effective<br>" + "Weakening a server is twice as effective<br>" +
"Company wages are decreased by 50%<br>" + "Company wages are decreased by 50%<br>" +
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" + "Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
@ -210,9 +228,9 @@ export function initBitNodes() {
"upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " +
"the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
"This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" + "This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
"Level 1: 24%<br>" + "Level 1: 32%<br>" +
"Level 2: 36%<br>" + "Level 2: 48%<br>" +
"Level 3: 42%"); "Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"To iterate is human, to recurse divine.<br><br>" + "To iterate is human, to recurse divine.<br><br>" +
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
@ -345,6 +363,27 @@ 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.4;
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 = 5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.1;
BitNodeMultipliers.HackExpGain = 0.05;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingSecurity = 2.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.FourSigmaMarketDataCost = 5;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
BitNodeMultipliers.BladeburnerRank = 0.9;
BitNodeMultipliers.BladeburnerSkillCost = 1.2;
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;
@ -369,9 +408,11 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.BladeburnerRank = 0.8; BitNodeMultipliers.BladeburnerRank = 0.8;
break; break;
case 11: //The Big Crash case 11: //The Big Crash
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.ServerMaxMoney = 0.1; BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1; BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerGrowthRate = 0.5; BitNodeMultipliers.ServerGrowthRate = 0.2;
BitNodeMultipliers.ServerWeakenRate = 2; BitNodeMultipliers.ServerWeakenRate = 2;
BitNodeMultipliers.CrimeMoney = 3; BitNodeMultipliers.CrimeMoney = 3;
BitNodeMultipliers.CompanyWorkMoney = 0.5; BitNodeMultipliers.CompanyWorkMoney = 0.5;
@ -379,8 +420,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.AugmentationMoneyCost = 2; BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.InfiltrationMoney = 2.5; BitNodeMultipliers.InfiltrationMoney = 2.5;
BitNodeMultipliers.InfiltrationRep = 2.5; BitNodeMultipliers.InfiltrationRep = 2.5;
BitNodeMultipliers.CorporationValuation = 0.01; BitNodeMultipliers.CorporationValuation = 0.1;
BitNodeMultipliers.CodingContractMoney = 0.5; BitNodeMultipliers.CodingContractMoney = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 4; BitNodeMultipliers.FourSigmaMarketDataCost = 4;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
break; break;

@ -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;

@ -78,8 +78,8 @@ const RanksPerSkillPoint = 3; //How many ranks needed to get 1 Skill
const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber
const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber
//DOM related variables //DOM related variables
var ActiveActionCssClass = "bladeburner-active-action"; var ActiveActionCssClass = "bladeburner-active-action";
@ -1417,14 +1417,17 @@ Bladeburner.prototype.completeAction = function() {
} }
this.startAction(this.action); // Repeat Action this.startAction(this.action); // Repeat Action
break; break;
case ActionTypes["Hyperbolic Regeneration Chamber"]: case ActionTypes["Hyperbolic Regeneration Chamber"]: {
Player.regenerateHp(HrcHpGain); Player.regenerateHp(HrcHpGain);
this.stamina = Math.min(this.maxStamina, this.stamina + HrcStaminaGain);
const staminaGain = this.maxStamina * (HrcStaminaGain / 100);
this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain);
this.startAction(this.action); this.startAction(this.action);
if (this.logging.general) { if (this.logging.general) {
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`); this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`);
} }
break; break;
}
default: default:
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
break; break;

@ -1,7 +1,12 @@
/**
* 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> = {
Version: "0.45.1", Version: "0.46.0",
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience //Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then //and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -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,
@ -126,6 +116,7 @@ export let CONSTANTS: IMap<any> = {
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
InfiltrationExpPow: 0.8,
//Stock market constants //Stock market constants
WSEAccountCost: 200e6, WSEAccountCost: 200e6,
@ -282,37 +273,21 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.45.1 v0.46.0
* Added two new Corporation Researches * Added BitNode-9: Hacktocracy
* General UI improvements (by hydroflame and koriar) * Changed BitNode-11's multipliers to make it slightly harder overall
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors * Source-File 11 is now slightly stronger
* Bug Fix: sleeve.getSleeveStats() should now work properly * Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame)
* Added a new stat for Duplicate Sleeves: Memory
* Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels
* In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina
v0.45.0 * Corporation Changes:
* Corporation changes: ** 'Demand' value of products decreases more slowly
** Decreased the time of a full market cycle from 15 seconds to 10 seconds. ** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
** This means that each Corporation 'state' will now only take 2 seconds, rather than 3 ** Bug Fix: Issuing New Shares now works properly
** Increased initial salaries for newly-hired employees
** Increased the cost multiplier for upgrading office size (the cost will increase faster) * Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
** The stats of your employees now has a slightly larger effect on production & sales * Bug Fix: Fixed an issue with the job requirement tooltip for security jobs
** Added several new Research upgrades
** Market-TA research now allows you to automatically set sale price at optimal values
** Market-TA research now works for Products (not just Materials)
** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k
** Energy Material requirement of the Software industry reduced from 1 to 0.5
** It is now slightly easier to increase the Software industry's production multiplier
** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research.
** You can now see an approximation of how each material affects an industry's production multiplier by clicking the "?" help tip next to it
** Significantly changed the effects of the different employee positions. See updated descriptions
** Reduced the amount of money you gain from private investors
** Training employees is now 3x more effective
** Bug Fix: An industry's products are now properly separated between different cities
* The QLink Augemntation is now significantly stronger, but also significantly more expensive (by hydroflame)
* Added a Netscript API for Duplicate Sleeves (by hydroflame)
* Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder
* After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0
* Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina
* Bug Fix: growthAnalyze() function now properly accounts for BitNode multipliers
* Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself
` `
} }

@ -566,10 +566,12 @@ Industry.prototype.processMaterialMarket = function(marketCycles=1) {
// Process change in demand and competition for this industry's products // Process change in demand and competition for this industry's products
Industry.prototype.processProductMarket = function(marketCycles=1) { Industry.prototype.processProductMarket = function(marketCycles=1) {
// Demand gradually decreases, and competition gradually increases // Demand gradually decreases, and competition gradually increases
for (var name in this.products) { for (const name in this.products) {
if (this.products.hasOwnProperty(name)) { if (this.products.hasOwnProperty(name)) {
var product = this.products[name]; const product = this.products[name];
var change = getRandomInt(1, 3) * 0.0004; let change = getRandomInt(0, 3) * 0.0004;
if (change === 0) { continue; }
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
this.type === Industries.Robotics) { this.type === Industries.Robotics) {
change *= 3; change *= 3;
@ -770,7 +772,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
* advertisingFactor * advertisingFactor
* this.getSalesMultiplier()); * this.getSalesMultiplier());
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
const optimalPrice = (numerator / denominator) + mat.bCost; let optimalPrice;
if (sqrtDenominator === 0 || denominator === 0) {
if (sqrtNumerator === 0) {
optimalPrice = 0; // No production
} else {
optimalPrice = mat.bCost + markupLimit;
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
}
} else {
optimalPrice = (numerator / denominator) + mat.bCost;
}
// We'll store this "Optimal Price" in a property so that we don't have // We'll store this "Optimal Price" in a property so that we don't have
// to re-calculate it for the UI // to re-calculate it for the UI
@ -1089,7 +1101,12 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
let optimalPrice; let optimalPrice;
if (sqrtDenominator === 0 || denominator === 0) { if (sqrtDenominator === 0 || denominator === 0) {
optimalPrice = 0; if (sqrtNumerator === 0) {
optimalPrice = 0; // No production
} else {
optimalPrice = product.pCost + markupLimit;
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
}
} else { } else {
optimalPrice = (numerator / denominator) + product.pCost; optimalPrice = (numerator / denominator) + product.pCost;
} }
@ -1251,7 +1268,7 @@ Industry.prototype.getAdvertisingFactors = function() {
//Returns a multiplier based on a materials demand and competition that affects sales //Returns a multiplier based on a materials demand and competition that affects sales
Industry.prototype.getMarketFactor = function(mat) { Industry.prototype.getMarketFactor = function(mat) {
return mat.dmd * (100 - mat.cmp)/100; return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100);
} }
// Returns a boolean indicating whether this Industry has the specified Research // Returns a boolean indicating whether this Industry has the specified Research

@ -86,7 +86,7 @@ export class Material {
this.mku = 6; this.mku = 6;
break; break;
case "Energy": case "Energy":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 80; this.cmpR = [65, 95]; this.cmp = 80; this.cmpR = [65, 95];
this.bCost = 2000; this.mv = 0.2; this.bCost = 2000; this.mv = 0.2;
this.mku = 6; this.mku = 6;
@ -122,26 +122,26 @@ export class Material {
this.mku = 2; this.mku = 2;
break; break;
case "Real Estate": case "Real Estate":
this.dmd = 50; this.dmdR = [5, 100]; this.dmd = 50; this.dmdR = [5, 99];
this.cmp = 50; this.cmpR = [25, 75]; this.cmp = 50; this.cmpR = [25, 75];
this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
this.mku = 1.5; this.mku = 1.5;
break; break;
case "Drugs": case "Drugs":
this.dmd = 60; this.dmdR = [45, 75]; this.dmd = 60; this.dmdR = [45, 75];
this.cmp = 70; this.cmpR = [40, 100]; this.cmp = 70; this.cmpR = [40, 99];
this.bCost = 40e3; this.mv = 1.6; this.bCost = 40e3; this.mv = 1.6;
this.mku = 1; this.mku = 1;
break; break;
case "Robots": case "Robots":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 9];
this.cmp = 90; this.cmpR = [80, 100]; this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
this.mku = 1; this.mku = 1;
break; break;
case "AI Cores": case "AI Cores":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 90; this.cmpR = [80, 100]; this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
this.mku = 0.5; this.mku = 0.5;
break; break;

@ -31,6 +31,7 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
import { KEY } from "../../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
@ -780,7 +781,12 @@ export class CorporationEventHandler {
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]); const ta2OverridesTa1 = createElement("p", {
innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
"both are enabled, then Market-TA.II will take effect, not Market-TA.I"
});
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
} else { } else {
// Market-TA.I only // Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
@ -1052,7 +1058,12 @@ export class CorporationEventHandler {
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]); const ta2OverridesTa1 = createElement("p", {
innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
"both are enabled, then Market-TA.II will take effect, not Market-TA.I"
});
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
} else { } else {
// Market-TA.I only // Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);

@ -300,8 +300,8 @@ export class IndustryOffice extends BaseReactComponent {
<br /> <br />
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p> <p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
<p>Avg Happiness Morale: {numeralWrapper.format(avgHappiness, "0.000")}</p> <p>Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}</p>
<p>Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}</p> <p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p> <p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{ {
vechain && vechain &&

@ -218,7 +218,7 @@ function MaterialComponent(props) {
mat.buy === 0 && mat.imp === 0; mat.buy === 0 && mat.imp === 0;
// Purchase material button // Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`; const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse); const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
@ -229,9 +229,9 @@ function MaterialComponent(props) {
let sellButtonText; let sellButtonText;
if (mat.sllman[0]) { if (mat.sllman[0]) {
if (isString(mat.sllman[1])) { if (isString(mat.sllman[1])) {
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${mat.sllman[1]})` sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
} else { } else {
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${numeralWrapper.format(mat.sllman[1], nf)})`; sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`;
} }
if (mat.marketTa2) { if (mat.marketTa2) {
@ -469,7 +469,7 @@ export class IndustryWarehouse extends BaseReactComponent {
return ( return (
<div className={"cmpy-mgmt-warehouse-panel"}> <div className={"cmpy-mgmt-warehouse-panel"}>
<p className={"tooltip"} style={sizeUsageStyle}> <p className={"tooltip"} style={sizeUsageStyle}>
Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")} Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)}
<span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span> <span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
</p> </p>

@ -14,7 +14,7 @@ import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { createPurchaseSleevesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases"; import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
import {Page, routing} from "../ui/navigationTracking"; import {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat"; import {numeralWrapper} from "../ui/numeralFormat";
@ -348,7 +348,7 @@ function displayFactionContent(factionName) {
class: "std-button", class: "std-button",
innerText: "Purchase Duplicate Sleeves", innerText: "Purchase Duplicate Sleeves",
clickListener: () => { clickListener: () => {
createPurchaseSleevesFromCovenantPopup(Player); createSleevePurchasesFromCovenantPopup(Player);
} }
})); }));
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", { covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {

1
src/Hacking/README.md Normal file

@ -0,0 +1 @@
Implementation of underlying Hacking mechanics

@ -0,0 +1,53 @@
/**
* Functions used to determine whether the target can be hacked (or grown/weakened).
* Meant to be used for Netscript implementation
*
* The returned status object's message should be used for logging in Netscript
*/
import { IReturnStatus } from "../types";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Server } from "../Server/Server";
function baseCheck(server: Server | HacknetServer, fnName: string): IReturnStatus {
if (server instanceof HacknetServer) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because it is a Hacknet Node`
}
}
if (server.hasAdminRights === false) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because you do not have root access`,
}
}
return { res: true }
}
export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IReturnStatus {
const initialCheck = baseCheck(server, "hack");
if (!initialCheck.res) { return initialCheck; }
let s = <Server>server;
if (s.requiredHackingSkill > p.hacking_skill) {
return {
res: false,
msg: `Cannot hack ${server.hostname} server because your hacking skill is not high enough`,
}
}
return { res: true }
}
export function netscriptCanGrow(server: Server | HacknetServer): IReturnStatus {
return baseCheck(server, "grow");
}
export function netscriptCanWeaken(server: Server | HacknetServer): IReturnStatus {
return baseCheck(server, "weaken");
}

@ -0,0 +1,437 @@
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,
AllServers } 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.removeEventListener("DOMContentLoaded", hacknetNodesInit);
}
document.addEventListener("DOMContentLoaded", hacknetNodesInit);
// 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 createHacknetServer() {
const numOwned = Player.hacknetNodes.length;
const name = `hacknet-node-${numOwned}`;
const server = new HacknetServer({
adminRights: true,
hostname: name,
player: Player,
});
Player.hacknetNodes.push(server.ip);
// 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 server;
}
export function purchaseHacknet() {
/* INTERACTIVE TUTORIAL */
if (ITutorial.isRunning) {
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
iTutorialNextStep();
} else {
return;
}
}
/* END INTERACTIVE TUTORIAL */
const numOwned = Player.hacknetNodes.length;
if (hasHacknetServers()) {
const cost = getCostOfNextHacknetServer();
if (isNaN(cost)) {
throw new Error(`Calculated cost of purchasing HacknetServer is NaN`)
}
if (!Player.canAfford(cost)) { return -1; }
Player.loseMoney(cost);
const server = createHacknetServer();
Player.hashManager.updateCapacity(Player);
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 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) {
const hserver = AllServers[Player.hacknetNodes[i]]; // hacknetNodes array only contains the IP addresses
hashes += hserver.process(numCycles);
}
Player.hashManager.storeHashes(hashes);
return hashes;
}
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 isnt in Bladeburner
try {
Player.bladeburner.changeRank(upg.value);
} catch(e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Exchange for Bladeburner SP": {
// This will throw if player isn't in Bladeburner
try {
// As long as we don't change `Bladeburner.totalSkillPoints`, this
// shouldn't affect anything else
Player.bladeburner.skillPoints += 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`)
Player.hashManager.refundUpgrade(upgName);
return false;
}
return true;
}
return false;
}

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;

@ -0,0 +1,350 @@
/**
* 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 = 50e3;
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;
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);
this.updateHashRate(p);
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 = 32 * Math.pow(2, this.cache);
}
updateHashRate(p: IPlayer): void {
const baseGain = HacknetServerHashesPerLevel * this.level;
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
const coreMultiplier = 1 + (this.cores - 1) / 5;
const ramRatio = (1 - this.ramUsed / this.maxRam);
const hashRate = baseGain * ramMultiplier * coreMultiplier * ramRatio;
this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.hashRate)) {
this.hashRate = 0;
console.error(`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;

178
src/Hacknet/HashManager.ts Normal file

@ -0,0 +1,178 @@
/**
* 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 { AllServers } from "../Server/AllServers";
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);
}
prestige(p: IPlayer): void {
for (const name in HashUpgrades) {
this.upgrades[name] = 0;
}
this.hashes = 0;
if (p != null) {
this.updateCapacity(p);
}
}
/**
* 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;
}
storeHashes(numHashes: number): void {
this.hashes += numHashes;
this.hashes = Math.min(this.hashes, this.capacity);
}
updateCapacity(p: IPlayer): void {
if (p.hacknetNodes.length <= 0) {
this.capacity = 0;
return;
}
// Make sure the Player's `hacknetNodes` property actually holds Hacknet Servers
const ip: string = <string>p.hacknetNodes[0];
if (typeof ip !== "string") {
this.capacity = 0;
return;
}
const hserver = <HacknetServer>AllServers[ip];
if (!(hserver instanceof HacknetServer)) {
this.capacity = 0;
return;
}
let total: number = 0;
for (let i = 0; i < p.hacknetNodes.length; ++i) {
const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
total += h.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;

@ -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);
}
}

@ -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);
}

@ -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;
}

@ -0,0 +1,70 @@
// Metadata used to construct all Hash Upgrades
import { IConstructorParams } from "../HashUpgrade";
export const HashUpgradesMetadata: IConstructorParams[] = [
{
costPerLevel: 1,
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: 50,
desc: "Use hashes to decrease the minimum security of a single server by 2%. " +
"Note that a server's minimum security cannot go below 1.",
hasTargetServer: true,
name: "Reduce Minimum Security",
value: 0.98,
},
{
costPerLevel: 50,
desc: "Use hashes to increase the maximum amount of money on a single server by 2%",
hasTargetServer: true,
name: "Increase Maximum Money",
value: 1.02,
},
{
costPerLevel: 50,
desc: "Use hashes to improve the experience earned when studying at a university by 20%. " +
"This effect persists until you install Augmentations",
name: "Improve Studying",
value: 20, // Improves studying by value%
},
{
costPerLevel: 50,
desc: "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
"persists until you install Augmentations",
name: "Improve Gym Training",
value: 20, // Improves training by value%
},
{
costPerLevel: 200,
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: 250,
desc: "Exchanges hashes for 10 Bladeburner Skill Points",
name: "Exchange for Bladeburner SP",
value: 10,
},
{
costPerLevel: 150,
desc: "Generate a random Coding Contract on your home computer",
name: "Generate Coding Contract",
value: 1,
},
]

@ -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>
)
}
}

@ -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>
)
}
}

@ -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>
)
}
}

@ -0,0 +1,130 @@
/**
* 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 { PopupCloseButton } from "../../ui/React/PopupCloseButton";
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.state = {
totalHashes: Player.hashManager.hashes,
}
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1e3);
}
componentWillUnmount() {
clearInterval(this.interval);
}
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>
<PopupCloseButton popup={this.props.popupId} text={"Close"} />
<p>Spend your hashes on a variety of different upgrades</p>
<p>Hashes: {numeralWrapper.formatBigNumber(this.state.totalHashes)}</p>
{upgradeElems}
</div>
)
}
}

@ -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>
)
}

@ -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>
)
}

@ -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>
)
}

154
src/Hacknet/ui/Root.jsx Normal file

@ -0,0 +1,154 @@
/**
* 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 { AllServers } from "../../Server/AllServers";
import { createPopup } from "../../ui/React/createPopup";
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
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()) {
const hserver = AllServers[Player.hacknetNodes[i]];
if (hserver) {
total += hserver.hashRate;
} else {
console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`)
}
} 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();
}
}
// 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()) {
const hserver = AllServers[node];
if (hserver == null) {
throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`);
}
return (
<HacknetServer
key={hserver.hostname}
node={hserver}
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
};

@ -52,41 +52,81 @@ function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff)
this.intExpGained = 0; this.intExpGained = 0;
} }
InfiltrationInstance.prototype.expMultiplier = function() {
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1;
return 2.5 * this.clearanceLevel / this.maxClearanceLevel;
}
InfiltrationInstance.prototype.gainHackingExp = function(amt) { InfiltrationInstance.prototype.gainHackingExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.hackingExpGained += amt; this.hackingExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedHackingExp = function() {
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0;
return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainStrengthExp = function(amt) { InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.strExpGained += amt; this.strExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedStrengthExp = function() {
if (!this.strExpGained || isNaN(this.strExpGained)) return 0;
return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainDefenseExp = function(amt) { InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.defExpGained += amt; this.defExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedDefenseExp = function() {
if (!this.defExpGained || isNaN(this.defExpGained)) return 0;
return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainDexterityExp = function(amt) { InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.dexExpGained += amt; this.dexExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedDexterityExp = function() {
if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0;
return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainAgilityExp = function(amt) { InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.agiExpGained += amt; this.agiExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedAgilityExp = function() {
if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0;
return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainCharismaExp = function(amt) { InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.chaExpGained += amt; this.chaExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedCharismaExp = function() {
if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0;
return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) { InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.intExpGained += amt; this.intExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedIntelligenceExp = function() {
if(!this.intExpGained || isNaN(this.intExpGained)) return 0;
return Math.pow(this.intExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) { function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff); var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
clearInfiltrationStatusText(); clearInfiltrationStatusText();
@ -477,12 +517,12 @@ function updateInfiltrationLevelText(inst) {
"Total value of stolen secrets<br>" + "Total value of stolen secrets<br>" +
"Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" + "Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" + "Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "<br>" + "Hack exp gained:  " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + formatNumber(inst.strExpGained * expMultiplier, 3) + "<br>" + "Str exp gained:   " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + formatNumber(inst.defExpGained * expMultiplier, 3) + "<br>" + "Def exp gained:   " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "<br>" + "Dex exp gained:   " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "<br>" + "Agi exp gained:   " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + formatNumber(inst.chaExpGained * expMultiplier, 3); "Cha exp gained:   " + formatNumber(inst.calcGainedCharismaExp(), 3);
/* eslint-enable no-irregular-whitespace */ /* eslint-enable no-irregular-whitespace */
} }

@ -327,7 +327,7 @@ function displayLocationContent() {
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[0]], securityJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[2]], securityJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob);
@ -654,7 +654,7 @@ function displayLocationContent() {
networkEngineerJob.style.display = "block"; networkEngineerJob.style.display = "block";
securityJob.style.display = "block"; securityJob.style.display = "block";
agentJob.style.display = "block"; agentJob.style.display = "block";
if (Player.bitNodeN === 6 || hasBladeburnerSF === true) { if (Player.bitNodeN === 6 ||Player.bitNodeN === 7 || hasBladeburnerSF === true) {
if (Player.bitNodeN === 8) {break;} if (Player.bitNodeN === 8) {break;}
if (Player.bladeburner instanceof Bladeburner) { if (Player.bladeburner instanceof Bladeburner) {
//Note: Can't infiltrate NSA when part of bladeburner //Note: Can't infiltrate NSA when part of bladeburner

@ -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,8 +196,11 @@ 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
addWorkerScript(runningScriptObj, server); addWorkerScript(runningScriptObj, server);
// Push onto runningScripts.
// This has to come after addWorkerScript() because that fn updates RAM usage
server.runScript(runningScriptObj, Player);
return Promise.resolve(true); return Promise.resolve(true);
} }
} }

@ -29,8 +29,15 @@ import { Factions,
import { joinFaction, import { joinFaction,
purchaseAugmentation } from "./Faction/FactionHelpers"; purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum"; import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { netscriptCanGrow,
netscriptCanHack,
netscriptCanWeaken } from "./Hacking/netscriptCanHack";
import { getCostOfNextHacknetNode, import { getCostOfNextHacknetNode,
purchaseHacknet } from "./HacknetNode"; getCostOfNextHacknetServer,
hasHacknetServers,
purchaseHacknet } from "./Hacknet/HacknetHelpers";
import { HacknetServer } from "./Hacknet/HacknetServer";
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";
@ -73,7 +80,8 @@ import {WorkerScript, workerScripts,
import {makeRuntimeRejectMsg, netscriptDelay, import {makeRuntimeRejectMsg, netscriptDelay,
runScriptFromScript} from "./NetscriptEvaluator"; runScriptFromScript} from "./NetscriptEvaluator";
import {NetscriptPort} from "./NetscriptPort"; import {NetscriptPort} from "./NetscriptPort";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum" import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/Sleeve";
import {Page, routing} from "./ui/navigationTracking"; import {Page, routing} from "./ui/navigationTracking";
import {numeralWrapper} from "./ui/numeralFormat"; import {numeralWrapper} from "./ui/numeralFormat";
@ -193,11 +201,11 @@ function initSingularitySFFlags() {
} }
function NetscriptFunctions(workerScript) { function NetscriptFunctions(workerScript) {
var updateDynamicRam = function(fnName, ramCost) { const updateDynamicRam = function(fnName, ramCost) {
if (workerScript.dynamicLoadedFns[fnName]) {return;} if (workerScript.dynamicLoadedFns[fnName]) {return;}
workerScript.dynamicLoadedFns[fnName] = true; workerScript.dynamicLoadedFns[fnName] = true;
const threads = workerScript.scriptRef.threads; let threads = workerScript.scriptRef.threads;
if (typeof threads !== 'number') { if (typeof threads !== 'number') {
console.warn(`WorkerScript detected NaN for threadcount for ${workerScript.name} on ${workerScript.serverIp}`); console.warn(`WorkerScript detected NaN for threadcount for ${workerScript.name} on ${workerScript.serverIp}`);
threads = 1; threads = 1;
@ -214,7 +222,7 @@ function NetscriptFunctions(workerScript) {
} }
}; };
var updateStaticRam = function(fnName, ramCost) { const updateStaticRam = function(fnName, ramCost) {
if (workerScript.loadedFns[fnName]) { if (workerScript.loadedFns[fnName]) {
return 0; return 0;
} else { } else {
@ -227,28 +235,56 @@ function NetscriptFunctions(workerScript) {
* Gets the Server for a specific hostname/ip, throwing an error * Gets the Server for a specific hostname/ip, throwing an error
* if the server doesn't exist. * if the server doesn't exist.
* @param {string} Hostname or IP of the server * @param {string} Hostname or IP of the server
* @param {string} callingFnName - Name of calling function. For logging purposes
* @returns {Server} The specified Server * @returns {Server} The specified Server
*/ */
var safeGetServer = function(ip, callingFnName="") { const safeGetServer = function(ip, callingFnName="") {
var server = getServer(ip); var server = getServer(ip);
if (server == null) { if (server == null) {
workerScript.log(`ERROR: Invalid IP or hostname passed into ${callingFnName}()`);
throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`); throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`);
} }
return server; return server;
} }
/**
* Used to fail a function if the function's target is a Hacknet Server.
* This is used for functions that should run on normal Servers, but not Hacknet Servers
* @param {Server} server - Target server
* @param {string} callingFn - Name of calling function. For logging purposes
* @returns {boolean} True if the server is a Hacknet Server, false otherwise
*/
const failOnHacknetServer = function(server, callingFn="") {
if (server instanceof HacknetServer) {
workerScript.log(`ERROR: ${callingFn}() failed because it does not work on Hacknet Servers`);
return true;
} else {
return false;
}
}
// Utility function to get Hacknet Node object // Utility function to get Hacknet Node object
var getHacknetNode = function(i) { const getHacknetNode = function(i) {
if (isNaN(i)) { if (isNaN(i)) {
throw makeRuntimeRejectMsg(workerScript, "Invalid index specified for Hacknet Node: " + i); throw makeRuntimeRejectMsg(workerScript, "Invalid index specified for Hacknet Node: " + i);
} }
if (i < 0 || i >= Player.hacknetNodes.length) { if (i < 0 || i >= Player.hacknetNodes.length) {
throw makeRuntimeRejectMsg(workerScript, "Index specified for Hacknet Node is out-of-bounds: " + i); throw makeRuntimeRejectMsg(workerScript, "Index specified for Hacknet Node is out-of-bounds: " + i);
} }
if (hasHacknetServers()) {
const hserver = AllServers[Player.hacknetNodes[i]];
if (hserver == null) {
throw makeRuntimeRejectMsg(workerScript, `Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`);
}
return hserver;
} else {
return Player.hacknetNodes[i]; return Player.hacknetNodes[i];
}
}; };
var getCodingContract = function(fn, ip) { const getCodingContract = function(fn, ip) {
var server = safeGetServer(ip, "getCodingContract"); var server = safeGetServer(ip, "getCodingContract");
return server.getContract(fn); return server.getContract(fn);
} }
@ -262,43 +298,68 @@ function NetscriptFunctions(workerScript) {
return purchaseHacknet(); return purchaseHacknet();
}, },
getPurchaseNodeCost : function() { getPurchaseNodeCost : function() {
if (hasHacknetServers()) {
return getCostOfNextHacknetServer();
} else {
return getCostOfNextHacknetNode(); return getCostOfNextHacknetNode();
}
}, },
getNodeStats : function(i) { getNodeStats : function(i) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return { const hasUpgraded = hasHacknetServers();
const res = {
name: node.name, name: node.name,
level: node.level, level: node.level,
ram: node.ram, ram: hasUpgraded ? node.maxRam : node.ram,
cores: node.cores, cores: node.cores,
production: node.moneyGainRatePerSecond, production: hasUpgraded ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds, timeOnline: node.onlineTimeSeconds,
totalProduction: node.totalMoneyGenerated, totalProduction: hasUpgraded ? node.totalHashesGenerated : node.totalMoneyGenerated,
}; };
if (hasUpgraded) {
res.cache = node.cache;
}
return res;
}, },
upgradeLevel : function(i, n) { upgradeLevel : function(i, n) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return node.purchaseLevelUpgrade(n); return node.purchaseLevelUpgrade(n, Player);
}, },
upgradeRam : function(i, n) { upgradeRam : function(i, n) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return node.purchaseRamUpgrade(n); return node.purchaseRamUpgrade(n, Player);
}, },
upgradeCore : function(i, n) { upgradeCore : function(i, n) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return node.purchaseCoreUpgrade(n); return node.purchaseCoreUpgrade(n, Player);
},
upgradeCache : function(i, n) {
if (!hasHacknetServers()) { return false; }
const node = getHacknetNode(i);
const res = node.purchaseCacheUpgrade(n, Player);
if (res) {
Player.hashManager.updateCapacity(Player);
}
return res;
}, },
getLevelUpgradeCost : function(i, n) { getLevelUpgradeCost : function(i, n) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return node.calculateLevelUpgradeCost(n); return node.calculateLevelUpgradeCost(n, Player);
}, },
getRamUpgradeCost : function(i, n) { getRamUpgradeCost : function(i, n) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return node.calculateRamUpgradeCost(n); return node.calculateRamUpgradeCost(n, Player);
}, },
getCoreUpgradeCost : function(i, n) { getCoreUpgradeCost : function(i, n) {
var node = getHacknetNode(i); const node = getHacknetNode(i);
return node.calculateCoreUpgradeCost(n); return node.calculateCoreUpgradeCost(n, Player);
},
getCacheUpgradeCost : function(i, n) {
if (!hasHacknetServers()) { return Infinity; }
const node = getHacknetNode(i);
return node.calculateCacheUpgradeCost(n);
} }
}, },
sprintf : sprintf, sprintf : sprintf,
@ -350,19 +411,16 @@ function NetscriptFunctions(workerScript) {
var hackingTime = calculateHackingTime(server); //This is in seconds var hackingTime = calculateHackingTime(server); //This is in seconds
//No root access or skill level too low //No root access or skill level too low
if (server.hasAdminRights == false) { const canHack = netscriptCanHack(server, Player);
workerScript.scriptRef.log("Cannot hack this server (" + server.hostname + ") because user does not have root access"); if (!canHack.res) {
throw makeRuntimeRejectMsg(workerScript, "Cannot hack this server (" + server.hostname + ") because user does not have root access"); workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
} throw makeRuntimeRejectMsg(workerScript, canHack.msg);
if (server.requiredHackingSkill > Player.hacking_skill) {
workerScript.scriptRef.log("Cannot hack this server (" + server.hostname + ") because user's hacking skill is not high enough");
throw makeRuntimeRejectMsg(workerScript, "Cannot hack this server (" + server.hostname + ") because user's hacking skill is not high enough");
} }
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.hack == null) { if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.hack == null) {
workerScript.scriptRef.log("Attempting to hack " + ip + " in " + hackingTime.toFixed(3) + " seconds (t=" + threads + ")"); workerScript.scriptRef.log("Attempting to hack " + ip + " in " + hackingTime.toFixed(3) + " seconds (t=" + threads + ")");
} }
return netscriptDelay(hackingTime * 1000, workerScript).then(function() { return netscriptDelay(hackingTime * 1000, workerScript).then(function() {
if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} if (workerScript.env.stopFlag) {return Promise.reject(workerScript);}
var hackChance = calculateHackingChance(server); var hackChance = calculateHackingChance(server);
@ -481,9 +539,10 @@ function NetscriptFunctions(workerScript) {
} }
//No root access or skill level too low //No root access or skill level too low
if (server.hasAdminRights == false) { const canHack = netscriptCanGrow(server);
workerScript.scriptRef.log("Cannot grow this server (" + server.hostname + ") because user does not have root access"); if (!canHack.res) {
throw makeRuntimeRejectMsg(workerScript, "Cannot grow this server (" + server.hostname + ") because user does not have root access"); workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
} }
var growTime = calculateGrowTime(server); var growTime = calculateGrowTime(server);
@ -542,9 +601,10 @@ function NetscriptFunctions(workerScript) {
} }
//No root access or skill level too low //No root access or skill level too low
if (server.hasAdminRights == false) { const canHack = netscriptCanWeaken(server);
workerScript.scriptRef.log("Cannot weaken this server (" + server.hostname + ") because user does not have root access"); if (!canHack.res) {
throw makeRuntimeRejectMsg(workerScript, "Cannot weaken this server (" + server.hostname + ") because user does not have root access"); workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
} }
var weakenTime = calculateWeakenTime(server); var weakenTime = calculateWeakenTime(server);
@ -1305,20 +1365,17 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerMoneyAvailable");
if (server == null) { if (failOnHacknetServer(server, "getServerMoneyAvailable")) { return 0; }
workerScript.scriptRef.log("getServerMoneyAvailable() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerMoneyAvailable() failed. Invalid IP or hostname passed in: " + ip);
}
if (server.hostname == "home") { if (server.hostname == "home") {
// Return player's money // Return player's money
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMoneyAvailable == null) { if (workerScript.shouldLog("getServerMoneyAvailable")) {
workerScript.scriptRef.log("getServerMoneyAvailable('home') returned player's money: $" + formatNumber(Player.money.toNumber(), 2)); workerScript.log("getServerMoneyAvailable('home') returned player's money: $" + formatNumber(Player.money.toNumber(), 2));
} }
return Player.money.toNumber(); return Player.money.toNumber();
} }
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMoneyAvailable == null) { if (workerScript.shouldLog("getServerMoneyAvailable")) {
workerScript.scriptRef.log("getServerMoneyAvailable() returned " + formatNumber(server.moneyAvailable, 2) + " for " + server.hostname); workerScript.log("getServerMoneyAvailable() returned " + formatNumber(server.moneyAvailable, 2) + " for " + server.hostname);
} }
return server.moneyAvailable; return server.moneyAvailable;
}, },
@ -1327,13 +1384,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerSecurityLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerSecurityLevel")) { return 1; }
workerScript.scriptRef.log("getServerSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerSecurityLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerSecurityLevel() returned " + formatNumber(server.hackDifficulty, 3) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerSecurityLevel == null) {
workerScript.scriptRef.log("getServerSecurityLevel() returned " + formatNumber(server.hackDifficulty, 3) + " for " + server.hostname);
} }
return server.hackDifficulty; return server.hackDifficulty;
}, },
@ -1342,13 +1396,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerBaseSecurityLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { return 1; }
workerScript.scriptRef.log("getServerBaseSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerBaseSecurityLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerBaseSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerBaseSecurityLevel() returned " + formatNumber(server.baseDifficulty, 3) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerBaseSecurityLevel == null) {
workerScript.scriptRef.log("getServerBaseSecurityLevel() returned " + formatNumber(server.baseDifficulty, 3) + " for " + server.hostname);
} }
return server.baseDifficulty; return server.baseDifficulty;
}, },
@ -1357,13 +1408,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerMinSecurityLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { return 1; }
workerScript.scriptRef.log("getServerMinSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerMinSecurityLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerMinSecurityLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerMinSecurityLevel() returned " + formatNumber(server.minDifficulty, 3) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMinSecurityLevel == null) {
workerScript.scriptRef.log("getServerMinSecurityLevel() returned " + formatNumber(server.minDifficulty, 3) + " for " + server.hostname);
} }
return server.minDifficulty; return server.minDifficulty;
}, },
@ -1372,13 +1420,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerRequiredHackingLevel");
if (server == null) { if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { return 1; }
workerScript.scriptRef.log("getServerRequiredHackingLevel() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerRequiredHackingLevel")) {
throw makeRuntimeRejectMsg(workerScript, "getServerRequiredHackingLevel() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerRequiredHackingLevel returned " + formatNumber(server.requiredHackingSkill, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerRequiredHackingLevel == null) {
workerScript.scriptRef.log("getServerRequiredHackingLevel returned " + formatNumber(server.requiredHackingSkill, 0) + " for " + server.hostname);
} }
return server.requiredHackingSkill; return server.requiredHackingSkill;
}, },
@ -1387,13 +1432,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerMaxMoney");
if (server == null) { if (failOnHacknetServer(server, "getServerMaxMoney")) { return 0; }
workerScript.scriptRef.log("getServerMaxMoney() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerMaxMoney")) {
throw makeRuntimeRejectMsg(workerScript, "getServerMaxMoney() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerMaxMoney() returned " + formatNumber(server.moneyMax, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMaxMoney == null) {
workerScript.scriptRef.log("getServerMaxMoney() returned " + formatNumber(server.moneyMax, 0) + " for " + server.hostname);
} }
return server.moneyMax; return server.moneyMax;
}, },
@ -1402,13 +1444,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerGrowth");
if (server == null) { if (failOnHacknetServer(server, "getServerGrowth")) { return 1; }
workerScript.scriptRef.log("getServerGrowth() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerGrowth")) {
throw makeRuntimeRejectMsg(workerScript, "getServerGrowth() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerGrowth() returned " + formatNumber(server.serverGrowth, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerGrowth == null) {
workerScript.scriptRef.log("getServerGrowth() returned " + formatNumber(server.serverGrowth, 0) + " for " + server.hostname);
} }
return server.serverGrowth; return server.serverGrowth;
}, },
@ -1417,13 +1456,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerNumPortsRequired");
if (server == null) { if (failOnHacknetServer(server, "getServerNumPortsRequired")) { return 5; }
workerScript.scriptRef.log("getServerNumPortsRequired() failed. Invalid IP or hostname passed in: " + ip); if (workerScript.shouldLog("getServerNumPortsRequired")) {
throw makeRuntimeRejectMsg(workerScript, "getServerNumPortsRequired() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerNumPortsRequired() returned " + formatNumber(server.numOpenPortsRequired, 0) + " for " + server.hostname);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerNumPortsRequired == null) {
workerScript.scriptRef.log("getServerNumPortsRequired() returned " + formatNumber(server.numOpenPortsRequired, 0) + " for " + server.hostname);
} }
return server.numOpenPortsRequired; return server.numOpenPortsRequired;
}, },
@ -1432,13 +1468,9 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerRam", CONSTANTS.ScriptGetServerRamCost); return updateStaticRam("getServerRam", CONSTANTS.ScriptGetServerRamCost);
} }
updateDynamicRam("getServerRam", CONSTANTS.ScriptGetServerRamCost); updateDynamicRam("getServerRam", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip); const server = safeGetServer(ip, "getServerRam");
if (server == null) { if (workerScript.shouldLog("getServerRam")) {
workerScript.scriptRef.log("getServerRam() failed. Invalid IP or hostname passed in: " + ip); workerScript.log("getServerRam() returned [" + formatNumber(server.maxRam, 2) + "GB, " + formatNumber(server.ramUsed, 2) + "GB]");
throw makeRuntimeRejectMsg(workerScript, "getServerRam() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerRam == null) {
workerScript.scriptRef.log("getServerRam() returned [" + formatNumber(server.maxRam, 2) + "GB, " + formatNumber(server.ramUsed, 2) + "GB]");
} }
return [server.maxRam, server.ramUsed]; return [server.maxRam, server.ramUsed];
}, },
@ -4730,7 +4762,7 @@ function NetscriptFunctions(workerScript) {
answer = String(answer); answer = String(answer);
} }
const serv = safeGetServer(ip, "codingcontract.attempt()"); const serv = safeGetServer(ip, "codingcontract.attempt");
if (contract.isSolution(answer)) { if (contract.isSolution(answer)) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty()); const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`); workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`);
@ -5080,6 +5112,70 @@ function NetscriptFunctions(workerScript) {
workRepGain: sl.getRepGain(), workRepGain: sl.getRepGain(),
} }
}, },
getSleeveAugmentations : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("getSleeveAugmentations", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getSleeveAugmentations() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getSleeveAugmentations", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.getSleeveAugmentations(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return [];
}
const augs = [];
for (let i = 0; i < Player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(Player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
getSleevePurchasableAugs : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("getSleevePurchasableAugs", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getSleevePurchasableAugs() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getSleevePurchasableAugs", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.getSleevePurchasableAugs(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return [];
}
const purchasableAugs = findSleevePurchasableAugs(Player.sleeves[sleeveNumber], Player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.startingCost,
});
}
return augs;
},
purchaseSleeveAug : function(sleeveNumber=0, augName="") {
if (workerScript.checkingRam) {
return updateStaticRam("purchaseSleeveAug", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "purchaseSleeveAug() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("purchaseSleeveAug", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.purchaseSleeveAug(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
const aug = Augmentations[augName];
if (!aug) {
workerScript.log(`ERROR: sleeve.purchaseSleeveAug(${sleeveNumber}) failed because ${augName} is not a valid aug.`);
}
return Player.sleeves[sleeveNumber].tryBuyAugmentation(Player, aug);
}
} // End sleeve } // End sleeve
} //End return } //End return
} //End NetscriptFunction() } //End NetscriptFunction()

@ -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 | string)[]; // HacknetNode object or IP of Hacknet Server
hasWseAccount: boolean; hasWseAccount: boolean;
jobs: IMap<string>; jobs: IMap<string>;
karma: number; karma: number;

@ -14,6 +14,8 @@ import { Person,
createTaskTracker } from "../Person"; createTaskTracker } from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
@ -112,10 +114,9 @@ export class Sleeve extends Person {
logs: string[] = []; logs: string[] = [];
/** /**
* Clone retains memory% of exp upon prestige. If exp would be lower than previously * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
* kept exp, nothing happens
*/ */
memory: number = 0; memory: number = 1;
/** /**
* Sleeve shock. Number between 0 and 100 * Sleeve shock. Number between 0 and 100
@ -337,6 +338,31 @@ export class Sleeve extends Person {
p.gainMoney(gain); p.gainMoney(gain);
} }
/**
* Returns the cost of upgrading this sleeve's memory by a certain amount
*/
getMemoryUpgradeCost(n: number): number {
const amt = Math.round(n);
if (amt < 0) {
return 0;
}
if (this.memory + amt > 100) {
return this.getMemoryUpgradeCost(100 - this.memory);
}
const mult = 1.02;
const baseCost = 1e12;
let currCost = 0;
let currMemory = this.memory-1;
for (let i = 0; i < n; ++i) {
currCost += (Math.pow(mult, currMemory));
++currMemory;
}
return currCost * baseCost;
}
/** /**
* Gets reputation gain for the current task * Gets reputation gain for the current task
* Only applicable when working for company or faction * Only applicable when working for company or faction
@ -406,6 +432,26 @@ export class Sleeve extends Person {
} }
} }
/**
* Called on every sleeve for a Source File prestige
*/
prestige(p: IPlayer) {
this.hacking_exp = 0;
this.strength_exp = 0;
this.defense_exp = 0;
this.dexterity_exp = 0;
this.agility_exp = 0;
this.charisma_exp = 0;
this.resetTaskStatus();
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.logs = [];
this.shock = 1;
this.storedCycles = 0;
this.sync = Math.max(this.memory, 1);
this.shockRecovery(p);
}
/** /**
* Process loop * Process loop
* Returns an object containing the amount of experience that should be * Returns an object containing the amount of experience that should be
@ -806,6 +852,25 @@ export class Sleeve extends Person {
return true; return true;
} }
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.startingCost)) {
return false;
}
p.loseMoney(aug.startingCost);
this.installAugmentation(aug);
return true;
}
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
return;
}
this.memory = Math.min(100, Math.round(this.memory + n));
}
/** /**
* Serialize the current object to a JSON save state. * Serialize the current object to a JSON save state.
*/ */
@ -814,4 +879,31 @@ export class Sleeve extends Person {
} }
} }
export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] {
// You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it.
const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name});
const availableAugs: Augmentation[] = [];
for (const facName of p.factions) {
if (facName === "Bladeburners") { continue; }
const fac: Faction | null = Factions[facName];
if (fac == null) { continue; }
for (const augName of fac.augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
if (ownedAugNames.includes(augName)) { continue; }
const aug: Augmentation | null = Augmentations[augName];
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
availableAugs.push(aug);
}
}
}
return availableAugs;
}
Reviver.constructors.Sleeve = Sleeve; Reviver.constructors.Sleeve = Sleeve;

@ -2,7 +2,7 @@
* Module for handling the UI for purchasing Sleeve Augmentations * Module for handling the UI for purchasing Sleeve Augmentations
* This UI is a popup, not a full page * This UI is a popup, not a full page
*/ */
import { Sleeve } from "./Sleeve"; import { Sleeve, findSleevePurchasableAugs } from "./Sleeve";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
@ -29,23 +29,7 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
// You can only purchase Augmentations that are actually available from // You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation // your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it. // and you must also have enough rep in that faction in order to purchase it.
const availableAugs: Augmentation[] = []; const availableAugs = findSleevePurchasableAugs(sleeve, p);
for (const facName of p.factions) {
if (facName === "Bladeburners") { continue; }
const fac: Faction | null = Factions[facName];
if (fac == null) { continue; }
for (const augName of fac.augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
if (ownedAugNames.includes(augName)) { continue; }
const aug: Augmentation | null = Augmentations[augName];
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
availableAugs.push(aug);
}
}
}
// Create popup // Create popup
const popupId = "purchase-sleeve-augs-popup"; const popupId = "purchase-sleeve-augs-popup";
@ -110,10 +94,8 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
].join(" "), ].join(" "),
padding: "2px", padding: "2px",
clickListener: () => { clickListener: () => {
if (p.canAfford(aug.startingCost)) { if (sleeve.tryBuyAugmentation(p, aug)) {
p.loseMoney(aug.startingCost); dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false);
sleeve.installAugmentation(aug);
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
removeElementById(popupId); removeElementById(popupId);
createSleevePurchaseAugsPopup(sleeve, p); createSleevePurchaseAugsPopup(sleeve, p);
} else { } else {

@ -1,48 +1,18 @@
/** /**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant * Implements the purchasing of extra Duplicate Sleeves from The Covenant,
* as well as the purchasing of upgrades (memory)
*/ */
import { Sleeve } from "./Sleeve";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat"; import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
import { createPopup,
import { dialogBoxCreate } from "../../../utils/DialogBox"; removePopup } from "../../ui/React/createPopup";
import { yesNoBoxCreate,
yesNoBoxClose,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
export const MaxSleevesFromCovenant: number = 5; export const MaxSleevesFromCovenant: number = 5;
export const BaseCostPerSleeve: number = 10e12;
export const PopupId: string = "covenant-sleeve-purchases-popup";
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) { export function createSleevePurchasesFromCovenantPopup(p: IPlayer) {
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; } const removePopupFn = removePopup.bind(null, PopupId);
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
// First sleeve purchased costs the base amount. Then, the price of
// each successive one increases by the same amount
const baseCostPerExtraSleeve: number = 10e12;
const cost: number = (p.sleevesFromCovenant + 1) * baseCostPerExtraSleeve;
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
yesBtn!.addEventListener("click", () => {
if (p.canAfford(cost)) {
p.loseMoney(cost);
p.sleevesFromCovenant += 1;
p.sleeves.push(new Sleeve(p));
yesNoBoxClose();
} else {
dialogBoxCreate("You cannot afford to purchase a Duplicate Sleeve", false);
}
});
noBtn!.addEventListener("click", () => {
yesNoBoxClose();
});
const txt = `Would you like to purchase an additional Duplicate Sleeve from The Covenant for ` +
`${numeralWrapper.formatMoney(cost)}?<br><br>` +
`These Duplicate Sleeves are permanent. You can purchase a total of 5 Duplicate ` +
`Sleeves from The Covenant`;
yesNoBoxCreate(txt);
} }

@ -383,7 +383,8 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
`HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`, `HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`,
`City: ${sleeve.city}`, `City: ${sleeve.city}`,
`Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`, `Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`,
`Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`].join("<br>"); `Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`,
`Memory: ${numeralWrapper.format(sleeve.memory, "0")}`].join("<br>");
let repGainText: string = ""; let repGainText: string = "";
if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) { if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) {

@ -45,5 +45,13 @@ export const SleeveFaq: string =
"are not available for sleeves.<br><br>", "are not available for sleeves.<br><br>",
"<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><br>", "<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><br>",
"Sleeves are reset when switching BitNodes, but not when installing Augmentations." "Sleeves are reset when switching BitNodes, but not when installing Augmentations.<br><br>",
"<strong><u>What is Memory?</u></strong><br>",
"Sleeve memory dictates what a sleeve's synchronization will be",
"when its reset by switching BitNodes. For example, if a sleeve has a memory of 25,",
"then when you switch BitNodes its synchronization will initially be set to 25, rather than 1.<br><br>",
"Memory can only be increased by purchasing upgrades from The Covenant. It is a",
"persistent stat, meaning it never gets resets back to 1. The maximum possible",
"value for a sleeve's memory is 100."
].join(" "); ].join(" ");

@ -0,0 +1,112 @@
/**
* Root React component for the popup that lets player purchase Duplicate
* Sleeves and Sleeve-related upgrades from The Covenant
*/
import * as React from "react";
import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades";
import { Sleeve } from "../Sleeve";
import { BaseCostPerSleeve,
MaxSleevesFromCovenant,
PopupId } from "../SleeveCovenantPurchases";
import { IPlayer } from "../../IPlayer";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { PopupCloseButton } from "../../../ui/React/PopupCloseButton";
import { StdButton } from "../../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../../utils/DialogBox";
interface IProps {
closeFn: () => void;
p: IPlayer;
rerender: () => void;
}
interface IState {
update: number;
}
export class CovenantPurchasesRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
update: 0,
}
this.rerender = this.rerender.bind(this);
}
/**
* Get the cost to purchase a new Duplicate Sleeve
*/
purchaseCost(): number {
return (this.props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve;
}
/**
* Force a rerender by just changing an arbitrary state value
*/
rerender() {
this.setState((state: IState) => ({
update: state.update + 1,
}));
}
render() {
// Purchasing a new Duplicate Sleeve
let purchaseDisabled = false;
if (!this.props.p.canAfford(this.purchaseCost())) {
purchaseDisabled = true;
}
if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) {
purchaseDisabled = true;
}
const purchaseOnClick = () => {
if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
if (this.props.p.canAfford(this.purchaseCost())) {
this.props.p.loseMoney(this.purchaseCost());
this.props.p.sleevesFromCovenant += 1;
this.props.p.sleeves.push(new Sleeve(this.props.p));
this.rerender();
} else {
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false);
}
}
// Purchasing Upgrades for Sleeves
const upgradePanels = [];
for (let i = 0; i < this.props.p.sleeves.length; ++i) {
const sleeve = this.props.p.sleeves[i];
upgradePanels.push(
<CovenantSleeveUpgrades {...this.props} sleeve={sleeve} index={i} rerender={this.rerender} key={i} />
)
}
return (
<div>
<PopupCloseButton popup={PopupId} text={"Close"} />
<p>
Would you like to purchase an additional Duplicate Sleeve from The Covenant
for {numeralWrapper.formatMoney(this.purchaseCost())}?
</p>
<br />
<p>
These Duplicate Sleeves are permanent (they persist through BitNodes). You can
purchase a total of {MaxSleevesFromCovenant} from The Covenant.
</p>
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
<br /><br />
<p>
Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades
are also permanent, meaning they persist across BitNodes.
</p>
{upgradePanels}
</div>
)
}
}

@ -0,0 +1,97 @@
/**
* React component for a panel that lets you purchase upgrades for a Duplicate
* Sleeve's Memory (through The Covenant)
*/
import * as React from "react";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { StdButton } from "../../../ui/React/StdButton";
interface IProps {
index: number;
p: IPlayer;
rerender: () => void;
sleeve: Sleeve;
}
interface IState {
amt: number;
}
export class CovenantSleeveMemoryUpgrade extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
amt: 1,
}
this.changePurchaseAmount = this.changePurchaseAmount.bind(this);
this.purchaseMemory = this.purchaseMemory.bind(this);
}
changePurchaseAmount(e: React.ChangeEvent<HTMLInputElement>): void {
const n: number = parseInt(e.target.value);
this.setState({
amt: n,
});
}
getPurchaseCost(): number {
if (isNaN(this.state.amt)) { return Infinity; }
const maxMemory = 100 - this.props.sleeve.memory;
if (this.state.amt > maxMemory) { return Infinity; }
return this.props.sleeve.getMemoryUpgradeCost(this.state.amt);
}
purchaseMemory(): void {
const cost = this.getPurchaseCost();
if (this.props.p.canAfford(cost)) {
this.props.sleeve.upgradeMemory(this.state.amt);
this.props.p.loseMoney(cost);
this.props.rerender();
}
}
render() {
const inputId = `sleeve-${this.props.index}-memory-upgrade-input`;
// Memory cannot go above 100
const maxMemory = 100 - this.props.sleeve.memory;
// Purchase button props
const cost = this.getPurchaseCost();
const purchaseBtnDisabled = !this.props.p.canAfford(cost);
let purchaseBtnText;
if (this.state.amt > maxMemory) {
purchaseBtnText = `Memory cannot exceed 100`;
} else if (isNaN(this.state.amt)) {
purchaseBtnText = "Invalid value";
} else {
purchaseBtnText = `Purchase ${this.state.amt} memory - ${numeralWrapper.formatMoney(cost)}`;
}
return (
<div>
<h2><u>Upgrade Memory</u></h2>
<p>
Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory
is 100 (current: {numeralWrapper.format(this.props.sleeve.memory, "0")})
</p>
<label htmlFor={inputId}>
Amount of memory to purchase (must be an integer):
</label>
<input id={inputId} onChange={this.changePurchaseAmount} type={"number"} value={this.state.amt} />
<br />
<StdButton disabled={purchaseBtnDisabled} onClick={this.purchaseMemory} text={purchaseBtnText} />
</div>
)
}
}

@ -0,0 +1,28 @@
/**
* React Component for a panel that lets you purchase upgrades for a single
* Duplicate Sleeve through The Covenant
*/
import * as React from "react";
import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
interface IProps {
index: number;
p: IPlayer;
rerender: () => void;
sleeve: Sleeve;
}
export class CovenantSleeveUpgrades extends React.Component<IProps, any> {
render() {
return (
<div className={"bladeburner-action"}>
<h1>Duplicate Sleeve {this.props.index}</h1>
<CovenantSleeveMemoryUpgrade {...this.props} />
</div>
)
}
}

@ -21,6 +21,8 @@ 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 { hasHacknetServers } from "./Hacknet/HacknetHelpers";
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";
@ -114,7 +116,10 @@ function PlayerObject() {
// 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
this.hacknetNodes = [];
// Hacknet Nodes/Servers
this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers
this.hashManager = new HashManager();
//Factions //Factions
this.factions = []; //Names of all factions player has joined this.factions = []; //Names of all factions player has joined
@ -326,6 +331,7 @@ PlayerObject.prototype.prestigeAugmentation = function() {
this.moneySourceA.reset(); this.moneySourceA.reset();
this.hacknetNodes.length = 0; this.hacknetNodes.length = 0;
this.hashManager.prestige(this);
//Re-calculate skills and reset HP //Re-calculate skills and reset HP
this.updateSkillLevels(); this.updateSkillLevels();
@ -378,8 +384,12 @@ PlayerObject.prototype.prestigeSourceFile = function() {
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant; this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant;
for (let i = 0; i < this.sleeves.length; ++i) { for (let i = 0; i < this.sleeves.length; ++i) {
if (this.sleeves[i] instanceof Sleeve) {
this.sleeves[i].prestige(this);
} else {
this.sleeves[i] = new Sleeve(this); this.sleeves[i] = new Sleeve(this);
} }
}
this.isWorking = false; this.isWorking = false;
this.currentWorkFactionName = ""; this.currentWorkFactionName = "";
@ -410,7 +420,9 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.lastUpdate = new Date().getTime(); this.lastUpdate = new Date().getTime();
// Hacknet Nodes
this.hacknetNodes.length = 0; this.hacknetNodes.length = 0;
this.hashManager.prestige(this);
// Gang // Gang
this.gang = null; this.gang = null;
@ -422,7 +434,7 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.has4SData = false; this.has4SData = false;
this.has4SDataTixApi = false; this.has4SDataTixApi = false;
//BitNode 3: Corporatocracy // Corporation
this.corporation = 0; this.corporation = 0;
// Statistics trackers // Statistics trackers
@ -1391,45 +1403,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");
@ -2328,11 +2341,20 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
var totalHacknetRam = 0; var totalHacknetRam = 0;
var totalHacknetCores = 0; var totalHacknetCores = 0;
var totalHacknetLevels = 0; var totalHacknetLevels = 0;
for (var i = 0; i < this.hacknetNodes.length; ++i) { for (let i = 0; i < this.hacknetNodes.length; ++i) {
if (hasHacknetServers()) {
const hserver = AllServers[this.hacknetNodes[i]];
if (hserver) {
totalHacknetLevels += hserver.level;
totalHacknetRam += hserver.maxRam;
totalHacknetCores += hserver.cores;
}
} else {
totalHacknetLevels += this.hacknetNodes[i].level; totalHacknetLevels += this.hacknetNodes[i].level;
totalHacknetRam += this.hacknetNodes[i].ram; totalHacknetRam += this.hacknetNodes[i].ram;
totalHacknetCores += this.hacknetNodes[i].cores; totalHacknetCores += this.hacknetNodes[i].cores;
} }
}
if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited && if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited &&
this.hacking_skill >= 80 && totalHacknetRam >= 8 && this.hacking_skill >= 80 && totalHacknetRam >= 8 &&
totalHacknetCores >= 4 && totalHacknetLevels >= 100) { totalHacknetCores >= 4 && totalHacknetLevels >= 100) {

@ -14,6 +14,7 @@ import { Faction } from "./Faction/Faction";
import { Factions, import { Factions,
initFactions } from "./Faction/Factions"; initFactions } from "./Faction/Factions";
import { joinFaction } from "./Faction/FactionHelpers"; import { joinFaction } from "./Faction/FactionHelpers";
import { createHacknetServer } from "./Hacknet/HacknetHelpers";
import {deleteGangDisplayContent} from "./Gang"; import {deleteGangDisplayContent} from "./Gang";
import {Locations} from "./Location"; import {Locations} from "./Location";
import { Message } from "./Message/Message"; import { Message } from "./Message/Message";
@ -30,7 +31,8 @@ import { AllServers,
prestigeAllServers } from "./Server/AllServers"; prestigeAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server" import { Server } from "./Server/Server"
import { prestigeHomeComputer } from "./Server/ServerHelpers"; import { prestigeHomeComputer } from "./Server/ServerHelpers";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SourceFileFlags,
updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SpecialServerIps, import { SpecialServerIps,
SpecialServerIpsMap, SpecialServerIpsMap,
prestigeSpecialServerIps, prestigeSpecialServerIps,
@ -201,13 +203,9 @@ function prestigeSourceFile() {
//Re-create foreign servers //Re-create foreign servers
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());
var srcFile1Owned = false; if (SourceFileFlags[9] >= 2) {
for (var i = 0; i < Player.sourceFiles.length; ++i) { homeComp.setMaxRam(128);
if (Player.sourceFiles[i].n == 1) { } else if (SourceFileFlags[1] > 0) {
srcFile1Owned = true;
}
}
if (srcFile1Owned) {
homeComp.setMaxRam(32); homeComp.setMaxRam(32);
} else { } else {
homeComp.setMaxRam(8); homeComp.setMaxRam(8);
@ -341,6 +339,16 @@ function prestigeSourceFile() {
Player.corporation = null; resetIndustryResearchTrees(); Player.corporation = null; resetIndustryResearchTrees();
Player.bladeburner = null; Player.bladeburner = null;
// Source-File 9 (level 3) effect
if (SourceFileFlags[9] >= 3) {
const hserver = createHacknetServer();
hserver.level = 100;
hserver.cores = 10;
hserver.cache = 5;
hserver.updateHashRate(Player);
hserver.updateHashCapacity();
Player.hashManager.updateCapacity(Player);
}
// Refresh Main Menu (the 'World' menu, specifically) // Refresh Main Menu (the 'World' menu, specifically)
document.getElementById("world-menu-header").click(); document.getElementById("world-menu-header").click();

@ -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;
} }

@ -101,8 +101,8 @@ let NetscriptFunctions =
// Hacknet Node API // Hacknet Node API
"hacknet|numNodes|purchaseNode|getPurchaseNodeCost|getNodeStats|" + "hacknet|numNodes|purchaseNode|getPurchaseNodeCost|getNodeStats|" +
"upgradeLevel|upgradeRam|upgradeCore|getLevelUpgradeCost|" + "upgradeLevel|upgradeRam|upgradeCore|upgradeCache|getLevelUpgradeCost|" +
"getRamUpgradeCost|getCoreUpgradeCost|" + "getRamUpgradeCost|getCoreUpgradeCost|getCacheUpgradeCost|" +
// Gang API // Gang API
"gang|" + "gang|" +
@ -130,7 +130,8 @@ let NetscriptFunctions =
// Sleeve API // Sleeve API
"sleeve|getNumSleeves|setToShockRecovery|setToSynchronize|" + "sleeve|getNumSleeves|setToShockRecovery|setToSynchronize|" +
"setToCommitCrime|setToUniversityCourse|travel|setToCompanyWork|" + "setToCommitCrime|setToUniversityCourse|travel|setToCompanyWork|" +
"setToFactionWork|setToGymWorkout|getSleeveStats|getTask|getInformation"; "setToFactionWork|setToGymWorkout|getSleeveStats|getTask|getInformation|" +
"getSleeveAugmentations|getSleevePurchasableAugs|purchaseSleeveAug";
var NetscriptHighlightRules = function(options) { var NetscriptHighlightRules = function(options) {
var keywordMapper = this.createKeywordMapper({ var keywordMapper = this.createKeywordMapper({

@ -177,9 +177,11 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"upgradeLevel": atom, "upgradeLevel": atom,
"upgradeRam": atom, "upgradeRam": atom,
"upgradeCore": atom, "upgradeCore": atom,
"upgradeCache": atom,
"getLevelUpgradeCost": atom, "getLevelUpgradeCost": atom,
"getRamUpgradeCost": atom, "getRamUpgradeCost": atom,
"getCoreUpgradeCost": atom, "getCoreUpgradeCost": atom,
"getCacheUpgradeCost": atom,
// Netscript Gang API // Netscript Gang API
"gang": atom, "gang": atom,
@ -257,6 +259,9 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"getSleeveStats": atom, "getSleeveStats": atom,
"getTask": atom, "getTask": atom,
"getInformation": atom, "getInformation": atom,
"getSleeveAugmentations": atom,
"getSleevePurchasableAugs": atom,
"purchaseSleeveAug": atom,
}; };
}(); }();

@ -2,6 +2,8 @@ import { Server } from "./Server";
import { SpecialServerIps } from "./SpecialServerIps"; import { SpecialServerIps } from "./SpecialServerIps";
import { serverMetadata } from "./data/servers"; import { serverMetadata } from "./data/servers";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IMap } from "../types"; import { IMap } from "../types";
import { createRandomIp, import { createRandomIp,
ipExists } from "../../utils/IPAddress"; ipExists } from "../../utils/IPAddress";
@ -11,7 +13,7 @@ import { Reviver } from "../../utils/JSONReviver";
// Map of all Servers that exist in the game // Map of all Servers that exist in the game
// Key (string) = IP // Key (string) = IP
// Value = Server object // Value = Server object
export let AllServers: IMap<Server> = {}; export let AllServers: IMap<Server | HacknetServer> = {};
// Saftely add a Server to the AllServers map // Saftely add a Server to the AllServers map
export function AddToAllServers(server: Server): void { export function AddToAllServers(server: Server): void {

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);
} }

@ -3,6 +3,7 @@ import { Server } from "./Server";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Programs } from "../Programs/Programs"; import { Programs } from "../Programs/Programs";
@ -89,7 +90,7 @@ export function prestigeHomeComputer(homeComp: Server) {
//Returns server object with corresponding hostname //Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot // Relatively slow, would rather not use this a lot
export function GetServerByHostname(hostname: string): Server | null { export function GetServerByHostname(hostname: string): Server | HacknetServer | null {
for (var ip in AllServers) { for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) { if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) { if (AllServers[ip].hostname == hostname) {
@ -102,7 +103,7 @@ export function GetServerByHostname(hostname: string): Server | null {
} }
//Get server by IP or hostname. Returns null if invalid //Get server by IP or hostname. Returns null if invalid
export function getServer(s: string): Server | null { export function getServer(s: string): Server | HacknetServer | null {
if (!isValidIPAddress(s)) { if (!isValidIPAddress(s)) {
return GetServerByHostname(s); return GetServerByHostname(s);
} }

@ -62,15 +62,20 @@ function initSourceFiles() {
"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%");
SourceFiles["SourceFile9"] = new SourceFile(9); SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:<br><br>" +
"Level 1: Permanently unlocks the Hacknet Server in other BitNodes<br>" +
"Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode<br>" +
"Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode<br><br>" +
"(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " +
"when installing Augmentations)");
SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
"Source-File also grants you a Duplicate Sleeve"); "Source-File also grants you a Duplicate Sleeve");
SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
"at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
" increases the player's company salary and reputation gain multipliers by:<br><br>" + " increases the player's company salary and reputation gain multipliers by:<br><br>" +
"Level 1: 24%<br>" + "Level 1: 32%<br>" +
"Level 2: 36%<br>" + "Level 2: 48%<br>" +
"Level 3: 42%<br>"); "Level 3: 56%<br>");
SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " + SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " +
"In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
} }
@ -188,13 +193,16 @@ function applySourceFile(srcFile) {
var incMult = 1 + (mult / 100); var incMult = 1 + (mult / 100);
Player.hacking_grow_mult *= incMult; Player.hacking_grow_mult *= incMult;
break; break;
case 9: // Hacktocracy
// This has non-multiplier effects
break;
case 10: // Digital Carbon case 10: // Digital Carbon
// No effects, just grants sleeves // No effects, just grants sleeves
break; break;
case 11: // The Big Crash case 11: // The Big Crash
var mult = 0; var mult = 0;
for (var i = 0; i < srcFile.lvl; ++i) { for (var i = 0; i < srcFile.lvl; ++i) {
mult += (24 / (Math.pow(2, i))); mult += (32 / (Math.pow(2, i)));
} }
var incMult = 1 + (mult / 100); var incMult = 1 + (mult / 100);
Player.work_money_mult *= incMult; Player.work_money_mult *= incMult;

@ -462,9 +462,10 @@ function sellStock(stock, shares) {
shares = Math.round(shares); shares = Math.round(shares);
if (shares > stock.playerShares) {shares = stock.playerShares;} if (shares > stock.playerShares) {shares = stock.playerShares;}
if (shares === 0) {return false;} if (shares === 0) {return false;}
var gains = stock.price * shares - CONSTANTS.StockMarketCommission; const gains = stock.price * shares - CONSTANTS.StockMarketCommission;
const netProfit = ((stock.price - stock.playerAvgPx) * shares) - CONSTANTS.StockMarketCommission;
Player.gainMoney(gains); Player.gainMoney(gains);
Player.recordMoneySource(gains, "stock"); Player.recordMoneySource(netProfit, "stock");
stock.playerShares = Math.round(stock.playerShares - shares); stock.playerShares = Math.round(stock.playerShares - shares);
if (stock.playerShares == 0) { if (stock.playerShares == 0) {
stock.playerAvgPx = 0; stock.playerAvgPx = 0;

@ -2,6 +2,7 @@ import {substituteAliases, printAliases,
parseAliasDeclaration, parseAliasDeclaration,
removeAlias, GlobalAliases, removeAlias, GlobalAliases,
Aliases} from "./Alias"; Aliases} from "./Alias";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import {CodingContract, CodingContractResult, import {CodingContract, CodingContractResult,
CodingContractRewardType} from "./CodingContracts"; CodingContractRewardType} from "./CodingContracts";
import {CONSTANTS} from "./Constants"; import {CONSTANTS} from "./Constants";
@ -19,6 +20,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 +762,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")
@ -1244,23 +1247,26 @@ let Terminal = {
case "free": case "free":
Terminal.executeFreeCommand(commandArray); Terminal.executeFreeCommand(commandArray);
break; break;
case "hack": case "hack": {
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of hack command. Usage: hack"); postError("Incorrect usage of hack command. Usage: hack");
return; return;
} }
//Hack the current PC (usually for money) //Hack the current PC (usually for money)
//You can't hack your home pc or servers you purchased //You can't hack your home pc or servers you purchased
if (Player.getCurrentServer().purchasedByPlayer) { if (s.purchasedByPlayer) {
postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers"); postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers");
} else if (Player.getCurrentServer().hasAdminRights == false ) { } else if (s.hasAdminRights == false ) {
postError("You do not have admin rights for this machine! Cannot hack"); postError("You do not have admin rights for this machine! Cannot hack");
} else if (Player.getCurrentServer().requiredHackingSkill > Player.hacking_skill) { } else if (s.requiredHackingSkill > Player.hacking_skill) {
postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill"); postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill");
} else if (s instanceof HacknetServer) {
postError("Cannot hack this type of Server")
} else { } else {
Terminal.startHack(); Terminal.startHack();
} }
break; break;
}
case "help": case "help":
if (commandArray.length !== 1 && commandArray.length !== 2) { if (commandArray.length !== 1 && commandArray.length !== 2) {
postError("Incorrect usage of help command. Usage: help"); postError("Incorrect usage of help command. Usage: help");
@ -1638,9 +1644,15 @@ let Terminal = {
postError("Incorrect usage of free command. Usage: free"); postError("Incorrect usage of free command. Usage: free");
return; return;
} }
post("Total: " + numeralWrapper.format(Player.getCurrentServer().maxRam, '0.00') + " GB"); const ram = numeralWrapper.format(Player.getCurrentServer().maxRam, '0.00');
post("Used: " + numeralWrapper.format(Player.getCurrentServer().ramUsed, '0.00') + " GB"); const used = numeralWrapper.format(Player.getCurrentServer().ramUsed, '0.00');
post("Available: " + numeralWrapper.format(Player.getCurrentServer().maxRam - Player.getCurrentServer().ramUsed, '0.00') + " GB"); const avail = numeralWrapper.format(Player.getCurrentServer().maxRam - Player.getCurrentServer().ramUsed, '0.00');
const maxLength = Math.max(ram.length, Math.max(used.length, avail.length));
const usedPercent = numeralWrapper.format(Player.getCurrentServer().ramUsed/Player.getCurrentServer().maxRam*100, '0.00');
post(`Total: ${" ".repeat(maxLength-ram.length)}${ram} GB`);
post(`Used: ${" ".repeat(maxLength-used.length)}${used} GB (${usedPercent}%)`);
post(`Available: ${" ".repeat(maxLength-avail.length)}${avail} GB`);
}, },
executeKillCommand: function(commandArray) { executeKillCommand: function(commandArray) {
@ -1859,17 +1871,20 @@ let Terminal = {
visited[ip] = 0; visited[ip] = 0;
} }
var stack = []; const stack = [];
var depthQueue = [0]; const depthQueue = [0];
var currServ = Player.getCurrentServer(); const currServ = Player.getCurrentServer();
stack.push(currServ); stack.push(currServ);
while(stack.length != 0) { while(stack.length != 0) {
var s = stack.pop(); const s = stack.pop();
var d = depthQueue.pop(); const d = depthQueue.pop();
const isHacknet = s instanceof HacknetServer;
if (!all && s.purchasedByPlayer && s.hostname != "home") { if (!all && s.purchasedByPlayer && s.hostname != "home") {
continue; // Purchased server continue; // Purchased server
} else if (visited[s.ip] || d > depth) { } else if (visited[s.ip] || d > depth) {
continue; // Already visited or out-of-depth continue; // Already visited or out-of-depth
} else if (!all && isHacknet) {
continue; // Hacknet Server
} else { } else {
visited[s.ip] = 1; visited[s.ip] = 1;
} }
@ -1889,8 +1904,8 @@ let Terminal = {
//var dashes = Array(d * 2 + 1).join("-"); //var dashes = Array(d * 2 + 1).join("-");
var c = "NO"; var c = "NO";
if (s.hasAdminRights) {c = "YES";} if (s.hasAdminRights) {c = "YES";}
post(dashes + "Root Access: " + c + ", Required hacking skill: " + s.requiredHackingSkill); post(`${dashes}Root Access: ${c}${!isHacknet ? ", Required hacking skill: " + s.requiredHackingSkill : ""}`);
post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); if (!isHacknet) { post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); }
post(dashes + "RAM: " + s.maxRam); post(dashes + "RAM: " + s.maxRam);
post(" "); post(" ");
} }
@ -2133,7 +2148,8 @@ let Terminal = {
post("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10."); post("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.");
}; };
programHandlers[Programs.Flight.name] = () => { programHandlers[Programs.Flight.name] = () => {
const fulfilled = Player.augmentations.length >= 30 && const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement*30)
const fulfilled = Player.augmentations.length >= numAugReq &&
Player.money.gt(1e11) && Player.money.gt(1e11) &&
((Player.hacking_skill >= 2500)|| ((Player.hacking_skill >= 2500)||
(Player.strength >= 1500 && (Player.strength >= 1500 &&
@ -2141,17 +2157,17 @@ let Terminal = {
Player.dexterity >= 1500 && Player.dexterity >= 1500 &&
Player.agility >= 1500)); Player.agility >= 1500));
if(!fulfilled) { if(!fulfilled) {
post("Augmentations: " + Player.augmentations.length + " / 30"); post(`Augmentations: ${Player.augmentations.length} / ${numAugReq}`);
post("Money: " + numeralWrapper.format(Player.money.toNumber(), '($0.000a)') + " / " + numeralWrapper.format(1e11, '($0.000a)')); post(`Money: ${numeralWrapper.format(Player.money.toNumber(), '($0.000a)')} / ${numeralWrapper.format(1e11, '($0.000a)')}`);
post("One path below must be fulfilled..."); post("One path below must be fulfilled...");
post("----------HACKING PATH----------"); post("----------HACKING PATH----------");
post("Hacking skill: " + Player.hacking_skill + " / 2500"); post(`Hacking skill: ${Player.hacking_skill} / 2500`);
post("----------COMBAT PATH----------"); post("----------COMBAT PATH----------");
post("Strength: " + Player.strength + " / 1500"); post(`Strength: ${Player.strength} / 1500`);
post("Defense: " + Player.defense + " / 1500"); post(`Defense: ${Player.defense} / 1500`);
post("Dexterity: " + Player.dexterity + " / 1500"); post(`Dexterity: ${Player.dexterity} / 1500`);
post("Agility: " + Player.agility + " / 1500"); post(`Agility: ${Player.agility} / 1500`);
return; return;
} }
@ -2240,9 +2256,12 @@ 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);
addWorkerScript(runningScriptObj, server); addWorkerScript(runningScriptObj, server);
// This has to come after addWorkerScript() because that fn
// updates the RAM usage. This kinda sucks, address if possible
server.runScript(runningScriptObj, Player);
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 -->

@ -35,3 +35,12 @@ export interface ISelfLoading {
*/ */
load(saveState: string): void; load(saveState: string): void;
} }
/**
* Status object for functions that return a boolean indicating success/failure
* and an optional message
*/
export interface IReturnStatus {
res: boolean;
msg?: string;
}

@ -0,0 +1,23 @@
/**
* Text (p Element) with Tooltip
*/
import * as React from "react";
export interface IParagraphWithTooltipProps {
style?: object;
text: string;
tooltip: string;
}
export class ParagraphWithTooltip extends React.Component<IParagraphWithTooltipProps, any> {
render() {
return (
<p className={"tooltip"}>
{this.props.text}
<span className={"tooltiptext"}>
{this.props.tooltip}
</span>
</p>
)
}
}

24
src/ui/React/Popup.tsx Normal file

@ -0,0 +1,24 @@
/**
* React component for a popup content container
*
* Takes in a prop for rendering the content inside the popup
*/
import * as React from "react";
type ReactComponent = new(...args: any[]) => React.Component<any, any>
interface IProps {
content: ReactComponent;
id: string;
props: object;
}
export class Popup extends React.Component<IProps, any> {
render() {
return (
<div className={"popup-box-content"} id={`${this.props.id}-content`}>
{React.createElement(this.props.content, this.props.props)}
</div>
)
}
}

@ -0,0 +1,67 @@
/**
* Close button for popup dialog boxes
* It creates an event handler such that pressing Esc will close the binded popup
*
* Should only be used in other React components, otherwise it may not be properly
* unmounted
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import { KEY } from "../../../utils/helpers/keyCodes";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
export interface IPopupCloseButtonProps {
class?: string;
popup: HTMLElement | string;
style?: object;
text: string;
}
export class PopupCloseButton extends React.Component<IPopupCloseButtonProps, any> {
constructor(props: IPopupCloseButtonProps) {
super(props);
this.closePopup = this.closePopup.bind(this);
this.keyListener = this.keyListener.bind(this);
}
componentDidMount() {
document.addEventListener("keydown", this.keyListener);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.keyListener);
}
closePopup() {
let popup: HTMLElement | null;
if (typeof this.props.popup === "string") {
popup = document.getElementById(this.props.popup);
} else {
popup = this.props.popup;
}
// TODO Check if this is okay? This is essentially calling to unmount a parent component
if (popup instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(popup); // Removes everything inside the wrapper container
removeElement(popup); // Removes the wrapper container
}
}
keyListener(e: KeyboardEvent) {
if (e.keyCode === KEY.ESC) {
this.closePopup();
}
}
render() {
const className = this.props.class ? this.props.class : "std-button";
return (
<button className={className} onClick={this.closePopup} style={this.props.style}>
{this.props.text}
</button>
)
}
}

@ -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>
)
}
}

@ -0,0 +1,24 @@
/**
* Basic stateless button
* Uses the 'std-button' css class
*/
import * as React from "react";
export interface IStdButtonProps {
disabled?: boolean;
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
style?: object;
text: string;
}
export class StdButton extends React.Component<IStdButtonProps, any> {
render() {
const className = this.props.disabled ? "std-button-disabled" : "std-button";
return (
<button className={className} onClick={this.props.onClick} style={this.props.style}>
{this.props.text}
</button>
)
}
}

@ -0,0 +1,60 @@
/**
* 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 for the content (NOT the popup containers themselves)
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Popup } from "./Popup";
import { createElement } from "../../../utils/uiHelpers/createElement";
import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
type ReactComponent = new(...args: any[]) => React.Component<any, any>;
let gameContainer: HTMLElement;
function getGameContainer() {
let container = document.getElementById("entire-game-container");
if (container == null) {
throw new Error(`Failed to find game container DOM element`)
}
gameContainer = container;
document.removeEventListener("DOMContentLoaded", getGameContainer);
}
document.addEventListener("DOMContentLoaded", getGameContainer);
export function createPopup(id: string, rootComponent: ReactComponent, props: object): HTMLElement | null {
let container = document.getElementById(id);
if (container == null) {
container = createElement("div", {
class: "popup-box-container",
display: "flex",
id: id,
});
gameContainer.appendChild(container);
}
ReactDOM.render(<Popup content={rootComponent} id={id} props={props} />, container);
return container;
}
/**
* Closes a popup created with the createPopup() function above
*/
export function removePopup(id: string): void {
let content = document.getElementById(`${id}`);
if (content == null) { return; }
ReactDOM.unmountComponentAtNode(content);
removeElementById(id);
}

@ -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;
}

@ -30,22 +30,21 @@ function infiltrationSetText(txt) {
//ram argument is in GB //ram argument is in GB
function infiltrationBoxCreate(inst) { function infiltrationBoxCreate(inst) {
//Gain exp //Gain exp
var expMultiplier = 2 * inst.clearanceLevel / inst.maxClearanceLevel; Player.gainHackingExp(inst.calcGainedHackingExp());
Player.gainHackingExp(inst.hackingExpGained * expMultiplier); Player.gainStrengthExp(inst.calcGainedStrengthExp());
Player.gainStrengthExp(inst.strExpGained * expMultiplier); Player.gainDefenseExp(inst.calcGainedDefenseExp());
Player.gainDefenseExp(inst.defExpGained * expMultiplier); Player.gainDexterityExp(inst.calcGainedDexterityExp());
Player.gainDexterityExp(inst.dexExpGained * expMultiplier); Player.gainAgilityExp(inst.calcGainedAgilityExp());
Player.gainAgilityExp(inst.agiExpGained * expMultiplier); Player.gainCharismaExp(inst.calcGainedCharismaExp());
Player.gainCharismaExp(inst.chaExpGained * expMultiplier); Player.gainIntelligenceExp(inst.calcGainedIntelligenceExp());
Player.gainIntelligenceExp(inst.intExpGained * expMultiplier);
const expGainText = ["You gained:", const expGainText = ["You gained:",
`${formatNumber(inst.hackingExpGained * expMultiplier, 3)} hacking exp`, `${formatNumber(inst.calcGainedHackingExp(), 3)} hacking exp`,
`${formatNumber(inst.strExpGained * expMultiplier, 3)} str exp`, `${formatNumber(inst.calcGainedStrengthExp(), 3)} str exp`,
`${formatNumber(inst.defExpGained * expMultiplier, 3)} def exp`, `${formatNumber(inst.calcGainedDefenseExp(), 3)} def exp`,
`${formatNumber(inst.dexExpGained * expMultiplier, 3)} dex exp`, `${formatNumber(inst.calcGainedDexterityExp(), 3)} dex exp`,
`${formatNumber(inst.agiExpGained * expMultiplier, 3)} agi exp`, `${formatNumber(inst.calcGainedAgilityExp(), 3)} agi exp`,
`${formatNumber(inst.chaExpGained * expMultiplier, 3)} cha exp`].join("\n"); `${formatNumber(inst.calcGainedCharismaExp(), 3)} cha exp`].join("\n");
var totalValue = 0; var totalValue = 0;
for (var i = 0; i < inst.secretsStolen.length; ++i) { for (var i = 0; i < inst.secretsStolen.length; ++i) {

@ -1,4 +1,6 @@
/* Creates a Close/Cancel button that is used for removing popups */ /**
* Creates a Close/Cancel button that is used for removing popups
*/
import { createElement } from "./createElement"; import { createElement } from "./createElement";
import { removeElement } from "./removeElement"; import { removeElement } from "./removeElement";