Merge pull request #529 from danielyxie/dev

v0.43.0
This commit is contained in:
danielyxie 2019-02-04 21:43:15 -08:00 committed by GitHub
commit 112f00f3d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 13619 additions and 3490 deletions

45
Quotes.txt Normal file

@ -0,0 +1,45 @@
Collection of Quotes
The past is relevant only as data
Pull on the new flesh like borrowed gloves and burn your fingers once again.
A weapon is a tool. A tool for killing and destroying. And there will be times
when you must kill and destroy. Then you will choose and equip yourself with the tools
that you need. But remember the weakness of weapons. They are an extension --
You are the killer and destroyer. You are whole, with or without them.
For all that we have done, as a civilization, as individuals, the universe is
not stable, and nor is any single thing within it. Stars consume themselves,
the universe itself rushes apart, and we ourselves are composed of matter in
constant flux. Colonies of cells in temporary alliance, replicating and
decaying and housed within, an incandescent cloud of electrical impulse and
precariously stacked carbon code memory. This is reality, this is self knowledge,
and the perception of it will, of course, make you dizzy.
You are still young and stupid. Human life has no value. Haven't you learned
that yet, Takeshi, with all you've seen? It has no value, intrinsic to itself.
Machines cost money to build. Raw materials cost money to extract. But people?"
She made a tiny spitting sound. "You can always get some more people. they
reproduce like cancer cells, whether you want them or not. They are abundant,
Takeshi. Why should they be valuable? Do you know that it costs us less to
recruit and use up a real snuff whore than it does to set up and run the virtual
equivalent format. Real human flesh is cheaper than a machine. It's the axiomatic
truth of our times.
Peace is an illusion, no matter how tranquil the world seems, peace doesn't last long.
Peace is a struggle against our very nature. A skin we sketch over the bone, muscle,
and sinew of our own innate savagery.
The human eye is a wonderful device. With a little effort, it can fail to see even
the most glaring injustice.
Humanity has spread to the stars. We set out like ancient seafarers to explore
the limitless ocean of space. But no matter how far we venture into the unknown,
the worst monsters are those we bring with us.
What we believe shapes who we are. Belief can bring us salvation or destruction.
But when you believe a lie for too long, the truth doesn't set you free. It tears
you apart.
We aren't meant to live forever. It corrupts even the best of us.

@ -31,13 +31,12 @@
border: 1px solid #fff;
margin: 2px;
padding: 2px;
color: #fff;
}
.bladeburner-nav-button {
@extend %bladeburner-nav-button;
color: #fff;
&:hover {
background-color: #3d4044;
}

@ -0,0 +1,49 @@
@import "theme";
/**
* Customized styling for the Code Mirror editor
*/
#codemirror-form-wrapper {
height: 80%;
margin: 10px 0px 0px 6px;
}
.CodeMirror {
height: 100%;
width: 100%;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: $fontFamily;
font-size: $defaultFontSize;
}
/**
* Highlight matches
*/
.cm-matchhighlight {
background-color: #8F908A;
}
.CodeMirror-selection-highlight-scrollbar {
background-color: #8F908A;
}
/**
* Show Invisibles
*/
.cm-whitespace::before {
position: absolute;
pointer-events: none;
color: #404F7D;
}
/**
* Vim command display
*/
#codemirror-vim-command-display-wrapper {
background-color: white;
font-size: 13px;
height: 30px;
margin-left: 6px;
}

@ -18,116 +18,6 @@
position: fixed;
}
/* Script Editor */
#script-editor-container {
background-color: transparent;
}
#javascript-editor {
margin: 10px;
height: 80%;
width: 100%;
margin-left: 6px;
padding-left: 6px;
padding-top: 6px;
padding-bottom: 6px;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: $fontFamily;
}
.ace_line,
.ace_line * {
background-color: transparent;
margin: 0;
padding: 0;
}
.ace_text-input {
font-size: $defaultFontSize;
background-color: transparent;
}
/* This temp element is used for auto adjusting filename field */
.tmp-element {
visibility: hidden;
white-space: pre;
}
#script-editor-container {
position: fixed;
padding-top: 10px;
}
#script-editor-buttons-wrapper {
width: 100%;
padding-right: 0;
margin-right: 0;
}
#script-editor-wrapper {
height: 100%;
width: 70%;
background: transparent;
}
#script-editor-filename-wrapper {
background-color: #555;
margin-left: 6px;
margin-right: 0;
padding-left: 6px;
width: 100%;
border: 2px solid var(--my-highlight-color);
}
#script-editor-filename-tag {
display: inline-block;
padding-top: 10px;
padding-bottom: 0;
float: center;
background-color: #555;
color: #fff;
}
#script-editor-filename {
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
@include boxShadow($boxShadowArgs);
background-color: #555;
display: inline-block;
float: center;
resize: none;
color: #fff;
margin: 4px;
padding: 2px;
border: 2px solid var(--my-highlight-color);
}
#script-editor-status {
float: left;
color: #fff;
}
#script-editor-options-panel {
position: absolute;
right: 9%;
bottom: 15%;
border: 2px solid #fff;
width: 19%;
background-color: #444;
padding: 2px;
overflow: auto;
z-index: 1;
color: #fff;
}
#script-editor-options-panel fieldset {
margin-top: 8px;
margin-bottom: 8px;
padding: 2px;
font-size: $defaultFontSize * 0.75;
}
/* Active scripts */
.active-scripts-list {
list-style-type: none;
@ -537,70 +427,3 @@
display: inline;
width: 25%;
}
/* Stock market */
#stock-market-container {
position: fixed;
padding: 6px;
p {
font-size: $defaultFontSize * 0.8125;
}
a {
font-size: $defaultFontSize * 0.875;
}
h2 {
margin-top: 10px;
margin-left: 10px;
display: block;
}
}
/* Change font size of Stock TIcker headers */
#stock-market-list li {
button {
font-size: $defaultFontSize;
}
}
#stock-market-container p {
padding: 10px;
margin: 10px;
width: 70%;
}
#stock-market-container a {
margin: 10px;
}
#stock-market-watchlist-filter {
width: 50%;
margin-left: 10px;
}
.stock-market-input {
display: inline-block;
padding: 4px;
margin: 2px;
background-color: #000;
border: 1px solid #fff;
color: var(--my-font-color);
}
.stock-market-position-text {
color: #fff;
display: inline-block;
}
.stock-market-order-list {
overflow-y: auto;
max-height: 100px;
}
.stock-market-order-cancel-btn {
background-color: #000;
border: 1px solid #fff;
color: var(--my-font-color);
margin: 2px;
padding: 0;
}

@ -63,7 +63,7 @@
z-index: 10;
width: 50%;
height: auto;
max-height: 40%;
max-height: 50%;
top: 40%;
left: 50%;
margin: -10% 0 0 -25%;

22
css/redpill.scss Normal file

@ -0,0 +1,22 @@
@import "theme";
/**
* Styling for the Red Pill screen (the BitNode selection UI)
*/
#red-pill-container {
position: fixed;
}
.bitnode {
color: #00f;
}
.bitnode-destroyed {
color: #f00;
}
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff;
}

28
css/resleeving.scss Normal file

@ -0,0 +1,28 @@
/**
* Styling for the Re-Sleeving Page
*/
@import "theme";
.resleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%;
p {
font-size: $defaultFontSize * 0.8125;
}
}
.resleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px;
}
.resleeve-aug-selector {
font-size: $defaultFontSize * 0.8125;
option {
font-size: $defaultFontSize * 0.8125;
}
}

123
css/scripteditor.scss Normal file

@ -0,0 +1,123 @@
@import "mixins";
@import "theme";
/**
* Styling for Script Editor (both Ace and CodeMirror)
*/
#script-editor-container {
background-color: transparent;
}
#ace-editor {
margin: 10px;
height: 80%;
width: 100%;
margin-left: 6px;
padding-left: 6px;
padding-top: 6px;
padding-bottom: 6px;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: $fontFamily;
}
/* This temp element is used for auto adjusting filename field */
.tmp-element {
visibility: hidden;
white-space: pre;
}
#script-editor-container {
position: fixed;
padding-top: 10px;
}
#script-editor-buttons-wrapper {
width: 100%;
padding-right: 0;
margin-right: 0;
}
#script-editor-wrapper {
height: 100%;
width: 70%;
background: transparent;
}
#script-editor-filename-wrapper {
background-color: #555;
margin-left: 6px;
margin-right: 0;
padding-left: 6px;
width: 100%;
border: 2px solid var(--my-highlight-color);
}
#script-editor-filename-tag {
display: inline-block;
padding-top: 10px;
padding-bottom: 0;
float: center;
background-color: #555;
color: #fff;
}
#script-editor-filename {
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
@include boxShadow($boxShadowArgs);
background-color: #555;
display: inline-block;
float: center;
resize: none;
color: #fff;
margin: 4px;
padding: 2px;
border: 2px solid var(--my-highlight-color);
}
#script-editor-status {
float: left;
color: #fff;
}
#script-editor-options-panel {
position: absolute;
right: 9%;
bottom: 15%;
border: 2px solid #fff;
width: 19%;
background-color: #444;
padding: 2px;
overflow: auto;
z-index: 1;
color: #fff;
max-height: 50%;
}
#script-editor-options-panel fieldset {
margin-top: 8px;
margin-bottom: 8px;
padding: 2px;
font-size: $defaultFontSize * 0.75;
input {
margin: 2px;
}
}
/* Specific overrides for Ace Editor */
.ace_line,
.ace_line * {
background-color: transparent;
margin: 0;
padding: 0;
}
.ace_text-input {
font-size: $defaultFontSize;
background-color: transparent;
}
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */

29
css/sleeves.scss Normal file

@ -0,0 +1,29 @@
/**
* Styling for the Sleeves Management page
*/
@import "theme";
.sleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%;
p {
font-size: $defaultFontSize * 0.875;
}
}
.sleeves-page-info {
display: "block";
width: 75%;
}
.sleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px;
select {
display: block;
}
}

66
css/stockmarket.scss Normal file

@ -0,0 +1,66 @@
@import "theme";
#stock-market-container {
position: fixed;
padding: 6px;
p {
font-size: $defaultFontSize * 0.8125;
}
a {
font-size: $defaultFontSize * 0.875;
}
h2 {
margin-top: 10px;
margin-left: 10px;
display: block;
}
}
#stock-market-list li {
button {
font-size: $defaultFontSize;
}
}
#stock-market-container p {
padding: 6px;
margin: 6px;
width: 70%;
}
#stock-market-container a {
margin: 10px;
}
#stock-market-watchlist-filter {
width: 50%;
margin-left: 10px;
}
.stock-market-input {
display: inline-block;
padding: 4px;
margin: 2px;
background-color: #000;
border: 1px solid #fff;
color: var(--my-font-color);
}
.stock-market-position-text {
color: #fff;
display: inline-block;
}
.stock-market-order-list {
overflow-y: auto;
max-height: 100px;
}
.stock-market-order-cancel-btn {
background-color: #000;
border: 1px solid #fff;
color: var(--my-font-color);
margin: 2px;
padding: 0;
}

@ -35,11 +35,6 @@ li {
list-style-type: none;
}
span {
margin: 4px;
padding: 4px;
}
#entire-game-container {
background-color: transparent;
}

@ -67,5 +67,6 @@
}
#terminal-input-text-box {
margin-left: 2px;
flex: 1 1 auto;
}

@ -46,18 +46,6 @@
cursor: pointer;
}
#red-pill-container,
#cinematic-text-container {
position: fixed;
}
.bitnode {
color: #00f;
}
.bitnode-destroyed {
color: #f00;
}
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff;
}

File diff suppressed because one or more lines are too long

205
dist/engine.css vendored

