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

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

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

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

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

36
css/dev-menu.css Normal file

@ -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-container {
position: fixed;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1407
package-lock.json generated

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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