Compare commits

...

40 Commits

Author SHA1 Message Date
danielyxie
2e9b028174 v0.46.1 build 2019-04-12 16:52:59 -07:00
danielyxie
c56645c794 Updated doc for new Terminal filesystem. Fixed some minor issues with new Location UI 2019-04-09 23:34:23 -07:00
danielyxie
3ce2e83dd8 Finished rudimentary filesystem implementation for Terminal 2019-04-09 23:07:12 -07:00
danielyxie
3241945452 Added hacknet node api functions for spending hashes. Fixed several bugs with v0.46.0. Rebalanced hash upgrades. continued working on terminal directory implementation 2019-04-05 02:08:41 -07:00
danielyxie
fb857642e8 Merge pull request #591 from danielyxie/location-code-refactor
Location code refactor
2019-04-03 20:38:02 -07:00
Olivier Gagnon
cc0e6548ff folders 2019-04-03 20:37:38 -07:00
danielyxie
6c3c569a44 Merge branch 'master' of https://github.com/danielyxie/bitburner into location-code-refactor 2019-04-03 17:12:16 -07:00
danielyxie
b5ebbba43d Fixed merge conflicts with dev 2019-04-03 17:12:11 -07:00
danielyxie
bf9b837e31 Fixed bugs with Location code refactor 2019-04-03 17:08:11 -07:00
danielyxie
7f88ade30e Merge pull request #590 from danielyxie/dev
v0.46.0
2019-04-03 16:59:42 -07:00
danielyxie
36499ae9f2 Fixed bug where sleeves's exp didnt properly reset on prestige. Updated versions to v0.46.0. Added production build 2019-04-03 16:58:15 -07:00
danielyxie
4b95ba9ed1 Finished location code refactor. Has not yet been tested 2019-04-01 02:23:25 -07:00
danielyxie
804e4c23e3 Updated Hacknet Node API documentation for the BN-9 changes. netscript functions now properly fail for Hacknet Servers 2019-03-30 19:53:57 -07:00
danielyxie
b6b6d8e9fa Fixed bug with purchasing Hacknet Servers not updating hash capacity when done through Netscript 2019-03-30 17:39:37 -07:00
danielyxie
c566c838be Merge branch 'master' of https://github.com/danielyxie/bitburner into dev 2019-03-29 20:01:43 -07:00
danielyxie
51d9274626 Added effects for Source-File 9 2019-03-29 20:01:34 -07:00
danielyxie
c8b478c208 Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-03-29 16:14:40 -07:00
danielyxie
18a3f061b4 Rebalanced new Hacknet Node mechanics. Adjusted Hacknet API so that it'll work with hacknet Servers. Fixed Corporation bug with Issuing new Shares 2019-03-29 16:14:32 -07:00
danielyxie
3f8b9e4a32 Fixed merge conflicts. Rebalanced new Hacknet Node mechanics. Adjusted Hacknet API so that it'll work with hacknet Servers. Fixed Corporation bug with Issuing new Shares 2019-03-29 16:13:58 -07:00
Olivier Gagnon
e63ad76701 typo 2019-03-29 16:11:43 -07:00
Olivier Gagnon
cb66ad9628 fli1ght now displays the correct number of augs 2019-03-29 16:11:43 -07:00
danielyxie
971bfbada4 Merge pull request #587 from hydroflame/align-free
Align free
2019-03-29 16:11:25 -07:00
Olivier Gagnon
b646c15521 It is now possible to join bladeburners in BN7 without having SF6-1 2019-03-29 16:11:16 -07:00
Olivier Gagnon
92f7d12c0e free output is now aligned and shows percent used 2019-03-29 12:26:25 -04:00
danielyxie
7172f4e527 Began creating 'parent' components for the City and Location-specific parts of the UI 2019-03-29 00:12:41 -07:00
danielyxie
5592a8bc96 Updated several packages 2019-03-27 01:53:30 -07:00
danielyxie
c4cb7daac5 Initial v0.46.0 changes - Fixed BN9 bugs. Rebalanced BN11 and Corporations. Added memory to Dup sleeves. Various bug fixes 2019-03-27 01:36:14 -07:00
danielyxie
75bc34208c Initial commit for Location Code refactor 2019-03-27 01:31:47 -07:00
danielyxie
83fc4d81b2 Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-03-25 21:48:18 -07:00
danielyxie
3cf18f100a Added several resuable React components for commonly-used elements 2019-03-25 21:43:18 -07:00
danielyxie
8fbb072596 Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-03-24 23:08:32 -07:00
danielyxie
ea7f0752cb Merge pull request #572 from hydroflame/sleeve-buy-aug-api
Sleeve buy aug api
2019-03-24 23:07:34 -07:00
Olivier Gagnon
0f8f572519 Merge branch 'dev' into sleeve-buy-aug-api 2019-03-25 02:01:45 -04:00
danielyxie
52b6defebd Merge branch 'dev' of https://github.com/danielyxie/bitburner into dev 2019-03-24 20:18:44 -07:00
danielyxie
34d749809a BitNode-9 initial implementation 2019-03-24 20:17:47 -07:00
Olivier Gagnon
2ce4af2498 infiltration now scale exp gained by some factor to prevent some abuse 2019-03-24 20:09:39 -07:00
koriar
227bcf146e Fixing Corp Happiness typo 2019-03-24 20:03:22 -07:00
danielyxie
139a5add20 Cleaned up whitespace from a PR 2019-03-24 19:56:47 -07:00
danielyxie
3a61a5cfa1 Merge pull request #577 from danielyxie/dev
v0.45.1
2019-03-24 19:55:49 -07:00
Olivier Gagnon
96db360a36 added sleeve aug api 2019-03-23 00:22:40 -04:00
154 changed files with 12116 additions and 8842 deletions

View File

@@ -38,6 +38,7 @@ button {
}
.a-link-button-inactive,
.std-button-disabled,
.std-button:disabled {
text-decoration: none;
background-color: #333;

View File

@@ -2,7 +2,7 @@
@import "theme";
/**
* Styling for the Character Overview Panel (top-right)
* Styling for the Character Overview Panel (top-right panel)
*/
#character-overview-wrapper {

75
css/hacknetnodes.scss Normal file
View 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;
}
}

View File

@@ -138,81 +138,6 @@
}
}
/* Hacknet Nodes */
#hacknet-nodes-container {
position: fixed;
padding: 10px;
}
#hacknet-nodes-text,
#hacknet-nodes-container li {
margin: 10px;
padding: 10px;
}
#hacknet-nodes-container li {
float: left;
overflow: hidden;
white-space: nowrap;
&.hacknet-node {
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
@include boxShadow($boxShadowArgs);
margin: 6px;
padding: 7px;
width: 35vw;
border: 2px solid var(--my-highlight-color);
}
}
#hacknet-nodes-list {
list-style: none;
width: 82vw;
}
#hacknet-nodes-money {
margin: 10px;
float: left;
}
#hacknet-nodes-money-multipliers-div {
display: inline-block;
width: 70vw;
}
#hacknet-nodes-multipliers {
float: right;
}
#hacknet-nodes-purchase-button {
display: inline-block;
}
.hacknet-node-container {
display: inline-table;
.row {
display: table-row;
height: 30px;
p {
display: table-cell;
}
}
.upgradable-info {
display: inline-block;
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
padding: 0 4px;
width: $defaultFontSize * 4;
}
}
.menu-page-text {
width: 70vw;
}
/* World */
#world-container {
position: fixed;

View File

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

View File

@@ -68,13 +68,14 @@
@include boxShadow($boxShadowArgs);
background-color: #555;
border: 2px solid var(--my-highlight-color);
color: #fff;
display: inline-block;
float: center;
resize: none;
color: #fff;
margin: 4px;
padding: 2px;
border: 2px solid var(--my-highlight-color);
resize: none;
width: 50%;
}
#script-editor-status {

File diff suppressed because one or more lines are too long

132
dist/engine.css vendored
View File