@ -1,3 +1,46 @@
/* COLORS */
/* Attributes */
/**
* Customized styling for the Code Mirror editor
*/
#codemirror-form-wrapper {
height: 80%;
margin: 10px 0px 0px 6px; }
.CodeMirror {
height: 100%;
width: 100%;
border: 2px solid var(--my-highlight-color);
z-index: 1;
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman";
font-size: 16px; }
/**
* Highlight matches
*/
.cm-matchhighlight {
background-color: #8F908A; }
.CodeMirror-selection-highlight-scrollbar {
background-color: #8F908A; }
/**
* Show Invisibles
*/
.cm-whitespace::before {
position: absolute;
pointer-events: none;
color: #404F7D; }
/**
* Vim command display
*/
#codemirror-vim-command-display-wrapper {
background-color: white;
font-size: 13px;
height: 30px;
margin-left: 6px; }
/* COLORS */
/* Attributes */
/* COLORS */
@ -40,10 +83,6 @@ ul {
li {
list-style-type: none; }
span {
margin: 4px;
padding: 4px; }
#entire-game-container {
background-color: transparent; }
@ -771,29 +810,18 @@ button {
white-space: pre; }
#terminal-input-text-box {
margin-left: 2px;
flex: 1 1 auto; }
/* COLORS */
/* Attributes */
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
terminal which has its own page) */
.generic-menupage-container {
height: 100%;
padding-left: 10px;
margin-left: 10%;
width: 99%;
overflow-y: scroll; }
/* Character Info */
#character-container {
padding-top: 10px;
position: fixed; }
/* Script Editor */
/**
* Styling for Script Editor (both Ace and CodeMirror)
*/
#script-editor-container {
background-color: transparent; }
#javascript-editor {
#ace-editor {
margin: 10px;
height: 80%;
width: 100%;
@ -805,16 +833,6 @@ button {
z-index: 1;
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman"; }
.ace_line,
.ace_line * {
background-color: transparent;
margin: 0;
padding: 0; }
.ace_text-input {
font-size: 16px;
background-color: transparent; }
/* This temp element is used for auto adjusting filename field */
.tmp-element {
visibility: hidden;
@ -877,13 +895,45 @@ button {
padding: 2px;
overflow: auto;
z-index: 1;
color: #fff; }
color: #fff;
max-height: 50%; }
#script-editor-options-panel fieldset {
margin-top: 8px;
margin-bottom: 8px;
padding: 2px;
font-size: 12px; }
#script-editor-options-panel fieldset input {
margin: 2px; }
/* Specific overrides for Ace Editor */
.ace_line,
.ace_line * {
background-color: transparent;
margin: 0;
padding: 0; }
.ace_text-input {
font-size: 16px;
background-color: transparent; }
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
/* COLORS */
/* Attributes */
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
terminal which has its own page) */
.generic-menupage-container {
height: 100%;
padding-left: 10px;
margin-left: 10%;
width: 99%;
overflow-y: scroll; }
/* Character Info */
#character-container {
padding-top: 10px;
position: fixed; }
/* Active scripts */
.active-scripts-list {
@ -1222,7 +1272,26 @@ button {
display: inline;
width: 25%; }
/* Stock market */
/* COLORS */
/* Attributes */
/**
* Styling for the Red Pill screen (the BitNode selection UI)
*/
#red-pill-container {
position: fixed; }
.bitnode {
color: #00f; }
.bitnode-destroyed {
color: #f00; }
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff; }
/* COLORS */
/* Attributes */
#stock-market-container {
position: fixed;
padding: 6px; }
@ -1235,13 +1304,12 @@ button {
margin-left: 10px;
display: block; }
/* Change font size of Stock TIcker headers */
#stock-market-list li button {
font-size: 16px; }
#stock-market-container p {
padding: 10px;
margin: 10px;
padding: 6px;
margin: 6px;
width: 70%; }
#stock-market-container a {
@ -1318,20 +1386,9 @@ button {
text-decoration: none;
cursor: pointer; }
#red-pill-container,
#cinematic-text-container {
position: fixed; }
.bitnode {
color: #00f; }
.bitnode-destroyed {
color: #f00; }
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff; }
/* COLORS */
/* Attributes */
/* Pop-up boxes */
@ -1393,7 +1450,7 @@ button {
z-index: 10;
width: 50%;
height: auto;
max-height: 40%;
max-height: 50%;
top: 40%;
left: 50%;
margin: -10% 0 0 -25%;
@ -2047,12 +2104,11 @@ button {
.bladeburner-nav-button, .bladeburner-nav-button-inactive {
border: 1px solid #fff;
margin: 2px;
padding: 2px; }
.bladeburner-nav-button {
padding: 2px;
color: #fff; }
.bladeburner-nav-button:hover {
background-color: #3d4044; }
.bladeburner-nav-button:hover {
background-color: #3d4044; }
.bladeburner-nav-button-inactive {
text-decoration: none;
@ -2136,6 +2192,51 @@ button {
margin: 1px;
padding: 1px; }
/**
* Styling for the Sleeves Management page
*/
/* COLORS */
/* Attributes */
.sleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%; }
.sleeve-container p {
font-size: 14px; }
.sleeves-page-info {
display: "block";
width: 75%; }
.sleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px; }
.sleeve-panel select {
display: block; }
/**
* Styling for the Re-Sleeving Page
*/
/* COLORS */
/* Attributes */
.resleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%; }
.resleeve-container p {
font-size: 13px; }
.resleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px; }
.resleeve-aug-selector {
font-size: 13px; }
.resleeve-aug-selector option {
font-size: 13px; }
/* required LIB STYLES */
/* .Treant se automatski dodaje na svaki chart conatiner */
.Treant {

219
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

5527
dist/vendor.css vendored

File diff suppressed because one or more lines are too long

@ -0,0 +1,14 @@
Advanced Gameplay
=================
This section documents Bitburner gameplay elements that are **not** immediately
available and/or accessible to the player. These gameplay mechanics
must be unlocked.
.. toctree::
:maxdepth: 5
:caption: Elements:
BitNodes <advancedgameplay/bitnodes>
Source-Files <advancedgameplay/sourcefiles>
Intelligence <advancedgameplay/intelligence>
Sleeves <advancedgameplay/sleeves>

@ -0,0 +1,63 @@
.. _gameplay_bitnodes:
.. warning:: This page contains spoilers regarding the game's story/plot-line.
BitNodes
========
A BitNode is an important part of the game's storyline. In the game, you discover
what BitNodes are by following the trail of clues left by the mysterious jump3r
(essentially a minimal questline).
What is a BitNode
^^^^^^^^^^^^^^^^^
A BitNode is the complex simulated reality in which you reside. By following the messages
from jump3r, you discover that humanity was enslaved by an advanced alien race, called
the Enders, using virtual simulations that trapped the minds of humans.
However, the Enders didn't just create a single virtual reality to enslave humans, but many
different simulations. In other words, there are many different BitNodes that exist.
These BitNode are very different from each other.
jump3r tells you that the only hope for humanity is to destroy all of these BitNodes.
Therefore, the end goal for the player is to enter and then destroy each BitNode at least once.
Destroying a BitNode resets most of the player's progress but grants the player a
powerful second-tier persistent upgrade called a :ref:`Source-File <gameplay_sourcefiles>`.
Different BitNodes grant different Source-Files.
Each BitNode has unique characteristics that are related to varying backstories. For example,
in one BitNode the world is in the middle of a financial catastrophe with a collapsing
market. In this BitNode, most forms of income such as working at a company or Hacknet
Nodes are significantly less profitable. Servers have less money on them and lowered
growth rates, but it is easier to lower their security level using the weaken() Netscript function.
Furthermore, some BitNodes introduce new content and mechanics. For example there is one
BitNode that grants access to the :ref:`Netscript Singularity Functions <netscript_singularityfunctions>`.
There is another BitNode in which you can manage a gang to earn money and reputation.
How to destroy a BitNode
^^^^^^^^^^^^^^^^^^^^^^^^
Initially, the only way to destroy a BitNode is to join the Daedalus :ref:`Daedalus <gameplay_factions>`.
From Daedalus, the player can obtain an Augmentation called 'The Red Pill', which doesn't cost any money
but does require a good amount of faction reputation.
After installing 'The Red Pill', the player must search for and then manually hack a
server called 'w0r1d_d43m0n'. This server requires a hacking level of 3000 in order
to successfully hack it. This will destroy the player's current BitNode.
There is a second method of destroying a BitNode, but it must be unlocked by first
destroying BitNode-6 or BitNode-7 (Bladeburners).
.. todo:: Link to Bladeburner documentation page here
When the player destroys a BitNode, most of his/her progress will be reset. This includes things
such as Augmentations and RAM upgrades on the home computer. The only things that will persist
through destroying BitNodes is:
* Source-Files
* Scripts on the home computer
BitNode Details
^^^^^^^^^^^^^^^
TODO

@ -0,0 +1,21 @@
.. _gameplay_intelligence:
Intelligence
============
Intelligence is a :ref:`stat <gameplay_stats>` that is unlocked by having
:ref:`Source-File 5 <gameplay_sourcefiles>` (i.e. Destroying BitNode-5).
Intelligence is unique because it is permanent and persistent. It never gets reset
back to 1. However, gaining Intelligence experience is extremely slow. The methods
of gaining Intelligence exp is also hidden. You won't know when you gain
experience and how much. It is a stat that gradually builds up as you continue
to play the game.
Intelligence will boost your production for many actions in the game, including:
* Hacking
* Infiltration
* Hacking Missions
* Crime success rate
* Bladeburner
* Reputation gain for companies & factions

@ -0,0 +1,67 @@
.. _gameplay_sleeves:
Sleeves
=======
When VitaLife unveiled their Persona Core technology that allowed people to digitize
and transfer their consciousness into other vessels, human bodies became nothing more
than 'sleeves' for the human consciousness. This technology thus became known as
"Sleeve technology".
Sleeve technology unlocks two different gameplay features:
* Duplicate Sleeves
* Re-sleeving
Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
Duplicate Sleeves
^^^^^^^^^^^^^^^^^
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
has been copied. In other words, these Synthoids contain a perfect duplicate of your mind.
Duplicate Sleeves are essentially clones which you can use to perform work-type actions,
such as working for a company/faction or committing a crime. When sleeves perform these tasks,
they will earn money, experience, and reputation.
Sleeves are their own individuals, which means they each have their own experience and stats.
When a sleeve earns experience, it earns experience for itself, the player's
original consciousness, as well as all of the player's other sleeves.
Synchronization
~~~~~~~~~~~~~~~
Synchronization is a measure of how aligned your consciousness is with that of your
Duplicate Sleeves. It is a numeral value between 1 and 100, and it affects how much experience
is earned when the sleeve is performing a task.
Let N be the sleeve's synchronization. When the sleeve earns experience by performing
a task, both the sleeve and the player's original host consciousness of N% of the
amount of experience normally earned by the task. All of the player's other sleeves
earn ((N/100)^2 * 100)% of the experience.
Synchronization can be increased by assigning sleeves to the 'Synchronize' task.
Sleeve Shock
~~~~~~~~~~~~
Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new
body. It is a numeral value between 0 and 99, where 99 indicates full shock and 0 indicates
no shock. Shock affects the amount of experience earned by the sleeve.
Sleeve shock slowly decreases over time. You can further increase the rate at which
it decreases by assigning sleeves to the 'Shock Recovery' task.
Re-sleeving
^^^^^^^^^^^
Re-sleeving is the process of digitizing and transferring your consciousness into a
new human body, or "sleeve". When you re-sleeve into a new body, your stat experience
and Augmentations get replaced with those of the new body.
In order to re-sleeve, you must purchase new bodies. This can be done at VitaLife in
New Tokyo. Once you purchase a body to re-sleeve into, the effects will take
place immediately.
Note that resleeving **REMOVES** all of your currently-installed Augmentations,
and replaces them with the ones provided by the purchased sleeve. However,
Augmentations that are purchased but not installed will **not** be removed. If you have purchased
an Augmentation and then re-sleeve into a body which already has that Augmentation,
it will be removed since you cannot have duplicate Augmentations.

@ -0,0 +1,86 @@
.. _gameplay_sourcefiles:
.. warning:: This page contains spoilers regarding the game's story/plot-line.
Source-Files
============
Source-Files are a type of persistent upgrade that are more powerful than Augmentations.
Source-Files are received by destroying a BitNode. There are many different BitNodes
in the game and each BitNode will grant a different Source-File when it is destroyed.
A Source-File can be upgraded by destroying its corresponding BitNode a second or
third time (AKA playing through that BitNode again). It can be upgraded to a maximum
of level 3.
List of all Source-Files
^^^^^^^^^^^^^^^^^^^^^^^^
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-1: Source Genesis | * Lets the player start with 32 GB of RAM on home computer |
| | * Increases all of the player's multipliers by 16%/24%/28% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-2: Rise of the Underworld | * Increases the player's crime success rate, crime money, and |
| | charisma multipliers by 24%/36%/42% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-3: Corporatocracy | * Lets the player create Corporations in other BitNodes (although some |
| | BitNodes will disable this mechanic) |
| | * Increases the player's charisma and company salary multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-4: The Singularity | * Lets the player access and use Netscript Singularity Functions in other BitNodes. |
| | * Each level of this Source-File opens up more of the Singularity Functions to use |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-5: Artificial Intelligence | * Unlocks :ref:`gameplay_intelligence` |
| | * Unlocks getBitNodeMultipliers() Netscript function |
| | * Increases all of the player's hacking-related multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-6: Bladeburners | * Unlocks the Bladeburner feature in other BitNodes |
| | * Increases all of the player's level and experience gain rate multipliers for |
| | combat stats by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-7: Bladeburners 2079 | * Allows the player to access the :ref:`netscript_bladeburnerapi` in other BitNodes |
| | * Increases all of the player's Bladeburner multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-8: Ghost of Wall Street | * Increases the player's hacking growth multiplier by 12%/18%/21% |
| | * Level 1 grants permanent access to :ref:`WSE <gameplay_stock_market>` and |
| | :ref:`TIX API <netscript_tixapi>` |
| | * Level 2 grants permanent access to shorting stocks |
| | * Level 3 grants permanent access to use limit/stop orders |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-9: Coming Soon | |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-11: The Big Crash | * Company favor increases both the player's salary and reputation gain at that |
| | company by 1% per favor (rather than just the reputation gain) |
| | * Increases the player's company salary and reputation gain multipliers by |
| | 24%/36%/42% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-12: The Recursion | * There is no maximum level for this Source-File |
| | * Each level of this Source-File increases all of the player's multipliers by 1%. |
| | * This affect is multiplicative with itself. This means that level N of this |
| | Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers |
| | that decrease) |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+

@ -2,7 +2,7 @@
Companies
=========
When exploring the :ref:`world <World>`, you can visit various companies. At
When exploring the :ref:`world <gameplay_world>`, you can visit various companies. At
these companies, you can apply for jobs.
Working a job lets you earn money, experience, and reputation with that company.

@ -4,7 +4,7 @@ Crimes
======
Commiting crimes is an active gameplay mechanic that allows the player to train
their stats and potentially earn money. The player can attempt to commit crimes
by visiting 'The Slums' through the 'City' tab (:ref:`Keyboard shortcut <_shortcuts>` Alt + w).
by visiting 'The Slums' through the 'City' tab (:ref:`Keyboard shortcut <shortcuts>` Alt + w).
'The Slums' is available in every city.

@ -110,7 +110,7 @@ List of Factions and their Requirements
| | Clarke | * Have 200k reputation with | |
| | Incorporated | the Corporation | |
+ +----------------+-----------------------------------------+-------------------------------+
| | Fulcrum Secret | * Have 200k reputation with | |
| | Fulcrum Secret | * Have 250k reputation with | |
| | Technologies | the Corporation | |
| | | * Hack fulcrumassets manually | |
+---------------------+----------------+-----------------------------------------+-------------------------------+

@ -3,6 +3,30 @@
Changelog
=========
v0.43.0 - 2/4/2019
------------------
* Added BitNode-10: Digital Carbon
* Stock Market Changes:
* Each stock now has a maximum number of shares you can purchase (both Long and Short positions combined)
* Added getStockMaxShares() Netscript function to the TIX API
* The cost of 4S Market Data TIX API Access increased from $20b to $25b
* Job Changes:
* You can now hold multiple jobs at once. This means you no longer lose reputation when leaving a company
* Because of this change, the getCharacterInformation() Netscript function returns a slightly different value
* Script Editor Changes:
* Added new script editor: CodeMirror. You can choose between the old editor (Ace) or CodeMirror
* Navigation keyboard shortcuts no longer work if the script editor is focused
* Trying to programmatically run a script (run(), exec()) with a 'threads' argument of 0 will now cause the function to return false without running the script
* Home Computer RAM is now capped at 2 ^ 30 GB (1073741824 GB)
* The maximum amount, maximum RAM, and cost of purchasing servers can now vary between different BitNodes (new BitNode multipliers)
* Pop-up dialog boxes are a little bit bigger
* Bug Fix: When importing scripts, "./" will now be properly ignored (e.g. import { foo } from "./lib.script" )
v0.42.0 - 1/8/2019
------------------

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents.
#
# The short X.Y version.
version = '1.0'
version = '0.43'
# The full version, including alpha/beta/rc tags.
release = '1.0'
release = '0.43.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -92,7 +92,11 @@ todo_include_todos = True
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'agogo'
#html_theme = 'agogo'
html_theme = "sphinx_rtd_theme"
html_theme_options = {
"navigation_depth": 5,
}
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@ -178,7 +182,10 @@ texinfo_documents = [
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
def setup(app):
print("Initializing (setup())");
app.add_stylesheet('maxwidthoverride.css')

@ -21,7 +21,9 @@ secrets that you've been searching for.
Netscript <netscript>
Basic Gameplay <basicgameplay>
Advanced Gameplay <advancedgameplay>
Keyboard Shortcuts <shortcuts>
Script Editors <scripteditors>
Game Frozen or Stuck? <gamefrozen>
Changelog <changelog>

@ -1,3 +1,5 @@
.. _netscript_bladeburnerapi:
Netscript Bladeburner API
=========================
Netscript provides the following API for interacting with the game's Bladeburner mechanic.

@ -380,8 +380,10 @@ run
Run a script as a separate process. This function can only be used to run scripts located on the current server (the server
running the script that calls this function).
Returns true if the script is successfully started, and false otherwise. Requires a significant amount of RAM to run this
command.
Returns true if the script is successfully started, and false otherwise.
Running this function with a *numThreads* argument of 0 will return false without running the script.
However, running this function with a negative *numThreads* argument will cause a runtime error.
The simplest way to use the *run* command is to call it with just the script name. The following example will run
'foo.script' single-threaded with no arguments::
@ -415,6 +417,9 @@ exec
Returns true if the script is successfully started, and false otherwise.
Running this function with a *numThreads* argument of 0 will return false without running the script.
However, running this function with a negative *numThreads* argument will cause a runtime error.
The simplest way to use the *exec* command is to call it with just the script name and the target server.
The following example will try to run *generic-hack.script* on the *foodnstuff* server::

@ -1,9 +1,12 @@
.. _netscript_tixapi:
Netscript Trade Information eXchange (TIX) API
==============================================
The Trade Information eXchange (TIX) is the communications protocol supported by the World Stock Exchange (WSE).
The WSE provides an API that allows you to automatically communicate with the
`Stock Market <http://bitburner.wikia.com/wiki/Stock_Market>`_. This API lets you write code using Netscript
:ref:`Stock Market <gameplay_stock_market>`.
This API lets you write code using Netscript
to build automated trading systems and create your own algorithmic trading strategies. Access to this
TIX API can be purchased by visiting the World Stock Exchange in-game.
@ -62,6 +65,19 @@ getStockPosition
sharesShort = pos[2];
avgPxShort = pos[3];
getStockMaxShares
-----------------
.. js:function:: getStockMaxShares(sym)
:param string sym: Stock symbol
:RAM cost: 2 GB
Returns the maximum number of shares that the stock has. This is the maximum
amount of the stock that can be purchased in both the Long and Short
positions combined
buyStock
--------

@ -1,3 +1,5 @@
.. _netscript_singularityfunctions:
Netscript Singularity Functions
===============================
@ -164,9 +166,9 @@ getCharacterInformation
{
bitnode: Current BitNode number
city: Name of city you are currently in
company: Name of company
factions: Array of factions you are currently a member of
jobTitle: Name of job
jobs: Array of all companies at which you have jobs
jobTitle: Array of job positions for all companies you are employed at. Same order as 'jobs'
tor: Boolean indicating whether or not you have a tor router
// The following is an object with many of the player's multipliers from Augmentations/Source Files

@ -0,0 +1,140 @@
.. _scripteditors:
Script Editors
==============
Third-party libraries are used to implement the game's Script Editor(s). There are
currently two options for the Script Editor:
* `Ace <https://ace.c9.io/>`_
* `CodeMirror <https://codemirror.net/>`_
You can select which of the two editors you want to use on the Script Editor page
('Create Script' on the main menu).
Ace was the game's original Script Editor, while CodeMirror was added later in
v0.43.0. The two editors share many of the same features, so there is not a significant
difference between the two. Currently, CodeMirror is slightly more modern,
more customizable, and has a few quality-of-life improvements compared to Ace.
Universal Key Bindings
----------------------
These keyboard shortcuts are available in both the Ace and CodeMirror editors, regardless
of what key binding option you are using:
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Ctrl + b Save script and return to :ref:`terminal`
Ctrl + space Show Autocomplete Hints
============= ===========================================================================
.. _scripteditor_linter:
Linter
------
Both script editors contain a linter, which is a tool that analyzes your
code and flags anything it thinks might be an error. You can see
warnings and errors from the linter on the left-hand side of the script editor. There
will be an icon on whatever lines the linter thinks might be problematic. Hovering
over the icon will display information on what the issue is.
Note that **just because the linter shows an error/warning, this does NOT automatically mean that**
**your script is broken and will fail to run.** This is especially true if you are using
:ref:`netscriptjs`. The linter used by the script editors isn't necessarily perfect or
up-to-date. Furthermore, the linter does not affect anything when you actually run scripts.
Ace
---
The following documents what the different settings/options do for the Ace editor,
as well as the different key binding settings. Note that the
information for the key bindings may not be completely comprehensive. You'll
have to dig into the editor source code if you want to learn more.
Settings
~~~~~~~~
===================== ===========================================================================================================
Setting Effect
===================== ===========================================================================================================
Theme Switch between different color schemes
Key Binding Switch between different key binding options. This changes what keyboard shortcuts are available
Highlight Active Line When enabled, the line on which the cursor currently resides will be highlighted.
Show Invisibles When enabled, you will be able to view hidden whitespace characters such as spaces, tabs, and newlines.
Use Soft Tab When enabled, tabs will be replaced with spaces
Max Error Count Specifies the (approximate) number of lines that will be linted
===================== ===========================================================================================================
Ace Key Bindings
~~~~~~~~~~~~~~~~
For Ace, the "Ace" Key Binding setting uses the default configuration. A list of these
`can be found here <https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts>`_.
Vim Key Bindings
~~~~~~~~~~~~~~~~
For Ace, the "Vim" Key Binding setting configures the editor to use
`Vim <https://en.wikipedia.org/wiki/Vim_(text_editor)>`_ key mappings. Note that while this tries
to emulate Vim features as faithfully as possible, it is not a complete Vim implementation.
Since I'm not familiar with Vim, I'll leave
`Ace's Vim Mode implementation here <https://github.com/ajaxorg/ace/blob/96088d0fc292daf0706b2d029cc03c3799be5974/lib/ace/keyboard/vim.js#L860>`_,
which I believe shows most of the implemented features.
Note that the following Vim Ex commands will properly save the script and/or quit the editor in game:
======= ==============================================
Command Effect
======= ==============================================
:w Save the script and return to :ref:`terminal`
:q Return to :ref:`terminal` **WITHOUT** saving
:x Save the script and return to :ref:`terminal`
:wq Save the script and return to :ref:`terminal`
======= ==============================================
Emacs Key Bindings
~~~~~~~~~~~~~~~~~~
For Ace, the "Emacs" Key Binding setting configures the editor to use
`Emacs <https://en.wikipedia.org/wiki/Emacs>`_ key mappings. Note that while this tries
to emulate the Emacs key mappings as faithfully as possible, it won't necessarily be a
complete implementation.
Since I'm not familiar with Emacs, I'll leave
`Ace's Emacs Mode implementation here <https://github.com/ajaxorg/ace/blob/96088d0fc292daf0706b2d029cc03c3799be5974/lib/ace/keyboard/emacs.js#L343>`_,
which I believe shows most of the implemented features.
CodeMirror
----------
The following documents what the different settings/options do for the CodeMirror editor,
as well as the shortcuts for the different key binding settings. Note that the
information for the key bindings may not be completely comprehensive. You'll
have to dig into the editor source code if you want to learn everything.
Settings
~~~~~~~~
========================== ===========================================================================================================
Setting Effect
========================== ===========================================================================================================
Theme Switch between different color schemes
Key Binding Switch between different key binding options. This changes what keyboard shortcuts are available
Highlight Active Line When enabled, the line on which the cursor currently resides will be highlighted.
Show Invisibles When enabled, you will be able to view hidden whitespace characters such as spaces, tabs, and newlines.
Use Soft Tab When enabled, tabs will be replaced with spaces
Auto-Close Brackets/Quotes When enabled, any opening brackets or quotes that are typed will be closed
Enable Linting Enable/Disable the :ref:`scripteditor_linter`
Continue Comments When enabled, pressing 'Enter' inside a comment block will continue the comment on the next line
========================== ===========================================================================================================
Default Key Bindings
~~~~~~~~~~~~~~~~~~~~
.. todo:: Fill out
Sublime Key Bindings
~~~~~~~~~~~~~~~~~~~~
.. todo:: Fill out
Vim Key Bindings
~~~~~~~~~~~~~~~~
.. todo:: Fill out
Emacs Key Bindings
~~~~~~~~~~~~~~~~~~
.. todo:: Fill out

@ -18,7 +18,7 @@ These shortcuts are almost always available. Exceptions include:
========== ===========================================================================
Shortcut Action
========== ===========================================================================
Alt + t Switch to :doc:`terminal`
Alt + t Switch to :ref:`terminal`
Alt + c Switch to 'Stats' page
Alt + e Switch to Script Editor. Will open up the last-edited file or a new file
Alt + s Switch to 'Active Scripts' page
@ -35,24 +35,11 @@ Alt + o Switch to 'Options' page
Script Editor
-------------
These shortcuts are available only in the Script Editor
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Ctrl + b Save script and return to :doc:`terminal`
Ctrl + space Function autocompletion
============= ===========================================================================
In the Script Editor you can configure your key binding mode to three preset options:
* `Ace <https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts>`_
* Vim
* Emacs
See the :ref:`Script Editor <scripteditors>` documentation for more details.
Terminal Shortcuts
------------------
These shortcuts are available only in the :doc:`terminal`
These shortcuts are available only in the :ref:`terminal`
============= ===========================================================================
Shortcut Action
@ -66,7 +53,7 @@ Tab Autocomplete command
Terminal Bash Shortcuts
-----------------------
These shortcuts were implemented to better emulate a bash shell. They must be enabled
in your :doc:`terminal`'s *.fconf* file. This can be done be entering the Terminal command::
in your :ref:`terminal`'s *.fconf* file. This can be done be entering the Terminal command::
nano .fconf
@ -92,10 +79,13 @@ Alt + f Move cursor to next word
Ctrl + h/d Delete previous character ('Backspace')
============= ===========================================================================
Misc Shortcuts
--------------
Popup/Dialog Box Shortcuts
--------------------------
The following shortcuts work if there are any popup or dialog boxes on the screen.
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Esc Close a script's log window
Esc Close the current popup, cancelling any prompts on a dialog box
Enter Clicks the "Yes/Confirm" option for every dialog box
============= ===========================================================================

@ -0,0 +1,3 @@
.wy-nav-content {
max-width: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -65,6 +65,9 @@
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>
</li>
<li id="sleeves-tab" class="mainmenu-accordion-panel">
<button id="sleeves-menu-link"> Sleeves </button>
</li>
<!-- World dropdown -->
<li id="world-menu-header-li">
@ -115,7 +118,11 @@
<input id="script-editor-filename" type="text" maxlength="30" tabindex="1"/>
</div>
<div id="javascript-editor"></div>
<div id="ace-editor"></div>
<form id="codemirror-form-wrapper"><textarea id="codemirror-editor"></textarea></form>
<div id="codemirror-vim-command-display-wrapper">
Key Buffer: <span id="codemirror-vim-command-display"></span>
</div>
<div id="script-editor-buttons-wrapper"></div> <!-- Buttons below script editor -->
</div> <!-- End wrapper -->
@ -123,26 +130,21 @@
<div id="script-editor-options-panel">
<h1 style="color:white;"> Script Editor Options </h1>
<fieldset>
<label for="script-editor-option-theme">Theme</label>
<select id="script-editor-option-theme">
<option value="Chaos">Chaos</option>
<option value="Chrome">Chrome</option>
<option value="Monokai">Monokai</option>
<option value="Solarized_Dark">Solarized Dark</option>
<option value="Solarized_Light">Solarized Light</option>
<option value="Terminal">Terminal</option>
<option value="Twilight">Twilight</option>
<option value="XCode">XCode</option>
<label for="script-editor-option-editor">Editor</label>
<select id="script-editor-option-editor">
<option value="Ace">Ace</option>
<option value="CodeMirror">CodeMirror</option>
</select>
</fieldset>
<fieldset>
<label for="script-editor-option-theme">Theme</label>
<select id="script-editor-option-theme"></select>
</fieldset>
<fieldset>
<label for="script-editor-option-keybinding">Key Binding</label>
<select id="script-editor-option-keybinding">
<option value="ace">Ace</option>
<option value="vim">Vim</option>
<option value="emacs">Emacs</option>
</select>
<select id="script-editor-option-keybinding"></select>
</fieldset>
<fieldset>
@ -160,11 +162,11 @@
<input type="checkbox" name="script-editor-option-usesofttab" id="script-editor-option-usesofttab" checked>
</fieldset>
<fieldset>
<label for="script-editor-option-maxerr" class="tooltip">Max Error Count</label>
<input type="range" max="1000" min="50" value="200" step="1" name="script-editor-option-maxerr" id="script-editor-option-maxerr"/>
<em id="script-editor-option-maxerror-value-label" style="font-style: normal;"></em>
</fieldset>
<fieldset id="script-editor-option-flex1-fieldset"></fieldset>
<fieldset id="script-editor-option-flex2-fieldset"></fieldset>
<fieldset id="script-editor-option-flex3-fieldset"></fieldset>
<fieldset id="script-editor-option-flex4-fieldset"></fieldset>
</div> <!-- End script editor options panel -->
</div>
@ -594,8 +596,11 @@
<!-- City Hall -->
<a id="location-cityhall-create-corporation" class="a-link-button">Create a Corporation</a>
<!-- Bladeburner@NSA -->
<!-- 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">

1138
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -15,6 +15,7 @@
"autosize": "^4.0.2",
"bluebird": "^3.5.1",
"brace": "^0.11.1",
"codemirror": "^5.43.0",
"decimal.js": "7.2.3",
"enhanced-resolve": "^4.0.0",
"escodegen": "^1.11.0",
@ -22,6 +23,7 @@
"file-saver": "^1.3.8",
"interpret": "^1.0.0",
"jquery": "^3.3.1",
"jshint": "^2.9.7",
"json-loader": "^0.5.4",
"jsplumb": "^2.6.8",
"jszip": "^3.1.5",
@ -58,10 +60,9 @@
"lodash": "^4.17.10",
"mini-css-extract-plugin": "^0.4.1",
"mkdirp": "^0.5.1",
"mocha": "^3.2.0",
"mocha": "^5.2.0",
"mocha-lcov-reporter": "^1.0.0",
"node-sass": "^4.9.2",
"nsp": "^3.2.1",
"node-sass": "^4.10.0",
"raw-loader": "~0.5.0",
"sass-loader": "^7.0.3",
"script-loader": "~0.7.0",

@ -0,0 +1,156 @@
// Class definition for a single Augmentation object
import { CONSTANTS } from "../Constants";
import { IMap } from "../types";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Faction } from "../Faction/Faction";
import { Factions } from "../Faction/Factions";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
interface IConstructorParams {
info: string;
moneyCost: number;
name: string;
prereqs?: string[];
repCost: number;
hacking_mult?: number;
strength_mult?: number;
defense_mult?: number;
dexterity_mult?: number;
agility_mult?: number;
charisma_mult?: number;
hacking_exp_mult?: number;
strength_exp_mult?: number;
defense_exp_mult?: number;
dexterity_exp_mult?: number;
agility_exp_mult?: number;
charisma_exp_mult?: number;
hacking_chance_mult?: number;
hacking_speed_mult?: number;
hacking_money_mult?: number;
hacking_grow_mult?: number;
company_rep_mult?: number;
faction_rep_mult?: number;
crime_money_mult?: number;
crime_success_mult?: number;
work_money_mult?: number;
hacknet_node_money_mult?: number;
hacknet_node_purchase_cost_mult?: number;
hacknet_node_ram_cost_mult?: number;
hacknet_node_core_cost_mult?: number;
hacknet_node_level_cost_mult?: number;
bladeburner_max_stamina_mult?: number;
bladeburner_stamina_gain_mult?: number;
bladeburner_analysis_mult?: number;
bladeburner_success_chance_mult?: number;
}
export class Augmentation {
// Initiatizes a Augmentation object from a JSON save state.
static fromJSON(value: any): Augmentation {
return Generic_fromJSON(Augmentation, value.data);
}
// How much money this costs to buy
baseCost: number = 0;
// How much faction reputation is required to unlock this
baseRepRequirement: number = 0;
// Description of what this Aug is and what it does
info: string = "";
// Augmentation level - for repeatable Augs like NeuroFlux Governor
level: number = 0;
// Name of Augmentation
name: string = "";
// Whether the player owns this Augmentation
owned: boolean = false;
// Array of names of all prerequisites
prereqs: string[] = [];
// Multipliers given by this Augmentation. Must match the property name in
// The Player/Person classes
mults: IMap<number> = {}
constructor(params: IConstructorParams={ info: "", moneyCost: 0, name: "", repCost: 0 }) {
this.name = params.name;
this.info = params.info;
this.prereqs = params.prereqs ? params.prereqs : [];
this.baseRepRequirement = params.repCost * CONSTANTS.AugmentationRepMultiplier * BitNodeMultipliers.AugmentationRepCost;
this.baseCost = params.moneyCost * CONSTANTS.AugmentationCostMultiplier * BitNodeMultipliers.AugmentationMoneyCost;
this.level = 0;
// Set multipliers
if (params.hacking_mult) { this.mults.hacking_mult = params.hacking_mult; }
if (params.strength_mult) { this.mults.strength_mult = params.strength_mult; }
if (params.defense_mult) { this.mults.defense_mult = params.defense_mult; }
if (params.dexterity_mult) { this.mults.dexterity_mult = params.dexterity_mult; }
if (params.agility_mult) { this.mults.agility_mult = params.agility_mult; }
if (params.charisma_mult) { this.mults.charisma_mult = params.charisma_mult; }
if (params.hacking_exp_mult) { this.mults.hacking_exp_mult = params.hacking_exp_mult; }
if (params.strength_exp_mult) { this.mults.strength_exp_mult = params.strength_exp_mult; }
if (params.defense_exp_mult) { this.mults.defense_exp_mult = params.defense_exp_mult; }
if (params.dexterity_exp_mult) { this.mults.dexterity_exp_mult = params.dexterity_exp_mult; }
if (params.agility_exp_mult) { this.mults.agility_exp_mult = params.agility_exp_mult; }
if (params.charisma_exp_mult) { this.mults.charisma_exp_mult = params.charisma_exp_mult; }
if (params.hacking_chance_mult) { this.mults.hacking_chance_mult = params.hacking_chance_mult; }
if (params.hacking_speed_mult) { this.mults.hacking_speed_mult = params.hacking_speed_mult; }
if (params.hacking_money_mult) { this.mults.hacking_money_mult = params.hacking_money_mult; }
if (params.hacking_grow_mult) { this.mults.hacking_grow_mult = params.hacking_grow_mult; }
if (params.company_rep_mult) { this.mults.company_rep_mult = params.company_rep_mult; }
if (params.faction_rep_mult) { this.mults.faction_rep_mult = params.faction_rep_mult; }
if (params.crime_money_mult) { this.mults.crime_money_mult = params.crime_money_mult; }
if (params.crime_success_mult) { this.mults.crime_success_mult = params.crime_success_mult; }
if (params.work_money_mult) { this.mults.work_money_mult = params.work_money_mult; }
if (params.hacknet_node_money_mult) { this.mults.hacknet_node_money_mult = params.hacknet_node_money_mult; }
if (params.hacknet_node_purchase_cost_mult) { this.mults.hacknet_node_purchase_cost_mult = params.hacknet_node_purchase_cost_mult; }
if (params.hacknet_node_ram_cost_mult) { this.mults.hacknet_node_ram_cost_mult = params.hacknet_node_ram_cost_mult; }
if (params.hacknet_node_core_cost_mult) { this.mults.hacknet_node_core_cost_mult = params.hacknet_node_core_cost_mult; }
if (params.hacknet_node_level_cost_mult) { this.mults.hacknet_node_level_cost_mult = params.hacknet_node_level_cost_mult; }
if (params.bladeburner_max_stamina_mult) { this.mults.bladeburner_max_stamina_mult = params.bladeburner_max_stamina_mult; }
if (params.bladeburner_stamina_gain_mult) { this.mults.bladeburner_stamina_gain_mult = params.bladeburner_stamina_gain_mult; }
if (params.bladeburner_analysis_mult) { this.mults.bladeburner_analysis_mult = params.bladeburner_analysis_mult; }
if (params.bladeburner_success_chance_mult) { this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult; }
}
// Adds this Augmentation to the specified Factions
addToFactions(factionList: string[]): void {
for (let i = 0; i < factionList.length; ++i) {
const faction: Faction | null = Factions[factionList[i]];
if (faction == null) {
console.warn(`In Augmentation.addToFactions(), could not find faction with this name: ${factionList[i]}`);
continue;
}
faction!.augmentations.push(this.name);
}
}
// Adds this Augmentation to all Factions
addToAllFactions(): void {
for (const fac in Factions) {
if (Factions.hasOwnProperty(fac)) {
const facObj: Faction | null = Factions[fac];
if (facObj == null) {
console.warn(`Invalid Faction object in addToAllFactions(). Key value: ${fac}`);
continue;
}
facObj!.augmentations.push(this.name);
}
}
}
// Serialize the current object to a JSON save state.
toJSON(): any {
return Generic_toJSON("Augmentation", this);
}
}
Reviver.constructors.Augmentation = Augmentation;

@ -0,0 +1,4 @@
import { Augmentation } from "./Augmentation";
import { IMap } from "../types";
export let Augmentations: IMap<Augmentation> = {};

@ -0,0 +1,13 @@
export class PlayerOwnedAugmentation {
level: number = 1;
name: string = "";
constructor(name: string = "") {
this.name = name;
}
}
export interface IPlayerOwnedAugmentation {
level: number;
name: string;
}

@ -0,0 +1,114 @@
import { IMap } from "../../types";
export let AugmentationNames: IMap<string> = {
Targeting1: "Augmented Targeting I",
Targeting2: "Augmented Targeting II",
Targeting3: "Augmented Targeting III",
SyntheticHeart: "Synthetic Heart",
SynfibrilMuscle: "Synfibril Muscle",
CombatRib1: "Combat Rib I",
CombatRib2: "Combat Rib II",
CombatRib3: "Combat Rib III",
NanofiberWeave: "Nanofiber Weave",
SubdermalArmor: "NEMEAN Subdermal Weave",
WiredReflexes: "Wired Reflexes",
GrapheneBoneLacings: "Graphene Bone Lacings",
BionicSpine: "Bionic Spine",
GrapheneBionicSpine: "Graphene Bionic Spine Upgrade",
BionicLegs: "Bionic Legs",
GrapheneBionicLegs: "Graphene Bionic Legs Upgrade",
SpeechProcessor: "Speech Processor Implant",
TITN41Injection: "TITN-41 Gene-Modification Injection",
EnhancedSocialInteractionImplant: "Enhanced Social Interaction Implant",
BitWire: "BitWire",
ArtificialBioNeuralNetwork: "Artificial Bio-neural Network Implant",
ArtificialSynapticPotentiation: "Artificial Synaptic Potentiation",
EnhancedMyelinSheathing: "Enhanced Myelin Sheathing",
SynapticEnhancement: "Synaptic Enhancement Implant",
NeuralRetentionEnhancement: "Neural-Retention Enhancement",
DataJack: "DataJack",
ENM: "Embedded Netburner Module",
ENMCore: "Embedded Netburner Module Core Implant",
ENMCoreV2: "Embedded Netburner Module Core V2 Upgrade",
ENMCoreV3: "Embedded Netburner Module Core V3 Upgrade",
ENMAnalyzeEngine: "Embedded Netburner Module Analyze Engine",
ENMDMA: "Embedded Netburner Module Direct Memory Access Upgrade",
Neuralstimulator: "Neuralstimulator",
NeuralAccelerator: "Neural Accelerator",
CranialSignalProcessorsG1: "Cranial Signal Processors - Gen I",
CranialSignalProcessorsG2: "Cranial Signal Processors - Gen II",
CranialSignalProcessorsG3: "Cranial Signal Processors - Gen III",
CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV",
CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V",
NeuronalDensification: "Neuronal Densification",
NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant",
SpeechEnhancement: "Speech Enhancement",
FocusWire: "FocusWire",
PCDNI: "PC Direct-Neural Interface",
PCDNIOptimizer: "PC Direct-Neural Interface Optimization Submodule",
PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector",
ADRPheromone1: "ADR-V1 Pheromone Gene",
ADRPheromone2: "ADR-V2 Pheromone Gene",
HacknetNodeCPUUpload: "Hacknet Node CPU Architecture Neural-Upload",
HacknetNodeCacheUpload: "Hacknet Node Cache Architecture Neural-Upload",
HacknetNodeNICUpload: "Hacknet Node NIC Architecture Neural-Upload",
HacknetNodeKernelDNI: "Hacknet Node Kernel Direct-Neural Interface",
HacknetNodeCoreDNI: "Hacknet Node Core Direct-Neural Interface",
NeuroFluxGovernor: "NeuroFlux Governor",
Neurotrainer1: "Neurotrainer I",
Neurotrainer2: "Neurotrainer II",
Neurotrainer3: "Neurotrainer III",
Hypersight: "HyperSight Corneal Implant",
LuminCloaking1: "LuminCloaking-V1 Skin Implant",
LuminCloaking2: "LuminCloaking-V2 Skin Implant",
HemoRecirculator: "HemoRecirculator",
SmartSonar: "SmartSonar Implant",
PowerRecirculator: "Power Recirculation Core",
QLink: "QLink",
TheRedPill: "The Red Pill",
SPTN97: "SPTN-97 Gene Modification",
HiveMind: "ECorp HVMind Implant",
CordiARCReactor: "CordiARC Fusion Reactor",
SmartJaw: "SmartJaw",
Neotra: "Neotra",
Xanipher: "Xanipher",
nextSENS: "nextSENS Gene Modification",
OmniTekInfoLoad: "OmniTek InfoLoad",
PhotosyntheticCells: "Photosynthetic Cells",
Neurolink: "BitRunners Neurolink",
TheBlackHand: "The Black Hand",
CRTX42AA: "CRTX42-AA Gene Modification",
Neuregen: "Neuregen Gene Modification",
CashRoot: "CashRoot Starter Kit",
NutriGen: "NutriGen Implant",
INFRARet: "INFRARET Enhancement",
DermaForce: "DermaForce Particle Barrier",
GrapheneBrachiBlades: "Graphene BranchiBlades Upgrade",
GrapheneBionicArms: "Graphene Bionic Arms Upgrade",
BrachiBlades: "BrachiBlades",
BionicArms: "Bionic Arms",
SNA: "Social Negotiation Assistant (S.N.A)",
EsperEyewear: "EsperTech Bladeburner Eyewear",
EMS4Recombination: "EMS-4 Recombination",
OrionShoulder: "ORION-MKIV Shoulder",
HyperionV1: "Hyperion Plasma Cannon V1",
HyperionV2: "Hyperion Plasma Cannon V2",
GolemSerum: "GOLEM Serum",
VangelisVirus: "Vangelis Virus",
VangelisVirus3: "Vangelis Virus 3.0",
INTERLINKED: "I.N.T.E.R.L.I.N.K.E.D",
BladeRunner: "Blade's Runners",
BladeArmor: "BLADE-51b Tesla Armor",
BladeArmorPowerCells: "BLADE-51b Tesla Armor: Power Cells Upgrade",
BladeArmorEnergyShielding: "BLADE-51b Tesla Armor: Energy Shielding Upgrade",
BladeArmorUnibeam: "BLADE-51b Tesla Armor: Unibeam Upgrade",
BladeArmorOmnibeam: "BLADE-51b Tesla Armor: Omnibeam Upgrade",
BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade",
BladesSimulacrum: "The Blade's Simulacrum",
//Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
//PepBoyForceField Generates plasma force fields
//PepBoyBlasts Generate high density plasma concussive blasts
//PepBoyDataStorage STore more data on pep boy,
}

@ -1,5 +1,5 @@
import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {Player} from "./Player";
import { BitNodeMultipliers } from "./BitNodeMultipliers";
import { Player } from "../Player";
function BitNode(n, name, desc="", info="") {
this.number = n;
@ -56,7 +56,6 @@ function initBitNodes() {
"The price and reputation cost of all Augmentations is tripled<br>" +
"The starting and maximum amount of money on servers is reduced by 75%<br>" +
"Server growth rate is reduced by 80%<br>" +
"You will start out with $150b so that you can start your corporation<br>" +
"You now only need 75 favour with a faction in order to donate to it, rather than 150<br><br>" +
"Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " +
@ -157,7 +156,22 @@ function initBitNodes() {
"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["BitNode10"] = new BitNode(10, "MegaCorp", "COMING SOON"); //Not sure yet
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 " +
"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.<br><br>" +
"This BitNode unlocks Sleeve technology. Sleeve technology allows you to:<br><br>" +
"1. Re-sleeve: Purchase and transfer your consciousness into a new body<br>" +
"2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously<br><br>" +
"In this BitNode:<br><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>" +
"Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " +
"Each level of this Source-File also grants you a Duplicate Sleeve");
BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
"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 " +
@ -306,6 +320,27 @@ function initBitNodeMultipliers() {
BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0;
break;
case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
BitNodeMultipliers.DefenseLevelMultiplier = 0.4;
BitNodeMultipliers.DexterityLevelMultiplier = 0.4;
BitNodeMultipliers.AgilityLevelMultiplier = 0.4;
BitNodeMultipliers.CharismaLevelMultiplier = 0.4;
BitNodeMultipliers.CompanyWorkMoney = 0.5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.HacknetNodeMoney = 0.5;
BitNodeMultipliers.ManualHackMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.5;
BitNodeMultipliers.CodingContractMoney = 0.5;
BitNodeMultipliers.InfiltrationMoney = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.AugmentationMoneyCost = 5;
BitNodeMultipliers.AugmentationRepCost = 2;
BitNodeMultipliers.PurchasedServerCost = 5;
BitNodeMultipliers.PurchasedServerLimit = 0.6;
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
break;
case 11: //The Big Crash
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;

@ -4,6 +4,11 @@
* player toward the intended strategy. Unless they really want to play the long, slow game of waiting...
*/
interface IBitNodeMultipliers {
/**
* Influences how quickly the player's agility level (not exp) scales
*/
AgilityLevelMultiplier: number;
/**
* Influences the base cost to purchase an augmentation.
*/
@ -24,6 +29,11 @@ interface IBitNodeMultipliers {
*/
BladeburnerSkillCost: number;
/**
* Influences how quickly the player's charisma level (not exp) scales
*/
CharismaLevelMultiplier: number;
/**
* Influences the experience gained for each ability when a player completes a class.
*/
@ -59,6 +69,16 @@ interface IBitNodeMultipliers {
*/
CrimeMoney: number;
/**
* Influences how quickly the player's defense level (not exp) scales
*/
DefenseLevelMultiplier: number;
/**
* Influences how quickly the player's dexterity level (not exp) scales
*/
DexterityLevelMultiplier: number;
/**
* Influences how much rep the player gains in each faction simply by being a member.
*/
@ -105,6 +125,20 @@ interface IBitNodeMultipliers {
*/
ManualHackMoney: number;
/**
* Influence how much it costs to purchase a server
*/
PurchasedServerCost: number;
/**
* Influences the maximum number of purchased servers you can have
*/
PurchasedServerLimit: number;
/**
* Influences the maximum allowed RAM for a purchased server
*/
PurchasedServerMaxRam: number;
/**
* Influences the minimum favor the player must have with a faction before they can donate to gain rep.
*/
@ -139,6 +173,11 @@ interface IBitNodeMultipliers {
* Influences the weaken amount per invocation against a server.
*/
ServerWeakenRate: number;
/**
* Influences how quickly the player's strength level (not exp) scales
*/
StrengthLevelMultiplier: number;
}
/**
@ -147,6 +186,11 @@ interface IBitNodeMultipliers {
// tslint:disable-next-line:variable-name
export const BitNodeMultipliers: IBitNodeMultipliers = {
HackingLevelMultiplier: 1,
StrengthLevelMultiplier: 1,
DefenseLevelMultiplier: 1,
DexterityLevelMultiplier: 1,
AgilityLevelMultiplier: 1,
CharismaLevelMultiplier: 1,
ServerGrowthRate: 1,
ServerMaxMoney: 1,
@ -154,6 +198,10 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
ServerStartingSecurity: 1,
ServerWeakenRate: 1,
PurchasedServerCost: 1,
PurchasedServerLimit: 1,
PurchasedServerMaxRam: 1,
CompanyWorkMoney: 1,
CrimeMoney: 1,
HacknetNodeMoney: 1,

1
src/BitNode/README.md Normal file

@ -0,0 +1 @@
Contains implementation of BitNodes and BitNode-specific mechanics

@ -1,5 +1,6 @@
import { Augmentations , AugmentationNames } from "./Augmentations";
import { BitNodeMultipliers } from "./BitNodeMultipliers";
import { Augmentations } from "./Augmentation/Augmentations";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Faction } from "./Faction/Faction";

@ -84,7 +84,7 @@ function sanitizeRewardType(rewardType) {
if (type === CodingContractRewardType.FactionReputationAll && factionsThatAllowHacking.length === 0) {
type = CodingContractRewardType.CompanyReputation;
}
if (type === CodingContractRewardType.CompanyReputation && Player.companyName === "") {
if (type === CodingContractRewardType.CompanyReputation && Object.keys(Player.jobs).length === 0) {
type = CodingContractRewardType.Money;
}
@ -115,8 +115,9 @@ function getRandomReward() {
reward.name = randFaction;
break;
case CodingContractRewardType.CompanyReputation:
if (Player.companyName !== "") {
reward.name = Player.companyName;
const allJobs = Object.keys(Player.jobs);
if (allJobs.length > 0) {
reward.name = allJobs[getRandomInt(0, allJobs.length - 1)];
} else {
reward.type = CodingContractRewardType.Money;
}

@ -19,7 +19,7 @@ export let CONSTANTS: IMap<any> = {
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
BaseCostFor1GBOfRamHacknetNode: 30000,
TravelCost: 200000,
TravelCost: 200e3,
BaseCostForHacknetNode: 1000,
BaseCostForHacknetNodeCore: 500000,
@ -104,10 +104,11 @@ export let CONSTANTS: IMap<any> = {
NumNetscriptPorts: 20,
//Server constants
ServerBaseGrowthRate: 1.03, //Unadjusted Growth rate
ServerMaxGrowthRate: 1.0035, //Maximum possible growth rate (max rate accounting for server security)
ServerFortifyAmount: 0.002, //Amount by which server's security increases when its hacked/grown
ServerWeakenAmount: 0.05, //Amount by which server's security decreases when weakened
HomeComputerMaxRam: 1073741824, // 2 ^ 30
ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate
ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security)
ServerFortifyAmount: 0.002, // Amount by which server's security increases when its hacked/grown
ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened
PurchasedServerLimit: 25,
PurchasedServerMaxRam: 1048576, //2^20
@ -129,7 +130,7 @@ export let CONSTANTS: IMap<any> = {
WSEAccountCost: 200e6,
TIXAPICost: 5e9,
MarketData4SCost: 1e9,
MarketDataTixApi4SCost: 20e9,
MarketDataTixApi4SCost: 25e9,
StockMarketCommission: 100e3,
//Hospital/Health
@ -275,6 +276,9 @@ export let CONSTANTS: IMap<any> = {
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 50e6,
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
/* Tutorial related things */
TutorialNetworkingText: "Servers are a central part of the game. You start with a single personal server (your home computer) " +
"and you can purchase additional servers as you progress through the game. Connecting to other servers " +
@ -506,40 +510,27 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.42.0
* Corporation Changes:
** Corporation can now be self-funded with $150b or using seed money in exchange for 500m newly-issued shares
** In BitNode-3, you no longer start with $150b
** Changed initial market prices for many materials
** Changed the way a material's demand, competition, and market price change over time
** The sale price of materials can no longer be marked-up as high
** Added a Research Tree mechanic. Spend Scientific Research on permanent upgrades for each industry
** You can now redistribute earnings to shareholders (including yourself) as dividends
** Cost of "Smart Supply" upgraded reduced from $50b to $25b
** Now has offline progress, which works similarly to the Gang/Bladeburner mechanics
** Slightly reduced the amount of money offered to you by investment firms
** Employee salaries now slowly increase over time
** Slightly reduced the effect "Real Estate" has on the Production Multiplier for the Agriculture industry
** Changed the way your Corporation's value is calculated (this is what determines stock price)
** After taking your corporation public, it is now possible to issue new shares to raise capital
** Issuing new shares can only be done once every 12 hours
** Buying back shares must now be done at a premium
** Selling shares can now only be done once per hour
** Selling large amounts of shares now immediately impacts stock price (during the transaction)
** Reduced the initial cost of the DreamSense upgrade from $8b to $4b, but increased its price multiplier
** Reduced the price multiplier for ABC SalesBots upgrade
v0.43.0
* Added BitNode-10: Digital Carbon
* Added getOrders() Netscript function to the TIX API
* Added getAugmentationPrereq() Singularity function (by havocmayhem)
* Added hackAnalyzePercent() and hackAnalyzeThreads() Netscript functions
* Stock Market, Travel, and Corporation main menu links are now properly styled
* Many pop-up/dialog boxes now support the 'Enter' and 'Esc' hotkeys. If you find a pop-up/dialog box that doesnt support this, let me know specifically which one ('Enter' for the default option, 'Esc' for cancelling and closing the pop-up box)
* Added "brace_style = preserve_inline" configuration to Script Editor Beautifier
* ServerProfiler.exe can now be purchased from the Dark Web
* Added an option to copy save data to clipboard
* Added total multiplier information on the "Augmentations" page
* Bug Fix: gymWorkout() Singularity function should now work properly with Millenium Fitness Gym
* Began migrating gameplay information to the ReadTheDocs documentation
`
* Stock Market Changes:
** Each stock now has a maximum number of shares you can purchase (both Long and Short positions combined)
** Added getStockMaxShares() Netscript function to the TIX API
** The cost of 4S Market Data TIX API Access increased from $20b to $25b
* Job Changes:
** You can now hold multiple jobs at once. This means you no longer lose reputation when leaving a company
** Because of this change, the getCharacterInformation() Netscript function returns a slightly different value
* Script Editor Changes:
** Added new script editor: CodeMirror. You can choose between the old editor (Ace) or CodeMirror
** Navigation keyboard shortcuts no longer work if the script editor is focused
* Trying to programmatically run a script (run(), exec()) with a 'threads' argument of 0 will now cause the function to return false without running the script
* Home Computer RAM is now capped at 2 ^ 30 GB (1073741824 GB)
* The maximum amount, maximum RAM, and cost of purchasing servers can now vary between different BitNodes (new BitNode multipliers)
* Pop-up dialog boxes are a little bit bigger
* Bug Fix: When importing scripts, "./" will now be properly ignored (e.g. import { foo } from "./lib.script" )
`
}

@ -13,7 +13,7 @@ import { MaterialSizes } from "./MaterialSizes";
import { Product } from "./Product";
import { ResearchMap } from "./ResearchMap";
import { BitNodeMultipliers } from "../BitNodeMultipliers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Factions } from "../Faction/Factions";
import { showLiterature } from "../Literature";

147
src/Crime/Crime.ts Normal file

@ -0,0 +1,147 @@
import { CONSTANTS } from "../Constants";
export interface IConstructorParams {
hacking_success_weight?: number;
strength_success_weight?: number;
defense_success_weight?: number;
dexterity_success_weight?: number;
agility_success_weight?: number;
charisma_success_weight?: number;
hacking_exp?: number;
strength_exp?: number;
defense_exp?: number;
dexterity_exp?: number;
agility_exp?: number;
charisma_exp?: number;
intelligence_exp?: number;
kills?: number;
}
interface IPlayer {
startCrime(crimeType: string,
hackExp: number,
strExp: number,
defExp: number,
dexExp: number,
agiExp: number,
chaExp: number,
money: number,
time: number,
singParams: any): void;
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
intelligence: number;
crime_success_mult: number;
}
export class Crime {
// Number representing the difficulty of the crime. Used for success chance calculations
difficulty: number = 0;
// Amount of karma lost for SUCCESSFULLY committing this crime
karma: number = 0;
// How many people die as a result of this crime
kills: number = 0;
// How much money is given by the
money: number = 0;
// Name of crime
name: string = "";
// Milliseconds it takes to attempt the crime
time: number = 0;
// Corresponding type in CONSTANTS. Contains a description for the crime activity
type: string = "";
// Weighting factors that determine how stats affect the success rate of this crime
hacking_success_weight: number = 0;
strength_success_weight: number = 0;
defense_success_weight: number = 0;
dexterity_success_weight: number = 0;
agility_success_weight: number = 0;
charisma_success_weight: number = 0;
// How much stat experience is granted by this crime
hacking_exp: number = 0;
strength_exp: number = 0;
defense_exp: number = 0;
dexterity_exp: number = 0;
agility_exp: number = 0;
charisma_exp: number = 0;
intelligence_exp: number = 0;
constructor(name: string = "",
type: string = "",
time: number = 0,
money: number = 0,
difficulty: number = 0,
karma: number = 0,
params: IConstructorParams={}) {
this.name = name;
this.type = type;
this.time = time;
this.money = money;
this.difficulty = difficulty;
this.karma = karma;
this.hacking_success_weight = params.hacking_success_weight ? params.hacking_success_weight : 0;
this.strength_success_weight = params.strength_success_weight ? params.strength_success_weight : 0;
this.defense_success_weight = params.defense_success_weight ? params.defense_success_weight : 0;
this.dexterity_success_weight = params.dexterity_success_weight ? params.dexterity_success_weight : 0;
this.agility_success_weight = params.agility_success_weight ? params.agility_success_weight : 0;
this.charisma_success_weight = params.charisma_success_weight ? params.charisma_success_weight : 0;
this.hacking_exp = params.hacking_exp ? params.hacking_exp : 0;
this.strength_exp = params.strength_exp ? params.strength_exp : 0;
this.defense_exp = params.defense_exp ? params.defense_exp : 0;
this.dexterity_exp = params.dexterity_exp ? params.dexterity_exp : 0;
this.agility_exp = params.agility_exp ? params.agility_exp : 0;
this.charisma_exp = params.charisma_exp ? params.charisma_exp : 0;
this.intelligence_exp = params.intelligence_exp ? params.intelligence_exp : 0;
this.kills = params.kills ? params.kills : 0;
}
commit(p: IPlayer, div: number=1, singParams: any=null): number {
if (div <= 0) { div = 1; }
p.startCrime(
this.type,
this.hacking_exp/div,
this.strength_exp/div,
this.defense_exp/div,
this.dexterity_exp/div,
this.agility_exp/div,
this.charisma_exp/div,
this.money/div,
this.time,
singParams
);
return this.time;
}
successRate(p: IPlayer): number {
let chance: number = (this.hacking_success_weight * p.hacking_skill +
this.strength_success_weight * p.strength +
this.defense_success_weight * p.defense +
this.dexterity_success_weight * p.dexterity +
this.agility_success_weight * p.agility +
this.charisma_success_weight * p.charisma +
CONSTANTS.IntelligenceCrimeWeight * p.intelligence);
chance /= CONSTANTS.MaxSkillLevel;
chance /= this.difficulty;
chance *= p.crime_success_mult;
return Math.min(chance, 1);
}
}

63
src/Crime/CrimeHelpers.js Normal file

@ -0,0 +1,63 @@
import { Crimes } from "./Crimes";
import { Player } from "../Player";
import { dialogBoxCreate } from "../../utils/DialogBox";
export function determineCrimeSuccess(type, moneyGained) {
var chance = 0;
var found = false;
for(const i in Crimes) {
const crime = Crimes[i];
if(crime.type == type) {
chance = crime.successRate(Player);
found = true;
break;
}
}
if(!found) {
console.log(crime);
dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer");
return;
}
if (Math.random() <= chance) {
//Success
Player.gainMoney(moneyGained);
return true;
} else {
//Failure
return false;
}
}
export function findCrime(roughName) {
if (roughName.includes("shoplift")) {
return Crimes.Shoplift;
} else if (roughName.includes("rob") && roughName.includes("store")) {
return Crimes.RobStore;
} else if (roughName.includes("mug")) {
return Crimes.Mug;
} else if (roughName.includes("larceny")) {
return Crimes.Larceny;
} else if (roughName.includes("drugs")) {
return Crimes.DealDrugs;
} else if (roughName.includes("bond") && roughName.includes("forge")) {
return Crimes.BondForgery;
} else if (roughName.includes("traffick") && roughName.includes("arms")) {
return Crimes.TraffickArms;
} else if (roughName.includes("homicide")) {
return Crimes.Homicide;
} else if (roughName.includes("grand") && roughName.includes("auto")) {
return Crimes.GrandTheftAuto;
} else if (roughName.includes("kidnap")) {
return Crimes.Kidnap;
} else if (roughName.includes("assassinate")) {
return Crimes.Assassination;
} else if (roughName.includes("heist")) {
return Crimes.Heist;
}
return null;
}

163
src/Crime/Crimes.ts Normal file

@ -0,0 +1,163 @@
import { Crime } from "./Crime";
import { CONSTANTS } from "../Constants";
import { IMap } from "../types";
export const Crimes: IMap<Crime> = {
Shoplift: new Crime("Shoplift", CONSTANTS.CrimeShoplift, 2e3, 15e3, 1/20, 0.1, {
dexterity_success_weight: 1,
agility_success_weight: 1,
dexterity_exp: 2,
agility_exp: 2,
}),
RobStore: new Crime("Rob Store", CONSTANTS.CrimeRobStore, 60e3, 400e3, 1/5, 0.5, {
hacking_exp: 30,
dexterity_exp: 45,
agility_exp: 45,
hacking_success_weight: 0.5 ,
dexterity_success_weight: 2,
agility_success_weight: 1,
intelligence_exp: 0.25 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Mug: new Crime("Mug", CONSTANTS.CrimeMug, 4e3, 36e3, 1/5, 0.25, {
strength_exp: 3,
defense_exp: 3,
dexterity_exp: 3,
agility_exp: 3,
strength_success_weight: 1.5,
defense_success_weight: 0.5,
dexterity_success_weight: 1.5,
agility_success_weight: 0.5,
}),
Larceny: new Crime("Larceny", CONSTANTS.CrimeLarceny, 90e3, 800e3, 1/3, 1.5, {
hacking_exp: 45,
dexterity_exp: 60,
agility_exp: 60,
hacking_success_weight: 0.5,
dexterity_success_weight: 1,
agility_success_weight: 1,
intelligence_exp: 0.5 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
DealDrugs: new Crime("Deal Drugs", CONSTANTS.CrimeDrugs, 10e3, 120e3, 1, 0.5, {
dexterity_exp: 5,
agility_exp: 5,
charisma_exp: 10,
charisma_success_weight: 3,
dexterity_success_weight: 2,
agility_success_weight: 1,
}),
BondForgery: new Crime("Bond Forgery", CONSTANTS.CrimeBondForgery, 300e3, 4.5e6, 1/2, 0.1, {
hacking_exp: 100,
dexterity_exp: 150,
charisma_exp: 15,
hacking_success_weight: 0.05,
dexterity_success_weight: 1.25,
intelligence_exp: 2 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
TraffickArms: new Crime("Traffick Arms", CONSTANTS.CrimeTraffickArms, 40e3, 600e3, 2, 1, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
agility_exp: 20,
charisma_exp: 40,
charisma_success_weight: 1,
strength_success_weight: 1,
defense_success_weight: 1,
dexterity_success_weight: 1,
agility_success_weight: 1,
}),
Homicide: new Crime("Homicide", CONSTANTS.CrimeHomicide, 3e3, 45e3, 1, 3, {
strength_exp: 2,
defense_exp: 2,
dexterity_exp: 2,
agility_exp: 2,
strength_success_weight: 2,
defense_success_weight: 2,
dexterity_success_weight: 0.5,
agility_success_weight: 0.5,
kills: 1,
}),
GrandTheftAuto: new Crime("Grand Theft Auto", CONSTANTS.CrimeGrandTheftAuto, 80e3, 1.6e6, 8, 5, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
agility_exp: 80,
charisma_exp: 40,
hacking_success_weight: 1,
strength_success_weight: 1,
dexterity_success_weight: 4,
agility_success_weight: 2,
charisma_success_weight: 2,
intelligence_exp: CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Kidnap: new Crime("Kidnap", CONSTANTS.CrimeKidnap, 120e3, 3.6e6, 5, 6, {
strength_exp: 80,
defense_exp: 80,
dexterity_exp: 80,
agility_exp: 80,
charisma_exp: 80,
charisma_success_weight: 1,
strength_success_weight: 1,
dexterity_success_weight: 1,
agility_success_weight: 1,
intelligence_exp: 2 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Assassination: new Crime("Assassination", CONSTANTS.CrimeAssassination, 300e3, 12e6, 8, 10, {
strength_exp: 300,
defense_exp: 300,
dexterity_exp: 300,
agility_exp: 300,
strength_success_weight: 1,
dexterity_success_weight: 2,
agility_success_weight: 1,
intelligence_exp: 5 * CONSTANTS.IntelligenceCrimeBaseExpGain,
kills: 1,
}),
Heist: new Crime("Heist", CONSTANTS.CrimeHeist, 600e3, 120e6, 18, 15, {
hacking_exp: 450,
strength_exp: 450,
defense_exp: 450,
dexterity_exp: 450,
agility_exp: 450,
charisma_exp: 450,
hacking_success_weight: 1,
strength_success_weight: 1,
defense_success_weight: 1,
dexterity_success_weight: 1,
agility_success_weight: 1,
charisma_success_weight: 1,
intelligence_exp: 10 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
};

@ -1,275 +0,0 @@
import {CONSTANTS} from "./Constants";
import {Player} from "./Player";
import {dialogBoxCreate} from "../utils/DialogBox";
function Crime(name, type, time, money, difficulty, karma, params) {
this.name = name;
this.type = type;
this.time = time;
this.money = money;
this.difficulty = difficulty;
this.karma = karma;
this.hacking_success_weight = params.hacking_success_weight ? params.hacking_success_weight : 0;
this.strength_success_weight = params.strength_success_weight ? params.strength_success_weight : 0;
this.defense_success_weight = params.defense_success_weight ? params.defense_success_weight : 0;
this.dexterity_success_weight = params.dexterity_success_weight ? params.dexterity_success_weight : 0;
this.agility_success_weight = params.agility_success_weight ? params.agility_success_weight : 0;
this.charisma_success_weight = params.charisma_success_weight ? params.charisma_success_weight : 0;
this.hacking_exp = params.hacking_exp ? params.hacking_exp : 0;
this.strength_exp = params.strength_exp ? params.strength_exp : 0;
this.defense_exp = params.defense_exp ? params.defense_exp : 0;
this.dexterity_exp = params.dexterity_exp ? params.dexterity_exp : 0;
this.agility_exp = params.agility_exp ? params.agility_exp : 0;
this.charisma_exp = params.charisma_exp ? params.charisma_exp : 0;
this.intelligence_exp = params.intelligence_exp ? params.intelligence_exp : 0;
this.kills = params.kills ? params.kills : 0;
}
Crime.prototype.commit = function(div=1, singParams=null) {
if (div <= 0) {div = 1;}
Player.crimeType = this.type;
Player.startCrime(
this.hacking_exp/div,
this.strength_exp/div,
this.defense_exp/div,
this.dexterity_exp/div,
this.agility_exp/div,
this.charisma_exp/div,
this.money/div, this.time, singParams);
return this.time;
}
Crime.prototype.successRate = function() {
var chance = (this.hacking_success_weight * Player.hacking_skill +
this.strength_success_weight * Player.strength +
this.defense_success_weight * Player.defense +
this.dexterity_success_weight * Player.dexterity +
this.agility_success_weight * Player.agility +
this.charisma_success_weight * Player.charisma +
CONSTANTS.IntelligenceCrimeWeight * Player.intelligence);
chance /= CONSTANTS.MaxSkillLevel;
chance /= this.difficulty;
chance *= Player.crime_success_mult;
return Math.min(chance, 1);
}
const Crimes = {
Shoplift: new Crime("Shoplift", CONSTANTS.CrimeShoplift, 2e3, 15e3, 1/20, 0.1, {
dexterity_success_weight: 1,
agility_success_weight: 1,
dexterity_exp: 2,
agility_exp: 2,
}),
RobStore: new Crime("Rob Store", CONSTANTS.CrimeRobStore, 60e3, 400e3, 1/5, 0.5, {
hacking_exp: 30,
dexterity_exp: 45,
agility_exp: 45,
hacking_success_weight: 0.5 ,
dexterity_success_weight: 2,
agility_success_weight: 1,
intelligence_exp: 0.25 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Mug: new Crime("Mug", CONSTANTS.CrimeMug, 4e3, 36e3, 1/5, 0.25, {
strength_exp: 3,
defense_exp: 3,
dexterity_exp: 3,
agility_exp: 3,
strength_success_weight: 1.5,
defense_success_weight: 0.5,
dexterity_success_weight: 1.5,
agility_success_weight: 0.5,
}),
Larceny: new Crime("Larceny", CONSTANTS.CrimeLarceny, 90e3, 800e3, 1/3, 1.5, {
hacking_exp: 45,
dexterity_exp: 60,
agility_exp: 60,
hacking_success_weight: 0.5,
dexterity_success_weight: 1,
agility_success_weight: 1,
intelligence_exp: 0.5 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
DealDrugs: new Crime("Deal Drugs", CONSTANTS.CrimeDrugs, 10e3, 120e3, 1, 0.5, {
dexterity_exp: 5,
agility_exp: 5,
charisma_exp: 10,
charisma_success_weight: 3,
dexterity_success_weight: 2,
agility_success_weight: 1,
}),
BondForgery: new Crime("Bond Forgery", CONSTANTS.CrimeBondForgery, 300e3, 4.5e6, 1/2, 0.1, {
hacking_exp: 100,
dexterity_exp: 150,
charisma_exp: 15,
hacking_success_weight: 0.05,
dexterity_success_weight: 1.25,
intelligence_exp: 2 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
TraffickArms: new Crime("Traffick Arms", CONSTANTS.CrimeTraffickArms, 40e3, 600e3, 2, 1, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
agility_exp: 20,
charisma_exp: 40,
charisma_success_weight: 1,
strength_success_weight: 1,
defense_success_weight: 1,
dexterity_success_weight: 1,
agility_success_weight: 1,
}),
Homicide: new Crime("Homicide", CONSTANTS.CrimeHomicide, 3e3, 45e3, 1, 3, {
strength_exp: 2,
defense_exp: 2,
dexterity_exp: 2,
agility_exp: 2,
strength_success_weight: 2,
defense_success_weight: 2,
dexterity_success_weight: 0.5,
agility_success_weight: 0.5,
kills: 1,
}),
GrandTheftAuto: new Crime("Grand Theft Auto", CONSTANTS.CrimeGrandTheftAuto, 80e3, 1.6e6, 8, 5, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
agility_exp: 80,
charisma_exp: 40,
hacking_success_weight: 1,
strength_success_weight: 1,
dexterity_success_weight: 4,
agility_success_weight: 2,
charisma_success_weight: 2,
intelligence_exp: CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Kidnap: new Crime("Kidnap", CONSTANTS.CrimeKidnap, 120e3, 3.6e6, 5, 6, {
strength_exp: 80,
defense_exp: 80,
dexterity_exp: 80,
agility_exp: 80,
charisma_exp: 80,
charisma_success_weight: 1,
strength_success_weight: 1,
dexterity_success_weight: 1,
agility_success_weight: 1,
intelligence_exp: 2 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Assassination: new Crime("Assassination", CONSTANTS.CrimeAssassination, 300e3, 12e6, 8, 10, {
strength_exp: 300,
defense_exp: 300,
dexterity_exp: 300,
agility_exp: 300,
strength_success_weight: 1,
dexterity_success_weight: 2,
agility_success_weight: 1,
intelligence_exp: 5 * CONSTANTS.IntelligenceCrimeBaseExpGain,
kills: 1,
}),
Heist: new Crime("Heist", CONSTANTS.CrimeHeist, 600e3, 120e6, 18, 15, {
hacking_exp: 450,
strength_exp: 450,
defense_exp: 450,
dexterity_exp: 450,
agility_exp: 450,
charisma_exp: 450,
hacking_success_weight: 1,
strength_success_weight: 1,
defense_success_weight: 1,
dexterity_success_weight: 1,
agility_success_weight: 1,
charisma_success_weight: 1,
intelligence_exp: 10 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
};
function determineCrimeSuccess(type, moneyGained) {
var chance = 0;
var found = false;
for(const i in Crimes) {
const crime = Crimes[i];
if(crime.type == type) {
chance = crime.successRate();
found = true;
break;
}
}
if(!found) {
console.log(crime);
dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer");
return;
}
if (Math.random() <= chance) {
//Success
Player.gainMoney(moneyGained);
return true;
} else {
//Failure
return false;
}
}
function findCrime(roughName) {
if (roughName.includes("shoplift")) {
return Crimes.Shoplift;
} else if (roughName.includes("rob") && roughName.includes("store")) {
return Crimes.RobStore;
} else if (roughName.includes("mug")) {
return Crimes.Mug;
} else if (roughName.includes("larceny")) {
return Crimes.Larceny;
} else if (roughName.includes("drugs")) {
return Crimes.DealDrugs;
} else if (roughName.includes("bond") && roughName.includes("forge")) {
return Crimes.BondForgery;
} else if (roughName.includes("traffick") && roughName.includes("arms")) {
return Crimes.TraffickArms;
} else if (roughName.includes("homicide")) {
return Crimes.Homicide;
} else if (roughName.includes("grand") && roughName.includes("auto")) {
return Crimes.GrandTheftAuto;
} else if (roughName.includes("kidnap")) {
return Crimes.Kidnap;
} else if (roughName.includes("assassinate")) {
return Crimes.Assassination;
} else if (roughName.includes("heist")) {
return Crimes.Heist;
}
return null;
}
export {determineCrimeSuccess,findCrime,Crimes};

@ -1,4 +1,4 @@
import { AugmentationNames } from "./Augmentations";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { generateRandomContract } from "./CodingContractGenerator";
import { Programs } from "./Programs/Programs";
import { Factions } from "./Faction/Factions";
@ -6,13 +6,17 @@ import { Player } from "./Player";
import { AllServers } from "./Server";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket,
SymbolToStockMap } from "./StockMarket";
import { Stock } from "./Stock";
SymbolToStockMap } from "./StockMarket/StockMarket";
import { Stock } from "./StockMarket/Stock";
import { Terminal } from "./Terminal";
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";
const devMenuContainerId = "dev-menu-container";
@ -222,7 +226,7 @@ export function createDevMenu() {
innerText: "Receive Invite to Faction",
});
// Augmentations / Source Files
// Augmentations
const augmentationsHeader = createElement("h2", {innerText: "Augmentations"});
const augmentationsDropdown = createElement("select", {
@ -242,6 +246,32 @@ export function createDevMenu() {
innerText: "Queue Augmentation",
})
// Source Files
const sourceFilesHeader = createElement("h2", { innerText: "Source-Files" });
const removeSourceFileDropdown = createElement("select", {
class: "dropdown",
margin: "5px",
});
for (let i = 0; i < 24; ++i) {
removeSourceFileDropdown.add(createOptionElement(String(i)));
}
const removeSourceFileButton = createElement("button", {
class: "std-button",
clickListener: () => {
const numToRemove = parseInt(getSelectText(removeSourceFileDropdown));
for (let i = 0; i < Player.sourceFiles.length; ++i) {
if (Player.sourceFiles[i].n === numToRemove) {
Player.sourceFiles.splice(i, 1);
hackWorldDaemon(Player.bitNodeN, true);
return;
}
}
},
innerText: "Remove Source File and Trigger Bitflume",
});
// Programs
const programsHeader = createElement("h2", {innerText: "Programs"});
@ -508,6 +538,9 @@ export function createDevMenu() {
devMenuContainer.appendChild(augmentationsHeader);
devMenuContainer.appendChild(augmentationsDropdown);
devMenuContainer.appendChild(augmentationsQueueButton);
devMenuContainer.appendChild(sourceFilesHeader);
devMenuContainer.appendChild(removeSourceFileDropdown);
devMenuContainer.appendChild(removeSourceFileButton);
devMenuContainer.appendChild(programsHeader);
devMenuContainer.appendChild(programsAddDropdown);
devMenuContainer.appendChild(programsAddButton);

@ -1,6 +1,7 @@
import { Augmentations, AugmentationNames,
PlayerOwnedAugmentation } from "../Augmentations";
import { BitNodeMultipliers } from "../BitNodeMultipliers";
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";
@ -9,8 +10,8 @@ import { FactionInfos } from "./FactionInfo";
import { Locations} from "../Location";
import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player";
import { PurchaseAugmentationsOrderSetting } from "../SettingEnums";
import { Settings } from "../Settings";
import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings";
import {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat";
@ -480,7 +481,7 @@ function createFactionAugmentationDisplayElements(augmentationsList, augs, facti
}
var item = createElement("li");
var span = createElement("span", {display:"inline-block"});
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",

@ -1,4 +1,4 @@
import { BitNodeMultipliers } from "./BitNodeMultipliers";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { Player } from "./Player";
import { Server } from "./Server";

@ -1,6 +1,6 @@
import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {CONSTANTS} from "./Constants";
import {Engine} from "./engine";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import {iTutorialSteps, iTutorialNextStep,
ITutorial} from "./InteractiveTutorial";
import {Player} from "./Player";

@ -1,12 +1,12 @@
import {BitNodeMultipliers} from "./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";
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
*

@ -1,10 +1,12 @@
import {Engine} from "./engine";
import {Player} from "./Player";
import {Settings} from "./Settings";
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";
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 = [
@ -472,7 +474,19 @@ function iTutorialEnd() {
}
console.log("Ending interactive tutorial");
// Initialize references to main menu links
// We have to call initializeMainMenuLinks() again because the Interactive Tutorial
// re-creates Main menu links with clearEventListeners()
if (!initializeMainMenuLinks()) {
const errorMsg = "Failed to initialize Main Menu Links. Please try refreshing the page. " +
"If that doesn't work, report the issue to the developer";
exceptionAlert(new Error(errorMsg));
console.error(errorMsg);
return;
}
Engine.init();
ITutorial.currStep = iTutorialSteps.End;
ITutorial.isRunning = false;
document.getElementById("interactive-tutorial-container").style.display = "none";

@ -5,16 +5,18 @@ import {getJobRequirementText} from "./Company/GetJobRequiremen
import * as posNames from "./Company/data/CompanyPositionNames";
import { Corporation } from "./Corporation/Corporation";
import {CONSTANTS} from "./Constants";
import {Crimes} from "./Crimes";
import { Crimes } from "./Crime/Crimes";
import {Engine} from "./engine";
import {beginInfiltration} from "./Infiltration";
import {hasBladeburnerSF} from "./NetscriptFunctions";
import {Locations} from "./Locations";
import {Player} from "./Player";
import {Server, AllServers, AddToAllServers} from "./Server";
import {purchaseServer,
purchaseRamForHomeComputer} from "./ServerPurchases";
import {Settings} from "./Settings";
import { getPurchaseServerCost,
purchaseServer,
purchaseRamForHomeComputer} from "./ServerPurchases";
import {Settings} from "./Settings/Settings";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import {SpecialServerNames, SpecialServerIps} from "./SpecialServerIps";
import {numeralWrapper} from "./ui/numeralFormat";
@ -121,6 +123,8 @@ function displayLocationContent() {
var nsaBladeburner = document.getElementById("location-nsa-bladeburner");
const vitalifeResleeve = document.getElementById("location-vitalife-resleeve");
var loc = Player.location;
returnToWorld.addEventListener("click", function() {
@ -188,16 +192,16 @@ function displayLocationContent() {
purchaseHomeRam.style.display = "none";
purchaseHomeCores.style.display = "none";
purchase2gb.innerHTML = "Purchase 2GB Server - $" + formatNumber(2*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase4gb.innerHTML = "Purchase 4GB Server - $" + formatNumber(4*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase8gb.innerHTML = "Purchase 8GB Server - $" + formatNumber(8*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase16gb.innerHTML = "Purchase 16GB Server - $" + formatNumber(16*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase32gb.innerHTML = "Purchase 32GB Server - $" + formatNumber(32*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase64gb.innerHTML = "Purchase 64GB Server - $" + formatNumber(64*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase128gb.innerHTML = "Purchase 128GB Server - $" + formatNumber(128*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase256gb.innerHTML = "Purchase 256GB Server - $" + formatNumber(256*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase512gb.innerHTML = "Purchase 512GB Server - $" + formatNumber(512*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase1tb.innerHTML = "Purchase 1TB Server - $" + formatNumber(1024*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase2gb.innerHTML = "Purchase 2GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(2));
purchase4gb.innerHTML = "Purchase 4GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(4));
purchase8gb.innerHTML = "Purchase 8GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(8));
purchase16gb.innerHTML = "Purchase 16GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(16));
purchase32gb.innerHTML = "Purchase 32GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(32));
purchase64gb.innerHTML = "Purchase 64GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(64));
purchase128gb.innerHTML = "Purchase 128GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(128));
purchase256gb.innerHTML = "Purchase 256GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(256));
purchase512gb.innerHTML = "Purchase 512GB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(512));
purchase1tb.innerHTML = "Purchase 1TB Server - " + numeralWrapper.formatMoney(getPurchaseServerCost(1024));
if (!SpecialServerIps.hasOwnProperty("Darkweb Server")) {
purchaseTor.classList.add("a-link-button");
purchaseTor.classList.remove("a-link-button-bought");
@ -237,10 +241,11 @@ function displayLocationContent() {
cityHallCreateCorporation.style.display = "none";
nsaBladeburner.style.display = "none";
vitalifeResleeve.style.display = "none";
//Check if the player is employed at this Location. If he is, display the "Work" button,
//update the job title, etc.
if (loc != "" && loc === Player.companyName) {
if (loc != "" && Object.keys(Player.jobs).includes(loc)) {
let company = Companies[loc];
jobTitle.style.display = "block";
@ -249,7 +254,7 @@ function displayLocationContent() {
locationTxtDiv1.style.display = "block";
locationTxtDiv2.style.display = "block";
locationTxtDiv3.style.display = "block";
jobTitle.innerHTML = "Job Title: " + Player.companyPosition;
jobTitle.innerHTML = `Job Title: ${Player.jobs[loc]}`;
let repGain = company.getFavorGain();
if (repGain.length != 2) {repGain = 0;}
repGain = repGain[0];
@ -264,16 +269,16 @@ function displayLocationContent() {
"favor you gain depends on how much reputation you have with the company</span>";
work.style.display = "block";
let currPos = CompanyPositions[Player.companyPosition];
let currPos = CompanyPositions[Player.jobs[loc]];
if (currPos == null) {
throw new Error("Player's companyPosition property has an invalid value");
}
work.addEventListener("click", function() {
if (currPos.isPartTimeJob() || currPos.isSoftwareConsultantJob() || currPos.isBusinessConsultantJob()) {
Player.startWorkPartTime();
Player.startWorkPartTime(loc);
} else {
Player.startWork();
Player.startWork(loc);
}
return false;
});
@ -761,6 +766,10 @@ function displayLocationContent() {
businessJob.style.display = "block";
setInfiltrateButton(infiltrate, Locations.NewTokyoVitaLife,
605, 22, 100, 3.5);
if (Player.bitNodeN === 10 || SourceFileFlags[10]) {
vitalifeResleeve.style.display = "block";
}
break;
case Locations.NewTokyoGlobalPharmaceuticals:
@ -979,18 +988,18 @@ function displayLocationContent() {
case Locations.NewTokyoSlums:
case Locations.IshimaSlums:
case Locations.VolhavenSlums:
var shopliftChance = Crimes.Shoplift.successRate();
var robStoreChance = Crimes.RobStore.successRate();
var mugChance = Crimes.Mug.successRate();
var larcenyChance = Crimes.Larceny.successRate();
var drugsChance = Crimes.DealDrugs.successRate();
var bondChance = Crimes.BondForgery.successRate();
var armsChance = Crimes.TraffickArms.successRate();
var homicideChance = Crimes.Homicide.successRate();
var gtaChance = Crimes.GrandTheftAuto.successRate();
var kidnapChance = Crimes.Kidnap.successRate();
var assassinateChance = Crimes.Assassination.successRate();
var heistChance = Crimes.Heist.successRate();
var shopliftChance = Crimes.Shoplift.successRate(Player);
var robStoreChance = Crimes.RobStore.successRate(Player);
var mugChance = Crimes.Mug.successRate(Player);
var larcenyChance = Crimes.Larceny.successRate(Player);
var drugsChance = Crimes.DealDrugs.successRate(Player);
var bondChance = Crimes.BondForgery.successRate(Player);
var armsChance = Crimes.TraffickArms.successRate(Player);
var homicideChance = Crimes.Homicide.successRate(Player);
var gtaChance = Crimes.GrandTheftAuto.successRate(Player);
var kidnapChance = Crimes.Kidnap.successRate(Player);
var assassinateChance = Crimes.Assassination.successRate(Player);
var heistChance = Crimes.Heist.successRate(Player);
slumsDescText.style.display = "block";
slumsShoplift.style.display = "block";
@ -1043,8 +1052,8 @@ function displayLocationContent() {
// Make the "Apply to be Employee and Waiter" texts disappear if you already hold the job
// Includes part-time stuff
if (loc == Player.companyName) {
var currPos = Player.companyPosition;
if (Object.keys(Player.jobs).includes(loc)) {
var currPos = Player.jobs[loc];
if (currPos == "Employee") {
employeeJob.style.display = "none";
@ -1634,6 +1643,8 @@ function initLocationButtons() {
var nsaBladeburner = document.getElementById("location-nsa-bladeburner");
const vitalifeResleeve = document.getElementById("location-vitalife-resleeve");
var hospitalTreatment = document.getElementById("location-hospital-treatment");
softwareJob.addEventListener("click", function(e) {
@ -1716,61 +1727,61 @@ function initLocationButtons() {
purchase2gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(2, 2 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(2);
return false;
});
purchase4gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(4, 4 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(4);
return false;
});
purchase8gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(8, 8 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(8);
return false;
});
purchase16gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(16, 16 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(16);
return false;
});
purchase32gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(32, 32 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(32);
return false;
});
purchase64gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(64, 64 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(64);
return false;
});
purchase128gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(128, 128 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(128);
return false;
});
purchase256gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(256, 256 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(256);
return false;
});
purchase512gb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(512, 512 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(512);
return false;
});
purchase1tb.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
purchaseServerBoxCreate(1024, 1024 * CONSTANTS.BaseCostFor1GBOfRamServer);
purchaseServerBoxCreate(1024);
return false;
});
@ -1874,73 +1885,73 @@ function initLocationButtons() {
slumsShoplift.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Shoplift.commit();
Crimes.Shoplift.commit(Player);
return false;
});
slumsRobStore.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.RobStore.commit();
Crimes.RobStore.commit(Player);
return false;
});
slumsMug.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Mug.commit();
Crimes.Mug.commit(Player);
return false;
});
slumsLarceny.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Larceny.commit();
Crimes.Larceny.commit(Player);
return false;
});
slumsDealDrugs.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.DealDrugs.commit();
Crimes.DealDrugs.commit(Player);
return false;
});
slumsBondForgery.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.BondForgery.commit();
Crimes.BondForgery.commit(Player);
return false;
});
slumsTrafficArms.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.TraffickArms.commit();
Crimes.TraffickArms.commit(Player);
return false;
});
slumsHomicide.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Homicide.commit();
Crimes.Homicide.commit(Player);
return false;
});
slumsGta.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.GrandTheftAuto.commit();
Crimes.GrandTheftAuto.commit(Player);
return false;
});
slumsKidnap.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Kidnap.commit();
Crimes.Kidnap.commit(Player);
return false;
});
slumsAssassinate.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Assassination.commit();
Crimes.Assassination.commit(Player);
return false;
});
slumsHeist.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Crimes.Heist.commit();
Crimes.Heist.commit(Player);
return false;
});
@ -2041,6 +2052,10 @@ function initLocationButtons() {
}
});
vitalifeResleeve.addEventListener("click", function() {
Engine.loadResleevingContent();
});
hospitalTreatment.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
if (Player.hp < 0) {Player.hp = 0;}
@ -2250,13 +2265,19 @@ function travelBoxCreate(destCityName, cost) {
yesNoBoxCreate("Would you like to travel to " + destCityName + "? The trip will cost $" + formatNumber(cost, 2) + ".");
}
function purchaseServerBoxCreate(ram, cost) {
function purchaseServerBoxCreate(ram) {
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
dialogBoxCreate("Something went wrong when trying to purchase this server. Please contact developer");
return;
}
var yesBtn = yesNoTxtInpBoxGetYesButton();
var noBtn = yesNoTxtInpBoxGetNoButton();
yesBtn.innerHTML = "Purchase Server";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", function() {
purchaseServer(ram, cost);
purchaseServer(ram);
yesNoTxtInpBoxClose();
});
noBtn.addEventListener("click", function() {

@ -1,12 +1,12 @@
import { Augmentations,
Augmentation,
AugmentationNames } from "./Augmentations";
import { Augmentatation } from "./Augmentation/Augmentation";
import { Augmentations } from "./Augmentation/Augmentations";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { Programs } from "./Programs/Programs";
import { inMission } from "./Missions";
import { Player } from "./Player";
import { redPillFlag } from "./RedPill";
import { GetServerByHostname } from "./Server";
import { Settings } from "./Settings";
import { Settings } from "./Settings/Settings";
import { dialogBoxCreate,
dialogBoxOpened} from "../utils/DialogBox";
import {Reviver, Generic_toJSON,

@ -1,12 +1,12 @@
import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {CONSTANTS} from "./Constants";
import {Player} from "./Player";
import {Environment} from "./NetscriptEnvironment";
import {WorkerScript, addWorkerScript} from "./NetscriptWorker";
import {Server, getServer} from "./Server";
import {Settings} from "./Settings";
import {Script, findRunningScript,
RunningScript} from "./Script";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Player } from "./Player";
import { Environment } from "./NetscriptEnvironment";
import { WorkerScript, addWorkerScript} from "./NetscriptWorker";
import { Server, getServer} from "./Server";
import { Settings } from "./Settings/Settings";
import { Script, findRunningScript,
RunningScript } from "./Script";
import {parse, Node} from "../utils/acorn";
import {arrayToString} from "../utils/helpers/arrayToString";
@ -860,6 +860,7 @@ function runScriptFromScript(server, scriptname, args, workerScript, threads=1)
var script = server.scripts[i];
var ramUsage = script.ramUsage;
threads = Math.round(Number(threads)); //Convert to number and round
if (threads === 0) { return Promise.resolve(false); }
ramUsage = ramUsage * threads;
var ramAvailable = server.maxRam - server.ramUsed;

@ -2,11 +2,13 @@ var sprintf = require('sprintf-js').sprintf,
vsprintf = require('sprintf-js').vsprintf
import {updateActiveScriptsItems} from "./ActiveScriptsUI";
import {Augmentations, Augmentation,
augmentationExists, installAugmentations,
AugmentationNames} from "./Augmentations";
import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {determineCrimeSuccess, findCrime} from "./Crimes";
import { Augmentation } from "./Augmentation/Augmentation";
import { Augmentations } from "./Augmentation/Augmentations";
import { augmentationExists,
installAugmentations } from "./Augmentation/AugmentationHelpers";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { determineCrimeSuccess, findCrime } from "./Crime/CrimeHelpers";
import {Bladeburner} from "./Bladeburner";
import {Company} from "./Company/Company";
import {Companies, companyExists} from "./Company/Companies";
@ -38,14 +40,17 @@ import {Script, findRunningScript, RunningScript,
import {Server, getServer, AddToAllServers,
AllServers, processSingleServerGrowth,
GetServerByHostname, numCycleForGrowth} from "./Server";
import {Settings} from "./Settings";
import { getPurchaseServerCost,
getPurchaseServerLimit,
getPurchaseServerMaxRam } from "./ServerPurchases";
import {Settings} from "./Settings/Settings";
import {SpecialServerIps} from "./SpecialServerIps";
import {Stock} from "./Stock";
import {Stock} from "./StockMarket/Stock";
import {StockMarket, StockSymbols, SymbolToStockMap,
initStockMarket, initSymbolToStockMap, buyStock,
sellStock, updateStockPlayerPosition,
shortStock, sellShort, OrderTypes,
PositionTypes, placeOrder, cancelOrder} from "./StockMarket";
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
import {post} from "./ui/postToTerminal";
import {TextFile, getTextFile, createTextFile} from "./TextFile";
@ -232,24 +237,6 @@ function NetscriptFunctions(workerScript) {
return server.getContract(fn);
}
/**
* @param {number} ram The amount of server RAM to calculate cost of.
* @exception {Error} If the value passed in is not numeric, out of range, or too large of a value.
* @returns {number} The cost of
*/
const getPurchaseServerRamCostGuard = (ram) => {
const guardedRam = Math.round(ram);
if (isNaN(guardedRam) || !isPowerOfTwo(guardedRam)) {
throw Error("failed due to invalid ram argument. Must be numeric and a power of 2");
}
if (guardedRam > CONSTANTS.PurchasedServerMaxRam) {
throw Error("failed because specified RAM was too high. Maximum RAM on a purchased server is " + CONSTANTS.PurchasedServerMaxRam + "GB");
}
return guardedRam * CONSTANTS.BaseCostFor1GBOfRamServer;
};
return {
hacknet : {
numNodes : function() {
@ -417,7 +404,7 @@ function NetscriptFunctions(workerScript) {
// Check argument validity
const server = safeGetServer(ip, 'hackAnalyzeThreads');
if (isNaN(hackAmount)) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${hackAmount}. Must be numeric`);
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into hackAnalyzeThreads: ${hackAmount}. Must be numeric`);
}
if (hackAmount < 0 || hackAmount > server.moneyAvailable) {
@ -819,7 +806,7 @@ function NetscriptFunctions(workerScript) {
if (scriptname === undefined) {
throw makeRuntimeRejectMsg(workerScript, "run() call has incorrect number of arguments. Usage: run(scriptname, [numThreads], [arg1], [arg2]...)");
}
if (isNaN(threads) || threads < 1) {
if (isNaN(threads) || threads < 0) {
throw makeRuntimeRejectMsg(workerScript, "Invalid argument for thread count passed into run(). Must be numeric and greater than 0");
}
var argsForNewScript = [];
@ -841,7 +828,7 @@ function NetscriptFunctions(workerScript) {
if (scriptname === undefined || ip === undefined) {
throw makeRuntimeRejectMsg(workerScript, "exec() call has incorrect number of arguments. Usage: exec(scriptname, server, [numThreads], [arg1], [arg2]...)");
}
if (isNaN(threads) || threads < 1) {
if (isNaN(threads) || threads < 0) {
throw makeRuntimeRejectMsg(workerScript, "Invalid argument for thread count passed into exec(). Must be numeric and greater than 0");
}
var argsForNewScript = [];
@ -866,7 +853,7 @@ function NetscriptFunctions(workerScript) {
if (scriptname === undefined) {
throw makeRuntimeRejectMsg(workerScript, "spawn() call has incorrect number of arguments. Usage: spawn(scriptname, numThreads, [arg1], [arg2]...)");
}
if (isNaN(threads) || threads < 1) {
if (isNaN(threads) || threads < 0) {
throw makeRuntimeRejectMsg(workerScript, "Invalid argument for thread count passed into run(). Must be numeric and greater than 0");
}
var argsForNewScript = [];
@ -1537,6 +1524,22 @@ function NetscriptFunctions(workerScript) {
}
return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
},
getStockMaxShares : function(symbol) {
if (workerScript.checkingRam) {
return updateStaticRam("getStockMaxShares", CONSTANTS.ScriptGetStockRamCost);
}
updateDynamicRam("getStockMaxShares", CONSTANTS.ScriptGetStockRamCost);
if (!Player.hasTixApiAccess) {
throw makeRuntimeRejectMsg(workerScript, "You don't have TIX API Access! Cannot use getStockMaxShares()");
}
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw makeRuntimeRejectMsg(workerScript, "Invalid stock symbol passed into getStockMaxShares()");
}
return stock.maxShares;
},
buyStock : function(symbol, shares) {
if (workerScript.checkingRam) {
return updateStaticRam("buyStock", CONSTANTS.ScriptBuySellStockRamCost);
@ -1556,6 +1559,7 @@ function NetscriptFunctions(workerScript) {
shares = Math.round(shares);
if (shares === 0) {return 0;}
// Does player have enough money?
var totalPrice = stock.price * shares;
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
workerScript.scriptRef.log("Not enough money to purchase " + formatNumber(shares, 0) + " shares of " +
@ -1564,6 +1568,13 @@ function NetscriptFunctions(workerScript) {
return 0;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
workerScript.scriptRef.log(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ` +
`${stock.maxShares} shares.`);
return 0;
}
var origTotal = stock.playerShares * stock.playerAvgPx;
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
var newTotal = origTotal + totalPrice;
@ -1885,7 +1896,7 @@ function NetscriptFunctions(workerScript) {
}
updateDynamicRam("getPurchasedServerLimit", CONSTANTS.ScriptGetPurchasedServerLimit);
return CONSTANTS.PurchasedServerLimit;
return getPurchaseServerLimit();
},
getPurchasedServerMaxRam: function() {
if (workerScript.checkingRam) {
@ -1893,7 +1904,7 @@ function NetscriptFunctions(workerScript) {
}
updateDynamicRam("getPurchasedServerMaxRam", CONSTANTS.ScriptGetPurchasedServerMaxRam);
return CONSTANTS.PurchasedServerMaxRam;
return getPurchaseServerMaxRam();
},
getPurchasedServerCost: function(ram) {
if (workerScript.checkingRam) {
@ -1901,11 +1912,9 @@ function NetscriptFunctions(workerScript) {
}
updateDynamicRam("getPurchasedServerCost", CONSTANTS.ScriptGetPurchaseServerRamCost);
let cost = 0;
try {
cost = getPurchaseServerRamCostGuard(ram);
} catch (e) {
workerScript.scriptRef.log("ERROR: 'getPurchasedServerCost()' " + e.message);
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
workerScript.scriptRef.log("ERROR: 'getPurchasedServerCost()' failed due to an invalid 'ram' argument");
return Infinity;
}
@ -1919,26 +1928,23 @@ function NetscriptFunctions(workerScript) {
var hostnameStr = String(hostname);
hostnameStr = hostnameStr.replace(/\s+/g, '');
if (hostnameStr == "") {
workerScript.scriptRef.log("ERROR: Passed empty string for hostname argument of purchaseServer()");
workerScript.log("ERROR: Passed empty string for hostname argument of purchaseServer()");
return "";
}
if (Player.purchasedServers.length >= CONSTANTS.PurchasedServerLimit) {
workerScript.scriptRef.log("ERROR: You have reached the maximum limit of " + CONSTANTS.PurchasedServerLimit +
" servers. You cannot purchase any more.");
if (Player.purchasedServers.length >= getPurchaseServerLimit()) {
workerScript.log(`ERROR: You have reached the maximum limit of ${getPurchaseServerLimit()} servers. You cannot purchase any more.`);
return "";
}
let cost = 0;
try {
cost = getPurchaseServerRamCostGuard(ram);
} catch (e) {
workerScript.scriptRef.log("ERROR: 'purchaseServer()' " + e.message);
return "";
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
workerScript.log("ERROR: 'purchaseServer()' failed due to an invalid 'ram' argument");
return Infinity;
}
if (Player.money.lt(cost)) {
workerScript.scriptRef.log("ERROR: Not enough money to purchase server. Need $" + formatNumber(cost, 2));
workerScript.log("ERROR: Not enough money to purchase server. Need $" + formatNumber(cost, 2));
return "";
}
var newServ = new Server({
@ -2878,16 +2884,12 @@ function NetscriptFunctions(workerScript) {
}
}
var companyPositionTitle = "";
if (CompanyPositions[Player.companyPosition] instanceof CompanyPosition) {
companyPositionTitle = Player.companyPosition;
}
return {
bitnode: Player.bitNodeN,
city: Player.city,
company: Player.companyName,
factions: Player.factions.slice(),
jobTitle: companyPositionTitle,
jobs: Object.keys(Player.jobs),
jobTitles: Object.values(Player.jobs),
mult: {
agility: Player.agility_mult,
agilityExp: Player.agility_exp_mult,
@ -2968,16 +2970,20 @@ function NetscriptFunctions(workerScript) {
}
}
const cost = Player.getUpgradeHomeRamCost();
// Check if we're at max RAM
const homeComputer = Player.getHomeComputer();
if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {
workerScript.log(`ERROR: upgradeHomeRam() failed because your home computer is at max RAM`);
return false;
}
const cost = Player.getUpgradeHomeRamCost();
if (Player.money.lt(cost)) {
workerScript.scriptRef.log("ERROR: upgradeHomeRam() failed because you don't have enough money");
return false;
}
var homeComputer = Player.getHomeComputer();
homeComputer.maxRam *= 2;
Player.loseMoney(cost);
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
@ -3002,7 +3008,7 @@ function NetscriptFunctions(workerScript) {
return Player.getUpgradeHomeRamCost();
},
workForCompany : function() {
workForCompany : function(companyName) {
var ramCost = CONSTANTS.ScriptSingularityFn2RamCost;
if (Player.bitNodeN !== 4) {ramCost *= CONSTANTS.ScriptSingularityFnRamMult;}
if (workerScript.checkingRam) {
@ -3016,13 +3022,33 @@ function NetscriptFunctions(workerScript) {
}
}
if (inMission) {
workerScript.scriptRef.log("ERROR: workForCompany() failed because you are in the middle of a mission.");
return;
// Sanitize input
if (companyName == null) {
companyName = Player.companyName;
}
const companyPosition = CompanyPositions[Player.companyPosition];
if (Player.companyPosition === "" || !(companyPosition instanceof CompanyPosition)) {
// Make sure its a valid company
if (companyName == null || companyName === "" || !(Companies[companyName] instanceof Company)) {
workerScript.scriptRef.log(`ERROR: workForCompany() failed because of an invalid company specified: ${companyName}`);
return false;
}
// Make sure player is actually employed at the comapny
if (!Object.keys(Player.jobs).includes(companyName)) {
workerScript.scriptRef.log(`ERROR: workForCompany() failed because you do not have a job at ${companyName}`);
return false;
}
// Cant work while in a mission
if (inMission) {
workerScript.scriptRef.log("ERROR: workForCompany() failed because you are in the middle of a mission.");
return false;
}
// Check to make sure company position data is valid
const companyPositionName = Player.jobs[companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (companyPositionName === "" || !(companyPosition instanceof CompanyPosition)) {
workerScript.scriptRef.log("ERROR: workForCompany() failed because you do not have a job");
return false;
}
@ -3035,12 +3061,12 @@ function NetscriptFunctions(workerScript) {
}
if (companyPosition.isPartTimeJob()) {
Player.startWorkPartTime();
Player.startWorkPartTime(companyName);
} else {
Player.startWork();
Player.startWork(companyName);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.workForCompany == null) {
workerScript.log(`Began working at ${Player.companyName} as a ${Player.companyPosition}`);
workerScript.log(`Began working at ${Player.companyName} as a ${companyPositionName}`);
}
return true;
},
@ -3116,7 +3142,7 @@ function NetscriptFunctions(workerScript) {
}
if (res) {
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.applyToCompany == null) {
workerScript.log(`You were offered a new job at ${companyName} as a ${Player.companyPosition}`);
workerScript.log(`You were offered a new job at ${companyName} as a ${Player.jobs[companyName]}`);
}
} else {
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.applyToCompany == null) {
@ -3554,7 +3580,7 @@ function NetscriptFunctions(workerScript) {
if(workerScript.disableLogs.ALL == null && workerScript.disableLogs.commitCrime == null) {
workerScript.scriptRef.log("Attempting to commit crime: "+crime.name+"...");
}
return crime.commit(1, {workerscript: workerScript});
return crime.commit(Player, 1, {workerscript: workerScript});
},
getCrimeChance : function(crimeRoughName) {
var ramCost = CONSTANTS.ScriptSingularityFn3RamCost;

@ -1,4 +1,4 @@
import {Settings} from "./Settings";
import {Settings} from "./Settings/Settings";
function NetscriptPort() {
this.data = [];

@ -12,7 +12,7 @@ import {NetscriptFunctions} from "./NetscriptFunctions";
import {executeJSScript} from "./NetscriptJSEvaluator";
import {NetscriptPort} from "./NetscriptPort";
import {AllServers} from "./Server";
import {Settings} from "./Settings";
import {Settings} from "./Settings/Settings";
import {generate} from 'escodegen';
@ -347,6 +347,9 @@ function processNetscript1Imports(code, workerScript) {
ImportDeclaration: (node) => {
hasImports = true;
let scriptName = node.source.value;
if (scriptName.startsWith("./")) {
scriptName = scriptName.slice(2);
}
let script = getScript(scriptName);
if (script == null) {
throw new Error("'Import' failed due to invalid script: " + scriptName);
@ -496,19 +499,6 @@ function runScriptsLoop() {
} else {
p = startNetscript1Script(workerScripts[i]);
if (!(p instanceof Promise)) {continue;}
/*
try {
var ast = parse(workerScripts[i].code, {sourceType:"module"});
//console.log(ast);
} catch (e) {
console.log("Error parsing script: " + workerScripts[i].name);
dialogBoxCreate("Syntax ERROR in " + workerScripts[i].name + ":<br>" + e);
workerScripts[i].env.stopFlag = true;
continue;
}
workerScripts[i].running = true;
p = evaluate(ast, workerScripts[i]);
*/
}
//Once the code finishes (either resolved or rejected, doesnt matter), set its
@ -521,7 +511,7 @@ function runScriptsLoop() {
}).catch(function(w) {
if (w instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.log("ERROR: Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
return;
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
//Script ends with a return statement

@ -0,0 +1,74 @@
// Interface for an object that represents the player (PlayerObject)
// Used because at the time of implementation, the PlayerObject
// cant be converted to TypeScript.
//
// Only contains the needed properties for Sleeve implementation
import { Resleeve } from "./Resleeving/Resleeve";
import { Sleeve } from "./Sleeve/Sleeve";
import { IMap } from "../types";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
export interface IPlayer {
// Class members
augmentations: IPlayerOwnedAugmentation[];
bladeburner: any;
companyName: string;
corporation: any;
factions: string[];
hasWseAccount: boolean;
jobs: IMap<string>;
money: any;
queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
sleeves: Sleeve[];
sourceFiles: IPlayerOwnedSourceFile[];
// Stats
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
intelligence: number;
// Experience
hacking_exp: number;
strength_exp: number;
defense_exp: number;
dexterity_exp: number;
agility_exp: number;
charisma_exp: number;
// Multipliers
crime_success_mult: number;
// Methods
canAfford(cost: number): boolean;
gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void;
gainDefenseExp(exp: number): void;
gainDexterityExp(exp: number): void;
gainAgilityExp(exp: number): void;
gainCharismaExp(exp: number): void;
gainMoney(money: number): void;
hasCorporation(): boolean;
inBladeburner(): boolean;
inGang(): boolean;
loseMoney(money: number): void;
reapplyAllAugmentations(resetMultipliers: boolean): void;
reapplyAllSourceFiles(): void;
startCrime(crimeType: string,
hackExp: number,
strExp: number,
defExp: number,
dexExp: number,
agiExp: number,
chaExp: number,
money: number,
time: number,
singParams: any): void;
}

@ -1,63 +1,11 @@
// Base class representing a person-like object
import { BitNodeMultipliers } from "../BitNodeMultipliers";
import { Augmentation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Cities } from "../Locations/Cities";
import { CONSTANTS } from "../Constants";
// Interface for an object that represents the player (PlayerObject)
// Used because at the time of implementation, the PlayerObject
// cant be converted to TypeScript.
//
// Only contains the needed properties for Sleeve implementation
export interface IPlayer {
companyName: string;
factions: string[];
money: any;
gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void;
gainDefenseExp(exp: number): void;
gainDexterityExp(exp: number): void;
gainAgilityExp(exp: number): void;
gainCharismaExp(exp: number): void;
gainMoney(money: number): void;
loseMoney(money: number): void;
}
// Interface for a Crime object
// Used because at the time of implementation, the Crime object has not been converted
// to Typescript
export interface ICrime {
name: string;
type: string;
time: number;
money: number;
difficulty: number;
karma: number;
hacking_success_weight: number;
strength_success_weight: number;
defense_success_weight: number;
dexterity_success_weight: number;
agility_success_weight: number;
charisma_success_weight: number;
hacking_exp: number;
strength_exp: number;
defense_exp: number;
dexterity_exp: number;
agility_exp: number;
charisma_exp: number;
intelligence_exp: number;
kills: number;
}
// Interface for Faction object
// Used because at the time of implementation, the Faction object has not been
// converted to TypeScript
export interface IFaction {
name: string;
playerReputation: number;
}
import { IMap } from "../types";
// Interface that defines a generic object used to track experience/money
// earnings for tasks
@ -87,95 +35,91 @@ export abstract class Person {
/**
* Stats
*/
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
hp: number;
max_hp: number;
hacking_skill: number = 1;
strength: number = 1;
defense: number = 1;
dexterity: number = 1;
agility: number = 1;
charisma: number = 1;
hp: number = 10;
max_hp: number = 10;
/**
* Experience
*/
hacking_exp: number = 0;
strength_exp: number = 0;
defense_exp: number = 0;
dexterity_exp: number = 0;
agility_exp: number = 0;
charisma_exp: number = 0;
intelligence_exp: number = 0;
/**
* Multipliers
*/
hacking_exp: number;
strength_exp: number;
defense_exp: number;
dexterity_exp: number;
agility_exp: number;
charisma_exp: number;
intelligence_exp: number;
hacking_mult: number = 1;
strength_mult: number = 1;
defense_mult: number = 1;
dexterity_mult: number = 1;
agility_mult: number = 1;
charisma_mult: number = 1;
hacking_mult: number;
strength_mult: number;
defense_mult: number;
dexterity_mult: number;
agility_mult: number;
charisma_mult: number;
hacking_exp_mult: number = 1;
strength_exp_mult: number = 1;
defense_exp_mult: number = 1;
dexterity_exp_mult: number = 1;
agility_exp_mult: number = 1;
charisma_exp_mult: number = 1;
hacking_exp_mult: number;
strength_exp_mult: number;
defense_exp_mult: number;
dexterity_exp_mult: number;
agility_exp_mult: number;
charisma_exp_mult: number;
hacking_chance_mult: number = 1;
hacking_speed_mult: number = 1;
hacking_money_mult: number = 1;
hacking_grow_mult: number = 1;
company_rep_mult: number;
faction_rep_mult: number;
company_rep_mult: number = 1;
faction_rep_mult: number = 1;
crime_money_mult: number;
crime_success_mult: number;
crime_money_mult: number = 1;
crime_success_mult: number = 1;
work_money_mult: number;
work_money_mult: number = 1;
hacknet_node_money_mult: number = 1;
hacknet_node_purchase_cost_mult: number = 1;
hacknet_node_ram_cost_mult: number = 1;
hacknet_node_core_cost_mult: number = 1;
hacknet_node_level_cost_mult: number = 1;
bladeburner_max_stamina_mult: number = 1;
bladeburner_stamina_gain_mult: number = 1;
bladeburner_analysis_mult: number = 1;
bladeburner_success_chance_mult : number = 1;
/**
* Augmentations
*/
augmentations: IPlayerOwnedAugmentation[] = [];
queuedAugmentations: IPlayerOwnedAugmentation[] = [];
/**
* City that the person is in
*/
city: string;
city: string = Cities.Sector12;
constructor() {
this.hacking_skill = 1;
this.strength = 1;
this.defense = 1;
this.dexterity = 1;
this.agility = 1;
this.charisma = 1;
this.hp = 10;
this.max_hp = 10;
constructor() {}
// Multipliers
this.hacking_exp = 0;
this.strength_exp = 0;
this.defense_exp = 0;
this.dexterity_exp = 0;
this.agility_exp = 0;
this.charisma_exp = 0;
this.intelligence_exp = 0;
this.hacking_mult = 1;
this.strength_mult = 1;
this.defense_mult = 1;
this.dexterity_mult = 1;
this.agility_mult = 1;
this.charisma_mult = 1;
this.hacking_exp_mult = 1;
this.strength_exp_mult = 1;
this.defense_exp_mult = 1;
this.dexterity_exp_mult = 1;
this.agility_exp_mult = 1;
this.charisma_exp_mult = 1;
this.company_rep_mult = 1;
this.faction_rep_mult = 1;
this.crime_money_mult = 1;
this.crime_success_mult = 1;
this.work_money_mult = 1;
this.city = Cities.Sector12;
/**
* Updates this object's multipliers for the given augmentation
*/
applyAugmentation(aug: Augmentation) {
for (const mult in aug.mults) {
if ((<any>this)[mult] == null) {
console.warn(`Augmentation has unrecognized multiplier property: ${mult}`);
} else {
(<any>this)[mult] *= aug.mults[mult];
}
}
}
/**
@ -255,11 +199,11 @@ export abstract class Person {
*/
updateStatLevels(): void {
this.hacking_skill = Math.max(1, Math.floor(this.calculateStat(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier)));
this.strength = Math.max(1, Math.floor(this.calculateStat(this.strength_exp, this.strength_mult)));
this.defense = Math.max(1, Math.floor(this.calculateStat(this.defense_exp, this.defense_mult)));
this.dexterity = Math.max(1, Math.floor(this.calculateStat(this.dexterity_exp, this.dexterity_mult)));
this.agility = Math.max(1, Math.floor(this.calculateStat(this.agility_exp, this.agility_mult)));
this.charisma = Math.max(1, Math.floor(this.calculateStat(this.charisma_exp, this.charisma_mult)));
this.strength = Math.max(1, Math.floor(this.calculateStat(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier)));
this.defense = Math.max(1, Math.floor(this.calculateStat(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier)));
this.dexterity = Math.max(1, Math.floor(this.calculateStat(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier)));
this.agility = Math.max(1, Math.floor(this.calculateStat(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier)));
this.charisma = Math.max(1, Math.floor(this.calculateStat(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier)));
const ratio: number = this.hp / this.max_hp;
this.max_hp = Math.floor(10 + this.defense / 10);

@ -0,0 +1,10 @@
Implements the Re-sleeving feature, which allows players to purchase a new body
that comes with pre-existing Augmentations and experience. Note that purchasing
a new body causes you to lose all of your old Augmentations and experience
This feature is introduced in BitNode-10, and destroying BitNode-10 allows
the user to use it in other BitNodes (provided that they purchase the required
cortical stack Augmentation)
While they are based on the same concept, this feature is different than the
"Duplicate Sleeve" mechanic (which is referred to as just "Sleeve" in the source code).

@ -0,0 +1,61 @@
/**
* Implements the Resleeve class, which defines a new body
* that the player can "re-sleeve" into.
*/
import { Person } from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../../utils/JSONReviver";
export class Resleeve extends Person {
/**
* Initiatizes a Resleeve object from a JSON save state.
*/
static fromJSON(value: any): Resleeve {
return Generic_fromJSON(Resleeve, value.data);
}
constructor() {
super();
}
getCost(): number {
// Each experience point adds this to the cost
const CostPerExp: number = 25e3;
// Final cost is multiplied by this constant ^ # Augs
const NumAugsExponent: number = 1.2;
// Get total exp in this re-sleeve
let totalExp: number = this.hacking_exp +
this.strength_exp +
this.defense_exp +
this.dexterity_exp +
this.agility_exp +
this.charisma_exp;
// Get total base Augmentation cost for this re-sleeve
let totalAugmentationCost: number = 0;
for (let i = 0; i < this.augmentations.length; ++i) {
const aug: Augmentation | null = Augmentations[this.augmentations[i].name];
if (aug == null) {
console.error(`Could not find Augmentation ${this.augmentations[i].name}`);
continue;
}
totalAugmentationCost += aug!.baseCost;
}
return (totalExp * CostPerExp) + (totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length));
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("Resleeve", this);
}
}
Reviver.constructors.Resleeve = Resleeve;

@ -0,0 +1,120 @@
/**
* Implements the Re-sleeving mechanic for BitNode-10.
* This allows the player to purchase and "use" new sleeves at VitaLife.
* These new sleeves come with different starting experience and Augmentations
* The cost of these new sleeves scales based on the exp and Augs.
*
* Note that this is different from the "Sleeve mechanic". The "Sleeve" mechanic
* provides new sleeves, essentially clones. This Re-sleeving mechanic lets
* the player purchase a new body with pre-existing Augmentations and experience
*
* As of right now, this feature is only available in BitNode 10
*/
import { Resleeve } from "./Resleeve";
import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { IPlayerOwnedAugmentation,
PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
// Executes the actual re-sleeve when one is purchased
export function purchaseResleeve(r: Resleeve, p: IPlayer): boolean {
const cost: number = r.getCost();
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
// Set the player's exp
p.hacking_exp = r.hacking_exp;
p.strength_exp = r.strength_exp;
p.defense_exp = r.defense_exp;
p.dexterity_exp = r.dexterity_exp;
p.agility_exp = r.agility_exp;
p.charisma_exp = r.charisma_exp;
// Reset Augmentation "owned" data
for (const augKey in Augmentations) {
Augmentations[augKey].owned = false;
}
// Clear all of the player's augmentations, except the NeuroFlux Governor
// which is kept
for (let i = p.augmentations.length - 1; i >= 0; --i) {
if (p.augmentations[i].name !== AugmentationNames.NeuroFluxGovernor) {
p.augmentations.splice(i, 1);
} else {
// NeuroFlux Governor
Augmentations[AugmentationNames.NeuroFluxGovernor].owned = true;
}
}
for (let i = 0; i < r.augmentations.length; ++i) {
p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name));
Augmentations[r.augmentations[i].name].owned = true;
}
// The player's purchased Augmentations should remain the same, but any purchased
// Augmentations that are given by the resleeve should be removed so there are no duplicates
for (let i = p.queuedAugmentations.length - 1; i >= 0; --i) {
const name: string = p.queuedAugmentations[i].name;
if (p.augmentations.filter((e: IPlayerOwnedAugmentation) => {return e.name !== AugmentationNames.NeuroFluxGovernor && e.name === name}).length >= 1) {
p.queuedAugmentations.splice(i, 1);
}
}
p.reapplyAllAugmentations(true);
p.reapplyAllSourceFiles(); //Multipliers get reset, so have to re-process source files too
return true;
}
// Creates all of the Re-sleeves that will be available for purchase at VitaLife
export function generateResleeves(): Resleeve[] {
const NumResleeves: number = 40; // Total number of Resleeves to generate
let ret: Resleeve[] = [];
for (let i = 0; i < NumResleeves; ++i) {
// i will be a number indicating how "powerful" the Re-sleeve should be
let r: Resleeve = new Resleeve();
// Generate experience
const expMult: number = (5 * i) + 1;
r.hacking_exp = expMult * getRandomInt(1000, 5000);
r.strength_exp = expMult * getRandomInt(1000, 5000);
r.defense_exp = expMult * getRandomInt(1000, 5000);
r.dexterity_exp = expMult * getRandomInt(1000, 5000);
r.agility_exp = expMult * getRandomInt(1000, 5000);
r.charisma_exp = expMult * getRandomInt(1000, 5000);
// Generate Augs
// Augmentation prequisites will be ignored for this
const baseNumAugs: number = Math.max(2, Math.ceil((i + 3) / 2));
const numAugs: number = getRandomInt(baseNumAugs, baseNumAugs + 2);
const augKeys: string[] = Object.keys(Augmentations);
for (let a = 0; a < numAugs; ++a) {
// Get a random aug
const randIndex: number = getRandomInt(0, augKeys.length - 1)
const randKey: string = augKeys[randIndex];
if (randKey === AugmentationNames.TheRedPill) {
continue; // A sleeve can't have The Red Pill
}
const randAug: Augmentation | null = Augmentations[randKey];
r.augmentations.push({name: randAug!.name, level: 1});
r.applyAugmentation(Augmentations[randKey]);
r.updateStatLevels();
// Remove Augmentation so that there are no duplicates
augKeys.splice(randIndex, 1);
}
ret.push(r);
}
return ret;
}

@ -0,0 +1,360 @@
/**
* Module for handling the Re-sleeving UI
*/
import { Resleeve } from "./Resleeve";
import { generateResleeves,
purchaseResleeve } from "./Resleeving";
import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Page,
routing } from "../../ui/navigationTracking";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { exceptionAlert } from "../../../utils/helpers/exceptionAlert";
import { createElement } from "../../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
import { getSelectValue } from "../../../utils/uiHelpers/getSelectData";
import { removeChildrenFromElement } from "../../../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
interface IResleeveUIElems {
container: HTMLElement | null;
statsPanel: HTMLElement | null;
stats: HTMLElement | null;
multipliersButton: HTMLElement | null;
augPanel: HTMLElement | null;
augSelector: HTMLSelectElement | null;
augDescription: HTMLElement | null;
costPanel: HTMLElement | null;
costText: HTMLElement | null;
buyButton: HTMLElement | null;
}
interface IPageUIElems {
container: HTMLElement | null;
info: HTMLElement | null;
sortTag: HTMLElement | null;
sortSelector: HTMLSelectElement | null;
resleeveList: HTMLElement | null;
resleeves: IResleeveUIElems[] | null;
}
const UIElems: IPageUIElems = {
container: null,
info: null,
sortTag: null,
sortSelector: null,
resleeveList: null,
resleeves: null,
}
let playerRef: IPlayer | null;
export function createResleevesPage(p: IPlayer) {
if (!routing.isOn(Page.Resleeves)) { return; }
try {
playerRef = p;
UIElems.container = createElement("div", {
class: "generic-menupage-container",
id: "resleeves-container",
position: "fixed",
});
UIElems.info = createElement("p", {
display: "block",
innerHTML: "Re-sleeving is the process of digitizing and transferring your consciousness " +
"into a new human body, or 'sleeve'. Here at VitaLife, you can purchase new " +
"specially-engineered bodies for the re-sleeve process. Many of these bodies " +
"even come with genetic and cybernetic Augmentations!<br><br>" +
"Re-sleeving will change your experience for every stat. It will also REMOVE " +
"all of your currently-installed Augmentations, and replace " +
"them with the ones provided by the purchased sleeve. However, Augmentations that you have " +
"purchased but not installed will NOT be removed. If you have purchased an " +
"Augmentation and then re-sleeve into a body which already has that Augmentation, " +
"it will be removed (since you cannot have duplicate Augmentations).<br><br>" +
"NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from " +
"Source-File.",
width: "75%",
});
// Randomly create all Resleeves if they dont already exist
if (p.resleeves.length === 0) {
p.resleeves = generateResleeves();
}
// Create a selector for sorting the list of Resleeves
UIElems.sortTag = createElement("p", {
display: "inline-block",
innerText: "Sort By: "
});
UIElems.sortSelector = createElement("select") as HTMLSelectElement;
enum SortOption {
Cost = "Cost",
Hacking = "Hacking",
Strength = "Strength",
Defense = "Defense",
Dexterity = "Dexterity",
Agility = "Agility",
Charisma = "Charisma",
AverageCombatStats = "AverageCombat",
AverageAllStats = "AverageAllStats",
TotalNumAugmentations = "TotalNumAugmentations",
}
UIElems.sortSelector!.add(createOptionElement("Cost", SortOption.Cost));
UIElems.sortSelector!.add(createOptionElement("Hacking Level", SortOption.Hacking));
UIElems.sortSelector!.add(createOptionElement("Strength Level", SortOption.Strength));
UIElems.sortSelector!.add(createOptionElement("Defense Level", SortOption.Defense));
UIElems.sortSelector!.add(createOptionElement("Dexterity Level", SortOption.Dexterity));
UIElems.sortSelector!.add(createOptionElement("Agility Level", SortOption.Agility));
UIElems.sortSelector!.add(createOptionElement("Charisma Level", SortOption.Charisma));
UIElems.sortSelector!.add(createOptionElement("Average Combat Stats", SortOption.AverageCombatStats));
UIElems.sortSelector!.add(createOptionElement("Average Stats", SortOption.AverageAllStats));
UIElems.sortSelector!.add(createOptionElement("Number of Augmentations", SortOption.TotalNumAugmentations));
UIElems.resleeveList = createElement("ul");
UIElems.sortSelector!.onchange = () => {
removeChildrenFromElement(UIElems.resleeveList);
UIElems.resleeves = [];
// Helper function for averaging
function getAverage(...values: number[]) {
let sum: number = 0;
for (let i = 0; i < values.length; ++i) {
sum += values[i];
}
return sum / values.length;
}
const sortOpt = getSelectValue(UIElems.sortSelector!);
switch (sortOpt) {
case SortOption.Hacking:
p.resleeves.sort((a, b) => {
return a.hacking_skill - b.hacking_skill;
});
break;
case SortOption.Strength:
p.resleeves.sort((a, b) => {
return a.strength - b.strength;
});
break;
case SortOption.Defense:
p.resleeves.sort((a, b) => {
return a.defense - b.defense;
});
break;
case SortOption.Dexterity:
p.resleeves.sort((a, b) => {
return a.dexterity - b.dexterity;
});
break;
case SortOption.Agility:
p.resleeves.sort((a, b) => {
return a.agility - b.agility;
});
break;
case SortOption.Charisma:
p.resleeves.sort((a, b) => {
return a.charisma - b.charisma;
});
break;
case SortOption.AverageCombatStats:
p.resleeves.sort((a, b) => {
let aAvg = getAverage(a.strength, a.defense, a.dexterity, a.agility);
let bAvg = getAverage(b.strength, b.defense, b.dexterity, b.agility);
return aAvg - bAvg;
});
break;
case SortOption.AverageAllStats:
p.resleeves.sort((a, b) => {
let aAvg = getAverage(a.hacking_skill, a.strength, a.defense, a.dexterity, a.agility, a.charisma);
let bAvg = getAverage(b.hacking_skill, b.strength, b.defense, b.dexterity, b.agility, b.charisma);
return aAvg - bAvg;
});
break;
case SortOption.TotalNumAugmentations:
p.resleeves.sort((a, b) => {
return a.augmentations.length - b.augmentations.length;
});
break;
case SortOption.Cost:
default:
p.resleeves.sort((a, b) => {
return a.getCost() - b.getCost();
});
break;
}
// Create UI for all Resleeves
for (const resleeve of p.resleeves) {
const resleeveUi = createResleeveUi(resleeve);
UIElems.resleeveList!.appendChild(resleeveUi.container!);
UIElems.resleeves!.push(resleeveUi);
}
}
UIElems.sortSelector!.dispatchEvent(new Event('change')); // Force onchange event
UIElems.container.appendChild(UIElems.info);
UIElems.container.appendChild(createElement("br"));
UIElems.container.appendChild(UIElems.sortTag);
UIElems.container.appendChild(UIElems.sortSelector);
UIElems.container.appendChild(UIElems.resleeveList);
document.getElementById("entire-game-container")!.appendChild(UIElems.container);
} catch(e) {
exceptionAlert(e);
}
}
export function clearResleevesPage() {
if (UIElems.container instanceof HTMLElement) {
removeElement(UIElems.container);
}
for (const prop in UIElems) {
(<any>UIElems)[prop] = null;
}
playerRef = null;
}
function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
const elems: IResleeveUIElems = {
container: null,
statsPanel: null,
stats: null,
multipliersButton: null,
augPanel: null,
augSelector: null,
augDescription: null,
costPanel: null,
costText: null,
buyButton: null,
};
if (!routing.isOn(Page.Resleeves)) { return elems; }
elems.container = createElement("div", {
class: "resleeve-container",
display: "block",
});
elems.statsPanel = createElement("div", { class: "resleeve-panel", width: "30%" });
elems.stats = createElement("p", {
class: "resleeve-stats-text",
innerHTML:
`Hacking: ${numeralWrapper.format(resleeve.hacking_skill, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.hacking_exp)} exp)<br>` +
`Strength: ${numeralWrapper.format(resleeve.strength, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.strength_exp)} exp)<br>` +
`Defense: ${numeralWrapper.format(resleeve.defense, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.defense_exp)} exp)<br>` +
`Dexterity: ${numeralWrapper.format(resleeve.dexterity, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.dexterity_exp)} exp)<br>` +
`Agility: ${numeralWrapper.format(resleeve.agility, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.agility_exp)} exp)<br>` +
`Charisma: ${numeralWrapper.format(resleeve.charisma, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.charisma_exp)} exp)<br>` +
`# Augmentations: ${resleeve.augmentations.length}`,
});
elems.multipliersButton = createElement("button", {
class: "std-button",
innerText: "Multipliers",
clickListener: () => {
dialogBoxCreate(
[
"<h2><u>Total Multipliers:</u></h2>",
`Hacking Level multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_mult)}`,
`Hacking Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_exp_mult)}`,
`Strength Level multiplier: ${numeralWrapper.formatPercentage(resleeve.strength_mult)}`,
`Strength Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.strength_exp_mult)}`,
`Defense Level multiplier: ${numeralWrapper.formatPercentage(resleeve.defense_mult)}`,
`Defense Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.defense_exp_mult)}`,
`Dexterity Level multiplier: ${numeralWrapper.formatPercentage(resleeve.dexterity_mult)}`,
`Dexterity Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.dexterity_exp_mult)}`,
`Agility Level multiplier: ${numeralWrapper.formatPercentage(resleeve.agility_mult)}`,
`Agility Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.agility_exp_mult)}`,
`Charisma Level multiplier: ${numeralWrapper.formatPercentage(resleeve.charisma_mult)}`,
`Charisma Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.charisma_exp_mult)}`,
`Hacking Chance multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_chance_mult)}`,
`Hacking Speed multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_speed_mult)}`,
`Hacking Money multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_money_mult)}`,
`Hacking Growth multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_grow_mult)}`,
`Salary multiplier: ${numeralWrapper.formatPercentage(resleeve.work_money_mult)}`,
`Company Reputation Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.company_rep_mult)}`,
`Faction Reputation Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.faction_rep_mult)}`,
`Crime Money multiplier: ${numeralWrapper.formatPercentage(resleeve.crime_money_mult)}`,
`Crime Success multiplier: ${numeralWrapper.formatPercentage(resleeve.crime_success_mult)}`,
`Hacknet Income multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_money_mult)}`,
`Hacknet Purchase Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_purchase_cost_mult)}`,
`Hacknet Level Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_level_cost_mult)}`,
`Hacknet Ram Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_ram_cost_mult)}`,
`Hacknet Core Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_core_cost_mult)}`,
`Bladeburner Max Stamina multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_max_stamina_mult)}`,
`Bladeburner Stamina Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_stamina_gain_mult)}`,
`Bladeburner Field Analysis multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_analysis_mult)}`,
`Bladeburner Success Chance multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_success_chance_mult)}`
].join("<br>"), false
)
}
});
elems.statsPanel.appendChild(elems.stats);
elems.statsPanel.appendChild(elems.multipliersButton);
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement;
elems.augDescription = createElement("p");
for (let i = 0; i < resleeve.augmentations.length; ++i) {
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
};
elems.augSelector.addEventListener("change", () => {
updateAugDescription(elems);
});
elems.augSelector.dispatchEvent(new Event('change')); // Set inital description by manually triggering change event
elems.augPanel.appendChild(elems.augSelector);
elems.augPanel.appendChild(elems.augDescription);
const cost: number = resleeve.getCost();
elems.costPanel = createElement("div", { class: "resleeve-panel", width: "20%" });
elems.costText = createElement("p", {
innerText: `It costs ${numeralWrapper.formatMoney(cost)} ` +
`to purchase this Sleeve.`,
});
elems.buyButton = createElement("button", {
class: "std-button",
innerText: "Purchase",
clickListener: () => {
if (purchaseResleeve(resleeve, playerRef!)) {
dialogBoxCreate(`You re-sleeved for ${numeralWrapper.formatMoney(cost)}!`, false);
} else {
dialogBoxCreate(`You cannot afford to re-sleeve into this body`, false);
}
}
});
elems.costPanel.appendChild(elems.costText);
elems.costPanel.appendChild(elems.buyButton);
elems.container.appendChild(elems.statsPanel);
elems.container.appendChild(elems.augPanel);
elems.container.appendChild(elems.costPanel);
return elems;
}
function updateAugDescription(elems: IResleeveUIElems) {
const augName: string = getSelectValue(elems.augSelector);
const aug: Augmentation | null = Augmentations[augName];
if (aug == null) {
console.warn(`Could not find Augmentation with name ${augName}`);
return;
}
elems.augDescription!.innerHTML = aug!.info;
}

@ -0,0 +1,9 @@
Implements the "Duplicate Sleeves" feature, which allows the player to purchase
new duplicate sleeves. These are synthetic bodies that contain the player's
cloned consciousness. The player can use these sleeves to perform
different tasks synchronously.
This feature is introduced and unlocked in BitNode-10.
Note that while they are based on the same concept, this feature is different
than the "Re-sleeving" mechanic (which is referred to as "Resleeve" in the source code).

@ -1,6 +1,6 @@
/**
* Sleeves are clones of the player that can be used to perform
* different tasks synchronously.
* Sleeves are bodies that contain the player's cloned consciousness.
* The player can use these bodies to perform different tasks synchronously.
*
* Each sleeve is its own individual, meaning it has its own stats/exp
*
@ -8,21 +8,29 @@
*/
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer";
import { Person,
IPlayer,
ICrime,
IFaction,
ITaskTracker,
createTaskTracker } from "../Person";
import { BitNodeMultipliers } from "../../BitNodeMultipliers";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Crime } from "../../Crime/Crime";
import { Crimes } from "../../Crime/Crimes";
import { Cities } from "../../Locations/Cities";
import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { CompanyPositions } from "../../Company/CompanyPositions";
import { CONSTANTS } from "../../Constants";
import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { FactionWorkType } from "../../Faction/FactionWorkTypeEnum";
import { Locations } from "../../Locations";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../../utils/JSONReviver";
@ -35,19 +43,24 @@ export class Sleeve extends Person {
return Generic_fromJSON(Sleeve, value.data);
}
/**
* Stores the type of crime the sleeve is currently attempting
* Must match the name of a Crime object
*/
crimeType: string = "";
/**
* Enum value for current task
*/
currentTask: SleeveTaskType = SleeveTaskType.Idle;
/**
* Description of current task. Used only for logging purposes
*/
currentTaskDescription: string = "";
/**
* For what company/faction the current task is assigned to.
* Only applicable when working for faction or company, obviously
* Contains details about the sleeve's current task. The info stored
* in this depends on the task type
*
* Faction/Company Work: Name of Faction/Company
* Crime: Money earned if successful
* Class/Gym: Name of university/gym
*/
currentTaskLocation: string = "";
@ -86,6 +99,11 @@ export class Sleeve extends Person {
*/
gainRatesForTask: ITaskTracker = createTaskTracker();
/**
* String that stores what stat the sleeve is training at the gym
*/
gymStatType: string = "";
/**
* Keeps track of events/notifications for this sleeve
*/
@ -101,6 +119,8 @@ export class Sleeve extends Person {
* Sleeve shock. Number between 1 and 100
* Trauma/shock that comes with being in a sleeve. Experience earned
* is multipled by shock%. This gets applied before synchronization
*
* Reputation earned is also multiplied by shock%
*/
shock: number = 1;
@ -118,29 +138,17 @@ export class Sleeve extends Person {
constructor() {
super();
/*
this.currentTask = SleeveTaskType.Idle;
this.currentTaskDescription = "";
this.currentTaskTime = 0;
this.currentTaskMaxTime = 0;
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker();
this.logs = [];
this.memory = 0;
this.shock = 1;
this.storedCycles = 0;
this.sync = 1;
*/
}
/**
* Commit crimes
*/
commitCrime(p: IPlayer, crime: ICrime): void {
commitCrime(p: IPlayer, crimeKey: string): boolean {
const crime: Crime | null = Crimes[crimeKey];
if (!(crime instanceof Crime)) { return false; }
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask();
this.finishTask(p);
} else {
this.resetTaskStatus();
}
@ -151,29 +159,94 @@ export class Sleeve extends Person {
this.gainRatesForTask.dex = crime.dexterity_exp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.agi = crime.agility_exp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.cha = crime.charisma_exp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.currentTaskLocation = String(this.gainRatesForTask.money);
this.crimeType = crimeKey;
this.currentTaskMaxTime = crime.time;
this.currentTask = SleeveTaskType.Crime;
return true;
}
/**
* Called to stop the current task
*/
finishTask(): void {
if (this.currentTask === SleeveTaskType.Crime) {
} else {
finishTask(p: IPlayer): ITaskTracker {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
const crime: Crime | null = Crimes[this.crimeType];
if (!(crime instanceof Crime)) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus();
return retValue;
}
if (Math.random() < crime.successRate(p)) {
// Success
const successGainRates: ITaskTracker = createTaskTracker();
const keysForIteration: (keyof ITaskTracker)[] = (<(keyof ITaskTracker)[]>Object.keys(successGainRates));
for (let i = 0; i < keysForIteration.length; ++i) {
const key = keysForIteration[i];
successGainRates[key] = this.gainRatesForTask[key] * 2;
}
retValue = this.gainExperience(p, successGainRates);
this.gainMoney(p, this.gainRatesForTask);
} else {
retValue = this.gainExperience(p, this.gainRatesForTask);
}
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
}
} else {
// For other crimes... I dont think anything else needs to be done
}
this.resetTaskStatus();
return retValue;
}
/**
* Earn experience for any stats (supports multiple)
* This function also handles experience propogating to Player and other sleeves
*/
gainExperience(p: IPlayer, exp: ITaskTracker, numCycles: number=1): ITaskTracker {
gainExperience(p: IPlayer, exp: ITaskTracker, numCycles: number=1, fromOtherSleeve: boolean=false): ITaskTracker {
// If the experience is coming from another sleeve, it is not multiplied by anything.
// Also the player does not earn anything
if (fromOtherSleeve) {
if (exp.hack > 0) {
this.hacking_exp += exp.hack;
}
if (exp.str > 0) {
this.strength_exp += exp.str;
}
if (exp.def > 0) {
this.defense_exp += exp.def;
}
if (exp.dex > 0) {
this.dexterity_exp += exp.dex;
}
if (exp.agi > 0) {
this.agility_exp += exp.agi;
}
if (exp.cha > 0) {
this.charisma_exp += exp.cha;
}
return createTaskTracker();
}
// Experience is first multiplied by shock. Then 'synchronization'
// is accounted for
const multFac = (this.shock / 100) * (this.sync / 100) * numCycles;
@ -203,7 +276,7 @@ export class Sleeve extends Person {
this.defense_exp += pDefExp;
p.gainDefenseExp(pDefExp);
this.earningsForPlayer.def += pDefExp;
this.earningsForTask.dex += pDefExp;
this.earningsForTask.def += pDefExp;
}
if (pDexExp > 0) {
@ -251,28 +324,49 @@ export class Sleeve extends Person {
* Earn money for player
*/
gainMoney(p: IPlayer, task: ITaskTracker, numCycles: number=1): void {
p.gainMoney(task.money * numCycles);
const gain: number = (task.money * numCycles);
this.earningsForTask.money += gain;
this.earningsForPlayer.money += gain;
p.gainMoney(gain);
}
/**
* Gets reputation gain for the current task
* Only applicable when working for company or faction
*/
getRepGain(): number {
getRepGain(p: IPlayer): number {
if (this.currentTask === SleeveTaskType.Faction) {
switch (this.factionWorkType) {
case FactionWorkType.Hacking:
return this.getFactionHackingWorkRepGain();
return this.getFactionHackingWorkRepGain() * (this.shock / 100);
case FactionWorkType.Field:
return this.getFactionFieldWorkRepGain();
return this.getFactionFieldWorkRepGain() * (this.shock / 100);
case FactionWorkType.Security:
return this.getFactionSecurityWorkRepGain();
return this.getFactionSecurityWorkRepGain() * (this.shock / 100);
default:
console.warn(`Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`);
return 0;
}
} else if (this.currentTask === SleeveTaskType.Company) {
return 0;
const companyName: string = this.currentTaskLocation;
const company: Company | null = Companies[companyName];
if (company == null) {
console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`);
return 0;
}
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (companyPosition == null) {
console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`);
return 0;
}
const jobPerformance: number = companyPosition!.calculateJobPerformance(this.hacking_skill, this.strength,
this.defense, this.dexterity,
this.agility, this.charisma);
const favorMult = 1 + (company!.favor / 100);
return jobPerformance * this.company_rep_mult * favorMult;
} else {
console.warn(`Sleeve.getRepGain() called for invalid task type: ${this.currentTask}`);
return 0;
@ -298,14 +392,9 @@ export class Sleeve extends Person {
this.storedCycles += numCycles;
if (this.storedCycles < CyclesPerSecond) { return null; }
// Shock gradually goes towards 100
this.shock = Math.max(100, this.shock + (0.0001 * this.storedCycles));
if (this.currentTask === SleeveTaskType.Idle) { return null; }
let time = this.storedCycles * CONSTANTS.MilliPerCycle;
let cyclesUsed = this.storedCycles;
if (this.currentTaskTime + time > this.currentTaskMaxTime) {
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) {
time = this.currentTaskMaxTime - this.currentTaskTime;
cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle);
@ -317,9 +406,15 @@ export class Sleeve extends Person {
}
this.currentTaskTime += time;
// Shock gradually goes towards 100
this.shock = Math.min(100, this.shock + (0.0001 * this.storedCycles));
let retValue: ITaskTracker = createTaskTracker();
switch (this.currentTask) {
case SleeveTaskType.Idle:
break;
case SleeveTaskType.Class:
case SleeveTaskType.Gym:
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
break;
@ -327,34 +422,50 @@ export class Sleeve extends Person {
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
// TODO REP for both this and company
const fac = Factions[this.currentTaskLocation];
// Gain faction reputation
const fac: Faction = Factions[this.currentTaskLocation];
if (!(fac instanceof Faction)) {
console.error(`Invalid faction for Sleeve task: ${this.currentTaskLocation}`);
break;
}
fac.playerReputation += (this.getRepGain(p) * cyclesUsed);
break;
case SleeveTaskType.Company:
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
const company: Company = Companies[this.currentTaskLocation];
if (!(company instanceof Company)) {
console.error(`Invalid company for Sleeve task: ${this.currentTaskLocation}`);
break;
}
company!.playerReputation += (this.getRepGain(p) * cyclesUsed);
break;
case SleeveTaskType.Recovery:
this.shock = Math.max(100, this.shock + (0.001 * this.storedCycles));
this.shock = Math.min(100, this.shock + (0.0001 * cyclesUsed));
break;
case SleeveTaskType.Sync:
this.sync = Math.max(100, this.sync + (0.001 * this.storedCycles));
this.sync = Math.min(100, this.sync + (0.0001 * cyclesUsed));
break;
default:
break;
}
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
this.finishTask();
if (this.currentTask === SleeveTaskType.Crime) {
retValue = this.finishTask(p);
} else {
this.finishTask(p);
}
}
this.updateStatLevels();
this.storedCycles -= cyclesUsed;
// TODO Finish this
return retValue;
}
@ -368,6 +479,9 @@ export class Sleeve extends Person {
this.currentTaskTime = 0;
this.currentTaskMaxTime = 0;
this.factionWorkType = FactionWorkType.None;
this.crimeType = "";
this.currentTaskLocation = "";
this.gymStatType = "";
}
/**
@ -375,7 +489,7 @@ export class Sleeve extends Person {
*/
takeUniversityCourse(p: IPlayer, universityName: string, className: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask();
this.finishTask(p);
} else {
this.resetTaskStatus();
}
@ -387,16 +501,19 @@ export class Sleeve extends Person {
switch (universityName.toLowerCase()) {
case Locations.AevumSummitUniversity.toLowerCase():
if (this.city !== Cities.Aevum) { return false; }
this.currentTaskLocation = Locations.AevumSummitUniversity;
costMult = 4;
expMult = 3;
break;
case Locations.Sector12RothmanUniversity.toLowerCase():
if (this.city !== Cities.Sector12) { return false; }
this.currentTaskLocation = Locations.Sector12RothmanUniversity;
costMult = 3;
expMult = 2;
break;
case Locations.VolhavenZBInstituteOfTechnology.toLowerCase():
if (this.city !== Cities.Volhaven) { return false; }
this.currentTaskLocation = Locations.VolhavenZBInstituteOfTechnology;
costMult = 5;
expMult = 4;
break;
@ -404,9 +521,6 @@ export class Sleeve extends Person {
return false;
}
// Number of game cycles in a second
const cps: number = 1000 / CONSTANTS.MilliPerCycle;
// Set experience/money gains based on class
// TODO Refactor University Courses into its own class or something
const baseStudyComputerScienceExp: number = 0.5;
@ -464,32 +578,86 @@ export class Sleeve extends Person {
}
/**
* Work for a company
* Start work for one of the player's companies
* Returns boolean indicating success
*/
workForCompany(p: IPlayer): boolean {
return true;
}
/**
* Work for one of the player's factions
*/
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
workForCompany(p: IPlayer, companyName: string): boolean {
if (!(Companies[companyName] instanceof Company) || p.jobs[companyName] == null) {
return false;
}
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask();
this.finishTask(p);
} else {
this.resetTaskStatus();
}
const company: Company | null = Companies[companyName];
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); }
if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); }
this.gainRatesForTask.money = companyPosition.baseSalary *
company.salaryMultiplier *
this.work_money_mult *
BitNodeMultipliers.CompanyWorkMoney;
this.gainRatesForTask.hack = companyPosition.hackingExpGain *
company.expMultiplier *
this.hacking_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = companyPosition.strengthExpGain *
company.expMultiplier *
this.strength_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.def = companyPosition.defenseExpGain *
company.expMultiplier *
this.defense_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.dex = companyPosition.dexterityExpGain *
company.expMultiplier *
this.dexterity_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.agi = companyPosition.agilityExpGain *
company.expMultiplier *
this.agility_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.cha = companyPosition.charismaExpGain *
company.expMultiplier *
this.charisma_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
this.currentTaskLocation = companyName;
this.currentTask = SleeveTaskType.Company;
this.currentTaskMaxTime = CONSTANTS.MillisecondsPer8Hours;
return true;
}
/**
* Start work for one of the player's factions
* Returns boolean indicating success
*/
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
throw new Error(`Invalid Faction specified for Sleeve.workForFaction(): ${factionName}`);
return false;
}
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
}
const factionInfo = Factions[factionName].getInfo();
// Set type of work (hacking/field/security), and the experience gains
const sanitizedWorkType: string = workType.toLowerCase();
if (sanitizedWorkType.includes("hack")) {
if (!factionInfo.offerHackingWork) { return false; }
this.factionWorkType = FactionWorkType.Hacking;
this.gainRatesForTask.hack = .15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
} else if (sanitizedWorkType.includes("field")) {
if (!factionInfo.offerFieldWork) { return false; }
this.factionWorkType = FactionWorkType.Field;
this.gainRatesForTask.hack = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = .1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -498,6 +666,7 @@ export class Sleeve extends Person {
this.gainRatesForTask.agi = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.cha = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
} else if (sanitizedWorkType.includes("security")) {
if (!factionInfo.offerSecurityWork) { return false; }
this.factionWorkType = FactionWorkType.Security;
this.gainRatesForTask.hack = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = .15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@ -510,6 +679,7 @@ export class Sleeve extends Person {
this.currentTaskLocation = factionName;
this.currentTask = SleeveTaskType.Faction;
this.currentTaskMaxTime = CONSTANTS.MillisecondsPer20Hours;
return true;
}
@ -519,7 +689,7 @@ export class Sleeve extends Person {
*/
workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask();
this.finishTask(p);
} else {
this.resetTaskStatus();
}
@ -531,26 +701,31 @@ export class Sleeve extends Person {
switch (gymName.toLowerCase()) {
case Locations.AevumCrushFitnessGym.toLowerCase():
if (this.city != Cities.Aevum) { return false; }
this.currentTaskLocation = Locations.AevumCrushFitnessGym;
costMult = 3;
expMult = 2;
break;
case Locations.AevumSnapFitnessGym.toLowerCase():
if (this.city != Cities.Aevum) { return false; }
this.currentTaskLocation = Locations.AevumSnapFitnessGym;
costMult = 10;
expMult = 5;
break;
case Locations.Sector12IronGym.toLowerCase():
if (this.city != Cities.Sector12) { return false; }
this.currentTaskLocation = Locations.Sector12IronGym;
costMult = 1;
expMult = 1;
break;
case Locations.Sector12PowerhouseGym.toLowerCase():
if (this.city != Cities.Sector12) { return false; }
this.currentTaskLocation = Locations.Sector12PowerhouseGym;
costMult = 20;
expMult = 10;
break;
case Locations.VolhavenMilleniumFitnessGym:
if (this.city != Cities.Volhaven) { return false; }
this.currentTaskLocation = Locations.VolhavenMilleniumFitnessGym;
costMult = 7;
expMult = 4;
break;
@ -558,9 +733,6 @@ export class Sleeve extends Person {
return false;
}
// Number of game cycles in a second
const cps = 1000 / CONSTANTS.MilliPerCycle;
// Set experience/money gains based on class
// TODO Refactor University Courses into its own class or something
const baseGymExp: number = 1;
@ -582,7 +754,8 @@ export class Sleeve extends Person {
return false;
}
this.currentTask = SleeveTaskType.Class;
this.gymStatType = stat;
this.currentTask = SleeveTaskType.Gym;
return true;
}

@ -2,11 +2,13 @@
* Enum for different types of tasks that a Sleeve can perform
*/
export enum SleeveTaskType {
Class,
Company,
Crime,
Faction,
// Same Order as selectable order in UI
Idle,
Company,
Faction,
Crime,
Class,
Gym,
Recovery,
Sync,
}

@ -0,0 +1,741 @@
/**
* Module for handling the Sleeve UI
*/
import { Sleeve } from "./Sleeve";
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { SleeveFaq } from "./data/SleeveFaq";
import { IPlayer } from "../IPlayer";
import { CONSTANTS } from "../../Constants";
import { Locations } from "../../Locations";
import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { FactionWorkType } from "../../Faction/FactionWorkTypeEnum";
import { Cities } from "../../Locations/Cities";
import { Crime } from "../../Crime/Crime";
import { Crimes } from "../../Crime/Crimes";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Page,
routing } from "../../ui/navigationTracking";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { exceptionAlert } from "../../../utils/helpers/exceptionAlert";
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
import { createPopup } from "../../../utils/uiHelpers/createPopup";
import { createPopupCloseButton } from "../../../utils/uiHelpers/createPopupCloseButton";
import { getSelectValue } from "../../../utils/uiHelpers/getSelectData";
import { removeChildrenFromElement } from "../../../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
// Object that keeps track of all DOM elements for the UI for a single Sleeve
interface ISleeveUIElems {
container: HTMLElement | null;
statsPanel: HTMLElement | null;
stats: HTMLElement | null;
moreStatsButton: HTMLElement | null;
travelButton: HTMLElement | null;
taskPanel: HTMLElement | null;
taskSelector: HTMLSelectElement | null;
taskDetailsSelector: HTMLSelectElement | null;
taskDetailsSelector2: HTMLSelectElement | null;
taskDescription: HTMLElement | null;
taskSetButton: HTMLElement | null;
taskProgressBar: HTMLElement | null;
earningsPanel: HTMLElement | null;
currentEarningsInfo: HTMLElement | null;
totalEarningsButton: HTMLElement | null;
}
// Object that keeps track of all DOM elements for the entire Sleeve UI
interface IPageUIElems {
container: HTMLElement | null;
docButton: HTMLElement | null;
faqButton: HTMLElement | null;
info: HTMLElement | null;
sleeveList: HTMLElement | null;
sleeves: ISleeveUIElems[] | null;
}
const UIElems: IPageUIElems = {
container: null,
docButton: null,
faqButton: null,
info: null,
sleeveList: null,
sleeves: null,
}
// Creates the UI for the entire Sleeves page
let playerRef: IPlayer | null;
export function createSleevesPage(p: IPlayer) {
if (!routing.isOn(Page.Sleeves)) { return; }
try {
playerRef = p;
UIElems.container = createElement("div", {
class: "generic-menupage-container",
id: "sleeves-container",
position: "fixed",
});
UIElems.info = createElement("p", {
class: "sleeves-page-info",
innerHTML: "Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your " +
"consciousness has been copied. In other words, these Synthoids contain " +
"a perfect duplicate of your mind.<br><br>" +
"Sleeves can be used to perform different tasks synchronously.<br><br>",
});
UIElems.faqButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "FAQ",
clickListener: () => {
dialogBoxCreate(SleeveFaq, false);
}
});
UIElems.docButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Documentation",
});
UIElems.sleeveList = createElement("ul");
UIElems.sleeves = [];
// Create UI modules for all Sleeve
for (const sleeve of p.sleeves) {
const sleeveUi = createSleeveUi(sleeve, p.sleeves);
UIElems.sleeveList.appendChild(sleeveUi.container!);
UIElems.sleeves.push(sleeveUi);
}
UIElems.container.appendChild(UIElems.info);
UIElems.container.appendChild(UIElems.faqButton);
UIElems.container.appendChild(UIElems.sleeveList);
document.getElementById("entire-game-container")!.appendChild(UIElems.container);
} catch(e) {
exceptionAlert(e);
}
}
// Updates the UI for the entire Sleeves page
export function updateSleevesPage() {
if (!routing.isOn(Page.Sleeves)) { return; }
try {
for (let i = 0; i < playerRef!.sleeves.length; ++i) {
const sleeve: Sleeve = playerRef!.sleeves[i];
const elems: ISleeveUIElems = UIElems.sleeves![i];
updateSleeveUi(sleeve!, elems!);
}
} catch(e) {
exceptionAlert(e);
}
}
export function clearSleevesPage() {
if (UIElems.container instanceof HTMLElement) {
removeElement(UIElems.container);
}
for (const prop in UIElems) {
(<any>UIElems)[prop] = null;
}
playerRef = null;
}
// Creates the UI for a single Sleeve
// Returns an object containing the DOM elements in the UI (ISleeveUIElems)
function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
const elems: ISleeveUIElems = {
container: null,
statsPanel: null,
stats: null,
moreStatsButton: null,
travelButton: null,
taskPanel: null,
taskSelector: null,
taskDetailsSelector: null,
taskDetailsSelector2: null,
taskDescription: null,
taskSetButton: null,
taskProgressBar: null,
earningsPanel: null,
currentEarningsInfo: null,
totalEarningsButton: null,
}
if (!routing.isOn(Page.Sleeves)) { return elems; }
elems.container = createElement("div", {
class: "sleeve-container",
display: "block",
});
elems.statsPanel = createElement("div", { class: "sleeve-panel", width: "25%" });
elems.stats = createElement("p", { class: "sleeve-stats-text" });
elems.moreStatsButton = createElement("button", {
class: "std-button",
innerText: "More Stats",
clickListener: () => {
dialogBoxCreate(
[
"<h2><u>Stats:</u></h2>",
`Hacking: ${sleeve.hacking_skill} (${numeralWrapper.formatBigNumber(sleeve.hacking_exp)} exp)`,
`Strength: ${sleeve.strength} (${numeralWrapper.formatBigNumber(sleeve.strength_exp)} exp)`,
`Defense: ${sleeve.defense} (${numeralWrapper.formatBigNumber(sleeve.defense_exp)} exp)`,
`Dexterity: ${sleeve.dexterity} (${numeralWrapper.formatBigNumber(sleeve.dexterity_exp)} exp)`,
`Agility: ${sleeve.agility} (${numeralWrapper.formatBigNumber(sleeve.agility_exp)} exp)`,
`Charisma: ${sleeve.charisma} (${numeralWrapper.formatBigNumber(sleeve.charisma_exp)} exp)<br>`,
"<h2><u>Multipliers:</u></h2>",
`Hacking Level multiplier: ${numeralWrapper.formatPercentage(sleeve.hacking_mult)}`,
`Hacking Experience multiplier: ${numeralWrapper.formatPercentage(sleeve.hacking_exp_mult)}`,
`Strength Level multiplier: ${numeralWrapper.formatPercentage(sleeve.strength_mult)}`,
`Strength Experience multiplier: ${numeralWrapper.formatPercentage(sleeve.strength_exp_mult)}`,
`Defense Level multiplier: ${numeralWrapper.formatPercentage(sleeve.defense_mult)}`,
`Defense Experience multiplier: ${numeralWrapper.formatPercentage(sleeve.defense_exp_mult)}`,
`Dexterity Level multiplier: ${numeralWrapper.formatPercentage(sleeve.dexterity_mult)}`,
`Dexterity Experience multiplier: ${numeralWrapper.formatPercentage(sleeve.dexterity_exp_mult)}`,
`Agility Level multiplier: ${numeralWrapper.formatPercentage(sleeve.agility_mult)}`,
`Agility Experience multiplier: ${numeralWrapper.formatPercentage(sleeve.agility_exp_mult)}`,
`Charisma Level multiplier: ${numeralWrapper.formatPercentage(sleeve.charisma_mult)}`,
`Charisma Experience multiplier: ${numeralWrapper.formatPercentage(sleeve.charisma_exp_mult)}`,
`Faction Reputation Gain multiplier: ${numeralWrapper.formatPercentage(sleeve.faction_rep_mult)}`,
`Company Reputation Gain multiplier: ${numeralWrapper.formatPercentage(sleeve.company_rep_mult)}`,
`Salary multiplier: ${numeralWrapper.formatPercentage(sleeve.work_money_mult)}`,
`Crime Money multiplier: ${numeralWrapper.formatPercentage(sleeve.crime_money_mult)}`,
`Crime Success multiplier: ${numeralWrapper.formatPercentage(sleeve.crime_success_mult)}`,
].join("<br>"), false
);
}
});
elems.travelButton = createElement("button", {
class: "std-button",
innerText: "Travel",
clickListener: () => {
const popupId: string = "sleeve-travel-popup";
const popupArguments: HTMLElement[] = [];
popupArguments.push(createPopupCloseButton(popupId, { class: "std-button" }));
popupArguments.push(createElement("p", {
innerText: "Have this sleeve travel to a different city. This affects " +
"the gyms and universities at which this sleeve can study. " +
`Traveling to a different city costs ${numeralWrapper.formatMoney(CONSTANTS.TravelCost)}. ` +
"It will also CANCEL the sleeve's current task (setting it to idle)",
}));
for (const label in Cities) {
if (sleeve.city === Cities[label]) { continue; }
(function(sleeve, label) {
popupArguments.push(createElement("div", {
// Reusing this css class. It adds a border and makes it so that
// the background color changes when you hover
class: "cmpy-mgmt-find-employee-option",
innerText: Cities[label],
clickListener: () => {
if (!playerRef!.canAfford(CONSTANTS.TravelCost)) {
dialogBoxCreate("You cannot afford to have this sleeve travel to another city", false);
return false;
}
sleeve.city = Cities[label];
playerRef!.loseMoney(CONSTANTS.TravelCost);
sleeve.resetTaskStatus();
removeElementById(popupId);
updateSleeveUi(sleeve, elems);
updateSleeveTaskSelector(sleeve, elems, allSleeves);
return false;
}
}));
})(sleeve, label);
}
createPopup(popupId, popupArguments);
}
})
elems.statsPanel.appendChild(elems.stats);
elems.statsPanel.appendChild(elems.moreStatsButton);
elems.statsPanel.appendChild(elems.travelButton);
elems.taskPanel = createElement("div", { class: "sleeve-panel", width: "40%" });
elems.taskSelector = createElement("select") as HTMLSelectElement;
elems.taskSelector.add(createOptionElement("------"));
elems.taskSelector.add(createOptionElement("Work for Company"));
elems.taskSelector.add(createOptionElement("Work for Faction"));
elems.taskSelector.add(createOptionElement("Commit Crime"));
elems.taskSelector.add(createOptionElement("Take University Course"));
elems.taskSelector.add(createOptionElement("Workout at Gym"));
elems.taskSelector.add(createOptionElement("Shock Recovery"));
elems.taskSelector.add(createOptionElement("Synchronize"));
elems.taskDetailsSelector = createElement("select") as HTMLSelectElement;
elems.taskDetailsSelector2 = createElement("select") as HTMLSelectElement;
elems.taskDescription = createElement("p");
elems.taskProgressBar = createElement("p");
elems.taskSelector.addEventListener("change", () => {
updateSleeveTaskSelector(sleeve, elems, allSleeves);
});
elems.taskSelector.selectedIndex = sleeve.currentTask; // Set initial value for Task Selector
elems.taskSelector.dispatchEvent(new Event('change'));
updateSleeveTaskDescription(sleeve, elems);
elems.taskSetButton = createElement("button", {
class: "std-button",
innerText: "Set Task",
clickListener: () => {
setSleeveTask(sleeve, elems);
}
});
elems.taskPanel.appendChild(elems.taskSelector);
elems.taskPanel.appendChild(elems.taskDetailsSelector);
elems.taskPanel.appendChild(elems.taskDetailsSelector2);
elems.taskPanel.appendChild(elems.taskSetButton);
elems.taskPanel.appendChild(elems.taskDescription);
elems.taskPanel.appendChild(elems.taskProgressBar);
elems.earningsPanel = createElement("div", { class: "sleeve-panel", width: "35%" });
elems.currentEarningsInfo = createElement("p");
elems.totalEarningsButton = createElement("button", {
class: "std-button",
innerText: "More Earnings Info",
clickListener: () => {
dialogBoxCreate(
[
"<h2><u>Earnings for Current Task:</u></h2>",
`Money: ${numeralWrapper.formatMoney(sleeve.earningsForTask.money)}`,
`Hacking Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.hack)}`,
`Strength Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.str)}`,
`Defense Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.def)}`,
`Dexterity Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.dex)}`,
`Agility Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.agi)}`,
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.cha)}<br>`,
"<h2><u>Total Earnings for Host Consciousness:</u></h2>",
`Money: ${numeralWrapper.formatMoney(sleeve.earningsForPlayer.money)}`,
`Hacking Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.hack)}`,
`Strength Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.str)}`,
`Defense Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.def)}`,
`Dexterity Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.dex)}`,
`Agility Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.agi)}`,
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.cha)}<br>`,
"<h2><u>Total Earnings for Other Sleeves:</u></h2>",
`Money: ${numeralWrapper.formatMoney(sleeve.earningsForSleeves.money)}`,
`Hacking Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.hack)}`,
`Strength Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.str)}`,
`Defense Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.def)}`,
`Dexterity Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.dex)}`,
`Agility Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.agi)}`,
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.cha)}`,
].join("<br>"), false
);
}
});
elems.earningsPanel.appendChild(elems.currentEarningsInfo);
elems.earningsPanel.appendChild(elems.totalEarningsButton);
updateSleeveUi(sleeve, elems);
elems.container.appendChild(elems.statsPanel);
elems.container.appendChild(elems.taskPanel);
elems.container.appendChild(elems.earningsPanel);
return elems;
}
// Updates the UI for a single Sleeve
function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
if (!routing.isOn(Page.Sleeves)) { return; }
elems.stats!.innerHTML = [`Hacking: ${numeralWrapper.format(sleeve.hacking_skill, "0,0")}`,
`Strength: ${numeralWrapper.format(sleeve.strength, "0,0")}`,
`Defense: ${numeralWrapper.format(sleeve.defense, "0,0")}`,
`Dexterity: ${numeralWrapper.format(sleeve.dexterity, "0,0")}`,
`Agility: ${numeralWrapper.format(sleeve.agility, "0,0")}`,
`Charisma: ${numeralWrapper.format(sleeve.charisma, "0,0")}`,
`HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`,
`City: ${sleeve.city}`,
`Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`,
`Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`].join("<br>");
let repGainText: string = "";
if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) {
const repGain: number = sleeve.getRepGain(playerRef!);
repGainText = `Reputation: ${numeralWrapper.format(5 * repGain, "0.00")} / s`
}
if (sleeve.currentTask === SleeveTaskType.Crime) {
elems.currentEarningsInfo!.innerHTML = [
`Earnings (Pre-Synchronization):`,
`Money: ${numeralWrapper.formatMoney(parseFloat(sleeve.currentTaskLocation))} if successful`,
`Hacking Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.hack, "0.00")} (2x if successful)`,
`Strength Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.str, "0.00")} (2x if successful)`,
`Defense Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.def, "0.00")} (2x if successful)`,
`Dexterity Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.dex, "0.00")} (2x if successful)`,
`Agility Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.agi, "0.00")} (2x if successful)`,
`Charisma Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.cha, "0.00")} (2x if successful)`
].join("<br>");
elems.taskProgressBar!.innerText = createProgressBarText({
progress: sleeve.currentTaskTime / sleeve.currentTaskMaxTime,
totalTicks: 25,
});
} else {
const lines = [
`Earnings (Pre-Synchronization):`,
`Money: ${numeralWrapper.formatMoney(5 * sleeve.gainRatesForTask.money)} / s`,
`Hacking Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.hack, "0.00")} / s`,
`Strength Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.str, "0.00")} / s`,
`Defense Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.def, "0.00")} / s`,
`Dexterity Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.dex, "0.00")} / s`,
`Agility Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.agi, "0.00")} / s`,
`Charisma Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.cha, "0.00")} / s`
];
if (repGainText !== "") { lines.push(repGainText); }
elems.currentEarningsInfo!.innerHTML = lines.join("<br>");
elems.taskProgressBar!.innerText = "";
}
}
const universitySelectorOptions: string[] = [
"Study Computer Science",
"Data Structures",
"Networks",
"Algorithms",
"Management",
"Leadership"
];
const gymSelectorOptions: string[] = [
"Train Strength",
"Train Defense",
"Train Dexterity",
"Train Agility"
];
// Whenever a new task is selected, the "details" selector must update accordingly
function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSleeves: Sleeve[]) {
if (playerRef == null) {
throw new Error(`playerRef is null in updateSleeveTaskSelector()`);
}
// Array of all companies that other sleeves are working at
const forbiddenCompanies: string[] = [];
for (const otherSleeve of allSleeves) {
if (sleeve === otherSleeve) { continue; }
if (otherSleeve.currentTask === SleeveTaskType.Company) {
forbiddenCompanies.push(otherSleeve.currentTaskLocation);
}
}
// Array of all factions that other sleeves are working for
const forbiddenFactions: string[] = [];
for (const otherSleeve of allSleeves) {
if (sleeve === otherSleeve) { continue; }
if (otherSleeve.currentTask === SleeveTaskType.Faction) {
forbiddenFactions.push(otherSleeve.currentTaskLocation);
}
}
// Reset Selectors
removeChildrenFromElement(elems.taskDetailsSelector);
removeChildrenFromElement(elems.taskDetailsSelector2);
elems.taskDetailsSelector2 = clearEventListeners(elems.taskDetailsSelector2!) as HTMLSelectElement;
const value: string = getSelectValue(elems.taskSelector);
switch(value) {
case "Work for Company":
let companyCount: number = 0;
const allJobs: string[] = Object.keys(playerRef!.jobs!);
for (let i = 0; i < allJobs.length; ++i) {
if (!forbiddenCompanies.includes(allJobs[i])) {
elems.taskDetailsSelector!.add(createOptionElement(allJobs[i]));
// Set initial value of the 'Details' selector
if (sleeve.currentTaskLocation === allJobs[i]) {
elems.taskDetailsSelector!.selectedIndex = companyCount;
}
++companyCount;
}
elems.taskDetailsSelector2!.add(createOptionElement("------"));
}
break;
case "Work for Faction":
let factionCount: number = 0;
for (let i = 0; i < playerRef!.factions!.length; ++i) {
const fac: string = playerRef!.factions[i]!;
if (!forbiddenFactions.includes(fac)) {
elems.taskDetailsSelector!.add(createOptionElement(fac));
// Set initial value of the 'Details' Selector
if (sleeve.currentTaskLocation === fac) {
elems.taskDetailsSelector!.selectedIndex = factionCount;
}
++factionCount;
}
}
// The available faction work types depends on the faction
elems.taskDetailsSelector!.addEventListener("change", () => {
const facName = getSelectValue(elems.taskDetailsSelector!);
const faction: Faction | null = Factions[facName];
if (faction == null) {
console.warn(`Invalid faction name when trying to update Sleeve Task Selector: ${facName}`);
return;
}
const facInfo = faction.getInfo();
removeChildrenFromElement(elems.taskDetailsSelector2!);
let numOptionsAdded = 0;
if (facInfo.offerHackingWork) {
elems.taskDetailsSelector2!.add(createOptionElement("Hacking Contracts"));
if (sleeve.factionWorkType === FactionWorkType.Hacking) {
elems.taskDetailsSelector2!.selectedIndex = numOptionsAdded;
}
++numOptionsAdded;
}
if (facInfo.offerFieldWork) {
elems.taskDetailsSelector2!.add(createOptionElement("Field Work"));
if (sleeve.factionWorkType === FactionWorkType.Field) {
elems.taskDetailsSelector2!.selectedIndex = numOptionsAdded;
}
++numOptionsAdded;
}
if (facInfo.offerSecurityWork) {
elems.taskDetailsSelector2!.add(createOptionElement("Security Work"));
if (sleeve.factionWorkType === FactionWorkType.Security) {
elems.taskDetailsSelector2!.selectedIndex = numOptionsAdded;
}
++numOptionsAdded;
}
});
elems.taskDetailsSelector!.dispatchEvent(new Event("change"));
break;
case "Commit Crime":
let i = 0;
for (const crimeLabel in Crimes) {
const name: string = Crimes[crimeLabel].name;
elems.taskDetailsSelector!.add(createOptionElement(name, crimeLabel));
// Set initial value for crime type
if (sleeve.crimeType === "") { continue; }
const crime: Crime | null = Crimes[sleeve.crimeType];
if (crime == null) { continue; }
if (name === crime!.name) {
elems.taskDetailsSelector!.selectedIndex = i;
}
++i;
}
elems.taskDetailsSelector2!.add(createOptionElement("------"));
break;
case "Take University Course":
// First selector has class type
for (let i = 0; i < universitySelectorOptions.length; ++i) {
elems.taskDetailsSelector!.add(createOptionElement(universitySelectorOptions[i]));
}
// Second selector has which university
switch (sleeve.city) {
case Cities.Aevum:
elems.taskDetailsSelector2!.add(createOptionElement(Locations.AevumSummitUniversity));
break;
case Cities.Sector12:
elems.taskDetailsSelector2!.add(createOptionElement(Locations.Sector12RothmanUniversity));
break;
case Cities.Volhaven:
elems.taskDetailsSelector2!.add(createOptionElement(Locations.VolhavenZBInstituteOfTechnology));
break;
default:
elems.taskDetailsSelector2!.add(createOptionElement("No university available in city!"));
break;
}
break;
case "Workout at Gym":
// First selector has what stat is being trained
for (let i = 0; i < gymSelectorOptions.length; ++i) {
elems.taskDetailsSelector!.add(createOptionElement(gymSelectorOptions[i]));
// Set initial value
if (sleeve.gymStatType === gymSelectorOptions[i]) {
elems.taskDetailsSelector!.selectedIndex = i;
}
}
// Second selector has gym
// In this switch statement we also set the initial value of the second selector
switch (sleeve.city) {
case Cities.Aevum:
elems.taskDetailsSelector2!.add(createOptionElement(Locations.AevumCrushFitnessGym));
elems.taskDetailsSelector2!.add(createOptionElement(Locations.AevumSnapFitnessGym));
// Set initial value
if (sleeve.currentTaskLocation === Locations.AevumCrushFitnessGym) {
elems.taskDetailsSelector2!.selectedIndex = 0;
} else if (sleeve.currentTaskLocation === Locations.AevumSnapFitnessGym) {
elems.taskDetailsSelector2!.selectedIndex = 1;
}
break;
case Cities.Sector12:
elems.taskDetailsSelector2!.add(createOptionElement(Locations.Sector12IronGym));
elems.taskDetailsSelector2!.add(createOptionElement(Locations.Sector12PowerhouseGym));
// Set initial value
if (sleeve.currentTaskLocation === Locations.Sector12IronGym) {
elems.taskDetailsSelector2!.selectedIndex = 0;
} else if (sleeve.currentTaskLocation === Locations.Sector12PowerhouseGym) {
elems.taskDetailsSelector2!.selectedIndex = 1;
}
break;
case Cities.Volhaven:
elems.taskDetailsSelector2!.add(createOptionElement(Locations.VolhavenMilleniumFitnessGym));
break;
default:
elems.taskDetailsSelector2!.add(createOptionElement("No gym available in city!"));
break;
}
break;
case "Shock Recovery":
case "Synchronize":
case "------":
// No options in "Details" selector
elems.taskDetailsSelector!.add(createOptionElement("------"));
elems.taskDetailsSelector2!.add(createOptionElement("------"));
return;
default:
break;
}
}
function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): boolean {
try {
if (playerRef == null) {
throw new Error("playerRef is null in Sleeve UI's setSleeveTask()");
}
const taskValue: string = getSelectValue(elems.taskSelector);
const detailValue: string = getSelectValue(elems.taskDetailsSelector);
const detailValue2: string = getSelectValue(elems.taskDetailsSelector2);
let res: boolean = false;
switch(taskValue) {
case "------":
elems.taskDescription!.innerText = "This sleeve is currently idle";
break;
case "Work for Company":
res = sleeve.workForCompany(playerRef!, detailValue);
break;
case "Work for Faction":
res = sleeve.workForFaction(playerRef!, detailValue, detailValue2);
break;
case "Commit Crime":
res = sleeve.commitCrime(playerRef!, detailValue);
break;
case "Take University Course":
res = sleeve.takeUniversityCourse(playerRef!, detailValue2, detailValue);
break;
case "Workout at Gym":
res = sleeve.workoutAtGym(playerRef!, detailValue2, detailValue);
break;
case "Shock Recovery":
sleeve.currentTask = SleeveTaskType.Recovery;
res = true;
break;
case "Synchronize":
sleeve.currentTask = SleeveTaskType.Sync;
res = true;
break;
default:
console.error(`Invalid/Unrecognized taskValue in setSleeveTask(): ${taskValue}`);
}
if (res) {
updateSleeveTaskDescription(sleeve, elems);
} else {
switch (taskValue) {
case "Work for Faction":
elems.taskDescription!.innerText = "Failed to assign sleeve to task. This is most likely because the selected faction does not offer the selected work type.";
break;
default:
elems.taskDescription!.innerText = "Failed to assign sleeve to task. Invalid choice(s).";
break;
}
}
if (routing.isOn(Page.Sleeves)) {
updateSleevesPage();
// Update the task selector for all sleeves by triggering a change event
for (const e of UIElems.sleeves!) {
e.taskSelector!.dispatchEvent(new Event('change'));
}
}
return res;
} catch(e) {
console.error(`Exception caught in setSleeveTask(): ${e}`);
exceptionAlert(e);
return false;
}
}
function updateSleeveTaskDescription(sleeve: Sleeve, elems: ISleeveUIElems): void {
try {
if (playerRef == null) {
throw new Error("playerRef is null in Sleeve UI's setSleeveTask()");
}
const taskValue: string = getSelectValue(elems.taskSelector);
const detailValue: string = getSelectValue(elems.taskDetailsSelector);
const detailValue2: string = getSelectValue(elems.taskDetailsSelector2);
switch(taskValue) {
case "------":
elems.taskDescription!.innerText = "This sleeve is currently idle";
break;
case "Work for Company":
elems.taskDescription!.innerText = `This sleeve is currently working your job at ${sleeve.currentTaskLocation}.`;
break;
case "Work for Faction":
elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ${sleeve.currentTaskLocation}.`;
break;
case "Commit Crime":
elems.taskDescription!.innerText = `This sleeve is currently attempting to ${Crimes[detailValue].type} (Success Rate: ${numeralWrapper.formatPercentage(Crimes[detailValue].successRate(playerRef))}).`;
break;
case "Take University Course":
elems.taskDescription!.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`;
break;
case "Workout at Gym":
elems.taskDescription!.innerText = `This sleeve is currently working out at ${sleeve.currentTaskLocation}.`;
break;
case "Shock Recovery":
elems.taskDescription!.innerText = "This sleeve is currently set to focus on shock recovery. This causes " +
"the Sleeve's shock to decrease at a faster rate.";
break;
case "Synchronize":
elems.taskDescription!.innerText = "This sleeve is currently set to synchronize with the original consciousness. " +
"This causes the Sleeve's synchronization to increase."
break;
default:
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${taskValue}`);
}
} catch(e) {
console.error(`Exception caught in updateSleeveTaskDescription(): ${e}`);
exceptionAlert(e);
}
}

