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:
danielyxie 2019-03-29 16:13:58 -07:00
commit 3f8b9e4a32
137 changed files with 10196 additions and 3142 deletions

@ -5,7 +5,7 @@ played at https://danielyxie.github.io/bitburner.
# Documentation # Documentation
The game's official documentation can be found on [Read The The game's official documentation can be found on [Read The
Docs](http://bitburner.readthedocs.io/). Please note that this is still a Docs](http://bitburner.readthedocs.io/). Please note that this is still a
work-in-progress and is in its early stages. work-in-progress.
The documentation is created using [Sphinx](http://www.sphinx-doc.org). The documentation is created using [Sphinx](http://www.sphinx-doc.org).
@ -14,11 +14,6 @@ files](/doc/source) and then making a pull request with your contributions.
For further guidance, please refer to the "As A Documentor" section of For further guidance, please refer to the "As A Documentor" section of
[CONTRIBUTING](CONTRIBUTING.md). [CONTRIBUTING](CONTRIBUTING.md).
# Wiki
The game's wiki can be found on [Wikia](http://bitburner.wikia.com/). Please
note that the wiki is in the process of being deprecated. Eventually all of
the wiki content will be moved into the Read The Docs documentation.
# Contribution # Contribution
There are many ways to contribute to the game. It can be as simple as fixing There are many ways to contribute to the game. It can be as simple as fixing
a typo, correcting a bug, or improving the UI. For guidance on doing so, a typo, correcting a bug, or improving the UI. For guidance on doing so,

@ -51,6 +51,44 @@
pointer-events: none; pointer-events: none;
} }
/* Checkbox for (de)selecting autoleveling */
.bbcheckbox {
position: relative;
display: inline;
label {
width: 20px;
height: 20px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
background: black;
border-width: 1px;
border-color: white;
border-style: solid;
&:after {
content: '';
width: 9px;
height: 5px;
position: absolute;
top: 5px;
left: 5px;
border: 3px solid white;
border-top: none;
border-right: none;
opacity: 0;
transform: rotate(-45deg);
}
}
input[type=checkbox] {
margin: 3px;
visibility: hidden;
&:checked + label:after {
opacity: 1;
}
}
}
/* Bladeburner Console */ /* Bladeburner Console */
.bladeburner-console-div { .bladeburner-console-div {
display: inline-block; display: inline-block;

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

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

@ -10,7 +10,8 @@
#cmpy-mgmt-container p, #cmpy-mgmt-container p,
#cmpy-mgmt-container a, #cmpy-mgmt-container a,
#cmpy-mgmt-container div { #cmpy-mgmt-container div,
#cmpy-mgmt-container br {
font-size: $defaultFontSize * 0.8125; font-size: $defaultFontSize * 0.8125;
} }
@ -159,5 +160,6 @@
/* Research */ /* Research */
#corporation-research-popup-box-content { #corporation-research-popup-box-content {
overflow-x: visible !important; overflow-x: auto !important;
overflow-y: auto !important;
} }

36
css/dev-menu.css Normal file

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

File diff suppressed because one or more lines are too long

75
css/hacknetnodes.scss Normal file

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

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

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

@ -62,6 +62,9 @@ a:visited {
.text-input { .text-input {
color: #fff; color: #fff;
background-color: #000; background-color: #000;
border-style: solid;
border-width: 1px;
border-color: white;
} }
/* Notification icon (for create program right now only) */ /* Notification icon (for create program right now only) */

File diff suppressed because one or more lines are too long

2578
dist/engine.css vendored

File diff suppressed because it is too large Load Diff

152
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -14,6 +14,8 @@ Sleeve technology unlocks two different gameplay features:
Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`. Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
.. _gameplay_duplicatesleeves:
Duplicate Sleeves Duplicate Sleeves
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
@ -28,6 +30,19 @@ Sleeves are their own individuals, which means they each have their own experien
When a sleeve earns experience, it earns experience for itself, the player's When a sleeve earns experience, it earns experience for itself, the player's
original consciousness, as well as all of the player's other sleeves. original consciousness, as well as all of the player's other sleeves.
Duplicate Sleeves are **not** reset when installing Augmentations, but they are reset
when switching BitNodes.
Obtaining Duplicate Sleeves
~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two methods of obtaining Duplicate Sleeves:
1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve
2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant <gameplay_factions>`.
This is only available in BitNodes-10 and above, and is only available after defeating
BitNode-10 at least once. Sleeves purchased this way are **permanent** (they persist
through BitNodes). You can purchase up to 5 Duplicate Sleeves from The Covenant.
Synchronization Synchronization
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Synchronization is a measure of how aligned your consciousness is with that of your Synchronization is a measure of how aligned your consciousness is with that of your
@ -50,15 +65,30 @@ no shock. Shock affects the amount of experience earned by the sleeve.
Sleeve shock slowly decreases over time. You can further increase the rate at which Sleeve shock slowly decreases over time. You can further increase the rate at which
it decreases by assigning sleeves to the 'Shock Recovery' task. it decreases by assigning sleeves to the 'Shock Recovery' task.
Obtaining Duplicate Sleeves Augmentations
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~
There are two methods of obtaining Duplicate Sleeves: You can purchase :ref:`Augmentations <gameplay_augmentations>` for your Duplicate
Sleeves. In order to do this, the Sleeve's Shock must be at 0. Any Augmentation
that is currently available to you through a faction is also available for your
Duplicate Sleeves. There are a few Augmentations, such as NeuroFlux Governor and
Bladeburner-specific ones, that cannot be purchased for a Duplicate Sleeve.
1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve When you purchase an Augmentation for a Duplicate Sleeve, it is instantly installed.
2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant <gameplay_factions>`. When this happens, the Sleeve's stats are instantly reset back to 0, similar to
This is only available in BitNodes-10 and above, and is only available after defeating when you normally install Augmentations.
BitNode-10 at least once. Sleeves purchased this way are permanent. You can purchase
up to 5 Duplicate Sleeves from The Covenant. The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected
by how many Augmentations you have purchased for yourself, and vice versa.
Memory
~~~~~~
Sleeve memory dictates what a sleeve's synchronization will be when its reset by
switching BitNodes. For example, if a sleeve has a memory of 10, then when you
switch BitNodes its synchronization will initially be set to 10, rather than 1.
Memory can only be increased by purchasing upgrades from The Covenant.
It is a persistent stat, meaning it never gets reset back to 1.
The maximum possible value for a sleeve's memory is 100.
Re-sleeving Re-sleeving
^^^^^^^^^^^ ^^^^^^^^^^^

@ -48,6 +48,7 @@ List of all Source-Files
| BitNode-9: Coming Soon | | | BitNode-9: Coming Soon | |
+------------------------------------+-------------------------------------------------------------------------------------+ +------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve | | BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve |
| | * Allows the player to access the :ref:`netscript_sleeveapi` in other BitNodes |
+------------------------------------+-------------------------------------------------------------------------------------+ +------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-11: The Big Crash | * Company favor increases both the player's salary and reputation gain at that | | BitNode-11: The Big Crash | * Company favor increases both the player's salary and reputation gain at that |
| | company by 1% per favor (rather than just the reputation gain) | | | company by 1% per favor (rather than just the reputation gain) |

@ -3,6 +3,42 @@
Changelog Changelog
========= =========
v0.45.1 - 3/23/2019
-------------------
* Added two new Corporation Researches
* General UI improvements (by hydroflame and koriar)
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors
* Bug Fix: sleeve.getSleeveStats() should now work properly
v0.45.0 - 3/22/2019
-------------------
* Corporation changes:
* Decreased the time of a full market cycle from 15 seconds to 10 seconds.
* This means that each Corporation 'state' will now only take 2 seconds, rather than 3
* Increased initial salaries for newly-hired employees
* Increased the cost multiplier for upgrading office size (the cost will increase faster)
* The stats of your employees now has a slightly larger effect on production & sales
* Added several new Research upgrades
* Market-TA research now allows you to automatically set sale price at optimal values
* Market-TA research now works for Products (not just Materials)
* Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k
* Energy Material requirement of the Software industry reduced from 1 to 0.5
* It is now slightly easier to increase the Software industry's production multiplier
* Industries now have a maximum number of allowed products, starting at 3. This can be increased through research.
* You can now see an approximation of how each material affects an industry's production multiplier by clicking the "?" help tip next to it
* Significantly changed the effects of the different employee positions. See updated descriptions
* Reduced the amount of money you gain from private investors
* Training employees is now 3x more effective
* Bug Fix: An industry's products are now properly separated between different cities
* The QLink Augemntation is now significantly stronger, but also significantly more expensive (by hydroflame)
* Added a Netscript API for Duplicate Sleeves (by hydroflame)
* Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder
* After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0
* Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina
* Bug Fix: growthAnalyze() function now properly accounts for BitNode multipliers
* Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself
v0.44.1 - 3/4/2019 v0.44.1 - 3/4/2019
------------------ ------------------
* Duplicate Sleeve changes: * Duplicate Sleeve changes:

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.44' version = '0.45'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.44.1' release = '0.45.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

@ -29,4 +29,5 @@ to reach out to the developer!
Bladeburner API <netscript/netscriptbladeburnerapi> Bladeburner API <netscript/netscriptbladeburnerapi>
Gang API <netscript/netscriptgangapi> Gang API <netscript/netscriptgangapi>
Coding Contract API <netscript/netscriptcodingcontractapi> Coding Contract API <netscript/netscriptcodingcontractapi>
Sleeve API <netscript/netscriptsleeveapi>
Miscellaneous <netscript/netscriptmisc> Miscellaneous <netscript/netscriptmisc>

@ -1,5 +1,5 @@
enableLog() Netscript Function enableLog() Netscript Function
============================= ==============================
.. js:function:: enableLog(fn) .. js:function:: enableLog(fn)

@ -4,7 +4,7 @@ purchaseServer() Netscript Function
.. js:function:: purchaseServer(hostname, ram) .. js:function:: purchaseServer(hostname, ram)
:param string hostname: Hostname of the purchased server :param string hostname: Hostname of the purchased server
:param number ram: Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20) :param number ram: Amount of RAM of the purchased server. Must be a power of 2. Maximum value of :js:func:`getPurchasedServerMaxRam`
:RAM cost: 2.25 GB :RAM cost: 2.25 GB
Purchased a server with the specified hostname and amount of RAM. Purchased a server with the specified hostname and amount of RAM.

@ -4,6 +4,8 @@ Netscript Advanced Functions
These Netscript functions become relevant later on in the game. They are put on a separate page because These Netscript functions become relevant later on in the game. They are put on a separate page because
they contain spoilers for the game. they contain spoilers for the game.
.. warning:: This page contains spoilers for the game
.. toctree:: .. toctree::
getBitNodeMultipliers() <advancedfunctions/getBitNodeMultipliers> getBitNodeMultipliers() <advancedfunctions/getBitNodeMultipliers>

@ -7,7 +7,7 @@ Netscript provides the following API for interacting with the game's Bladeburner
The Bladeburner API is **not** immediately available to the player and must be unlocked The Bladeburner API is **not** immediately available to the player and must be unlocked
later in the game later in the game
**WARNING: This page contains spoilers for the game** .. warning:: This page contains spoilers for the game
The Bladeburner API is unlocked in BitNode-7. If you are in BitNode-7, you will The Bladeburner API is unlocked in BitNode-7. If you are in BitNode-7, you will
automatically gain access to this API. Otherwise, you must have Source-File 7 in automatically gain access to this API. Otherwise, you must have Source-File 7 in

@ -6,7 +6,7 @@ Netscript provides the following API for interacting with the game's Gang mechan
The Gang API is **not** immediately available to the player and must be unlocked The Gang API is **not** immediately available to the player and must be unlocked
later in the game later in the game
**WARNING: This page contains spoilers for the game** .. warning:: This page contains spoilers for the game
The Gang API is unlocked in BitNode-2. Currently, BitNode-2 is the only location The Gang API is unlocked in BitNode-2. Currently, BitNode-2 is the only location
where the Gang mechanic is accessible. This may change in the future where the Gang mechanic is accessible. This may change in the future

@ -9,7 +9,7 @@ and creating programs.
The Singularity Functions are **not** immediately available to the player and must be unlocked later in the game. The Singularity Functions are **not** immediately available to the player and must be unlocked later in the game.
**WARNING: This page contains spoilers for the game**. .. warning:: This page contains spoilers for the game
The Singularity Functions are unlocked in BitNode-4. If you are in BitNode-4, then you will automatically have access to all of these functions. The Singularity Functions are unlocked in BitNode-4. If you are in BitNode-4, then you will automatically have access to all of these functions.
You can use the Singularity Functions in other BitNodes if and only if you have the Source-File for BitNode-4 (aka Source-File 4). Each level of You can use the Singularity Functions in other BitNodes if and only if you have the Source-File for BitNode-4 (aka Source-File 4). Each level of

@ -0,0 +1,76 @@
.. _netscript_sleeveapi:
Netscript Sleeve API
=========================
Netscript provides the following API for interacting with the game's
:ref:`Duplicate Sleeve <gameplay_duplicatesleeves>` mechanic.
The Sleeve API is **not** immediately available to the player and must be unlocked
later in the game.
.. warning:: This page contains spoilers for the game
The Sleeve API is unlocked in BitNode-10. If you are in BitNode-10, you will
automatically gain access to this API. Otherwise, you must have Source-File 10 in
order to use this API in other BitNodes
**Sleeve API functions must be accessed through the 'sleeve' namespace**
In :ref:`netscript1`::
sleeve.synchronize(0);
sleeve.commitCrime(0, "shoplift");
In :ref:`netscriptjs`::
ns.sleeve.synchronize(0);
ns.sleeve.commitCrime(0, "shoplift");
.. toctree::
:caption: API Functions:
getNumSleeves() <sleeveapi/getNumSleeves>
getSleeveStats() <sleeveapi/getSleeveStats>
getInformation() <sleeveapi/getInformation>
getTask() <sleeveapi/getTask>
setToShockRecovery() <sleeveapi/setToShockRecovery>
setToSynchronize() <sleeveapi/setToSynchronize>
setToCommitCrime() <sleeveapi/setToCommitCrime>
setToFactionWork() <sleeveapi/setToFactionWork>
setToCompanyWork() <sleeveapi/setToCompanyWork>
setToUniversityCourse() <sleeveapi/setToUniversityCourse>
setToGymWorkout() <sleeveapi/setToGymWorkout>
travel() <sleeveapi/travel>
.. _netscript_sleeveapi_referencingaduplicatesleeve:
Referencing a Duplicate Sleeve
------------------------------
Most of the functions in the Sleeve API perform an operation on a single Duplicate
Sleeve. In order to specify which Sleeve the operation should be performed on,
a numeric index is used as an identifier. The index should follow array-notation, such
that the first Duplicate Sleeve has an index of 0, the second Duplicate Sleeve has
an index of 1, and so on.
The order of the Duplicate Sleeves matches the order on the UI page.
Examples
--------
**Basic example usage**::
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.shockRecovery(i);
}
sleep(10*60*60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.synchronize(i);
}
sleep(10*60*60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.commitCrime(i, 'shoplift');
}

@ -1,7 +1,11 @@
workForCompany() Netscript Function workForCompany() Netscript Function
=================================== ===================================
.. js:function:: workForCompany() .. js:function:: workForCompany(companyName=lastCompany)
:param string companyName: Name of company to work for. Must be an exact match.
Optional. If not specified, this argument defaults to
the last job that you worked
If you are not in BitNode-4, then you must have Level 2 of Source-File 4 in order to use this function. If you are not in BitNode-4, then you must have Level 2 of Source-File 4 in order to use this function.

@ -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(),
}

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -131,7 +131,7 @@
<h1 style="color:white;"> Script Editor Options </h1> <h1 style="color:white;"> Script Editor Options </h1>
<fieldset> <fieldset>
<label for="script-editor-option-editor">Editor</label> <label for="script-editor-option-editor">Editor</label>
<select id="script-editor-option-editor"> <select id="script-editor-option-editor" class="dropdown">
<option value="Ace">Ace</option> <option value="Ace">Ace</option>
<option value="CodeMirror">CodeMirror</option> <option value="CodeMirror">CodeMirror</option>
</select> </select>
@ -139,12 +139,12 @@
<fieldset> <fieldset>
<label for="script-editor-option-theme">Theme</label> <label for="script-editor-option-theme">Theme</label>
<select id="script-editor-option-theme"></select> <select id="script-editor-option-theme" class="dropdown"></select>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="script-editor-option-keybinding">Key Binding</label> <label for="script-editor-option-keybinding">Key Binding</label>
<select id="script-editor-option-keybinding"></select> <select id="script-editor-option-keybinding" class="dropdown"></select>
</fieldset> </fieldset>
<fieldset> <fieldset>
@ -689,7 +689,7 @@
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a> <a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a> <a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
<br/><br/> <br/><br/>
<input id="stock-market-watchlist-filter" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/> <input id="stock-market-watchlist-filter" class="text-input" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
<a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a> <a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a>
<ul id="stock-market-list" style="list-style:none;"> <ul id="stock-market-list" style="list-style:none;">
</ul> </ul>
@ -744,7 +744,7 @@
<p id="infiltration-box-text"> </p> <p id="infiltration-box-text"> </p>
<button id="infiltration-box-sell" class="a-link-button"> Sell on Black Market </button> <br/><br/> <button id="infiltration-box-sell" class="a-link-button"> Sell on Black Market </button> <br/><br/>
<select id="infiltration-faction-select"> </select> <br/> <select id="infiltration-faction-select" class="dropdown"> </select> <br/>
<button id="infiltration-box-faction" class="a-link-button"> Give to Faction for Reputation </button> <button id="infiltration-box-faction" class="a-link-button"> Give to Faction for Reputation </button>
</div> </div>
@ -783,35 +783,7 @@
<div id="character-overview-wrapper"> <div id="character-overview-wrapper">
<div id="character-overview-container"> <div id="character-overview-container">
<div id="character-overview-text"> <div id="character-overview-text">
<table> <!-- ReactJS Component -->
<tr id="character-hp-wrapper">
<td>Hp:</td><td id="character-hp-text" class="character-stat-cell"></td>
</tr>
<tr id="character-money-wrapper">
<td>Money:&nbsp;</td><td id="character-money-text" class="character-stat-cell"></td>
</tr>
<tr id="character-hack-wrapper">
<td>Hack:&nbsp;</td><td id="character-hack-text" class="character-stat-cell"></td>
</tr>
<tr id="character-str-wrapper">
<td>Str:&nbsp;</td><td id="character-str-text" class="character-stat-cell"></td>
</tr>
<tr id="character-def-wrapper">
<td>Def:&nbsp;</td><td id="character-def-text" class="character-stat-cell"></td>
</tr>
<tr id="character-dex-wrapper">
<td>Dex:&nbsp;</td><td id="character-dex-text" class="character-stat-cell"></td>
</tr>
<tr id="character-agi-wrapper">
<td>Agi:&nbsp;</td><td id="character-agi-text" class="character-stat-cell"></td>
</tr>
<tr id="character-cha-wrapper">
<td>Cha:&nbsp;</td><td id="character-cha-text" class="character-stat-cell"></td>
</tr>
<tr id="character-int-wrapper">
<td>Int:&nbsp;</td><td id="character-int-text" class="character-stat-cell"></td>
</tr>
</table>
</div> </div>
<div class="character-quick-options"> <div class="character-quick-options">
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button> <button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
@ -959,7 +931,7 @@
Sets the locale for displaying numbers. Defaults to 'en' Sets the locale for displaying numbers. Defaults to 'en'
</span> </span>
</label> </label>
<select name="settingsLocale" id="settingsLocale"> <select name="settingsLocale" id="settingsLocale" class="dropdown">
<option value="en">en</option> <option value="en">en</option>
<option value="bg">bg</option> <option value="bg">bg</option>
<option value="cs">cs</option> <option value="cs">cs</option>

1407
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -24,7 +24,7 @@
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"interpret": "^1.0.0", "interpret": "^1.0.0",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"jshint": "^2.9.7", "jshint": "^2.10.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"jsplumb": "^2.6.8", "jsplumb": "^2.6.8",
"jszip": "^3.1.5", "jszip": "^3.1.5",
@ -37,7 +37,6 @@
"react-dom": "^16.8.3", "react-dom": "^16.8.3",
"sprintf-js": "^1.1.1", "sprintf-js": "^1.1.1",
"tapable": "^1.0.0", "tapable": "^1.0.0",
"uglifyjs-webpack-plugin": "^1.2.5",
"uuid": "^3.2.1", "uuid": "^3.2.1",
"w3c-blob": "0.0.1" "w3c-blob": "0.0.1"
}, },
@ -61,7 +60,7 @@
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"js-beautify": "^1.5.10", "js-beautify": "^1.5.10",
"json5": "^1.0.1", "json5": "^1.0.1",
"less": "^3.0.4", "less": "^3.9.0",
"less-loader": "^4.1.0", "less-loader": "^4.1.0",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"mini-css-extract-plugin": "^0.4.1", "mini-css-extract-plugin": "^0.4.1",
@ -80,15 +79,17 @@
"stylelint": "^9.2.1", "stylelint": "^9.2.1",
"stylelint-declaration-use-variable": "^1.6.1", "stylelint-declaration-use-variable": "^1.6.1",
"stylelint-order": "^0.8.1", "stylelint-order": "^0.8.1",
"ts-loader": "^4.4.1", "ts-loader": "^4.5.0",
"tslint": "^5.10.0", "tslint": "^5.10.0",
"typescript": "^2.9.2", "typescript": "^2.9.2",
"uglify-es": "^3.3.9",
"uglifyjs-webpack-plugin": "^1.3.0",
"url-loader": "^1.0.1", "url-loader": "^1.0.1",
"watchpack": "^1.6.0", "watchpack": "^1.6.0",
"webpack": "^4.12.0", "webpack": "^4.12.0",
"webpack-cli": "^3.0.4", "webpack-cli": "^3.0.4",
"webpack-dev-middleware": "^3.1.3", "webpack-dev-middleware": "^3.1.3",
"webpack-dev-server": "^3.1.4", "webpack-dev-server": "^3.2.1",
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"engines": { "engines": {
@ -113,5 +114,5 @@
"watch": "webpack --watch --mode production", "watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development" "watch:dev": "webpack --watch --mode development"
}, },
"version": "0.40.2" "version": "0.45.0"
} }

@ -1213,17 +1213,19 @@ function initAugmentations() {
//Illuminati //Illuminati
var QLink = new Augmentation({ var QLink = new Augmentation({
name:AugmentationNames.QLink, repCost:750e3, moneyCost:1300e6, name:AugmentationNames.QLink, repCost:750e3, moneyCost:5e12,
info:"A brain implant that wirelessly connects you to the Illuminati's " + info:"A brain implant that wirelessly connects you to the Illuminati's " +
"quantum supercomputer, allowing you to access and use its incredible " + "quantum supercomputer, allowing you to access and use its incredible " +
"computing power.<br><br>" + "computing power.<br><br>" +
"This augmentation:<br>" + "This augmentation:<br>" +
"Increases the player's hacking speed by 10%.<br>" + "Increases the player's hacking skill by 75%.<br>" +
"Increases the player's chance of successfully performing a hack by 30%.<br>" + "Increases the player's hacking speed by 100%.<br>" +
"Increases the amount of money the player gains from hacking by 100%.", "Increases the player's chance of successfully performing a hack by 150%.<br>" +
hacking_speed_mult: 1.1, "Increases the amount of money the player gains from hacking by 300%.",
hacking_chance_mult: 1.3, hacking_mult: 1.75,
hacking_money_mult: 2, hacking_speed_mult: 2,
hacking_chance_mult: 2.5,
hacking_money_mult: 4,
}); });
QLink.addToFactions(["Illuminati"]); QLink.addToFactions(["Illuminati"]);
if (augmentationExists(AugmentationNames.QLink)) { if (augmentationExists(AugmentationNames.QLink)) {
@ -2067,7 +2069,7 @@ function installAugmentations(cbScript=null) {
} }
var runningScriptObj = new RunningScript(script, []); //No args var runningScriptObj = new RunningScript(script, []); //No args
runningScriptObj.threads = 1; //Only 1 thread runningScriptObj.threads = 1; //Only 1 thread
home.runningScripts.push(runningScriptObj); home.runScript(runningScriptObj, Player);
addWorkerScript(runningScriptObj, home); addWorkerScript(runningScriptObj, home);
} }
} }

@ -173,7 +173,10 @@ export function initBitNodes() {
"Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" + "Level 3: Ability to use limit/stop orders in other BitNodes<br><br>" +
"This Source-File also increases your hacking growth multipliers by: " + "This Source-File also increases your hacking growth multipliers by: " +
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%"); "<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
BitNodes["BitNode9"] = new BitNode(9, "Do Androids Dream?", "COMING SOON"); BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed",
"When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " +
"became the OS of choice for the underground hacking community. Chapeau became especially notorious for " +
"powering the Hacknet, ");
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are", BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
@ -198,8 +201,9 @@ export function initBitNodes() {
"were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " +
"governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" + "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.<br><br>" +
"In this BitNode:<br><br>" + "In this BitNode:<br><br>" +
"Your hacking stat and experience gain are halved<br>" +
"The starting and maximum amount of money available on servers is significantly decreased<br>" + "The starting and maximum amount of money available on servers is significantly decreased<br>" +
"The growth rate of servers is halved<br>" + "The growth rate of servers is significantly reduced<br>" +
"Weakening a server is twice as effective<br>" + "Weakening a server is twice as effective<br>" +
"Company wages are decreased by 50%<br>" + "Company wages are decreased by 50%<br>" +
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" + "Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
@ -210,9 +214,9 @@ export function initBitNodes() {
"upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " +
"the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " +
"This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" + "This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
"Level 1: 24%<br>" + "Level 1: 32%<br>" +
"Level 2: 36%<br>" + "Level 2: 48%<br>" +
"Level 3: 42%"); "Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"To iterate is human, to recurse divine.<br><br>" + "To iterate is human, to recurse divine.<br><br>" +
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " +
@ -245,9 +249,9 @@ export function initBitNodeMultipliers(p: IPlayer) {
} }
switch (p.bitNodeN) { switch (p.bitNodeN) {
case 1: //Source Genesis (every multiplier is 1) case 1: // Source Genesis (every multiplier is 1)
break; break;
case 2: //Rise of the Underworld case 2: // Rise of the Underworld
BitNodeMultipliers.HackingLevelMultiplier = 0.8; BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.ServerGrowthRate = 0.8; BitNodeMultipliers.ServerGrowthRate = 0.8;
BitNodeMultipliers.ServerMaxMoney = 0.2; BitNodeMultipliers.ServerMaxMoney = 0.2;
@ -257,7 +261,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FactionWorkRepGain = 0.5; BitNodeMultipliers.FactionWorkRepGain = 0.5;
BitNodeMultipliers.FactionPassiveRepGain = 0; BitNodeMultipliers.FactionPassiveRepGain = 0;
break; break;
case 3: //Corporatocracy case 3: // Corporatocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.RepToDonateToFaction = 0.5; BitNodeMultipliers.RepToDonateToFaction = 0.5;
BitNodeMultipliers.AugmentationRepCost = 3; BitNodeMultipliers.AugmentationRepCost = 3;
BitNodeMultipliers.AugmentationMoneyCost = 3; BitNodeMultipliers.AugmentationMoneyCost = 3;
@ -268,8 +273,10 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CompanyWorkMoney = 0.25; BitNodeMultipliers.CompanyWorkMoney = 0.25;
BitNodeMultipliers.CrimeMoney = 0.25; BitNodeMultipliers.CrimeMoney = 0.25;
BitNodeMultipliers.HacknetNodeMoney = 0.25; BitNodeMultipliers.HacknetNodeMoney = 0.25;
BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 2;
break; break;
case 4: //The Singularity case 4: // The Singularity
BitNodeMultipliers.ServerMaxMoney = 0.15; BitNodeMultipliers.ServerMaxMoney = 0.15;
BitNodeMultipliers.ServerStartingMoney = 0.75; BitNodeMultipliers.ServerStartingMoney = 0.75;
BitNodeMultipliers.ScriptHackMoney = 0.2; BitNodeMultipliers.ScriptHackMoney = 0.2;
@ -283,7 +290,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CrimeExpGain = 0.5; BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.75; BitNodeMultipliers.FactionWorkRepGain = 0.75;
break; break;
case 5: //Artificial intelligence case 5: // Artificial intelligence
BitNodeMultipliers.ServerMaxMoney = 2; BitNodeMultipliers.ServerMaxMoney = 2;
BitNodeMultipliers.ServerStartingSecurity = 2; BitNodeMultipliers.ServerStartingSecurity = 2;
BitNodeMultipliers.ServerStartingMoney = 0.5; BitNodeMultipliers.ServerStartingMoney = 0.5;
@ -296,7 +303,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HackExpGain = 0.5; BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5; BitNodeMultipliers.CorporationValuation = 0.5;
break; break;
case 6: //Bladeburner case 6: // Bladeburner
BitNodeMultipliers.HackingLevelMultiplier = 0.35; BitNodeMultipliers.HackingLevelMultiplier = 0.35;
BitNodeMultipliers.ServerMaxMoney = 0.4; BitNodeMultipliers.ServerMaxMoney = 0.4;
BitNodeMultipliers.ServerStartingMoney = 0.5; BitNodeMultipliers.ServerStartingMoney = 0.5;
@ -311,7 +318,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.HackExpGain = 0.25; BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
break; break;
case 7: //Bladeburner 2079 case 7: // Bladeburner 2079
BitNodeMultipliers.BladeburnerRank = 0.6; BitNodeMultipliers.BladeburnerRank = 0.6;
BitNodeMultipliers.BladeburnerSkillCost = 2; BitNodeMultipliers.BladeburnerSkillCost = 2;
BitNodeMultipliers.AugmentationMoneyCost = 3; BitNodeMultipliers.AugmentationMoneyCost = 3;
@ -331,7 +338,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2; BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
break; break;
case 8: //Ghost of Wall Street case 8: // Ghost of Wall Street
BitNodeMultipliers.ScriptHackMoney = 0; BitNodeMultipliers.ScriptHackMoney = 0;
BitNodeMultipliers.ManualHackMoney = 0; BitNodeMultipliers.ManualHackMoney = 0;
BitNodeMultipliers.CompanyWorkMoney = 0; BitNodeMultipliers.CompanyWorkMoney = 0;
@ -342,6 +349,23 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CorporationValuation = 0; BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0; BitNodeMultipliers.CodingContractMoney = 0;
break; break;
case 9: // Hacktocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.4;
BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
BitNodeMultipliers.PurchasedServerLimit = 0;
BitNodeMultipliers.HomeComputerRamCost = 5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.1;
BitNodeMultipliers.HackExpGain = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingSecurity = 2.5;
BitNodeMultipliers.CorporationValuation = 0.5;
break;
case 10: // Digital Carbon case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.2; BitNodeMultipliers.HackingLevelMultiplier = 0.2;
BitNodeMultipliers.StrengthLevelMultiplier = 0.4; BitNodeMultipliers.StrengthLevelMultiplier = 0.4;
@ -363,11 +387,14 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.PurchasedServerCost = 5; BitNodeMultipliers.PurchasedServerCost = 5;
BitNodeMultipliers.PurchasedServerLimit = 0.6; BitNodeMultipliers.PurchasedServerLimit = 0.6;
BitNodeMultipliers.PurchasedServerMaxRam = 0.5; BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
BitNodeMultipliers.BladeburnerRank = 0.8;
break; break;
case 11: //The Big Crash case 11: //The Big Crash
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.ServerMaxMoney = 0.1; BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1; BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerGrowthRate = 0.5; BitNodeMultipliers.ServerGrowthRate = 0.2;
BitNodeMultipliers.ServerWeakenRate = 2; BitNodeMultipliers.ServerWeakenRate = 2;
BitNodeMultipliers.CrimeMoney = 3; BitNodeMultipliers.CrimeMoney = 3;
BitNodeMultipliers.CompanyWorkMoney = 0.5; BitNodeMultipliers.CompanyWorkMoney = 0.5;
@ -375,8 +402,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.AugmentationMoneyCost = 2; BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.InfiltrationMoney = 2.5; BitNodeMultipliers.InfiltrationMoney = 2.5;
BitNodeMultipliers.InfiltrationRep = 2.5; BitNodeMultipliers.InfiltrationRep = 2.5;
BitNodeMultipliers.CorporationValuation = 0.01; BitNodeMultipliers.CorporationValuation = 0.1;
BitNodeMultipliers.CodingContractMoney = 0.5; BitNodeMultipliers.CodingContractMoney = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 4; BitNodeMultipliers.FourSigmaMarketDataCost = 4;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
break; break;

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

@ -31,6 +31,8 @@ import { getTimestamp } from "../utils/helpers/getTi
import { removeElement } from "../utils/uiHelpers/removeElement"; import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById"; import { removeElementById } from "../utils/uiHelpers/removeElementById";
const stealthIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style="fill:#adff2f;"><g><path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z"/><path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z"/></g></svg>&nbsp;`
const killIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style="fill:#adff2f;"><path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0"/><path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0"/><path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0"/><path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0"/></svg>`
const CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"]; const CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"];
@ -76,8 +78,8 @@ const RanksPerSkillPoint = 3; //How many ranks needed to get 1 Skill
const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber
const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber
//DOM related variables //DOM related variables
var ActiveActionCssClass = "bladeburner-active-action"; var ActiveActionCssClass = "bladeburner-active-action";
@ -1415,14 +1417,17 @@ Bladeburner.prototype.completeAction = function() {
} }
this.startAction(this.action); // Repeat Action this.startAction(this.action); // Repeat Action
break; break;
case ActionTypes["Hyperbolic Regeneration Chamber"]: case ActionTypes["Hyperbolic Regeneration Chamber"]: {
Player.regenerateHp(HrcHpGain); Player.regenerateHp(HrcHpGain);
this.stamina = Math.max(this.maxStamina, this.stamina + HrcStaminaGain); // TODO Turn this into a const and adjust value
const staminaGain = this.maxStamina * (HrcStaminaGain / 100);
this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain);
this.startAction(this.action); this.startAction(this.action);
if (this.logging.general) { if (this.logging.general) {
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`); this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`);
} }
break; break;
}
default: default:
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
break; break;
@ -1799,6 +1804,12 @@ Bladeburner.prototype.createContent = function() {
DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv); DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv);
DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv); DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv);
// legend
const legend = createElement("div")
legend.innerHTML = `<span class="text">${stealthIcon}= This action requires stealth, ${killIcon} = This action involves retirement</span>`
DomElems.bladeburnerDiv.appendChild(legend);
document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv); document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv);
if (this.consoleLogs.length === 0) { if (this.consoleLogs.length === 0) {
@ -2166,12 +2177,12 @@ Bladeburner.prototype.createBlackOpsContent = function() {
return (a.reqdRank - b.reqdRank); return (a.reqdRank - b.reqdRank);
}); });
for (var i = 0; i < blackops.length; ++i) { for (var i = blackops.length-1; i >= 0 ; --i) {
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
DomElems.blackops[blackops[i].name] = createElement("div", { DomElems.blackops[blackops[i].name] = createElement("div", {
class:"bladeburner-action", name:blackops[i].name class:"bladeburner-action", name:blackops[i].name
}); });
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]); DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
if (this.blackops[[blackops[i].name]] == null) {break;} //Can't be found in completed blackops
} }
} }
@ -2504,7 +2515,8 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
el.appendChild(createElement("pre", { //Info el.appendChild(createElement("pre", { //Info
display:"inline-block", display:"inline-block",
innerHTML:action.desc + "\n\n" + innerHTML:action.desc + "\n\n" +
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + `Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required (s): " + formatNumber(actionTime, 0) + "\n" + "Time Required (s): " + formatNumber(actionTime, 0) + "\n" +
"Contracts remaining: " + Math.floor(action.count) + "\n" + "Contracts remaining: " + Math.floor(action.count) + "\n" +
"Successes: " + action.successes + "\n" + "Successes: " + action.successes + "\n" +
@ -2518,14 +2530,21 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
for:autolevelCheckboxId, innerText:"Autolevel",color:"white", for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
tooltip:"Automatically increase contract level when possible" tooltip:"Automatically increase contract level when possible"
})); }));
var autolevelCheckbox = createElement("input", {
type:"checkbox", id:autolevelCheckboxId, margin:"4px", const checkboxDiv = createElement("div", { class: "bbcheckbox" });
checked:action.autoLevel, const checkboxInput = createElement("input", {
changeListener:()=>{ type:"checkbox",
action.autoLevel = autolevelCheckbox.checked; id: autolevelCheckboxId,
} checked: action.autoLevel,
changeListener: () => {
action.autoLevel = checkboxInput.checked;
},
}); });
el.appendChild(autolevelCheckbox); const checkmarkLabel = createElement("label", { for: autolevelCheckboxId });
checkboxDiv.appendChild(checkboxInput);
checkboxDiv.appendChild(checkmarkLabel);
el.appendChild(checkboxDiv);
} }
Bladeburner.prototype.updateOperationsUIElement = function(el, action) { Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
@ -2640,7 +2659,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
el.appendChild(createElement("pre", { el.appendChild(createElement("pre", {
display:"inline-block", display:"inline-block",
innerHTML:action.desc + "\n\n" + innerHTML:action.desc + "\n\n" +
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + `Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required(s): " + formatNumber(actionTime, 0) + "\n" + "Time Required(s): " + formatNumber(actionTime, 0) + "\n" +
"Operations remaining: " + Math.floor(action.count) + "\n" + "Operations remaining: " + Math.floor(action.count) + "\n" +
"Successes: " + action.successes + "\n" + "Successes: " + action.successes + "\n" +
@ -2654,14 +2673,21 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
for:autolevelCheckboxId, innerText:"Autolevel",color:"white", for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
tooltip:"Automatically increase operation level when possible" tooltip:"Automatically increase operation level when possible"
})); }));
var autolevelCheckbox = createElement("input", {
type:"checkbox", id:autolevelCheckboxId, margin:"4px", const checkboxDiv = createElement("div", { class: "bbcheckbox" });
checked:action.autoLevel, const checkboxInput = createElement("input", {
changeListener:()=>{ type:"checkbox",
action.autoLevel = autolevelCheckbox.checked; id: autolevelCheckboxId,
} checked: action.autoLevel,
changeListener: () => {
action.autoLevel = checkboxInput.checked;
},
}); });
el.appendChild(autolevelCheckbox); const checkmarkLabel = createElement("label", { for: autolevelCheckboxId });
checkboxDiv.appendChild(checkboxInput);
checkboxDiv.appendChild(checkmarkLabel);
el.appendChild(checkboxDiv);
} }
Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) { Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
@ -2760,7 +2786,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
})); }));
el.appendChild(createElement("p", { el.appendChild(createElement("p", {
display:"inline-block", display:"inline-block",
innerHTML:"Estimated Success Chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" + innerHTML:`Estimated Success Chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
"Time Required(s): " + formatNumber(actionTime, 0), "Time Required(s): " + formatNumber(actionTime, 0),
})) }))
} }

