mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 23:53: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
|
||||
The game's official documentation can be found on [Read The
|
||||
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).
|
||||
|
||||
@ -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
|
||||
[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
|
||||
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,
|
||||
|
@ -51,6 +51,44 @@
|
||||
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-div {
|
||||
display: inline-block;
|
||||
|
@ -38,6 +38,7 @@ button {
|
||||
}
|
||||
|
||||
.a-link-button-inactive,
|
||||
.std-button-disabled,
|
||||
.std-button:disabled {
|
||||
text-decoration: none;
|
||||
background-color: #333;
|
||||
|
@ -2,7 +2,7 @@
|
||||
@import "theme";
|
||||
|
||||
/**
|
||||
* Styling for the Character Overview Panel (top-right)
|
||||
* Styling for the Character Overview Panel (top-right panel)
|
||||
*/
|
||||
|
||||
#character-overview-wrapper {
|
||||
|
@ -10,7 +10,8 @@
|
||||
|
||||
#cmpy-mgmt-container p,
|
||||
#cmpy-mgmt-container a,
|
||||
#cmpy-mgmt-container div {
|
||||
#cmpy-mgmt-container div,
|
||||
#cmpy-mgmt-container br {
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
}
|
||||
|
||||
@ -159,5 +160,6 @@
|
||||
|
||||
/* Research */
|
||||
#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-container {
|
||||
position: fixed;
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
/* Pop-up boxes */
|
||||
.popup-box-container {
|
||||
display: none; /* Hidden by default */
|
||||
display: none; /* Initially hidden */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 10; /* Sit on top */
|
||||
left: 0;
|
||||
|
@ -62,6 +62,9 @@ a:visited {
|
||||
.text-input {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
/* 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>`.
|
||||
|
||||
.. _gameplay_duplicatesleeves:
|
||||
|
||||
Duplicate Sleeves
|
||||
^^^^^^^^^^^^^^^^^
|
||||
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
|
||||
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 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
|
||||
it decreases by assigning sleeves to the 'Shock Recovery' task.
|
||||
|
||||
Obtaining Duplicate Sleeves
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
There are two methods of obtaining Duplicate Sleeves:
|
||||
Augmentations
|
||||
~~~~~~~~~~~~~
|
||||
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
|
||||
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. You can purchase
|
||||
up to 5 Duplicate Sleeves from The Covenant.
|
||||
When you purchase an Augmentation for a Duplicate Sleeve, it is instantly installed.
|
||||
When this happens, the Sleeve's stats are instantly reset back to 0, similar to
|
||||
when you normally install Augmentations.
|
||||
|
||||
The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected
|
||||
by how many Augmentations you have purchased for yourself, and vice versa.
|
||||
|
||||
Memory
|
||||
~~~~~~
|
||||
Sleeve memory dictates what a sleeve's synchronization will be when its reset by
|
||||
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
|
||||
switch BitNodes its synchronization will initially be set to 10, rather than 1.
|
||||
|
||||
Memory can only be increased by purchasing upgrades from The Covenant.
|
||||
It is a persistent stat, meaning it never gets reset back to 1.
|
||||
The maximum possible value for a sleeve's memory is 100.
|
||||
|
||||
Re-sleeving
|
||||
^^^^^^^^^^^
|
||||
|
@ -48,6 +48,7 @@ List of all Source-Files
|
||||
| BitNode-9: Coming Soon | |
|
||||
+------------------------------------+-------------------------------------------------------------------------------------+
|
||||
| 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 |
|
||||
| | company by 1% per favor (rather than just the reputation gain) |
|
||||
|
@ -3,6 +3,42 @@
|
||||
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
|
||||
------------------
|
||||
* Duplicate Sleeve changes:
|
||||
|
@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.44'
|
||||
version = '0.45'
|
||||
# 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
|
||||
# for a list of supported languages.
|
||||
|
@ -29,4 +29,5 @@ to reach out to the developer!
|
||||
Bladeburner API <netscript/netscriptbladeburnerapi>
|
||||
Gang API <netscript/netscriptgangapi>
|
||||
Coding Contract API <netscript/netscriptcodingcontractapi>
|
||||
Sleeve API <netscript/netscriptsleeveapi>
|
||||
Miscellaneous <netscript/netscriptmisc>
|
||||
|
@ -1,5 +1,5 @@
|
||||
enableLog() Netscript Function
|
||||
=============================
|
||||
==============================
|
||||
|
||||
.. js:function:: enableLog(fn)
|
||||
|
||||
|
@ -4,7 +4,7 @@ purchaseServer() Netscript Function
|
||||
.. js:function:: purchaseServer(hostname, ram)
|
||||
|
||||
: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
|
||||
|
||||
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
|
||||
they contain spoilers for the game.
|
||||
|
||||
.. warning:: This page contains spoilers for the game
|
||||
|
||||
.. toctree::
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
**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.
|
||||
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
|
||||
===================================
|
||||
|
||||
.. 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.
|
||||
|
||||
|
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>
|
||||
<fieldset>
|
||||
<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="CodeMirror">CodeMirror</option>
|
||||
</select>
|
||||
@ -139,12 +139,12 @@
|
||||
|
||||
<fieldset>
|
||||
<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>
|
||||
<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>
|
||||
@ -689,7 +689,7 @@
|
||||
<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>
|
||||
<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>
|
||||
<ul id="stock-market-list" style="list-style:none;">
|
||||
</ul>
|
||||
@ -744,7 +744,7 @@
|
||||
<p id="infiltration-box-text"> </p>
|
||||
|
||||
<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>
|
||||
|
||||
</div>
|
||||
@ -783,35 +783,7 @@
|
||||
<div id="character-overview-wrapper">
|
||||
<div id="character-overview-container">
|
||||
<div id="character-overview-text">
|
||||
<table>
|
||||
<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>
|
||||
<!-- ReactJS Component -->
|
||||
</div>
|
||||
<div class="character-quick-options">
|
||||
<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'
|
||||
</span>
|
||||
</label>
|
||||
<select name="settingsLocale" id="settingsLocale">
|
||||
<select name="settingsLocale" id="settingsLocale" class="dropdown">
|
||||
<option value="en">en</option>
|
||||
<option value="bg">bg</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",
|
||||
"interpret": "^1.0.0",
|
||||
"jquery": "^3.3.1",
|
||||
"jshint": "^2.9.7",
|
||||
"jshint": "^2.10.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"jsplumb": "^2.6.8",
|
||||
"jszip": "^3.1.5",
|
||||
@ -37,7 +37,6 @@
|
||||
"react-dom": "^16.8.3",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tapable": "^1.0.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"uuid": "^3.2.1",
|
||||
"w3c-blob": "0.0.1"
|
||||
},
|
||||
@ -61,7 +60,7 @@
|
||||
"istanbul": "^0.4.5",
|
||||
"js-beautify": "^1.5.10",
|
||||
"json5": "^1.0.1",
|
||||
"less": "^3.0.4",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.1.0",
|
||||
"lodash": "^4.17.10",
|
||||
"mini-css-extract-plugin": "^0.4.1",
|
||||
@ -80,15 +79,17 @@
|
||||
"stylelint": "^9.2.1",
|
||||
"stylelint-declaration-use-variable": "^1.6.1",
|
||||
"stylelint-order": "^0.8.1",
|
||||
"ts-loader": "^4.4.1",
|
||||
"ts-loader": "^4.5.0",
|
||||
"tslint": "^5.10.0",
|
||||
"typescript": "^2.9.2",
|
||||
"uglify-es": "^3.3.9",
|
||||
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||
"url-loader": "^1.0.1",
|
||||
"watchpack": "^1.6.0",
|
||||
"webpack": "^4.12.0",
|
||||
"webpack-cli": "^3.0.4",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-dev-server": "^3.1.4",
|
||||
"webpack-dev-server": "^3.2.1",
|
||||
"worker-loader": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -113,5 +114,5 @@
|
||||
"watch": "webpack --watch --mode production",
|
||||
"watch:dev": "webpack --watch --mode development"
|
||||
},
|
||||
"version": "0.40.2"
|
||||
"version": "0.45.0"
|
||||
}
|
||||
|
@ -1213,17 +1213,19 @@ function initAugmentations() {
|
||||
|
||||
//Illuminati
|
||||
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 " +
|
||||
"quantum supercomputer, allowing you to access and use its incredible " +
|
||||
"computing power.<br><br>" +
|
||||
"This augmentation:<br>" +
|
||||
"Increases the player's hacking speed by 10%.<br>" +
|
||||
"Increases the player's chance of successfully performing a hack by 30%.<br>" +
|
||||
"Increases the amount of money the player gains from hacking by 100%.",
|
||||
hacking_speed_mult: 1.1,
|
||||
hacking_chance_mult: 1.3,
|
||||
hacking_money_mult: 2,
|
||||
"Increases the player's hacking skill by 75%.<br>" +
|
||||
"Increases the player's hacking speed by 100%.<br>" +
|
||||
"Increases the player's chance of successfully performing a hack by 150%.<br>" +
|
||||
"Increases the amount of money the player gains from hacking by 300%.",
|
||||
hacking_mult: 1.75,
|
||||
hacking_speed_mult: 2,
|
||||
hacking_chance_mult: 2.5,
|
||||
hacking_money_mult: 4,
|
||||
});
|
||||
QLink.addToFactions(["Illuminati"]);
|
||||
if (augmentationExists(AugmentationNames.QLink)) {
|
||||
@ -2067,7 +2069,7 @@ function installAugmentations(cbScript=null) {
|
||||
}
|
||||
var runningScriptObj = new RunningScript(script, []); //No args
|
||||
runningScriptObj.threads = 1; //Only 1 thread
|
||||
home.runningScripts.push(runningScriptObj);
|
||||
home.runScript(runningScriptObj, Player);
|
||||
addWorkerScript(runningScriptObj, home);
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,10 @@ export function initBitNodes() {
|
||||
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
|
||||
"This Source-File also increases your hacking growth multipliers by: " +
|
||||
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
|
||||
BitNodes["BitNode9"] = new BitNode(9, "Do Androids Dream?", "COMING SOON");
|
||||
BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
|
||||
"When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
|
||||
"became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
|
||||
"powering the Hacknet, ");
|
||||
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
|
||||
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
|
||||
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
|
||||
@ -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 " +
|
||||
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
|
||||
"In this BitNode:<br><br>" +
|
||||
"Your hacking stat and experience gain are halved<br>" +
|
||||
"The starting and maximum amount of money available on servers is significantly decreased<br>" +
|
||||
"The growth rate of servers is halved<br>" +
|
||||
"The growth rate of servers is significantly reduced<br>" +
|
||||
"Weakening a server is twice as effective<br>" +
|
||||
"Company wages are decreased by 50%<br>" +
|
||||
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
|
||||
@ -210,9 +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 " +
|
||||
"the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
|
||||
"This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
|
||||
"Level 1: 24%<br>" +
|
||||
"Level 2: 36%<br>" +
|
||||
"Level 3: 42%");
|
||||
"Level 1: 32%<br>" +
|
||||
"Level 2: 48%<br>" +
|
||||
"Level 3: 56%");
|
||||
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
|
||||
"To iterate is human, to recurse divine.<br><br>" +
|
||||
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
|
||||
@ -245,9 +249,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
}
|
||||
|
||||
switch (p.bitNodeN) {
|
||||
case 1: //Source Genesis (every multiplier is 1)
|
||||
case 1: // Source Genesis (every multiplier is 1)
|
||||
break;
|
||||
case 2: //Rise of the Underworld
|
||||
case 2: // Rise of the Underworld
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||
BitNodeMultipliers.ServerGrowthRate = 0.8;
|
||||
BitNodeMultipliers.ServerMaxMoney = 0.2;
|
||||
@ -257,7 +261,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.FactionWorkRepGain = 0.5;
|
||||
BitNodeMultipliers.FactionPassiveRepGain = 0;
|
||||
break;
|
||||
case 3: //Corporatocracy
|
||||
case 3: // Corporatocracy
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
|
||||
BitNodeMultipliers.RepToDonateToFaction = 0.5;
|
||||
BitNodeMultipliers.AugmentationRepCost = 3;
|
||||
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
||||
@ -268,8 +273,10 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.CompanyWorkMoney = 0.25;
|
||||
BitNodeMultipliers.CrimeMoney = 0.25;
|
||||
BitNodeMultipliers.HacknetNodeMoney = 0.25;
|
||||
BitNodeMultipliers.HomeComputerRamCost = 1.5;
|
||||
BitNodeMultipliers.PurchasedServerCost = 2;
|
||||
break;
|
||||
case 4: //The Singularity
|
||||
case 4: // The Singularity
|
||||
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
||||
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
||||
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
||||
@ -283,7 +290,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.CrimeExpGain = 0.5;
|
||||
BitNodeMultipliers.FactionWorkRepGain = 0.75;
|
||||
break;
|
||||
case 5: //Artificial intelligence
|
||||
case 5: // Artificial intelligence
|
||||
BitNodeMultipliers.ServerMaxMoney = 2;
|
||||
BitNodeMultipliers.ServerStartingSecurity = 2;
|
||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||
@ -296,7 +303,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.HackExpGain = 0.5;
|
||||
BitNodeMultipliers.CorporationValuation = 0.5;
|
||||
break;
|
||||
case 6: //Bladeburner
|
||||
case 6: // Bladeburner
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.35;
|
||||
BitNodeMultipliers.ServerMaxMoney = 0.4;
|
||||
BitNodeMultipliers.ServerStartingMoney = 0.5;
|
||||
@ -311,7 +318,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.HackExpGain = 0.25;
|
||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||
break;
|
||||
case 7: //Bladeburner 2079
|
||||
case 7: // Bladeburner 2079
|
||||
BitNodeMultipliers.BladeburnerRank = 0.6;
|
||||
BitNodeMultipliers.BladeburnerSkillCost = 2;
|
||||
BitNodeMultipliers.AugmentationMoneyCost = 3;
|
||||
@ -331,7 +338,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
|
||||
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
|
||||
break;
|
||||
case 8: //Ghost of Wall Street
|
||||
case 8: // Ghost of Wall Street
|
||||
BitNodeMultipliers.ScriptHackMoney = 0;
|
||||
BitNodeMultipliers.ManualHackMoney = 0;
|
||||
BitNodeMultipliers.CompanyWorkMoney = 0;
|
||||
@ -342,6 +349,23 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.CorporationValuation = 0;
|
||||
BitNodeMultipliers.CodingContractMoney = 0;
|
||||
break;
|
||||
case 9: // Hacktocracy
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.4;
|
||||
BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
|
||||
BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
|
||||
BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
|
||||
BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
|
||||
BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
|
||||
BitNodeMultipliers.PurchasedServerLimit = 0;
|
||||
BitNodeMultipliers.HomeComputerRamCost = 5;
|
||||
BitNodeMultipliers.CrimeMoney = 0.5;
|
||||
BitNodeMultipliers.ScriptHackMoney = 0.1;
|
||||
BitNodeMultipliers.HackExpGain = 0.1;
|
||||
BitNodeMultipliers.ServerStartingMoney = 0.1;
|
||||
BitNodeMultipliers.ServerMaxMoney = 0.1;
|
||||
BitNodeMultipliers.ServerStartingSecurity = 2.5;
|
||||
BitNodeMultipliers.CorporationValuation = 0.5;
|
||||
break;
|
||||
case 10: // Digital Carbon
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
|
||||
BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
|
||||
@ -363,11 +387,14 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.PurchasedServerCost = 5;
|
||||
BitNodeMultipliers.PurchasedServerLimit = 0.6;
|
||||
BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
|
||||
BitNodeMultipliers.BladeburnerRank = 0.8;
|
||||
break;
|
||||
case 11: //The Big Crash
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
|
||||
BitNodeMultipliers.HackExpGain = 0.5;
|
||||
BitNodeMultipliers.ServerMaxMoney = 0.1;
|
||||
BitNodeMultipliers.ServerStartingMoney = 0.1;
|
||||
BitNodeMultipliers.ServerGrowthRate = 0.5;
|
||||
BitNodeMultipliers.ServerGrowthRate = 0.2;
|
||||
BitNodeMultipliers.ServerWeakenRate = 2;
|
||||
BitNodeMultipliers.CrimeMoney = 3;
|
||||
BitNodeMultipliers.CompanyWorkMoney = 0.5;
|
||||
@ -375,8 +402,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
|
||||
BitNodeMultipliers.AugmentationMoneyCost = 2;
|
||||
BitNodeMultipliers.InfiltrationMoney = 2.5;
|
||||
BitNodeMultipliers.InfiltrationRep = 2.5;
|
||||
BitNodeMultipliers.CorporationValuation = 0.01;
|
||||
BitNodeMultipliers.CodingContractMoney = 0.5;
|
||||
BitNodeMultipliers.CorporationValuation = 0.1;
|
||||
BitNodeMultipliers.CodingContractMoney = 0.25;
|
||||
BitNodeMultipliers.FourSigmaMarketDataCost = 4;
|
||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
|
||||
break;
|
||||
|
@ -120,7 +120,8 @@ interface IBitNodeMultipliers {
|
||||
HackingLevelMultiplier: number;
|
||||
|
||||
/**
|
||||
* Influences how much money each Hacknet node can generate.
|
||||
* Influences how much money is produced by Hacknet Nodes.
|
||||
* Influeces the hash rate of Hacknet Servers (unlocked in BitNode-9)
|
||||
*/
|
||||
HacknetNodeMoney: number;
|
||||
|
||||
|
@ -31,6 +31,8 @@ import { getTimestamp } from "../utils/helpers/getTi
|
||||
import { removeElement } from "../utils/uiHelpers/removeElement";
|
||||
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"];
|
||||
|
||||
@ -76,8 +78,8 @@ const RanksPerSkillPoint = 3; //How many ranks needed to get 1 Skill
|
||||
|
||||
const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
|
||||
|
||||
const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber
|
||||
const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber
|
||||
const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber
|
||||
const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber
|
||||
|
||||
//DOM related variables
|
||||
var ActiveActionCssClass = "bladeburner-active-action";
|
||||
@ -1415,14 +1417,17 @@ Bladeburner.prototype.completeAction = function() {
|
||||
}
|
||||
this.startAction(this.action); // Repeat Action
|
||||
break;
|
||||
case ActionTypes["Hyperbolic Regeneration Chamber"]:
|
||||
case ActionTypes["Hyperbolic Regeneration Chamber"]: {
|
||||
Player.regenerateHp(HrcHpGain);
|
||||
this.stamina = Math.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);
|
||||
if (this.logging.general) {
|
||||
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`);
|
||||
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
|
||||
break;
|
||||
@ -1799,6 +1804,12 @@ Bladeburner.prototype.createContent = function() {
|
||||
DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv);
|
||||
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);
|
||||
|
||||
if (this.consoleLogs.length === 0) {
|
||||
@ -2166,12 +2177,12 @@ Bladeburner.prototype.createBlackOpsContent = function() {
|
||||
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", {
|
||||
class:"bladeburner-action", name: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
|
||||
display:"inline-block",
|
||||
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" +
|
||||
"Contracts remaining: " + Math.floor(action.count) + "\n" +
|
||||
"Successes: " + action.successes + "\n" +
|
||||
@ -2518,14 +2530,21 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
|
||||
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
||||
tooltip:"Automatically increase contract level when possible"
|
||||
}));
|
||||
var autolevelCheckbox = createElement("input", {
|
||||
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
||||
checked:action.autoLevel,
|
||||
changeListener:()=>{
|
||||
action.autoLevel = autolevelCheckbox.checked;
|
||||
}
|
||||
|
||||
const checkboxDiv = createElement("div", { class: "bbcheckbox" });
|
||||
const checkboxInput = createElement("input", {
|
||||
type:"checkbox",
|
||||
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) {
|
||||
@ -2640,7 +2659,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
||||
el.appendChild(createElement("pre", {
|
||||
display:"inline-block",
|
||||
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" +
|
||||
"Operations remaining: " + Math.floor(action.count) + "\n" +
|
||||
"Successes: " + action.successes + "\n" +
|
||||
@ -2654,14 +2673,21 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
||||
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
||||
tooltip:"Automatically increase operation level when possible"
|
||||
}));
|
||||
var autolevelCheckbox = createElement("input", {
|
||||
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
||||
checked:action.autoLevel,
|
||||
changeListener:()=>{
|
||||
action.autoLevel = autolevelCheckbox.checked;
|
||||
}
|
||||
|
||||
const checkboxDiv = createElement("div", { class: "bbcheckbox" });
|
||||
const checkboxInput = createElement("input", {
|
||||
type:"checkbox",
|
||||
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) {
|
||||
@ -2760,7 +2786,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
|
||||
}));
|
||||
el.appendChild(createElement("p", {
|
||||
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),
|
||||
}))
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
/**
|
||||
* Generic Game Constants
|
||||
*
|
||||
* Constants for specific mechanics or features will NOT be here.
|
||||
*/
|
||||
import {IMap} from "./types";
|
||||
|
||||
export let CONSTANTS: IMap<any> = {
|
||||
Version: "0.44.1",
|
||||
Version: "0.45.1",
|
||||
|
||||
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||
@ -17,24 +22,9 @@ export let CONSTANTS: IMap<any> = {
|
||||
/* Base costs */
|
||||
BaseCostFor1GBOfRamHome: 32000,
|
||||
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
|
||||
BaseCostFor1GBOfRamHacknetNode: 30000,
|
||||
|
||||
TravelCost: 200e3,
|
||||
|
||||
BaseCostForHacknetNode: 1000,
|
||||
BaseCostForHacknetNodeCore: 500000,
|
||||
|
||||
/* Hacknet Node constants */
|
||||
HacknetNodeMoneyGainPerLevel: 1.6,
|
||||
HacknetNodePurchaseNextMult: 1.85, //Multiplier when purchasing an additional hacknet node
|
||||
HacknetNodeUpgradeLevelMult: 1.04, //Multiplier for cost when upgrading level
|
||||
HacknetNodeUpgradeRamMult: 1.28, //Multiplier for cost when upgrading RAM
|
||||
HacknetNodeUpgradeCoreMult: 1.48, //Multiplier for cost when buying another core
|
||||
|
||||
HacknetNodeMaxLevel: 200,
|
||||
HacknetNodeMaxRam: 64,
|
||||
HacknetNodeMaxCores: 16,
|
||||
|
||||
/* Faction and Company favor */
|
||||
BaseFavorToDonate: 150,
|
||||
DonateMoneyToRepDivisor: 1e6,
|
||||
@ -85,11 +75,12 @@ export let CONSTANTS: IMap<any> = {
|
||||
ScriptGetPurchasedServerMaxRam: 0.05,
|
||||
ScriptRoundRamCost: 0.05,
|
||||
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,
|
||||
ScriptGetHackTimeRamCost: 0.05,
|
||||
ScriptGetFavorToDonate: 0.10,
|
||||
ScriptCodingContractBaseRamCost:10,
|
||||
ScriptSleeveBaseRamCost: 4,
|
||||
|
||||
ScriptSingularityFn1RamCost: 1,
|
||||
ScriptSingularityFn2RamCost: 2,
|
||||
@ -125,6 +116,7 @@ export let CONSTANTS: IMap<any> = {
|
||||
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
|
||||
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
|
||||
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
|
||||
InfiltrationExpPow: 0.8,
|
||||
|
||||
//Stock market constants
|
||||
WSEAccountCost: 200e6,
|
||||
@ -281,17 +273,20 @@ export let CONSTANTS: IMap<any> = {
|
||||
|
||||
LatestUpdate:
|
||||
`
|
||||
v0.45.0
|
||||
* Corporation changes:
|
||||
** Decreased the time of a full market cycle from 15 seconds to 10 seconds.
|
||||
** This means that each Corporation 'state' will now only take 2 seconds, rather than 3
|
||||
** Increased initial salaries for newly-hired employees
|
||||
** Increased the cost multiplier for upgrading office size (the cost will increase faster)
|
||||
** The stats of your employees now has a slightly larger effect on production & sales
|
||||
** Added several new Research upgrades
|
||||
** 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.
|
||||
`
|
||||
v0.46.0
|
||||
* Added BitNode-9: Hacktocracy
|
||||
* Changed BitNode-11's multipliers to make it slightly harder overall
|
||||
* Source-File 11 is now slightly stronger
|
||||
* Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame)
|
||||
* Added a new stat for Duplicate Sleeves: Memory
|
||||
* Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels
|
||||
* In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina
|
||||
|
||||
* Corporation Changes:
|
||||
** 'Demand' value of products decreases more slowly
|
||||
** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
|
||||
|
||||
* Bug Fix: 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 { showLiterature } from "../Literature";
|
||||
import { Locations } from "../Locations";
|
||||
import { createCityMap } from "../Locations/Cities";
|
||||
import { Player } from "../Player";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
|
||||
import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { clearSelector } from "../../utils/uiHelpers/clearSelector";
|
||||
import { Reviver,
|
||||
@ -37,7 +40,6 @@ import { formatNumber, generateRandomString } from "../../utils/String
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { isString } from "../../utils/helpers/isString";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
|
||||
import { removeElement } from "../../utils/uiHelpers/removeElement";
|
||||
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
|
||||
import { yesNoBoxCreate,
|
||||
@ -48,8 +50,7 @@ import { yesNoBoxCreate,
|
||||
yesNoTxtInpBoxGetNoButton,
|
||||
yesNoTxtInpBoxGetInput,
|
||||
yesNoBoxClose,
|
||||
yesNoTxtInpBoxClose,
|
||||
yesNoBoxOpen } from "../../utils/YesNoBox";
|
||||
yesNoTxtInpBoxClose } from "../../utils/YesNoBox";
|
||||
|
||||
// UI Related Imports
|
||||
import React from "react";
|
||||
@ -60,7 +61,6 @@ import { CorporationRouting } from "./ui/Routing";
|
||||
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
|
||||
/* Constants */
|
||||
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
|
||||
@ -124,18 +124,6 @@ function Industry(params={}) {
|
||||
[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.type = params.type ? params.type : 0;
|
||||
|
||||
@ -183,6 +171,20 @@ function Industry(params={}) {
|
||||
this.state = "START";
|
||||
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();
|
||||
}
|
||||
|
||||
@ -340,8 +342,8 @@ Industry.prototype.init = function() {
|
||||
this.sciFac = 0.62;
|
||||
this.advFac = 0.16;
|
||||
this.hwFac = 0.25;
|
||||
this.reFac = 0.1;
|
||||
this.aiFac = 0.15;
|
||||
this.reFac = 0.15;
|
||||
this.aiFac = 0.18;
|
||||
this.robFac = 0.05;
|
||||
this.reqMats = {
|
||||
"Hardware": 0.5,
|
||||
@ -487,11 +489,11 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
||||
this.thisCycleRevenue = new Decimal(0);
|
||||
this.thisCycleExpenses = new Decimal(0);
|
||||
|
||||
//Once you start making revenue, the player should no longer be
|
||||
//considered new, and therefore no longer needs the 'tutorial' UI elements
|
||||
// Once you start making revenue, the player should no longer be
|
||||
// considered new, and therefore no longer needs the 'tutorial' UI elements
|
||||
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;
|
||||
for (var officeLoc in this.offices) {
|
||||
if (this.offices[officeLoc] instanceof OfficeSpace) {
|
||||
@ -500,15 +502,15 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
||||
}
|
||||
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.processProductMarket(marketCycles);
|
||||
|
||||
//Process loss of popularity
|
||||
// Process loss of popularity
|
||||
this.popularity -= (marketCycles * .0001);
|
||||
this.popularity = Math.max(0, this.popularity);
|
||||
|
||||
//Process Dreamsense gains
|
||||
// Process Dreamsense gains
|
||||
var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4;
|
||||
if (popularityGain > 0) {
|
||||
this.popularity += (popularityGain * marketCycles);
|
||||
@ -518,19 +520,19 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Process production, purchase, and import/export of materials
|
||||
// Process production, purchase, and import/export of materials
|
||||
var res = this.processMaterials(marketCycles, company);
|
||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
|
||||
|
||||
//Process creation, production & sale of products
|
||||
// Process creation, production & sale of products
|
||||
res = this.processProducts(marketCycles, company);
|
||||
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
|
||||
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) {
|
||||
//References to prodMats and reqMats
|
||||
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) {
|
||||
//Demand gradually decreases, and competition gradually increases
|
||||
for (var name in this.products) {
|
||||
// Demand gradually decreases, and competition gradually increases
|
||||
for (const name in this.products) {
|
||||
if (this.products.hasOwnProperty(name)) {
|
||||
var product = this.products[name];
|
||||
var change = getRandomInt(1, 3) * 0.0004;
|
||||
const product = this.products[name];
|
||||
let change = getRandomInt(0, 3) * 0.0004;
|
||||
if (change === 0) { continue; }
|
||||
|
||||
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
|
||||
this.type === Industries.Robotics) {
|
||||
change *= 3;
|
||||
@ -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
|
||||
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];
|
||||
if (!(this.warehouses[city] instanceof Warehouse)) {
|
||||
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];
|
||||
|
||||
if (this.warehouses[city] instanceof Warehouse) {
|
||||
@ -663,19 +667,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
prod = maxProd;
|
||||
}
|
||||
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;
|
||||
for (var tmp = 0; tmp < this.prodMats.length; ++tmp) {
|
||||
for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
|
||||
totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
|
||||
}
|
||||
for (var reqMatName in this.reqMats) {
|
||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||
var normQty = this.reqMats[reqMatName];
|
||||
totalMatSize -= (MaterialSizes[reqMatName] * normQty);
|
||||
}
|
||||
for (const reqMatName in this.reqMats) {
|
||||
var normQty = this.reqMats[reqMatName];
|
||||
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) {
|
||||
var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
|
||||
prod = Math.min(maxAmt, prod);
|
||||
@ -683,10 +685,10 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
|
||||
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));
|
||||
|
||||
//Make sure we have enough resource to make our materials
|
||||
// Make sure we have enough resource to make our materials
|
||||
var producableFrac = 1;
|
||||
for (var reqMatName in this.reqMats) {
|
||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||
@ -698,25 +700,23 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
}
|
||||
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) {
|
||||
for (var reqMatName in this.reqMats) {
|
||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
|
||||
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
|
||||
warehouse.materials[reqMatName].prd = 0;
|
||||
warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
|
||||
}
|
||||
for (const reqMatName in this.reqMats) {
|
||||
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
|
||||
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
|
||||
warehouse.materials[reqMatName].prd = 0;
|
||||
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]].qlt =
|
||||
(office.employeeProd[EmployeePositions.Engineer] / 100 +
|
||||
(office.employeeProd[EmployeePositions.Engineer] / 90 +
|
||||
Math.pow(this.sciResearch.qty, this.sciFac) +
|
||||
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
|
||||
}
|
||||
} else {
|
||||
for (var reqMatName in this.reqMats) {
|
||||
for (const reqMatName in this.reqMats) {
|
||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||
warehouse.materials[reqMatName].prd = 0;
|
||||
}
|
||||
@ -724,18 +724,16 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
}
|
||||
|
||||
//Per second
|
||||
var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
|
||||
for (var fooI = 0; fooI < this.prodMats.length; ++fooI) {
|
||||
const fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
|
||||
for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {
|
||||
warehouse.materials[this.prodMats[fooI]].prd = fooProd;
|
||||
}
|
||||
} else {
|
||||
//If this doesn't produce any materials, then it only creates
|
||||
//Products. Creating products will consume materials. The
|
||||
//Production of all consumed materials must be set to 0
|
||||
for (var reqMatName in this.reqMats) {
|
||||
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
||||
warehouse.materials[reqMatName].prd = 0;
|
||||
}
|
||||
for (const reqMatName in this.reqMats) {
|
||||
warehouse.materials[reqMatName].prd = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -749,12 +747,49 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
mat.sll = 0;
|
||||
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();
|
||||
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;
|
||||
} else if (isString(mat.sCost)) {
|
||||
sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
||||
@ -778,9 +813,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
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)
|
||||
* marketFactor
|
||||
* markup
|
||||
@ -903,8 +936,8 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
|
||||
//Produce Scientific Research based on R&D employees
|
||||
//Scientific Research can be produced without a warehouse
|
||||
if (office instanceof OfficeSpace) {
|
||||
this.sciResearch.qty += (.005
|
||||
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55)
|
||||
this.sciResearch.qty += (.004
|
||||
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5)
|
||||
* company.getScientificResearchMultiplier()
|
||||
* this.getScientificResearchMultiplier());
|
||||
}
|
||||
@ -918,27 +951,30 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
|
||||
|
||||
//Create products
|
||||
if (this.state === "PRODUCTION") {
|
||||
for (var prodName in this.products) {
|
||||
if (this.products.hasOwnProperty(prodName)) {
|
||||
var prod = this.products[prodName];
|
||||
if (!prod.fin) {
|
||||
var city = prod.createCity, office = this.offices[city];
|
||||
var total = office.employeeProd[EmployeePositions.Operations] +
|
||||
office.employeeProd[EmployeePositions.Engineer] +
|
||||
office.employeeProd[EmployeePositions.Management], ratio;
|
||||
if (total === 0) {
|
||||
ratio = 0;
|
||||
} else {
|
||||
ratio = office.employeeProd[EmployeePositions.Engineer] / total +
|
||||
office.employeeProd[EmployeePositions.Operations] / total +
|
||||
office.employeeProd[EmployeePositions.Management] / total;
|
||||
}
|
||||
prod.createProduct(marketCycles, ratio * Math.pow(total, 0.35));
|
||||
if (prod.prog >= 100) {
|
||||
prod.finishProduct(office.employeeProd, this);
|
||||
}
|
||||
break;
|
||||
for (const prodName in this.products) {
|
||||
const prod = this.products[prodName];
|
||||
if (!prod.fin) {
|
||||
const city = prod.createCity;
|
||||
const office = this.offices[city];
|
||||
|
||||
// Designing/Creating a Product is based mostly off Engineers
|
||||
const engrProd = office.employeeProd[EmployeePositions.Engineer];
|
||||
const mgmtProd = office.employeeProd[EmployeePositions.Management];
|
||||
const opProd = office.employeeProd[EmployeePositions.Operations];
|
||||
const total = engrProd + mgmtProd + opProd;
|
||||
|
||||
if (total <= 0) { break; }
|
||||
|
||||
// Management is a multiplier for the production from Engineers
|
||||
const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
|
||||
|
||||
const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;
|
||||
|
||||
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
|
||||
Industry.prototype.processProduct = function(marketCycles=1, product, corporation) {
|
||||
var totalProfit = 0;
|
||||
for (var i = 0; i < Cities.length; ++i) {
|
||||
var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
|
||||
let totalProfit = 0;
|
||||
for (let i = 0; i < Cities.length; ++i) {
|
||||
let city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
|
||||
if (warehouse instanceof Warehouse) {
|
||||
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;
|
||||
|
||||
//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;
|
||||
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 = eval(sCost);
|
||||
} else {
|
||||
sCost = product.sCost;
|
||||
}
|
||||
|
||||
var markup = 1, markupLimit = product.rat / product.mku;
|
||||
var markup = 1;
|
||||
if (sCost > product.pCost) {
|
||||
if ((sCost - product.pCost) > markupLimit) {
|
||||
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
|
||||
* Math.pow(product.rat, 0.65)
|
||||
* marketFactor
|
||||
@ -1080,8 +1154,9 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
|
||||
} else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
|
||||
//Sell amount is manually limited
|
||||
sellAmt = Math.min(maxSell, product.sllman[city][1]);
|
||||
} else if (product.sllman[city][0] === false){
|
||||
sellAmt = 0;
|
||||
} else {
|
||||
//Backwards compatibility, -1 = 0
|
||||
sellAmt = maxSell;
|
||||
}
|
||||
if (sellAmt < 0) { sellAmt = 0; }
|
||||
@ -1109,8 +1184,7 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
|
||||
return totalProfit;
|
||||
}
|
||||
|
||||
Industry.prototype.discontinueProduct = function(product, parentRefs) {
|
||||
var company = parentRefs.company, industry = parentRefs.industry;
|
||||
Industry.prototype.discontinueProduct = function(product) {
|
||||
for (var productName in this.products) {
|
||||
if (this.products.hasOwnProperty(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) {
|
||||
var total = office.employeeProd[EmployeePositions.Operations] +
|
||||
office.employeeProd[EmployeePositions.Engineer] +
|
||||
office.employeeProd[EmployeePositions.Management], ratio;
|
||||
if (total === 0) {
|
||||
ratio = 0;
|
||||
} else {
|
||||
ratio = (office.employeeProd[EmployeePositions.Operations] / total) *
|
||||
(office.employeeProd[EmployeePositions.Engineer] / total) *
|
||||
(office.employeeProd[EmployeePositions.Management] / total);
|
||||
ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees
|
||||
}
|
||||
const opProd = office.employeeProd[EmployeePositions.Operations];
|
||||
const engrProd = office.employeeProd[EmployeePositions.Engineer];
|
||||
const mgmtProd = office.employeeProd[EmployeePositions.Management]
|
||||
const total = opProd + engrProd + mgmtProd;
|
||||
|
||||
if (total <= 0) { return 0; }
|
||||
|
||||
// Management is a multiplier for the production from Operations and Engineers
|
||||
const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
|
||||
|
||||
// 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) {
|
||||
return ratio * Math.pow(total, 0.25);
|
||||
// Products are harder to create and therefore have less production
|
||||
return 0.5 * balancingMult * prod;
|
||||
} 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) {
|
||||
var ratioMult = 1;
|
||||
if (office.employeeProd["total"] > 0) {
|
||||
ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]);
|
||||
}
|
||||
return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.25);
|
||||
const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
|
||||
|
||||
return calculateEffectWithFactors(businessProd, 0.26, 10e3);
|
||||
}
|
||||
|
||||
//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
|
||||
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
|
||||
@ -1271,10 +1350,8 @@ Industry.prototype.createResearchBox = function() {
|
||||
researchTreeBox = null;
|
||||
}
|
||||
|
||||
this.updateResearchTree();
|
||||
const researchTree = IndustryResearchTrees[this.type];
|
||||
|
||||
|
||||
// Create the popup first, so that the tree diagram can be added to it
|
||||
// This is handled by Treant
|
||||
researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" });
|
||||
@ -1324,6 +1401,10 @@ Industry.prototype.createResearchBox = function() {
|
||||
researchTree.research(allResearch[i]);
|
||||
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();
|
||||
} else {
|
||||
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
|
||||
Employee.prototype.process = function(marketCycles=1, office) {
|
||||
var gain = 0.001 * marketCycles,
|
||||
var gain = 0.003 * marketCycles,
|
||||
det = gain * Math.random();
|
||||
this.age += gain;
|
||||
this.exp += gain;
|
||||
@ -1425,16 +1506,9 @@ Employee.prototype.process = function(marketCycles=1, office) {
|
||||
this.eff += trainingEff;
|
||||
}
|
||||
|
||||
//Weight based on how full office is
|
||||
//Too many employees = more likely to decrease energy and happiness
|
||||
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;
|
||||
}
|
||||
this.ene -= det;
|
||||
this.hap -= det;
|
||||
|
||||
if (this.ene < office.minEne) {this.ene = office.minEne;}
|
||||
if (this.hap < office.minHap) {this.hap = office.minHap;}
|
||||
var salary = this.sal * marketCycles * SecsPerMarketCycle;
|
||||
@ -1591,6 +1665,14 @@ OfficeSpace.prototype.atCapacity = function() {
|
||||
OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
|
||||
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
|
||||
this.maxEne = 100;
|
||||
this.maxHap = 100;
|
||||
@ -1640,18 +1722,16 @@ OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
|
||||
salaryPaid += salary;
|
||||
}
|
||||
|
||||
this.calculateEmployeeProductivity(marketCycles, parentRefs);
|
||||
this.calculateEmployeeProductivity(parentRefs);
|
||||
return salaryPaid;
|
||||
}
|
||||
|
||||
OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) {
|
||||
OfficeSpace.prototype.calculateEmployeeProductivity = function(parentRefs) {
|
||||
var company = parentRefs.corporation, industry = parentRefs.industry;
|
||||
|
||||
//Reset
|
||||
for (const name in this.employeeProd) {
|
||||
if (this.employeeProd.hasOwnProperty(name)) {
|
||||
this.employeeProd[name] = 0;
|
||||
}
|
||||
this.employeeProd[name] = 0;
|
||||
}
|
||||
|
||||
var total = 0;
|
||||
@ -1774,8 +1854,7 @@ OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) {
|
||||
yesNoTxtInpBoxCreate("Give your employee a nickname!");
|
||||
}
|
||||
|
||||
OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) {
|
||||
var company = parentRefs.corporation, division = parentRefs.industry;
|
||||
OfficeSpace.prototype.hireRandomEmployee = function() {
|
||||
if (this.atCapacity()) { return; }
|
||||
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
|
||||
|
||||
@ -1799,13 +1878,15 @@ OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) {
|
||||
|
||||
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) {
|
||||
return this.hireRandomEmployee(parentRefs);
|
||||
return this.hireRandomEmployee();
|
||||
}
|
||||
}
|
||||
emp.name = name;
|
||||
this.employees.push(emp);
|
||||
|
||||
return emp;
|
||||
}
|
||||
|
||||
//Finds the first unassigned employee and assigns its to the specified job
|
||||
@ -1977,19 +2058,19 @@ Corporation.prototype.getInvestment = function() {
|
||||
switch (this.fundingRound) {
|
||||
case 0: //Seed
|
||||
percShares = 0.10;
|
||||
roundMultiplier = 5;
|
||||
roundMultiplier = 4;
|
||||
break;
|
||||
case 1: //Series A
|
||||
percShares = 0.35;
|
||||
roundMultiplier = 4;
|
||||
roundMultiplier = 3;
|
||||
break;
|
||||
case 2: //Series B
|
||||
percShares = 0.25;
|
||||
roundMultiplier = 4;
|
||||
roundMultiplier = 3;
|
||||
break;
|
||||
case 3: //Series C
|
||||
percShares = 0.20;
|
||||
roundMultiplier = 3.5;
|
||||
roundMultiplier = 2.5;
|
||||
break;
|
||||
case 4:
|
||||
return;
|
||||
@ -2292,8 +2373,6 @@ Corporation.prototype.rerender = function() {
|
||||
}
|
||||
if (!routing.isOn(Page.Corporation)) { return; }
|
||||
|
||||
console.log("Re-rendering...");
|
||||
|
||||
ReactDOM.render(<CorporationRoot
|
||||
corp={this}
|
||||
routing={corpRouting}
|
||||
|
@ -66,6 +66,7 @@ export class Material {
|
||||
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
||||
marketTa1: boolean = false;
|
||||
marketTa2: boolean = false;
|
||||
marketTa2Price: number = 0;
|
||||
|
||||
constructor(params: IConstructorParams = {}) {
|
||||
if (params.name) { this.name = params.name; }
|
||||
@ -85,7 +86,7 @@ export class Material {
|
||||
this.mku = 6;
|
||||
break;
|
||||
case "Energy":
|
||||
this.dmd = 90; this.dmdR = [80, 100];
|
||||
this.dmd = 90; this.dmdR = [80, 99];
|
||||
this.cmp = 80; this.cmpR = [65, 95];
|
||||
this.bCost = 2000; this.mv = 0.2;
|
||||
this.mku = 6;
|
||||
@ -121,26 +122,26 @@ export class Material {
|
||||
this.mku = 2;
|
||||
break;
|
||||
case "Real Estate":
|
||||
this.dmd = 50; this.dmdR = [5, 100];
|
||||
this.dmd = 50; this.dmdR = [5, 99];
|
||||
this.cmp = 50; this.cmpR = [25, 75];
|
||||
this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
|
||||
this.mku = 1.5;
|
||||
break;
|
||||
case "Drugs":
|
||||
this.dmd = 60; this.dmdR = [45, 75];
|
||||
this.cmp = 70; this.cmpR = [40, 100];
|
||||
this.cmp = 70; this.cmpR = [40, 99];
|
||||
this.bCost = 40e3; this.mv = 1.6;
|
||||
this.mku = 1;
|
||||
break;
|
||||
case "Robots":
|
||||
this.dmd = 90; this.dmdR = [80, 100];
|
||||
this.cmp = 90; this.cmpR = [80, 100];
|
||||
this.dmd = 90; this.dmdR = [80, 9];
|
||||
this.cmp = 90; this.cmpR = [80, 9];
|
||||
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
|
||||
this.mku = 1;
|
||||
break;
|
||||
case "AI Cores":
|
||||
this.dmd = 90; this.dmdR = [80, 100];
|
||||
this.cmp = 90; this.cmpR = [80, 100];
|
||||
this.dmd = 90; this.dmdR = [80, 99];
|
||||
this.cmp = 90; this.cmpR = [80, 9];
|
||||
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
|
||||
this.mku = 0.5;
|
||||
break;
|
||||
|
@ -2,15 +2,17 @@ import { IMap } from "../types";
|
||||
|
||||
// Map of material (by name) to their sizes (how much space it takes in warehouse)
|
||||
export const MaterialSizes: IMap<number> = {
|
||||
Water: 0.05,
|
||||
Energy: 0.01,
|
||||
Food: 0.03,
|
||||
Plants: 0.05,
|
||||
Metal: 0.1,
|
||||
Hardware: 0.06,
|
||||
Chemicals: 0.05,
|
||||
Drugs: 0.02,
|
||||
Robots: 0.5,
|
||||
AICores: 0.1,
|
||||
RealEstate: 0,
|
||||
Water: 0.05,
|
||||
Energy: 0.01,
|
||||
Food: 0.03,
|
||||
Plants: 0.05,
|
||||
Metal: 0.1,
|
||||
Hardware: 0.06,
|
||||
Chemicals: 0.05,
|
||||
Drugs: 0.02,
|
||||
Robots: 0.5,
|
||||
AICores: 0.1,
|
||||
RealEstate: 0,
|
||||
"Real Estate": 0,
|
||||
"AI Cores": 0,
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { MaterialSizes } from "./MaterialSizes";
|
||||
import { ProductRatingWeights,
|
||||
IProductRatingWeight } from "./ProductRatingWeights";
|
||||
|
||||
import { CityName } from "../Locations/data/CityNames";
|
||||
import { createCityMap } from "../Locations/createCityMap";
|
||||
import { IMap } from "../types";
|
||||
|
||||
import { Generic_fromJSON,
|
||||
@ -89,14 +89,7 @@ export class Product {
|
||||
// Data refers to the production, sale, and quantity of the products
|
||||
// These values are specific to a city
|
||||
// For each city, the data is [qty, prod, sell]
|
||||
data: IMap<number[]> = {
|
||||
[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],
|
||||
}
|
||||
data: IMap<number[]> = createCityMap<number[]>([0, 0, 0]);
|
||||
|
||||
// Location of this Product
|
||||
// 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
|
||||
// manually limited. These values are specific to a city
|
||||
// [Whether production/sale is limited, limit amount]
|
||||
prdman: IMap<any[]> = {
|
||||
[Cities.Aevum]: [false, 0],
|
||||
[Cities.Chongqing]: [false, 0],
|
||||
[Cities.Sector12]: [false, 0],
|
||||
[Cities.NewTokyo]: [false, 0],
|
||||
[Cities.Ishima]: [false, 0],
|
||||
[Cities.Volhaven]: [false, 0],
|
||||
}
|
||||
prdman: IMap<any[]> = createCityMap<any[]>([false, 0]);
|
||||
sllman: IMap<any[]> = createCityMap<any[]>([false, 0]);
|
||||
|
||||
sllman: IMap<any[]> = {
|
||||
[Cities.Aevum]: [false, 0],
|
||||
[Cities.Chongqing]: [false, 0],
|
||||
[Cities.Sector12]: [false, 0],
|
||||
[Cities.NewTokyo]: [false, 0],
|
||||
[Cities.Ishima]: [false, 0],
|
||||
[Cities.Volhaven]: [false, 0],
|
||||
}
|
||||
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
||||
marketTa1: boolean = false;
|
||||
marketTa2: boolean = false;
|
||||
marketTa2Price: IMap<number> = createCityMap<number>(0);
|
||||
|
||||
constructor(params: IConstructorParams={}) {
|
||||
this.name = params.name ? params.name : "";
|
||||
@ -164,11 +147,11 @@ export class Product {
|
||||
//Calculate properties
|
||||
var progrMult = this.prog / 100;
|
||||
|
||||
var engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"],
|
||||
mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"],
|
||||
rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"],
|
||||
opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"],
|
||||
busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"];
|
||||
const engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"];
|
||||
const mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"];
|
||||
const rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"];
|
||||
const opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"];
|
||||
const busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"];
|
||||
var designMult = 1 + (Math.pow(this.designCost, 0.1) / 100);
|
||||
console.log("designMult: " + designMult);
|
||||
var balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) +
|
||||
|
@ -8,6 +8,8 @@ import { ResearchMap } from "./ResearchMap";
|
||||
|
||||
import { IMap } from "../types";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
interface IConstructorParams {
|
||||
children?: Node[];
|
||||
cost: number;
|
||||
@ -83,7 +85,7 @@ export class Node {
|
||||
children: childrenArray,
|
||||
HTMLclass: htmlClass,
|
||||
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">` +
|
||||
`${research.desc}` +
|
||||
`</span>` +
|
||||
|
@ -5,16 +5,19 @@ import { numeralWrapper } from "../ui/numeralFormat";
|
||||
import { Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IConstructorParams {
|
||||
loc?: string;
|
||||
size?: number;
|
||||
}
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
|
||||
interface IParent {
|
||||
getStorageMultiplier(): number;
|
||||
}
|
||||
|
||||
interface IConstructorParams {
|
||||
corp?: IParent;
|
||||
industry?: IParent;
|
||||
loc?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export class Warehouse {
|
||||
// Initiatizes a Warehouse object from a JSON save state.
|
||||
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)
|
||||
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.
|
||||
// The production tracked by smart supply is always based on the previous 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"}),
|
||||
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
|
||||
@ -76,7 +87,7 @@ export class Warehouse {
|
||||
if (MaterialSizes.hasOwnProperty(matName)) {
|
||||
this.sizeUsed += (mat.qty * MaterialSizes[matName]);
|
||||
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) {
|
||||
this.size = (this.level * 100)
|
||||
* corporation.getStorageMultiplier()
|
||||
* industry.getStorageMultiplier();
|
||||
try {
|
||||
this.size = (this.level * 100)
|
||||
* corporation.getStorageMultiplier()
|
||||
* industry.getStorageMultiplier();
|
||||
} catch(e) {
|
||||
exceptionAlert(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the current object to a JSON save state.
|
||||
|
@ -27,6 +27,8 @@ function createBaseResearchTreeNodes(): Node {
|
||||
const dronesAssembly: Node = makeNode("Drones - Assembly");
|
||||
const dronesTransport: Node = makeNode("Drones - Transport");
|
||||
const goJuice: Node = makeNode("Go-Juice");
|
||||
const hrRecruitment: Node = makeNode("HRBuddy-Recruitment");
|
||||
const hrTraining: Node = makeNode("HRBuddy-Training");
|
||||
const joywire: Node = makeNode("JoyWire");
|
||||
const marketta1: Node = makeNode("Market-TA.I");
|
||||
const marketta2: Node = makeNode("Market-TA.II");
|
||||
@ -40,6 +42,8 @@ function createBaseResearchTreeNodes(): Node {
|
||||
drones.addChild(dronesAssembly);
|
||||
drones.addChild(dronesTransport);
|
||||
|
||||
hrRecruitment.addChild(hrTraining);
|
||||
|
||||
marketta1.addChild(marketta2);
|
||||
|
||||
overclock.addChild(stimu);
|
||||
@ -49,6 +53,7 @@ function createBaseResearchTreeNodes(): Node {
|
||||
rootNode.addChild(autoDrugs);
|
||||
rootNode.addChild(bulkPurchasing);
|
||||
rootNode.addChild(drones);
|
||||
rootNode.addChild(hrRecruitment);
|
||||
rootNode.addChild(joywire);
|
||||
rootNode.addChild(marketta1);
|
||||
rootNode.addChild(overclock);
|
||||
|
@ -77,6 +77,21 @@ export const researchMetadata: IConstructorParams[] = [
|
||||
"production by 10%.",
|
||||
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",
|
||||
cost: 20e3,
|
||||
@ -94,11 +109,13 @@ export const researchMetadata: IConstructorParams[] = [
|
||||
},
|
||||
{
|
||||
name: "Market-TA.II",
|
||||
cost: 40e3,
|
||||
cost: 50e3,
|
||||
desc: "Develop double-advanced AI software that uses technical analysis to " +
|
||||
"help you understand and exploit the market. This research " +
|
||||
"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",
|
||||
|
@ -14,6 +14,9 @@ export class CityTabs extends BaseReactComponent {
|
||||
if (props.city == null) {
|
||||
throw new Error(`CityTabs component constructed without 'city' property`)
|
||||
}
|
||||
if (props.cityStateSetter == null) {
|
||||
throw new Error(`CityTabs component constructed without 'cityStateSetter' property`)
|
||||
}
|
||||
|
||||
super(props);
|
||||
}
|
||||
@ -46,7 +49,8 @@ export class CityTabs extends BaseReactComponent {
|
||||
}
|
||||
|
||||
// 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({
|
||||
current: false,
|
||||
key: "Expand into new City",
|
||||
|
@ -10,15 +10,23 @@ import { Corporation,
|
||||
OfficeInitialSize,
|
||||
SellSharesCooldown,
|
||||
WarehouseInitialCost,
|
||||
WarehouseInitialSize } from "../Corporation";
|
||||
WarehouseInitialSize,
|
||||
BribeToRepRatio } from "../Corporation";
|
||||
|
||||
import { Industries,
|
||||
IndustryStartingCosts,
|
||||
IndustryDescriptions,
|
||||
IndustryResearchTrees } from "../IndustryData";
|
||||
|
||||
import { MaterialSizes } from "../MaterialSizes";
|
||||
|
||||
import { Product } from "../Product";
|
||||
|
||||
import { Player } from "../../Player";
|
||||
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Cities } from "../../Locations/Cities";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
@ -79,7 +87,7 @@ export class CorporationEventHandler {
|
||||
|
||||
var totalAmount = Number(money) + (stockShares * stockPrice);
|
||||
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 " +
|
||||
factionSelector.options[factionSelector.selectedIndex].value +
|
||||
" with this bribe";
|
||||
@ -102,14 +110,14 @@ export class CorporationEventHandler {
|
||||
var totalAmount = money + (stockShares * stockPrice);
|
||||
var repGain = totalAmount / BribeToRepRatio;
|
||||
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 " +
|
||||
factionSelector.options[factionSelector.selectedIndex].value +
|
||||
" with this bribe";
|
||||
}
|
||||
}
|
||||
});
|
||||
var confirmButton = createElement("a", {
|
||||
var confirmButton = createElement("button", {
|
||||
class:"a-link-button", innerText:"Bribe", display:"inline-block",
|
||||
clickListener:()=>{
|
||||
var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
|
||||
@ -129,7 +137,7 @@ export class CorporationEventHandler {
|
||||
} else {
|
||||
var totalAmount = money + (stockShares * stockPrice);
|
||||
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.");
|
||||
fac.playerReputation += repGain;
|
||||
this.corp.funds = this.corp.funds.minus(money);
|
||||
@ -140,6 +148,7 @@ export class CorporationEventHandler {
|
||||
}
|
||||
});
|
||||
const cancelButton = createPopupCloseButton(popupId, {
|
||||
class: "std-button",
|
||||
display: "inline-block",
|
||||
innerText: "Cancel",
|
||||
})
|
||||
@ -168,7 +177,6 @@ export class CorporationEventHandler {
|
||||
type:"number", placeholder:"Shares to buyback", margin:"5px",
|
||||
inputListener: ()=> {
|
||||
var numShares = Math.round(input.value);
|
||||
//TODO add conditional for if player doesn't have enough money
|
||||
if (isNaN(numShares) || numShares <= 0) {
|
||||
costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback"
|
||||
} 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",
|
||||
clickListener: () => {
|
||||
var shares = Math.round(input.value);
|
||||
@ -226,7 +234,7 @@ export class CorporationEventHandler {
|
||||
}
|
||||
|
||||
// Create a popup that lets the player discontinue a product
|
||||
createDiscontinueProductPopup(product) {
|
||||
createDiscontinueProductPopup(product, industry) {
|
||||
const popupId = "cmpy-mgmt-discontinue-product-popup";
|
||||
const txt = createElement("p", {
|
||||
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 " +
|
||||
"removed and left unsold",
|
||||
});
|
||||
const confirmBtn = createElement("a", {
|
||||
class:"a-link-button",innerText:"Discontinue",
|
||||
clickListener:()=>{
|
||||
industry.discontinueProduct(product, parentRefs);
|
||||
const confirmBtn = createElement("button", {
|
||||
class:"popup-box-button",innerText:"Discontinue",
|
||||
clickListener: () => {
|
||||
industry.discontinueProduct(product);
|
||||
removeElementById(popupId);
|
||||
this.corp.rerender();
|
||||
this.rerender();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
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
|
||||
@ -294,7 +302,7 @@ export class CorporationEventHandler {
|
||||
placeholder:"Export amount / s"
|
||||
});
|
||||
|
||||
const exportBtn = createElement("a", {
|
||||
const exportBtn = createElement("button", {
|
||||
class: "std-button", display:"inline-block", innerText:"Export",
|
||||
clickListener: () => {
|
||||
const industryName = getSelectText(industrySelector);
|
||||
@ -340,7 +348,7 @@ export class CorporationEventHandler {
|
||||
clickListener:()=>{
|
||||
mat.exp.splice(i, 1); //Remove export object
|
||||
removeElementById(popupId);
|
||||
createExportPopup();
|
||||
createExportMaterialPopup(mat);
|
||||
}
|
||||
}));
|
||||
})(i, mat, currExports);
|
||||
@ -473,7 +481,7 @@ export class CorporationEventHandler {
|
||||
}
|
||||
});
|
||||
|
||||
issueBtn = createElement("a", {
|
||||
issueBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
display: "inline-block",
|
||||
innerText: "Issue New Shares",
|
||||
@ -529,7 +537,7 @@ export class CorporationEventHandler {
|
||||
}
|
||||
|
||||
// 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 txt = createElement("p", {
|
||||
innerText:"Enter a limit to the amount of this product you would " +
|
||||
@ -537,17 +545,19 @@ export class CorporationEventHandler {
|
||||
});
|
||||
let confirmBtn;
|
||||
const input = createElement("input", {
|
||||
type:"number", placeholder:"Limit",
|
||||
margin: "5px",
|
||||
placeholder:"Limit",
|
||||
type:"number",
|
||||
onkeyup: (e) => {
|
||||
e.preventDefault();
|
||||
if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
|
||||
}
|
||||
});
|
||||
confirmBtn = createElement("a", {
|
||||
confirmBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
display:"inline-block",
|
||||
innerText:"Limit production",
|
||||
margin:'6px',
|
||||
display: "inline-block",
|
||||
innerText: "Limit production",
|
||||
margin: "5px",
|
||||
clickListener: () => {
|
||||
if (input.value === "") {
|
||||
product.prdman[city][0] = false;
|
||||
@ -585,7 +595,7 @@ export class CorporationEventHandler {
|
||||
const txt = createElement("p", {
|
||||
innerHTML: popupText,
|
||||
});
|
||||
const designCity = createElement("select");
|
||||
const designCity = createElement("select", { margin: "5px" });
|
||||
for (const cityName in division.offices) {
|
||||
if (division.offices[cityName] instanceof OfficeSpace) {
|
||||
designCity.add(createElement("option", {
|
||||
@ -603,18 +613,26 @@ export class CorporationEventHandler {
|
||||
productNamePlaceholder = "Property Name";
|
||||
}
|
||||
var productNameInput = createElement("input", {
|
||||
margin: "5px",
|
||||
placeholder: productNamePlaceholder,
|
||||
});
|
||||
var lineBreak1 = createElement("br");
|
||||
var designInvestInput = createElement("input", {
|
||||
margin: "5px",
|
||||
placeholder: "Design investment",
|
||||
type: "number",
|
||||
placeholder: "Design investment"
|
||||
});
|
||||
let confirmBtn;
|
||||
var marketingInvestInput = createElement("input", {
|
||||
margin: "5px",
|
||||
placeholder: "Marketing investment",
|
||||
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",
|
||||
innerText: "Develop Product",
|
||||
clickListener: () => {
|
||||
@ -637,17 +655,19 @@ export class CorporationEventHandler {
|
||||
designCost: designInvest,
|
||||
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);
|
||||
division.products[product.name] = product;
|
||||
removeElementById(popupId);
|
||||
}
|
||||
this.rerender();
|
||||
//this.updateUIContent();
|
||||
//this.displayDivisionContent(division, city);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
const cancelBtn = createPopupCloseButton(popupid, {
|
||||
const cancelBtn = createPopupCloseButton(popupId, {
|
||||
class: "std-button",
|
||||
innerText: "Cancel",
|
||||
});
|
||||
@ -657,8 +677,8 @@ export class CorporationEventHandler {
|
||||
productNameInput.focus();
|
||||
}
|
||||
|
||||
// Create a popup that lets the player use the Market TA research
|
||||
createMarketTaPopup(mat, industry) {
|
||||
// Create a popup that lets the player use the Market TA research for Materials
|
||||
createMaterialMarketTaPopup(mat, industry) {
|
||||
const corp = this.corp;
|
||||
|
||||
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)"
|
||||
})
|
||||
const useTa1AutoSaleCheckbox = createElement("input", {
|
||||
checked: mat.marketTa1,
|
||||
id: useTa1AutoSaleId,
|
||||
margin: "3px",
|
||||
type: "checkbox",
|
||||
value: mat.marketTa1,
|
||||
changeListener: (e) => {
|
||||
mat.marketTa1 = e.target.value;
|
||||
mat.marketTa1 = e.target.checked;
|
||||
}
|
||||
});
|
||||
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
|
||||
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
|
||||
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
|
||||
|
||||
const closeBtn = createPopupCloseButton(popupId, {
|
||||
class: "std-button",
|
||||
display: "block",
|
||||
innerText: "Close",
|
||||
});
|
||||
|
||||
if (industry.hasResearch("Market-TA.II")) {
|
||||
@ -729,11 +751,41 @@ export class CorporationEventHandler {
|
||||
}
|
||||
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
|
||||
`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.`;
|
||||
}
|
||||
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 {
|
||||
// Market-TA.I only
|
||||
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)
|
||||
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 text = createElement("p", {
|
||||
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",
|
||||
display:"inline-block",
|
||||
innerText: "Confirm",
|
||||
clickListener: () => {
|
||||
if (citySelector.length <= 0) { return false; }
|
||||
let city = citySelector.options[citySelector.selectedIndex].value;
|
||||
if (this.corp.funds.lt(OfficeInitialCost)) {
|
||||
dialogBoxCreate("You don't have enough company funds to open a new office!");
|
||||
@ -772,9 +826,11 @@ export class CorporationEventHandler {
|
||||
loc: city,
|
||||
size: OfficeInitialSize,
|
||||
});
|
||||
this.corp.displayDivisionContent(division, city);
|
||||
}
|
||||
|
||||
cityStateSetter(city);
|
||||
removeElementById(popupId);
|
||||
this.rerender();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@ -836,15 +892,17 @@ export class CorporationEventHandler {
|
||||
} else {
|
||||
this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]);
|
||||
var newInd = new Industry({
|
||||
name:newDivisionName,
|
||||
type:ind,
|
||||
corp: this.corp,
|
||||
name: newDivisionName,
|
||||
type: ind,
|
||||
});
|
||||
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");
|
||||
this.rerender();
|
||||
// this.corp.displayDivisionContent(newInd, Locations.Sector12);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -855,14 +913,14 @@ export class CorporationEventHandler {
|
||||
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 = {};
|
||||
for (let i = 0; i < this.corp.divisions.length; ++i) {
|
||||
ownedIndustries[this.corp.divisions[i].type] = true;
|
||||
}
|
||||
|
||||
//Add industry types to selector
|
||||
//Have Agriculture be first as recommended option
|
||||
// Add industry types to selector
|
||||
// Have Agriculture be first as recommended option
|
||||
if (!ownedIndustries["Agriculture"]) {
|
||||
selector.add(createElement("option", {
|
||||
text:Industries["Agriculture"], value:"Agriculture"
|
||||
@ -904,8 +962,115 @@ export class CorporationEventHandler {
|
||||
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
|
||||
createPurchaseMaterialPopup(mat, industry) {
|
||||
createPurchaseMaterialPopup(mat, industry, warehouse) {
|
||||
const corp = this.corp;
|
||||
|
||||
const purchasePopupId = "cmpy-mgmt-material-purchase-popup";
|
||||
@ -933,6 +1098,7 @@ export class CorporationEventHandler {
|
||||
mat.buy = parseFloat(input.value);
|
||||
if (isNaN(mat.buy)) {mat.buy = 0;}
|
||||
removeElementById(purchasePopupId);
|
||||
this.rerender();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -942,6 +1108,7 @@ export class CorporationEventHandler {
|
||||
clickListener: () => {
|
||||
mat.buy = 0;
|
||||
removeElementById(purchasePopupId);
|
||||
this.rerender();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@ -961,15 +1128,20 @@ export class CorporationEventHandler {
|
||||
|
||||
let bulkPurchaseCostTxt = createElement("p");
|
||||
function updateBulkPurchaseText(amount) {
|
||||
const cost = parseFloat(amount) * mat.bCost;
|
||||
if (isNaN(cost)) {
|
||||
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;
|
||||
}
|
||||
const parsedAmt = parseFloat(amount);
|
||||
const cost = parsedAmt * mat.bCost;
|
||||
|
||||
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` +
|
||||
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
|
||||
const matSize = MaterialSizes[mat.name];
|
||||
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;
|
||||
@ -979,7 +1151,7 @@ export class CorporationEventHandler {
|
||||
type: "number",
|
||||
onkeyup: (e) => {
|
||||
e.preventDefault();
|
||||
bulkPurchaseUpdateCostTxt();
|
||||
updateBulkPurchaseText(e.target.value);
|
||||
if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();}
|
||||
}
|
||||
});
|
||||
@ -988,7 +1160,15 @@ export class CorporationEventHandler {
|
||||
class: "std-button",
|
||||
innerText: "Confirm Bulk Purchase",
|
||||
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)) {
|
||||
dialogBoxCreate("Invalid input amount");
|
||||
} else {
|
||||
@ -1010,6 +1190,7 @@ export class CorporationEventHandler {
|
||||
elems.push(bulkPurchaseInfo);
|
||||
elems.push(bulkPurchaseCostTxt);
|
||||
elems.push(bulkPurchaseInput);
|
||||
elems.push(bulkPurchaseConfirmBtn);
|
||||
}
|
||||
|
||||
createPopup(purchasePopupId, elems);
|
||||
@ -1045,9 +1226,18 @@ export class CorporationEventHandler {
|
||||
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", {
|
||||
type: "text", marginTop: "4px",
|
||||
value: mat.sCost ? mat.sCost : null, placeholder: "Sell price",
|
||||
value: inputButtonInitValue,
|
||||
placeholder: "Sell price",
|
||||
onkeyup: (e) => {
|
||||
e.preventDefault();
|
||||
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
|
||||
createSellProductPopup(product) {
|
||||
createSellProductPopup(product, city) {
|
||||
const popupId = "cmpy-mgmt-sell-product-popup";
|
||||
const txt = createElement("p", {
|
||||
innerHTML:"Enter the maximum amount of " + product.name + " you would like " +
|
||||
@ -1150,24 +1340,51 @@ export class CorporationEventHandler {
|
||||
});
|
||||
let confirmBtn;
|
||||
const inputQty = createElement("input", {
|
||||
margin: "5px 0px 5px 0px",
|
||||
placeholder: "Sell amount",
|
||||
type: "text",
|
||||
value:product.sllman[city][1] ? product.sllman[city][1] : null,
|
||||
value: product.sllman[city][1] ? product.sllman[city][1] : null,
|
||||
onkeyup: (e) => {
|
||||
e.preventDefault();
|
||||
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", {
|
||||
margin: "5px 0px 5px 0px",
|
||||
placeholder: "Sell price",
|
||||
type: "text",
|
||||
value: product.sCost ? product.sCost : null,
|
||||
value: inputButtonInitValue,
|
||||
onkeyup: (e) => {
|
||||
e.preventDefault();
|
||||
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",
|
||||
innerText: "Confirm",
|
||||
clickListener: () => {
|
||||
@ -1198,7 +1415,10 @@ export class CorporationEventHandler {
|
||||
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")) {
|
||||
//Dynamically evaluated quantity. First test to make sure its valid
|
||||
var qty = inputQty.value.replace(/\s+/g, '');
|
||||
@ -1216,8 +1436,16 @@ export class CorporationEventHandler {
|
||||
dialogBoxCreate("Invalid value or expression for sell price field");
|
||||
return false;
|
||||
}
|
||||
product.sllman[city][0] = true;
|
||||
product.sllman[city][1] = qty; //Use sanitized input
|
||||
if (checkbox.checked) {
|
||||
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)) {
|
||||
dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
|
||||
return false;
|
||||
@ -1225,10 +1453,25 @@ export class CorporationEventHandler {
|
||||
var qty = parseFloat(inputQty.value);
|
||||
if (isNaN(qty)) {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 {
|
||||
product.sllman[city][0] = true;
|
||||
product.sllman[city][1] = qty;
|
||||
if (checkbox.checked) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
|
||||
@ -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",
|
||||
clickListener:()=>{
|
||||
var shares = Math.round(input.value);
|
||||
@ -1355,7 +1601,7 @@ export class CorporationEventHandler {
|
||||
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
|
||||
}
|
||||
});
|
||||
confirmBtn = createElement("a", {
|
||||
confirmBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
innerText: "Throw Party",
|
||||
clickListener:()=>{
|
||||
@ -1366,20 +1612,20 @@ export class CorporationEventHandler {
|
||||
if (this.corp.funds.lt(totalCost)) {
|
||||
dialogBoxCreate("You don't have enough company funds to throw this.corp party!");
|
||||
} else {
|
||||
this.corp.funds = this.funds.minus(totalCost);
|
||||
this.corp.funds = this.corp.funds.minus(totalCost);
|
||||
var mult;
|
||||
for (let fooit = 0; fooit < office.employees.length; ++fooit) {
|
||||
mult = office.employees[fooit].throwParty(input.value);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
|
||||
const cancelBtn = createPopupCloseButton(popupId, { class: "std-button", innerText: "Cancel" });
|
||||
|
||||
createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]);
|
||||
input.focus();
|
||||
@ -1420,7 +1666,7 @@ export class CorporationEventHandler {
|
||||
});
|
||||
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",
|
||||
display:"inline-block", margin:"4px", innerText:"by 3",
|
||||
tooltip:numeralWrapper.format(upgradeCost, "$0.000a"),
|
||||
@ -1437,7 +1683,7 @@ export class CorporationEventHandler {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const confirmBtn15 = createElement("a", {
|
||||
const confirmBtn15 = createElement("button", {
|
||||
class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button",
|
||||
display:"inline-block", margin:"4px", innerText:"by 15",
|
||||
tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"),
|
||||
@ -1454,7 +1700,7 @@ export class CorporationEventHandler {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const confirmBtnMax = createElement("a", {
|
||||
const confirmBtnMax = createElement("button", {
|
||||
class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button",
|
||||
display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")",
|
||||
tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"),
|
||||
@ -1484,6 +1730,8 @@ export class CorporationEventHandler {
|
||||
dialogBoxCreate("You do not have enough funds to do this!");
|
||||
} else {
|
||||
division.warehouses[city] = new Warehouse({
|
||||
corp: corp,
|
||||
industry: division,
|
||||
loc: city,
|
||||
size: WarehouseInitialSize,
|
||||
});
|
||||
|
@ -15,6 +15,8 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
city: "",
|
||||
division: "",
|
||||
employeeManualAssignMode: false,
|
||||
employee: null, // Reference to employee being referenced if in Manual Mode
|
||||
numEmployees: 0,
|
||||
@ -30,6 +32,17 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
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() {
|
||||
const division = this.routing().currentDivision;
|
||||
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`);
|
||||
}
|
||||
|
||||
// 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
|
||||
const currentNumEmployees = office.employees.length;
|
||||
const newEmployees = currentNumEmployees - this.state.numEmployees;
|
||||
@ -151,6 +171,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
--this.state.numUnassigned;
|
||||
|
||||
office.assignEmployeeToJob(to);
|
||||
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
||||
this.corp().rerender();
|
||||
}
|
||||
|
||||
@ -194,6 +215,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
++this.state.numUnassigned;
|
||||
|
||||
office.unassignEmployeeFromJob(from);
|
||||
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
||||
this.corp().rerender();
|
||||
}
|
||||
|
||||
@ -278,41 +300,54 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<br />
|
||||
|
||||
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
|
||||
<p>Avg Happiness Morale: {numeralWrapper.format(avgHappiness, "0.000")}</p>
|
||||
<p>Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}</p>
|
||||
<p>Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}</p>
|
||||
<p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
|
||||
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
|
||||
{
|
||||
vechain &&
|
||||
<div>
|
||||
<p className={"tooltip"}>
|
||||
<p className={"tooltip"} style={{display: "inline-block"}}>
|
||||
Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")}
|
||||
<span className={"tooltiptext"}>
|
||||
The base amount of material this office can produce. Does not include
|
||||
production multipliers from upgrades and materials. This value is based off
|
||||
the productivity of your Operations, Engineering, and Management employees
|
||||
</span>
|
||||
</p><br />
|
||||
<p className={"tooltip"}>
|
||||
</p>
|
||||
}
|
||||
{
|
||||
vechain && <br />
|
||||
}
|
||||
{
|
||||
vechain &&
|
||||
<p className={"tooltip"} style={{display: "inline-block"}}>
|
||||
Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")}
|
||||
<span className={"tooltiptext"}>
|
||||
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
|
||||
the productivity of your Operations, Engineering, and Management employees
|
||||
</span>
|
||||
</p><br />
|
||||
<p className={"tooltip"}>
|
||||
Business Multiplier: x" ${numeralWrapper.format(division.getBusinessFactor(office), "0.000")}
|
||||
</p>
|
||||
}
|
||||
{
|
||||
vechain && <br />
|
||||
}
|
||||
{
|
||||
vechain &&
|
||||
<p className={"tooltip"} style={{display: "inline-block"}}>
|
||||
Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")}
|
||||
<span className={"tooltiptext"}>
|
||||
The effect this office's 'Business' employees has on boosting sales
|
||||
</span>
|
||||
</p><br />
|
||||
</div>
|
||||
</p>
|
||||
}
|
||||
{
|
||||
vechain && <br />
|
||||
}
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Operations} ({this.state.numOperations})
|
||||
<span className={"tooltiptext"}>
|
||||
Manages supply chain operations. Improves production.
|
||||
Manages supply chain operations. Improves the amount of Materials and Products you produce.
|
||||
</span>
|
||||
</h2>
|
||||
<button className={assignButtonClass} onClick={operationAssignButtonOnClick}>+</button>
|
||||
@ -322,7 +357,9 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Engineer} ({this.state.numEngineers})
|
||||
<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>
|
||||
</h2>
|
||||
<button className={assignButtonClass} onClick={engineerAssignButtonOnClick}>+</button>
|
||||
@ -332,7 +369,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Business} ({this.state.numBusiness})
|
||||
<span className={"tooltiptext"}>
|
||||
Handles sales and finances. Improves sales.
|
||||
Handles sales and finances. Improves the amount of Materials and Products you can sell.
|
||||
</span>
|
||||
</h2>
|
||||
<button className={assignButtonClass} onClick={businessAssignButtonOnClick}>+</button>
|
||||
@ -342,7 +379,8 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Management} ({this.state.numManagement})
|
||||
<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>
|
||||
</h2>
|
||||
<button className={assignButtonClass} onClick={managementAssignButtonOnClick}>+</button>
|
||||
@ -398,27 +436,36 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
for (let i = 0; i < office.employees.length; ++i) {
|
||||
if (name === office.employees[i].name) {
|
||||
this.state.employee = office.employees[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
corp.rerender();
|
||||
}
|
||||
|
||||
// Employee Positions Selector
|
||||
const emp = this.state.employee;
|
||||
let employeePositionSelectorInitialValue = null;
|
||||
const employeePositions = [];
|
||||
const positionNames = Object.values(EmployeePositions);
|
||||
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 pos = getSelectText(e.target);
|
||||
this.state.employee.pos = pos;
|
||||
this.resetEmployeeCount();
|
||||
corp.rerender();
|
||||
}
|
||||
|
||||
// Numeraljs formatter
|
||||
const nf = "0.000";
|
||||
|
||||
// Employee stats (after applying multipliers)
|
||||
const emp = this.state.employee;
|
||||
const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0;
|
||||
const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0;
|
||||
const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0;
|
||||
@ -436,6 +483,9 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
</button>
|
||||
|
||||
<div style={employeeInfoDivStyle}>
|
||||
<select onChange={employeeSelectorOnChange}>
|
||||
{employees}
|
||||
</select>
|
||||
{
|
||||
this.state.employee != null &&
|
||||
<p>
|
||||
@ -462,15 +512,11 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
}
|
||||
{
|
||||
this.state.employee != null &&
|
||||
<select onChange={employeePositionSelectorOnChange}>
|
||||
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
|
||||
{employeePositions}
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
|
||||
<select onChange={employeeSelectorOnChange}>
|
||||
{employees}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -495,7 +541,6 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const hireEmployeeButtonOnClick = () => {
|
||||
office.findEmployees({ corporation: corp, industry: division });
|
||||
}
|
||||
@ -509,7 +554,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
}
|
||||
const autohireEmployeeButtonOnClick = () => {
|
||||
if (office.atCapacity()) { return; }
|
||||
office.hireRandomEmployee({ corporation: corp, industry: division });
|
||||
office.hireRandomEmployee();
|
||||
this.corp().rerender();
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { Industries } from "../IndustryData";
|
||||
import { IndustryUpgrades } from "../IndustryUpgrades";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
|
||||
export class IndustryOverview extends BaseReactComponent {
|
||||
renderMakeProductButton() {
|
||||
@ -109,6 +110,16 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
|
||||
|
||||
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 " +
|
||||
"can boost your Industry's production. The effect these " +
|
||||
"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. " +
|
||||
"This production multiplier is applied to each office. Therefore, it is " +
|
||||
"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 (
|
||||
@ -129,15 +146,15 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
{popularity} <br />
|
||||
{
|
||||
(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"}>
|
||||
Total multiplier for this industrys sales due to its awareness and popularity
|
||||
<br />
|
||||
Awareness Bonus: x{formatNumber(Math.pow(awarenessFac, 0.85), 3)}
|
||||
Awareness Bonus: x{numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")}
|
||||
<br />
|
||||
Popularity Bonus: x{formatNumber(Math.pow(popularityFac, 0.85), 3)}
|
||||
Popularity Bonus: x{numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")}
|
||||
<br />
|
||||
Ratio Multiplier: x{formatNumber(Math.pow(ratioFac, 0.85), 3)}
|
||||
Ratio Multiplier: x{numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")}
|
||||
</span>
|
||||
</p>
|
||||
}
|
||||
@ -157,7 +174,7 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
<div className={"help-tip"} onClick={productionMultHelpTipOnClick}>?</div>
|
||||
<br /> <br />
|
||||
<p className={"tooltip"}>
|
||||
Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000")}
|
||||
Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")}
|
||||
<span className={"tooltiptext"}>
|
||||
Scientific Research increases the quality of the materials and
|
||||
products that you produce.
|
||||
@ -252,7 +269,7 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
|
||||
{
|
||||
division.makesProducts &&
|
||||
{makeProductButton}
|
||||
makeProductButton
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
@ -1,63 +1,83 @@
|
||||
// React Component for displaying an Industry's warehouse information
|
||||
// (right-side panel in the Industry UI)
|
||||
import React from "react";
|
||||
import { BaseReactComponent } from "./BaseReactComponent";
|
||||
import { BaseReactComponent } from "./BaseReactComponent";
|
||||
|
||||
import { Material } from "../Material";
|
||||
import { Product } from "../Product";
|
||||
|
||||
import { Warehouse,
|
||||
import { OfficeSpace,
|
||||
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
|
||||
function ProductComponent(props) {
|
||||
const corp = props.corp;
|
||||
const division = props.division;
|
||||
const warehouse = props.warehouse;
|
||||
const city = props.city;
|
||||
const product = props.product;
|
||||
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");
|
||||
|
||||
// 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
|
||||
const sellButtonText = product.sllman[city][1] === -1
|
||||
? "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/MAX)"
|
||||
: "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/" + numeralWrapper.format(product.sllman[city][1], nf) + ")";
|
||||
if (product.sCost) {
|
||||
let sellButtonText;
|
||||
if (product.sllman[city][0]) {
|
||||
if (isString(product.sllman[city][1])) {
|
||||
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)) {
|
||||
sellButtonText += (" @ " + product.sCost);
|
||||
} else {
|
||||
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
|
||||
const limitProductionButtonText = "Limit Production";
|
||||
let limitProductionButtonText = "Limit Production";
|
||||
if (product.prdman[city][0]) {
|
||||
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
|
||||
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
|
||||
if (!product.fin) {
|
||||
if (hasUpgradeDashboard) {
|
||||
return (
|
||||
<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>
|
||||
<br />
|
||||
|
||||
@ -71,13 +91,19 @@ function ProductComponent(props) {
|
||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
||||
Discontinue
|
||||
</button>
|
||||
{
|
||||
division.hasResearch("Market-TA.I") &&
|
||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||
Market-TA
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
@ -85,15 +111,15 @@ function ProductComponent(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-warehouse-product-div"} key={props.key}>
|
||||
<div className={"cmpy-mgmt-warehouse-product-div"}>
|
||||
<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"}>
|
||||
Prod: {numeralWrapper.format(product.data[city][1], nf)}/s
|
||||
Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s
|
||||
<br />
|
||||
Sell: {numeralWrapper.format(product.data[city][2], nf)} /s
|
||||
Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s
|
||||
</span>
|
||||
</p>
|
||||
</p><br />
|
||||
<p className={"tooltip"}>
|
||||
Rating: {numeralWrapper.format(product.rat, nf)}
|
||||
<span className={"tooltiptext"}>
|
||||
@ -119,15 +145,15 @@ function ProductComponent(props) {
|
||||
}
|
||||
|
||||
</span>
|
||||
</p>
|
||||
</p><br />
|
||||
<p className={"tooltip"}>
|
||||
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
|
||||
<span className={"tooltiptext"}>
|
||||
An estimate of the material cost it takes to create this Product.
|
||||
</span>
|
||||
</p>
|
||||
</p><br />
|
||||
<p className={"tooltip"}>
|
||||
Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)}
|
||||
Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}
|
||||
<span className={"tooltiptext"}>
|
||||
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
|
||||
@ -145,6 +171,12 @@ function ProductComponent(props) {
|
||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
||||
Discontinue
|
||||
</button>
|
||||
{
|
||||
division.hasResearch("Market-TA.I") &&
|
||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||
Market-TA
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -155,12 +187,18 @@ function MaterialComponent(props) {
|
||||
const corp = props.corp;
|
||||
const division = props.division;
|
||||
const warehouse = props.warehouse;
|
||||
const city = props.city;
|
||||
const mat = props.mat;
|
||||
const eventHandler = props.eventHandler;
|
||||
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
|
||||
const nf = "0.000";
|
||||
const nfB = "0.000a"; // For numbers that might be biger
|
||||
|
||||
// Total gain or loss of this material (per second)
|
||||
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;
|
||||
|
||||
// Purchase material button
|
||||
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`;
|
||||
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
|
||||
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
||||
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division);
|
||||
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
|
||||
|
||||
// Export material button
|
||||
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
|
||||
@ -190,12 +228,18 @@ function MaterialComponent(props) {
|
||||
// Sell material button
|
||||
let sellButtonText;
|
||||
if (mat.sllman[0]) {
|
||||
sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" :
|
||||
"Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")");
|
||||
if (mat.sCost) {
|
||||
if (mat.marketTa1) {
|
||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
||||
} else if (isString(mat.sCost)) {
|
||||
if (isString(mat.sllman[1])) {
|
||||
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
|
||||
} else {
|
||||
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`;
|
||||
}
|
||||
|
||||
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);
|
||||
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
|
||||
} else {
|
||||
@ -208,19 +252,19 @@ function MaterialComponent(props) {
|
||||
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
|
||||
|
||||
// Market TA button
|
||||
const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division);
|
||||
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-warehouse-material-div"} key={props.key}>
|
||||
<div className={"cmpy-mgmt-warehouse-material-div"}>
|
||||
<div style={{display: "inline-block"}}>
|
||||
<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"}>
|
||||
Buy: {numeralWrapper.format(mat.buy, nf)} <br />
|
||||
Prod: {numeralWrapper.format(mat.prd, nf)} <br />
|
||||
Sell: {numeralWrapper.format(mat.sll, nf)} <br />
|
||||
Export: {numeralWrapper.format(mat.totalExp, nf)} <br />
|
||||
Import: {numeralWrapper.format(mat.imp, nf)}
|
||||
Buy: {numeralWrapper.format(mat.buy, nfB)} <br />
|
||||
Prod: {numeralWrapper.format(mat.prd, nfB)} <br />
|
||||
Sell: {numeralWrapper.format(mat.sll, nfB)} <br />
|
||||
Export: {numeralWrapper.format(mat.totalExp, nfB)} <br />
|
||||
Import: {numeralWrapper.format(mat.imp, nfB)}
|
||||
{
|
||||
corp.unlockUpgrades[2] === 1 && <br />
|
||||
}
|
||||
@ -244,7 +288,7 @@ function MaterialComponent(props) {
|
||||
</span>
|
||||
</p> <br />
|
||||
<p className={"tooltip"}>
|
||||
Quality: {numeralWrapper.format(mat.qlt, "0.00")}
|
||||
Quality: {numeralWrapper.format(mat.qlt, "0.00a")}
|
||||
<span className={"tooltiptext"}>
|
||||
The quality of your material. Higher quality will lead to more sales
|
||||
</span>
|
||||
@ -287,6 +331,19 @@ function MaterialComponent(props) {
|
||||
}
|
||||
|
||||
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() {
|
||||
const corp = this.corp();
|
||||
const division = this.routing().currentDivision; // Validated in render()
|
||||
@ -370,20 +427,8 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
// Smart Supply Checkbox
|
||||
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
||||
const smartSupplyOnChange = (e) => {
|
||||
warehouse.smartSupplyEnabled = e.target.value;
|
||||
}
|
||||
|
||||
// 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;
|
||||
warehouse.smartSupplyEnabled = e.target.checked;
|
||||
corp.rerender();
|
||||
}
|
||||
|
||||
// Create React components for materials
|
||||
@ -391,15 +436,15 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
for (const matName in warehouse.materials) {
|
||||
if (warehouse.materials[matName] instanceof Material) {
|
||||
// Only create UI for materials that are relevant for the industry
|
||||
if (isRelevantMaterial(matName)) {
|
||||
mats.push(MaterialComponent({
|
||||
corp: corp,
|
||||
division: division,
|
||||
eventHandler: this.eventHandler(),
|
||||
key: matName,
|
||||
mat: warehouse.materials[matName],
|
||||
warehouse: warehouse,
|
||||
}));
|
||||
if (this.isRelevantMaterial(matName, division)) {
|
||||
mats.push(<MaterialComponent
|
||||
city={this.props.currentCity}
|
||||
corp={corp}
|
||||
division={division}
|
||||
eventHandler={this.eventHandler()}
|
||||
key={matName}
|
||||
mat={warehouse.materials[matName]}
|
||||
warehouse={warehouse} />);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,14 +454,14 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
||||
for (const productName in division.products) {
|
||||
if (division.products[productName] instanceof Product) {
|
||||
products.push({
|
||||
corp: corp,
|
||||
division: division,
|
||||
eventHandler: this.eventHandler(),
|
||||
key: productName,
|
||||
product: division.products[productName],
|
||||
warehouse: warehouse,
|
||||
})
|
||||
products.push(<ProductComponent
|
||||
city={this.props.currentCity}
|
||||
corp={corp}
|
||||
division={division}
|
||||
eventHandler={this.eventHandler()}
|
||||
key={productName}
|
||||
product={division.products[productName]}
|
||||
warehouse={warehouse} />);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -424,10 +469,8 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
return (
|
||||
<div className={"cmpy-mgmt-warehouse-panel"}>
|
||||
<p className={"tooltip"} style={sizeUsageStyle}>
|
||||
Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")}
|
||||
<span className={"tooltiptext"}>
|
||||
{warehouse.breakdown}
|
||||
</span>
|
||||
Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)}
|
||||
<span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
|
||||
</p>
|
||||
|
||||
<button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}>
|
||||
@ -454,7 +497,7 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
id={smartSupplyCheckboxId}
|
||||
onChange={smartSupplyOnChange}
|
||||
style={{margin: "3px"}}
|
||||
value={warehouse.smartSupplyEnabled}
|
||||
checked={warehouse.smartSupplyEnabled}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@ -481,9 +524,11 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
return this.renderWarehouseUI();
|
||||
} else {
|
||||
return (
|
||||
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
||||
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
|
||||
</button>
|
||||
<div className={"cmpy-mgmt-warehouse-panel"}>
|
||||
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
||||
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
|
||||
renderContent() {
|
||||
if (this.routing().isOnOverviewPage()) {
|
||||
@ -68,11 +77,13 @@ export class MainPanel extends BaseReactComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cityTabs = (
|
||||
<CityTabs
|
||||
{...this.props}
|
||||
city={this.state.city}
|
||||
onClicks={onClicks}
|
||||
cityStateSetter={this.changeCityState.bind(this)}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -64,7 +64,7 @@ export class Overview extends BaseReactComponent {
|
||||
dividendStr +
|
||||
"Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "<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") +
|
||||
"<span class='tooltiptext'>" +
|
||||
`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 { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
|
||||
import { createPurchaseSleevesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
|
||||
import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
|
||||
|
||||
import {Page, routing} from "../ui/navigationTracking";
|
||||
import {numeralWrapper} from "../ui/numeralFormat";
|
||||
@ -199,7 +199,7 @@ function displayFactionContent(factionName) {
|
||||
innerText:"This donation will result in 0.000 reputation gain"
|
||||
});
|
||||
var donateAmountInput = createElement("input", {
|
||||
placeholder:"Donation amount",
|
||||
class: "text-input", placeholder:"Donation amount",
|
||||
inputListener:()=>{
|
||||
let amt = 0;
|
||||
if(donateAmountInput.value !== "") {
|
||||
@ -348,7 +348,7 @@ function displayFactionContent(factionName) {
|
||||
class: "std-button",
|
||||
innerText: "Purchase Duplicate Sleeves",
|
||||
clickListener: () => {
|
||||
createPurchaseSleevesFromCovenantPopup(Player);
|
||||
createSleevePurchasesFromCovenantPopup(Player);
|
||||
}
|
||||
}));
|
||||
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {
|
||||
|
@ -1784,6 +1784,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
|
||||
id: name + "gang-member-task",
|
||||
});
|
||||
const taskSelector = createElement("select", {
|
||||
class: "dropdown",
|
||||
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;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.expMultiplier = function() {
|
||||
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1;
|
||||
return 2.5 * this.clearanceLevel / this.maxClearanceLevel;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainHackingExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.hackingExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedHackingExp = function() {
|
||||
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0;
|
||||
return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.strExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedStrengthExp = function() {
|
||||
if (!this.strExpGained || isNaN(this.strExpGained)) return 0;
|
||||
return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.defExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedDefenseExp = function() {
|
||||
if (!this.defExpGained || isNaN(this.defExpGained)) return 0;
|
||||
return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.dexExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedDexterityExp = function() {
|
||||
if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0;
|
||||
return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.agiExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedAgilityExp = function() {
|
||||
if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0;
|
||||
return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.chaExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedCharismaExp = function() {
|
||||
if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0;
|
||||
return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
|
||||
if (isNaN(amt)) {return;}
|
||||
this.intExpGained += amt;
|
||||
}
|
||||
|
||||
InfiltrationInstance.prototype.calcGainedIntelligenceExp = function() {
|
||||
if(!this.intExpGained || isNaN(this.intExpGained)) return 0;
|
||||
return Math.pow(this.intExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
|
||||
}
|
||||
|
||||
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
|
||||
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
|
||||
clearInfiltrationStatusText();
|
||||
@ -477,12 +517,12 @@ function updateInfiltrationLevelText(inst) {
|
||||
"Total value of stolen secrets<br>" +
|
||||
"Reputation: <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
|
||||
"Money: <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
|
||||
"Hack exp gained: " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "<br>" +
|
||||
"Str exp gained: " + formatNumber(inst.strExpGained * expMultiplier, 3) + "<br>" +
|
||||
"Def exp gained: " + formatNumber(inst.defExpGained * expMultiplier, 3) + "<br>" +
|
||||
"Dex exp gained: " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "<br>" +
|
||||
"Agi exp gained: " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "<br>" +
|
||||
"Cha exp gained: " + formatNumber(inst.chaExpGained * expMultiplier, 3);
|
||||
"Hack exp gained: " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" +
|
||||
"Str exp gained: " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" +
|
||||
"Def exp gained: " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" +
|
||||
"Dex exp gained: " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" +
|
||||
"Agi exp gained: " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" +
|
||||
"Cha exp gained: " + formatNumber(inst.calcGainedCharismaExp(), 3);
|
||||
/* eslint-enable no-irregular-whitespace */
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@ function displayLocationContent() {
|
||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
|
||||
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
|
||||
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.MiscCompanyPositions[1]], employeeJob);
|
||||
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 { 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 { Company } from "../../Company/Company";
|
||||
import { CompanyPosition } from "../../Company/CompanyPosition";
|
||||
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 = {
|
||||
locName: LocationName;
|
||||
@ -99,6 +101,18 @@ export class CompanyLocation extends React.Component<IProps, any> {
|
||||
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() {
|
||||
return (
|
||||
<div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import {HacknetNode} from "./HacknetNode";
|
||||
import {NetscriptFunctions} from "./NetscriptFunctions";
|
||||
import {NetscriptPort} from "./NetscriptPort";
|
||||
|
||||
@ -56,37 +55,6 @@ Environment.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
setArrayElement: function(name, idx, value) {
|
||||
if (!(idx instanceof Array)) {
|
||||
throw new Error("idx parameter is not an Array");
|
||||
}
|
||||
var scope = this.lookup(name);
|
||||
if (!scope && this.parent) {
|
||||
throw new Error("Undefined variable " + name);
|
||||
}
|
||||
var arr = (scope || this).vars[name];
|
||||
if (!(arr.constructor === Array || arr instanceof Array)) {
|
||||
throw new Error("Variable is not an array: " + name);
|
||||
}
|
||||
var res = arr;
|
||||
for (var iterator = 0; iterator < idx.length-1; ++iterator) {
|
||||
var i = idx[iterator];
|
||||
if (!(res instanceof Array) || i >= res.length) {
|
||||
throw new Error("Out-of-bounds array access");
|
||||
}
|
||||
res = res[i];
|
||||
}
|
||||
|
||||
//Cant assign to ports or HacknetNodes
|
||||
if (res[idx[idx.length-1]] instanceof HacknetNode) {
|
||||
throw new Error("Cannot assign a Hacknet Node handle to a new value");
|
||||
}
|
||||
if (res[idx[idx.length-1]] instanceof NetscriptPort) {
|
||||
throw new Error("Cannot assign a Netscript Port handle to a new value");
|
||||
}
|
||||
return res[idx[idx.length-1]] = value;
|
||||
},
|
||||
|
||||
//Creates (or overwrites) a variable in the current scope
|
||||
def: function(name, value) {
|
||||
return this.vars[name] = value;
|
||||
|
@ -196,8 +196,11 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
|
||||
}
|
||||
var runningScriptObj = new RunningScript(script, args);
|
||||
runningScriptObj.threads = threads;
|
||||
server.runningScripts.push(runningScriptObj); //Push onto runningScripts
|
||||
addWorkerScript(runningScriptObj, server);
|
||||
|
||||
// Push onto runningScripts.
|
||||
// This has to come after addWorkerScript() because that fn updates RAM usage
|
||||
server.runScript(runningScriptObj, Player);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,9 @@ import { Factions,
|
||||
factionExists } from "./Faction/Factions";
|
||||
import { joinFaction,
|
||||
purchaseAugmentation } from "./Faction/FactionHelpers";
|
||||
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
|
||||
import { getCostOfNextHacknetNode,
|
||||
purchaseHacknet } from "./HacknetNode";
|
||||
purchaseHacknet } from "./Hacknet/HacknetNode";
|
||||
import {Locations} from "./Locations";
|
||||
import { Message } from "./Message/Message";
|
||||
import { Messages } from "./Message/MessageHelpers";
|
||||
@ -60,6 +61,7 @@ import {StockMarket, StockSymbols, SymbolToStockMap,
|
||||
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
|
||||
import { getStockmarket4SDataCost,
|
||||
getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts";
|
||||
import { SourceFileFlags } from "./SourceFile/SourceFileFlags"
|
||||
import {TextFile, getTextFile, createTextFile} from "./TextFile";
|
||||
|
||||
import {unknownBladeburnerActionErrorMessage,
|
||||
@ -71,6 +73,8 @@ import {WorkerScript, workerScripts,
|
||||
import {makeRuntimeRejectMsg, netscriptDelay,
|
||||
runScriptFromScript} from "./NetscriptEvaluator";
|
||||
import {NetscriptPort} from "./NetscriptPort";
|
||||
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
|
||||
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/Sleeve";
|
||||
|
||||
import {Page, routing} from "./ui/navigationTracking";
|
||||
import {numeralWrapper} from "./ui/numeralFormat";
|
||||
@ -275,27 +279,27 @@ function NetscriptFunctions(workerScript) {
|
||||
},
|
||||
upgradeLevel : function(i, n) {
|
||||
var node = getHacknetNode(i);
|
||||
return node.purchaseLevelUpgrade(n);
|
||||
return node.purchaseLevelUpgrade(n, Player);
|
||||
},
|
||||
upgradeRam : function(i, n) {
|
||||
var node = getHacknetNode(i);
|
||||
return node.purchaseRamUpgrade(n);
|
||||
return node.purchaseRamUpgrade(n, Player);
|
||||
},
|
||||
upgradeCore : function(i, n) {
|
||||
var node = getHacknetNode(i);
|
||||
return node.purchaseCoreUpgrade(n);
|
||||
return node.purchaseCoreUpgrade(n, Player);
|
||||
},
|
||||
getLevelUpgradeCost : function(i, n) {
|
||||
var node = getHacknetNode(i);
|
||||
return node.calculateLevelUpgradeCost(n);
|
||||
return node.calculateLevelUpgradeCost(n, Player);
|
||||
},
|
||||
getRamUpgradeCost : function(i, n) {
|
||||
var node = getHacknetNode(i);
|
||||
return node.calculateRamUpgradeCost(n);
|
||||
return node.calculateRamUpgradeCost(n, Player);
|
||||
},
|
||||
getCoreUpgradeCost : function(i, n) {
|
||||
var node = getHacknetNode(i);
|
||||
return node.calculateCoreUpgradeCost(n);
|
||||
return node.calculateCoreUpgradeCost(n, Player);
|
||||
}
|
||||
},
|
||||
sprintf : sprintf,
|
||||
@ -4807,7 +4811,341 @@ function NetscriptFunctions(workerScript) {
|
||||
}
|
||||
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 NetscriptFunction()
|
||||
|
||||
|
@ -12,6 +12,8 @@ import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugment
|
||||
import { Company } from "../Company/Company";
|
||||
import { CompanyPosition } from "../Company/CompanyPosition";
|
||||
import { CityName } from "../Locations/data/CityNames";
|
||||
import { HacknetNode } from "../Hacknet/HacknetNode";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { LocationName } from "../Locations/data/LocationNames";
|
||||
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
||||
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
||||
@ -26,7 +28,7 @@ export interface IPlayer {
|
||||
corporation: any;
|
||||
currentServer: string;
|
||||
factions: string[];
|
||||
hacknetNodes: any[];
|
||||
hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
|
||||
hasWseAccount: boolean;
|
||||
jobs: IMap<string>;
|
||||
karma: number;
|
||||
|
@ -105,7 +105,7 @@ export abstract class Person {
|
||||
/**
|
||||
* City that the person is in
|
||||
*/
|
||||
city: string = CityName.Sector12;
|
||||
city: CityName = CityName.Sector12;
|
||||
|
||||
constructor() {}
|
||||
|
||||
|
@ -96,7 +96,7 @@ export function createResleevesPage(p: IPlayer) {
|
||||
display: "inline-block",
|
||||
innerText: "Sort By: "
|
||||
});
|
||||
UIElems.sortSelector = createElement("select") as HTMLSelectElement;
|
||||
UIElems.sortSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
|
||||
|
||||
enum SortOption {
|
||||
Cost = "Cost",
|
||||
@ -309,7 +309,7 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
|
||||
elems.statsPanel.appendChild(elems.multipliersButton);
|
||||
|
||||
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
|
||||
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement;
|
||||
elems.augSelector = createElement("select", { class: "resleeve-aug-selector dropdown" }) as HTMLSelectElement;
|
||||
elems.augDescription = createElement("p");
|
||||
for (let i = 0; i < resleeve.augmentations.length; ++i) {
|
||||
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
|
||||
|
@ -14,6 +14,8 @@ import { Person,
|
||||
createTaskTracker } from "../Person";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
|
||||
@ -112,13 +114,12 @@ export class Sleeve extends Person {
|
||||
logs: string[] = [];
|
||||
|
||||
/**
|
||||
* Clone retains memory% of exp upon prestige. If exp would be lower than previously
|
||||
* kept exp, nothing happens
|
||||
* Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
|
||||
*/
|
||||
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
|
||||
* is multipled by shock%. This gets applied before synchronization
|
||||
*
|
||||
@ -337,6 +338,31 @@ export class Sleeve extends Person {
|
||||
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
|
||||
* 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
|
||||
* 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(p: IPlayer, newCity: string): boolean {
|
||||
if (CityName[newCity] == null) {
|
||||
console.error(`Invalid city ${newCity} passed into Sleeve.travel()`);
|
||||
return false;
|
||||
}
|
||||
|
||||
travel(p: IPlayer, newCity: CityName): boolean {
|
||||
p.loseMoney(CONSTANTS.TravelCost);
|
||||
this.city = newCity;
|
||||
|
||||
@ -641,8 +676,8 @@ export class Sleeve extends Person {
|
||||
|
||||
const company: Company | null = Companies[companyName];
|
||||
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
|
||||
if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); }
|
||||
if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); }
|
||||
if (company == null) { return false; }
|
||||
if (companyPosition == null) { return false; }
|
||||
this.gainRatesForTask.money = companyPosition.baseSalary *
|
||||
company.salaryMultiplier *
|
||||
this.work_money_mult *
|
||||
@ -684,8 +719,8 @@ export class Sleeve extends Person {
|
||||
* Returns boolean indicating success
|
||||
*/
|
||||
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
|
||||
if (factionName === "") { return false; }
|
||||
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
|
||||
throw new Error(`Invalid Faction specified for Sleeve.workForFaction(): ${factionName}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -807,6 +842,25 @@ export class Sleeve extends Person {
|
||||
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.
|
||||
*/
|
||||
@ -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;
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Module for handling the UI for purchasing Sleeve Augmentations
|
||||
* This UI is a popup, not a full page
|
||||
*/
|
||||
import { Sleeve } from "./Sleeve";
|
||||
import { Sleeve, findSleevePurchasableAugs } from "./Sleeve";
|
||||
|
||||
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
|
||||
// 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
const availableAugs = findSleevePurchasableAugs(sleeve, p);
|
||||
|
||||
// Create popup
|
||||
const popupId = "purchase-sleeve-augs-popup";
|
||||
@ -105,15 +89,13 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
|
||||
innerHTML:
|
||||
[
|
||||
`<h2>${aug.name}</h2><br>`,
|
||||
`Cost: ${numeralWrapper.formatMoney(aug.baseCost)}<br><br>`,
|
||||
`Cost: ${numeralWrapper.formatMoney(aug.startingCost)}<br><br>`,
|
||||
`${aug.info}`
|
||||
].join(" "),
|
||||
padding: "2px",
|
||||
clickListener: () => {
|
||||
if (p.canAfford(aug.baseCost)) {
|
||||
p.loseMoney(aug.baseCost);
|
||||
sleeve.installAugmentation(aug);
|
||||
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
|
||||
if (sleeve.tryBuyAugmentation(p, aug)) {
|
||||
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false);
|
||||
removeElementById(popupId);
|
||||
createSleevePurchaseAugsPopup(sleeve, p);
|
||||
} 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 { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { yesNoBoxCreate,
|
||||
yesNoBoxClose,
|
||||
yesNoBoxGetYesButton,
|
||||
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
|
||||
import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
|
||||
import { createPopup,
|
||||
removePopup } from "../../ui/React/createPopup";
|
||||
|
||||
export const MaxSleevesFromCovenant: number = 5;
|
||||
export const BaseCostPerSleeve: number = 10e12;
|
||||
export const PopupId: string = "covenant-sleeve-purchases-popup";
|
||||
|
||||
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) {
|
||||
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
|
||||
|
||||
// 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);
|
||||
export function createSleevePurchasesFromCovenantPopup(p: IPlayer) {
|
||||
const removePopupFn = removePopup.bind(null, PopupId);
|
||||
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user