@@ -486,6 +486,7 @@ button {
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6); }
.a-link-button-inactive,
.std-button-disabled,
.std-button:disabled {
text-decoration: none;
background-color: #333;
@@ -501,11 +502,15 @@ button {
.a-link-button-inactive:hover .tooltiptext,
.a-link-button-inactive:hover .tooltiptexthigh,
.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 .tooltiptexthigh,
.std-button:disabled:hover .tooltiptextleft {
visibility: visible; }
.a-link-button-inactive:active,
.std-button-disabled:active,
.std-button:disabled:active {
pointer-events: none; }
@@ -671,7 +676,7 @@ button {
/* COLORS */
/* Attributes */
/**
* Styling for the Character Overview Panel (top-right)
* Styling for the Character Overview Panel (top-right panel)
*/
#character-overview-wrapper {
position: relative; }
@@ -881,13 +886,14 @@ button {
-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);
background-color: #555;
border: 2px solid var(--my-highlight-color);
color: #fff;
display: inline-block;
float: center;
resize: none;
color: #fff;
margin: 4px;
padding: 2px;
border: 2px solid var(--my-highlight-color); }
resize: none;
width: 50%; }
#script-editor-status {
float: left;
@@ -927,6 +933,64 @@ button {
/* 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 */
/* Attributes */
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
@@ -1044,64 +1108,6 @@ button {
color: #fff;
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-container {
position: fixed;
@@ -1402,7 +1408,7 @@ button {
/* Pop-up boxes */
.popup-box-container {
display: none;
/* Hidden by default */
/* Initially hidden */
position: fixed;
/* Stay in place */
z-index: 10;

130
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -80,6 +80,16 @@ when you normally install Augmentations.
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.
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 is the process of digitizing and transferring your consciousness into a

View File

@@ -16,6 +16,85 @@ the terminal and enter::
nano .fconf
.. _terminal_filesystem:
Filesystem (Directories)
------------------------
The Terminal contains a **very** basic filesystem that allows you to store and
organize your files into different directories. Note that this is **not** a true
filesystem implementation. Instead, it is done almost entirely using string manipulation.
For this reason, many of the nice & useful features you'd find in a real
filesystem do not exist.
Here are the Terminal commands you'll commonly use when dealing with the filesystem.
* :ref:`ls_terminal_command`
* :ref:`cd_terminal_command`
* :ref:`mv_terminal_command`
Directories
^^^^^^^^^^^
In order to create a directory, simply name a file using a full absolute Linux-style path::
/scripts/myScript.js
This will automatically create a "directory" called :code:`scripts`. This will also work
for subdirectories::
/scripts/hacking/helpers/myHelperScripts.script
Files in the root directory do not need to begin with a forward slash::
thisIsAFileInTheRootDirectory.txt
Note that there is no way to manually create or remove directories. The creation and
deletion of directories is automatically handled as you name/rename/delete
files.
Absolute vs Relative Paths
^^^^^^^^^^^^^^^^^^^^^^^^^^
Many Terminal commands accept absolute both absolute and relative paths for specifying a
file.
An absolute path specifies the location of the file from the root directory (/).
Any path that begins with the forward slash is an absolute path::
$ nano /scripts/myScript.js
$ cat /serverList.txt
A relative path specifies the location of the file relative to the current working directory.
Any path that does **not** begin with a forward slash is a relative path. Note that the
Linux-style dot symbols will work for relative paths::
. (a single dot) - represents the current directory
.. (two dots) - represents the parent directory
$ cd ..
$ nano ../scripts/myScript.js
$ nano ../../helper.js
Netscript
^^^^^^^^^
Note that in order to reference a file, :ref:`netscript` functions require the
**full** absolute file path. For example
.. code:: javascript
run("/scripts/hacking/helpers.myHelperScripts.script");
rm("/logs/myHackingLogs.txt");
rm("thisIsAFileInTheRootDirectory.txt");
.. note:: A full file path **must** begin with a forward slash (/) if that file
is not in the root directory.
Missing Features
^^^^^^^^^^^^^^^^
These features that are typically in Linux filesystems have not yet been added to the game:
* Tab autocompletion does not work with relative paths
* :code:`mv` only accepts full filepaths for the destination argument. It does not accept directories
Commands
--------
@@ -98,6 +177,25 @@ Display a message (.msg), literature (.lit), or text (.txt) file::
$ cat foo.lit
$ cat servers.txt
.. _cd_terminal_command:
cd
^^
$ cd [dir]
Change to the specified directory.
See :ref:`terminal_filesystem` for details on directories.
Note that this command works even for directories that don't exist. If you change
to a directory that doesn't exist, it will not be created. A directory is only created
once there is a file in it::
$ cd scripts/hacking
$ cd /logs
$ cd ..
check
^^^^^
@@ -234,27 +332,35 @@ killall
Kills all scripts on the current server.
.. _ls_terminal_command:
ls
^^
$ ls [| grep pattern]
$ ls [dir] [| grep pattern]
Prints files on the current server to the Terminal screen.
Prints files and directories on the current server to the Terminal screen.
If this command is run with no arguments, then it prints all files on the current
server to the Terminal screen. The files will be displayed in alphabetical
order.
If this command is run with no arguments, then it prints all files and directories on the current
server to the Terminal screen. Directories will be printed first in alphabetical order,
followed by the files (also in alphabetical order).
The '| grep pattern' is an optional parameter that can be used to only display files
whose filenames match the specified pattern. For example, if you wanted to only display
files with the .script extension, you could use::
The :code:`dir` optional parameter allows you to specify the directory for which to display
files.
The :code:`| grep pattern` optional parameter allows you to only display files and directories
with a certain pattern in their names.
Examples::
// List files/directories with the '.script' extension in the current directory
$ ls | grep .script
Alternatively, if you wanted to display all files with the word *purchase* in the filename,
you could use::
// List files/directories with the '.js' extension in the root directory
$ ls / | grep .js
$ ls | grep purchase
// List files/directories with the word 'purchase' in the name, in the :code:`scripts` directory
$ ls scripts | grep purchase
lscpu
@@ -282,6 +388,28 @@ The first example above will print the amount of RAM needed to run 'foo.script'
with a single thread. The second example above will print the amount of RAM needed
to run 'foo.script' with 50 threads.
.. _mv_terminal_command:
mv
^^
$ mv [source] [destination]
Move the source file to the specified destination in the filesystem.
See :ref:`terminal_filesystem` for more details about the Terminal's filesystem.
This command only works for scripts and text files (.txt). It cannot, however, be used
to convert from script to text file, or vice versa.
This function can also be used to rename files.
.. note:: Unlike the Linux :code:`mv` command, the *destination* argument must be the
full filepath. It cannot be a directory.
Examples::
$ mv hacking.script scripts/hacking.script
$ mv myScript.js myOldScript.js
nano
^^^^

View File

@@ -3,6 +3,24 @@
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
-------------------
* Added two new Corporation Researches

View File

@@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents.
#
# The short X.Y version.
version = '0.45'
version = '0.46'
# 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
# for a list of supported languages.

View File

@@ -1,5 +1,5 @@
getScriptRam() Netscript Function
===========================
=================================
.. js:function:: getScriptRam(scriptname[, hostname/ip])

View 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.

View File

@@ -1,6 +1,8 @@
getNodeStats() Netscript Function
=================================
.. warning:: This page contains spoilers for the game
.. js:function:: getNodeStats(i)
: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,
ram: Node's RAM,
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,
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.

View File

@@ -0,0 +1,23 @@
hashCost() Netscript Function
=============================
.. warning:: This page contains spoilers for the game
.. js:function:: hashCost(upgName, upgTarget)
:param string upgName: Name of upgrade to get the cost of. Must be an exact match
.. note:: This function is only applicable for Hacknet Servers (the upgraded version
of a Hacknet Node).
Returns the number of hashes required for the specified upgrade. The name of the
upgrade must be an exact match.
Example:
.. code:: javascript
var upgradeName = "Sell for Corporation Funds";
if (hacknet.numHashes() > hacknet.hashCost(upgradeName)) {
hacknet.spendHashes(upgName);
}

View File

@@ -0,0 +1,11 @@
numHashes() Netscript Function
==============================
.. warning:: This page contains spoilers for the game
.. js:function:: numHashes()
.. note:: This function is only applicable for Hacknet Servers (the upgraded version
of a Hacknet Node).
Returns the number of hashes you have

View File

@@ -0,0 +1,26 @@
spendHashes() Netscript Function
================================
.. warning:: This page contains spoilers for the game
.. js:function:: spendHashes(upgName, upgTarget)
:param string upgName: Name of upgrade to spend hashes on. Must be an exact match
:param string upgTarget: Object to which upgrade applies. Required for certain upgrades
.. note:: This function is only applicable for Hacknet Servers (the upgraded version
of a Hacknet Node).
Spend the hashes generated by your Hacknet Servers on an upgrade. Returns a boolean value -
true if the upgrade is successfully purchased, and false otherwise.
The name of the upgrade must be an exact match. The :code:`upgTarget` argument is used
for upgrades such as :code:`Reduce Minimum Security`, which applies to a specific server.
In this case, the :code:`upgTarget` argument must be the hostname of the server.
Example:
.. code:: javascript
hacknet.spendHashes("Sell for Corporation Funds");
hacknet.spendHashes("Increase Maximum Money", "foodnstuff");

View 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.

View File

@@ -3,6 +3,10 @@
Netscript Hacknet Node API
==========================
.. warning:: Not all functions in the Hacknet Node API are immediately available.
For this reason, the documentation for this API may contains spoilers
for the game.
Netscript provides the following API for accessing and upgrading your Hacknet Nodes
through scripts.
@@ -31,9 +35,14 @@ In :ref:`netscriptjs`::
upgradeLevel() <hacknetnodeapi/upgradeLevel>
upgradeRam() <hacknetnodeapi/upgradeRam>
upgradeCore() <hacknetnodeapi/upgradeCore>
upgradeCache() <hacknetnodeapi/upgradeCache>
getLevelUpgradeCost() <hacknetnodeapi/getLevelUpgradeCost>
getRamUpgradeCost() <hacknetnodeapi/getRamUpgradeCost>
getCoreUpgradeCost() <hacknetnodeapi/getCoreUpgradeCost>
getCacheUpgradeCost() <hacknetnodeapi/getCacheUpgradeCost>
numHashes() <hacknetnodeapi/numHashes>
hashCost() <hacknetnodeapi/hashCost>
spendHashes() <hacknetnodeapi/spendHashes>
.. _netscript_hacknetnodeapi_referencingahacknetnode:
@@ -68,23 +77,25 @@ The following is an example of one way a script can be used to automate the
purchasing and upgrading of Hacknet Nodes.
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() {
return getServerMoneyAvailable("home");() <hacknetnodeapi/ return getServerMoneyAvailable("home");>
}
}() <hacknetnodeapi/>
return getServerMoneyAvailable("home");
}
disableLog("getServerMoneyAvailable");
disableLog("sleep");
cnt = 8;
var cnt = 8;
while(hacknet.numNodes() < cnt) {
res = hacknet.purchaseNode();
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) {
var cost = hacknet.getLevelUpgradeCost(i, 10);
while (myMoney() < cost) {
@@ -95,9 +106,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) {
var cost = hacknet.getRamUpgradeCost(i, 2);
while (myMoney() < cost) {
@@ -108,43 +119,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");
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");
print("All nodes upgraded to 16GB RAM");

View File

@@ -41,6 +41,9 @@ In :ref:`netscriptjs`::
setToUniversityCourse() <sleeveapi/setToUniversityCourse>
setToGymWorkout() <sleeveapi/setToGymWorkout>
travel() <sleeveapi/travel>
getSleeveAugmentations() <sleeveapi/getSleeveAugmentations>
getSleevePurchasableAugs() <sleeveapi/getSleevePurchasableAugs>
purchaseSleeveAug() <sleeveapi/purchaseSleeveAug>
.. _netscript_sleeveapi_referencingaduplicatesleeve:

View File

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

View 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
}
]

View File

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

View 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.

View File

@@ -201,257 +201,9 @@
<!-- Hacknet Nodes -->
<div id="hacknet-nodes-container" class="generic-menupage-container">
<h1 id="hacknet-nodes-title"> Hacknet Nodes </h1>
<p id="hacknet-nodes-text" class="menu-page-text">
The Hacknet is a global, decentralized network of machines. It is used by hackers all around
the world to anonymously share computing power and perform distributed cyberattacks without the
fear of being traced.
<br/><br/>
Here, you can purchase a Hacknet Node, a specialized machine that can connect and contribute its
resources to the Hacknet network. This allows you to take a small percentage of profits
from hacks performed on the network. Essentially, you are renting out your Node's computing power.
<br/><br/>
Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node can be upgraded
in order to increase its computing power and thereby increase the profit you earn from it.
</p>
<a id="hacknet-nodes-purchase-button" class="a-link-button"> Purchase Hacknet Node </a>
<br/>
<div id="hacknet-nodes-money-multipliers-div">
<p id="hacknet-nodes-money">
<span>Money:</span><span id="hacknet-nodes-player-money" class="money-gold"></span><br/>
<span>Total Hacknet Node Production:</span><span id="hacknet-nodes-total-production" class="money-gold"></span>
</p>
<span id="hacknet-nodes-multipliers">
<a id="hacknet-nodes-1x-multiplier" class="a-link-button-inactive"> x1 </a>
<a id="hacknet-nodes-5x-multiplier" class="a-link-button"> x5 </a>
<a id="hacknet-nodes-10x-multiplier" class="a-link-button"> x10 </a>
<a id="hacknet-nodes-max-multiplier" class="a-link-button"> MAX </a>
</span>
</div>
<ul id="hacknet-nodes-list">
</ul>
<!-- React Component -->
</div>
<!-- World -->
<div id="world-container" class="generic-menupage-container">
<h2 id="world-city-name"> </h2>
<p id="world-city-desc"> </p>
<ul id="aevum-locations-list">
<li id="aevum-travelagency-li">
<a id="aevum-travelagency" class="a-link-button"> Travel Agency </a>
</li>
<li id="aevum-hospital-li">
<a id="aevum-hospital" class="a-link-button">Hospital</a>
</li>
<li id="aevum-summituniversity-li">
<a id="aevum-summituniversity" class="a-link-button"> Summit University </a>
</li>
<li id="aevum-ecorp-li">
<a id="aevum-ecorp" class="a-link-button"> ECorp </a>
</li>
<li id="aevum-bachmanandassociates-li">
<a id="aevum-bachmanandassociates" class="a-link-button"> Bachman & Associates</a>
</li>
<li id="aevum-clarkeincorporated-li">
<a id="aevum-clarkeincorporated" class="a-link-button"> Clarke Incorporated </a>
</li>
<li id="aevum-fulcrumtechnologies-li">
<a id="aevum-fulcrumtechnologies" class="a-link-button"> Fulcrum Technologies </a>
</li>
<li id="aevum-aerocorp-li">
<a id="aevum-aerocorp" class="a-link-button"> AeroCorp </a>
</li>
<li id="aevum-galacticcybersystems-li">
<a id="aevum-galacticcybersystems" class="a-link-button"> Galactic Cybersystems </a>
</li>
<li id="aevum-watchdogsecurity-li">
<a id="aevum-watchdogsecurity" class="a-link-button">Watchdog Security </a>
</li>
<li id="aevum-rhoconstruction-li">
<a id="aevum-rhoconstruction" class="a-link-button">Rho Construction </a>
</li>
<li id="aevum-aevumpolice-li">
<a id="aevum-aevumpolice" class="a-link-button">Aevum Police</a>
</li>
<li id="aevum-netlinktechnologies-li">
<a id="aevum-netlinktechnologies" class="a-link-button">NetLink Technologies</a>
</li>
<li id="aevum-crushfitnessgym-li">
<a id="aevum-crushfitnessgym" class="a-link-button">Crush Fitness Gym </a>
</li>
<li id="aevum-snapfitnessgym-li">
<a id="aevum-snapfitnessgym" class="a-link-button">Snap Fitness Gym</a>
</li>
<li id="aevum-slums-li">
<a id="aevum-slums" class="a-link-button">The Slums</a>
</li>
</ul>
<ul id="chongqing-locations-list">
<li id="chongqing-travelagency-li">
<a id="chongqing-travelagency" class="a-link-button"> Travel Agency </a>
</li>
<li id="chongqing-hospital-li">
<a id="chongqing-hospital" class="a-link-button">Hospital</a>
</li>
<li id="chonqging-kuaigonginternational-li">
<a id="chongqing-kuaigonginternational" class="a-link-button">KuaiGong International </a>
</li>
<li id="chongqing-solarisspacesystems-li">
<a id="chongqing-solarisspacesystems" class="a-link-button">Solaris Space Systems</a>
</li>
<li id="chongqing-slums-li">
<a id="chongqing-slums" class="a-link-button">The Slums</a>
</li>
</ul>
<ul id="sector12-locations-list">
<li id="sector12-travelagency-li">
<a id="sector12-travelagency" class="a-link-button">Travel Agency </a>
</li>
<li id="sector12-hospital-li">
<a id="sector12-hospital" class="a-link-button">Hospital</a>
</li>
<li id="sector12-rothmanuniversity-li">
<a id="sector12-rothmanuniversity" class="a-link-button"> Rothman University</a>
</li>
<li id="sector12-megacorp-li">
<a id="sector12-megacorp" class="a-link-button">MegaCorp</a>
</li>
<li id="sector12-bladeindustries-li">
<a id="sector12-bladeindustries" class="a-link-button"> Blade Industries</a>
</li>
<li id="sector12-foursigma-li">
<a id="sector12-foursigma" class="a-link-button">Four Sigma</a>
</li>
<li id="sector12-icarusmicrosystems-li">
<a id="sector12-icarusmicrosystems" class="a-link-button"> Icarus Microsystems</a>
</li>
<li id="sector12-universalenergy-li">
<a id="sector12-universalenergy" class="a-link-button">Universal Energy </a>
</li>
<li id="sector12-deltaone-li">
<a id="sector12-deltaone" class="a-link-button">DeltaOne </a>
</li>
<li id="sector12-cia-li">
<a id="sector12-cia" class="a-link-button">Central Intelligence Agency </a>
</li>
<li id="sector12-nsa-li">
<a id="sector12-nsa" class="a-link-button">National Security Agency </a>
</li>
<li id="sector12-alphaenterprises-li">
<a id="sector12-alphaenterprises" class="a-link-button">Alpha Enterprises</a>
</li>
<li id="sector12-carmichaelsecurity-li">
<a id="sector12-carmichaelsecurity" class="a-link-button"> Carmichael Security</a>
</li>
<li id="sector12-foodnstuff-li">
<a id="sector12-foodnstuff" class="a-link-button">FoodNStuff</a>
</li>
<li id="sector12-joesguns-li">
<a id="sector12-joesguns" class="a-link-button"> Joe's Guns</a>
</li>
<li id="sector12-irongym-li">
<a id="sector12-irongym" class="a-link-button">Iron Gym </a>
</li>
<li id="sector12-powerhousegym-li">
<a id="sector12-powerhousegym" class="a-link-button">Powerhouse Gym</a>
</li>
<li id="sector12-slums-li">
<a id="sector12-slums" class="a-link-button">The Slums</a>
</li>
<li id="sector12-cityhall-li">
<a id="sector12-cityhall" class="a-link-button">City Hall</a>
</li>
</ul>
<ul id="newtokyo-locations-list">
<li id="newtokyo-travelagency-li">
<a id="newtokyo-travelagency" class="a-link-button"> Travel Agency</a>
</li>
<li id="newtokyo-hospital-li">
<a id="newtokyo-hospital" class="a-link-button">Hospital</a>
</li>
<li id="newtokyo-defcomm-li">
<a id="newtokyo-defcomm" class="a-link-button"> DefComm</a>
</li>
<li id="newtokyo-vitalife-li">
<a id="newtokyo-vitalife" class="a-link-button">VitaLife </a>
</li>
<li id="newtokyo-globalpharmaceuticals-li">
<a id="newtokyo-globalpharmaceuticals" class="a-link-button">Global Pharmaceuticals</a>
</li>
<li id="newtokyo-noodlebar-li">
<a id="newtokyo-noodlebar" class="a-link-button">Noodle Bar </a>
</li>
<li id="newtokyo-slums-li">
<a id="newtokyo-slums" class="a-link-button">The Slums</a>
</li>
</ul>
<ul id="ishima-locations-list">
<li id="ishima-travelagency-li">
<a id="ishima-travelagency" class="a-link-button">Travel Agency </a>
</li>
<li id="ishima-hospital-li">
<a id="ishima-hospital" class="a-link-button">Hospital</a>
</li>
<li id="ishima-stormtechnologies-li">
<a id="ishima-stormtechnologies" class="a-link-button">Storm Technologies</a>
</li>
<li id="ishima-novamedical-li">
<a id="ishima-novamedical" class="a-link-button">Nova Medical</a>
</li>
<li id="ishima-omegasoftware-li">
<a id="ishima-omegasoftware" class="a-link-button">Omega Software </a>
</li>
<li id="ishima-slums-li">
<a id="ishima-slums" class="a-link-button">The Slums</a>
</li>
</ul>
<ul id="volhaven-locations-list">
<li id="volhaven-travelagency-li">
<a id="volhaven-travelagency" class="a-link-button">Travel Agency </a>
</li>
<li id="volhaven-hospital-li">
<a id="volhaven-hospital" class="a-link-button">Hospital</a>
</li>
<li id="volhaven-zbinstituteoftechnology-li">
<a id="volhaven-zbinstituteoftechnology" class="a-link-button">ZB Insitute of Technology</a>
</li>
<li id="volhaven-omnitekincorporated-li">
<a id="volhaven-omnitekincorporated" class="a-link-button">OmniTek Incorporated </a>
</li>
<li id="volhaven-nwo-li">
<a id="volhaven-nwo" class="a-link-button">NWO</a>
</li>
<li id="volhaven-helislabs-li">
<a id="volhaven-helioslabs" class="a-link-button">Helios Labs</a>
</li>
<li id="volhaven-omniacybersystems-li">
<a id="volhaven-omniacybersystems" class="a-link-button">Omnia Cybersystems</a>
</li>
<li id="volhaven-lexocorp-li">
<a id="volhaven-lexocorp" class="a-link-button">LexoCorp</a>
</li>
<li id="volhaven-syscoresecurities-li">
<a id="volhaven-syscoresecurities" class="a-link-button">SysCore Securities</a>
</li>
<li id="volhaven-computek-li">
<a id="volhaven-computek" class="a-link-button">CompuTek</a>
</li>
<li id="volhaven-milleniumfitnessgym-li">
<a id="volhaven-milleniumfitnessgym" class="a-link-button">Millenium Fitness Gym</a>
</li>
<li id="volhaven-slums-li">
<a id="volhaven-slums" class="a-link-button">The Slums</a>
</li>
</ul>
<ul id="generic-locations-list"></ul>
</div>
<!-- Create a program(executable) -->
<div id="create-program-container" class="generic-menupage-container">
<p id="create-program-page-text">
@@ -478,150 +230,31 @@
<div id="tutorial-container" class="generic-menupage-container">
<h1> Tutorial (AKA Links to Documentation) </h1>
<a id="tutorial-getting-started-link" class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/guidesandtips/gettingstartedguideforbeginnerprogrammers.html">
Getting Started
</a><br><br>
Getting Started</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/servers.html">
Servers & Networking
</a><br><br>
Servers & Networking</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/hacking.html">
Hacking
</a><br><br>
Hacking</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/scripts.html">
Scripts
</a><br><br>
Scripts</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/netscript.html">
Netscript Programming Language
</a><br><br>
Netscript Programming Language</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/world.html">
Traveling
</a><br><br>
Traveling</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/companies.html">
Companies
</a><br><br>
Companies</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/infiltration.html">
Infiltration
</a><br><br>
Infiltration</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/factions.html">
Factions
</a><br><br>
Factions</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/basicgameplay/augmentations.html">
Augmentations
</a><br><br>
Augmentations</a><br><br>
<a class="a-link-button" target="_blank" href="https://bitburner.readthedocs.io/en/latest/shortcuts.html">
Keyboard Shortcuts
</a>
Keyboard Shortcuts</a>
</div>
<!-- Location (visiting a location in World) -->
<div id="location-container" class="generic-menupage-container">
<a id="location-return-to-world-button" class="a-link-button"> Return to World </a>
<h1 id="location-name"></h1>
<p id="location-info"> </p>
<p id="location-job-title"> </p>
<p id="location-text-divider-1"> --------------- </p>
<p id="location-job-reputation" class="tooltip"> </p>
<p id="location-text-divider-2"> --------------- </p>
<p id="location-company-favor" class="tooltip"> </p>
<p id="location-text-divider-3"> --------------- </p>
<!-- Jobs/Work at a company -->
<a id="location-software-job" class="a-link-button tooltip"> Apply for Software Job</a>
<a id="location-software-consultant-job" class="a-link-button tooltip"> Apply for Software Consultant Job</a>
<a id="location-it-job" class="a-link-button tooltip"> Apply for IT Job </a>
<a id="location-security-engineer-job" class="a-link-button tooltip"> Apply for Security Engineer Job</a>
<a id="location-network-engineer-job" class="a-link-button tooltip"> Apply for Network Engineer Job</a>
<a id="location-business-job" class="a-link-button tooltip"> Apply for Business Job</a>
<a id="location-business-consultant-job" class="a-link-button tooltip"> Apply for Business Consultant Job </a>
<a id="location-security-job" class="a-link-button tooltip"> Apply for Security Job</a>
<a id="location-agent-job" class="a-link-button tooltip"> Apply to be an Agent</a>
<a id="location-employee-job" class="a-link-button tooltip"> Apply to be an Employee </a>
<a id="location-parttime-employee-job" class="a-link-button tooltip"> Apply to be a Part-time Employee </a>
<a id="location-waiter-job" class="a-link-button tooltip"> Apply to be a Waiter</a>
<a id="location-parttime-waiter-job" class="a-link-button tooltip"> Apply to be a Part-time Waiter</a>
<a id="location-work" class="a-link-button"> Work </a>
<!-- Gym -->
<a id="location-gym-train-str" class="a-link-button">Train Strength</a>
<a id="location-gym-train-def" class="a-link-button">Train Defense </a>
<a id="location-gym-train-dex" class="a-link-button">Train Dexterity</a>
<a id="location-gym-train-agi" class="a-link-button">Train Agility</a>
<!-- Study/Take classes at a university -->
<a id="location-study-computer-science" class="a-link-button">Study Computer Science (free)</a>
<a id="location-data-structures-class" class="a-link-button">Take Data Structures course</a>
<a id="location-networks-class" class="a-link-button">Take Networks course</a>
<a id="location-algorithms-class" class="a-link-button">Take Algorithms course</a>
<a id="location-management-class" class="a-link-button">Take Management course</a>
<a id="location-leadership-class" class="a-link-button">Take Leadership course</a>
<!-- Purchase servers -->
<a id="location-purchase-2gb" class="a-link-button"> Purchase 2GB Server - $150,000</a>
<a id="location-purchase-4gb" class="a-link-button"> Purchase 4GB Server - $300,000</a>
<a id="location-purchase-8gb" class="a-link-button"> Purchase 8GB Server - $600,000</a>
<a id="location-purchase-16gb" class="a-link-button"> Purchase 16GB Server - $1,200,000</a>
<a id="location-purchase-32gb" class="a-link-button"> Purchase 32GB Server - $2,400,000</a>
<a id="location-purchase-64gb" class="a-link-button"> Purchase 64GB Server - $4,800,000</a>
<a id="location-purchase-128gb" class="a-link-button"> Purchase 128GB Server - $9,600,000</a>
<a id="location-purchase-256gb" class="a-link-button"> Purchase 256GB Server - $19,200,000</a>
<a id="location-purchase-512gb" class="a-link-button"> Purchase 512GB Server - $38,400,000</a>
<a id="location-purchase-1tb" class="a-link-button"> Purchase 1TB Server - $75,000,000</a>
<a id="location-purchase-tor" class="a-link-button"> Purchase TOR Router - $100,000</a>
<a id="location-purchase-home-ram" class="a-link-button"> Purchase additional RAM for Home computer </a>
<a id="location-purchase-home-cores" class="a-link-button"> Purchase additional Core for Home computer </a>
<!-- Infiltrate -->
<a id="location-infiltrate" class="a-link-button tooltip"> Infiltrate Company
<span class="tooltiptext">
Infiltrate this company's facility to try and steal their classified secrets!
Warning: You may end up hospitalized if you are unsuccessful!
</span>
</a>
<!-- Hospital -->
<a id="location-hospital-treatment" class="a-link-button"> Get Treatment for Wounds </a>
<!-- Travel agency -->
<p id="location-travel-agency-text">
From here, you can travel to any other city! A ticket costs $200,000.
</p>
<a id="location-travel-to-aevum" class="a-link-button"> Travel to Aevum </a>
<a id="location-travel-to-chongqing" class="a-link-button"> Travel to Chongqing</a>
<a id="location-travel-to-sector12" class="a-link-button"> Travel to Sector-12</a>
<a id="location-travel-to-newtokyo" class="a-link-button"> Travel to New Tokyo</a>
<a id="location-travel-to-ishima" class="a-link-button"> Travel to Ishima</a>
<a id="location-travel-to-volhaven" class="a-link-button"> Travel to Volhaven</a>
<!-- Slums -->
<p id="location-slums-description">
You have entered the Slums, a poverty-ridden district filled with gangs, criminals, and
other shadowy entities. The city's government and police have neglected this area for years...
<br/><br/><br/>
In the Slums, you can commit crimes to earn money and experience. Crime attempts are not always
successful. Your chance at successfully committing a crime is determined by your stats.
</p>
<a class="a-link-button tooltip" id="location-slums-shoplift"> Shoplift </a>
<a id="location-slums-rob-store" class="a-link-button tooltip"> Rob a store </a>
<a id="location-slums-mug" class="a-link-button tooltip"> Mug someone </a>
<a id="location-slums-larceny" class="a-link-button tooltip"> Commit Larceny </a>
<a id="location-slums-deal-drugs" class="a-link-button tooltip"> Deal Drugs </a>
<a id="location-slums-bond-forgery" class="a-link-button tooltip">Bond Forgery</a>
<a id="location-slums-traffic-arms" class="a-link-button tooltip">Traffick Illegal Arms</a>
<a id="location-slums-homicide" class="a-link-button tooltip">Homicide</a>
<a id="location-slums-gta" class="a-link-button tooltip"> Grand Theft Auto </a>
<a id="location-slums-kidnap" class="a-link-button tooltip"> Kidnap and Ransom </a>
<a id="location-slums-assassinate" class="a-link-button tooltip"> Assassinate </a>
<a id="location-slums-heist" class="a-link-button tooltip"> Heist </a>
<!-- City Hall -->
<a id="location-cityhall-create-corporation" class="a-link-button">Create a Corporation</a>
<!-- Bladeburner @ NSA -->
<a id="location-nsa-bladeburner" class="a-link-button">Bladeburner Division</a>
<!-- Re-sleeving @ VitaLife -->
<a id="location-vitalife-resleeve" class="a-link-button">Re-Sleeve</a>
</div>
<div id="infiltration-container" class="generic-menupage-container">

1090
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,9 +1,10 @@
import {post} from "./ui/postToTerminal";
import { IMap } from "./types";
import { post } from "./ui/postToTerminal";
let Aliases = {};
let GlobalAliases = {};
export let Aliases: IMap<string> = {};
export let GlobalAliases: IMap<string> = {};
function loadAliases(saveString) {
export function loadAliases(saveString: string): void {
if (saveString === "") {
Aliases = {};
} else {
@@ -11,7 +12,7 @@ function loadAliases(saveString) {
}
}
function loadGlobalAliases(saveString) {
export function loadGlobalAliases(saveString: string): void {
if (saveString === "") {
GlobalAliases = {};
} else {
@@ -20,7 +21,7 @@ function loadGlobalAliases(saveString) {
}
//Print all aliases to terminal
function printAliases() {
export function printAliases(): void {
for (var name in Aliases) {
if (Aliases.hasOwnProperty(name)) {
post("alias " + name + "=" + Aliases[name]);
@@ -34,7 +35,7 @@ function printAliases() {
}
//True if successful, false otherwise
function parseAliasDeclaration(dec,global=false) {
export function parseAliasDeclaration(dec: string, global: boolean=false) {
var re = /^([_|\w|!|%|,|@]+)="(.+)"$/;
var matches = dec.match(re);
if (matches == null || matches.length != 3) {return false;}
@@ -46,50 +47,53 @@ function parseAliasDeclaration(dec,global=false) {
return true;
}
function addAlias(name, value) {
if (name in GlobalAliases){
function addAlias(name: string, value: string): void {
if (name in GlobalAliases) {
delete GlobalAliases[name];
}
Aliases[name] = value;
}
function addGlobalAlias(name, value) {
function addGlobalAlias(name: string, value: string): void {
if (name in Aliases){
delete Aliases[name];
}
GlobalAliases[name] = value;
}
function getAlias(name) {
function getAlias(name: string): string | null {
if (Aliases.hasOwnProperty(name)) {
return Aliases[name];
}
return null;
}
function getGlobalAlias(name) {
function getGlobalAlias(name: string): string | null {
if (GlobalAliases.hasOwnProperty(name)) {
return GlobalAliases[name];
}
return null;
}
function removeAlias(name) {
export function removeAlias(name: string): boolean {
if (Aliases.hasOwnProperty(name)) {
delete Aliases[name];
return true;
}
if (GlobalAliases.hasOwnProperty(name)) {
delete GlobalAliases[name];
return true;
}
return false;
}
//Returns the original string with any aliases substituted in
//Aliases only applied to "whole words", one level deep
function substituteAliases(origCommand) {
var commandArray = origCommand.split(" ");
export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" ");
if (commandArray.length > 0){
// For the unalias command, dont substite
if (commandArray[0] === "unalias") { return commandArray.join(" "); }
@@ -112,6 +116,3 @@ function substituteAliases(origCommand) {
}
return commandArray.join(" ");
}
export {Aliases, GlobalAliases, printAliases, parseAliasDeclaration,
removeAlias, substituteAliases, loadAliases, loadGlobalAliases};

View File

@@ -2069,7 +2069,7 @@ function installAugmentations(cbScript=null) {
}
var runningScriptObj = new RunningScript(script, []); //No args
runningScriptObj.threads = 1; //Only 1 thread
home.runningScripts.push(runningScriptObj);
home.runScript(runningScriptObj, Player);
addWorkerScript(runningScriptObj, home);
}
}
@@ -2097,12 +2097,6 @@ function displayAugmentationsContent(contentEl) {
innerText:"Purchased Augmentations",
}));
//Bladeburner text, once mechanic is unlocked
var bladeburnerText = "\n";
if (Player.bitNodeN === 6 || hasBladeburnerSF) {
bladeburnerText = "Bladeburner Progress\n\n";
}
contentEl.appendChild(createElement("pre", {
width:"70%", whiteSpace:"pre-wrap", display:"block",
innerText:"Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to install them.\n" +
@@ -2114,7 +2108,6 @@ function displayAugmentationsContent(contentEl) {
"Hacknet Nodes\n" +
"Faction/Company reputation\n" +
"Stocks\n" +
bladeburnerText +
"Installing Augmentations lets you start over with the perks and benefits granted by all " +
"of the Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades " +
"on your home computer (but you will lose all programs besides NUKE.exe)."

View File

@@ -173,7 +173,24 @@ export function initBitNodes() {
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
"This Source-File also increases your hacking growth multipliers by: " +
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
BitNodes["BitNode9"] = new BitNode(9, "Do Androids Dream?", "COMING SOON");
BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
"When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
"became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
"powering the Hacknet, 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",
"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 " +
@@ -183,7 +200,7 @@ export function initBitNodes() {
"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>" +
"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>" +
"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>" +
@@ -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 " +
"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>" +
"Your hacking stat and experience gain are halved<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>" +
"Company wages are decreased by 50%<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 " +
"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>" +
"Level 1: 24%<br>" +
"Level 2: 36%<br>" +
"Level 3: 42%");
"Level 1: 32%<br>" +
"Level 2: 48%<br>" +
"Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"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 " +
@@ -245,9 +263,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
}
switch (p.bitNodeN) {
case 1: //Source Genesis (every multiplier is 1)
case 1: // Source Genesis (every multiplier is 1)
break;
case 2: //Rise of the Underworld
case 2: // Rise of the Underworld
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.ServerGrowthRate = 0.8;
BitNodeMultipliers.ServerMaxMoney = 0.2;
@@ -257,7 +275,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FactionWorkRepGain = 0.5;
BitNodeMultipliers.FactionPassiveRepGain = 0;
break;
case 3: //Corporatocracy
case 3: // Corporatocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.RepToDonateToFaction = 0.5;
BitNodeMultipliers.AugmentationRepCost = 3;
@@ -272,7 +290,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 2;
break;
case 4: //The Singularity
case 4: // The Singularity
BitNodeMultipliers.ServerMaxMoney = 0.15;
BitNodeMultipliers.ServerStartingMoney = 0.75;
BitNodeMultipliers.ScriptHackMoney = 0.2;
@@ -286,7 +304,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.75;
break;
case 5: //Artificial intelligence
case 5: // Artificial intelligence
BitNodeMultipliers.ServerMaxMoney = 2;
BitNodeMultipliers.ServerStartingSecurity = 2;
BitNodeMultipliers.ServerStartingMoney = 0.5;
@@ -299,7 +317,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5;
break;
case 6: //Bladeburner
case 6: // Bladeburner
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
BitNodeMultipliers.ServerMaxMoney = 0.4;
BitNodeMultipliers.ServerStartingMoney = 0.5;
@@ -314,7 +332,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
break;
case 7: //Bladeburner 2079
case 7: // Bladeburner 2079
BitNodeMultipliers.BladeburnerRank = 0.6;
BitNodeMultipliers.BladeburnerSkillCost = 2;
BitNodeMultipliers.AugmentationMoneyCost = 3;
@@ -334,7 +352,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
break;
case 8: //Ghost of Wall Street
case 8: // Ghost of Wall Street
BitNodeMultipliers.ScriptHackMoney = 0;
BitNodeMultipliers.ManualHackMoney = 0;
BitNodeMultipliers.CompanyWorkMoney = 0;
@@ -345,6 +363,27 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0;
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
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
@@ -369,9 +408,11 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.BladeburnerRank = 0.8;
break;
case 11: //The Big Crash
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerGrowthRate = 0.5;
BitNodeMultipliers.ServerGrowthRate = 0.2;
BitNodeMultipliers.ServerWeakenRate = 2;
BitNodeMultipliers.CrimeMoney = 3;
BitNodeMultipliers.CompanyWorkMoney = 0.5;
@@ -379,8 +420,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.InfiltrationMoney = 2.5;
BitNodeMultipliers.InfiltrationRep = 2.5;
BitNodeMultipliers.CorporationValuation = 0.01;
BitNodeMultipliers.CodingContractMoney = 0.5;
BitNodeMultipliers.CorporationValuation = 0.1;
BitNodeMultipliers.CodingContractMoney = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 4;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
break;

View File

@@ -120,7 +120,8 @@ interface IBitNodeMultipliers {
HackingLevelMultiplier: number;
/**
* Influences how much money each Hacknet node can generate.
* Influences how much money is produced by Hacknet Nodes.
* Influeces the hash rate of Hacknet Servers (unlocked in BitNode-9)
*/
HacknetNodeMoney: number;

View File

@@ -6,7 +6,6 @@ import { Engine } from "./engine";
import { Faction } from "./Faction/Faction";
import { Factions, factionExists } from "./Faction/Factions";
import { joinFaction, displayFactionContent } from "./Faction/FactionHelpers";
import { Locations } from "./Locations";
import { Player } from "./Player";
import { hackWorldDaemon, redPillFlag } from "./RedPill";
import { numeralWrapper } from "./ui/numeralFormat";
@@ -78,8 +77,8 @@ const RanksPerSkillPoint = 3; //How many ranks needed to get 1 Skill
const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber
const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber
const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber
const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber
//DOM related variables
var ActiveActionCssClass = "bladeburner-active-action";
@@ -218,7 +217,7 @@ $(document).keydown(function(event) {
});
function City(params={}) {
this.name = params.name ? params.name : Locations.Sector12;
this.name = params.name ? params.name : CityNames[2]; // Sector-12
//Synthoid population and estimate
this.pop = params.pop ? params.pop : getRandomInt(PopulationThreshold, 1.5 * PopulationThreshold);
@@ -690,7 +689,7 @@ function Bladeburner(params={}) {
for (var i = 0; i < CityNames.length; ++i) {
this.cities[CityNames[i]] = new City({name:CityNames[i]});
}
this.city = Locations.Sector12;
this.city = CityNames[2]; // Sector-12
//Map of SkillNames -> level
this.skills = {};
@@ -1417,14 +1416,17 @@ Bladeburner.prototype.completeAction = function() {
}
this.startAction(this.action); // Repeat Action
break;
case ActionTypes["Hyperbolic Regeneration Chamber"]:
case ActionTypes["Hyperbolic Regeneration Chamber"]: {
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);
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;
}
default:
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
break;
@@ -2513,7 +2515,7 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
display:"inline-block",
innerHTML:action.desc + "\n\n" +
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required (s): " + formatNumber(actionTime, 0) + "\n" +
"Contracts remaining: " + Math.floor(action.count) + "\n" +
"Successes: " + action.successes + "\n" +

View File

@@ -1,8 +1,11 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { CompanyPosition } from "./CompanyPosition";
import * as posNames from "./data/companypositionnames";
import { CONSTANTS } from "../Constants";
import { IMap } from "../types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
export interface IConstructorParams {
name: string;
info: string;
@@ -93,6 +96,43 @@ export class Company {
}
}
hasAgentPositions(): boolean {
return (this.companyPositions[posNames.AgentCompanyPositions[0]] != null);
}
hasBusinessConsultantPositions(): boolean {
return (this.companyPositions[posNames.BusinessConsultantCompanyPositions[0]] != null);
}
hasBusinessPositions(): boolean {
return (this.companyPositions[posNames.BusinessCompanyPositions[0]] != null);
}
hasEmployeePositions(): boolean {
return (this.companyPositions[posNames.MiscCompanyPositions[1]] != null);
}
hasITPositions(): boolean {
return (this.companyPositions[posNames.ITCompanyPositions[0]] != null);
}
hasSecurityPositions(): boolean {
return (this.companyPositions[posNames.SecurityCompanyPositions[2]] != null);
}
hasSoftwareConsultantPositions(): boolean {
return (this.companyPositions[posNames.SoftwareConsultantCompanyPositions[0]] != null);
}
hasSoftwarePositions(): boolean {
return (this.companyPositions[posNames.SoftwareCompanyPositions[0]] != null);
}
hasWaiterPositions(): boolean {
return (this.companyPositions[posNames.MiscCompanyPositions[0]] != null);
}
gainFavor(): void {
if (this.favor == null) { this.favor = 0; }
if (this.rolloverRep == null) { this.rolloverRep = 0; }

View File

@@ -1,5 +1,5 @@
import { CONSTANTS } from "../Constants";
import * as names from "./data/CompanyPositionNames";
import * as names from "./data/companypositionnames";
/* tslint:disable:completed-docs */

View File

@@ -3,7 +3,7 @@
import { CompanyPosition } from "./CompanyPosition";
import { CompanyPositions } from "./CompanyPositions";
export function getNextCompanyPosition(currPos: CompanyPosition | null): CompanyPosition | null {
export function getNextCompanyPositionHelper(currPos: CompanyPosition | null): CompanyPosition | null {
if (currPos == null) { return null; }
const nextPosName: string | null = currPos.nextPosition;

View File

@@ -1,7 +1,8 @@
import { IConstructorParams } from "../Company";
import { Locations } from "../../Locations";
import * as posNames from "./CompanyPositionNames";
import { IMap } from "../../types";
import * as posNames from "./companypositionnames";
import { IConstructorParams } from "../Company";
import { IMap } from "../../types";
import { LocationName } from "../../Locations/data/LocationNames";
// Create Objects containing Company Positions by category
// Will help in metadata construction later
@@ -89,7 +90,7 @@ CEOOnly[posNames.BusinessCompanyPositions[5]] = true;
// Metadata
export const companiesMetadata: IConstructorParams[] = [
{
name: Locations.AevumECorp,
name: LocationName.AevumECorp,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -101,7 +102,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 249,
},
{
name: Locations.Sector12MegaCorp,
name: LocationName.Sector12MegaCorp,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -113,7 +114,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 249,
},
{
name: Locations.AevumBachmanAndAssociates,
name: LocationName.AevumBachmanAndAssociates,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -125,7 +126,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.Sector12BladeIndustries,
name: LocationName.Sector12BladeIndustries,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -137,7 +138,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.VolhavenNWO,
name: LocationName.VolhavenNWO,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -149,7 +150,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 249,
},
{
name: Locations.AevumClarkeIncorporated,
name: LocationName.AevumClarkeIncorporated,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -161,7 +162,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.VolhavenOmniTekIncorporated,
name: LocationName.VolhavenOmniTekIncorporated,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -173,7 +174,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.Sector12FourSigma,
name: LocationName.Sector12FourSigma,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -185,7 +186,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.ChongqingKuaiGongInternational,
name: LocationName.ChongqingKuaiGongInternational,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -197,7 +198,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.AevumFulcrumTechnologies,
name: LocationName.AevumFulcrumTechnologies,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -208,7 +209,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.IshimaStormTechnologies,
name: LocationName.IshimaStormTechnologies,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -220,7 +221,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.NewTokyoDefComm,
name: LocationName.NewTokyoDefComm,
info: "",
companyPositions: Object.assign({},
CEOOnly,
@@ -232,7 +233,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.VolhavenHeliosLabs,
name: LocationName.VolhavenHeliosLabs,
info: "",
companyPositions: Object.assign({},
CEOOnly,
@@ -244,7 +245,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.NewTokyoVitaLife,
name: LocationName.NewTokyoVitaLife,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -256,7 +257,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.Sector12IcarusMicrosystems,
name: LocationName.Sector12IcarusMicrosystems,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -268,7 +269,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.Sector12UniversalEnergy,
name: LocationName.Sector12UniversalEnergy,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -280,7 +281,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.AevumGalacticCybersystems,
name: LocationName.AevumGalacticCybersystems,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -292,7 +293,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.AevumAeroCorp,
name: LocationName.AevumAeroCorp,
info: "",
companyPositions: Object.assign({},
CEOOnly,
@@ -305,7 +306,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.VolhavenOmniaCybersystems,
name: LocationName.VolhavenOmniaCybersystems,
info: "",
companyPositions: Object.assign({},
CEOOnly,
@@ -318,7 +319,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.ChongqingSolarisSpaceSystems,
name: LocationName.ChongqingSolarisSpaceSystems,
info: "",
companyPositions: Object.assign({},
CEOOnly,
@@ -331,7 +332,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.Sector12DeltaOne,
name: LocationName.Sector12DeltaOne,
info: "",
companyPositions: Object.assign({},
CEOOnly,
@@ -344,7 +345,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.NewTokyoGlobalPharmaceuticals,
name: LocationName.NewTokyoGlobalPharmaceuticals,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -357,7 +358,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 224,
},
{
name: Locations.IshimaNovaMedical,
name: LocationName.IshimaNovaMedical,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -370,7 +371,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 199,
},
{
name: Locations.Sector12CIA,
name: LocationName.Sector12CIA,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering,
@@ -385,7 +386,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 149,
},
{
name: Locations.Sector12NSA,
name: LocationName.Sector12NSA,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering,
@@ -400,7 +401,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 149,
},
{
name: Locations.AevumWatchdogSecurity,
name: LocationName.AevumWatchdogSecurity,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering,
@@ -415,7 +416,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 124,
},
{
name: Locations.VolhavenLexoCorp,
name: LocationName.VolhavenLexoCorp,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -428,7 +429,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 99,
},
{
name: Locations.AevumRhoConstruction,
name: LocationName.AevumRhoConstruction,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToLeadDeveloper,
@@ -439,7 +440,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 49,
},
{
name: Locations.Sector12AlphaEnterprises,
name: LocationName.Sector12AlphaEnterprises,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToLeadDeveloper,
@@ -451,7 +452,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 99,
},
{
name: Locations.AevumPolice,
name: LocationName.AevumPolice,
info: "",
companyPositions: Object.assign({},
AllSecurityPositions,
@@ -462,7 +463,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 99,
},
{
name: Locations.VolhavenSysCoreSecurities,
name: LocationName.VolhavenSysCoreSecurities,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions
@@ -472,7 +473,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 124,
},
{
name: Locations.VolhavenCompuTek,
name: LocationName.VolhavenCompuTek,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions
@@ -482,7 +483,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 74,
},
{
name: Locations.AevumNetLinkTechnologies,
name: LocationName.AevumNetLinkTechnologies,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions
@@ -492,7 +493,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 99,
},
{
name: Locations.Sector12CarmichaelSecurity,
name: LocationName.Sector12CarmichaelSecurity,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
@@ -505,7 +506,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 74,
},
{
name: Locations.Sector12FoodNStuff,
name: LocationName.Sector12FoodNStuff,
info: "",
companyPositions: Object.assign({},
EmployeeOnly, PartTimeEmployeeOnly
@@ -515,7 +516,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 0,
},
{
name: Locations.Sector12JoesGuns,
name: LocationName.Sector12JoesGuns,
info: "",
companyPositions: Object.assign({},
EmployeeOnly, PartTimeEmployeeOnly
@@ -525,7 +526,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 0,
},
{
name: Locations.IshimaOmegaSoftware,
name: LocationName.IshimaOmegaSoftware,
info: "",
companyPositions: Object.assign({},
AllSoftwarePositions,
@@ -537,7 +538,7 @@ export const companiesMetadata: IConstructorParams[] = [
jobStatReqOffset: 49,
},
{
name: Locations.NewTokyoNoodleBar,
name: LocationName.NewTokyoNoodleBar,
info: "",
companyPositions: Object.assign({},
WaiterOnly, PartTimeWaiterOnly

View File

@@ -1,6 +1,6 @@
// Metadata used for constructing Company Positions
import { IConstructorParams } from "../CompanyPosition";
import * as posNames from "./CompanyPositionNames";
import * as posNames from "./companypositionnames";
export const companyPositionMetadata: IConstructorParams[] = [
{

View File

@@ -1,7 +1,12 @@
/**
* Generic Game Constants
*
* Constants for specific mechanics or features will NOT be here.
*/
import {IMap} from "./types";
export let CONSTANTS: IMap<any> = {
Version: "0.45.1",
Version: "0.46.1",
//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
@@ -17,24 +22,9 @@ export let CONSTANTS: IMap<any> = {
/* Base costs */
BaseCostFor1GBOfRamHome: 32000,
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
BaseCostFor1GBOfRamHacknetNode: 30000,
TravelCost: 200e3,
BaseCostForHacknetNode: 1000,
BaseCostForHacknetNodeCore: 500000,
/* Hacknet Node constants */
HacknetNodeMoneyGainPerLevel: 1.6,
HacknetNodePurchaseNextMult: 1.85, //Multiplier when purchasing an additional hacknet node
HacknetNodeUpgradeLevelMult: 1.04, //Multiplier for cost when upgrading level
HacknetNodeUpgradeRamMult: 1.28, //Multiplier for cost when upgrading RAM
HacknetNodeUpgradeCoreMult: 1.48, //Multiplier for cost when buying another core
HacknetNodeMaxLevel: 200,
HacknetNodeMaxRam: 64,
HacknetNodeMaxCores: 16,
/* Faction and Company favor */
BaseFavorToDonate: 150,
DonateMoneyToRepDivisor: 1e6,
@@ -126,6 +116,7 @@ export let CONSTANTS: IMap<any> = {
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
InfiltrationExpPow: 0.8,
//Stock market constants
WSEAccountCost: 200e6,
@@ -282,37 +273,16 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.45.1
* Added two new Corporation Researches
* General UI improvements (by hydroflame and koriar)
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors
* Bug Fix: sleeve.getSleeveStats() should now work properly
v0.46.1
* Added a very rudimentary directory system to the Terminal
** Details here: https://bitburner.readthedocs.io/en/latest/basicgameplay/terminal.html#filesystem-directories
v0.45.0
* Corporation changes:
** Decreased the time of a full market cycle from 15 seconds to 10 seconds.
** This means that each Corporation 'state' will now only take 2 seconds, rather than 3
** Increased initial salaries for newly-hired employees
** Increased the cost multiplier for upgrading office size (the cost will increase faster)
** The stats of your employees now has a slightly larger effect on production & sales
** 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
* Added numHashes(), hashCost(), and spendHashes() functions to the Netscript Hacknet Node API
* 'Generate Coding Contract' hash upgrade is now more expensive
* 'Generate Coding Contract' hash upgrade now generates the contract randomly on the server, rather than on home computer
* The cost of selling hashes for money no longer increases each time
* Selling hashes for money now costs 4 hashes (in exchange for $1m)
* Bug Fix: Hacknet Node earnings should work properly when game is inactive/offline
* Bug Fix: Duplicate Sleeve augmentations are now properly reset when switching to a new BitNode
`
}

View File

@@ -18,8 +18,8 @@ import { BitNodeMultipliers } from "../BitNode/BitNode
import { CONSTANTS } from "../Constants";
import { Factions } from "../Faction/Factions";
import { showLiterature } from "../Literature";
import { Locations } from "../Locations";
import { createCityMap } from "../Locations/Cities";
import { CityName } from "../Locations/data/CityNames";
import { Player } from "../Player";
import { numeralWrapper } from "../ui/numeralFormat";
@@ -113,15 +113,15 @@ $(document).mousedown(function(event) {
var empManualAssignmentModeActive = false;
function Industry(params={}) {
this.offices = { //Maps locations to offices. 0 if no office at that location
[Locations.Aevum]: 0,
[Locations.Chongqing]: 0,
[Locations.Sector12]: new OfficeSpace({
loc:Locations.Sector12,
[CityName.Aevum]: 0,
[CityName.Chongqing]: 0,
[CityName.Sector12]: new OfficeSpace({
loc:CityName.Sector12,
size:OfficeInitialSize,
}),
[Locations.NewTokyo]: 0,
[Locations.Ishima]: 0,
[Locations.Volhaven]: 0
[CityName.NewTokyo]: 0,
[CityName.Ishima]: 0,
[CityName.Volhaven]: 0
};
this.name = params.name ? params.name : 0;
@@ -172,17 +172,17 @@ function Industry(params={}) {
this.newInd = true;
this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
[Locations.Aevum]: 0,
[Locations.Chonqing]: 0,
[Locations.Sector12]: new Warehouse({
[CityName.Aevum]: 0,
[CityName.Chonqing]: 0,
[CityName.Sector12]: new Warehouse({
corp: params.corp,
industry: this,
loc: Locations.Sector12,
loc: CityName.Sector12,
size: WarehouseInitialSize,
}),
[Locations.NewTokyo]: 0,
[Locations.Ishima]: 0,
[Locations.Volhaven]: 0
[CityName.NewTokyo]: 0,
[CityName.Ishima]: 0,
[CityName.Volhaven]: 0
};
this.init();
@@ -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) {
//Demand gradually decreases, and competition gradually increases
for (var name in this.products) {
// Demand gradually decreases, and competition gradually increases
for (const name in this.products) {
if (this.products.hasOwnProperty(name)) {
var product = this.products[name];
var change = getRandomInt(1, 3) * 0.0004;
const product = this.products[name];
let change = getRandomInt(0, 3) * 0.0004;
if (change === 0) { continue; }
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
this.type === Industries.Robotics) {
change *= 3;
@@ -770,7 +772,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
* advertisingFactor
* this.getSalesMultiplier());
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
// 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);
let optimalPrice;
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 {
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
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

View File

@@ -86,7 +86,7 @@ export class Material {
this.mku = 6;
break;
case "Energy":
this.dmd = 90; this.dmdR = [80, 100];
this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 80; this.cmpR = [65, 95];
this.bCost = 2000; this.mv = 0.2;
this.mku = 6;
@@ -122,26 +122,26 @@ export class Material {
this.mku = 2;
break;
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.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
this.mku = 1.5;
break;
case "Drugs":
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.mku = 1;
break;
case "Robots":
this.dmd = 90; this.dmdR = [80, 100];
this.cmp = 90; this.cmpR = [80, 100];
this.dmd = 90; this.dmdR = [80, 9];
this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
this.mku = 1;
break;
case "AI Cores":
this.dmd = 90; this.dmdR = [80, 100];
this.cmp = 90; this.cmpR = [80, 100];
this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
this.mku = 0.5;
break;

View File

@@ -3,11 +3,9 @@ import { MaterialSizes } from "./MaterialSizes";
import { ProductRatingWeights,
IProductRatingWeight } from "./ProductRatingWeights";
import { Cities } from "../Locations/Cities";
import { createCityMap } from "../Locations/createCityMap";
import { IMap } from "../types";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";

View File

@@ -31,6 +31,7 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
import { KEY } from "../../../utils/helpers/keyCodes";
import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
@@ -780,7 +781,12 @@ export class CorporationEventHandler {
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
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 {
// Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
@@ -1052,7 +1058,12 @@ export class CorporationEventHandler {
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
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 {
// Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
@@ -1406,7 +1417,7 @@ export class CorporationEventHandler {
}
// Array of all cities. Used later
const cities = Object.values(Cities);
const cities = Object.keys(Cities);
// Parse quantity
if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {

View File

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

View File

@@ -218,7 +218,7 @@ function MaterialComponent(props) {
mat.buy === 0 && mat.imp === 0;
// 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 purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
@@ -229,9 +229,9 @@ function MaterialComponent(props) {
let sellButtonText;
if (mat.sllman[0]) {
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 {
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) {
@@ -469,7 +469,7 @@ export class IndustryWarehouse extends BaseReactComponent {
return (
<div className={"cmpy-mgmt-warehouse-panel"}>
<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>
</p>

View File

@@ -11,7 +11,7 @@ import { overviewPage } from "./Routing";
import { OfficeSpace } from "../Corporation";
import { Cities } from "../../Locations/Cities";
import { CityName } from "../../Locations/data/CityNames";
export class MainPanel extends BaseReactComponent {
constructor(props) {
@@ -19,13 +19,13 @@ export class MainPanel extends BaseReactComponent {
this.state = {
division: "",
city: Cities.Sector12,
city: CityName.Sector12,
}
}
// We can pass this setter to child components
changeCityState(newCity) {
if (Object.values(Cities).includes(newCity)) {
if (Object.values(CityName).includes(newCity)) {
this.state.city = newCity;
} else {
console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
@@ -45,7 +45,7 @@ export class MainPanel extends BaseReactComponent {
const currentDivision = this.routing().current();
if (currentDivision !== this.state.division) {
this.state.division = currentDivision;
this.state.city = Cities.Sector12;
this.state.city = CityName.Sector12;
}
return this.renderDivisionPage();

View File

@@ -54,7 +54,7 @@ export class Overview extends BaseReactComponent {
`Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s<br>` +
`Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s<br>` +
`Dividend Tax Rate: ${this.corp().dividendTaxPercentage}%<br>` +
`Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s<br>`;
`Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s<br><br>`;
}
let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + "<br>" +

View File

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

1
src/Hacking/README.md Normal file
View File

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

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

View File

@@ -0,0 +1,418 @@
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 { generateRandomContract } 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 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 = Player.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(numCycles);
} else if (Player.hacknetNodes[0] instanceof HacknetNode) {
return processAllHacknetNodeEarnings(numCycles);
} 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": {
generateRandomContract();
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
View 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;

View File

@@ -0,0 +1,350 @@
/**
* Hacknet Servers - Reworked Hacknet Node mechanic for BitNode-9
*/
import { CONSTANTS } from "../Constants";
import { IHacknetNode } from "./IHacknetNode";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { BaseServer } from "../Server/BaseServer";
import { RunningScript } from "../Script/RunningScript";
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;

177
src/Hacknet/HashManager.ts Normal file
View File

@@ -0,0 +1,177 @@
/**
* 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];
if (upg == null) {
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
return false;
}
const cost = this.getUpgradeCost(upgName);
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;

View File

@@ -0,0 +1,61 @@
/**
* Object representing an upgrade that can be purchased with hashes
*/
export interface IConstructorParams {
cost?: number;
costPerLevel: number;
desc: string;
hasTargetServer?: boolean;
name: string;
value: number;
}
export class HashUpgrade {
/**
* If the upgrade has a flat cost (never increases), it goes here
* Otherwise, this property should be undefined
*
* This property overrides the 'costPerLevel' property
*/
cost?: number;
/**
* 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) {
if (p.cost != null) { this.cost = p.cost; }
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 {
if (typeof this.cost === "number") { return this.cost; }
return Math.round((levels + 1) * this.costPerLevel);
}
}

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

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

View File

@@ -0,0 +1,71 @@
// Metadata used to construct all Hash Upgrades
import { IConstructorParams } from "../HashUpgrade";
export const HashUpgradesMetadata: IConstructorParams[] = [
{
cost: 4,
costPerLevel: 4,
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: 200,
desc: "Generate a random Coding Contract somewhere on the network",
name: "Generate Coding Contract",
value: 1,
},
]

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

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

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

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

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

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

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

View File

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

10
src/IEngine.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* TypeScript interface for the game engine (engine.js), which can't be converted
* to TypeScript at the moment
*/
export interface IEngine {
loadBladeburnerContent: () => void;
loadInfiltrationContent: () => void;
loadResleevingContent: () => void;
loadStockMarketContent: () => void;
}

7
src/Infiltration.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { LocationName } from "./Locations/data/LocationNames";
export declare function beginInfiltration(companyName: LocationName,
startLevel: number,
rewardVal: number,
maxClearance: number,
diff: number): void;

View File

@@ -52,41 +52,81 @@ function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff)
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) {
if (isNaN(amt)) {return;}
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) {
if (isNaN(amt)) {return;}
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) {
if (isNaN(amt)) {return;}
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) {
if (isNaN(amt)) {return;}
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) {
if (isNaN(amt)) {return;}
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) {
if (isNaN(amt)) {return;}
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) {
if (isNaN(amt)) {return;}
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) {
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
clearInfiltrationStatusText();
@@ -477,12 +517,12 @@ function updateInfiltrationLevelText(inst) {
"Total value of stolen secrets<br>" +
"Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "<br>" +
"Str exp gained:   " + formatNumber(inst.strExpGained * expMultiplier, 3) + "<br>" +
"Def exp gained:   " + formatNumber(inst.defExpGained * expMultiplier, 3) + "<br>" +
"Dex exp gained:   " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "<br>" +
"Agi exp gained:   " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "<br>" +
"Cha exp gained:   " + formatNumber(inst.chaExpGained * expMultiplier, 3);
"Hack exp gained:  " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + formatNumber(inst.calcGainedCharismaExp(), 3);
/* eslint-enable no-irregular-whitespace */
}

View File

@@ -382,13 +382,13 @@ function iTutorialEvaluateStep() {
//Flash 'City' menu and set its tutorial click handler
cityMainMenu.setAttribute("class", "flashing-button");
cityMainMenu.addEventListener("click", function() {
Engine.loadWorldContent();
Engine.loadLocationContent();
iTutorialNextStep();
return false;
});
break;
case iTutorialSteps.WorldDescription:
Engine.loadWorldContent();
Engine.loadLocationContent();
iTutorialSetText("This page lists all of the different locations you can currently " +
"travel to. Each location has something that you can do. " +
"There's a lot of content out in the world, make sure " +

File diff suppressed because it is too large Load Diff

View File

@@ -1,90 +0,0 @@
import { IMap } from "./types";
/**
* Display Location Content when visiting somewhere in the World
*/
// tslint:disable-next-line:variable-name
export const Locations: IMap<string> = {
// Cities
Aevum: "Aevum",
Chongqing: "Chongqing",
Ishima: "Ishima",
NewTokyo: "New Tokyo",
Sector12: "Sector-12",
Volhaven: "Volhaven",
// Aevum Locations
AevumAeroCorp: "AeroCorp",
AevumBachmanAndAssociates: "Bachman & Associates",
AevumClarkeIncorporated: "Clarke Incorporated",
AevumCrushFitnessGym: "Crush Fitness Gym",
AevumECorp: "ECorp",
AevumFulcrumTechnologies: "Fulcrum Technologies",
AevumGalacticCybersystems: "Galactic Cybersystems",
AevumNetLinkTechnologies: "NetLink Technologies",
AevumPolice: "Aevum Police Headquarters",
AevumRhoConstruction: "Rho Construction",
AevumSlums: "Aevum Slums",
AevumSnapFitnessGym: "Snap Fitness Gym",
AevumSummitUniversity: "Summit University",
AevumTravelAgency: "Aevum Travel Agency",
AevumWatchdogSecurity: "Watchdog Security",
// Chongqing locations
ChongqingKuaiGongInternational: "KuaiGong International",
ChongqingSlums: "Chongqing Slums",
ChongqingSolarisSpaceSystems: "Solaris Space Systems",
ChongqingTravelAgency: "Chongqing Travel Agency",
// Sector 12
Sector12AlphaEnterprises: "Alpha Enterprises",
Sector12BladeIndustries: "Blade Industries",
Sector12CIA: "Central Intelligence Agency",
Sector12CarmichaelSecurity: "Carmichael Security",
Sector12CityHall: "Sector-12 City Hall",
Sector12DeltaOne: "DeltaOne",
Sector12FoodNStuff: "FoodNStuff",
Sector12FourSigma: "Four Sigma",
Sector12IcarusMicrosystems: "Icarus Microsystems",
Sector12IronGym: "Iron Gym",
Sector12JoesGuns: "Joe's Guns",
Sector12MegaCorp: "MegaCorp",
Sector12NSA: "National Security Agency",
Sector12PowerhouseGym: "Powerhouse Gym",
Sector12RothmanUniversity: "Rothman University",
Sector12Slums: "Sector-12 Slums",
Sector12TravelAgency: "Sector-12 Travel Agency",
Sector12UniversalEnergy: "Universal Energy",
// New Tokyo
NewTokyoDefComm: "DefComm",
NewTokyoGlobalPharmaceuticals: "Global Pharmaceuticals",
NewTokyoNoodleBar: "Noodle Bar",
NewTokyoSlums: "New Tokyo Slums",
NewTokyoTravelAgency: "New Tokyo Travel Agency",
NewTokyoVitaLife: "VitaLife",
// Ishima
IshimaNovaMedical: "Nova Medical",
IshimaOmegaSoftware: "Omega Software",
IshimaSlums: "Ishima Slums",
IshimaStormTechnologies: "Storm Technologies",
IshimaTravelAgency: "Ishima Travel Agency",
// Volhaven
VolhavenCompuTek: "CompuTek",
VolhavenHeliosLabs: "Helios Labs",
VolhavenLexoCorp: "LexoCorp",
VolhavenMilleniumFitnessGym: "Millenium Fitness Gym",
VolhavenNWO: "NWO",
VolhavenOmniTekIncorporated: "OmniTek Incorporated",
VolhavenOmniaCybersystems: "Omnia Cybersystems",
VolhavenSlums: "Volhaven Slums",
VolhavenSysCoreSecurities: "SysCore Securities",
VolhavenTravelAgency: "Volhaven Travel Agency",
VolhavenZBInstituteOfTechnology: "ZB Institute of Technology",
// Generic locations
Hospital: "Hospital",
WorldStockExchange: "World Stock Exchange",
};

View File

@@ -1,14 +1,8 @@
/**
* Map of all Cities in the game
* Key = City Name, Value = City object
*/
import { City } from "./City";
import { IMap } from "../types";
/**
* Display Location Content when visiting somewhere in the World
*/
// tslint:disable-next-line:variable-name
export const Cities: IMap<string> = {
Aevum: "Aevum",
Chongqing: "Chongqing",
Ishima: "Ishima",
NewTokyo: "New Tokyo",
Sector12: "Sector-12",
Volhaven: "Volhaven",
};
export const Cities: IMap<City> = {};

26
src/Locations/City.ts Normal file
View File

@@ -0,0 +1,26 @@
/**
* Class representing a City in the game
*/
import { CityName } from "./data/CityNames";
import { LocationName } from "./data/LocationNames";
export class City {
/**
* List of all locations in this city
*/
locations: LocationName[];
/**
* Name of this city
*/
name: CityName;
constructor(name: CityName, locations: LocationName[]=[]) {
this.name = name;
this.locations = locations;
}
addLocation(loc: LocationName): void {
this.locations.push(loc);
}
}

82
src/Locations/Location.ts Normal file
View File

@@ -0,0 +1,82 @@
/**
* Class representing a visitable location in the world
*/
import { CityName } from "./data/CityNames";
import { LocationName } from "./data/LocationNames";
import { LocationType } from "./LocationTypeEnum";
interface IInfiltrationMetadata {
baseRewardValue: number;
difficulty: number;
maxClearanceLevel: number;
startingSecurityLevel: number;
}
export interface IConstructorParams {
city?: CityName | null;
costMult?: number;
expMult?: number;
infiltrationData?: IInfiltrationMetadata;
name?: LocationName;
types?: LocationType[];
techVendorMaxRam?: number;
techVendorMinRam?: number;
}
export class Location {
/**
* Name of city this location is in. If this property is null, it means this i
* is a generic location that is available in all cities
*/
city: CityName | null = null;
/**
* Cost multiplier that influences how expensive a gym/university is
*/
costMult: number = 0;
/**
* Exp multiplier that influences how effective a gym/university is
*/
expMult: number = 0;
/**
* Companies can be infiltrated. This contains the data required for that
* infiltration event
*/
infiltrationData?: IInfiltrationMetadata;
/**
* Identifier for location
*/
name: LocationName = LocationName.Void;
/**
* List of what type(s) this location is. A location can be multiple types
* (e.g. company and tech vendor)
*/
types: LocationType[] = [];
/**
* Tech vendors allow you to purchase servers.
* This property defines the max RAM server you can purchase from this vendor
*/
techVendorMaxRam: number = 0;
/**
* Tech vendors allow you to purchase servers.
* This property defines the max RAM server you can purchase from this vendor
*/
techVendorMinRam: number = 0;
constructor(p: IConstructorParams) {
if (p.city) { this.city = p.city; }
if (p.costMult) { this.costMult = p.costMult; }
if (p.expMult) { this.expMult = p.expMult; }
if (p.infiltrationData) { this.infiltrationData = p.infiltrationData; }
if (p.name) { this.name = p.name; }
if (p.types) { this.types = p.types; }
if (p.techVendorMaxRam) { this.techVendorMaxRam = p.techVendorMaxRam; }
if (p.techVendorMinRam) { this.techVendorMinRam = p.techVendorMinRam; }
}
}

View File

@@ -0,0 +1,14 @@
/**
* Enum defining the different types of possible locations
*/
export enum LocationType {
Company,
Gym,
Hospital,
Slums,
Special, // This location has special options/activities (e.g. Bladeburner, Re-sleeving)
StockMarket,
TechVendor,
TravelAgency,
University,
}

View File

@@ -0,0 +1,56 @@
/**
* Map of all Locations in the game
* Key = Location name, value = Location object
*/
import { City } from "./City";
import { Cities } from "./Cities";
import { Location,
IConstructorParams } from "./Location";
import { CityName } from "./data/CityNames";
import { LocationsMetadata } from "./data/LocationsMetadata";
import { IMap } from "../types";
export const Locations: IMap<Location> = {};
/**
* Here, we'll initialize both Locations and Cities data. These can both
* be initialized from the `LocationsMetadata`
*/
function constructLocation(p: IConstructorParams): Location {
if (!p.name) {
throw new Error(`Invalid constructor parameters for Location. No 'name' property`);
}
if (Locations[p.name] instanceof Location) {
console.warn(`Property with name ${p.name} already exists and is being overwritten`);
}
Locations[p.name] = new Location(p);
return Locations[p.name];
}
// First construct all cities
Cities[CityName.Aevum] = new City(CityName.Aevum);
Cities[CityName.Chongqing] = new City(CityName.Chongqing);
Cities[CityName.Ishima] = new City(CityName.Ishima);
Cities[CityName.NewTokyo] = new City(CityName.NewTokyo);
Cities[CityName.Sector12] = new City(CityName.Sector12);
Cities[CityName.Volhaven] = new City(CityName.Volhaven);
// Then construct all locations, and add them to the cities as we go.
for (const metadata of LocationsMetadata) {
const loc = constructLocation(metadata);
const cityName = loc.city;
if (cityName === null) {
// Generic location, add to all cities
for (const city in Cities) {
Cities[city].addLocation(loc.name);
}
} else {
Cities[cityName].addLocation(loc.name);
}
}

View File

@@ -0,0 +1,282 @@
/**
* Location and traveling-related helper functions.
* Mostly used for UI
*/
import { CONSTANTS } from "../Constants";
import { CityName } from "./data/CityNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers,
AddToAllServers } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { getPurchaseServerCost,
purchaseRamForHomeComputer,
purchaseServer } from "../Server/ServerPurchases";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { Settings } from "../Settings/Settings";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { createRandomIp } from "../../utils/IPAddress";
import { yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose,
yesNoBoxCreate,
yesNoTxtInpBoxGetYesButton,
yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxClose,
yesNoTxtInpBoxCreate } from "../../utils/YesNoBox";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createPopup } from "../../utils/uiHelpers/createPopup";
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
/**
* Create a pop-up box that lets the player confirm traveling to a different city
* If settings are configured to suppress this popup, just instantly travel
* The actual "Travel" implementation is implemented in the UI, and is passed in
* as an argument
*/
type TravelFunction = (to: CityName) => void;
export function createTravelPopup(destination: CityName, travelFn: TravelFunction) {
const cost = CONSTANTS.TravelCost;
if (Settings.SuppressTravelConfirmation) {
travelFn(destination);
return;
}
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) {
console.warn(`Could nto find YesNo pop-up box buttons`);
return;
}
yesBtn.innerHTML = "Yes";
yesBtn.addEventListener("click", () => {
yesNoBoxClose();
travelFn(destination);
return false;
});
noBtn.innerHTML = "No";
noBtn.addEventListener("click", () => {
yesNoBoxClose();
return false;
});
yesNoBoxCreate(`Would you like to travel to ${destination}? The trip will ` +
`cost ${numeralWrapper.formatMoney(cost)}`);
}
/**
* Create a pop-up box that lets the player purchase a server.
* @param ram - Amount of RAM (GB) on server
* @param p - Player object
*/
export function createPurchaseServerPopup(ram: number, p: IPlayer) {
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
dialogBoxCreate("Something went wrong when trying to purchase this server. Please contact developer");
return;
}
var yesBtn = yesNoTxtInpBoxGetYesButton();
var noBtn = yesNoTxtInpBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; }
yesBtn.innerHTML = "Purchase Server";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", function() {
purchaseServer(ram, p);
yesNoTxtInpBoxClose();
});
noBtn.addEventListener("click", function() {
yesNoTxtInpBoxClose();
});
yesNoTxtInpBoxCreate(
`Would you like to purchase a new server with ${ram} GB of RAM for ` +
`${numeralWrapper.formatMoney(cost)}?<br><br>Please enter the server hostname below:<br>`
);
}
/**
* Create a popup that lets the player start a Corporation
*/
export function createStartCorporationPopup(p: IPlayer) {
if (!p.canAccessCorporation() || p.hasCorporation) { return; }
const popupId = "create-corporation-popup";
const txt = createElement("p", {
innerHTML: "Would you like to start a corporation? This will require $150b for registration " +
"and initial funding. This $150b can either be self-funded, or you can obtain " +
"the seed money from the government in exchange for 500 million shares<br><br>" +
"If you would like to start one, please enter a name for your corporation below:",
});
const nameInput = createElement("input", {
placeholder: "Corporation Name",
}) as HTMLInputElement;
const selfFundedButton = createElement("button", {
class: "popup-box-button",
innerText: "Self-Fund",
clickListener: () => {
if (!p.canAfford(150e9)) {
dialogBoxCreate("You don't have enough money to create a corporation! You need $150b");
return false;
}
p.loseMoney(150e9);
const companyName = nameInput.value;
if (companyName == null || companyName == "") {
dialogBoxCreate("Invalid company name!");
return false;
}
p.startCorporation(companyName);
const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) {
worldHeader.click(); worldHeader.click();
}
dialogBoxCreate("Congratulations! You just self-funded your own corporation. You can visit " +
"and manage your company in the City");
removeElementById(popupId);
return false;
}
});
const seedMoneyButton = createElement("button", {
class: "popup-box-button",
innerText: "Use Seed Money",
clickListener: () => {
const companyName = nameInput.value;
if (companyName == null || companyName == "") {
dialogBoxCreate("Invalid company name!");
return false;
}
p.startCorporation(companyName, 500e6);
const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) {
worldHeader.click(); worldHeader.click();
}
dialogBoxCreate("Congratulations! You just started your own corporation with government seed money. " +
"You can visit and manage your company in the City");
removeElementById(popupId);
return false;
}
})
const cancelBtn = createPopupCloseButton(popupId, { class: "popup-box-button" });
createPopup(popupId, [txt, nameInput, cancelBtn, selfFundedButton, seedMoneyButton]);
nameInput.focus();
}
/**
* Create a popup that lets the player upgrade the cores on his/her home computer
* @param p - Player object
*/
export function createUpgradeHomeCoresPopup(p: IPlayer) {
const currentCores = p.getHomeComputer().cpuCores;
if (currentCores >= 8) { return; } // Max of 8 cores
//Cost of purchasing another cost is found by indexing this array with number of current cores
const allCosts = [0,
10e9, // 1->2 Cores - 10 bn
250e9, // 2->3 Cores - 250 bn
5e12, // 3->4 Cores - 5 trillion
100e12, // 4->5 Cores - 100 trillion
1e15, // 5->6 Cores - 1 quadrillion
20e15, // 6->7 Cores - 20 quadrillion
200e15]; // 7->8 Cores - 200 quadrillion
const cost: number = allCosts[currentCores];
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; }
yesBtn.innerHTML = "Purchase";
yesBtn.addEventListener("click", ()=>{
if (!p.canAfford(cost)) {
dialogBoxCreate("You do not have enough money to purchase an additional CPU Core for your home computer!");
} else {
p.loseMoney(cost);
p.getHomeComputer().cpuCores++;
dialogBoxCreate("You purchased an additional CPU Core for your home computer! It now has " +
p.getHomeComputer().cpuCores + " cores.");
}
yesNoBoxClose();
});
noBtn.innerHTML = "Cancel";
noBtn.addEventListener("click", ()=>{
yesNoBoxClose();
});
yesNoBoxCreate("Would you like to purchase an additional CPU Core for your home computer? Each CPU Core " +
"lets you start with an additional Core Node in Hacking Missions.<br><br>" +
"Purchasing an additional core (for a total of " + (p.getHomeComputer().cpuCores + 1) + ") will " +
"cost " + numeralWrapper.formatMoney(cost));
}
/**
* Create a popup that lets the player upgrade the RAM on his/her home computer
* @param p - Player object
*/
export function createUpgradeHomeRamPopup(p: IPlayer) {
const cost: number = p.getUpgradeHomeRamCost();
const ram: number = p.getHomeComputer().maxRam;
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; }
yesBtn.innerText = "Purchase";
yesBtn.addEventListener("click", ()=>{
purchaseRamForHomeComputer(cost, p);
yesNoBoxClose();
});
noBtn.innerText = "Cancel";
noBtn.addEventListener("click", ()=>{
yesNoBoxClose();
});
yesNoBoxCreate("Would you like to purchase additional RAM for your home computer? <br><br>" +
"This will upgrade your RAM from " + ram + "GB to " + ram*2 + "GB. <br><br>" +
"This will cost " + numeralWrapper.format(cost, '$0.000a'));
}
/**
* Attempt to purchase a TOR router
* @param p - Player object
*/
export function purchaseTorRouter(p: IPlayer) {
if (!p.canAfford(CONSTANTS.TorRouterCost)) {
dialogBoxCreate("You cannot afford to purchase the Tor router");
return;
}
p.loseMoney(CONSTANTS.TorRouterCost);
const darkweb = new Server({
ip: createRandomIp(), hostname:"darkweb", organizationName:"",
isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1
});
AddToAllServers(darkweb);
SpecialServerIps.addIp("Darkweb Server", darkweb.ip);
p.getHomeComputer().serversOnNetwork.push(darkweb.ip);
darkweb.serversOnNetwork.push(p.getHomeComputer().ip);
dialogBoxCreate("You have purchased a Tor router!<br>" +
"You now have access to the dark web from your home computer<br>" +
"Use the scan/scan-analyze commands to search for the dark web connection.");
}

View File

@@ -9,7 +9,7 @@ import { IMap } from "../types";
export function createCityMap<T>(initValue: T): IMap<T> {
const map: IMap<any> = {};
const cities = Object.values(Cities);
const cities = Object.keys(Cities);
for (let i = 0; i < cities.length; ++i) {
map[cities[i]] = initValue;
}

View File

@@ -0,0 +1,12 @@
/**
* All possible Cities in the game. Names only, not actual "City" object
* Implemented as an enum for typing purposes
*/
export enum CityName {
Aevum = "Aevum",
Chongqing = "Chongqing",
Ishima = "Ishima",
NewTokyo = "New Tokyo",
Sector12 = "Sector-12",
Volhaven = "Volhaven",
};

View File

@@ -0,0 +1,80 @@
/**
* Names of all locations
*/
export enum LocationName {
// Cities
Aevum = "Aevum",
Chongqing = "Chongqing",
Ishima = "Ishima",
NewTokyo = "New Tokyo",
Sector12 = "Sector-12",
Volhaven = "Volhaven",
// Aevum Locations
AevumAeroCorp = "AeroCorp",
AevumBachmanAndAssociates = "Bachman & Associates",
AevumClarkeIncorporated = "Clarke Incorporated",
AevumCrushFitnessGym = "Crush Fitness Gym",
AevumECorp = "ECorp",
AevumFulcrumTechnologies = "Fulcrum Technologies",
AevumGalacticCybersystems = "Galactic Cybersystems",
AevumNetLinkTechnologies = "NetLink Technologies",
AevumPolice = "Aevum Police Headquarters",
AevumRhoConstruction = "Rho Construction",
AevumSnapFitnessGym = "Snap Fitness Gym",
AevumSummitUniversity = "Summit University",
AevumWatchdogSecurity = "Watchdog Security",
// Chongqing locations
ChongqingKuaiGongInternational = "KuaiGong International",
ChongqingSolarisSpaceSystems = "Solaris Space Systems",
// Sector 12
Sector12AlphaEnterprises = "Alpha Enterprises",
Sector12BladeIndustries = "Blade Industries",
Sector12CIA = "Central Intelligence Agency",
Sector12CarmichaelSecurity = "Carmichael Security",
Sector12CityHall = "Sector-12 City Hall",
Sector12DeltaOne = "DeltaOne",
Sector12FoodNStuff = "FoodNStuff",
Sector12FourSigma = "Four Sigma",
Sector12IcarusMicrosystems = "Icarus Microsystems",
Sector12IronGym = "Iron Gym",
Sector12JoesGuns = "Joe's Guns",
Sector12MegaCorp = "MegaCorp",
Sector12NSA = "National Security Agency",
Sector12PowerhouseGym = "Powerhouse Gym",
Sector12RothmanUniversity = "Rothman University",
Sector12UniversalEnergy = "Universal Energy",
// New Tokyo
NewTokyoDefComm = "DefComm",
NewTokyoGlobalPharmaceuticals = "Global Pharmaceuticals",
NewTokyoNoodleBar = "Noodle Bar",
NewTokyoVitaLife = "VitaLife",
// Ishima
IshimaNovaMedical = "Nova Medical",
IshimaOmegaSoftware = "Omega Software",
IshimaStormTechnologies = "Storm Technologies",
// Volhaven
VolhavenCompuTek = "CompuTek",
VolhavenHeliosLabs = "Helios Labs",
VolhavenLexoCorp = "LexoCorp",
VolhavenMilleniumFitnessGym = "Millenium Fitness Gym",
VolhavenNWO = "NWO",
VolhavenOmniTekIncorporated = "OmniTek Incorporated",
VolhavenOmniaCybersystems = "Omnia Cybersystems",
VolhavenSysCoreSecurities = "SysCore Securities",
VolhavenZBInstituteOfTechnology = "ZB Institute of Technology",
// Generic locations
Hospital = "Hospital",
Slums = "The Slums",
TravelAgency = "Travel Agency",
WorldStockExchange = "World Stock Exchange",
// Default name for Location objects
Void = "The Void",
};

View File

@@ -0,0 +1,502 @@
/**
* Metadata for constructing Location objects for all Locations
* in the game
*/
import { CityName } from "./CityNames";
import { LocationName } from "./LocationNames";
import { IConstructorParams } from "../Location";
import { LocationType } from "../LocationTypeEnum";
export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 32,
difficulty: 4.4,
maxClearanceLevel: 50,
startingSecurityLevel: 1350,
},
name: LocationName.AevumAeroCorp,
types: [LocationType.Company],
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 42,
difficulty: 4.1,
maxClearanceLevel: 60,
startingSecurityLevel: 1350,
},
name: LocationName.AevumBachmanAndAssociates,
types: [LocationType.Company],
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 34,
difficulty: 3.6,
maxClearanceLevel: 75,
startingSecurityLevel: 1800,
},
name: LocationName.AevumClarkeIncorporated,
types: [LocationType.Company],
},
{
city: CityName.Aevum,
costMult: 3,
expMult: 2,
name: LocationName.AevumCrushFitnessGym,
types: [LocationType.Gym],
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 116,
difficulty: 6,
maxClearanceLevel: 150,
startingSecurityLevel: 4800,
},
name: LocationName.AevumECorp,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 512,
techVendorMinRam: 128,
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 96,
difficulty: 6.2,
maxClearanceLevel: 100,
startingSecurityLevel: 4140,
},
name: LocationName.AevumFulcrumTechnologies,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 1024,
techVendorMinRam: 256,
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 30,
difficulty: 3.95,
maxClearanceLevel: 50,
startingSecurityLevel: 1260,
},
name: LocationName.AevumGalacticCybersystems,
types: [LocationType.Company],
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 10,
difficulty: 1.4,
maxClearanceLevel: 25,
startingSecurityLevel: 144,
},
name: LocationName.AevumNetLinkTechnologies,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 64,
techVendorMinRam: 8,
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 18,
difficulty: 2.2,
maxClearanceLevel: 25,
startingSecurityLevel: 565,
},
name: LocationName.AevumPolice,
types: [LocationType.Company],
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 16,
difficulty: 1.9,
maxClearanceLevel: 20,
startingSecurityLevel: 485,
},
name: LocationName.AevumRhoConstruction,
types: [LocationType.Company],
},
{
city: CityName.Aevum,
costMult: 10,
expMult: 5,
name: LocationName.AevumSnapFitnessGym,
types: [LocationType.Gym],
},
{
city: CityName.Aevum,
costMult: 4,
expMult: 3,
name: LocationName.AevumSummitUniversity,
types: [LocationType.University],
},
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 20,
difficulty: 3,
maxClearanceLevel: 30,
startingSecurityLevel: 690,
},
name: LocationName.AevumWatchdogSecurity,
types: [LocationType.Company],
},
{
city: CityName.Chongqing,
infiltrationData: {
baseRewardValue: 100,
difficulty: 6.1,
maxClearanceLevel: 100,
startingSecurityLevel: 4450,
},
name: LocationName.ChongqingKuaiGongInternational,
types: [LocationType.Company],
},
{
city: CityName.Chongqing,
infiltrationData: {
baseRewardValue: 52,
difficulty: 6,
maxClearanceLevel: 75,
startingSecurityLevel: 2915,
},
name: LocationName.ChongqingSolarisSpaceSystems,
types: [LocationType.Company],
},
{
city: CityName.Ishima,
infiltrationData: {
baseRewardValue: 20,
difficulty: 3.2,
maxClearanceLevel: 50,
startingSecurityLevel: 485,
},
name: LocationName.IshimaNovaMedical,
types: [LocationType.Company],
},
{
city: CityName.Ishima,
infiltrationData: {
baseRewardValue: 10,
difficulty: 1.6,
maxClearanceLevel: 40,
startingSecurityLevel: 130,
},
name: LocationName.IshimaOmegaSoftware,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 128,
techVendorMinRam: 4,
},
{
city: CityName.Ishima,
infiltrationData: {
baseRewardValue: 24,
difficulty: 4.1,
maxClearanceLevel: 100,
startingSecurityLevel: 570,
},
name: LocationName.IshimaStormTechnologies,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 512,
techVendorMinRam: 32,
},
{
city: CityName.NewTokyo,
infiltrationData: {
baseRewardValue: 28,
difficulty: 4,
maxClearanceLevel: 70,
startingSecurityLevel: 1050,
},
name: LocationName.NewTokyoDefComm,
types: [LocationType.Company],
},
{
city: CityName.NewTokyo,
infiltrationData: {
baseRewardValue: 24,
difficulty: 3.8,
maxClearanceLevel: 80,
startingSecurityLevel: 700,
},
name: LocationName.NewTokyoGlobalPharmaceuticals,
types: [LocationType.Company],
},
{
city: CityName.NewTokyo,
name: LocationName.NewTokyoNoodleBar,
types: [LocationType.Company],
},
{
city: CityName.NewTokyo,
infiltrationData: {
baseRewardValue: 22,
difficulty: 3.5,
maxClearanceLevel: 100,
startingSecurityLevel: 605,
},
name: LocationName.NewTokyoVitaLife,
types: [LocationType.Company, LocationType.Special],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 14,
difficulty: 2.25,
maxClearanceLevel: 40,
startingSecurityLevel: 200,
},
name: LocationName.Sector12AlphaEnterprises,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 8,
techVendorMinRam: 2,
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 46,
difficulty: 4.2,
maxClearanceLevel: 100,
startingSecurityLevel: 2160,
},
name: LocationName.Sector12BladeIndustries,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
name: LocationName.Sector12CIA,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 18,
difficulty: 2.5,
maxClearanceLevel: 60,
startingSecurityLevel: 405,
},
name: LocationName.Sector12CarmichaelSecurity,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
name: LocationName.Sector12CityHall,
types: [LocationType.Special],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 24,
difficulty: 4.3,
maxClearanceLevel: 50,
startingSecurityLevel: 700,
},
name: LocationName.Sector12DeltaOne,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
name: LocationName.Sector12FoodNStuff,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 58,
difficulty: 7,
maxClearanceLevel: 100,
startingSecurityLevel: 1350,
},
name: LocationName.Sector12FourSigma,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 32,
difficulty: 5.4,
maxClearanceLevel: 70,
startingSecurityLevel: 730,
},
name: LocationName.Sector12IcarusMicrosystems,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
expMult: 1,
costMult: 1,
name: LocationName.Sector12IronGym,
types: [LocationType.Gym],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 8,
difficulty: 1.8,
maxClearanceLevel: 20,
startingSecurityLevel: 120,
},
name: LocationName.Sector12JoesGuns,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 114,
difficulty: 6.75,
maxClearanceLevel: 125,
startingSecurityLevel: 4500,
},
name: LocationName.Sector12MegaCorp,
types: [LocationType.Company],
},
{
city: CityName.Sector12,
name: LocationName.Sector12NSA,
types: [LocationType.Company, LocationType.Special],
},
{
city: CityName.Sector12,
costMult: 20,
expMult: 10,
name: LocationName.Sector12PowerhouseGym,
types: [LocationType.Gym],
},
{
city: CityName.Sector12,
costMult: 3,
expMult: 2,
name: LocationName.Sector12RothmanUniversity,
types: [LocationType.University],
},
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 24,
difficulty: 4.3,
maxClearanceLevel: 50,
startingSecurityLevel: 700,
},
name: LocationName.Sector12UniversalEnergy,
types: [LocationType.Company],
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 12,
difficulty: 2.1,
maxClearanceLevel: 60,
startingSecurityLevel: 195,
},
name: LocationName.VolhavenCompuTek,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 256,
techVendorMinRam: 8,
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 28,
difficulty: 3,
maxClearanceLevel: 75,
startingSecurityLevel: 1080,
},
name: LocationName.VolhavenHeliosLabs,
types: [LocationType.Company],
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 14,
difficulty: 2,
maxClearanceLevel: 60,
startingSecurityLevel: 340,
},
name: LocationName.VolhavenLexoCorp,
types: [LocationType.Company],
},
{
city: CityName.Volhaven,
costMult: 7,
expMult: 4,
name: LocationName.VolhavenMilleniumFitnessGym,
types: [LocationType.Gym],
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 56,
difficulty: 6.8,
maxClearanceLevel: 200,
startingSecurityLevel: 1460,
},
name: LocationName.VolhavenNWO,
types: [LocationType.Company],
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 44,
difficulty: 4.4,
maxClearanceLevel: 100,
startingSecurityLevel: 1215,
},
name: LocationName.VolhavenOmniTekIncorporated,
types: [LocationType.Company, LocationType.TechVendor],
techVendorMaxRam: 1024,
techVendorMinRam: 128,
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 28,
difficulty: 4.9,
maxClearanceLevel: 90,
startingSecurityLevel: 725,
},
name: LocationName.VolhavenOmniaCybersystems,
types: [LocationType.Company],
},
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 18,
difficulty: 2.4,
maxClearanceLevel: 75,
startingSecurityLevel: 430,
},
name: LocationName.VolhavenSysCoreSecurities,
types: [LocationType.Company],
},
{
city: CityName.Volhaven,
costMult: 5,
expMult: 4,
name: LocationName.VolhavenZBInstituteOfTechnology,
types: [LocationType.University],
},
{
city: null,
name: LocationName.Hospital,
types: [LocationType.Hospital],
},
{
city: null,
name: LocationName.Slums,
types: [LocationType.Slums],
},
{
city: null,
name: LocationName.TravelAgency,
types: [LocationType.TravelAgency],
},
{
city: null,
name: LocationName.WorldStockExchange,
types: [LocationType.StockMarket],
},
];

View File

@@ -0,0 +1,48 @@
/**
* React Component for a button that's used to apply for a job
*/
import * as React from "react";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { getJobRequirementText } from "../../Company/GetJobRequirementText";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
company: Company;
entryPosType: CompanyPosition;
onClick: (e: React.MouseEvent<HTMLElement>) => void;
p: IPlayer;
style?: object;
text: string;
}
export class ApplyToJobButton extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
this.getJobRequirementTooltip = this.getJobRequirementTooltip.bind(this);
}
getJobRequirementTooltip(): string {
const pos = this.props.p.getNextCompanyPosition(this.props.company, this.props.entryPosType);
if (pos == null) { return "" };
if (!this.props.company.hasPosition(pos)) { return ""; }
return getJobRequirementText(this.props.company, pos, true);
}
render() {
return (
<StdButton
onClick={this.props.onClick}
style={this.props.style}
text={this.props.text}
tooltip={this.getJobRequirementTooltip()}
/>
)
}
}

34
src/Locations/ui/City.tsx Normal file
View File

@@ -0,0 +1,34 @@
/**
* React Component for displaying a City's UI.
* This UI shows all of the available locations in the city, and lets the player
* visit those locations
*/
import * as React from "react";
import { City } from "../City";
import { LocationName } from "../data/LocationNames";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
city: City;
enterLocation: (to: LocationName) => void;
}
export class LocationCity extends React.Component<IProps, any> {
render() {
const locationButtons = this.props.city.locations.map((locName) => {
return (
<li key={locName}>
<StdButton onClick={this.props.enterLocation.bind(this, locName)} text={locName} />
</li>
)
});
return (
<ul>
{locationButtons}
</ul>
)
}
}

View File

@@ -0,0 +1,371 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a company
*
* This subcomponent renders all of the buttons for applying to jobs at a company
*/
import * as React from "react";
import { ApplyToJobButton } from "./ApplyToJobButton";
import { Location } from "../Location";
import { Locations } from "../Locations";
import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { beginInfiltration } from "../../Infiltration";
import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { CompanyPositions } from "../../Company/CompanyPositions";
import * as posNames from "../../Company/data/companypositionnames";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
engine: IEngine;
locName: LocationName;
p: IPlayer;
}
type IState = {
employedHere: boolean;
}
export class CompanyLocation extends React.Component<IProps, IState> {
/**
* We'll keep a reference to the Company that this component is being rendered for,
* so we don't have to look it up every time
*/
company: Company;
/**
* CompanyPosition object for the job that the player holds at this company
* (if he has one)
*/
companyPosition: CompanyPosition | null = null;
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
/**
* Reference to the Location that this component is being rendered for
*/
location: Location;
/**
* Name of company position that player holds, if applicable
*/
jobTitle: string | null = null;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.applyForAgentJob = this.applyForAgentJob.bind(this);
this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this);
this.applyForBusinessJob = this.applyForBusinessJob.bind(this);
this.applyForEmployeeJob = this.applyForEmployeeJob.bind(this);
this.applyForItJob = this.applyForItJob.bind(this);
this.applyForPartTimeEmployeeJob = this.applyForPartTimeEmployeeJob.bind(this);
this.applyForPartTimeWaiterJob = this.applyForPartTimeWaiterJob.bind(this);
this.applyForSecurityJob = this.applyForSecurityJob.bind(this);
this.applyForSoftwareConsultantJob = this.applyForSoftwareConsultantJob.bind(this);
this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this);
this.applyForWaiterJob = this.applyForWaiterJob.bind(this);
this.startInfiltration = this.startInfiltration.bind(this);
this.work = this.work.bind(this);
this.location = Locations[props.locName];
if (this.location == null) {
throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`);
}
this.company = Companies[props.locName];
if (this.company == null) {
throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);
}
this.state = {
employedHere: false,
}
this.checkIfEmployedHere(false);
}
applyForAgentJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForAgentJob();
this.checkIfEmployedHere(true);
}
applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForBusinessConsultantJob();
this.checkIfEmployedHere(true);
}
applyForBusinessJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForBusinessJob();
this.checkIfEmployedHere(true);
}
applyForEmployeeJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForEmployeeJob();
this.checkIfEmployedHere(true);
}
applyForItJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForItJob();
this.checkIfEmployedHere(true);
}
applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForPartTimeEmployeeJob();
this.checkIfEmployedHere(true);
}
applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForPartTimeWaiterJob();
this.checkIfEmployedHere(true);
}
applyForSecurityJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForSecurityJob();
this.checkIfEmployedHere(true);
}
applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForSoftwareConsultantJob();
this.checkIfEmployedHere(true);
}
applyForSoftwareJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForSoftwareJob();
this.checkIfEmployedHere(true);
}
applyForWaiterJob(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
this.props.p.applyForWaiterJob();
this.checkIfEmployedHere(true);
}
checkIfEmployedHere(updateState=false) {
this.jobTitle = this.props.p.jobs[this.props.locName];
if (this.jobTitle != null) {
this.companyPosition = CompanyPositions[this.jobTitle];
}
if (updateState) {
this.setState({
employedHere: this.jobTitle != null
});
}
}
startInfiltration(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
const loc = this.location;
this.props.engine.loadInfiltrationContent();
const data = loc.infiltrationData;
if (data == null) { return false; }
beginInfiltration(this.props.locName, data.startingSecurityLevel, data.baseRewardValue, data.maxClearanceLevel, data.difficulty);
}
work(e: React.MouseEvent<HTMLElement>) {
if (!e.isTrusted) { return false; }
const pos = this.companyPosition;
if (pos instanceof CompanyPosition) {
if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) {
this.props.p.startWorkPartTime(this.props.locName);
} else {
this.props.p.startWork(this.props.locName);
}
}
}
render() {
const isEmployedHere = this.jobTitle != null;
const favorGain = this.company.getFavorGain();
return (
<div>
{
isEmployedHere &&
<div>
<p>Job Title: {this.jobTitle}</p>
<p>--------------------</p>
<p className={"tooltip"}>
Company reputation: {numeralWrapper.format(this.company.playerReputation, "0,0.000")}
<span className={"tooltiptext"}>
You will earn {numeralWrapper.format(favorGain[0], "0,0")} company
favor upon resetting after installing Augmentations
</span>
</p>
<p>--------------------</p>
<p className={"tooltip"}>
Company Favor: {numeralWrapper.format(this.company.favor, "0,0")}
<span className={"tooltiptext"}>
Company favor increases the rate at which you earn reputation for this company by
1% per favor. Company favor is gained whenever you reset after installing Augmentations. The amount
of favor you gain depends on how much reputation you have with the comapny.
</span>
</p>
<StdButton
id={"foo-work-button-id"}
onClick={this.work}
style={this.btnStyle}
text={"Work"}
/>
</div>
}
{
this.company.hasAgentPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
onClick={this.applyForAgentJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Agent Job"}
/>
}
{
this.company.hasBusinessConsultantPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
onClick={this.applyForBusinessConsultantJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Business Consultant Job"}
/>
}
{
this.company.hasBusinessPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
onClick={this.applyForBusinessJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Business Job"}
/>
}
{
this.company.hasEmployeePositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
onClick={this.applyForEmployeeJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be an Employee"}
/>
}
{
this.company.hasEmployeePositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
onClick={this.applyForPartTimeEmployeeJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a part-time Employee"}
/>
}
{
this.company.hasITPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
onClick={this.applyForItJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for IT Job"}
/>
}
{
this.company.hasSecurityPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
onClick={this.applyForSecurityJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Security Job"}
/>
}
{
this.company.hasSoftwareConsultantPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
onClick={this.applyForSoftwareConsultantJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Software Consultant Job"}
/>
}
{
this.company.hasSoftwarePositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
onClick={this.applyForSoftwareJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Software Job"}
/>
}
{
this.company.hasWaiterPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
onClick={this.applyForWaiterJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a Waiter"}
/>
}
{
this.company.hasWaiterPositions() &&
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
onClick={this.applyForPartTimeWaiterJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a part-time Waiter"}
/>
}
{
(this.location.infiltrationData != null) &&
<StdButton
onClick={this.startInfiltration}
style={this.btnStyle}
text={"Infiltration Company"}
/>
}
</div>
)
}
}

View File

@@ -0,0 +1,148 @@
/**
* React Component for displaying a location's UI
*
* This is a "router" component of sorts, meaning it deduces the type of
* location that is being rendered and then creates the proper component(s) for that.
*/
import * as React from "react";
import { CompanyLocation } from "./CompanyLocation";
import { GymLocation } from "./GymLocation";
import { HospitalLocation } from "./HospitalLocation";
import { SlumsLocation } from "./SlumsLocation";
import { SpecialLocation } from "./SpecialLocation";
import { TechVendorLocation } from "./TechVendorLocation";
import { TravelAgencyLocation } from "./TravelAgencyLocation";
import { UniversityLocation } from "./UniversityLocation";
import { Location } from "../Location";
import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
engine: IEngine;
loc: Location;
p: IPlayer;
returnToCity: () => void;
travel: (to: CityName) => void;
}
export class GenericLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
}
/**
* Determine what needs to be rendered for this location based on the locations
* type. Returns an array of React components that should be rendered
*/
getLocationSpecificContent(): React.ReactNode[] {
const content: React.ReactNode[] = [];
if (this.props.loc.types.includes(LocationType.Company)) {
content.push(
<CompanyLocation
engine={this.props.engine}
key={"companylocation"}
locName={this.props.loc.name}
p={this.props.p}
/>
)
}
if (this.props.loc.types.includes(LocationType.Gym)) {
content.push(
<GymLocation
key={"gymlocation"}
loc={this.props.loc}
p={this.props.p}
/>
)
}
if (this.props.loc.types.includes(LocationType.Hospital)) {
content.push(
<HospitalLocation
key={"hospitallocation"}
p={this.props.p}
/>
)
}
if (this.props.loc.types.includes(LocationType.Slums)) {
content.push(
<SlumsLocation
key={"slumslocation"}
p={this.props.p}
/>
)
}
if (this.props.loc.types.includes(LocationType.Special)) {
content.push(
<SpecialLocation
engine={this.props.engine}
key={"speciallocation"}
loc={this.props.loc}
p={this.props.p}
/>
)
}
if (this.props.loc.types.includes(LocationType.TechVendor)) {
content.push(
<TechVendorLocation
key={"techvendorlocation"}
loc={this.props.loc}
p={this.props.p}
/>
)
}
if (this.props.loc.types.includes(LocationType.TravelAgency)) {
content.push(
<TravelAgencyLocation
key={"travelagencylocation"}
p={this.props.p}
travel={this.props.travel}
/>
)
}
if (this.props.loc.types.includes(LocationType.University)) {
content.push(
<UniversityLocation
key={"universitylocation"}
loc={this.props.loc}
p={this.props.p}
/>
)
}
return content;
}
render() {
const locContent: React.ReactNode[] = this.getLocationSpecificContent();
return (
<div>
<StdButton onClick={this.props.returnToCity} style={this.btnStyle} text={"Return to World"} />
<h1>{this.props.loc.name}</h1>
{locContent}
</div>
)
}
}

View File

@@ -0,0 +1,89 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a gym
*
* This subcomponent renders all of the buttons for training at the gym
*/
import * as React from "react";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
loc: Location;
p: IPlayer;
}
export class GymLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.trainStrength = this.trainStrength.bind(this);
this.trainDefense = this.trainDefense.bind(this);
this.trainDexterity = this.trainDexterity.bind(this);
this.trainAgility = this.trainAgility.bind(this);
}
train(stat: string) {
const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat);
}
trainStrength() {
return this.train(CONSTANTS.ClassGymStrength);
}
trainDefense() {
return this.train(CONSTANTS.ClassGymDefense);
}
trainDexterity() {
return this.train(CONSTANTS.ClassGymDexterity);
}
trainAgility() {
return this.train(CONSTANTS.ClassGymAgility);
}
render() {
const costMult: number = this.props.loc.costMult;
const cost = CONSTANTS.ClassGymBaseCost * costMult;
return (
<div>
<StdButton
onClick={this.trainStrength}
style={this.btnStyle}
text={`Train Strength (${numeralWrapper.formatMoney(cost)} / sec)`}
/>
<StdButton
onClick={this.trainDefense}
style={this.btnStyle}
text={`Train Defense (${numeralWrapper.formatMoney(cost)} / sec)`}
/>
<StdButton
onClick={this.trainDexterity}
style={this.btnStyle}
text={`Train Dexterity (${numeralWrapper.formatMoney(cost)} / sec)`}
/>
<StdButton
onClick={this.trainAgility}
style={this.btnStyle}
text={`Train Agility (${numeralWrapper.formatMoney(cost)} / sec)`}
/>
</div>
)
}
}

View File

@@ -0,0 +1,76 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a hospital
*
* This subcomponent renders all of the buttons for hospital options
*/
import * as React from "react";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
p: IPlayer;
}
type IState = {
currHp: number;
}
export class HospitalLocation extends React.Component<IProps, IState> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.getCost = this.getCost.bind(this);
this.getHealed = this.getHealed.bind(this);
this.state = {
currHp: this.props.p.hp,
}
}
getCost(): number {
return (this.props.p.max_hp - this.props.p.hp) * CONSTANTS.HospitalCostPerHp;
}
getHealed(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
if (this.props.p.hp < 0) { this.props.p.hp = 0; }
if (this.props.p.hp >= this.props.p.max_hp) { return; }
const cost = this.getCost();
this.props.p.loseMoney(cost);
this.props.p.hp = this.props.p.max_hp;
// This just forces a re-render to update the cost
this.setState({
currHp: this.props.p.hp,
});
dialogBoxCreate(`You were healed to full health! The hospital billed you for ${numeralWrapper.formatMoney(cost)}`);
}
render() {
const cost = this.getCost();
return (
<AutoupdatingStdButton
onClick={this.getHealed}
style={this.btnStyle}
text={`Get treatment for wounds - ${numeralWrapper.formatMoney(cost)}`}
/>
)
}
}

151
src/Locations/ui/Root.tsx Normal file
View File

@@ -0,0 +1,151 @@
/**
* Root React Component for displaying overall Location UI
*/
import * as React from "react";
import { LocationCity } from "./City";
import { GenericLocation } from "./GenericLocation";
import { Cities } from "../Cities";
import { Locations } from "../Locations";
import { LocationType } from "../LocationTypeEnum";
import { CityName } from "../data/CityNames";
import { LocationName } from "../data/LocationNames";
import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
initiallyInCity?: boolean;
engine: IEngine;
p: IPlayer;
}
type IState = {
city: CityName;
inCity: boolean;
location: LocationName;
}
export class LocationRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
city: props.p.city,
inCity: props.initiallyInCity == null ? true : props.initiallyInCity,
location: props.p.location,
}
this.enterLocation = this.enterLocation.bind(this);
this.returnToCity = this.returnToCity.bind(this);
this.travel = this.travel.bind(this);
}
enterLocation(to: LocationName): void {
this.props.p.gotoLocation(to);
this.setState({
inCity: false,
location: to,
});
}
/**
* Click listener for a button that lets the player go from a specific location
* back to the city
*/
returnToCity(): void {
this.setState({
inCity: true,
});
}
/**
* Render UI for a city
*/
renderCity(): React.ReactNode {
const city = Cities[this.state.city];
if (city == null) {
throw new Error(`Invalid city when rendering UI: ${this.state.city}`);
}
return (
<div>
<h2>{this.state.city}</h2>
<LocationCity city={city} enterLocation={this.enterLocation} />
</div>
)
}
/**
* Render UI for a specific location
*/
renderLocation(): React.ReactNode {
const loc = Locations[this.state.location];
if (loc == null) {
throw new Error(`Invalid location when rendering UI: ${this.state.location}`);
}
if (loc.types.includes(LocationType.StockMarket)) {
this.props.engine.loadStockMarketContent();
}
return (
<GenericLocation
engine={this.props.engine}
loc={loc}
p={this.props.p}
returnToCity={this.returnToCity}
travel={this.travel}
/>
)
}
/**
* Travel to a different city
* @param {CityName} to - Destination city
*/
travel(to: CityName): void {
const p = this.props.p;
const cost = CONSTANTS.TravelCost;
if (!p.canAfford(cost)) {
dialogBoxCreate(`You cannot afford to travel to ${to}`);
return;
}
p.loseMoney(cost);
p.travel(to);
dialogBoxCreate(`You are now in ${to}!`);
// Dynamically update main menu
if (p.firstTimeTraveled === false) {
p.firstTimeTraveled = true;
const travelTab = document.getElementById("travel-tab");
const worldHeader = document.getElementById("world-menu-header");
if (travelTab != null && worldHeader !== null) {
travelTab.style.display = "list-item";
worldHeader.click(); worldHeader.click();
}
}
if (this.props.p.travel(to)) {
this.setState({
inCity: true,
city: to
});
}
}
render() {
if (this.state.inCity) {
return this.renderCity();
} else {
return this.renderLocation();
}
}
}

View File

@@ -0,0 +1,206 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a slum
*
* This subcomponent renders all of the buttons for committing crimes
*/
import * as React from "react";
import { Crimes } from "../../Crime/Crimes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
type IProps = {
p: IPlayer;
}
export class SlumsLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.shoplift = this.shoplift.bind(this);
this.robStore = this.robStore.bind(this);
this.mug = this.mug.bind(this);
this.larceny = this.larceny.bind(this);
this.dealDrugs = this.dealDrugs.bind(this);
this.bondForgery = this.bondForgery.bind(this);
this.traffickArms = this.traffickArms.bind(this);
this.homicide = this.homicide.bind(this);
this.grandTheftAuto = this.grandTheftAuto.bind(this);
this.kidnap = this.kidnap.bind(this);
this.assassinate = this.assassinate.bind(this);
this.heist = this.heist.bind(this);
}
shoplift(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Shoplift.commit(this.props.p);
}
robStore(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.RobStore.commit(this.props.p);
}
mug(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Mug.commit(this.props.p);
}
larceny(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Larceny.commit(this.props.p);
}
dealDrugs(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.DealDrugs.commit(this.props.p);
}
bondForgery(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.BondForgery.commit(this.props.p);
}
traffickArms(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.TraffickArms.commit(this.props.p);
}
homicide(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Homicide.commit(this.props.p);
}
grandTheftAuto(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.GrandTheftAuto.commit(this.props.p);
}
kidnap(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Kidnap.commit(this.props.p);
}
assassinate(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Assassination.commit(this.props.p);
}
heist(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
Crimes.Heist.commit(this.props.p);
}
render() {
const shopliftChance = Crimes.Shoplift.successRate(this.props.p);
const robStoreChance = Crimes.RobStore.successRate(this.props.p);
const mugChance = Crimes.Mug.successRate(this.props.p);
const larcenyChance = Crimes.Larceny.successRate(this.props.p);
const drugsChance = Crimes.DealDrugs.successRate(this.props.p);
const bondChance = Crimes.BondForgery.successRate(this.props.p);
const armsChance = Crimes.TraffickArms.successRate(this.props.p);
const homicideChance = Crimes.Homicide.successRate(this.props.p);
const gtaChance = Crimes.GrandTheftAuto.successRate(this.props.p);
const kidnapChance = Crimes.Kidnap.successRate(this.props.p);
const assassinateChance = Crimes.Assassination.successRate(this.props.p);
const heistChance = Crimes.Heist.successRate(this.props.p);
return (
<div>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.shoplift}
style={this.btnStyle}
text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
tooltip={"Attempt to shoplift from a low-end retailer"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.robStore}
style={this.btnStyle}
text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
tooltip={"Attempt to commit armed robbery on a high-end store"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.mug}
style={this.btnStyle}
text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
tooltip={"Attempt to mug a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.larceny}
style={this.btnStyle}
text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
tooltip={"Attempt to rob property from someone's house"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.dealDrugs}
style={this.btnStyle}
text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
tooltip={"Attempt to deal drugs"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.bondForgery}
style={this.btnStyle}
text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
tooltip={"Attempt to forge corporate bonds"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.traffickArms}
style={this.btnStyle}
text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
tooltip={"Attempt to smuggle illegal arms into the city"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.homicide}
style={this.btnStyle}
text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
tooltip={"Attempt to murder a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.grandTheftAuto}
style={this.btnStyle}
text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
tooltip={"Attempt to commit grand theft auto"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.kidnap}
style={this.btnStyle}
text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
tooltip={"Attempt to kidnap and ransom a high-profile-target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.assassinate}
style={this.btnStyle}
text={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
tooltip={"Attempt to assassinate a high-profile target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.heist}
style={this.btnStyle}
text={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`}
tooltip={"Attempt to pull off the ultimate heist"}
/>
</div>
)
}
}

View File

@@ -0,0 +1,149 @@
/**
* React Subcomponent for displaying a location's UI, when that location has special
* actions/options/properties
*
* Examples:
* - Bladeburner @ NSA
* - Re-sleeving @ VitaLife
* - Create Corporation @ City Hall
*
* This subcomponent creates all of the buttons for interacting with those special
* properties
*/
import * as React from "react";
import { Location } from "../Location";
import { createStartCorporationPopup } from "../LocationsHelpers";
import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { StdButton } from "../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
engine: IEngine;
loc: Location;
p: IPlayer;
}
type IState = {
inBladeburner: boolean;
}
export class SpecialLocation extends React.Component<IProps, IState> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.createCorporationPopup = this.createCorporationPopup.bind(this);
this.handleBladeburner = this.handleBladeburner.bind(this);
this.handleResleeving = this.handleResleeving.bind(this);
this.state = {
inBladeburner: this.props.p.inBladeburner(),
}
}
/**
* Click handler for "Create Corporation" button at Sector-12 City Hall
*/
createCorporationPopup() {
createStartCorporationPopup(this.props.p);
}
/**
* Click handler for Bladeburner button at Sector-12 NSA
*/
handleBladeburner() {
const p = this.props.p;
if (p.inBladeburner()) {
// Enter Bladeburner division
this.props.engine.loadBladeburnerContent();
} else {
// Apply for Bladeburner division
if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {
p.startBladeburner({ new: true });
dialogBoxCreate("You have been accepted into the Bladeburner division!");
this.setState({
inBladeburner: true,
});
const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) {
worldHeader.click(); worldHeader.click();
}
} else {
dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)");
}
}
}
/**
* Click handler for Resleeving button at New Tokyo VitaLife
*/
handleResleeving() {
this.props.engine.loadResleevingContent();
}
renderBladeburner(): React.ReactNode {
if (!this.props.p.canAccessBladeburner()) { return null; }
const text = this.state.inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division";
return (
<StdButton
onClick={this.handleBladeburner}
style={this.btnStyle}
text={text}
/>
)
}
renderCreateCorporation(): React.ReactNode {
if (!this.props.p.canAccessCorporation()) { return null; }
return (
<AutoupdatingStdButton
disabled={!this.props.p.canAccessCorporation() || this.props.p.hasCorporation()}
onClick={this.createCorporationPopup}
style={this.btnStyle}
text={"Create a Corporation"}
/>
)
}
renderResleeving(): React.ReactNode {
if (!this.props.p.canAccessResleeving()) { return null; }
return (
<StdButton
onClick={this.handleResleeving}
style={this.btnStyle}
text={"Re-Sleeve"}
/>
)
}
render() {
switch (this.props.loc.name) {
case LocationName.NewTokyoVitaLife: {
return this.renderResleeving();
}
case LocationName.Sector12CityHall: {
return this.renderCreateCorporation();
}
case LocationName.Sector12NSA: {
return this.renderBladeburner();
}
default:
console.error(`Location ${this.props.loc.name} doesn't have any special properties`);
break;
}
}
}

View File

@@ -0,0 +1,106 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a tech vendor
*
* This subcomponent renders all of the buttons for purchasing things from tech vendors
*/
import * as React from "react";
import { Location } from "../Location";
import { createPurchaseServerPopup,
createUpgradeHomeCoresPopup,
createUpgradeHomeRamPopup,
purchaseTorRouter } from "../LocationsHelpers";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { getPurchaseServerCost } from "../../Server/ServerPurchases";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
loc: Location;
p: IPlayer;
}
export class TechVendorLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.state = {
torPurchased: props.p.hasTorRouter(),
}
this.createUpgradeHomeCoresPopup = this.createUpgradeHomeCoresPopup.bind(this);
this.createUpgradeHomeRamPopup = this.createUpgradeHomeRamPopup.bind(this);
this.purchaseTorRouter = this.purchaseTorRouter.bind(this);
}
createUpgradeHomeCoresPopup() {
createUpgradeHomeCoresPopup(this.props.p);
}
createUpgradeHomeRamPopup() {
createUpgradeHomeRamPopup(this.props.p);
}
purchaseTorRouter() {
purchaseTorRouter(this.props.p);
}
render() {
const loc: Location = this.props.loc;
const purchaseServerButtons: React.ReactNode[] = [];
for (let i = loc.techVendorMinRam; i <= loc.techVendorMaxRam; i *= 2) {
const cost = getPurchaseServerCost(i);
purchaseServerButtons.push(
<StdButton
key={i}
onClick={() => createPurchaseServerPopup(i, this.props.p)}
style={this.btnStyle}
text={`Purchase ${i}GB Server - ${numeralWrapper.formatMoney(cost)}`}
/>
)
}
return (
<div>
{purchaseServerButtons}
{
this.state.torPurchased ? (
<StdButtonPurchased
style={this.btnStyle}
text={"TOR Router - Purchased"}
/>
) : (
<StdButton
onClick={this.purchaseTorRouter}
style={this.btnStyle}
text={`Purchase TOR Router - ${numeralWrapper.formatMoney(CONSTANTS.TorRouterCost)}`}
/>
)
}
<StdButton
onClick={this.createUpgradeHomeRamPopup}
style={this.btnStyle}
text={`Purchase additional RAM for Home computer`}
/>
<StdButton
onClick={this.createUpgradeHomeCoresPopup}
style={this.btnStyle}
text={`Purchase additional Core for Home computer`}
/>
</div>
)
}
}

View File

@@ -0,0 +1,62 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a Travel Agency
*
* This subcomponent renders all of the buttons for traveling to different cities
*/
import * as React from "react";
import { CityName } from "../data/CityNames";
import { createTravelPopup } from "../LocationsHelpers";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
p: IPlayer;
travel: (to: CityName) => void;
}
export class TravelAgencyLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
}
render() {
const travelBtns: React.ReactNode[] = [];
for (const key in CityName) {
const city = CityName[key];
// Skip current city
if (city === this.props.p.city) { continue; }
travelBtns.push(
<StdButton
key={city}
onClick={createTravelPopup.bind(null, city, this.props.travel)}
style={this.btnStyle}
text={`Travel to ${city}`}
/>
)
}
return (
<div>
<p>
From here, you can travel to any other city! A ticket
costs {numeralWrapper.formatMoney(CONSTANTS.TravelCost)}
</p>
{travelBtns}
</div>
)
}
}

View File

@@ -0,0 +1,114 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a university
*
* This subcomponent renders all of the buttons for studying/taking courses
*/
import * as React from "react";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
loc: Location;
p: IPlayer;
}
export class UniversityLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: object;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.take = this.take.bind(this);
this.study = this.study.bind(this);
this.dataStructures = this.dataStructures.bind(this);
this.networks = this.networks.bind(this);
this.algorithms = this.algorithms.bind(this);
this.management = this.management.bind(this);
this.leadership = this.leadership.bind(this);
}
take(stat: string) {
const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat);
}
study() {
return this.take(CONSTANTS.ClassStudyComputerScience);
}
dataStructures() {
return this.take(CONSTANTS.ClassDataStructures);
}
networks() {
return this.take(CONSTANTS.ClassNetworks);
}
algorithms() {
return this.take(CONSTANTS.ClassAlgorithms);
}
management() {
return this.take(CONSTANTS.ClassManagement);
}
leadership() {
return this.take(CONSTANTS.ClassLeadership);
}
render() {
const costMult: number = this.props.loc.costMult;
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
return (
<div>
<StdButton
onClick={this.study}
style={this.btnStyle}
text={`Study Computer Science (free)`}
/>
<StdButton
onClick={this.dataStructures}
style={this.btnStyle}
text={`Take Data Structures course (${numeralWrapper.formatMoney(dataStructuresCost)} / sec)`}
/>
<StdButton
onClick={this.networks}
style={this.btnStyle}
text={`Take Networks course (${numeralWrapper.formatMoney(networksCost)} / sec)`}
/>
<StdButton
onClick={this.algorithms}
style={this.btnStyle}
text={`Take Algorithms course (${numeralWrapper.formatMoney(algorithmsCost)} / sec)`}
/>
<StdButton
onClick={this.management}
style={this.btnStyle}
text={`Take Management course (${numeralWrapper.formatMoney(managementCost)} / sec)`}
/>
<StdButton
onClick={this.leadership}
style={this.btnStyle}
text={`Take Leadership course (${numeralWrapper.formatMoney(leadershipCost)} / sec)`}
/>
</div>
)
}
}

View File

@@ -70,7 +70,11 @@ function checkForMessagesToSend() {
}
} else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) {
sendMessage(jumper0);
Player.getHomeComputer().programs.push(Programs.Flight.name);
const flightName = Programs.Flight.name;
const homeComp = Player.getHomeComputer();
if (!homeComp.programs.includes(flightName)) {
homeComp.programs.push(flightName);
}
} else if (jumper1 && !jumper1.recvd && Player.hacking_skill >= 40) {
sendMessage(jumper1);
} else if (cybersecTest && !cybersecTest.recvd && Player.hacking_skill >= 50) {

View File

@@ -1,4 +1,3 @@
import {HacknetNode} from "./HacknetNode";
import {NetscriptFunctions} from "./NetscriptFunctions";
import {NetscriptPort} from "./NetscriptPort";
@@ -56,37 +55,6 @@ Environment.prototype = {
}
},
setArrayElement: function(name, idx, value) {
if (!(idx instanceof Array)) {
throw new Error("idx parameter is not an Array");
}
var scope = this.lookup(name);
if (!scope && this.parent) {
throw new Error("Undefined variable " + name);
}
var arr = (scope || this).vars[name];
if (!(arr.constructor === Array || arr instanceof Array)) {
throw new Error("Variable is not an array: " + name);
}
var res = arr;
for (var iterator = 0; iterator < idx.length-1; ++iterator) {
var i = idx[iterator];
if (!(res instanceof Array) || i >= res.length) {
throw new Error("Out-of-bounds array access");
}
res = res[i];
}
//Cant assign to ports or HacknetNodes
if (res[idx[idx.length-1]] instanceof HacknetNode) {
throw new Error("Cannot assign a Hacknet Node handle to a new value");
}
if (res[idx[idx.length-1]] instanceof NetscriptPort) {
throw new Error("Cannot assign a Netscript Port handle to a new value");
}
return res[idx[idx.length-1]] = value;
},
//Creates (or overwrites) a variable in the current scope
def: function(name, value) {
return this.vars[name] = value;

View File

@@ -196,8 +196,11 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
}
var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
server.runningScripts.push(runningScriptObj); //Push onto runningScripts
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);
}
}

View File

@@ -29,9 +29,19 @@ import { Factions,
import { joinFaction,
purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { netscriptCanGrow,
netscriptCanHack,
netscriptCanWeaken } from "./Hacking/netscriptCanHack";
import { getCostOfNextHacknetNode,
purchaseHacknet } from "./HacknetNode";
import {Locations} from "./Locations";
getCostOfNextHacknetServer,
purchaseHacknet,
hasHacknetServers,
purchaseHashUpgrade } from "./Hacknet/HacknetHelpers";
import { CityName } from "./Locations/data/CityNames";
import { LocationName } from "./Locations/data/LocationNames";
import { HacknetServer } from "./Hacknet/HacknetServer";
import { Message } from "./Message/Message";
import { Messages } from "./Message/MessageHelpers";
import {inMission} from "./Missions";
@@ -73,7 +83,8 @@ import {WorkerScript, workerScripts,
import {makeRuntimeRejectMsg, netscriptDelay,
runScriptFromScript} from "./NetscriptEvaluator";
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 {numeralWrapper} from "./ui/numeralFormat";
@@ -193,11 +204,11 @@ function initSingularitySFFlags() {
}
function NetscriptFunctions(workerScript) {
var updateDynamicRam = function(fnName, ramCost) {
const updateDynamicRam = function(fnName, ramCost) {
if (workerScript.dynamicLoadedFns[fnName]) {return;}
workerScript.dynamicLoadedFns[fnName] = true;
const threads = workerScript.scriptRef.threads;
let threads = workerScript.scriptRef.threads;
if (typeof threads !== 'number') {
console.warn(`WorkerScript detected NaN for threadcount for ${workerScript.name} on ${workerScript.serverIp}`);
threads = 1;
@@ -214,7 +225,7 @@ function NetscriptFunctions(workerScript) {
}
};
var updateStaticRam = function(fnName, ramCost) {
const updateStaticRam = function(fnName, ramCost) {
if (workerScript.loadedFns[fnName]) {
return 0;
} else {
@@ -227,28 +238,56 @@ function NetscriptFunctions(workerScript) {
* Gets the Server for a specific hostname/ip, throwing an error
* if the server doesn't exist.
* @param {string} Hostname or IP of the server
* @param {string} callingFnName - Name of calling function. For logging purposes
* @returns {Server} The specified Server
*/
var safeGetServer = function(ip, callingFnName="") {
const safeGetServer = function(ip, callingFnName="") {
var server = getServer(ip);
if (server == null) {
workerScript.log(`ERROR: Invalid IP or hostname passed into ${callingFnName}()`);
throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`);
}
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
var getHacknetNode = function(i) {
const getHacknetNode = function(i) {
if (isNaN(i)) {
throw makeRuntimeRejectMsg(workerScript, "Invalid index specified for Hacknet Node: " + i);
}
if (i < 0 || i >= Player.hacknetNodes.length) {
throw makeRuntimeRejectMsg(workerScript, "Index specified for Hacknet Node is out-of-bounds: " + i);
}
return Player.hacknetNodes[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];
}
};
var getCodingContract = function(fn, ip) {
const getCodingContract = function(fn, ip) {
var server = safeGetServer(ip, "getCodingContract");
return server.getContract(fn);
}
@@ -262,43 +301,81 @@ function NetscriptFunctions(workerScript) {
return purchaseHacknet();
},
getPurchaseNodeCost : function() {
return getCostOfNextHacknetNode();
if (hasHacknetServers()) {
return getCostOfNextHacknetServer();
} else {
return getCostOfNextHacknetNode();
}
},
getNodeStats : function(i) {
var node = getHacknetNode(i);
return {
const node = getHacknetNode(i);
const hasUpgraded = hasHacknetServers();
const res = {
name: node.name,
level: node.level,
ram: node.ram,
ram: hasUpgraded ? node.maxRam : node.ram,
cores: node.cores,
production: node.moneyGainRatePerSecond,
production: hasUpgraded ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds,
totalProduction: node.totalMoneyGenerated,
totalProduction: hasUpgraded ? node.totalHashesGenerated : node.totalMoneyGenerated,
};
if (hasUpgraded) {
res.cache = node.cache;
}
return res;
},
upgradeLevel : function(i, n) {
var node = getHacknetNode(i);
return node.purchaseLevelUpgrade(n);
const node = getHacknetNode(i);
return node.purchaseLevelUpgrade(n, Player);
},
upgradeRam : function(i, n) {
var node = getHacknetNode(i);
return node.purchaseRamUpgrade(n);
const node = getHacknetNode(i);
return node.purchaseRamUpgrade(n, Player);
},
upgradeCore : function(i, n) {
var node = getHacknetNode(i);
return node.purchaseCoreUpgrade(n);
const node = getHacknetNode(i);
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) {
var node = getHacknetNode(i);
return node.calculateLevelUpgradeCost(n);
const node = getHacknetNode(i);
return node.calculateLevelUpgradeCost(n, Player);
},
getRamUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
return node.calculateRamUpgradeCost(n);
const node = getHacknetNode(i);
return node.calculateRamUpgradeCost(n, Player);
},
getCoreUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
return node.calculateCoreUpgradeCost(n);
const node = getHacknetNode(i);
return node.calculateCoreUpgradeCost(n, Player);
},
getCacheUpgradeCost : function(i, n) {
if (!hasHacknetServers()) { return Infinity; }
const node = getHacknetNode(i);
return node.calculateCacheUpgradeCost(n);
},
numHashes : function() {
if (!hasHacknetServers()) { return 0; }
return Player.hashManager.hashes;
},
hashCost : function(upgName) {
if (!hasHacknetServers()) { return Infinity; }
return Player.hashManager.getUpgradeCost(upgName);
},
spendHashes : function(upgName, upgTarget) {
if (!hasHacknetServers()) { return false; }
return purchaseHashUpgrade(upgName, upgTarget);
}
},
sprintf : sprintf,
@@ -350,19 +427,16 @@ function NetscriptFunctions(workerScript) {
var hackingTime = calculateHackingTime(server); //This is in seconds
//No root access or skill level too low
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot hack this server (" + server.hostname + ") because user does not have root access");
throw makeRuntimeRejectMsg(workerScript, "Cannot hack this server (" + server.hostname + ") because user does not have root access");
}
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");
const canHack = netscriptCanHack(server, Player);
if (!canHack.res) {
workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.hack == null) {
workerScript.scriptRef.log("Attempting to hack " + ip + " in " + hackingTime.toFixed(3) + " seconds (t=" + threads + ")");
}
return netscriptDelay(hackingTime * 1000, workerScript).then(function() {
if (workerScript.env.stopFlag) {return Promise.reject(workerScript);}
var hackChance = calculateHackingChance(server);
@@ -481,9 +555,10 @@ function NetscriptFunctions(workerScript) {
}
//No root access or skill level too low
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot grow this server (" + server.hostname + ") because user does not have root access");
throw makeRuntimeRejectMsg(workerScript, "Cannot grow this server (" + server.hostname + ") because user does not have root access");
const canHack = netscriptCanGrow(server);
if (!canHack.res) {
workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
}
var growTime = calculateGrowTime(server);
@@ -542,9 +617,10 @@ function NetscriptFunctions(workerScript) {
}
//No root access or skill level too low
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot weaken this server (" + server.hostname + ") because user does not have root access");
throw makeRuntimeRejectMsg(workerScript, "Cannot weaken this server (" + server.hostname + ") because user does not have root access");
const canHack = netscriptCanWeaken(server);
if (!canHack.res) {
workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
}
var weakenTime = calculateWeakenTime(server);
@@ -1300,25 +1376,22 @@ function NetscriptFunctions(workerScript) {
let copy = Object.assign({}, BitNodeMultipliers);
return copy;
},
getServerMoneyAvailable : function(ip){
getServerMoneyAvailable : function(ip) {
if (workerScript.checkingRam) {
return updateStaticRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerMoneyAvailable", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerMoneyAvailable() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerMoneyAvailable() failed. Invalid IP or hostname passed in: " + ip);
}
const server = safeGetServer(ip, "getServerMoneyAvailable");
if (failOnHacknetServer(server, "getServerMoneyAvailable")) { return 0; }
if (server.hostname == "home") {
//Return player's money
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMoneyAvailable == null) {
workerScript.scriptRef.log("getServerMoneyAvailable('home') returned player's money: $" + formatNumber(Player.money.toNumber(), 2));
// Return player's money
if (workerScript.shouldLog("getServerMoneyAvailable")) {
workerScript.log("getServerMoneyAvailable('home') returned player's money: $" + formatNumber(Player.money.toNumber(), 2));
}
return Player.money.toNumber();
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMoneyAvailable == null) {
workerScript.scriptRef.log("getServerMoneyAvailable() returned " + formatNumber(server.moneyAvailable, 2) + " for " + server.hostname);
if (workerScript.shouldLog("getServerMoneyAvailable")) {
workerScript.log("getServerMoneyAvailable() returned " + formatNumber(server.moneyAvailable, 2) + " for " + server.hostname);
}
return server.moneyAvailable;
},
@@ -1327,13 +1400,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerSecurityLevel() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerSecurityLevel() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerSecurityLevel == null) {
workerScript.scriptRef.log("getServerSecurityLevel() returned " + formatNumber(server.hackDifficulty, 3) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerSecurityLevel");
if (failOnHacknetServer(server, "getServerSecurityLevel")) { return 1; }
if (workerScript.shouldLog("getServerSecurityLevel")) {
workerScript.log("getServerSecurityLevel() returned " + formatNumber(server.hackDifficulty, 3) + " for " + server.hostname);
}
return server.hackDifficulty;
},
@@ -1342,13 +1412,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerBaseSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerBaseSecurityLevel() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerBaseSecurityLevel() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerBaseSecurityLevel == null) {
workerScript.scriptRef.log("getServerBaseSecurityLevel() returned " + formatNumber(server.baseDifficulty, 3) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerBaseSecurityLevel");
if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { return 1; }
if (workerScript.shouldLog("getServerBaseSecurityLevel")) {
workerScript.log("getServerBaseSecurityLevel() returned " + formatNumber(server.baseDifficulty, 3) + " for " + server.hostname);
}
return server.baseDifficulty;
},
@@ -1357,13 +1424,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerMinSecurityLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerMinSecurityLevel() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerMinSecurityLevel() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMinSecurityLevel == null) {
workerScript.scriptRef.log("getServerMinSecurityLevel() returned " + formatNumber(server.minDifficulty, 3) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerMinSecurityLevel");
if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { return 1; }
if (workerScript.shouldLog("getServerMinSecurityLevel")) {
workerScript.log("getServerMinSecurityLevel() returned " + formatNumber(server.minDifficulty, 3) + " for " + server.hostname);
}
return server.minDifficulty;
},
@@ -1372,13 +1436,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerRequiredHackingLevel", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerRequiredHackingLevel() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerRequiredHackingLevel() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerRequiredHackingLevel == null) {
workerScript.scriptRef.log("getServerRequiredHackingLevel returned " + formatNumber(server.requiredHackingSkill, 0) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerRequiredHackingLevel");
if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { return 1; }
if (workerScript.shouldLog("getServerRequiredHackingLevel")) {
workerScript.log("getServerRequiredHackingLevel returned " + formatNumber(server.requiredHackingSkill, 0) + " for " + server.hostname);
}
return server.requiredHackingSkill;
},
@@ -1387,13 +1448,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerMaxMoney", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerMaxMoney() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerMaxMoney() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerMaxMoney == null) {
workerScript.scriptRef.log("getServerMaxMoney() returned " + formatNumber(server.moneyMax, 0) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerMaxMoney");
if (failOnHacknetServer(server, "getServerMaxMoney")) { return 0; }
if (workerScript.shouldLog("getServerMaxMoney")) {
workerScript.log("getServerMaxMoney() returned " + formatNumber(server.moneyMax, 0) + " for " + server.hostname);
}
return server.moneyMax;
},
@@ -1402,13 +1460,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerGrowth", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerGrowth() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerGrowth() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerGrowth == null) {
workerScript.scriptRef.log("getServerGrowth() returned " + formatNumber(server.serverGrowth, 0) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerGrowth");
if (failOnHacknetServer(server, "getServerGrowth")) { return 1; }
if (workerScript.shouldLog("getServerGrowth")) {
workerScript.log("getServerGrowth() returned " + formatNumber(server.serverGrowth, 0) + " for " + server.hostname);
}
return server.serverGrowth;
},
@@ -1417,13 +1472,10 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerNumPortsRequired", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerNumPortsRequired() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "getServerNumPortsRequired() failed. Invalid IP or hostname passed in: " + ip);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.getServerNumPortsRequired == null) {
workerScript.scriptRef.log("getServerNumPortsRequired() returned " + formatNumber(server.numOpenPortsRequired, 0) + " for " + server.hostname);
const server = safeGetServer(ip, "getServerNumPortsRequired");
if (failOnHacknetServer(server, "getServerNumPortsRequired")) { return 5; }
if (workerScript.shouldLog("getServerNumPortsRequired")) {
workerScript.log("getServerNumPortsRequired() returned " + formatNumber(server.numOpenPortsRequired, 0) + " for " + server.hostname);
}
return server.numOpenPortsRequired;
},
@@ -1432,13 +1484,9 @@ function NetscriptFunctions(workerScript) {
return updateStaticRam("getServerRam", CONSTANTS.ScriptGetServerRamCost);
}
updateDynamicRam("getServerRam", CONSTANTS.ScriptGetServerRamCost);
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("getServerRam() failed. Invalid IP or hostname passed in: " + ip);
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]");
const server = safeGetServer(ip, "getServerRam");
if (workerScript.shouldLog("getServerRam")) {
workerScript.log("getServerRam() returned [" + formatNumber(server.maxRam, 2) + "GB, " + formatNumber(server.ramUsed, 2) + "GB]");
}
return [server.maxRam, server.ramUsed];
},
@@ -2267,56 +2315,14 @@ function NetscriptFunctions(workerScript) {
if (ip == null || ip === "") {
ip = workerScript.serverIp;
}
var s = getServer(ip);
if (s == null) {
throw makeRuntimeRejectMsg(workerScript, `Invalid server specified for rm(): ${ip}`);
const s = safeGetServer(ip, "rm");
const status = s.removeFile(fn);
if (!status.res) {
workerScript.log(status.msg);
}
if (fn.endsWith(".exe")) {
for (var i = 0; i < s.programs.length; ++i) {
if (s.programs[i] === fn) {
s.programs.splice(i, 1);
return true;
}
}
} else if (isScriptFilename(fn)) {
for (var i = 0; i < s.scripts.length; ++i) {
if (s.scripts[i].filename === fn) {
//Check that the script isnt currently running
for (var j = 0; j < s.runningScripts.length; ++j) {
if (s.runningScripts[j].filename === fn) {
workerScript.scriptRef.log("Cannot delete a script that is currently running!");
return false;
}
}
s.scripts.splice(i, 1);
return true;
}
}
} else if (fn.endsWith(".lit")) {
for (var i = 0; i < s.messages.length; ++i) {
var f = s.messages[i];
if (!(f instanceof Message) && isString(f) && f === fn) {
s.messages.splice(i, 1);
return true;
}
}
} else if (fn.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) {
if (s.textFiles[i].fn === fn) {
s.textFiles.splice(i, 1);
return true;
}
}
} else if (fn.endsWith(".cct")) {
for (var i = 0; i < s.contracts.length; ++i) {
if (s.contracts[i].fn === fn) {
s.contracts.splice(i, 1);
return true;
}
}
}
return false;
return status.res;
},
scriptRunning : function(scriptname, ip) {
if (workerScript.checkingRam) {
@@ -2583,30 +2589,30 @@ function NetscriptFunctions(workerScript) {
var costMult, expMult;
switch(universityName.toLowerCase()) {
case Locations.AevumSummitUniversity.toLowerCase():
if (Player.city != Locations.Aevum) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (Player.city != CityName.Aevum) {
workerScript.scriptRef.log("ERROR: You cannot study at Summit University because you are not in Aevum. universityCourse() failed");
return false;
}
Player.location = Locations.AevumSummitUniversity;
Player.gotoLocation(LocationName.AevumSummitUniversity);
costMult = 4;
expMult = 3;
break;
case Locations.Sector12RothmanUniversity.toLowerCase():
if (Player.city != Locations.Sector12) {
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (Player.city != CityName.Sector12) {
workerScript.scriptRef.log("ERROR: You cannot study at Rothman University because you are not in Sector-12. universityCourse() failed");
return false;
}
Player.location = Locations.Sector12RothmanUniversity;
Player.location = LocationName.Sector12RothmanUniversity;
costMult = 3;
expMult = 2;
break;
case Locations.VolhavenZBInstituteOfTechnology.toLowerCase():
if (Player.city != Locations.Volhaven) {
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (Player.city != CityName.Volhaven) {
workerScript.scriptRef.log("ERROR: You cannot study at ZB Institute of Technology because you are not in Volhaven. universityCourse() failed");
return false;
}
Player.location = Locations.VolhavenZBInstituteOfTechnology;
Player.location = LocationName.VolhavenZBInstituteOfTechnology;
costMult = 5;
expMult = 4;
break;
@@ -2671,48 +2677,48 @@ function NetscriptFunctions(workerScript) {
}
var costMult, expMult;
switch(gymName.toLowerCase()) {
case Locations.AevumCrushFitnessGym.toLowerCase():
if (Player.city != Locations.Aevum) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (Player.city != CityName.Aevum) {
workerScript.scriptRef.log("ERROR: You cannot workout at Crush Fitness because you are not in Aevum. gymWorkout() failed");
return false;
}
Player.location = Locations.AevumCrushFitnessGym;
Player.location = LocationName.AevumCrushFitnessGym;
costMult = 3;
expMult = 2;
break;
case Locations.AevumSnapFitnessGym.toLowerCase():
if (Player.city != Locations.Aevum) {
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (Player.city != CityName.Aevum) {
workerScript.scriptRef.log("ERROR: You cannot workout at Snap Fitness because you are not in Aevum. gymWorkout() failed");
return false;
}
Player.location = Locations.AevumSnapFitnessGym;
Player.location = LocationName.AevumSnapFitnessGym;
costMult = 10;
expMult = 5;
break;
case Locations.Sector12IronGym.toLowerCase():
if (Player.city != Locations.Sector12) {
case LocationName.Sector12IronGym.toLowerCase():
if (Player.city != CityName.Sector12) {
workerScript.scriptRef.log("ERROR: You cannot workout at Iron Gym because you are not in Sector-12. gymWorkout() failed");
return false;
}
Player.location = Locations.Sector12IronGym;
Player.location = LocationName.Sector12IronGym;
costMult = 1;
expMult = 1;
break;
case Locations.Sector12PowerhouseGym.toLowerCase():
if (Player.city != Locations.Sector12) {
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (Player.city != CityName.Sector12) {
workerScript.scriptRef.log("ERROR: You cannot workout at Powerhouse Gym because you are not in Sector-12. gymWorkout() failed");
return false;
}
Player.location = Locations.Sector12PowerhouseGym;
Player.location = LocationName.Sector12PowerhouseGym;
costMult = 20;
expMult = 10;
break;
case Locations.VolhavenMilleniumFitnessGym.toLowerCase():
if (Player.city != Locations.Volhaven) {
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
if (Player.city != CityName.Volhaven) {
workerScript.scriptRef.log("ERROR: You cannot workout at Millenium Fitness Gym because you are not in Volhaven. gymWorkout() failed");
return false;
}
Player.location = Locations.VolhavenMilleniumFitnessGym;
Player.location = LocationName.VolhavenMilleniumFitnessGym;
costMult = 7;
expMult = 4;
break;
@@ -2763,12 +2769,12 @@ function NetscriptFunctions(workerScript) {
}
switch(cityname) {
case Locations.Aevum:
case Locations.Chongqing:
case Locations.Sector12:
case Locations.NewTokyo:
case Locations.Ishima:
case Locations.Volhaven:
case CityName.Aevum:
case CityName.Chongqing:
case CityName.Sector12:
case CityName.NewTokyo:
case CityName.Ishima:
case CityName.Volhaven:
if(Player.money.lt(CONSTANTS.TravelCost)) {
workerScript.scriptRef.log("ERROR: not enough money to travel with travelToCity().");
throw makeRuntimeRejectMsg(workerScript, "ERROR: not enough money to travel with travelToCity().");
@@ -2817,10 +2823,6 @@ function NetscriptFunctions(workerScript) {
AddToAllServers(darkweb);
SpecialServerIps.addIp("Darkweb Server", darkweb.ip);
const purchaseTor = document.getElementById("location-purchase-tor");
purchaseTor.setAttribute("class", "a-link-button-bought");
purchaseTor.innerHTML = "TOR Router - Purchased";
Player.getHomeComputer().serversOnNetwork.push(darkweb.ip);
darkweb.serversOnNetwork.push(Player.getHomeComputer().ip);
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
@@ -3586,29 +3588,8 @@ function NetscriptFunctions(workerScript) {
}
}
//Set Location to slums
switch(Player.city) {
case Locations.Aevum:
Player.location = Locations.AevumSlums;
break;
case Locations.Chongqing:
Player.location = Locations.ChongqingSlums;
break;
case Locations.Sector12:
Player.location = Locations.Sector12Slums;
break;
case Locations.NewTokyo:
Player.location = Locations.NewTokyoSlums;
break;
case Locations.Ishima:
Player.location = Locations.IshimaSlums;
break;
case Locations.Volhaven:
Player.location = Locations.VolhavenSlums;
break;
default:
console.log("Invalid Player.city value");
}
// Set Location to slums
Player.gotoLocation(LocationName.Slums);
const crime = findCrime(crimeRoughName.toLowerCase());
if(crime == null) { // couldn't find crime
@@ -4730,7 +4711,7 @@ function NetscriptFunctions(workerScript) {
answer = String(answer);
}
const serv = safeGetServer(ip, "codingcontract.attempt()");
const serv = safeGetServer(ip, "codingcontract.attempt");
if (contract.isSolution(answer)) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`);
@@ -5080,6 +5061,70 @@ function NetscriptFunctions(workerScript) {
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 return
} //End NetscriptFunction()

Some files were not shown because too many files have changed in this diff Show More