Compare commits

..

61 Commits

Author SHA1 Message Date
danielyxie
bcb198220d v0.46.3 2019-04-19 22:27:33 -07:00
danielyxie
bf1af6a68c Merge branch 'master' of https://github.com/danielyxie/bitburner into dev 2019-04-16 01:22:22 -07:00
danielyxie
3dd2975c61 Fixed issue with faction reputation donation bug calculating invalid rep gain 2019-04-16 01:20:52 -07:00
danielyxie
33f1e0cb3c Fixed several typos. TechVendor location UI will now properly update when you purchase TOR router. 'Manage Gang' faction option should show up properly 2019-04-16 01:20:52 -07:00
danielyxie
7514f63dcd Fixed issue with faction reputation donation bug calculating invalid rep gain 2019-04-16 01:19:29 -07:00
danielyxie
b6ff73391d Fixed several typos. TechVendor location UI will now properly update when you purchase TOR router. 'Manage Gang' faction option should show up properly 2019-04-15 02:49:49 -07:00
danielyxie
369ea8d381 Merge pull request #597 from danielyxie/dev
Dev
2019-04-14 02:26:37 -07:00
danielyxie
a7296c512c Fix merge conflicts 2019-04-14 02:26:47 -07:00
danielyxie
8f70817c10 Adding tooltips to new purchase augmentation ui 2019-04-14 02:21:43 -07:00
danielyxie
7417fb6ef8 v0.46.2 2019-04-14 02:08:10 -07:00
danielyxie
d044739f1c v0.46.2 2019-04-14 02:07:29 -07:00
danielyxie
3d1684f825 Fix Terminal wget bug. Issue #593 2019-04-14 02:07:29 -07:00
danielyxie
f6af9e94ab Gang bug fixes. Issues #574 and #575 2019-04-14 02:07:29 -07:00
danielyxie
215cf59e0b Fixed comment styling for all top-level src files 2019-04-14 02:07:29 -07:00
danielyxie
0d14cd6e7e Improved module import styling for all top-level src files 2019-04-14 02:07:29 -07:00
danielyxie
98a04e4932 Fix Terminal wget bug. Issue #593 2019-04-13 00:33:30 -07:00
danielyxie
8d33c5b571 Gang bug fixes. Issues #574 and #575 2019-04-13 00:26:49 -07:00
danielyxie
221b81d802 Fixed comment styling for all top-level src files 2019-04-12 20:47:17 -07:00
danielyxie
df89cc5002 Improved module import styling for all top-level src files 2019-04-12 20:47:17 -07:00
danielyxie
3b6b37f8a6 Merge pull request #594 from danielyxie/dev
v0.46.1
2019-04-12 16:53:19 -07:00
danielyxie
cf2acb8844 Updated documentation changelog for v0.46.1 2019-04-12 16:54:11 -07:00
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
189 changed files with 16155 additions and 11400 deletions

View File

@@ -1,6 +1,7 @@
# Bitburner
Bitburner is a cyberpunk hacking-themed incremental game. The game can be
played at https://danielyxie.github.io/bitburner.
Bitburner is a programming-based [incremental game](https://en.wikipedia.org/wiki/Incremental_game)
that revolves around hacking and cyberpunk themes.
The game can be played at https://danielyxie.github.io/bitburner.
# Documentation
The game's official documentation can be found on [Read The

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: 60%;
}
#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: 60%; }
#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

@@ -40,7 +40,7 @@ solutions. Some may be numbers, others may be strings or arrays.
If a contract asks for a specific solution format, then
use that. Otherwise, follow these rules when submitting solutions:
* String-type solutions should not have quotation marks surrounding
* String-type solutions should **not** have quotation marks surrounding
the string (unless specifically asked for). Only quotation
marks that are part of the actual string solution should be included.
* Array-type solutions should be submitted with each element

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,50 @@
Changelog
=========
v0.46.2 - 4/14/2019
-------------------
* Source-File 2 now allows you to form gangs in other BitNodes when your karma reaches a very large negative value
* (Karma is a hidden stat and is lowered by committing crimes)
* Gang changes:
* Bug Fix: Gangs can no longer clash with themselve
* Bug Fix: Winning against another gang should properly reduce their power
* Bug Fix: Terminal 'wget' command now works properly
* Bug Fix: Hacknet Server Hash upgrades now properly reset upon installing Augs/switching BitNodes
* Bug Fix: Fixed button for creating Corporations
v0.46.1 - 4/12/2019
-------------------
* Added a very rudimentary directory system to the Terminal
* Details here: https://bitburner.readthedocs.io/en/latest/basicgameplay/terminal.html#filesystem-directories
* 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
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.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -2,10 +2,11 @@ Guides & Tips
=============
Getting Started Guide for Intermediate Programmers
What BitNode should I do?
Beginners FAQ
.. toctree::
:maxdepth: 3
Getting Started Guide for Beginner Programmers <guidesandtips/gettingstartedguideforbeginnerprogrammers>
What BitNode should I do?<guidesandtips/recommendedbitnodeorder>

View File

@@ -0,0 +1,490 @@
What BitNode should I do?
=========================
.. warning:: This page contains spoilers regarding the game's story/plot-line.
After destroying their first :ref:`BitNode <gameplay_bitnodes>`, many players
wonder which BitNode they should tackle next. This guide hopefully helps answer
that question.
Overview of each BitNode
------------------------
BitNode-1: Source Genesis
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
The first BitNode created by the Enders to imprison the minds of humans. It became
the prototype and testing-grounds for all of the BitNodes that followed.
This is the first BitNode that you play through. It has no special
modifications or mechanics.
Source-File
:Max Level: 3
This Source-File lets the player start with 32GB of RAM on his/her home computer when
entering a new BitNode, and also increases all of the player's multipliers by:
* Level 1: 16%
* Level 2: 24%
* Level 3: 28%
Difficulty
The easiest BitNode
BitNode-2: Rise of the Underworld
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
Organized crime groups quickly filled the void of power left behind from the collapse of
Western government in the 2050s. As society and civlization broke down, people quickly
succumbed to the innate human impulse of evil and savagery. The organized crime
factions quickly rose to the top of the modern world.
In this BitNode:
* Your hacking level is reduced by 20%
* The growth rate and maximum amount of money available on servers are significantly decreased
* The amount of money gained from crimes and Infiltration is tripled
* Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead,
NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs
will earn the player money and reputation with the corresponding Faction
* Every Augmentation in the game will be available through the Factions listed above
* For every Faction NOT listed above, reputation gains are halved
* You will no longer gain passive reputation with Factions
Source-File
:Max Level: 3
This Source-File allows you to form gangs in other BitNodes once your karma decreases to a certain value.
It also increases the player's crime success rate, crime money, and charisma multipliers by:
* Level 1: 24%
* Level 2: 36%
* Level 3: 42%
Difficulty
Fairly easy, as hacking is still very profitable and the costs of various purchases/upgrades
is not increased. The gang mechanic may seem strange as its very different from anything
else, but it can be very powerful once you get the hang of it.
BitNode-3: Corporatocracy
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
Our greatest illusion is that a healthy society can revolve around a
single-minded pursuit of wealth.
Sometime in the early 21st century economic and political globalization turned
the world into a corporatocracy, and it never looked back. Now, the privileged
elite will happily bankrupt their own countrymen, decimate their own community,
and evict their neighbors from houses in their desperate bid to increase their wealth.
In this BitNode you can create and manage your own corporation. Running a successful corporation
has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:
* The price and reputation cost of all Augmentations is tripled
* The starting and maximum amount of money on servers is reduced by 75%
* Server growth rate is reduced by 80%
* You now only need 75 favour with a faction in order to donate to it, rather than 150
Source-File
:Max Level: 3
This Source-File lets you create corporations on other BitNodes (although
some BitNodes will disable this mechanic). This Source-File also increases your
charisma and company salary multipliers by:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Somewhat-steep learning curve as you learn how to use and manage Corporations. Afterwards,
however, the BitNode is easy as Corporations can be very profitable.
BitNode-4: The Singularity
^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
The Singularity has arrived. The human race is gone, replaced by artificially superintelligent
beings that are more machine than man.
In this BitNode, progressing is significantly harder:
* Experience gain rates for all stats are reduced.
* Most methods of earning money will now give significantly less.
In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions.
These functions allow you to control most aspects of the game through scripts, including
working for factions/companies, purchasing/installing Augmentations, and creating programs.
Source-File
:Max Level: 3
This Source-File lets you access and use the Singularity Functions in other BitNodes.
Each level of this Source-File will open up more Singularity Functions that you can use.
Difficulty:
Depending on what Source-Files you have unlocked before attempting this BitNode,
it can range from easy to moderate.
BitNode-5: Artificial Intelligence
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
They said it couldn't be done. They said the human brain,
along with its consciousness and intelligence, couldn't be replicated. They said the complexity
of the brain results from unpredictable, nonlinear interactions that couldn't be modeled
by 1's and 0's. They were wrong.
In this BitNode:
* The base security level of servers is doubled
* The starting money on servers is halved, but the maximum money remains the same
* Most methods of earning money now give significantly less
* Infiltration gives 50% more reputation and money
* Corporations have 50% lower valuations and are therefore less profitable
* Augmentations are more expensive
* Hacking experience gain rates are reduced
Source-File
:Max Level: 3
This Source-File grants you a special new stat called Intelligence.
Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However
gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know
when you gain experience and how much). Higher Intelligence levels will boost your production for many actions
in the game.
In addition, this Source-File will unlock the :js:func:`getBitNodeMultipliers()` Netscript function,
and will also raise all of your hacking-related multipliers by:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Depending on what Source-Files you have unlocked before attempting this BitNode, it
can range from easy to moderate.
BitNode-6: Bladeburners
^^^^^^^^^^^^^^^^^^^^^^^
Description
In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic
androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation
of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was
the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent
than the humans that had created them.
In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides
a new mechanic for progression. Furthermore:
* Hacking and Hacknet Nodes will be less profitable
* Your hacking level is reduced by 65%
* Hacking experience gain from scripts is reduced by 75%
* Corporations have 80% lower valuations and are therefore less profitable
* Working for companies is 50% less profitable
* Crimes and Infiltration are 25% less profitable
Source-File
:Max Level: 3
This Source-File allows you to access the NSA's Bladeburner Division in other
BitNodes. In addition, this Source-File will raise both the level and experience
gain rate of all your combat stats by:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Initially difficult due to the fact that hacking is no longer profitable and you have
to learn a new mechanic. After you get the hang of the Bladeburner mechanic, however,
it becomes moderately easy.
BitNode-7: Bladeburners 2079
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated
as part of the AI design team for advanced synthetic androids, or Synthoids for short. You helped
achieve a major technological breakthrough in the sixth generation of the company's Synthoid
design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was the first
sentient AI ever created. This resulted in Synthoid models that were stronger, faster,
and more intelligent than the humans that had created them.
In this BitNode you will be able to access the Bladeburner API, which allows you to access
Bladeburner functionality through Netscript. Furthermore:
* The rank you gain from Bladeburner contracts/operations is reduced by 40%
* Bladeburner skills cost twice as many skill points
* Augmentations are 3x more expensive
* Hacking and Hacknet Nodes will be significantly less profitable
* Your hacking level is reduced by 65%
* Hacking experience gain from scripts is reduced by 75%
* Corporations have 80% lower valuations and are therefore less profitable
* Working for companies is 50% less profitable
* Crimes and Infiltration are 25% less profitable
Source-File
:Max Level: 3
This Source-File allows you to access the Bladeburner Netscript API in other
BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:
* Level 1: 8%
* Level 2: 12%
* Level 3: 14%
Difficulty
Slightly more difficult than BitNode-6. However, you will be able to automate more
aspects of the Bladeburner feature, which means it will be more passive.
BitNode-8: Ghost of Wall Street
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.
In this BitNode:
* You start with $250 million
* The only way to earn money is by trading on the stock market
* You start with a WSE membership and access to the TIX API
* You are able to short stocks and place different types of orders (limit/stop)
* You can immediately donate to factions to gain reputation
Source-File
:Max Level: 3
This Source-File grants the following benefits:
* Level 1: Permanent access to WSE and TIX API
* Level 2: Ability to short stocks in other BitNodes
* Level 3: Ability to use limit/stop orders in other BitNodes
This Source-File also increases your hacking growth multipliers by:
* Level 1: 12%
* Level 2: 18%
* Level 3: 21%
Difficulty
Very difficult until you unlock the Four Sigma (4S) Market Data API. After you
unlock the API however, it becomes moderately easy.
BitNode-9: Hacktocracy
^^^^^^^^^^^^^^^^^^^^^^
Description
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.
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.
In this BitNode:
* Your stats are significantly decreased
* You cannnot purchase additional servers
* Hacking is significantly less profitable
Source-File
:Max Level: 3
This Source-File grants the following benefits:
* Level 1: Permanently unlocks the Hacknet Server in other BitNodes
* Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
* Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT
when installing Augmentation
Difficulty
Hard
BitNode-10: Digital Carbon
^^^^^^^^^^^^^^^^^^^^^^^^^^
Description
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
or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves'
for the human consciousness. Mankind had finally achieved immortality - at least for those
that could afford it.
This BitNode unlocks Sleeve technology. Sleeve technology allows you to:
1. Re-sleeve: Purchase and transfer your consciousness into a new body
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
In this BitNode:
* Your stats are significantly decreased
* All methods of gaining money are half as profitable (except Stock Market)
* Purchased servers are more expensive, have less max RAM, and a lower maximum limit
* Augmentations are 5x as expensive and require twice as much reputation
Source-File
:Max Level: 3
This Source-File unlocks Sleeve technology in other BitNodes.
Each level of this Source-File also grants you a Duplicate Sleeve
Difficulty
Hard
BitNode-11: The Big Crash
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period
of disorder that eventually lead to the governmental reformation of many global superpowers, most notably
the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.
In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers
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.
In this BitNode:
* Your hacking stat and experience gain are halved
* The starting and maximum amount of money available on servers is significantly decreased
* The growth rate of servers is significantly reduced
* Weakening a server is twice as effective
* Company wages are decreased by 50%
* Corporation valuations are 99% lower and are therefore significantly less profitable
* Hacknet Node production is significantly decreased
* Crime and Infiltration are more lucrative
* Augmentations are twice as expensive
Source-File
:Max Level: 3
Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will
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:
* Level 1: 32%
* Level 2: 48%
* Level 3: 56%
Difficulty
Hard
BitNode-12: The Recursion
^^^^^^^^^^^^^^^^^^^^^^^^^
Description
Every time this BitNode is destroyed, it becomes slightly harder.
Source-File
:Max Level: Infinity
Each level of Source-File 12 will increase all of your multipliers by 1%. This effect
is multiplicative with itself. In other words, level N of this Source-File will result
in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)
Difficulty
Initially very easy, but then it (obviously) becomes harder as you continue to do it.
Recommended BitNodes
--------------------
As a player, you are not forced to tackle the BitNodes in any particular order. You are
free to choose whichever ones you want. The "best" order can vary between players,
depending on what you like to do any what kind of player you are. In general, here
are the recommended BitNodes for different things:
For fast progression
^^^^^^^^^^^^^^^^^^^^
.. note:: This does not recommend the absolute fastest path, as I don't know what
exactly the fastest path is. But it does recommend the BitNodes that are
commonly considered to be optimal by players.
1. Repeat **BitNode-1: Source Genesis** until you max out its Source-File. Its Source-File
is extremely powerful, as it raises all multipliers by a significant amount.
2. Repeat **BitNode-12: The Recursion** several times. This BitNode will be extremely easy the
first few times you tackle it, and its Source-File raises all multipliers. Furthermore,
its effect stacks multiplicatively with itself and other Source-Files/Augmentations,
which gets better as time goes on
3. Do **BitNode-5: Artificial Intelligence** once or twice. The intelligence stat it unlocks
will gradually build up as you continue to play the game, and will be helpful
in the future. The Source-File also provides hacking multipliers, which are
strong because hacking is typically one of the best ways of earning money.
4. (Optional) Consider doing **BitNode-4: The Singularity**. Its Source-File does not directly make you
more powerful in any way, but it does unlock :ref:`netscript_singularityfunctions` which
let you automate significantly more aspects of the game.
5. Do **BitNode-3: Corporatocracy** once to unlock the Corporation mechanic. This mechanic
has high profit potential.
6. Do **BitNode-6: Bladeburners** once to unlock the Bladeburners mechanic. The Bladeburner
mechanic is useful for some of the future BitNodes (such as 9 and 10).
7. Do **BitNode-9: Hacktocracy** to unlock the Hacknet Server mechanic. You can
consider repeating it as well, as its Level 2 and 3 effects are pretty helpful as well.
.. todo:: To be continued as more BitNodes get added
For the strongest Source-Files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note that the strongest Source-Files are typically rewarded by the hardest BitNodes.
The strongest Source-File is that from **BitNode-1: Source Genesis**, as it raises
all multipliers by a significant amount.
Similarly, the Source-File from **BitNode-12: The Recursion** is also very strong
because it raises all multipliers. Each level of Source-File 12 is fairly weak,
but its effectiveness gets better over time since the effects of Source-Files and
Augmentations are multiplicative with each other.
The Source-File from **BitNode-9: Hacktocracy** is good because it unlocks the Hacknet
Server mechanic. The Hacknet Server mechanic causes Hacknet Nodes to produce a new
currency called *hashes*, rather than money. *Hashes* can be spent on powerful upgrades
that benefit your hacking, Corporation, Bladeburner, etc.
The Duplicate Sleeves granted by the Source-File from **BitNode-10: Digital Carbon**
are strong, but only after you have several of them and have spent some time/money upgrading
them.
For more scripting/hacking
^^^^^^^^^^^^^^^^^^^^^^^^^^
**BitNode-4: The Singularity** unlocks the :ref:`netscript_singularityfunctions`, which
can be used to automate many different aspects of the game, including working for factions/companies,
purchasing & installing Augmentations, and creating programs
**BitNode-6** and **BitNode-7** unlock Bladeburner and its corresponding
:ref:`Netscript API <netscript_bladeburnerapi>`. This allows you to automate an entire
new mechanic.
**BitNode-2: Rise of the Underworld** also unlocks a new mechanic and Netscript API for automating
it (the Gang mechanic). However, it is not as interesting as Bladeburner (in my opinion)
**BitNode-9: Hacktocracy** unlocks the Hacknet Server mechanic and several new
functions in the :ref:`Hacknet Node API <netscript_hacknetnodeapi>` for using it.
For new mechanics
^^^^^^^^^^^^^^^^^
**BitNode-2: Rise of the Underworld** unlocks a new mechanic in which you can
manage a gang. Gangs earn you money and can be very profitable once they get large
and powerful. The biggest benefit of gangs, however, is that they make all
Augmentations available to you through their corresponding faction.
**BitNode-3: Corporatocracy** unlocks a new mechanic in which you can manage a
corporation. You can earn money through Corporations by selling your stocks, or by
configuring your corporation to pay dividends to shareholders. If your Corporation
gets big enough, it can also bribe factions in exchange for faction reputation.
**BitNode-6: Bladeburners** unlocks a new mechanic that centers around combat rather
than hacking. The main benefit of the Bladeburner mechanic is that it offers a new
method of destroying a BitNode.
**BitNode-9: Hacktocracy** unlocks the Hacknet Server, which is an upgraded version of a
Hacknet Node. The Hacknet Server generates a computational unit called a *hash*. *Hashes*
can be spent on a variety of different upgrades that can benefit your hacking,
Corporation, Bladeburner progress, and more. It transforms the Hacknet Node from a
simple money-generator to a more interesting mechanic.
**BitNode-10: Digital Carbon** unlocks two new mechanics: Re-Sleeving and
Duplicate Sleeves.
For a Challenge
^^^^^^^^^^^^^^^
In general, the higher BitNodes are more difficult than the lower ones.
**BitNode-12: The Recursion** is an obvious exception as it gets progressively harder.
**BitNode-8: Ghost of Wall Street** provides a unique challenge as the only method
of earning money in that BitNode is through trading at the stock market.