@ -0,0 +1,36 @@
export const SleeveFaq: string =
[
"<strong><u>How do sleeves work?</strong></u><br>",
"Sleeves are essentially clones. You can use them to perform any work type",
"action, such as working for a company/faction or committing a crime.",
"Having sleeves perform these tasks earns you money, experience, and reputation.<br><br>",
"Sleeves are their own individuals, which means they each have their own",
"experience and stats.<br><br>",
"When a sleeve earns experience, it earns experience for itself, the player's",
"original 'consciousness', as well as all of the player's other sleeves.<br><br>",
"<strong><u>What is Synchronization (Sync)?</strong></u><br>",
"Synchronization is a measure of how aligned your consciousness is with",
"that of your Duplicate Sleeves. It is a numerical value between 1 and 100, and",
"it affects how much experience is earned when the sleeve is performing a task.<br><br>",
"Let N be the sleeve's synchronization. When the sleeve earns experience by performing a",
"task, both the sleeve and the player's original host consciousness earn N%",
"of the amount of experience normally earned by the task. All of the player's",
"other sleeves earn ((N/100)^2 * 100)% of the experience.<br><br>",
"Synchronization can be increased by assigning sleeves to the 'Synchronize' task.<br><br>",
"<strong><u>What is Shock?</u></strong><br>",
"Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new",
"body. It is a numerical value between 0 and 99, where 99 indicates full shock and 0 indicates",
"no shock. Shock affects the amount of experience earned by the sleeve.<br><br>",
"Sleeve shock slowly decreases over time. You can further increase the rate at which",
"it decreases by assigning sleeves to the 'Shock Recovery' task.<br><br>",
"<strong><u>Why can't I work for this company or faction?</u></strong><br>",
"Only one of your sleeves can work for a given company/faction a time.",
"To clarify further, if you have two sleeves they can work for two different",
"companies, but they cannot both work for the same company.<br><br>",
"<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><br>",
"Sleeves are reset when switching BitNodes, but not when installing Augmentations."
].join(" ");

