mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 17:43:48 +01:00
Fixed merge conflicts. Rebalanced new Hacknet Node mechanics. Adjusted Hacknet API so that it'll work with hacknet Servers. Fixed Corporation bug with Issuing new Shares
This commit is contained in:
commit
3f8b9e4a32
@ -5,7 +5,7 @@ played at https://danielyxie.github.io/bitburner.
|
|||||||
# Documentation
|
# Documentation
|
||||||
The game's official documentation can be found on [Read The
|
The game's official documentation can be found on [Read The
|
||||||
Docs](http://bitburner.readthedocs.io/). Please note that this is still a
|
Docs](http://bitburner.readthedocs.io/). Please note that this is still a
|
||||||
work-in-progress and is in its early stages.
|
work-in-progress.
|
||||||
|
|
||||||
The documentation is created using [Sphinx](http://www.sphinx-doc.org).
|
The documentation is created using [Sphinx](http://www.sphinx-doc.org).
|
||||||
|
|
||||||
@ -14,11 +14,6 @@ files](/doc/source) and then making a pull request with your contributions.
|
|||||||
For further guidance, please refer to the "As A Documentor" section of
|
For further guidance, please refer to the "As A Documentor" section of
|
||||||
[CONTRIBUTING](CONTRIBUTING.md).
|
[CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
# Wiki
|
|
||||||
The game's wiki can be found on [Wikia](http://bitburner.wikia.com/). Please
|
|
||||||
note that the wiki is in the process of being deprecated. Eventually all of
|
|
||||||
the wiki content will be moved into the Read The Docs documentation.
|
|
||||||
|
|
||||||
# Contribution
|
# Contribution
|
||||||
There are many ways to contribute to the game. It can be as simple as fixing
|
There are many ways to contribute to the game. It can be as simple as fixing
|
||||||
a typo, correcting a bug, or improving the UI. For guidance on doing so,
|
a typo, correcting a bug, or improving the UI. For guidance on doing so,
|
||||||
|
@ -51,6 +51,44 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Checkbox for (de)selecting autoleveling */
|
||||||
|
.bbcheckbox {
|
||||||
|
position: relative;
|
||||||
|
display: inline;
|
||||||
|
label {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: black;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: solid;
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
width: 9px;
|
||||||
|
height: 5px;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 5px;
|
||||||
|
border: 3px solid white;
|
||||||
|
border-top: none;
|
||||||
|
border-right: none;
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin: 3px;
|
||||||
|
visibility: hidden;
|
||||||
|
&:checked + label:after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Bladeburner Console */
|
/* Bladeburner Console */
|
||||||
.bladeburner-console-div {
|
.bladeburner-console-div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -38,6 +38,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.a-link-button-inactive,
|
.a-link-button-inactive,
|
||||||
|
.std-button-disabled,
|
||||||
.std-button:disabled {
|
.std-button:disabled {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
@import "theme";
|
@import "theme";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Styling for the Character Overview Panel (top-right)
|
* Styling for the Character Overview Panel (top-right panel)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#character-overview-wrapper {
|
#character-overview-wrapper {
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
#cmpy-mgmt-container p,
|
#cmpy-mgmt-container p,
|
||||||
#cmpy-mgmt-container a,
|
#cmpy-mgmt-container a,
|
||||||
#cmpy-mgmt-container div {
|
#cmpy-mgmt-container div,
|
||||||
|
#cmpy-mgmt-container br {
|
||||||
font-size: $defaultFontSize * 0.8125;
|
font-size: $defaultFontSize * 0.8125;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,5 +160,6 @@
|
|||||||
|
|
||||||
/* Research */
|
/* Research */
|
||||||
#corporation-research-popup-box-content {
|
#corporation-research-popup-box-content {
|
||||||
overflow-x: visible !important;
|
overflow-x: auto !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
}
|
}
|
||||||
|
36
css/dev-menu.css
Normal file
36
css/dev-menu.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
.add-exp-button {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-exp-button {
|
||||||
|
margin-left:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exp-input {
|
||||||
|
margin-right: 0px;
|
||||||
|
margin-left:0px;
|
||||||
|
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.touch-right {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.touch-left {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.touch-sides {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
6
css/grid.min.css
vendored
Normal file
6
css/grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
75
css/hacknetnodes.scss
Normal file
75
css/hacknetnodes.scss
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
@import "mixins";
|
||||||
|
@import "theme";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styling for the Hacknet Nodes UI Page
|
||||||
|
*/
|
||||||
|
|
||||||
|
#hacknet-nodes-container {
|
||||||
|
position: fixed;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hacknet-general-info {
|
||||||
|
margin: 10px;
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-container li {
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.hacknet-node {
|
||||||
|
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
|
||||||
|
@include boxShadow($boxShadowArgs);
|
||||||
|
|
||||||
|
margin: 6px;
|
||||||
|
padding: 7px;
|
||||||
|
width: 35vw;
|
||||||
|
border: 2px solid var(--my-highlight-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-list {
|
||||||
|
list-style: none;
|
||||||
|
width: 82vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-money {
|
||||||
|
margin: 10px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-money-multipliers-div {
|
||||||
|
display: inline-block;
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-multipliers {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hacknet-nodes-purchase-button {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hacknet-node-container {
|
||||||
|
display: inline-table;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: table-row;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgradable-info {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
|
||||||
|
padding: 0 4px;
|
||||||
|
width: $defaultFontSize * 4;
|
||||||
|
}
|
||||||
|
}
|
@ -138,81 +138,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hacknet Nodes */
|
|
||||||
#hacknet-nodes-container {
|
|
||||||
position: fixed;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-text,
|
|
||||||
#hacknet-nodes-container li {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-container li {
|
|
||||||
float: left;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.hacknet-node {
|
|
||||||
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
|
|
||||||
@include boxShadow($boxShadowArgs);
|
|
||||||
|
|
||||||
margin: 6px;
|
|
||||||
padding: 7px;
|
|
||||||
width: 35vw;
|
|
||||||
border: 2px solid var(--my-highlight-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-list {
|
|
||||||
list-style: none;
|
|
||||||
width: 82vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-money {
|
|
||||||
margin: 10px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-money-multipliers-div {
|
|
||||||
display: inline-block;
|
|
||||||
width: 70vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-multipliers {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hacknet-nodes-purchase-button {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hacknet-node-container {
|
|
||||||
display: inline-table;
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: table-row;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upgradable-info {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
|
|
||||||
padding: 0 4px;
|
|
||||||
width: $defaultFontSize * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-page-text {
|
|
||||||
width: 70vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* World */
|
/* World */
|
||||||
#world-container {
|
#world-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
/* Pop-up boxes */
|
/* Pop-up boxes */
|
||||||
.popup-box-container {
|
.popup-box-container {
|
||||||
display: none; /* Hidden by default */
|
display: none; /* Initially hidden */
|
||||||
position: fixed; /* Stay in place */
|
position: fixed; /* Stay in place */
|
||||||
z-index: 10; /* Sit on top */
|
z-index: 10; /* Sit on top */
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -62,6 +62,9 @@ a:visited {
|
|||||||
.text-input {
|
.text-input {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Notification icon (for create program right now only) */
|
/* Notification icon (for create program right now only) */
|
||||||
|
2
dist/engine.bundle.js
vendored
2
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2578
dist/engine.css
vendored
2578
dist/engine.css
vendored
File diff suppressed because it is too large
Load Diff
152
dist/vendor.bundle.js
vendored
152
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -14,6 +14,8 @@ Sleeve technology unlocks two different gameplay features:
|
|||||||
|
|
||||||
Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
|
Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
|
||||||
|
|
||||||
|
.. _gameplay_duplicatesleeves:
|
||||||
|
|
||||||
Duplicate Sleeves
|
Duplicate Sleeves
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
|
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
|
||||||
@ -28,6 +30,19 @@ Sleeves are their own individuals, which means they each have their own experien
|
|||||||
When a sleeve earns experience, it earns experience for itself, the player's
|
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.
|
original consciousness, as well as all of the player's other sleeves.
|
||||||
|
|
||||||
|
Duplicate Sleeves are **not** reset when installing Augmentations, but they are reset
|
||||||
|
when switching BitNodes.
|
||||||
|
|
||||||
|
Obtaining Duplicate Sleeves
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
There are two methods of obtaining Duplicate Sleeves:
|
||||||
|
|
||||||
|
1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve
|
||||||
|
2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant <gameplay_factions>`.
|
||||||
|
This is only available in BitNodes-10 and above, and is only available after defeating
|
||||||
|
BitNode-10 at least once. Sleeves purchased this way are **permanent** (they persist
|
||||||
|
through BitNodes). You can purchase up to 5 Duplicate Sleeves from The Covenant.
|
||||||
|
|
||||||
Synchronization
|
Synchronization
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
Synchronization is a measure of how aligned your consciousness is with that of your
|
Synchronization is a measure of how aligned your consciousness is with that of your
|
||||||
@ -50,15 +65,30 @@ 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
|
Sleeve shock slowly decreases over time. You can further increase the rate at which
|
||||||
it decreases by assigning sleeves to the 'Shock Recovery' task.
|
it decreases by assigning sleeves to the 'Shock Recovery' task.
|
||||||
|
|
||||||
Obtaining Duplicate Sleeves
|
Augmentations
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
There are two methods of obtaining Duplicate Sleeves:
|
You can purchase :ref:`Augmentations <gameplay_augmentations>` for your Duplicate
|
||||||
|
Sleeves. In order to do this, the Sleeve's Shock must be at 0. Any Augmentation
|
||||||
|
that is currently available to you through a faction is also available for your
|
||||||
|
Duplicate Sleeves. There are a few Augmentations, such as NeuroFlux Governor and
|
||||||
|
Bladeburner-specific ones, that cannot be purchased for a Duplicate Sleeve.
|
||||||
|
|
||||||
1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve
|
When you purchase an Augmentation for a Duplicate Sleeve, it is instantly installed.
|
||||||
2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant <gameplay_factions>`.
|
When this happens, the Sleeve's stats are instantly reset back to 0, similar to
|
||||||
This is only available in BitNodes-10 and above, and is only available after defeating
|
when you normally install Augmentations.
|
||||||
BitNode-10 at least once. Sleeves purchased this way are permanent. You can purchase
|
|
||||||
up to 5 Duplicate Sleeves from The Covenant.
|
The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected
|
||||||
|
by how many Augmentations you have purchased for yourself, and vice versa.
|
||||||
|
|
||||||
|
Memory
|
||||||
|
~~~~~~
|
||||||
|
Sleeve memory dictates what a sleeve's synchronization will be when its reset by
|
||||||
|
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
|
||||||
|
switch BitNodes its synchronization will initially be set to 10, rather than 1.
|
||||||
|
|
||||||
|
Memory can only be increased by purchasing upgrades from The Covenant.
|
||||||
|
It is a persistent stat, meaning it never gets reset back to 1.
|
||||||
|
The maximum possible value for a sleeve's memory is 100.
|
||||||
|
|
||||||
Re-sleeving
|
Re-sleeving
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
|
@ -48,6 +48,7 @@ List of all Source-Files
|
|||||||
| BitNode-9: Coming Soon | |
|
| BitNode-9: Coming Soon | |
|
||||||
+------------------------------------+-------------------------------------------------------------------------------------+
|
+------------------------------------+-------------------------------------------------------------------------------------+
|
||||||
| BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve |
|
| BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve |
|
||||||
|
| | * Allows the player to access the :ref:`netscript_sleeveapi` in other BitNodes |
|
||||||
+------------------------------------+-------------------------------------------------------------------------------------+
|
+------------------------------------+-------------------------------------------------------------------------------------+
|
||||||
| BitNode-11: The Big Crash | * Company favor increases both the player's salary and reputation gain at that |
|
| 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) |
|
| | company by 1% per favor (rather than just the reputation gain) |
|
||||||
|
@ -3,6 +3,42 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
v0.45.1 - 3/23/2019
|
||||||
|
-------------------
|
||||||
|
* Added two new Corporation Researches
|
||||||
|
* General UI improvements (by hydroflame and koriar)
|
||||||
|
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors
|
||||||
|
* Bug Fix: sleeve.getSleeveStats() should now work properly
|
||||||
|
|
||||||
|
v0.45.0 - 3/22/2019
|
||||||
|
-------------------
|
||||||
|
* Corporation changes:
|
||||||
|
* Decreased the time of a full market cycle from 15 seconds to 10 seconds.
|
||||||
|
* This means that each Corporation 'state' will now only take 2 seconds, rather than 3
|
||||||
|
* Increased initial salaries for newly-hired employees
|
||||||
|
* Increased the cost multiplier for upgrading office size (the cost will increase faster)
|
||||||
|
* The stats of your employees now has a slightly larger effect on production & sales
|
||||||
|
* Added several new Research upgrades
|
||||||
|
* Market-TA research now allows you to automatically set sale price at optimal values
|
||||||
|
* Market-TA research now works for Products (not just Materials)
|
||||||
|
* Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k
|
||||||
|
* Energy Material requirement of the Software industry reduced from 1 to 0.5
|
||||||
|
* It is now slightly easier to increase the Software industry's production multiplier
|
||||||
|
* Industries now have a maximum number of allowed products, starting at 3. This can be increased through research.
|
||||||
|
* You can now see an approximation of how each material affects an industry's production multiplier by clicking the "?" help tip next to it
|
||||||
|
* Significantly changed the effects of the different employee positions. See updated descriptions
|
||||||
|
* Reduced the amount of money you gain from private investors
|
||||||
|
* Training employees is now 3x more effective
|
||||||
|
* Bug Fix: An industry's products are now properly separated between different cities
|
||||||
|
|
||||||
|
* The QLink Augemntation is now significantly stronger, but also significantly more expensive (by hydroflame)
|
||||||
|
* Added a Netscript API for Duplicate Sleeves (by hydroflame)
|
||||||
|
* Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder
|
||||||
|
* After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0
|
||||||
|
* Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina
|
||||||
|
* Bug Fix: growthAnalyze() function now properly accounts for BitNode multipliers
|
||||||
|
* Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself
|
||||||
|
|
||||||
v0.44.1 - 3/4/2019
|
v0.44.1 - 3/4/2019
|
||||||
------------------
|
------------------
|
||||||
* Duplicate Sleeve changes:
|
* Duplicate Sleeve changes:
|
||||||
|
@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.44'
|
version = '0.45'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.44.1'
|
release = '0.45.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -29,4 +29,5 @@ to reach out to the developer!
|
|||||||
Bladeburner API <netscript/netscriptbladeburnerapi>
|
Bladeburner API <netscript/netscriptbladeburnerapi>
|
||||||
Gang API <netscript/netscriptgangapi>
|
Gang API <netscript/netscriptgangapi>
|
||||||
Coding Contract API <netscript/netscriptcodingcontractapi>
|
Coding Contract API <netscript/netscriptcodingcontractapi>
|
||||||
|
Sleeve API <netscript/netscriptsleeveapi>
|
||||||
Miscellaneous <netscript/netscriptmisc>
|
Miscellaneous <netscript/netscriptmisc>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
enableLog() Netscript Function
|
enableLog() Netscript Function
|
||||||
=============================
|
==============================
|
||||||
|
|
||||||
.. js:function:: enableLog(fn)
|
.. js:function:: enableLog(fn)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ purchaseServer() Netscript Function
|
|||||||
.. js:function:: purchaseServer(hostname, ram)
|
.. js:function:: purchaseServer(hostname, ram)
|
||||||
|
|
||||||
:param string hostname: Hostname of the purchased server
|
:param string hostname: Hostname of the purchased server
|
||||||
:param number ram: Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20)
|
:param number ram: Amount of RAM of the purchased server. Must be a power of 2. Maximum value of :js:func:`getPurchasedServerMaxRam`
|
||||||
:RAM cost: 2.25 GB
|
:RAM cost: 2.25 GB
|
||||||
|
|
||||||
Purchased a server with the specified hostname and amount of RAM.
|
Purchased a server with the specified hostname and amount of RAM.
|
||||||
|
@ -4,6 +4,8 @@ Netscript Advanced Functions
|
|||||||
These Netscript functions become relevant later on in the game. They are put on a separate page because
|
These Netscript functions become relevant later on in the game. They are put on a separate page because
|
||||||
they contain spoilers for the game.
|
they contain spoilers for the game.
|
||||||
|
|
||||||
|
.. warning:: This page contains spoilers for the game
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
getBitNodeMultipliers() <advancedfunctions/getBitNodeMultipliers>
|
getBitNodeMultipliers() <advancedfunctions/getBitNodeMultipliers>
|
||||||
|
@ -7,7 +7,7 @@ Netscript provides the following API for interacting with the game's Bladeburner
|
|||||||
The Bladeburner API is **not** immediately available to the player and must be unlocked
|
The Bladeburner API is **not** immediately available to the player and must be unlocked
|
||||||
later in the game
|
later in the game
|
||||||
|
|
||||||
**WARNING: This page contains spoilers for the game**
|
.. warning:: This page contains spoilers for the game
|
||||||
|
|
||||||
The Bladeburner API is unlocked in BitNode-7. If you are in BitNode-7, you will
|
The Bladeburner API is unlocked in BitNode-7. If you are in BitNode-7, you will
|
||||||
automatically gain access to this API. Otherwise, you must have Source-File 7 in
|
automatically gain access to this API. Otherwise, you must have Source-File 7 in
|
||||||
|
@ -6,7 +6,7 @@ Netscript provides the following API for interacting with the game's Gang mechan
|
|||||||
The Gang API is **not** immediately available to the player and must be unlocked
|
The Gang API is **not** immediately available to the player and must be unlocked
|
||||||
later in the game
|
later in the game
|
||||||
|
|
||||||
**WARNING: This page contains spoilers for the game**
|
.. warning:: This page contains spoilers for the game
|
||||||
|
|
||||||
The Gang API is unlocked in BitNode-2. Currently, BitNode-2 is the only location
|
The Gang API is unlocked in BitNode-2. Currently, BitNode-2 is the only location
|
||||||
where the Gang mechanic is accessible. This may change in the future
|
where the Gang mechanic is accessible. This may change in the future
|
||||||
|
@ -9,7 +9,7 @@ and creating programs.
|
|||||||
|
|
||||||
The Singularity Functions are **not** immediately available to the player and must be unlocked later in the game.
|
The Singularity Functions are **not** immediately available to the player and must be unlocked later in the game.
|
||||||
|
|
||||||
**WARNING: This page contains spoilers for the game**.
|
.. warning:: This page contains spoilers for the game
|
||||||
|
|
||||||
The Singularity Functions are unlocked in BitNode-4. If you are in BitNode-4, then you will automatically have access to all of these functions.
|
The Singularity Functions are unlocked in BitNode-4. If you are in BitNode-4, then you will automatically have access to all of these functions.
|
||||||
You can use the Singularity Functions in other BitNodes if and only if you have the Source-File for BitNode-4 (aka Source-File 4). Each level of
|
You can use the Singularity Functions in other BitNodes if and only if you have the Source-File for BitNode-4 (aka Source-File 4). Each level of
|
||||||
|
76
doc/source/netscript/netscriptsleeveapi.rst
Normal file
76
doc/source/netscript/netscriptsleeveapi.rst
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
.. _netscript_sleeveapi:
|
||||||
|
|
||||||
|
Netscript Sleeve API
|
||||||
|
=========================
|
||||||
|
Netscript provides the following API for interacting with the game's
|
||||||
|
:ref:`Duplicate Sleeve <gameplay_duplicatesleeves>` mechanic.
|
||||||
|
|
||||||
|
The Sleeve API is **not** immediately available to the player and must be unlocked
|
||||||
|
later in the game.
|
||||||
|
|
||||||
|
.. warning:: This page contains spoilers for the game
|
||||||
|
|
||||||
|
The Sleeve API is unlocked in BitNode-10. If you are in BitNode-10, you will
|
||||||
|
automatically gain access to this API. Otherwise, you must have Source-File 10 in
|
||||||
|
order to use this API in other BitNodes
|
||||||
|
|
||||||
|
**Sleeve API functions must be accessed through the 'sleeve' namespace**
|
||||||
|
|
||||||
|
In :ref:`netscript1`::
|
||||||
|
|
||||||
|
sleeve.synchronize(0);
|
||||||
|
sleeve.commitCrime(0, "shoplift");
|
||||||
|
|
||||||
|
In :ref:`netscriptjs`::
|
||||||
|
|
||||||
|
ns.sleeve.synchronize(0);
|
||||||
|
ns.sleeve.commitCrime(0, "shoplift");
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: API Functions:
|
||||||
|
|
||||||
|
getNumSleeves() <sleeveapi/getNumSleeves>
|
||||||
|
getSleeveStats() <sleeveapi/getSleeveStats>
|
||||||
|
getInformation() <sleeveapi/getInformation>
|
||||||
|
getTask() <sleeveapi/getTask>
|
||||||
|
setToShockRecovery() <sleeveapi/setToShockRecovery>
|
||||||
|
setToSynchronize() <sleeveapi/setToSynchronize>
|
||||||
|
setToCommitCrime() <sleeveapi/setToCommitCrime>
|
||||||
|
setToFactionWork() <sleeveapi/setToFactionWork>
|
||||||
|
setToCompanyWork() <sleeveapi/setToCompanyWork>
|
||||||
|
setToUniversityCourse() <sleeveapi/setToUniversityCourse>
|
||||||
|
setToGymWorkout() <sleeveapi/setToGymWorkout>
|
||||||
|
travel() <sleeveapi/travel>
|
||||||
|
|
||||||
|
.. _netscript_sleeveapi_referencingaduplicatesleeve:
|
||||||
|
|
||||||
|
Referencing a Duplicate Sleeve
|
||||||
|
------------------------------
|
||||||
|
Most of the functions in the Sleeve API perform an operation on a single Duplicate
|
||||||
|
Sleeve. In order to specify which Sleeve the operation should be performed on,
|
||||||
|
a numeric index is used as an identifier. The index should follow array-notation, such
|
||||||
|
that the first Duplicate Sleeve has an index of 0, the second Duplicate Sleeve has
|
||||||
|
an index of 1, and so on.
|
||||||
|
|
||||||
|
The order of the Duplicate Sleeves matches the order on the UI page.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
**Basic example usage**::
|
||||||
|
|
||||||
|
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
|
||||||
|
sleeve.shockRecovery(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(10*60*60); // wait 10h
|
||||||
|
|
||||||
|
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
|
||||||
|
sleeve.synchronize(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(10*60*60); // wait 10h
|
||||||
|
|
||||||
|
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
|
||||||
|
sleeve.commitCrime(i, 'shoplift');
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
workForCompany() Netscript Function
|
workForCompany() Netscript Function
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
.. js:function:: workForCompany()
|
.. js:function:: workForCompany(companyName=lastCompany)
|
||||||
|
|
||||||
|
:param string companyName: Name of company to work for. Must be an exact match.
|
||||||
|
Optional. If not specified, this argument defaults to
|
||||||
|
the last job that you worked
|
||||||
|
|
||||||
If you are not in BitNode-4, then you must have Level 2 of Source-File 4 in order to use this function.
|
If you are not in BitNode-4, then you must have Level 2 of Source-File 4 in order to use this function.
|
||||||
|
|
||||||
|
65
doc/source/netscript/sleeveapi/getInformation.rst
Normal file
65
doc/source/netscript/sleeveapi/getInformation.rst
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
getInformation() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: getInformation(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to retrieve information. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return a struct containing tons of information about this sleeve
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
city: location of the sleeve,
|
||||||
|
hp: current hp of the sleeve,
|
||||||
|
maxHp: max hp of the sleeve,
|
||||||
|
jobs: jobs available to the sleeve,
|
||||||
|
jobTitle: job titles available to the sleeve,
|
||||||
|
tor: does this sleeve have access to the tor router,
|
||||||
|
mult: {
|
||||||
|
agility: agility multiplier,
|
||||||
|
agilityExp: agility exp multiplier,
|
||||||
|
companyRep: company reputation multiplier,
|
||||||
|
crimeMoney: crime money multiplier,
|
||||||
|
crimeSuccess: crime success chance multiplier,
|
||||||
|
defense: defense multiplier,
|
||||||
|
defenseExp: defense exp multiplier,
|
||||||
|
dexterity: dexterity multiplier,
|
||||||
|
dexterityExp: dexterity exp multiplier,
|
||||||
|
factionRep: faction reputation multiplier,
|
||||||
|
hacking: hacking skill multiplier,
|
||||||
|
hackingExp: hacking exp multiplier,
|
||||||
|
strength: strength multiplier,
|
||||||
|
strengthExp: strength exp multiplier,
|
||||||
|
workMoney: work money multiplier,
|
||||||
|
},
|
||||||
|
timeWorked: time spent on the current task in milliseconds,
|
||||||
|
earningsForSleeves : { earnings synchronized to other sleeves
|
||||||
|
workHackExpGain: hacking exp gained from work,
|
||||||
|
workStrExpGain: strength exp gained from work,
|
||||||
|
workDefExpGain: defense exp gained from work,
|
||||||
|
workDexExpGain: dexterity exp gained from work,
|
||||||
|
workAgiExpGain: agility exp gained from work,
|
||||||
|
workChaExpGain: charisma exp gained from work,
|
||||||
|
workMoneyGain: money gained from work,
|
||||||
|
},
|
||||||
|
earningsForPlayer : { earnings synchronized to the player
|
||||||
|
workHackExpGain: hacking exp gained from work,
|
||||||
|
workStrExpGain: strength exp gained from work,
|
||||||
|
workDefExpGain: defense exp gained from work,
|
||||||
|
workDexExpGain: dexterity exp gained from work,
|
||||||
|
workAgiExpGain: agility exp gained from work,
|
||||||
|
workChaExpGain: charisma exp gained from work,
|
||||||
|
workMoneyGain: money gained from work,
|
||||||
|
},
|
||||||
|
earningsForTask : { earnings for this sleeve
|
||||||
|
workHackExpGain: hacking exp gained from work,
|
||||||
|
workStrExpGain: strength exp gained from work,
|
||||||
|
workDefExpGain: defense exp gained from work,
|
||||||
|
workDexExpGain: dexterity exp gained from work,
|
||||||
|
workAgiExpGain: agility exp gained from work,
|
||||||
|
workChaExpGain: charisma exp gained from work,
|
||||||
|
workMoneyGain: money gained from work,
|
||||||
|
},
|
||||||
|
workRepGain: sl.getRepGain(),
|
||||||
|
}
|
6
doc/source/netscript/sleeveapi/getNumSleeves.rst
Normal file
6
doc/source/netscript/sleeveapi/getNumSleeves.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
getNumSleeves() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: getNumSleeves()
|
||||||
|
|
||||||
|
Return the number of duplicate sleeves the player has.
|
@ -0,0 +1,8 @@
|
|||||||
|
getSleeveAugmentations() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: getSleeveAugmentations(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to retrieve augmentations from. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return a list of augmentation names that this sleeve has installed.
|
17
doc/source/netscript/sleeveapi/getSleevePurchasableAugs.rst
Normal file
17
doc/source/netscript/sleeveapi/getSleevePurchasableAugs.rst
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
getSleevePurchasableAugs() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: getSleevePurchasableAugs(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to retrieve purchasable augmentations from. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return a list of augmentations that the player can buy for this sleeve.
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: string, // augmentation name
|
||||||
|
cost: number, // augmentation cost
|
||||||
|
}
|
||||||
|
]
|
21
doc/source/netscript/sleeveapi/getSleeveStats.rst
Normal file
21
doc/source/netscript/sleeveapi/getSleeveStats.rst
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
getSleeveStats() Netscript Function
|
||||||
|
===================================
|
||||||
|
|
||||||
|
.. js:function:: getSleeveStats(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to get stats of. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return a structure containing the stats of the sleeve
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
shock: current shock of the sleeve [0-100],
|
||||||
|
sync: current sync of the sleeve [0-100],
|
||||||
|
hacking_skill: current hacking skill of the sleeve,
|
||||||
|
strength: current strength of the sleeve,
|
||||||
|
defense: current defense of the sleeve,
|
||||||
|
dexterity: current dexterity of the sleeve,
|
||||||
|
agility: current agility of the sleeve,
|
||||||
|
charisma: current charisma of the sleeve,
|
||||||
|
}
|
18
doc/source/netscript/sleeveapi/getTask.rst
Normal file
18
doc/source/netscript/sleeveapi/getTask.rst
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
getTask() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: getTask(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to retrieve task from. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return the current task that the sleeve is performing. type is set to "Idle" if the sleeve isn't doing anything
|
||||||
|
|
||||||
|
.. code-block:: javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
task: string, // task type
|
||||||
|
crime: string, // crime currently attempting, if any
|
||||||
|
location: string, // location of the task, if any
|
||||||
|
gymStatType: string, // stat being trained at the gym, if any
|
||||||
|
factionWorkType: string, // faction work type being performed, if any
|
||||||
|
}
|
9
doc/source/netscript/sleeveapi/purchaseSleeveAug.rst
Normal file
9
doc/source/netscript/sleeveapi/purchaseSleeveAug.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
purchaseSleeveAug() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: purchaseSleeveAug(sleeveNumber, augName)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to buy an aug for. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string augName: Name of the aug to buy. Must be an exact match
|
||||||
|
|
||||||
|
Return true if the aug was purchased and installed on the sleeve.
|
11
doc/source/netscript/sleeveapi/setToCommitCrime.rst
Normal file
11
doc/source/netscript/sleeveapi/setToCommitCrime.rst
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
setToCommitCrime() Netscript Function
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. js:function:: setToCommitCrime(sleeveNumber, name)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to start commiting crime. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string name: Name of the crime. Must be an exact match.
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not this action was set successfully.
|
||||||
|
|
||||||
|
Returns false if an invalid action is specified.
|
9
doc/source/netscript/sleeveapi/setToCompanyWork.rst
Normal file
9
doc/source/netscript/sleeveapi/setToCompanyWork.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
setToCompanyWork() Netscript Function
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. js:function:: setToCompanyWork(sleeveNumber, companyName)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to work for the company. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string companyName: Name of the company to work for.
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not the sleeve started working or this company.
|
10
doc/source/netscript/sleeveapi/setToFactionWork.rst
Normal file
10
doc/source/netscript/sleeveapi/setToFactionWork.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
setToFactionWork() Netscript Function
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. js:function:: setToFactionWork(sleeveNumber, factionName, factionWorkType)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to work for the faction. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string factionName: Name of the faction to work for.
|
||||||
|
:param string factionWorkType: Name of the action to perform for this faction.
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not the sleeve started working or this faction.
|
10
doc/source/netscript/sleeveapi/setToGymWorkout.rst
Normal file
10
doc/source/netscript/sleeveapi/setToGymWorkout.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
setToGymWorkout() Netscript Function
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. js:function:: setToGymWorkout(sleeveNumber, gymName, stat)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to workout at the gym. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string gymName: Name of the gym.
|
||||||
|
:param string stat: Name of the stat to train.
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not the sleeve started working out.
|
8
doc/source/netscript/sleeveapi/setToShockRecovery.rst
Normal file
8
doc/source/netscript/sleeveapi/setToShockRecovery.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
setToShockRecovery() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: setToShockRecovery(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to start recovery. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not this action was set successfully.
|
8
doc/source/netscript/sleeveapi/setToSynchronize.rst
Normal file
8
doc/source/netscript/sleeveapi/setToSynchronize.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
setToSynchronize() Netscript Function
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
.. js:function:: setToSynchronize(sleeveNumber)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to start synchronizing. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not this action was set successfully.
|
10
doc/source/netscript/sleeveapi/setToUniversityCourse.rst
Normal file
10
doc/source/netscript/sleeveapi/setToUniversityCourse.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
setToUniversityCourse() Netscript Function
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
.. js:function:: setToUniversityCourse(sleeveNumber, university, className)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to start taking class. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string university: Name of the university to attend.
|
||||||
|
:param string className: Name of the class to follow.
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not this action was set successfully.
|
9
doc/source/netscript/sleeveapi/travel.rst
Normal file
9
doc/source/netscript/sleeveapi/travel.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
travel() Netscript Function
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. js:function:: travel(sleeveNumber, cityName)
|
||||||
|
|
||||||
|
:param int sleeveNumber: Index of the sleeve to travel. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||||
|
:param string cityName: Name of the destination city.
|
||||||
|
|
||||||
|
Return a boolean indicating whether or not the sleeve reached destination.
|
42
index.html
42
index.html
@ -131,7 +131,7 @@
|
|||||||
<h1 style="color:white;"> Script Editor Options </h1>
|
<h1 style="color:white;"> Script Editor Options </h1>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="script-editor-option-editor">Editor</label>
|
<label for="script-editor-option-editor">Editor</label>
|
||||||
<select id="script-editor-option-editor">
|
<select id="script-editor-option-editor" class="dropdown">
|
||||||
<option value="Ace">Ace</option>
|
<option value="Ace">Ace</option>
|
||||||
<option value="CodeMirror">CodeMirror</option>
|
<option value="CodeMirror">CodeMirror</option>
|
||||||
</select>
|
</select>
|
||||||
@ -139,12 +139,12 @@
|
|||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="script-editor-option-theme">Theme</label>
|
<label for="script-editor-option-theme">Theme</label>
|
||||||
<select id="script-editor-option-theme"></select>
|
<select id="script-editor-option-theme" class="dropdown"></select>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="script-editor-option-keybinding">Key Binding</label>
|
<label for="script-editor-option-keybinding">Key Binding</label>
|
||||||
<select id="script-editor-option-keybinding"></select>
|
<select id="script-editor-option-keybinding" class="dropdown"></select>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -689,7 +689,7 @@
|
|||||||
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
|
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
|
||||||
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
|
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<input id="stock-market-watchlist-filter" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
|
<input id="stock-market-watchlist-filter" class="text-input" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
|
||||||
<a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a>
|
<a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a>
|
||||||
<ul id="stock-market-list" style="list-style:none;">
|
<ul id="stock-market-list" style="list-style:none;">
|
||||||
</ul>
|
</ul>
|
||||||
@ -744,7 +744,7 @@
|
|||||||
<p id="infiltration-box-text"> </p>
|
<p id="infiltration-box-text"> </p>
|
||||||
|
|
||||||
<button id="infiltration-box-sell" class="a-link-button"> Sell on Black Market </button> <br/><br/>
|
<button id="infiltration-box-sell" class="a-link-button"> Sell on Black Market </button> <br/><br/>
|
||||||
<select id="infiltration-faction-select"> </select> <br/>
|
<select id="infiltration-faction-select" class="dropdown"> </select> <br/>
|
||||||
<button id="infiltration-box-faction" class="a-link-button"> Give to Faction for Reputation </button>
|
<button id="infiltration-box-faction" class="a-link-button"> Give to Faction for Reputation </button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -783,35 +783,7 @@
|
|||||||
<div id="character-overview-wrapper">
|
<div id="character-overview-wrapper">
|
||||||
<div id="character-overview-container">
|
<div id="character-overview-container">
|
||||||
<div id="character-overview-text">
|
<div id="character-overview-text">
|
||||||
<table>
|
<!-- ReactJS Component -->
|
||||||
<tr id="character-hp-wrapper">
|
|
||||||
<td>Hp:</td><td id="character-hp-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-money-wrapper">
|
|
||||||
<td>Money: </td><td id="character-money-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-hack-wrapper">
|
|
||||||
<td>Hack: </td><td id="character-hack-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-str-wrapper">
|
|
||||||
<td>Str: </td><td id="character-str-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-def-wrapper">
|
|
||||||
<td>Def: </td><td id="character-def-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-dex-wrapper">
|
|
||||||
<td>Dex: </td><td id="character-dex-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-agi-wrapper">
|
|
||||||
<td>Agi: </td><td id="character-agi-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-cha-wrapper">
|
|
||||||
<td>Cha: </td><td id="character-cha-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
<tr id="character-int-wrapper">
|
|
||||||
<td>Int: </td><td id="character-int-text" class="character-stat-cell"></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="character-quick-options">
|
<div class="character-quick-options">
|
||||||
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
|
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
|
||||||
@ -959,7 +931,7 @@
|
|||||||
Sets the locale for displaying numbers. Defaults to 'en'
|
Sets the locale for displaying numbers. Defaults to 'en'
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="settingsLocale" id="settingsLocale">
|
<select name="settingsLocale" id="settingsLocale" class="dropdown">
|
||||||
<option value="en">en</option>
|
<option value="en">en</option>
|
||||||
<option value="bg">bg</option>
|
<option value="bg">bg</option>
|
||||||
<option value="cs">cs</option>
|
<option value="cs">cs</option>
|
||||||
|
1407
package-lock.json
generated
1407
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -24,7 +24,7 @@
|
|||||||
"file-saver": "^1.3.8",
|
"file-saver": "^1.3.8",
|
||||||
"interpret": "^1.0.0",
|
"interpret": "^1.0.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"jshint": "^2.9.7",
|
"jshint": "^2.10.2",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"jsplumb": "^2.6.8",
|
"jsplumb": "^2.6.8",
|
||||||
"jszip": "^3.1.5",
|
"jszip": "^3.1.5",
|
||||||
@ -37,7 +37,6 @@
|
|||||||
"react-dom": "^16.8.3",
|
"react-dom": "^16.8.3",
|
||||||
"sprintf-js": "^1.1.1",
|
"sprintf-js": "^1.1.1",
|
||||||
"tapable": "^1.0.0",
|
"tapable": "^1.0.0",
|
||||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"w3c-blob": "0.0.1"
|
"w3c-blob": "0.0.1"
|
||||||
},
|
},
|
||||||
@ -61,7 +60,7 @@
|
|||||||
"istanbul": "^0.4.5",
|
"istanbul": "^0.4.5",
|
||||||
"js-beautify": "^1.5.10",
|
"js-beautify": "^1.5.10",
|
||||||
"json5": "^1.0.1",
|
"json5": "^1.0.1",
|
||||||
"less": "^3.0.4",
|
"less": "^3.9.0",
|
||||||
"less-loader": "^4.1.0",
|
"less-loader": "^4.1.0",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"mini-css-extract-plugin": "^0.4.1",
|
"mini-css-extract-plugin": "^0.4.1",
|
||||||
@ -80,15 +79,17 @@
|
|||||||
"stylelint": "^9.2.1",
|
"stylelint": "^9.2.1",
|
||||||
"stylelint-declaration-use-variable": "^1.6.1",
|
"stylelint-declaration-use-variable": "^1.6.1",
|
||||||
"stylelint-order": "^0.8.1",
|
"stylelint-order": "^0.8.1",
|
||||||
"ts-loader": "^4.4.1",
|
"ts-loader": "^4.5.0",
|
||||||
"tslint": "^5.10.0",
|
"tslint": "^5.10.0",
|
||||||
"typescript": "^2.9.2",
|
"typescript": "^2.9.2",
|
||||||
|
"uglify-es": "^3.3.9",
|
||||||
|
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||||
"url-loader": "^1.0.1",
|
"url-loader": "^1.0.1",
|
||||||
"watchpack": "^1.6.0",
|
"watchpack": "^1.6.0",
|
||||||
"webpack": "^4.12.0",
|
"webpack": "^4.12.0",
|
||||||
"webpack-cli": "^3.0.4",
|
"webpack-cli": "^3.0.4",
|
||||||
"webpack-dev-middleware": "^3.1.3",
|
"webpack-dev-middleware": "^3.1.3",
|
||||||
"webpack-dev-server": "^3.1.4",
|
"webpack-dev-server": "^3.2.1",
|
||||||
"worker-loader": "^2.0.0"
|
"worker-loader": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -113,5 +114,5 @@
|
|||||||
"watch": "webpack --watch --mode production",
|
"watch": "webpack --watch --mode production",
|
||||||
"watch:dev": "webpack --watch --mode development"
|
"watch:dev": "webpack --watch --mode development"
|
||||||
},
|
},
|
||||||
"version": "0.40.2"
|
"version": "0.45.0"
|
||||||
}
|
}
|
||||||
|
@ -1213,17 +1213,19 @@ function initAugmentations() {
|
|||||||
|
|
||||||
//Illuminati
|
//Illuminati
|
||||||
var QLink = new Augmentation({
|
var QLink = new Augmentation({
|
||||||
name:AugmentationNames.QLink, repCost:750e3, moneyCost:1300e6,
|
name:AugmentationNames.QLink, repCost:750e3, moneyCost:5e12,
|
||||||
info:"A brain implant that wirelessly connects you to the Illuminati's " +
|
info:"A brain implant that wirelessly connects you to the Illuminati's " +
|
||||||
"quantum supercomputer, allowing you to access and use its incredible " +
|
"quantum supercomputer, allowing you to access and use its incredible " +
|
||||||
"computing power.<br><br>" +
|
"computing power.<br><br>" +
|
||||||
"This augmentation:<br>" +
|
"This augmentation:<br>" +
|
||||||
"Increases the player's hacking speed by 10%.<br>" +
|
"Increases the player's hacking skill by 75%.<br>" +
|
||||||
"Increases the player's chance of successfully performing a hack by 30%.<br>" +
|
"Increases the player's hacking speed by 100%.<br>" +
|
||||||
"Increases the amount of money the player gains from hacking by 100%.",
|
"Increases the player's chance of successfully performing a hack by 150%.<br>" +
|
||||||
hacking_speed_mult: 1.1,
|
"Increases the amount of money the player gains from hacking by 300%.",
|
||||||
hacking_chance_mult: 1.3,
|
hacking_mult: 1.75,
|
||||||
hacking_money_mult: 2,
|
hacking_speed_mult: 2,
|
||||||
|
hacking_chance_mult: 2.5,
|
||||||
|
hacking_money_mult: 4,
|
||||||
});
|
});
|
||||||
QLink.addToFactions(["Illuminati"]);
|
QLink.addToFactions(["Illuminati"]);
|
||||||
if (augmentationExists(AugmentationNames.QLink)) {
|
if (augmentationExists(AugmentationNames.QLink)) {
|
||||||
@ -2067,7 +2069,7 @@ function installAugmentations(cbScript=null) {
|
|||||||
}
|
}
|
||||||
var runningScriptObj = new RunningScript(script, []); //No args
|
var runningScriptObj = new RunningScript(script, []); //No args
|
||||||
runningScriptObj.threads = 1; //Only 1 thread
|
runningScriptObj.threads = 1; //Only 1 thread
|
||||||
home.runningScripts.push(runningScriptObj);
|
home.runScript(runningScriptObj, Player);
|
||||||
addWorkerScript(runningScriptObj, home);
|
addWorkerScript(runningScriptObj, home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,10 @@ export function initBitNodes() {
|
|||||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||||
"This Source-File also increases your hacking growth multipliers by: " +
|
"This Source-File also increases your hacking growth multipliers by: " +
|
||||||
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
||||||
BitNodes["BitNode9"] = new BitNode(9, "Do Androids Dream?", "COMING SOON");
|
BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
|
||||||
|
"When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
|
||||||
|
"became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
|
||||||
|
"powering the Hacknet, ");
|
||||||
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
|
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
|
||||||
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
|
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
|
||||||
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
|
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
|
||||||
@ -198,8 +201,9 @@ export function initBitNodes() {
|
|||||||
"were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
|
"were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
|
||||||
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
|
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
|
||||||
"In this BitNode:<br><br>" +
|
"In this BitNode:<br><br>" +
|
||||||
|
"Your hacking stat and experience gain are halved<br>" +
|
||||||
"The starting and maximum amount of money available on servers is significantly decreased<br>" +
|
"The starting and maximum amount of money available on servers is significantly decreased<br>" +
|
||||||
"The growth rate of servers is halved<br>" +
|
"The growth rate of servers is significantly reduced<br>" +
|
||||||
"Weakening a server is twice as effective<br>" +
|
"Weakening a server is twice as effective<br>" +
|
||||||
"Company wages are decreased by 50%<br>" +
|
"Company wages are decreased by 50%<br>" +
|
||||||
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
|
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
|
||||||
@ -210,9 +214,9 @@ export function initBitNodes() {
|
|||||||
"upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " +
|
"upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " +
|
||||||
"the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
|
"the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
|
||||||
"This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
|
"This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
|
||||||
"Level 1: 24%<br>" +
|
"Level 1: 32%<br>" +
|
||||||
"Level 2: 36%<br>" +
|
"Level 2: 48%<br>" +
|
||||||
"Level 3: 42%");
|
"Level 3: 56%");
|
||||||
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
|
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
|
||||||
"To iterate is human, to recurse divine.<br><br>" +
|
"To iterate is human, to recurse divine.<br><br>" +
|
||||||
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
|
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
|
||||||
@ -245,9 +249,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (p.bitNodeN) {
|
switch (p.bitNodeN) {
|
||||||
case 1: //Source Genesis (every multiplier is 1)
|
case 1: // Source Genesis (every multiplier is 1)
|
||||||
break;
|
break;
|
||||||
case 2: //Rise of the Underworld
|
case 2: // Rise of the Underworld
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||||
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
||||||
@ -257,7 +261,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
||||||
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
||||||
break;
|
break;
|
||||||
case 3: //Corporatocracy
|
case 3: // Corporatocracy
|
||||||
|
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||||
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
||||||
BitNodeMultipliers.AugmentationRepCost = 3;
|
BitNodeMultipliers.AugmentationRepCost = 3;
|
||||||
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
||||||
@ -268,8 +273,10 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.CompanyWorkMoney = 0.25;
|
BitNodeMultipliers.CompanyWorkMoney = 0.25;
|
||||||
BitNodeMultipliers.CrimeMoney = 0.25;
|
BitNodeMultipliers.CrimeMoney = 0.25;
|
||||||
BitNodeMultipliers.HacknetNodeMoney = 0.25;
|
BitNodeMultipliers.HacknetNodeMoney = 0.25;
|
||||||
|
BitNodeMultipliers.HomeComputerRamCost = 1.5;
|
||||||
|
BitNodeMultipliers.PurchasedServerCost = 2;
|
||||||
break;
|
break;
|
||||||
case 4: //The Singularity
|
case 4: // The Singularity
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
||||||
@ -283,7 +290,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.CrimeExpGain = 0.5;
|
BitNodeMultipliers.CrimeExpGain = 0.5;
|
||||||
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
||||||
break;
|
break;
|
||||||
case 5: //Artificial intelligence
|
case 5: // Artificial intelligence
|
||||||
BitNodeMultipliers.ServerMaxMoney = 2;
|
BitNodeMultipliers.ServerMaxMoney = 2;
|
||||||
BitNodeMultipliers.ServerStartingSecurity = 2;
|
BitNodeMultipliers.ServerStartingSecurity = 2;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||||
@ -296,7 +303,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HackExpGain = 0.5;
|
BitNodeMultipliers.HackExpGain = 0.5;
|
||||||
BitNodeMultipliers.CorporationValuation = 0.5;
|
BitNodeMultipliers.CorporationValuation = 0.5;
|
||||||
break;
|
break;
|
||||||
case 6: //Bladeburner
|
case 6: // Bladeburner
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||||
@ -311,7 +318,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.HackExpGain = 0.25;
|
BitNodeMultipliers.HackExpGain = 0.25;
|
||||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||||
break;
|
break;
|
||||||
case 7: //Bladeburner 2079
|
case 7: // Bladeburner 2079
|
||||||
BitNodeMultipliers.BladeburnerRank = 0.6;
|
BitNodeMultipliers.BladeburnerRank = 0.6;
|
||||||
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
||||||
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
||||||
@ -331,7 +338,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
||||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||||
break;
|
break;
|
||||||
case 8: //Ghost of Wall Street
|
case 8: // Ghost of Wall Street
|
||||||
BitNodeMultipliers.ScriptHackMoney = 0;
|
BitNodeMultipliers.ScriptHackMoney = 0;
|
||||||
BitNodeMultipliers.ManualHackMoney = 0;
|
BitNodeMultipliers.ManualHackMoney = 0;
|
||||||
BitNodeMultipliers.CompanyWorkMoney = 0;
|
BitNodeMultipliers.CompanyWorkMoney = 0;
|
||||||
@ -342,6 +349,23 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.CorporationValuation = 0;
|
BitNodeMultipliers.CorporationValuation = 0;
|
||||||
BitNodeMultipliers.CodingContractMoney = 0;
|
BitNodeMultipliers.CodingContractMoney = 0;
|
||||||
break;
|
break;
|
||||||
|
case 9: // Hacktocracy
|
||||||
|
BitNodeMultipliers.HackingLevelMultiplier = 0.4;
|
||||||
|
BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
|
||||||
|
BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
|
||||||
|
BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
|
||||||
|
BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
|
||||||
|
BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
|
||||||
|
BitNodeMultipliers.PurchasedServerLimit = 0;
|
||||||
|
BitNodeMultipliers.HomeComputerRamCost = 5;
|
||||||
|
BitNodeMultipliers.CrimeMoney = 0.5;
|
||||||
|
BitNodeMultipliers.ScriptHackMoney = 0.1;
|
||||||
|
BitNodeMultipliers.HackExpGain = 0.1;
|
||||||
|
BitNodeMultipliers.ServerStartingMoney = 0.1;
|
||||||
|
BitNodeMultipliers.ServerMaxMoney = 0.1;
|
||||||
|
BitNodeMultipliers.ServerStartingSecurity = 2.5;
|
||||||
|
BitNodeMultipliers.CorporationValuation = 0.5;
|
||||||
|
break;
|
||||||
case 10: // Digital Carbon
|
case 10: // Digital Carbon
|
||||||
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
|
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
|
||||||
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
|
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
|
||||||
@ -363,11 +387,14 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.PurchasedServerCost = 5;
|
BitNodeMultipliers.PurchasedServerCost = 5;
|
||||||
BitNodeMultipliers.PurchasedServerLimit = 0.6;
|
BitNodeMultipliers.PurchasedServerLimit = 0.6;
|
||||||
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
|
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
|
||||||
|
BitNodeMultipliers.BladeburnerRank = 0.8;
|
||||||
break;
|
break;
|
||||||
case 11: //The Big Crash
|
case 11: //The Big Crash
|
||||||
|
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
|
||||||
|
BitNodeMultipliers.HackExpGain = 0.5;
|
||||||
BitNodeMultipliers.ServerMaxMoney = 0.1;
|
BitNodeMultipliers.ServerMaxMoney = 0.1;
|
||||||
BitNodeMultipliers.ServerStartingMoney = 0.1;
|
BitNodeMultipliers.ServerStartingMoney = 0.1;
|
||||||
BitNodeMultipliers.ServerGrowthRate = 0.5;
|
BitNodeMultipliers.ServerGrowthRate = 0.2;
|
||||||
BitNodeMultipliers.ServerWeakenRate = 2;
|
BitNodeMultipliers.ServerWeakenRate = 2;
|
||||||
BitNodeMultipliers.CrimeMoney = 3;
|
BitNodeMultipliers.CrimeMoney = 3;
|
||||||
BitNodeMultipliers.CompanyWorkMoney = 0.5;
|
BitNodeMultipliers.CompanyWorkMoney = 0.5;
|
||||||
@ -375,8 +402,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
|||||||
BitNodeMultipliers.AugmentationMoneyCost = 2;
|
BitNodeMultipliers.AugmentationMoneyCost = 2;
|
||||||
BitNodeMultipliers.InfiltrationMoney = 2.5;
|
BitNodeMultipliers.InfiltrationMoney = 2.5;
|
||||||
BitNodeMultipliers.InfiltrationRep = 2.5;
|
BitNodeMultipliers.InfiltrationRep = 2.5;
|
||||||
BitNodeMultipliers.CorporationValuation = 0.01;
|
BitNodeMultipliers.CorporationValuation = 0.1;
|
||||||
BitNodeMultipliers.CodingContractMoney = 0.5;
|
BitNodeMultipliers.CodingContractMoney = 0.25;
|
||||||
BitNodeMultipliers.FourSigmaMarketDataCost = 4;
|
BitNodeMultipliers.FourSigmaMarketDataCost = 4;
|
||||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
|
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
|
||||||
break;
|
break;
|
||||||
|
@ -120,7 +120,8 @@ interface IBitNodeMultipliers {
|
|||||||
HackingLevelMultiplier: number;
|
HackingLevelMultiplier: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Influences how much money each Hacknet node can generate.
|
* Influences how much money is produced by Hacknet Nodes.
|
||||||
|
* Influeces the hash rate of Hacknet Servers (unlocked in BitNode-9)
|
||||||
*/
|
*/
|
||||||
HacknetNodeMoney: number;
|
HacknetNodeMoney: number;
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ import { getTimestamp } from "../utils/helpers/getTi
|
|||||||
import { removeElement } from "../utils/uiHelpers/removeElement";
|
import { removeElement } from "../utils/uiHelpers/removeElement";
|
||||||
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
||||||
|
|
||||||
|
const stealthIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style="fill:#adff2f;"><g><path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z"/><path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z"/></g></svg> `
|
||||||
|
const killIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style="fill:#adff2f;"><path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0"/><path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0"/><path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0"/><path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0"/></svg>`
|
||||||
|
|
||||||
const CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"];
|
const CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"];
|
||||||
|
|
||||||
@ -76,8 +78,8 @@ const RanksPerSkillPoint = 3; //How many ranks needed to get 1 Skill
|
|||||||
|
|
||||||
const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
|
const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
|
||||||
|
|
||||||
const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber
|
const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber
|
||||||
const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber
|
const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber
|
||||||
|
|
||||||
//DOM related variables
|
//DOM related variables
|
||||||
var ActiveActionCssClass = "bladeburner-active-action";
|
var ActiveActionCssClass = "bladeburner-active-action";
|
||||||
@ -1415,14 +1417,17 @@ Bladeburner.prototype.completeAction = function() {
|
|||||||
}
|
}
|
||||||
this.startAction(this.action); // Repeat Action
|
this.startAction(this.action); // Repeat Action
|
||||||
break;
|
break;
|
||||||
case ActionTypes["Hyperbolic Regeneration Chamber"]:
|
case ActionTypes["Hyperbolic Regeneration Chamber"]: {
|
||||||
Player.regenerateHp(HrcHpGain);
|
Player.regenerateHp(HrcHpGain);
|
||||||
this.stamina = Math.max(this.maxStamina, this.stamina + HrcStaminaGain); // TODO Turn this into a const and adjust value
|
|
||||||
|
const staminaGain = this.maxStamina * (HrcStaminaGain / 100);
|
||||||
|
this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain);
|
||||||
this.startAction(this.action);
|
this.startAction(this.action);
|
||||||
if (this.logging.general) {
|
if (this.logging.general) {
|
||||||
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`);
|
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
|
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
|
||||||
break;
|
break;
|
||||||
@ -1799,6 +1804,12 @@ Bladeburner.prototype.createContent = function() {
|
|||||||
DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv);
|
DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv);
|
||||||
DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv);
|
DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv);
|
||||||
|
|
||||||
|
|
||||||
|
// legend
|
||||||
|
const legend = createElement("div")
|
||||||
|
legend.innerHTML = `<span class="text">${stealthIcon}= This action requires stealth, ${killIcon} = This action involves retirement</span>`
|
||||||
|
DomElems.bladeburnerDiv.appendChild(legend);
|
||||||
|
|
||||||
document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv);
|
document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv);
|
||||||
|
|
||||||
if (this.consoleLogs.length === 0) {
|
if (this.consoleLogs.length === 0) {
|
||||||
@ -2166,12 +2177,12 @@ Bladeburner.prototype.createBlackOpsContent = function() {
|
|||||||
return (a.reqdRank - b.reqdRank);
|
return (a.reqdRank - b.reqdRank);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (var i = 0; i < blackops.length; ++i) {
|
for (var i = blackops.length-1; i >= 0 ; --i) {
|
||||||
|
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
|
||||||
DomElems.blackops[blackops[i].name] = createElement("div", {
|
DomElems.blackops[blackops[i].name] = createElement("div", {
|
||||||
class:"bladeburner-action", name:blackops[i].name
|
class:"bladeburner-action", name:blackops[i].name
|
||||||
});
|
});
|
||||||
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
|
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
|
||||||
if (this.blackops[[blackops[i].name]] == null) {break;} //Can't be found in completed blackops
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2504,7 +2515,8 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
|
|||||||
el.appendChild(createElement("pre", { //Info
|
el.appendChild(createElement("pre", { //Info
|
||||||
display:"inline-block",
|
display:"inline-block",
|
||||||
innerHTML:action.desc + "\n\n" +
|
innerHTML:action.desc + "\n\n" +
|
||||||
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
|
||||||
|
|
||||||
"Time Required (s): " + formatNumber(actionTime, 0) + "\n" +
|
"Time Required (s): " + formatNumber(actionTime, 0) + "\n" +
|
||||||
"Contracts remaining: " + Math.floor(action.count) + "\n" +
|
"Contracts remaining: " + Math.floor(action.count) + "\n" +
|
||||||
"Successes: " + action.successes + "\n" +
|
"Successes: " + action.successes + "\n" +
|
||||||
@ -2518,14 +2530,21 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
|
|||||||
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
||||||
tooltip:"Automatically increase contract level when possible"
|
tooltip:"Automatically increase contract level when possible"
|
||||||
}));
|
}));
|
||||||
var autolevelCheckbox = createElement("input", {
|
|
||||||
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
const checkboxDiv = createElement("div", { class: "bbcheckbox" });
|
||||||
checked:action.autoLevel,
|
const checkboxInput = createElement("input", {
|
||||||
changeListener:()=>{
|
type:"checkbox",
|
||||||
action.autoLevel = autolevelCheckbox.checked;
|
id: autolevelCheckboxId,
|
||||||
}
|
checked: action.autoLevel,
|
||||||
|
changeListener: () => {
|
||||||
|
action.autoLevel = checkboxInput.checked;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
el.appendChild(autolevelCheckbox);
|
const checkmarkLabel = createElement("label", { for: autolevelCheckboxId });
|
||||||
|
checkboxDiv.appendChild(checkboxInput);
|
||||||
|
checkboxDiv.appendChild(checkmarkLabel);
|
||||||
|
|
||||||
|
el.appendChild(checkboxDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
||||||
@ -2640,7 +2659,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
|||||||
el.appendChild(createElement("pre", {
|
el.appendChild(createElement("pre", {
|
||||||
display:"inline-block",
|
display:"inline-block",
|
||||||
innerHTML:action.desc + "\n\n" +
|
innerHTML:action.desc + "\n\n" +
|
||||||
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
|
||||||
"Time Required(s): " + formatNumber(actionTime, 0) + "\n" +
|
"Time Required(s): " + formatNumber(actionTime, 0) + "\n" +
|
||||||
"Operations remaining: " + Math.floor(action.count) + "\n" +
|
"Operations remaining: " + Math.floor(action.count) + "\n" +
|
||||||
"Successes: " + action.successes + "\n" +
|
"Successes: " + action.successes + "\n" +
|
||||||
@ -2654,14 +2673,21 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
|||||||
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
||||||
tooltip:"Automatically increase operation level when possible"
|
tooltip:"Automatically increase operation level when possible"
|
||||||
}));
|
}));
|
||||||
var autolevelCheckbox = createElement("input", {
|
|
||||||
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
const checkboxDiv = createElement("div", { class: "bbcheckbox" });
|
||||||
checked:action.autoLevel,
|
const checkboxInput = createElement("input", {
|
||||||
changeListener:()=>{
|
type:"checkbox",
|
||||||
action.autoLevel = autolevelCheckbox.checked;
|
id: autolevelCheckboxId,
|
||||||
}
|
checked: action.autoLevel,
|
||||||
|
changeListener: () => {
|
||||||
|
action.autoLevel = checkboxInput.checked;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
el.appendChild(autolevelCheckbox);
|
const checkmarkLabel = createElement("label", { for: autolevelCheckboxId });
|
||||||
|
checkboxDiv.appendChild(checkboxInput);
|
||||||
|
checkboxDiv.appendChild(checkmarkLabel);
|
||||||
|
|
||||||
|
el.appendChild(checkboxDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
|
Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
|
||||||
@ -2760,7 +2786,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
|
|||||||
}));
|
}));
|
||||||
el.appendChild(createElement("p", {
|
el.appendChild(createElement("p", {
|
||||||
display:"inline-block",
|
display:"inline-block",
|
||||||
innerHTML:"Estimated Success Chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
innerHTML:`Estimated Success Chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
|
||||||
"Time Required(s): " + formatNumber(actionTime, 0),
|
"Time Required(s): " + formatNumber(actionTime, 0),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Generic Game Constants
|
||||||
|
*
|
||||||
|
* Constants for specific mechanics or features will NOT be here.
|
||||||
|
*/
|
||||||
import {IMap} from "./types";
|
import {IMap} from "./types";
|
||||||
|
|
||||||
export let CONSTANTS: IMap<any> = {
|
export let CONSTANTS: IMap<any> = {
|
||||||
Version: "0.44.1",
|
Version: "0.45.1",
|
||||||
|
|
||||||
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||||
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||||
@ -17,24 +22,9 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
/* Base costs */
|
/* Base costs */
|
||||||
BaseCostFor1GBOfRamHome: 32000,
|
BaseCostFor1GBOfRamHome: 32000,
|
||||||
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
|
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
|
||||||
BaseCostFor1GBOfRamHacknetNode: 30000,
|
|
||||||
|
|
||||||
TravelCost: 200e3,
|
TravelCost: 200e3,
|
||||||
|
|
||||||
BaseCostForHacknetNode: 1000,
|
|
||||||
BaseCostForHacknetNodeCore: 500000,
|
|
||||||
|
|
||||||
/* Hacknet Node constants */
|
|
||||||
HacknetNodeMoneyGainPerLevel: 1.6,
|
|
||||||
HacknetNodePurchaseNextMult: 1.85, //Multiplier when purchasing an additional hacknet node
|
|
||||||
HacknetNodeUpgradeLevelMult: 1.04, //Multiplier for cost when upgrading level
|
|
||||||
HacknetNodeUpgradeRamMult: 1.28, //Multiplier for cost when upgrading RAM
|
|
||||||
HacknetNodeUpgradeCoreMult: 1.48, //Multiplier for cost when buying another core
|
|
||||||
|
|
||||||
HacknetNodeMaxLevel: 200,
|
|
||||||
HacknetNodeMaxRam: 64,
|
|
||||||
HacknetNodeMaxCores: 16,
|
|
||||||
|
|
||||||
/* Faction and Company favor */
|
/* Faction and Company favor */
|
||||||
BaseFavorToDonate: 150,
|
BaseFavorToDonate: 150,
|
||||||
DonateMoneyToRepDivisor: 1e6,
|
DonateMoneyToRepDivisor: 1e6,
|
||||||
@ -85,11 +75,12 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
ScriptGetPurchasedServerMaxRam: 0.05,
|
ScriptGetPurchasedServerMaxRam: 0.05,
|
||||||
ScriptRoundRamCost: 0.05,
|
ScriptRoundRamCost: 0.05,
|
||||||
ScriptReadWriteRamCost: 1.0,
|
ScriptReadWriteRamCost: 1.0,
|
||||||
ScriptArbScriptRamCost: 1.0, //Functions that apply to all scripts regardless of args
|
ScriptArbScriptRamCost: 1.0, // Functions that apply to all scripts regardless of args
|
||||||
ScriptGetScriptRamCost: 0.1,
|
ScriptGetScriptRamCost: 0.1,
|
||||||
ScriptGetHackTimeRamCost: 0.05,
|
ScriptGetHackTimeRamCost: 0.05,
|
||||||
ScriptGetFavorToDonate: 0.10,
|
ScriptGetFavorToDonate: 0.10,
|
||||||
ScriptCodingContractBaseRamCost:10,
|
ScriptCodingContractBaseRamCost:10,
|
||||||
|
ScriptSleeveBaseRamCost: 4,
|
||||||
|
|
||||||
ScriptSingularityFn1RamCost: 1,
|
ScriptSingularityFn1RamCost: 1,
|
||||||
ScriptSingularityFn2RamCost: 2,
|
ScriptSingularityFn2RamCost: 2,
|
||||||
@ -125,6 +116,7 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
|
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
|
||||||
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
|
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
|
||||||
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
|
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
|
||||||
|
InfiltrationExpPow: 0.8,
|
||||||
|
|
||||||
//Stock market constants
|
//Stock market constants
|
||||||
WSEAccountCost: 200e6,
|
WSEAccountCost: 200e6,
|
||||||
@ -281,17 +273,20 @@ export let CONSTANTS: IMap<any> = {
|
|||||||
|
|
||||||
LatestUpdate:
|
LatestUpdate:
|
||||||
`
|
`
|
||||||
v0.45.0
|
v0.46.0
|
||||||
* Corporation changes:
|
* Added BitNode-9: Hacktocracy
|
||||||
** Decreased the time of a full market cycle from 15 seconds to 10 seconds.
|
* Changed BitNode-11's multipliers to make it slightly harder overall
|
||||||
** This means that each Corporation 'state' will now only take 2 seconds, rather than 3
|
* Source-File 11 is now slightly stronger
|
||||||
** Increased initial salaries for newly-hired employees
|
* Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame)
|
||||||
** Increased the cost multiplier for upgrading office size (the cost will increase faster)
|
* Added a new stat for Duplicate Sleeves: Memory
|
||||||
** The stats of your employees now has a slightly larger effect on production & sales
|
* Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels
|
||||||
** Added several new Research upgrades
|
* In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina
|
||||||
** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k
|
|
||||||
** Energy Material requirement of the Software industry reduced from 1 to 0.5
|
|
||||||
** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research.
|
|
||||||
`
|
|
||||||
|
|
||||||
|
* Corporation Changes:
|
||||||
|
** 'Demand' value of products decreases more slowly
|
||||||
|
** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
|
||||||
|
|
||||||
|
* Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
|
||||||
|
* Bug Fix: Fixed an issue with the job requirement tooltip for security jobs
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,14 @@ import { CONSTANTS } from "../Constants";
|
|||||||
import { Factions } from "../Faction/Factions";
|
import { Factions } from "../Faction/Factions";
|
||||||
import { showLiterature } from "../Literature";
|
import { showLiterature } from "../Literature";
|
||||||
import { Locations } from "../Locations";
|
import { Locations } from "../Locations";
|
||||||
|
import { createCityMap } from "../Locations/Cities";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
|
|
||||||
import { numeralWrapper } from "../ui/numeralFormat";
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||||||
import { Page, routing } from "../ui/navigationTracking";
|
import { Page, routing } from "../ui/navigationTracking";
|
||||||
|
|
||||||
|
import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
|
||||||
|
|
||||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
import { clearSelector } from "../../utils/uiHelpers/clearSelector";
|
import { clearSelector } from "../../utils/uiHelpers/clearSelector";
|
||||||
import { Reviver,
|
import { Reviver,
|
||||||
@ -37,7 +40,6 @@ import { formatNumber, generateRandomString } from "../../utils/String
|
|||||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||||
import { isString } from "../../utils/helpers/isString";
|
import { isString } from "../../utils/helpers/isString";
|
||||||
import { KEY } from "../../utils/helpers/keyCodes";
|
import { KEY } from "../../utils/helpers/keyCodes";
|
||||||
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
|
|
||||||
import { removeElement } from "../../utils/uiHelpers/removeElement";
|
import { removeElement } from "../../utils/uiHelpers/removeElement";
|
||||||
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
||||||
import { yesNoBoxCreate,
|
import { yesNoBoxCreate,
|
||||||
@ -48,8 +50,7 @@ import { yesNoBoxCreate,
|
|||||||
yesNoTxtInpBoxGetNoButton,
|
yesNoTxtInpBoxGetNoButton,
|
||||||
yesNoTxtInpBoxGetInput,
|
yesNoTxtInpBoxGetInput,
|
||||||
yesNoBoxClose,
|
yesNoBoxClose,
|
||||||
yesNoTxtInpBoxClose,
|
yesNoTxtInpBoxClose } from "../../utils/YesNoBox";
|
||||||
yesNoBoxOpen } from "../../utils/YesNoBox";
|
|
||||||
|
|
||||||
// UI Related Imports
|
// UI Related Imports
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -60,7 +61,6 @@ import { CorporationRouting } from "./ui/Routing";
|
|||||||
|
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
|
|
||||||
|
|
||||||
/* Constants */
|
/* Constants */
|
||||||
export const INITIALSHARES = 1e9; //Total number of shares you have at your company
|
export const INITIALSHARES = 1e9; //Total number of shares you have at your company
|
||||||
export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount
|
export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount
|
||||||
@ -124,18 +124,6 @@ function Industry(params={}) {
|
|||||||
[Locations.Volhaven]: 0
|
[Locations.Volhaven]: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
|
|
||||||
[Locations.Aevum]: 0,
|
|
||||||
[Locations.Chonqing]: 0,
|
|
||||||
[Locations.Sector12]: new Warehouse({
|
|
||||||
loc:Locations.Sector12,
|
|
||||||
size: WarehouseInitialSize,
|
|
||||||
}),
|
|
||||||
[Locations.NewTokyo]: 0,
|
|
||||||
[Locations.Ishima]: 0,
|
|
||||||
[Locations.Volhaven]: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.name = params.name ? params.name : 0;
|
this.name = params.name ? params.name : 0;
|
||||||
this.type = params.type ? params.type : 0;
|
this.type = params.type ? params.type : 0;
|
||||||
|
|
||||||
@ -183,6 +171,20 @@ function Industry(params={}) {
|
|||||||
this.state = "START";
|
this.state = "START";
|
||||||
this.newInd = true;
|
this.newInd = true;
|
||||||
|
|
||||||
|
this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
|
||||||
|
[Locations.Aevum]: 0,
|
||||||
|
[Locations.Chonqing]: 0,
|
||||||
|
[Locations.Sector12]: new Warehouse({
|
||||||
|
corp: params.corp,
|
||||||
|
industry: this,
|
||||||
|
loc: Locations.Sector12,
|
||||||
|
size: WarehouseInitialSize,
|
||||||
|
}),
|
||||||
|
[Locations.NewTokyo]: 0,
|
||||||
|
[Locations.Ishima]: 0,
|
||||||
|
[Locations.Volhaven]: 0
|
||||||
|
};
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,8 +342,8 @@ Industry.prototype.init = function() {
|
|||||||
this.sciFac = 0.62;
|
this.sciFac = 0.62;
|
||||||
this.advFac = 0.16;
|
this.advFac = 0.16;
|
||||||
this.hwFac = 0.25;
|
this.hwFac = 0.25;
|
||||||
this.reFac = 0.1;
|
this.reFac = 0.15;
|
||||||
this.aiFac = 0.15;
|
this.aiFac = 0.18;
|
||||||
this.robFac = 0.05;
|
this.robFac = 0.05;
|
||||||
this.reqMats = {
|
this.reqMats = {
|
||||||
"Hardware": 0.5,
|
"Hardware": 0.5,
|
||||||
@ -487,11 +489,11 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
|||||||
this.thisCycleRevenue = new Decimal(0);
|
this.thisCycleRevenue = new Decimal(0);
|
||||||
this.thisCycleExpenses = new Decimal(0);
|
this.thisCycleExpenses = new Decimal(0);
|
||||||
|
|
||||||
//Once you start making revenue, the player should no longer be
|
// Once you start making revenue, the player should no longer be
|
||||||
//considered new, and therefore no longer needs the 'tutorial' UI elements
|
// considered new, and therefore no longer needs the 'tutorial' UI elements
|
||||||
if (this.lastCycleRevenue.gt(0)) {this.newInd = false;}
|
if (this.lastCycleRevenue.gt(0)) {this.newInd = false;}
|
||||||
|
|
||||||
//Process offices (and the employees in them)
|
// Process offices (and the employees in them)
|
||||||
var employeeSalary = 0;
|
var employeeSalary = 0;
|
||||||
for (var officeLoc in this.offices) {
|
for (var officeLoc in this.offices) {
|
||||||
if (this.offices[officeLoc] instanceof OfficeSpace) {
|
if (this.offices[officeLoc] instanceof OfficeSpace) {
|
||||||
@ -500,15 +502,15 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
|||||||
}
|
}
|
||||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary);
|
this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary);
|
||||||
|
|
||||||
//Process change in demand/competition of materials/products
|
// Process change in demand/competition of materials/products
|
||||||
this.processMaterialMarket(marketCycles);
|
this.processMaterialMarket(marketCycles);
|
||||||
this.processProductMarket(marketCycles);
|
this.processProductMarket(marketCycles);
|
||||||
|
|
||||||
//Process loss of popularity
|
// Process loss of popularity
|
||||||
this.popularity -= (marketCycles * .0001);
|
this.popularity -= (marketCycles * .0001);
|
||||||
this.popularity = Math.max(0, this.popularity);
|
this.popularity = Math.max(0, this.popularity);
|
||||||
|
|
||||||
//Process Dreamsense gains
|
// Process Dreamsense gains
|
||||||
var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4;
|
var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4;
|
||||||
if (popularityGain > 0) {
|
if (popularityGain > 0) {
|
||||||
this.popularity += (popularityGain * marketCycles);
|
this.popularity += (popularityGain * marketCycles);
|
||||||
@ -518,19 +520,19 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Process production, purchase, and import/export of materials
|
// Process production, purchase, and import/export of materials
|
||||||
var res = this.processMaterials(marketCycles, company);
|
var res = this.processMaterials(marketCycles, company);
|
||||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||||
|
|
||||||
//Process creation, production & sale of products
|
// Process creation, production & sale of products
|
||||||
res = this.processProducts(marketCycles, company);
|
res = this.processProducts(marketCycles, company);
|
||||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Process change in demand and competition for this industry's materials
|
// Process change in demand and competition for this industry's materials
|
||||||
Industry.prototype.processMaterialMarket = function(marketCycles=1) {
|
Industry.prototype.processMaterialMarket = function(marketCycles=1) {
|
||||||
//References to prodMats and reqMats
|
//References to prodMats and reqMats
|
||||||
var reqMats = this.reqMats, prodMats = this.prodMats;
|
var reqMats = this.reqMats, prodMats = this.prodMats;
|
||||||
@ -561,13 +563,15 @@ Industry.prototype.processMaterialMarket = function(marketCycles=1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Process change in demand and competition for this industry's products
|
// Process change in demand and competition for this industry's products
|
||||||
Industry.prototype.processProductMarket = function(marketCycles=1) {
|
Industry.prototype.processProductMarket = function(marketCycles=1) {
|
||||||
//Demand gradually decreases, and competition gradually increases
|
// Demand gradually decreases, and competition gradually increases
|
||||||
for (var name in this.products) {
|
for (const name in this.products) {
|
||||||
if (this.products.hasOwnProperty(name)) {
|
if (this.products.hasOwnProperty(name)) {
|
||||||
var product = this.products[name];
|
const product = this.products[name];
|
||||||
var change = getRandomInt(1, 3) * 0.0004;
|
let change = getRandomInt(0, 3) * 0.0004;
|
||||||
|
if (change === 0) { continue; }
|
||||||
|
|
||||||
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
|
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
|
||||||
this.type === Industries.Robotics) {
|
this.type === Industries.Robotics) {
|
||||||
change *= 3;
|
change *= 3;
|
||||||
@ -588,7 +592,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
|
|
||||||
//At the start of the export state, set the imports of everything to 0
|
//At the start of the export state, set the imports of everything to 0
|
||||||
if (this.state === "EXPORT") {
|
if (this.state === "EXPORT") {
|
||||||
for (var i = 0; i < Cities.length; ++i) {
|
for (let i = 0; i < Cities.length; ++i) {
|
||||||
var city = Cities[i], office = this.offices[city];
|
var city = Cities[i], office = this.offices[city];
|
||||||
if (!(this.warehouses[city] instanceof Warehouse)) {
|
if (!(this.warehouses[city] instanceof Warehouse)) {
|
||||||
continue;
|
continue;
|
||||||
@ -603,7 +607,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Cities.length; ++i) {
|
for (let i = 0; i < Cities.length; ++i) {
|
||||||
var city = Cities[i], office = this.offices[city];
|
var city = Cities[i], office = this.offices[city];
|
||||||
|
|
||||||
if (this.warehouses[city] instanceof Warehouse) {
|
if (this.warehouses[city] instanceof Warehouse) {
|
||||||
@ -663,19 +667,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
prod = maxProd;
|
prod = maxProd;
|
||||||
}
|
}
|
||||||
prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle
|
prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle
|
||||||
//Calculate net change in warehouse storage making
|
|
||||||
//the produced materials will cost
|
// Calculate net change in warehouse storage making the produced materials will cost
|
||||||
var totalMatSize = 0;
|
var totalMatSize = 0;
|
||||||
for (var tmp = 0; tmp < this.prodMats.length; ++tmp) {
|
for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
|
||||||
totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
|
totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
|
||||||
}
|
}
|
||||||
for (var reqMatName in this.reqMats) {
|
for (const reqMatName in this.reqMats) {
|
||||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
var normQty = this.reqMats[reqMatName];
|
||||||
var normQty = this.reqMats[reqMatName];
|
totalMatSize -= (MaterialSizes[reqMatName] * normQty);
|
||||||
totalMatSize -= (MaterialSizes[reqMatName] * normQty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//If not enough space in warehouse, limit the amount of produced materials
|
// If not enough space in warehouse, limit the amount of produced materials
|
||||||
if (totalMatSize > 0) {
|
if (totalMatSize > 0) {
|
||||||
var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
|
var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
|
||||||
prod = Math.min(maxAmt, prod);
|
prod = Math.min(maxAmt, prod);
|
||||||
@ -683,10 +685,10 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
|
|
||||||
if (prod < 0) {prod = 0;}
|
if (prod < 0) {prod = 0;}
|
||||||
|
|
||||||
//Keep track of production for smart supply (/s)
|
// Keep track of production for smart supply (/s)
|
||||||
warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles));
|
warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles));
|
||||||
|
|
||||||
//Make sure we have enough resource to make our materials
|
// Make sure we have enough resource to make our materials
|
||||||
var producableFrac = 1;
|
var producableFrac = 1;
|
||||||
for (var reqMatName in this.reqMats) {
|
for (var reqMatName in this.reqMats) {
|
||||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||||
@ -698,25 +700,23 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
}
|
}
|
||||||
if (producableFrac <= 0) {producableFrac = 0; prod = 0;}
|
if (producableFrac <= 0) {producableFrac = 0; prod = 0;}
|
||||||
|
|
||||||
//Make our materials if they are producable
|
// Make our materials if they are producable
|
||||||
if (producableFrac > 0 && prod > 0) {
|
if (producableFrac > 0 && prod > 0) {
|
||||||
for (var reqMatName in this.reqMats) {
|
for (const reqMatName in this.reqMats) {
|
||||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
|
||||||
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
|
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
|
||||||
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
|
warehouse.materials[reqMatName].prd = 0;
|
||||||
warehouse.materials[reqMatName].prd = 0;
|
warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
|
||||||
warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (var j = 0; j < this.prodMats.length; ++j) {
|
for (let j = 0; j < this.prodMats.length; ++j) {
|
||||||
warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac);
|
warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac);
|
||||||
warehouse.materials[this.prodMats[j]].qlt =
|
warehouse.materials[this.prodMats[j]].qlt =
|
||||||
(office.employeeProd[EmployeePositions.Engineer] / 100 +
|
(office.employeeProd[EmployeePositions.Engineer] / 90 +
|
||||||
Math.pow(this.sciResearch.qty, this.sciFac) +
|
Math.pow(this.sciResearch.qty, this.sciFac) +
|
||||||
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
|
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (var reqMatName in this.reqMats) {
|
for (const reqMatName in this.reqMats) {
|
||||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||||
warehouse.materials[reqMatName].prd = 0;
|
warehouse.materials[reqMatName].prd = 0;
|
||||||
}
|
}
|
||||||
@ -724,18 +724,16 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Per second
|
//Per second
|
||||||
var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
|
const fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
|
||||||
for (var fooI = 0; fooI < this.prodMats.length; ++fooI) {
|
for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {
|
||||||
warehouse.materials[this.prodMats[fooI]].prd = fooProd;
|
warehouse.materials[this.prodMats[fooI]].prd = fooProd;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//If this doesn't produce any materials, then it only creates
|
//If this doesn't produce any materials, then it only creates
|
||||||
//Products. Creating products will consume materials. The
|
//Products. Creating products will consume materials. The
|
||||||
//Production of all consumed materials must be set to 0
|
//Production of all consumed materials must be set to 0
|
||||||
for (var reqMatName in this.reqMats) {
|
for (const reqMatName in this.reqMats) {
|
||||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
warehouse.materials[reqMatName].prd = 0;
|
||||||
warehouse.materials[reqMatName].prd = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -749,12 +747,49 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
mat.sll = 0;
|
mat.sll = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var mat = warehouse.materials[matName];
|
|
||||||
|
|
||||||
// Calculate sale cost
|
// Sale multipliers
|
||||||
|
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
|
||||||
|
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
|
||||||
|
const marketFactor = this.getMarketFactor(mat); //Competition + demand
|
||||||
|
|
||||||
|
// Determine the cost that the material will be sold at
|
||||||
const markupLimit = mat.getMarkupLimit();
|
const markupLimit = mat.getMarkupLimit();
|
||||||
var sCost;
|
var sCost;
|
||||||
if (mat.marketTa1) {
|
if (mat.marketTa2) {
|
||||||
|
const prod = mat.prd;
|
||||||
|
|
||||||
|
// Reverse engineer the 'maxSell' formula
|
||||||
|
// 1. Set 'maxSell' = prod
|
||||||
|
// 2. Substitute formula for 'markup'
|
||||||
|
// 3. Solve for 'sCost'
|
||||||
|
const numerator = markupLimit;
|
||||||
|
const sqrtNumerator = prod;
|
||||||
|
const sqrtDenominator = ((mat.qlt + .001)
|
||||||
|
* marketFactor
|
||||||
|
* businessFactor
|
||||||
|
* company.getSalesMultiplier()
|
||||||
|
* advertisingFactor
|
||||||
|
* this.getSalesMultiplier());
|
||||||
|
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
|
||||||
|
let optimalPrice;
|
||||||
|
if (sqrtDenominator === 0 || denominator === 0) {
|
||||||
|
if (sqrtNumerator === 0) {
|
||||||
|
optimalPrice = 0; // No production
|
||||||
|
} else {
|
||||||
|
optimalPrice = mat.bCost + markupLimit;
|
||||||
|
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optimalPrice = (numerator / denominator) + mat.bCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll store this "Optimal Price" in a property so that we don't have
|
||||||
|
// to re-calculate it for the UI
|
||||||
|
mat.marketTa2Price = optimalPrice;
|
||||||
|
|
||||||
|
sCost = optimalPrice;
|
||||||
|
} else if (mat.marketTa1) {
|
||||||
sCost = mat.bCost + markupLimit;
|
sCost = mat.bCost + markupLimit;
|
||||||
} else if (isString(mat.sCost)) {
|
} else if (isString(mat.sCost)) {
|
||||||
sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
||||||
@ -778,9 +813,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
markup = mat.bCost / sCost;
|
markup = mat.bCost / sCost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var businessFactor = this.getBusinessFactor(office); //Business employee productivity
|
|
||||||
var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
|
|
||||||
var marketFactor = this.getMarketFactor(mat); //Competition + demand
|
|
||||||
var maxSell = (mat.qlt + .001)
|
var maxSell = (mat.qlt + .001)
|
||||||
* marketFactor
|
* marketFactor
|
||||||
* markup
|
* markup
|
||||||
@ -903,8 +936,8 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
|||||||
//Produce Scientific Research based on R&D employees
|
//Produce Scientific Research based on R&D employees
|
||||||
//Scientific Research can be produced without a warehouse
|
//Scientific Research can be produced without a warehouse
|
||||||
if (office instanceof OfficeSpace) {
|
if (office instanceof OfficeSpace) {
|
||||||
this.sciResearch.qty += (.005
|
this.sciResearch.qty += (.004
|
||||||
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55)
|
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5)
|
||||||
* company.getScientificResearchMultiplier()
|
* company.getScientificResearchMultiplier()
|
||||||
* this.getScientificResearchMultiplier());
|
* this.getScientificResearchMultiplier());
|
||||||
}
|
}
|
||||||
@ -918,27 +951,30 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
|
|||||||
|
|
||||||
//Create products
|
//Create products
|
||||||
if (this.state === "PRODUCTION") {
|
if (this.state === "PRODUCTION") {
|
||||||
for (var prodName in this.products) {
|
for (const prodName in this.products) {
|
||||||
if (this.products.hasOwnProperty(prodName)) {
|
const prod = this.products[prodName];
|
||||||
var prod = this.products[prodName];
|
if (!prod.fin) {
|
||||||
if (!prod.fin) {
|
const city = prod.createCity;
|
||||||
var city = prod.createCity, office = this.offices[city];
|
const office = this.offices[city];
|
||||||
var total = office.employeeProd[EmployeePositions.Operations] +
|
|
||||||
office.employeeProd[EmployeePositions.Engineer] +
|
// Designing/Creating a Product is based mostly off Engineers
|
||||||
office.employeeProd[EmployeePositions.Management], ratio;
|
const engrProd = office.employeeProd[EmployeePositions.Engineer];
|
||||||
if (total === 0) {
|
const mgmtProd = office.employeeProd[EmployeePositions.Management];
|
||||||
ratio = 0;
|
const opProd = office.employeeProd[EmployeePositions.Operations];
|
||||||
} else {
|
const total = engrProd + mgmtProd + opProd;
|
||||||
ratio = office.employeeProd[EmployeePositions.Engineer] / total +
|
|
||||||
office.employeeProd[EmployeePositions.Operations] / total +
|
if (total <= 0) { break; }
|
||||||
office.employeeProd[EmployeePositions.Management] / total;
|
|
||||||
}
|
// Management is a multiplier for the production from Engineers
|
||||||
prod.createProduct(marketCycles, ratio * Math.pow(total, 0.35));
|
const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
|
||||||
if (prod.prog >= 100) {
|
|
||||||
prod.finishProduct(office.employeeProd, this);
|
const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;
|
||||||
}
|
|
||||||
break;
|
prod.createProduct(marketCycles, progress);
|
||||||
|
if (prod.prog >= 100) {
|
||||||
|
prod.finishProduct(office.employeeProd, this);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -957,9 +993,9 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
|
|||||||
|
|
||||||
//Processes FINISHED products
|
//Processes FINISHED products
|
||||||
Industry.prototype.processProduct = function(marketCycles=1, product, corporation) {
|
Industry.prototype.processProduct = function(marketCycles=1, product, corporation) {
|
||||||
var totalProfit = 0;
|
let totalProfit = 0;
|
||||||
for (var i = 0; i < Cities.length; ++i) {
|
for (let i = 0; i < Cities.length; ++i) {
|
||||||
var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
|
let city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
|
||||||
if (warehouse instanceof Warehouse) {
|
if (warehouse instanceof Warehouse) {
|
||||||
switch(this.state) {
|
switch(this.state) {
|
||||||
|
|
||||||
@ -1035,27 +1071,65 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Since its a product, its production cost is increased for labor
|
// Since its a product, its production cost is increased for labor
|
||||||
product.pCost *= ProductProductionCostRatio;
|
product.pCost *= ProductProductionCostRatio;
|
||||||
|
|
||||||
//Calculate Sale Cost (sCost), which could be dynamically evaluated
|
// Sale multipliers
|
||||||
|
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
|
||||||
|
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
|
||||||
|
const marketFactor = this.getMarketFactor(product); //Competition + demand
|
||||||
|
|
||||||
|
// Calculate Sale Cost (sCost), which could be dynamically evaluated
|
||||||
|
const markupLimit = product.rat / product.mku;
|
||||||
var sCost;
|
var sCost;
|
||||||
if (isString(product.sCost)) {
|
if (product.marketTa2) {
|
||||||
|
const prod = product.data[city][1];
|
||||||
|
|
||||||
|
// Reverse engineer the 'maxSell' formula
|
||||||
|
// 1. Set 'maxSell' = prod
|
||||||
|
// 2. Substitute formula for 'markup'
|
||||||
|
// 3. Solve for 'sCost'roduct.pCost = sCost
|
||||||
|
const numerator = markupLimit;
|
||||||
|
const sqrtNumerator = prod;
|
||||||
|
const sqrtDenominator = (0.5
|
||||||
|
* Math.pow(product.rat, 0.65)
|
||||||
|
* marketFactor
|
||||||
|
* corporation.getSalesMultiplier()
|
||||||
|
* businessFactor
|
||||||
|
* advertisingFactor
|
||||||
|
* this.getSalesMultiplier());
|
||||||
|
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
|
||||||
|
let optimalPrice;
|
||||||
|
if (sqrtDenominator === 0 || denominator === 0) {
|
||||||
|
if (sqrtNumerator === 0) {
|
||||||
|
optimalPrice = 0; // No production
|
||||||
|
} else {
|
||||||
|
optimalPrice = product.pCost + markupLimit;
|
||||||
|
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optimalPrice = (numerator / denominator) + product.pCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store this "optimal Price" in a property so we don't have to re-calculate for UI
|
||||||
|
product.marketTa2Price[city] = optimalPrice;
|
||||||
|
sCost = optimalPrice;
|
||||||
|
} else if (product.marketTa1) {
|
||||||
|
sCost = product.pCost + markupLimit;
|
||||||
|
} else if (isString(product.sCost)) {
|
||||||
sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku);
|
sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku);
|
||||||
sCost = eval(sCost);
|
sCost = eval(sCost);
|
||||||
} else {
|
} else {
|
||||||
sCost = product.sCost;
|
sCost = product.sCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
var markup = 1, markupLimit = product.rat / product.mku;
|
var markup = 1;
|
||||||
if (sCost > product.pCost) {
|
if (sCost > product.pCost) {
|
||||||
if ((sCost - product.pCost) > markupLimit) {
|
if ((sCost - product.pCost) > markupLimit) {
|
||||||
markup = markupLimit / (sCost - product.pCost);
|
markup = markupLimit / (sCost - product.pCost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var businessFactor = this.getBusinessFactor(office); //Business employee productivity
|
|
||||||
var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
|
|
||||||
var marketFactor = this.getMarketFactor(product); //Competition + demand
|
|
||||||
var maxSell = 0.5
|
var maxSell = 0.5
|
||||||
* Math.pow(product.rat, 0.65)
|
* Math.pow(product.rat, 0.65)
|
||||||
* marketFactor
|
* marketFactor
|
||||||
@ -1080,8 +1154,9 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
|
|||||||
} else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
|
} else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
|
||||||
//Sell amount is manually limited
|
//Sell amount is manually limited
|
||||||
sellAmt = Math.min(maxSell, product.sllman[city][1]);
|
sellAmt = Math.min(maxSell, product.sllman[city][1]);
|
||||||
|
} else if (product.sllman[city][0] === false){
|
||||||
|
sellAmt = 0;
|
||||||
} else {
|
} else {
|
||||||
//Backwards compatibility, -1 = 0
|
|
||||||
sellAmt = maxSell;
|
sellAmt = maxSell;
|
||||||
}
|
}
|
||||||
if (sellAmt < 0) { sellAmt = 0; }
|
if (sellAmt < 0) { sellAmt = 0; }
|
||||||
@ -1109,8 +1184,7 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
|
|||||||
return totalProfit;
|
return totalProfit;
|
||||||
}
|
}
|
||||||
|
|
||||||
Industry.prototype.discontinueProduct = function(product, parentRefs) {
|
Industry.prototype.discontinueProduct = function(product) {
|
||||||
var company = parentRefs.company, industry = parentRefs.industry;
|
|
||||||
for (var productName in this.products) {
|
for (var productName in this.products) {
|
||||||
if (this.products.hasOwnProperty(productName)) {
|
if (this.products.hasOwnProperty(productName)) {
|
||||||
if (product === this.products[productName]) {
|
if (product === this.products[productName]) {
|
||||||
@ -1147,33 +1221,38 @@ Industry.prototype.upgrade = function(upgrade, refs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Returns how much of a material can be produced based of office productivity (employee stats)
|
// Returns how much of a material can be produced based of office productivity (employee stats)
|
||||||
Industry.prototype.getOfficeProductivity = function(office, params) {
|
Industry.prototype.getOfficeProductivity = function(office, params) {
|
||||||
var total = office.employeeProd[EmployeePositions.Operations] +
|
const opProd = office.employeeProd[EmployeePositions.Operations];
|
||||||
office.employeeProd[EmployeePositions.Engineer] +
|
const engrProd = office.employeeProd[EmployeePositions.Engineer];
|
||||||
office.employeeProd[EmployeePositions.Management], ratio;
|
const mgmtProd = office.employeeProd[EmployeePositions.Management]
|
||||||
if (total === 0) {
|
const total = opProd + engrProd + mgmtProd;
|
||||||
ratio = 0;
|
|
||||||
} else {
|
if (total <= 0) { return 0; }
|
||||||
ratio = (office.employeeProd[EmployeePositions.Operations] / total) *
|
|
||||||
(office.employeeProd[EmployeePositions.Engineer] / total) *
|
// Management is a multiplier for the production from Operations and Engineers
|
||||||
(office.employeeProd[EmployeePositions.Management] / total);
|
const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
|
||||||
ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees
|
|
||||||
}
|
// For production, Operations is slightly more important than engineering
|
||||||
|
// Both Engineering and Operations have diminishing returns
|
||||||
|
const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor;
|
||||||
|
|
||||||
|
// Generic multiplier for the production. Used for game-balancing purposes
|
||||||
|
const balancingMult = 0.05;
|
||||||
|
|
||||||
if (params && params.forProduct) {
|
if (params && params.forProduct) {
|
||||||
return ratio * Math.pow(total, 0.25);
|
// Products are harder to create and therefore have less production
|
||||||
|
return 0.5 * balancingMult * prod;
|
||||||
} else {
|
} else {
|
||||||
return 2 * ratio * Math.pow(total, 0.35);
|
return balancingMult * prod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Returns a multiplier based on the office' 'Business' employees that affects sales
|
// Returns a multiplier based on the office' 'Business' employees that affects sales
|
||||||
Industry.prototype.getBusinessFactor = function(office) {
|
Industry.prototype.getBusinessFactor = function(office) {
|
||||||
var ratioMult = 1;
|
const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
|
||||||
if (office.employeeProd["total"] > 0) {
|
|
||||||
ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]);
|
return calculateEffectWithFactors(businessProd, 0.26, 10e3);
|
||||||
}
|
|
||||||
return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.25);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This
|
//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This
|
||||||
@ -1189,7 +1268,7 @@ Industry.prototype.getAdvertisingFactors = function() {
|
|||||||
|
|
||||||
//Returns a multiplier based on a materials demand and competition that affects sales
|
//Returns a multiplier based on a materials demand and competition that affects sales
|
||||||
Industry.prototype.getMarketFactor = function(mat) {
|
Industry.prototype.getMarketFactor = function(mat) {
|
||||||
return mat.dmd * (100 - mat.cmp)/100;
|
return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a boolean indicating whether this Industry has the specified Research
|
// Returns a boolean indicating whether this Industry has the specified Research
|
||||||
@ -1271,10 +1350,8 @@ Industry.prototype.createResearchBox = function() {
|
|||||||
researchTreeBox = null;
|
researchTreeBox = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateResearchTree();
|
|
||||||
const researchTree = IndustryResearchTrees[this.type];
|
const researchTree = IndustryResearchTrees[this.type];
|
||||||
|
|
||||||
|
|
||||||
// Create the popup first, so that the tree diagram can be added to it
|
// Create the popup first, so that the tree diagram can be added to it
|
||||||
// This is handled by Treant
|
// This is handled by Treant
|
||||||
researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" });
|
researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" });
|
||||||
@ -1324,6 +1401,10 @@ Industry.prototype.createResearchBox = function() {
|
|||||||
researchTree.research(allResearch[i]);
|
researchTree.research(allResearch[i]);
|
||||||
this.researched[allResearch[i]] = true;
|
this.researched[allResearch[i]] = true;
|
||||||
|
|
||||||
|
dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
|
||||||
|
`(~${SecsPerMarketCycle} seconds) before the effects of ` +
|
||||||
|
`the Research apply.`);
|
||||||
|
|
||||||
return this.createResearchBox();
|
return this.createResearchBox();
|
||||||
} else {
|
} else {
|
||||||
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
|
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
|
||||||
@ -1399,7 +1480,7 @@ function Employee(params={}) {
|
|||||||
|
|
||||||
//Returns the amount the employee needs to be paid
|
//Returns the amount the employee needs to be paid
|
||||||
Employee.prototype.process = function(marketCycles=1, office) {
|
Employee.prototype.process = function(marketCycles=1, office) {
|
||||||
var gain = 0.001 * marketCycles,
|
var gain = 0.003 * marketCycles,
|
||||||
det = gain * Math.random();
|
det = gain * Math.random();
|
||||||
this.age += gain;
|
this.age += gain;
|
||||||
this.exp += gain;
|
this.exp += gain;
|
||||||
@ -1425,16 +1506,9 @@ Employee.prototype.process = function(marketCycles=1, office) {
|
|||||||
this.eff += trainingEff;
|
this.eff += trainingEff;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Weight based on how full office is
|
this.ene -= det;
|
||||||
//Too many employees = more likely to decrease energy and happiness
|
this.hap -= det;
|
||||||
var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5);
|
|
||||||
if (Math.random() < 0.5 - officeCapacityWeight) {
|
|
||||||
this.ene += det;
|
|
||||||
this.hap += det;
|
|
||||||
} else {
|
|
||||||
this.ene -= det;
|
|
||||||
this.hap -= det;
|
|
||||||
}
|
|
||||||
if (this.ene < office.minEne) {this.ene = office.minEne;}
|
if (this.ene < office.minEne) {this.ene = office.minEne;}
|
||||||
if (this.hap < office.minHap) {this.hap = office.minHap;}
|
if (this.hap < office.minHap) {this.hap = office.minHap;}
|
||||||
var salary = this.sal * marketCycles * SecsPerMarketCycle;
|
var salary = this.sal * marketCycles * SecsPerMarketCycle;
|
||||||
@ -1591,6 +1665,14 @@ OfficeSpace.prototype.atCapacity = function() {
|
|||||||
OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
|
OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
|
||||||
var corporation = parentRefs.corporation, industry = parentRefs.industry;
|
var corporation = parentRefs.corporation, industry = parentRefs.industry;
|
||||||
|
|
||||||
|
// HRBuddy AutoRecruitment and training
|
||||||
|
if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
|
||||||
|
const emp = this.hireRandomEmployee();
|
||||||
|
if (industry.hasResearch("HRBuddy-Training")) {
|
||||||
|
emp.pos = EmployeePositions.Training;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process Office properties
|
// Process Office properties
|
||||||
this.maxEne = 100;
|
this.maxEne = 100;
|
||||||
this.maxHap = 100;
|
this.maxHap = 100;
|
||||||
@ -1640,18 +1722,16 @@ OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
|
|||||||
salaryPaid += salary;
|
salaryPaid += salary;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.calculateEmployeeProductivity(marketCycles, parentRefs);
|
this.calculateEmployeeProductivity(parentRefs);
|
||||||
return salaryPaid;
|
return salaryPaid;
|
||||||
}
|
}
|
||||||
|
|
||||||
OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) {
|
OfficeSpace.prototype.calculateEmployeeProductivity = function(parentRefs) {
|
||||||
var company = parentRefs.corporation, industry = parentRefs.industry;
|
var company = parentRefs.corporation, industry = parentRefs.industry;
|
||||||
|
|
||||||
//Reset
|
//Reset
|
||||||
for (const name in this.employeeProd) {
|
for (const name in this.employeeProd) {
|
||||||
if (this.employeeProd.hasOwnProperty(name)) {
|
this.employeeProd[name] = 0;
|
||||||
this.employeeProd[name] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var total = 0;
|
var total = 0;
|
||||||
@ -1774,8 +1854,7 @@ OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) {
|
|||||||
yesNoTxtInpBoxCreate("Give your employee a nickname!");
|
yesNoTxtInpBoxCreate("Give your employee a nickname!");
|
||||||
}
|
}
|
||||||
|
|
||||||
OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) {
|
OfficeSpace.prototype.hireRandomEmployee = function() {
|
||||||
var company = parentRefs.corporation, division = parentRefs.industry;
|
|
||||||
if (this.atCapacity()) { return; }
|
if (this.atCapacity()) { return; }
|
||||||
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
|
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
|
||||||
|
|
||||||
@ -1799,13 +1878,15 @@ OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) {
|
|||||||
|
|
||||||
var name = generateRandomString(7);
|
var name = generateRandomString(7);
|
||||||
|
|
||||||
for (var i = 0; i < this.employees.length; ++i) {
|
for (let i = 0; i < this.employees.length; ++i) {
|
||||||
if (this.employees[i].name === name) {
|
if (this.employees[i].name === name) {
|
||||||
return this.hireRandomEmployee(parentRefs);
|
return this.hireRandomEmployee();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emp.name = name;
|
emp.name = name;
|
||||||
this.employees.push(emp);
|
this.employees.push(emp);
|
||||||
|
|
||||||
|
return emp;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finds the first unassigned employee and assigns its to the specified job
|
//Finds the first unassigned employee and assigns its to the specified job
|
||||||
@ -1977,19 +2058,19 @@ Corporation.prototype.getInvestment = function() {
|
|||||||
switch (this.fundingRound) {
|
switch (this.fundingRound) {
|
||||||
case 0: //Seed
|
case 0: //Seed
|
||||||
percShares = 0.10;
|
percShares = 0.10;
|
||||||
roundMultiplier = 5;
|
roundMultiplier = 4;
|
||||||
break;
|
break;
|
||||||
case 1: //Series A
|
case 1: //Series A
|
||||||
percShares = 0.35;
|
percShares = 0.35;
|
||||||
roundMultiplier = 4;
|
roundMultiplier = 3;
|
||||||
break;
|
break;
|
||||||
case 2: //Series B
|
case 2: //Series B
|
||||||
percShares = 0.25;
|
percShares = 0.25;
|
||||||
roundMultiplier = 4;
|
roundMultiplier = 3;
|
||||||
break;
|
break;
|
||||||
case 3: //Series C
|
case 3: //Series C
|
||||||
percShares = 0.20;
|
percShares = 0.20;
|
||||||
roundMultiplier = 3.5;
|
roundMultiplier = 2.5;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
return;
|
return;
|
||||||
@ -2292,8 +2373,6 @@ Corporation.prototype.rerender = function() {
|
|||||||
}
|
}
|
||||||
if (!routing.isOn(Page.Corporation)) { return; }
|
if (!routing.isOn(Page.Corporation)) { return; }
|
||||||
|
|
||||||
console.log("Re-rendering...");
|
|
||||||
|
|
||||||
ReactDOM.render(<CorporationRoot
|
ReactDOM.render(<CorporationRoot
|
||||||
corp={this}
|
corp={this}
|
||||||
routing={corpRouting}
|
routing={corpRouting}
|
||||||
|
@ -66,6 +66,7 @@ export class Material {
|
|||||||
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
||||||
marketTa1: boolean = false;
|
marketTa1: boolean = false;
|
||||||
marketTa2: boolean = false;
|
marketTa2: boolean = false;
|
||||||
|
marketTa2Price: number = 0;
|
||||||
|
|
||||||
constructor(params: IConstructorParams = {}) {
|
constructor(params: IConstructorParams = {}) {
|
||||||
if (params.name) { this.name = params.name; }
|
if (params.name) { this.name = params.name; }
|
||||||
@ -85,7 +86,7 @@ export class Material {
|
|||||||
this.mku = 6;
|
this.mku = 6;
|
||||||
break;
|
break;
|
||||||
case "Energy":
|
case "Energy":
|
||||||
this.dmd = 90; this.dmdR = [80, 100];
|
this.dmd = 90; this.dmdR = [80, 99];
|
||||||
this.cmp = 80; this.cmpR = [65, 95];
|
this.cmp = 80; this.cmpR = [65, 95];
|
||||||
this.bCost = 2000; this.mv = 0.2;
|
this.bCost = 2000; this.mv = 0.2;
|
||||||
this.mku = 6;
|
this.mku = 6;
|
||||||
@ -121,26 +122,26 @@ export class Material {
|
|||||||
this.mku = 2;
|
this.mku = 2;
|
||||||
break;
|
break;
|
||||||
case "Real Estate":
|
case "Real Estate":
|
||||||
this.dmd = 50; this.dmdR = [5, 100];
|
this.dmd = 50; this.dmdR = [5, 99];
|
||||||
this.cmp = 50; this.cmpR = [25, 75];
|
this.cmp = 50; this.cmpR = [25, 75];
|
||||||
this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
|
this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
|
||||||
this.mku = 1.5;
|
this.mku = 1.5;
|
||||||
break;
|
break;
|
||||||
case "Drugs":
|
case "Drugs":
|
||||||
this.dmd = 60; this.dmdR = [45, 75];
|
this.dmd = 60; this.dmdR = [45, 75];
|
||||||
this.cmp = 70; this.cmpR = [40, 100];
|
this.cmp = 70; this.cmpR = [40, 99];
|
||||||
this.bCost = 40e3; this.mv = 1.6;
|
this.bCost = 40e3; this.mv = 1.6;
|
||||||
this.mku = 1;
|
this.mku = 1;
|
||||||
break;
|
break;
|
||||||
case "Robots":
|
case "Robots":
|
||||||
this.dmd = 90; this.dmdR = [80, 100];
|
this.dmd = 90; this.dmdR = [80, 9];
|
||||||
this.cmp = 90; this.cmpR = [80, 100];
|
this.cmp = 90; this.cmpR = [80, 9];
|
||||||
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
|
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
|
||||||
this.mku = 1;
|
this.mku = 1;
|
||||||
break;
|
break;
|
||||||
case "AI Cores":
|
case "AI Cores":
|
||||||
this.dmd = 90; this.dmdR = [80, 100];
|
this.dmd = 90; this.dmdR = [80, 99];
|
||||||
this.cmp = 90; this.cmpR = [80, 100];
|
this.cmp = 90; this.cmpR = [80, 9];
|
||||||
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
|
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
|
||||||
this.mku = 0.5;
|
this.mku = 0.5;
|
||||||
break;
|
break;
|
||||||
|
@ -2,15 +2,17 @@ import { IMap } from "../types";
|
|||||||
|
|
||||||
// Map of material (by name) to their sizes (how much space it takes in warehouse)
|
// Map of material (by name) to their sizes (how much space it takes in warehouse)
|
||||||
export const MaterialSizes: IMap<number> = {
|
export const MaterialSizes: IMap<number> = {
|
||||||
Water: 0.05,
|
Water: 0.05,
|
||||||
Energy: 0.01,
|
Energy: 0.01,
|
||||||
Food: 0.03,
|
Food: 0.03,
|
||||||
Plants: 0.05,
|
Plants: 0.05,
|
||||||
Metal: 0.1,
|
Metal: 0.1,
|
||||||
Hardware: 0.06,
|
Hardware: 0.06,
|
||||||
Chemicals: 0.05,
|
Chemicals: 0.05,
|
||||||
Drugs: 0.02,
|
Drugs: 0.02,
|
||||||
Robots: 0.5,
|
Robots: 0.5,
|
||||||
AICores: 0.1,
|
AICores: 0.1,
|
||||||
RealEstate: 0,
|
RealEstate: 0,
|
||||||
|
"Real Estate": 0,
|
||||||
|
"AI Cores": 0,
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { MaterialSizes } from "./MaterialSizes";
|
|||||||
import { ProductRatingWeights,
|
import { ProductRatingWeights,
|
||||||
IProductRatingWeight } from "./ProductRatingWeights";
|
IProductRatingWeight } from "./ProductRatingWeights";
|
||||||
|
|
||||||
import { CityName } from "../Locations/data/CityNames";
|
import { createCityMap } from "../Locations/createCityMap";
|
||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
|
|
||||||
import { Generic_fromJSON,
|
import { Generic_fromJSON,
|
||||||
@ -89,14 +89,7 @@ export class Product {
|
|||||||
// Data refers to the production, sale, and quantity of the products
|
// Data refers to the production, sale, and quantity of the products
|
||||||
// These values are specific to a city
|
// These values are specific to a city
|
||||||
// For each city, the data is [qty, prod, sell]
|
// For each city, the data is [qty, prod, sell]
|
||||||
data: IMap<number[]> = {
|
data: IMap<number[]> = createCityMap<number[]>([0, 0, 0]);
|
||||||
[Cities.Aevum]: [0, 0, 0],
|
|
||||||
[Cities.Chongqing]: [0, 0, 0],
|
|
||||||
[Cities.Sector12]: [0, 0, 0],
|
|
||||||
[Cities.NewTokyo]: [0, 0, 0],
|
|
||||||
[Cities.Ishima]: [0, 0, 0],
|
|
||||||
[Cities.Volhaven]: [0, 0, 0],
|
|
||||||
}
|
|
||||||
|
|
||||||
// Location of this Product
|
// Location of this Product
|
||||||
// Only applies for location-based products like restaurants/hospitals
|
// Only applies for location-based products like restaurants/hospitals
|
||||||
@ -113,23 +106,13 @@ export class Product {
|
|||||||
// Data to keep track of whether production/sale of this Product is
|
// Data to keep track of whether production/sale of this Product is
|
||||||
// manually limited. These values are specific to a city
|
// manually limited. These values are specific to a city
|
||||||
// [Whether production/sale is limited, limit amount]
|
// [Whether production/sale is limited, limit amount]
|
||||||
prdman: IMap<any[]> = {
|
prdman: IMap<any[]> = createCityMap<any[]>([false, 0]);
|
||||||
[Cities.Aevum]: [false, 0],
|
sllman: IMap<any[]> = createCityMap<any[]>([false, 0]);
|
||||||
[Cities.Chongqing]: [false, 0],
|
|
||||||
[Cities.Sector12]: [false, 0],
|
|
||||||
[Cities.NewTokyo]: [false, 0],
|
|
||||||
[Cities.Ishima]: [false, 0],
|
|
||||||
[Cities.Volhaven]: [false, 0],
|
|
||||||
}
|
|
||||||
|
|
||||||
sllman: IMap<any[]> = {
|
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
||||||
[Cities.Aevum]: [false, 0],
|
marketTa1: boolean = false;
|
||||||
[Cities.Chongqing]: [false, 0],
|
marketTa2: boolean = false;
|
||||||
[Cities.Sector12]: [false, 0],
|
marketTa2Price: IMap<number> = createCityMap<number>(0);
|
||||||
[Cities.NewTokyo]: [false, 0],
|
|
||||||
[Cities.Ishima]: [false, 0],
|
|
||||||
[Cities.Volhaven]: [false, 0],
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(params: IConstructorParams={}) {
|
constructor(params: IConstructorParams={}) {
|
||||||
this.name = params.name ? params.name : "";
|
this.name = params.name ? params.name : "";
|
||||||
@ -164,11 +147,11 @@ export class Product {
|
|||||||
//Calculate properties
|
//Calculate properties
|
||||||
var progrMult = this.prog / 100;
|
var progrMult = this.prog / 100;
|
||||||
|
|
||||||
var engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"],
|
const engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"];
|
||||||
mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"],
|
const mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"];
|
||||||
rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"],
|
const rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"];
|
||||||
opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"],
|
const opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"];
|
||||||
busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"];
|
const busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"];
|
||||||
var designMult = 1 + (Math.pow(this.designCost, 0.1) / 100);
|
var designMult = 1 + (Math.pow(this.designCost, 0.1) / 100);
|
||||||
console.log("designMult: " + designMult);
|
console.log("designMult: " + designMult);
|
||||||
var balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) +
|
var balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) +
|
||||||
|
@ -8,6 +8,8 @@ import { ResearchMap } from "./ResearchMap";
|
|||||||
|
|
||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||||||
|
|
||||||
interface IConstructorParams {
|
interface IConstructorParams {
|
||||||
children?: Node[];
|
children?: Node[];
|
||||||
cost: number;
|
cost: number;
|
||||||
@ -83,7 +85,7 @@ export class Node {
|
|||||||
children: childrenArray,
|
children: childrenArray,
|
||||||
HTMLclass: htmlClass,
|
HTMLclass: htmlClass,
|
||||||
innerHTML: `<div id="${sanitizedName}-corp-research-click-listener" class="tooltip">` +
|
innerHTML: `<div id="${sanitizedName}-corp-research-click-listener" class="tooltip">` +
|
||||||
`${this.text}<br>${this.cost} Scientific Research` +
|
`${this.text}<br>${numeralWrapper.format(this.cost, "0,0")} Scientific Research` +
|
||||||
`<span class="tooltiptext">` +
|
`<span class="tooltiptext">` +
|
||||||
`${research.desc}` +
|
`${research.desc}` +
|
||||||
`</span>` +
|
`</span>` +
|
||||||
|
@ -5,16 +5,19 @@ import { numeralWrapper } from "../ui/numeralFormat";
|
|||||||
import { Generic_fromJSON,
|
import { Generic_fromJSON,
|
||||||
Generic_toJSON,
|
Generic_toJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Reviver } from "../../utils/JSONReviver";
|
||||||
|
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||||
interface IConstructorParams {
|
|
||||||
loc?: string;
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IParent {
|
interface IParent {
|
||||||
getStorageMultiplier(): number;
|
getStorageMultiplier(): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IConstructorParams {
|
||||||
|
corp?: IParent;
|
||||||
|
industry?: IParent;
|
||||||
|
loc?: string;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Warehouse {
|
export class Warehouse {
|
||||||
// Initiatizes a Warehouse object from a JSON save state.
|
// Initiatizes a Warehouse object from a JSON save state.
|
||||||
static fromJSON(value: any): Warehouse {
|
static fromJSON(value: any): Warehouse {
|
||||||
@ -43,6 +46,10 @@ export class Warehouse {
|
|||||||
// Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for)
|
// Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for)
|
||||||
smartSupplyEnabled: boolean = false;
|
smartSupplyEnabled: boolean = false;
|
||||||
|
|
||||||
|
// Flag that indicates whether Smart Supply accounts for imports when calculating
|
||||||
|
// the amount fo purchase
|
||||||
|
smartSupplyConsiderExports: boolean = false;
|
||||||
|
|
||||||
// Stores the amount of product to be produced. Used for Smart Supply unlock.
|
// Stores the amount of product to be produced. Used for Smart Supply unlock.
|
||||||
// The production tracked by smart supply is always based on the previous cycle,
|
// The production tracked by smart supply is always based on the previous cycle,
|
||||||
// so it will always trail the "true" production by 1 cycle
|
// so it will always trail the "true" production by 1 cycle
|
||||||
@ -65,6 +72,10 @@ export class Warehouse {
|
|||||||
AICores: new Material({name: "AI Cores"}),
|
AICores: new Material({name: "AI Cores"}),
|
||||||
RealEstate: new Material({name: "Real Estate"})
|
RealEstate: new Material({name: "Real Estate"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.corp && params.industry) {
|
||||||
|
this.updateSize(params.corp, params.industry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-calculate how much space is being used by this Warehouse
|
// Re-calculate how much space is being used by this Warehouse
|
||||||
@ -76,7 +87,7 @@ export class Warehouse {
|
|||||||
if (MaterialSizes.hasOwnProperty(matName)) {
|
if (MaterialSizes.hasOwnProperty(matName)) {
|
||||||
this.sizeUsed += (mat.qty * MaterialSizes[matName]);
|
this.sizeUsed += (mat.qty * MaterialSizes[matName]);
|
||||||
if (mat.qty > 0) {
|
if (mat.qty > 0) {
|
||||||
this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0") + "<br>");
|
this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0") + "<br>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,9 +97,13 @@ export class Warehouse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSize(corporation: IParent, industry: IParent) {
|
updateSize(corporation: IParent, industry: IParent) {
|
||||||
this.size = (this.level * 100)
|
try {
|
||||||
* corporation.getStorageMultiplier()
|
this.size = (this.level * 100)
|
||||||
* industry.getStorageMultiplier();
|
* corporation.getStorageMultiplier()
|
||||||
|
* industry.getStorageMultiplier();
|
||||||
|
} catch(e) {
|
||||||
|
exceptionAlert(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the current object to a JSON save state.
|
// Serialize the current object to a JSON save state.
|
||||||
|
@ -27,6 +27,8 @@ function createBaseResearchTreeNodes(): Node {
|
|||||||
const dronesAssembly: Node = makeNode("Drones - Assembly");
|
const dronesAssembly: Node = makeNode("Drones - Assembly");
|
||||||
const dronesTransport: Node = makeNode("Drones - Transport");
|
const dronesTransport: Node = makeNode("Drones - Transport");
|
||||||
const goJuice: Node = makeNode("Go-Juice");
|
const goJuice: Node = makeNode("Go-Juice");
|
||||||
|
const hrRecruitment: Node = makeNode("HRBuddy-Recruitment");
|
||||||
|
const hrTraining: Node = makeNode("HRBuddy-Training");
|
||||||
const joywire: Node = makeNode("JoyWire");
|
const joywire: Node = makeNode("JoyWire");
|
||||||
const marketta1: Node = makeNode("Market-TA.I");
|
const marketta1: Node = makeNode("Market-TA.I");
|
||||||
const marketta2: Node = makeNode("Market-TA.II");
|
const marketta2: Node = makeNode("Market-TA.II");
|
||||||
@ -40,6 +42,8 @@ function createBaseResearchTreeNodes(): Node {
|
|||||||
drones.addChild(dronesAssembly);
|
drones.addChild(dronesAssembly);
|
||||||
drones.addChild(dronesTransport);
|
drones.addChild(dronesTransport);
|
||||||
|
|
||||||
|
hrRecruitment.addChild(hrTraining);
|
||||||
|
|
||||||
marketta1.addChild(marketta2);
|
marketta1.addChild(marketta2);
|
||||||
|
|
||||||
overclock.addChild(stimu);
|
overclock.addChild(stimu);
|
||||||
@ -49,6 +53,7 @@ function createBaseResearchTreeNodes(): Node {
|
|||||||
rootNode.addChild(autoDrugs);
|
rootNode.addChild(autoDrugs);
|
||||||
rootNode.addChild(bulkPurchasing);
|
rootNode.addChild(bulkPurchasing);
|
||||||
rootNode.addChild(drones);
|
rootNode.addChild(drones);
|
||||||
|
rootNode.addChild(hrRecruitment);
|
||||||
rootNode.addChild(joywire);
|
rootNode.addChild(joywire);
|
||||||
rootNode.addChild(marketta1);
|
rootNode.addChild(marketta1);
|
||||||
rootNode.addChild(overclock);
|
rootNode.addChild(overclock);
|
||||||
|
@ -77,6 +77,21 @@ export const researchMetadata: IConstructorParams[] = [
|
|||||||
"production by 10%.",
|
"production by 10%.",
|
||||||
sciResearchMult: 1.1,
|
sciResearchMult: 1.1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "HRBuddy-Recruitment",
|
||||||
|
cost: 15e3,
|
||||||
|
desc: "Use automated software to handle the hiring of employees. With this " +
|
||||||
|
"research, each office will automatically hire one employee per " +
|
||||||
|
"market cycle if there is available space."
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HRBuddy-Training",
|
||||||
|
cost: 20e3,
|
||||||
|
desc: "Use automated software to handle the training of employees. With this " +
|
||||||
|
"research, each employee hired with HRBuddy-Recruitment will automatically " +
|
||||||
|
"be assigned to 'Training', rather than being unassigned."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "JoyWire",
|
name: "JoyWire",
|
||||||
cost: 20e3,
|
cost: 20e3,
|
||||||
@ -94,11 +109,13 @@ export const researchMetadata: IConstructorParams[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Market-TA.II",
|
name: "Market-TA.II",
|
||||||
cost: 40e3,
|
cost: 50e3,
|
||||||
desc: "Develop double-advanced AI software that uses technical analysis to " +
|
desc: "Develop double-advanced AI software that uses technical analysis to " +
|
||||||
"help you understand and exploit the market. This research " +
|
"help you understand and exploit the market. This research " +
|
||||||
"allows you to know how many sales of a Material/Product you lose or gain " +
|
"allows you to know how many sales of a Material/Product you lose or gain " +
|
||||||
"from having too high or too low or a sale price.",
|
"from having too high or too low or a sale price. It also lets you automatically " +
|
||||||
|
"set the sale price of your Materials/Products at the optimal price such that " +
|
||||||
|
"the amount sold matches the amount produced.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Overclock",
|
name: "Overclock",
|
||||||
|
@ -14,6 +14,9 @@ export class CityTabs extends BaseReactComponent {
|
|||||||
if (props.city == null) {
|
if (props.city == null) {
|
||||||
throw new Error(`CityTabs component constructed without 'city' property`)
|
throw new Error(`CityTabs component constructed without 'city' property`)
|
||||||
}
|
}
|
||||||
|
if (props.cityStateSetter == null) {
|
||||||
|
throw new Error(`CityTabs component constructed without 'cityStateSetter' property`)
|
||||||
|
}
|
||||||
|
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
@ -46,7 +49,8 @@ export class CityTabs extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tab to "Expand into new City"
|
// Tab to "Expand into new City"
|
||||||
const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division);
|
const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division, this.props.cityStateSetter);
|
||||||
|
|
||||||
tabs.push(this.renderTab({
|
tabs.push(this.renderTab({
|
||||||
current: false,
|
current: false,
|
||||||
key: "Expand into new City",
|
key: "Expand into new City",
|
||||||
|
@ -10,15 +10,23 @@ import { Corporation,
|
|||||||
OfficeInitialSize,
|
OfficeInitialSize,
|
||||||
SellSharesCooldown,
|
SellSharesCooldown,
|
||||||
WarehouseInitialCost,
|
WarehouseInitialCost,
|
||||||
WarehouseInitialSize } from "../Corporation";
|
WarehouseInitialSize,
|
||||||
|
BribeToRepRatio } from "../Corporation";
|
||||||
|
|
||||||
import { Industries,
|
import { Industries,
|
||||||
IndustryStartingCosts,
|
IndustryStartingCosts,
|
||||||
IndustryDescriptions,
|
IndustryDescriptions,
|
||||||
IndustryResearchTrees } from "../IndustryData";
|
IndustryResearchTrees } from "../IndustryData";
|
||||||
|
|
||||||
|
import { MaterialSizes } from "../MaterialSizes";
|
||||||
|
|
||||||
|
import { Product } from "../Product";
|
||||||
|
|
||||||
import { Player } from "../../Player";
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
|
import { Factions } from "../../Faction/Factions";
|
||||||
|
import { Cities } from "../../Locations/Cities";
|
||||||
|
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||||
@ -79,7 +87,7 @@ export class CorporationEventHandler {
|
|||||||
|
|
||||||
var totalAmount = Number(money) + (stockShares * stockPrice);
|
var totalAmount = Number(money) + (stockShares * stockPrice);
|
||||||
var repGain = totalAmount / BribeToRepRatio;
|
var repGain = totalAmount / BribeToRepRatio;
|
||||||
repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") +
|
repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
|
||||||
" reputation with " +
|
" reputation with " +
|
||||||
factionSelector.options[factionSelector.selectedIndex].value +
|
factionSelector.options[factionSelector.selectedIndex].value +
|
||||||
" with this bribe";
|
" with this bribe";
|
||||||
@ -102,14 +110,14 @@ export class CorporationEventHandler {
|
|||||||
var totalAmount = money + (stockShares * stockPrice);
|
var totalAmount = money + (stockShares * stockPrice);
|
||||||
var repGain = totalAmount / BribeToRepRatio;
|
var repGain = totalAmount / BribeToRepRatio;
|
||||||
console.log("repGain: " + repGain);
|
console.log("repGain: " + repGain);
|
||||||
repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") +
|
repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
|
||||||
" reputation with " +
|
" reputation with " +
|
||||||
factionSelector.options[factionSelector.selectedIndex].value +
|
factionSelector.options[factionSelector.selectedIndex].value +
|
||||||
" with this bribe";
|
" with this bribe";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var confirmButton = createElement("a", {
|
var confirmButton = createElement("button", {
|
||||||
class:"a-link-button", innerText:"Bribe", display:"inline-block",
|
class:"a-link-button", innerText:"Bribe", display:"inline-block",
|
||||||
clickListener:()=>{
|
clickListener:()=>{
|
||||||
var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
|
var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
|
||||||
@ -129,7 +137,7 @@ export class CorporationEventHandler {
|
|||||||
} else {
|
} else {
|
||||||
var totalAmount = money + (stockShares * stockPrice);
|
var totalAmount = money + (stockShares * stockPrice);
|
||||||
var repGain = totalAmount / BribeToRepRatio;
|
var repGain = totalAmount / BribeToRepRatio;
|
||||||
dialogBoxCreate("You gained " + formatNumber(repGain, 0) +
|
dialogBoxCreate("You gained " + numeralWrapper.format(repGain, "0,0") +
|
||||||
" reputation with " + fac.name + " by bribing them.");
|
" reputation with " + fac.name + " by bribing them.");
|
||||||
fac.playerReputation += repGain;
|
fac.playerReputation += repGain;
|
||||||
this.corp.funds = this.corp.funds.minus(money);
|
this.corp.funds = this.corp.funds.minus(money);
|
||||||
@ -140,6 +148,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const cancelButton = createPopupCloseButton(popupId, {
|
const cancelButton = createPopupCloseButton(popupId, {
|
||||||
|
class: "std-button",
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
innerText: "Cancel",
|
innerText: "Cancel",
|
||||||
})
|
})
|
||||||
@ -168,7 +177,6 @@ export class CorporationEventHandler {
|
|||||||
type:"number", placeholder:"Shares to buyback", margin:"5px",
|
type:"number", placeholder:"Shares to buyback", margin:"5px",
|
||||||
inputListener: ()=> {
|
inputListener: ()=> {
|
||||||
var numShares = Math.round(input.value);
|
var numShares = Math.round(input.value);
|
||||||
//TODO add conditional for if player doesn't have enough money
|
|
||||||
if (isNaN(numShares) || numShares <= 0) {
|
if (isNaN(numShares) || numShares <= 0) {
|
||||||
costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback"
|
costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback"
|
||||||
} else if (numShares > this.corp.issuedShares) {
|
} else if (numShares > this.corp.issuedShares) {
|
||||||
@ -181,7 +189,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var confirmBtn = createElement("a", {
|
var confirmBtn = createElement("button", {
|
||||||
class:"a-link-button", innerText:"Buy shares", display:"inline-block",
|
class:"a-link-button", innerText:"Buy shares", display:"inline-block",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
var shares = Math.round(input.value);
|
var shares = Math.round(input.value);
|
||||||
@ -226,7 +234,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player discontinue a product
|
// Create a popup that lets the player discontinue a product
|
||||||
createDiscontinueProductPopup(product) {
|
createDiscontinueProductPopup(product, industry) {
|
||||||
const popupId = "cmpy-mgmt-discontinue-product-popup";
|
const popupId = "cmpy-mgmt-discontinue-product-popup";
|
||||||
const txt = createElement("p", {
|
const txt = createElement("p", {
|
||||||
innerText:"Are you sure you want to do this? Discontinuing a product " +
|
innerText:"Are you sure you want to do this? Discontinuing a product " +
|
||||||
@ -234,18 +242,18 @@ export class CorporationEventHandler {
|
|||||||
"produce this product and all of its existing stock will be " +
|
"produce this product and all of its existing stock will be " +
|
||||||
"removed and left unsold",
|
"removed and left unsold",
|
||||||
});
|
});
|
||||||
const confirmBtn = createElement("a", {
|
const confirmBtn = createElement("button", {
|
||||||
class:"a-link-button",innerText:"Discontinue",
|
class:"popup-box-button",innerText:"Discontinue",
|
||||||
clickListener:()=>{
|
clickListener: () => {
|
||||||
industry.discontinueProduct(product, parentRefs);
|
industry.discontinueProduct(product);
|
||||||
removeElementById(popupId);
|
removeElementById(popupId);
|
||||||
this.corp.rerender();
|
this.rerender();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
|
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
|
||||||
|
|
||||||
createPopup(popupId, [txt, confirmBtn, cancelBtn]);
|
createPopup(popupId, [txt, cancelBtn, confirmBtn]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player manage exports
|
// Create a popup that lets the player manage exports
|
||||||
@ -294,7 +302,7 @@ export class CorporationEventHandler {
|
|||||||
placeholder:"Export amount / s"
|
placeholder:"Export amount / s"
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportBtn = createElement("a", {
|
const exportBtn = createElement("button", {
|
||||||
class: "std-button", display:"inline-block", innerText:"Export",
|
class: "std-button", display:"inline-block", innerText:"Export",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
const industryName = getSelectText(industrySelector);
|
const industryName = getSelectText(industrySelector);
|
||||||
@ -340,7 +348,7 @@ export class CorporationEventHandler {
|
|||||||
clickListener:()=>{
|
clickListener:()=>{
|
||||||
mat.exp.splice(i, 1); //Remove export object
|
mat.exp.splice(i, 1); //Remove export object
|
||||||
removeElementById(popupId);
|
removeElementById(popupId);
|
||||||
createExportPopup();
|
createExportMaterialPopup(mat);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
})(i, mat, currExports);
|
})(i, mat, currExports);
|
||||||
@ -473,7 +481,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
issueBtn = createElement("a", {
|
issueBtn = createElement("button", {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
innerText: "Issue New Shares",
|
innerText: "Issue New Shares",
|
||||||
@ -529,7 +537,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player limit the production of a product
|
// Create a popup that lets the player limit the production of a product
|
||||||
createLimitProductProdutionPopup(product) {
|
createLimitProductProdutionPopup(product, city) {
|
||||||
const popupId = "cmpy-mgmt-limit-product-production-popup";
|
const popupId = "cmpy-mgmt-limit-product-production-popup";
|
||||||
const txt = createElement("p", {
|
const txt = createElement("p", {
|
||||||
innerText:"Enter a limit to the amount of this product you would " +
|
innerText:"Enter a limit to the amount of this product you would " +
|
||||||
@ -537,17 +545,19 @@ export class CorporationEventHandler {
|
|||||||
});
|
});
|
||||||
let confirmBtn;
|
let confirmBtn;
|
||||||
const input = createElement("input", {
|
const input = createElement("input", {
|
||||||
type:"number", placeholder:"Limit",
|
margin: "5px",
|
||||||
|
placeholder:"Limit",
|
||||||
|
type:"number",
|
||||||
onkeyup: (e) => {
|
onkeyup: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
|
if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmBtn = createElement("a", {
|
confirmBtn = createElement("button", {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
display:"inline-block",
|
display: "inline-block",
|
||||||
innerText:"Limit production",
|
innerText: "Limit production",
|
||||||
margin:'6px',
|
margin: "5px",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
if (input.value === "") {
|
if (input.value === "") {
|
||||||
product.prdman[city][0] = false;
|
product.prdman[city][0] = false;
|
||||||
@ -585,7 +595,7 @@ export class CorporationEventHandler {
|
|||||||
const txt = createElement("p", {
|
const txt = createElement("p", {
|
||||||
innerHTML: popupText,
|
innerHTML: popupText,
|
||||||
});
|
});
|
||||||
const designCity = createElement("select");
|
const designCity = createElement("select", { margin: "5px" });
|
||||||
for (const cityName in division.offices) {
|
for (const cityName in division.offices) {
|
||||||
if (division.offices[cityName] instanceof OfficeSpace) {
|
if (division.offices[cityName] instanceof OfficeSpace) {
|
||||||
designCity.add(createElement("option", {
|
designCity.add(createElement("option", {
|
||||||
@ -603,18 +613,26 @@ export class CorporationEventHandler {
|
|||||||
productNamePlaceholder = "Property Name";
|
productNamePlaceholder = "Property Name";
|
||||||
}
|
}
|
||||||
var productNameInput = createElement("input", {
|
var productNameInput = createElement("input", {
|
||||||
|
margin: "5px",
|
||||||
placeholder: productNamePlaceholder,
|
placeholder: productNamePlaceholder,
|
||||||
});
|
});
|
||||||
var lineBreak1 = createElement("br");
|
var lineBreak1 = createElement("br");
|
||||||
var designInvestInput = createElement("input", {
|
var designInvestInput = createElement("input", {
|
||||||
|
margin: "5px",
|
||||||
|
placeholder: "Design investment",
|
||||||
type: "number",
|
type: "number",
|
||||||
placeholder: "Design investment"
|
|
||||||
});
|
});
|
||||||
|
let confirmBtn;
|
||||||
var marketingInvestInput = createElement("input", {
|
var marketingInvestInput = createElement("input", {
|
||||||
|
margin: "5px",
|
||||||
|
placeholder: "Marketing investment",
|
||||||
type: "number",
|
type: "number",
|
||||||
placeholder: "Marketing investment"
|
onkeyup: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const confirmBtn = createElement("a", {
|
confirmBtn = createElement("button", {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
innerText: "Develop Product",
|
innerText: "Develop Product",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
@ -637,17 +655,19 @@ export class CorporationEventHandler {
|
|||||||
designCost: designInvest,
|
designCost: designInvest,
|
||||||
advCost: marketingInvest,
|
advCost: marketingInvest,
|
||||||
});
|
});
|
||||||
|
if (division.products[product.name] instanceof Product) {
|
||||||
|
dialogBoxCreate(`You already have a product with this name!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest);
|
this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest);
|
||||||
division.products[product.name] = product;
|
division.products[product.name] = product;
|
||||||
removeElementById(popupId);
|
removeElementById(popupId);
|
||||||
}
|
}
|
||||||
this.rerender();
|
this.rerender();
|
||||||
//this.updateUIContent();
|
|
||||||
//this.displayDivisionContent(division, city);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const cancelBtn = createPopupCloseButton(popupid, {
|
const cancelBtn = createPopupCloseButton(popupId, {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
innerText: "Cancel",
|
innerText: "Cancel",
|
||||||
});
|
});
|
||||||
@ -657,8 +677,8 @@ export class CorporationEventHandler {
|
|||||||
productNameInput.focus();
|
productNameInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player use the Market TA research
|
// Create a popup that lets the player use the Market TA research for Materials
|
||||||
createMarketTaPopup(mat, industry) {
|
createMaterialMarketTaPopup(mat, industry) {
|
||||||
const corp = this.corp;
|
const corp = this.corp;
|
||||||
|
|
||||||
const popupId = "cmpy-mgmt-marketta-popup";
|
const popupId = "cmpy-mgmt-marketta-popup";
|
||||||
@ -682,19 +702,21 @@ export class CorporationEventHandler {
|
|||||||
"be sold at the price identified by Market-TA.I (i.e. the price shown above)"
|
"be sold at the price identified by Market-TA.I (i.e. the price shown above)"
|
||||||
})
|
})
|
||||||
const useTa1AutoSaleCheckbox = createElement("input", {
|
const useTa1AutoSaleCheckbox = createElement("input", {
|
||||||
|
checked: mat.marketTa1,
|
||||||
id: useTa1AutoSaleId,
|
id: useTa1AutoSaleId,
|
||||||
|
margin: "3px",
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
value: mat.marketTa1,
|
|
||||||
changeListener: (e) => {
|
changeListener: (e) => {
|
||||||
mat.marketTa1 = e.target.value;
|
mat.marketTa1 = e.target.checked;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
|
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
|
||||||
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
|
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
|
||||||
|
|
||||||
const closeBtn = createPopupCloseButton(popupId, {
|
const closeBtn = createPopupCloseButton(popupId, {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
display: "block",
|
display: "block",
|
||||||
|
innerText: "Close",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (industry.hasResearch("Market-TA.II")) {
|
if (industry.hasResearch("Market-TA.II")) {
|
||||||
@ -729,11 +751,41 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
|
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
|
||||||
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
|
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
|
||||||
`then you will sell ${formatNumber(markup, 5)}x as much compared ` +
|
`then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
|
||||||
`to if you sold at market price.`;
|
`to if you sold at market price.`;
|
||||||
}
|
}
|
||||||
updateTa2Text();
|
updateTa2Text();
|
||||||
createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]);
|
|
||||||
|
// Enable using Market-TA2 for automatically setting sale price
|
||||||
|
const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
|
||||||
|
const useTa2AutoSaleDiv = createElement("div", { display: "block" });
|
||||||
|
const useTa2AutoSaleLabel = createElement("label", {
|
||||||
|
color: "white",
|
||||||
|
for: useTa2AutoSaleId,
|
||||||
|
innerText: "Use Market-TA.II for Auto-Sale Price",
|
||||||
|
tooltip: "If this is enabled, then this Material will automatically " +
|
||||||
|
"be sold at the optimal price such that the amount sold matches the " +
|
||||||
|
"amount produced. (i.e. the highest possible price, while still ensuring " +
|
||||||
|
" that all produced materials will be sold)"
|
||||||
|
})
|
||||||
|
const useTa2AutoSaleCheckbox = createElement("input", {
|
||||||
|
checked: mat.marketTa2,
|
||||||
|
id: useTa2AutoSaleId,
|
||||||
|
margin: "3px",
|
||||||
|
type: "checkbox",
|
||||||
|
changeListener: (e) => {
|
||||||
|
mat.marketTa2 = e.target.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
|
||||||
|
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
|
||||||
|
|
||||||
|
const ta2OverridesTa1 = createElement("p", {
|
||||||
|
innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
|
||||||
|
"both are enabled, then Market-TA.II will take effect, not Market-TA.I"
|
||||||
|
});
|
||||||
|
|
||||||
|
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
|
||||||
} else {
|
} else {
|
||||||
// Market-TA.I only
|
// Market-TA.I only
|
||||||
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
|
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
|
||||||
@ -741,7 +793,8 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player expand into a new city (for the current industry)
|
// Create a popup that lets the player expand into a new city (for the current industry)
|
||||||
createNewCityPopup(division) {
|
// The 'cityStateSetter' arg is a function that sets the UI's 'city' state property
|
||||||
|
createNewCityPopup(division, cityStateSetter) {
|
||||||
const popupId = "cmpy-mgmt-expand-city-popup";
|
const popupId = "cmpy-mgmt-expand-city-popup";
|
||||||
const text = createElement("p", {
|
const text = createElement("p", {
|
||||||
innerText: "Would you like to expand into a new city by opening an office? " +
|
innerText: "Would you like to expand into a new city by opening an office? " +
|
||||||
@ -757,11 +810,12 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmBtn = createElement("a", {
|
const confirmBtn = createElement("button", {
|
||||||
class:"std-button",
|
class:"std-button",
|
||||||
display:"inline-block",
|
display:"inline-block",
|
||||||
innerText: "Confirm",
|
innerText: "Confirm",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
|
if (citySelector.length <= 0) { return false; }
|
||||||
let city = citySelector.options[citySelector.selectedIndex].value;
|
let city = citySelector.options[citySelector.selectedIndex].value;
|
||||||
if (this.corp.funds.lt(OfficeInitialCost)) {
|
if (this.corp.funds.lt(OfficeInitialCost)) {
|
||||||
dialogBoxCreate("You don't have enough company funds to open a new office!");
|
dialogBoxCreate("You don't have enough company funds to open a new office!");
|
||||||
@ -772,9 +826,11 @@ export class CorporationEventHandler {
|
|||||||
loc: city,
|
loc: city,
|
||||||
size: OfficeInitialSize,
|
size: OfficeInitialSize,
|
||||||
});
|
});
|
||||||
this.corp.displayDivisionContent(division, city);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cityStateSetter(city);
|
||||||
removeElementById(popupId);
|
removeElementById(popupId);
|
||||||
|
this.rerender();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -836,15 +892,17 @@ export class CorporationEventHandler {
|
|||||||
} else {
|
} else {
|
||||||
this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]);
|
this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]);
|
||||||
var newInd = new Industry({
|
var newInd = new Industry({
|
||||||
name:newDivisionName,
|
corp: this.corp,
|
||||||
type:ind,
|
name: newDivisionName,
|
||||||
|
type: ind,
|
||||||
});
|
});
|
||||||
this.corp.divisions.push(newInd);
|
this.corp.divisions.push(newInd);
|
||||||
// this.corp.updateUIHeaderTabs();
|
|
||||||
// this.corp.selectHeaderTab(headerTabs[headerTabs.length-2]);
|
// Set routing to the new division so that the UI automatically switches to it
|
||||||
|
this.routing.routeTo(newDivisionName);
|
||||||
|
|
||||||
removeElementById("cmpy-mgmt-expand-industry-popup");
|
removeElementById("cmpy-mgmt-expand-industry-popup");
|
||||||
this.rerender();
|
this.rerender();
|
||||||
// this.corp.displayDivisionContent(newInd, Locations.Sector12);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -855,14 +913,14 @@ export class CorporationEventHandler {
|
|||||||
innerText: "Cancel",
|
innerText: "Cancel",
|
||||||
});
|
});
|
||||||
|
|
||||||
//Make an object to keep track of what industries you're already in
|
// Make an object to keep track of what industries you're already in
|
||||||
const ownedIndustries = {};
|
const ownedIndustries = {};
|
||||||
for (let i = 0; i < this.corp.divisions.length; ++i) {
|
for (let i = 0; i < this.corp.divisions.length; ++i) {
|
||||||
ownedIndustries[this.corp.divisions[i].type] = true;
|
ownedIndustries[this.corp.divisions[i].type] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add industry types to selector
|
// Add industry types to selector
|
||||||
//Have Agriculture be first as recommended option
|
// Have Agriculture be first as recommended option
|
||||||
if (!ownedIndustries["Agriculture"]) {
|
if (!ownedIndustries["Agriculture"]) {
|
||||||
selector.add(createElement("option", {
|
selector.add(createElement("option", {
|
||||||
text:Industries["Agriculture"], value:"Agriculture"
|
text:Industries["Agriculture"], value:"Agriculture"
|
||||||
@ -904,8 +962,115 @@ export class CorporationEventHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a popup that lets the player use the Market TA research for Products
|
||||||
|
createProductMarketTaPopup(product, industry) {
|
||||||
|
const corp = this.corp;
|
||||||
|
|
||||||
|
const popupId = "cmpy-mgmt-marketta-popup";
|
||||||
|
const markupLimit = product.rat / product.mku;
|
||||||
|
const ta1 = createElement("p", {
|
||||||
|
innerHTML: "<u><strong>Market-TA.I</strong></u><br>" +
|
||||||
|
"The maximum sale price you can mark this up to is " +
|
||||||
|
numeralWrapper.formatMoney(product.pCost + markupLimit) +
|
||||||
|
". This means that if you set the sale price higher than this, " +
|
||||||
|
"you will begin to experience a loss in number of sales",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable using Market-TA1 for automatically setting sale price
|
||||||
|
const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox";
|
||||||
|
const useTa1AutoSaleDiv = createElement("div", { display: "block" });
|
||||||
|
const useTa1AutoSaleLabel = createElement("label", {
|
||||||
|
color: "white",
|
||||||
|
for: useTa1AutoSaleId,
|
||||||
|
innerText: "Use Market-TA.I for Auto-Sale Price",
|
||||||
|
tooltip: "If this is enabled, then this Product will automatically " +
|
||||||
|
"be sold at the price identified by Market-TA.I (i.e. the price shown above)"
|
||||||
|
})
|
||||||
|
const useTa1AutoSaleCheckbox = createElement("input", {
|
||||||
|
checked: product.marketTa1,
|
||||||
|
id: useTa1AutoSaleId,
|
||||||
|
margin: "3px",
|
||||||
|
type: "checkbox",
|
||||||
|
changeListener: (e) => {
|
||||||
|
product.marketTa1 = e.target.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
|
||||||
|
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
|
||||||
|
|
||||||
|
const closeBtn = createPopupCloseButton(popupId, {
|
||||||
|
class: "std-button",
|
||||||
|
display: "block",
|
||||||
|
innerText: "Close",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (industry.hasResearch("Market-TA.II")) {
|
||||||
|
let updateTa2Text;
|
||||||
|
const ta2Text = createElement("p");
|
||||||
|
const ta2Input = createElement("input", {
|
||||||
|
marginTop: "4px",
|
||||||
|
onkeyup: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
updateTa2Text();
|
||||||
|
},
|
||||||
|
type: "number",
|
||||||
|
value: product.pCost,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function that updates the text in ta2Text element
|
||||||
|
updateTa2Text = function() {
|
||||||
|
const sCost = parseFloat(ta2Input.value);
|
||||||
|
let markup = 1;
|
||||||
|
if (sCost > product.pCost) {
|
||||||
|
if ((sCost - product.pCost) > markupLimit) {
|
||||||
|
markup = markupLimit / (sCost - product.pCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
|
||||||
|
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
|
||||||
|
`then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
|
||||||
|
`to if you sold at market price.`;
|
||||||
|
}
|
||||||
|
updateTa2Text();
|
||||||
|
|
||||||
|
// Enable using Market-TA2 for automatically setting sale price
|
||||||
|
const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
|
||||||
|
const useTa2AutoSaleDiv = createElement("div", { display: "block" });
|
||||||
|
const useTa2AutoSaleLabel = createElement("label", {
|
||||||
|
color: "white",
|
||||||
|
for: useTa2AutoSaleId,
|
||||||
|
innerText: "Use Market-TA.II for Auto-Sale Price",
|
||||||
|
tooltip: "If this is enabled, then this Product will automatically " +
|
||||||
|
"be sold at the optimal price such that the amount sold matches the " +
|
||||||
|
"amount produced. (i.e. the highest possible price, while still ensuring " +
|
||||||
|
" that all produced materials will be sold)"
|
||||||
|
})
|
||||||
|
const useTa2AutoSaleCheckbox = createElement("input", {
|
||||||
|
checked: product.marketTa2,
|
||||||
|
id: useTa2AutoSaleId,
|
||||||
|
margin: "3px",
|
||||||
|
type: "checkbox",
|
||||||
|
changeListener: (e) => {
|
||||||
|
product.marketTa2 = e.target.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
|
||||||
|
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
|
||||||
|
|
||||||
|
const ta2OverridesTa1 = createElement("p", {
|
||||||
|
innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
|
||||||
|
"both are enabled, then Market-TA.II will take effect, not Market-TA.I"
|
||||||
|
});
|
||||||
|
|
||||||
|
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
|
||||||
|
} else {
|
||||||
|
// Market-TA.I only
|
||||||
|
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player purchase a Material
|
// Create a popup that lets the player purchase a Material
|
||||||
createPurchaseMaterialPopup(mat, industry) {
|
createPurchaseMaterialPopup(mat, industry, warehouse) {
|
||||||
const corp = this.corp;
|
const corp = this.corp;
|
||||||
|
|
||||||
const purchasePopupId = "cmpy-mgmt-material-purchase-popup";
|
const purchasePopupId = "cmpy-mgmt-material-purchase-popup";
|
||||||
@ -933,6 +1098,7 @@ export class CorporationEventHandler {
|
|||||||
mat.buy = parseFloat(input.value);
|
mat.buy = parseFloat(input.value);
|
||||||
if (isNaN(mat.buy)) {mat.buy = 0;}
|
if (isNaN(mat.buy)) {mat.buy = 0;}
|
||||||
removeElementById(purchasePopupId);
|
removeElementById(purchasePopupId);
|
||||||
|
this.rerender();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -942,6 +1108,7 @@ export class CorporationEventHandler {
|
|||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
mat.buy = 0;
|
mat.buy = 0;
|
||||||
removeElementById(purchasePopupId);
|
removeElementById(purchasePopupId);
|
||||||
|
this.rerender();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -961,15 +1128,20 @@ export class CorporationEventHandler {
|
|||||||
|
|
||||||
let bulkPurchaseCostTxt = createElement("p");
|
let bulkPurchaseCostTxt = createElement("p");
|
||||||
function updateBulkPurchaseText(amount) {
|
function updateBulkPurchaseText(amount) {
|
||||||
const cost = parseFloat(amount) * mat.bCost;
|
const parsedAmt = parseFloat(amount);
|
||||||
if (isNaN(cost)) {
|
const cost = parsedAmt * mat.bCost;
|
||||||
dialogBoxCreate(`Bulk Purchase Cost calculated to be NaN. This is either due to ` +
|
|
||||||
`invalid input, or it is a bug (in which case you should report to dev)`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` +
|
const matSize = MaterialSizes[mat.name];
|
||||||
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
|
const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
|
||||||
|
|
||||||
|
if (parsedAmt * matSize > maxAmount) {
|
||||||
|
bulkPurchaseCostTxt.innerText = "Not enough warehouse space to purchase this amount";
|
||||||
|
} else if (isNaN(cost)) {
|
||||||
|
bulkPurchaseCostTxt.innerText = "Invalid put for Bulk Purchase amount";
|
||||||
|
} else {
|
||||||
|
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(parsedAmt, "0,0.00")} of ` +
|
||||||
|
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bulkPurchaseConfirmBtn;
|
let bulkPurchaseConfirmBtn;
|
||||||
@ -979,7 +1151,7 @@ export class CorporationEventHandler {
|
|||||||
type: "number",
|
type: "number",
|
||||||
onkeyup: (e) => {
|
onkeyup: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
bulkPurchaseUpdateCostTxt();
|
updateBulkPurchaseText(e.target.value);
|
||||||
if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();}
|
if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -988,7 +1160,15 @@ export class CorporationEventHandler {
|
|||||||
class: "std-button",
|
class: "std-button",
|
||||||
innerText: "Confirm Bulk Purchase",
|
innerText: "Confirm Bulk Purchase",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
const amount = parseFloat(input.value);
|
const amount = parseFloat(bulkPurchaseInput.value);
|
||||||
|
|
||||||
|
const matSize = MaterialSizes[mat.name];
|
||||||
|
const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
|
||||||
|
if (amount * matSize > maxAmount) {
|
||||||
|
dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isNaN(amount)) {
|
if (isNaN(amount)) {
|
||||||
dialogBoxCreate("Invalid input amount");
|
dialogBoxCreate("Invalid input amount");
|
||||||
} else {
|
} else {
|
||||||
@ -1010,6 +1190,7 @@ export class CorporationEventHandler {
|
|||||||
elems.push(bulkPurchaseInfo);
|
elems.push(bulkPurchaseInfo);
|
||||||
elems.push(bulkPurchaseCostTxt);
|
elems.push(bulkPurchaseCostTxt);
|
||||||
elems.push(bulkPurchaseInput);
|
elems.push(bulkPurchaseInput);
|
||||||
|
elems.push(bulkPurchaseConfirmBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPopup(purchasePopupId, elems);
|
createPopup(purchasePopupId, elems);
|
||||||
@ -1045,9 +1226,18 @@ export class CorporationEventHandler {
|
|||||||
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let inputButtonInitValue = mat.sCost ? mat.sCost : null;
|
||||||
|
if (mat.marketTa2) {
|
||||||
|
inputButtonInitValue += " (Market-TA.II)";
|
||||||
|
} else if (mat.marketTa1) {
|
||||||
|
inputButtonInitValue += " (Market-TA.I)";
|
||||||
|
}
|
||||||
|
|
||||||
const inputPx = createElement("input", {
|
const inputPx = createElement("input", {
|
||||||
type: "text", marginTop: "4px",
|
type: "text", marginTop: "4px",
|
||||||
value: mat.sCost ? mat.sCost : null, placeholder: "Sell price",
|
value: inputButtonInitValue,
|
||||||
|
placeholder: "Sell price",
|
||||||
onkeyup: (e) => {
|
onkeyup: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
||||||
@ -1129,7 +1319,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a popup that lets the player manage sales of the product
|
// Create a popup that lets the player manage sales of the product
|
||||||
createSellProductPopup(product) {
|
createSellProductPopup(product, city) {
|
||||||
const popupId = "cmpy-mgmt-sell-product-popup";
|
const popupId = "cmpy-mgmt-sell-product-popup";
|
||||||
const txt = createElement("p", {
|
const txt = createElement("p", {
|
||||||
innerHTML:"Enter the maximum amount of " + product.name + " you would like " +
|
innerHTML:"Enter the maximum amount of " + product.name + " you would like " +
|
||||||
@ -1150,24 +1340,51 @@ export class CorporationEventHandler {
|
|||||||
});
|
});
|
||||||
let confirmBtn;
|
let confirmBtn;
|
||||||
const inputQty = createElement("input", {
|
const inputQty = createElement("input", {
|
||||||
|
margin: "5px 0px 5px 0px",
|
||||||
placeholder: "Sell amount",
|
placeholder: "Sell amount",
|
||||||
type: "text",
|
type: "text",
|
||||||
value:product.sllman[city][1] ? product.sllman[city][1] : null,
|
value: product.sllman[city][1] ? product.sllman[city][1] : null,
|
||||||
onkeyup: (e) => {
|
onkeyup: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let inputButtonInitValue = product.sCost ? product.sCost : null;
|
||||||
|
if (product.marketTa2) {
|
||||||
|
inputButtonInitValue += " (Market-TA.II)";
|
||||||
|
} else if (product.marketTa1) {
|
||||||
|
inputButtonInitValue += " (Market-TA.I)";
|
||||||
|
}
|
||||||
|
|
||||||
const inputPx = createElement("input", {
|
const inputPx = createElement("input", {
|
||||||
|
margin: "5px 0px 5px 0px",
|
||||||
placeholder: "Sell price",
|
placeholder: "Sell price",
|
||||||
type: "text",
|
type: "text",
|
||||||
value: product.sCost ? product.sCost : null,
|
value: inputButtonInitValue,
|
||||||
onkeyup: (e) => {
|
onkeyup: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmBtn = createElement("a", {
|
const checkboxDiv = createElement("div", {
|
||||||
|
border: "1px solid white",
|
||||||
|
display: "inline-block",
|
||||||
|
})
|
||||||
|
const checkboxLabel = createElement("label", {
|
||||||
|
for: popupId + "-checkbox",
|
||||||
|
innerText: "Use same 'Sell Amount' for all cities",
|
||||||
|
});
|
||||||
|
const checkbox = createElement("input", {
|
||||||
|
checked: true,
|
||||||
|
id: popupId + "-checkbox",
|
||||||
|
margin: "2px",
|
||||||
|
type: "checkbox",
|
||||||
|
});
|
||||||
|
checkboxDiv.appendChild(checkboxLabel);
|
||||||
|
checkboxDiv.appendChild(checkbox);
|
||||||
|
|
||||||
|
confirmBtn = createElement("button", {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
innerText: "Confirm",
|
innerText: "Confirm",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
@ -1198,7 +1415,10 @@ export class CorporationEventHandler {
|
|||||||
product.sCost = cost;
|
product.sCost = cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse quantity
|
// Array of all cities. Used later
|
||||||
|
const cities = Object.values(Cities);
|
||||||
|
|
||||||
|
// Parse quantity
|
||||||
if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {
|
if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {
|
||||||
//Dynamically evaluated quantity. First test to make sure its valid
|
//Dynamically evaluated quantity. First test to make sure its valid
|
||||||
var qty = inputQty.value.replace(/\s+/g, '');
|
var qty = inputQty.value.replace(/\s+/g, '');
|
||||||
@ -1216,8 +1436,16 @@ export class CorporationEventHandler {
|
|||||||
dialogBoxCreate("Invalid value or expression for sell price field");
|
dialogBoxCreate("Invalid value or expression for sell price field");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
product.sllman[city][0] = true;
|
if (checkbox.checked) {
|
||||||
product.sllman[city][1] = qty; //Use sanitized input
|
for (let i = 0; i < cities.length; ++i) {
|
||||||
|
const tempCity = cities[i];
|
||||||
|
product.sllman[tempCity][0] = true;
|
||||||
|
product.sllman[tempCity][1] = qty; //Use sanitized input
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
product.sllman[city][0] = true;
|
||||||
|
product.sllman[city][1] = qty; //Use sanitized input
|
||||||
|
}
|
||||||
} else if (isNaN(inputQty.value)) {
|
} else if (isNaN(inputQty.value)) {
|
||||||
dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
|
dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
|
||||||
return false;
|
return false;
|
||||||
@ -1225,10 +1453,25 @@ export class CorporationEventHandler {
|
|||||||
var qty = parseFloat(inputQty.value);
|
var qty = parseFloat(inputQty.value);
|
||||||
if (isNaN(qty)) {qty = 0;}
|
if (isNaN(qty)) {qty = 0;}
|
||||||
if (qty === 0) {
|
if (qty === 0) {
|
||||||
product.sllman[city][0] = false;
|
if (checkbox.checked) {
|
||||||
|
for (let i = 0; i < cities.length; ++i) {
|
||||||
|
const tempCity = cities[i];
|
||||||
|
product.sllman[tempCity][0] = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
product.sllman[city][0] = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
product.sllman[city][0] = true;
|
if (checkbox.checked) {
|
||||||
product.sllman[city][1] = qty;
|
for (let i = 0; i < cities.length; ++i) {
|
||||||
|
const tempCity = cities[i];
|
||||||
|
product.sllman[tempCity][0] = true;
|
||||||
|
product.sllman[tempCity][1] = qty;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
product.sllman[city][0] = true;
|
||||||
|
product.sllman[city][1] = qty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1237,9 +1480,12 @@ export class CorporationEventHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
|
const cancelBtn = createPopupCloseButton(popupId, { class: "std-button" });
|
||||||
|
|
||||||
createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]);
|
const linebreak1 = createElement("br");
|
||||||
|
|
||||||
|
createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn, linebreak1,
|
||||||
|
checkboxDiv]);
|
||||||
inputQty.focus();
|
inputQty.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1276,7 +1522,7 @@ export class CorporationEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const confirmBtn = createElement("a", {
|
const confirmBtn = createElement("button", {
|
||||||
class:"a-link-button", innerText:"Sell shares", display:"inline-block",
|
class:"a-link-button", innerText:"Sell shares", display:"inline-block",
|
||||||
clickListener:()=>{
|
clickListener:()=>{
|
||||||
var shares = Math.round(input.value);
|
var shares = Math.round(input.value);
|
||||||
@ -1355,7 +1601,7 @@ export class CorporationEventHandler {
|
|||||||
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmBtn = createElement("a", {
|
confirmBtn = createElement("button", {
|
||||||
class: "std-button",
|
class: "std-button",
|
||||||
innerText: "Throw Party",
|
innerText: "Throw Party",
|
||||||
clickListener:()=>{
|
clickListener:()=>{
|
||||||
@ -1366,20 +1612,20 @@ export class CorporationEventHandler {
|
|||||||
if (this.corp.funds.lt(totalCost)) {
|
if (this.corp.funds.lt(totalCost)) {
|
||||||
dialogBoxCreate("You don't have enough company funds to throw this.corp party!");
|
dialogBoxCreate("You don't have enough company funds to throw this.corp party!");
|
||||||
} else {
|
} else {
|
||||||
this.corp.funds = this.funds.minus(totalCost);
|
this.corp.funds = this.corp.funds.minus(totalCost);
|
||||||
var mult;
|
var mult;
|
||||||
for (let fooit = 0; fooit < office.employees.length; ++fooit) {
|
for (let fooit = 0; fooit < office.employees.length; ++fooit) {
|
||||||
mult = office.employees[fooit].throwParty(input.value);
|
mult = office.employees[fooit].throwParty(input.value);
|
||||||
}
|
}
|
||||||
dialogBoxCreate("You threw a party for the office! The morale and happiness " +
|
dialogBoxCreate("You threw a party for the office! The morale and happiness " +
|
||||||
"of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%.");
|
"of each employee increased by " + numeralWrapper.formatPercentage((mult-1)));
|
||||||
removeElementById(popupId);
|
removeElementById(popupId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
|
const cancelBtn = createPopupCloseButton(popupId, { class: "std-button", innerText: "Cancel" });
|
||||||
|
|
||||||
createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]);
|
createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]);
|
||||||
input.focus();
|
input.focus();
|
||||||
@ -1420,7 +1666,7 @@ export class CorporationEventHandler {
|
|||||||
});
|
});
|
||||||
const text2 = createElement("p", { innerText: "Upgrade size: " });
|
const text2 = createElement("p", { innerText: "Upgrade size: " });
|
||||||
|
|
||||||
const confirmBtn = createElement("a", {
|
const confirmBtn = createElement("button", {
|
||||||
class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button",
|
class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button",
|
||||||
display:"inline-block", margin:"4px", innerText:"by 3",
|
display:"inline-block", margin:"4px", innerText:"by 3",
|
||||||
tooltip:numeralWrapper.format(upgradeCost, "$0.000a"),
|
tooltip:numeralWrapper.format(upgradeCost, "$0.000a"),
|
||||||
@ -1437,7 +1683,7 @@ export class CorporationEventHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const confirmBtn15 = createElement("a", {
|
const confirmBtn15 = createElement("button", {
|
||||||
class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button",
|
class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button",
|
||||||
display:"inline-block", margin:"4px", innerText:"by 15",
|
display:"inline-block", margin:"4px", innerText:"by 15",
|
||||||
tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"),
|
tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"),
|
||||||
@ -1454,7 +1700,7 @@ export class CorporationEventHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const confirmBtnMax = createElement("a", {
|
const confirmBtnMax = createElement("button", {
|
||||||
class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button",
|
class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button",
|
||||||
display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")",
|
display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")",
|
||||||
tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"),
|
tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"),
|
||||||
@ -1484,6 +1730,8 @@ export class CorporationEventHandler {
|
|||||||
dialogBoxCreate("You do not have enough funds to do this!");
|
dialogBoxCreate("You do not have enough funds to do this!");
|
||||||
} else {
|
} else {
|
||||||
division.warehouses[city] = new Warehouse({
|
division.warehouses[city] = new Warehouse({
|
||||||
|
corp: corp,
|
||||||
|
industry: division,
|
||||||
loc: city,
|
loc: city,
|
||||||
size: WarehouseInitialSize,
|
size: WarehouseInitialSize,
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,8 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
city: "",
|
||||||
|
division: "",
|
||||||
employeeManualAssignMode: false,
|
employeeManualAssignMode: false,
|
||||||
employee: null, // Reference to employee being referenced if in Manual Mode
|
employee: null, // Reference to employee being referenced if in Manual Mode
|
||||||
numEmployees: 0,
|
numEmployees: 0,
|
||||||
@ -30,6 +32,17 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
this.updateEmployeeCount(); // This function validates division and office refs
|
this.updateEmployeeCount(); // This function validates division and office refs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetEmployeeCount() {
|
||||||
|
this.state.numEmployees = 0;
|
||||||
|
this.state.numOperations = 0;
|
||||||
|
this.state.numEngineers = 0;
|
||||||
|
this.state.numBusiness = 0;
|
||||||
|
this.state.numManagement = 0;
|
||||||
|
this.state.numResearch = 0;
|
||||||
|
this.state.numUnassigned = 0;
|
||||||
|
this.state.numTraining = 0;
|
||||||
|
}
|
||||||
|
|
||||||
updateEmployeeCount() {
|
updateEmployeeCount() {
|
||||||
const division = this.routing().currentDivision;
|
const division = this.routing().currentDivision;
|
||||||
if (division == null) {
|
if (division == null) {
|
||||||
@ -40,6 +53,13 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
|
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're in a new city, we have to reset the state
|
||||||
|
if (division.name !== this.state.division || this.props.currentCity !== this.state.city) {
|
||||||
|
this.resetEmployeeCount();
|
||||||
|
this.state.division = division.name;
|
||||||
|
this.state.city = this.props.currentCity;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate how many NEW emplyoees we need to account for
|
// Calculate how many NEW emplyoees we need to account for
|
||||||
const currentNumEmployees = office.employees.length;
|
const currentNumEmployees = office.employees.length;
|
||||||
const newEmployees = currentNumEmployees - this.state.numEmployees;
|
const newEmployees = currentNumEmployees - this.state.numEmployees;
|
||||||
@ -151,6 +171,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
--this.state.numUnassigned;
|
--this.state.numUnassigned;
|
||||||
|
|
||||||
office.assignEmployeeToJob(to);
|
office.assignEmployeeToJob(to);
|
||||||
|
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
||||||
this.corp().rerender();
|
this.corp().rerender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +215,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
++this.state.numUnassigned;
|
++this.state.numUnassigned;
|
||||||
|
|
||||||
office.unassignEmployeeFromJob(from);
|
office.unassignEmployeeFromJob(from);
|
||||||
|
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
||||||
this.corp().rerender();
|
this.corp().rerender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,41 +300,54 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
|
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
|
||||||
<p>Avg Happiness Morale: {numeralWrapper.format(avgHappiness, "0.000")}</p>
|
<p>Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}</p>
|
||||||
<p>Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}</p>
|
<p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
|
||||||
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
|
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
|
||||||
{
|
{
|
||||||
vechain &&
|
vechain &&
|
||||||
<div>
|
<p className={"tooltip"} style={{display: "inline-block"}}>
|
||||||
<p className={"tooltip"}>
|
|
||||||
Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")}
|
Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
The base amount of material this office can produce. Does not include
|
The base amount of material this office can produce. Does not include
|
||||||
production multipliers from upgrades and materials. This value is based off
|
production multipliers from upgrades and materials. This value is based off
|
||||||
the productivity of your Operations, Engineering, and Management employees
|
the productivity of your Operations, Engineering, and Management employees
|
||||||
</span>
|
</span>
|
||||||
</p><br />
|
</p>
|
||||||
<p className={"tooltip"}>
|
}
|
||||||
|
{
|
||||||
|
vechain && <br />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
vechain &&
|
||||||
|
<p className={"tooltip"} style={{display: "inline-block"}}>
|
||||||
Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")}
|
Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
The base amount of any given Product this office can produce. Does not include
|
The base amount of any given Product this office can produce. Does not include
|
||||||
production multipliers from upgrades and materials. This value is based off
|
production multipliers from upgrades and materials. This value is based off
|
||||||
the productivity of your Operations, Engineering, and Management employees
|
the productivity of your Operations, Engineering, and Management employees
|
||||||
</span>
|
</span>
|
||||||
</p><br />
|
</p>
|
||||||
<p className={"tooltip"}>
|
}
|
||||||
Business Multiplier: x" ${numeralWrapper.format(division.getBusinessFactor(office), "0.000")}
|
{
|
||||||
|
vechain && <br />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
vechain &&
|
||||||
|
<p className={"tooltip"} style={{display: "inline-block"}}>
|
||||||
|
Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
The effect this office's 'Business' employees has on boosting sales
|
The effect this office's 'Business' employees has on boosting sales
|
||||||
</span>
|
</span>
|
||||||
</p><br />
|
</p>
|
||||||
</div>
|
}
|
||||||
|
{
|
||||||
|
vechain && <br />
|
||||||
}
|
}
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Operations} ({this.state.numOperations})
|
{EmployeePositions.Operations} ({this.state.numOperations})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Manages supply chain operations. Improves production.
|
Manages supply chain operations. Improves the amount of Materials and Products you produce.
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<button className={assignButtonClass} onClick={operationAssignButtonOnClick}>+</button>
|
<button className={assignButtonClass} onClick={operationAssignButtonOnClick}>+</button>
|
||||||
@ -322,7 +357,9 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Engineer} ({this.state.numEngineers})
|
{EmployeePositions.Engineer} ({this.state.numEngineers})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Develops and maintains products and production systems. Improves production.
|
Develops and maintains products and production systems. Increases the quality of
|
||||||
|
everything you produce. Also increases the amount you produce (not as much
|
||||||
|
as Operations, however)
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<button className={assignButtonClass} onClick={engineerAssignButtonOnClick}>+</button>
|
<button className={assignButtonClass} onClick={engineerAssignButtonOnClick}>+</button>
|
||||||
@ -332,7 +369,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Business} ({this.state.numBusiness})
|
{EmployeePositions.Business} ({this.state.numBusiness})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Handles sales and finances. Improves sales.
|
Handles sales and finances. Improves the amount of Materials and Products you can sell.
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<button className={assignButtonClass} onClick={businessAssignButtonOnClick}>+</button>
|
<button className={assignButtonClass} onClick={businessAssignButtonOnClick}>+</button>
|
||||||
@ -342,7 +379,8 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Management} ({this.state.numManagement})
|
{EmployeePositions.Management} ({this.state.numManagement})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Leads and oversees employees and office operations. Improves production.
|
Leads and oversees employees and office operations. Improves the effectiveness of
|
||||||
|
Engineer and Operations employees
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<button className={assignButtonClass} onClick={managementAssignButtonOnClick}>+</button>
|
<button className={assignButtonClass} onClick={managementAssignButtonOnClick}>+</button>
|
||||||
@ -398,27 +436,36 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
for (let i = 0; i < office.employees.length; ++i) {
|
for (let i = 0; i < office.employees.length; ++i) {
|
||||||
if (name === office.employees[i].name) {
|
if (name === office.employees[i].name) {
|
||||||
this.state.employee = office.employees[i];
|
this.state.employee = office.employees[i];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
corp.rerender();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Employee Positions Selector
|
// Employee Positions Selector
|
||||||
|
const emp = this.state.employee;
|
||||||
|
let employeePositionSelectorInitialValue = null;
|
||||||
const employeePositions = [];
|
const employeePositions = [];
|
||||||
const positionNames = Object.values(EmployeePositions);
|
const positionNames = Object.values(EmployeePositions);
|
||||||
for (let i = 0; i < positionNames.length; ++i) {
|
for (let i = 0; i < positionNames.length; ++i) {
|
||||||
employeePositions.push(<option key={positionNames[i]}>{positionNames[i]}</option>);
|
employeePositions.push(<option key={positionNames[i]} value={positionNames[i]}>{positionNames[i]}</option>);
|
||||||
|
if (emp != null && emp.pos === positionNames[i]) {
|
||||||
|
employeePositionSelectorInitialValue = positionNames[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const employeePositionSelectorOnChange = (e) => {
|
const employeePositionSelectorOnChange = (e) => {
|
||||||
const pos = getSelectText(e.target);
|
const pos = getSelectText(e.target);
|
||||||
this.state.employee.pos = pos;
|
this.state.employee.pos = pos;
|
||||||
|
this.resetEmployeeCount();
|
||||||
|
corp.rerender();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeraljs formatter
|
// Numeraljs formatter
|
||||||
const nf = "0.000";
|
const nf = "0.000";
|
||||||
|
|
||||||
// Employee stats (after applying multipliers)
|
// Employee stats (after applying multipliers)
|
||||||
const emp = this.state.employee;
|
|
||||||
const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0;
|
const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0;
|
||||||
const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0;
|
const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0;
|
||||||
const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0;
|
const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0;
|
||||||
@ -436,6 +483,9 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div style={employeeInfoDivStyle}>
|
<div style={employeeInfoDivStyle}>
|
||||||
|
<select onChange={employeeSelectorOnChange}>
|
||||||
|
{employees}
|
||||||
|
</select>
|
||||||
{
|
{
|
||||||
this.state.employee != null &&
|
this.state.employee != null &&
|
||||||
<p>
|
<p>
|
||||||
@ -462,15 +512,11 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.employee != null &&
|
this.state.employee != null &&
|
||||||
<select onChange={employeePositionSelectorOnChange}>
|
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
|
||||||
{employeePositions}
|
{employeePositions}
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select onChange={employeeSelectorOnChange}>
|
|
||||||
{employees}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -495,7 +541,6 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const hireEmployeeButtonOnClick = () => {
|
const hireEmployeeButtonOnClick = () => {
|
||||||
office.findEmployees({ corporation: corp, industry: division });
|
office.findEmployees({ corporation: corp, industry: division });
|
||||||
}
|
}
|
||||||
@ -509,7 +554,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
const autohireEmployeeButtonOnClick = () => {
|
const autohireEmployeeButtonOnClick = () => {
|
||||||
if (office.atCapacity()) { return; }
|
if (office.atCapacity()) { return; }
|
||||||
office.hireRandomEmployee({ corporation: corp, industry: division });
|
office.hireRandomEmployee();
|
||||||
this.corp().rerender();
|
this.corp().rerender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { Industries } from "../IndustryData";
|
|||||||
import { IndustryUpgrades } from "../IndustryUpgrades";
|
import { IndustryUpgrades } from "../IndustryUpgrades";
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||||
|
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||||
|
|
||||||
export class IndustryOverview extends BaseReactComponent {
|
export class IndustryOverview extends BaseReactComponent {
|
||||||
renderMakeProductButton() {
|
renderMakeProductButton() {
|
||||||
@ -109,6 +110,16 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
|
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
|
||||||
|
|
||||||
const productionMultHelpTipOnClick = () => {
|
const productionMultHelpTipOnClick = () => {
|
||||||
|
// Wrapper for createProgressBarText()
|
||||||
|
// Converts the industry's "effectiveness factors"
|
||||||
|
// into a graphic (string) depicting how high that effectiveness is
|
||||||
|
function convertEffectFacToGraphic(fac) {
|
||||||
|
return createProgressBarText({
|
||||||
|
progress: fac,
|
||||||
|
totalTicks: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " +
|
dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " +
|
||||||
"can boost your Industry's production. The effect these " +
|
"can boost your Industry's production. The effect these " +
|
||||||
"materials have on your production varies between Industries. " +
|
"materials have on your production varies between Industries. " +
|
||||||
@ -118,7 +129,13 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
"the individual production multiplier of each of its office locations. " +
|
"the individual production multiplier of each of its office locations. " +
|
||||||
"This production multiplier is applied to each office. Therefore, it is " +
|
"This production multiplier is applied to each office. Therefore, it is " +
|
||||||
"beneficial to expand into new cities as this can greatly increase the " +
|
"beneficial to expand into new cities as this can greatly increase the " +
|
||||||
"production multiplier of your entire Division.");
|
"production multiplier of your entire Division.<br><br>" +
|
||||||
|
"Below are approximations for how effective each material is at boosting " +
|
||||||
|
"this industry's production multiplier (Bigger bars = more effective):<br><br>" +
|
||||||
|
`Hardware: ${convertEffectFacToGraphic(division.hwFac)}<br>` +
|
||||||
|
`Robots: ${convertEffectFacToGraphic(division.robFac)}<br>` +
|
||||||
|
`AI Cores: ${convertEffectFacToGraphic(division.aiFac)}<br>` +
|
||||||
|
`Real Estate: ${convertEffectFacToGraphic(division.reFac)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -129,15 +146,15 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
{popularity} <br />
|
{popularity} <br />
|
||||||
{
|
{
|
||||||
(advertisingInfo !== false) &&
|
(advertisingInfo !== false) &&
|
||||||
<p className={"tooltip"}>Advertising Multiplier: {numeralWrapper.format(totalAdvertisingFac, "0.000")}
|
<p className={"tooltip"}>Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")}
|
||||||
<span className={"tooltiptext cmpy-mgmt-advertising-info"}>
|
<span className={"tooltiptext cmpy-mgmt-advertising-info"}>
|
||||||
Total multiplier for this industrys sales due to its awareness and popularity
|
Total multiplier for this industrys sales due to its awareness and popularity
|
||||||
<br />
|
<br />
|
||||||
Awareness Bonus: x{formatNumber(Math.pow(awarenessFac, 0.85), 3)}
|
Awareness Bonus: x{numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")}
|
||||||
<br />
|
<br />
|
||||||
Popularity Bonus: x{formatNumber(Math.pow(popularityFac, 0.85), 3)}
|
Popularity Bonus: x{numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")}
|
||||||
<br />
|
<br />
|
||||||
Ratio Multiplier: x{formatNumber(Math.pow(ratioFac, 0.85), 3)}
|
Ratio Multiplier: x{numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
@ -157,7 +174,7 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
<div className={"help-tip"} onClick={productionMultHelpTipOnClick}>?</div>
|
<div className={"help-tip"} onClick={productionMultHelpTipOnClick}>?</div>
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000")}
|
Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Scientific Research increases the quality of the materials and
|
Scientific Research increases the quality of the materials and
|
||||||
products that you produce.
|
products that you produce.
|
||||||
@ -252,7 +269,7 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
|
|
||||||
{
|
{
|
||||||
division.makesProducts &&
|
division.makesProducts &&
|
||||||
{makeProductButton}
|
makeProductButton
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,63 +1,83 @@
|
|||||||
// React Component for displaying an Industry's warehouse information
|
// React Component for displaying an Industry's warehouse information
|
||||||
// (right-side panel in the Industry UI)
|
// (right-side panel in the Industry UI)
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { BaseReactComponent } from "./BaseReactComponent";
|
import { BaseReactComponent } from "./BaseReactComponent";
|
||||||
|
|
||||||
import { Material } from "../Material";
|
import { OfficeSpace,
|
||||||
import { Product } from "../Product";
|
|
||||||
|
|
||||||
import { Warehouse,
|
|
||||||
WarehouseInitialCost,
|
WarehouseInitialCost,
|
||||||
WarehouseUpgradeBaseCost } from "../Corporation";
|
WarehouseUpgradeBaseCost,
|
||||||
|
ProductProductionCostRatio } from "../Corporation";
|
||||||
|
import { Material } from "../Material";
|
||||||
|
import { Product } from "../Product";
|
||||||
|
import { Warehouse } from "../Warehouse";
|
||||||
|
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
import { isString } from "../../../utils/helpers/isString";
|
import { isString } from "../../../utils/helpers/isString";
|
||||||
|
|
||||||
// Creates the UI for a single Product type
|
// Creates the UI for a single Product type
|
||||||
function ProductComponent(props) {
|
function ProductComponent(props) {
|
||||||
const corp = props.corp;
|
const corp = props.corp;
|
||||||
const division = props.division;
|
const division = props.division;
|
||||||
const warehouse = props.warehouse;
|
const warehouse = props.warehouse;
|
||||||
|
const city = props.city;
|
||||||
const product = props.product;
|
const product = props.product;
|
||||||
const eventHandler = props.eventHandler;
|
const eventHandler = props.eventHandler;
|
||||||
|
|
||||||
const nf = "0.000"; // Numeraljs formatter
|
// Numeraljs formatters
|
||||||
|
const nf = "0.000";
|
||||||
|
const nfB = "0.000a"; // For numbers that might be big
|
||||||
|
|
||||||
const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard");
|
const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard");
|
||||||
|
|
||||||
// Total product gain = production - sale
|
// Total product gain = production - sale
|
||||||
const totalGain = totalGain = product.data[city][1] - product.data[city][2];
|
const totalGain = product.data[city][1] - product.data[city][2];
|
||||||
|
|
||||||
// Sell button
|
// Sell button
|
||||||
const sellButtonText = product.sllman[city][1] === -1
|
let sellButtonText;
|
||||||
? "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/MAX)"
|
if (product.sllman[city][0]) {
|
||||||
: "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/" + numeralWrapper.format(product.sllman[city][1], nf) + ")";
|
if (isString(product.sllman[city][1])) {
|
||||||
if (product.sCost) {
|
sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${product.sllman[city][1]})`;
|
||||||
|
} else {
|
||||||
|
sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${numeralWrapper.format(product.sllman[city][1], nfB)})`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sellButtonText = "Sell (0.000/0.000)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product.marketTa2) {
|
||||||
|
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.marketTa2Price[city]));
|
||||||
|
} else if (product.marketTa1) {
|
||||||
|
const markupLimit = product.rat / product.mku;
|
||||||
|
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.pCost + markupLimit));
|
||||||
|
} else if (product.sCost) {
|
||||||
if (isString(product.sCost)) {
|
if (isString(product.sCost)) {
|
||||||
sellButtonText += (" @ " + product.sCost);
|
sellButtonText += (" @ " + product.sCost);
|
||||||
} else {
|
} else {
|
||||||
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
|
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product);
|
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
|
||||||
|
|
||||||
// Limit Production button
|
// Limit Production button
|
||||||
const limitProductionButtonText = "Limit Production";
|
let limitProductionButtonText = "Limit Production";
|
||||||
if (product.prdman[city][0]) {
|
if (product.prdman[city][0]) {
|
||||||
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
|
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
|
||||||
}
|
}
|
||||||
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product);
|
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
|
||||||
|
|
||||||
// Discontinue Button
|
// Discontinue Button
|
||||||
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product);
|
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
|
||||||
|
|
||||||
|
// Market TA button
|
||||||
|
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
|
||||||
|
|
||||||
// Unfinished Product
|
// Unfinished Product
|
||||||
if (!product.fin) {
|
if (!product.fin) {
|
||||||
if (hasUpgradeDashboard) {
|
if (hasUpgradeDashboard) {
|
||||||
return (
|
return (
|
||||||
<div className={"cmpy-mgmt-warehouse-product-div"}>
|
<div className={"cmpy-mgmt-warehouse-product-div"}>
|
||||||
<p>Designing {product.name}...</p>
|
<p>Designing {product.name}...</p><br />
|
||||||
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
|
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
@ -71,13 +91,19 @@ function ProductComponent(props) {
|
|||||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
||||||
Discontinue
|
Discontinue
|
||||||
</button>
|
</button>
|
||||||
|
{
|
||||||
|
division.hasResearch("Market-TA.I") &&
|
||||||
|
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||||
|
Market-TA
|
||||||
|
</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className={"cmpy-mgmt-warehouse-product-div"}>
|
<div className={"cmpy-mgmt-warehouse-product-div"}>
|
||||||
<p>Designing {product.name}...</p>
|
<p>Designing {product.name}...</p><br />
|
||||||
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
|
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -85,15 +111,15 @@ function ProductComponent(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"cmpy-mgmt-warehouse-product-div"} key={props.key}>
|
<div className={"cmpy-mgmt-warehouse-product-div"}>
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
{product.name}: {numeralWrapper.format(product.data[city][0], nf)} ({numeralWrapper.format(totalGain, nf)}/s)
|
{product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Prod: {numeralWrapper.format(product.data[city][1], nf)}/s
|
Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s
|
||||||
<br />
|
<br />
|
||||||
Sell: {numeralWrapper.format(product.data[city][2], nf)} /s
|
Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p><br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Rating: {numeralWrapper.format(product.rat, nf)}
|
Rating: {numeralWrapper.format(product.rat, nf)}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
@ -119,15 +145,15 @@ function ProductComponent(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p><br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
|
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
An estimate of the material cost it takes to create this Product.
|
An estimate of the material cost it takes to create this Product.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p><br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)}
|
Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
An estimate of how much consumers are willing to pay for this product.
|
An estimate of how much consumers are willing to pay for this product.
|
||||||
Setting the sale price above this may result in less sales. Setting the sale price below this may result
|
Setting the sale price above this may result in less sales. Setting the sale price below this may result
|
||||||
@ -145,6 +171,12 @@ function ProductComponent(props) {
|
|||||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
||||||
Discontinue
|
Discontinue
|
||||||
</button>
|
</button>
|
||||||
|
{
|
||||||
|
division.hasResearch("Market-TA.I") &&
|
||||||
|
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||||
|
Market-TA
|
||||||
|
</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -155,12 +187,18 @@ function MaterialComponent(props) {
|
|||||||
const corp = props.corp;
|
const corp = props.corp;
|
||||||
const division = props.division;
|
const division = props.division;
|
||||||
const warehouse = props.warehouse;
|
const warehouse = props.warehouse;
|
||||||
|
const city = props.city;
|
||||||
const mat = props.mat;
|
const mat = props.mat;
|
||||||
const eventHandler = props.eventHandler;
|
const eventHandler = props.eventHandler;
|
||||||
const markupLimit = mat.getMarkupLimit();
|
const markupLimit = mat.getMarkupLimit();
|
||||||
|
const office = division.offices[city];
|
||||||
|
if (!(office instanceof OfficeSpace)) {
|
||||||
|
throw new Error(`Could not get OfficeSpace object for this city (${city})`);
|
||||||
|
}
|
||||||
|
|
||||||
// Numeraljs formatter
|
// Numeraljs formatter
|
||||||
const nf = "0.000";
|
const nf = "0.000";
|
||||||
|
const nfB = "0.000a"; // For numbers that might be biger
|
||||||
|
|
||||||
// Total gain or loss of this material (per second)
|
// Total gain or loss of this material (per second)
|
||||||
const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;
|
const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;
|
||||||
@ -180,9 +218,9 @@ function MaterialComponent(props) {
|
|||||||
mat.buy === 0 && mat.imp === 0;
|
mat.buy === 0 && mat.imp === 0;
|
||||||
|
|
||||||
// Purchase material button
|
// Purchase material button
|
||||||
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`;
|
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
|
||||||
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
||||||
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division);
|
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
|
||||||
|
|
||||||
// Export material button
|
// Export material button
|
||||||
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
|
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
|
||||||
@ -190,12 +228,18 @@ function MaterialComponent(props) {
|
|||||||
// Sell material button
|
// Sell material button
|
||||||
let sellButtonText;
|
let sellButtonText;
|
||||||
if (mat.sllman[0]) {
|
if (mat.sllman[0]) {
|
||||||
sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" :
|
if (isString(mat.sllman[1])) {
|
||||||
"Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")");
|
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
|
||||||
if (mat.sCost) {
|
} else {
|
||||||
if (mat.marketTa1) {
|
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`;
|
||||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
}
|
||||||
} else if (isString(mat.sCost)) {
|
|
||||||
|
if (mat.marketTa2) {
|
||||||
|
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.marketTa2Price);
|
||||||
|
} else if (mat.marketTa1) {
|
||||||
|
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
||||||
|
} else if (mat.sCost) {
|
||||||
|
if (isString(mat.sCost)) {
|
||||||
var sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
var sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
||||||
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
|
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
|
||||||
} else {
|
} else {
|
||||||
@ -208,19 +252,19 @@ function MaterialComponent(props) {
|
|||||||
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
|
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
|
||||||
|
|
||||||
// Market TA button
|
// Market TA button
|
||||||
const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division);
|
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"cmpy-mgmt-warehouse-material-div"} key={props.key}>
|
<div className={"cmpy-mgmt-warehouse-material-div"}>
|
||||||
<div style={{display: "inline-block"}}>
|
<div style={{display: "inline-block"}}>
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
{mat.name}: {numeralWrapper.format(mat.qty, nf)} ({numeralWrapper.format(totalGain, nf)}/s)
|
{mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Buy: {numeralWrapper.format(mat.buy, nf)} <br />
|
Buy: {numeralWrapper.format(mat.buy, nfB)} <br />
|
||||||
Prod: {numeralWrapper.format(mat.prd, nf)} <br />
|
Prod: {numeralWrapper.format(mat.prd, nfB)} <br />
|
||||||
Sell: {numeralWrapper.format(mat.sll, nf)} <br />
|
Sell: {numeralWrapper.format(mat.sll, nfB)} <br />
|
||||||
Export: {numeralWrapper.format(mat.totalExp, nf)} <br />
|
Export: {numeralWrapper.format(mat.totalExp, nfB)} <br />
|
||||||
Import: {numeralWrapper.format(mat.imp, nf)}
|
Import: {numeralWrapper.format(mat.imp, nfB)}
|
||||||
{
|
{
|
||||||
corp.unlockUpgrades[2] === 1 && <br />
|
corp.unlockUpgrades[2] === 1 && <br />
|
||||||
}
|
}
|
||||||
@ -244,7 +288,7 @@ function MaterialComponent(props) {
|
|||||||
</span>
|
</span>
|
||||||
</p> <br />
|
</p> <br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Quality: {numeralWrapper.format(mat.qlt, "0.00")}
|
Quality: {numeralWrapper.format(mat.qlt, "0.00a")}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
The quality of your material. Higher quality will lead to more sales
|
The quality of your material. Higher quality will lead to more sales
|
||||||
</span>
|
</span>
|
||||||
@ -287,6 +331,19 @@ function MaterialComponent(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class IndustryWarehouse extends BaseReactComponent {
|
export class IndustryWarehouse extends BaseReactComponent {
|
||||||
|
// Returns a boolean indicating whether the given material is relevant for the
|
||||||
|
// current industry.
|
||||||
|
isRelevantMaterial(matName, division) {
|
||||||
|
// Materials that affect Production multiplier
|
||||||
|
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
|
||||||
|
|
||||||
|
if (Object.keys(division.reqMats).includes(matName)) { return true; }
|
||||||
|
if (division.prodMats.includes(matName)) { return true; }
|
||||||
|
if (prodMultiplierMats.includes(matName)) { return true; }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
renderWarehouseUI() {
|
renderWarehouseUI() {
|
||||||
const corp = this.corp();
|
const corp = this.corp();
|
||||||
const division = this.routing().currentDivision; // Validated in render()
|
const division = this.routing().currentDivision; // Validated in render()
|
||||||
@ -370,20 +427,8 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
// Smart Supply Checkbox
|
// Smart Supply Checkbox
|
||||||
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
||||||
const smartSupplyOnChange = (e) => {
|
const smartSupplyOnChange = (e) => {
|
||||||
warehouse.smartSupplyEnabled = e.target.value;
|
warehouse.smartSupplyEnabled = e.target.checked;
|
||||||
}
|
corp.rerender();
|
||||||
|
|
||||||
// Materials that affect Production multiplier
|
|
||||||
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
|
|
||||||
|
|
||||||
// Returns a boolean indicating whether the given material is relevant for the
|
|
||||||
// current industry.
|
|
||||||
function isRelevantMaterial(matName) {
|
|
||||||
if (Object.keys(division.reqMats).includes(matName)) { return true; }
|
|
||||||
if (division.prodMats.includes(matName)) { return true; }
|
|
||||||
if (prodMultiplierMats.includes(matName)) { return true; }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create React components for materials
|
// Create React components for materials
|
||||||
@ -391,15 +436,15 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
for (const matName in warehouse.materials) {
|
for (const matName in warehouse.materials) {
|
||||||
if (warehouse.materials[matName] instanceof Material) {
|
if (warehouse.materials[matName] instanceof Material) {
|
||||||
// Only create UI for materials that are relevant for the industry
|
// Only create UI for materials that are relevant for the industry
|
||||||
if (isRelevantMaterial(matName)) {
|
if (this.isRelevantMaterial(matName, division)) {
|
||||||
mats.push(MaterialComponent({
|
mats.push(<MaterialComponent
|
||||||
corp: corp,
|
city={this.props.currentCity}
|
||||||
division: division,
|
corp={corp}
|
||||||
eventHandler: this.eventHandler(),
|
division={division}
|
||||||
key: matName,
|
eventHandler={this.eventHandler()}
|
||||||
mat: warehouse.materials[matName],
|
key={matName}
|
||||||
warehouse: warehouse,
|
mat={warehouse.materials[matName]}
|
||||||
}));
|
warehouse={warehouse} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,14 +454,14 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
||||||
for (const productName in division.products) {
|
for (const productName in division.products) {
|
||||||
if (division.products[productName] instanceof Product) {
|
if (division.products[productName] instanceof Product) {
|
||||||
products.push({
|
products.push(<ProductComponent
|
||||||
corp: corp,
|
city={this.props.currentCity}
|
||||||
division: division,
|
corp={corp}
|
||||||
eventHandler: this.eventHandler(),
|
division={division}
|
||||||
key: productName,
|
eventHandler={this.eventHandler()}
|
||||||
product: division.products[productName],
|
key={productName}
|
||||||
warehouse: warehouse,
|
product={division.products[productName]}
|
||||||
})
|
warehouse={warehouse} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,10 +469,8 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
return (
|
return (
|
||||||
<div className={"cmpy-mgmt-warehouse-panel"}>
|
<div className={"cmpy-mgmt-warehouse-panel"}>
|
||||||
<p className={"tooltip"} style={sizeUsageStyle}>
|
<p className={"tooltip"} style={sizeUsageStyle}>
|
||||||
Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")}
|
Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
|
||||||
{warehouse.breakdown}
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}>
|
<button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}>
|
||||||
@ -454,7 +497,7 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
id={smartSupplyCheckboxId}
|
id={smartSupplyCheckboxId}
|
||||||
onChange={smartSupplyOnChange}
|
onChange={smartSupplyOnChange}
|
||||||
style={{margin: "3px"}}
|
style={{margin: "3px"}}
|
||||||
value={warehouse.smartSupplyEnabled}
|
checked={warehouse.smartSupplyEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -481,9 +524,11 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
return this.renderWarehouseUI();
|
return this.renderWarehouseUI();
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
<div className={"cmpy-mgmt-warehouse-panel"}>
|
||||||
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
|
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
||||||
</button>
|
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,15 @@ export class MainPanel extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We can pass this setter to child components
|
||||||
|
changeCityState(newCity) {
|
||||||
|
if (Object.values(Cities).includes(newCity)) {
|
||||||
|
this.state.city = newCity;
|
||||||
|
} else {
|
||||||
|
console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determines what UI content to render based on routing
|
// Determines what UI content to render based on routing
|
||||||
renderContent() {
|
renderContent() {
|
||||||
if (this.routing().isOnOverviewPage()) {
|
if (this.routing().isOnOverviewPage()) {
|
||||||
@ -68,11 +77,13 @@ export class MainPanel extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cityTabs = (
|
const cityTabs = (
|
||||||
<CityTabs
|
<CityTabs
|
||||||
{...this.props}
|
{...this.props}
|
||||||
city={this.state.city}
|
city={this.state.city}
|
||||||
onClicks={onClicks}
|
onClicks={onClicks}
|
||||||
|
cityStateSetter={this.changeCityState.bind(this)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ export class Overview extends BaseReactComponent {
|
|||||||
dividendStr +
|
dividendStr +
|
||||||
"Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "<br>" +
|
"Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "<br>" +
|
||||||
"Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "<br>" +
|
"Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "<br>" +
|
||||||
"Stock Price: " + (this.corp().public ? "$" + numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "<br>" +
|
"Stock Price: " + (this.corp().public ? numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "<br>" +
|
||||||
"<p class='tooltip'>Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") +
|
"<p class='tooltip'>Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") +
|
||||||
"<span class='tooltiptext'>" +
|
"<span class='tooltiptext'>" +
|
||||||
`Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}<br>` +
|
`Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}<br>` +
|
||||||
|
740
src/DevMenu.js
740
src/DevMenu.js
@ -1,740 +0,0 @@
|
|||||||
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
|
||||||
import { CodingContractTypes } from "./CodingContracts";
|
|
||||||
import { generateContract,
|
|
||||||
generateRandomContract,
|
|
||||||
generateRandomContractOnHome } from "./CodingContractGenerator";
|
|
||||||
import { Companies } from "./Company/Companies";
|
|
||||||
import { Company } from "./Company/Company";
|
|
||||||
import { Programs } from "./Programs/Programs";
|
|
||||||
import { Factions } from "./Faction/Factions";
|
|
||||||
import { Player } from "./Player";
|
|
||||||
import { AllServers } from "./Server/AllServers";
|
|
||||||
import { hackWorldDaemon } from "./RedPill";
|
|
||||||
import { StockMarket,
|
|
||||||
SymbolToStockMap } from "./StockMarket/StockMarket";
|
|
||||||
import { Stock } from "./StockMarket/Stock";
|
|
||||||
import { Terminal } from "./Terminal";
|
|
||||||
|
|
||||||
import { numeralWrapper } from "./ui/numeralFormat";
|
|
||||||
|
|
||||||
import { 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";
|
|
||||||
|
|
||||||
export function createDevMenu() {
|
|
||||||
if (process.env.NODE_ENV !== "development") {
|
|
||||||
throw new Error("Cannot create Dev Menu because you are not in a dev build");
|
|
||||||
}
|
|
||||||
|
|
||||||
const devMenuText = createElement("h1", {
|
|
||||||
display: "block",
|
|
||||||
innerText: "Development Menu - Only meant to be used for testing/debugging",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
const genericHeader = createElement("h2", {
|
|
||||||
display: "block",
|
|
||||||
innerText: "Generic"
|
|
||||||
});
|
|
||||||
|
|
||||||
const addMoney = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
Player.gainMoney(1e15);
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Add $1000t",
|
|
||||||
});
|
|
||||||
|
|
||||||
const addMoney2 = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
Player.gainMoney(1e12);
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Add $1t",
|
|
||||||
})
|
|
||||||
|
|
||||||
const addRam = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
Player.getHomeComputer().maxRam *= 2;
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Double Home Computer RAM",
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerBitflume = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
hackWorldDaemon(Player.bitNodeN, true);
|
|
||||||
},
|
|
||||||
innerText: "Trigger BitFlume",
|
|
||||||
});
|
|
||||||
|
|
||||||
const destroyCurrentBitnode = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
hackWorldDaemon(Player.bitNodeN);
|
|
||||||
},
|
|
||||||
innerText: "Destroy Current BitNode",
|
|
||||||
tooltip: "Will grant Source-File for the BitNode",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Experience / stats
|
|
||||||
const statsHeader = createElement("h2", {
|
|
||||||
display: "block",
|
|
||||||
innerText: "Experience/Stats"
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsHackingExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- hacking exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsHackingExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsHackingExpInput.value);
|
|
||||||
Player.gainHackingExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Hacking Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsStrengthExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- strength exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsStrengthExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsStrengthExpInput.value);
|
|
||||||
Player.gainStrengthExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Strength Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsDefenseExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- defense exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsDefenseExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsDefenseExpInput.value);
|
|
||||||
Player.gainDefenseExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Defense Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsDexterityExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- dexterity exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsDexterityExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsDexterityExpInput.value);
|
|
||||||
Player.gainDexterityExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Dexterity Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsAgilityExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- agility exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsAgilityExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsAgilityExpInput.value);
|
|
||||||
Player.gainAgilityExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Agility Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsCharismaExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- charisma exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsCharismaExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsCharismaExpInput.value);
|
|
||||||
Player.gainCharismaExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Charisma Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsIntelligenceExpInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "+/- intelligence exp",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
const statsIntelligenceExpButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const exp = parseInt(statsIntelligenceExpInput.value);
|
|
||||||
Player.gainIntelligenceExp(exp);
|
|
||||||
Player.updateSkillLevels();
|
|
||||||
},
|
|
||||||
innerText: "Add Intelligence Exp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsEnableIntelligenceButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
Player.intelligence = 1;
|
|
||||||
},
|
|
||||||
innerText: "Enable Intelligence"
|
|
||||||
});
|
|
||||||
|
|
||||||
const statsDisableIntelligenceButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
Player.intelligence = 0;
|
|
||||||
},
|
|
||||||
innerText: "Disable Intelligence"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Factions
|
|
||||||
const factionsHeader = createElement("h2", {innerText: "Factions"});
|
|
||||||
|
|
||||||
const factionsDropdown = createElement("select", {
|
|
||||||
class: "dropdown",
|
|
||||||
margin: "5px",
|
|
||||||
});
|
|
||||||
for (const i in Factions) {
|
|
||||||
factionsDropdown.options[factionsDropdown.options.length] = new Option(Factions[i].name, Factions[i].name);
|
|
||||||
}
|
|
||||||
|
|
||||||
const factionsAddButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const facName = factionsDropdown.options[factionsDropdown.selectedIndex].value;
|
|
||||||
Player.receiveInvite(facName);
|
|
||||||
},
|
|
||||||
innerText: "Receive Invite to Faction",
|
|
||||||
});
|
|
||||||
|
|
||||||
const factionsReputationInput = createElement("input", {
|
|
||||||
placeholder: "Rep to add to faction",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const factionsReputationButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
innerText: "Add rep to faction",
|
|
||||||
clickListener: () => {
|
|
||||||
const facName = getSelectText(factionsDropdown);
|
|
||||||
const fac = Factions[facName];
|
|
||||||
const rep = parseFloat(factionsReputationInput.value);
|
|
||||||
if (fac != null && !isNaN(rep)) {
|
|
||||||
fac.playerReputation += rep;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Augmentations
|
|
||||||
const augmentationsHeader = createElement("h2", {innerText: "Augmentations"});
|
|
||||||
|
|
||||||
const augmentationsDropdown = createElement("select", {
|
|
||||||
class: "dropdown",
|
|
||||||
margin: "5px",
|
|
||||||
});
|
|
||||||
for (const i in AugmentationNames) {
|
|
||||||
const augName = AugmentationNames[i];
|
|
||||||
augmentationsDropdown.options[augmentationsDropdown.options.length] = new Option(augName, augName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const augmentationsQueueButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
Player.queueAugmentation(augmentationsDropdown.options[augmentationsDropdown.selectedIndex].value);
|
|
||||||
},
|
|
||||||
innerText: "Queue Augmentation",
|
|
||||||
});
|
|
||||||
|
|
||||||
const giveAllAugmentationsButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
for (const i in AugmentationNames) {
|
|
||||||
const augName = AugmentationNames[i];
|
|
||||||
Player.queueAugmentation(augName);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Queue All Augmentations",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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"});
|
|
||||||
|
|
||||||
const programsAddDropdown = createElement("select", {
|
|
||||||
class: "dropdown",
|
|
||||||
margin: "5px",
|
|
||||||
});
|
|
||||||
for (const i in Programs) {
|
|
||||||
const progName = Programs[i].name;
|
|
||||||
programsAddDropdown.options[programsAddDropdown.options.length] = new Option(progName, progName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const programsAddButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const program = programsAddDropdown.options[programsAddDropdown.selectedIndex].value;
|
|
||||||
if(!Player.hasProgram(program)) {
|
|
||||||
Player.getHomeComputer().programs.push(program);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
innerText: "Add Program",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Servers
|
|
||||||
const serversHeader = createElement("h2", {innerText: "Servers"});
|
|
||||||
|
|
||||||
const serversOpenAll = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
for (const i in AllServers) {
|
|
||||||
AllServers[i].hasAdminRights = true;
|
|
||||||
AllServers[i].sshPortOpen = true;
|
|
||||||
AllServers[i].ftpPortOpen = true;
|
|
||||||
AllServers[i].smtpPortOpen = true;
|
|
||||||
AllServers[i].httpPortOpen = true;
|
|
||||||
AllServers[i].sqlPortOpen = true;
|
|
||||||
AllServers[i].openPortCount = 5;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Get Admin Rights to all servers",
|
|
||||||
});
|
|
||||||
|
|
||||||
const serversMinSecurityAll = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
for (const i in AllServers) {
|
|
||||||
AllServers[i].hackDifficulty = AllServers[i].minDifficulty;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Set all servers to min security",
|
|
||||||
});
|
|
||||||
|
|
||||||
const serversMaxMoneyAll = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
for (const i in AllServers) {
|
|
||||||
AllServers[i].moneyAvailable = AllServers[i].moneyMax;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
display: "block",
|
|
||||||
innerText: "Set all servers to max money",
|
|
||||||
});
|
|
||||||
|
|
||||||
const serversConnectToDropdown = createElement("select", {class: "dropdown"});
|
|
||||||
for (const i in AllServers) {
|
|
||||||
const hn = AllServers[i].hostname;
|
|
||||||
serversConnectToDropdown.options[serversConnectToDropdown.options.length] = new Option(hn, hn);
|
|
||||||
}
|
|
||||||
|
|
||||||
const serversConnectToButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const host = serversConnectToDropdown.options[serversConnectToDropdown.selectedIndex].value;
|
|
||||||
Terminal.connectToServer(host);
|
|
||||||
},
|
|
||||||
innerText: "Connect to server",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Companies
|
|
||||||
const companiesHeader = createElement("h2", { innerText: "Companies" });
|
|
||||||
|
|
||||||
const companiesDropdown = createElement("select", {
|
|
||||||
class: "dropdown",
|
|
||||||
margin: "5px",
|
|
||||||
});
|
|
||||||
for (const c in Companies) {
|
|
||||||
companiesDropdown.add(createOptionElement(Companies[c].name));
|
|
||||||
}
|
|
||||||
|
|
||||||
const companyReputationInput = createElement("input", {
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "Rep to add to company",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const companyReputationButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
innerText: "Add rep to company",
|
|
||||||
clickListener: () => {
|
|
||||||
const compName = getSelectText(companiesDropdown);
|
|
||||||
const company = Companies[compName];
|
|
||||||
const rep = parseFloat(companyReputationInput.value);
|
|
||||||
if (company != null && !isNaN(rep)) {
|
|
||||||
company.playerReputation += rep;
|
|
||||||
} else {
|
|
||||||
console.warn(`Invalid input for Dev Menu Company Rep. Company Name: ${compName}. Rep: ${rep}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bladeburner
|
|
||||||
const bladeburnerHeader = createElement("h2", {innerText: "Bladeburner"});
|
|
||||||
|
|
||||||
const bladeburnerGainRankInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "Rank to gain (or negative to lose rank)",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const bladeburnerGainRankButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
try {
|
|
||||||
const rank = parseInt(bladeburnerGainRankInput.value);
|
|
||||||
Player.bladeburner.changeRank(rank);
|
|
||||||
} catch(e) {
|
|
||||||
exceptionAlert(`Failed to change Bladeburner Rank in dev menu: ${e}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
innerText: "Gain Bladeburner Rank",
|
|
||||||
});
|
|
||||||
|
|
||||||
const bladeburnerStoredCyclesInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "# Cycles to Add",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const bladeburnerStoredCyclesButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
try {
|
|
||||||
const cycles = parseInt(bladeburnerStoredCyclesInput.value);
|
|
||||||
Player.bladeburner.storedCycles += cycles;
|
|
||||||
} catch(e) {
|
|
||||||
exceptionAlert(`Failed to add cycles to Bladeburner in dev menu: ${e}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
innerText: "Add Cycles to Bladeburner mechanic",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Gang
|
|
||||||
const gangHeader = createElement("h2", {innerText: "Gang"});
|
|
||||||
|
|
||||||
const gangStoredCyclesInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "# Cycles to add",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const gangAddStoredCycles = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
try {
|
|
||||||
const cycles = parseInt(gangStoredCyclesInput.value);
|
|
||||||
Player.gang.storedCycles += cycles;
|
|
||||||
} catch(e) {
|
|
||||||
exceptionAlert(`Failed to add stored cycles to gang mechanic: ${e}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
innerText: "Add cycles to Gang mechanic",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Corporation
|
|
||||||
const corpHeader = createElement("h2", { innerText: "Corporation" });
|
|
||||||
|
|
||||||
const corpStoredCyclesInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "# Cycles to Add",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const corpStoredCyclesButton = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
try {
|
|
||||||
const cycles = parseInt(bladeburnerStoredCyclesInput.value);
|
|
||||||
Player.corporation.storeCycles(cycles);
|
|
||||||
} catch(e) {
|
|
||||||
exceptionAlert(`Failed to add cycles to Bladeburner in dev menu: ${e}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
innerText: "Add Cycles to Corporation mechanic",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Coding Contracts
|
|
||||||
const contractsHeader = createElement("h2", {innerText: "Coding Contracts"});
|
|
||||||
|
|
||||||
const generateRandomContractBtn = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
generateRandomContract();
|
|
||||||
},
|
|
||||||
innerText: "Generate Random Contract",
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateRandomContractOnHomeBtn = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
generateRandomContractOnHome();
|
|
||||||
},
|
|
||||||
innerText: "Generate Random Contract on Home Comp",
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateContractWithTypeSelector = createElement("select", { margin: "5px" });
|
|
||||||
const contractTypes = Object.keys(CodingContractTypes);
|
|
||||||
for (let i = 0; i < contractTypes.length; ++i) {
|
|
||||||
generateContractWithTypeSelector.add(createOptionElement(contractTypes[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateContractWithTypeBtn = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
generateContract({
|
|
||||||
problemType: getSelectText(generateContractWithTypeSelector),
|
|
||||||
server: "home",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
innerText: "Generate Specified Contract Type on Home Comp",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stock Market
|
|
||||||
const stockmarketHeader = createElement("h2", {innerText: "Stock Market"});
|
|
||||||
|
|
||||||
const stockInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
display: "block",
|
|
||||||
placeholder: "Stock symbol(s), or 'all'",
|
|
||||||
});
|
|
||||||
|
|
||||||
function processStocks(cb) {
|
|
||||||
const input = stockInput.value.toString().replace(/\s/g, '');
|
|
||||||
|
|
||||||
// Empty input, or "all", will process all stocks
|
|
||||||
if (input === "" || input.toLowerCase() === "all") {
|
|
||||||
for (const name in StockMarket) {
|
|
||||||
if (StockMarket.hasOwnProperty(name)) {
|
|
||||||
const stock = StockMarket[name];
|
|
||||||
if (stock instanceof Stock) {
|
|
||||||
cb(stock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stockSymbols = input.split(",");
|
|
||||||
for (let i = 0; i < stockSymbols.length; ++i) {
|
|
||||||
const stock = SymbolToStockMap[stockSymbols];
|
|
||||||
if (stock instanceof Stock) {
|
|
||||||
cb(stock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stockPriceChangeInput = createElement("input", {
|
|
||||||
class: "text-input",
|
|
||||||
margin: "5px",
|
|
||||||
placeholder: "Price to change stock(s) to",
|
|
||||||
type: "number",
|
|
||||||
});
|
|
||||||
|
|
||||||
const stockPriceChangeBtn = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
const price = parseInt(stockPriceChangeInput.value);
|
|
||||||
if (isNaN(price)) { return; }
|
|
||||||
|
|
||||||
processStocks((stock) => {
|
|
||||||
stock.price = price;
|
|
||||||
});
|
|
||||||
dialogBoxCreate(`Stock Prices changed to ${price}`);
|
|
||||||
},
|
|
||||||
innerText: "Change Stock Price(s)",
|
|
||||||
});
|
|
||||||
|
|
||||||
const stockViewPriceCapBtn = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
clickListener: () => {
|
|
||||||
let text = "";
|
|
||||||
processStocks((stock) => {
|
|
||||||
text += `${stock.symbol}: ${numeralWrapper.format(stock.cap, '$0.000a')}<br>`;
|
|
||||||
});
|
|
||||||
dialogBoxCreate(text);
|
|
||||||
},
|
|
||||||
innerText: "View Stock Price Caps",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sleeves
|
|
||||||
const sleevesHeader = createElement("h2", { innerText: "Sleeves" });
|
|
||||||
|
|
||||||
const sleevesRemoveAllShockRecovery = createElement("button", {
|
|
||||||
class: "std-button",
|
|
||||||
display: "block",
|
|
||||||
innerText: "Set Shock Recovery of All Sleeves to 0",
|
|
||||||
clickListener: () => {
|
|
||||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
|
||||||
Player.sleeves[i].shock = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add everything to container, then append to main menu
|
|
||||||
const devMenuContainer = createElement("div", {
|
|
||||||
class: "generic-menupage-container",
|
|
||||||
id: devMenuContainerId,
|
|
||||||
});
|
|
||||||
|
|
||||||
devMenuContainer.appendChild(devMenuText);
|
|
||||||
devMenuContainer.appendChild(genericHeader);
|
|
||||||
devMenuContainer.appendChild(addMoney);
|
|
||||||
devMenuContainer.appendChild(addMoney2);
|
|
||||||
devMenuContainer.appendChild(addRam);
|
|
||||||
devMenuContainer.appendChild(triggerBitflume);
|
|
||||||
devMenuContainer.appendChild(destroyCurrentBitnode);
|
|
||||||
devMenuContainer.appendChild(statsHeader);
|
|
||||||
devMenuContainer.appendChild(statsHackingExpInput);
|
|
||||||
devMenuContainer.appendChild(statsHackingExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsStrengthExpInput);
|
|
||||||
devMenuContainer.appendChild(statsStrengthExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsDefenseExpInput);
|
|
||||||
devMenuContainer.appendChild(statsDefenseExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsDexterityExpInput);
|
|
||||||
devMenuContainer.appendChild(statsDexterityExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsAgilityExpInput);
|
|
||||||
devMenuContainer.appendChild(statsAgilityExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsCharismaExpInput);
|
|
||||||
devMenuContainer.appendChild(statsCharismaExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsIntelligenceExpInput);
|
|
||||||
devMenuContainer.appendChild(statsIntelligenceExpButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(statsEnableIntelligenceButton);
|
|
||||||
devMenuContainer.appendChild(statsDisableIntelligenceButton);
|
|
||||||
devMenuContainer.appendChild(factionsHeader);
|
|
||||||
devMenuContainer.appendChild(factionsDropdown);
|
|
||||||
devMenuContainer.appendChild(factionsAddButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(factionsReputationInput);
|
|
||||||
devMenuContainer.appendChild(factionsReputationButton);
|
|
||||||
devMenuContainer.appendChild(augmentationsHeader);
|
|
||||||
devMenuContainer.appendChild(augmentationsDropdown);
|
|
||||||
devMenuContainer.appendChild(augmentationsQueueButton);
|
|
||||||
devMenuContainer.appendChild(giveAllAugmentationsButton);
|
|
||||||
devMenuContainer.appendChild(sourceFilesHeader);
|
|
||||||
devMenuContainer.appendChild(removeSourceFileDropdown);
|
|
||||||
devMenuContainer.appendChild(removeSourceFileButton);
|
|
||||||
devMenuContainer.appendChild(programsHeader);
|
|
||||||
devMenuContainer.appendChild(programsAddDropdown);
|
|
||||||
devMenuContainer.appendChild(programsAddButton);
|
|
||||||
devMenuContainer.appendChild(serversHeader);
|
|
||||||
devMenuContainer.appendChild(serversOpenAll);
|
|
||||||
devMenuContainer.appendChild(serversMinSecurityAll);
|
|
||||||
devMenuContainer.appendChild(serversMaxMoneyAll);
|
|
||||||
devMenuContainer.appendChild(serversConnectToDropdown);
|
|
||||||
devMenuContainer.appendChild(serversConnectToButton);
|
|
||||||
devMenuContainer.appendChild(companiesHeader);
|
|
||||||
devMenuContainer.appendChild(companiesDropdown);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(companyReputationInput);
|
|
||||||
devMenuContainer.appendChild(companyReputationButton);
|
|
||||||
devMenuContainer.appendChild(bladeburnerHeader);
|
|
||||||
devMenuContainer.appendChild(bladeburnerGainRankInput);
|
|
||||||
devMenuContainer.appendChild(bladeburnerGainRankButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(bladeburnerStoredCyclesInput);
|
|
||||||
devMenuContainer.appendChild(bladeburnerStoredCyclesButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(gangHeader);
|
|
||||||
devMenuContainer.appendChild(gangStoredCyclesInput);
|
|
||||||
devMenuContainer.appendChild(gangAddStoredCycles);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(corpHeader);
|
|
||||||
devMenuContainer.appendChild(corpStoredCyclesInput);
|
|
||||||
devMenuContainer.appendChild(corpStoredCyclesButton);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(contractsHeader);
|
|
||||||
devMenuContainer.appendChild(generateRandomContractBtn);
|
|
||||||
devMenuContainer.appendChild(generateRandomContractOnHomeBtn);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(generateContractWithTypeSelector);
|
|
||||||
devMenuContainer.appendChild(generateContractWithTypeBtn);
|
|
||||||
devMenuContainer.appendChild(stockmarketHeader);
|
|
||||||
devMenuContainer.appendChild(stockInput);
|
|
||||||
devMenuContainer.appendChild(stockPriceChangeInput);
|
|
||||||
devMenuContainer.appendChild(stockPriceChangeBtn);
|
|
||||||
devMenuContainer.appendChild(createElement("br"));
|
|
||||||
devMenuContainer.appendChild(stockViewPriceCapBtn);
|
|
||||||
devMenuContainer.appendChild(sleevesHeader);
|
|
||||||
devMenuContainer.appendChild(sleevesRemoveAllShockRecovery);
|
|
||||||
|
|
||||||
const entireGameContainer = document.getElementById("entire-game-container");
|
|
||||||
if (entireGameContainer == null) {
|
|
||||||
throw new Error("Could not find entire-game-container DOM element");
|
|
||||||
}
|
|
||||||
entireGameContainer.appendChild(devMenuContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeDevMenu() {
|
|
||||||
removeElementById(devMenuContainerId);
|
|
||||||
}
|
|
1213
src/DevMenu.jsx
Normal file
1213
src/DevMenu.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums";
|
|||||||
import { Settings } from "../Settings/Settings";
|
import { Settings } from "../Settings/Settings";
|
||||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||||
|
|
||||||
import { createPurchaseSleevesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
|
import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
|
||||||
|
|
||||||
import {Page, routing} from "../ui/navigationTracking";
|
import {Page, routing} from "../ui/navigationTracking";
|
||||||
import {numeralWrapper} from "../ui/numeralFormat";
|
import {numeralWrapper} from "../ui/numeralFormat";
|
||||||
@ -199,7 +199,7 @@ function displayFactionContent(factionName) {
|
|||||||
innerText:"This donation will result in 0.000 reputation gain"
|
innerText:"This donation will result in 0.000 reputation gain"
|
||||||
});
|
});
|
||||||
var donateAmountInput = createElement("input", {
|
var donateAmountInput = createElement("input", {
|
||||||
placeholder:"Donation amount",
|
class: "text-input", placeholder:"Donation amount",
|
||||||
inputListener:()=>{
|
inputListener:()=>{
|
||||||
let amt = 0;
|
let amt = 0;
|
||||||
if(donateAmountInput.value !== "") {
|
if(donateAmountInput.value !== "") {
|
||||||
@ -348,7 +348,7 @@ function displayFactionContent(factionName) {
|
|||||||
class: "std-button",
|
class: "std-button",
|
||||||
innerText: "Purchase Duplicate Sleeves",
|
innerText: "Purchase Duplicate Sleeves",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
createPurchaseSleevesFromCovenantPopup(Player);
|
createSleevePurchasesFromCovenantPopup(Player);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {
|
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {
|
||||||
|
@ -1784,6 +1784,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
|
|||||||
id: name + "gang-member-task",
|
id: name + "gang-member-task",
|
||||||
});
|
});
|
||||||
const taskSelector = createElement("select", {
|
const taskSelector = createElement("select", {
|
||||||
|
class: "dropdown",
|
||||||
id: name + "gang-member-task-selector",
|
id: name + "gang-member-task-selector",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
423
src/Hacknet/HacknetHelpers.jsx
Normal file
423
src/Hacknet/HacknetHelpers.jsx
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
import { HacknetNode,
|
||||||
|
BaseCostForHacknetNode,
|
||||||
|
HacknetNodePurchaseNextMult,
|
||||||
|
HacknetNodeMaxLevel,
|
||||||
|
HacknetNodeMaxRam,
|
||||||
|
HacknetNodeMaxCores } from "./HacknetNode";
|
||||||
|
import { HacknetServer,
|
||||||
|
BaseCostForHacknetServer,
|
||||||
|
HacknetServerPurchaseMult,
|
||||||
|
HacknetServerMaxLevel,
|
||||||
|
HacknetServerMaxRam,
|
||||||
|
HacknetServerMaxCores,
|
||||||
|
HacknetServerMaxCache,
|
||||||
|
MaxNumberHacknetServers } from "./HacknetServer";
|
||||||
|
import { HashManager } from "./HashManager";
|
||||||
|
import { HashUpgrades } from "./HashUpgrades";
|
||||||
|
|
||||||
|
import { generateRandomContractOnHome } from "../CodingContractGenerator";
|
||||||
|
import { iTutorialSteps, iTutorialNextStep,
|
||||||
|
ITutorial} from "../InteractiveTutorial";
|
||||||
|
import { Player } from "../Player";
|
||||||
|
import { AddToAllServers,
|
||||||
|
AllServers } from "../Server/AllServers";
|
||||||
|
import { GetServerByHostname } from "../Server/ServerHelpers";
|
||||||
|
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||||
|
import { Page, routing } from "../ui/navigationTracking";
|
||||||
|
|
||||||
|
import {getElementById} from "../../utils/uiHelpers/getElementById";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { HacknetRoot } from "./ui/Root";
|
||||||
|
|
||||||
|
let hacknetNodesDiv;
|
||||||
|
function hacknetNodesInit() {
|
||||||
|
hacknetNodesDiv = document.getElementById("hacknet-nodes-container");
|
||||||
|
document.removeEventListener("DOMContentLoaded", hacknetNodesInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", hacknetNodesInit);
|
||||||
|
|
||||||
|
// Returns a boolean indicating whether the player has Hacknet Servers
|
||||||
|
// (the upgraded form of Hacknet Nodes)
|
||||||
|
export function hasHacknetServers() {
|
||||||
|
return (Player.bitNodeN === 9 || SourceFileFlags[9] > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function purchaseHacknet() {
|
||||||
|
/* INTERACTIVE TUTORIAL */
|
||||||
|
if (ITutorial.isRunning) {
|
||||||
|
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
|
||||||
|
iTutorialNextStep();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END INTERACTIVE TUTORIAL */
|
||||||
|
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
const cost = getCostOfNextHacknetServer();
|
||||||
|
if (isNaN(cost)) {
|
||||||
|
throw new Error(`Calculated cost of purchasing HacknetServer is NaN`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Player.canAfford(cost)) { return -1; }
|
||||||
|
|
||||||
|
// Auto generate a hostname for this Server
|
||||||
|
const numOwned = Player.hacknetNodes.length;
|
||||||
|
const name = `hacknet-node-${numOwned}`;
|
||||||
|
const server = new HacknetServer({
|
||||||
|
adminRights: true,
|
||||||
|
hostname: name,
|
||||||
|
player: Player,
|
||||||
|
});
|
||||||
|
|
||||||
|
Player.loseMoney(cost);
|
||||||
|
Player.hacknetNodes.push(server.ip);
|
||||||
|
|
||||||
|
// Configure the HacknetServer to actually act as a Server
|
||||||
|
AddToAllServers(server);
|
||||||
|
const homeComputer = Player.getHomeComputer();
|
||||||
|
homeComputer.serversOnNetwork.push(server.ip);
|
||||||
|
server.serversOnNetwork.push(homeComputer.ip);
|
||||||
|
|
||||||
|
return numOwned;
|
||||||
|
} else {
|
||||||
|
const cost = getCostOfNextHacknetNode();
|
||||||
|
if (isNaN(cost)) {
|
||||||
|
throw new Error(`Calculated cost of purchasing HacknetNode is NaN`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Player.canAfford(cost)) { return -1; }
|
||||||
|
|
||||||
|
// Auto generate a name for the Node
|
||||||
|
const numOwned = Player.hacknetNodes.length;
|
||||||
|
const name = "hacknet-node-" + numOwned;
|
||||||
|
const node = new HacknetNode(name);
|
||||||
|
node.updateMoneyGainRate(Player);
|
||||||
|
|
||||||
|
Player.loseMoney(cost);
|
||||||
|
Player.hacknetNodes.push(node);
|
||||||
|
|
||||||
|
return numOwned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasMaxNumberHacknetServers() {
|
||||||
|
return hasHacknetServers() && Player.hacknetNodes.length >= MaxNumberHacknetServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCostOfNextHacknetNode() {
|
||||||
|
// Cost increases exponentially based on how many you own
|
||||||
|
const numOwned = Player.hacknetNodes.length;
|
||||||
|
const mult = HacknetNodePurchaseNextMult;
|
||||||
|
|
||||||
|
return BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCostOfNextHacknetServer() {
|
||||||
|
const numOwned = Player.hacknetNodes.length;
|
||||||
|
const mult = HacknetServerPurchaseMult;
|
||||||
|
|
||||||
|
if (numOwned > MaxNumberHacknetServers) { return Infinity; }
|
||||||
|
|
||||||
|
return BaseCostForHacknetServer * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
|
||||||
|
export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
|
||||||
|
if (maxLevel == null) {
|
||||||
|
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = 1;
|
||||||
|
let max = maxLevel - 1;
|
||||||
|
let levelsToMax = maxLevel - nodeObj.level;
|
||||||
|
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player))) {
|
||||||
|
return levelsToMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (min <= max) {
|
||||||
|
var curr = (min + max) / 2 | 0;
|
||||||
|
if (curr !== maxLevel &&
|
||||||
|
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
|
||||||
|
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
|
||||||
|
return Math.min(levelsToMax, curr);
|
||||||
|
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
|
||||||
|
max = curr - 1;
|
||||||
|
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
|
||||||
|
min = curr + 1;
|
||||||
|
} else {
|
||||||
|
return Math.min(levelsToMax, curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
||||||
|
if (maxLevel == null) {
|
||||||
|
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let levelsToMax;
|
||||||
|
if (nodeObj instanceof HacknetServer) {
|
||||||
|
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.maxRam));
|
||||||
|
} else {
|
||||||
|
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
|
||||||
|
}
|
||||||
|
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player))) {
|
||||||
|
return levelsToMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
//We'll just loop until we find the max
|
||||||
|
for (let i = levelsToMax-1; i >= 0; --i) {
|
||||||
|
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player))) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
||||||
|
if (maxLevel == null) {
|
||||||
|
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = 1;
|
||||||
|
let max = maxLevel - 1;
|
||||||
|
const levelsToMax = maxLevel - nodeObj.cores;
|
||||||
|
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player))) {
|
||||||
|
return levelsToMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Use a binary search to find the max possible number of upgrades
|
||||||
|
while (min <= max) {
|
||||||
|
let curr = (min + max) / 2 | 0;
|
||||||
|
if (curr != maxLevel &&
|
||||||
|
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
|
||||||
|
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
|
||||||
|
return Math.min(levelsToMax, curr);
|
||||||
|
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
|
||||||
|
max = curr - 1;
|
||||||
|
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
|
||||||
|
min = curr + 1;
|
||||||
|
} else {
|
||||||
|
return Math.min(levelsToMax, curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
|
||||||
|
if (maxLevel == null) {
|
||||||
|
throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = 1;
|
||||||
|
let max = maxLevel - 1;
|
||||||
|
const levelsToMax = maxLevel - nodeObj.cache;
|
||||||
|
if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) {
|
||||||
|
return levelsToMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a binary search to find the max possible number of upgrades
|
||||||
|
while (min <= max) {
|
||||||
|
let curr = (min + max) / 2 | 0;
|
||||||
|
if (curr != maxLevel &&
|
||||||
|
Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) &&
|
||||||
|
!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1))) {
|
||||||
|
return Math.min(levelsToMax, curr);
|
||||||
|
} else if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
|
||||||
|
max = curr -1 ;
|
||||||
|
} else if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
|
||||||
|
min = curr + 1;
|
||||||
|
} else {
|
||||||
|
return Math.min(levelsToMax, curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial construction of Hacknet Nodes UI
|
||||||
|
export function renderHacknetNodesUI() {
|
||||||
|
if (!routing.isOn(Page.HacknetNodes)) { return; }
|
||||||
|
|
||||||
|
ReactDOM.render(<HacknetRoot />, hacknetNodesDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearHacknetNodesUI() {
|
||||||
|
if (hacknetNodesDiv instanceof HTMLElement) {
|
||||||
|
ReactDOM.unmountComponentAtNode(hacknetNodesDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
hacknetNodesDiv.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processHacknetEarnings(numCycles) {
|
||||||
|
// Determine if player has Hacknet Nodes or Hacknet Servers, then
|
||||||
|
// call the appropriate function
|
||||||
|
if (Player.hacknetNodes.length === 0) { return 0; }
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
return processAllHacknetServerEarnings();
|
||||||
|
} else if (Player.hacknetNodes[0] instanceof HacknetNode) {
|
||||||
|
return processAllHacknetNodeEarnings();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processAllHacknetNodeEarnings(numCycles) {
|
||||||
|
let total = 0;
|
||||||
|
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||||
|
total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
|
||||||
|
const totalEarnings = nodeObj.process(numCycles);
|
||||||
|
Player.gainMoney(totalEarnings);
|
||||||
|
Player.recordMoneySource(totalEarnings, "hacknetnode");
|
||||||
|
|
||||||
|
return totalEarnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processAllHacknetServerEarnings(numCycles) {
|
||||||
|
if (!(Player.hashManager instanceof HashManager)) {
|
||||||
|
throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashes = 0;
|
||||||
|
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||||
|
const hserver = AllServers[Player.hacknetNodes[i]]; // hacknetNodes array only contains the IP addresses
|
||||||
|
hashes += hserver.process(numCycles);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player.hashManager.storeHashes(hashes);
|
||||||
|
|
||||||
|
return hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function purchaseHashUpgrade(upgName, upgTarget) {
|
||||||
|
if (!(Player.hashManager instanceof HashManager)) {
|
||||||
|
console.error(`Player does not have a HashManager`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashManager handles the transaction. This just needs to actually implement
|
||||||
|
// the effects of the upgrade
|
||||||
|
if (Player.hashManager.upgrade(upgName)) {
|
||||||
|
const upg = HashUpgrades[upgName];
|
||||||
|
|
||||||
|
switch (upgName) {
|
||||||
|
case "Sell for Money": {
|
||||||
|
Player.gainMoney(upg.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Sell for Corporation Funds": {
|
||||||
|
// This will throw if player doesn't have a corporation
|
||||||
|
try {
|
||||||
|
Player.corporation.funds = Player.corporation.funds.plus(upg.value);
|
||||||
|
} catch(e) {
|
||||||
|
Player.hashManager.refundUpgrade(upgName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Reduce Minimum Security": {
|
||||||
|
try {
|
||||||
|
const target = GetServerByHostname(upgTarget);
|
||||||
|
if (target == null) {
|
||||||
|
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.changeMinimumSecurity(upg.value, true);
|
||||||
|
} catch(e) {
|
||||||
|
Player.hashManager.refundUpgrade(upgName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Increase Maximum Money": {
|
||||||
|
try {
|
||||||
|
const target = GetServerByHostname(upgTarget);
|
||||||
|
if (target == null) {
|
||||||
|
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.changeMaximumMoney(upg.value, true);
|
||||||
|
} catch(e) {
|
||||||
|
Player.hashManager.refundUpgrade(upgName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Improve Studying": {
|
||||||
|
// Multiplier handled by HashManager
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Improve Gym Training": {
|
||||||
|
// Multiplier handled by HashManager
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Exchange for Corporation Research": {
|
||||||
|
// This will throw if player doesn't have a corporation
|
||||||
|
try {
|
||||||
|
for (const division of Player.corporation.divisions) {
|
||||||
|
division.sciResearch.qty += upg.value;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
Player.hashManager.refundUpgrade(upgName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Exchange for Bladeburner Rank": {
|
||||||
|
// This will throw if player doesn't have a corporation
|
||||||
|
try {
|
||||||
|
for (const division of Player.corporation.divisions) {
|
||||||
|
division.sciResearch.qty += upg.value;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
Player.hashManager.refundUpgrade(upgName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Generate Coding Contract": {
|
||||||
|
generateRandomContractOnHome();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
285
src/Hacknet/HacknetNode.ts
Normal file
285
src/Hacknet/HacknetNode.ts
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
/**
|
||||||
|
* Hacknet Node Class
|
||||||
|
*
|
||||||
|
* Hacknet Nodes are specialized machines that passively earn the player money over time.
|
||||||
|
* They can be upgraded to increase their production
|
||||||
|
*/
|
||||||
|
import { IHacknetNode } from "./IHacknetNode";
|
||||||
|
|
||||||
|
import { CONSTANTS } from "../Constants";
|
||||||
|
|
||||||
|
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
|
||||||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
|
import { Generic_fromJSON,
|
||||||
|
Generic_toJSON,
|
||||||
|
Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
|
// Constants for Hacknet Node production
|
||||||
|
export const HacknetNodeMoneyGainPerLevel: number = 1.6; // Base production per level
|
||||||
|
|
||||||
|
// Constants for Hacknet Node purchase/upgrade costs
|
||||||
|
export const BaseCostForHacknetNode: number = 1000;
|
||||||
|
export const BaseCostFor1GBOfRamHacknetNode: number = 30e3;
|
||||||
|
export const BaseCostForHacknetNodeCore: number = 500e3;
|
||||||
|
export const HacknetNodePurchaseNextMult: number = 1.85; // Multiplier when purchasing an additional hacknet node
|
||||||
|
export const HacknetNodeUpgradeLevelMult: number = 1.04; // Multiplier for cost when upgrading level
|
||||||
|
export const HacknetNodeUpgradeRamMult: number = 1.28; // Multiplier for cost when upgrading RAM
|
||||||
|
export const HacknetNodeUpgradeCoreMult: number = 1.48; // Multiplier for cost when buying another core
|
||||||
|
|
||||||
|
// Constants for max upgrade levels for Hacknet Nodes
|
||||||
|
export const HacknetNodeMaxLevel: number = 200;
|
||||||
|
export const HacknetNodeMaxRam: number = 64;
|
||||||
|
export const HacknetNodeMaxCores: number = 16;
|
||||||
|
|
||||||
|
export class HacknetNode implements IHacknetNode {
|
||||||
|
/**
|
||||||
|
* Initiatizes a HacknetNode object from a JSON save state.
|
||||||
|
*/
|
||||||
|
static fromJSON(value: any): HacknetNode {
|
||||||
|
return Generic_fromJSON(HacknetNode, value.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node's number of cores
|
||||||
|
cores: number = 1;
|
||||||
|
|
||||||
|
// Node's Level
|
||||||
|
level: number = 1;
|
||||||
|
|
||||||
|
// Node's production per second
|
||||||
|
moneyGainRatePerSecond: number = 0;
|
||||||
|
|
||||||
|
// Identifier for Node. Includes the full "name" (hacknet-node-N)
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
// How long this Node has existed, in seconds
|
||||||
|
onlineTimeSeconds: number = 0;
|
||||||
|
|
||||||
|
// Node's RAM (GB)
|
||||||
|
ram: number = 1;
|
||||||
|
|
||||||
|
// Total money earned by this Node
|
||||||
|
totalMoneyGenerated: number = 0;
|
||||||
|
|
||||||
|
constructor(name: string="") {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the cost to upgrade this Node's number of cores
|
||||||
|
calculateCoreUpgradeCost(levels: number=1, p: IPlayer): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cores >= HacknetNodeMaxCores) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const coreBaseCost = BaseCostForHacknetNodeCore;
|
||||||
|
const mult = HacknetNodeUpgradeCoreMult;
|
||||||
|
let totalCost = 0;
|
||||||
|
let currentCores = this.cores;
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
totalCost += (coreBaseCost * Math.pow(mult, currentCores-1));
|
||||||
|
++currentCores;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCost *= p.hacknet_node_core_cost_mult;
|
||||||
|
|
||||||
|
return totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the cost to upgrade this Node's level
|
||||||
|
calculateLevelUpgradeCost(levels: number=1, p: IPlayer): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.level >= HacknetNodeMaxLevel) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mult = HacknetNodeUpgradeLevelMult;
|
||||||
|
let totalMultiplier = 0;
|
||||||
|
let currLevel = this.level;
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
totalMultiplier += Math.pow(mult, currLevel);
|
||||||
|
++currLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BaseCostForHacknetNode / 2 * totalMultiplier * p.hacknet_node_level_cost_mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the cost to upgrade this Node's RAM
|
||||||
|
calculateRamUpgradeCost(levels: number=1, p: IPlayer): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ram >= HacknetNodeMaxRam) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalCost = 0;
|
||||||
|
let numUpgrades = Math.round(Math.log2(this.ram));
|
||||||
|
let currentRam = this.ram;
|
||||||
|
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
let baseCost = currentRam * BaseCostFor1GBOfRamHacknetNode;
|
||||||
|
let mult = Math.pow(HacknetNodeUpgradeRamMult, numUpgrades);
|
||||||
|
|
||||||
|
totalCost += (baseCost * mult);
|
||||||
|
|
||||||
|
currentRam *= 2;
|
||||||
|
++numUpgrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCost *= p.hacknet_node_ram_cost_mult;
|
||||||
|
|
||||||
|
return totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process this Hacknet Node in the game loop.
|
||||||
|
// Returns the amount of money generated
|
||||||
|
process(numCycles: number=1): number {
|
||||||
|
const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000;
|
||||||
|
let gain = this.moneyGainRatePerSecond * seconds;
|
||||||
|
if (isNaN(gain)) {
|
||||||
|
console.error(`Hacknet Node ${this.name} calculated earnings of NaN`);
|
||||||
|
gain = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.totalMoneyGenerated += gain;
|
||||||
|
this.onlineTimeSeconds += seconds;
|
||||||
|
|
||||||
|
return gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade this Node's number of cores, if possible
|
||||||
|
// Returns a boolean indicating whether new cores were successfully bought
|
||||||
|
purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
||||||
|
if (isNaN(cost) || sanitizedLevels < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if we're already at max
|
||||||
|
if (this.cores >= HacknetNodeMaxCores) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the specified number of upgrades would exceed the max Cores, calculate
|
||||||
|
// the max possible number of upgrades and use that
|
||||||
|
if (this.cores + sanitizedLevels > HacknetNodeMaxCores) {
|
||||||
|
const diff = Math.max(0, HacknetNodeMaxCores - this.cores);
|
||||||
|
return this.purchaseCoreUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
this.cores = Math.round(this.cores + sanitizedLevels); // Just in case of floating point imprecision
|
||||||
|
this.updateMoneyGainRate(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade this Node's level, if possible
|
||||||
|
// Returns a boolean indicating whether the level was successfully updated
|
||||||
|
purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
||||||
|
if (isNaN(cost) || sanitizedLevels < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're at max level, return false
|
||||||
|
if (this.level >= HacknetNodeMaxLevel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the number of specified upgrades would exceed the max level, calculate
|
||||||
|
// the maximum number of upgrades and use that
|
||||||
|
if (this.level + sanitizedLevels > HacknetNodeMaxLevel) {
|
||||||
|
var diff = Math.max(0, HacknetNodeMaxLevel - this.level);
|
||||||
|
return this.purchaseLevelUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
this.level = Math.round(this.level + sanitizedLevels); // Just in case of floating point imprecision
|
||||||
|
this.updateMoneyGainRate(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade this Node's RAM, if possible
|
||||||
|
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||||||
|
purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
|
||||||
|
if (isNaN(cost) || sanitizedLevels < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if we're already at max
|
||||||
|
if (this.ram >= HacknetNodeMaxRam) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the number of specified upgrades would exceed the max RAM, calculate the
|
||||||
|
// max possible number of upgrades and use that
|
||||||
|
if (this.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
|
||||||
|
var diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / this.ram)));
|
||||||
|
return this.purchaseRamUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
this.ram *= 2; // Ram is always doubled
|
||||||
|
}
|
||||||
|
this.ram = Math.round(this.ram); // Handle any floating point precision issues
|
||||||
|
this.updateMoneyGainRate(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-calculate this Node's production and update the moneyGainRatePerSecond prop
|
||||||
|
updateMoneyGainRate(p: IPlayer): void {
|
||||||
|
//How much extra $/s is gained per level
|
||||||
|
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
|
||||||
|
|
||||||
|
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
||||||
|
Math.pow(1.035, this.ram - 1) *
|
||||||
|
((this.cores + 5) / 6) *
|
||||||
|
p.hacknet_node_money_mult *
|
||||||
|
BitNodeMultipliers.HacknetNodeMoney;
|
||||||
|
if (isNaN(this.moneyGainRatePerSecond)) {
|
||||||
|
this.moneyGainRatePerSecond = 0;
|
||||||
|
dialogBoxCreate("Error in calculating Hacknet Node production. Please report to game developer", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize the current object to a JSON save state.
|
||||||
|
*/
|
||||||
|
toJSON(): any {
|
||||||
|
return Generic_toJSON("HacknetNode", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reviver.constructors.HacknetNode = HacknetNode;
|
349
src/Hacknet/HacknetServer.ts
Normal file
349
src/Hacknet/HacknetServer.ts
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
/**
|
||||||
|
* Hacknet Servers - Reworked Hacknet Node mechanic for BitNode-9
|
||||||
|
*/
|
||||||
|
import { CONSTANTS } from "../Constants";
|
||||||
|
|
||||||
|
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||||
|
import { IHacknetNode } from "../Hacknet/IHacknetNode";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
import { BaseServer } from "../Server/BaseServer";
|
||||||
|
import { RunningScript } from "../Script/RunningScript";
|
||||||
|
|
||||||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
|
import { createRandomIp } from "../../utils/IPAddress";
|
||||||
|
|
||||||
|
import { Generic_fromJSON,
|
||||||
|
Generic_toJSON,
|
||||||
|
Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
|
// Constants for Hacknet Server stats/production
|
||||||
|
export const HacknetServerHashesPerLevel: number = 0.001;
|
||||||
|
|
||||||
|
// Constants for Hacknet Server purchase/upgrade costs
|
||||||
|
export const BaseCostForHacknetServer: number = 50e3;
|
||||||
|
export const BaseCostFor1GBHacknetServerRam: number = 200e3;
|
||||||
|
export const BaseCostForHacknetServerCore: number = 1e6;
|
||||||
|
export const BaseCostForHacknetServerCache: number = 10e6;
|
||||||
|
|
||||||
|
export const HacknetServerPurchaseMult: number = 3.2; // Multiplier for puchasing an additional Hacknet Server
|
||||||
|
export const HacknetServerUpgradeLevelMult: number = 1.1; // Multiplier for cost when upgrading level
|
||||||
|
export const HacknetServerUpgradeRamMult: number = 1.4; // Multiplier for cost when upgrading RAM
|
||||||
|
export const HacknetServerUpgradeCoreMult: number = 1.55; // Multiplier for cost when buying another core
|
||||||
|
export const HacknetServerUpgradeCacheMult: number = 1.85; // Multiplier for cost when upgrading cache
|
||||||
|
|
||||||
|
export const MaxNumberHacknetServers: number = 25; // Max number of Hacknet Servers you can own
|
||||||
|
|
||||||
|
// Constants for max upgrade levels for Hacknet Server
|
||||||
|
export const HacknetServerMaxLevel: number = 300;
|
||||||
|
export const HacknetServerMaxRam: number = 8192;
|
||||||
|
export const HacknetServerMaxCores: number = 128;
|
||||||
|
export const HacknetServerMaxCache: number = 15;
|
||||||
|
|
||||||
|
interface IConstructorParams {
|
||||||
|
adminRights?: boolean;
|
||||||
|
hostname: string;
|
||||||
|
ip?: string;
|
||||||
|
isConnectedTo?: boolean;
|
||||||
|
maxRam?: number;
|
||||||
|
organizationName?: string;
|
||||||
|
player?: IPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HacknetServer extends BaseServer implements IHacknetNode {
|
||||||
|
// Initializes a HacknetServer Object from a JSON save state
|
||||||
|
static fromJSON(value: any): HacknetServer {
|
||||||
|
return Generic_fromJSON(HacknetServer, value.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache level. Affects hash Capacity
|
||||||
|
cache: number = 1;
|
||||||
|
|
||||||
|
// Number of cores. Improves hash production
|
||||||
|
cores: number = 1;
|
||||||
|
|
||||||
|
// Number of hashes that can be stored by this Hacknet Server
|
||||||
|
hashCapacity: number = 0;
|
||||||
|
|
||||||
|
// Hashes produced per second
|
||||||
|
hashRate: number = 0;
|
||||||
|
|
||||||
|
// Similar to Node level. Improves hash production
|
||||||
|
level: number = 1;
|
||||||
|
|
||||||
|
// How long this HacknetServer has existed, in seconds
|
||||||
|
onlineTimeSeconds: number = 0;
|
||||||
|
|
||||||
|
// Total number of hashes earned by this
|
||||||
|
totalHashesGenerated: number = 0;
|
||||||
|
|
||||||
|
constructor(params: IConstructorParams={ hostname: "", ip: createRandomIp() }) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
this.maxRam = 1;
|
||||||
|
this.updateHashCapacity();
|
||||||
|
|
||||||
|
if (params.player) {
|
||||||
|
this.updateHashRate(params.player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateCacheUpgradeCost(levels: number): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cache >= HacknetServerMaxCache) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mult = HacknetServerUpgradeCacheMult;
|
||||||
|
let totalCost = 0;
|
||||||
|
let currentCache = this.cache;
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
totalCost += Math.pow(mult, currentCache - 1);
|
||||||
|
++currentCache;
|
||||||
|
}
|
||||||
|
totalCost *= BaseCostForHacknetServerCache;
|
||||||
|
|
||||||
|
return totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateCoreUpgradeCost(levels: number, p: IPlayer): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cores >= HacknetServerMaxCores) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mult = HacknetServerUpgradeCoreMult;
|
||||||
|
let totalCost = 0;
|
||||||
|
let currentCores = this.cores;
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
totalCost += Math.pow(mult, currentCores-1);
|
||||||
|
++currentCores;
|
||||||
|
}
|
||||||
|
totalCost *= BaseCostForHacknetServerCore;
|
||||||
|
totalCost *= p.hacknet_node_core_cost_mult;
|
||||||
|
|
||||||
|
return totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateLevelUpgradeCost(levels: number, p: IPlayer): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.level >= HacknetServerMaxLevel) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mult = HacknetServerUpgradeLevelMult;
|
||||||
|
let totalMultiplier = 0;
|
||||||
|
let currLevel = this.level;
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
totalMultiplier += Math.pow(mult, currLevel);
|
||||||
|
++currLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 10 * BaseCostForHacknetServer * totalMultiplier * p.hacknet_node_level_cost_mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateRamUpgradeCost(levels: number, p: IPlayer): number {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.maxRam >= HacknetServerMaxRam) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalCost = 0;
|
||||||
|
let numUpgrades = Math.round(Math.log2(this.maxRam));
|
||||||
|
let currentRam = this.maxRam;
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
let baseCost = currentRam * BaseCostFor1GBHacknetServerRam;
|
||||||
|
let mult = Math.pow(HacknetServerUpgradeRamMult, numUpgrades);
|
||||||
|
|
||||||
|
totalCost += (baseCost * mult);
|
||||||
|
|
||||||
|
currentRam *= 2;
|
||||||
|
++numUpgrades;
|
||||||
|
}
|
||||||
|
totalCost *= p.hacknet_node_ram_cost_mult;
|
||||||
|
|
||||||
|
return totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process this Hacknet Server in the game loop.
|
||||||
|
// Returns the number of hashes generated
|
||||||
|
process(numCycles: number=1): number {
|
||||||
|
const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000;
|
||||||
|
|
||||||
|
return this.hashRate * seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean indicating whether the cache was successfully upgraded
|
||||||
|
purchaseCacheUpgrade(levels: number, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateCacheUpgradeCost(levels);
|
||||||
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cache >= HacknetServerMaxCache) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the specified number of upgrades would exceed the max, try to purchase
|
||||||
|
// the maximum possible
|
||||||
|
if (this.cache + levels > HacknetServerMaxCache) {
|
||||||
|
const diff = Math.max(0, HacknetServerMaxCache - this.cache);
|
||||||
|
return this.purchaseCacheUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
this.cache = Math.round(this.cache + sanitizedLevels);
|
||||||
|
this.updateHashCapacity();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean indicating whether the number of cores was successfully upgraded
|
||||||
|
purchaseCoreUpgrade(levels: number, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
||||||
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cores >= HacknetServerMaxCores) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the specified number of upgrades would exceed the max, try to purchase
|
||||||
|
// the maximum possible
|
||||||
|
if (this.cores + sanitizedLevels > HacknetServerMaxCores) {
|
||||||
|
const diff = Math.max(0, HacknetServerMaxCores - this.cores);
|
||||||
|
return this.purchaseCoreUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
this.cores = Math.round(this.cores + sanitizedLevels);
|
||||||
|
this.updateHashRate(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean indicating whether the level was successfully upgraded
|
||||||
|
purchaseLevelUpgrade(levels: number, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
||||||
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.level >= HacknetServerMaxLevel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the specified number of upgrades would exceed the max, try to purchase the
|
||||||
|
// maximum possible
|
||||||
|
if (this.level + sanitizedLevels > HacknetServerMaxLevel) {
|
||||||
|
const diff = Math.max(0, HacknetServerMaxLevel - this.level);
|
||||||
|
return this.purchaseLevelUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
this.level = Math.round(this.level + sanitizedLevels);
|
||||||
|
this.updateHashRate(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||||||
|
purchaseRamUpgrade(levels: number, p: IPlayer): boolean {
|
||||||
|
const sanitizedLevels = Math.round(levels);
|
||||||
|
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
|
||||||
|
if(isNaN(cost) || cost <= 0 || sanitizedLevels <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.maxRam >= HacknetServerMaxRam) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the specified number of upgrades would exceed the max, try to purchase
|
||||||
|
// just the maximum possible
|
||||||
|
if (this.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerMaxRam) {
|
||||||
|
const diff = Math.max(0, Math.log2(Math.round(HacknetServerMaxRam / this.maxRam)));
|
||||||
|
return this.purchaseRamUpgrade(diff, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p.canAfford(cost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(cost);
|
||||||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||||||
|
this.maxRam *= 2;
|
||||||
|
}
|
||||||
|
this.maxRam = Math.round(this.maxRam);
|
||||||
|
this.updateHashRate(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whenever a script is run, we must update this server's hash rate
|
||||||
|
*/
|
||||||
|
runScript(script: RunningScript, p?: IPlayer): void {
|
||||||
|
super.runScript(script);
|
||||||
|
if (p) {
|
||||||
|
this.updateHashRate(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHashCapacity(): void {
|
||||||
|
this.hashCapacity = 32 * Math.pow(2, this.cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHashRate(p: IPlayer): void {
|
||||||
|
const baseGain = HacknetServerHashesPerLevel * this.level;
|
||||||
|
const coreMultiplier = Math.pow(1.1, this.cores - 1);
|
||||||
|
const ramRatio = (1 - this.ramUsed / this.maxRam);
|
||||||
|
|
||||||
|
const hashRate = baseGain * coreMultiplier * ramRatio;
|
||||||
|
|
||||||
|
this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
|
||||||
|
|
||||||
|
if (isNaN(this.hashRate)) {
|
||||||
|
this.hashRate = 0;
|
||||||
|
dialogBoxCreate(`Error calculating Hacknet Server hash production. This is a bug. Please report to game dev`, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the current object to a JSON save state
|
||||||
|
toJSON(): any {
|
||||||
|
return Generic_toJSON("HacknetServer", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reviver.constructors.HacknetServer = HacknetServer;
|
168
src/Hacknet/HashManager.ts
Normal file
168
src/Hacknet/HashManager.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* This is a central class for storing and managing the player's hashes,
|
||||||
|
* which are generated by Hacknet Servers
|
||||||
|
*
|
||||||
|
* It is also used to keep track of what upgrades the player has bought with
|
||||||
|
* his hashes, and contains method for grabbing the data/multipliers from
|
||||||
|
* those upgrades
|
||||||
|
*/
|
||||||
|
import { HacknetServer } from "./HacknetServer";
|
||||||
|
import { HashUpgrades } from "./HashUpgrades";
|
||||||
|
|
||||||
|
import { IMap } from "../types";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
import { AllServers } from "../Server/AllServers";
|
||||||
|
import { Generic_fromJSON,
|
||||||
|
Generic_toJSON,
|
||||||
|
Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
|
export class HashManager {
|
||||||
|
// Initiatizes a HashManager object from a JSON save state.
|
||||||
|
static fromJSON(value: any): HashManager {
|
||||||
|
return Generic_fromJSON(HashManager, value.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max number of hashes this can hold. Equal to the sum of capacities of
|
||||||
|
// all Hacknet Servers
|
||||||
|
capacity: number = 0;
|
||||||
|
|
||||||
|
// Number of hashes currently in storage
|
||||||
|
hashes: number = 0;
|
||||||
|
|
||||||
|
// Map of Hash Upgrade Name -> levels in that upgrade
|
||||||
|
upgrades: IMap<number> = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
for (const name in HashUpgrades) {
|
||||||
|
this.upgrades[name] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic helper function for getting a multiplier from a HashUpgrade
|
||||||
|
*/
|
||||||
|
getMult(upgName: string): number {
|
||||||
|
const upg = HashUpgrades[upgName];
|
||||||
|
const currLevel = this.upgrades[upgName];
|
||||||
|
if (upg == null || currLevel == null) {
|
||||||
|
console.error(`Could not find Hash Study upgrade`);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 + ((upg.value * currLevel) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of the Hash upgrades improves studying. This returns that multiplier
|
||||||
|
*/
|
||||||
|
getStudyMult(): number {
|
||||||
|
const upgName = "Improve Studying";
|
||||||
|
|
||||||
|
return this.getMult(upgName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of the Hash upgrades improves gym training. This returns that multiplier
|
||||||
|
*/
|
||||||
|
getTrainingMult(): number {
|
||||||
|
const upgName = "Improve Gym Training";
|
||||||
|
|
||||||
|
return this.getMult(upgName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cost (in hashes) of an upgrade
|
||||||
|
*/
|
||||||
|
getUpgradeCost(upgName: string): number {
|
||||||
|
const upg = HashUpgrades[upgName];
|
||||||
|
const currLevel = this.upgrades[upgName];
|
||||||
|
if (upg == null || currLevel == null) {
|
||||||
|
console.error(`Invalid Upgrade Name given to HashManager.getUpgradeCost(): ${upgName}`);
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return upg.getCost(currLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
storeHashes(numHashes: number): void {
|
||||||
|
this.hashes += numHashes;
|
||||||
|
this.hashes = Math.min(this.hashes, this.capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts an upgrade and refunds the hashes used to buy it
|
||||||
|
*/
|
||||||
|
refundUpgrade(upgName: string): void {
|
||||||
|
const upg = HashUpgrades[upgName];
|
||||||
|
const currLevel = this.upgrades[upgName];
|
||||||
|
if (upg == null || currLevel == null || currLevel === 0) {
|
||||||
|
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce the level first, so we get the right cost
|
||||||
|
--this.upgrades[upgName];
|
||||||
|
const cost = upg.getCost(currLevel);
|
||||||
|
this.hashes += cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCapacity(p: IPlayer): void {
|
||||||
|
if (p.hacknetNodes.length <= 0) {
|
||||||
|
this.capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the Player's `hacknetNodes` property actually holds Hacknet Servers
|
||||||
|
const ip: string = <string>p.hacknetNodes[0];
|
||||||
|
if (typeof ip !== "string") {
|
||||||
|
this.capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hserver = <HacknetServer>AllServers[ip];
|
||||||
|
if (!(hserver instanceof HacknetServer)) {
|
||||||
|
this.capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let total: number = 0;
|
||||||
|
for (let i = 0; i < p.hacknetNodes.length; ++i) {
|
||||||
|
const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
|
||||||
|
total += h.hashCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.capacity = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns boolean indicating whether or not the upgrade was successfully purchased
|
||||||
|
* Note that this does NOT actually implement the effect
|
||||||
|
*/
|
||||||
|
upgrade(upgName: string): boolean {
|
||||||
|
const upg = HashUpgrades[upgName];
|
||||||
|
const currLevel = this.upgrades[upgName];
|
||||||
|
if (upg == null || currLevel == null) {
|
||||||
|
console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cost = upg.getCost(currLevel);
|
||||||
|
|
||||||
|
if (this.hashes < cost) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hashes -= cost;
|
||||||
|
++this.upgrades[upgName];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Serialize the current object to a JSON save state.
|
||||||
|
toJSON(): any {
|
||||||
|
return Generic_toJSON("HashManager", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reviver.constructors.HashManager = HashManager;
|
48
src/Hacknet/HashUpgrade.ts
Normal file
48
src/Hacknet/HashUpgrade.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Object representing an upgrade that can be purchased with hashes
|
||||||
|
*/
|
||||||
|
export interface IConstructorParams {
|
||||||
|
costPerLevel: number;
|
||||||
|
desc: string;
|
||||||
|
hasTargetServer?: boolean;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HashUpgrade {
|
||||||
|
/**
|
||||||
|
* Base cost for this upgrade. Every time the upgrade is purchased,
|
||||||
|
* its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.)
|
||||||
|
*/
|
||||||
|
costPerLevel: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of what the upgrade does
|
||||||
|
*/
|
||||||
|
desc: string = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean indicating that this upgrade's effect affects a single server,
|
||||||
|
* the "target" server
|
||||||
|
*/
|
||||||
|
hasTargetServer: boolean = false;
|
||||||
|
|
||||||
|
// Name of upgrade
|
||||||
|
name: string = "";
|
||||||
|
|
||||||
|
// Generic value used to indicate the potency/amount of this upgrade's effect
|
||||||
|
// The meaning varies between different upgrades
|
||||||
|
value: number = 0;
|
||||||
|
|
||||||
|
constructor(p: IConstructorParams) {
|
||||||
|
this.costPerLevel = p.costPerLevel;
|
||||||
|
this.desc = p.desc;
|
||||||
|
this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false;
|
||||||
|
this.name = p.name;
|
||||||
|
this.value = p.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCost(levels: number): number {
|
||||||
|
return Math.round((levels + 1) * this.costPerLevel);
|
||||||
|
}
|
||||||
|
}
|
18
src/Hacknet/HashUpgrades.ts
Normal file
18
src/Hacknet/HashUpgrades.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Map of all Hash Upgrades
|
||||||
|
* Key = Hash name, Value = HashUpgrade object
|
||||||
|
*/
|
||||||
|
import { HashUpgrade,
|
||||||
|
IConstructorParams } from "./HashUpgrade";
|
||||||
|
import { HashUpgradesMetadata } from "./data/HashUpgradesMetadata";
|
||||||
|
import { IMap } from "../types";
|
||||||
|
|
||||||
|
export const HashUpgrades: IMap<HashUpgrade> = {};
|
||||||
|
|
||||||
|
function createHashUpgrade(p: IConstructorParams) {
|
||||||
|
HashUpgrades[p.name] = new HashUpgrade(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const metadata of HashUpgradesMetadata) {
|
||||||
|
createHashUpgrade(metadata);
|
||||||
|
}
|
16
src/Hacknet/IHacknetNode.ts
Normal file
16
src/Hacknet/IHacknetNode.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Interface for a Hacknet Node. Implemented by both a basic Hacknet Node,
|
||||||
|
// and the upgraded Hacknet Server in BitNode-9
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
|
||||||
|
export interface IHacknetNode {
|
||||||
|
cores: number;
|
||||||
|
level: number;
|
||||||
|
onlineTimeSeconds: number;
|
||||||
|
|
||||||
|
calculateCoreUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||||
|
calculateLevelUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||||
|
calculateRamUpgradeCost: (levels: number, p: IPlayer) => number;
|
||||||
|
purchaseCoreUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||||
|
purchaseLevelUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||||
|
purchaseRamUpgrade: (levels: number, p: IPlayer) => boolean;
|
||||||
|
}
|
64
src/Hacknet/data/HashUpgradesMetadata.ts
Normal file
64
src/Hacknet/data/HashUpgradesMetadata.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Metadata used to construct all Hash Upgrades
|
||||||
|
import { IConstructorParams } from "../HashUpgrade";
|
||||||
|
|
||||||
|
export const HashUpgradesMetadata: IConstructorParams[] = [
|
||||||
|
{
|
||||||
|
costPerLevel: 2,
|
||||||
|
desc: "Sell hashes for $1m",
|
||||||
|
name: "Sell for Money",
|
||||||
|
value: 1e6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 100,
|
||||||
|
desc: "Sell hashes for $1b in Corporation funds",
|
||||||
|
name: "Sell for Corporation Funds",
|
||||||
|
value: 1e9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 100,
|
||||||
|
desc: "Use hashes to decrease the minimum security of a single server by 5%. " +
|
||||||
|
"Note that a server's minimum security cannot go below 1.",
|
||||||
|
hasTargetServer: true,
|
||||||
|
name: "Reduce Minimum Security",
|
||||||
|
value: 0.95,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 100,
|
||||||
|
desc: "Use hashes to increase the maximum amount of money on a single server by 5%",
|
||||||
|
hasTargetServer: true,
|
||||||
|
name: "Increase Maximum Money",
|
||||||
|
value: 1.05,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 100,
|
||||||
|
desc: "Use hashes to improve the experience earned when studying at a university. " +
|
||||||
|
"This effect persists until you install Augmentations",
|
||||||
|
name: "Improve Studying",
|
||||||
|
value: 20, // Improves studying by value%
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 100,
|
||||||
|
desc: "Use hashes to improve the experience earned when training at the gym. This effect " +
|
||||||
|
"persists until you install Augmentations",
|
||||||
|
name: "Improve Gym Training",
|
||||||
|
value: 20, // Improves training by value%
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 250,
|
||||||
|
desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries",
|
||||||
|
name: "Exchange for Corporation Research",
|
||||||
|
value: 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 250,
|
||||||
|
desc: "Exchange hashes for 100 Bladeburner Rank",
|
||||||
|
name: "Exchange for Bladeburner Rank",
|
||||||
|
value: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
costPerLevel: 200,
|
||||||
|
desc: "Generate a random Coding Contract on your home computer",
|
||||||
|
name: "Generate Coding Contract",
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
]
|
56
src/Hacknet/ui/GeneralInfo.jsx
Normal file
56
src/Hacknet/ui/GeneralInfo.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Hacknet Node UI
|
||||||
|
*
|
||||||
|
* Displays general information about Hacknet Nodes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { hasHacknetServers } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
export class GeneralInfo extends React.Component {
|
||||||
|
getSecondParagraph() {
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
return `Here, you can purchase a Hacknet Server, an upgraded version of the Hacknet Node. ` +
|
||||||
|
`Hacknet Servers will perform computations and operations on the network, earning ` +
|
||||||
|
`you hashes. Hashes can be spent on a variety of different upgrades.`;
|
||||||
|
} else {
|
||||||
|
return `Here, you can purchase a Hacknet Node, a specialized machine that can connect ` +
|
||||||
|
`and contribute its resources to the Hacknet networ. This allows you to take ` +
|
||||||
|
`a small percentage of profits from hacks performed on the network. Essentially, ` +
|
||||||
|
`you are renting out your Node's computing power.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getThirdParagraph() {
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
return `Hacknet Servers can also be used as servers to run scripts. However, running scripts ` +
|
||||||
|
`on a server will reduce its hash rate (hashes generated per second). A Hacknet Server's hash ` +
|
||||||
|
`rate will be reduced by the percentage of RAM that is being used by that Server to run ` +
|
||||||
|
`scripts.`
|
||||||
|
} else {
|
||||||
|
return `Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node ` +
|
||||||
|
`can be upgraded in order to increase its computing power and thereby increase ` +
|
||||||
|
`the profit you earn from it.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className={"hacknet-general-info"}>
|
||||||
|
The Hacknet is a global, decentralized network of machines. It is used by
|
||||||
|
hackers all around the world to anonymously share computing power and
|
||||||
|
perform distributed cyberattacks without the fear of being traced.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p className={"hacknet-general-info"}>
|
||||||
|
{this.getSecondParagraph()}
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p className={"hacknet-general-info"}>
|
||||||
|
{this.getThirdParagraph()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
153
src/Hacknet/ui/HacknetNode.jsx
Normal file
153
src/Hacknet/ui/HacknetNode.jsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Hacknet Node UI.
|
||||||
|
* This Component displays the panel for a single Hacknet Node
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { HacknetNodeMaxLevel,
|
||||||
|
HacknetNodeMaxRam,
|
||||||
|
HacknetNodeMaxCores } from "../HacknetNode";
|
||||||
|
import { getMaxNumberLevelUpgrades,
|
||||||
|
getMaxNumberRamUpgrades,
|
||||||
|
getMaxNumberCoreUpgrades } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export class HacknetNode extends React.Component {
|
||||||
|
render() {
|
||||||
|
const node = this.props.node;
|
||||||
|
const purchaseMult = this.props.purchaseMultiplier;
|
||||||
|
const recalculate = this.props.recalculate;
|
||||||
|
|
||||||
|
// Upgrade Level Button
|
||||||
|
let upgradeLevelText, upgradeLevelClass;
|
||||||
|
if (node.level >= HacknetNodeMaxLevel) {
|
||||||
|
upgradeLevelText = "MAX LEVEL";
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetNodeMaxLevel - node.level;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||||
|
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||||
|
if (Player.money.lt(upgradeLevelCost)) {
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeLevelClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeLevelOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeMaxLevel);
|
||||||
|
}
|
||||||
|
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let upgradeRamText, upgradeRamClass;
|
||||||
|
if (node.ram >= HacknetNodeMaxRam) {
|
||||||
|
upgradeRamText = "MAX RAM";
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = Math.round(Math.log2(HacknetNodeMaxRam / node.ram));
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||||
|
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||||
|
if (Player.money.lt(upgradeRamCost)) {
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeRamClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeRamOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeMaxRam);
|
||||||
|
}
|
||||||
|
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let upgradeCoresText, upgradeCoresClass;
|
||||||
|
if (node.cores >= HacknetNodeMaxCores) {
|
||||||
|
upgradeCoresText = "MAX CORES";
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetNodeMaxCores - node.cores;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||||
|
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||||
|
if (Player.money.lt(upgradeCoreCost)) {
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeCoresClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeCoresOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeMaxCores);
|
||||||
|
}
|
||||||
|
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={"hacknet-node"}>
|
||||||
|
<div className={"hacknet-node-container"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Node name:</p>
|
||||||
|
<span className={"text"}>{node.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Production:</p>
|
||||||
|
<span className={"text money-gold"}>
|
||||||
|
{numeralWrapper.formatMoney(node.totalMoneyGenerated)} ({numeralWrapper.formatMoney(node.moneyGainRatePerSecond)} / sec)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span>
|
||||||
|
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
|
||||||
|
{upgradeLevelText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>RAM:</p><span className={"text upgradable-info"}>{node.ram}GB</span>
|
||||||
|
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
|
||||||
|
{upgradeRamText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span>
|
||||||
|
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
|
||||||
|
{upgradeCoresText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
200
src/Hacknet/ui/HacknetServer.jsx
Normal file
200
src/Hacknet/ui/HacknetServer.jsx
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Hacknet Node UI.
|
||||||
|
* This Component displays the panel for a single Hacknet Node
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { HacknetServerMaxLevel,
|
||||||
|
HacknetServerMaxRam,
|
||||||
|
HacknetServerMaxCores,
|
||||||
|
HacknetServerMaxCache } from "../HacknetServer";
|
||||||
|
import { getMaxNumberLevelUpgrades,
|
||||||
|
getMaxNumberRamUpgrades,
|
||||||
|
getMaxNumberCoreUpgrades,
|
||||||
|
getMaxNumberCacheUpgrades } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export class HacknetServer extends React.Component {
|
||||||
|
render() {
|
||||||
|
const node = this.props.node;
|
||||||
|
const purchaseMult = this.props.purchaseMultiplier;
|
||||||
|
const recalculate = this.props.recalculate;
|
||||||
|
|
||||||
|
// Upgrade Level Button
|
||||||
|
let upgradeLevelText, upgradeLevelClass;
|
||||||
|
if (node.level >= HacknetServerMaxLevel) {
|
||||||
|
upgradeLevelText = "MAX LEVEL";
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetServerMaxLevel - node.level;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player);
|
||||||
|
upgradeLevelText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeLevelCost)}`;
|
||||||
|
if (Player.money.lt(upgradeLevelCost)) {
|
||||||
|
upgradeLevelClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeLevelClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeLevelOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerMaxLevel);
|
||||||
|
}
|
||||||
|
node.purchaseLevelUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade RAM Button
|
||||||
|
let upgradeRamText, upgradeRamClass;
|
||||||
|
if (node.maxRam >= HacknetServerMaxRam) {
|
||||||
|
upgradeRamText = "MAX RAM";
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = Math.round(Math.log2(HacknetServerMaxRam / node.maxRam));
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player);
|
||||||
|
upgradeRamText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeRamCost)}`;
|
||||||
|
if (Player.money.lt(upgradeRamCost)) {
|
||||||
|
upgradeRamClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeRamClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeRamOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerMaxRam);
|
||||||
|
}
|
||||||
|
node.purchaseRamUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade Cores Button
|
||||||
|
let upgradeCoresText, upgradeCoresClass;
|
||||||
|
if (node.cores >= HacknetServerMaxCores) {
|
||||||
|
upgradeCoresText = "MAX CORES";
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetServerMaxCores - node.cores;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player);
|
||||||
|
upgradeCoresText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCoreCost)}`;
|
||||||
|
if (Player.money.lt(upgradeCoreCost)) {
|
||||||
|
upgradeCoresClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeCoresClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeCoresOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerMaxCores);
|
||||||
|
}
|
||||||
|
node.purchaseCoreUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade Cache button
|
||||||
|
let upgradeCacheText, upgradeCacheClass;
|
||||||
|
if (node.cache >= HacknetServerMaxCache) {
|
||||||
|
upgradeCacheText = "MAX CACHE";
|
||||||
|
upgradeCacheClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
let multiplier = 0;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
multiplier = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||||
|
} else {
|
||||||
|
const levelsToMax = HacknetServerMaxCache - node.cache;
|
||||||
|
multiplier = Math.min(levelsToMax, purchaseMult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier);
|
||||||
|
upgradeCacheText = `Upgrade x${multiplier} - ${numeralWrapper.formatMoney(upgradeCacheCost)}`;
|
||||||
|
if (Player.money.lt(upgradeCacheCost)) {
|
||||||
|
upgradeCacheClass = "std-button-disabled";
|
||||||
|
} else {
|
||||||
|
upgradeCacheClass = "std-button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const upgradeCacheOnClick = () => {
|
||||||
|
let numUpgrades = purchaseMult;
|
||||||
|
if (purchaseMult === "MAX") {
|
||||||
|
numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerMaxCache);
|
||||||
|
}
|
||||||
|
node.purchaseCacheUpgrade(numUpgrades, Player);
|
||||||
|
recalculate();
|
||||||
|
Player.hashManager.updateCapacity(Player);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={"hacknet-node"}>
|
||||||
|
<div className={"hacknet-node-container"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Node name:</p>
|
||||||
|
<span className={"text"}>{node.hostname}</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Production:</p>
|
||||||
|
<span className={"text money-gold"}>
|
||||||
|
{numeralWrapper.formatBigNumber(node.totalHashesGenerated)} ({numeralWrapper.formatBigNumber(node.hashRate)} / sec)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Hash Capacity:</p>
|
||||||
|
<span className={"text"}>{node.hashCapacity}</span>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span>
|
||||||
|
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
|
||||||
|
{upgradeLevelText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>RAM:</p><span className={"text upgradable-info"}>{node.maxRam}GB</span>
|
||||||
|
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
|
||||||
|
{upgradeRamText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span>
|
||||||
|
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
|
||||||
|
{upgradeCoresText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={"row"}>
|
||||||
|
<p>Cache Level:</p><span className={"text upgradable-info"}>{node.cache}</span>
|
||||||
|
<button className={upgradeCacheClass} onClick={upgradeCacheOnClick}>
|
||||||
|
{upgradeCacheText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
137
src/Hacknet/ui/HashUpgradePopup.jsx
Normal file
137
src/Hacknet/ui/HashUpgradePopup.jsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Create the pop-up for purchasing upgrades with hashes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { purchaseHashUpgrade } from "../HacknetHelpers";
|
||||||
|
import { HashManager } from "../HashManager";
|
||||||
|
import { HashUpgrades } from "../HashUpgrades";
|
||||||
|
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
import { AllServers } from "../../Server/AllServers";
|
||||||
|
import { Server } from "../../Server/Server";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
import { removePopup } from "../../ui/React/createPopup";
|
||||||
|
import { ServerDropdown,
|
||||||
|
ServerType } from "../../ui/React/ServerDropdown"
|
||||||
|
|
||||||
|
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||||
|
|
||||||
|
class HashUpgrade extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedServer: "foodnstuff",
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeTargetServer = this.changeTargetServer.bind(this);
|
||||||
|
this.purchase = this.purchase.bind(this, this.props.hashManager, this.props.upg);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTargetServer(e) {
|
||||||
|
this.setState({
|
||||||
|
selectedServer: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
purchase(hashManager, upg) {
|
||||||
|
const canPurchase = hashManager.hashes >= hashManager.getUpgradeCost(upg.name);
|
||||||
|
if (canPurchase) {
|
||||||
|
const res = purchaseHashUpgrade(upg.name, this.state.selectedServer);
|
||||||
|
if (res) {
|
||||||
|
this.props.rerender();
|
||||||
|
} else {
|
||||||
|
dialogBoxCreate("Failed to purchase upgrade. This may be because you do not have enough hashes, " +
|
||||||
|
"or because you do not have access to the feature this upgrade affects.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const hashManager = this.props.hashManager;
|
||||||
|
const upg = this.props.upg;
|
||||||
|
const cost = hashManager.getUpgradeCost(upg.name);
|
||||||
|
|
||||||
|
// Purchase button
|
||||||
|
const canPurchase = hashManager.hashes >= cost;
|
||||||
|
const btnClass = canPurchase ? "std-button" : "std-button-disabled";
|
||||||
|
|
||||||
|
// We'll reuse a Bladeburner css class
|
||||||
|
return (
|
||||||
|
<div className={"bladeburner-action"}>
|
||||||
|
<h2>{upg.name}</h2>
|
||||||
|
<p>Cost: {numeralWrapper.format(cost, "0.000a")}</p>
|
||||||
|
<p>{upg.desc}</p>
|
||||||
|
<button className={btnClass} onClick={this.purchase}>
|
||||||
|
Purchase
|
||||||
|
</button>
|
||||||
|
{
|
||||||
|
upg.hasTargetServer &&
|
||||||
|
<ServerDropdown
|
||||||
|
serverType={ServerType.Foreign}
|
||||||
|
onChange={this.changeTargetServer}
|
||||||
|
style={{margin: "5px"}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HashUpgradePopup extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.closePopup = this.closePopup.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
totalHashes: Player.hashManager.hashes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.interval = setInterval(() => this.tick(), 1e3);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePopup() {
|
||||||
|
removePopup(this.props.popupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
this.setState({
|
||||||
|
totalHashes: Player.hashManager.hashes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const rerender = this.props.rerender;
|
||||||
|
|
||||||
|
const hashManager = Player.hashManager;
|
||||||
|
if (!(hashManager instanceof HashManager)) {
|
||||||
|
throw new Error(`Player does not have a HashManager)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeElems = Object.keys(HashUpgrades).map((upgName) => {
|
||||||
|
const upg = HashUpgrades[upgName];
|
||||||
|
return <HashUpgrade upg={upg} hashManager={hashManager} key={upg.name} rerender={rerender} />
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button className={"std-button"} onClick={this.closePopup}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<p>Spend your hashes on a variety of different upgrades</p>
|
||||||
|
<p>Hashes: {numeralWrapper.formatBigNumber(this.state.totalHashes)}</p>
|
||||||
|
{upgradeElems}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
41
src/Hacknet/ui/MultiplierButtons.jsx
Normal file
41
src/Hacknet/ui/MultiplierButtons.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the Multiplier buttons on the Hacknet page.
|
||||||
|
* These buttons let the player control how many Nodes/Upgrades they're
|
||||||
|
* purchasing when using the UI (x1, x5, x10, MAX)
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { PurchaseMultipliers } from "./Root";
|
||||||
|
|
||||||
|
function MultiplierButton(props) {
|
||||||
|
return (
|
||||||
|
<button className={props.className} onClick={props.onClick}>{props.text}</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MultiplierButtons(props) {
|
||||||
|
if (props.purchaseMultiplier == null) {
|
||||||
|
throw new Error(`MultiplierButtons constructed without required props`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mults = ["x1", "x5", "x10", "MAX"];
|
||||||
|
const onClicks = props.onClicks;
|
||||||
|
const buttons = [];
|
||||||
|
for (let i = 0; i < mults.length; ++i) {
|
||||||
|
const mult = mults[i];
|
||||||
|
const btnProps = {
|
||||||
|
className: props.purchaseMultiplier === PurchaseMultipliers[mult] ? "std-button-disabled" : "std-button",
|
||||||
|
key: mult,
|
||||||
|
onClick: onClicks[i],
|
||||||
|
text: mult,
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push(<MultiplierButton {...btnProps} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span id={"hacknet-nodes-multipliers"}>
|
||||||
|
{buttons}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
51
src/Hacknet/ui/PlayerInfo.jsx
Normal file
51
src/Hacknet/ui/PlayerInfo.jsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* React Component for displaying Player info and stats on the Hacknet Node UI.
|
||||||
|
* This includes:
|
||||||
|
* - Player's money
|
||||||
|
* - Player's production from Hacknet Nodes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { hasHacknetServers } from "../HacknetHelpers";
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export function PlayerInfo(props) {
|
||||||
|
const hasServers = hasHacknetServers();
|
||||||
|
|
||||||
|
let prod;
|
||||||
|
if (hasServers) {
|
||||||
|
prod = numeralWrapper.format(props.totalProduction, "0.000a") + " hashes / sec";
|
||||||
|
} else {
|
||||||
|
prod = numeralWrapper.formatMoney(props.totalProduction) + " / sec";
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashInfo;
|
||||||
|
if (hasServers) {
|
||||||
|
hashInfo = numeralWrapper.format(Player.hashManager.hashes, "0.000a") + " / " +
|
||||||
|
numeralWrapper.format(Player.hashManager.capacity, "0.000a");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p id={"hacknet-nodes-money"}>
|
||||||
|
<span>Money:</span>
|
||||||
|
<span className={"money-gold"}>{numeralWrapper.formatMoney(Player.money.toNumber())}</span><br />
|
||||||
|
|
||||||
|
{
|
||||||
|
hasServers &&
|
||||||
|
<span>Hashes:</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasServers &&
|
||||||
|
<span className={"money-gold"}>{hashInfo}</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hasServers &&
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>Total Hacknet Node Production:</span>
|
||||||
|
<span className={"money-gold"}>{prod}</span>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
40
src/Hacknet/ui/PurchaseButton.jsx
Normal file
40
src/Hacknet/ui/PurchaseButton.jsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* React Component for the button that is used to purchase new Hacknet Nodes
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { hasHacknetServers,
|
||||||
|
hasMaxNumberHacknetServers } from "../HacknetHelpers";
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
|
export function PurchaseButton(props) {
|
||||||
|
if (props.multiplier == null || props.onClick == null) {
|
||||||
|
throw new Error(`PurchaseButton constructed without required props`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cost = props.cost;
|
||||||
|
let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled";
|
||||||
|
let text;
|
||||||
|
let style = null;
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
if (hasMaxNumberHacknetServers()) {
|
||||||
|
className = "std-button-disabled";
|
||||||
|
text = "Hacknet Server limit reached";
|
||||||
|
style = {color: "red"};
|
||||||
|
} else {
|
||||||
|
text = `Purchase Hacknet Server - ${numeralWrapper.formatMoney(cost)}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = `Purchase Hacknet Node - ${numeralWrapper.formatMoney(cost)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={className}
|
||||||
|
onClick={props.onClick}
|
||||||
|
style={style}>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
155
src/Hacknet/ui/Root.jsx
Normal file
155
src/Hacknet/ui/Root.jsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* Root React Component for the Hacknet Node UI
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { GeneralInfo } from "./GeneralInfo";
|
||||||
|
import { HacknetNode } from "./HacknetNode";
|
||||||
|
import { HacknetServer } from "./HacknetServer";
|
||||||
|
import { HashUpgradePopup } from "./HashUpgradePopup";
|
||||||
|
import { MultiplierButtons } from "./MultiplierButtons";
|
||||||
|
import { PlayerInfo } from "./PlayerInfo";
|
||||||
|
import { PurchaseButton } from "./PurchaseButton";
|
||||||
|
|
||||||
|
import { getCostOfNextHacknetNode,
|
||||||
|
getCostOfNextHacknetServer,
|
||||||
|
hasHacknetServers,
|
||||||
|
purchaseHacknet } from "../HacknetHelpers";
|
||||||
|
|
||||||
|
import { Player } from "../../Player";
|
||||||
|
import { AllServers } from "../../Server/AllServers";
|
||||||
|
|
||||||
|
import { createPopup } from "../../ui/React/createPopup";
|
||||||
|
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
|
||||||
|
|
||||||
|
export const PurchaseMultipliers = Object.freeze({
|
||||||
|
"x1": 1,
|
||||||
|
"x5": 5,
|
||||||
|
"x10": 10,
|
||||||
|
"MAX": "MAX",
|
||||||
|
});
|
||||||
|
|
||||||
|
export class HacknetRoot extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
purchaseMultiplier: PurchaseMultipliers.x1,
|
||||||
|
totalProduction: 0, // Total production ($ / s) of Hacknet Nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.recalculateTotalProduction();
|
||||||
|
}
|
||||||
|
|
||||||
|
createHashUpgradesPopup() {
|
||||||
|
const id = "hacknet-server-hash-upgrades-popup";
|
||||||
|
createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup });
|
||||||
|
}
|
||||||
|
|
||||||
|
recalculateTotalProduction() {
|
||||||
|
let total = 0;
|
||||||
|
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
const hserver = AllServers[Player.hacknetNodes[i]];
|
||||||
|
if (hserver) {
|
||||||
|
total += hserver.hashRate;
|
||||||
|
} else {
|
||||||
|
console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
total += Player.hacknetNodes[i].moneyGainRatePerSecond;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
totalProduction: total,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setPurchaseMultiplier(mult) {
|
||||||
|
this.setState({
|
||||||
|
purchaseMultiplier: mult,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// Cost to purchase a new Hacknet Node
|
||||||
|
let purchaseCost;
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
purchaseCost = getCostOfNextHacknetServer();
|
||||||
|
} else {
|
||||||
|
purchaseCost = getCostOfNextHacknetNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// onClick event handler for purchase button
|
||||||
|
const purchaseOnClick = () => {
|
||||||
|
if (purchaseHacknet() >= 0) {
|
||||||
|
this.recalculateTotalProduction();
|
||||||
|
Player.hashManager.updateCapacity(Player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onClick event handlers for purchase multiplier buttons
|
||||||
|
const purchaseMultiplierOnClicks = [
|
||||||
|
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1),
|
||||||
|
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x5),
|
||||||
|
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x10),
|
||||||
|
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.MAX),
|
||||||
|
];
|
||||||
|
|
||||||
|
// HacknetNode components
|
||||||
|
const nodes = Player.hacknetNodes.map((node) => {
|
||||||
|
if (hasHacknetServers()) {
|
||||||
|
const hserver = AllServers[node];
|
||||||
|
if (hserver == null) {
|
||||||
|
throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<HacknetServer
|
||||||
|
key={hserver.hostname}
|
||||||
|
node={hserver}
|
||||||
|
purchaseMultiplier={this.state.purchaseMultiplier}
|
||||||
|
recalculate={this.recalculateTotalProduction.bind(this)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<HacknetNode
|
||||||
|
key={node.name}
|
||||||
|
node={node}
|
||||||
|
purchaseMultiplier={this.state.purchaseMultiplier}
|
||||||
|
recalculate={this.recalculateTotalProduction.bind(this)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Hacknet Nodes</h1>
|
||||||
|
<GeneralInfo />
|
||||||
|
|
||||||
|
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={purchaseOnClick} />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div id={"hacknet-nodes-money-multipliers-div"}>
|
||||||
|
<PlayerInfo totalProduction={this.state.totalProduction} />
|
||||||
|
<MultiplierButtons onClicks={purchaseMultiplierOnClicks} purchaseMultiplier={this.state.purchaseMultiplier} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
hasHacknetServers() &&
|
||||||
|
<button className={"std-button"} onClick={this.createHashUpgradesPopup} style={{display: "block"}}>
|
||||||
|
{"Spend Hashes on Upgrades"}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ul id={"hacknet-nodes-list"}>{nodes}</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,694 +0,0 @@
|
|||||||
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
|
|
||||||
import { CONSTANTS } from "./Constants";
|
|
||||||
import { Engine } from "./engine";
|
|
||||||
import {iTutorialSteps, iTutorialNextStep,
|
|
||||||
ITutorial} from "./InteractiveTutorial";
|
|
||||||
import {Player} from "./Player";
|
|
||||||
import {Page, routing} from "./ui/navigationTracking";
|
|
||||||
import { numeralWrapper } from "./ui/numeralFormat";
|
|
||||||
|
|
||||||
import {dialogBoxCreate} from "../utils/DialogBox";
|
|
||||||
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
|
|
||||||
import {Reviver, Generic_toJSON,
|
|
||||||
Generic_fromJSON} from "../utils/JSONReviver";
|
|
||||||
import {createElement} from "../utils/uiHelpers/createElement";
|
|
||||||
import {getElementById} from "../utils/uiHelpers/getElementById";
|
|
||||||
|
|
||||||
// Stores total money gain rate from all of the player's Hacknet Nodes
|
|
||||||
let TotalHacknetNodeProduction = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the inner text of the specified HTML element if it is different from what currently exists.
|
|
||||||
* @param {string} elementId The HTML ID to find the first instance of.
|
|
||||||
* @param {string} text The inner text that should be set.
|
|
||||||
*/
|
|
||||||
function updateText(elementId, text) {
|
|
||||||
var el = getElementById(elementId);
|
|
||||||
if (el.innerText != text) {
|
|
||||||
el.innerText = text;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* HacknetNode.js */
|
|
||||||
function hacknetNodesInit() {
|
|
||||||
var performMapping = function(x) {
|
|
||||||
getElementById("hacknet-nodes-" + x.id + "-multiplier")
|
|
||||||
.addEventListener("click", function() {
|
|
||||||
hacknetNodePurchaseMultiplier = x.multiplier;
|
|
||||||
updateHacknetNodesMultiplierButtons();
|
|
||||||
updateHacknetNodesContent();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var mappings = [
|
|
||||||
{ id: "1x", multiplier: 1 },
|
|
||||||
{ id: "5x", multiplier: 5 },
|
|
||||||
{ id: "10x", multiplier: 10 },
|
|
||||||
{ id: "max", multiplier: 0 }
|
|
||||||
];
|
|
||||||
for (var elem of mappings) {
|
|
||||||
// Encapsulate in a function so that the appropriate scope is kept in the click handler.
|
|
||||||
performMapping(elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", hacknetNodesInit, false);
|
|
||||||
|
|
||||||
function HacknetNode(name) {
|
|
||||||
this.level = 1;
|
|
||||||
this.ram = 1; //GB
|
|
||||||
this.cores = 1;
|
|
||||||
|
|
||||||
this.name = name;
|
|
||||||
|
|
||||||
this.totalMoneyGenerated = 0;
|
|
||||||
this.onlineTimeSeconds = 0;
|
|
||||||
|
|
||||||
this.moneyGainRatePerSecond = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.updateMoneyGainRate = function() {
|
|
||||||
//How much extra $/s is gained per level
|
|
||||||
var gainPerLevel = CONSTANTS.HacknetNodeMoneyGainPerLevel;
|
|
||||||
|
|
||||||
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
|
||||||
Math.pow(1.035, this.ram-1) *
|
|
||||||
((this.cores + 5) / 6) *
|
|
||||||
Player.hacknet_node_money_mult *
|
|
||||||
BitNodeMultipliers.HacknetNodeMoney;
|
|
||||||
if (isNaN(this.moneyGainRatePerSecond)) {
|
|
||||||
this.moneyGainRatePerSecond = 0;
|
|
||||||
dialogBoxCreate("Error in calculating Hacknet Node production. Please report to game developer");
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTotalHacknetProduction();
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.calculateLevelUpgradeCost = function(levels=1) {
|
|
||||||
levels = Math.round(levels);
|
|
||||||
if (isNaN(levels) || levels < 1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.level >= CONSTANTS.HacknetNodeMaxLevel) {
|
|
||||||
return Infinity;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mult = CONSTANTS.HacknetNodeUpgradeLevelMult;
|
|
||||||
var totalMultiplier = 0; //Summed
|
|
||||||
var currLevel = this.level;
|
|
||||||
for (var i = 0; i < levels; ++i) {
|
|
||||||
totalMultiplier += Math.pow(mult, currLevel);
|
|
||||||
++currLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CONSTANTS.BaseCostForHacknetNode / 2 * totalMultiplier * Player.hacknet_node_level_cost_mult;
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.purchaseLevelUpgrade = function(levels=1) {
|
|
||||||
levels = Math.round(levels);
|
|
||||||
var cost = this.calculateLevelUpgradeCost(levels);
|
|
||||||
if (isNaN(cost) || levels < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If we're at max level, return false
|
|
||||||
if (this.level >= CONSTANTS.HacknetNodeMaxLevel) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If the number of specified upgrades would exceed the max level, calculate
|
|
||||||
//the maximum number of upgrades and use that
|
|
||||||
if (this.level + levels > CONSTANTS.HacknetNodeMaxLevel) {
|
|
||||||
var diff = Math.max(0, CONSTANTS.HacknetNodeMaxLevel - this.level);
|
|
||||||
return this.purchaseLevelUpgrade(diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Player.money.lt(cost)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player.loseMoney(cost);
|
|
||||||
this.level = Math.round(this.level + levels); //Just in case of floating point imprecision
|
|
||||||
this.updateMoneyGainRate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.calculateRamUpgradeCost = function(levels=1) {
|
|
||||||
levels = Math.round(levels);
|
|
||||||
if (isNaN(levels) || levels < 1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ram >= CONSTANTS.HacknetNodeMaxRam) {
|
|
||||||
return Infinity;
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalCost = 0;
|
|
||||||
let numUpgrades = Math.round(Math.log2(this.ram));
|
|
||||||
let currentRam = this.ram;
|
|
||||||
|
|
||||||
for (let i = 0; i < levels; ++i) {
|
|
||||||
let baseCost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHacknetNode;
|
|
||||||
let mult = Math.pow(CONSTANTS.HacknetNodeUpgradeRamMult, numUpgrades);
|
|
||||||
|
|
||||||
totalCost += (baseCost * mult);
|
|
||||||
|
|
||||||
currentRam *= 2;
|
|
||||||
++numUpgrades;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalCost *= Player.hacknet_node_ram_cost_mult
|
|
||||||
return totalCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.purchaseRamUpgrade = function(levels=1) {
|
|
||||||
levels = Math.round(levels);
|
|
||||||
var cost = this.calculateRamUpgradeCost(levels);
|
|
||||||
if (isNaN(cost) || levels < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if we're already at max
|
|
||||||
if (this.ram >= CONSTANTS.HacknetNodeMaxRam) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If the number of specified upgrades would exceed the max RAM, calculate the
|
|
||||||
//max possible number of upgrades and use that
|
|
||||||
if (this.ram * Math.pow(2, levels) > CONSTANTS.HacknetNodeMaxRam) {
|
|
||||||
var diff = Math.max(0, Math.log2(Math.round(CONSTANTS.HacknetNodeMaxRam / this.ram)));
|
|
||||||
return this.purchaseRamUpgrade(diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Player.money.lt(cost)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player.loseMoney(cost);
|
|
||||||
for (let i = 0; i < levels; ++i) {
|
|
||||||
this.ram *= 2; //Ram is always doubled
|
|
||||||
}
|
|
||||||
this.ram = Math.round(this.ram); //Handle any floating point precision issues
|
|
||||||
this.updateMoneyGainRate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.calculateCoreUpgradeCost = function(levels=1) {
|
|
||||||
levels = Math.round(levels);
|
|
||||||
if (isNaN(levels) || levels < 1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.cores >= CONSTANTS.HacknetNodeMaxCores) {
|
|
||||||
return Infinity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coreBaseCost = CONSTANTS.BaseCostForHacknetNodeCore;
|
|
||||||
const mult = CONSTANTS.HacknetNodeUpgradeCoreMult;
|
|
||||||
let totalCost = 0;
|
|
||||||
let currentCores = this.cores;
|
|
||||||
for (let i = 0; i < levels; ++i) {
|
|
||||||
totalCost += (coreBaseCost * Math.pow(mult, currentCores-1));
|
|
||||||
++currentCores;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalCost *= Player.hacknet_node_core_cost_mult;
|
|
||||||
|
|
||||||
return totalCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.prototype.purchaseCoreUpgrade = function(levels=1) {
|
|
||||||
levels = Math.round(levels);
|
|
||||||
var cost = this.calculateCoreUpgradeCost(levels);
|
|
||||||
if (isNaN(cost) || levels < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fail if we're already at max
|
|
||||||
if (this.cores >= CONSTANTS.HacknetNodeMaxCores) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If the specified number of upgrades would exceed the max Cores, calculate
|
|
||||||
//the max possible number of upgrades and use that
|
|
||||||
if (this.cores + levels > CONSTANTS.HacknetNodeMaxCores) {
|
|
||||||
var diff = Math.max(0, CONSTANTS.HacknetNodeMaxCores - this.cores);
|
|
||||||
return this.purchaseCoreUpgrade(diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Player.money.lt(cost)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player.loseMoney(cost);
|
|
||||||
this.cores = Math.round(this.cores + levels); //Just in case of floating point imprecision
|
|
||||||
this.updateMoneyGainRate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Saving and loading HackNets */
|
|
||||||
HacknetNode.prototype.toJSON = function() {
|
|
||||||
return Generic_toJSON("HacknetNode", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
HacknetNode.fromJSON = function(value) {
|
|
||||||
return Generic_fromJSON(HacknetNode, value.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Reviver.constructors.HacknetNode = HacknetNode;
|
|
||||||
|
|
||||||
function purchaseHacknet() {
|
|
||||||
/* INTERACTIVE TUTORIAL */
|
|
||||||
if (ITutorial.isRunning) {
|
|
||||||
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
|
|
||||||
iTutorialNextStep();
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* END INTERACTIVE TUTORIAL */
|
|
||||||
|
|
||||||
var cost = getCostOfNextHacknetNode();
|
|
||||||
if (isNaN(cost)) {
|
|
||||||
throw new Error("Cost is NaN");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Player.money.lt(cost)) {
|
|
||||||
//dialogBoxCreate("You cannot afford to purchase a Hacknet Node!");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Auto generate a name for the node for now...TODO
|
|
||||||
var numOwned = Player.hacknetNodes.length;
|
|
||||||
var name = "hacknet-node-" + numOwned;
|
|
||||||
var node = new HacknetNode(name);
|
|
||||||
node.updateMoneyGainRate();
|
|
||||||
|
|
||||||
Player.loseMoney(cost);
|
|
||||||
Player.hacknetNodes.push(node);
|
|
||||||
|
|
||||||
if (routing.isOn(Page.HacknetNodes)) {
|
|
||||||
displayHacknetNodesContent();
|
|
||||||
}
|
|
||||||
updateTotalHacknetProduction();
|
|
||||||
return numOwned;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Calculates the total production from all HacknetNodes
|
|
||||||
function updateTotalHacknetProduction() {
|
|
||||||
var total = 0;
|
|
||||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
|
||||||
total += Player.hacknetNodes[i].moneyGainRatePerSecond;
|
|
||||||
}
|
|
||||||
TotalHacknetNodeProduction = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCostOfNextHacknetNode() {
|
|
||||||
//Cost increases exponentially based on how many you own
|
|
||||||
var numOwned = Player.hacknetNodes.length;
|
|
||||||
var mult = CONSTANTS.HacknetNodePurchaseNextMult;
|
|
||||||
return CONSTANTS.BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hacknetNodePurchaseMultiplier = 1;
|
|
||||||
function updateHacknetNodesMultiplierButtons() {
|
|
||||||
var mult1x = document.getElementById("hacknet-nodes-1x-multiplier");
|
|
||||||
var mult5x = document.getElementById("hacknet-nodes-5x-multiplier");
|
|
||||||
var mult10x = document.getElementById("hacknet-nodes-10x-multiplier");
|
|
||||||
var multMax = document.getElementById("hacknet-nodes-max-multiplier");
|
|
||||||
mult1x.setAttribute("class", "a-link-button");
|
|
||||||
mult5x.setAttribute("class", "a-link-button");
|
|
||||||
mult10x.setAttribute("class", "a-link-button");
|
|
||||||
multMax.setAttribute("class", "a-link-button");
|
|
||||||
if (Player.hacknetNodes.length == 0) {
|
|
||||||
mult1x.setAttribute("class", "a-link-button-inactive");
|
|
||||||
mult5x.setAttribute("class", "a-link-button-inactive");
|
|
||||||
mult10x.setAttribute("class", "a-link-button-inactive");
|
|
||||||
multMax.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else if (hacknetNodePurchaseMultiplier == 1) {
|
|
||||||
mult1x.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else if (hacknetNodePurchaseMultiplier == 5) {
|
|
||||||
mult5x.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else if (hacknetNodePurchaseMultiplier == 10) {
|
|
||||||
mult10x.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
multMax.setAttribute("class", "a-link-button-inactive");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
|
|
||||||
function getMaxNumberLevelUpgrades(nodeObj) {
|
|
||||||
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var min = 1;
|
|
||||||
var max = CONSTANTS.HacknetNodeMaxLevel - 1;
|
|
||||||
var levelsToMax = CONSTANTS.HacknetNodeMaxLevel - nodeObj.level;
|
|
||||||
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax))) {
|
|
||||||
return levelsToMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (min <= max) {
|
|
||||||
var curr = (min + max) / 2 | 0;
|
|
||||||
if (curr != CONSTANTS.HacknetNodeMaxLevel &&
|
|
||||||
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr)) &&
|
|
||||||
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1))) {
|
|
||||||
return Math.min(levelsToMax, curr);
|
|
||||||
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr))) {
|
|
||||||
max = curr - 1;
|
|
||||||
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr))) {
|
|
||||||
min = curr + 1;
|
|
||||||
} else {
|
|
||||||
return Math.min(levelsToMax, curr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMaxNumberRamUpgrades(nodeObj) {
|
|
||||||
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const levelsToMax = Math.round(Math.log2(CONSTANTS.HacknetNodeMaxRam / nodeObj.ram));
|
|
||||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax))) {
|
|
||||||
return levelsToMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
//We'll just loop until we find the max
|
|
||||||
for (let i = levelsToMax-1; i >= 0; --i) {
|
|
||||||
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i))) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMaxNumberCoreUpgrades(nodeObj) {
|
|
||||||
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1))) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var min = 1;
|
|
||||||
var max = CONSTANTS.HacknetNodeMaxCores - 1;
|
|
||||||
const levelsToMax = CONSTANTS.HacknetNodeMaxCores - nodeObj.cores;
|
|
||||||
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax))) {
|
|
||||||
return levelsToMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Use a binary search to find the max possible number of upgrades
|
|
||||||
while (min <= max) {
|
|
||||||
let curr = (min + max) / 2 | 0;
|
|
||||||
if (curr != CONSTANTS.HacknetNodeMaxCores &&
|
|
||||||
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr)) &&
|
|
||||||
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1))) {
|
|
||||||
return Math.min(levelsToMax, curr);
|
|
||||||
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr))) {
|
|
||||||
max = curr - 1;
|
|
||||||
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr))) {
|
|
||||||
min = curr + 1;
|
|
||||||
} else {
|
|
||||||
return Math.min(levelsToMax, curr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Creates Hacknet Node DOM elements when the page is opened
|
|
||||||
function displayHacknetNodesContent() {
|
|
||||||
//Update Hacknet Nodes button
|
|
||||||
var newPurchaseButton = clearEventListeners("hacknet-nodes-purchase-button");
|
|
||||||
|
|
||||||
newPurchaseButton.addEventListener("click", function() {
|
|
||||||
purchaseHacknet();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
//Handle Purchase multiplier buttons
|
|
||||||
updateHacknetNodesMultiplierButtons();
|
|
||||||
|
|
||||||
//Remove all old hacknet Node DOM elements
|
|
||||||
var hacknetNodesList = document.getElementById("hacknet-nodes-list");
|
|
||||||
while (hacknetNodesList.firstChild) {
|
|
||||||
hacknetNodesList.removeChild(hacknetNodesList.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Then re-create them
|
|
||||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
|
||||||
createHacknetNodeDomElement(Player.hacknetNodes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTotalHacknetProduction();
|
|
||||||
updateHacknetNodesContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update information on all Hacknet Node DOM elements
|
|
||||||
function updateHacknetNodesContent() {
|
|
||||||
//Set purchase button to inactive if not enough money, and update its price display
|
|
||||||
var cost = getCostOfNextHacknetNode();
|
|
||||||
var purchaseButton = getElementById("hacknet-nodes-purchase-button");
|
|
||||||
var formattedCost = numeralWrapper.formatMoney(cost);
|
|
||||||
|
|
||||||
updateText("hacknet-nodes-purchase-button", `Purchase Hacknet Node - ${formattedCost}`);
|
|
||||||
|
|
||||||
if (Player.money.lt(cost)) {
|
|
||||||
purchaseButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
purchaseButton.setAttribute("class", "a-link-button");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update player's money
|
|
||||||
updateText("hacknet-nodes-player-money", numeralWrapper.formatMoney(Player.money.toNumber()));
|
|
||||||
updateText("hacknet-nodes-total-production", numeralWrapper.formatMoney(TotalHacknetNodeProduction) + " / sec");
|
|
||||||
|
|
||||||
//Update information in each owned hacknet node
|
|
||||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
|
||||||
updateHacknetNodeDomElement(Player.hacknetNodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Creates a single Hacknet Node DOM element
|
|
||||||
function createHacknetNodeDomElement(nodeObj) {
|
|
||||||
var nodeName = nodeObj.name;
|
|
||||||
|
|
||||||
var nodeLevelContainer = createElement("div", {
|
|
||||||
class: "hacknet-node-level-container row",
|
|
||||||
innerHTML: "<p>Level:</p><span class=\"text upgradable-info\" id=\"hacknet-node-level-" + nodeName + "\"></span>"
|
|
||||||
});
|
|
||||||
|
|
||||||
var nodeRamContainer = createElement("div", {
|
|
||||||
class: "hacknet-node-ram-container row",
|
|
||||||
innerHTML: "<p>RAM:</p><span class=\"text upgradable-info\" id=\"hacknet-node-ram-" + nodeName + "\"></span>"
|
|
||||||
});
|
|
||||||
|
|
||||||
var nodeCoresContainer = createElement("div", {
|
|
||||||
class: "hacknet-node-cores-container row",
|
|
||||||
innerHTML: "<p>Cores:</p><span class=\"text upgradable-info\" id=\"hacknet-node-cores-" + nodeName + "\"><span>"
|
|
||||||
})
|
|
||||||
var containingDiv = createElement("div", {
|
|
||||||
class: "hacknet-node-container",
|
|
||||||
innerHTML: "<div class=\"hacknet-node-name-container row\">" +
|
|
||||||
"<p>Node name:</p>" +
|
|
||||||
"<span class=\"text\" id=\"hacknet-node-name-" + nodeName + "\"></span>" +
|
|
||||||
"</div>" +
|
|
||||||
"<div class=\"hacknet-node-production-container row\">" +
|
|
||||||
"<p>Production:</p>" +
|
|
||||||
"<span class=\"text\" id=\"hacknet-node-total-production-" + nodeName + "\"></span>" +
|
|
||||||
"<span class=\"text\" id=\"hacknet-node-production-rate-" + nodeName + "\"></span>" +
|
|
||||||
"</div>"
|
|
||||||
});
|
|
||||||
containingDiv.appendChild(nodeLevelContainer);
|
|
||||||
containingDiv.appendChild(nodeRamContainer);
|
|
||||||
containingDiv.appendChild(nodeCoresContainer);
|
|
||||||
|
|
||||||
var listItem = createElement("li", {
|
|
||||||
class: "hacknet-node"
|
|
||||||
});
|
|
||||||
listItem.appendChild(containingDiv);
|
|
||||||
|
|
||||||
//Upgrade buttons
|
|
||||||
nodeLevelContainer.appendChild(createElement("a", {
|
|
||||||
id: "hacknet-node-upgrade-level-" + nodeName,
|
|
||||||
class: "a-link-button-inactive",
|
|
||||||
clickListener: function() {
|
|
||||||
let numUpgrades = hacknetNodePurchaseMultiplier;
|
|
||||||
if (hacknetNodePurchaseMultiplier == 0) {
|
|
||||||
numUpgrades = getMaxNumberLevelUpgrades(nodeObj);
|
|
||||||
}
|
|
||||||
nodeObj.purchaseLevelUpgrade(numUpgrades);
|
|
||||||
updateHacknetNodesContent();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
nodeRamContainer.appendChild(createElement("a", {
|
|
||||||
id: "hacknet-node-upgrade-ram-" + nodeName,
|
|
||||||
class: "a-link-button-inactive",
|
|
||||||
clickListener: function() {
|
|
||||||
let numUpgrades = hacknetNodePurchaseMultiplier;
|
|
||||||
if (hacknetNodePurchaseMultiplier == 0) {
|
|
||||||
numUpgrades = getMaxNumberRamUpgrades(nodeObj);
|
|
||||||
}
|
|
||||||
nodeObj.purchaseRamUpgrade(numUpgrades);
|
|
||||||
updateHacknetNodesContent();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
nodeCoresContainer.appendChild(createElement("a", {
|
|
||||||
id: "hacknet-node-upgrade-core-" + nodeName,
|
|
||||||
class: "a-link-button-inactive",
|
|
||||||
clickListener: function() {
|
|
||||||
let numUpgrades = hacknetNodePurchaseMultiplier;
|
|
||||||
if (hacknetNodePurchaseMultiplier == 0) {
|
|
||||||
numUpgrades = getMaxNumberCoreUpgrades(nodeObj);
|
|
||||||
}
|
|
||||||
nodeObj.purchaseCoreUpgrade(numUpgrades);
|
|
||||||
updateHacknetNodesContent();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
document.getElementById("hacknet-nodes-list").appendChild(listItem);
|
|
||||||
|
|
||||||
//Set the text and stuff inside the DOM element
|
|
||||||
updateHacknetNodeDomElement(nodeObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Updates information on a single hacknet node DOM element
|
|
||||||
function updateHacknetNodeDomElement(nodeObj) {
|
|
||||||
var nodeName = nodeObj.name;
|
|
||||||
|
|
||||||
updateText("hacknet-node-name-" + nodeName, nodeName);
|
|
||||||
updateText("hacknet-node-total-production-" + nodeName, numeralWrapper.formatMoney(nodeObj.totalMoneyGenerated));
|
|
||||||
updateText("hacknet-node-production-rate-" + nodeName, "(" + numeralWrapper.formatMoney(nodeObj.moneyGainRatePerSecond) + " / sec)");
|
|
||||||
updateText("hacknet-node-level-" + nodeName, nodeObj.level);
|
|
||||||
updateText("hacknet-node-ram-" + nodeName, nodeObj.ram + "GB");
|
|
||||||
updateText("hacknet-node-cores-" + nodeName, nodeObj.cores);
|
|
||||||
|
|
||||||
//Upgrade level
|
|
||||||
var upgradeLevelButton = getElementById("hacknet-node-upgrade-level-" + nodeName);
|
|
||||||
|
|
||||||
if (nodeObj.level >= CONSTANTS.HacknetNodeMaxLevel) {
|
|
||||||
updateText("hacknet-node-upgrade-level-" + nodeName, "MAX LEVEL");
|
|
||||||
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
let multiplier = 0;
|
|
||||||
if (hacknetNodePurchaseMultiplier == 0) {
|
|
||||||
//Max
|
|
||||||
multiplier = getMaxNumberLevelUpgrades(nodeObj);
|
|
||||||
} else {
|
|
||||||
var levelsToMax = CONSTANTS.HacknetNodeMaxLevel - nodeObj.level;
|
|
||||||
multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost(multiplier);
|
|
||||||
updateText("hacknet-node-upgrade-level-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeLevelCost))
|
|
||||||
if (Player.money.lt(upgradeLevelCost)) {
|
|
||||||
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
upgradeLevelButton.setAttribute("class", "a-link-button");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Upgrade RAM
|
|
||||||
var upgradeRamButton = getElementById("hacknet-node-upgrade-ram-" + nodeName);
|
|
||||||
|
|
||||||
if (nodeObj.ram >= CONSTANTS.HacknetNodeMaxRam) {
|
|
||||||
updateText("hacknet-node-upgrade-ram-" + nodeName, "MAX RAM");
|
|
||||||
upgradeRamButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
let multiplier = 0;
|
|
||||||
if (hacknetNodePurchaseMultiplier == 0) {
|
|
||||||
multiplier = getMaxNumberRamUpgrades(nodeObj);
|
|
||||||
} else {
|
|
||||||
var levelsToMax = Math.round(Math.log2(CONSTANTS.HacknetNodeMaxRam / nodeObj.ram));
|
|
||||||
multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
var upgradeRamCost = nodeObj.calculateRamUpgradeCost(multiplier);
|
|
||||||
updateText("hacknet-node-upgrade-ram-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeRamCost));
|
|
||||||
if (Player.money.lt(upgradeRamCost)) {
|
|
||||||
upgradeRamButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
upgradeRamButton.setAttribute("class", "a-link-button");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Upgrade Cores
|
|
||||||
var upgradeCoreButton = getElementById("hacknet-node-upgrade-core-" + nodeName);
|
|
||||||
|
|
||||||
if (nodeObj.cores >= CONSTANTS.HacknetNodeMaxCores) {
|
|
||||||
updateText("hacknet-node-upgrade-core-" + nodeName, "MAX CORES");
|
|
||||||
upgradeCoreButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
let multiplier = 0;
|
|
||||||
if (hacknetNodePurchaseMultiplier == 0) {
|
|
||||||
multiplier = getMaxNumberCoreUpgrades(nodeObj);
|
|
||||||
} else {
|
|
||||||
var levelsToMax = CONSTANTS.HacknetNodeMaxCores - nodeObj.cores;
|
|
||||||
multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
|
|
||||||
}
|
|
||||||
var upgradeCoreCost = nodeObj.calculateCoreUpgradeCost(multiplier);
|
|
||||||
updateText("hacknet-node-upgrade-core-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeCoreCost));
|
|
||||||
if (Player.money.lt(upgradeCoreCost)) {
|
|
||||||
upgradeCoreButton.setAttribute("class", "a-link-button-inactive");
|
|
||||||
} else {
|
|
||||||
upgradeCoreButton.setAttribute("class", "a-link-button");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function processAllHacknetNodeEarnings(numCycles) {
|
|
||||||
var total = 0;
|
|
||||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
|
||||||
total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
|
|
||||||
var cyclesPerSecond = 1000 / Engine._idleSpeed;
|
|
||||||
var earningPerCycle = nodeObj.moneyGainRatePerSecond / cyclesPerSecond;
|
|
||||||
if (isNaN(earningPerCycle)) {
|
|
||||||
console.error("Hacknet Node '" + nodeObj.name + "' Calculated earnings is NaN");
|
|
||||||
earningPerCycle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalEarnings = numCycles * earningPerCycle;
|
|
||||||
nodeObj.totalMoneyGenerated += totalEarnings;
|
|
||||||
nodeObj.onlineTimeSeconds += (numCycles * (Engine._idleSpeed / 1000));
|
|
||||||
Player.gainMoney(totalEarnings);
|
|
||||||
Player.recordMoneySource(totalEarnings, "hacknetnode");
|
|
||||||
return totalEarnings;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHacknetNode(name) {
|
|
||||||
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
|
|
||||||
if (Player.hacknetNodes[i].name == name) {
|
|
||||||
return Player.hacknetNodes[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
HacknetNode,
|
|
||||||
displayHacknetNodesContent,
|
|
||||||
getCostOfNextHacknetNode,
|
|
||||||
getHacknetNode,
|
|
||||||
getMaxNumberLevelUpgrades,
|
|
||||||
hacknetNodesInit,
|
|
||||||
processAllHacknetNodeEarnings,
|
|
||||||
purchaseHacknet,
|
|
||||||
updateHacknetNodesContent,
|
|
||||||
updateHacknetNodesMultiplierButtons,
|
|
||||||
updateTotalHacknetProduction
|
|
||||||
};
|
|
@ -52,41 +52,81 @@ function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff)
|
|||||||
this.intExpGained = 0;
|
this.intExpGained = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.expMultiplier = function() {
|
||||||
|
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1;
|
||||||
|
return 2.5 * this.clearanceLevel / this.maxClearanceLevel;
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainHackingExp = function(amt) {
|
InfiltrationInstance.prototype.gainHackingExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.hackingExpGained += amt;
|
this.hackingExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedHackingExp = function() {
|
||||||
|
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0;
|
||||||
|
return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
|
InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.strExpGained += amt;
|
this.strExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedStrengthExp = function() {
|
||||||
|
if (!this.strExpGained || isNaN(this.strExpGained)) return 0;
|
||||||
|
return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
|
InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.defExpGained += amt;
|
this.defExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedDefenseExp = function() {
|
||||||
|
if (!this.defExpGained || isNaN(this.defExpGained)) return 0;
|
||||||
|
return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
|
InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.dexExpGained += amt;
|
this.dexExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedDexterityExp = function() {
|
||||||
|
if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0;
|
||||||
|
return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
|
InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.agiExpGained += amt;
|
this.agiExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedAgilityExp = function() {
|
||||||
|
if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0;
|
||||||
|
return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
|
InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.chaExpGained += amt;
|
this.chaExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedCharismaExp = function() {
|
||||||
|
if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0;
|
||||||
|
return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
|
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
|
||||||
if (isNaN(amt)) {return;}
|
if (isNaN(amt)) {return;}
|
||||||
this.intExpGained += amt;
|
this.intExpGained += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InfiltrationInstance.prototype.calcGainedIntelligenceExp = function() {
|
||||||
|
if(!this.intExpGained || isNaN(this.intExpGained)) return 0;
|
||||||
|
return Math.pow(this.intExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||||
|
}
|
||||||
|
|
||||||
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
|
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
|
||||||
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
|
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
|
||||||
clearInfiltrationStatusText();
|
clearInfiltrationStatusText();
|
||||||
@ -477,12 +517,12 @@ function updateInfiltrationLevelText(inst) {
|
|||||||
"Total value of stolen secrets<br>" +
|
"Total value of stolen secrets<br>" +
|
||||||
"Reputation: <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
|
"Reputation: <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
|
||||||
"Money: <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
|
"Money: <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
|
||||||
"Hack exp gained: " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "<br>" +
|
"Hack exp gained: " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" +
|
||||||
"Str exp gained: " + formatNumber(inst.strExpGained * expMultiplier, 3) + "<br>" +
|
"Str exp gained: " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" +
|
||||||
"Def exp gained: " + formatNumber(inst.defExpGained * expMultiplier, 3) + "<br>" +
|
"Def exp gained: " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" +
|
||||||
"Dex exp gained: " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "<br>" +
|
"Dex exp gained: " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" +
|
||||||
"Agi exp gained: " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "<br>" +
|
"Agi exp gained: " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" +
|
||||||
"Cha exp gained: " + formatNumber(inst.chaExpGained * expMultiplier, 3);
|
"Cha exp gained: " + formatNumber(inst.calcGainedCharismaExp(), 3);
|
||||||
/* eslint-enable no-irregular-whitespace */
|
/* eslint-enable no-irregular-whitespace */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +327,7 @@ function displayLocationContent() {
|
|||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
|
||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
|
||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob);
|
||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[0]], securityJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[2]], securityJob);
|
||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob);
|
||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob);
|
||||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob);
|
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob);
|
||||||
|
@ -1 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Location and traveling-related helper functions.
|
||||||
|
* Mostly used for UI
|
||||||
|
*/
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
|
|
||||||
|
import { Company } from "../Company/Company";
|
||||||
|
import { getJobRequirementText } from "../Company/GetJobRequirementText";
|
||||||
|
import * as posNames from "../Company/data/companypositionnames";
|
||||||
|
18
src/Locations/createCityMap.ts
Normal file
18
src/Locations/createCityMap.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Utility function that creates a "city map", which is an object where
|
||||||
|
* each city is a key (property).
|
||||||
|
*
|
||||||
|
* This map uses the official name of the city, NOT its key in the 'Cities' object
|
||||||
|
*/
|
||||||
|
import { Cities } from "./Cities";
|
||||||
|
import { IMap } from "../types";
|
||||||
|
|
||||||
|
export function createCityMap<T>(initValue: T): IMap<T> {
|
||||||
|
const map: IMap<any> = {};
|
||||||
|
const cities = Object.keys(Cities);
|
||||||
|
for (let i = 0; i < cities.length; ++i) {
|
||||||
|
map[cities[i]] = initValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
@ -7,9 +7,11 @@ import { LocationName } from "../data/LocationNames";
|
|||||||
|
|
||||||
import { Companies } from "../../Company/Companies";
|
import { Companies } from "../../Company/Companies";
|
||||||
import { Company } from "../../Company/Company";
|
import { Company } from "../../Company/Company";
|
||||||
|
import { CompanyPosition } from "../../Company/CompanyPosition";
|
||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
|
|
||||||
import { StdButton } from "../../ui/React/StdButton";
|
import { StdButton } from "../../ui/React/StdButton";
|
||||||
|
import { StdButtonWithTooltip } from "../../ui/React/StdButtonWithTooltip";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
locName: LocationName;
|
locName: LocationName;
|
||||||
@ -99,6 +101,18 @@ export class CompanyLocation extends React.Component<IProps, any> {
|
|||||||
this.props.p.applyForWaiterJob();
|
this.props.p.applyForWaiterJob();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getJobRequirementTooltip(company: Company, entryPosType: CompanyPosition) {
|
||||||
|
if (!(company instanceof Company)) { return; }
|
||||||
|
|
||||||
|
const pos = this.props.p.getNextCompanyPosition(company, entryPosType);
|
||||||
|
if (pos == null) { return };
|
||||||
|
|
||||||
|
if (!company.hasPosition(pos)) { return; }
|
||||||
|
|
||||||
|
return getJobRequirementText(company, pos, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import {HacknetNode} from "./HacknetNode";
|
|
||||||
import {NetscriptFunctions} from "./NetscriptFunctions";
|
import {NetscriptFunctions} from "./NetscriptFunctions";
|
||||||
import {NetscriptPort} from "./NetscriptPort";
|
import {NetscriptPort} from "./NetscriptPort";
|
||||||
|
|
||||||
@ -56,37 +55,6 @@ Environment.prototype = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setArrayElement: function(name, idx, value) {
|
|
||||||
if (!(idx instanceof Array)) {
|
|
||||||
throw new Error("idx parameter is not an Array");
|
|
||||||
}
|
|
||||||
var scope = this.lookup(name);
|
|
||||||
if (!scope && this.parent) {
|
|
||||||
throw new Error("Undefined variable " + name);
|
|
||||||
}
|
|
||||||
var arr = (scope || this).vars[name];
|
|
||||||
if (!(arr.constructor === Array || arr instanceof Array)) {
|
|
||||||
throw new Error("Variable is not an array: " + name);
|
|
||||||
}
|
|
||||||
var res = arr;
|
|
||||||
for (var iterator = 0; iterator < idx.length-1; ++iterator) {
|
|
||||||
var i = idx[iterator];
|
|
||||||
if (!(res instanceof Array) || i >= res.length) {
|
|
||||||
throw new Error("Out-of-bounds array access");
|
|
||||||
}
|
|
||||||
res = res[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
//Cant assign to ports or HacknetNodes
|
|
||||||
if (res[idx[idx.length-1]] instanceof HacknetNode) {
|
|
||||||
throw new Error("Cannot assign a Hacknet Node handle to a new value");
|
|
||||||
}
|
|
||||||
if (res[idx[idx.length-1]] instanceof NetscriptPort) {
|
|
||||||
throw new Error("Cannot assign a Netscript Port handle to a new value");
|
|
||||||
}
|
|
||||||
return res[idx[idx.length-1]] = value;
|
|
||||||
},
|
|
||||||
|
|
||||||
//Creates (or overwrites) a variable in the current scope
|
//Creates (or overwrites) a variable in the current scope
|
||||||
def: function(name, value) {
|
def: function(name, value) {
|
||||||
return this.vars[name] = value;
|
return this.vars[name] = value;
|
||||||
|
@ -196,8 +196,11 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
|
|||||||
}
|
}
|
||||||
var runningScriptObj = new RunningScript(script, args);
|
var runningScriptObj = new RunningScript(script, args);
|
||||||
runningScriptObj.threads = threads;
|
runningScriptObj.threads = threads;
|
||||||
server.runningScripts.push(runningScriptObj); //Push onto runningScripts
|
|
||||||
addWorkerScript(runningScriptObj, server);
|
addWorkerScript(runningScriptObj, server);
|
||||||
|
|
||||||
|
// Push onto runningScripts.
|
||||||
|
// This has to come after addWorkerScript() because that fn updates RAM usage
|
||||||
|
server.runScript(runningScriptObj, Player);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,9 @@ import { Factions,
|
|||||||
factionExists } from "./Faction/Factions";
|
factionExists } from "./Faction/Factions";
|
||||||
import { joinFaction,
|
import { joinFaction,
|
||||||
purchaseAugmentation } from "./Faction/FactionHelpers";
|
purchaseAugmentation } from "./Faction/FactionHelpers";
|
||||||
|
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
|
||||||
import { getCostOfNextHacknetNode,
|
import { getCostOfNextHacknetNode,
|
||||||
purchaseHacknet } from "./HacknetNode";
|
purchaseHacknet } from "./Hacknet/HacknetNode";
|
||||||
import {Locations} from "./Locations";
|
import {Locations} from "./Locations";
|
||||||
import { Message } from "./Message/Message";
|
import { Message } from "./Message/Message";
|
||||||
import { Messages } from "./Message/MessageHelpers";
|
import { Messages } from "./Message/MessageHelpers";
|
||||||
@ -60,6 +61,7 @@ import {StockMarket, StockSymbols, SymbolToStockMap,
|
|||||||
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
|
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
|
||||||
import { getStockmarket4SDataCost,
|
import { getStockmarket4SDataCost,
|
||||||
getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts";
|
getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts";
|
||||||
|
import { SourceFileFlags } from "./SourceFile/SourceFileFlags"
|
||||||
import {TextFile, getTextFile, createTextFile} from "./TextFile";
|
import {TextFile, getTextFile, createTextFile} from "./TextFile";
|
||||||
|
|
||||||
import {unknownBladeburnerActionErrorMessage,
|
import {unknownBladeburnerActionErrorMessage,
|
||||||
@ -71,6 +73,8 @@ import {WorkerScript, workerScripts,
|
|||||||
import {makeRuntimeRejectMsg, netscriptDelay,
|
import {makeRuntimeRejectMsg, netscriptDelay,
|
||||||
runScriptFromScript} from "./NetscriptEvaluator";
|
runScriptFromScript} from "./NetscriptEvaluator";
|
||||||
import {NetscriptPort} from "./NetscriptPort";
|
import {NetscriptPort} from "./NetscriptPort";
|
||||||
|
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
|
||||||
|
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/Sleeve";
|
||||||
|
|
||||||
import {Page, routing} from "./ui/navigationTracking";
|
import {Page, routing} from "./ui/navigationTracking";
|
||||||
import {numeralWrapper} from "./ui/numeralFormat";
|
import {numeralWrapper} from "./ui/numeralFormat";
|
||||||
@ -275,27 +279,27 @@ function NetscriptFunctions(workerScript) {
|
|||||||
},
|
},
|
||||||
upgradeLevel : function(i, n) {
|
upgradeLevel : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.purchaseLevelUpgrade(n);
|
return node.purchaseLevelUpgrade(n, Player);
|
||||||
},
|
},
|
||||||
upgradeRam : function(i, n) {
|
upgradeRam : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.purchaseRamUpgrade(n);
|
return node.purchaseRamUpgrade(n, Player);
|
||||||
},
|
},
|
||||||
upgradeCore : function(i, n) {
|
upgradeCore : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.purchaseCoreUpgrade(n);
|
return node.purchaseCoreUpgrade(n, Player);
|
||||||
},
|
},
|
||||||
getLevelUpgradeCost : function(i, n) {
|
getLevelUpgradeCost : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.calculateLevelUpgradeCost(n);
|
return node.calculateLevelUpgradeCost(n, Player);
|
||||||
},
|
},
|
||||||
getRamUpgradeCost : function(i, n) {
|
getRamUpgradeCost : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.calculateRamUpgradeCost(n);
|
return node.calculateRamUpgradeCost(n, Player);
|
||||||
},
|
},
|
||||||
getCoreUpgradeCost : function(i, n) {
|
getCoreUpgradeCost : function(i, n) {
|
||||||
var node = getHacknetNode(i);
|
var node = getHacknetNode(i);
|
||||||
return node.calculateCoreUpgradeCost(n);
|
return node.calculateCoreUpgradeCost(n, Player);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sprintf : sprintf,
|
sprintf : sprintf,
|
||||||
@ -4807,7 +4811,341 @@ function NetscriptFunctions(workerScript) {
|
|||||||
}
|
}
|
||||||
return contract.getMaxNumTries() - contract.tries;
|
return contract.getMaxNumTries() - contract.tries;
|
||||||
},
|
},
|
||||||
}
|
}, // End coding contracts
|
||||||
|
sleeve : {
|
||||||
|
getNumSleeves : function() {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "getNumSleeves() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
return Player.sleeves.length;
|
||||||
|
},
|
||||||
|
setToShockRecovery : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToShockRecovery", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToShockRecovery() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToShockRecovery", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToShockRecovery(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].shockRecovery(Player);
|
||||||
|
},
|
||||||
|
setToSynchronize : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToSynchronize", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToSynchronize() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToSynchronize", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToSynchronize(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].synchronize(Player);
|
||||||
|
},
|
||||||
|
setToCommitCrime : function(sleeveNumber=0, crimeName="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToCommitCrime", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToCommitCrime() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToCommitCrime", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToCommitCrime(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName);
|
||||||
|
},
|
||||||
|
setToUniversityCourse : function(sleeveNumber=0, universityName="", className="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToUniversityCourse() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToUniversityCourse(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].takeUniversityCourse(Player, universityName, className);
|
||||||
|
},
|
||||||
|
travel : function(sleeveNumber=0, cityName="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("travel", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "travel() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("travel", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.travel(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].travel(Player, cityName);
|
||||||
|
},
|
||||||
|
setToCompanyWork : function(sleeveNumber=0, companyName="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToCompanyWork", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToCompanyWork() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToCompanyWork", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToCompanyWork(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot work at the same company that another sleeve is working at
|
||||||
|
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||||
|
if (i === sleeveNumber) { continue; }
|
||||||
|
const other = Player.sleeves[i];
|
||||||
|
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToCompanyWork() failed for Sleeve ${sleeveNumber} because Sleeve ${i} is doing the same task`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].workForCompany(Player, companyName);
|
||||||
|
},
|
||||||
|
setToFactionWork : function(sleeveNumber=0, factionName="", workType="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToFactionWork", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToFactionWork() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToFactionWork", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToFactionWork(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot work at the same faction that another sleeve is working at
|
||||||
|
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||||
|
if (i === sleeveNumber) { continue; }
|
||||||
|
const other = Player.sleeves[i];
|
||||||
|
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToFactionWork() failed for Sleeve ${sleeveNumber} because Sleeve ${i} is doing the same task`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType);
|
||||||
|
},
|
||||||
|
setToGymWorkout : function(sleeveNumber=0, gymName="", stat="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("setToGymWorkout", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "setToGymWorkout() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("setToGymWorkout", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.setToGymWorkout(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat);
|
||||||
|
},
|
||||||
|
getSleeveStats : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "getStats() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sl = Player.sleeves[sleeveNumber];
|
||||||
|
return {
|
||||||
|
shock: 100 - sl.shock,
|
||||||
|
sync: sl.sync,
|
||||||
|
hacking_skill: sl.hacking_skill,
|
||||||
|
strength: sl.strength,
|
||||||
|
defense: sl.defense,
|
||||||
|
dexterity: sl.dexterity,
|
||||||
|
agility: sl.agility,
|
||||||
|
charisma: sl.charisma,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getTask : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "getTask() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.getTask(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sl = Player.sleeves[sleeveNumber];
|
||||||
|
return {
|
||||||
|
task: SleeveTaskType[sl.currentTask],
|
||||||
|
crime: sl.crimeType,
|
||||||
|
location: sl.currentTaskLocation,
|
||||||
|
gymStatType: sl.gymStatType,
|
||||||
|
factionWorkType: FactionWorkType[sl.factionWorkType],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInformation : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "getInformation() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.getInformation(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sl = Player.sleeves[sleeveNumber];
|
||||||
|
return {
|
||||||
|
city: sl.city,
|
||||||
|
hp: sl.hp,
|
||||||
|
jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player.
|
||||||
|
jobTitle: Object.values(Player.jobs),
|
||||||
|
maxHp: sl.max_hp,
|
||||||
|
tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used.
|
||||||
|
|
||||||
|
mult: {
|
||||||
|
agility: sl.agility_mult,
|
||||||
|
agilityExp: sl.agility_exp_mult,
|
||||||
|
companyRep: sl.company_rep_mult,
|
||||||
|
crimeMoney: sl.crime_money_mult,
|
||||||
|
crimeSuccess: sl.crime_success_mult,
|
||||||
|
defense: sl.defense_mult,
|
||||||
|
defenseExp: sl.defense_exp_mult,
|
||||||
|
dexterity: sl.dexterity_mult,
|
||||||
|
dexterityExp: sl.dexterity_exp_mult,
|
||||||
|
factionRep: sl.faction_rep_mult,
|
||||||
|
hacking: sl.hacking_mult,
|
||||||
|
hackingExp: sl.hacking_exp_mult,
|
||||||
|
strength: sl.strength_mult,
|
||||||
|
strengthExp: sl.strength_exp_mult,
|
||||||
|
workMoney: sl.work_money_mult,
|
||||||
|
},
|
||||||
|
|
||||||
|
timeWorked: sl.currentTaskTime,
|
||||||
|
earningsForSleeves : {
|
||||||
|
workHackExpGain: sl.earningsForSleeves.hack,
|
||||||
|
workStrExpGain: sl.earningsForSleeves.str,
|
||||||
|
workDefExpGain: sl.earningsForSleeves.def,
|
||||||
|
workDexExpGain: sl.earningsForSleeves.dex,
|
||||||
|
workAgiExpGain: sl.earningsForSleeves.agi,
|
||||||
|
workChaExpGain: sl.earningsForSleeves.cha,
|
||||||
|
workMoneyGain: sl.earningsForSleeves.money,
|
||||||
|
},
|
||||||
|
earningsForPlayer : {
|
||||||
|
workHackExpGain: sl.earningsForPlayer.hack,
|
||||||
|
workStrExpGain: sl.earningsForPlayer.str,
|
||||||
|
workDefExpGain: sl.earningsForPlayer.def,
|
||||||
|
workDexExpGain: sl.earningsForPlayer.dex,
|
||||||
|
workAgiExpGain: sl.earningsForPlayer.agi,
|
||||||
|
workChaExpGain: sl.earningsForPlayer.cha,
|
||||||
|
workMoneyGain: sl.earningsForPlayer.money,
|
||||||
|
},
|
||||||
|
earningsForTask : {
|
||||||
|
workHackExpGain: sl.earningsForTask.hack,
|
||||||
|
workStrExpGain: sl.earningsForTask.str,
|
||||||
|
workDefExpGain: sl.earningsForTask.def,
|
||||||
|
workDexExpGain: sl.earningsForTask.dex,
|
||||||
|
workAgiExpGain: sl.earningsForTask.agi,
|
||||||
|
workChaExpGain: sl.earningsForTask.cha,
|
||||||
|
workMoneyGain: sl.earningsForTask.money,
|
||||||
|
},
|
||||||
|
workRepGain: sl.getRepGain(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSleeveAugmentations : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("getSleeveAugmentations", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "getSleeveAugmentations() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("getSleeveAugmentations", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.getSleeveAugmentations(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const augs = [];
|
||||||
|
for (let i = 0; i < Player.sleeves[sleeveNumber].augmentations.length; i++) {
|
||||||
|
augs.push(Player.sleeves[sleeveNumber].augmentations[i].name);
|
||||||
|
}
|
||||||
|
return augs;
|
||||||
|
},
|
||||||
|
getSleevePurchasableAugs : function(sleeveNumber=0) {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("getSleevePurchasableAugs", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "getSleevePurchasableAugs() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("getSleevePurchasableAugs", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.getSleevePurchasableAugs(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const purchasableAugs = findSleevePurchasableAugs(Player.sleeves[sleeveNumber], Player);
|
||||||
|
const augs = [];
|
||||||
|
for (let i = 0; i < purchasableAugs.length; i++) {
|
||||||
|
const aug = purchasableAugs[i];
|
||||||
|
augs.push({
|
||||||
|
name: aug.name,
|
||||||
|
cost: aug.startingCost,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return augs;
|
||||||
|
},
|
||||||
|
purchaseSleeveAug : function(sleeveNumber=0, augName="") {
|
||||||
|
if (workerScript.checkingRam) {
|
||||||
|
return updateStaticRam("purchaseSleeveAug", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
}
|
||||||
|
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||||
|
throw makeRuntimeRejectMsg(workerScript, "purchaseSleeveAug() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||||
|
}
|
||||||
|
updateDynamicRam("purchaseSleeveAug", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||||
|
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||||
|
workerScript.log(`ERROR: sleeve.purchaseSleeveAug(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aug = Augmentations[augName];
|
||||||
|
if (!aug) {
|
||||||
|
workerScript.log(`ERROR: sleeve.purchaseSleeveAug(${sleeveNumber}) failed because ${augName} is not a valid aug.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.sleeves[sleeveNumber].tryBuyAugmentation(Player, aug);
|
||||||
|
}
|
||||||
|
} // End sleeve
|
||||||
} //End return
|
} //End return
|
||||||
} //End NetscriptFunction()
|
} //End NetscriptFunction()
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugment
|
|||||||
import { Company } from "../Company/Company";
|
import { Company } from "../Company/Company";
|
||||||
import { CompanyPosition } from "../Company/CompanyPosition";
|
import { CompanyPosition } from "../Company/CompanyPosition";
|
||||||
import { CityName } from "../Locations/data/CityNames";
|
import { CityName } from "../Locations/data/CityNames";
|
||||||
|
import { HacknetNode } from "../Hacknet/HacknetNode";
|
||||||
|
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||||
import { LocationName } from "../Locations/data/LocationNames";
|
import { LocationName } from "../Locations/data/LocationNames";
|
||||||
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
||||||
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
||||||
@ -26,7 +28,7 @@ export interface IPlayer {
|
|||||||
corporation: any;
|
corporation: any;
|
||||||
currentServer: string;
|
currentServer: string;
|
||||||
factions: string[];
|
factions: string[];
|
||||||
hacknetNodes: any[];
|
hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
|
||||||
hasWseAccount: boolean;
|
hasWseAccount: boolean;
|
||||||
jobs: IMap<string>;
|
jobs: IMap<string>;
|
||||||
karma: number;
|
karma: number;
|
||||||
|
@ -105,7 +105,7 @@ export abstract class Person {
|
|||||||
/**
|
/**
|
||||||
* City that the person is in
|
* City that the person is in
|
||||||
*/
|
*/
|
||||||
city: string = CityName.Sector12;
|
city: CityName = CityName.Sector12;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ export function createResleevesPage(p: IPlayer) {
|
|||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
innerText: "Sort By: "
|
innerText: "Sort By: "
|
||||||
});
|
});
|
||||||
UIElems.sortSelector = createElement("select") as HTMLSelectElement;
|
UIElems.sortSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
|
||||||
|
|
||||||
enum SortOption {
|
enum SortOption {
|
||||||
Cost = "Cost",
|
Cost = "Cost",
|
||||||
@ -309,7 +309,7 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
|
|||||||
elems.statsPanel.appendChild(elems.multipliersButton);
|
elems.statsPanel.appendChild(elems.multipliersButton);
|
||||||
|
|
||||||
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
|
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
|
||||||
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement;
|
elems.augSelector = createElement("select", { class: "resleeve-aug-selector dropdown" }) as HTMLSelectElement;
|
||||||
elems.augDescription = createElement("p");
|
elems.augDescription = createElement("p");
|
||||||
for (let i = 0; i < resleeve.augmentations.length; ++i) {
|
for (let i = 0; i < resleeve.augmentations.length; ++i) {
|
||||||
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
|
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
|
||||||
|
@ -14,6 +14,8 @@ import { Person,
|
|||||||
createTaskTracker } from "../Person";
|
createTaskTracker } from "../Person";
|
||||||
|
|
||||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||||
|
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||||
|
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||||
|
|
||||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||||
|
|
||||||
@ -112,13 +114,12 @@ export class Sleeve extends Person {
|
|||||||
logs: string[] = [];
|
logs: string[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clone retains memory% of exp upon prestige. If exp would be lower than previously
|
* Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
|
||||||
* kept exp, nothing happens
|
|
||||||
*/
|
*/
|
||||||
memory: number = 0;
|
memory: number = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sleeve shock. Number between 1 and 100
|
* Sleeve shock. Number between 0 and 100
|
||||||
* Trauma/shock that comes with being in a sleeve. Experience earned
|
* Trauma/shock that comes with being in a sleeve. Experience earned
|
||||||
* is multipled by shock%. This gets applied before synchronization
|
* is multipled by shock%. This gets applied before synchronization
|
||||||
*
|
*
|
||||||
@ -337,6 +338,31 @@ export class Sleeve extends Person {
|
|||||||
p.gainMoney(gain);
|
p.gainMoney(gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cost of upgrading this sleeve's memory by a certain amount
|
||||||
|
*/
|
||||||
|
getMemoryUpgradeCost(n: number): number {
|
||||||
|
const amt = Math.round(n);
|
||||||
|
if (amt < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.memory + amt > 100) {
|
||||||
|
return this.getMemoryUpgradeCost(100 - this.memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mult = 1.02;
|
||||||
|
const baseCost = 1e12;
|
||||||
|
let currCost = 0;
|
||||||
|
let currMemory = this.memory-1;
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
currCost += (Math.pow(mult, currMemory));
|
||||||
|
++currMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currCost * baseCost;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets reputation gain for the current task
|
* Gets reputation gain for the current task
|
||||||
* Only applicable when working for company or faction
|
* Only applicable when working for company or faction
|
||||||
@ -406,6 +432,20 @@ export class Sleeve extends Person {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on every sleeve for a Source File prestige
|
||||||
|
*/
|
||||||
|
prestige(p: IPlayer) {
|
||||||
|
this.resetTaskStatus();
|
||||||
|
this.earningsForSleeves = createTaskTracker();
|
||||||
|
this.earningsForPlayer = createTaskTracker();
|
||||||
|
this.logs = [];
|
||||||
|
this.shock = 1;
|
||||||
|
this.storedCycles = 0;
|
||||||
|
this.sync = Math.max(this.memory, 1);
|
||||||
|
this.shockRecovery(p);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process loop
|
* Process loop
|
||||||
* Returns an object containing the amount of experience that should be
|
* Returns an object containing the amount of experience that should be
|
||||||
@ -612,12 +652,7 @@ export class Sleeve extends Person {
|
|||||||
/**
|
/**
|
||||||
* Travel to another City. Costs money from player
|
* Travel to another City. Costs money from player
|
||||||
*/
|
*/
|
||||||
travel(p: IPlayer, newCity: string): boolean {
|
travel(p: IPlayer, newCity: CityName): boolean {
|
||||||
if (CityName[newCity] == null) {
|
|
||||||
console.error(`Invalid city ${newCity} passed into Sleeve.travel()`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.loseMoney(CONSTANTS.TravelCost);
|
p.loseMoney(CONSTANTS.TravelCost);
|
||||||
this.city = newCity;
|
this.city = newCity;
|
||||||
|
|
||||||
@ -641,8 +676,8 @@ export class Sleeve extends Person {
|
|||||||
|
|
||||||
const company: Company | null = Companies[companyName];
|
const company: Company | null = Companies[companyName];
|
||||||
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
|
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
|
||||||
if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); }
|
if (company == null) { return false; }
|
||||||
if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); }
|
if (companyPosition == null) { return false; }
|
||||||
this.gainRatesForTask.money = companyPosition.baseSalary *
|
this.gainRatesForTask.money = companyPosition.baseSalary *
|
||||||
company.salaryMultiplier *
|
company.salaryMultiplier *
|
||||||
this.work_money_mult *
|
this.work_money_mult *
|
||||||
@ -684,8 +719,8 @@ export class Sleeve extends Person {
|
|||||||
* Returns boolean indicating success
|
* Returns boolean indicating success
|
||||||
*/
|
*/
|
||||||
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
|
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
|
||||||
|
if (factionName === "") { return false; }
|
||||||
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
|
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
|
||||||
throw new Error(`Invalid Faction specified for Sleeve.workForFaction(): ${factionName}`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -807,6 +842,25 @@ export class Sleeve extends Person {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
|
||||||
|
if (!p.canAfford(aug.startingCost)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.loseMoney(aug.startingCost);
|
||||||
|
this.installAugmentation(aug);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeMemory(n: number): void {
|
||||||
|
if (n < 0) {
|
||||||
|
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.memory = Math.min(100, Math.round(this.memory + n));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize the current object to a JSON save state.
|
* Serialize the current object to a JSON save state.
|
||||||
*/
|
*/
|
||||||
@ -815,4 +869,31 @@ export class Sleeve extends Person {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] {
|
||||||
|
// You can only purchase Augmentations that are actually available from
|
||||||
|
// your factions. I.e. you must be in a faction that has the Augmentation
|
||||||
|
// and you must also have enough rep in that faction in order to purchase it.
|
||||||
|
|
||||||
|
const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name});
|
||||||
|
const availableAugs: Augmentation[] = [];
|
||||||
|
|
||||||
|
for (const facName of p.factions) {
|
||||||
|
if (facName === "Bladeburners") { continue; }
|
||||||
|
const fac: Faction | null = Factions[facName];
|
||||||
|
if (fac == null) { continue; }
|
||||||
|
|
||||||
|
for (const augName of fac.augmentations) {
|
||||||
|
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
|
||||||
|
if (ownedAugNames.includes(augName)) { continue; }
|
||||||
|
const aug: Augmentation | null = Augmentations[augName];
|
||||||
|
|
||||||
|
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
|
||||||
|
availableAugs.push(aug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableAugs;
|
||||||
|
}
|
||||||
|
|
||||||
Reviver.constructors.Sleeve = Sleeve;
|
Reviver.constructors.Sleeve = Sleeve;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* Module for handling the UI for purchasing Sleeve Augmentations
|
* Module for handling the UI for purchasing Sleeve Augmentations
|
||||||
* This UI is a popup, not a full page
|
* This UI is a popup, not a full page
|
||||||
*/
|
*/
|
||||||
import { Sleeve } from "./Sleeve";
|
import { Sleeve, findSleevePurchasableAugs } from "./Sleeve";
|
||||||
|
|
||||||
import { IPlayer } from "../IPlayer";
|
import { IPlayer } from "../IPlayer";
|
||||||
|
|
||||||
@ -29,23 +29,7 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
|
|||||||
// You can only purchase Augmentations that are actually available from
|
// You can only purchase Augmentations that are actually available from
|
||||||
// your factions. I.e. you must be in a faction that has the Augmentation
|
// your factions. I.e. you must be in a faction that has the Augmentation
|
||||||
// and you must also have enough rep in that faction in order to purchase it.
|
// and you must also have enough rep in that faction in order to purchase it.
|
||||||
const availableAugs: Augmentation[] = [];
|
const availableAugs = findSleevePurchasableAugs(sleeve, p);
|
||||||
|
|
||||||
for (const facName of p.factions) {
|
|
||||||
if (facName === "Bladeburners") { continue; }
|
|
||||||
const fac: Faction | null = Factions[facName];
|
|
||||||
if (fac == null) { continue; }
|
|
||||||
|
|
||||||
for (const augName of fac.augmentations) {
|
|
||||||
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
|
|
||||||
if (ownedAugNames.includes(augName)) { continue; }
|
|
||||||
const aug: Augmentation | null = Augmentations[augName];
|
|
||||||
|
|
||||||
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
|
|
||||||
availableAugs.push(aug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create popup
|
// Create popup
|
||||||
const popupId = "purchase-sleeve-augs-popup";
|
const popupId = "purchase-sleeve-augs-popup";
|
||||||
@ -105,15 +89,13 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
|
|||||||
innerHTML:
|
innerHTML:
|
||||||
[
|
[
|
||||||
`<h2>${aug.name}</h2><br>`,
|
`<h2>${aug.name}</h2><br>`,
|
||||||
`Cost: ${numeralWrapper.formatMoney(aug.baseCost)}<br><br>`,
|
`Cost: ${numeralWrapper.formatMoney(aug.startingCost)}<br><br>`,
|
||||||
`${aug.info}`
|
`${aug.info}`
|
||||||
].join(" "),
|
].join(" "),
|
||||||
padding: "2px",
|
padding: "2px",
|
||||||
clickListener: () => {
|
clickListener: () => {
|
||||||
if (p.canAfford(aug.baseCost)) {
|
if (sleeve.tryBuyAugmentation(p, aug)) {
|
||||||
p.loseMoney(aug.baseCost);
|
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false);
|
||||||
sleeve.installAugmentation(aug);
|
|
||||||
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
|
|
||||||
removeElementById(popupId);
|
removeElementById(popupId);
|
||||||
createSleevePurchaseAugsPopup(sleeve, p);
|
createSleevePurchaseAugsPopup(sleeve, p);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,48 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* Implements the purchasing of extra Duplicate Sleeves from The Covenant
|
* Implements the purchasing of extra Duplicate Sleeves from The Covenant,
|
||||||
|
* as well as the purchasing of upgrades (memory)
|
||||||
*/
|
*/
|
||||||
import { Sleeve } from "./Sleeve";
|
|
||||||
import { IPlayer } from "../IPlayer";
|
import { IPlayer } from "../IPlayer";
|
||||||
|
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
|
||||||
|
import { createPopup,
|
||||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
removePopup } from "../../ui/React/createPopup";
|
||||||
import { yesNoBoxCreate,
|
|
||||||
yesNoBoxClose,
|
|
||||||
yesNoBoxGetYesButton,
|
|
||||||
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
|
|
||||||
|
|
||||||
export const MaxSleevesFromCovenant: number = 5;
|
export const MaxSleevesFromCovenant: number = 5;
|
||||||
|
export const BaseCostPerSleeve: number = 10e12;
|
||||||
|
export const PopupId: string = "covenant-sleeve-purchases-popup";
|
||||||
|
|
||||||
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) {
|
export function createSleevePurchasesFromCovenantPopup(p: IPlayer) {
|
||||||
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
|
const removePopupFn = removePopup.bind(null, PopupId);
|
||||||
|
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
|
||||||
// First sleeve purchased costs the base amount. Then, the price of
|
|
||||||
// each successive one increases by the same amount
|
|
||||||
const baseCostPerExtraSleeve: number = 10e12;
|
|
||||||
const cost: number = (p.sleevesFromCovenant + 1) * baseCostPerExtraSleeve;
|
|
||||||
|
|
||||||
const yesBtn = yesNoBoxGetYesButton();
|
|
||||||
const noBtn = yesNoBoxGetNoButton();
|
|
||||||
|
|
||||||
yesBtn!.addEventListener("click", () => {
|
|
||||||
if (p.canAfford(cost)) {
|
|
||||||
p.loseMoney(cost);
|
|
||||||
p.sleevesFromCovenant += 1;
|
|
||||||
p.sleeves.push(new Sleeve(p));
|
|
||||||
yesNoBoxClose();
|
|
||||||
} else {
|
|
||||||
dialogBoxCreate("You cannot afford to purchase a Duplicate Sleeve", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
noBtn!.addEventListener("click", () => {
|
|
||||||
yesNoBoxClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
const txt = `Would you like to purchase an additional Duplicate Sleeve from The Covenant for ` +
|
|
||||||
`${numeralWrapper.formatMoney(cost)}?<br><br>` +
|
|
||||||
`These Duplicate Sleeves are permanent. You can purchase a total of 5 Duplicate ` +
|
|
||||||
`Sleeves from The Covenant`;
|
|
||||||
yesNoBoxCreate(txt);
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user