@ -1,7 +1,12 @@
/**
* Generic Game Constants
*
* Constants for specific mechanics or features will NOT be here.
*/
import {IMap} from "./types"; import {IMap} from "./types";
export let CONSTANTS: IMap<any> = { export let CONSTANTS: IMap<any> = {
Version: "0.44.1", Version: "0.45.1",
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience //Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then //and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -17,24 +22,9 @@ export let CONSTANTS: IMap<any> = {
/* Base costs */ /* Base costs */
BaseCostFor1GBOfRamHome: 32000, BaseCostFor1GBOfRamHome: 32000,
BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM
BaseCostFor1GBOfRamHacknetNode: 30000,
TravelCost: 200e3, TravelCost: 200e3,
BaseCostForHacknetNode: 1000,
BaseCostForHacknetNodeCore: 500000,
/* Hacknet Node constants */
HacknetNodeMoneyGainPerLevel: 1.6,
HacknetNodePurchaseNextMult: 1.85, //Multiplier when purchasing an additional hacknet node
HacknetNodeUpgradeLevelMult: 1.04, //Multiplier for cost when upgrading level
HacknetNodeUpgradeRamMult: 1.28, //Multiplier for cost when upgrading RAM
HacknetNodeUpgradeCoreMult: 1.48, //Multiplier for cost when buying another core
HacknetNodeMaxLevel: 200,
HacknetNodeMaxRam: 64,
HacknetNodeMaxCores: 16,
/* Faction and Company favor */ /* Faction and Company favor */
BaseFavorToDonate: 150, BaseFavorToDonate: 150,
DonateMoneyToRepDivisor: 1e6, DonateMoneyToRepDivisor: 1e6,
@ -85,11 +75,12 @@ export let CONSTANTS: IMap<any> = {
ScriptGetPurchasedServerMaxRam: 0.05, ScriptGetPurchasedServerMaxRam: 0.05,
ScriptRoundRamCost: 0.05, ScriptRoundRamCost: 0.05,
ScriptReadWriteRamCost: 1.0, ScriptReadWriteRamCost: 1.0,
ScriptArbScriptRamCost: 1.0, //Functions that apply to all scripts regardless of args ScriptArbScriptRamCost: 1.0, // Functions that apply to all scripts regardless of args
ScriptGetScriptRamCost: 0.1, ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05, ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10, ScriptGetFavorToDonate: 0.10,
ScriptCodingContractBaseRamCost:10, ScriptCodingContractBaseRamCost:10,
ScriptSleeveBaseRamCost: 4,
ScriptSingularityFn1RamCost: 1, ScriptSingularityFn1RamCost: 1,
ScriptSingularityFn2RamCost: 2, ScriptSingularityFn2RamCost: 2,
@ -125,6 +116,7 @@ export let CONSTANTS: IMap<any> = {
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
InfiltrationExpPow: 0.8,
//Stock market constants //Stock market constants
WSEAccountCost: 200e6, WSEAccountCost: 200e6,
@ -281,17 +273,20 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.45.0 v0.46.0
* Corporation changes: * Added BitNode-9: Hacktocracy
** Decreased the time of a full market cycle from 15 seconds to 10 seconds. * Changed BitNode-11's multipliers to make it slightly harder overall
** This means that each Corporation 'state' will now only take 2 seconds, rather than 3 * Source-File 11 is now slightly stronger
** Increased initial salaries for newly-hired employees * Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame)
** Increased the cost multiplier for upgrading office size (the cost will increase faster) * Added a new stat for Duplicate Sleeves: Memory
** The stats of your employees now has a slightly larger effect on production & sales * Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels
** Added several new Research upgrades * In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina
** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k
** Energy Material requirement of the Software industry reduced from 1 to 0.5
** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research.
`
* Corporation Changes:
** 'Demand' value of products decreases more slowly
** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
* Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
* Bug Fix: Fixed an issue with the job requirement tooltip for security jobs
`
} }

@ -19,11 +19,14 @@ import { CONSTANTS } from "../Constants";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { showLiterature } from "../Literature"; import { showLiterature } from "../Literature";
import { Locations } from "../Locations"; import { Locations } from "../Locations";
import { createCityMap } from "../Locations/Cities";
import { Player } from "../Player"; import { Player } from "../Player";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { Page, routing } from "../ui/navigationTracking"; import { Page, routing } from "../ui/navigationTracking";
import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
import { clearSelector } from "../../utils/uiHelpers/clearSelector"; import { clearSelector } from "../../utils/uiHelpers/clearSelector";
import { Reviver, import { Reviver,
@ -37,7 +40,6 @@ import { formatNumber, generateRandomString } from "../../utils/String
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { isString } from "../../utils/helpers/isString"; import { isString } from "../../utils/helpers/isString";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../../utils/uiHelpers/removeElement"; import { removeElement } from "../../utils/uiHelpers/removeElement";
import { removeElementById } from "../../utils/uiHelpers/removeElementById"; import { removeElementById } from "../../utils/uiHelpers/removeElementById";
import { yesNoBoxCreate, import { yesNoBoxCreate,
@ -48,8 +50,7 @@ import { yesNoBoxCreate,
yesNoTxtInpBoxGetNoButton, yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxGetInput, yesNoTxtInpBoxGetInput,
yesNoBoxClose, yesNoBoxClose,
yesNoTxtInpBoxClose, yesNoTxtInpBoxClose } from "../../utils/YesNoBox";
yesNoBoxOpen } from "../../utils/YesNoBox";
// UI Related Imports // UI Related Imports
import React from "react"; import React from "react";
@ -60,7 +61,6 @@ import { CorporationRouting } from "./ui/Routing";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
/* Constants */ /* Constants */
export const INITIALSHARES = 1e9; //Total number of shares you have at your company export const INITIALSHARES = 1e9; //Total number of shares you have at your company
export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount
@ -124,18 +124,6 @@ function Industry(params={}) {
[Locations.Volhaven]: 0 [Locations.Volhaven]: 0
}; };
this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
[Locations.Aevum]: 0,
[Locations.Chonqing]: 0,
[Locations.Sector12]: new Warehouse({
loc:Locations.Sector12,
size: WarehouseInitialSize,
}),
[Locations.NewTokyo]: 0,
[Locations.Ishima]: 0,
[Locations.Volhaven]: 0
};
this.name = params.name ? params.name : 0; this.name = params.name ? params.name : 0;
this.type = params.type ? params.type : 0; this.type = params.type ? params.type : 0;
@ -183,6 +171,20 @@ function Industry(params={}) {
this.state = "START"; this.state = "START";
this.newInd = true; this.newInd = true;
this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
[Locations.Aevum]: 0,
[Locations.Chonqing]: 0,
[Locations.Sector12]: new Warehouse({
corp: params.corp,
industry: this,
loc: Locations.Sector12,
size: WarehouseInitialSize,
}),
[Locations.NewTokyo]: 0,
[Locations.Ishima]: 0,
[Locations.Volhaven]: 0
};
this.init(); this.init();
} }
@ -340,8 +342,8 @@ Industry.prototype.init = function() {
this.sciFac = 0.62; this.sciFac = 0.62;
this.advFac = 0.16; this.advFac = 0.16;
this.hwFac = 0.25; this.hwFac = 0.25;
this.reFac = 0.1; this.reFac = 0.15;
this.aiFac = 0.15; this.aiFac = 0.18;
this.robFac = 0.05; this.robFac = 0.05;
this.reqMats = { this.reqMats = {
"Hardware": 0.5, "Hardware": 0.5,
@ -487,11 +489,11 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
this.thisCycleRevenue = new Decimal(0); this.thisCycleRevenue = new Decimal(0);
this.thisCycleExpenses = new Decimal(0); this.thisCycleExpenses = new Decimal(0);
//Once you start making revenue, the player should no longer be // Once you start making revenue, the player should no longer be
//considered new, and therefore no longer needs the 'tutorial' UI elements // considered new, and therefore no longer needs the 'tutorial' UI elements
if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} if (this.lastCycleRevenue.gt(0)) {this.newInd = false;}
//Process offices (and the employees in them) // Process offices (and the employees in them)
var employeeSalary = 0; var employeeSalary = 0;
for (var officeLoc in this.offices) { for (var officeLoc in this.offices) {
if (this.offices[officeLoc] instanceof OfficeSpace) { if (this.offices[officeLoc] instanceof OfficeSpace) {
@ -500,15 +502,15 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
} }
this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary);
//Process change in demand/competition of materials/products // Process change in demand/competition of materials/products
this.processMaterialMarket(marketCycles); this.processMaterialMarket(marketCycles);
this.processProductMarket(marketCycles); this.processProductMarket(marketCycles);
//Process loss of popularity // Process loss of popularity
this.popularity -= (marketCycles * .0001); this.popularity -= (marketCycles * .0001);
this.popularity = Math.max(0, this.popularity); this.popularity = Math.max(0, this.popularity);
//Process Dreamsense gains // Process Dreamsense gains
var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4;
if (popularityGain > 0) { if (popularityGain > 0) {
this.popularity += (popularityGain * marketCycles); this.popularity += (popularityGain * marketCycles);
@ -518,19 +520,19 @@ Industry.prototype.process = function(marketCycles=1, state, company) {
return; return;
} }
//Process production, purchase, and import/export of materials // Process production, purchase, and import/export of materials
var res = this.processMaterials(marketCycles, company); var res = this.processMaterials(marketCycles, company);
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
//Process creation, production & sale of products // Process creation, production & sale of products
res = this.processProducts(marketCycles, company); res = this.processProducts(marketCycles, company);
this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
} }
//Process change in demand and competition for this industry's materials // Process change in demand and competition for this industry's materials
Industry.prototype.processMaterialMarket = function(marketCycles=1) { Industry.prototype.processMaterialMarket = function(marketCycles=1) {
//References to prodMats and reqMats //References to prodMats and reqMats
var reqMats = this.reqMats, prodMats = this.prodMats; var reqMats = this.reqMats, prodMats = this.prodMats;
@ -561,13 +563,15 @@ Industry.prototype.processMaterialMarket = function(marketCycles=1) {
} }
} }
//Process change in demand and competition for this industry's products // Process change in demand and competition for this industry's products
Industry.prototype.processProductMarket = function(marketCycles=1) { Industry.prototype.processProductMarket = function(marketCycles=1) {
//Demand gradually decreases, and competition gradually increases // Demand gradually decreases, and competition gradually increases
for (var name in this.products) { for (const name in this.products) {
if (this.products.hasOwnProperty(name)) { if (this.products.hasOwnProperty(name)) {
var product = this.products[name]; const product = this.products[name];
var change = getRandomInt(1, 3) * 0.0004; let change = getRandomInt(0, 3) * 0.0004;
if (change === 0) { continue; }
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
this.type === Industries.Robotics) { this.type === Industries.Robotics) {
change *= 3; change *= 3;
@ -588,7 +592,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
//At the start of the export state, set the imports of everything to 0 //At the start of the export state, set the imports of everything to 0
if (this.state === "EXPORT") { if (this.state === "EXPORT") {
for (var i = 0; i < Cities.length; ++i) { for (let i = 0; i < Cities.length; ++i) {
var city = Cities[i], office = this.offices[city]; var city = Cities[i], office = this.offices[city];
if (!(this.warehouses[city] instanceof Warehouse)) { if (!(this.warehouses[city] instanceof Warehouse)) {
continue; continue;
@ -603,7 +607,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
} }
} }
for (var i = 0; i < Cities.length; ++i) { for (let i = 0; i < Cities.length; ++i) {
var city = Cities[i], office = this.offices[city]; var city = Cities[i], office = this.offices[city];
if (this.warehouses[city] instanceof Warehouse) { if (this.warehouses[city] instanceof Warehouse) {
@ -663,19 +667,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
prod = maxProd; prod = maxProd;
} }
prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle
//Calculate net change in warehouse storage making
//the produced materials will cost // Calculate net change in warehouse storage making the produced materials will cost
var totalMatSize = 0; var totalMatSize = 0;
for (var tmp = 0; tmp < this.prodMats.length; ++tmp) { for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
totalMatSize += (MaterialSizes[this.prodMats[tmp]]); totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
} }
for (var reqMatName in this.reqMats) { for (const reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) { var normQty = this.reqMats[reqMatName];
var normQty = this.reqMats[reqMatName]; totalMatSize -= (MaterialSizes[reqMatName] * normQty);
totalMatSize -= (MaterialSizes[reqMatName] * normQty);
}
} }
//If not enough space in warehouse, limit the amount of produced materials // If not enough space in warehouse, limit the amount of produced materials
if (totalMatSize > 0) { if (totalMatSize > 0) {
var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
prod = Math.min(maxAmt, prod); prod = Math.min(maxAmt, prod);
@ -683,10 +685,10 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
if (prod < 0) {prod = 0;} if (prod < 0) {prod = 0;}
//Keep track of production for smart supply (/s) // Keep track of production for smart supply (/s)
warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles));
//Make sure we have enough resource to make our materials // Make sure we have enough resource to make our materials
var producableFrac = 1; var producableFrac = 1;
for (var reqMatName in this.reqMats) { for (var reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) { if (this.reqMats.hasOwnProperty(reqMatName)) {
@ -698,25 +700,23 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
} }
if (producableFrac <= 0) {producableFrac = 0; prod = 0;} if (producableFrac <= 0) {producableFrac = 0; prod = 0;}
//Make our materials if they are producable // Make our materials if they are producable
if (producableFrac > 0 && prod > 0) { if (producableFrac > 0 && prod > 0) {
for (var reqMatName in this.reqMats) { for (const reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) { var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; warehouse.materials[reqMatName].prd = 0;
warehouse.materials[reqMatName].prd = 0; warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
}
} }
for (var j = 0; j < this.prodMats.length; ++j) { for (let j = 0; j < this.prodMats.length; ++j) {
warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac);
warehouse.materials[this.prodMats[j]].qlt = warehouse.materials[this.prodMats[j]].qlt =
(office.employeeProd[EmployeePositions.Engineer] / 100 + (office.employeeProd[EmployeePositions.Engineer] / 90 +
Math.pow(this.sciResearch.qty, this.sciFac) + Math.pow(this.sciResearch.qty, this.sciFac) +
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
} }
} else { } else {
for (var reqMatName in this.reqMats) { for (const reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) { if (this.reqMats.hasOwnProperty(reqMatName)) {
warehouse.materials[reqMatName].prd = 0; warehouse.materials[reqMatName].prd = 0;
} }
@ -724,18 +724,16 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
} }
//Per second //Per second
var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); const fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
for (var fooI = 0; fooI < this.prodMats.length; ++fooI) { for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {
warehouse.materials[this.prodMats[fooI]].prd = fooProd; warehouse.materials[this.prodMats[fooI]].prd = fooProd;
} }
} else { } else {
//If this doesn't produce any materials, then it only creates //If this doesn't produce any materials, then it only creates
//Products. Creating products will consume materials. The //Products. Creating products will consume materials. The
//Production of all consumed materials must be set to 0 //Production of all consumed materials must be set to 0
for (var reqMatName in this.reqMats) { for (const reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) { warehouse.materials[reqMatName].prd = 0;
warehouse.materials[reqMatName].prd = 0;
}
} }
} }
break; break;
@ -749,12 +747,49 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
mat.sll = 0; mat.sll = 0;
continue; continue;
} }
var mat = warehouse.materials[matName];
// Calculate sale cost // Sale multipliers
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
const marketFactor = this.getMarketFactor(mat); //Competition + demand
// Determine the cost that the material will be sold at
const markupLimit = mat.getMarkupLimit(); const markupLimit = mat.getMarkupLimit();
var sCost; var sCost;
if (mat.marketTa1) { if (mat.marketTa2) {
const prod = mat.prd;
// Reverse engineer the 'maxSell' formula
// 1. Set 'maxSell' = prod
// 2. Substitute formula for 'markup'
// 3. Solve for 'sCost'
const numerator = markupLimit;
const sqrtNumerator = prod;
const sqrtDenominator = ((mat.qlt + .001)
* marketFactor
* businessFactor
* company.getSalesMultiplier()
* advertisingFactor
* this.getSalesMultiplier());
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
let optimalPrice;
if (sqrtDenominator === 0 || denominator === 0) {
if (sqrtNumerator === 0) {
optimalPrice = 0; // No production
} else {
optimalPrice = mat.bCost + markupLimit;
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
}
} else {
optimalPrice = (numerator / denominator) + mat.bCost;
}
// We'll store this "Optimal Price" in a property so that we don't have
// to re-calculate it for the UI
mat.marketTa2Price = optimalPrice;
sCost = optimalPrice;
} else if (mat.marketTa1) {
sCost = mat.bCost + markupLimit; sCost = mat.bCost + markupLimit;
} else if (isString(mat.sCost)) { } else if (isString(mat.sCost)) {
sCost = mat.sCost.replace(/MP/g, mat.bCost); sCost = mat.sCost.replace(/MP/g, mat.bCost);
@ -778,9 +813,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
markup = mat.bCost / sCost; markup = mat.bCost / sCost;
} }
} }
var businessFactor = this.getBusinessFactor(office); //Business employee productivity
var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
var marketFactor = this.getMarketFactor(mat); //Competition + demand
var maxSell = (mat.qlt + .001) var maxSell = (mat.qlt + .001)
* marketFactor * marketFactor
* markup * markup
@ -903,8 +936,8 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
//Produce Scientific Research based on R&D employees //Produce Scientific Research based on R&D employees
//Scientific Research can be produced without a warehouse //Scientific Research can be produced without a warehouse
if (office instanceof OfficeSpace) { if (office instanceof OfficeSpace) {
this.sciResearch.qty += (.005 this.sciResearch.qty += (.004
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55) * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5)
* company.getScientificResearchMultiplier() * company.getScientificResearchMultiplier()
* this.getScientificResearchMultiplier()); * this.getScientificResearchMultiplier());
} }
@ -918,27 +951,30 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
//Create products //Create products
if (this.state === "PRODUCTION") { if (this.state === "PRODUCTION") {
for (var prodName in this.products) { for (const prodName in this.products) {
if (this.products.hasOwnProperty(prodName)) { const prod = this.products[prodName];
var prod = this.products[prodName]; if (!prod.fin) {
if (!prod.fin) { const city = prod.createCity;
var city = prod.createCity, office = this.offices[city]; const office = this.offices[city];
var total = office.employeeProd[EmployeePositions.Operations] +
office.employeeProd[EmployeePositions.Engineer] + // Designing/Creating a Product is based mostly off Engineers
office.employeeProd[EmployeePositions.Management], ratio; const engrProd = office.employeeProd[EmployeePositions.Engineer];
if (total === 0) { const mgmtProd = office.employeeProd[EmployeePositions.Management];
ratio = 0; const opProd = office.employeeProd[EmployeePositions.Operations];
} else { const total = engrProd + mgmtProd + opProd;
ratio = office.employeeProd[EmployeePositions.Engineer] / total +
office.employeeProd[EmployeePositions.Operations] / total + if (total <= 0) { break; }
office.employeeProd[EmployeePositions.Management] / total;
} // Management is a multiplier for the production from Engineers
prod.createProduct(marketCycles, ratio * Math.pow(total, 0.35)); const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
if (prod.prog >= 100) {
prod.finishProduct(office.employeeProd, this); const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;
}
break; prod.createProduct(marketCycles, progress);
if (prod.prog >= 100) {
prod.finishProduct(office.employeeProd, this);
} }
break;
} }
} }
} }
@ -957,9 +993,9 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
//Processes FINISHED products //Processes FINISHED products
Industry.prototype.processProduct = function(marketCycles=1, product, corporation) { Industry.prototype.processProduct = function(marketCycles=1, product, corporation) {
var totalProfit = 0; let totalProfit = 0;
for (var i = 0; i < Cities.length; ++i) { for (let i = 0; i < Cities.length; ++i) {
var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; let city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
if (warehouse instanceof Warehouse) { if (warehouse instanceof Warehouse) {
switch(this.state) { switch(this.state) {
@ -1035,27 +1071,65 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
} }
} }
//Since its a product, its production cost is increased for labor // Since its a product, its production cost is increased for labor
product.pCost *= ProductProductionCostRatio; product.pCost *= ProductProductionCostRatio;
//Calculate Sale Cost (sCost), which could be dynamically evaluated // Sale multipliers
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
const marketFactor = this.getMarketFactor(product); //Competition + demand
// Calculate Sale Cost (sCost), which could be dynamically evaluated
const markupLimit = product.rat / product.mku;
var sCost; var sCost;
if (isString(product.sCost)) { if (product.marketTa2) {
const prod = product.data[city][1];
// Reverse engineer the 'maxSell' formula
// 1. Set 'maxSell' = prod
// 2. Substitute formula for 'markup'
// 3. Solve for 'sCost'roduct.pCost = sCost
const numerator = markupLimit;
const sqrtNumerator = prod;
const sqrtDenominator = (0.5
* Math.pow(product.rat, 0.65)
* marketFactor
* corporation.getSalesMultiplier()
* businessFactor
* advertisingFactor
* this.getSalesMultiplier());
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
let optimalPrice;
if (sqrtDenominator === 0 || denominator === 0) {
if (sqrtNumerator === 0) {
optimalPrice = 0; // No production
} else {
optimalPrice = product.pCost + markupLimit;
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
}
} else {
optimalPrice = (numerator / denominator) + product.pCost;
}
// Store this "optimal Price" in a property so we don't have to re-calculate for UI
product.marketTa2Price[city] = optimalPrice;
sCost = optimalPrice;
} else if (product.marketTa1) {
sCost = product.pCost + markupLimit;
} else if (isString(product.sCost)) {
sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku); sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku);
sCost = eval(sCost); sCost = eval(sCost);
} else { } else {
sCost = product.sCost; sCost = product.sCost;
} }
var markup = 1, markupLimit = product.rat / product.mku; var markup = 1;
if (sCost > product.pCost) { if (sCost > product.pCost) {
if ((sCost - product.pCost) > markupLimit) { if ((sCost - product.pCost) > markupLimit) {
markup = markupLimit / (sCost - product.pCost); markup = markupLimit / (sCost - product.pCost);
} }
} }
var businessFactor = this.getBusinessFactor(office); //Business employee productivity
var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
var marketFactor = this.getMarketFactor(product); //Competition + demand
var maxSell = 0.5 var maxSell = 0.5
* Math.pow(product.rat, 0.65) * Math.pow(product.rat, 0.65)
* marketFactor * marketFactor
@ -1080,8 +1154,9 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
} else if (product.sllman[city][0] && product.sllman[city][1] > 0) { } else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
//Sell amount is manually limited //Sell amount is manually limited
sellAmt = Math.min(maxSell, product.sllman[city][1]); sellAmt = Math.min(maxSell, product.sllman[city][1]);
} else if (product.sllman[city][0] === false){
sellAmt = 0;
} else { } else {
//Backwards compatibility, -1 = 0
sellAmt = maxSell; sellAmt = maxSell;
} }
if (sellAmt < 0) { sellAmt = 0; } if (sellAmt < 0) { sellAmt = 0; }
@ -1109,8 +1184,7 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
return totalProfit; return totalProfit;
} }
Industry.prototype.discontinueProduct = function(product, parentRefs) { Industry.prototype.discontinueProduct = function(product) {
var company = parentRefs.company, industry = parentRefs.industry;
for (var productName in this.products) { for (var productName in this.products) {
if (this.products.hasOwnProperty(productName)) { if (this.products.hasOwnProperty(productName)) {
if (product === this.products[productName]) { if (product === this.products[productName]) {
@ -1147,33 +1221,38 @@ Industry.prototype.upgrade = function(upgrade, refs) {
} }
} }
//Returns how much of a material can be produced based of office productivity (employee stats) // Returns how much of a material can be produced based of office productivity (employee stats)
Industry.prototype.getOfficeProductivity = function(office, params) { Industry.prototype.getOfficeProductivity = function(office, params) {
var total = office.employeeProd[EmployeePositions.Operations] + const opProd = office.employeeProd[EmployeePositions.Operations];
office.employeeProd[EmployeePositions.Engineer] + const engrProd = office.employeeProd[EmployeePositions.Engineer];
office.employeeProd[EmployeePositions.Management], ratio; const mgmtProd = office.employeeProd[EmployeePositions.Management]
if (total === 0) { const total = opProd + engrProd + mgmtProd;
ratio = 0;
} else { if (total <= 0) { return 0; }
ratio = (office.employeeProd[EmployeePositions.Operations] / total) *
(office.employeeProd[EmployeePositions.Engineer] / total) * // Management is a multiplier for the production from Operations and Engineers
(office.employeeProd[EmployeePositions.Management] / total); const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees
} // For production, Operations is slightly more important than engineering
// Both Engineering and Operations have diminishing returns
const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor;
// Generic multiplier for the production. Used for game-balancing purposes
const balancingMult = 0.05;
if (params && params.forProduct) { if (params && params.forProduct) {
return ratio * Math.pow(total, 0.25); // Products are harder to create and therefore have less production
return 0.5 * balancingMult * prod;
} else { } else {
return 2 * ratio * Math.pow(total, 0.35); return balancingMult * prod;
} }
} }
//Returns a multiplier based on the office' 'Business' employees that affects sales // Returns a multiplier based on the office' 'Business' employees that affects sales
Industry.prototype.getBusinessFactor = function(office) { Industry.prototype.getBusinessFactor = function(office) {
var ratioMult = 1; const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
if (office.employeeProd["total"] > 0) {
ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); return calculateEffectWithFactors(businessProd, 0.26, 10e3);
}
return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.25);
} }
//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This //Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This
@ -1189,7 +1268,7 @@ Industry.prototype.getAdvertisingFactors = function() {
//Returns a multiplier based on a materials demand and competition that affects sales //Returns a multiplier based on a materials demand and competition that affects sales
Industry.prototype.getMarketFactor = function(mat) { Industry.prototype.getMarketFactor = function(mat) {
return mat.dmd * (100 - mat.cmp)/100; return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100);
} }
// Returns a boolean indicating whether this Industry has the specified Research // Returns a boolean indicating whether this Industry has the specified Research
@ -1271,10 +1350,8 @@ Industry.prototype.createResearchBox = function() {
researchTreeBox = null; researchTreeBox = null;
} }
this.updateResearchTree();
const researchTree = IndustryResearchTrees[this.type]; const researchTree = IndustryResearchTrees[this.type];
// Create the popup first, so that the tree diagram can be added to it // Create the popup first, so that the tree diagram can be added to it
// This is handled by Treant // This is handled by Treant
researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" }); researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" });
@ -1324,6 +1401,10 @@ Industry.prototype.createResearchBox = function() {
researchTree.research(allResearch[i]); researchTree.research(allResearch[i]);
this.researched[allResearch[i]] = true; this.researched[allResearch[i]] = true;
dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
`(~${SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`);
return this.createResearchBox(); return this.createResearchBox();
} else { } else {
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`); dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
@ -1399,7 +1480,7 @@ function Employee(params={}) {
//Returns the amount the employee needs to be paid //Returns the amount the employee needs to be paid
Employee.prototype.process = function(marketCycles=1, office) { Employee.prototype.process = function(marketCycles=1, office) {
var gain = 0.001 * marketCycles, var gain = 0.003 * marketCycles,
det = gain * Math.random(); det = gain * Math.random();
this.age += gain; this.age += gain;
this.exp += gain; this.exp += gain;
@ -1425,16 +1506,9 @@ Employee.prototype.process = function(marketCycles=1, office) {
this.eff += trainingEff; this.eff += trainingEff;
} }
//Weight based on how full office is this.ene -= det;
//Too many employees = more likely to decrease energy and happiness this.hap -= det;
var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5);
if (Math.random() < 0.5 - officeCapacityWeight) {
this.ene += det;
this.hap += det;
} else {
this.ene -= det;
this.hap -= det;
}
if (this.ene < office.minEne) {this.ene = office.minEne;} if (this.ene < office.minEne) {this.ene = office.minEne;}
if (this.hap < office.minHap) {this.hap = office.minHap;} if (this.hap < office.minHap) {this.hap = office.minHap;}
var salary = this.sal * marketCycles * SecsPerMarketCycle; var salary = this.sal * marketCycles * SecsPerMarketCycle;
@ -1591,6 +1665,14 @@ OfficeSpace.prototype.atCapacity = function() {
OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
var corporation = parentRefs.corporation, industry = parentRefs.industry; var corporation = parentRefs.corporation, industry = parentRefs.industry;
// HRBuddy AutoRecruitment and training
if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
const emp = this.hireRandomEmployee();
if (industry.hasResearch("HRBuddy-Training")) {
emp.pos = EmployeePositions.Training;
}
}
// Process Office properties // Process Office properties
this.maxEne = 100; this.maxEne = 100;
this.maxHap = 100; this.maxHap = 100;
@ -1640,18 +1722,16 @@ OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
salaryPaid += salary; salaryPaid += salary;
} }
this.calculateEmployeeProductivity(marketCycles, parentRefs); this.calculateEmployeeProductivity(parentRefs);
return salaryPaid; return salaryPaid;
} }
OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) { OfficeSpace.prototype.calculateEmployeeProductivity = function(parentRefs) {
var company = parentRefs.corporation, industry = parentRefs.industry; var company = parentRefs.corporation, industry = parentRefs.industry;
//Reset //Reset
for (const name in this.employeeProd) { for (const name in this.employeeProd) {
if (this.employeeProd.hasOwnProperty(name)) { this.employeeProd[name] = 0;
this.employeeProd[name] = 0;
}
} }
var total = 0; var total = 0;
@ -1774,8 +1854,7 @@ OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) {
yesNoTxtInpBoxCreate("Give your employee a nickname!"); yesNoTxtInpBoxCreate("Give your employee a nickname!");
} }
OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { OfficeSpace.prototype.hireRandomEmployee = function() {
var company = parentRefs.corporation, division = parentRefs.industry;
if (this.atCapacity()) { return; } if (this.atCapacity()) { return; }
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
@ -1799,13 +1878,15 @@ OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) {
var name = generateRandomString(7); var name = generateRandomString(7);
for (var i = 0; i < this.employees.length; ++i) { for (let i = 0; i < this.employees.length; ++i) {
if (this.employees[i].name === name) { if (this.employees[i].name === name) {
return this.hireRandomEmployee(parentRefs); return this.hireRandomEmployee();
} }
} }
emp.name = name; emp.name = name;
this.employees.push(emp); this.employees.push(emp);
return emp;
} }
//Finds the first unassigned employee and assigns its to the specified job //Finds the first unassigned employee and assigns its to the specified job
@ -1977,19 +2058,19 @@ Corporation.prototype.getInvestment = function() {
switch (this.fundingRound) { switch (this.fundingRound) {
case 0: //Seed case 0: //Seed
percShares = 0.10; percShares = 0.10;
roundMultiplier = 5; roundMultiplier = 4;
break; break;
case 1: //Series A case 1: //Series A
percShares = 0.35; percShares = 0.35;
roundMultiplier = 4; roundMultiplier = 3;
break; break;
case 2: //Series B case 2: //Series B
percShares = 0.25; percShares = 0.25;
roundMultiplier = 4; roundMultiplier = 3;
break; break;
case 3: //Series C case 3: //Series C
percShares = 0.20; percShares = 0.20;
roundMultiplier = 3.5; roundMultiplier = 2.5;
break; break;
case 4: case 4:
return; return;
@ -2292,8 +2373,6 @@ Corporation.prototype.rerender = function() {
} }
if (!routing.isOn(Page.Corporation)) { return; } if (!routing.isOn(Page.Corporation)) { return; }
console.log("Re-rendering...");
ReactDOM.render(<CorporationRoot ReactDOM.render(<CorporationRoot
corp={this} corp={this}
routing={corpRouting} routing={corpRouting}

@ -66,6 +66,7 @@ export class Material {
// Flags that signal whether automatic sale pricing through Market TA is enabled // Flags that signal whether automatic sale pricing through Market TA is enabled
marketTa1: boolean = false; marketTa1: boolean = false;
marketTa2: boolean = false; marketTa2: boolean = false;
marketTa2Price: number = 0;
constructor(params: IConstructorParams = {}) { constructor(params: IConstructorParams = {}) {
if (params.name) { this.name = params.name; } if (params.name) { this.name = params.name; }
@ -85,7 +86,7 @@ export class Material {
this.mku = 6; this.mku = 6;
break; break;
case "Energy": case "Energy":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 80; this.cmpR = [65, 95]; this.cmp = 80; this.cmpR = [65, 95];
this.bCost = 2000; this.mv = 0.2; this.bCost = 2000; this.mv = 0.2;
this.mku = 6; this.mku = 6;
@ -121,26 +122,26 @@ export class Material {
this.mku = 2; this.mku = 2;
break; break;
case "Real Estate": case "Real Estate":
this.dmd = 50; this.dmdR = [5, 100]; this.dmd = 50; this.dmdR = [5, 99];
this.cmp = 50; this.cmpR = [25, 75]; this.cmp = 50; this.cmpR = [25, 75];
this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
this.mku = 1.5; this.mku = 1.5;
break; break;
case "Drugs": case "Drugs":
this.dmd = 60; this.dmdR = [45, 75]; this.dmd = 60; this.dmdR = [45, 75];
this.cmp = 70; this.cmpR = [40, 100]; this.cmp = 70; this.cmpR = [40, 99];
this.bCost = 40e3; this.mv = 1.6; this.bCost = 40e3; this.mv = 1.6;
this.mku = 1; this.mku = 1;
break; break;
case "Robots": case "Robots":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 9];
this.cmp = 90; this.cmpR = [80, 100]; this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
this.mku = 1; this.mku = 1;
break; break;
case "AI Cores": case "AI Cores":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 90; this.cmpR = [80, 100]; this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
this.mku = 0.5; this.mku = 0.5;
break; break;

@ -2,15 +2,17 @@ import { IMap } from "../types";
// Map of material (by name) to their sizes (how much space it takes in warehouse) // Map of material (by name) to their sizes (how much space it takes in warehouse)
export const MaterialSizes: IMap<number> = { export const MaterialSizes: IMap<number> = {
Water: 0.05, Water: 0.05,
Energy: 0.01, Energy: 0.01,
Food: 0.03, Food: 0.03,
Plants: 0.05, Plants: 0.05,
Metal: 0.1, Metal: 0.1,
Hardware: 0.06, Hardware: 0.06,
Chemicals: 0.05, Chemicals: 0.05,
Drugs: 0.02, Drugs: 0.02,
Robots: 0.5, Robots: 0.5,
AICores: 0.1, AICores: 0.1,
RealEstate: 0, RealEstate: 0,
"Real Estate": 0,
"AI Cores": 0,
} }

@ -3,7 +3,7 @@ import { MaterialSizes } from "./MaterialSizes";
import { ProductRatingWeights, import { ProductRatingWeights,
IProductRatingWeight } from "./ProductRatingWeights"; IProductRatingWeight } from "./ProductRatingWeights";
import { CityName } from "../Locations/data/CityNames"; import { createCityMap } from "../Locations/createCityMap";
import { IMap } from "../types"; import { IMap } from "../types";
import { Generic_fromJSON, import { Generic_fromJSON,
@ -89,14 +89,7 @@ export class Product {
// Data refers to the production, sale, and quantity of the products // Data refers to the production, sale, and quantity of the products
// These values are specific to a city // These values are specific to a city
// For each city, the data is [qty, prod, sell] // For each city, the data is [qty, prod, sell]
data: IMap<number[]> = { data: IMap<number[]> = createCityMap<number[]>([0, 0, 0]);
[Cities.Aevum]: [0, 0, 0],
[Cities.Chongqing]: [0, 0, 0],
[Cities.Sector12]: [0, 0, 0],
[Cities.NewTokyo]: [0, 0, 0],
[Cities.Ishima]: [0, 0, 0],
[Cities.Volhaven]: [0, 0, 0],
}
// Location of this Product // Location of this Product
// Only applies for location-based products like restaurants/hospitals // Only applies for location-based products like restaurants/hospitals
@ -113,23 +106,13 @@ export class Product {
// Data to keep track of whether production/sale of this Product is // Data to keep track of whether production/sale of this Product is
// manually limited. These values are specific to a city // manually limited. These values are specific to a city
// [Whether production/sale is limited, limit amount] // [Whether production/sale is limited, limit amount]
prdman: IMap<any[]> = { prdman: IMap<any[]> = createCityMap<any[]>([false, 0]);
[Cities.Aevum]: [false, 0], sllman: IMap<any[]> = createCityMap<any[]>([false, 0]);
[Cities.Chongqing]: [false, 0],
[Cities.Sector12]: [false, 0],
[Cities.NewTokyo]: [false, 0],
[Cities.Ishima]: [false, 0],
[Cities.Volhaven]: [false, 0],
}
sllman: IMap<any[]> = { // Flags that signal whether automatic sale pricing through Market TA is enabled
[Cities.Aevum]: [false, 0], marketTa1: boolean = false;
[Cities.Chongqing]: [false, 0], marketTa2: boolean = false;
[Cities.Sector12]: [false, 0], marketTa2Price: IMap<number> = createCityMap<number>(0);
[Cities.NewTokyo]: [false, 0],
[Cities.Ishima]: [false, 0],
[Cities.Volhaven]: [false, 0],
}
constructor(params: IConstructorParams={}) { constructor(params: IConstructorParams={}) {
this.name = params.name ? params.name : ""; this.name = params.name ? params.name : "";
@ -164,11 +147,11 @@ export class Product {
//Calculate properties //Calculate properties
var progrMult = this.prog / 100; var progrMult = this.prog / 100;
var engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"], const engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"];
mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"], const mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"];
rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"], const rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"];
opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"], const opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"];
busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"]; const busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"];
var designMult = 1 + (Math.pow(this.designCost, 0.1) / 100); var designMult = 1 + (Math.pow(this.designCost, 0.1) / 100);
console.log("designMult: " + designMult); console.log("designMult: " + designMult);
var balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) + var balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) +

@ -8,6 +8,8 @@ import { ResearchMap } from "./ResearchMap";
import { IMap } from "../types"; import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat";
interface IConstructorParams { interface IConstructorParams {
children?: Node[]; children?: Node[];
cost: number; cost: number;
@ -83,7 +85,7 @@ export class Node {
children: childrenArray, children: childrenArray,
HTMLclass: htmlClass, HTMLclass: htmlClass,
innerHTML: `<div id="${sanitizedName}-corp-research-click-listener" class="tooltip">` + innerHTML: `<div id="${sanitizedName}-corp-research-click-listener" class="tooltip">` +
`${this.text}<br>${this.cost} Scientific Research` + `${this.text}<br>${numeralWrapper.format(this.cost, "0,0")} Scientific Research` +
`<span class="tooltiptext">` + `<span class="tooltiptext">` +
`${research.desc}` + `${research.desc}` +
`</span>` + `</span>` +

@ -5,16 +5,19 @@ import { numeralWrapper } from "../ui/numeralFormat";
import { Generic_fromJSON, import { Generic_fromJSON,
Generic_toJSON, Generic_toJSON,
Reviver } from "../../utils/JSONReviver"; Reviver } from "../../utils/JSONReviver";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
interface IConstructorParams {
loc?: string;
size?: number;
}
interface IParent { interface IParent {
getStorageMultiplier(): number; getStorageMultiplier(): number;
} }
interface IConstructorParams {
corp?: IParent;
industry?: IParent;
loc?: string;
size?: number;
}
export class Warehouse { export class Warehouse {
// Initiatizes a Warehouse object from a JSON save state. // Initiatizes a Warehouse object from a JSON save state.
static fromJSON(value: any): Warehouse { static fromJSON(value: any): Warehouse {
@ -43,6 +46,10 @@ export class Warehouse {
// Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for) // Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for)
smartSupplyEnabled: boolean = false; smartSupplyEnabled: boolean = false;
// Flag that indicates whether Smart Supply accounts for imports when calculating
// the amount fo purchase
smartSupplyConsiderExports: boolean = false;
// Stores the amount of product to be produced. Used for Smart Supply unlock. // Stores the amount of product to be produced. Used for Smart Supply unlock.
// The production tracked by smart supply is always based on the previous cycle, // The production tracked by smart supply is always based on the previous cycle,
// so it will always trail the "true" production by 1 cycle // so it will always trail the "true" production by 1 cycle
@ -65,6 +72,10 @@ export class Warehouse {
AICores: new Material({name: "AI Cores"}), AICores: new Material({name: "AI Cores"}),
RealEstate: new Material({name: "Real Estate"}) RealEstate: new Material({name: "Real Estate"})
} }
if (params.corp && params.industry) {
this.updateSize(params.corp, params.industry);
}
} }
// Re-calculate how much space is being used by this Warehouse // Re-calculate how much space is being used by this Warehouse
@ -76,7 +87,7 @@ export class Warehouse {
if (MaterialSizes.hasOwnProperty(matName)) { if (MaterialSizes.hasOwnProperty(matName)) {
this.sizeUsed += (mat.qty * MaterialSizes[matName]); this.sizeUsed += (mat.qty * MaterialSizes[matName]);
if (mat.qty > 0) { if (mat.qty > 0) {
this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0") + "<br>"); this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0") + "<br>");
} }
} }
} }
@ -86,9 +97,13 @@ export class Warehouse {
} }
updateSize(corporation: IParent, industry: IParent) { updateSize(corporation: IParent, industry: IParent) {
this.size = (this.level * 100) try {
* corporation.getStorageMultiplier() this.size = (this.level * 100)
* industry.getStorageMultiplier(); * corporation.getStorageMultiplier()
* industry.getStorageMultiplier();
} catch(e) {
exceptionAlert(e);
}
} }
// Serialize the current object to a JSON save state. // Serialize the current object to a JSON save state.

@ -27,6 +27,8 @@ function createBaseResearchTreeNodes(): Node {
const dronesAssembly: Node = makeNode("Drones - Assembly"); const dronesAssembly: Node = makeNode("Drones - Assembly");
const dronesTransport: Node = makeNode("Drones - Transport"); const dronesTransport: Node = makeNode("Drones - Transport");
const goJuice: Node = makeNode("Go-Juice"); const goJuice: Node = makeNode("Go-Juice");
const hrRecruitment: Node = makeNode("HRBuddy-Recruitment");
const hrTraining: Node = makeNode("HRBuddy-Training");
const joywire: Node = makeNode("JoyWire"); const joywire: Node = makeNode("JoyWire");
const marketta1: Node = makeNode("Market-TA.I"); const marketta1: Node = makeNode("Market-TA.I");
const marketta2: Node = makeNode("Market-TA.II"); const marketta2: Node = makeNode("Market-TA.II");
@ -40,6 +42,8 @@ function createBaseResearchTreeNodes(): Node {
drones.addChild(dronesAssembly); drones.addChild(dronesAssembly);
drones.addChild(dronesTransport); drones.addChild(dronesTransport);
hrRecruitment.addChild(hrTraining);
marketta1.addChild(marketta2); marketta1.addChild(marketta2);
overclock.addChild(stimu); overclock.addChild(stimu);
@ -49,6 +53,7 @@ function createBaseResearchTreeNodes(): Node {
rootNode.addChild(autoDrugs); rootNode.addChild(autoDrugs);
rootNode.addChild(bulkPurchasing); rootNode.addChild(bulkPurchasing);
rootNode.addChild(drones); rootNode.addChild(drones);
rootNode.addChild(hrRecruitment);
rootNode.addChild(joywire); rootNode.addChild(joywire);
rootNode.addChild(marketta1); rootNode.addChild(marketta1);
rootNode.addChild(overclock); rootNode.addChild(overclock);

@ -77,6 +77,21 @@ export const researchMetadata: IConstructorParams[] = [
"production by 10%.", "production by 10%.",
sciResearchMult: 1.1, sciResearchMult: 1.1,
}, },
{
name: "HRBuddy-Recruitment",
cost: 15e3,
desc: "Use automated software to handle the hiring of employees. With this " +
"research, each office will automatically hire one employee per " +
"market cycle if there is available space."
},
{
name: "HRBuddy-Training",
cost: 20e3,
desc: "Use automated software to handle the training of employees. With this " +
"research, each employee hired with HRBuddy-Recruitment will automatically " +
"be assigned to 'Training', rather than being unassigned."
},
{ {
name: "JoyWire", name: "JoyWire",
cost: 20e3, cost: 20e3,
@ -94,11 +109,13 @@ export const researchMetadata: IConstructorParams[] = [
}, },
{ {
name: "Market-TA.II", name: "Market-TA.II",
cost: 40e3, cost: 50e3,
desc: "Develop double-advanced AI software that uses technical analysis to " + desc: "Develop double-advanced AI software that uses technical analysis to " +
"help you understand and exploit the market. This research " + "help you understand and exploit the market. This research " +
"allows you to know how many sales of a Material/Product you lose or gain " + "allows you to know how many sales of a Material/Product you lose or gain " +
"from having too high or too low or a sale price.", "from having too high or too low or a sale price. It also lets you automatically " +
"set the sale price of your Materials/Products at the optimal price such that " +
"the amount sold matches the amount produced.",
}, },
{ {
name: "Overclock", name: "Overclock",

@ -14,6 +14,9 @@ export class CityTabs extends BaseReactComponent {
if (props.city == null) { if (props.city == null) {
throw new Error(`CityTabs component constructed without 'city' property`) throw new Error(`CityTabs component constructed without 'city' property`)
} }
if (props.cityStateSetter == null) {
throw new Error(`CityTabs component constructed without 'cityStateSetter' property`)
}
super(props); super(props);
} }
@ -46,7 +49,8 @@ export class CityTabs extends BaseReactComponent {
} }
// Tab to "Expand into new City" // Tab to "Expand into new City"
const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division); const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division, this.props.cityStateSetter);
tabs.push(this.renderTab({ tabs.push(this.renderTab({
current: false, current: false,
key: "Expand into new City", key: "Expand into new City",

@ -10,15 +10,23 @@ import { Corporation,
OfficeInitialSize, OfficeInitialSize,
SellSharesCooldown, SellSharesCooldown,
WarehouseInitialCost, WarehouseInitialCost,
WarehouseInitialSize } from "../Corporation"; WarehouseInitialSize,
BribeToRepRatio } from "../Corporation";
import { Industries, import { Industries,
IndustryStartingCosts, IndustryStartingCosts,
IndustryDescriptions, IndustryDescriptions,
IndustryResearchTrees } from "../IndustryData"; IndustryResearchTrees } from "../IndustryData";
import { MaterialSizes } from "../MaterialSizes";
import { Product } from "../Product";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Factions } from "../../Faction/Factions";
import { Cities } from "../../Locations/Cities";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
@ -79,7 +87,7 @@ export class CorporationEventHandler {
var totalAmount = Number(money) + (stockShares * stockPrice); var totalAmount = Number(money) + (stockShares * stockPrice);
var repGain = totalAmount / BribeToRepRatio; var repGain = totalAmount / BribeToRepRatio;
repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
" reputation with " + " reputation with " +
factionSelector.options[factionSelector.selectedIndex].value + factionSelector.options[factionSelector.selectedIndex].value +
" with this bribe"; " with this bribe";
@ -102,14 +110,14 @@ export class CorporationEventHandler {
var totalAmount = money + (stockShares * stockPrice); var totalAmount = money + (stockShares * stockPrice);
var repGain = totalAmount / BribeToRepRatio; var repGain = totalAmount / BribeToRepRatio;
console.log("repGain: " + repGain); console.log("repGain: " + repGain);
repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
" reputation with " + " reputation with " +
factionSelector.options[factionSelector.selectedIndex].value + factionSelector.options[factionSelector.selectedIndex].value +
" with this bribe"; " with this bribe";
} }
} }
}); });
var confirmButton = createElement("a", { var confirmButton = createElement("button", {
class:"a-link-button", innerText:"Bribe", display:"inline-block", class:"a-link-button", innerText:"Bribe", display:"inline-block",
clickListener:()=>{ clickListener:()=>{
var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
@ -129,7 +137,7 @@ export class CorporationEventHandler {
} else { } else {
var totalAmount = money + (stockShares * stockPrice); var totalAmount = money + (stockShares * stockPrice);
var repGain = totalAmount / BribeToRepRatio; var repGain = totalAmount / BribeToRepRatio;
dialogBoxCreate("You gained " + formatNumber(repGain, 0) + dialogBoxCreate("You gained " + numeralWrapper.format(repGain, "0,0") +
" reputation with " + fac.name + " by bribing them."); " reputation with " + fac.name + " by bribing them.");
fac.playerReputation += repGain; fac.playerReputation += repGain;
this.corp.funds = this.corp.funds.minus(money); this.corp.funds = this.corp.funds.minus(money);
@ -140,6 +148,7 @@ export class CorporationEventHandler {
} }
}); });
const cancelButton = createPopupCloseButton(popupId, { const cancelButton = createPopupCloseButton(popupId, {
class: "std-button",
display: "inline-block", display: "inline-block",
innerText: "Cancel", innerText: "Cancel",
}) })
@ -168,7 +177,6 @@ export class CorporationEventHandler {
type:"number", placeholder:"Shares to buyback", margin:"5px", type:"number", placeholder:"Shares to buyback", margin:"5px",
inputListener: ()=> { inputListener: ()=> {
var numShares = Math.round(input.value); var numShares = Math.round(input.value);
//TODO add conditional for if player doesn't have enough money
if (isNaN(numShares) || numShares <= 0) { if (isNaN(numShares) || numShares <= 0) {
costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback" costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback"
} else if (numShares > this.corp.issuedShares) { } else if (numShares > this.corp.issuedShares) {
@ -181,7 +189,7 @@ export class CorporationEventHandler {
} }
} }
}); });
var confirmBtn = createElement("a", { var confirmBtn = createElement("button", {
class:"a-link-button", innerText:"Buy shares", display:"inline-block", class:"a-link-button", innerText:"Buy shares", display:"inline-block",
clickListener: () => { clickListener: () => {
var shares = Math.round(input.value); var shares = Math.round(input.value);
@ -226,7 +234,7 @@ export class CorporationEventHandler {
} }
// Create a popup that lets the player discontinue a product // Create a popup that lets the player discontinue a product
createDiscontinueProductPopup(product) { createDiscontinueProductPopup(product, industry) {
const popupId = "cmpy-mgmt-discontinue-product-popup"; const popupId = "cmpy-mgmt-discontinue-product-popup";
const txt = createElement("p", { const txt = createElement("p", {
innerText:"Are you sure you want to do this? Discontinuing a product " + innerText:"Are you sure you want to do this? Discontinuing a product " +
@ -234,18 +242,18 @@ export class CorporationEventHandler {
"produce this product and all of its existing stock will be " + "produce this product and all of its existing stock will be " +
"removed and left unsold", "removed and left unsold",
}); });
const confirmBtn = createElement("a", { const confirmBtn = createElement("button", {
class:"a-link-button",innerText:"Discontinue", class:"popup-box-button",innerText:"Discontinue",
clickListener:()=>{ clickListener: () => {
industry.discontinueProduct(product, parentRefs); industry.discontinueProduct(product);
removeElementById(popupId); removeElementById(popupId);
this.corp.rerender(); this.rerender();
return false; return false;
} }
}); });
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
createPopup(popupId, [txt, confirmBtn, cancelBtn]); createPopup(popupId, [txt, cancelBtn, confirmBtn]);
} }
// Create a popup that lets the player manage exports // Create a popup that lets the player manage exports
@ -294,7 +302,7 @@ export class CorporationEventHandler {
placeholder:"Export amount / s" placeholder:"Export amount / s"
}); });
const exportBtn = createElement("a", { const exportBtn = createElement("button", {
class: "std-button", display:"inline-block", innerText:"Export", class: "std-button", display:"inline-block", innerText:"Export",
clickListener: () => { clickListener: () => {
const industryName = getSelectText(industrySelector); const industryName = getSelectText(industrySelector);
@ -340,7 +348,7 @@ export class CorporationEventHandler {
clickListener:()=>{ clickListener:()=>{
mat.exp.splice(i, 1); //Remove export object mat.exp.splice(i, 1); //Remove export object
removeElementById(popupId); removeElementById(popupId);
createExportPopup(); createExportMaterialPopup(mat);
} }
})); }));
})(i, mat, currExports); })(i, mat, currExports);
@ -473,7 +481,7 @@ export class CorporationEventHandler {
} }
}); });
issueBtn = createElement("a", { issueBtn = createElement("button", {
class: "std-button", class: "std-button",
display: "inline-block", display: "inline-block",
innerText: "Issue New Shares", innerText: "Issue New Shares",
@ -529,7 +537,7 @@ export class CorporationEventHandler {
} }
// Create a popup that lets the player limit the production of a product // Create a popup that lets the player limit the production of a product
createLimitProductProdutionPopup(product) { createLimitProductProdutionPopup(product, city) {
const popupId = "cmpy-mgmt-limit-product-production-popup"; const popupId = "cmpy-mgmt-limit-product-production-popup";
const txt = createElement("p", { const txt = createElement("p", {
innerText:"Enter a limit to the amount of this product you would " + innerText:"Enter a limit to the amount of this product you would " +
@ -537,17 +545,19 @@ export class CorporationEventHandler {
}); });
let confirmBtn; let confirmBtn;
const input = createElement("input", { const input = createElement("input", {
type:"number", placeholder:"Limit", margin: "5px",
placeholder:"Limit",
type:"number",
onkeyup: (e) => { onkeyup: (e) => {
e.preventDefault(); e.preventDefault();
if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
} }
}); });
confirmBtn = createElement("a", { confirmBtn = createElement("button", {
class: "std-button", class: "std-button",
display:"inline-block", display: "inline-block",
innerText:"Limit production", innerText: "Limit production",
margin:'6px', margin: "5px",
clickListener: () => { clickListener: () => {
if (input.value === "") { if (input.value === "") {
product.prdman[city][0] = false; product.prdman[city][0] = false;
@ -585,7 +595,7 @@ export class CorporationEventHandler {
const txt = createElement("p", { const txt = createElement("p", {
innerHTML: popupText, innerHTML: popupText,
}); });
const designCity = createElement("select"); const designCity = createElement("select", { margin: "5px" });
for (const cityName in division.offices) { for (const cityName in division.offices) {
if (division.offices[cityName] instanceof OfficeSpace) { if (division.offices[cityName] instanceof OfficeSpace) {
designCity.add(createElement("option", { designCity.add(createElement("option", {
@ -603,18 +613,26 @@ export class CorporationEventHandler {
productNamePlaceholder = "Property Name"; productNamePlaceholder = "Property Name";
} }
var productNameInput = createElement("input", { var productNameInput = createElement("input", {
margin: "5px",
placeholder: productNamePlaceholder, placeholder: productNamePlaceholder,
}); });
var lineBreak1 = createElement("br"); var lineBreak1 = createElement("br");
var designInvestInput = createElement("input", { var designInvestInput = createElement("input", {
margin: "5px",
placeholder: "Design investment",
type: "number", type: "number",
placeholder: "Design investment"
}); });
let confirmBtn;
var marketingInvestInput = createElement("input", { var marketingInvestInput = createElement("input", {
margin: "5px",
placeholder: "Marketing investment",
type: "number", type: "number",
placeholder: "Marketing investment" onkeyup: (e) => {
e.preventDefault();
if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
}
}); });
const confirmBtn = createElement("a", { confirmBtn = createElement("button", {
class: "std-button", class: "std-button",
innerText: "Develop Product", innerText: "Develop Product",
clickListener: () => { clickListener: () => {
@ -637,17 +655,19 @@ export class CorporationEventHandler {
designCost: designInvest, designCost: designInvest,
advCost: marketingInvest, advCost: marketingInvest,
}); });
if (division.products[product.name] instanceof Product) {
dialogBoxCreate(`You already have a product with this name!`);
return;
}
this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest); this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest);
division.products[product.name] = product; division.products[product.name] = product;
removeElementById(popupId); removeElementById(popupId);
} }
this.rerender(); this.rerender();
//this.updateUIContent();
//this.displayDivisionContent(division, city);
return false; return false;
} }
}) })
const cancelBtn = createPopupCloseButton(popupid, { const cancelBtn = createPopupCloseButton(popupId, {
class: "std-button", class: "std-button",
innerText: "Cancel", innerText: "Cancel",
}); });
@ -657,8 +677,8 @@ export class CorporationEventHandler {
productNameInput.focus(); productNameInput.focus();
} }
// Create a popup that lets the player use the Market TA research // Create a popup that lets the player use the Market TA research for Materials
createMarketTaPopup(mat, industry) { createMaterialMarketTaPopup(mat, industry) {
const corp = this.corp; const corp = this.corp;
const popupId = "cmpy-mgmt-marketta-popup"; const popupId = "cmpy-mgmt-marketta-popup";
@ -682,19 +702,21 @@ export class CorporationEventHandler {
"be sold at the price identified by Market-TA.I (i.e. the price shown above)" "be sold at the price identified by Market-TA.I (i.e. the price shown above)"
}) })
const useTa1AutoSaleCheckbox = createElement("input", { const useTa1AutoSaleCheckbox = createElement("input", {
checked: mat.marketTa1,
id: useTa1AutoSaleId, id: useTa1AutoSaleId,
margin: "3px",
type: "checkbox", type: "checkbox",
value: mat.marketTa1,
changeListener: (e) => { changeListener: (e) => {
mat.marketTa1 = e.target.value; mat.marketTa1 = e.target.checked;
} }
}); });
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
const closeBtn = createPopupCloseButton(popupId, { const closeBtn = createPopupCloseButton(popupId, {
class: "std-button", class: "std-button",
display: "block", display: "block",
innerText: "Close",
}); });
if (industry.hasResearch("Market-TA.II")) { if (industry.hasResearch("Market-TA.II")) {
@ -729,11 +751,41 @@ export class CorporationEventHandler {
} }
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` + ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` + `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
`then you will sell ${formatNumber(markup, 5)}x as much compared ` + `then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
`to if you sold at market price.`; `to if you sold at market price.`;
} }
updateTa2Text(); updateTa2Text();
createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]);
// Enable using Market-TA2 for automatically setting sale price
const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
const useTa2AutoSaleDiv = createElement("div", { display: "block" });
const useTa2AutoSaleLabel = createElement("label", {
color: "white",
for: useTa2AutoSaleId,
innerText: "Use Market-TA.II for Auto-Sale Price",
tooltip: "If this is enabled, then this Material will automatically " +
"be sold at the optimal price such that the amount sold matches the " +
"amount produced. (i.e. the highest possible price, while still ensuring " +
" that all produced materials will be sold)"
})
const useTa2AutoSaleCheckbox = createElement("input", {
checked: mat.marketTa2,
id: useTa2AutoSaleId,
margin: "3px",
type: "checkbox",
changeListener: (e) => {
mat.marketTa2 = e.target.checked;
}
});
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
const ta2OverridesTa1 = createElement("p", {
innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
"both are enabled, then Market-TA.II will take effect, not Market-TA.I"
});
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
} else { } else {
// Market-TA.I only // Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
@ -741,7 +793,8 @@ export class CorporationEventHandler {
} }
// Create a popup that lets the player expand into a new city (for the current industry) // Create a popup that lets the player expand into a new city (for the current industry)
createNewCityPopup(division) { // The 'cityStateSetter' arg is a function that sets the UI's 'city' state property
createNewCityPopup(division, cityStateSetter) {
const popupId = "cmpy-mgmt-expand-city-popup"; const popupId = "cmpy-mgmt-expand-city-popup";
const text = createElement("p", { const text = createElement("p", {
innerText: "Would you like to expand into a new city by opening an office? " + innerText: "Would you like to expand into a new city by opening an office? " +
@ -757,11 +810,12 @@ export class CorporationEventHandler {
} }
} }
const confirmBtn = createElement("a", { const confirmBtn = createElement("button", {
class:"std-button", class:"std-button",
display:"inline-block", display:"inline-block",
innerText: "Confirm", innerText: "Confirm",
clickListener: () => { clickListener: () => {
if (citySelector.length <= 0) { return false; }
let city = citySelector.options[citySelector.selectedIndex].value; let city = citySelector.options[citySelector.selectedIndex].value;
if (this.corp.funds.lt(OfficeInitialCost)) { if (this.corp.funds.lt(OfficeInitialCost)) {
dialogBoxCreate("You don't have enough company funds to open a new office!"); dialogBoxCreate("You don't have enough company funds to open a new office!");
@ -772,9 +826,11 @@ export class CorporationEventHandler {
loc: city, loc: city,
size: OfficeInitialSize, size: OfficeInitialSize,
}); });
this.corp.displayDivisionContent(division, city);
} }
cityStateSetter(city);
removeElementById(popupId); removeElementById(popupId);
this.rerender();
return false; return false;
} }
}); });
@ -836,15 +892,17 @@ export class CorporationEventHandler {
} else { } else {
this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]); this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]);
var newInd = new Industry({ var newInd = new Industry({
name:newDivisionName, corp: this.corp,
type:ind, name: newDivisionName,
type: ind,
}); });
this.corp.divisions.push(newInd); this.corp.divisions.push(newInd);
// this.corp.updateUIHeaderTabs();
// this.corp.selectHeaderTab(headerTabs[headerTabs.length-2]); // Set routing to the new division so that the UI automatically switches to it
this.routing.routeTo(newDivisionName);
removeElementById("cmpy-mgmt-expand-industry-popup"); removeElementById("cmpy-mgmt-expand-industry-popup");
this.rerender(); this.rerender();
// this.corp.displayDivisionContent(newInd, Locations.Sector12);
} }
return false; return false;
} }
@ -855,14 +913,14 @@ export class CorporationEventHandler {
innerText: "Cancel", innerText: "Cancel",
}); });
//Make an object to keep track of what industries you're already in // Make an object to keep track of what industries you're already in
const ownedIndustries = {}; const ownedIndustries = {};
for (let i = 0; i < this.corp.divisions.length; ++i) { for (let i = 0; i < this.corp.divisions.length; ++i) {
ownedIndustries[this.corp.divisions[i].type] = true; ownedIndustries[this.corp.divisions[i].type] = true;
} }
//Add industry types to selector // Add industry types to selector
//Have Agriculture be first as recommended option // Have Agriculture be first as recommended option
if (!ownedIndustries["Agriculture"]) { if (!ownedIndustries["Agriculture"]) {
selector.add(createElement("option", { selector.add(createElement("option", {
text:Industries["Agriculture"], value:"Agriculture" text:Industries["Agriculture"], value:"Agriculture"
@ -904,8 +962,115 @@ export class CorporationEventHandler {
return false; return false;
} }
// Create a popup that lets the player use the Market TA research for Products
createProductMarketTaPopup(product, industry) {
const corp = this.corp;
const popupId = "cmpy-mgmt-marketta-popup";
const markupLimit = product.rat / product.mku;
const ta1 = createElement("p", {
innerHTML: "<u><strong>Market-TA.I</strong></u><br>" +
"The maximum sale price you can mark this up to is " +
numeralWrapper.formatMoney(product.pCost + markupLimit) +
". This means that if you set the sale price higher than this, " +
"you will begin to experience a loss in number of sales",
});
// Enable using Market-TA1 for automatically setting sale price
const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox";
const useTa1AutoSaleDiv = createElement("div", { display: "block" });
const useTa1AutoSaleLabel = createElement("label", {
color: "white",
for: useTa1AutoSaleId,
innerText: "Use Market-TA.I for Auto-Sale Price",
tooltip: "If this is enabled, then this Product will automatically " +
"be sold at the price identified by Market-TA.I (i.e. the price shown above)"
})
const useTa1AutoSaleCheckbox = createElement("input", {
checked: product.marketTa1,
id: useTa1AutoSaleId,
margin: "3px",
type: "checkbox",
changeListener: (e) => {
product.marketTa1 = e.target.checked;
}
});
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
const closeBtn = createPopupCloseButton(popupId, {
class: "std-button",
display: "block",
innerText: "Close",
});
if (industry.hasResearch("Market-TA.II")) {
let updateTa2Text;
const ta2Text = createElement("p");
const ta2Input = createElement("input", {
marginTop: "4px",
onkeyup: (e) => {
e.preventDefault();
updateTa2Text();
},
type: "number",
value: product.pCost,
});
// Function that updates the text in ta2Text element
updateTa2Text = function() {
const sCost = parseFloat(ta2Input.value);
let markup = 1;
if (sCost > product.pCost) {
if ((sCost - product.pCost) > markupLimit) {
markup = markupLimit / (sCost - product.pCost);
}
}
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
`then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
`to if you sold at market price.`;
}
updateTa2Text();
// Enable using Market-TA2 for automatically setting sale price
const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
const useTa2AutoSaleDiv = createElement("div", { display: "block" });
const useTa2AutoSaleLabel = createElement("label", {
color: "white",
for: useTa2AutoSaleId,
innerText: "Use Market-TA.II for Auto-Sale Price",
tooltip: "If this is enabled, then this Product will automatically " +
"be sold at the optimal price such that the amount sold matches the " +
"amount produced. (i.e. the highest possible price, while still ensuring " +
" that all produced materials will be sold)"
})
const useTa2AutoSaleCheckbox = createElement("input", {
checked: product.marketTa2,
id: useTa2AutoSaleId,
margin: "3px",
type: "checkbox",
changeListener: (e) => {
product.marketTa2 = e.target.checked;
}
});
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
const ta2OverridesTa1 = createElement("p", {
innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
"both are enabled, then Market-TA.II will take effect, not Market-TA.I"
});
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
} else {
// Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
}
}
// Create a popup that lets the player purchase a Material // Create a popup that lets the player purchase a Material
createPurchaseMaterialPopup(mat, industry) { createPurchaseMaterialPopup(mat, industry, warehouse) {
const corp = this.corp; const corp = this.corp;
const purchasePopupId = "cmpy-mgmt-material-purchase-popup"; const purchasePopupId = "cmpy-mgmt-material-purchase-popup";
@ -933,6 +1098,7 @@ export class CorporationEventHandler {
mat.buy = parseFloat(input.value); mat.buy = parseFloat(input.value);
if (isNaN(mat.buy)) {mat.buy = 0;} if (isNaN(mat.buy)) {mat.buy = 0;}
removeElementById(purchasePopupId); removeElementById(purchasePopupId);
this.rerender();
return false; return false;
} }
} }
@ -942,6 +1108,7 @@ export class CorporationEventHandler {
clickListener: () => { clickListener: () => {
mat.buy = 0; mat.buy = 0;
removeElementById(purchasePopupId); removeElementById(purchasePopupId);
this.rerender();
return false; return false;
} }
}); });
@ -961,15 +1128,20 @@ export class CorporationEventHandler {
let bulkPurchaseCostTxt = createElement("p"); let bulkPurchaseCostTxt = createElement("p");
function updateBulkPurchaseText(amount) { function updateBulkPurchaseText(amount) {
const cost = parseFloat(amount) * mat.bCost; const parsedAmt = parseFloat(amount);
if (isNaN(cost)) { const cost = parsedAmt * mat.bCost;
dialogBoxCreate(`Bulk Purchase Cost calculated to be NaN. This is either due to ` +
`invalid input, or it is a bug (in which case you should report to dev)`);
return;
}
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` + const matSize = MaterialSizes[mat.name];
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`; const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
if (parsedAmt * matSize > maxAmount) {
bulkPurchaseCostTxt.innerText = "Not enough warehouse space to purchase this amount";
} else if (isNaN(cost)) {
bulkPurchaseCostTxt.innerText = "Invalid put for Bulk Purchase amount";
} else {
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(parsedAmt, "0,0.00")} of ` +
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
}
} }
let bulkPurchaseConfirmBtn; let bulkPurchaseConfirmBtn;
@ -979,7 +1151,7 @@ export class CorporationEventHandler {
type: "number", type: "number",
onkeyup: (e) => { onkeyup: (e) => {
e.preventDefault(); e.preventDefault();
bulkPurchaseUpdateCostTxt(); updateBulkPurchaseText(e.target.value);
if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();} if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();}
} }
}); });
@ -988,7 +1160,15 @@ export class CorporationEventHandler {
class: "std-button", class: "std-button",
innerText: "Confirm Bulk Purchase", innerText: "Confirm Bulk Purchase",
clickListener: () => { clickListener: () => {
const amount = parseFloat(input.value); const amount = parseFloat(bulkPurchaseInput.value);
const matSize = MaterialSizes[mat.name];
const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
if (amount * matSize > maxAmount) {
dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);
return false;
}
if (isNaN(amount)) { if (isNaN(amount)) {
dialogBoxCreate("Invalid input amount"); dialogBoxCreate("Invalid input amount");
} else { } else {
@ -1010,6 +1190,7 @@ export class CorporationEventHandler {
elems.push(bulkPurchaseInfo); elems.push(bulkPurchaseInfo);
elems.push(bulkPurchaseCostTxt); elems.push(bulkPurchaseCostTxt);
elems.push(bulkPurchaseInput); elems.push(bulkPurchaseInput);
elems.push(bulkPurchaseConfirmBtn);
} }
createPopup(purchasePopupId, elems); createPopup(purchasePopupId, elems);
@ -1045,9 +1226,18 @@ export class CorporationEventHandler {
if (e.keyCode === KEY.ENTER) {confirmBtn.click();} if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
} }
}); });
let inputButtonInitValue = mat.sCost ? mat.sCost : null;
if (mat.marketTa2) {
inputButtonInitValue += " (Market-TA.II)";
} else if (mat.marketTa1) {
inputButtonInitValue += " (Market-TA.I)";
}
const inputPx = createElement("input", { const inputPx = createElement("input", {
type: "text", marginTop: "4px", type: "text", marginTop: "4px",
value: mat.sCost ? mat.sCost : null, placeholder: "Sell price", value: inputButtonInitValue,
placeholder: "Sell price",
onkeyup: (e) => { onkeyup: (e) => {
e.preventDefault(); e.preventDefault();
if (e.keyCode === KEY.ENTER) {confirmBtn.click();} if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
@ -1129,7 +1319,7 @@ export class CorporationEventHandler {
} }
// Create a popup that lets the player manage sales of the product // Create a popup that lets the player manage sales of the product
createSellProductPopup(product) { createSellProductPopup(product, city) {
const popupId = "cmpy-mgmt-sell-product-popup"; const popupId = "cmpy-mgmt-sell-product-popup";
const txt = createElement("p", { const txt = createElement("p", {
innerHTML:"Enter the maximum amount of " + product.name + " you would like " + innerHTML:"Enter the maximum amount of " + product.name + " you would like " +
@ -1150,24 +1340,51 @@ export class CorporationEventHandler {
}); });
let confirmBtn; let confirmBtn;
const inputQty = createElement("input", { const inputQty = createElement("input", {
margin: "5px 0px 5px 0px",
placeholder: "Sell amount", placeholder: "Sell amount",
type: "text", type: "text",
value:product.sllman[city][1] ? product.sllman[city][1] : null, value: product.sllman[city][1] ? product.sllman[city][1] : null,
onkeyup: (e) => { onkeyup: (e) => {
e.preventDefault(); e.preventDefault();
if (e.keyCode === KEY.ENTER) {confirmBtn.click();} if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
} }
}); });
let inputButtonInitValue = product.sCost ? product.sCost : null;
if (product.marketTa2) {
inputButtonInitValue += " (Market-TA.II)";
} else if (product.marketTa1) {
inputButtonInitValue += " (Market-TA.I)";
}
const inputPx = createElement("input", { const inputPx = createElement("input", {
margin: "5px 0px 5px 0px",
placeholder: "Sell price", placeholder: "Sell price",
type: "text", type: "text",
value: product.sCost ? product.sCost : null, value: inputButtonInitValue,
onkeyup: (e) => { onkeyup: (e) => {
e.preventDefault(); e.preventDefault();
if (e.keyCode === KEY.ENTER) {confirmBtn.click();} if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
} }
}); });
confirmBtn = createElement("a", { const checkboxDiv = createElement("div", {
border: "1px solid white",
display: "inline-block",
})
const checkboxLabel = createElement("label", {
for: popupId + "-checkbox",
innerText: "Use same 'Sell Amount' for all cities",
});
const checkbox = createElement("input", {
checked: true,
id: popupId + "-checkbox",
margin: "2px",
type: "checkbox",
});
checkboxDiv.appendChild(checkboxLabel);
checkboxDiv.appendChild(checkbox);
confirmBtn = createElement("button", {
class: "std-button", class: "std-button",
innerText: "Confirm", innerText: "Confirm",
clickListener: () => { clickListener: () => {
@ -1198,7 +1415,10 @@ export class CorporationEventHandler {
product.sCost = cost; product.sCost = cost;
} }
//Parse quantity // Array of all cities. Used later
const cities = Object.values(Cities);
// Parse quantity
if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {
//Dynamically evaluated quantity. First test to make sure its valid //Dynamically evaluated quantity. First test to make sure its valid
var qty = inputQty.value.replace(/\s+/g, ''); var qty = inputQty.value.replace(/\s+/g, '');
@ -1216,8 +1436,16 @@ export class CorporationEventHandler {
dialogBoxCreate("Invalid value or expression for sell price field"); dialogBoxCreate("Invalid value or expression for sell price field");
return false; return false;
} }
product.sllman[city][0] = true; if (checkbox.checked) {
product.sllman[city][1] = qty; //Use sanitized input for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty; //Use sanitized input
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty; //Use sanitized input
}
} else if (isNaN(inputQty.value)) { } else if (isNaN(inputQty.value)) {
dialogBoxCreate("Invalid value for sell quantity field! Must be numeric"); dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
return false; return false;
@ -1225,10 +1453,25 @@ export class CorporationEventHandler {
var qty = parseFloat(inputQty.value); var qty = parseFloat(inputQty.value);
if (isNaN(qty)) {qty = 0;} if (isNaN(qty)) {qty = 0;}
if (qty === 0) { if (qty === 0) {
product.sllman[city][0] = false; if (checkbox.checked) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = false;
}
} else {
product.sllman[city][0] = false;
}
} else { } else {
product.sllman[city][0] = true; if (checkbox.checked) {
product.sllman[city][1] = qty; for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty;
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty;
}
} }
} }
@ -1237,9 +1480,12 @@ export class CorporationEventHandler {
return false; return false;
} }
}); });
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); const cancelBtn = createPopupCloseButton(popupId, { class: "std-button" });
createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]); const linebreak1 = createElement("br");
createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn, linebreak1,
checkboxDiv]);
inputQty.focus(); inputQty.focus();
} }
@ -1276,7 +1522,7 @@ export class CorporationEventHandler {
} }
} }
}); });
const confirmBtn = createElement("a", { const confirmBtn = createElement("button", {
class:"a-link-button", innerText:"Sell shares", display:"inline-block", class:"a-link-button", innerText:"Sell shares", display:"inline-block",
clickListener:()=>{ clickListener:()=>{
var shares = Math.round(input.value); var shares = Math.round(input.value);
@ -1355,7 +1601,7 @@ export class CorporationEventHandler {
if (e.keyCode === KEY.ENTER) {confirmBtn.click();} if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
} }
}); });
confirmBtn = createElement("a", { confirmBtn = createElement("button", {
class: "std-button", class: "std-button",
innerText: "Throw Party", innerText: "Throw Party",
clickListener:()=>{ clickListener:()=>{
@ -1366,20 +1612,20 @@ export class CorporationEventHandler {
if (this.corp.funds.lt(totalCost)) { if (this.corp.funds.lt(totalCost)) {
dialogBoxCreate("You don't have enough company funds to throw this.corp party!"); dialogBoxCreate("You don't have enough company funds to throw this.corp party!");
} else { } else {
this.corp.funds = this.funds.minus(totalCost); this.corp.funds = this.corp.funds.minus(totalCost);
var mult; var mult;
for (let fooit = 0; fooit < office.employees.length; ++fooit) { for (let fooit = 0; fooit < office.employees.length; ++fooit) {
mult = office.employees[fooit].throwParty(input.value); mult = office.employees[fooit].throwParty(input.value);
} }
dialogBoxCreate("You threw a party for the office! The morale and happiness " + dialogBoxCreate("You threw a party for the office! The morale and happiness " +
"of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%."); "of each employee increased by " + numeralWrapper.formatPercentage((mult-1)));
removeElementById(popupId); removeElementById(popupId);
} }
} }
return false; return false;
} }
}); });
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); const cancelBtn = createPopupCloseButton(popupId, { class: "std-button", innerText: "Cancel" });
createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]); createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]);
input.focus(); input.focus();
@ -1420,7 +1666,7 @@ export class CorporationEventHandler {
}); });
const text2 = createElement("p", { innerText: "Upgrade size: " }); const text2 = createElement("p", { innerText: "Upgrade size: " });
const confirmBtn = createElement("a", { const confirmBtn = createElement("button", {
class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button",
display:"inline-block", margin:"4px", innerText:"by 3", display:"inline-block", margin:"4px", innerText:"by 3",
tooltip:numeralWrapper.format(upgradeCost, "$0.000a"), tooltip:numeralWrapper.format(upgradeCost, "$0.000a"),
@ -1437,7 +1683,7 @@ export class CorporationEventHandler {
return false; return false;
} }
}); });
const confirmBtn15 = createElement("a", { const confirmBtn15 = createElement("button", {
class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button", class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button",
display:"inline-block", margin:"4px", innerText:"by 15", display:"inline-block", margin:"4px", innerText:"by 15",
tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"), tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"),
@ -1454,7 +1700,7 @@ export class CorporationEventHandler {
return false; return false;
} }
}); });
const confirmBtnMax = createElement("a", { const confirmBtnMax = createElement("button", {
class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button", class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button",
display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")", display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")",
tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"), tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"),
@ -1484,6 +1730,8 @@ export class CorporationEventHandler {
dialogBoxCreate("You do not have enough funds to do this!"); dialogBoxCreate("You do not have enough funds to do this!");
} else { } else {
division.warehouses[city] = new Warehouse({ division.warehouses[city] = new Warehouse({
corp: corp,
industry: division,
loc: city, loc: city,
size: WarehouseInitialSize, size: WarehouseInitialSize,
}); });

@ -15,6 +15,8 @@ export class IndustryOffice extends BaseReactComponent {
super(props); super(props);
this.state = { this.state = {
city: "",
division: "",
employeeManualAssignMode: false, employeeManualAssignMode: false,
employee: null, // Reference to employee being referenced if in Manual Mode employee: null, // Reference to employee being referenced if in Manual Mode
numEmployees: 0, numEmployees: 0,
@ -30,6 +32,17 @@ export class IndustryOffice extends BaseReactComponent {
this.updateEmployeeCount(); // This function validates division and office refs this.updateEmployeeCount(); // This function validates division and office refs
} }
resetEmployeeCount() {
this.state.numEmployees = 0;
this.state.numOperations = 0;
this.state.numEngineers = 0;
this.state.numBusiness = 0;
this.state.numManagement = 0;
this.state.numResearch = 0;
this.state.numUnassigned = 0;
this.state.numTraining = 0;
}
updateEmployeeCount() { updateEmployeeCount() {
const division = this.routing().currentDivision; const division = this.routing().currentDivision;
if (division == null) { if (division == null) {
@ -40,6 +53,13 @@ export class IndustryOffice extends BaseReactComponent {
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`); throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
} }
// If we're in a new city, we have to reset the state
if (division.name !== this.state.division || this.props.currentCity !== this.state.city) {
this.resetEmployeeCount();
this.state.division = division.name;
this.state.city = this.props.currentCity;
}
// Calculate how many NEW emplyoees we need to account for // Calculate how many NEW emplyoees we need to account for
const currentNumEmployees = office.employees.length; const currentNumEmployees = office.employees.length;
const newEmployees = currentNumEmployees - this.state.numEmployees; const newEmployees = currentNumEmployees - this.state.numEmployees;
@ -151,6 +171,7 @@ export class IndustryOffice extends BaseReactComponent {
--this.state.numUnassigned; --this.state.numUnassigned;
office.assignEmployeeToJob(to); office.assignEmployeeToJob(to);
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
this.corp().rerender(); this.corp().rerender();
} }
@ -194,6 +215,7 @@ export class IndustryOffice extends BaseReactComponent {
++this.state.numUnassigned; ++this.state.numUnassigned;
office.unassignEmployeeFromJob(from); office.unassignEmployeeFromJob(from);
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
this.corp().rerender(); this.corp().rerender();
} }
@ -278,41 +300,54 @@ export class IndustryOffice extends BaseReactComponent {
<br /> <br />
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p> <p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
<p>Avg Happiness Morale: {numeralWrapper.format(avgHappiness, "0.000")}</p> <p>Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}</p>
<p>Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}</p> <p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p> <p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{ {
vechain && vechain &&
<div> <p className={"tooltip"} style={{display: "inline-block"}}>
<p className={"tooltip"}>
Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")} Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
The base amount of material this office can produce. Does not include The base amount of material this office can produce. Does not include
production multipliers from upgrades and materials. This value is based off production multipliers from upgrades and materials. This value is based off
the productivity of your Operations, Engineering, and Management employees the productivity of your Operations, Engineering, and Management employees
</span> </span>
</p><br /> </p>
<p className={"tooltip"}> }
{
vechain && <br />
}
{
vechain &&
<p className={"tooltip"} style={{display: "inline-block"}}>
Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")} Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
The base amount of any given Product this office can produce. Does not include The base amount of any given Product this office can produce. Does not include
production multipliers from upgrades and materials. This value is based off production multipliers from upgrades and materials. This value is based off
the productivity of your Operations, Engineering, and Management employees the productivity of your Operations, Engineering, and Management employees
</span> </span>
</p><br /> </p>
<p className={"tooltip"}> }
Business Multiplier: x" ${numeralWrapper.format(division.getBusinessFactor(office), "0.000")} {
vechain && <br />
}
{
vechain &&
<p className={"tooltip"} style={{display: "inline-block"}}>
Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
The effect this office's 'Business' employees has on boosting sales The effect this office's 'Business' employees has on boosting sales
</span> </span>
</p><br /> </p>
</div> }
{
vechain && <br />
} }
<h2 className={"tooltip"} style={positionHeaderStyle}> <h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Operations} ({this.state.numOperations}) {EmployeePositions.Operations} ({this.state.numOperations})
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Manages supply chain operations. Improves production. Manages supply chain operations. Improves the amount of Materials and Products you produce.
</span> </span>
</h2> </h2>
<button className={assignButtonClass} onClick={operationAssignButtonOnClick}>+</button> <button className={assignButtonClass} onClick={operationAssignButtonOnClick}>+</button>
@ -322,7 +357,9 @@ export class IndustryOffice extends BaseReactComponent {
<h2 className={"tooltip"} style={positionHeaderStyle}> <h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Engineer} ({this.state.numEngineers}) {EmployeePositions.Engineer} ({this.state.numEngineers})
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Develops and maintains products and production systems. Improves production. Develops and maintains products and production systems. Increases the quality of
everything you produce. Also increases the amount you produce (not as much
as Operations, however)
</span> </span>
</h2> </h2>
<button className={assignButtonClass} onClick={engineerAssignButtonOnClick}>+</button> <button className={assignButtonClass} onClick={engineerAssignButtonOnClick}>+</button>
@ -332,7 +369,7 @@ export class IndustryOffice extends BaseReactComponent {
<h2 className={"tooltip"} style={positionHeaderStyle}> <h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Business} ({this.state.numBusiness}) {EmployeePositions.Business} ({this.state.numBusiness})
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Handles sales and finances. Improves sales. Handles sales and finances. Improves the amount of Materials and Products you can sell.
</span> </span>
</h2> </h2>
<button className={assignButtonClass} onClick={businessAssignButtonOnClick}>+</button> <button className={assignButtonClass} onClick={businessAssignButtonOnClick}>+</button>
@ -342,7 +379,8 @@ export class IndustryOffice extends BaseReactComponent {
<h2 className={"tooltip"} style={positionHeaderStyle}> <h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Management} ({this.state.numManagement}) {EmployeePositions.Management} ({this.state.numManagement})
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Leads and oversees employees and office operations. Improves production. Leads and oversees employees and office operations. Improves the effectiveness of
Engineer and Operations employees
</span> </span>
</h2> </h2>
<button className={assignButtonClass} onClick={managementAssignButtonOnClick}>+</button> <button className={assignButtonClass} onClick={managementAssignButtonOnClick}>+</button>
@ -398,27 +436,36 @@ export class IndustryOffice extends BaseReactComponent {
for (let i = 0; i < office.employees.length; ++i) { for (let i = 0; i < office.employees.length; ++i) {
if (name === office.employees[i].name) { if (name === office.employees[i].name) {
this.state.employee = office.employees[i]; this.state.employee = office.employees[i];
break;
} }
} }
corp.rerender();
} }
// Employee Positions Selector // Employee Positions Selector
const emp = this.state.employee;
let employeePositionSelectorInitialValue = null;
const employeePositions = []; const employeePositions = [];
const positionNames = Object.values(EmployeePositions); const positionNames = Object.values(EmployeePositions);
for (let i = 0; i < positionNames.length; ++i) { for (let i = 0; i < positionNames.length; ++i) {
employeePositions.push(<option key={positionNames[i]}>{positionNames[i]}</option>); employeePositions.push(<option key={positionNames[i]} value={positionNames[i]}>{positionNames[i]}</option>);
if (emp != null && emp.pos === positionNames[i]) {
employeePositionSelectorInitialValue = positionNames[i];
}
} }
const employeePositionSelectorOnChange = (e) => { const employeePositionSelectorOnChange = (e) => {
const pos = getSelectText(e.target); const pos = getSelectText(e.target);
this.state.employee.pos = pos; this.state.employee.pos = pos;
this.resetEmployeeCount();
corp.rerender();
} }
// Numeraljs formatter // Numeraljs formatter
const nf = "0.000"; const nf = "0.000";
// Employee stats (after applying multipliers) // Employee stats (after applying multipliers)
const emp = this.state.employee;
const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0; const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0;
const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0; const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0;
const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0; const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0;
@ -436,6 +483,9 @@ export class IndustryOffice extends BaseReactComponent {
</button> </button>
<div style={employeeInfoDivStyle}> <div style={employeeInfoDivStyle}>
<select onChange={employeeSelectorOnChange}>
{employees}
</select>
{ {
this.state.employee != null && this.state.employee != null &&
<p> <p>
@ -462,15 +512,11 @@ export class IndustryOffice extends BaseReactComponent {
} }
{ {
this.state.employee != null && this.state.employee != null &&
<select onChange={employeePositionSelectorOnChange}> <select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
{employeePositions} {employeePositions}
</select> </select>
} }
</div> </div>
<select onChange={employeeSelectorOnChange}>
{employees}
</select>
</div> </div>
) )
} }
@ -495,7 +541,6 @@ export class IndustryOffice extends BaseReactComponent {
} }
} }
const hireEmployeeButtonOnClick = () => { const hireEmployeeButtonOnClick = () => {
office.findEmployees({ corporation: corp, industry: division }); office.findEmployees({ corporation: corp, industry: division });
} }
@ -509,7 +554,7 @@ export class IndustryOffice extends BaseReactComponent {
} }
const autohireEmployeeButtonOnClick = () => { const autohireEmployeeButtonOnClick = () => {
if (office.atCapacity()) { return; } if (office.atCapacity()) { return; }
office.hireRandomEmployee({ corporation: corp, industry: division }); office.hireRandomEmployee();
this.corp().rerender(); this.corp().rerender();
} }

