mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-27 01:53:48 +01:00
commit
7f88ade30e
@ -38,6 +38,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.a-link-button-inactive,
|
.a-link-button-inactive,
|
||||||
|
.std-button-disabled,
|
||||||
.std-button:disabled {
|
.std-button:disabled {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
@import "theme";
|
@import "theme";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Styling for the Character Overview Panel (top-right)
|
* Styling for the Character Overview Panel (top-right panel)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#character-overview-wrapper {
|
#character-overview-wrapper {
|
||||||
|
75
css/hacknetnodes.scss
Normal file
75
css/hacknetnodes.scss
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
@import "mixins";
|
||||||
|
@import "theme";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styling for the Hacknet Nodes UI Page
|
||||||
|
*/
|
||||||
|
|
||||||
|
#hacknet-nodes-container {
|
||||||
|
position: fixed;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hacknet-general-info {
|
||||||
|
margin: 10px;
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-container li {
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.hacknet-node {
|
||||||
|
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
|
||||||
|
@include boxShadow($boxShadowArgs);
|
||||||
|
|
||||||
|
margin: 6px;
|
||||||
|
padding: 7px;
|
||||||
|
width: 35vw;
|
||||||
|
border: 2px solid var(--my-highlight-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-list {
|
||||||
|
list-style: none;
|
||||||
|
width: 82vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-money {
|
||||||
|
margin: 10px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-money-multipliers-div {
|
||||||
|
display: inline-block;
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-multipliers {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-purchase-button {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hacknet-node-container {
|
||||||
|
display: inline-table;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: table-row;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgradable-info {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
|
||||||
|
padding: 0 4px;
|
||||||
|
width: $defaultFontSize * 4;
|
||||||
|
}
|
||||||
|
}
|
@ -138,81 +138,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hacknet Nodes */
|
|
||||||
#hacknet-nodes-container {
|
|
||||||
position: fixed;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-text,
|
|
||||||
#hacknet-nodes-container li {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-container li {
|
|
||||||
float: left;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.hacknet-node {
|
|
||||||
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
|
|
||||||
@include boxShadow($boxShadowArgs);
|
|
||||||
|
|
||||||
margin: 6px;
|
|
||||||
padding: 7px;
|
|
||||||
width: 35vw;
|
|
||||||
border: 2px solid var(--my-highlight-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-list {
|
|
||||||
list-style: none;
|
|
||||||
width: 82vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-money {
|
|
||||||
margin: 10px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-money-multipliers-div {
|
|
||||||
display: inline-block;
|
|
||||||
width: 70vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-multipliers {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-purchase-button {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hacknet-node-container {
|
|
||||||
display: inline-table;
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: table-row;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upgradable-info {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
|
|
||||||
padding: 0 4px;
|
|
||||||
width: $defaultFontSize * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-page-text {
|
|
||||||
width: 70vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* World */
|
/* World */
|
||||||
#world-container {
|
#world-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -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;
|
||||||
|
2
dist/engine.bundle.js
vendored
2
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
125
dist/engine.css
vendored
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
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.
|
||||||
|
17
doc/source/netscript/hacknetnodeapi/getCacheUpgradeCost.rst
Normal file
17
doc/source/netscript/hacknetnodeapi/getCacheUpgradeCost.rst
Normal file
@ -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.
|
||||||
|
19
doc/source/netscript/hacknetnodeapi/upgradeCache.rst
Normal file
19
doc/source/netscript/hacknetnodeapi/upgradeCache.rst
Normal file
@ -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.
|
17
doc/source/netscript/sleeveapi/getSleevePurchasableAugs.rst
Normal file
17
doc/source/netscript/sleeveapi/getSleevePurchasableAugs.rst
Normal file
@ -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,
|
||||||
|
9
doc/source/netscript/sleeveapi/purchaseSleeveAug.rst
Normal file
9
doc/source/netscript/sleeveapi/purchaseSleeveAug.rst
Normal file
@ -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.
|
30
index.html
30
index.html
@ -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
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 " +
|
||||||
@ -245,9 +263,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (p.bitNodeN) {
|
switch (p.bitNodeN) {
|
||||||
case 1: //Source Genesis (every multiplier is 1)
|
case 1: // Source Genesis (every multiplier is 1)
|
||||||
break;
|
break;
|
||||||
case 2: //Rise of the Underworld
|
case 2: // Rise of the Underworld
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||||
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
||||||
@ -257,7 +275,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
||||||
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
||||||
break;
|
break;
|
||||||
case 3: //Corporatocracy
|
case 3: // Corporatocracy
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||||
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
||||||
BitNodeMultipliers.AugmentationRepCost = 3;
|
BitNodeMultipliers.AugmentationRepCost = 3;
|
||||||
@ -272,7 +290,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HomeComputerRamCost = 1.5;
|
BitNodeMultipliers.HomeComputerRamCost = 1.5;
|
||||||
BitNodeMultipliers.PurchasedServerCost = 2;
|
BitNodeMultipliers.PurchasedServerCost = 2;
|
||||||
break;
|
break;
|
||||||
case 4: //The Singularity
|
case 4: // The Singularity
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
||||||
@ -286,7 +304,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.CrimeExpGain = 0.5;
|
BitNodeMultipliers.CrimeExpGain = 0.5;
|
||||||
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
||||||
break;
|
break;
|
||||||
case 5: //Artificial intelligence
|
case 5: // Artificial intelligence
|
||||||
BitNodeMultipliers.ServerMaxMoney = 2;
|
BitNodeMultipliers.ServerMaxMoney = 2;
|
||||||
BitNodeMultipliers.ServerStartingSecurity = 2;
|
BitNodeMultipliers.ServerStartingSecurity = 2;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||||
@ -299,7 +317,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HackExpGain = 0.5;
|
BitNodeMultipliers.HackExpGain = 0.5;
|
||||||
BitNodeMultipliers.CorporationValuation = 0.5;
|
BitNodeMultipliers.CorporationValuation = 0.5;
|
||||||
break;
|
break;
|
||||||
case 6: //Bladeburner
|
case 6: // Bladeburner
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||||
@ -314,7 +332,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HackExpGain = 0.25;
|
BitNodeMultipliers.HackExpGain = 0.25;
|
||||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||||
break;
|
break;
|
||||||
case 7: //Bladeburner 2079
|
case 7: // Bladeburner 2079
|
||||||
BitNodeMultipliers.BladeburnerRank = 0.6;
|
BitNodeMultipliers.BladeburnerRank = 0.6;
|
||||||
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
||||||
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
||||||
@ -334,7 +352,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
||||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||||
break;
|
break;
|
||||||
case 8: //Ghost of Wall Street
|
case 8: // Ghost of Wall Street
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0;
|
BitNodeMultipliers.ScriptHackMoney = 0;
|
||||||
BitNodeMultipliers.ManualHackMoney = 0;
|
BitNodeMultipliers.ManualHackMoney = 0;
|
||||||
BitNodeMultipliers.CompanyWorkMoney = 0;
|
BitNodeMultipliers.CompanyWorkMoney = 0;
|
||||||
@ -345,6 +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
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -563,13 +563,15 @@ 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
1
src/Hacking/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implementation of underlying Hacking mechanics
|
53
src/Hacking/netscriptCanHack.ts
Normal file
53
src/Hacking/netscriptCanHack.ts
Normal file
@ -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");
|
||||||
|
}
|
437
src/Hacknet/HacknetHelpers.jsx
Normal file
437
src/Hacknet/HacknetHelpers.jsx
Normal file
@ -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
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;
|
350
src/Hacknet/HacknetServer.ts
Normal file
350
src/Hacknet/HacknetServer.ts
Normal file
@ -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
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;
|
48
src/Hacknet/HashUpgrade.ts
Normal file
48
src/Hacknet/HashUpgrade.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Object representing an upgrade that can be purchased with hashes
|
||||||
|
*/
|
||||||
|
export interface IConstructorParams {
|
||||||
|
costPerLevel: number;
|
||||||
|
desc: string;
|
||||||
|
hasTargetServer?: boolean;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HashUpgrade {
|
||||||
|
/**
|
||||||
|
* Base cost for this upgrade. Every time the upgrade is purchased,
|
||||||
|
* its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.)
|
||||||
|
*/
|
||||||
|
costPerLevel: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of what the upgrade does
|
||||||
|
*/
|
||||||
|
desc: string = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean indicating that this upgrade's effect affects a single server,
|
||||||
|
* the "target" server
|
||||||
|
*/
|
||||||
|
hasTargetServer: boolean = false;
|
||||||
|
|
||||||
|
// Name of upgrade
|
||||||
|
name: string = "";
|
||||||
|
|
||||||
|
// Generic value used to indicate the potency/amount of this upgrade's effect
|
||||||
|
// The meaning varies between different upgrades
|
||||||
|
value: number = 0;
|
||||||
|
|
||||||
|
constructor(p: IConstructorParams) {
|
||||||
|
this.costPerLevel = p.costPerLevel;
|
||||||
|
this.desc = p.desc;
|
||||||
|
this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false;
|
||||||
|
this.name = p.name;
|
||||||
|
this.value = p.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCost(levels: number): number {
|
||||||
|
return Math.round((levels + 1) * this.costPerLevel);
|
||||||
|
}
|
||||||
|
}
|
18
src/Hacknet/HashUpgrades.ts
Normal file
18
src/Hacknet/HashUpgrades.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Map of all Hash Upgrades
|
||||||
|
* Key = Hash name, Value = HashUpgrade object
|
||||||
|
*/
|
||||||
|
import { HashUpgrade,
|
||||||
|
IConstructorParams } from "./HashUpgrade";
|
||||||
|
import { HashUpgradesMetadata } from "./data/HashUpgradesMetadata";
|
||||||
|
import { IMap } from "../types";
|
||||||
|
|
||||||
|
export const HashUpgrades: IMap<HashUpgrade> = {};
|
||||||
|
|
||||||
|
function createHashUpgrade(p: IConstructorParams) {
|
||||||
|
HashUpgrades[p.name] = new HashUpgrade(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const metadata of HashUpgradesMetadata) {
|
||||||
|
createHashUpgrade(metadata);
|
||||||
|
}
|
16
src/Hacknet/IHacknetNode.ts
Normal file
16
src/Hacknet/IHacknetNode.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
|
||||||
|
// and the upgraded Hacknet Server in BitNode-9
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
|
||||||
|
export interface IHacknetNode {
|
||||||
|
cores: number;
|
||||||
|
level: number;
|
||||||
|
onlineTimeSeconds: number;
|
||||||
|
|
||||||
|
calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||||
|
calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||||
|
calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||||
|
purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||||
|
purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||||
|
purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||||
|
}
|
70
src/Hacknet/data/HashUpgradesMetadata.ts
Normal file
70
src/Hacknet/data/HashUpgradesMetadata.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
]
|
56
src/Hacknet/ui/GeneralInfo.jsx
Normal file
56
src/Hacknet/ui/GeneralInfo.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Hacknet Node UI
|
||||||
|
*
|
||||||
|
* Displays general information about Hacknet Nodes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { hasHacknetServers } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
export class GeneralInfo extends React.Component {
|
||||||
|
getSecondParagraph() {
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
return `Here, you can purchase a Hacknet Server, an upgraded version of the Hacknet Node. ` +
|
||||||
|
`Hacknet Servers will perform computations and operations on the network, earning ` +
|
||||||
|
`you hashes. Hashes can be spent on a variety of different upgrades.`;
|
||||||
|
} else {
|
||||||
|
return `Here, you can purchase a Hacknet Node, a specialized machine that can connect ` +
|
||||||
|
`and contribute its resources to the Hacknet networ. This allows you to take ` +
|
||||||
|
`a small percentage of profits from hacks performed on the network. Essentially, ` +
|
||||||
|
`you are renting out your Node's computing power.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getThirdParagraph() {
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
return `Hacknet Servers can also be used as servers to run scripts. However, running scripts ` +
|
||||||
|
`on a server will reduce its hash rate (hashes generated per second). A Hacknet Server's hash ` +
|
||||||
|
`rate will be reduced by the percentage of RAM that is being used by that Server to run ` +
|
||||||
|
`scripts.`
|
||||||
|
} else {
|
||||||
|
return `Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node ` +
|
||||||
|
`can be upgraded in order to increase its computing power and thereby increase ` +
|
||||||
|
`the profit you earn from it.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className={"hacknet-general-info"}>
|
||||||
|
The Hacknet is a global, decentralized network of machines. It is used by
|
||||||
|
hackers all around the world to anonymously share computing power and
|
||||||
|
perform distributed cyberattacks without the fear of being traced.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p className={"hacknet-general-info"}>
|
||||||
|
{this.getSecondParagraph()}
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p className={"hacknet-general-info"}>
|
||||||
|
{this.getThirdParagraph()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
153
src/Hacknet/ui/HacknetNode.jsx
Normal file
153
src/Hacknet/ui/HacknetNode.jsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Hacknet Node UI.
|
||||||
|
* This Component displays the panel for a single Hacknet Node
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { HacknetNodeMaxLevel,
|
||||||
|
HacknetNodeMaxRam,
|
||||||
|
HacknetNodeMaxCores } from "../HacknetNode";
|
||||||
|
import { getMaxNumberLevelUpgrades,
|
||||||
|
getMaxNumberRamUpgrades,
|
||||||
|
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export class HacknetNode extends React.Component {
|
||||||
|
render() {
|
||||||
|
const node = this.props.node;
|
||||||
|
const purchaseMult = this.props.purchaseMultiplier;
|
||||||
|
const recalculate = this.props.recalculate;
|
||||||
|
|
||||||
|
// Upgrade Level Button
|
||||||
|
let upgradeLevelText, upgradeLevelClass;
|
||||||
|
if (node.level >= HacknetNodeMaxLevel) {
|
||||||
|
upgradeLevelText = "MAX LEVEL";
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetNodeMaxLevel - node.level;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||||
|
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||||
|
if (Player.money.lt(upgradeLevelCost)) {
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeLevelClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeLevelOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||||
|
}
|
||||||
|
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let upgradeRamText, upgradeRamClass;
|
||||||
|
if (node.ram >= HacknetNodeMaxRam) {
|
||||||
|
upgradeRamText = "MAX RAM";
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = Math.round(Math.log2(HacknetNodeMaxRam / node.ram));
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||||
|
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||||
|
if (Player.money.lt(upgradeRamCost)) {
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeRamClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeRamOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||||
|
}
|
||||||
|
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let upgradeCoresText, upgradeCoresClass;
|
||||||
|
if (node.cores >= HacknetNodeMaxCores) {
|
||||||
|
upgradeCoresText = "MAX CORES";
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetNodeMaxCores - node.cores;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||||
|
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||||
|
if (Player.money.lt(upgradeCoreCost)) {
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeCoresClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeCoresOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||||
|
}
|
||||||
|
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={"hacknet-node"}>
|
||||||
|
<div className={"hacknet-node-container"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Node name:</p>
|
||||||
|
<span className={"text"}>{node.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Production:</p>
|
||||||
|
<span className={"text money-gold"}>
|
||||||
|
{numeralWrapper.formatMoney(node.totalMoneyGenerated)} ({numeralWrapper.formatMoney(node.moneyGainRatePerSecond)} / sec)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span>
|
||||||
|
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
|
||||||
|
{upgradeLevelText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>RAM:</p><span className={"text upgradable-info"}>{node.ram}GB</span>
|
||||||
|
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
|
||||||
|
{upgradeRamText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span>
|
||||||
|
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
|
||||||
|
{upgradeCoresText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
200
src/Hacknet/ui/HacknetServer.jsx
Normal file
200
src/Hacknet/ui/HacknetServer.jsx
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Hacknet Node UI.
|
||||||
|
* This Component displays the panel for a single Hacknet Node
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { HacknetServerMaxLevel,
|
||||||
|
HacknetServerMaxRam,
|
||||||
|
HacknetServerMaxCores,
|
||||||
|
HacknetServerMaxCache } from "../HacknetServer";
|
||||||
|
import { getMaxNumberLevelUpgrades,
|
||||||
|
getMaxNumberRamUpgrades,
|
||||||
|
getMaxNumberCoreUpgrades,
|
||||||
|
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export class HacknetServer extends React.Component {
|
||||||
|
render() {
|
||||||
|
const node = this.props.node;
|
||||||
|
const purchaseMult = this.props.purchaseMultiplier;
|
||||||
|
const recalculate = this.props.recalculate;
|
||||||
|
|
||||||
|
// Upgrade Level Button
|
||||||
|
let upgradeLevelText, upgradeLevelClass;
|
||||||
|
if (node.level >= HacknetServerMaxLevel) {
|
||||||
|
upgradeLevelText = "MAX LEVEL";
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetServerMaxLevel - node.level;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||||
|
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||||
|
if (Player.money.lt(upgradeLevelCost)) {
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeLevelClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeLevelOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||||
|
}
|
||||||
|
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade RAM Button
|
||||||
|
let upgradeRamText, upgradeRamClass;
|
||||||
|
if (node.maxRam >= HacknetServerMaxRam) {
|
||||||
|
upgradeRamText = "MAX RAM";
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = Math.round(Math.log2(HacknetServerMaxRam / node.maxRam));
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||||
|
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||||
|
if (Player.money.lt(upgradeRamCost)) {
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeRamClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeRamOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||||
|
}
|
||||||
|
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade Cores Button
|
||||||
|
let upgradeCoresText, upgradeCoresClass;
|
||||||
|
if (node.cores >= HacknetServerMaxCores) {
|
||||||
|
upgradeCoresText = "MAX CORES";
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetServerMaxCores - node.cores;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||||
|
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||||
|
if (Player.money.lt(upgradeCoreCost)) {
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeCoresClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeCoresOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||||
|
}
|
||||||
|
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade Cache button
|
||||||
|
let upgradeCacheText, upgradeCacheClass;
|
||||||
|
if (node.cache >= HacknetServerMaxCache) {
|
||||||
|
upgradeCacheText = "MAX CACHE";
|
||||||
|
upgradeCacheClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetServerMaxCache - node.cache;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier);
|
||||||
|
upgradeCacheText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCacheCost)}`;
|
||||||
|
if (Player.money.lt(upgradeCacheCost)) {
|
||||||
|
upgradeCacheClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeCacheClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeCacheOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||||
|
}
|
||||||
|
node.purchaseCacheUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
Player.hashManager.updateCapacity(Player);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={"hacknet-node"}>
|
||||||
|
<div className={"hacknet-node-container"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Node name:</p>
|
||||||
|
<span className={"text"}>{node.hostname}</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Production:</p>
|
||||||
|
<span className={"text money-gold"}>
|
||||||
|
{numeralWrapper.formatBigNumber(node.totalHashesGenerated)} ({numeralWrapper.formatBigNumber(node.hashRate)} / sec)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Hash Capacity:</p>
|
||||||
|
<span className={"text"}>{node.hashCapacity}</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span>
|
||||||
|
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
|
||||||
|
{upgradeLevelText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>RAM:</p><span className={"text upgradable-info"}>{node.maxRam}GB</span>
|
||||||
|
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
|
||||||
|
{upgradeRamText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span>
|
||||||
|
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
|
||||||
|
{upgradeCoresText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Cache Level:</p><span className={"text upgradable-info"}>{node.cache}</span>
|
||||||
|
<button className={upgradeCacheClass} onClick={upgradeCacheOnClick}>
|
||||||
|
{upgradeCacheText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
130
src/Hacknet/ui/HashUpgradePopup.jsx
Normal file
130
src/Hacknet/ui/HashUpgradePopup.jsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
41
src/Hacknet/ui/MultiplierButtons.jsx
Normal file
41
src/Hacknet/ui/MultiplierButtons.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Multiplier buttons on the Hacknet page.
|
||||||
|
* These buttons let the player control how many Nodes/Upgrades they're
|
||||||
|
* purchasing when using the UI (x1, x5, x10, MAX)
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { PurchaseMultipliers } from "./Root";
|
||||||
|
|
||||||
|
function MultiplierButton(props) {
|
||||||
|
return (
|
||||||
|
<button className={props.className} onClick={props.onClick}>{props.text}</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MultiplierButtons(props) {
|
||||||
|
if (props.purchaseMultiplier == null) {
|
||||||
|
throw new Error(`MultiplierButtons constructed without required props`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mults = ["x1", "x5", "x10", "MAX"];
|
||||||
|
const onClicks = props.onClicks;
|
||||||
|
const buttons = [];
|
||||||
|
for (let i = 0; i < mults.length; ++i) {
|
||||||
|
const mult = mults[i];
|
||||||
|
const btnProps = {
|
||||||
|
className: props.purchaseMultiplier === PurchaseMultipliers[mult] ? "std-button-disabled" : "std-button",
|
||||||
|
key: mult,
|
||||||
|
onClick: onClicks[i],
|
||||||
|
text: mult,
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push(<MultiplierButton {...btnProps} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span id={"hacknet-nodes-multipliers"}>
|
||||||
|
{buttons}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
51
src/Hacknet/ui/PlayerInfo.jsx
Normal file
51
src/Hacknet/ui/PlayerInfo.jsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* React Component for displaying Player info and stats on the Hacknet Node UI.
|
||||||
|
* This includes:
|
||||||
|
* - Player's money
|
||||||
|
* - Player's production from Hacknet Nodes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { hasHacknetServers } from "../HacknetHelpers";
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export function PlayerInfo(props) {
|
||||||
|
const hasServers = hasHacknetServers();
|
||||||
|
|
||||||
|
let prod;
|
||||||
|
if (hasServers) {
|
||||||
|
prod = numeralWrapper.format(props.totalProduction, "0.000a") + " hashes / sec";
|
||||||
|
} else {
|
||||||
|
prod = numeralWrapper.formatMoney(props.totalProduction) + " / sec";
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashInfo;
|
||||||
|
if (hasServers) {
|
||||||
|
hashInfo = numeralWrapper.format(Player.hashManager.hashes, "0.000a") + " / " +
|
||||||
|
numeralWrapper.format(Player.hashManager.capacity, "0.000a");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p id={"hacknet-nodes-money"}>
|
||||||
|
<span>Money:</span>
|
||||||
|
<span className={"money-gold"}>{numeralWrapper.formatMoney(Player.money.toNumber())}</span><br />
|
||||||
|
|
||||||
|
{
|
||||||
|
hasServers &&
|
||||||
|
<span>Hashes:</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasServers &&
|
||||||
|
<span className={"money-gold"}>{hashInfo}</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasServers &&
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>Total Hacknet Node Production:</span>
|
||||||
|
<span className={"money-gold"}>{prod}</span>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
40
src/Hacknet/ui/PurchaseButton.jsx
Normal file
40
src/Hacknet/ui/PurchaseButton.jsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the button that is used to purchase new Hacknet Nodes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { hasHacknetServers,
|
||||||
|
hasMaxNumberHacknetServers } from "../HacknetHelpers";
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export function PurchaseButton(props) {
|
||||||
|
if (props.multiplier == null || props.onClick == null) {
|
||||||
|
throw new Error(`PurchaseButton constructed without required props`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cost = props.cost;
|
||||||
|
let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled";
|
||||||
|
let text;
|
||||||
|
let style = null;
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
if (hasMaxNumberHacknetServers()) {
|
||||||
|
className = "std-button-disabled";
|
||||||
|
text = "Hacknet Server limit reached";
|
||||||
|
style = {color: "red"};
|
||||||
|
} else {
|
||||||
|
text = `Purchase Hacknet Server - ${numeralWrapper.formatMoney(cost)}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = `Purchase Hacknet Node - ${numeralWrapper.formatMoney(cost)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={className}
|
||||||
|
onClick={props.onClick}
|
||||||
|
style={style}>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
154
src/Hacknet/ui/Root.jsx
Normal file
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);
|
||||||
@ -1300,25 +1360,22 @@ function NetscriptFunctions(workerScript) {
|
|||||||
let copy = Object.assign({}, BitNodeMultipliers);
|
let copy = Object.assign({}, BitNodeMultipliers);
|
||||||
return copy;
|
return copy;
|
||||||
},
|
},
|
||||||
getServerMoneyAvailable : function(ip){
|
getServerMoneyAvailable : function(ip) {
|
||||||
if (workerScript.checkingRam) {
|
if (workerScript.checkingRam) {
|
||||||
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;
|
||||||
|
@ -96,7 +96,7 @@ export function createResleevesPage(p: IPlayer) {
|
|||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
innerText: "Sort By: "
|
innerText: "Sort By: "
|
||||||
});
|
});
|
||||||
UIElems.sortSelector = createElement("select",{class:"dropdown"}) as HTMLSelectElement;
|
UIElems.sortSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
|
||||||
|
|
||||||
enum SortOption {
|
enum SortOption {
|
||||||
Cost = "Cost",
|
Cost = "Cost",
|
||||||
|
@ -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(" ");
|
||||||
|
112
src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx
Normal file
112
src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
97
src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx
Normal file
97
src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
28
src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx
Normal file
28
src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx
Normal file
@ -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";
|
||||||
@ -111,10 +113,13 @@ function PlayerObject() {
|
|||||||
// Company at which player is CURRENTLY working (only valid when the player is actively working)
|
// Company at which player is CURRENTLY working (only valid when the player is actively working)
|
||||||
this.companyName = ""; // Name of Company. Must match a key value in Companies map
|
this.companyName = ""; // Name of Company. Must match a key value in Companies map
|
||||||
|
|
||||||
//Servers
|
// Servers
|
||||||
this.currentServer = ""; //IP address of Server currently being accessed through terminal
|
this.currentServer = ""; //IP address of Server currently being accessed through terminal
|
||||||
this.purchasedServers = []; //IP Addresses of purchased servers
|
this.purchasedServers = []; //IP Addresses of purchased servers
|
||||||
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,19 +420,21 @@ 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;
|
||||||
resetGangs();
|
resetGangs();
|
||||||
|
|
||||||
//Reset Stock market
|
// Reset Stock market
|
||||||
this.hasWseAccount = false;
|
this.hasWseAccount = false;
|
||||||
this.hasTixApiAccess = false;
|
this.hasTixApiAccess = false;
|
||||||
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,49 +203,45 @@ 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);
|
||||||
}
|
}
|
||||||
homeComp.cpuCores = 1;
|
homeComp.cpuCores = 1;
|
||||||
|
|
||||||
//Reset favor for Companies
|
// Reset favor for Companies
|
||||||
for (var member in Companies) {
|
for (var member in Companies) {
|
||||||
if (Companies.hasOwnProperty(member)) {
|
if (Companies.hasOwnProperty(member)) {
|
||||||
Companies[member].favor = 0;
|
Companies[member].favor = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Reset favor for factions
|
// Reset favor for factions
|
||||||
for (var member in Factions) {
|
for (var member in Factions) {
|
||||||
if (Factions.hasOwnProperty(member)) {
|
if (Factions.hasOwnProperty(member)) {
|
||||||
Factions[member].favor = 0;
|
Factions[member].favor = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Stop a Terminal action if there is one
|
// Stop a Terminal action if there is one
|
||||||
if (Engine._actionInProgress) {
|
if (Engine._actionInProgress) {
|
||||||
Engine._actionInProgress = false;
|
Engine._actionInProgress = false;
|
||||||
Terminal.finishAction(true);
|
Terminal.finishAction(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Delete all Augmentations
|
// Delete all Augmentations
|
||||||
for (var name in Augmentations) {
|
for (var name in Augmentations) {
|
||||||
if (Augmentations.hasOwnProperty(name)) {
|
if (Augmentations.hasOwnProperty(name)) {
|
||||||
delete Augmentations[name];
|
delete Augmentations[name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Re-initialize things - This will update any changes
|
// Re-initialize things - This will update any changes
|
||||||
initFactions(); //Factions must be initialized before augmentations
|
initFactions(); // Factions must be initialized before augmentations
|
||||||
initAugmentations(); //Calls reapplyAllAugmentations() and resets Player multipliers
|
initAugmentations(); // Calls reapplyAllAugmentations() and resets Player multipliers
|
||||||
Player.reapplyAllSourceFiles();
|
Player.reapplyAllSourceFiles();
|
||||||
initCompanies();
|
initCompanies();
|
||||||
|
|
||||||
@ -325,7 +323,7 @@ function prestigeSourceFile() {
|
|||||||
dialogBoxCreate("Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!");
|
dialogBoxCreate("Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Reset Stock market, gang, and corporation
|
// Reset Stock market, gang, and corporation
|
||||||
if (Player.hasWseAccount) {
|
if (Player.hasWseAccount) {
|
||||||
initStockMarket();
|
initStockMarket();
|
||||||
initSymbolToStockMap();
|
initSymbolToStockMap();
|
||||||
@ -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
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)");
|
||||||
}
|
}
|
||||||
@ -119,7 +124,7 @@ function applySourceFile(srcFile) {
|
|||||||
Player.hacknet_node_level_cost_mult *= decMult;
|
Player.hacknet_node_level_cost_mult *= decMult;
|
||||||
Player.work_money_mult *= incMult;
|
Player.work_money_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 2: //Rise of the Underworld
|
case 2: // Rise of the Underworld
|
||||||
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 += (24 / (Math.pow(2, i)));
|
||||||
@ -129,7 +134,7 @@ function applySourceFile(srcFile) {
|
|||||||
Player.crime_success_mult *= incMult;
|
Player.crime_success_mult *= incMult;
|
||||||
Player.charisma_mult *= incMult;
|
Player.charisma_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 3: //Corporatocracy
|
case 3: // Corporatocracy
|
||||||
var mult = 0;
|
var mult = 0;
|
||||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||||
mult += (8 / (Math.pow(2, i)));
|
mult += (8 / (Math.pow(2, i)));
|
||||||
@ -138,10 +143,10 @@ function applySourceFile(srcFile) {
|
|||||||
Player.charisma_mult *= incMult;
|
Player.charisma_mult *= incMult;
|
||||||
Player.work_money_mult *= incMult;
|
Player.work_money_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 4: //The Singularity
|
case 4: // The Singularity
|
||||||
//No effects, just gives access to Singularity functions
|
// No effects, just gives access to Singularity functions
|
||||||
break;
|
break;
|
||||||
case 5: //Artificial Intelligence
|
case 5: // Artificial Intelligence
|
||||||
var mult = 0;
|
var mult = 0;
|
||||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||||
mult += (8 / (Math.pow(2, i)));
|
mult += (8 / (Math.pow(2, i)));
|
||||||
@ -154,7 +159,7 @@ function applySourceFile(srcFile) {
|
|||||||
Player.hacking_mult *= incMult;
|
Player.hacking_mult *= incMult;
|
||||||
Player.hacking_exp_mult *= incMult;
|
Player.hacking_exp_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 6: //Bladeburner
|
case 6: // Bladeburner
|
||||||
var mult = 0;
|
var mult = 0;
|
||||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||||
mult += (8 / (Math.pow(2, i)));
|
mult += (8 / (Math.pow(2, i)));
|
||||||
@ -169,7 +174,7 @@ function applySourceFile(srcFile) {
|
|||||||
Player.dexterity_mult *= incMult;
|
Player.dexterity_mult *= incMult;
|
||||||
Player.agility_mult *= incMult;
|
Player.agility_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 7: //Bladeburner 2079
|
case 7: // Bladeburner 2079
|
||||||
var mult = 0;
|
var mult = 0;
|
||||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||||
mult += (8 / (Math.pow(2, i)));
|
mult += (8 / (Math.pow(2, i)));
|
||||||
@ -180,7 +185,7 @@ function applySourceFile(srcFile) {
|
|||||||
Player.bladeburner_analysis_mult *= incMult;
|
Player.bladeburner_analysis_mult *= incMult;
|
||||||
Player.bladeburner_success_chance_mult *= incMult;
|
Player.bladeburner_success_chance_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 8: //Ghost of Wall Street
|
case 8: // Ghost of Wall Street
|
||||||
var mult = 0;
|
var mult = 0;
|
||||||
for (var i = 0; i < srcFile.lvl; ++i) {
|
for (var i = 0; i < srcFile.lvl; ++i) {
|
||||||
mult += (12 / (Math.pow(2, i)));
|
mult += (12 / (Math.pow(2, i)));
|
||||||
@ -188,19 +193,22 @@ 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;
|
||||||
Player.company_rep_mult *= incMult;
|
Player.company_rep_mult *= incMult;
|
||||||
break;
|
break;
|
||||||
case 12: //The Recursion
|
case 12: // The Recursion
|
||||||
var inc = Math.pow(1.01, srcFile.lvl);
|
var inc = Math.pow(1.01, srcFile.lvl);
|
||||||
var dec = Math.pow(0.99, srcFile.lvl);
|
var dec = Math.pow(0.99, srcFile.lvl);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
23
src/ui/React/ParagraphWithTooltip.tsx
Normal file
23
src/ui/React/ParagraphWithTooltip.tsx
Normal file
@ -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
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
67
src/ui/React/PopupCloseButton.tsx
Normal file
67
src/ui/React/PopupCloseButton.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
65
src/ui/React/ServerDropdown.jsx
Normal file
65
src/ui/React/ServerDropdown.jsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Creates a dropdown (select HTML element) with server hostnames as options
|
||||||
|
*
|
||||||
|
* Configurable to only contain certain types of servers
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
import { AllServers } from "../../Server/AllServers";
|
||||||
|
|
||||||
|
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||||
|
|
||||||
|
// TODO make this an enum when this gets converted to TypeScript
|
||||||
|
export const ServerType = {
|
||||||
|
All: 0,
|
||||||
|
Foreign: 1, // Hackable, non-owned servers
|
||||||
|
Owned: 2, // Home Computer, Purchased Servers, and Hacknet Servers
|
||||||
|
Purchased: 3, // Everything from Owned except home computer
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerDropdown extends React.Component {
|
||||||
|
/**
|
||||||
|
* Checks if the server should be shown in the dropdown menu, based on the
|
||||||
|
* 'serverType' property
|
||||||
|
*/
|
||||||
|
isValidServer(s) {
|
||||||
|
const type = this.props.serverType;
|
||||||
|
switch (type) {
|
||||||
|
case ServerType.All:
|
||||||
|
return true;
|
||||||
|
case ServerType.Foreign:
|
||||||
|
return (s.hostname !== "home" && !s.purchasedByPlayer);
|
||||||
|
case ServerType.Owned:
|
||||||
|
return s.purchasedByPlayer || (s instanceof HacknetServer) || s.hostname === "home";
|
||||||
|
case ServerType.Purchased:
|
||||||
|
return s.purchasedByPlayer || (s instanceof HacknetServer);
|
||||||
|
default:
|
||||||
|
console.warn(`Invalid ServerType specified for ServerDropdown component: ${type}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a Server object, creates a Option element
|
||||||
|
*/
|
||||||
|
renderOption(s) {
|
||||||
|
return (
|
||||||
|
<option key={s.hostname} value={s.hostname}>{s.hostname}</option>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const servers = [];
|
||||||
|
for (const serverName in AllServers) {
|
||||||
|
const server = AllServers[serverName];
|
||||||
|
if (this.isValidServer(server)) {
|
||||||
|
servers.push(this.renderOption(server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select className={"dropdown"} onChange={this.props.onChange} style={this.props.style}>
|
||||||
|
{servers}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
24
src/ui/React/StdButton.tsx
Normal file
24
src/ui/React/StdButton.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
60
src/ui/React/createPopup.tsx
Normal file
60
src/ui/React/createPopup.tsx
Normal file
@ -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);
|
||||||
|
}
|
12
src/utils/createRandomString.ts
Normal file
12
src/utils/createRandomString.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Function that generates a random gibberish string of length n
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
export function createRandomString(n: number): string {
|
||||||
|
let str: string = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
str += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user