@ -1,8 +1,9 @@
import { Augmentations,
applyAugmentation,
AugmentationNames,
PlayerOwnedAugmentation } from "./Augmentations";
import { BitNodeMultipliers } from "./BitNodeMultipliers";
import { Augmentations } from "./Augmentation/Augmentations";
import { applyAugmentation } from "./Augmentation/AugmentationHelpers";
import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { Bladeburner } from "./Bladeburner";
import { CodingContractRewardType } from "./CodingContracts";
import { Company } from "./Company/Company";
import { Companies } from "./Company/Companies";
@ -13,7 +14,8 @@ import * as posNames from "./Company/data/CompanyPosi
import {CONSTANTS} from "./Constants";
import { Corporation } from "./Corporation/Corporation";
import { Programs } from "./Programs/Programs";
import {determineCrimeSuccess, Crimes} from "./Crimes";
import { determineCrimeSuccess } from "./Crime/CrimeHelpers";
import { Crimes } from "./Crime/Crimes";
import {Engine} from "./engine";
import { Faction } from "./Faction/Faction";
import { Factions } from "./Faction/Factions";
@ -21,10 +23,12 @@ import { displayFactionContent } from "./Faction/FactionHelpers";
import {Gang, resetGangs} from "./Gang";
import {Locations} from "./Locations";
import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import {AllServers, Server, AddToAllServers} from "./Server";
import {Settings} from "./Settings";
import {Settings} from "./Settings/Settings";
import {SpecialServerIps, SpecialServerNames} from "./SpecialServerIps";
import {SourceFiles, applySourceFile} from "./SourceFile";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import Decimal from "decimal.js";
import {numeralWrapper} from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
@ -98,9 +102,13 @@ function PlayerObject() {
this.city = Locations.Sector12;
this.location = "";
//Company Information
// Jobs that the player holds
// Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object)
// The CompanyPosition name must match a key value in CompanyPositions
this.jobs = {};
// Company at which player is CURRENTLY working (only valid when the player is actively working)
this.companyName = ""; // Name of Company. Must match a key value in Companies map
this.companyPosition = ""; // Name of Company Position. Must match a key value in CompanyPositions map
//Servers
this.currentServer = ""; //IP address of Server currently being accessed through terminal
@ -190,6 +198,11 @@ function PlayerObject() {
this.bladeburner_analysis_mult = 1; //Field Analysis Only
this.bladeburner_success_chance_mult = 1;
// Sleeves & Re-sleeving
this.sleeves = [];
this.resleeves = [];
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant
//bitnode
this.bitNodeN = 1;
@ -256,7 +269,7 @@ PlayerObject.prototype.prestigeAugmentation = function() {
this.location = "";
this.companyName = "";
this.companyPosition = "";
this.jobs = {};
this.purchasedServers = [];
@ -265,6 +278,14 @@ PlayerObject.prototype.prestigeAugmentation = function() {
this.queuedAugmentations = [];
this.resleeves = [];
for (let i = 0; i < this.sleeves.length; ++i) {
if (this.sleeves[i] instanceof Sleeve) {
this.sleeves[i].resetTaskStatus();
}
}
this.isWorking = false;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
@ -336,7 +357,7 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.location = "";
this.companyName = "";
this.companyPosition = "";
this.jobs = {};
this.purchasedServers = [];
@ -346,6 +367,14 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.queuedAugmentations = [];
this.augmentations = [];
this.resleeves = [];
// Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant;
for (let i = 0; i < this.sleeves.length; ++i) {
this.sleeves[i] = new Sleeve();
}
this.isWorking = false;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
@ -434,11 +463,11 @@ PlayerObject.prototype.calculateSkill = function(exp, mult=1) {
PlayerObject.prototype.updateSkillLevels = function() {
this.hacking_skill = Math.max(1, Math.floor(this.calculateSkill(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier)));
this.strength = this.calculateSkill(this.strength_exp, this.strength_mult);
this.defense = this.calculateSkill(this.defense_exp, this.defense_mult);
this.dexterity = this.calculateSkill(this.dexterity_exp, this.dexterity_mult);
this.agility = this.calculateSkill(this.agility_exp, this.agility_mult);
this.charisma = this.calculateSkill(this.charisma_exp, this.charisma_mult);
this.strength = Math.max(1, Math.floor(this.calculateSkill(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier)));
this.defense = Math.max(1, Math.floor(this.calculateSkill(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier)));
this.dexterity = Math.max(1, Math.floor(this.calculateSkill(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier)));
this.agility = Math.max(1, Math.floor(this.calculateSkill(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier)));
this.charisma = Math.max(1, Math.floor(this.calculateSkill(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier)));
if (this.intelligence > 0) {
this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp));
@ -524,6 +553,14 @@ PlayerObject.prototype.loseMoney = function(money) {
this.money = this.money.minus(money);
}
PlayerObject.prototype.canAfford = function(cost) {
if (isNaN(cost)) {
console.error(`NaN passed into Player.canAfford()`);
return false;
}
return this.money.gte(cost);
}
PlayerObject.prototype.gainHackingExp = function(exp) {
if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainHackingExp()"); return;
@ -665,9 +702,10 @@ PlayerObject.prototype.processWorkEarnings = function(numCycles=1) {
}
/* Working for Company */
PlayerObject.prototype.startWork = function() {
PlayerObject.prototype.startWork = function(companyName) {
this.resetWorkStatus();
this.isWorking = true;
this.companyName = companyName;
this.workType = CONSTANTS.WorkTypeCompany;
this.workHackExpGainRate = this.getWorkHackExpGain();
@ -718,8 +756,10 @@ PlayerObject.prototype.work = function(numCycles) {
companyRep = comp.playerReputation;
}
const position = this.jobs[this.companyName];
var txt = document.getElementById("work-in-progress-text");
txt.innerHTML = "You are currently working as a " + this.companyPosition +
txt.innerHTML = "You are currently working as a " + position +
" at " + this.companyName + " (Current Company Reputation: " +
numeralWrapper.format(companyRep, '0,0') + ")<br><br>" +
"You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "<br><br>" +
@ -786,9 +826,10 @@ PlayerObject.prototype.finishWork = function(cancelled, sing=false) {
this.resetWorkStatus();
}
PlayerObject.prototype.startWorkPartTime = function() {
PlayerObject.prototype.startWorkPartTime = function(companyName) {
this.resetWorkStatus();
this.isWorking = true;
this.companyName = companyName;
this.workType = CONSTANTS.WorkTypeCompanyPartTime;
this.workHackExpGainRate = this.getWorkHackExpGain();
@ -838,9 +879,11 @@ PlayerObject.prototype.workPartTime = function(numCycles) {
companyRep = comp.playerReputation;
}
const position = this.jobs[this.companyName];
var txt = document.getElementById("work-in-progress-text");
txt.innerHTML = "You are currently working as a " + this.companyPosition +
" at " + Player.companyName + " (Current Company Reputation: " +
txt.innerHTML = "You are currently working as a " + position +
" at " + this.companyName + " (Current Company Reputation: " +
numeralWrapper.format(companyRep, '0,0') + ")<br><br>" +
"You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "<br><br>" +
"You have earned: <br><br>" +
@ -1073,9 +1116,10 @@ PlayerObject.prototype.getWorkMoneyGain = function() {
if (hasBn11SF) { bn11Mult = 1 + (company.favor / 100); }
// Get base salary
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (companyPosition == null) {
console.error(`Could not find CompanyPosition object for ${this.companyPosition}. Work salary will be 0`);
console.error(`Could not find CompanyPosition object for ${companyPositionName}. Work salary will be 0`);
return 0;
}
@ -1085,10 +1129,11 @@ PlayerObject.prototype.getWorkMoneyGain = function() {
//Hack exp gained per game cycle
PlayerObject.prototype.getWorkHackExpGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work hack exp gain will be 0`].join(" "));
return 0;
}
@ -1099,10 +1144,11 @@ PlayerObject.prototype.getWorkHackExpGain = function() {
//Str exp gained per game cycle
PlayerObject.prototype.getWorkStrExpGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work str exp gain will be 0`].join(" "));
return 0;
}
@ -1113,10 +1159,11 @@ PlayerObject.prototype.getWorkStrExpGain = function() {
//Def exp gained per game cycle
PlayerObject.prototype.getWorkDefExpGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work def exp gain will be 0`].join(" "));
return 0;
}
@ -1127,10 +1174,11 @@ PlayerObject.prototype.getWorkDefExpGain = function() {
//Dex exp gained per game cycle
PlayerObject.prototype.getWorkDexExpGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work dex exp gain will be 0`].join(" "));
return 0;
}
@ -1141,10 +1189,11 @@ PlayerObject.prototype.getWorkDexExpGain = function() {
//Agi exp gained per game cycle
PlayerObject.prototype.getWorkAgiExpGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work agi exp gain will be 0`].join(" "));
return 0;
}
@ -1155,10 +1204,11 @@ PlayerObject.prototype.getWorkAgiExpGain = function() {
//Charisma exp gained per game cycle
PlayerObject.prototype.getWorkChaExpGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work cha exp gain will be 0`].join(" "));
return 0;
}
@ -1169,10 +1219,11 @@ PlayerObject.prototype.getWorkChaExpGain = function() {
//Reputation gained per game cycle
PlayerObject.prototype.getWorkRepGain = function() {
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
const companyPositionName = this.jobs[this.companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`or CompanyPosition object for ${companyPositionName}.`,
`Work rep gain will be 0`].join(" "));
return 0;
}
@ -1455,7 +1506,9 @@ PlayerObject.prototype.finishClass = function(sing=false) {
}
//The EXP and $ gains are hardcoded. Time is in ms
PlayerObject.prototype.startCrime = function(hackExp, strExp, defExp, dexExp, agiExp, chaExp, money, time, singParams=null) {
PlayerObject.prototype.startCrime = function(crimeType, hackExp, strExp, defExp, dexExp, agiExp, chaExp, money, time, singParams=null) {
this.crimeType = crimeType;
this.resetWorkStatus();
this.isWorking = true;
this.workType = CONSTANTS.WorkTypeCrime;
@ -1669,7 +1722,7 @@ PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
if (this.companyName !== "") {
currCompany = Companies[this.companyName];
}
const currPositionName = this.companyPosition;
const currPositionName = this.jobs[this.companyName];
// Get company that's being applied to
const company = Companies[this.location]; //Company being applied to
@ -1726,33 +1779,14 @@ PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
}
}
//Lose reputation from a Company if you are leaving it for another job
let leaveCompany = false;
let oldCompanyName = "";
if (currCompany != null) {
if (currCompany.name != company.name) {
leaveCompany = true;
oldCompanyName = currCompany.name;
currCompany.playerReputation -= 1000;
if (currCompany.playerReputation < 0) { currCompany.playerReputation = 0; }
}
}
this.companyName = company.name;
this.companyPosition = pos.name;
this.jobs[company.name] = pos.name;
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (leaveCompany) {
if (sing) { return true; }
dialogBoxCreate([`Congratulations! You were offered a new job at ${this.companyName} as a ${pos.name}!`,
`You lost 1000 reputation at your old company ${oldCompanyName} because you left.`].join("<br>"));
} else {
if (sing) { return true; }
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
}
if (sing) { return true; }
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
Engine.loadLocationContent();
}
@ -1772,7 +1806,8 @@ PlayerObject.prototype.getNextCompanyPosition = function(company, entryPosType)
//If the entry pos type and the player's current position have the same type,
//return the player's "nextCompanyPosition". Otherwise return the entryposType
//Employed at this company, so just return the next position if it exists.
const currentPosition = CompanyPositions[this.companyPosition];
const currentPositionName = this.jobs[this.companyName];
const currentPosition = CompanyPositions[currentPositionName];
if ((currentPosition.isSoftwareJob() && entryPosType.isSoftwareJob()) ||
(currentPosition.isITJob() && entryPosType.isITJob()) ||
(currentPosition.isBusinessJob() && entryPosType.isBusinessJob()) ||
@ -1849,7 +1884,7 @@ PlayerObject.prototype.applyForEmployeeJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) {
this.companyName = company.name;
this.companyPosition = posNames.MiscCompanyPositions[1];
this.jobs[company.name] = posNames.MiscCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1865,7 +1900,7 @@ PlayerObject.prototype.applyForPartTimeEmployeeJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) {
this.companyName = company.name;
this.companyPosition = posNames.PartTimeCompanyPositions[1];
this.jobs[company.name] = posNames.PartTimeCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1881,7 +1916,7 @@ PlayerObject.prototype.applyForWaiterJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) {
this.companyName = company.name;
this.companyPosition = posNames.MiscCompanyPositions[0];
this.jobs[company.name] = posNames.MiscCompanyPositions[0];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1897,7 +1932,7 @@ PlayerObject.prototype.applyForPartTimeWaiterJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) {
this.companyName = company.name;
this.companyPosition = posNames.PartTimeCompanyPositions[0];
this.jobs[company.name] = posNames.PartTimeCompanyPositions[0];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1992,6 +2027,9 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
companyRep = company.playerReputation;
}
const allCompanies = Object.keys(this.jobs);
const allPositions = Object.values(this.jobs);
//Illuminati
var illuminatiFac = Factions["Illuminati"];
if (!illuminatiFac.isBanned && !illuminatiFac.isMember && !illuminatiFac.alreadyInvited &&
@ -2030,14 +2068,14 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
//ECorp
var ecorpFac = Factions["ECorp"];
if (!ecorpFac.isBanned && !ecorpFac.isMember && !ecorpFac.alreadyInvited &&
this.companyName == Locations.AevumECorp && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.AevumECorp) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(ecorpFac);
}
//MegaCorp
var megacorpFac = Factions["MegaCorp"];
if (!megacorpFac.isBanned && !megacorpFac.isMember && !megacorpFac.alreadyInvited &&
this.companyName == Locations.Sector12MegaCorp && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.Sector12MegaCorp) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(megacorpFac);
}
@ -2045,42 +2083,42 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
var bachmanandassociatesFac = Factions["Bachman & Associates"];
if (!bachmanandassociatesFac.isBanned && !bachmanandassociatesFac.isMember &&
!bachmanandassociatesFac.alreadyInvited &&
this.companyName == Locations.AevumBachmanAndAssociates && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.AevumBachmanAndAssociates) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(bachmanandassociatesFac);
}
//Blade Industries
var bladeindustriesFac = Factions["Blade Industries"];
if (!bladeindustriesFac.isBanned && !bladeindustriesFac.isMember && !bladeindustriesFac.alreadyInvited &&
this.companyName == Locations.Sector12BladeIndustries && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.Sector12BladeIndustries) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(bladeindustriesFac);
}
//NWO
var nwoFac = Factions["NWO"];
if (!nwoFac.isBanned && !nwoFac.isMember && !nwoFac.alreadyInvited &&
this.companyName == Locations.VolhavenNWO && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.VolhavenNWO) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(nwoFac);
}
//Clarke Incorporated
var clarkeincorporatedFac = Factions["Clarke Incorporated"];
if (!clarkeincorporatedFac.isBanned && !clarkeincorporatedFac.isMember && !clarkeincorporatedFac.alreadyInvited &&
this.companyName == Locations.AevumClarkeIncorporated && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.AevumClarkeIncorporated) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(clarkeincorporatedFac);
}
//OmniTek Incorporated
var omnitekincorporatedFac = Factions["OmniTek Incorporated"];
if (!omnitekincorporatedFac.isBanned && !omnitekincorporatedFac.isMember && !omnitekincorporatedFac.alreadyInvited &&
this.companyName == Locations.VolhavenOmniTekIncorporated && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.VolhavenOmniTekIncorporated) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(omnitekincorporatedFac);
}
//Four Sigma
var foursigmaFac = Factions["Four Sigma"];
if (!foursigmaFac.isBanned && !foursigmaFac.isMember && !foursigmaFac.alreadyInvited &&
this.companyName == Locations.Sector12FourSigma && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.Sector12FourSigma) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(foursigmaFac);
}
@ -2088,7 +2126,7 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
var kuaigonginternationalFac = Factions["KuaiGong International"];
if (!kuaigonginternationalFac.isBanned && !kuaigonginternationalFac.isMember &&
!kuaigonginternationalFac.alreadyInvited &&
this.companyName == Locations.ChongqingKuaiGongInternational && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
allCompanies.includes(Locations.ChongqingKuaiGongInternational) && companyRep >= CONSTANTS.CorpFactionRepRequirement) {
invitedFactions.push(kuaigonginternationalFac);
}
@ -2101,7 +2139,7 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember &&
!fulcrumsecrettechonologiesFac.alreadyInvited &&
fulcrumSecretServer.manuallyHacked &&
this.companyName == Locations.AevumFulcrumTechnologies && companyRep >= 250000) {
allCompanies.includes(Locations.AevumFulcrumTechnologies) && companyRep >= 250000) {
invitedFactions.push(fulcrumsecrettechonologiesFac);
}
}
@ -2184,8 +2222,8 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
if (!speakersforthedeadFac.isBanned && !speakersforthedeadFac.isMember && !speakersforthedeadFac.alreadyInvited &&
this.hacking_skill >= 100 && this.strength >= 300 && this.defense >= 300 &&
this.dexterity >= 300 && this.agility >= 300 && this.numPeopleKilled >= 30 &&
this.karma <= -45 && this.companyName != Locations.Sector12CIA &&
this.companyName != Locations.Sector12NSA) {
this.karma <= -45 && !allCompanies.includes(Locations.Sector12CIA) &&
!allCompanies.includes(Locations.Sector12NSA)) {
invitedFactions.push(speakersforthedeadFac);
}
@ -2194,8 +2232,8 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
if (!thedarkarmyFac.isBanned && !thedarkarmyFac.isMember && !thedarkarmyFac.alreadyInvited &&
this.hacking_skill >= 300 && this.strength >= 300 && this.defense >= 300 &&
this.dexterity >= 300 && this.agility >= 300 && this.city == Locations.Chongqing &&
this.numPeopleKilled >= 5 && this.karma <= -45 && this.companyName != Locations.Sector12CIA &&
this.companyName != Locations.Sector12NSA) {
this.numPeopleKilled >= 5 && this.karma <= -45 && !allCompanies.includes(Locations.Sector12CIA) &&
!allCompanies.includes(Locations.Sector12NSA)) {
invitedFactions.push(thedarkarmyFac);
}
@ -2206,18 +2244,16 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
this.dexterity >= 200 && this.agility >= 200 &&
(this.city == Locations.Aevum || this.city == Locations.Sector12) &&
this.money.gte(10000000) && this.karma <= -90 &&
this.companyName != Locations.Sector12CIA && this.companyName != Locations.Sector12NSA) {
!allCompanies.includes(Locations.Sector12CIA) && !allCompanies.includes(Locations.Sector12NSA)) {
invitedFactions.push(thesyndicateFac);
}
//Silhouette
var silhouetteFac = Factions["Silhouette"];
const companyPosition = CompanyPositions[this.companyPosition];
if (!silhouetteFac.isBanned && !silhouetteFac.isMember && !silhouetteFac.alreadyInvited &&
companyPosition != null &&
(companyPosition.name == "Chief Technology Officer" ||
companyPosition.name == "Chief Financial Officer" ||
companyPosition.name == "Chief Executive Officer") &&
(allPositions.includes("Chief Technology Officer") ||
allPositions.includes("Chief Financial Officer") ||
allPositions.includes("Chief Executive Officer")) &&
this.money.gte(15000000) && this.karma <= -22) {
invitedFactions.push(silhouetteFac);
}
@ -2289,6 +2325,18 @@ PlayerObject.prototype.startGang = function(factionName, hacking) {
this.gang = new Gang(factionName, hacking);
}
/*************** Corporation ****************/
PlayerObject.prototype.hasCorporation = function() {
if (this.corporation == null) { return false; }
return (this.corporation instanceof Corporation);
}
/*************** Bladeburner ****************/
PlayerObject.prototype.inBladeburner = function() {
if (this.bladeburner == null) { return false; }
return (this.bladeburner instanceof Bladeburner);
}
/************* BitNodes **************/
PlayerObject.prototype.setBitNodeNumber = function(n) {
this.bitNodeN = n;

@ -1,7 +1,9 @@
import {deleteActiveScriptsItem} from "./ActiveScriptsUI";
import {Augmentations, augmentationExists,
initAugmentations, AugmentationNames} from "./Augmentations";
import {initBitNodeMultipliers} from "./BitNode";
import { Augmentations } from "./Augmentation/Augmentations";
import { augmentationExists,
initAugmentations } from "./Augmentation/AugmentationHelpers";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { initBitNodeMultipliers } from "./BitNode/BitNode";
import {Bladeburner} from "./Bladeburner";
import {writeCinematicText} from "./CinematicText";
import {Companies, initCompanies} from "./Company/Companies";
@ -24,12 +26,13 @@ import {AllServers, AddToAllServers,
initForeignServers, Server,
prestigeAllServers,
prestigeHomeComputer} from "./Server";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import {SpecialServerIps, SpecialServerIpsMap,
prestigeSpecialServerIps,
SpecialServerNames} from "./SpecialServerIps";
import {initStockMarket, initSymbolToStockMap,
stockMarketContentCreated,
setStockMarketContentCreated} from "./StockMarket";
setStockMarketContentCreated} from "./StockMarket/StockMarket";
import {Terminal, postNetburnerText} from "./Terminal";
import Decimal from "decimal.js";
import {dialogBoxCreate} from "../utils/DialogBox";
@ -169,6 +172,7 @@ function prestigeAugmentation() {
//Prestige by destroying Bit Node and gaining a Source File
function prestigeSourceFile() {
initBitNodeMultipliers();
updateSourceFileFlags(Player);
Player.prestigeSourceFile();
prestigeWorkerScripts(); //Delete all Worker Scripts objects
@ -307,6 +311,11 @@ function prestigeSourceFile() {
Player.hasTixApiAccess = true;
}
// Bit Node 10: Digital Carbon
if (Player.bitNodeN === 10) {
dialogBoxCreate("Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!");
}
//Reset Stock market, gang, and corporation
if (Player.hasWseAccount) {
initStockMarket();

@ -1,10 +1,11 @@
import {BitNodes} from "./BitNode";
import {Engine} from "./engine";
import {Player} from "./Player";
import {prestigeSourceFile} from "./Prestige";
import {SourceFiles, SourceFile,
PlayerOwnedSourceFile} from "./SourceFile";
import {Terminal} from "./Terminal";
import { BitNodes } from "./BitNode/BitNode";
import { Engine } from "./engine";
import { Player } from "./Player";
import { prestigeSourceFile } from "./Prestige";
import { SourceFiles,
SourceFile } from "./SourceFile";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { Terminal } from "./Terminal";
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
import {dialogBoxCreate} from "../utils/DialogBox";
@ -212,7 +213,8 @@ function loadBitVerse(destroyedBitNodeNum, flume=false) {
var elem = clearEventListeners(elemId);
if (elem == null) {return;}
if (i === 1 || i === 2 || i === 3 || i === 4 || i === 5 ||
i === 6 || i === 7 || i === 8 || i === 11 || i === 12) {
i === 6 || i === 7 || i === 8 || i === 10 || i === 11 ||
i === 12) {
elem.addEventListener("click", function() {
var bitNodeKey = "BitNode" + i;
var bitNode = BitNodes[bitNodeKey];

@ -14,9 +14,9 @@ import {loadMessages, initMessages, Messages} from "./Message";
import {Player, loadPlayer} from "./Player";
import {loadAllRunningScripts} from "./Script";
import {AllServers, loadAllServers} from "./Server";
import {Settings} from "./Settings";
import {Settings} from "./Settings/Settings";
import {loadSpecialServerIps, SpecialServerIps} from "./SpecialServerIps";
import {loadStockMarket, StockMarket} from "./StockMarket";
import {loadStockMarket, StockMarket} from "./StockMarket/StockMarket";
import {dialogBoxCreate} from "../utils/DialogBox";
import {gameOptionsBoxClose} from "../utils/GameOptions";
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
@ -151,6 +151,16 @@ function evaluateVersionCompatibility(ver) {
}
}
}
// This version allowed players to hold multiple jobs
if (ver < "0.43.0") {
if (Player.companyName !== "" && Player.companyPosition != null && Player.companyPosition !== "") {
console.log("Copied player's companyName and companyPosition properties to the Player.jobs map for v0.43.0");
Player.jobs[Player.companyName] = Player.companyPosition;
}
delete Player.companyPosition;
}
}
function loadGame(saveString) {

@ -1,19 +1,3 @@
var ace = require('brace');
var beautify = require('js-beautify').js_beautify;
require('brace/mode/javascript');
require('../netscript');
require('brace/theme/chaos');
require('brace/theme/chrome');
require('brace/theme/monokai');
require('brace/theme/solarized_dark');
require('brace/theme/solarized_light');
require('brace/theme/terminal');
require('brace/theme/twilight');
require('brace/theme/xcode');
require("brace/keybinding/vim");
require("brace/keybinding/emacs");
require("brace/ext/language_tools");
// Importing this doesn't work for some reason.
const walk = require("acorn/dist/walk");
@ -26,8 +10,11 @@ import {evaluateImport} from "./NetscriptEvaluator";
import {NetscriptFunctions} from "./NetscriptFunctions";
import {addWorkerScript, WorkerScript} from "./NetscriptWorker";
import {Player} from "./Player";
import { AceEditor } from "./ScriptEditor/Ace";
import { CodeMirrorEditor } from "./ScriptEditor/CodeMirror";
import {AllServers, processSingleServerGrowth} from "./Server";
import {Settings} from "./Settings";
import { Settings } from "./Settings/Settings";
import { EditorSetting } from "./Settings/SettingEnums";
import {post} from "./ui/postToTerminal";
import {TextFile} from "./TextFile";
import {parse, Node} from "../utils/acorn";
@ -41,47 +28,40 @@ import {createElement} from "../utils/uiHelpers/createE
import {getTimestamp} from "../utils/helpers/getTimestamp";
import {roundToTwo} from "../utils/helpers/roundToTwo";
var keybindings = {
ace: null,
vim: "ace/keyboard/vim",
emacs: "ace/keyboard/emacs",
};
function isScriptFilename(f) {
return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns");
}
var scriptEditorRamCheck = null, scriptEditorRamText = null;
function scriptEditorInit() {
//Create buttons at the bottom of script editor
var wrapper = document.getElementById("script-editor-buttons-wrapper");
// Wrapper container that holds all the buttons below the script editor
const wrapper = document.getElementById("script-editor-buttons-wrapper");
if (wrapper == null) {
console.log("Error finding 'script-editor-buttons-wrapper'");
return;
console.error("Could not find 'script-editor-buttons-wrapper'");
return false;
}
var beautifyButton = createElement("a", {
class:"a-link-button", display:"inline-block",
innerText:"Beautify",
// Beautify button
const beautifyButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Beautify",
clickListener:()=>{
beautifyScript();
return false;
}
});
var closeButton = createElement("a", {
class:"a-link-button", display:"inline-block",
innerText:"Save & Close (Ctrl/Cmd + b)",
clickListener:()=>{
saveAndCloseScriptEditor();
let editor = getCurrentEditor();
if (editor != null) {
editor.beautifyScript();
}
return false;
}
});
// Text that displays RAM calculation
scriptEditorRamText = createElement("p", {
display:"inline-block", margin:"10px", id:"script-editor-status-text"
});
var checkboxLabel = createElement("label", {
// Label for checkbox (defined below)
const checkboxLabel = createElement("label", {
for:"script-editor-ram-check", margin:"4px", marginTop: "8px",
innerText:"Dynamic RAM Usage Checker", color:"white",
tooltip:"Enable/Disable the dynamic RAM Usage display. You may " +
@ -89,18 +69,34 @@ function scriptEditorInit() {
"performance issues"
});
// Checkbox for enabling/disabling dynamic RAM calculation
scriptEditorRamCheck = createElement("input", {
type:"checkbox", name:"script-editor-ram-check", id:"script-editor-ram-check",
margin:"4px", marginTop: "8px",
});
scriptEditorRamCheck.checked = true;
var documentationButton = createElement("a", {
display:"inline-block", class:"a-link-button", innerText:"Netscript Documentation",
// Link to Netscript documentation
const documentationButton = createElement("a", {
class: "std-button",
display: "inline-block",
href:"https://bitburner.readthedocs.io/en/latest/index.html",
target:"_blank"
innerText:"Netscript Documentation",
target:"_blank",
});
// Save and Close button
const closeButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Save & Close (Ctrl/Cmd + b)",
clickListener:()=>{
saveAndCloseScriptEditor();
return false;
}
});
// Add all buttons to the UI
wrapper.appendChild(beautifyButton);
wrapper.appendChild(closeButton);
wrapper.appendChild(scriptEditorRamText);
@ -108,146 +104,86 @@ function scriptEditorInit() {
wrapper.appendChild(checkboxLabel);
wrapper.appendChild(documentationButton);
//Initialize ACE Script editor
var editor = ace.edit('javascript-editor');
editor.getSession().setMode('ace/mode/netscript');
editor.setTheme('ace/theme/monokai');
document.getElementById('javascript-editor').style.fontSize='16px';
editor.setOption("showPrintMargin", false);
// Initialize editors
const initParams = {
saveAndCloseFn: saveAndCloseScriptEditor,
quitFn: Engine.loadTerminalContent,
}
/* Script editor options */
//Theme
var themeDropdown = document.getElementById("script-editor-option-theme");
if (Settings.EditorTheme) {
var initialIndex = 2;
for (var i = 0; i < themeDropdown.options.length; ++i) {
if (themeDropdown.options[i].value === Settings.EditorTheme) {
initialIndex = i;
break;
}
AceEditor.init(initParams);
CodeMirrorEditor.init(initParams);
// Setup the selector for which Editor to use
const editorSelector = document.getElementById("script-editor-option-editor");
if (editorSelector == null) {
console.error(`Could not find DOM Element for editor selector (id=script-editor-option-editor)`);
return false;
}
for (let i = 0; i < editorSelector.options.length; ++i) {
if (editorSelector.options[i].value === Settings.Editor) {
editorSelector.selectedIndex = i;
break;
}
themeDropdown.selectedIndex = initialIndex;
} else {
themeDropdown.selectedIndex = 2;
}
themeDropdown.onchange = function() {
var val = themeDropdown.value;
Settings.EditorTheme = val;
var themePath = "ace/theme/" + val.toLowerCase();
editor.setTheme(themePath);
};
themeDropdown.onchange();
//Keybinding
var keybindingDropdown = document.getElementById("script-editor-option-keybinding");
if (Settings.EditorKeybinding) {
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) {
initialIndex = i;
editorSelector.onchange = () => {
const opt = editorSelector.value;
switch (opt) {
case EditorSetting.Ace:
const codeMirrorCode = CodeMirrorEditor.getCode();
const codeMirrorFn = CodeMirrorEditor.getFilename();
AceEditor.create();
CodeMirrorEditor.setInvisible();
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
break;
}
case EditorSetting.CodeMirror:
const aceCode = AceEditor.getCode();
const aceFn = AceEditor.getFilename();
CodeMirrorEditor.create();
AceEditor.setInvisible();
CodeMirrorEditor.openScript(aceFn, aceCode);
break;
default:
console.error(`Unrecognized Editor Setting: ${opt}`);
return;
}
keybindingDropdown.selectedIndex = initialIndex;
} else {
keybindingDropdown.selectedIndex = 0;
}
keybindingDropdown.onchange = function() {
var val = keybindingDropdown.value;
Settings.EditorKeybinding = val;
editor.setKeyboardHandler(keybindings[val.toLowerCase()]);
};
keybindingDropdown.onchange();
//Highlight Active line
var highlightActiveChkBox = document.getElementById("script-editor-option-highlightactiveline");
highlightActiveChkBox.onchange = function() {
editor.setHighlightActiveLine(highlightActiveChkBox.checked);
};
//Show Invisibles
var showInvisiblesChkBox = document.getElementById("script-editor-option-showinvisibles");
showInvisiblesChkBox.onchange = function() {
editor.setShowInvisibles(showInvisiblesChkBox.checked);
};
//Use Soft Tab
var softTabChkBox = document.getElementById("script-editor-option-usesofttab");
softTabChkBox.onchange = function() {
editor.getSession().setUseSoftTabs(softTabChkBox.checked);
};
//Jshint Maxerr
var maxerr = document.getElementById("script-editor-option-maxerr");
var maxerrLabel = document.getElementById("script-editor-option-maxerror-value-label");
maxerrLabel.innerHTML = maxerr.value;
maxerr.onchange = function() {
editor.getSession().$worker.send("changeOptions", [{maxerr:maxerr.value}]);
maxerrLabel.innerHTML = maxerr.value;
Settings.Editor = opt;
}
//Configure some of the VIM keybindings
ace.config.loadModule('ace/keyboard/vim', function(module) {
var VimApi = module.CodeMirror.Vim;
VimApi.defineEx('write', 'w', function(cm, input) {
saveAndCloseScriptEditor();
});
VimApi.defineEx('quit', 'q', function(cm, input) {
Engine.loadTerminalContent();
});
VimApi.defineEx('xwritequit', 'x', function(cm, input) {
saveAndCloseScriptEditor();
});
VimApi.defineEx('wqwritequit', 'wq', function(cm, input) {
saveAndCloseScriptEditor();
});
});
editorSelector.onchange(); // Trigger the onchange event handler
}
//Function autocompleter
editor.setOption("enableBasicAutocompletion", true);
var autocompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {callback(null, []); return;}
var words = [];
var fns = NetscriptFunctions(null);
for (let name in fns) {
if (fns.hasOwnProperty(name)) {
words.push({
name: name,
value: name,
});
//Get functions from namespaces
const namespaces = ["bladeburner", "hacknet", "codingcontract", "gang"];
if (namespaces.includes(name)) {
let namespace = fns[name];
if (typeof namespace !== "object") {continue;}
let namespaceFns = Object.keys(namespace);
for (let i = 0; i < namespaceFns.length; ++i) {
words.push({
name: namespaceFns[i],
value: namespaceFns[i],
});
}
}
}
}
callback(null, words);
},
export function getCurrentEditor() {
switch (Settings.Editor) {
case EditorSetting.Ace:
return AceEditor;
case EditorSetting.CodeMirror:
return CodeMirrorEditor;
default:
console.log(`Invalid Editor Setting: ${Settings.Editor}`);
throw new Error(`Invalid Editor Setting: ${Settings.Editor}`);
return null;
}
editor.completers = [autocompleter];
}
//Updates RAM usage in script
async function updateScriptEditorContent() {
export async function updateScriptEditorContent() {
var filename = document.getElementById("script-editor-filename").value;
if (scriptEditorRamCheck == null || !scriptEditorRamCheck.checked || !isScriptFilename(filename)) {
scriptEditorRamText.innerText = "N/A";
return;
}
var editor = ace.edit('javascript-editor');
var code = editor.getValue();
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
scriptEditorRamText.innerText = "RAM: ERROR";
return;
}
var codeCopy = code.repeat(1);
var ramUsage = await calculateRamUsage(codeCopy);
if (ramUsage !== -1) {
@ -269,20 +205,17 @@ $(document).keydown(function(e) {
}
});
function beautifyScript() {
var editor = ace.edit('javascript-editor');
var code = editor.getValue();
code = beautify(code, {
indent_size: 4,
brace_style: "preserve-inline",
});
editor.setValue(code);
}
function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value;
var editor = ace.edit('javascript-editor');
var code = editor.getValue();
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details");
return;
}
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (filename !== "foodnstuff.script") {
@ -387,8 +320,7 @@ function Script(fn = "", code = "", server = "") {
Script.prototype.saveScript = function() {
if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename
var editor = ace.edit('javascript-editor');
var code = editor.getValue();
const code = getCurrentEditor().getCode();
this.code = code.replace(/^\s+|\s+$/g, '');
var filename = document.getElementById("script-editor-filename").value;
@ -483,8 +415,11 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
return -1;
}
} else {
const script = server.getScript(nextModule);
if (!script) return -1; // No such script on the server.
const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule);
if (!script) {
console.warn("Invalid script");
return -1; // No such script on the server.
}
code = script.code;
}
@ -1096,5 +1031,5 @@ AllServersMap.fromJSON = function(value) {
Reviver.constructors.AllServersMap = AllServersMap;
export {updateScriptEditorContent, loadAllRunningScripts, findRunningScript,
export {loadAllRunningScripts, findRunningScript,
RunningScript, Script, AllServersMap, scriptEditorInit, isScriptFilename};

319
src/ScriptEditor/Ace.js Normal file

@ -0,0 +1,319 @@
import { ScriptEditor } from "./ScriptEditor";
const ace = require('brace');
require('brace/mode/javascript');
require('./AceNetscriptMode');
require('brace/theme/chaos');
require('brace/theme/chrome');
require('brace/theme/monokai');
require('brace/theme/solarized_dark');
require('brace/theme/solarized_light');
require('brace/theme/terminal');
require('brace/theme/twilight');
require('brace/theme/xcode');
require("brace/keybinding/vim");
require("brace/keybinding/emacs");
require("brace/ext/language_tools");
import { NetscriptFunctions } from "../NetscriptFunctions";
import { Settings } from "../Settings/Settings";
import { AceKeybindingSetting } from "../Settings/SettingEnums";
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../utils/uiHelpers/createOptionElement";
import { getSelectText,
getSelectValue } from "../../utils/uiHelpers/getSelectData";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
// Wrapper for Ace editor
const Keybindings = {
ace: null,
vim: "ace/keyboard/vim",
emacs: "ace/keyboard/emacs",
};
function validateInitializationParamters(params) {
if (params.saveAndCloseFn == null) { return false; } // Save & close button function
if (params.quitFn == null) { return false; } // Quitting editor, aka Engine.loadTerminalContent
return true;
}
class AceEditorWrapper extends ScriptEditor {
constructor() {
super();
this.vimCommandDisplayWrapper = null;
}
init(params) {
if (this.editor != null) {
console.error(`AceEditor.init() called when it's already initialized`);
return false;
}
// Validate/Sanitize input
if (!validateInitializationParamters(params)) {
console.error(`'params' argument passed into initAceEditor() does not have proper properties`);
return false;
}
// Store the filename input
this.filenameInput = document.getElementById("script-editor-filename");
if (this.filenameInput == null) {
console.error(`Could not get Script Editor filename element (id=script-editor-filename)`);
return false;
}
// Initialize ACE Script editor
this.editor = ace.edit('ace-editor');
this.editor.getSession().setMode('ace/mode/netscript');
this.editor.setTheme('ace/theme/monokai');
const editorElement = document.getElementById('ace-editor');
if (editorElement == null) { return false; }
editorElement.style.fontSize = '16px';
this.editor.setOption("showPrintMargin", false);
// Configure some of the VIM keybindings
ace.config.loadModule('ace/keyboard/vim', function(module) {
var VimApi = module.CodeMirror.Vim;
VimApi.defineEx('write', 'w', function(cm, input) {
params.saveAndCloseFn();
});
VimApi.defineEx('quit', 'q', function(cm, input) {
params.quitFn();
});
VimApi.defineEx('xwritequit', 'x', function(cm, input) {
params.saveAndCloseFn();
});
VimApi.defineEx('wqwritequit', 'wq', function(cm, input) {
params.saveAndCloseFn();
});
});
// Store a reference to the VIM command display
this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper");
if (this.vimCommandDisplayWrapper == null) {
console.error(`Could not get Vim Command Display element (id=codemirror-vim-command-display-wrapper)`);
return false;
}
//Function autocompleter
this.editor.setOption("enableBasicAutocompletion", true);
var autocompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {callback(null, []); return;}
var words = [];
var fns = NetscriptFunctions(null);
for (let name in fns) {
if (fns.hasOwnProperty(name)) {
words.push({
name: name,
value: name,
});
//Get functions from namespaces
const namespaces = ["bladeburner", "hacknet", "codingcontract", "gang"];
if (namespaces.includes(name)) {
let namespace = fns[name];
if (typeof namespace !== "object") {continue;}
let namespaceFns = Object.keys(namespace);
for (let i = 0; i < namespaceFns.length; ++i) {
words.push({
name: namespaceFns[i],
value: namespaceFns[i],
});
}
}
}
}
callback(null, words);
},
}
this.editor.completers = [autocompleter];
return true;
}
initialized() {
return (this.editor != null);
}
// Create the configurable Options for this Editor
create() {
function safeGetElementById(id, whatFor="") {
const elem = document.getElementById(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
function safeClearEventListeners(id, whatFor="") {
const elem = clearEventListeners(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
try {
const optionsPanel = safeGetElementById("script-editor-options-panel", "Script Editor Options Panel");
// Set editor to visible
const elem = document.getElementById("ace-editor");
if (elem instanceof HTMLElement) {
elem.style.display = "block";
}
// Make sure the Vim command display from CodeMirror is invisible
if (this.vimCommandDisplayWrapper instanceof HTMLElement) {
this.vimCommandDisplayWrapper.style.display = "none";
}
// Theme
const themeDropdown = safeClearEventListeners("script-editor-option-theme", "Theme Selector");
removeChildrenFromElement(themeDropdown);
themeDropdown.add(createOptionElement("Chaos"));
themeDropdown.add(createOptionElement("Chrome"));
themeDropdown.add(createOptionElement("Monokai"));
themeDropdown.add(createOptionElement("Solarized Dark", "Solarized_Dark"));
themeDropdown.add(createOptionElement("Solarized Light", "Solarized_Light"));
themeDropdown.add(createOptionElement("Terminal"));
themeDropdown.add(createOptionElement("Twilight"));
themeDropdown.add(createOptionElement("XCode"));
if (Settings.EditorTheme) {
var initialIndex = 2;
for (var i = 0; i < themeDropdown.options.length; ++i) {
if (themeDropdown.options[i].value === Settings.EditorTheme) {
initialIndex = i;
break;
}
}
themeDropdown.selectedIndex = initialIndex;
} else {
themeDropdown.selectedIndex = 2;
}
themeDropdown.onchange = () => {
const val = themeDropdown.value;
Settings.EditorTheme = val;
const themePath = "ace/theme/" + val.toLowerCase();
this.editor.setTheme(themePath);
};
themeDropdown.onchange();
// Keybinding
const keybindingDropdown = safeClearEventListeners("script-editor-option-keybinding", "Keybinding Selector");
removeChildrenFromElement(keybindingDropdown);
keybindingDropdown.add(createOptionElement("Ace", AceKeybindingSetting.Ace));
keybindingDropdown.add(createOptionElement("Vim", AceKeybindingSetting.Vim));
keybindingDropdown.add(createOptionElement("Emacs", AceKeybindingSetting.Emacs));
if (Settings.EditorKeybinding) {
// Sanitize the Keybinding setting
if (!(Object.values(AceKeybindingSetting).includes(Settings.EditorKeybinding))) {
Settings.EditorKeybinding = AceKeybindingSetting.Ace;
}
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) {
initialIndex = i;
break;
}
}
keybindingDropdown.selectedIndex = initialIndex;
} else {
keybindingDropdown.selectedIndex = 0;
}
keybindingDropdown.onchange = () => {
var val = keybindingDropdown.value;
Settings.EditorKeybinding = val;
this.editor.setKeyboardHandler(Keybindings[val.toLowerCase()]);
};
keybindingDropdown.onchange();
// Highlight Active line
const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox");
highlightActiveChkBox.onchange = () => {
this.editor.setHighlightActiveLine(highlightActiveChkBox.checked);
};
// Show Invisibles
const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox");
showInvisiblesChkBox.onchange = () => {
this.editor.setShowInvisibles(showInvisiblesChkBox.checked);
};
// Use Soft Tab
const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox");
softTabChkBox.onchange = () => {
this.editor.getSession().setUseSoftTabs(softTabChkBox.checked);
};
// Some helper functions for dealing with flexible options
function resetFlexibleOption(id) {
const fieldset = safeGetElementById(id);
removeChildrenFromElement(fieldset);
fieldset.style.display = "block";
return fieldset;
}
function removeFlexibleOption(id) {
// This doesn't really remove it, just sets it to invisible
const fieldset = resetFlexibleOption(id);
fieldset.style.display = "none";
return fieldset;
}
// Jshint Maxerr (Flex 1)
const flex1Fieldset = resetFlexibleOption("script-editor-option-flex1-fieldset");
const flex1Id = "script-editor-option-maxerr";
const flex1ValueLabel = createElement("em", { innerText: "200" });
flex1Fieldset.appendChild(createElement("label", {
for: flex1Id,
innerText: "Max Error Count",
}));
const flex1Input = createElement("input", {
id: flex1Id,
max: "1000",
min: "50",
name: flex1Id,
step: "1",
type: "range",
value: "200",
changeListener: () => {
this.editor.getSession().$worker.send("changeOptions", [{maxerr:flex1Input.value}]);
flex1ValueLabel.innerText = flex1Input.value;
}
});
flex1Fieldset.appendChild(flex1Input);
flex1Fieldset.appendChild(flex1ValueLabel);
// Nothing for Flex Options 2-4
removeFlexibleOption("script-editor-option-flex2-fieldset");
removeFlexibleOption("script-editor-option-flex3-fieldset");
removeFlexibleOption("script-editor-option-flex4-fieldset");
} catch(e) {
console.error(`Exception caught: ${e}`);
return false;
}
}
isFocused() {
if (this.editor == null) { return false; }
return this.editor.isFocused();
}
// Sets the editor to be invisible. Does not require this class to be initialized
setInvisible() {
const elem = document.getElementById("ace-editor");
if (elem instanceof HTMLElement) {
elem.style.display = "none";
}
}
}
export const AceEditor = new AceEditorWrapper();

@ -94,8 +94,8 @@ let NetscriptFunctions =
"installAugmentations|" +
// TIX API
"getStockPrice|getStockPosition|getStockSymbols|buyStock|sellStock|" +
"shortStock|sellShort|" +
"getStockPrice|getStockPosition|getStockSymbols|getStockMaxShares|" +
"buyStock|sellStock|shortStock|sellShort|" +
"placeOrder|cancelOrder|getOrders|getStockVolatility|getStockForecast|" +
"purchase4SMarketData|purchase4SMarketDataTixApi|" +

@ -0,0 +1,577 @@
// Wrapper for CodeMirror editor
// https://github.com/codemirror/codemirror
import { ScriptEditor } from "./ScriptEditor";
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/monokai.css';
import 'codemirror/theme/3024-day.css';
import 'codemirror/theme/3024-night.css';
import 'codemirror/theme/abcdef.css';
import 'codemirror/theme/ambiance-mobile.css';
import 'codemirror/theme/ambiance.css';
import 'codemirror/theme/base16-dark.css';
import 'codemirror/theme/base16-light.css';
import 'codemirror/theme/bespin.css';
import 'codemirror/theme/blackboard.css';
import 'codemirror/theme/cobalt.css';
import 'codemirror/theme/colorforth.css';
import 'codemirror/theme/darcula.css';
import 'codemirror/theme/dracula.css';
import 'codemirror/theme/duotone-dark.css';
import 'codemirror/theme/duotone-light.css';
import 'codemirror/theme/eclipse.css';
import 'codemirror/theme/elegant.css';
import 'codemirror/theme/erlang-dark.css';
import 'codemirror/theme/gruvbox-dark.css';
import 'codemirror/theme/hopscotch.css';
import 'codemirror/theme/icecoder.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/isotope.css';
import 'codemirror/theme/lesser-dark.css';
import 'codemirror/theme/liquibyte.css';
import 'codemirror/theme/lucario.css';
import 'codemirror/theme/material.css';
import 'codemirror/theme/mbo.css';
import 'codemirror/theme/mdn-like.css';
import 'codemirror/theme/midnight.css';
import 'codemirror/theme/neat.css';
import 'codemirror/theme/neo.css';
import 'codemirror/theme/night.css';
import 'codemirror/theme/oceanic-next.css';
import 'codemirror/theme/panda-syntax.css';
import 'codemirror/theme/paraiso-dark.css';
import 'codemirror/theme/paraiso-light.css';
import 'codemirror/theme/pastel-on-dark.css';
import 'codemirror/theme/railscasts.css';
import 'codemirror/theme/rubyblue.css';
import 'codemirror/theme/seti.css';
import 'codemirror/theme/shadowfox.css';
import 'codemirror/theme/solarized.css';
import 'codemirror/theme/ssms.css';
import 'codemirror/theme/the-matrix.css';
import 'codemirror/theme/tomorrow-night-bright.css';
import 'codemirror/theme/tomorrow-night-eighties.css';
import 'codemirror/theme/ttcn.css';
import 'codemirror/theme/twilight.css';
import 'codemirror/theme/vibrant-ink.css';
import 'codemirror/theme/xq-dark.css';
import 'codemirror/theme/xq-light.css';
import 'codemirror/theme/yeti.css';
import 'codemirror/theme/zenburn.css';
import "../../css/codemirror-overrides.scss";
import CodeMirror from "codemirror/lib/codemirror.js";
import "codemirror/mode/javascript/javascript.js";
import "./CodeMirrorNetscriptMode";
import 'codemirror/keymap/sublime.js';
import 'codemirror/keymap/vim.js';
import 'codemirror/keymap/emacs.js';
import 'codemirror/addon/comment/continuecomment.js';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/dialog/dialog.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter.js';
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/brace-fold.js';
import 'codemirror/addon/fold/indent-fold.js';
import 'codemirror/addon/fold/comment-fold.js';
import 'codemirror/addon/hint/javascript-hint.js';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/lint/lint.js';
import 'codemirror/addon/lint/lint.css';
import 'codemirror/addon/search/match-highlighter.js';
import 'codemirror/addon/selection/active-line.js';
window.JSHINT = require('jshint').JSHINT;
import './CodeMirrorNetscriptLint.js';
import { NetscriptFunctions } from "../NetscriptFunctions";
import { CodeMirrorKeybindingSetting,
CodeMirrorThemeSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings";
import { clearEventListeners } from "../../utils/uiHelpers/clearEventListeners";
import { createElement } from "../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../utils/uiHelpers/createOptionElement";
import { getSelectText,
getSelectValue } from "../../utils/uiHelpers/getSelectData";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
// Max number of invisibles to be shown in a group if the "Show Invisibles" option
// is marked
const MaxInvisibles = 20;
function validateInitializationParamters(params) {
if (params.saveAndCloseFn == null) { return false; } // Save & close button function
if (params.quitFn == null) { return false; } // Quitting editor, aka Engine.loadTerminalContent
return true;
}
class CodeMirrorEditorWrapper extends ScriptEditor {
constructor() {
super();
this.vimCommandDisplay = null;
this.vimCommandDisplayWrapper = null;
this.tabsStyleElement = null;
}
init(params) {
if (this.editor != null) {
console.error(`CodeMirrorEditor.init() called when it's already initialized`);
return false;
}
// Validate/Sanitize input
if (!validateInitializationParamters(params)) {
console.error(`'params' argument passed into CodeMirrorEditor.init() does not have proper properties`);
return false;
}
// Store the filename input
this.filenameInput = document.getElementById("script-editor-filename");
if (this.filenameInput == null) {
console.error(`Could not get Script Editor filename element (id=script-editor-filename)`);
return false;
}
// Add styling for the "Show Invisibles" option for spaces
const classBase = '.CodeMirror .cm-whitespace-';
const spaceChar = '·';
const style = document.createElement('style');
style.setAttribute('data-name', 'js-show-invisibles');
let rules = '';
let spaceChars = '';
for (let i = 1; i <= MaxInvisibles; ++i) {
spaceChars += spaceChar;
const rule = classBase + i + '::before { content: "' + spaceChars + '";}\n';
rules += rule;
}
style.textContent = rules;
document.head.appendChild(style);
// Add an element for the "Show Invisible" option for tabs
this.tabsStyleElement = document.createElement('style');
document.head.appendChild(this.tabsStyleElement);
// Store a reference to the VIM command display
this.vimCommandDisplay = document.getElementById("codemirror-vim-command-display");
this.vimCommandDisplayWrapper = document.getElementById("codemirror-vim-command-display-wrapper");
// Define a "Save" command for CodeMirror so shortcuts like Ctrl + s
// will save in-game
CodeMirror.commands.save = function() { params.saveAndCloseFn(); }
// Add Netscript Functions to the autocompleter
const netscriptFns = [];
var fnsObj = NetscriptFunctions(null);
for (let name in fnsObj) {
if (fnsObj.hasOwnProperty(name)) {
netscriptFns.push(name);
//Get functions from namespaces
const namespaces = ["bladeburner", "hacknet", "codingcontract", "gang"];
if (namespaces.includes(name)) {
let namespace = fnsObj[name];
if (typeof namespace !== "object") {continue;}
let namespaceFns = Object.keys(namespace);
for (let i = 0; i < namespaceFns.length; ++i) {
netscriptFns.push(namespaceFns[i]);
}
}
}
}
CodeMirror.hint.netscript = function(editor) {
const origList = CodeMirror.hint.javascript(editor) || {from: editor.getCursor(), to: editor.getCursor(), list: []};
origList.list.push(...netscriptFns);
let list = origList.list || [];
let cursor = editor.getCursor();
let currentLine = editor.getLine(cursor.line);
let start = cursor.ch;
let end = start;
while (end < currentLine.length && /[\w$]+/.test(currentLine.charAt(end))) ++end;
while (start && /[\w$]+/.test(currentLine.charAt(start - 1))) --start;
let curWord = start != end && currentLine.slice(start, end);
let regex = new RegExp('^' + curWord, 'i');
let result = {
list: (!curWord ? list : list.filter(function (item) {
return item.match(regex);
})).sort(),
from: CodeMirror.Pos(cursor.line, start),
to: CodeMirror.Pos(cursor.line, end)
};
return result;
};
// Configure VIM keybindings
var VimApi = CodeMirror.Vim;
VimApi.defineEx('write', 'w', function(cm, input) {
params.saveAndCloseFn();
});
VimApi.defineEx('quit', 'q', function(cm, input) {
params.quitFn();
});
VimApi.defineEx('xwritequit', 'x', function(cm, input) {
params.saveAndCloseFn();
});
VimApi.defineEx('wqwritequit', 'wq', function(cm, input) {
params.saveAndCloseFn();
});
}
initialized() {
return (this.filenameInput != null);
}
create() {
function safeGetElementById(id, whatFor="") {
const elem = document.getElementById(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
function safeClearEventListeners(id, whatFor="") {
const elem = clearEventListeners(id);
if (elem == null) {
throw new Error(`Could not find ${whatFor} DOM element(id=${id})`);
}
return elem;
}
try {
if (!this.initialized()) {
console.warn(`CodeMirrorEditor.create() called when editor was not initialized`);
return;
}
// Get and sanitize the keybinding (keymap) setting
if (!(Object.values(CodeMirrorKeybindingSetting).includes(Settings.EditorKeybinding))) {
Settings.EditorKeybinding = CodeMirrorKeybindingSetting.Default;
}
// Initialize CodeMirror Editor
const textAreaElement = safeGetElementById("codemirror-editor", "CodeMirror Textarea");
const formElement = safeGetElementById("codemirror-form-wrapper", "CodeMirror Form Wrapper");
formElement.style.display = "block";
this.editor = CodeMirror.fromTextArea(textAreaElement, {
autofocus: true,
extraKeys: { "Ctrl-Space": "autocomplete" },
foldGutter: true,
gutters: ["CodeMirror-lint-markers", "CodeMirror-linenumbers", "CodeMirror-foldgutter"],
highlightSelectionMatches: true,
hintOptions: { hint: CodeMirror.hint.netscript },
indentUnit: 4,
keyMap: "default",
lineNumbers: true,
matchBrackets: true,
maxInvisibles: 32,
mode: "netscript",
theme: Settings.EditorTheme,
});
// Setup Theme Option
const themeDropdown = safeClearEventListeners("script-editor-option-theme", "Theme Selector");
removeChildrenFromElement(themeDropdown);
const themeOptions = Object.keys(CodeMirrorThemeSetting);
for (let i = 0; i < themeOptions.length; ++i) {
const themeKey = themeOptions[i];
const themeValue = CodeMirrorThemeSetting[themeKey];
themeDropdown.add(createOptionElement(themeKey, themeValue));
}
if (Settings.EditorTheme) {
var initialIndex = 0;
for (var i = 0; i < themeDropdown.options.length; ++i) {
if (themeDropdown.options[i].value === Settings.EditorTheme) {
initialIndex = i;
break;
}
}
themeDropdown.selectedIndex = initialIndex;
} else {
themeDropdown.selectedIndex = 0;
}
themeDropdown.onchange = () => {
const val = themeDropdown.value;
Settings.EditorTheme = val;
this.editor.setOption("theme", val);
};
themeDropdown.onchange();
// Setup Keymap Option
const keybindingDropdown = safeClearEventListeners("script-editor-option-keybinding", "Keymap Selector");
if (keybindingDropdown == null) {
console.error(`Could not find Script Editor's keybinding selector element (id="script-editor-option-keybinding")`);
return false;
}
removeChildrenFromElement(keybindingDropdown);
keybindingDropdown.add(createOptionElement("Default", CodeMirrorKeybindingSetting.Default));
keybindingDropdown.add(createOptionElement("Sublime", CodeMirrorKeybindingSetting.Sublime));
keybindingDropdown.add(createOptionElement("Vim", CodeMirrorKeybindingSetting.Vim));
keybindingDropdown.add(createOptionElement("Emacs", CodeMirrorKeybindingSetting.Emacs));
if (Settings.EditorKeybinding) {
var initialIndex = 0;
for (var i = 0; i < keybindingDropdown.options.length; ++i) {
if (keybindingDropdown.options[i].value === Settings.EditorKeybinding) {
initialIndex = i;
break;
}
}
keybindingDropdown.selectedIndex = initialIndex;
} else {
keybindingDropdown.selectedIndex = 0;
}
keybindingDropdown.onchange = () => {
// Set Vim command display to be invisible initially
this.vimCommandDisplayWrapper.style.display = "none";
const val = keybindingDropdown.value;
Settings.EditorKeybinding = val;
this.editor.removeKeyMap(CodeMirror.keyMap.default);
this.editor.removeKeyMap(CodeMirror.keyMap.sublime);
this.editor.removeKeyMap(CodeMirror.keyMap.emacs);
this.editor.removeKeyMap(CodeMirror.keyMap.vim);
// Setup the VIM command display
let keys = '';
const handleVimKeyPress = (key) => {
keys = keys + key;
this.vimCommandDisplay.innerHTML = keys;
}
const handleVimCommandDone = (e) => {
keys = '';
this.vimCommandDisplay.innerHTML = keys;
}
if (val === CodeMirrorKeybindingSetting.Vim) {
this.vimCommandDisplayWrapper.style.display = "block";
this.editor.on('vim-keypress', handleVimKeyPress);
this.editor.on('vim-command-done', handleVimCommandDone);
} else {
this.vimCommandDisplayWrapper.style.display = "none";
this.editor.off('vim-keypress', handleVimKeyPress);
this.editor.off('vim-command-done', handleVimCommandDone);
}
this.editor.addKeyMap(val);
this.editor.setOption("keyMap", val);
};
keybindingDropdown.onchange();
// Highlight Active line
const highlightActiveChkBox = safeClearEventListeners("script-editor-option-highlightactiveline", "Active Line Checkbox");
highlightActiveChkBox.onchange = () => {
this.editor.setOption("styleActiveLine", highlightActiveChkBox.checked);
};
highlightActiveChkBox.onchange();
// Show Invisibles
const showInvisiblesChkBox = safeClearEventListeners("script-editor-option-showinvisibles", "Show Invisible Checkbox");
showInvisiblesChkBox.onchange = () => {
const overlayMode = {
name: 'invisibles',
token: function nextToken(stream) {
var ret,
spaces = 0,
space = stream.peek() === ' ';
if (space) {
while (space && spaces < MaxInvisibles) {
++spaces;
stream.next();
space = stream.peek() === ' ';
}
ret = 'whitespace whitespace-' + spaces;
} else {
while (!stream.eol() && !space) {
stream.next();
space = stream.peek() === ' ';
}
ret = 'cm-eol';
}
return ret;
}
};
if (showInvisiblesChkBox.checked) {
// Spaces
this.editor.addOverlay(overlayMode);
// Tabs
this.tabsStyleElement.innerHTML = ".cm-tab {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABDSURBVEhLYxgFo2BkAGYH/9r/QFoAxIGyhx6AORzZA4xD1TcHNjYzQplDB2CLgaECYHkADIZqqgGBoZdsRsHgBgwMAB8iFHF42AERAAAAAElFTkSuQmCC);background-position: right;background-repeat: no-repeat;}";
} else {
this.editor.removeOverlay("invisibles");
this.tabsStyleElement.innerHTML = "";
}
};
showInvisiblesChkBox.onchange();
//Use Soft Tab
const softTabChkBox = safeClearEventListeners("script-editor-option-usesofttab", "Soft Tab Checkbox");
softTabChkBox.onchange = () => {
this.editor.setOption("indentWithTabs", !softTabChkBox.checked);
if (softTabChkBox.checked) {
this.editor.addKeyMap({
name: "soft-tabs-keymap",
"Tab": function (cm) {
if (cm.somethingSelected()) {
var sel = cm.getSelection("\n");
// Indent only if there are multiple lines selected, or if the selection spans a full line
if (sel.length > 0 && (sel.indexOf("\n") > -1 || sel.length === cm.getLine(cm.getCursor().line).length)) {
cm.indentSelection("add");
return;
}
}
if (cm.options.indentWithTabs)
cm.execCommand("insertTab");
else
cm.execCommand("insertSoftTab");
},
"Shift-Tab": function (cm) {
cm.indentSelection("subtract");
}
});
} else {
this.editor.removeKeyMap("soft-tabs-keymap");
}
};
softTabChkBox.onchange();
// Some helper functions for dealing with flexible options
function resetFlexibleOption(id) {
const fieldset = safeGetElementById(id);
removeChildrenFromElement(fieldset);
fieldset.style.display = "block";
return fieldset;
}
function removeFlexibleOption(id) {
// This doesn't really remove it, just sets it to invisible
const fieldset = resetFlexibleOption(id);
fieldset.style.display = "none";
return fieldset;
}
// Flex 1: Automatically Close Brackets and Quotes
const flex1Fieldset = resetFlexibleOption("script-editor-option-flex1-fieldset");
const flex1Id = "script-editor-option-flex1";
flex1Fieldset.appendChild(createElement("label", {
for: flex1Id,
innerText: "Auto-Close Brackets/Quotes",
}));
const flex1Checkbox = createElement("input", {
checked: true,
id: flex1Id,
name: flex1Id,
type: "checkbox",
});
flex1Fieldset.appendChild(flex1Checkbox);
flex1Checkbox.onchange = () => {
this.editor.setOption("autoCloseBrackets", flex1Checkbox.checked);
};
flex1Checkbox.onchange();
// Flex 2: Disable/Enable Linting
const flex2Fieldset = resetFlexibleOption("script-editor-option-flex2-fieldset");
const flex2Id = "script-editor-option-flex2";
flex2Fieldset.appendChild(createElement("label", {
for: flex2Id,
innerText: "Enable Linting",
}));
const flex2Checkbox = createElement("input", {
checked: true,
id: flex2Id,
name: flex2Id,
type: "checkbox",
});
flex2Fieldset.appendChild(flex2Checkbox);
flex2Checkbox.onchange = () => {
if (flex2Checkbox.checked) {
this.editor.setOption("lint", CodeMirror.lint.netscript);
} else {
this.editor.setOption("lint", false);
}
}
flex2Checkbox.onchange();
// Flex 3: Continue Comments
const flex3Fieldset = resetFlexibleOption("script-editor-option-flex3-fieldset");
const flex3Id = "script-editor-option-flex3";
flex3Fieldset.appendChild(createElement("label", {
for: flex3Id,
innerText: "Continue Comments",
}));
const flex3Checkbox = createElement("input", {
checked: true,
id: flex3Id,
name: flex3Id,
type: "checkbox",
});
flex3Fieldset.appendChild(flex3Checkbox);
flex3Checkbox.onchange = () => {
this.editor.setOption("continueComments", flex3Checkbox.checked);
}
flex3Checkbox.onchange();
removeFlexibleOption("script-editor-option-flex4-fieldset");
this.editor.refresh();
} catch(e) {
console.error(`Exception caught: ${e}. ${e.stack}`);
return false;
}
}
isFocused() {
if (this.editor == null) { return false; }
return this.editor.hasFocus();
}
// Sets the editor to be invisible
setInvisible() {
if (!this.initialized()) {
console.warn(`CodeMirrorEditor.setInvisible() called when editor was not initialized`);
return;
}
if (this.editor != null) {
this.editor.toTextArea();
this.editor = null;
}
const elem = document.getElementById("codemirror-form-wrapper");
if (elem instanceof HTMLElement) {
elem.style.display = "none";
}
}
}
export const CodeMirrorEditor = new CodeMirrorEditorWrapper();

@ -0,0 +1,82 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("codemirror/lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["codemirror/lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
// declare global: JSHINT
function validator(text, options) {
if (!window.JSHINT) {
if (window.console) {
window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run.");
}
return [];
}
// To ignore the 'async/await' errors, we'll manually edit the code ('text')
// that gets processed by JSHINT to include the ignore directory
const splitText = text.split("\n");
const ignoreDirective = " // jshint ignore:line";
for (let i = 0; i < splitText.length; ++i) {
if (splitText[i].match(/.*async function.+{/g)) {
splitText[i] += ignoreDirective;
} else if (splitText[i].match(/.*await.+;/g)) {
splitText[i] += ignoreDirective;
}
}
const sanitizedText = splitText.join("\n");
// Configure JSHINT options
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
options.esversion = 6;
JSHINT(sanitizedText, options, options.globals);
var errors = JSHINT.data().errors, result = [];
if (errors) parseErrors(errors, result);
return result;
}
CodeMirror.registerHelper("lint", "netscript", validator);
function parseErrors(errors, output) {
for ( var i = 0; i < errors.length; i++) {
var error = errors[i];
if (error) {
if (error.line == 0) { continue; }
if (error.line < 0) {
if (window.console) {
window.console.warn("Cannot display JSHint error (invalid line " + error.line + ")", error);
}
continue;
}
var start = error.character - 1, end = start + 1;
if (error.evidence) {
var index = error.evidence.substring(start).search(/.\b/);
if (index > -1) {
end += index;
}
}
// Convert to format expected by validation service
var hint = {
message: error.reason,
severity: error.code ? (error.code.startsWith('W') ? "warning" : "error") : "error",
from: CodeMirror.Pos(error.line - 1, start),
to: CodeMirror.Pos(error.line - 1, end)
};
output.push(hint);
}
}
}
});

File diff suppressed because it is too large Load Diff

@ -0,0 +1,58 @@
// Base Script Editor class for the Ace/CodeMirror/etc. wrappers
const beautify = require('js-beautify').js_beautify;
export class ScriptEditor {
constructor() {
this.editor = null; // Stores the CodeMirror editor reference
this.filenameInput = null; // Stores the filename input DOM element
}
init() {
throw new Error(`Tried to initialize base ScriptEditor class`);
}
beautifyScript() {
if (this.editor == null) {
console.warn(`ScriptEditor.beautifyScript() called when editor was not initialized`);
return;
}
let code = this.editor.getValue();
code = beautify(code, {
indent_size: 4,
brace_style: "preserve-inline",
});
this.editor.setValue(code);
}
openScript(filename="", code="") {
if (this.editor == null || this.filenameInput == null) {
console.warn(`ScriptEditor.openScript() called when editor was not initialized`);
return;
}
if (filename != "") {
this.filenameInput.value = filename;
this.editor.setValue(code);
}
this.editor.focus();
}
getCode() {
if (this.editor == null) {
console.warn(`ScriptEditor.getCode() called when editor was not initialized`);
return "";
}
return this.editor.getValue();
}
getFilename() {
if (this.filenameInput == null) {
console.warn(`ScriptEditor.getFilename() called when editor was not initialized`);
return "";
}
return this.filenameInput.value;
}
}

@ -1,4 +1,4 @@
import { BitNodeMultipliers } from "./BitNodeMultipliers";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CodingContract,
ContractTypes } from "./CodingContracts";
import { CONSTANTS } from "./Constants";

@ -1,16 +1,46 @@
import {CONSTANTS} from "./Constants";
import {Player} from "./Player";
import {Server, AllServers, AddToAllServers} from "./Server";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createRandomIp} from "../utils/IPAddress";
import {yesNoTxtInpBoxGetInput} from "../utils/YesNoBox";
/* Functions to handle any server-related purchasing:
* Purchasing new servers
* Purchasing more RAM for home computer
/**
* Implements functions for purchasing servers or purchasing more RAM for
* the home computer
*/
function purchaseServer(ram, cost) {
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Player } from "./Player";
import { Server,
AllServers,
AddToAllServers} from "./Server";
import { dialogBoxCreate } from "../utils/DialogBox";
import { createRandomIp } from "../utils/IPAddress";
import { yesNoTxtInpBoxGetInput } from "../utils/YesNoBox";
import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo";
// Returns the cost of purchasing a server with the given RAM
// Returns Infinity for invalid 'ram' arguments
export function getPurchaseServerCost(ram) {
const sanitizedRam = Math.round(ram);
if (isNaN(sanitizedRam) || !isPowerOfTwo(sanitizedRam)) {
return Infinity;
}
if (sanitizedRam > getPurchaseServerMaxRam()) {
return Infinity;
}
return sanitizedRam * CONSTANTS.BaseCostFor1GBOfRamServer * BitNodeMultipliers.PurchasedServerCost;
}
export function getPurchaseServerLimit() {
return Math.round(CONSTANTS.PurchasedServerLimit * BitNodeMultipliers.PurchasedServerLimit);
}
export function getPurchaseServerMaxRam() {
// TODO ensure this is a power of 2?
return Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam);
}
// Manually purchase a server (NOT through Netscript)
export function purchaseServer(ram) {
const cost = getPurchaseServerCost(ram);
//Check if player has enough money
if (Player.money.lt(cost)) {
dialogBoxCreate("You don't have enough money to purchase this server!");
@ -18,8 +48,8 @@ function purchaseServer(ram, cost) {
}
//Maximum server limit
if (Player.purchasedServers.length >= CONSTANTS.PurchasedServerLimit) {
dialogBoxCreate("You have reached the maximum limit of " + CONSTANTS.PurchasedServerLimit + " servers. " +
if (Player.purchasedServers.length >= getPurchaseServerLimit()) {
dialogBoxCreate("You have reached the maximum limit of " + getPurchaseServerLimit() + " servers. " +
"You cannot purchase any more. You can " +
"delete some of your purchased servers using the deleteServer() Netscript function in a script");
return;
@ -51,19 +81,22 @@ function purchaseServer(ram, cost) {
dialogBoxCreate("Server successfully purchased with hostname " + hostname);
}
function purchaseRamForHomeComputer(cost) {
// Manually upgrade RAM on home computer (NOT through Netscript)
export function purchaseRamForHomeComputer(cost) {
if (Player.money.lt(cost)) {
dialogBoxCreate("You do not have enough money to purchase additional RAM for your home computer");
return;
}
var homeComputer = Player.getHomeComputer();
homeComputer.maxRam *= 2;
const homeComputer = Player.getHomeComputer();
if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {
dialogBoxCreate(`You cannot upgrade your home computer RAM because it is at its maximum possible value`);
return;
}
homeComputer.maxRam *= 2;
Player.loseMoney(cost);
dialogBoxCreate("Purchased additional RAM for home computer! It now has " + homeComputer.maxRam + "GB of RAM.");
}
export {purchaseServer, purchaseRamForHomeComputer};

@ -1,16 +0,0 @@
/**
* Enum Of allowed values for the 'OwnedAugmentationsOrder' setting
*/
export enum OwnedAugmentationsOrderSetting {
Alphabetically,
AcquirementTime,
}
/**
* Enum Of allowed values for the 'OwnedAugmentationsOrder' setting
*/
export enum PurchaseAugmentationsOrderSetting {
Cost,
Default,
Reputation,
}

@ -0,0 +1,106 @@
// Enums that defined allowed values for setting configuration
/**
* Allowed values for 'Keybinding/Keymap' setting in Ace editor
*/
export enum AceKeybindingSetting {
Ace = "ace",
Emacs = "emacs",
Vim = "vim",
}
/**
* Allowed values for 'Keybinding/Keymap' setting in Code Mirror editor
*/
export enum CodeMirrorKeybindingSetting {
Default = "default",
Emacs = "emacs",
Sublime = "sublime",
Vim = "vim",
}
/**
* Allowed values for 'Theme' setting in Code Mirror editor
*/
export enum CodeMirrorThemeSetting {
Monokai = "monokai",
Day_3024 = "3024-day",
Night_3024 = "3024-night",
abcdef = "abcdef",
Ambiance_mobile = "ambiance-mobile",
Ambiance = "ambiance",
Base16_dark = "base16-dark",
Base16_light = "base16-light",
Bespin = "bespin",
Blackboard = "blackboard",
Cobalt = "cobalt",
Colorforth = "colorforth",
Darcula = "darcula",
Dracula = "dracula",
Duotone_dark = "duotone-dark",
Duotone_light = "duotone-light",
Eclipse = "eclipse",
Elegant = "elegant",
Erlang_dark = "erlang-dark",
Gruvbox_dark = "gruvbox-dark",
Hopscotch = "hopscotch",
Icecoder = "icecoder",
Idea = "idea",
Isotope = "isotope",
Lesser_dark = "lesser-dark",
Liquibyte = "liquibyte",
Lucario = "lucario",
Material = "material",
Mbo = "mbo",
Mdn_like = "mdn-like",
Midnight = "midnight",
Neat = "neat",
Neo = "neo",
Night = "night",
Oceanic_next = "oceanic-next",
Panda_syntax = "panda-syntax",
Paraiso_dark = "paraiso-dark",
Paraiso_light = "paraiso-light",
Pastel_on_dark = "pastel-on-dark",
Railscasts = "railscasts",
Rubyblue = "rubyblue",
Seti = "seti",
Shadowfox = "shadowfox",
Solarized = "solarized",
ssms = "ssms",
The_matrix = "the-matrix",
Tomorrow_night_bright = "tomorrow-night-bright",
Tomorrow_night_eighties = "tomorrow-night-eighties",
Ttcn = "ttcn",
Twilight = "twilight",
Vibrant_ink = "vibrant-ink",
xq_dark = "xq-dark",
xq_light = "xq-light",
Yeti = "yeti",
Zenburn = "zenburn",
}
/**
* Allowed values for the "Editor" setting
*/
export enum EditorSetting {
Ace = "Ace",
CodeMirror = "CodeMirror",
}
/**
* Allowed values for the 'OwnedAugmentationsOrder' setting
*/
export enum PurchaseAugmentationsOrderSetting {
Cost,
Default,
Reputation,
}
/**
* Allowed values for the 'OwnedAugmentationsOrder' setting
*/
export enum OwnedAugmentationsOrderSetting {
Alphabetically,
AcquirementTime,
}

@ -1,5 +1,10 @@
import { ISelfInitializer, ISelfLoading } from "./types";
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
import { ISelfInitializer, ISelfLoading } from "../types";
import { AceKeybindingSetting,
CodeMirrorKeybindingSetting,
CodeMirrorThemeSetting,
EditorSetting,
OwnedAugmentationsOrderSetting,
PurchaseAugmentationsOrderSetting } from "./SettingEnums";
/**
* Represents the default settings the player could customize.
@ -65,17 +70,22 @@ interface IDefaultSettings {
* Represents all possible settings the player wants to customize to their play style.
*/
interface ISettings extends IDefaultSettings {
/**
* Which editor should be used (CodeMirror or Ace)?
*/
Editor: EditorSetting;
/**
* The keybinding to use in the script editor.
* TODO: This should really be an enum of allowed values.
*/
EditorKeybinding: string;
EditorKeybinding: AceKeybindingSetting | CodeMirrorKeybindingSetting;
/**
* The theme used in the script editor.
* TODO: This should really be an enum of allowed values.
*/
EditorTheme: string;
EditorTheme: string | CodeMirrorThemeSetting;
/**
* What order the player's owned Augmentations/Source Files should be displayed in
@ -110,7 +120,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
AutosaveInterval: defaultSettings.AutosaveInterval,
CodeInstructionRunTime: 25,
DisableHotkeys: defaultSettings.DisableHotkeys,
EditorKeybinding: "ace",
Editor: EditorSetting.Ace,
EditorKeybinding: AceKeybindingSetting.Ace,
EditorTheme: "Monokai",
Locale: "en",
MaxLogCapacity: defaultSettings.MaxLogCapacity,

@ -1,5 +1,5 @@
import {Player} from "./Player";
import {BitNodes} from "./BitNode";
import { Player } from "./Player";
import { BitNodes } from "./BitNode/BitNode";
/* SourceFile.js */
//Each SourceFile corresponds to a BitNode with the same number
@ -63,7 +63,8 @@ function initSourceFiles() {
"This Source-File also increases your hacking growth multipliers by: " +
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
SourceFiles["SourceFile9"] = new SourceFile(9);
SourceFiles["SourceFile10"] = new SourceFile(10);
SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
"Source-File also grants you a Duplicate Sleeve");
SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
"at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
" increases the player's company salary and reputation gain multipliers by:<br><br>" +
@ -74,11 +75,6 @@ function initSourceFiles() {
"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)");
}
function PlayerOwnedSourceFile(number, level) {
this.n = number;
this.lvl = level;
}
//Takes in a PlayerOwnedSourceFile as the "srcFile" argument
function applySourceFile(srcFile) {
var srcFileKey = "SourceFile" + srcFile.n;
@ -192,6 +188,9 @@ function applySourceFile(srcFile) {
var incMult = 1 + (mult / 100);
Player.hacking_grow_mult *= incMult;
break;
case 10: // Digital Carbon
// No effects, just grants sleeves
break;
case 11: //The Big Crash
var mult = 0;
for (var i = 0; i < srcFile.lvl; ++i) {
@ -246,4 +245,4 @@ function applySourceFile(srcFile) {
sourceFileObject.owned = true;
}
export {SourceFiles, PlayerOwnedSourceFile, applySourceFile, initSourceFiles};
export {SourceFiles, applySourceFile, initSourceFiles};

@ -0,0 +1,17 @@
export class PlayerOwnedSourceFile {
// Source-File level
lvl: number = 1;
// Source-File number
n: number = 1;
constructor(n: number, level: number) {
this.n = n;
this.lvl = level;
}
}
export interface IPlayerOwnedSourceFile {
lvl: number;
n: number;
}

@ -0,0 +1,18 @@
// Contains an array containing information about the player's source files
// Array[n] returns what level the player has of Source-File N.
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip 0
export function updateSourceFileFlags(p: IPlayer) {
for (let i = 0; i < SourceFileFlags.length; ++i) {
SourceFileFlags[i] = 0;
}
for (let i = 0; i < p.sourceFiles.length; ++i) {
const sf = p.sourceFiles[i];
SourceFileFlags[sf.n] = sf.lvl;
}
}

@ -1,5 +1,5 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
/**
* Represents the valuation of a company in the World Stock Exchange.
@ -22,6 +22,11 @@ export class Stock {
*/
readonly cap: number;
/**
* Maximum number of shares that player can own (both long and short combined)
*/
readonly maxShares: number;
/**
* Maximum volatility
*/
@ -73,12 +78,20 @@ export class Stock {
*/
readonly symbol: string;
/**
* Total number of shares of this stock
* This is different than maxShares, as this is like authorized stock while
* maxShares is outstanding stock.
*/
readonly totalShares: number;
constructor(name: string = "",
symbol: string = "",
mv: number = 1,
b: boolean = true,
otlkMag: number = 0,
initPrice: number = 10e3) {
initPrice: number = 10e3,
marketCap: number = 1e12) {
this.name = name;
this.symbol = symbol;
this.price = initPrice;
@ -91,6 +104,14 @@ export class Stock {
this.otlkMag = otlkMag;
this.cap = getRandomInt(initPrice * 1e3, initPrice * 25e3);
// Total shares is determined by market cap, and is rounded to nearest 100k
let totalSharesUnrounded: number = (marketCap / initPrice);
this.totalShares = Math.round(totalSharesUnrounded / 1e5) * 1e5;
// Max Shares (Outstanding shares) is a percentage of total shares
const outstandingSharePercentage: number = 0.2;
this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5;
this.posTxtEl = null;
}

145
src/StockMarket.js → src/StockMarket/StockMarket.js Executable file → Normal file

@ -1,27 +1,29 @@
import {CONSTANTS} from "./Constants";
import {Locations} from "./Locations";
import {hasWallStreetSF, wallStreetSFLvl} from "./NetscriptFunctions";
import {WorkerScript} from "./NetscriptWorker";
import {Player} from "./Player";
import {Stock} from "./Stock";
import {dialogBoxCreate} from "../utils/DialogBox";
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
import {CONSTANTS} from "../Constants";
import {Locations} from "../Locations";
import {hasWallStreetSF, wallStreetSFLvl} from "../NetscriptFunctions";
import {WorkerScript} from "../NetscriptWorker";
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 {Page, routing} from "./ui/navigationTracking";
import {numeralWrapper} from "./ui/numeralFormat";
import {exceptionAlert} from "../utils/helpers/exceptionAlert";
import {getRandomInt} from "../utils/helpers/getRandomInt";
import {KEY} from "../utils/helpers/keyCodes";
import {createElement} from "../utils/uiHelpers/createElement";
import {removeChildrenFromElement} from "../utils/uiHelpers/removeChildrenFromElement";
import {removeElementById} from "../utils/uiHelpers/removeElementById";
Generic_fromJSON} from "../../utils/JSONReviver";
import {exceptionAlert} from "../../utils/helpers/exceptionAlert";
import {getRandomInt} from "../../utils/helpers/getRandomInt";
import {KEY} from "../../utils/helpers/keyCodes";
import {createElement} from "../../utils/uiHelpers/createElement";
import {removeChildrenFromElement} from "../../utils/uiHelpers/removeChildrenFromElement";
import {removeElementById} from "../../utils/uiHelpers/removeElementById";
import {yesNoBoxCreate, yesNoTxtInpBoxCreate,
yesNoBoxGetYesButton, yesNoBoxGetNoButton,
yesNoTxtInpBoxGetYesButton, yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxGetInput, yesNoBoxClose,
yesNoTxtInpBoxClose, yesNoBoxOpen} from "../utils/YesNoBox";
yesNoTxtInpBoxClose, yesNoBoxOpen} from "../../utils/YesNoBox";
var OrderTypes = {
LimitBuy: "Limit Buy Order",
@ -228,135 +230,135 @@ function initStockMarket() {
const randInt = getRandomInt;
var ecorp = Locations.AevumECorp;
var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], randInt(40, 50) / 100, true, 19, randInt(17e3, 28e3));
var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], randInt(40, 50) / 100, true, 19, randInt(17e3, 28e3), 2.4e12);
StockMarket[ecorp] = ecorpStk;
var megacorp = Locations.Sector12MegaCorp;
var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], randInt(40,50)/100, true, 19, randInt(24e3, 34e3));
var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], randInt(40,50)/100, true, 19, randInt(24e3, 34e3), 2.4e12);
StockMarket[megacorp] = megacorpStk;
var blade = Locations.Sector12BladeIndustries;
var bladeStk = new Stock(blade, StockSymbols[blade], randInt(70, 80)/100, true, 13, randInt(12e3, 25e3));
var bladeStk = new Stock(blade, StockSymbols[blade], randInt(70, 80)/100, true, 13, randInt(12e3, 25e3), 1.6e12);
StockMarket[blade] = bladeStk;
var clarke = Locations.AevumClarkeIncorporated;
var clarkeStk = new Stock(clarke, StockSymbols[clarke], randInt(65, 75)/100, true, 12, randInt(10e3, 25e3));
var clarkeStk = new Stock(clarke, StockSymbols[clarke], randInt(65, 75)/100, true, 12, randInt(10e3, 25e3), 1.5e12);
StockMarket[clarke] = clarkeStk;
var omnitek = Locations.VolhavenOmniTekIncorporated;
var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], randInt(60, 70)/100, true, 12, randInt(32e3, 43e3));
var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], randInt(60, 70)/100, true, 12, randInt(32e3, 43e3), 1.8e12);
StockMarket[omnitek] = omnitekStk;
var foursigma = Locations.Sector12FourSigma;
var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], randInt(100, 110)/100, true, 17, randInt(50e3, 80e3));
var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], randInt(100, 110)/100, true, 17, randInt(50e3, 80e3), 2e12);
StockMarket[foursigma] = foursigmaStk;
var kuaigong = Locations.ChongqingKuaiGongInternational;
var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], randInt(75, 85)/100, true, 10, randInt(16e3, 28e3));
var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], randInt(75, 85)/100, true, 10, randInt(16e3, 28e3), 1.9e12);
StockMarket[kuaigong] = kuaigongStk;
var fulcrum = Locations.AevumFulcrumTechnologies;
var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], randInt(120, 130)/100, true, 16, randInt(29e3, 36e3));
var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], randInt(120, 130)/100, true, 16, randInt(29e3, 36e3), 2e12);
StockMarket[fulcrum] = fulcrumStk;
var storm = Locations.IshimaStormTechnologies;
var stormStk = new Stock(storm, StockSymbols[storm], randInt(80, 90)/100, true, 7, randInt(20e3, 25e3));
var stormStk = new Stock(storm, StockSymbols[storm], randInt(80, 90)/100, true, 7, randInt(20e3, 25e3), 1.2e12);
StockMarket[storm] = stormStk;
var defcomm = Locations.NewTokyoDefComm;
var defcommStk = new Stock(defcomm, StockSymbols[defcomm], randInt(60, 70)/100, true, 10, randInt(6e3, 19e3));
var defcommStk = new Stock(defcomm, StockSymbols[defcomm], randInt(60, 70)/100, true, 10, randInt(6e3, 19e3), 900e9);
StockMarket[defcomm] = defcommStk;
var helios = Locations.VolhavenHeliosLabs;
var heliosStk = new Stock(helios, StockSymbols[helios], randInt(55, 65)/100, true, 9, randInt(10e3, 18e3));
var heliosStk = new Stock(helios, StockSymbols[helios], randInt(55, 65)/100, true, 9, randInt(10e3, 18e3), 825e9);
StockMarket[helios] = heliosStk;
var vitalife = Locations.NewTokyoVitaLife;
var vitalifeStk = new Stock(vitalife, StockSymbols[vitalife], randInt(70, 80)/100, true, 7, randInt(8e3, 14e3));
var vitalifeStk = new Stock(vitalife, StockSymbols[vitalife], randInt(70, 80)/100, true, 7, randInt(8e3, 14e3), 1e12);
StockMarket[vitalife] = vitalifeStk;
var icarus = Locations.Sector12IcarusMicrosystems;
var icarusStk = new Stock(icarus, StockSymbols[icarus], randInt(60, 70)/100, true, 7.5, randInt(12e3, 24e3));
var icarusStk = new Stock(icarus, StockSymbols[icarus], randInt(60, 70)/100, true, 7.5, randInt(12e3, 24e3), 800e9);
StockMarket[icarus] = icarusStk;
var universalenergy = Locations.Sector12UniversalEnergy;
var universalenergyStk = new Stock(universalenergy, StockSymbols[universalenergy], randInt(50, 60)/100, true, 10, randInt(16e3, 29e3));
var universalenergyStk = new Stock(universalenergy, StockSymbols[universalenergy], randInt(50, 60)/100, true, 10, randInt(16e3, 29e3), 900e9);
StockMarket[universalenergy] = universalenergyStk;
var aerocorp = Locations.AevumAeroCorp;
var aerocorpStk = new Stock(aerocorp, StockSymbols[aerocorp], randInt(55, 65)/100, true, 6, randInt(8e3, 17e3));
var aerocorpStk = new Stock(aerocorp, StockSymbols[aerocorp], randInt(55, 65)/100, true, 6, randInt(8e3, 17e3), 640e9);
StockMarket[aerocorp] = aerocorpStk;
var omnia = Locations.VolhavenOmniaCybersystems;
var omniaStk = new Stock(omnia, StockSymbols[omnia], randInt(65, 75)/100, true, 4.5, randInt(6e3, 15e3));
var omniaStk = new Stock(omnia, StockSymbols[omnia], randInt(65, 75)/100, true, 4.5, randInt(6e3, 15e3), 600e9);
StockMarket[omnia] = omniaStk;
var solaris = Locations.ChongqingSolarisSpaceSystems;
var solarisStk = new Stock(solaris, StockSymbols[solaris], randInt(70, 80)/100, true, 8.5, randInt(14e3, 28e3));
var solarisStk = new Stock(solaris, StockSymbols[solaris], randInt(70, 80)/100, true, 8.5, randInt(14e3, 28e3), 705e9);
StockMarket[solaris] = solarisStk;
var globalpharm = Locations.NewTokyoGlobalPharmaceuticals;
var globalpharmStk = new Stock(globalpharm, StockSymbols[globalpharm], randInt(55, 65)/100, true, 10.5, randInt(12e3, 30e3));
var globalpharmStk = new Stock(globalpharm, StockSymbols[globalpharm], randInt(55, 65)/100, true, 10.5, randInt(12e3, 30e3), 695e9);
StockMarket[globalpharm] = globalpharmStk;
var nova = Locations.IshimaNovaMedical;
var novaStk = new Stock(nova, StockSymbols[nova], randInt(70, 80)/100, true, 5, randInt(15e3, 27e3));
var novaStk = new Stock(nova, StockSymbols[nova], randInt(70, 80)/100, true, 5, randInt(15e3, 27e3), 600e9);
StockMarket[nova] = novaStk;
var watchdog = Locations.AevumWatchdogSecurity;
var watchdogStk = new Stock(watchdog, StockSymbols[watchdog], randInt(240, 260)/100, true, 1.5, randInt(4e3, 8.5e3));
var watchdogStk = new Stock(watchdog, StockSymbols[watchdog], randInt(240, 260)/100, true, 1.5, randInt(4e3, 8.5e3), 450e9);
StockMarket[watchdog] = watchdogStk;
var lexocorp = Locations.VolhavenLexoCorp;
var lexocorpStk = new Stock(lexocorp, StockSymbols[lexocorp], randInt(115, 135)/100, true, 6, randInt(4.5e3, 8e3));
var lexocorpStk = new Stock(lexocorp, StockSymbols[lexocorp], randInt(115, 135)/100, true, 6, randInt(4.5e3, 8e3), 300e9);
StockMarket[lexocorp] = lexocorpStk;
var rho = Locations.AevumRhoConstruction;
var rhoStk = new Stock(rho, StockSymbols[rho], randInt(50, 70)/100, true, 1, randInt(2e3, 7e3));
var rhoStk = new Stock(rho, StockSymbols[rho], randInt(50, 70)/100, true, 1, randInt(2e3, 7e3), 180e9);
StockMarket[rho] = rhoStk;
var alpha = Locations.Sector12AlphaEnterprises;
var alphaStk = new Stock(alpha, StockSymbols[alpha], randInt(175, 205)/100, true, 10, randInt(4e3, 8.5e3));
var alphaStk = new Stock(alpha, StockSymbols[alpha], randInt(175, 205)/100, true, 10, randInt(4e3, 8.5e3), 240e9);
StockMarket[alpha] = alphaStk;
var syscore = Locations.VolhavenSysCoreSecurities;
var syscoreStk = new Stock(syscore, StockSymbols[syscore], randInt(150, 170)/100, true, 3, randInt(3e3, 8e3));
var syscoreStk = new Stock(syscore, StockSymbols[syscore], randInt(150, 170)/100, true, 3, randInt(3e3, 8e3), 200e9);
StockMarket[syscore] = syscoreStk;
var computek = Locations.VolhavenCompuTek;
var computekStk = new Stock(computek, StockSymbols[computek], randInt(80, 100)/100, true, 4, randInt(1e3, 6e3));
var computekStk = new Stock(computek, StockSymbols[computek], randInt(80, 100)/100, true, 4, randInt(1e3, 6e3), 185e9);
StockMarket[computek] = computekStk;
var netlink = Locations.AevumNetLinkTechnologies;
var netlinkStk = new Stock(netlink, StockSymbols[netlink], randInt(400, 430)/100, true, 1, randInt(1e3, 5e3));
var netlinkStk = new Stock(netlink, StockSymbols[netlink], randInt(400, 430)/100, true, 1, randInt(1e3, 5e3), 58e9);
StockMarket[netlink] = netlinkStk;
var omega = Locations.IshimaOmegaSoftware;
var omegaStk = new Stock(omega, StockSymbols[omega], randInt(90, 110)/100, true, 0.5, randInt(1e3, 8e3));
var omegaStk = new Stock(omega, StockSymbols[omega], randInt(90, 110)/100, true, 0.5, randInt(1e3, 8e3), 60e9);
StockMarket[omega] = omegaStk;
var fns = Locations.Sector12FoodNStuff;
var fnsStk = new Stock(fns, StockSymbols[fns], randInt(70, 80)/100, false, 1, randInt(500, 4.5e3));
var fnsStk = new Stock(fns, StockSymbols[fns], randInt(70, 80)/100, false, 1, randInt(500, 4.5e3), 45e9);
StockMarket[fns] = fnsStk;
var sigmacosm = "Sigma Cosmetics";
var sigmacosmStk = new Stock(sigmacosm, StockSymbols[sigmacosm], randInt(260, 300)/100, true, 0, randInt(1.5e3, 3.5e3));
var sigmacosmStk = new Stock(sigmacosm, StockSymbols[sigmacosm], randInt(260, 300)/100, true, 0, randInt(1.5e3, 3.5e3), 30e9);
StockMarket[sigmacosm] = sigmacosmStk;
var joesguns = "Joes Guns";
var joesgunsStk = new Stock(joesguns, StockSymbols[joesguns], randInt(360, 400)/100, true, 1, randInt(250, 1.5e3));
var joesgunsStk = new Stock(joesguns, StockSymbols[joesguns], randInt(360, 400)/100, true, 1, randInt(250, 1.5e3), 42e9);
StockMarket[joesguns] = joesgunsStk;
var catalyst = "Catalyst Ventures";
var catalystStk = new Stock(catalyst, StockSymbols[catalyst], randInt(120, 175)/100, true, 13.5, randInt(250, 1.5e3));
var catalystStk = new Stock(catalyst, StockSymbols[catalyst], randInt(120, 175)/100, true, 13.5, randInt(250, 1.5e3), 100e9);
StockMarket[catalyst] = catalystStk;
var microdyne = "Microdyne Technologies";
var microdyneStk = new Stock(microdyne, StockSymbols[microdyne], randInt(70, 80)/100, true, 8, randInt(15e3, 30e3));
var microdyneStk = new Stock(microdyne, StockSymbols[microdyne], randInt(70, 80)/100, true, 8, randInt(15e3, 30e3), 360e9);
StockMarket[microdyne] = microdyneStk;
var titanlabs = "Titan Laboratories";
var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], randInt(50, 70)/100, true, 11, randInt(12e3, 24e3));
var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], randInt(50, 70)/100, true, 11, randInt(12e3, 24e3), 420e9);
StockMarket[titanlabs] = titanlabsStk;
var orders = {};
@ -411,6 +413,7 @@ function buyStock(stock, shares) {
return false;
}
// Does player have enough money?
var totalPrice = stock.price * shares;
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
dialogBoxCreate("You do not have enough money to purchase this. You need " +
@ -418,6 +421,13 @@ function buyStock(stock, shares) {
return false;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ` +
`${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
return false;
}
var origTotal = stock.playerShares * stock.playerAvgPx;
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
var newTotal = origTotal + totalPrice;
@ -470,6 +480,7 @@ function shortStock(stock, shares, workerScript=null) {
return false;
}
// Does the player have enough money?
var totalPrice = stock.price * shares;
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
if (tixApi) {
@ -484,6 +495,19 @@ function shortStock(stock, shares, workerScript=null) {
return false;
}
// Would this purchase exceed the maximum number of shares?
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) {
workerScript.scriptRef.log("ERROR: shortStock() failed because purchasing this many short shares would exceed " +
`${stock.symbol}'s maximum number of shares.`);
} else {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ` +
`${stock.maxShares} shares.`);
}
return false;
}
var origTotal = stock.playerShortShares * stock.playerAvgShortPx;
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
var newTotal = origTotal + totalPrice;
@ -1190,6 +1214,7 @@ function createStockTicker(stock) {
switch (ordType) {
case "Market Order":
var shares = Math.floor((money - COMM) / stock.price);
shares = Math.min(shares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares));
pos === PositionTypes.Long ? buyStock(stock, shares) : shortStock(stock, shares, null);
break;
case "Limit Order":
@ -1206,6 +1231,7 @@ function createStockTicker(stock) {
type = OrderTypes.StopBuy;
}
var shares = Math.floor((money-COMM) / price);
shares = Math.min(shares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares));
placeOrder(stock, shares, price, type, pos);
yesNoTxtInpBoxClose();
});
@ -1384,25 +1410,26 @@ function updateStockPlayerPosition(stock) {
if (isNaN(shortPercentageGains)) { shortPercentageGains = 0; }
stock.posTxtEl.innerHTML =
"<h1 class='tooltip stock-market-position-text'>Long Position: " +
`Max Shares: ${numeralWrapper.format(stock.maxShares, "0.000a")}<br>` +
"<h3 class='tooltip stock-market-position-text'>Long Position: " +
"<span class='tooltiptext'>Shares in the long position will increase " +
"in value if the price of the corresponding stock increases</span></h1>" +
"in value if the price of the corresponding stock increases</span></h3>" +
"<br>Shares: " + numeralWrapper.format(stock.playerShares, '0,0') +
"<br>Average Price: " + numeralWrapper.format(stock.playerAvgPx, '$0.000a') +
" (Total Cost: " + numeralWrapper.format(totalCost, '$0.000a') + ")" +
"<br>Profit: " + numeralWrapper.format(gains, '$0.000a') +
" (" + numeralWrapper.format(percentageGains, '0.00%') + ")<br><br>";
" (" + numeralWrapper.format(percentageGains, '0.00%') + ")<br>";
if (Player.bitNodeN === 8 || (hasWallStreetSF && wallStreetSFLvl >= 2)) {
stock.posTxtEl.innerHTML +=
"<h1 class='tooltip stock-market-position-text'>Short Position: " +
"<br><h3 class='tooltip stock-market-position-text'>Short Position: " +
"<span class='tooltiptext'>Shares in short position will increase " +
"in value if the price of the corresponding stock decreases</span></h1>" +
"in value if the price of the corresponding stock decreases</span></h3>" +
"<br>Shares: " + numeralWrapper.format(stock.playerShortShares, '0,0') +
"<br>Average Price: " + numeralWrapper.format(stock.playerAvgShortPx, '$0.000a') +
" (Total Cost: " + numeralWrapper.format(shortTotalCost, '$0.000a') + ")" +
"<br>Profit: " + numeralWrapper.format(shortGains, '$0.000a') +
"<br>Average Price: " + numeralWrapper.formatMoney(stock.playerAvgShortPx) +
" (Total Cost: " + numeralWrapper.formatMoney(shortTotalCost) + ")" +
"<br>Profit: " + numeralWrapper.formatMoney(shortGains) +
" (" + numeralWrapper.format(shortPercentageGains, '0.00%') + ")" +
"<br><br><h1 class='stock-market-position-text'>Orders: </h1>";
"<br><br><h3 class='stock-market-position-text'>Orders:</h3>";
}
}

@ -30,7 +30,7 @@ import {findRunningScript, RunningScript,
AllServersMap, isScriptFilename} from "./Script";
import {AllServers, GetServerByHostname,
getServer, Server} from "./Server";
import {Settings} from "./Settings";
import {Settings} from "./Settings/Settings";
import {SpecialServerIps,
SpecialServerNames} from "./SpecialServerIps";
import {getTextFile} from "./TextFile";

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