@ -8,6 +8,7 @@ import { Industries } from "../IndustryData";
import { IndustryUpgrades } from "../IndustryUpgrades"; import { IndustryUpgrades } from "../IndustryUpgrades";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
export class IndustryOverview extends BaseReactComponent { export class IndustryOverview extends BaseReactComponent {
renderMakeProductButton() { renderMakeProductButton() {
@ -109,6 +110,16 @@ export class IndustryOverview extends BaseReactComponent {
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`; const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
const productionMultHelpTipOnClick = () => { const productionMultHelpTipOnClick = () => {
// Wrapper for createProgressBarText()
// Converts the industry's "effectiveness factors"
// into a graphic (string) depicting how high that effectiveness is
function convertEffectFacToGraphic(fac) {
return createProgressBarText({
progress: fac,
totalTicks: 20,
});
}
dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " +
"can boost your Industry's production. The effect these " + "can boost your Industry's production. The effect these " +
"materials have on your production varies between Industries. " + "materials have on your production varies between Industries. " +
@ -118,7 +129,13 @@ export class IndustryOverview extends BaseReactComponent {
"the individual production multiplier of each of its office locations. " + "the individual production multiplier of each of its office locations. " +
"This production multiplier is applied to each office. Therefore, it is " + "This production multiplier is applied to each office. Therefore, it is " +
"beneficial to expand into new cities as this can greatly increase the " + "beneficial to expand into new cities as this can greatly increase the " +
"production multiplier of your entire Division."); "production multiplier of your entire Division.<br><br>" +
"Below are approximations for how effective each material is at boosting " +
"this industry's production multiplier (Bigger bars = more effective):<br><br>" +
`Hardware:&nbsp;&nbsp;&nbsp; ${convertEffectFacToGraphic(division.hwFac)}<br>` +
`Robots:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ${convertEffectFacToGraphic(division.robFac)}<br>` +
`AI Cores:&nbsp;&nbsp;&nbsp; ${convertEffectFacToGraphic(division.aiFac)}<br>` +
`Real Estate: ${convertEffectFacToGraphic(division.reFac)}`);
} }
return ( return (
@ -129,15 +146,15 @@ export class IndustryOverview extends BaseReactComponent {
{popularity} <br /> {popularity} <br />
{ {
(advertisingInfo !== false) && (advertisingInfo !== false) &&
<p className={"tooltip"}>Advertising Multiplier: {numeralWrapper.format(totalAdvertisingFac, "0.000")} <p className={"tooltip"}>Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")}
<span className={"tooltiptext cmpy-mgmt-advertising-info"}> <span className={"tooltiptext cmpy-mgmt-advertising-info"}>
Total multiplier for this industrys sales due to its awareness and popularity Total multiplier for this industrys sales due to its awareness and popularity
<br /> <br />
Awareness Bonus: x{formatNumber(Math.pow(awarenessFac, 0.85), 3)} Awareness Bonus: x{numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")}
<br /> <br />
Popularity Bonus: x{formatNumber(Math.pow(popularityFac, 0.85), 3)} Popularity Bonus: x{numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")}
<br /> <br />
Ratio Multiplier: x{formatNumber(Math.pow(ratioFac, 0.85), 3)} Ratio Multiplier: x{numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")}
</span> </span>
</p> </p>
} }
@ -157,7 +174,7 @@ export class IndustryOverview extends BaseReactComponent {
<div className={"help-tip"} onClick={productionMultHelpTipOnClick}>?</div> <div className={"help-tip"} onClick={productionMultHelpTipOnClick}>?</div>
<br /> <br /> <br /> <br />
<p className={"tooltip"}> <p className={"tooltip"}>
Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000")} Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Scientific Research increases the quality of the materials and Scientific Research increases the quality of the materials and
products that you produce. products that you produce.
@ -252,7 +269,7 @@ export class IndustryOverview extends BaseReactComponent {
{ {
division.makesProducts && division.makesProducts &&
{makeProductButton} makeProductButton
} }
</div> </div>
) )

@ -1,63 +1,83 @@
// React Component for displaying an Industry's warehouse information // React Component for displaying an Industry's warehouse information
// (right-side panel in the Industry UI) // (right-side panel in the Industry UI)
import React from "react"; import React from "react";
import { BaseReactComponent } from "./BaseReactComponent"; import { BaseReactComponent } from "./BaseReactComponent";
import { Material } from "../Material"; import { OfficeSpace,
import { Product } from "../Product";
import { Warehouse,
WarehouseInitialCost, WarehouseInitialCost,
WarehouseUpgradeBaseCost } from "../Corporation"; WarehouseUpgradeBaseCost,
ProductProductionCostRatio } from "../Corporation";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { isString } from "../../../utils/helpers/isString"; import { isString } from "../../../utils/helpers/isString";
// Creates the UI for a single Product type // Creates the UI for a single Product type
function ProductComponent(props) { function ProductComponent(props) {
const corp = props.corp; const corp = props.corp;
const division = props.division; const division = props.division;
const warehouse = props.warehouse; const warehouse = props.warehouse;
const city = props.city;
const product = props.product; const product = props.product;
const eventHandler = props.eventHandler; const eventHandler = props.eventHandler;
const nf = "0.000"; // Numeraljs formatter // Numeraljs formatters
const nf = "0.000";
const nfB = "0.000a"; // For numbers that might be big
const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard");
// Total product gain = production - sale // Total product gain = production - sale
const totalGain = totalGain = product.data[city][1] - product.data[city][2]; const totalGain = product.data[city][1] - product.data[city][2];
// Sell button // Sell button
const sellButtonText = product.sllman[city][1] === -1 let sellButtonText;
? "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/MAX)" if (product.sllman[city][0]) {
: "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/" + numeralWrapper.format(product.sllman[city][1], nf) + ")"; if (isString(product.sllman[city][1])) {
if (product.sCost) { sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${product.sllman[city][1]})`;
} else {
sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${numeralWrapper.format(product.sllman[city][1], nfB)})`;
}
} else {
sellButtonText = "Sell (0.000/0.000)";
}
if (product.marketTa2) {
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.marketTa2Price[city]));
} else if (product.marketTa1) {
const markupLimit = product.rat / product.mku;
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.pCost + markupLimit));
} else if (product.sCost) {
if (isString(product.sCost)) { if (isString(product.sCost)) {
sellButtonText += (" @ " + product.sCost); sellButtonText += (" @ " + product.sCost);
} else { } else {
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a")); sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
} }
} }
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product); const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
// Limit Production button // Limit Production button
const limitProductionButtonText = "Limit Production"; let limitProductionButtonText = "Limit Production";
if (product.prdman[city][0]) { if (product.prdman[city][0]) {
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
} }
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product); const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
// Discontinue Button // Discontinue Button
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product); const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
// Market TA button
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
// Unfinished Product // Unfinished Product
if (!product.fin) { if (!product.fin) {
if (hasUpgradeDashboard) { if (hasUpgradeDashboard) {
return ( return (
<div className={"cmpy-mgmt-warehouse-product-div"}> <div className={"cmpy-mgmt-warehouse-product-div"}>
<p>Designing {product.name}...</p> <p>Designing {product.name}...</p><br />
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p> <p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
<br /> <br />
@ -71,13 +91,19 @@ function ProductComponent(props) {
<button className={"std-button"} onClick={discontinueButtonOnClick}> <button className={"std-button"} onClick={discontinueButtonOnClick}>
Discontinue Discontinue
</button> </button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
Market-TA
</button>
}
</div> </div>
</div> </div>
) )
} else { } else {
return ( return (
<div className={"cmpy-mgmt-warehouse-product-div"}> <div className={"cmpy-mgmt-warehouse-product-div"}>
<p>Designing {product.name}...</p> <p>Designing {product.name}...</p><br />
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p> <p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
</div> </div>
); );
@ -85,15 +111,15 @@ function ProductComponent(props) {
} }
return ( return (
<div className={"cmpy-mgmt-warehouse-product-div"} key={props.key}> <div className={"cmpy-mgmt-warehouse-product-div"}>
<p className={"tooltip"}> <p className={"tooltip"}>
{product.name}: {numeralWrapper.format(product.data[city][0], nf)} ({numeralWrapper.format(totalGain, nf)}/s) {product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Prod: {numeralWrapper.format(product.data[city][1], nf)}/s Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s
<br /> <br />
Sell: {numeralWrapper.format(product.data[city][2], nf)} /s Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s
</span> </span>
</p> </p><br />
<p className={"tooltip"}> <p className={"tooltip"}>
Rating: {numeralWrapper.format(product.rat, nf)} Rating: {numeralWrapper.format(product.rat, nf)}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
@ -119,15 +145,15 @@ function ProductComponent(props) {
} }
</span> </span>
</p> </p><br />
<p className={"tooltip"}> <p className={"tooltip"}>
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)} Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
An estimate of the material cost it takes to create this Product. An estimate of the material cost it takes to create this Product.
</span> </span>
</p> </p><br />
<p className={"tooltip"}> <p className={"tooltip"}>
Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)} Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
An estimate of how much consumers are willing to pay for this product. An estimate of how much consumers are willing to pay for this product.
Setting the sale price above this may result in less sales. Setting the sale price below this may result Setting the sale price above this may result in less sales. Setting the sale price below this may result
@ -145,6 +171,12 @@ function ProductComponent(props) {
<button className={"std-button"} onClick={discontinueButtonOnClick}> <button className={"std-button"} onClick={discontinueButtonOnClick}>
Discontinue Discontinue
</button> </button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
Market-TA
</button>
}
</div> </div>
</div> </div>
) )
@ -155,12 +187,18 @@ function MaterialComponent(props) {
const corp = props.corp; const corp = props.corp;
const division = props.division; const division = props.division;
const warehouse = props.warehouse; const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat; const mat = props.mat;
const eventHandler = props.eventHandler; const eventHandler = props.eventHandler;
const markupLimit = mat.getMarkupLimit(); const markupLimit = mat.getMarkupLimit();
const office = division.offices[city];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Could not get OfficeSpace object for this city (${city})`);
}
// Numeraljs formatter // Numeraljs formatter
const nf = "0.000"; const nf = "0.000";
const nfB = "0.000a"; // For numbers that might be biger
// Total gain or loss of this material (per second) // Total gain or loss of this material (per second)
const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;
@ -180,9 +218,9 @@ function MaterialComponent(props) {
mat.buy === 0 && mat.imp === 0; mat.buy === 0 && mat.imp === 0;
// Purchase material button // Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`; const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division); const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
// Export material button // Export material button
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat); const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
@ -190,12 +228,18 @@ function MaterialComponent(props) {
// Sell material button // Sell material button
let sellButtonText; let sellButtonText;
if (mat.sllman[0]) { if (mat.sllman[0]) {
sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" : if (isString(mat.sllman[1])) {
"Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")"); sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
if (mat.sCost) { } else {
if (mat.marketTa1) { sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`;
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); }
} else if (isString(mat.sCost)) {
if (mat.marketTa2) {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.marketTa2Price);
} else if (mat.marketTa1) {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
} else if (mat.sCost) {
if (isString(mat.sCost)) {
var sCost = mat.sCost.replace(/MP/g, mat.bCost); var sCost = mat.sCost.replace(/MP/g, mat.bCost);
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost)); sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
} else { } else {
@ -208,19 +252,19 @@ function MaterialComponent(props) {
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat); const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
// Market TA button // Market TA button
const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division); const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
return ( return (
<div className={"cmpy-mgmt-warehouse-material-div"} key={props.key}> <div className={"cmpy-mgmt-warehouse-material-div"}>
<div style={{display: "inline-block"}}> <div style={{display: "inline-block"}}>
<p className={"tooltip"}> <p className={"tooltip"}>
{mat.name}: {numeralWrapper.format(mat.qty, nf)} ({numeralWrapper.format(totalGain, nf)}/s) {mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
Buy: {numeralWrapper.format(mat.buy, nf)} <br /> Buy: {numeralWrapper.format(mat.buy, nfB)} <br />
Prod: {numeralWrapper.format(mat.prd, nf)} <br /> Prod: {numeralWrapper.format(mat.prd, nfB)} <br />
Sell: {numeralWrapper.format(mat.sll, nf)} <br /> Sell: {numeralWrapper.format(mat.sll, nfB)} <br />
Export: {numeralWrapper.format(mat.totalExp, nf)} <br /> Export: {numeralWrapper.format(mat.totalExp, nfB)} <br />
Import: {numeralWrapper.format(mat.imp, nf)} Import: {numeralWrapper.format(mat.imp, nfB)}
{ {
corp.unlockUpgrades[2] === 1 && <br /> corp.unlockUpgrades[2] === 1 && <br />
} }
@ -244,7 +288,7 @@ function MaterialComponent(props) {
</span> </span>
</p> <br /> </p> <br />
<p className={"tooltip"}> <p className={"tooltip"}>
Quality: {numeralWrapper.format(mat.qlt, "0.00")} Quality: {numeralWrapper.format(mat.qlt, "0.00a")}
<span className={"tooltiptext"}> <span className={"tooltiptext"}>
The quality of your material. Higher quality will lead to more sales The quality of your material. Higher quality will lead to more sales
</span> </span>
@ -287,6 +331,19 @@ function MaterialComponent(props) {
} }
export class IndustryWarehouse extends BaseReactComponent { export class IndustryWarehouse extends BaseReactComponent {
// Returns a boolean indicating whether the given material is relevant for the
// current industry.
isRelevantMaterial(matName, division) {
// Materials that affect Production multiplier
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
if (Object.keys(division.reqMats).includes(matName)) { return true; }
if (division.prodMats.includes(matName)) { return true; }
if (prodMultiplierMats.includes(matName)) { return true; }
return false;
}
renderWarehouseUI() { renderWarehouseUI() {
const corp = this.corp(); const corp = this.corp();
const division = this.routing().currentDivision; // Validated in render() const division = this.routing().currentDivision; // Validated in render()
@ -370,20 +427,8 @@ export class IndustryWarehouse extends BaseReactComponent {
// Smart Supply Checkbox // Smart Supply Checkbox
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
const smartSupplyOnChange = (e) => { const smartSupplyOnChange = (e) => {
warehouse.smartSupplyEnabled = e.target.value; warehouse.smartSupplyEnabled = e.target.checked;
} corp.rerender();
// Materials that affect Production multiplier
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
// Returns a boolean indicating whether the given material is relevant for the
// current industry.
function isRelevantMaterial(matName) {
if (Object.keys(division.reqMats).includes(matName)) { return true; }
if (division.prodMats.includes(matName)) { return true; }
if (prodMultiplierMats.includes(matName)) { return true; }
return false;
} }
// Create React components for materials // Create React components for materials
@ -391,15 +436,15 @@ export class IndustryWarehouse extends BaseReactComponent {
for (const matName in warehouse.materials) { for (const matName in warehouse.materials) {
if (warehouse.materials[matName] instanceof Material) { if (warehouse.materials[matName] instanceof Material) {
// Only create UI for materials that are relevant for the industry // Only create UI for materials that are relevant for the industry
if (isRelevantMaterial(matName)) { if (this.isRelevantMaterial(matName, division)) {
mats.push(MaterialComponent({ mats.push(<MaterialComponent
corp: corp, city={this.props.currentCity}
division: division, corp={corp}
eventHandler: this.eventHandler(), division={division}
key: matName, eventHandler={this.eventHandler()}
mat: warehouse.materials[matName], key={matName}
warehouse: warehouse, mat={warehouse.materials[matName]}
})); warehouse={warehouse} />);
} }
} }
} }
@ -409,14 +454,14 @@ export class IndustryWarehouse extends BaseReactComponent {
if (division.makesProducts && Object.keys(division.products).length > 0) { if (division.makesProducts && Object.keys(division.products).length > 0) {
for (const productName in division.products) { for (const productName in division.products) {
if (division.products[productName] instanceof Product) { if (division.products[productName] instanceof Product) {
products.push({ products.push(<ProductComponent
corp: corp, city={this.props.currentCity}
division: division, corp={corp}
eventHandler: this.eventHandler(), division={division}
key: productName, eventHandler={this.eventHandler()}
product: division.products[productName], key={productName}
warehouse: warehouse, product={division.products[productName]}
}) warehouse={warehouse} />);
} }
} }
} }
@ -424,10 +469,8 @@ export class IndustryWarehouse extends BaseReactComponent {
return ( return (
<div className={"cmpy-mgmt-warehouse-panel"}> <div className={"cmpy-mgmt-warehouse-panel"}>
<p className={"tooltip"} style={sizeUsageStyle}> <p className={"tooltip"} style={sizeUsageStyle}>
Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")} Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)}
<span className={"tooltiptext"}> <span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
{warehouse.breakdown}
</span>
</p> </p>
<button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}> <button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}>
@ -454,7 +497,7 @@ export class IndustryWarehouse extends BaseReactComponent {
id={smartSupplyCheckboxId} id={smartSupplyCheckboxId}
onChange={smartSupplyOnChange} onChange={smartSupplyOnChange}
style={{margin: "3px"}} style={{margin: "3px"}}
value={warehouse.smartSupplyEnabled} checked={warehouse.smartSupplyEnabled}
/> />
</div> </div>
} }
@ -481,9 +524,11 @@ export class IndustryWarehouse extends BaseReactComponent {
return this.renderWarehouseUI(); return this.renderWarehouseUI();
} else { } else {
return ( return (
<button className={"std-button"} onClick={newWarehouseOnClick}> <div className={"cmpy-mgmt-warehouse-panel"}>
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)}) <button className={"std-button"} onClick={newWarehouseOnClick}>
</button> Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
</button>
</div>
) )
} }
} }

@ -23,6 +23,15 @@ export class MainPanel extends BaseReactComponent {
} }
} }
// We can pass this setter to child components
changeCityState(newCity) {
if (Object.values(Cities).includes(newCity)) {
this.state.city = newCity;
} else {
console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
}
}
// Determines what UI content to render based on routing // Determines what UI content to render based on routing
renderContent() { renderContent() {
if (this.routing().isOnOverviewPage()) { if (this.routing().isOnOverviewPage()) {
@ -68,11 +77,13 @@ export class MainPanel extends BaseReactComponent {
} }
} }
} }
const cityTabs = ( const cityTabs = (
<CityTabs <CityTabs
{...this.props} {...this.props}
city={this.state.city} city={this.state.city}
onClicks={onClicks} onClicks={onClicks}
cityStateSetter={this.changeCityState.bind(this)}
/> />
) )

@ -64,7 +64,7 @@ export class Overview extends BaseReactComponent {
dividendStr + dividendStr +
"Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "<br>" + "Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "<br>" +
"Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "<br>" + "Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "<br>" +
"Stock Price: " + (this.corp().public ? "$" + numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "<br>" + "Stock Price: " + (this.corp().public ? numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "<br>" +
"<p class='tooltip'>Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") + "<p class='tooltip'>Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") +
"<span class='tooltiptext'>" + "<span class='tooltiptext'>" +
`Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}<br>` + `Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}<br>` +

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

File diff suppressed because it is too large Load Diff

@ -14,7 +14,7 @@ import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { createPurchaseSleevesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases"; import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
import {Page, routing} from "../ui/navigationTracking"; import {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat"; import {numeralWrapper} from "../ui/numeralFormat";
@ -199,7 +199,7 @@ function displayFactionContent(factionName) {
innerText:"This donation will result in 0.000 reputation gain" innerText:"This donation will result in 0.000 reputation gain"
}); });
var donateAmountInput = createElement("input", { var donateAmountInput = createElement("input", {
placeholder:"Donation amount", class: "text-input", placeholder:"Donation amount",
inputListener:()=>{ inputListener:()=>{
let amt = 0; let amt = 0;
if(donateAmountInput.value !== "") { if(donateAmountInput.value !== "") {
@ -348,7 +348,7 @@ function displayFactionContent(factionName) {
class: "std-button", class: "std-button",
innerText: "Purchase Duplicate Sleeves", innerText: "Purchase Duplicate Sleeves",
clickListener: () => { clickListener: () => {
createPurchaseSleevesFromCovenantPopup(Player); createSleevePurchasesFromCovenantPopup(Player);
} }
})); }));
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", { covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {

@ -1784,6 +1784,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
id: name + "gang-member-task", id: name + "gang-member-task",
}); });
const taskSelector = createElement("select", { const taskSelector = createElement("select", {
class: "dropdown",
id: name + "gang-member-task-selector", id: name + "gang-member-task-selector",
}); });

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

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

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

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

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

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

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

@ -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,
},
]

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

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

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

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

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

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

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

@ -0,0 +1,155 @@
/**
* Root React Component for the Hacknet Node UI
*/
import React from "react";
import { GeneralInfo } from "./GeneralInfo";
import { HacknetNode } from "./HacknetNode";
import { HacknetServer } from "./HacknetServer";
import { HashUpgradePopup } from "./HashUpgradePopup";
import { MultiplierButtons } from "./MultiplierButtons";
import { PlayerInfo } from "./PlayerInfo";
import { PurchaseButton } from "./PurchaseButton";
import { getCostOfNextHacknetNode,
getCostOfNextHacknetServer,
hasHacknetServers,
purchaseHacknet } from "../HacknetHelpers";
import { Player } from "../../Player";
import { AllServers } from "../../Server/AllServers";
import { createPopup } from "../../ui/React/createPopup";
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
export const PurchaseMultipliers = Object.freeze({
"x1": 1,
"x5": 5,
"x10": 10,
"MAX": "MAX",
});
export class HacknetRoot extends React.Component {
constructor(props) {
super(props);
this.state = {
purchaseMultiplier: PurchaseMultipliers.x1,
totalProduction: 0, // Total production ($ / s) of Hacknet Nodes
}
this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this);
}
componentDidMount() {
this.recalculateTotalProduction();
}
createHashUpgradesPopup() {
const id = "hacknet-server-hash-upgrades-popup";
createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup });
}
recalculateTotalProduction() {
let total = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
if (hasHacknetServers()) {
const hserver = AllServers[Player.hacknetNodes[i]];
if (hserver) {
total += hserver.hashRate;
} else {
console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`)
}
} else {
total += Player.hacknetNodes[i].moneyGainRatePerSecond;
}
}
this.setState({
totalProduction: total,
});
}
setPurchaseMultiplier(mult) {
this.setState({
purchaseMultiplier: mult,
});
}
render() {
// Cost to purchase a new Hacknet Node
let purchaseCost;
if (hasHacknetServers()) {
purchaseCost = getCostOfNextHacknetServer();
} else {
purchaseCost = getCostOfNextHacknetNode();
}
// onClick event handler for purchase button
const purchaseOnClick = () => {
if (purchaseHacknet() >= 0) {
this.recalculateTotalProduction();
Player.hashManager.updateCapacity(Player);
}
}
// onClick event handlers for purchase multiplier buttons
const purchaseMultiplierOnClicks = [
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1),
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x5),
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x10),
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.MAX),
];
// HacknetNode components
const nodes = Player.hacknetNodes.map((node) => {
if (hasHacknetServers()) {
const hserver = AllServers[node];
if (hserver == null) {
throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`);
}
return (
<HacknetServer
key={hserver.hostname}
node={hserver}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)}
/>
)
} else {
return (
<HacknetNode
key={node.name}
node={node}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)}
/>
)
}
});
return (
<div>
<h1>Hacknet Nodes</h1>
<GeneralInfo />
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={purchaseOnClick} />
<br />
<div id={"hacknet-nodes-money-multipliers-div"}>
<PlayerInfo totalProduction={this.state.totalProduction} />
<MultiplierButtons onClicks={purchaseMultiplierOnClicks} purchaseMultiplier={this.state.purchaseMultiplier} />
</div>
{
hasHacknetServers() &&
<button className={"std-button"} onClick={this.createHashUpgradesPopup} style={{display: "block"}}>
{"Spend Hashes on Upgrades"}
</button>
}
<ul id={"hacknet-nodes-list"}>{nodes}</ul>
</div>
)
}
}

@ -1,694 +0,0 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import {iTutorialSteps, iTutorialNextStep,
ITutorial} from "./InteractiveTutorial";
import {Player} from "./Player";
import {Page, routing} from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver";
import {createElement} from "../utils/uiHelpers/createElement";
import {getElementById} from "../utils/uiHelpers/getElementById";
// Stores total money gain rate from all of the player's Hacknet Nodes
let TotalHacknetNodeProduction = 0;
/**
* Overwrites the inner text of the specified HTML element if it is different from what currently exists.
* @param {string} elementId The HTML ID to find the first instance of.
* @param {string} text The inner text that should be set.
*/
function updateText(elementId, text) {
var el = getElementById(elementId);
if (el.innerText != text) {
el.innerText = text;
}
};
/* HacknetNode.js */
function hacknetNodesInit() {
var performMapping = function(x) {
getElementById("hacknet-nodes-" + x.id + "-multiplier")
.addEventListener("click", function() {
hacknetNodePurchaseMultiplier = x.multiplier;
updateHacknetNodesMultiplierButtons();
updateHacknetNodesContent();
return false;
});
};
var mappings = [
{ id: "1x", multiplier: 1 },
{ id: "5x", multiplier: 5 },
{ id: "10x", multiplier: 10 },
{ id: "max", multiplier: 0 }
];
for (var elem of mappings) {
// Encapsulate in a function so that the appropriate scope is kept in the click handler.
performMapping(elem);
}
}
document.addEventListener("DOMContentLoaded", hacknetNodesInit, false);
function HacknetNode(name) {
this.level = 1;
this.ram = 1; //GB
this.cores = 1;
this.name = name;
this.totalMoneyGenerated = 0;
this.onlineTimeSeconds = 0;
this.moneyGainRatePerSecond = 0;
}
HacknetNode.prototype.updateMoneyGainRate = function() {
//How much extra $/s is gained per level
var gainPerLevel = CONSTANTS.HacknetNodeMoneyGainPerLevel;
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
Math.pow(1.035, this.ram-1) *
((this.cores + 5) / 6) *
Player.hacknet_node_money_mult *
BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.moneyGainRatePerSecond)) {
this.moneyGainRatePerSecond = 0;
dialogBoxCreate("Error in calculating Hacknet Node production. Please report to game developer");
}
updateTotalHacknetProduction();
}
HacknetNode.prototype.calculateLevelUpgradeCost = function(levels=1) {
levels = Math.round(levels);
if (isNaN(levels) || levels < 1) {
return 0;
}
if (this.level >= CONSTANTS.HacknetNodeMaxLevel) {
return Infinity;
}
var mult = CONSTANTS.HacknetNodeUpgradeLevelMult;
var totalMultiplier = 0; //Summed
var currLevel = this.level;
for (var i = 0; i < levels; ++i) {
totalMultiplier += Math.pow(mult, currLevel);
++currLevel;
}
return CONSTANTS.BaseCostForHacknetNode / 2 * totalMultiplier * Player.hacknet_node_level_cost_mult;
}
HacknetNode.prototype.purchaseLevelUpgrade = function(levels=1) {
levels = Math.round(levels);
var cost = this.calculateLevelUpgradeCost(levels);
if (isNaN(cost) || levels < 0) {
return false;
}
//If we're at max level, return false
if (this.level >= CONSTANTS.HacknetNodeMaxLevel) {
return false;
}
//If the number of specified upgrades would exceed the max level, calculate
//the maximum number of upgrades and use that
if (this.level + levels > CONSTANTS.HacknetNodeMaxLevel) {
var diff = Math.max(0, CONSTANTS.HacknetNodeMaxLevel - this.level);
return this.purchaseLevelUpgrade(diff);
}
if (Player.money.lt(cost)) {
return false;
}
Player.loseMoney(cost);
this.level = Math.round(this.level + levels); //Just in case of floating point imprecision
this.updateMoneyGainRate();
return true;
}
HacknetNode.prototype.calculateRamUpgradeCost = function(levels=1) {
levels = Math.round(levels);
if (isNaN(levels) || levels < 1) {
return 0;
}
if (this.ram >= CONSTANTS.HacknetNodeMaxRam) {
return Infinity;
}
let totalCost = 0;
let numUpgrades = Math.round(Math.log2(this.ram));
let currentRam = this.ram;
for (let i = 0; i < levels; ++i) {
let baseCost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHacknetNode;
let mult = Math.pow(CONSTANTS.HacknetNodeUpgradeRamMult, numUpgrades);
totalCost += (baseCost * mult);
currentRam *= 2;
++numUpgrades;
}
totalCost *= Player.hacknet_node_ram_cost_mult
return totalCost;
}
HacknetNode.prototype.purchaseRamUpgrade = function(levels=1) {
levels = Math.round(levels);
var cost = this.calculateRamUpgradeCost(levels);
if (isNaN(cost) || levels < 0) {
return false;
}
// Fail if we're already at max
if (this.ram >= CONSTANTS.HacknetNodeMaxRam) {
return false;
}
//If the number of specified upgrades would exceed the max RAM, calculate the
//max possible number of upgrades and use that
if (this.ram * Math.pow(2, levels) > CONSTANTS.HacknetNodeMaxRam) {
var diff = Math.max(0, Math.log2(Math.round(CONSTANTS.HacknetNodeMaxRam / this.ram)));
return this.purchaseRamUpgrade(diff);
}
if (Player.money.lt(cost)) {
return false;
}
Player.loseMoney(cost);
for (let i = 0; i < levels; ++i) {
this.ram *= 2; //Ram is always doubled
}
this.ram = Math.round(this.ram); //Handle any floating point precision issues
this.updateMoneyGainRate();
return true;
}
HacknetNode.prototype.calculateCoreUpgradeCost = function(levels=1) {
levels = Math.round(levels);
if (isNaN(levels) || levels < 1) {
return 0;
}
if (this.cores >= CONSTANTS.HacknetNodeMaxCores) {
return Infinity;
}
const coreBaseCost = CONSTANTS.BaseCostForHacknetNodeCore;
const mult = CONSTANTS.HacknetNodeUpgradeCoreMult;
let totalCost = 0;
let currentCores = this.cores;
for (let i = 0; i < levels; ++i) {
totalCost += (coreBaseCost * Math.pow(mult, currentCores-1));
++currentCores;
}
totalCost *= Player.hacknet_node_core_cost_mult;
return totalCost;
}
HacknetNode.prototype.purchaseCoreUpgrade = function(levels=1) {
levels = Math.round(levels);
var cost = this.calculateCoreUpgradeCost(levels);
if (isNaN(cost) || levels < 0) {
return false;
}
//Fail if we're already at max
if (this.cores >= CONSTANTS.HacknetNodeMaxCores) {
return false;
}
//If the specified number of upgrades would exceed the max Cores, calculate
//the max possible number of upgrades and use that
if (this.cores + levels > CONSTANTS.HacknetNodeMaxCores) {
var diff = Math.max(0, CONSTANTS.HacknetNodeMaxCores - this.cores);
return this.purchaseCoreUpgrade(diff);
}
if (Player.money.lt(cost)) {
return false;
}
Player.loseMoney(cost);
this.cores = Math.round(this.cores + levels); //Just in case of floating point imprecision
this.updateMoneyGainRate();
return true;
}
/* Saving and loading HackNets */
HacknetNode.prototype.toJSON = function() {
return Generic_toJSON("HacknetNode", this);
}
HacknetNode.fromJSON = function(value) {
return Generic_fromJSON(HacknetNode, value.data);
}
Reviver.constructors.HacknetNode = HacknetNode;
function purchaseHacknet() {
/* INTERACTIVE TUTORIAL */
if (ITutorial.isRunning) {
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
iTutorialNextStep();
} else {
return;
}
}
/* END INTERACTIVE TUTORIAL */
var cost = getCostOfNextHacknetNode();
if (isNaN(cost)) {
throw new Error("Cost is NaN");
}
if (Player.money.lt(cost)) {
//dialogBoxCreate("You cannot afford to purchase a Hacknet Node!");
return -1;
}
//Auto generate a name for the node for now...TODO
var numOwned = Player.hacknetNodes.length;
var name = "hacknet-node-" + numOwned;
var node = new HacknetNode(name);
node.updateMoneyGainRate();
Player.loseMoney(cost);
Player.hacknetNodes.push(node);
if (routing.isOn(Page.HacknetNodes)) {
displayHacknetNodesContent();
}
updateTotalHacknetProduction();
return numOwned;
}
//Calculates the total production from all HacknetNodes
function updateTotalHacknetProduction() {
var total = 0;
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
total += Player.hacknetNodes[i].moneyGainRatePerSecond;
}
TotalHacknetNodeProduction = total;
}
function getCostOfNextHacknetNode() {
//Cost increases exponentially based on how many you own
var numOwned = Player.hacknetNodes.length;
var mult = CONSTANTS.HacknetNodePurchaseNextMult;
return CONSTANTS.BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
}
var hacknetNodePurchaseMultiplier = 1;
function updateHacknetNodesMultiplierButtons() {
var mult1x = document.getElementById("hacknet-nodes-1x-multiplier");
var mult5x = document.getElementById("hacknet-nodes-5x-multiplier");
var mult10x = document.getElementById("hacknet-nodes-10x-multiplier");
var multMax = document.getElementById("hacknet-nodes-max-multiplier");
mult1x.setAttribute("class", "a-link-button");
mult5x.setAttribute("class", "a-link-button");
mult10x.setAttribute("class", "a-link-button");
multMax.setAttribute("class", "a-link-button");
if (Player.hacknetNodes.length == 0) {
mult1x.setAttribute("class", "a-link-button-inactive");
mult5x.setAttribute("class", "a-link-button-inactive");
mult10x.setAttribute("class", "a-link-button-inactive");
multMax.setAttribute("class", "a-link-button-inactive");
} else if (hacknetNodePurchaseMultiplier == 1) {
mult1x.setAttribute("class", "a-link-button-inactive");
} else if (hacknetNodePurchaseMultiplier == 5) {
mult5x.setAttribute("class", "a-link-button-inactive");
} else if (hacknetNodePurchaseMultiplier == 10) {
mult10x.setAttribute("class", "a-link-button-inactive");
} else {
multMax.setAttribute("class", "a-link-button-inactive");
}
}
//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
function getMaxNumberLevelUpgrades(nodeObj) {
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1))) {
return 0;
}
var min = 1;
var max = CONSTANTS.HacknetNodeMaxLevel - 1;
var levelsToMax = CONSTANTS.HacknetNodeMaxLevel - nodeObj.level;
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax))) {
return levelsToMax;
}
while (min <= max) {
var curr = (min + max) / 2 | 0;
if (curr != CONSTANTS.HacknetNodeMaxLevel &&
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr)) &&
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1))) {
return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr))) {
max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr))) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
}
}
return 0;
}
function getMaxNumberRamUpgrades(nodeObj) {
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1))) {
return 0;
}
const levelsToMax = Math.round(Math.log2(CONSTANTS.HacknetNodeMaxRam / nodeObj.ram));
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax))) {
return levelsToMax;
}
//We'll just loop until we find the max
for (let i = levelsToMax-1; i >= 0; --i) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i))) {
return i;
}
}
return 0;
}
function getMaxNumberCoreUpgrades(nodeObj) {
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1))) {
return 0;
}
var min = 1;
var max = CONSTANTS.HacknetNodeMaxCores - 1;
const levelsToMax = CONSTANTS.HacknetNodeMaxCores - nodeObj.cores;
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax))) {
return levelsToMax;
}
//Use a binary search to find the max possible number of upgrades
while (min <= max) {
let curr = (min + max) / 2 | 0;
if (curr != CONSTANTS.HacknetNodeMaxCores &&
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr)) &&
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1))) {
return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr))) {
max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr))) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
}
}
return 0;
}
//Creates Hacknet Node DOM elements when the page is opened
function displayHacknetNodesContent() {
//Update Hacknet Nodes button
var newPurchaseButton = clearEventListeners("hacknet-nodes-purchase-button");
newPurchaseButton.addEventListener("click", function() {
purchaseHacknet();
return false;
});
//Handle Purchase multiplier buttons
updateHacknetNodesMultiplierButtons();
//Remove all old hacknet Node DOM elements
var hacknetNodesList = document.getElementById("hacknet-nodes-list");
while (hacknetNodesList.firstChild) {
hacknetNodesList.removeChild(hacknetNodesList.firstChild);
}
//Then re-create them
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
createHacknetNodeDomElement(Player.hacknetNodes[i]);
}
updateTotalHacknetProduction();
updateHacknetNodesContent();
}
//Update information on all Hacknet Node DOM elements
function updateHacknetNodesContent() {
//Set purchase button to inactive if not enough money, and update its price display
var cost = getCostOfNextHacknetNode();
var purchaseButton = getElementById("hacknet-nodes-purchase-button");
var formattedCost = numeralWrapper.formatMoney(cost);
updateText("hacknet-nodes-purchase-button", `Purchase Hacknet Node - ${formattedCost}`);
if (Player.money.lt(cost)) {
purchaseButton.setAttribute("class", "a-link-button-inactive");
} else {
purchaseButton.setAttribute("class", "a-link-button");
}
//Update player's money
updateText("hacknet-nodes-player-money", numeralWrapper.formatMoney(Player.money.toNumber()));
updateText("hacknet-nodes-total-production", numeralWrapper.formatMoney(TotalHacknetNodeProduction) + " / sec");
//Update information in each owned hacknet node
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
updateHacknetNodeDomElement(Player.hacknetNodes[i]);
}
}
//Creates a single Hacknet Node DOM element
function createHacknetNodeDomElement(nodeObj) {
var nodeName = nodeObj.name;
var nodeLevelContainer = createElement("div", {
class: "hacknet-node-level-container row",
innerHTML: "<p>Level:</p><span class=\"text upgradable-info\" id=\"hacknet-node-level-" + nodeName + "\"></span>"
});
var nodeRamContainer = createElement("div", {
class: "hacknet-node-ram-container row",
innerHTML: "<p>RAM:</p><span class=\"text upgradable-info\" id=\"hacknet-node-ram-" + nodeName + "\"></span>"
});
var nodeCoresContainer = createElement("div", {
class: "hacknet-node-cores-container row",
innerHTML: "<p>Cores:</p><span class=\"text upgradable-info\" id=\"hacknet-node-cores-" + nodeName + "\"><span>"
})
var containingDiv = createElement("div", {
class: "hacknet-node-container",
innerHTML: "<div class=\"hacknet-node-name-container row\">" +
"<p>Node name:</p>" +
"<span class=\"text\" id=\"hacknet-node-name-" + nodeName + "\"></span>" +
"</div>" +
"<div class=\"hacknet-node-production-container row\">" +
"<p>Production:</p>" +
"<span class=\"text\" id=\"hacknet-node-total-production-" + nodeName + "\"></span>" +
"<span class=\"text\" id=\"hacknet-node-production-rate-" + nodeName + "\"></span>" +
"</div>"
});
containingDiv.appendChild(nodeLevelContainer);
containingDiv.appendChild(nodeRamContainer);
containingDiv.appendChild(nodeCoresContainer);
var listItem = createElement("li", {
class: "hacknet-node"
});
listItem.appendChild(containingDiv);
//Upgrade buttons
nodeLevelContainer.appendChild(createElement("a", {
id: "hacknet-node-upgrade-level-" + nodeName,
class: "a-link-button-inactive",
clickListener: function() {
let numUpgrades = hacknetNodePurchaseMultiplier;
if (hacknetNodePurchaseMultiplier == 0) {
numUpgrades = getMaxNumberLevelUpgrades(nodeObj);
}
nodeObj.purchaseLevelUpgrade(numUpgrades);
updateHacknetNodesContent();
return false;
}
}));
nodeRamContainer.appendChild(createElement("a", {
id: "hacknet-node-upgrade-ram-" + nodeName,
class: "a-link-button-inactive",
clickListener: function() {
let numUpgrades = hacknetNodePurchaseMultiplier;
if (hacknetNodePurchaseMultiplier == 0) {
numUpgrades = getMaxNumberRamUpgrades(nodeObj);
}
nodeObj.purchaseRamUpgrade(numUpgrades);
updateHacknetNodesContent();
return false;
}
}));
nodeCoresContainer.appendChild(createElement("a", {
id: "hacknet-node-upgrade-core-" + nodeName,
class: "a-link-button-inactive",
clickListener: function() {
let numUpgrades = hacknetNodePurchaseMultiplier;
if (hacknetNodePurchaseMultiplier == 0) {
numUpgrades = getMaxNumberCoreUpgrades(nodeObj);
}
nodeObj.purchaseCoreUpgrade(numUpgrades);
updateHacknetNodesContent();
return false;
}
}));
document.getElementById("hacknet-nodes-list").appendChild(listItem);
//Set the text and stuff inside the DOM element
updateHacknetNodeDomElement(nodeObj);
}
//Updates information on a single hacknet node DOM element
function updateHacknetNodeDomElement(nodeObj) {
var nodeName = nodeObj.name;
updateText("hacknet-node-name-" + nodeName, nodeName);
updateText("hacknet-node-total-production-" + nodeName, numeralWrapper.formatMoney(nodeObj.totalMoneyGenerated));
updateText("hacknet-node-production-rate-" + nodeName, "(" + numeralWrapper.formatMoney(nodeObj.moneyGainRatePerSecond) + " / sec)");
updateText("hacknet-node-level-" + nodeName, nodeObj.level);
updateText("hacknet-node-ram-" + nodeName, nodeObj.ram + "GB");
updateText("hacknet-node-cores-" + nodeName, nodeObj.cores);
//Upgrade level
var upgradeLevelButton = getElementById("hacknet-node-upgrade-level-" + nodeName);
if (nodeObj.level >= CONSTANTS.HacknetNodeMaxLevel) {
updateText("hacknet-node-upgrade-level-" + nodeName, "MAX LEVEL");
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
} else {
let multiplier = 0;
if (hacknetNodePurchaseMultiplier == 0) {
//Max
multiplier = getMaxNumberLevelUpgrades(nodeObj);
} else {
var levelsToMax = CONSTANTS.HacknetNodeMaxLevel - nodeObj.level;
multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
}
var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost(multiplier);
updateText("hacknet-node-upgrade-level-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeLevelCost))
if (Player.money.lt(upgradeLevelCost)) {
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
} else {
upgradeLevelButton.setAttribute("class", "a-link-button");
}
}
//Upgrade RAM
var upgradeRamButton = getElementById("hacknet-node-upgrade-ram-" + nodeName);
if (nodeObj.ram >= CONSTANTS.HacknetNodeMaxRam) {
updateText("hacknet-node-upgrade-ram-" + nodeName, "MAX RAM");
upgradeRamButton.setAttribute("class", "a-link-button-inactive");
} else {
let multiplier = 0;
if (hacknetNodePurchaseMultiplier == 0) {
multiplier = getMaxNumberRamUpgrades(nodeObj);
} else {
var levelsToMax = Math.round(Math.log2(CONSTANTS.HacknetNodeMaxRam / nodeObj.ram));
multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
}
var upgradeRamCost = nodeObj.calculateRamUpgradeCost(multiplier);
updateText("hacknet-node-upgrade-ram-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeRamCost));
if (Player.money.lt(upgradeRamCost)) {
upgradeRamButton.setAttribute("class", "a-link-button-inactive");
} else {
upgradeRamButton.setAttribute("class", "a-link-button");
}
}
//Upgrade Cores
var upgradeCoreButton = getElementById("hacknet-node-upgrade-core-" + nodeName);
if (nodeObj.cores >= CONSTANTS.HacknetNodeMaxCores) {
updateText("hacknet-node-upgrade-core-" + nodeName, "MAX CORES");
upgradeCoreButton.setAttribute("class", "a-link-button-inactive");
} else {
let multiplier = 0;
if (hacknetNodePurchaseMultiplier == 0) {
multiplier = getMaxNumberCoreUpgrades(nodeObj);
} else {
var levelsToMax = CONSTANTS.HacknetNodeMaxCores - nodeObj.cores;
multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier);
}
var upgradeCoreCost = nodeObj.calculateCoreUpgradeCost(multiplier);
updateText("hacknet-node-upgrade-core-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeCoreCost));
if (Player.money.lt(upgradeCoreCost)) {
upgradeCoreButton.setAttribute("class", "a-link-button-inactive");
} else {
upgradeCoreButton.setAttribute("class", "a-link-button");
}
}
}
function processAllHacknetNodeEarnings(numCycles) {
var total = 0;
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
}
return total;
}
function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
var cyclesPerSecond = 1000 / Engine._idleSpeed;
var earningPerCycle = nodeObj.moneyGainRatePerSecond / cyclesPerSecond;
if (isNaN(earningPerCycle)) {
console.error("Hacknet Node '" + nodeObj.name + "' Calculated earnings is NaN");
earningPerCycle = 0;
}
var totalEarnings = numCycles * earningPerCycle;
nodeObj.totalMoneyGenerated += totalEarnings;
nodeObj.onlineTimeSeconds += (numCycles * (Engine._idleSpeed / 1000));
Player.gainMoney(totalEarnings);
Player.recordMoneySource(totalEarnings, "hacknetnode");
return totalEarnings;
}
function getHacknetNode(name) {
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
if (Player.hacknetNodes[i].name == name) {
return Player.hacknetNodes[i];
}
}
return null;
}
export {
HacknetNode,
displayHacknetNodesContent,
getCostOfNextHacknetNode,
getHacknetNode,
getMaxNumberLevelUpgrades,
hacknetNodesInit,
processAllHacknetNodeEarnings,
purchaseHacknet,
updateHacknetNodesContent,
updateHacknetNodesMultiplierButtons,
updateTotalHacknetProduction
};

@ -52,41 +52,81 @@ function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff)
this.intExpGained = 0; this.intExpGained = 0;
} }
InfiltrationInstance.prototype.expMultiplier = function() {
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1;
return 2.5 * this.clearanceLevel / this.maxClearanceLevel;
}
InfiltrationInstance.prototype.gainHackingExp = function(amt) { InfiltrationInstance.prototype.gainHackingExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.hackingExpGained += amt; this.hackingExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedHackingExp = function() {
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0;
return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainStrengthExp = function(amt) { InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.strExpGained += amt; this.strExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedStrengthExp = function() {
if (!this.strExpGained || isNaN(this.strExpGained)) return 0;
return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainDefenseExp = function(amt) { InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.defExpGained += amt; this.defExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedDefenseExp = function() {
if (!this.defExpGained || isNaN(this.defExpGained)) return 0;
return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainDexterityExp = function(amt) { InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.dexExpGained += amt; this.dexExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedDexterityExp = function() {
if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0;
return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainAgilityExp = function(amt) { InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.agiExpGained += amt; this.agiExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedAgilityExp = function() {
if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0;
return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainCharismaExp = function(amt) { InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.chaExpGained += amt; this.chaExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedCharismaExp = function() {
if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0;
return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) { InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
if (isNaN(amt)) {return;} if (isNaN(amt)) {return;}
this.intExpGained += amt; this.intExpGained += amt;
} }
InfiltrationInstance.prototype.calcGainedIntelligenceExp = function() {
if(!this.intExpGained || isNaN(this.intExpGained)) return 0;
return Math.pow(this.intExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) { function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff); var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
clearInfiltrationStatusText(); clearInfiltrationStatusText();
@ -477,12 +517,12 @@ function updateInfiltrationLevelText(inst) {
"Total value of stolen secrets<br>" + "Total value of stolen secrets<br>" +
"Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" + "Reputation:       <span class='light-yellow'>" + formatNumber(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" + "Money:           <span class='money-gold'>$" + formatNumber(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + formatNumber(inst.hackingExpGained * expMultiplier, 3) + "<br>" + "Hack exp gained:  " + formatNumber(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + formatNumber(inst.strExpGained * expMultiplier, 3) + "<br>" + "Str exp gained:   " + formatNumber(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + formatNumber(inst.defExpGained * expMultiplier, 3) + "<br>" + "Def exp gained:   " + formatNumber(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + formatNumber(inst.dexExpGained * expMultiplier, 3) + "<br>" + "Dex exp gained:   " + formatNumber(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + formatNumber(inst.agiExpGained * expMultiplier, 3) + "<br>" + "Agi exp gained:   " + formatNumber(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + formatNumber(inst.chaExpGained * expMultiplier, 3); "Cha exp gained:   " + formatNumber(inst.calcGainedCharismaExp(), 3);
/* eslint-enable no-irregular-whitespace */ /* eslint-enable no-irregular-whitespace */
} }

@ -327,7 +327,7 @@ function displayLocationContent() {
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[0]], securityJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[2]], securityJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob);

@ -1 +1,9 @@
/**
* Location and traveling-related helper functions.
* Mostly used for UI
*/
import { Player } from "../Player"; import { Player } from "../Player";
import { Company } from "../Company/Company";
import { getJobRequirementText } from "../Company/GetJobRequirementText";
import * as posNames from "../Company/data/companypositionnames";

@ -0,0 +1,18 @@
/**
* Utility function that creates a "city map", which is an object where
* each city is a key (property).
*
* This map uses the official name of the city, NOT its key in the 'Cities' object
*/
import { Cities } from "./Cities";
import { IMap } from "../types";
export function createCityMap<T>(initValue: T): IMap<T> {
const map: IMap<any> = {};
const cities = Object.keys(Cities);
for (let i = 0; i < cities.length; ++i) {
map[cities[i]] = initValue;
}
return map;
}

@ -7,9 +7,11 @@ import { LocationName } from "../data/LocationNames";
import { Companies } from "../../Company/Companies"; import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company"; import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
import { StdButtonWithTooltip } from "../../ui/React/StdButtonWithTooltip";
type IProps = { type IProps = {
locName: LocationName; locName: LocationName;
@ -99,6 +101,18 @@ export class CompanyLocation extends React.Component<IProps, any> {
this.props.p.applyForWaiterJob(); this.props.p.applyForWaiterJob();
} }
getJobRequirementTooltip(company: Company, entryPosType: CompanyPosition) {
if (!(company instanceof Company)) { return; }
const pos = this.props.p.getNextCompanyPosition(company, entryPosType);
if (pos == null) { return };
if (!company.hasPosition(pos)) { return; }
return getJobRequirementText(company, pos, true);
}
render() { render() {
return ( return (
<div> <div>

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

@ -196,8 +196,11 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre
} }
var runningScriptObj = new RunningScript(script, args); var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads; runningScriptObj.threads = threads;
server.runningScripts.push(runningScriptObj); //Push onto runningScripts
addWorkerScript(runningScriptObj, server); addWorkerScript(runningScriptObj, server);
// Push onto runningScripts.
// This has to come after addWorkerScript() because that fn updates RAM usage
server.runScript(runningScriptObj, Player);
return Promise.resolve(true); return Promise.resolve(true);
} }
} }

@ -28,8 +28,9 @@ import { Factions,
factionExists } from "./Faction/Factions"; factionExists } from "./Faction/Factions";
import { joinFaction, import { joinFaction,
purchaseAugmentation } from "./Faction/FactionHelpers"; purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { getCostOfNextHacknetNode, import { getCostOfNextHacknetNode,
purchaseHacknet } from "./HacknetNode"; purchaseHacknet } from "./Hacknet/HacknetNode";
import {Locations} from "./Locations"; import {Locations} from "./Locations";
import { Message } from "./Message/Message"; import { Message } from "./Message/Message";
import { Messages } from "./Message/MessageHelpers"; import { Messages } from "./Message/MessageHelpers";
@ -60,6 +61,7 @@ import {StockMarket, StockSymbols, SymbolToStockMap,
PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket"; PositionTypes, placeOrder, cancelOrder} from "./StockMarket/StockMarket";
import { getStockmarket4SDataCost, import { getStockmarket4SDataCost,
getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts"; getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags"
import {TextFile, getTextFile, createTextFile} from "./TextFile"; import {TextFile, getTextFile, createTextFile} from "./TextFile";
import {unknownBladeburnerActionErrorMessage, import {unknownBladeburnerActionErrorMessage,
@ -71,6 +73,8 @@ import {WorkerScript, workerScripts,
import {makeRuntimeRejectMsg, netscriptDelay, import {makeRuntimeRejectMsg, netscriptDelay,
runScriptFromScript} from "./NetscriptEvaluator"; runScriptFromScript} from "./NetscriptEvaluator";
import {NetscriptPort} from "./NetscriptPort"; import {NetscriptPort} from "./NetscriptPort";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/Sleeve";
import {Page, routing} from "./ui/navigationTracking"; import {Page, routing} from "./ui/navigationTracking";
import {numeralWrapper} from "./ui/numeralFormat"; import {numeralWrapper} from "./ui/numeralFormat";
@ -275,27 +279,27 @@ function NetscriptFunctions(workerScript) {
}, },
upgradeLevel : function(i, n) { upgradeLevel : function(i, n) {
var node = getHacknetNode(i); var node = getHacknetNode(i);
return node.purchaseLevelUpgrade(n); return node.purchaseLevelUpgrade(n, Player);
}, },
upgradeRam : function(i, n) { upgradeRam : function(i, n) {
var node = getHacknetNode(i); var node = getHacknetNode(i);
return node.purchaseRamUpgrade(n); return node.purchaseRamUpgrade(n, Player);
}, },
upgradeCore : function(i, n) { upgradeCore : function(i, n) {
var node = getHacknetNode(i); var node = getHacknetNode(i);
return node.purchaseCoreUpgrade(n); return node.purchaseCoreUpgrade(n, Player);
}, },
getLevelUpgradeCost : function(i, n) { getLevelUpgradeCost : function(i, n) {
var node = getHacknetNode(i); var node = getHacknetNode(i);
return node.calculateLevelUpgradeCost(n); return node.calculateLevelUpgradeCost(n, Player);
}, },
getRamUpgradeCost : function(i, n) { getRamUpgradeCost : function(i, n) {
var node = getHacknetNode(i); var node = getHacknetNode(i);
return node.calculateRamUpgradeCost(n); return node.calculateRamUpgradeCost(n, Player);
}, },
getCoreUpgradeCost : function(i, n) { getCoreUpgradeCost : function(i, n) {
var node = getHacknetNode(i); var node = getHacknetNode(i);
return node.calculateCoreUpgradeCost(n); return node.calculateCoreUpgradeCost(n, Player);
} }
}, },
sprintf : sprintf, sprintf : sprintf,
@ -4807,7 +4811,341 @@ function NetscriptFunctions(workerScript) {
} }
return contract.getMaxNumTries() - contract.tries; return contract.getMaxNumTries() - contract.tries;
}, },
} }, // End coding contracts
sleeve : {
getNumSleeves : function() {
if (workerScript.checkingRam) {
return updateStaticRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getNumSleeves() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getNumSleeves", CONSTANTS.ScriptSleeveBaseRamCost);
return Player.sleeves.length;
},
setToShockRecovery : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("setToShockRecovery", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToShockRecovery() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToShockRecovery", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToShockRecovery(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
return Player.sleeves[sleeveNumber].shockRecovery(Player);
},
setToSynchronize : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("setToSynchronize", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToSynchronize() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToSynchronize", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToSynchronize(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
return Player.sleeves[sleeveNumber].synchronize(Player);
},
setToCommitCrime : function(sleeveNumber=0, crimeName="") {
if (workerScript.checkingRam) {
return updateStaticRam("setToCommitCrime", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToCommitCrime() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToCommitCrime", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToCommitCrime(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName);
},
setToUniversityCourse : function(sleeveNumber=0, universityName="", className="") {
if (workerScript.checkingRam) {
return updateStaticRam("setToUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToUniversityCourse() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToUniversityCourse", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToUniversityCourse(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
return Player.sleeves[sleeveNumber].takeUniversityCourse(Player, universityName, className);
},
travel : function(sleeveNumber=0, cityName="") {
if (workerScript.checkingRam) {
return updateStaticRam("travel", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "travel() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("travel", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.travel(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
return Player.sleeves[sleeveNumber].travel(Player, cityName);
},
setToCompanyWork : function(sleeveNumber=0, companyName="") {
if (workerScript.checkingRam) {
return updateStaticRam("setToCompanyWork", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToCompanyWork() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToCompanyWork", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToCompanyWork(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
// Cannot work at the same company that another sleeve is working at
for (let i = 0; i < Player.sleeves.length; ++i) {
if (i === sleeveNumber) { continue; }
const other = Player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
workerScript.log(`ERROR: sleeve.setToCompanyWork() failed for Sleeve ${sleeveNumber} because Sleeve ${i} is doing the same task`);
return false;
}
}
return Player.sleeves[sleeveNumber].workForCompany(Player, companyName);
},
setToFactionWork : function(sleeveNumber=0, factionName="", workType="") {
if (workerScript.checkingRam) {
return updateStaticRam("setToFactionWork", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToFactionWork() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToFactionWork", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToFactionWork(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < Player.sleeves.length; ++i) {
if (i === sleeveNumber) { continue; }
const other = Player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
workerScript.log(`ERROR: sleeve.setToFactionWork() failed for Sleeve ${sleeveNumber} because Sleeve ${i} is doing the same task`);
return false;
}
}
return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType);
},
setToGymWorkout : function(sleeveNumber=0, gymName="", stat="") {
if (workerScript.checkingRam) {
return updateStaticRam("setToGymWorkout", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "setToGymWorkout() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("setToGymWorkout", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.setToGymWorkout(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat);
},
getSleeveStats : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getStats() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("workoutAtGym", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.workoutAtGym(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
const sl = Player.sleeves[sleeveNumber];
return {
shock: 100 - sl.shock,
sync: sl.sync,
hacking_skill: sl.hacking_skill,
strength: sl.strength,
defense: sl.defense,
dexterity: sl.dexterity,
agility: sl.agility,
charisma: sl.charisma,
};
},
getTask : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getTask() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.getTask(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
const sl = Player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getInformation() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.getInformation(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
const sl = Player.sleeves[sleeveNumber];
return {
city: sl.city,
hp: sl.hp,
jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(Player.jobs),
maxHp: sl.max_hp,
tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used.
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves : {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer : {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask : {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(),
}
},
getSleeveAugmentations : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("getSleeveAugmentations", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getSleeveAugmentations() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getSleeveAugmentations", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.getSleeveAugmentations(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return [];
}
const augs = [];
for (let i = 0; i < Player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(Player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
getSleevePurchasableAugs : function(sleeveNumber=0) {
if (workerScript.checkingRam) {
return updateStaticRam("getSleevePurchasableAugs", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "getSleevePurchasableAugs() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("getSleevePurchasableAugs", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.getSleevePurchasableAugs(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return [];
}
const purchasableAugs = findSleevePurchasableAugs(Player.sleeves[sleeveNumber], Player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.startingCost,
});
}
return augs;
},
purchaseSleeveAug : function(sleeveNumber=0, augName="") {
if (workerScript.checkingRam) {
return updateStaticRam("purchaseSleeveAug", CONSTANTS.ScriptSleeveBaseRamCost);
}
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
throw makeRuntimeRejectMsg(workerScript, "purchaseSleeveAug() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
}
updateDynamicRam("purchaseSleeveAug", CONSTANTS.ScriptSleeveBaseRamCost);
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
workerScript.log(`ERROR: sleeve.purchaseSleeveAug(${sleeveNumber}) failed because it is an invalid sleeve number.`);
return false;
}
const aug = Augmentations[augName];
if (!aug) {
workerScript.log(`ERROR: sleeve.purchaseSleeveAug(${sleeveNumber}) failed because ${augName} is not a valid aug.`);
}
return Player.sleeves[sleeveNumber].tryBuyAugmentation(Player, aug);
}
} // End sleeve
} //End return } //End return
} //End NetscriptFunction() } //End NetscriptFunction()

@ -12,6 +12,8 @@ import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugment
import { Company } from "../Company/Company"; import { Company } from "../Company/Company";
import { CompanyPosition } from "../Company/CompanyPosition"; import { CompanyPosition } from "../Company/CompanyPosition";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
import { HacknetNode } from "../Hacknet/HacknetNode";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { LocationName } from "../Locations/data/LocationNames"; import { LocationName } from "../Locations/data/LocationNames";
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile"; import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
import { MoneySourceTracker } from "../utils/MoneySourceTracker"; import { MoneySourceTracker } from "../utils/MoneySourceTracker";
@ -26,7 +28,7 @@ export interface IPlayer {
corporation: any; corporation: any;
currentServer: string; currentServer: string;
factions: string[]; factions: string[];
hacknetNodes: any[]; hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
hasWseAccount: boolean; hasWseAccount: boolean;
jobs: IMap<string>; jobs: IMap<string>;
karma: number; karma: number;

@ -105,7 +105,7 @@ export abstract class Person {
/** /**
* City that the person is in * City that the person is in
*/ */
city: string = CityName.Sector12; city: CityName = CityName.Sector12;
constructor() {} constructor() {}

@ -96,7 +96,7 @@ export function createResleevesPage(p: IPlayer) {
display: "inline-block", display: "inline-block",
innerText: "Sort By: " innerText: "Sort By: "
}); });
UIElems.sortSelector = createElement("select") as HTMLSelectElement; UIElems.sortSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
enum SortOption { enum SortOption {
Cost = "Cost", Cost = "Cost",
@ -309,7 +309,7 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
elems.statsPanel.appendChild(elems.multipliersButton); elems.statsPanel.appendChild(elems.multipliersButton);
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" }); elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement; elems.augSelector = createElement("select", { class: "resleeve-aug-selector dropdown" }) as HTMLSelectElement;
elems.augDescription = createElement("p"); elems.augDescription = createElement("p");
for (let i = 0; i < resleeve.augmentations.length; ++i) { for (let i = 0; i < resleeve.augmentations.length; ++i) {
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name)); elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));

@ -14,6 +14,8 @@ import { Person,
createTaskTracker } from "../Person"; createTaskTracker } from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
@ -112,13 +114,12 @@ export class Sleeve extends Person {
logs: string[] = []; logs: string[] = [];
/** /**
* Clone retains memory% of exp upon prestige. If exp would be lower than previously * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
* kept exp, nothing happens
*/ */
memory: number = 0; memory: number = 1;
/** /**
* Sleeve shock. Number between 1 and 100 * Sleeve shock. Number between 0 and 100
* Trauma/shock that comes with being in a sleeve. Experience earned * Trauma/shock that comes with being in a sleeve. Experience earned
* is multipled by shock%. This gets applied before synchronization * is multipled by shock%. This gets applied before synchronization
* *
@ -337,6 +338,31 @@ export class Sleeve extends Person {
p.gainMoney(gain); p.gainMoney(gain);
} }
/**
* Returns the cost of upgrading this sleeve's memory by a certain amount
*/
getMemoryUpgradeCost(n: number): number {
const amt = Math.round(n);
if (amt < 0) {
return 0;
}
if (this.memory + amt > 100) {
return this.getMemoryUpgradeCost(100 - this.memory);
}
const mult = 1.02;
const baseCost = 1e12;
let currCost = 0;
let currMemory = this.memory-1;
for (let i = 0; i < n; ++i) {
currCost += (Math.pow(mult, currMemory));
++currMemory;
}
return currCost * baseCost;
}
/** /**
* Gets reputation gain for the current task * Gets reputation gain for the current task
* Only applicable when working for company or faction * Only applicable when working for company or faction
@ -406,6 +432,20 @@ export class Sleeve extends Person {
} }
} }
/**
* Called on every sleeve for a Source File prestige
*/
prestige(p: IPlayer) {
this.resetTaskStatus();
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.logs = [];
this.shock = 1;
this.storedCycles = 0;
this.sync = Math.max(this.memory, 1);
this.shockRecovery(p);
}
/** /**
* Process loop * Process loop
* Returns an object containing the amount of experience that should be * Returns an object containing the amount of experience that should be
@ -612,12 +652,7 @@ export class Sleeve extends Person {
/** /**
* Travel to another City. Costs money from player * Travel to another City. Costs money from player
*/ */
travel(p: IPlayer, newCity: string): boolean { travel(p: IPlayer, newCity: CityName): boolean {
if (CityName[newCity] == null) {
console.error(`Invalid city ${newCity} passed into Sleeve.travel()`);
return false;
}
p.loseMoney(CONSTANTS.TravelCost); p.loseMoney(CONSTANTS.TravelCost);
this.city = newCity; this.city = newCity;
@ -641,8 +676,8 @@ export class Sleeve extends Person {
const company: Company | null = Companies[companyName]; const company: Company | null = Companies[companyName];
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); } if (company == null) { return false; }
if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); } if (companyPosition == null) { return false; }
this.gainRatesForTask.money = companyPosition.baseSalary * this.gainRatesForTask.money = companyPosition.baseSalary *
company.salaryMultiplier * company.salaryMultiplier *
this.work_money_mult * this.work_money_mult *
@ -684,8 +719,8 @@ export class Sleeve extends Person {
* Returns boolean indicating success * Returns boolean indicating success
*/ */
workForFaction(p: IPlayer, factionName: string, workType: string): boolean { workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
if (factionName === "") { return false; }
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) { if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
throw new Error(`Invalid Faction specified for Sleeve.workForFaction(): ${factionName}`);
return false; return false;
} }
@ -807,6 +842,25 @@ export class Sleeve extends Person {
return true; return true;
} }
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.startingCost)) {
return false;
}
p.loseMoney(aug.startingCost);
this.installAugmentation(aug);
return true;
}
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
return;
}
this.memory = Math.min(100, Math.round(this.memory + n));
}
/** /**
* Serialize the current object to a JSON save state. * Serialize the current object to a JSON save state.
*/ */
@ -815,4 +869,31 @@ export class Sleeve extends Person {
} }
} }
export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] {
// You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it.
const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name});
const availableAugs: Augmentation[] = [];
for (const facName of p.factions) {
if (facName === "Bladeburners") { continue; }
const fac: Faction | null = Factions[facName];
if (fac == null) { continue; }
for (const augName of fac.augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
if (ownedAugNames.includes(augName)) { continue; }
const aug: Augmentation | null = Augmentations[augName];
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
availableAugs.push(aug);
}
}
}
return availableAugs;
}
Reviver.constructors.Sleeve = Sleeve; Reviver.constructors.Sleeve = Sleeve;

@ -2,7 +2,7 @@
* Module for handling the UI for purchasing Sleeve Augmentations * Module for handling the UI for purchasing Sleeve Augmentations
* This UI is a popup, not a full page * This UI is a popup, not a full page
*/ */
import { Sleeve } from "./Sleeve"; import { Sleeve, findSleevePurchasableAugs } from "./Sleeve";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
@ -29,23 +29,7 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
// You can only purchase Augmentations that are actually available from // You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation // your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it. // and you must also have enough rep in that faction in order to purchase it.
const availableAugs: Augmentation[] = []; const availableAugs = findSleevePurchasableAugs(sleeve, p);
for (const facName of p.factions) {
if (facName === "Bladeburners") { continue; }
const fac: Faction | null = Factions[facName];
if (fac == null) { continue; }
for (const augName of fac.augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
if (ownedAugNames.includes(augName)) { continue; }
const aug: Augmentation | null = Augmentations[augName];
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
availableAugs.push(aug);
}
}
}
// Create popup // Create popup
const popupId = "purchase-sleeve-augs-popup"; const popupId = "purchase-sleeve-augs-popup";
@ -105,15 +89,13 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
innerHTML: innerHTML:
[ [
`<h2>${aug.name}</h2><br>`, `<h2>${aug.name}</h2><br>`,
`Cost: ${numeralWrapper.formatMoney(aug.baseCost)}<br><br>`, `Cost: ${numeralWrapper.formatMoney(aug.startingCost)}<br><br>`,
`${aug.info}` `${aug.info}`
].join(" "), ].join(" "),
padding: "2px", padding: "2px",
clickListener: () => { clickListener: () => {
if (p.canAfford(aug.baseCost)) { if (sleeve.tryBuyAugmentation(p, aug)) {
p.loseMoney(aug.baseCost); dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false);
sleeve.installAugmentation(aug);
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
removeElementById(popupId); removeElementById(popupId);
createSleevePurchaseAugsPopup(sleeve, p); createSleevePurchaseAugsPopup(sleeve, p);
} else { } else {

@ -1,48 +1,18 @@
/** /**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant * Implements the purchasing of extra Duplicate Sleeves from The Covenant,
* as well as the purchasing of upgrades (memory)
*/ */
import { Sleeve } from "./Sleeve";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat"; import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
import { createPopup,
import { dialogBoxCreate } from "../../../utils/DialogBox"; removePopup } from "../../ui/React/createPopup";
import { yesNoBoxCreate,
yesNoBoxClose,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
export const MaxSleevesFromCovenant: number = 5; export const MaxSleevesFromCovenant: number = 5;
export const BaseCostPerSleeve: number = 10e12;
export const PopupId: string = "covenant-sleeve-purchases-popup";
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) { export function createSleevePurchasesFromCovenantPopup(p: IPlayer) {
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; } const removePopupFn = removePopup.bind(null, PopupId);
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
// First sleeve purchased costs the base amount. Then, the price of
// each successive one increases by the same amount
const baseCostPerExtraSleeve: number = 10e12;
const cost: number = (p.sleevesFromCovenant + 1) * baseCostPerExtraSleeve;
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
yesBtn!.addEventListener("click", () => {
if (p.canAfford(cost)) {
p.loseMoney(cost);
p.sleevesFromCovenant += 1;
p.sleeves.push(new Sleeve(p));
yesNoBoxClose();
} else {
dialogBoxCreate("You cannot afford to purchase a Duplicate Sleeve", false);
}
});
noBtn!.addEventListener("click", () => {
yesNoBoxClose();
});
const txt = `Would you like to purchase an additional Duplicate Sleeve from The Covenant for ` +
`${numeralWrapper.formatMoney(cost)}?<br><br>` +
`These Duplicate Sleeves are permanent. You can purchase a total of 5 Duplicate ` +
`Sleeves from The Covenant`;
yesNoBoxCreate(txt);
} }

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