View File

@@ -5,8 +5,9 @@
Welcome to Bitburner's documentation!
=====================================
Bitburner is a cyberpunk-themed `incremental game <https://en.wikipedia.org/wiki/Incremental_game>`_ that is currently in the
early beta stage of development. The game `can be played here <https://danielyxie.github.io/bitburner/>`_.
Bitburner is a programming-based `incremental game <https://en.wikipedia.org/wiki/Incremental_game>`_
that revolves around hacking and cyberpunk themes. The game is currently in the
early beta stage of development. It `can be played here <https://danielyxie.github.io/bitburner/>`_.
What is Bitburner?
------------------

View File

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

View File

@@ -8,6 +8,7 @@ recruitMember() Netscript Function
Attempt to recruit a new gang member.
Possible reasons for failure:
* Cannot currently recruit a new member
* There already exists a member with the specified name

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)
: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

@@ -115,7 +115,7 @@
<div id="script-editor-filename-wrapper">
<p id="script-editor-filename-tag"> <strong style="background-color:#555;">Script name: </strong></p>
<input id="script-editor-filename" type="text" maxlength="30" tabindex="1"/>
<input id="script-editor-filename" type="text" maxlength="100" tabindex="1"/>
</div>
<div id="ace-editor"></div>
@@ -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">
@@ -469,8 +221,6 @@
<!-- Single Faction info (when you select a faction from the Factions menu) -->
<div id="faction-container" class="generic-menupage-container"></div>
<div id="faction-augmentations-container" class="generic-menupage-container"></div>
<!-- Augmentations -->
<div id="augmentations-container" class="generic-menupage-container"></div>
@@ -478,150 +228,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": {
@@ -114,5 +114,5 @@
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development"
},
"version": "0.45.0"
"version": "0.46.2"
}

View File

@@ -1,35 +1,38 @@
import {workerScripts,
killWorkerScript} from "./NetscriptWorker";
import { Player } from "./Player";
import { getServer } from "./Server/ServerHelpers";
import {numeralWrapper} from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement";
import {arrayToString} from "../utils/helpers/arrayToString";
import {createElement} from "../utils/uiHelpers/createElement";
import {createProgressBarText} from "../utils/helpers/createProgressBarText";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {getElementById} from "../utils/uiHelpers/getElementById";
import {logBoxCreate} from "../utils/LogBox";
import {formatNumber,
convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {removeElement} from "../utils/uiHelpers/removeElement";
import {roundToTwo} from "../utils/helpers/roundToTwo";
import {Page, routing} from "./ui/navigationTracking";
// TODO - Convert this to React
import { workerScripts, killWorkerScript } from "./NetscriptWorker";
import { Player } from "./Player";
import { getServer } from "./Server/ServerHelpers";
/* {
* serverName: {
* header: Server Header Element
* panel: Server Panel List (ul) element
* scripts: {
* script id: Ref to Script information
* }
* }
* ...
import { Page, routing } from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import { logBoxCreate } from "../utils/LogBox";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { arrayToString } from "../utils/helpers/arrayToString";
import { createProgressBarText } from "../utils/helpers/createProgressBarText";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { getElementById } from "../utils/uiHelpers/getElementById";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../utils/uiHelpers/removeElement";
/**
* {
* serverName: {
* header: Server Header Element
* panel: Server Panel List (ul) element
* scripts: {
* script id: Ref to Script information
* }
* }
* ...
*/
let ActiveScriptsUI = {};
let ActiveScriptsTasks = []; //Sequentially schedule the creation/deletion of UI elements
const ActiveScriptsUI = {};
const ActiveScriptsTasks = []; // Sequentially schedule the creation/deletion of UI elements
const getHeaderHtml = (server) => {
// TODO: calculate the longest hostname length rather than hard coding it
@@ -48,7 +51,7 @@ const updateHeaderHtml = (server) => {
return;
}
// convert it to a string, as that's how it's stored it will come out of the data attributes
// Convert it to a string, as that's how it's stored it will come out of the data attributes
const ramPercentage = '' + roundToTwo(server.ramUsed / server.maxRam);
if (accordion.header.dataset.ramPercentage !== ramPercentage) {
accordion.header.dataset.ramPercentage = ramPercentage;
@@ -81,16 +84,18 @@ function createActiveScriptsServerPanel(server) {
header: hdr,
panel: panel,
panelList: panelScriptList,
scripts: {}, //Holds references to li elements for each active script
scriptHdrs: {}, //Holds references to header elements for each active script
scriptStats: {} //Holds references to the p elements containing text for each active script
scripts: {}, // Holds references to li elements for each active script
scriptHdrs: {}, // Holds references to header elements for each active script
scriptStats: {}, // Holds references to the p elements containing text for each active script
};
return li;
}
//Deletes the info for a particular server (Dropdown header + Panel with all info)
//in the Active Scripts page if it exists
/**
* Deletes the info for a particular server (Dropdown header + Panel with all info)
* in the Active Scripts page if it exists
*/
function deleteActiveScriptsServerPanel(server) {
let hostname = server.hostname;
if (ActiveScriptsUI[hostname] == null) {
@@ -98,9 +103,9 @@ function deleteActiveScriptsServerPanel(server) {
return;
}
//Make sure it's empty
// Make sure it's empty
if (Object.keys(ActiveScriptsUI[hostname].scripts).length > 0) {
console.log("WARNING: Tried to delete Active Scripts Server panel that still has scripts. Aborting");
console.warn("Tried to delete Active Scripts Server panel that still has scripts. Aborting");
return;
}
@@ -112,7 +117,7 @@ function deleteActiveScriptsServerPanel(server) {
function addActiveScriptsItem(workerscript) {
var server = getServer(workerscript.serverIp);
if (server == null) {
console.log("ERROR: Invalid server IP for workerscript in addActiveScriptsItem()");
console.warn("Invalid server IP for workerscript in addActiveScriptsItem()");
return;
}
let hostname = server.hostname;
@@ -122,7 +127,7 @@ function addActiveScriptsItem(workerscript) {
createActiveScriptsServerPanel(server);
}
//Create the unique identifier (key) for this script
// Create the unique identifier (key) for this script
var itemNameArray = ["active", "scripts", hostname, workerscript.name];
for (var i = 0; i < workerscript.args.length; ++i) {
itemNameArray.push(String(workerscript.args[i]));
@@ -139,8 +144,10 @@ function addActiveScriptsItem(workerscript) {
panel.classList.remove("accordion-panel");
panel.classList.add("active-scripts-script-panel");
//Handle the constant elements on the panel that don't change after creation
//Threads, args, kill/log button
/**
* Handle the constant elements on the panel that don't change after creation:
* Threads, args, kill/log button
*/
panel.appendChild(createElement("p", {
innerHTML: "Threads: " + workerscript.scriptRef.threads + "<br>" +
"Args: " + arrayToString(workerscript.args)
@@ -173,7 +180,7 @@ function addActiveScriptsItem(workerscript) {
}
}));
//Append element to list
// Append element to list
ActiveScriptsUI[hostname]["panelList"].appendChild(li);
ActiveScriptsUI[hostname].scripts[itemName] = li;
ActiveScriptsUI[hostname].scriptHdrs[itemName] = hdr;
@@ -218,11 +225,13 @@ function deleteActiveScriptsItem(workerscript) {
}.bind(null, workerscript));
}
//Update the ActiveScriptsItems array
function updateActiveScriptsItems(maxTasks=150) {
//Run tasks that need to be done sequentially (adding items, creating/deleting server panels)
//We'll limit this to 150 at a time in case someone decides to start a bunch of scripts all at once...
let numTasks = Math.min(maxTasks, ActiveScriptsTasks.length);
/**
* Run tasks that need to be done sequentially (adding items, creating/deleting server panels)
* We'll limit this to 150 at a time for performance (in case someone decides to start a
* bunch of scripts all at once...)
*/
const numTasks = Math.min(maxTasks, ActiveScriptsTasks.length);
for (let i = 0; i < numTasks; ++i) {
let task = ActiveScriptsTasks.shift();
try {
@@ -233,8 +242,8 @@ function updateActiveScriptsItems(maxTasks=150) {
}
}
if (!routing.isOn(Page.ActiveScripts)) {return;}
var total = 0;
if (!routing.isOn(Page.ActiveScripts)) { return; }
let total = 0;
for (var i = 0; i < workerScripts.length; ++i) {
try {
total += updateActiveScriptsItemContent(workerScripts[i]);
@@ -246,10 +255,10 @@ function updateActiveScriptsItems(maxTasks=150) {
getElementById("active-scripts-total-production-active").innerText = numeralWrapper.formatMoney(total);
getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug);
getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000));
return total;
}
//Updates the content of the given item in the Active Scripts list
function updateActiveScriptsItemContent(workerscript) {
var server = getServer(workerscript.serverIp);
if (server == null) {
@@ -258,7 +267,7 @@ function updateActiveScriptsItemContent(workerscript) {
}
let hostname = server.hostname;
if (ActiveScriptsUI[hostname] == null) {
return; //Hasn't been created yet. We'll skip it
return; // Hasn't been created yet. We'll skip it
}
updateHeaderHtml(server);
@@ -270,11 +279,11 @@ function updateActiveScriptsItemContent(workerscript) {
var itemName = itemNameArray.join("-");
if (ActiveScriptsUI[hostname].scriptStats[itemName] == null) {
return; //Hasn't been fully added yet. We'll skip it
return; // Hasn't been fully added yet. We'll skip it
}
var item = ActiveScriptsUI[hostname].scriptStats[itemName];
//Update the text if necessary. This fn returns the online $/s production
// Update the text if necessary. This fn returns the online $/s production
return updateActiveScriptsText(workerscript, item, itemName);
}
@@ -293,7 +302,7 @@ function updateActiveScriptsText(workerscript, item, itemName) {
updateHeaderHtml(server);
var onlineMps = workerscript.scriptRef.onlineMoneyMade / workerscript.scriptRef.onlineRunningTime;
//Only update if the item is visible
// Only update if the item is visible
if (ActiveScriptsUI[hostname].header.classList.contains("active") === false) {return onlineMps;}
if (ActiveScriptsUI[hostname].scriptHdrs[itemName].classList.contains("active") === false) {return onlineMps;}
@@ -302,7 +311,7 @@ function updateActiveScriptsText(workerscript, item, itemName) {
var onlineTime = "Online Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.onlineRunningTime * 1e3);
var offlineTime = "Offline Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.offlineRunningTime * 1e3);
//Online
// Online
var onlineTotalMoneyMade = "Total online production: " + numeralWrapper.formatMoney(workerscript.scriptRef.onlineMoneyMade);
var onlineTotalExpEarned = (Array(26).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.onlineExpGained) + " hacking exp").replace( / /g, "&nbsp;");
@@ -310,7 +319,7 @@ function updateActiveScriptsText(workerscript, item, itemName) {
var onlineEps = workerscript.scriptRef.onlineExpGained / workerscript.scriptRef.onlineRunningTime;
var onlineEpsText = (Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, "&nbsp;");
//Offline
// Offline
var offlineTotalMoneyMade = "Total offline production: " + numeralWrapper.formatMoney(workerscript.scriptRef.offlineMoneyMade);
var offlineTotalExpEarned = (Array(27).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.offlineExpGained) + " hacking exp").replace( / /g, "&nbsp;");

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 {
@@ -19,8 +20,8 @@ function loadGlobalAliases(saveString) {
}
}
//Print all aliases to terminal
function printAliases() {
// Prints all aliases to terminal
export function printAliases(): void {
for (var name in Aliases) {
if (Aliases.hasOwnProperty(name)) {
post("alias " + name + "=" + Aliases[name]);
@@ -33,8 +34,8 @@ function printAliases() {
}
}
//True if successful, false otherwise
function parseAliasDeclaration(dec,global=false) {
// Returns true if successful, false otherwise
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,55 @@ 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(" ");
/**
* Returns the original string with any aliases substituted in.
* Aliases are only applied to "whole words", one level deep
*/
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 +118,3 @@ function substituteAliases(origCommand) {
}
return commandArray.join(" ");
}
export {Aliases, GlobalAliases, printAliases, parseAliasDeclaration,
removeAlias, substituteAliases, loadAliases, loadGlobalAliases};

View File

@@ -10,6 +10,7 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviv
interface IConstructorParams {
info: string;
isSpecial?: boolean;
moneyCost: number;
name: string;
prereqs?: string[];
@@ -62,6 +63,9 @@ export class Augmentation {
// Description of what this Aug is and what it does
info: string = "";
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
isSpecial: boolean = false;
// Augmentation level - for repeatable Augs like NeuroFlux Governor
level: number = 0;
@@ -90,6 +94,10 @@ export class Augmentation {
this.baseCost = params.moneyCost * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
this.startingCost = this.baseCost;
if (params.isSpecial) {
this.isSpecial = true;
}
this.level = 0;
// Set multipliers

File diff suppressed because it is too large Load Diff

View File

@@ -49,6 +49,7 @@ export let AugmentationNames: IMap<string> = {
PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector",
ADRPheromone1: "ADR-V1 Pheromone Gene",
ADRPheromone2: "ADR-V2 Pheromone Gene",
ShadowsSimulacrum: "The Shadow's Simulacrum",
HacknetNodeCPUUpload: "Hacknet Node CPU Architecture Neural-Upload",
HacknetNodeCacheUpload: "Hacknet Node Cache Architecture Neural-Upload",
HacknetNodeNICUpload: "Hacknet Node NIC Architecture Neural-Upload",

View File

@@ -57,8 +57,9 @@ export function initBitNodes() {
"For every Faction NOT listed above, reputation gains are halved<br>" +
"You will no longer gain passive reputation with Factions<br><br>" +
"Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File increases the player's crime success rate, " +
"crime money, and charisma multipliers by:<br><br>" +
"upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " +
"once your karma decreases to a certain value. " +
"It also increases the player's crime success rate, crime money, and charisma multipliers by:<br><br>" +
"Level 1: 24%<br>" +
"Level 2: 36%<br>" +
"Level 3: 42%");
@@ -81,7 +82,8 @@ export function initBitNodes() {
"Level 1: 8%<br>" +
"Level 2: 12%<br>" +
"Level 3: 14%");
BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine", "The Singularity has arrived. The human race is gone, replaced " +
BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine",
"The Singularity has arrived. The human race is gone, replaced " +
"by artificially superintelligent beings that are more machine than man. <br><br>" +
"In this BitNode, progressing is significantly harder. Experience gain rates " +
"for all stats are reduced. Most methods of earning money will now give significantly less.<br><br>" +
@@ -92,7 +94,8 @@ export function initBitNodes() {
"upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " +
"Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " +
"that you can use.");
BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman", "They said it couldn't be done. They said the human brain, " +
BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman",
"They said it couldn't be done. They said the human brain, " +
"along with its consciousness and intelligence, couldn't be replicated. They said the complexity " +
"of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " +
"by 1's and 0's. They were wrong.<br><br>" +
@@ -173,7 +176,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 +203,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 +218,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 +231,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 +266,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 +278,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 +293,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 +307,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 +320,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 +335,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 +355,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 +366,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 +411,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 +423,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;

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,35 @@
import {Engine} from "./engine";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {createElement} from "../utils/uiHelpers/createElement";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {isString} from "../utils/helpers/isString";
import { Engine } from "./engine";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
var cinematicTextFlag = false;
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { isString } from "../utils/helpers/isString";
//Lines must be an array of strings
function writeCinematicText(lines) {
export let cinematicTextFlag = false;
/**
* Print a message using a hacking-style "typing" effect.
* Note that this clears the UI so that the text from this is the only thing visible.
*
* @param lines {string[]} Array of strings to print, where each element is a separate line
*/
export function writeCinematicText(lines) {
cinematicTextFlag = true;
if (lines.constructor !== Array) {
throw new Error("Invalid non-array argument passed into writeCinematicText()");
}
//We'll reuse the 'Red Pill' content
// Reuse the 'Red Pill' content
Engine.loadCinematicTextContent();
var container = document.getElementById("cinematic-text-container");
const container = document.getElementById("cinematic-text-container");
container.style.width = "75%";
if (container == null) {throw new Error("Could not find cinematic-text-container for writeCinematicText()");}
removeChildrenFromElement(container);
for (var i = 0; i < lines.length; ++i) {
for (let i = 0; i < lines.length; ++i) {
if (!isString(lines[i])) {
throw new Error("Invalid non-string element in 'lines' argument. writeCinematicText() failed");
}
@@ -45,11 +51,11 @@ function writeCinematicTextRecurse(lines, lineNumber=0) {
function writeCinematicTextLine(line) {
return new Promise(function(resolve, reject) {
var container = document.getElementById("cinematic-text-container");
var pElem = document.createElement("p");
const container = document.getElementById("cinematic-text-container");
const pElem = document.createElement("p");
container.appendChild(pElem);
var promise = writeCinematicTextLetter(pElem, line, 0);
const promise = writeCinematicTextLetter(pElem, line, 0);
promise.then(function(res) {
resolve(res);
}, function(e) {
@@ -61,14 +67,15 @@ function writeCinematicTextLine(line) {
function writeCinematicTextLetter(pElem, line, i=0) {
return new Promise(function(resolve, reject) {
setTimeoutRef(function() {
const textToShow = line.substring(0, i);
if (i >= line.length) {
var textToShow = line.substring(0, i);
pElem.innerHTML = textToShow;
return resolve(true);
}
var textToShow = line.substring(0, i);
pElem.innerHTML = textToShow + "<span class='typed-cursor'> &#9608; </span>";
var promise = writeCinematicTextLetter(pElem, line, i+1);
const promise = writeCinematicTextLetter(pElem, line, i+1);
promise.then(function(res) {
resolve(res);
}, function(e) {
@@ -96,5 +103,3 @@ function cinematicTextEnd() {
}));
});
}
export {cinematicTextFlag, writeCinematicText};

View File

@@ -1,12 +1,15 @@
import { CodingContract,
CodingContractRewardType,
CodingContractTypes } from "./CodingContracts";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import {
CodingContract,
CodingContractRewardType,
CodingContractTypes
} from "./CodingContracts";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { getRandomInt } from "../utils/helpers/getRandomInt";
export function generateRandomContract() {
// First select a random problem type
@@ -127,14 +130,15 @@ function getRandomReward() {
});
switch (reward.type) {
case CodingContractRewardType.FactionReputation:
case CodingContractRewardType.FactionReputation: {
// Get a random faction that player is a part of. That
// faction must allow hacking contracts
var numFactions = factionsThatAllowHacking.length;
var randFaction = factionsThatAllowHacking[getRandomInt(0, numFactions - 1)];
reward.name = randFaction;
break;
case CodingContractRewardType.CompanyReputation:
}
case CodingContractRewardType.CompanyReputation: {
const allJobs = Object.keys(Player.jobs);
if (allJobs.length > 0) {
reward.name = allJobs[getRandomInt(0, allJobs.length - 1)];
@@ -142,6 +146,7 @@ function getRandomReward() {
reward.type = CodingContractRewardType.Money;
}
break;
}
default:
break;
}

View File

@@ -1,11 +1,17 @@
import { codingContractTypesMetadata,
DescriptionFunc,
GeneratorFunc,
SolverFunc } from "./data/codingcontracttypes";
import {
codingContractTypesMetadata,
DescriptionFunc,
GeneratorFunc,
SolverFunc
} from "./data/codingcontracttypes";
import { IMap } from "./types";
import { IMap } from "./types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import {
Generic_fromJSON,
Generic_toJSON,
Reviver
} from "../utils/JSONReviver";
import { KEY } from "../utils/helpers/keyCodes";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
@@ -13,6 +19,7 @@ import { removeElementById } from "../utils/uiHelpers/removeElementById";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
/* Represents different types of problems that a Coding Contract can have */

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,110 +1,100 @@
import {IMap} from "./types";
/**
* 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.3",
//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
//the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
/** 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
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
*/
MaxSkillLevel: 975,
//Milliseconds per game cycle
// Milliseconds per game cycle
MilliPerCycle: 200,
//How much reputation is needed to join a megacorporation's faction
// How much reputation is needed to join a megacorporation's faction
CorpFactionRepRequirement: 200e3,
/* Base costs */
// Base RAM costs
BaseCostFor1GBOfRamHome: 32000,
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
BaseCostFor1GBOfRamHacknetNode: 30000,
// Cost to travel to another city
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,
// Faction and Company favor-related things
BaseFavorToDonate: 150,
DonateMoneyToRepDivisor: 1e6,
FactionReputationToFavorBase: 500,
FactionReputationToFavorMult: 1.02,
CompanyReputationToFavorBase: 500,
CompanyReputationToFavorMult: 1.02,
/* Augmentation */
//NeuroFlux Governor cost multiplier as you level up
// NeuroFlux Governor Augmentation cost multiplier
NeuroFluxGovernorLevelMult: 1.14,
/* Netscript Constants */
//RAM Costs for different commands
ScriptBaseRamCost: 1.6,
ScriptDomRamCost: 25,
ScriptWhileRamCost: 0,
ScriptForRamCost: 0,
ScriptIfRamCost: 0,
ScriptHackRamCost: 0.1,
ScriptHackAnalyzeRamCost: 1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
ScriptSpawnRamCost: 2.0,
ScriptScpRamCost: 0.6,
ScriptKillRamCost: 0.5, //Kill and killall
ScriptHasRootAccessRamCost: 0.05,
ScriptGetHostnameRamCost: 0.05, //getHostname() and getIp()
ScriptGetHackingLevelRamCost: 0.05, //getHackingLevel()
ScriptGetMultipliersRamCost: 4.0, //getHackingMultipliers() and getBitNodeMultipliers()
ScriptGetServerRamCost: 0.1,
ScriptFileExistsRamCost: 0.1,
ScriptIsRunningRamCost: 0.1,
ScriptHacknetNodesRamCost: 4.0, //Base cost for accessing Hacknet Node API
ScriptHNUpgLevelRamCost: 0.4,
ScriptHNUpgRamRamCost: 0.6,
ScriptHNUpgCoreRamCost: 0.8,
ScriptGetStockRamCost: 2.0,
ScriptBuySellStockRamCost: 2.5,
// RAM Costs for Netscript functions
ScriptBaseRamCost: 1.6,
ScriptDomRamCost: 25,
ScriptWhileRamCost: 0,
ScriptForRamCost: 0,
ScriptIfRamCost: 0,
ScriptHackRamCost: 0.1,
ScriptHackAnalyzeRamCost: 1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
ScriptRunRamCost: 1.0,
ScriptExecRamCost: 1.3,
ScriptSpawnRamCost: 2.0,
ScriptScpRamCost: 0.6,
ScriptKillRamCost: 0.5,
ScriptHasRootAccessRamCost: 0.05,
ScriptGetHostnameRamCost: 0.05,
ScriptGetHackingLevelRamCost: 0.05,
ScriptGetMultipliersRamCost: 4.0,
ScriptGetServerRamCost: 0.1,
ScriptFileExistsRamCost: 0.1,
ScriptIsRunningRamCost: 0.1,
ScriptHacknetNodesRamCost: 4.0,
ScriptHNUpgLevelRamCost: 0.4,
ScriptHNUpgRamRamCost: 0.6,
ScriptHNUpgCoreRamCost: 0.8,
ScriptGetStockRamCost: 2.0,
ScriptBuySellStockRamCost: 2.5,
ScriptGetPurchaseServerRamCost: 0.25,
ScriptPurchaseServerRamCost: 2.25,
ScriptGetPurchasedServerLimit: 0.05,
ScriptPurchaseServerRamCost: 2.25,
ScriptGetPurchasedServerLimit: 0.05,
ScriptGetPurchasedServerMaxRam: 0.05,
ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0, // Functions that apply to all scripts regardless of args
ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost:10,
ScriptSleeveBaseRamCost: 4,
ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0,
ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost: 10,
ScriptSleeveBaseRamCost: 4,
ScriptSingularityFn1RamCost: 1,
ScriptSingularityFn2RamCost: 2,
ScriptSingularityFn3RamCost: 3,
ScriptSingularityFn1RamCost: 1,
ScriptSingularityFn2RamCost: 2,
ScriptSingularityFn3RamCost: 3,
ScriptSingularityFnRamMult: 2, // Multiplier for RAM cost outside of BN-4
ScriptSingularityFnRamMult: 2, // Multiplier for RAM cost outside of BN-4
ScriptGangApiBaseRamCost: 4,
ScriptGangApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 4,
NumNetscriptPorts: 20,
NumNetscriptPorts: 20,
//Server constants
// Server-related constants
HomeComputerMaxRam: 1073741824, // 2 ^ 30
ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate
ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security)
@@ -112,48 +102,50 @@ export let CONSTANTS: IMap<any> = {
ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened
PurchasedServerLimit: 25,
PurchasedServerMaxRam: 1048576, //2^20
PurchasedServerMaxRam: 1048576, // 2^20
//Augmentation Constants
AugmentationCostMultiplier: 5, //Used for balancing costs without having to readjust every Augmentation cost
AugmentationRepMultiplier: 2.5, //Used for balancing rep cost without having to readjust every value
MultipleAugMultiplier: 1.9,
// Augmentation Constants
AugmentationCostMultiplier: 5, // Used for balancing costs without having to readjust every Augmentation cost
AugmentationRepMultiplier: 2.5, // Used for balancing rep cost without having to readjust every value
MultipleAugMultiplier: 1.9,
//How much a TOR router costs
TorRouterCost: 200000,
// TOR Router
TorRouterCost: 200e3,
//Infiltration constants
// Infiltration
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
InfiltrationExpPow: 0.8,
//Stock market constants
WSEAccountCost: 200e6,
TIXAPICost: 5e9,
MarketData4SCost: 1e9,
// Stock market
WSEAccountCost: 200e6,
TIXAPICost: 5e9,
MarketData4SCost: 1e9,
MarketDataTixApi4SCost: 25e9,
StockMarketCommission: 100e3,
StockMarketCommission: 100e3,
//Hospital/Health
// Hospital/Health
HospitalCostPerHp: 100e3,
//Intelligence-related constants
IntelligenceCrimeWeight: 0.05, //Weight for how much int affects crime success rates
IntelligenceInfiltrationWeight: 0.1, //Weight for how much int affects infiltration success rates
// Intelligence-related constants
IntelligenceCrimeWeight: 0.05, // Weight for how much int affects crime success rates
IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates
IntelligenceCrimeBaseExpGain: 0.001,
IntelligenceProgramBaseExpGain: 500, //Program required hack level divided by this to determine int exp gain
IntelligenceTerminalHackBaseExpGain: 200, //Hacking exp divided by this to determine int exp gain
IntelligenceProgramBaseExpGain: 500, // Program required hack level divided by this to determine int exp gain
IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain
IntelligenceSingFnBaseExpGain: 0.002,
IntelligenceClassBaseExpGain: 0.000001,
IntelligenceHackingMissionBaseExpGain: 0.03, //Hacking Mission difficulty multiplied by this to get exp gain
IntelligenceHackingMissionBaseExpGain: 0.03, // Hacking Mission difficulty multiplied by this to get exp gain
//Hacking Missions
HackingMissionRepToDiffConversion: 10000, //Faction rep is divided by this to get mission difficulty
HackingMissionRepToRewardConversion: 7, //Faction rep divided byt his to get mission rep reward
HackingMissionSpamTimeIncrease: 25000, //How much time limit increase is gained when conquering a Spam Node (ms)
HackingMissionTransferAttackIncrease: 1.05, //Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node
HackingMissionMiscDefenseIncrease: 1.05, //The amount by which every misc node's defense is multiplied when one is conquered
HackingMissionDifficultyToHacking: 135, //Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc)
// Hacking Missions
// TODO Move this into Hacking Mission implementation
HackingMissionRepToDiffConversion: 10000, // Faction rep is divided by this to get mission difficulty
HackingMissionRepToRewardConversion: 7, // Faction rep divided byt his to get mission rep reward
HackingMissionSpamTimeIncrease: 25000, // How much time limit increase is gained when conquering a Spam Node (ms)
HackingMissionTransferAttackIncrease: 1.05, // Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node
HackingMissionMiscDefenseIncrease: 1.05, // The amount by which every misc node's defense is multiplied when one is conquered
HackingMissionDifficultyToHacking: 135, // Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc)
HackingMissionHowToPlay: "Hacking missions are a minigame that, if won, will reward you with faction reputation.<br><br>" +
"In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " +
"are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " +
@@ -201,7 +193,7 @@ export let CONSTANTS: IMap<any> = {
"-Miscellaneous Nodes slowly raise their defense over time<br><br>" +
"-Nodes slowly regenerate health over time.",
/* Time Constants */
// Time-related constants
MillisecondsPer20Hours: 72000000,
GameCyclesPer20Hours: 72000000 / 200,
@@ -229,7 +221,7 @@ export let CONSTANTS: IMap<any> = {
MillisecondsPerFiveMinutes: 300000,
GameCyclesPerFiveMinutes: 300000 / 200,
/* Player Work / Action related Constants */
// Player Work & Action
FactionWorkHacking: "Faction Hacking Work",
FactionWorkField: "Faction Field Work",
FactionWorkSecurity: "Faction Security Work",
@@ -272,47 +264,46 @@ export let CONSTANTS: IMap<any> = {
CrimeAssassination: "assassinate a high-profile target",
CrimeHeist: "pull off the ultimate heist",
/* Coding Contract Constants */
CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6,
// Coding Contract
// TODO Move this into Coding contract impelmentation?
CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6,
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
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.3
* Added a new Augmentation: The Shadow's Simulacrum
* Improved tab autocompletion feature in Terminal so that it works better with directories
* Bug Fix: Tech vendor location UI now properly refreshed when purchasing a TOR router
* Bug Fix: Fixed UI issue with faction donations
* Bug Fix: The money statistics & breakdown should now properly track money earned from Hacknet Server (hashes -> money)
* Bug Fix: Fixed issue with changing input in 'Minimum Path Sum in a Triangle' coding contract problem
* Fixed several typos in various places
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
v0.46.2
* Source-File 2 now allows you to form gangs in other BitNodes when your karma reaches a very large negative value
** (Karma is a hidden stat and is lowered by committing crimes)
* Gang changes:
** Bug Fix: Gangs can no longer clash with themselve
** Bug Fix: Winning against another gang should properly reduce their power
* Bug Fix: Terminal 'wget' command now works properly
* Bug Fix: Hacknet Server Hash upgrades now properly reset upon installing Augs/switching BitNodes
* Bug Fix: Fixed button for creating Corporations
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
* 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

@@ -1,38 +1,44 @@
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { CodingContractTypes } from "./CodingContracts";
import { generateContract,
generateRandomContract,
generateRandomContractOnHome } from "./CodingContractGenerator";
import { Companies } from "./Company/Companies";
import { Company } from "./Company/Company";
import { Programs } from "./Programs/Programs";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket,
SymbolToStockMap } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock";
import { Terminal } from "./Terminal";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { CodingContractTypes } from "./CodingContracts";
import {
generateContract,
generateRandomContract,
generateRandomContractOnHome
} from "./CodingContractGenerator";
import { Companies } from "./Company/Companies";
import { Company } from "./Company/Company";
import { Programs } from "./Programs/Programs";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket, SymbolToStockMap } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock";
import { Terminal } from "./Terminal";
import { numeralWrapper } from "./ui/numeralFormat";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { createElement } from "../utils/uiHelpers/createElement";
import { createOptionElement } from "../utils/uiHelpers/createOptionElement";
import { getSelectText } from "../utils/uiHelpers/getSelectData";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { createElement } from "../utils/uiHelpers/createElement";
import { createOptionElement } from "../utils/uiHelpers/createOptionElement";
import { getSelectText } from "../utils/uiHelpers/getSelectData";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import React from "react";
import ReactDOM from "react-dom";
import ReactDOM from "react-dom";
const Component = React.Component;
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12];
// Update as additional BitNodes get implemented
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
// Some dev menu buttons just add a lot of something for convenience
const tonsPP = 1e27;
const tonsP = 1e12;
class ValueAdjusterComponent extends Component {
constructor(props) {
@@ -41,7 +47,7 @@ class ValueAdjusterComponent extends Component {
this.setValue = this.setValue.bind(this);
}
setValue(event) {
this.setState({ value: event.target.value });
this.setState({ value: parseFloat(event.target.value) });
}
render() {
const { title, add, subtract, reset } = this.props;
@@ -124,7 +130,6 @@ class DevMenuComponent extends Component {
this.setState({ codingcontract: event.target.value });
}
addMoney(n) {
return function() {
Player.gainMoney(n);
@@ -186,14 +191,20 @@ class DevMenuComponent extends Component {
}
}
modifyKarma(modifier) {
return function(amt) {
Player.karma += (amt * modifier);
}
}
tonsOfExp() {
Player.gainHackingExp(1e27);
Player.gainStrengthExp(1e27);
Player.gainDefenseExp(1e27);
Player.gainDexterityExp(1e27);
Player.gainAgilityExp(1e27);
Player.gainCharismaExp(1e27);
Player.gainIntelligenceExp(1e27);
Player.gainHackingExp(tonsPP);
Player.gainStrengthExp(tonsPP);
Player.gainDefenseExp(tonsPP);
Player.gainDexterityExp(tonsPP);
Player.gainAgilityExp(tonsPP);
Player.gainCharismaExp(tonsPP);
Player.gainIntelligenceExp(tonsPP);
Player.updateSkillLevels();
}
@@ -237,6 +248,12 @@ class DevMenuComponent extends Component {
}
}
resetKarma() {
return function() {
Player.karma = 0;
}
}
enableIntelligence() {
if(Player.intelligence === 0) {
Player.intelligence = 1;
@@ -296,7 +313,7 @@ class DevMenuComponent extends Component {
tonsOfRep() {
for (const i in Factions) {
Factions[i].playerReputation = 1e27;
Factions[i].playerReputation = tonsPP;
}
}
@@ -308,7 +325,7 @@ class DevMenuComponent extends Component {
tonsOfFactionFavor() {
for (const i in Factions) {
Factions[i].favor = 1e27;
Factions[i].favor = tonsPP;
}
}
@@ -454,7 +471,7 @@ class DevMenuComponent extends Component {
tonsOfRepCompanies() {
for (const c in Companies) {
Companies[c].playerReputation = 1e12;
Companies[c].playerReputation = tonsP;
}
}
@@ -466,7 +483,7 @@ class DevMenuComponent extends Component {
tonsOfFavorCompanies() {
for (const c in Companies) {
Companies[c].favor = 1e12;
Companies[c].favor = tonsP;
}
}
@@ -491,7 +508,7 @@ class DevMenuComponent extends Component {
addTonsBladeburnerRank() {
if (!!Player.bladeburner) {
Player.bladeburner.changeRank(1e12);
Player.bladeburner.changeRank(tonsP);
}
}
@@ -511,13 +528,13 @@ class DevMenuComponent extends Component {
addTonsBladeburnerCycles() {
if (!!Player.bladeburner) {
Player.bladeburner.storedCycles += 1e12;
Player.bladeburner.storedCycles += tonsP;
}
}
addTonsGangCycles() {
if (!!Player.gang) {
Player.gang.storedCycles = 1e12;
Player.gang.storedCycles = tonsP;
}
}
@@ -537,7 +554,7 @@ class DevMenuComponent extends Component {
addTonsCorporationCycles() {
if (!!Player.corporation) {
Player.corporation.storedCycles = 1e12;
Player.corporation.storedCycles = tonsP;
}
}
@@ -644,16 +661,16 @@ class DevMenuComponent extends Component {
let sourceFiles = [];
validSFN.forEach( i => sourceFiles.push(
<tr key={'sf-'+i}>
<td><span className="text">SF-{i}:</span></td>
<td>
<button className="std-button touch-right" onClick={this.setSF(i, 0)}>0</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 1)}>1</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 2)}>2</button>
<button className="std-button touch-left" onClick={this.setSF(i, 3)}>3</button>
</td>
</tr>
));
<tr key={'sf-'+i}>
<td><span className="text">SF-{i}:</span></td>
<td>
<button className="std-button touch-right" onClick={this.setSF(i, 0)}>0</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 1)}>1</button>
<button className="std-button touch-sides" onClick={this.setSF(i, 2)}>2</button>
<button className="std-button touch-left" onClick={this.setSF(i, 3)}>3</button>
</td>
</tr>
));
@@ -713,11 +730,11 @@ class DevMenuComponent extends Component {
<span className="text text-center">Hacking:</span>
</td>
<td>
<ValueAdjusterComponent
title="hacking exp"
add={this.modifyExp('hacking', 1)}
subtract={this.modifyExp('hacking', -1)}
reset={this.resetExperience('hacking')}
<ValueAdjusterComponent
title="hacking exp"
add={this.modifyExp('hacking', 1)}
subtract={this.modifyExp('hacking', -1)}
reset={this.resetExperience('hacking')}
/>
</td>
</tr>
@@ -726,7 +743,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Strength:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="strength exp"
add={this.modifyExp('strength', 1)}
subtract={this.modifyExp('strength', -1)}
@@ -739,7 +756,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Defense:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="defense exp"
add={this.modifyExp('defense', 1)}
subtract={this.modifyExp('defense', -1)}
@@ -752,7 +769,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Dexterity:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="dexterity exp"
add={this.modifyExp('dexterity', 1)}
subtract={this.modifyExp('dexterity', -1)}
@@ -765,7 +782,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Agility:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="agility exp"
add={this.modifyExp('agility', 1)}
subtract={this.modifyExp('agility', -1)}
@@ -778,7 +795,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Charisma:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="charisma exp"
add={this.modifyExp('charisma', 1)}
subtract={this.modifyExp('charisma', -1)}
@@ -791,7 +808,7 @@ class DevMenuComponent extends Component {
<span className="text text-center">Intelligence:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="intelligence exp"
add={this.modifyExp('intelligence', 1)}
subtract={this.modifyExp('intelligence', -1)}
@@ -805,6 +822,19 @@ class DevMenuComponent extends Component {
<button className="std-button" onClick={this.disableIntelligence}>Disable</button>
</td>
</tr>
<tr>
<td>
<span className="text text-center">Karma:</span>
</td>
<td>
<ValueAdjusterComponent
title="karma"
add={this.modifyKarma(1)}
subtract={this.modifyKarma(-1)}
reset={this.resetKarma()}
/>
</td>
</tr>
</tbody>
</table>
</div>
@@ -832,7 +862,7 @@ class DevMenuComponent extends Component {
<span className="text">Reputation:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="reputation"
add={this.modifyFactionRep(1)}
subtract={this.modifyFactionRep(-1)}
@@ -845,7 +875,7 @@ class DevMenuComponent extends Component {
<span className="text">Favor:</span>
</td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="favor"
add={this.modifyFactionFavor(1)}
subtract={this.modifyFactionFavor(-1)}
@@ -979,7 +1009,7 @@ class DevMenuComponent extends Component {
<tr>
<td><span className="text">Reputation:</span></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="reputation"
add={this.modifyCompanyRep(1)}
subtract={this.modifyCompanyRep(-1)}
@@ -990,7 +1020,7 @@ class DevMenuComponent extends Component {
<tr>
<td><span className="text">Favor:</span></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="favor"
add={this.modifyCompanyFavor(1)}
subtract={this.modifyCompanyFavor(-1)}
@@ -1028,7 +1058,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Rank:</span></td>
<td><button className="std-button" onClick={this.addTonsBladeburnerRank}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="rank"
add={this.modifyBladeburnerRank(1)}
subtract={this.modifyBladeburnerRank(-1)}
@@ -1040,7 +1070,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Cycles:</span></td>
<td><button className="std-button" onClick={this.addTonsBladeburnerCycles}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="cycles"
add={this.modifyBladeburnerCycles(1)}
subtract={this.modifyBladeburnerCycles(-1)}
@@ -1064,7 +1094,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Cycles:</span></td>
<td><button className="std-button" onClick={this.addTonsGangCycles}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="cycles"
add={this.modifyGangCycles(1)}
subtract={this.modifyGangCycles(-1)}
@@ -1088,7 +1118,7 @@ class DevMenuComponent extends Component {
<td><span className="text">Cycles:</span></td>
<td><button className="std-button" onClick={this.addTonsCorporationCycles}>Tons</button></td>
<td>
<ValueAdjusterComponent
<ValueAdjusterComponent
title="cycles"
add={this.modifyCorporationCycles(1)}
subtract={this.modifyCorporationCycles(-1)}
@@ -1121,7 +1151,7 @@ class DevMenuComponent extends Component {
{contractTypes}
</select>
<button className="std-button" onClick={this.specificContract}>Generate Specified Contract Type on Home Comp</button>
</td>
</tr>
</tbody>
@@ -1185,7 +1215,6 @@ class DevMenuComponent extends Component {
}
}
const devMenuContainerId = "dev-menu-container";
export function createDevMenu() {
@@ -1199,11 +1228,11 @@ export function createDevMenu() {
id: devMenuContainerId,
});
const entireGameContainer = document.getElementById("entire-game-container");
if (entireGameContainer == null) {
throw new Error("Could not find entire-game-container DOM element");
}
entireGameContainer.appendChild(devMenuContainer);
const entireGameContainer = document.getElementById("entire-game-container");
if (entireGameContainer == null) {
throw new Error("Could not find entire-game-container DOM element");
}
entireGameContainer.appendChild(devMenuContainer);
ReactDOM.render(<DevMenuComponent />, devMenuContainer);
}

View File

@@ -97,16 +97,6 @@ export class Faction {
return [favorGain, rep];
}
//Adds all Augmentations to this faction.
addAllAugmentations(augs: object): void {
this.augmentations.length = 0;
for (const name in augs) {
if (augs.hasOwnProperty(name)) {
this.augmentations.push(name);
}
}
}
/**
* Serialize the current object to a JSON save state.
*/

7
src/Faction/FactionHelpers.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { Augmentation } from "../Augmentation/Augmentation";
import { Faction } from "../Faction/Faction";
export declare function getNextNeurofluxLevel(): number;
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
export declare function purchaseAugmentationBoxCreate(aug: Augmentation, fac: Faction): void;
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;

View File

@@ -1,695 +0,0 @@
import { Augmentations } from "../Augmentation/Augmentations";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
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 {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat";
import {dialogBoxCreate} from "../../utils/DialogBox";
import {factionInvitationBoxCreate} from "../../utils/FactionInvitationBox";
import {removeChildrenFromElement} from "../../utils/uiHelpers/removeChildrenFromElement";
import {createElement} from "../../utils/uiHelpers/createElement";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../../utils/JSONReviver";
import {formatNumber} from "../../utils/StringHelperFunctions";
import {yesNoBoxCreate, yesNoBoxGetYesButton,
yesNoBoxGetNoButton, yesNoBoxClose} from "../../utils/YesNoBox";
function inviteToFaction(faction) {
if (Settings.SuppressFactionInvites) {
faction.alreadyInvited = true;
Player.factionInvitations.push(faction.name);
if (routing.isOn(Page.Factions)) {
Engine.loadFactionsContent();
}
} else {
factionInvitationBoxCreate(faction);
}
}
function joinFaction(faction) {
faction.isMember = true;
Player.factions.push(faction.name);
const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction
for(const i in factionInfo.enemies) {
const enemy = factionInfo.enemies[i];
if (Factions[enemy] instanceof Faction) {
Factions[enemy].isBanned = true;
}
}
}
//Displays the HTML content for a specific faction
function displayFactionContent(factionName) {
var faction = Factions[factionName];
if (faction == null) {
throw new Error("Invalid factionName passed into displayFactionContent: " + factionName);
}
if (!faction.isMember) {
throw new Error("Not a member of this faction, cannot display faction information");
}
var factionInfo = faction.getInfo();
removeChildrenFromElement(Engine.Display.factionContent);
var elements = [];
//Header and faction info
elements.push(createElement("h1", {
innerText:factionName
}));
elements.push(createElement("pre", {
innerHTML:"<i>" + factionInfo.infoText + "</i>"
}));
elements.push(createElement("p", {
innerText:"---------------",
}));
//Faction reputation and favor
var favorGain = faction.getFavorGain();
if (favorGain.length != 2) {favorGain = 0;}
favorGain = favorGain[0];
elements.push(createElement("p", {
innerText: "Reputation: " + formatNumber(faction.playerReputation, 4),
tooltip:"You will earn " + formatNumber(favorGain, 0) +
" faction favor upon resetting after installing an Augmentation"
}))
elements.push(createElement("p", {
innerText:"---------------",
}));
elements.push(createElement("p", {
innerText:"Faction Favor: " + formatNumber(faction.favor, 0),
tooltip:"Faction favor increases the rate at which " +
"you earn reputation for this faction by 1% per favor. Faction favor " +
"is gained whenever you reset after installing an Augmentation. The amount of " +
"favor you gain depends on how much reputation you have with the faction"
}));
elements.push(createElement("p", {
innerText:"---------------",
}));
//Faction Work Description Text
elements.push(createElement("pre", {
id:"faction-work-description-text",
innerText:"Perform work/carry out assignments for your faction to help further its cause! By doing so " +
"you will earn reputation for your faction. You will also gain reputation passively over time, " +
"although at a very slow rate. Earning reputation will allow you to purchase Augmentations " +
"through this faction, which are powerful upgrades that enhance your abilities. Note that you cannot " +
"use your terminal or create scripts when you are performing a task!"
}));
elements.push(createElement("br"));
//Hacking Mission Option
var hackMissionDiv = createElement("div", { class:"faction-work-div" });
var hackMissionDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
hackMissionDiv.appendChild(hackMissionDivWrapper);
hackMissionDivWrapper.appendChild(createElement("a", {
class:"a-link-button", innerText:"Hacking Mission",
clickListener:()=>{
Engine.loadMissionContent();
var mission = new HackingMission(faction.playerReputation, faction);
setInMission(true, mission); //Sets inMission flag to true
mission.init();
return false;
}
}));
hackMissionDivWrapper.appendChild(createElement("p", {
innerText:"Attempt a hacking mission for your faction. " +
"A mission is a mini game that, if won, earns you " +
"significant reputation with this faction. (Recommended hacking level: 200+)"
}));
elements.push(hackMissionDiv);
//Hacking Contracts Option
var hackDiv = createElement("div", { class:"faction-work-div", });
var hackDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
hackDiv.appendChild(hackDivWrapper);
hackDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Hacking Contracts",
clickListener:()=>{
Player.startFactionHackWork(faction);
return false;
}
}));
hackDivWrapper.appendChild(createElement("p", {
innerText:"Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your hacking skill. " +
"You will gain hacking exp."
}));
elements.push(hackDiv);
//Field Work Option
var fieldWorkDiv = createElement("div", { class:"faction-work-div" });
var fieldWorkDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
fieldWorkDiv.appendChild(fieldWorkDivWrapper);
fieldWorkDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Field Work",
clickListener:()=>{
Player.startFactionFieldWork(faction);
return false;
}
}));
fieldWorkDivWrapper.appendChild(createElement("p", {
innerText:"Carry out field missions for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on all of your stats. " +
"You will gain exp for all stats."
}));
elements.push(fieldWorkDiv);
//Security Work Option
var securityWorkDiv = createElement("div", { class:"faction-work-div" });
var securityWorkDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
securityWorkDiv.appendChild(securityWorkDivWrapper);
securityWorkDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Security Work",
clickListener:()=>{
Player.startFactionSecurityWork(faction);
return false;
}
}));
securityWorkDivWrapper.appendChild(createElement("p", {
innerText:"Serve in a security detail for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your combat stats. " +
"You will gain exp for all combat stats."
}));
elements.push(securityWorkDiv);
//Donate for reputation
var donateDiv = createElement("div", { class:"faction-work-div" });
var donateDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
donateDiv.appendChild(donateDivWrapper);
var donateRepGain = createElement("p", {
innerText:"This donation will result in 0.000 reputation gain"
});
var donateAmountInput = createElement("input", {
class: "text-input", placeholder:"Donation amount",
inputListener:()=>{
let amt = 0;
if(donateAmountInput.value !== "") {
amt = parseFloat(donateAmountInput.value);
}
if (isNaN(amt)) {
donateRepGain.innerText = "Invalid donate amount entered!";
} else {
var repGain = amt / CONSTANTS.DonateMoneyToRepDivisor * Player.faction_rep_mult;
donateRepGain.innerText = "This donation will result in " +
formatNumber(repGain, 3) + " reputation gain";
}
},
});
donateDivWrapper.appendChild(createElement("a", {
class:"std-button", innerText:"Donate Money",
clickListener:()=>{
var amt = parseFloat(donateAmountInput.value);
if (isNaN(amt) || amt < 0) {
dialogBoxCreate("Invalid amount entered!");
} else if (Player.money.lt(amt)) {
dialogBoxCreate("You cannot afford to donate this much money!");
} else {
Player.loseMoney(amt);
var repGain = amt / CONSTANTS.DonateMoneyToRepDivisor * Player.faction_rep_mult;
faction.playerReputation += repGain;
dialogBoxCreate("You just donated " + numeralWrapper.format(amt, "$0.000a") + " to " +
faction.name + " to gain " + formatNumber(repGain, 3) + " reputation");
displayFactionContent(factionName);
}
}
}));
donateDivWrapper.appendChild(donateAmountInput);
donateDivWrapper.appendChild(donateRepGain);
elements.push(donateDiv);
//Purchase Augmentations
const purchaseAugmentationsDiv = createElement("div", { class: "faction-work-div", display: "inline" });
const purchaseAugmentationsDivWrapper = createElement("div", { class: "faction-work-div-wrapper" });
purchaseAugmentationsDiv.appendChild(purchaseAugmentationsDivWrapper);
purchaseAugmentationsDivWrapper.appendChild(createElement("a", {
class:"std-button",
innerText:"Purchase Augmentations",
margin: "5px",
clickListener:()=>{
Engine.hideAllContent();
Engine.Display.factionAugmentationsContent.style.display = "block";
displayFactionAugmentations(factionName);
return false;
}
}));
purchaseAugmentationsDivWrapper.appendChild(createElement("pre", {
innerHTML: "<br>As your reputation with this faction rises, you will " +
"unlock Augmentations, which you can purchase to enhance " +
"your abilities.<br><br>"
}));
elements.push(purchaseAugmentationsDiv);
//Gang (BitNode-2)
if (Player.bitNodeN == 2 && (factionName == "Slum Snakes" || factionName == "Tetrads" ||
factionName == "The Syndicate" || factionName == "The Dark Army" || factionName == "Speakers for the Dead" ||
factionName == "NiteSec" || factionName == "The Black Hand")) {
//Set everything else to invisible
hackMissionDiv.style.display = "none";
hackDiv.style.display = "none";
fieldWorkDiv.style.display = "none";
securityWorkDiv.style.display = "none";
donateDiv.style.display = "none";
//Create the 'Manage Gang' button
var gangDiv = createElement("div", {
id:"faction-gang-div", class:"faction-work-div", display:"inline"
});
var gangDivWrapper = createElement("div", {class:"faction-work-div-wrapper"});
gangDiv.appendChild(gangDivWrapper);
gangDivWrapper.appendChild(createElement("a", {
class:"a-link-button", innerText:"Manage Gang",
clickListener: () => {
if (!Player.inGang()) {
// Determine whether this is a hacking gang
let hacking = false;
if (factionName === "NiteSec" || factionName === "The Black Hand") { hacking = true; }
// Configure Yes/No buttons for the pop-up
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Create Gang";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", () => {
Player.startGang(factionName, hacking);
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
Engine.loadGangContent();
yesNoBoxClose();
});
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
// Pop-up text
let gangTypeText = "";
if (hacking) {
gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.<br><br>";
} else {
gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.<br><br>";
}
yesNoBoxCreate(`Would you like to create a new Gang with ${factionName}?<br><br>` +
"Note that this will prevent you from creating a Gang with any other Faction until " +
"this BitNode is destroyed.<br><br>" +
gangTypeText +
"Other than hacking vs combat, there are NO differences between the Factions you can " +
"create a Gang with, and each of these Factions have all Augmentations available.");
} else {
Engine.loadGangContent();
}
}
}));
gangDivWrapper.appendChild(createElement("p", {
innerText:"Create and manage a gang for this Faction. " +
"Gangs will earn you money and faction reputation."
}));
//Manage Gang button goes before Faction work stuff
elements.splice(7, 1, gangDiv);
if (Player.inGang() && Player.gang.facName != factionName) {
//If the player has a gang but its not for this faction
gangDiv.style.display = "none";
}
//Display all elements
for (var i = 0; i < elements.length; ++i) {
Engine.Display.factionContent.appendChild(elements[i]);
}
return;
}
// Purchase Sleeves from Covenant
if (factionName === "The Covenant" && Player.bitNodeN >= 10 && SourceFileFlags[10]) {
const covenantPurchaseSleevesDiv = createElement("div", { class: "faction-work-div", display: "inline" });
const covenantPurchaseSleevesDivWrapper = createElement("div", { class: "faction-work-div-wrapper" });
covenantPurchaseSleevesDiv.appendChild(covenantPurchaseSleevesDivWrapper);
covenantPurchaseSleevesDivWrapper.appendChild(createElement("a", {
class: "std-button",
innerText: "Purchase Duplicate Sleeves",
clickListener: () => {
createPurchaseSleevesFromCovenantPopup(Player);
}
}));
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {
innerText: "Purchase Duplicate Sleeves. These are permanent! You can purchase up to 5 total.",
}));
elements.push(covenantPurchaseSleevesDiv);
}
// Determine if actions should be possible
donateDiv.style.display = faction.favor >= Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction) ? "inline" : "none";
hackMissionDiv.style.display = factionInfo.offerHackingMission ? "inline": "none";
hackDiv.style.display = factionInfo.offerHackingWork ? "inline" : "none";
fieldWorkDiv.style.display = factionInfo.offerFieldWork ? "inline" : "none";
securityWorkDiv.style.display = factionInfo.offerSecurityWork ? "inline" : "none";
//Display all elements
for (var i = 0; i < elements.length; ++i) {
Engine.Display.factionContent.appendChild(elements[i]);
}
}
function displayFactionAugmentations(factionName) {
var faction = Factions[factionName];
if (faction == null) {
throw new Error("Could not find faction " + factionName + " in displayFactionAugmentations");
}
removeChildrenFromElement(Engine.Display.factionAugmentationsContent);
var elements = [];
//Back button
elements.push(createElement("a", {
innerText:"Back", class:"a-link-button",
clickListener:()=>{
Engine.loadFactionContent();
displayFactionContent(factionName);
return false;
}
}));
//Header text
elements.push(createElement("h1", {innerText:"Faction Augmentations"}));
elements.push(createElement("p", {
id:"faction-augmentations-page-desc",
innerHTML:"Lists all Augmentations that are available to purchase from " + factionName + "<br><br>" +
"Augmentations are powerful upgrades that will enhance your abilities."
}));
elements.push(createElement("br"));
elements.push(createElement("br"));
//Augmentations List
var augmentationsList = createElement("ul");
//Sort buttons
const sortByCostBtn = createElement("a", {
innerText:"Sort by Cost", class:"a-link-button",
clickListener:()=>{
Settings.PurchaseAugmentationsOrder = PurchaseAugmentationsOrderSetting.Cost;
var augs = faction.augmentations.slice();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseCost - aug2.baseCost;
});
removeChildrenFromElement(augmentationsList);
createFactionAugmentationDisplayElements(augmentationsList, augs, faction);
}
});
const sortByRepBtn = createElement("a", {
innerText:"Sort by Reputation", class:"a-link-button",
clickListener:()=>{
Settings.PurchaseAugmentationsOrder = PurchaseAugmentationsOrderSetting.Reputation;
var augs = faction.augmentations.slice();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseRepRequirement - aug2.baseRepRequirement;
});
removeChildrenFromElement(augmentationsList);
createFactionAugmentationDisplayElements(augmentationsList, augs, faction);
}
});
const defaultSortBtn = createElement("a", {
innerText:"Sort by Default Order", class:"a-link-button",
clickListener:()=>{
Settings.PurchaseAugmentationsOrder = PurchaseAugmentationsOrderSetting.Default;
removeChildrenFromElement(augmentationsList);
createFactionAugmentationDisplayElements(augmentationsList, faction.augmentations, faction);
}
});
elements.push(sortByCostBtn);
elements.push(sortByRepBtn);
elements.push(defaultSortBtn);
switch(Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost:
sortByCostBtn.click();
break;
case PurchaseAugmentationsOrderSetting.Reputation:
sortByRepBtn.click();
break;
default:
defaultSortBtn.click();
break;
}
elements.push(augmentationsList);
for (var i = 0; i < elements.length; ++i) {
Engine.Display.factionAugmentationsContent.appendChild(elements[i]);
}
}
//Takes in an array of Augmentation Names, constructs DOM elements
//to list them on the faction page, and appends them to the given
//DOM element
// @augmentationsList DOM List to append Aug DOM elements to
// @augs Array of Aug names
// @faction Faction for which to display Augmentations
function createFactionAugmentationDisplayElements(augmentationsList, augs, faction) {
const factionInfo = faction.getInfo();
for (var i = 0; i < augs.length; ++i) {
(function () {
var aug = Augmentations[augs[i]];
if (aug == null) {
throw new Error("Invalid Augmentation when trying to create Augmentation display Elements");
}
var owned = false;
for (var j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name == aug.name) {
owned = true;
break;
}
}
for (var j = 0; j < Player.augmentations.length; ++j) {
if (Player.augmentations[j].name == aug.name) {
owned = true;
break;
}
}
var item = createElement("li");
var span = createElement("span", { display:"inline-block", margin: "4px", padding: "4px" });
var aDiv = createElement("div", {tooltip:aug.info});
var aElem = createElement("a", {
innerText:aug.name, display:"inline",
clickListener:()=>{
if (!Settings.SuppressBuyAugmentationConfirmation) {
purchaseAugmentationBoxCreate(aug, faction);
} else {
purchaseAugmentation(aug, faction);
}
return false;
}
});
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
aElem.innerText += " - Level " + (getNextNeurofluxLevel());
}
var pElem = createElement("p", {
display:"inline",
})
var req = aug.baseRepRequirement * factionInfo.augmentationRepRequirementMult;
var hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
aElem.setAttribute("class", "a-link-button-inactive");
pElem.innerHTML = "LOCKED (Requires " + aug.prereqs.join(",") + " as prerequisite(s))";
pElem.style.color = "red";
} else if (aug.name != AugmentationNames.NeuroFluxGovernor && (aug.owned || owned)) {
aElem.setAttribute("class", "a-link-button-inactive");
pElem.innerHTML = "ALREADY OWNED";
} else if (faction.playerReputation >= req) {
aElem.setAttribute("class", "a-link-button");
pElem.innerHTML = "UNLOCKED - " + numeralWrapper.format(aug.baseCost * factionInfo.augmentationPriceMult, "$0.000a");
} else {
aElem.setAttribute("class", "a-link-button-inactive");
pElem.innerHTML = "LOCKED (Requires " + formatNumber(req, 1) + " faction reputation) - " + numeralWrapper.format(aug.baseCost * factionInfo.augmentationPriceMult, "$0.000a");
pElem.style.color = "red";
}
aDiv.appendChild(aElem);
span.appendChild(aDiv);
span.appendChild(pElem);
item.appendChild(span);
augmentationsList.appendChild(item);
}()); //Immediate invocation closure
}
}
function purchaseAugmentationBoxCreate(aug, fac) {
const factionInfo = fac.getInfo();
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Purchase";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", function() {
purchaseAugmentation(aug, fac);
});
noBtn.addEventListener("click", function() {
yesNoBoxClose();
});
yesNoBoxCreate("<h2>" + aug.name + "</h2><br>" +
aug.info + "<br><br>" +
"<br>Would you like to purchase the " + aug.name + " Augmentation for $" +
formatNumber(aug.baseCost * factionInfo.augmentationPriceMult, 2) + "?");
}
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
function hasAugmentationPrereqs(aug) {
var hasPrereqs = true;
if (aug.prereqs && aug.prereqs.length > 0) {
for (var i = 0; i < aug.prereqs.length; ++i) {
var prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.log("ERROR: Invalid prereq Augmentation: " + aug.prereqs[i]);
continue;
}
if (prereqAug.owned === false) {
hasPrereqs = false;
//Check if the aug is purchased
for (var j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
}
function purchaseAugmentation(aug, fac, sing=false) {
const factionInfo = fac.getInfo();
var hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
var txt = "You must first purchase or install " + aug.prereqs.join(",") + " before you can " +
"purchase this one.";
if (sing) {return txt;} else {dialogBoxCreate(txt);}
} else if (aug.baseCost !== 0 && Player.money.lt(aug.baseCost * factionInfo.augmentationPriceMult)) {
let txt = "You don't have enough money to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (fac.playerReputation < aug.baseRepRequirement) {
let txt = "You don't have enough faction reputation to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money.gte(aug.baseCost * factionInfo.augmentationPriceMult)) {
if (Player.firstAugPurchased === false) {
Player.firstAugPurchased = true;
document.getElementById("augmentations-tab").style.display = "list-item";
document.getElementById("character-menu-header").click();
document.getElementById("character-menu-header").click();
}
var queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney((aug.baseCost * factionInfo.augmentationPriceMult));
//If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
var nextLevel = getNextNeurofluxLevel();
--nextLevel;
var mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
aug.baseRepRequirement = 500 * mult * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
aug.baseCost = 750e3 * mult * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (var i = 0; i < Player.queuedAugmentations.length-1; ++i) {
aug.baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
for (var name in Augmentations) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
if (sing) {
return "You purchased " + aug.name;
} else {
if(!Settings.SuppressBuyAugmentationConfirmation){
dialogBoxCreate("You purchased " + aug.name + ". It's enhancements will not take " +
"effect until they are installed. To install your augmentations, go to the " +
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
"augmentations will now be more expensive.");
}
}
displayFactionAugmentations(fac.name);
} else {
dialogBoxCreate("Hmm, something went wrong when trying to purchase an Augmentation. " +
"Please report this to the game developer with an explanation of how to " +
"reproduce this.");
}
yesNoBoxClose();
}
function getNextNeurofluxLevel() {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (var i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (var i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult;
for (var name in Factions) {
if (Factions.hasOwnProperty(name)) {
var faction = Factions[name];
//TODO Get hard value of 1 rep per "rep gain cycle"" for now..
//maybe later make this based on
//a player's 'status' like how powerful they are and how much money they have
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);}
}
}
}
export {getNextNeurofluxLevel, inviteToFaction,
joinFaction, displayFactionContent, processPassiveFactionRepGain,
purchaseAugmentation};

View File

@@ -0,0 +1,240 @@
import React from "react";
import ReactDOM from "react-dom";
import { FactionRoot } from "./ui/Root";
import { Augmentations } from "../Augmentation/Augmentations";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Engine } from "../engine";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player";
import { Settings } from "../Settings/Settings";
import { Page, routing } from "../ui/navigationTracking";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { factionInvitationBoxCreate } from "../../utils/FactionInvitationBox";
import {
Reviver,
Generic_toJSON,
Generic_fromJSON
} from "../../utils/JSONReviver";
import { formatNumber } from "../../utils/StringHelperFunctions";
import {
yesNoBoxCreate,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
yesNoBoxClose
} from "../../utils/YesNoBox";
export function inviteToFaction(faction) {
if (Settings.SuppressFactionInvites) {
faction.alreadyInvited = true;
Player.factionInvitations.push(faction.name);
if (routing.isOn(Page.Factions)) {
Engine.loadFactionsContent();
}
} else {
factionInvitationBoxCreate(faction);
}
}
export function joinFaction(faction) {
faction.isMember = true;
Player.factions.push(faction.name);
const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction
for(const i in factionInfo.enemies) {
const enemy = factionInfo.enemies[i];
if (Factions[enemy] instanceof Faction) {
Factions[enemy].isBanned = true;
}
}
}
export function startHackingMission(faction) {
const mission = new HackingMission(faction.playerReputation, faction);
setInMission(true, mission); //Sets inMission flag to true
mission.init();
}
//Displays the HTML content for a specific faction
export function displayFactionContent(factionName, initiallyOnAugmentationsPage=false) {
const faction = Factions[factionName];
if (faction == null) {
throw new Error(`Invalid factionName passed into displayFactionContent(): ${factionName}`);
}
if (!faction.isMember) {
throw new Error(`Not a member of this faction. Cannot display faction information`);
}
ReactDOM.render(
<FactionRoot
engine={Engine}
initiallyOnAugmentationsPage={initiallyOnAugmentationsPage}
faction={faction}
p={Player}
startHackingMissionFn={startHackingMission}
/>,
Engine.Display.factionContent
)
}
export function purchaseAugmentationBoxCreate(aug, fac) {
const factionInfo = fac.getInfo();
const yesBtn = yesNoBoxGetYesButton();
yesBtn.innerHTML = "Purchase";
yesBtn.addEventListener("click", function() {
purchaseAugmentation(aug, fac);
});
const noBtn = yesNoBoxGetNoButton();
noBtn.innerHTML = "Cancel";
noBtn.addEventListener("click", function() {
yesNoBoxClose();
});
yesNoBoxCreate("<h2>" + aug.name + "</h2><br>" +
aug.info + "<br><br>" +
"<br>Would you like to purchase the " + aug.name + " Augmentation for $" +
formatNumber(aug.baseCost * factionInfo.augmentationPriceMult, 2) + "?");
}
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
export function hasAugmentationPrereqs(aug) {
let hasPrereqs = true;
if (aug.prereqs && aug.prereqs.length > 0) {
for (let i = 0; i < aug.prereqs.length; ++i) {
const prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`);
continue;
}
if (prereqAug.owned === false) {
hasPrereqs = false;
// Check if the aug is purchased
for (let j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
}
export function purchaseAugmentation(aug, fac, sing=false) {
const factionInfo = fac.getInfo();
var hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) {
var txt = "You must first purchase or install " + aug.prereqs.join(",") + " before you can " +
"purchase this one.";
if (sing) {return txt;} else {dialogBoxCreate(txt);}
} else if (aug.baseCost !== 0 && Player.money.lt(aug.baseCost * factionInfo.augmentationPriceMult)) {
let txt = "You don't have enough money to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (fac.playerReputation < aug.baseRepRequirement) {
let txt = "You don't have enough faction reputation to purchase " + aug.name;
if (sing) {return txt;}
dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money.gte(aug.baseCost * factionInfo.augmentationPriceMult)) {
if (Player.firstAugPurchased === false) {
Player.firstAugPurchased = true;
document.getElementById("augmentations-tab").style.display = "list-item";
document.getElementById("character-menu-header").click();
document.getElementById("character-menu-header").click();
}
var queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel();
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney((aug.baseCost * factionInfo.augmentationPriceMult));
// If you just purchased Neuroflux Governor, recalculate the cost
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
var nextLevel = getNextNeurofluxLevel();
--nextLevel;
var mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
aug.baseRepRequirement = 500 * mult * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
aug.baseCost = 750e3 * mult * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (var i = 0; i < Player.queuedAugmentations.length-1; ++i) {
aug.baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
for (var name in Augmentations) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *= CONSTANTS.MultipleAugMultiplier;
}
}
if (sing) {
return "You purchased " + aug.name;
} else {
if(!Settings.SuppressBuyAugmentationConfirmation){
dialogBoxCreate("You purchased " + aug.name + ". It's enhancements will not take " +
"effect until they are installed. To install your augmentations, go to the " +
"'Augmentations' tab on the left-hand navigation menu. Purchasing additional " +
"augmentations will now be more expensive.");
}
}
// Force a rerender of the Augmentations page
displayFactionContent(fac.name, true);
} else {
dialogBoxCreate("Hmm, something went wrong when trying to purchase an Augmentation. " +
"Please report this to the game developer with an explanation of how to " +
"reproduce this.");
}
yesNoBoxClose();
}
export function getNextNeurofluxLevel() {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (var i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (var i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
export function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult;
for (var name in Factions) {
if (Factions.hasOwnProperty(name)) {
var faction = Factions[name];
//TODO Get hard value of 1 rep per "rep gain cycle"" for now..
//maybe later make this based on
//a player's 'status' like how powerful they are and how much money they have
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);}
}
}
}

View File

@@ -0,0 +1,163 @@
/**
* Root React Component for displaying a faction's "Purchase Augmentations" page
*/
import * as React from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
faction: Faction;
p: IPlayer;
routeToMainPage: () => void;
}
type IState = {
rerenderFlag: boolean;
sortOrder: PurchaseAugmentationsOrderSetting;
}
const infoStyleMarkup = {
width: "70%",
}
export class AugmentationsPage extends React.Component<IProps, IState> {
// Flag for whether the player has a gang with this faction
isPlayersGang: boolean;
constructor(props: IProps) {
super(props);
this.isPlayersGang = props.p.inGang() && (props.p.getGangName() === props.faction.name);
this.state = {
rerenderFlag: false,
sortOrder: PurchaseAugmentationsOrderSetting.Default,
}
this.rerender = this.rerender.bind(this);
}
getAugs() {
if (this.isPlayersGang) {
const augs: string[] = [];
for (const augName in Augmentations) {
const aug = Augmentations[augName];
if (!aug.isSpecial) {
augs.push(augName);
}
}
return augs;
} else {
return this.props.faction.augmentations.slice();
}
}
getAugsSorted() {
switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost: {
return this.getAugsSortedByCost();
}
case PurchaseAugmentationsOrderSetting.Reputation: {
return this.getAugsSortedByReputation();
}
default:
return this.getAugsSortedByDefault();
}
}
getAugsSortedByCost() {
const augs = this.getAugs();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseCost - aug2.baseCost;
});
return augs;
}
getAugsSortedByReputation() {
const augs = this.getAugs();
augs.sort((augName1, augName2)=>{
var aug1 = Augmentations[augName1], aug2 = Augmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseRepRequirement - aug2.baseRepRequirement;
});
return augs;
}
getAugsSortedByDefault() {
return this.getAugs();
}
switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting) {
Settings.PurchaseAugmentationsOrder = newOrder;
this.rerender();
}
rerender() {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
render() {
const augs = this.getAugsSorted();
const augList = augs.map((aug) => {
return (
<PurchaseableAugmentation
augName={aug}
faction={this.props.faction}
key={aug}
p={this.props.p}
rerender={this.rerender}
/>
)
});
return (
<div>
<StdButton
onClick={this.props.routeToMainPage}
text={"Back"}
/>
<h1>Faction Augmentations</h1>
<p style={infoStyleMarkup}>
These are all of the Augmentations that are available to purchase
from {this.props.faction.name}. Augmentations are powerful upgrades
that will enhance your abilities.
</p>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}
text={"Sort by Cost"}
/>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
<br />
{augList}
</div>
)
}
}

View File

@@ -0,0 +1,101 @@
/**
* React component for a donate option on the Faction UI
*/
import * as React from "react";
import { CONSTANTS } from "../../Constants";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../utils/DialogBox";
type IProps = {
faction: Faction;
p: IPlayer;
rerender: () => void;
}
type IState = {
donateAmt: number;
statusTxt: string;
}
const inputStyleMarkup = {
margin: "5px",
}
export class DonateOption extends React.Component<IProps, IState> {
// Style markup for block elements. Stored as property
blockStyle: object = { display: "block" };
constructor(props: IProps) {
super(props);
this.state = {
donateAmt: 0,
statusTxt: "",
}
this.calculateRepGain = this.calculateRepGain.bind(this);
this.donate = this.donate.bind(this);
this.handleChange = this.handleChange.bind(this);
}
// Returns rep gain for a given donation amount
calculateRepGain(amt: number): number {
return amt / CONSTANTS.DonateMoneyToRepDivisor * this.props.p.faction_rep_mult;
}
donate(): void {
const fac = this.props.faction;
const amt = this.state.donateAmt;
if (isNaN(amt) || amt <= 0) {
dialogBoxCreate(`Invalid amount entered!`);
} else if (!this.props.p.canAfford(amt)) {
dialogBoxCreate(`You cannot afford to donate this much money!`);
} else {
this.props.p.loseMoney(amt);
const repGain = this.calculateRepGain(amt);
this.props.faction.playerReputation += repGain;
dialogBoxCreate(`You just donated ${numeralWrapper.formatMoney(amt)} to ${fac.name} to gain ` +
`${numeralWrapper.format(repGain, "0,0.000")} reputation`);
this.props.rerender();
}
}
handleChange(e: React.ChangeEvent<HTMLInputElement>): void {
const amt = parseFloat(e.target.value);
if (isNaN(amt)) {
this.setState({
donateAmt: 0,
statusTxt: "Invalid donate amount entered!",
});
} else {
const repGain = this.calculateRepGain(amt);
this.setState({
donateAmt: amt,
statusTxt: `This donation will result in ${numeralWrapper.format(repGain, "0,0.000")} reputation gain`,
});
}
}
render() {
return (
<div className={"faction-work-div"}>
<div className={"faction-work-div-wrapper"}>
<input onChange={this.handleChange} placeholder={"Donation amount"} style={inputStyleMarkup} />
<StdButton
onClick={this.donate}
text={"Donate Money"}
/>
<p style={this.blockStyle}>{this.state.statusTxt}</p>
</div>
</div>
)
}
}

74
src/Faction/ui/Info.tsx Normal file
View File

@@ -0,0 +1,74 @@
/**
* React component for general information about the faction. This includes the
* factions "motto", reputation, favor, and gameplay instructions
*/
import * as React from "react";
import { Faction } from "../../Faction/Faction";
import { FactionInfo } from "../../Faction/FactionInfo";
import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingParagraph } from "../../ui/React/AutoupdatingParagraph";
import { ParagraphWithTooltip } from "../../ui/React/ParagraphWithTooltip";
type IProps = {
faction: Faction;
factionInfo: FactionInfo;
}
type IInnerHTMLMarkup = {
__html: string;
}
const blockStyleMarkup = {
display: "block",
}
const infoStyleMarkup = {
display: "block",
width: "70%",
}
export class Info extends React.Component<IProps, any> {
render() {
const formattedRep = numeralWrapper.format(this.props.faction.playerReputation, "0.000a");
const favorGain = this.props.faction.getFavorGain()[0];
const favorTooltip = "Faction favor increases the rate at which you earn reputation for " +
"this faction by 1% per favor. Faction favor is gained whenever you " +
"reset after installing an Augmentation. The amount of " +
"favor you gain depends on how much reputation you have with the faction"
const infoText: IInnerHTMLMarkup = {
__html: this.props.factionInfo.infoText,
}
return (
<div>
<pre>
<i className={"text"} dangerouslySetInnerHTML={infoText}></i>
</pre>
<p style={blockStyleMarkup}>-------------------------</p>
<AutoupdatingParagraph
intervalTime={5e3}
text={`Reputation: ${formattedRep}`}
tooltip={`You will earn ${numeralWrapper.format(favorGain, "0,0")} faction favor upon resetting after installing an Augmentation`}
/>
<p style={blockStyleMarkup}>-------------------------</p>
<ParagraphWithTooltip
text={`Faction Favor: ${numeralWrapper.format(this.props.faction.favor, "0,0")}`}
tooltip={favorTooltip}
/>
<p style={blockStyleMarkup}>-------------------------</p>
<p style={infoStyleMarkup}>
Perform work/carry out assignments for your faction to help further its cause!
By doing so you will earn reputation for your faction. You will also gain
reputation passively over time, although at a very slow rate. Earning
reputation will allow you to purchase Augmentations through this faction, which
are powerful upgrades that enhance your abilities. Note that you cannot use your
terminal or create scripts when you are performing a task!
</p>
</div>
)
}
}

30
src/Faction/ui/Option.tsx Normal file
View File

@@ -0,0 +1,30 @@
/**
* React component for a selectable option on the Faction UI. These
* options including working for the faction, hacking missions, purchasing
* augmentations, etc.
*/
import * as React from "react";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
buttonText: string;
infoText: string;
onClick: (e: React.MouseEvent<HTMLElement>) => void;
}
export class Option extends React.Component<IProps, any> {
render() {
return (
<div className={"faction-work-div"}>
<div className={"faction-work-div-wrapper"}>
<StdButton
onClick={this.props.onClick}
text={this.props.buttonText}
/>
<p>{this.props.infoText}</p>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,152 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import * as React from "react";
import {
getNextNeurofluxLevel,
hasAugmentationPrereqs,
purchaseAugmentation,
purchaseAugmentationBoxCreate,
} from "../FactionHelpers";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { IMap } from "../../types";
import { StdButton } from "../../ui/React/StdButton";
type IProps = {
augName: string;
faction: Faction;
p: IPlayer;
rerender: () => void;
}
const spanStyleMarkup = {
margin: "4px",
padding: "4px",
}
const inlineStyleMarkup = {
display: "inline-block",
}
export class PurchaseableAugmentation extends React.Component<IProps, any> {
aug: Augmentation | null;
constructor(props: IProps) {
super(props);
this.aug = Augmentations[this.props.augName];
this.handleClick = this.handleClick.bind(this);
}
getMoneyCost(): number {
return this.aug!.baseCost * this.props.faction.getInfo().augmentationPriceMult;
}
getRepCost(): number {
return this.aug!.baseRepRequirement * this.props.faction.getInfo().augmentationRepRequirementMult;
}
handleClick() {
if (!Settings.SuppressBuyAugmentationConfirmation) {
purchaseAugmentationBoxCreate(this.aug!, this.props.faction);
} else {
purchaseAugmentation(this.aug!, this.props.faction);
}
}
// Whether the player has the prerequisite Augmentations
hasPrereqs(): boolean {
return hasAugmentationPrereqs(this.aug!);
}
// Whether the player has enough rep for this Augmentation
hasReputation(): boolean {
return this.props.faction.playerReputation >= this.getRepCost();
}
// Whether the player has this augmentations (purchased OR installed)
owned(): boolean {
let owned = false;
for (const queuedAug of this.props.p.queuedAugmentations) {
if (queuedAug.name === this.props.augName) {
owned = true;
break;
}
}
for (const installedAug of this.props.p.augmentations) {
if (installedAug.name === this.props.augName) {
owned = true;
break;
}
}
return owned;
}
render() {
if (this.aug == null) {
console.error(`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`);
return null;
}
const moneyCost = this.getMoneyCost();
const repCost = this.getRepCost();
// Determine UI properties
let disabled: boolean = false;
let statusTxt: string = "";
let color: string = "";
if (!this.hasPrereqs()) {
disabled = true;
statusTxt = `LOCKED (Requires ${this.aug.prereqs.join(",")} as prerequisite(s))`;
color = "red";
} else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) {
disabled = true;
statusTxt = "ALREADY OWNED";
} else if (this.hasReputation()) {
statusTxt = `UNLOCKED - ${numeralWrapper.formatMoney(moneyCost)}`;
} else {
disabled = true;
statusTxt = `LOCKED (Requires ${numeralWrapper.format(repCost, "0,0.0")} faction reputation - ${numeralWrapper.formatMoney(moneyCost)})`;
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
}
if (color !== "") { txtStyle.color = color; }
// Determine button txt
let btnTxt = this.aug.name;
if (this.aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
}
return (
<li>
<span style={spanStyleMarkup}>
<StdButton
disabled={disabled}
onClick={this.handleClick}
style={inlineStyleMarkup}
text={btnTxt}
tooltip={this.aug.info}
/>
<p style={txtStyle}>{statusTxt}</p>
</span>
</li>
)
}
}

302
src/Faction/ui/Root.tsx Normal file
View File

@@ -0,0 +1,302 @@
/**
* Root React Component for displaying a Faction's UI.
* This is the component for displaying a single faction's UI, not the list of all
* accessible factions
*/
import * as React from "react";
import { AugmentationsPage } from "./AugmentationsPage";
import { DonateOption } from "./DonateOption";
import { Info } from "./Info";
import { Option } from "./Option";
import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import {
yesNoBoxClose,
yesNoBoxCreate,
yesNoBoxGetNoButton,
yesNoBoxGetYesButton,
} from "../../../utils/YesNoBox";
type IProps = {
engine: IEngine;
initiallyOnAugmentationsPage?: boolean;
faction: Faction;
p: IPlayer;
startHackingMissionFn: (faction: Faction) => void;
}
type IState = {
rerenderFlag: boolean;
purchasingAugs: boolean;
}
// Info text for all options on the UI
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and " +
"faction reputation";
const hackingMissionInfo = "Attempt a hacking mission for your faction. " +
"A mission is a mini game that, if won, earns you " +
"significant reputation with this faction. (Recommended hacking level: 200+)";
const hackingContractsInfo = "Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your hacking skill. " +
"You will gain hacking exp.";
const fieldWorkInfo = "Carry out field missions for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on all of your stats. " +
"You will gain exp for all stats.";
const securityWorkInfo = "Serve in a security detail for your faction. " +
"Your effectiveness, which determines how much " +
"reputation you gain for this faction, is based on your combat stats. " +
"You will gain exp for all combat stats.";
const augmentationsInfo = "As your reputation with this faction rises, you will " +
"unlock Augmentations, which you can purchase to enhance " +
"your abilities.";
const sleevePurchasesInfo = "Purchase Duplicate Sleeves and upgrades. These are permanent!";
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand"
];
export class FactionRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
rerenderFlag: false,
purchasingAugs: props.initiallyOnAugmentationsPage ? props.initiallyOnAugmentationsPage : false,
}
this.manageGang = this.manageGang.bind(this);
this.rerender = this.rerender.bind(this);
this.routeToMain = this.routeToMain.bind(this);
this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this);
this.sleevePurchases = this.sleevePurchases.bind(this);
this.startFieldWork = this.startFieldWork.bind(this);
this.startHackingContracts = this.startHackingContracts.bind(this);
this.startHackingMission = this.startHackingMission.bind(this);
this.startSecurityWork = this.startSecurityWork.bind(this);
}
manageGang() {
// If player already has a gang, just go to the gang UI
if (this.props.p.inGang()) {
return this.props.engine.loadGangContent();
}
// Otherwise, we have to create the gang
const facName = this.props.faction.name;
let isHacking = false;
if (facName === "NiteSec" || facName === "The Black Hand") {
isHacking = true;
}
// A Yes/No popup box will allow the player to confirm gang creation
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; }
yesBtn.innerHTML = "Create Gang";
yesBtn.addEventListener("click", () => {
this.props.p.startGang(facName, isHacking);
const worldMenuHeader = document.getElementById("world-menu-header");
if (worldMenuHeader instanceof HTMLElement) {
worldMenuHeader.click(); worldMenuHeader.click();
}
this.props.engine.loadGangContent();
yesNoBoxClose();
});
noBtn.innerHTML = "Cancel";
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
// Pop-up text
let gangTypeText = "";
if (isHacking) {
gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.<br><br>";
} else {
gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.<br><br>";
}
yesNoBoxCreate(`Would you like to create a new Gang with ${facName}?<br><br>` +
"Note that this will prevent you from creating a Gang with any other Faction until " +
"this BitNode is destroyed. It also resets your reputation with this faction.<br><br>" +
gangTypeText +
"Other than hacking vs combat, there are NO differences between the Factions you can " +
"create a Gang with, and each of these Factions have all Augmentations available.");
}
rerender() {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
}
});
}
// Route to the main faction page
routeToMain() {
this.setState({ purchasingAugs: false });
}
// Route to the purchase augmentation UI for this faction
routeToPurchaseAugs() {
this.setState({ purchasingAugs: true });
}
sleevePurchases() {
createSleevePurchasesFromCovenantPopup(this.props.p);
}
startFieldWork() {
this.props.p.startFactionFieldWork(this.props.faction);
}
startHackingContracts() {
this.props.p.startFactionHackWork(this.props.faction);
}
startHackingMission() {
const fac = this.props.faction;
this.props.engine.loadMissionContent();
this.props.startHackingMissionFn(fac);
}
startSecurityWork() {
this.props.p.startFactionSecurityWork(this.props.faction);
}
render() {
return this.state.purchasingAugs ? this.renderAugmentationsPage() : this.renderMainPage();
}
renderMainPage() {
const p = this.props.p;
const faction = this.props.faction;
const factionInfo = faction.getInfo();
// We have a special flag for whether the player this faction is the player's
// gang faction because if the player has a gang, they cannot do any other action
const isPlayersGang = p.inGang() && (p.getGangName() === faction.name);
// Flags for whether special options (gang, sleeve purchases, donate, etc.)
// should be shown
const favorToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
const canDonate = faction.favor >= favorToDonate;
const canPurchaseSleeves = (faction.name === "The Covenant" && p.bitNodeN >= 10 && SourceFileFlags[10]);
let canAccessGang = (p.canAccessGang() && GangNames.includes(faction.name));
if (p.inGang()) {
if (p.getGangName() !== faction.name) {
canAccessGang = false;
} else if (p.getGangName() === faction.name) {
canAccessGang = true;
}
}
return (
<div>
<h1>{faction.name}</h1>
<Info
faction={faction}
factionInfo={factionInfo}
/>
{
canAccessGang &&
<Option
buttonText={"Manage Gang"}
infoText={gangInfo}
onClick={this.manageGang}
/>
}
{
(!isPlayersGang && factionInfo.offerHackingMission) &&
<Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={this.startHackingMission}
/>
}
{
(!isPlayersGang && factionInfo.offerHackingWork) &&
<Option
buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo}
onClick={this.startHackingContracts}
/>
}
{
(!isPlayersGang && factionInfo.offerFieldWork) &&
<Option
buttonText={"Field Work"}
infoText={fieldWorkInfo}
onClick={this.startFieldWork}
/>
}
{
(!isPlayersGang && factionInfo.offerSecurityWork) &&
<Option
buttonText={"Security Work"}
infoText={securityWorkInfo}
onClick={this.startSecurityWork}
/>
}
{
(!isPlayersGang && canDonate) &&
<DonateOption
faction={this.props.faction}
p={this.props.p}
rerender={this.rerender}
/>
}
<Option
buttonText={"Purchase Augmentations"}
infoText={augmentationsInfo}
onClick={this.routeToPurchaseAugs}
/>
{
canPurchaseSleeves &&
<Option
buttonText={"Purchase Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={this.sleevePurchases}
/>
}
</div>
)
}
renderAugmentationsPage() {
return (
<div>
<AugmentationsPage
faction={this.props.faction}
p={this.props.p}
routeToMainPage={this.routeToMain}
/>
</div>
)
}
}

View File

@@ -1,31 +1,39 @@
/*
Also add police clashes
balance point to keep them from running out of control
/**
* TODO
* Add police clashes
* balance point to keep them from running out of control
*/
import { gangMemberTasksMetadata } from "./data/gangmembertasks";
import { gangMemberUpgradesMetadata } from "./data/gangmemberupgrades";
import { gangMemberTasksMetadata } from "./data/gangmembertasks";
import { gangMemberUpgradesMetadata } from "./data/gangmemberupgrades";
import { Engine } from "./engine";
import { Faction } from "./Faction/Faction";
import { Factions } from "./Faction/Factions";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Page, routing } from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import {
Reviver,
Generic_toJSON,
Generic_fromJSON
} from "../utils/JSONReviver";
import { formatNumber } from "../utils/StringHelperFunctions";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { KEY } from "../utils/helpers/keyCodes";
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { Engine } from "./engine";
import { Faction } from "./Faction/Faction";
import { Factions } from "./Faction/Factions";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { numeralWrapper } from "./ui/numeralFormat";
import { dialogBoxCreate } from "../utils/DialogBox";
import { Reviver, Generic_toJSON,
Generic_fromJSON } from "../utils/JSONReviver";
import { KEY } from "../utils/helpers/keyCodes";
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { Page,
routing } from "./ui/navigationTracking";
import { formatNumber } from "../utils/StringHelperFunctions";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Constants
const GangRespectToReputationRatio = 5; // Respect is divided by this to get rep gain
@@ -50,7 +58,7 @@ $(document).keydown(function(event) {
}
});
//Delete upgrade box when clicking outside
// Delete upgrade box when clicking outside
$(document).mousedown(function(event) {
var boxId = "gang-member-upgrade-popup-box";
var contentId = "gang-member-upgrade-popup-box-content";
@@ -66,8 +74,16 @@ $(document).mousedown(function(event) {
}
});
let GangNames = ["Slum Snakes", "Tetrads", "The Syndicate", "The Dark Army", "Speakers for the Dead",
"NiteSec", "The Black Hand"];
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand"
];
export let AllGangs = {
"Slum Snakes" : {
power: 1,
@@ -137,12 +153,12 @@ export function loadAllGangs(saveString) {
}
/**
* @param facName - Name of corresponding faction
* @param hacking - Boolean indicating whether or not its a hacking gang
* @param facName {string} Name of corresponding faction
* @param hacking {bollean} Whether or not its a hacking gang
*/
export function Gang(facName, hacking=false) {
this.facName = facName;
this.members = []; //Array of GangMembers
this.members = [];
this.wanted = 1;
this.respect = 1;
@@ -197,7 +213,7 @@ Gang.prototype.process = function(numCycles=1, player) {
}
Gang.prototype.processGains = function(numCycles=1, player) {
//Get gains per cycle
// Get gains per cycle
var moneyGains = 0, respectGains = 0, wantedLevelGains = 0;
for (var i = 0; i < this.members.length; ++i) {
respectGains += (this.members[i].calculateRespectGain(this));
@@ -291,9 +307,9 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
}
// Then process territory
for (var i = 0; i < GangNames.length; ++i) {
for (let i = 0; i < GangNames.length; ++i) {
const others = GangNames.filter((e) => {
return e !== i;
return e !== GangNames[i];
});
const other = getRandomInt(0, others.length - 1);
@@ -318,6 +334,7 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
AllGangs[otherGang].territory -= 0.0001;
if (thisGang === gangName) {
this.clash(true); // Player won
AllGangs[otherGang].power *= (1 / 1.01);
} else if (otherGang === gangName) {
this.clash(false); // Player lost
} else {
@@ -333,6 +350,7 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
this.clash(false); // Player lost
} else if (otherGang === gangName) {
this.clash(true); // Player won
AllGangs[thisGang].power *= (1 / 1.01);
} else {
AllGangs[thisGang].power *= (1 / 1.01);
}
@@ -562,10 +580,9 @@ Gang.fromJSON = function(value) {
Reviver.constructors.Gang = Gang;
/*** Gang Member object ***/
function GangMember(name) {
this.name = name;
this.task = "Unassigned"; //GangMemberTask object
this.task = "Unassigned";
this.earnedRespect = 0;
@@ -597,11 +614,11 @@ function GangMember(name) {
this.agi_asc_mult = 1;
this.cha_asc_mult = 1;
this.upgrades = []; //Names of upgrades
this.augmentations = []; //Names only
this.upgrades = []; // Names of upgrades
this.augmentations = []; // Names of augmentations only
}
//Same formula for Player
// Same skill calculation formula as Player
GangMember.prototype.calculateSkill = function(exp, mult=1) {
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
}
@@ -645,7 +662,7 @@ GangMember.prototype.getTask = function() {
return GangMemberTasks["Unassigned"];
}
//Gains are per cycle
// Gains are per cycle
GangMember.prototype.calculateRespectGain = function(gang) {
const task = this.getTask();
if (task == null || !(task instanceof GangMemberTask) || task.baseRespect === 0) {return 0;}
@@ -770,9 +787,11 @@ GangMember.prototype.ascend = function() {
// Returns the multipliers that would be gained from ascension
GangMember.prototype.getAscensionResults = function() {
// Calculate ascension bonus to stat multipliers.
// This is based on the current number of multipliers from Non-Augmentation upgrades
// + Ascension Bonus = N% of current bonus from Augmentations
/**
* Calculate ascension bonus to stat multipliers.
* This is based on the current number of multipliers from Non-Augmentation upgrades
* + Ascension Bonus = N% of current bonus from Augmentations
*/
let hack = 1;
let str = 1;
let def = 1;
@@ -837,7 +856,7 @@ GangMember.fromJSON = function(value) {
Reviver.constructors.GangMember = GangMember;
//Defines tasks that Gang Members can work on
// Defines tasks that Gang Members can work on
function GangMemberTask(name="", desc="", isHacking=false, isCombat=false,
params={baseRespect: 0, baseWanted: 0, baseMoney: 0,
hackWeight: 0, strWeight: 0, defWeight: 0,
@@ -935,7 +954,7 @@ GangMemberUpgrade.prototype.createDescription = function() {
this.desc = lines.join("<br>");
}
//Passes in a GangMember object
// Passes in a GangMember object
GangMemberUpgrade.prototype.apply = function(member) {
if (this.mults.str != null) { member.str_mult *= this.mults.str; }
if (this.mults.def != null) { member.def_mult *= this.mults.def; }
@@ -971,7 +990,7 @@ gangMemberUpgradesMetadata.forEach((e) => {
Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") {
const boxId = "gang-member-upgrade-popup-box";
if (UIElems.gangMemberUpgradeBoxOpened) {
//Already opened, refreshing
// Already opened, refreshing
if (UIElems.gangMemberUpgradeBoxElements == null || UIElems.gangMemberUpgradeBox == null || UIElems.gangMemberUpgradeBoxContent == null) {
console.error("Refreshing Gang member upgrade box throws error because required elements are null");
return;
@@ -991,7 +1010,7 @@ Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") {
}
}
} else {
//New popup
// New popup
UIElems.gangMemberUpgradeBoxFilter = createElement("input", {
type:"text", placeholder:"Filter gang members",
value:initialFilter,
@@ -1023,7 +1042,7 @@ Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") {
}
}
//Create upgrade panels for each individual Gang Member
// Create upgrade panels for each individual Gang Member
GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) {
var container = createElement("div", {
border:"1px solid white",
@@ -1045,7 +1064,7 @@ GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) {
"Cha: " + this.cha + " (x" + formatNumber(this.cha_mult * this.cha_asc_mult, 2) + ")\n",
});
//Already purchased upgrades
// Already purchased upgrades
const ownedUpgradesElements = [];
function pushOwnedUpgrade(upgName) {
const upg = GangMemberUpgrades[upgName];
@@ -1071,7 +1090,7 @@ GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) {
container.appendChild(ownedUpgrades);
container.appendChild(createElement("br", {}));
//Upgrade buttons. Only show upgrades that can be afforded
// Upgrade buttons. Only show upgrades that can be afforded
const weaponUpgrades = [];
const armorUpgrades = [];
const vehicleUpgrades = [];
@@ -1203,18 +1222,18 @@ Gang.prototype.displayGangContent = function(player) {
if (!UIElems.gangContentCreated || UIElems.gangContainer == null) {
UIElems.gangContentCreated = true;
//Create gang container
// Create gang container
UIElems.gangContainer = createElement("div", {
id:"gang-container", class:"generic-menupage-container",
});
//Get variables
// Get variables
var facName = this.facName,
members = this.members,
wanted = this.wanted,
respect = this.respect;
//Back button
// Back button
UIElems.gangContainer.appendChild(createElement("a", {
class:"a-link-button", display:"inline-block", innerText:"Back",
clickListener:()=>{
@@ -1224,7 +1243,7 @@ Gang.prototype.displayGangContent = function(player) {
}
}));
//Buttons to switch between panels
// Buttons to switch between panels
UIElems.managementButton = createElement("a", {
id:"gang-management-subpage-button", class:"a-link-button-inactive",
display:"inline-block", innerHTML: "Gang Management (Alt+1)",
@@ -1256,7 +1275,7 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.gangContainer.appendChild(UIElems.managementButton);
UIElems.gangContainer.appendChild(UIElems.territoryButton);
//Subpage for managing gang members
// Subpage for managing gang members
UIElems.gangManagementSubpage = createElement("div", {
display:"block", id:"gang-management-subpage",
});
@@ -1340,6 +1359,7 @@ Gang.prototype.displayGangContent = function(player) {
innerText: "Cancel",
});
createPopup(popupId, [txt, br, nameInput, yesBtn, noBtn]);
nameInput.focus();
}
});
UIElems.gangManagementSubpage.appendChild(UIElems.gangRecruitMemberButton);
@@ -1352,7 +1372,7 @@ Gang.prototype.displayGangContent = function(player) {
});
UIElems.gangManagementSubpage.appendChild(UIElems.gangRecruitRequirementText);
//Gang Member List management buttons (Expand/Collapse All, select a single member)
// Gang Member List management buttons (Expand/Collapse All, select a single member)
UIElems.gangManagementSubpage.appendChild(createElement("br", {}));
UIElems.gangExpandAllButton = createElement("a", {
class:"a-link-button", display:"inline-block",
@@ -1400,17 +1420,17 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.gangManagementSubpage.appendChild(UIElems.gangMemberFilter);
UIElems.gangManagementSubpage.appendChild(UIElems.gangManageEquipmentButton);
//Gang Member list
// Gang Member list
UIElems.gangMemberList = createElement("ul", {id:"gang-member-list"});
this.displayGangMemberList();
UIElems.gangManagementSubpage.appendChild(UIElems.gangMemberList);
//Subpage for seeing gang territory information
// Subpage for seeing gang territory information
UIElems.gangTerritorySubpage = createElement("div", {
id:"gang-territory-subpage", display:"none"
});
//Info text for territory page
// Info text for territory page
UIElems.gangTerritoryDescText = createElement("p", {
width:"70%",
innerHTML:
@@ -1575,7 +1595,7 @@ Gang.prototype.updateGangContent = function() {
}
}
} else {
//Update information for overall gang
// Update information for overall gang
if (UIElems.gangInfo instanceof Element) {
var faction = Factions[this.facName];
var rep;
@@ -1585,7 +1605,7 @@ Gang.prototype.updateGangContent = function() {
rep = faction.playerReputation;
}
removeChildrenFromElement(UIElems.gangInfo);
UIElems.gangInfo.appendChild(createElement("p", { // Respect
UIElems.gangInfo.appendChild(createElement("p", { // Respect
display: "inline-block",
innerText: "Respect: " + formatNumber(this.respect, 6) +
" (" + formatNumber(5*this.respectGainRate, 6) + " / sec)",
@@ -1596,7 +1616,7 @@ Gang.prototype.updateGangContent = function() {
}));
UIElems.gangInfo.appendChild(createElement("br"));
UIElems.gangInfo.appendChild(createElement("p", { // Wanted level
UIElems.gangInfo.appendChild(createElement("p", { // Wanted level
display: "inline-block",
innerText: "Wanted Level: " + formatNumber(this.wanted, 6) +
" (" + formatNumber(5*this.wantedGainRate, 6) + " / sec)",
@@ -1608,20 +1628,20 @@ Gang.prototype.updateGangContent = function() {
var wantedPenalty = this.getWantedPenalty();
wantedPenalty = (1 - wantedPenalty) * 100;
UIElems.gangInfo.appendChild(createElement("p", { // Wanted Level multiplier
UIElems.gangInfo.appendChild(createElement("p", { // Wanted Level multiplier
display: "inline-block",
innerText: `Wanted Level Penalty: -${formatNumber(wantedPenalty, 2)}%`,
tooltip: "Penalty for respect and money gain rates due to Wanted Level"
}));
UIElems.gangInfo.appendChild(createElement("br"));
UIElems.gangInfo.appendChild(createElement("p", { // Money gain rate
UIElems.gangInfo.appendChild(createElement("p", { // Money gain rate
display: "inline-block",
innerText: `Money gain rate: ${numeralWrapper.format(5 * this.moneyGainRate, "$0.000a")} / sec`,
}));
UIElems.gangInfo.appendChild(createElement("br"));
//Fix some rounding issues graphically
// Fix some rounding issues graphically
var territoryMult = AllGangs[this.facName].territory * 100;
let displayNumber;
if (territoryMult <= 0) {
@@ -1656,7 +1676,7 @@ Gang.prototype.updateGangContent = function() {
console.error("gang-info DOM element DNE");
}
//Toggle the 'Recruit member button' if valid
// Toggle the 'Recruit member button' if valid
const numMembers = this.members.length;
const respectCost = this.getRespectNeededToRecruitMember();
@@ -1674,14 +1694,14 @@ Gang.prototype.updateGangContent = function() {
UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`;
}
//Update information for each gang member
// Update information for each gang member
for (let i = 0; i < this.members.length; ++i) {
this.updateGangMemberDisplayElement(this.members[i]);
}
}
}
//Takes in a GangMember object
// Takes in a GangMember object
Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
if (!UIElems.gangContentCreated) { return; }
const name = memberObj.name;
@@ -1822,7 +1842,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
taskDiv.appendChild(taskSelector);
taskDiv.appendChild(gainInfo);
//Panel for Description of task
// Panel for Description of task
var taskDescDiv = createElement("div", {
class:"gang-member-info-div",
id: name + "gang-member-task-desc",

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,419 @@
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);
Player.recordMoneySource(upg.value, "hacknetnode");
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 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.`;
}
}
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
};

15
src/IEngine.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* TypeScript interface for the game engine (engine.js), which can't be converted
* to TypeScript at the moment
*/
export interface IEngine {
hideAllContent: () => void;
loadBladeburnerContent: () => void;
loadFactionContent: () => void;
loadFactionsContent: () => void;
loadGangContent: () => void;
loadInfiltrationContent: () => void;
loadMissionContent: () => 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

@@ -1,30 +1,12 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions";
/* Infiltration.js
*
* Kill
* Knockout (nonlethal)
* Stealth Knockout (nonlethal)
* Assassinate
*
* Hack Security
* Destroy Security
* Sneak past Security
*
* Pick the locked door
*
* Bribe security
*
* Escape
*/
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions";
let InfiltrationScenarios = {
Guards: "You see an armed security guard patrolling the area.",
@@ -35,21 +17,26 @@ let InfiltrationScenarios = {
}
function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff) {
this.companyName = companyName;
this.clearanceLevel = 0;
this.maxClearanceLevel = maxClearance;
this.securityLevel = startLevel;
this.difficulty = diff; //Affects how much security level increases. Represents a percentage
this.baseValue = val; //Base value of company secrets
this.secretsStolen = []; //Numbers representing value of stolen secrets
this.companyName = companyName;
this.clearanceLevel = 0;
this.maxClearanceLevel = maxClearance;
this.securityLevel = startLevel;
this.difficulty = diff; // Affects how much security level increases. Represents a percentage
this.baseValue = val; // Base value of company secrets
this.secretsStolen = []; // Numbers representing value of stolen secrets
this.hackingExpGained = 0;
this.strExpGained = 0;
this.defExpGained = 0;
this.dexExpGained = 0;
this.agiExpGained = 0;
this.chaExpGained = 0;
this.intExpGained = 0;
this.hackingExpGained = 0;
this.strExpGained = 0;
this.defExpGained = 0;
this.dexExpGained = 0;
this.agiExpGained = 0;
this.chaExpGained = 0;
this.intExpGained = 0;
}
InfiltrationInstance.prototype.expMultiplier = function() {
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1;
return 2.5 * this.clearanceLevel / this.maxClearanceLevel;
}
InfiltrationInstance.prototype.gainHackingExp = function(amt) {
@@ -57,36 +44,71 @@ InfiltrationInstance.prototype.gainHackingExp = function(amt) {
this.hackingExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedHackingExp = function() {
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0;
return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
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();
@@ -137,7 +159,7 @@ function nextInfiltrationLevel(inst) {
bribeButton.style.display = "none";
escapeButton.style.display = "none";
var rand = getRandomInt(0, 5); //This needs to change if more scenarios are added
var rand = getRandomInt(0, 5); // This needs to change if more scenarios are added
var scenario = null;
switch (rand) {
case 1:
@@ -420,7 +442,7 @@ function nextInfiltrationLevel(inst) {
function endInfiltrationLevel(inst) {
//Check if you gained any secrets
// Check if you gained any secrets
if (inst.clearanceLevel % 5 == 0) {
var baseSecretValue = inst.baseValue * inst.clearanceLevel / 2;
var secretValue = baseSecretValue * Player.faction_rep_mult *
@@ -434,12 +456,12 @@ function endInfiltrationLevel(inst) {
"could be given to factions for reputation (<span class='light-yellow'>" + formatNumber(secretValue, 3) + " rep</span>)");
}
//Increase security level based on difficulty
// Increase security level based on difficulty
inst.securityLevel *= (1 + (inst.difficulty / 100));
writeInfiltrationStatusText("You move on to the facility's next clearance level. This " +
"clearance level has " + inst.difficulty + "% higher security");
//If this is max level, force endInfiltration
// If this is max level, force endInfiltration
if (inst.clearanceLevel >= inst.maxClearanceLevel) {
endInfiltration(inst, true);
} else {
@@ -477,12 +499,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 */
}
@@ -607,8 +629,8 @@ function updateInfiltrationButtons(inst, scenario) {
let intWgt = CONSTANTS.IntelligenceInfiltrationWeight;
//Kill
//Success: 5%, Failure 10%, -Karma
// Kill
// Success: 5%, Failure 10%, -Karma
function attemptInfiltrationKill(inst) {
var chance = getInfiltrationKillChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -633,8 +655,8 @@ function getInfiltrationKillChance(inst) {
}
//Knockout
//Success: 3%, Failure: 10%
// Knockout
// Success: 3%, Failure: 10%
function attemptInfiltrationKnockout(inst) {
var chance = getInfiltrationKnockoutChance(inst);
inst.gainStrengthExp(inst.securityLevel / 70) * Player.strength_exp_mult;
@@ -658,8 +680,8 @@ function getInfiltrationKnockoutChance(inst) {
Player.agility) / (1.7 * lvl));
}
//Stealth knockout
//Success: 0%, Failure: 10%
// Stealth knockout
// Success: 0%, Failure: 10%
function attemptInfiltrationStealthKnockout(inst) {
var chance = getInfiltrationStealthKnockoutChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -682,8 +704,8 @@ function getInfiltrationStealthKnockoutChance(inst) {
intWgt * Player.intelligence) / (3 * lvl));
}
//Assassination
//Success: 0%, Failure: 5%, -Karma
// Assassination
// Success: 0%, Failure: 5%, -Karma
function attemptInfiltrationAssassinate(inst) {
var chance = getInfiltrationAssassinateChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -706,8 +728,8 @@ function getInfiltrationAssassinateChance(inst) {
}
//Destroy security
//Success: 5%, Failure: 10%
// Destroy security
// Success: 5%, Failure: 10%
function attemptInfiltrationDestroySecurity(inst) {
var chance = getInfiltrationDestroySecurityChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
@@ -733,8 +755,8 @@ function getInfiltrationDestroySecurityChance(inst) {
}
//Hack security
//Success: 3%, Failure: 5%
// Hack security
// Success: 3%, Failure: 5%
function attemptInfiltrationHack(inst) {
var chance = getInfiltrationHackChance(inst);
inst.gainHackingExp(inst.securityLevel / 30) * Player.hacking_exp_mult;
@@ -756,8 +778,8 @@ function getInfiltrationHackChance(inst) {
(intWgt * Player.intelligence)) / lvl);
}
//Sneak past security
//Success: 0%, Failure: 8%
// Sneak past security
// Success: 0%, Failure: 8%
function attemptInfiltrationSneak(inst) {
var chance = getInfiltrationSneakChance(inst);
inst.gainAgilityExp(inst.securityLevel / 30) * Player.agility_exp_mult;
@@ -777,8 +799,8 @@ function getInfiltrationSneakChance(inst) {
intWgt * Player.intelligence) / (2 * lvl));
}
//Pick locked door
//Success: 1%, Failure: 3%
// Pick locked door
// Success: 1%, Failure: 3%
function attemptInfiltrationPickLockedDoor(inst) {
var chance = getInfiltrationPickLockedDoorChance(inst);
inst.gainDexterityExp(inst.securityLevel / 25) * Player.dexterity_exp_mult;
@@ -798,8 +820,8 @@ function getInfiltrationPickLockedDoorChance(inst) {
intWgt * Player.intelligence) / lvl);
}
//Bribe
//Success: 0%, Failure: 15%,
// Bribe
// Success: 0%, Failure: 15%,
function attemptInfiltrationBribe(inst) {
var chance = getInfiltrationBribeChance(inst);
inst.gainCharismaExp(inst.securityLevel / 8) * Player.charisma_exp_mult;
@@ -817,8 +839,8 @@ function getInfiltrationBribeChance(inst) {
(Player.charisma) / lvl);
}
//Escape
//Failure: 5%
// Escape
// Failure: 5%
function attemptInfiltrationEscape(inst) {
var chance = getInfiltrationEscapeChance(inst);
inst.gainAgilityExp(inst.securityLevel / 30) * Player.agility_exp_mult;

View File

@@ -1,34 +1,37 @@
import { Engine } from "./engine";
import { Player } from "./Player";
import { Settings } from "./Settings/Settings";
import { initializeMainMenuLinks } from "./ui/MainMenu/Links";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { Engine } from "./engine";
import { Player } from "./Player";
import { Settings } from "./Settings/Settings";
//Ordered array of keys to Interactive Tutorial Steps
import { initializeMainMenuLinks } from "./ui/MainMenu/Links";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Ordered array of keys to Interactive Tutorial Steps
const orderedITutorialSteps = [
"Start",
"GoToCharacterPage", //Click on 'Stats' page
"CharacterPage", //Introduction to 'Stats' page
"CharacterGoToTerminalPage", //Go back to Terminal
"TerminalIntro", //Introduction to Terminal
"TerminalHelp", //Using 'help' Terminal command
"TerminalLs", //Using 'ls' Terminal command
"TerminalScan", //Using 'scan' Terminal command
"TerminalScanAnalyze1", //Using 'scan-analyze' Terminal command
"TerminalScanAnalyze2", //Using 'scan-analyze 3' Terminal command
"TerminalConnect", //Connecting to foodnstuff
"TerminalAnalyze", //Analyzing foodnstuff
"TerminalNuke", //NUKE foodnstuff
"TerminalManualHack", //Hack foodnstuff
"TerminalHackingMechanics", //Explanation of hacking mechanics
"TerminalCreateScript", //Create a script using 'nano'
"TerminalTypeScript", //Script Editor page - Type script and then save & close
"TerminalFree", //Using 'Free' Terminal command
"TerminalRunScript", //Running script using 'run' Terminal command
"GoToCharacterPage", // Click on 'Stats' page
"CharacterPage", // Introduction to 'Stats' page
"CharacterGoToTerminalPage", // Go back to Terminal
"TerminalIntro", // Introduction to Terminal
"TerminalHelp", // Using 'help' Terminal command
"TerminalLs", // Using 'ls' Terminal command
"TerminalScan", // Using 'scan' Terminal command
"TerminalScanAnalyze1", // Using 'scan-analyze' Terminal command
"TerminalScanAnalyze2", // Using 'scan-analyze 3' Terminal command
"TerminalConnect", // Connecting to foodnstuff
"TerminalAnalyze", // Analyzing foodnstuff
"TerminalNuke", // NUKE foodnstuff
"TerminalManualHack", // Hack foodnstuff
"TerminalHackingMechanics", // Explanation of hacking mechanics
"TerminalCreateScript", // Create a script using 'nano'
"TerminalTypeScript", // Script Editor page - Type script and then save & close
"TerminalFree", // Using 'Free' Terminal command
"TerminalRunScript", // Running script using 'run' Terminal command
"TerminalGoToActiveScriptsPage",
"ActiveScriptsPage",
"ActiveScriptsToTerminal",
@@ -41,22 +44,22 @@ const orderedITutorialSteps = [
"End"
]
//Create an 'enum' for the Steps
// Create an 'enum' for the Steps
const iTutorialSteps = {};
for (let i = 0; i < orderedITutorialSteps.length; ++i) {
iTutorialSteps[orderedITutorialSteps[i]] = i;
}
var ITutorial = {
currStep: 0, //iTutorialSteps.Start
currStep: 0, // iTutorialSteps.Start
isRunning: false,
//Keeps track of whether each step has been done
// Keeps track of whether each step has been done
stepIsDone: {},
}
function iTutorialStart() {
//Initialize Interactive Tutorial state by settings 'done' for each state to false
// Initialize Interactive Tutorial state by settings 'done' for each state to false
ITutorial.stepIsDone = {};
for (let i = 0; i < orderedITutorialSteps.length; ++i) {
ITutorial.stepIsDone[i] = false;
@@ -64,7 +67,7 @@ function iTutorialStart() {
Engine.loadTerminalContent();
//Don't autosave during this interactive tutorial
// Don't autosave during this interactive tutorial
Engine.Counters.autoSaveCounter = Infinity;
console.log("Interactive Tutorial started");
ITutorial.currStep = 0;
@@ -72,21 +75,21 @@ function iTutorialStart() {
document.getElementById("interactive-tutorial-container").style.display = "block";
//Exit tutorial button
// Exit tutorial button
var exitButton = clearEventListeners("interactive-tutorial-exit");
exitButton.addEventListener("click", function() {
iTutorialEnd();
return false;
});
//Back button
// Back button
var backButton = clearEventListeners("interactive-tutorial-back");
backButton.addEventListener("click", function() {
iTutorialPrevStep();
return false;
});
//Next button
// Next button
var nextButton = clearEventListeners("interactive-tutorial-next");
nextButton.addEventListener("click", function() {
iTutorialNextStep();
@@ -99,7 +102,7 @@ function iTutorialStart() {
function iTutorialEvaluateStep() {
if (!ITutorial.isRunning) {console.log("Interactive Tutorial not running"); return;}
//Disable and clear main menu
// Disable and clear main menu
var terminalMainMenu = clearEventListeners("terminal-menu-link");
var statsMainMenu = clearEventListeners("stats-menu-link");
var activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link");
@@ -113,7 +116,7 @@ function iTutorialEvaluateStep() {
cityMainMenu.removeAttribute("class");
tutorialMainMenu.removeAttribute("class");
//Interactive Tutorial Next button
// Interactive Tutorial Next button
var nextBtn = document.getElementById("interactive-tutorial-next");
switch(ITutorial.currStep) {
@@ -131,7 +134,7 @@ function iTutorialEvaluateStep() {
"the main navigation menu (left-hand side of the screen)");
nextBtn.style.display = "none";
//Flash 'Stats' menu and set its tutorial click handler
// Flash 'Stats' menu and set its tutorial click handler
statsMainMenu.setAttribute("class", "flashing-button");
statsMainMenu.addEventListener("click", function() {
Engine.loadCharacterContent();
@@ -151,7 +154,7 @@ function iTutorialEvaluateStep() {
"main navigation menu.");
nextBtn.style.display = "none";
//Flash 'Terminal' menu and set its tutorial click handler
// Flash 'Terminal' menu and set its tutorial click handler
terminalMainMenu.setAttribute("class", "flashing-button");
terminalMainMenu.addEventListener("click", function() {
Engine.loadTerminalContent();
@@ -169,13 +172,13 @@ function iTutorialEvaluateStep() {
Engine.loadTerminalContent();
iTutorialSetText("Let's try it out. Start by entering the 'help' command into the Terminal " +
"(Don't forget to press Enter after typing the command)");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalLs:
Engine.loadTerminalContent();
iTutorialSetText("The 'help' command displays a list of all available Terminal commands, how to use them, " +
"and a description of what they do. <br><br>Let's try another command. Enter the 'ls' command");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScan:
Engine.loadTerminalContent();
@@ -184,7 +187,7 @@ function iTutorialEvaluateStep() {
"We'll get to what this does later. <br><br>Using your home computer's terminal, you can connect " +
"to other machines throughout the world. Let's do that now by first entering " +
"the 'scan' command.");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze1:
Engine.loadTerminalContent();
@@ -194,7 +197,7 @@ function iTutorialEvaluateStep() {
"That's great and all, but there's so many servers. Which one should you go to? " +
"The 'scan-analyze' command gives some more detailed information about servers on the " +
"network. Try it now");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze2:
Engine.loadTerminalContent();
@@ -202,7 +205,7 @@ function iTutorialEvaluateStep() {
"information about each server that you can connect to (servers that are a distance of " +
"one node away). <br><br> It is also possible to run 'scan-analyze' with " +
"a higher depth. Let's try a depth of two with the following command: 'scan-analyze 2'.")
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalConnect:
Engine.loadTerminalContent();
@@ -212,7 +215,7 @@ function iTutorialEvaluateStep() {
"the ip or the hostname, but dont use both.<br><br>" +
"From the results of the 'scan-analyze' command, we can see that the 'foodnstuff' server is " +
"only one node away. Let's connect so it now using: 'connect foodnstuff'");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalAnalyze:
Engine.loadTerminalContent();
@@ -221,7 +224,7 @@ function iTutorialEvaluateStep() {
"on servers and computers. Using your hacking abilities, you can hack servers " +
"to steal money and gain experience. <br><br> " +
"Before you try to hack a server, you should run diagnostics using the 'analyze' command");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalNuke:
Engine.loadTerminalContent();
@@ -233,13 +236,13 @@ function iTutorialEvaluateStep() {
"open ports.<br><br> The 'analyze' results shows that there do not need to be any open ports " +
"on this machine for the NUKE virus to work, so go ahead and run the virus using the " +
"'run NUKE.exe' command.");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalManualHack:
Engine.loadTerminalContent();
iTutorialSetText("You now have root access! You can hack the server using the 'hack' command. " +
"Try doing that now.");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalHackingMechanics:
Engine.loadTerminalContent();
@@ -262,7 +265,7 @@ function iTutorialEvaluateStep() {
"command. Scripts must end with the '.script' extension. Let's make a script now by " +
"entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c" +
" will end a command like hack early)");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTypeScript:
Engine.loadScriptEditorContent("foodnstuff.script", "");
@@ -278,7 +281,7 @@ function iTutorialEvaluateStep() {
"For anyone with basic programming experience, this code should be straightforward. " +
"This script will continuously hack the 'foodnstuff' server.<br><br>" +
"To save and close the script editor, press the button in the bottom left, or press ctrl + b.");
nextBtn.style.display = "none"; //next step triggered in saveAndCloseScriptEditor() (Script.js)
nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js)
break;
case iTutorialSteps.TerminalFree:
Engine.loadTerminalContent();
@@ -286,13 +289,13 @@ function iTutorialEvaluateStep() {
"run on any machine which you have root access to. Different servers have different " +
"amounts of RAM. You can also purchase more RAM for your home server.<br><br>To check how much " +
"RAM is available on this machine, enter the 'free' command.");
nextBtn.style.display = "none"; //next step triggered by terminal commmand
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalRunScript:
Engine.loadTerminalContent();
iTutorialSetText("We have 16GB of free RAM on this machine, which is enough to run our " +
"script. Let's run our script using 'run foodnstuff.script'.");
nextBtn.style.display = "none"; //next step triggered by terminal commmand
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalGoToActiveScriptsPage:
Engine.loadTerminalContent();
@@ -306,7 +309,7 @@ function iTutorialEvaluateStep() {
"'Active Scripts' link in the main navigation menu.");
nextBtn.style.display = "none";
//Flash 'Active Scripts' menu and set its tutorial click handler
// Flash 'Active Scripts' menu and set its tutorial click handler
activeScriptsMainMenu.setAttribute("class", "flashing-button");
activeScriptsMainMenu.addEventListener("click", function() {
Engine.loadActiveScriptsContent();
@@ -322,7 +325,7 @@ function iTutorialEvaluateStep() {
"link.");
nextBtn.style.display = "none";
//Flash 'Terminal' button and set its tutorial click handler
// Flash 'Terminal' button and set its tutorial click handler
terminalMainMenu.setAttribute("class", "flashing-button");
terminalMainMenu.addEventListener("click", function() {
Engine.loadTerminalContent();
@@ -335,7 +338,7 @@ function iTutorialEvaluateStep() {
iTutorialSetText("One last thing about scripts, each active script contains logs that detail " +
"what it's doing. We can check these logs using the 'tail' command. Do that " +
"now for the script we just ran by typing 'tail foodnstuff.script'");
nextBtn.style.display = "none"; //next step triggered by terminal command
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTailScript:
Engine.loadTerminalContent();
@@ -356,7 +359,7 @@ function iTutorialEvaluateStep() {
"the 'Hacknet Nodes' page through the main navigation menu now.");
nextBtn.style.display = "none";
//Flash 'Hacknet' menu and set its tutorial click handler
// Flash 'Hacknet' menu and set its tutorial click handler
hacknetMainMenu.setAttribute("class", "flashing-button");
hacknetMainMenu.addEventListener("click", function() {
Engine.loadHacknetNodesContent();
@@ -368,7 +371,7 @@ function iTutorialEvaluateStep() {
Engine.loadHacknetNodesContent();
iTutorialSetText("From this page you can purchase new Hacknet Nodes and upgrade your " +
"existing ones. Let's purchase a new one now.");
nextBtn.style.display = "none"; //Next step triggered by purchaseHacknet() (HacknetNode.js)
nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js)
break;
case iTutorialSteps.HacknetNodesGoToWorldPage:
Engine.loadHacknetNodesContent();
@@ -379,16 +382,16 @@ function iTutorialEvaluateStep() {
"Let's go to the 'City' page through the main navigation menu.");
nextBtn.style.display = "none";
//Flash 'City' menu and set its tutorial click handler
// 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 " +
@@ -396,7 +399,7 @@ function iTutorialEvaluateStep() {
"Lastly, click on the 'Tutorial' link in the main navigation menu.");
nextBtn.style.display = "none";
//Flash 'Tutorial' menu and set its tutorial click handler
// Flash 'Tutorial' menu and set its tutorial click handler
tutorialMainMenu.setAttribute("class", "flashing-button");
tutorialMainMenu.addEventListener("click", function() {
Engine.loadTutorialContent();
@@ -425,9 +428,9 @@ function iTutorialEvaluateStep() {
}
}
//Go to the next step and evaluate it
// Go to the next step and evaluate it
function iTutorialNextStep() {
//Special behavior for certain steps
// Special behavior for certain steps
if (ITutorial.currStep === iTutorialSteps.GoToCharacterPage) {
document.getElementById("stats-menu-link").removeAttribute("class");
}
@@ -457,7 +460,7 @@ function iTutorialNextStep() {
iTutorialEvaluateStep();
}
//Go to previous step and evaluate
// Go to previous step and evaluate
function iTutorialPrevStep() {
if (ITutorial.currStep > iTutorialSteps.Start) {
ITutorial.currStep -= 1;
@@ -466,7 +469,7 @@ function iTutorialPrevStep() {
}
function iTutorialEnd() {
//Re-enable auto save
// Re-enable auto save
if (Settings.AutosaveInterval === 0) {
Engine.Counters.autoSaveCounter = Infinity;
} else {
@@ -491,7 +494,7 @@ function iTutorialEnd() {
ITutorial.isRunning = false;
document.getElementById("interactive-tutorial-container").style.display = "none";
//Create a popup with final introductory stuff
// Create a popup with final introductory stuff
var popupId = "interactive-tutorial-ending-popup";
var txt = createElement("p", {
innerHTML:

View File

@@ -1,8 +1,9 @@
import {dialogBoxCreate} from "../utils/DialogBox";
/* Literature.js
* Lore / world building literature that can be found on servers
/**
* Lore / world building literature files that can be found on servers.
* These files can be read by the player
*/
import { dialogBoxCreate } from "../utils/DialogBox";
function Literature(title, filename, txt) {
this.title = title;
this.fn = filename;
@@ -10,10 +11,9 @@ function Literature(title, filename, txt) {
}
function showLiterature(fn) {
var litObj = Literatures[fn];
if (litObj == null) {return;}
var txt = "<i>" + litObj.title + "</i><br><br>" +
litObj.txt;
const litObj = Literatures[fn];
if (litObj == null) { return; }
const txt = `<i>${litObj.title}</i><br><br>${litObj.txt}`;
dialogBoxCreate(txt);
}
@@ -430,7 +430,10 @@ function initLiterature() {
fn = "the-secret-war.lit";
txt = ""
Literatures[fn] = new Literature(title, fn, txt);
}
export {Literatures, initLiterature, showLiterature};
export {
Literatures,
initLiterature,
showLiterature
};

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

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