Merge pull request #573 from danielyxie/dev

v0.45.0
This commit is contained in:
danielyxie 2019-03-22 22:11:10 -07:00 committed by GitHub
commit 035cdb8b0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
117 changed files with 10125 additions and 7144 deletions

3
.babelrc Normal file

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-react"]
}

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

@ -47,6 +47,11 @@ button {
border: 1px solid #333;
cursor: default;
-moz-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
&:hover {
.tooltiptext,
.tooltiptexthigh,

@ -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;
}
@ -56,29 +57,33 @@
.cmpy-mgmt-industry-left-panel,
.cmpy-mgmt-industry-right-panel {
display: inline-block;
width: 45%;
height: 100%;
top: 10px;
overflow-y: auto;
overflow-x: auto;
overflow: visible;
top: 10px;
width: 45%;
}
.cmpy-mgmt-industry-overview-panel {
border: 1px solid #fff;
color: var(--my-font-color);
display: inline-block;
padding: 3px;
width: 100%;
}
.cmpy-mgmt-employee-panel {
border: 1px solid #fff;
display: block;
padding: 3px;
width: 100%;
}
.cmpy-mgmt-warehouse-panel {
border: 1px solid #fff;
display: inline-block;
padding: 3px;
width: 100%;
}
@ -115,13 +120,18 @@
background-color: #333;
}
/* Upgrades */
/* Corporation Upgrades */
.cmpy-mgmt-upgrade-container {
border: 1px solid #fff;
width: 60%;
margin: 4px;
}
.cmpy-mgmt-upgrade-header {
margin: 6px;
padding: 6px;
}
.cmpy-mgmt-upgrade-div {
display: inline-block;
border: 1px solid #fff;
@ -136,10 +146,20 @@
background-color: #333;
}
/* Industry Upgrades */
.industry-purchases-and-upgrades-header {
font-size: 14px;
margin: 2px;
padding: 2px;
}
/* Advertising */
.cmpy-mgmt-advertising-info {
font-size: $defaultFontSize * 0.75;
}
/* Research */
#corporation-research-popup-box-content {
overflow-x: visible !important;
overflow-x: auto !important;
overflow-y: auto !important;
}

@ -203,7 +203,6 @@ a:visited {
.status-text {
display: inline-block;
height: 15%;
position: fixed;
z-index: 2;
-webkit-animation: status-text 3s 1;
@ -215,10 +214,12 @@ a:visited {
#status-text {
background-color: transparent;
font-size: $defaultFontSize * 1.25;
bottom: 0;
color: #fff;
display: none;
font-size: $defaultFontSize * 1.25;
margin-right: 14px;
opacity: 0;
padding: 4px;
right: 0;
top: 0;

File diff suppressed because one or more lines are too long

41
dist/engine.css vendored

@ -270,7 +270,6 @@ a:visited {
.status-text {
display: inline-block;
height: 15%;
position: fixed;
z-index: 2;
-webkit-animation: status-text 3s 1; }
@ -280,10 +279,12 @@ a:visited {
#status-text {
background-color: transparent;
font-size: 20px;
bottom: 0;
color: #fff;
display: none;
font-size: 20px;
margin-right: 14px;
opacity: 0;
padding: 4px;
right: 0;
top: 0;
@ -489,7 +490,11 @@ button {
padding: 3px 5px;
margin: 5px;
border: 1px solid #333;
cursor: default; }
cursor: default;
-moz-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none; }
.a-link-button-inactive:hover .tooltiptext,
.a-link-button-inactive:hover .tooltiptexthigh,
.a-link-button-inactive:hover .tooltiptextleft,
@ -1968,7 +1973,8 @@ button {
*/
#cmpy-mgmt-container p,
#cmpy-mgmt-container a,
#cmpy-mgmt-container div {
#cmpy-mgmt-container div,
#cmpy-mgmt-container br {
font-size: 13px; }
/* Header tabs */
@ -2006,26 +2012,30 @@ button {
.cmpy-mgmt-industry-left-panel,
.cmpy-mgmt-industry-right-panel {
display: inline-block;
width: 45%;
height: 100%;
top: 10px;
overflow-y: auto;
overflow-x: auto; }
overflow-x: auto;
overflow: visible;
top: 10px;
width: 45%; }
.cmpy-mgmt-industry-overview-panel {
border: 1px solid #fff;
color: var(--my-font-color);
display: inline-block;
padding: 3px;
width: 100%; }
.cmpy-mgmt-employee-panel {
border: 1px solid #fff;
display: block;
padding: 3px;
width: 100%; }
.cmpy-mgmt-warehouse-panel {
border: 1px solid #fff;
display: inline-block;
padding: 3px;
width: 100%; }
/* Hiring new employees */
@ -2055,12 +2065,16 @@ button {
.cmpy-mgmt-existing-export:hover {
background-color: #333; }
/* Upgrades */
/* Corporation Upgrades */
.cmpy-mgmt-upgrade-container {
border: 1px solid #fff;
width: 60%;
margin: 4px; }
.cmpy-mgmt-upgrade-header {
margin: 6px;
padding: 6px; }
.cmpy-mgmt-upgrade-div {
display: inline-block;
border: 1px solid #fff;
@ -2073,11 +2087,20 @@ button {
.cmpy-mgmt-upgrade-div:hover {
background-color: #333; }
/* Industry Upgrades */
.industry-purchases-and-upgrades-header {
font-size: 14px;
margin: 2px;
padding: 2px; }
/* Advertising */
.cmpy-mgmt-advertising-info {
font-size: 12px; }
/* Research */
#corporation-research-popup-box-content {
overflow-x: visible !important; }
overflow-x: auto !important;
overflow-y: auto !important; }
/* COLORS */
/* Attributes */

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,20 @@ 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.
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) |

@ -7,6 +7,11 @@ buy and sell stocks in order to make money.
The WSE can be found in the 'City' tab, and is accessible in every city.
Automating the Stock Market
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can write scripts to perform automatic and algorithmic trading on the Stock Market.
See :ref:`netscript_tixapi` for more details.
Positions: Long vs Short
^^^^^^^^^^^^^^^^^^^^^^^^
When making a transaction on the stock market, there are two types of positions:

@ -3,12 +3,41 @@
Changelog
=========
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:
** You can now purchase Augmentations for your Duplicate Sleeves
** Sleeves are now assigned to Shock Recovery task by default
** Shock Recovery and Synchronize tasks are now twice as effective
* You can now purchase Augmentations for your Duplicate Sleeves
* Sleeves are now assigned to Shock Recovery task by default
* Shock Recovery and Synchronize tasks are now twice as effective
* Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing
* Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead

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

@ -5,7 +5,7 @@ Netscript
Netscript is the programming language used in the world of Bitburner.
When you write scripts in Bitburner, they are written in the Netscript language.
Netscript is simply a subset of `JavaScript <https://developer.mozilla.org/en-US/docs/Web/JavaScript>`_,.
Netscript is simply a subset of `JavaScript <https://developer.mozilla.org/en-US/docs/Web/JavaScript>`_.
This means that Netscript's syntax is
identical to that of JavaScript, but it does not implement some of the features
that JavaScript has.
@ -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.

@ -10,3 +10,4 @@ getActionCountRemaining() Netscript Function
Note that this is meant to be used for Contracts and Operations.
This function will return 'Infinity' for actions such as Training and Field Analysis.
This function will return 1 for BlackOps not yet completed regardless of wether the player has the required rank to attempt the mission or not.

@ -0,0 +1,10 @@
getBlackOpRank() Netscript Function
====================================
.. js:function:: getBlackOpRank(name)
:param string name: name of the BlackOp. Must be an exact match.
Returns the rank required to complete this BlackOp.
Returns -1 if an invalid action is specified.

@ -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
@ -46,6 +46,7 @@ In :ref:`netscriptjs`::
setActionAutolevel() <bladeburnerapi/setActionAutolevel>
setActionLevel() <bladeburnerapi/setActionLevel>
getRank() <bladeburnerapi/getRank>
getBlackOpRank() <bladeburnerapi/getBlackOpRank>
getSkillPoints() <bladeburnerapi/getSkillPoints>
getSkillLevel() <bladeburnerapi/getSkillLevel>
getSkillUpgradeCost() <bladeburnerapi/getSkillUpgradeCost>

@ -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,21 @@
getSleeveStats() Netscript Function
===================================
.. js:function:: getStatus(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-1],
sync: current sync of the sleeve [0-1],
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,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.

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

526
package-lock.json generated

@ -4,6 +4,347 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"dev": true,
"requires": {
"@babel/highlight": "7.0.0"
}
},
"@babel/core": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz",
"integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==",
"dev": true,
"requires": {
"@babel/code-frame": "7.0.0",
"@babel/generator": "7.3.4",
"@babel/helpers": "7.3.1",
"@babel/parser": "7.3.4",
"@babel/template": "7.2.2",
"@babel/traverse": "7.3.4",
"@babel/types": "7.3.4",
"convert-source-map": "1.6.0",
"debug": "4.1.1",
"json5": "2.1.0",
"lodash": "4.17.11",
"resolve": "1.5.0",
"semver": "5.5.0",
"source-map": "0.5.7"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "2.1.1"
}
},
"json5": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
"integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
"dev": true,
"requires": {
"minimist": "1.2.0"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
}
},
"@babel/generator": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz",
"integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==",
"dev": true,
"requires": {
"@babel/types": "7.3.4",
"jsesc": "2.5.2",
"lodash": "4.17.11",
"source-map": "0.5.7",
"trim-right": "1.0.1"
},
"dependencies": {
"jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
}
},
"@babel/helper-builder-react-jsx": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz",
"integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==",
"dev": true,
"requires": {
"@babel/types": "7.3.4",
"esutils": "2.0.2"
}
},
"@babel/helper-function-name": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
"integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
"dev": true,
"requires": {
"@babel/helper-get-function-arity": "7.0.0",
"@babel/template": "7.2.2",
"@babel/types": "7.3.4"
}
},
"@babel/helper-get-function-arity": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
"integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
"dev": true,
"requires": {
"@babel/types": "7.3.4"
}
},
"@babel/helper-plugin-utils": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
"integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
"dev": true
},
"@babel/helper-split-export-declaration": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz",
"integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==",
"dev": true,
"requires": {
"@babel/types": "7.3.4"
}
},
"@babel/helpers": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz",
"integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==",
"dev": true,
"requires": {
"@babel/template": "7.2.2",
"@babel/traverse": "7.3.4",
"@babel/types": "7.3.4"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"esutils": "2.0.2",
"js-tokens": "4.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.4.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
}
}
},
"@babel/parser": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz",
"integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==",
"dev": true
},
"@babel/plugin-syntax-jsx": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz",
"integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0"
}
},
"@babel/plugin-transform-react-display-name": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz",
"integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0"
}
},
"@babel/plugin-transform-react-jsx": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz",
"integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==",
"dev": true,
"requires": {
"@babel/helper-builder-react-jsx": "7.3.0",
"@babel/helper-plugin-utils": "7.0.0",
"@babel/plugin-syntax-jsx": "7.2.0"
}
},
"@babel/plugin-transform-react-jsx-self": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz",
"integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0",
"@babel/plugin-syntax-jsx": "7.2.0"
}
},
"@babel/plugin-transform-react-jsx-source": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz",
"integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0",
"@babel/plugin-syntax-jsx": "7.2.0"
}
},
"@babel/preset-react": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz",
"integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "7.0.0",
"@babel/plugin-transform-react-display-name": "7.2.0",
"@babel/plugin-transform-react-jsx": "7.3.0",
"@babel/plugin-transform-react-jsx-self": "7.2.0",
"@babel/plugin-transform-react-jsx-source": "7.2.0"
}
},
"@babel/template": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz",
"integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==",
"dev": true,
"requires": {
"@babel/code-frame": "7.0.0",
"@babel/parser": "7.3.4",
"@babel/types": "7.3.4"
}
},
"@babel/traverse": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz",
"integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==",
"dev": true,
"requires": {
"@babel/code-frame": "7.0.0",
"@babel/generator": "7.3.4",
"@babel/helper-function-name": "7.1.0",
"@babel/helper-split-export-declaration": "7.0.0",
"@babel/parser": "7.3.4",
"@babel/types": "7.3.4",
"debug": "4.1.1",
"globals": "11.3.0",
"lodash": "4.17.11"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "2.1.1"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
}
}
},
"@babel/types": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz",
"integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==",
"dev": true,
"requires": {
"esutils": "2.0.2",
"lodash": "4.17.11",
"to-fast-properties": "2.0.0"
},
"dependencies": {
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"dev": true
}
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -25,6 +366,28 @@
"resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz",
"integrity": "sha512-ShHzHkYD+Ldw3eyttptCpUhF1/mkInWwasQkCNXZHOsJMJ/UMa8wXrxSrTJaVk0r4pLK/VnESVM0wFsfQzNEKQ=="
},
"@types/prop-types": {
"version": "15.7.0",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.0.tgz",
"integrity": "sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg=="
},
"@types/react": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.6.tgz",
"integrity": "sha512-bN9qDjEMltmHrl0PZRI4IF2AbB7V5UlRfG+OOduckVnRQ4VzXVSzy/1eLAh778IEqhTnW0mmgL9yShfinNverA==",
"requires": {
"@types/prop-types": "15.7.0",
"csstype": "2.6.2"
}
},
"@types/react-dom": {
"version": "16.8.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.2.tgz",
"integrity": "sha512-MX7n1wq3G/De15RGAAqnmidzhr2Y9O/ClxPxyqaNg96pGyeXUYPSvujgzEVpLo9oIP4Wn1UETl+rxTN02KEpBw==",
"requires": {
"@types/react": "16.8.6"
}
},
"@webassemblyjs/ast": {
"version": "1.5.12",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.12.tgz",
@ -642,6 +1005,83 @@
"js-tokens": "3.0.2"
}
},
"babel-loader": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz",
"integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==",
"dev": true,
"requires": {
"find-cache-dir": "2.0.0",
"loader-utils": "1.1.0",
"mkdirp": "0.5.1",
"util.promisify": "1.0.0"
},
"dependencies": {
"find-cache-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz",
"integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==",
"dev": true,
"requires": {
"commondir": "1.0.1",
"make-dir": "1.2.0",
"pkg-dir": "3.0.0"
}
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "3.0.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "3.0.0",
"path-exists": "3.0.0"
}
},
"p-limit": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
"integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
"dev": true,
"requires": {
"p-try": "2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "2.2.0"
}
},
"p-try": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
"dev": true
},
"pkg-dir": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
"integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
"dev": true,
"requires": {
"find-up": "3.0.0"
}
}
}
},
"bail": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz",
@ -2034,6 +2474,15 @@
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"dev": true
},
"convert-source-map": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
"integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
"dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
@ -2309,6 +2758,11 @@
}
}
},
"csstype": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.2.tgz",
"integrity": "sha512-Rl7PvTae0pflc1YtxtKbiSqq20Ts6vpIYOD5WBafl4y123DyHUeLrRdQP66sQW8/6gmX8jrYJLXwNeMqYVJcow=="
},
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -5261,8 +5715,7 @@
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.6.1",
@ -5940,6 +6393,14 @@
"integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==",
"dev": true
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "3.0.2"
}
},
"loud-rejection": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
@ -6800,8 +7261,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@ -8192,6 +8652,16 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "1.4.0",
"object-assign": "4.1.1",
"react-is": "16.8.3"
}
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@ -8402,6 +8872,33 @@
"integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=",
"dev": true
},
"react": {
"version": "16.8.3",
"resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz",
"integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==",
"requires": {
"loose-envify": "1.4.0",
"object-assign": "4.1.1",
"prop-types": "15.7.2",
"scheduler": "0.13.3"
}
},
"react-dom": {
"version": "16.8.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz",
"integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==",
"requires": {
"loose-envify": "1.4.0",
"object-assign": "4.1.1",
"prop-types": "15.7.2",
"scheduler": "0.13.3"
}
},
"react-is": {
"version": "16.8.3",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz",
"integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA=="
},
"readable-stream": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz",
@ -9179,6 +9676,15 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
"scheduler": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz",
"integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==",
"requires": {
"loose-envify": "1.4.0",
"object-assign": "4.1.1"
}
},
"schema-utils": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz",
@ -10892,6 +11398,12 @@
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
"dev": true
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
"to-object-path": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
@ -10962,6 +11474,12 @@
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
"dev": true
},
"trim-right": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true
},
"trim-trailing-lines": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz",

@ -7,6 +7,8 @@
},
"dependencies": {
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
"acorn": "^5.0.0",
"acorn-dynamic-import": "^2.0.0",
"ajv": "^5.1.5",
@ -31,6 +33,8 @@
"memory-fs": "~0.4.1",
"normalize.css": "^8.0.0",
"numeral": "2.0.6",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"sprintf-js": "^1.1.1",
"tapable": "^1.0.0",
"uglifyjs-webpack-plugin": "^1.2.5",
@ -39,6 +43,9 @@
},
"description": "A cyberpunk-themed incremental game",
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"beautify-lint": "^1.0.3",
"benchmark": "^2.1.1",
"bundle-loader": "~0.5.0",

@ -1,7 +1,7 @@
import {workerScripts,
killWorkerScript} from "./NetscriptWorker";
import {Player} from "./Player";
import {getServer} from "./Server";
import { Player } from "./Player";
import { getServer } from "./Server/ServerHelpers";
import {numeralWrapper} from "./ui/numeralFormat";
import {dialogBoxCreate} from "../utils/DialogBox";
import {createAccordionElement} from "../utils/uiHelpers/createAccordionElement";

@ -12,9 +12,9 @@ import { addWorkerScript } from "../NetscriptWorker";
import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige";
import { saveObject } from "../SaveObject";
import { Script,
RunningScript} from "../Script";
import { Server } from "../Server";
import { RunningScript } from "../Script/RunningScript";
import { Script } from "../Script/Script";
import { Server } from "../Server/Server";
import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings";
@ -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)) {

@ -258,6 +258,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.FactionPassiveRepGain = 0;
break;
case 3: //Corporatocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.8;
BitNodeMultipliers.RepToDonateToFaction = 0.5;
BitNodeMultipliers.AugmentationRepCost = 3;
BitNodeMultipliers.AugmentationMoneyCost = 3;
@ -268,6 +269,8 @@ 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
BitNodeMultipliers.ServerMaxMoney = 0.15;
@ -363,6 +366,7 @@ 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.ServerMaxMoney = 0.1;

@ -1417,7 +1417,7 @@ Bladeburner.prototype.completeAction = function() {
break;
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
this.stamina = Math.min(this.maxStamina, this.stamina + HrcStaminaGain);
this.startAction(this.action);
if (this.logging.general) {
this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`);
@ -2641,7 +2641,7 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
display:"inline-block",
innerHTML:action.desc + "\n\n" +
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
"Time Required(s): " + formatNumber(actionTime, 1) + "\n" +
"Time Required(s): " + formatNumber(actionTime, 0) + "\n" +
"Operations remaining: " + Math.floor(action.count) + "\n" +
"Successes: " + action.successes + "\n" +
"Failures: " + action.failures,
@ -2761,7 +2761,7 @@ Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
el.appendChild(createElement("p", {
display:"inline-block",
innerHTML:"Estimated Success Chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
"Time Required(s): " + formatNumber(actionTime, 1),
"Time Required(s): " + formatNumber(actionTime, 0),
}))
}
@ -3953,7 +3953,7 @@ function initBladeburner() {
GeneralActions[actionName] = new Action({
name: actionName,
desc: "Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " +
"This will slowly heal your wounds and slightly increase your stamina gain.<br><br>",
"This will slowly heal your wounds and slightly increase your stamina.<br><br>",
});
//Black Operations

@ -1,59 +0,0 @@
import {Player} from "./Player";
import {numeralWrapper} from "./ui/numeralFormat";
function CharacterOverview() {
this.hp = document.getElementById("character-hp-text");
this.money = document.getElementById("character-money-text");
this.hack = document.getElementById("character-hack-text");
this.str = document.getElementById("character-str-text");
this.def = document.getElementById("character-def-text");
this.dex = document.getElementById("character-dex-text");
this.agi = document.getElementById("character-agi-text");
this.cha = document.getElementById("character-cha-text");
this.int = document.getElementById("character-int-text");
this.intWrapper = document.getElementById("character-int-wrapper");
this.repaintElem = document.getElementById("character-overview-text");
}
CharacterOverview.prototype.repaint = function() {
// this is an arbitrary function we can call to trigger a repaint.
this.repaintElem.getClientRects();
}
CharacterOverview.prototype.update = function() {
if (Player.hp == null) {Player.hp = Player.max_hp;}
const replaceAndChanged = function(elem, text) {
if(elem.textContent === text) {
return false;
}
elem.textContent = text;
return true;
}
let changed = false;
changed = replaceAndChanged(this.hp, Player.hp + " / " + Player.max_hp) || changed;
changed = replaceAndChanged(this.money, numeralWrapper.format(Player.money.toNumber(), '$0.000a')) || changed;
changed = replaceAndChanged(this.hack, (Player.hacking_skill).toLocaleString()) || changed;
changed = replaceAndChanged(this.str, (Player.strength).toLocaleString()) || changed;
changed = replaceAndChanged(this.def, (Player.defense).toLocaleString()) || changed;
changed = replaceAndChanged(this.dex, (Player.dexterity).toLocaleString()) || changed;
changed = replaceAndChanged(this.agi, (Player.agility).toLocaleString()) || changed;
changed = replaceAndChanged(this.cha, (Player.charisma).toLocaleString()) || changed;
changed = replaceAndChanged(this.int, (Player.intelligence).toLocaleString()) || changed;
// handle int appearing
const int = this.intWrapper;
const old = int.style.display;
const now = Player.intelligence >= 1 ? "" : "none";
if(old !== now) {
int.style.display = now;
changed = true;
}
// recalculate box size if something changed
if(changed) this.repaint();
}
export {CharacterOverview};

@ -3,8 +3,8 @@ import { CodingContract,
CodingContractTypes } from "./CodingContracts";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { GetServerByHostname,
AllServers } from "./Server";
import { AllServers } from "./Server/AllServers";
import { GetServerByHostname } from "./Server/ServerHelpers";
import { getRandomInt } from "../utils/helpers/getRandomInt";

@ -1,7 +1,7 @@
import {IMap} from "./types";
export let CONSTANTS: IMap<any> = {
Version: "0.44.1",
Version: "0.45.0",
//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
@ -85,11 +85,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,
@ -281,17 +282,33 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.44.1
* Duplicate Sleeve changes:
** You can now purchase Augmentations for your Duplicate Sleeves
** Sleeves are now assigned to Shock Recovery task by default
** Shock Recovery and Synchronize tasks are now twice as effective
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
** 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
* Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing
* Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead
* Bug Fix: 'rm' Terminal and Netscript commands now work on non-program files that have '.exe' in the name (by Github user MasonD)
* Bug Fix: The 'Find All Valid Math Expressions' Coding Contract should now properly ignore whitespace in answers
* Bug Fix: The 'Merge Overlapping Intervals' Coding Contract should now properly accept 2D arrays when being attempted through Netscript
* 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
`
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,5 +1,6 @@
import { ResearchTree } from "./ResearchTree";
import { getBaseResearchTreeCopy } from "./data/BaseResearchTree";
import { getBaseResearchTreeCopy,
getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree";
import { numeralWrapper } from "../ui/numeralFormat";
@ -112,15 +113,15 @@ export let IndustryResearchTrees: IIndustryMap<ResearchTree> = {
Agriculture: getBaseResearchTreeCopy(),
Fishing: getBaseResearchTreeCopy(),
Mining: getBaseResearchTreeCopy(),
Food: getBaseResearchTreeCopy(),
Tobacco: getBaseResearchTreeCopy(),
Food: getProductIndustryResearchTreeCopy(),
Tobacco: getProductIndustryResearchTreeCopy(),
Chemical: getBaseResearchTreeCopy(),
Pharmaceutical: getBaseResearchTreeCopy(),
Computer: getBaseResearchTreeCopy(),
Robotics: getBaseResearchTreeCopy(),
Software: getBaseResearchTreeCopy(),
Healthcare: getBaseResearchTreeCopy(),
RealEstate: getBaseResearchTreeCopy(),
Pharmaceutical: getProductIndustryResearchTreeCopy(),
Computer: getProductIndustryResearchTreeCopy(),
Robotics: getProductIndustryResearchTreeCopy(),
Software: getProductIndustryResearchTreeCopy(),
Healthcare: getProductIndustryResearchTreeCopy(),
RealEstate: getProductIndustryResearchTreeCopy(),
}
export function resetIndustryResearchTrees() {

@ -12,7 +12,6 @@ export class Material {
return Generic_fromJSON(Material, value.data);
}
// Name of material
name: string = "InitName";
@ -64,6 +63,11 @@ export class Material {
prdman: any[] = [false, 0]; // Production
sllman: any[] = [false, 0]; // Sale
// 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; }
this.init();

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

@ -4,8 +4,10 @@ import { ProductRatingWeights,
IProductRatingWeight } from "./ProductRatingWeights";
import { Cities } from "../Locations/Cities";
import { createCityMap } from "../Locations/createCityMap";
import { IMap } from "../types";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
@ -89,14 +91,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 +108,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 +149,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,7 @@ export interface IConstructorParams {
employeeEffMult?: number;
employeeIntMult?: number;
productionMult?: number;
productProductionMult?: number;
salesMult?: number;
sciResearchMult?: number;
storageMult?: number;
@ -30,6 +31,7 @@ export class Research {
employeeEffMult: number = 1;
employeeIntMult: number = 1;
productionMult: number = 1;
productProductionMult: number = 1;
salesMult: number = 1;
sciResearchMult: number = 1;
storageMult: number = 1;
@ -38,14 +40,15 @@ export class Research {
this.name = p.name;
this.cost = p.cost;
this.desc = p.desc;
if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; }
if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; }
if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; }
if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; }
if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; }
if (p.productionMult) { this.productionMult = p.productionMult; }
if (p.salesMult) { this.salesMult = p.salesMult; }
if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; }
if (p.storageMult) { this.storageMult = p.storageMult; }
if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; }
if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; }
if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; }
if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; }
if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; }
if (p.productionMult) { this.productionMult = p.productionMult; }
if (p.productProductionMult) { this.productProductionMult = p.productProductionMult; }
if (p.salesMult) { this.salesMult = p.salesMult; }
if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; }
if (p.storageMult) { this.storageMult = p.storageMult; }
}
}

@ -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>` +
@ -185,6 +187,10 @@ export class ResearchTree {
return this.getMultiplierHelper("productionMult");
}
getProductProductionMultiplier(): number {
return this.getMultiplierHelper("productProductionMult");
}
getSalesMultiplier(): number {
return this.getMultiplierHelper("salesMult");
}

@ -0,0 +1,115 @@
import { Material } from "./Material";
import { MaterialSizes } from "./MaterialSizes";
import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
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 {
return Generic_fromJSON(Warehouse, value.data);
}
// Text that describes how the space in this Warehouse is being used
// Used to create a tooltip in the UI
breakdown: string = "";
// Warehouse's level, which affects its maximum size
level: number = 1;
// City that this Warehouse is in
loc: string;
// Map of Materials held by this Warehouse
materials: IMap<Material>;
// Maximum amount warehouse can hold
size: number;
// Amount of space currently used by warehouse
sizeUsed: number = 0;
// 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
smartSupplyStore: number = 0;
constructor(params: IConstructorParams = {}) {
this.loc = params.loc ? params.loc : "";
this.size = params.size ? params.size : 0;
this.materials = {
Water: new Material({name: "Water"}),
Energy: new Material({name: "Energy"}),
Food: new Material({name: "Food"}),
Plants: new Material({name: "Plants"}),
Metal: new Material({name: "Metal"}),
Hardware: new Material({name: "Hardware"}),
Chemicals: new Material({name: "Chemicals"}),
Drugs: new Material({name: "Drugs"}),
Robots: new Material({name: "Robots"}),
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
updateMaterialSizeUsed() {
this.sizeUsed = 0;
this.breakdown = "";
for (const matName in this.materials) {
var mat = this.materials[matName];
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.0") + "<br>");
}
}
}
if (this.sizeUsed > this.size) {
console.warn("Warehouse size used greater than capacity, something went wrong");
}
}
updateSize(corporation: IParent, industry: IParent) {
try {
this.size = (this.level * 100)
* corporation.getStorageMultiplier()
* industry.getStorageMultiplier();
} catch(e) {
exceptionAlert(e);
}
}
// Serialize the current object to a JSON save state.
toJSON(): any {
return Generic_toJSON("Warehouse", this);
}
}
Reviver.constructors.Warehouse = Warehouse;

@ -14,14 +14,14 @@ function makeNode(name: string): Node {
return new Node({ text: research.name, cost: research.cost });
}
export function getBaseResearchTreeCopy(): ResearchTree {
const baseResearchTree: ResearchTree = new ResearchTree();
// Creates the Nodes for the BaseResearchTree.
// Return the Root Node
function createBaseResearchTreeNodes(): Node {
const rootNode: Node = makeNode("Hi-Tech R&D Laboratory");
const autoBrew: Node = makeNode("AutoBrew");
const autoParty: Node = makeNode("AutoPartyManager");
const autoDrugs: Node = makeNode("Automatic Drug Administration");
const bulkPurchasing: Node = makeNode("Bulk Purchasing");
const cph4: Node = makeNode("CPH4 Injections");
const drones: Node = makeNode("Drones");
const dronesAssembly: Node = makeNode("Drones - Assembly");
@ -47,13 +47,39 @@ export function getBaseResearchTreeCopy(): ResearchTree {
rootNode.addChild(autoBrew);
rootNode.addChild(autoParty);
rootNode.addChild(autoDrugs);
rootNode.addChild(bulkPurchasing);
rootNode.addChild(drones);
rootNode.addChild(joywire);
rootNode.addChild(marketta1);
rootNode.addChild(overclock);
rootNode.addChild(scAssemblers);
baseResearchTree.setRoot(rootNode);
return rootNode;
}
export function getBaseResearchTreeCopy(): ResearchTree {
const baseResearchTree: ResearchTree = new ResearchTree();
baseResearchTree.setRoot(createBaseResearchTreeNodes());
return baseResearchTree;
}
// Base Research Tree for Industry's that make products
export function getProductIndustryResearchTreeCopy(): ResearchTree {
const researchTree: ResearchTree = new ResearchTree();
const root = createBaseResearchTreeNodes();
const upgradeFulcrum = makeNode("uPgrade: Fulcrum");
const upgradeCapacity1 = makeNode("uPgrade: Capacity.I");
const upgradeCapacity2 = makeNode("uPgrade: Capacity.II");
const upgradeDashboard = makeNode("uPgrade: Dashboard");
upgradeCapacity1.addChild(upgradeCapacity2);
upgradeFulcrum.addChild(upgradeCapacity1);
upgradeFulcrum.addChild(upgradeDashboard);
root.addChild(upgradeFulcrum);
researchTree.setRoot(root);
return researchTree;
}

@ -24,6 +24,12 @@ export const researchMetadata: IConstructorParams[] = [
desc: "Research how to automatically administer performance-enhacing drugs to all of " +
"your employees. This unlocks Drug-related Research.",
},
{
name: "Bulk Purchasing",
cost: 5e3,
desc: "Research the art of buying materials in bulk. This allows you to purchase " +
"any amount of a material instantly.",
},
{
name: "CPH4 Injections",
cost: 25e3,
@ -64,7 +70,7 @@ export const researchMetadata: IConstructorParams[] = [
},
{
name: "Hi-Tech R&D Laboratory",
cost: 10e3,
cost: 5e3,
desc: "Construct a cutting edge facility dedicated to advanced research and " +
"and development. This allows you to spend Scientific Research " +
"on powerful upgrades. It also globally increases Scientific Research " +
@ -83,15 +89,18 @@ export const researchMetadata: IConstructorParams[] = [
desc: "Develop advanced AI software that uses technical analysis to " +
"help you understand and exploit the market. This research " +
"allows you to know what price to sell your Materials/Products " +
"at in order to avoid losing sales due to having too high of a mark-up.",
"at in order to avoid losing sales due to having too high of a mark-up. " +
"It also lets you automatically use that sale price.",
},
{
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",
@ -118,7 +127,38 @@ export const researchMetadata: IConstructorParams[] = [
"control confidence and enthusiasm. This research increases the max " +
"morale of all employees by 10.",
},
{
name: "sudo.Assist",
cost: 15e3,
desc: "Develop a virtual assistant AI to handle and manage administrative " +
"issues for your corporation.",
},
{
name: "uPgrade: Capacity.I",
cost: 20e3,
desc: "Expand the industry's capacity for designing and manufacturing its " +
"various products. This increases the industry's maximum number of products " +
"by 1 (from 3 to 4).",
},
{
name: "uPgrade: Capacity.II",
cost: 30e3,
desc: "Expand the industry's capacity for designing and manufacturing its " +
"various products. This increases the industry's maximum number of products " +
"by 1 (from 4 to 5).",
},
{
name: "uPgrade: Dashboard",
cost: 5e3,
desc: "Improve the software used to manage the industry's production line " +
"for its various products. This allows you to manage the production and " +
"sale of a product before it's finished being designed.",
},
{
name: "uPgrade: Fulcrum",
cost: 10e3,
desc: "Streamline the manufacturing of this industry's various products. " +
"This research increases the production of your products by 5%",
productProductionMult: 1.05,
},
];

@ -0,0 +1,22 @@
// Base class for React Components for Corporation UI
// Contains a few helper functions that let derived classes easily
// access Corporation properties
import React from "react";
const Component = React.Component;
export class BaseReactComponent extends Component {
corp() {
return this.props.corp;
}
eventHandler() {
return this.props.eventHandler;
}
routing() {
return this.props.routing;
}
render() {}
}

@ -0,0 +1,62 @@
// React Components for the Corporation UI's City navigation tabs
// These allow player to navigate between different cities for each industry
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
export class CityTabs extends BaseReactComponent {
constructor(props) {
// An object with [key = city name] and [value = click handler]
// needs to be passed into the constructor as the "onClicks" property.
// We'll make sure that that happens here
if (props.onClicks == null) {
throw new Error(`CityTabs component constructed without onClick handlers`);
}
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);
}
renderTab(props) {
let className = "cmpy-mgmt-city-tab";
if (props.current) {
className += " current";
}
return (
<button className={className} onClick={props.onClick} key={props.key}>
{props.key}
</button>
)
}
render() {
const division = this.routing().currentDivision;
const tabs = [];
// Tabs for each city
for (const cityName in this.props.onClicks) {
tabs.push(this.renderTab({
current: this.props.city === cityName,
key: cityName,
onClick: this.props.onClicks[cityName],
}));
}
// Tab to "Expand into new City"
const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division, this.props.cityStateSetter);
tabs.push(this.renderTab({
current: false,
key: "Expand into new City",
onClick: newCityOnClick,
}));
return tabs;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,80 @@
// React Components for the Corporation UI's navigation tabs
// These are the tabs at the top of the UI that let you switch to different
// divisions, see an overview of your corporation, or create a new industry
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { overviewPage } from "./Routing";
function HeaderTab(props) {
let className = "cmpy-mgmt-header-tab";
if (props.current) {
className += " current";
}
return (
<button className={className} onClick={props.onClick}>
{props.text}
</button>
)
}
export class HeaderTabs extends BaseReactComponent {
renderTab(props) {
return (
<HeaderTab
current={props.current}
key={props.key}
onClick={props.onClick}
text={props.text}
/>
)
}
render() {
const overviewOnClick = () => {
this.routing().routeToOverviewPage();
this.corp().rerender();
}
const divisionOnClicks = {};
for (const division of this.corp().divisions) {
const name = division.name;
const onClick = () => {
this.routing().routeTo(name);
this.corp().rerender();
}
divisionOnClicks[name] = onClick;
}
return (
<div>
{
this.renderTab({
current: this.routing().isOnOverviewPage(),
key: "overview",
onClick: overviewOnClick,
text: this.corp().name,
})
}
{
this.corp().divisions.map((division) => {
return this.renderTab({
current: this.routing().isOn(division.name),
key: division.name,
onClick: divisionOnClicks[division.name],
text: division.name,
});
})
}
{
this.renderTab({
onClick: this.eventHandler().createNewIndustryPopup.bind(this.eventHandler()),
text: "Expand into new Industry"
})
}
</div>
)
}
}

@ -0,0 +1,34 @@
// React Component for managing the Corporation's Industry UI
// This Industry component does NOT include the city tabs at the top
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { IndustryOffice } from "./IndustryOffice";
import { IndustryOverview } from "./IndustryOverview";
import { IndustryWarehouse } from "./IndustryWarehouse";
export class Industry extends BaseReactComponent {
constructor(props) {
if (props.currentCity == null) {
throw new Error(`Industry component constructed without 'city' prop`);
}
super(props);
}
render() {
return (
<div>
<div className={"cmpy-mgmt-industry-left-panel"}>
<IndustryOverview {...this.props} />
<IndustryOffice {...this.props} />
</div>
<div className={"cmpy-mgmt-industry-right-panel"}>
<IndustryWarehouse {...this.props} />
</div>
</div>
)
}
}

@ -0,0 +1,609 @@
// React Component for displaying an Industry's OfficeSpace information
// (bottom-left panel in the Industry UI)
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { OfficeSpace } from "../Corporation";
import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { getSelectText } from "../../../utils/uiHelpers/getSelectData";
export class IndustryOffice extends BaseReactComponent {
constructor(props) {
super(props);
this.state = {
city: "",
division: "",
employeeManualAssignMode: false,
employee: null, // Reference to employee being referenced if in Manual Mode
numEmployees: 0,
numOperations: 0,
numEngineers: 0,
numBusiness: 0,
numManagement: 0,
numResearch: 0,
numUnassigned: 0,
numTraining: 0,
}
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) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const office = division.offices[this.props.currentCity];
if (!(office instanceof OfficeSpace)) {
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;
// Record the number of employees in each position, for NEW employees only
for (let i = this.state.numEmployees; i < office.employees.length; ++i) {
switch (office.employees[i].pos) {
case EmployeePositions.Operations:
++this.state.numOperations;
break;
case EmployeePositions.Engineer:
++this.state.numEngineers;
break;
case EmployeePositions.Business:
++this.state.numBusiness;
break;
case EmployeePositions.Management:
++this.state.numManagement;
break;
case EmployeePositions.RandD:
++this.state.numResearch;
break;
case EmployeePositions.Unassigned:
++this.state.numUnassigned;
break;
case EmployeePositions.Training:
++this.state.numTraining;
break;
default:
console.error("Unrecognized employee position: " + office.employees[i].pos);
break;
}
}
this.state.numEmployees = currentNumEmployees;
}
// Renders the "Employee Management" section of the Office UI
renderEmployeeManagement() {
this.updateEmployeeCount();
if (this.state.employeeManualAssignMode) {
return this.renderManualEmployeeManagement();
} else {
return this.renderAutomaticEmployeeManagement();
}
}
renderAutomaticEmployeeManagement() {
const division = this.routing().currentDivision; // Validated in constructor
const office = division.offices[this.props.currentCity]; // Validated in constructor
const vechain = (this.corp().unlockUpgrades[4] === 1); // Has Vechain upgrade
const switchModeOnClick = () => {
this.state.employeeManualAssignMode = true;
this.corp().rerender();
}
// Calculate average morale, happiness, and energy. Also salary
// TODO is this efficient?
let totalMorale = 0, totalHappiness = 0, totalEnergy = 0, totalSalary = 0;
for (let i = 0; i < office.employees.length; ++i) {
totalMorale += office.employees[i].mor;
totalHappiness += office.employees[i].hap;
totalEnergy += office.employees[i].ene;
totalSalary += office.employees[i].sal;
}
let avgMorale = 0, avgHappiness = 0, avgEnergy = 0;
if (office.employees.length > 0) {
avgMorale = totalMorale / office.employees.length;
avgHappiness = totalHappiness / office.employees.length;
avgEnergy = totalEnergy / office.employees.length;
}
// Helper functions for (re-)assigning employees to different positions
const assignEmployee = (to) => {
if (this.state.numUnassigned <= 0) {
console.warn("Cannot assign employee. No unassigned employees available");
return;
}
switch (to) {
case EmployeePositions.Operations:
++this.state.numOperations;
break;
case EmployeePositions.Engineer:
++this.state.numEngineers;
break;
case EmployeePositions.Business:
++this.state.numBusiness;
break;
case EmployeePositions.Management:
++this.state.numManagement;
break;
case EmployeePositions.RandD:
++this.state.numResearch;
break;
case EmployeePositions.Unassigned:
++this.state.numUnassigned;
break;
case EmployeePositions.Training:
++this.state.numTraining;
break;
default:
console.error("Unrecognized employee position: " + to);
break;
}
--this.state.numUnassigned;
office.assignEmployeeToJob(to);
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
this.corp().rerender();
}
const unassignEmployee = (from) => {
function logWarning(pos) {
console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`);
}
switch (from) {
case EmployeePositions.Operations:
if (this.state.numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numOperations;
break;
case EmployeePositions.Engineer:
if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numEngineers;
break;
case EmployeePositions.Business:
if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numBusiness;
break;
case EmployeePositions.Management:
if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numManagement;
break;
case EmployeePositions.RandD:
if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numResearch;
break;
case EmployeePositions.Unassigned:
console.warn(`Tried to unassign from the Unassigned position`);
break;
case EmployeePositions.Training:
if (this.state.numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numTraining;
break;
default:
console.error("Unrecognized employee position: " + from);
break;
}
++this.state.numUnassigned;
office.unassignEmployeeFromJob(from);
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
this.corp().rerender();
}
const positionHeaderStyle = {
fontSize: "15px",
margin: "5px 0px 5px 0px",
width: "50%",
}
const assignButtonClass = this.state.numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
const operationAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Operations);
this.corp().rerender();
}
const operationUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Operations);
this.corp().rerender();
}
const operationUnassignButtonClass = this.state.numOperations > 0 ? "std-button" : "a-link-button-inactive";
const engineerAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Engineer);
this.corp().rerender();
}
const engineerUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Engineer);
this.corp().rerender();
}
const engineerUnassignButtonClass = this.state.numEngineers > 0 ? "std-button" : "a-link-button-inactive";
const businessAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Business);
this.corp().rerender();
}
const businessUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Business);
this.corp().rerender();
}
const businessUnassignButtonClass = this.state.numBusiness > 0 ? "std-button" : "a-link-button-inactive";
const managementAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Management);
this.corp().rerender();
}
const managementUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Management);
this.corp().rerender();
}
const managementUnassignButtonClass = this.state.numManagement > 0 ? "std-button" : "a-link-button-inactive";
const rndAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.RandD);
this.corp().rerender();
}
const rndUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.RandD);
this.corp().rerender();
}
const rndUnassignButtonClass = this.state.numResearch > 0 ? "std-button" : "a-link-button-inactive";
const trainingAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Training);
this.corp().rerender();
}
const trainingUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Training);
this.corp().rerender();
}
const trainingUnassignButtonClass = this.state.numTraining > 0 ? "std-button" : "a-link-button-inactive";
return (
<div>
<button className={"std-button tooltip"} onClick={switchModeOnClick}>
Switch to Manual Mode
<span className={"tooltiptext"}>
Switch to Manual Assignment Mode, which allows you to
specify which employees should get which jobs
</span>
</button>
<p><strong>Unassigned Employees: {this.state.numUnassigned}</strong></p>
<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>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{
vechain &&
<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>
}
{
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>
}
{
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>
}
{
vechain && <br />
}
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Operations} ({this.state.numOperations})
<span className={"tooltiptext"}>
Manages supply chain operations. Improves the amount of Materials and Products you produce.
</span>
</h2>
<button className={assignButtonClass} onClick={operationAssignButtonOnClick}>+</button>
<button className={operationUnassignButtonClass} onClick={operationUnassignButtonOnClick}>-</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Engineer} ({this.state.numEngineers})
<span className={"tooltiptext"}>
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>
<button className={engineerUnassignButtonClass} onClick={engineerUnassignButtonOnClick}>-</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Business} ({this.state.numBusiness})
<span className={"tooltiptext"}>
Handles sales and finances. Improves the amount of Materials and Products you can sell.
</span>
</h2>
<button className={assignButtonClass} onClick={businessAssignButtonOnClick}>+</button>
<button className={businessUnassignButtonClass} onClick={businessUnassignButtonOnClick}>-</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Management} ({this.state.numManagement})
<span className={"tooltiptext"}>
Leads and oversees employees and office operations. Improves the effectiveness of
Engineer and Operations employees
</span>
</h2>
<button className={assignButtonClass} onClick={managementAssignButtonOnClick}>+</button>
<button className={managementUnassignButtonClass} onClick={managementUnassignButtonOnClick}>-</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.RandD} ({this.state.numResearch})
<span className={"tooltiptext"}>
Research new innovative ways to improve the company. Generates Scientific Research
</span>
</h2>
<button className={assignButtonClass} onClick={rndAssignButtonOnClick}>+</button>
<button className={rndUnassignButtonClass} onClick={rndUnassignButtonOnClick}>-</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Training} ({this.state.numTraining})
<span className={"tooltiptext"}>
Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations.
</span>
</h2>
<button className={assignButtonClass} onClick={trainingAssignButtonOnClick}>+</button>
<button className={trainingUnassignButtonClass} onClick={trainingUnassignButtonOnClick}>-</button>
</div>
)
}
renderManualEmployeeManagement() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated in constructor
const office = division.offices[this.props.currentCity]; // Validated in constructor
const switchModeOnClick = () => {
this.state.employeeManualAssignMode = false;
this.corp().rerender();
}
const employeeInfoDivStyle = {
color: "white",
margin: "4px",
padding: "4px",
}
// Employee Selector
const employees = [];
for (let i = 0; i < office.employees.length; ++i) {
employees.push(<option key={office.employees[i].name}>{office.employees[i].name}</option>)
}
const employeeSelectorOnChange = (e) => {
const name = getSelectText(e.target);
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]} 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 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;
const effEff = emp ? emp.eff * corp.getEmployeeEffMultiplier() * division.getEmployeeEffMultiplier() : 0;
return (
<div>
<button className={"std-button tooltip"} onClick={switchModeOnClick}>
Switch to Auto Mode
<span className={"tooltiptext"}>
Switch to Automatic Assignment Mode, which will automatically
assign employees to your selected jobs. You simply have to select
the number of assignments for each job
</span>
</button>
<div style={employeeInfoDivStyle}>
<select onChange={employeeSelectorOnChange}>
{employees}
</select>
{
this.state.employee != null &&
<p>
Morale: {numeralWrapper.format(this.state.employee.mor, nf)}
<br />
Happiness: {numeralWrapper.format(this.state.employee.hap, nf)}
<br />
Energy: {numeralWrapper.format(this.state.employee.ene, nf)}
<br />
Age: {numeralWrapper.format(this.state.employee.age, nf)}
<br />
Intelligence: {numeralWrapper.format(effInt, nf)}
<br />
Charisma: {numeralWrapper.format(effCha, nf)}
<br />
Experience: {numeralWrapper.format(this.state.employee.exp, nf)}
<br />
Creativity: {numeralWrapper.format(effCre, nf)}
<br />
Efficiency: {numeralWrapper.format(effEff, nf)}
<br />
Salary: {numeralWrapper.formatMoney(this.state.employee.sal)}
</p>
}
{
this.state.employee != null &&
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
{employeePositions}
</select>
}
</div>
</div>
)
}
render() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated in constructor
const office = division.offices[this.props.currentCity]; // Validated in constructor
const buttonStyle = {
fontSize: "13px",
}
// Hire Employee button
let hireEmployeeButtonClass = "tooltip";
if (office.atCapacity()) {
hireEmployeeButtonClass += " a-link-button-inactive";
} else {
hireEmployeeButtonClass += " std-button";
if (office.employees.length === 0) {
hireEmployeeButtonClass += " flashing-button";
}
}
const hireEmployeeButtonOnClick = () => {
office.findEmployees({ corporation: corp, industry: division });
}
// Autohire employee button
let autohireEmployeeButtonClass = "tooltip";
if (office.atCapacity()) {
autohireEmployeeButtonClass += " a-link-button-inactive";
} else {
autohireEmployeeButtonClass += " std-button";
}
const autohireEmployeeButtonOnClick = () => {
if (office.atCapacity()) { return; }
office.hireRandomEmployee({ corporation: corp, industry: division });
this.corp().rerender();
}
// Upgrade Office Size Button
const upgradeOfficeSizeOnClick = this.eventHandler().createUpgradeOfficeSizePopup.bind(this.eventHandler(), office);
// Throw Office Party
const throwOfficePartyOnClick = this.eventHandler().createThrowOfficePartyPopup.bind(this.eventHandler(), office);
return (
<div className={"cmpy-mgmt-employee-panel"}>
<h1 style={{ margin: "4px 0px 5px 0px" }}>Office Space</h1>
<p>Size: {office.employees.length} / {office.size} employees</p>
<button className={hireEmployeeButtonClass} onClick={hireEmployeeButtonOnClick} style={buttonStyle}>
Hire Employee
{
office.employees.length === 0 &&
<span className={"tooltiptext"}>
You'll need to hire some employees to get your operations started!
It's recommended to have at least one employee in every position
</span>
}
</button>
<button className={autohireEmployeeButtonClass} onClick={autohireEmployeeButtonOnClick} style={buttonStyle}>
Autohire Employee
<span className={"tooltiptext"}>
Automatically hires an employee and gives him/her a random name
</span>
</button>
<br />
<button className={"std-button tooltip"} onClick={upgradeOfficeSizeOnClick} style={buttonStyle}>
Upgrade size
<span className={"tooltiptext"}>
Upgrade the office's size so that it can hold more employees!
</span>
</button>
{
!division.hasResearch("AutoPartyManager") &&
<button className={"std-button tooltip"} onClick={throwOfficePartyOnClick} style={buttonStyle}>
Throw Party
<span className={"tooltiptext"}>
"Throw an office party to increase your employee's morale and happiness"
</span>
</button>
}
<br />
{this.renderEmployeeManagement()}
</div>
)
}
}

@ -0,0 +1,277 @@
// React Component for displaying an Industry's overview information
// (top-left panel in the Industry UI)
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { OfficeSpace } from "../Corporation";
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() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated inside render()
var createProductButtonText, createProductPopupText;
switch(division.type) {
case Industries.Food:
createProductButtonText = "Build Restaurant";
createProductPopupText = "Build and manage a new restaurant!"
break;
case Industries.Tobacco:
createProductButtonText = "Create Product";
createProductPopupText = "Create a new tobacco product!";
break;
case Industries.Pharmaceutical:
createProductButtonText = "Create Drug";
createProductPopupText = "Design and develop a new pharmaceutical drug!";
break;
case Industries.Computer:
case "Computer":
createProductButtonText = "Create Product";
createProductPopupText = "Design and manufacture a new computer hardware product!";
break;
case Industries.Robotics:
createProductButtonText = "Design Robot";
createProductPopupText = "Design and create a new robot or robotic system!";
break;
case Industries.Software:
createProductButtonText = "Develop Software";
createProductPopupText = "Develop a new piece of software!";
break;
case Industries.Healthcare:
createProductButtonText = "Build Hospital";
createProductPopupText = "Build and manage a new hospital!";
break;
case Industries.RealEstate:
createProductButtonText = "Develop Property";
createProductPopupText = "Develop a new piece of real estate property!";
break;
default:
createProductButtonText = "Create Product";
createProductPopupText = "Create a new product!";
return "";
}
createProductPopupText += "<br><br>To begin developing a product, " +
"first choose the city in which it will be designed. The stats of your employees " +
"in the selected city affect the properties of the finished product, such as its " +
"quality, performance, and durability.<br><br>" +
"You can also choose to invest money in the design and marketing of " +
"the product. Investing money in its design will result in a superior product. " +
"Investing money in marketing the product will help the product's sales.";
const hasMaxProducts = division.hasMaximumNumberProducts();
const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button";
const onClick = this.eventHandler().createMakeProductPopup.bind(this.eventHandler(), createProductPopupText, division);
const buttonStyle = {
margin: "6px",
display: "inline-block",
}
return (
<button className={className} onClick={onClick} style={buttonStyle}>
{createProductButtonText}
{
hasMaxProducts &&
<span className={"tooltiptext"}>
You have reached the maximum number of products: {division.getMaximumNumberProducts()}
</span>
}
</button>
)
}
renderText() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated inside render()
const vechain = (corp.unlockUpgrades[4] === 1);
const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();
const genInfo = `Industry: ${division.type} (Corp Funds: ${numeralWrapper.formatMoney(corp.funds.toNumber())})`;
const awareness = `Awareness: ${numeralWrapper.format(division.awareness, "0.000")}`;
const popularity = `Popularity: ${numeralWrapper.format(division.popularity, "0.000")}`;
let advertisingInfo = false;
let advertisingTooltip;
const advertisingFactors = division.getAdvertisingFactors();
const awarenessFac = advertisingFactors[1];
const popularityFac = advertisingFactors[2];
const ratioFac = advertisingFactors[3];
const totalAdvertisingFac = advertisingFactors[0];
if (vechain) { advertisingInfo = true; }
const revenue = `Revenue: ${numeralWrapper.formatMoney(division.lastCycleRevenue.toNumber())} / s`;
const expenses = `Expenses: ${numeralWrapper.formatMoney(division.lastCycleExpenses.toNumber())} /s`;
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. " +
"For example, Real Estate may be very effective for some Industries, " +
"but ineffective for others.<br><br>" +
"This division's production multiplier is calculated by summing " +
"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.<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 (
<div>
{genInfo}
<br /> <br />
{awareness} <br />
{popularity} <br />
{
(advertisingInfo !== false) &&
<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{numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")}
<br />
Popularity Bonus: x{numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")}
<br />
Ratio Multiplier: x{numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")}
</span>
</p>
}
{advertisingInfo}
<br /><br />
{revenue} <br />
{expenses} <br />
{profitStr}
<br /> <br />
<p className={"tooltip"}>
Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")}
<span className={"tooltiptext"}>
Production gain from owning production-boosting materials
such as hardware, Robots, AI Cores, and Real Estate
</span>
</p>
<div className={"help-tip"} onClick={productionMultHelpTipOnClick}>?</div>
<br /> <br />
<p className={"tooltip"}>
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.
</span>
</p>
<button className={"help-tip"} onClick={division.createResearchBox.bind(division)}>
Research
</button>
</div>
)
}
renderUpgrades() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated inside render()
const office = division.offices[this.props.currentCity];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
}
const upgrades = [];
for (const index in IndustryUpgrades) {
const upgrade = IndustryUpgrades[index];
// AutoBrew research disables the Coffee upgrade
if (division.hasResearch("AutoBrew") && upgrade[4] === "Coffee") { continue; }
const i = upgrade[0];
const baseCost = upgrade[1];
const priceMult = upgrade[2];
let cost = 0;
switch (i) {
case 0: //Coffee, cost is static per employee
cost = office.employees.length * baseCost;
break;
default:
cost = baseCost * Math.pow(priceMult, division.upgrades[i]);
break;
}
const onClick = () => {
if (corp.funds.lt(cost)) {
dialogBoxCreate("Insufficient funds");
} else {
corp.funds = corp.funds.minus(cost);
division.upgrade(upgrade, {
corporation: corp,
office: office,
});
// corp.displayDivisionContent(division, city);
corp.rerender();
}
}
upgrades.push(this.renderUpgrade({
onClick: onClick,
text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`,
tooltip: upgrade[5],
}));
}
return upgrades;
}
renderUpgrade(props) {
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} onClick={props.onClick} key={props.text}>
{props.text}
{
props.tooltip != null &&
<span className={"tooltiptext"}>{props.tooltip}</span>
}
</div>
)
}
render() {
const division = this.routing().currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const makeProductButton = this.renderMakeProductButton();
return (
<div className={"cmpy-mgmt-industry-overview-panel"}>
{this.renderText()}
<br />
<u className={"industry-purchases-and-upgrades-header"}>Purchases & Upgrades</u><br />
{this.renderUpgrades()} <br />
{
division.makesProducts &&
makeProductButton
}
</div>
)
}
}

@ -0,0 +1,535 @@
// 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 { OfficeSpace,
WarehouseInitialCost,
WarehouseUpgradeBaseCost,
ProductProductionCostRatio } from "../Corporation";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
import { numeralWrapper } from "../../ui/numeralFormat";
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;
// 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 = product.data[city][1] - product.data[city][2];
// Sell button
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, city);
// Limit Production button
let limitProductionButtonText = "Limit Production";
if (product.prdman[city][0]) {
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
}
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
// Discontinue Button
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><br />
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
<br />
<div>
<button className={"std-button"} onClick={sellButtonOnClick}>
{sellButtonText}
</button><br />
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
{limitProductionButtonText}
</button>
<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><br />
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
</div>
);
}
}
return (
<div className={"cmpy-mgmt-warehouse-product-div"}>
<p className={"tooltip"}>
{product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
<span className={"tooltiptext"}>
Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s
<br />
Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s
</span>
</p><br />
<p className={"tooltip"}>
Rating: {numeralWrapper.format(product.rat, nf)}
<span className={"tooltiptext"}>
Quality: {numeralWrapper.format(product.qlt, nf)} <br />
Performance: {numeralWrapper.format(product.per, nf)} <br />
Durability: {numeralWrapper.format(product.dur, nf)} <br />
Reliability: {numeralWrapper.format(product.rel, nf)} <br />
Aesthetics: {numeralWrapper.format(product.aes, nf)} <br />
Features: {numeralWrapper.format(product.fea, nf)}
{
corp.unlockUpgrades[2] === 1 && <br />
}
{
corp.unlockUpgrades[2] === 1 &&
"Demand: " + numeralWrapper.format(product.dmd, nf)
}
{
corp.unlockUpgrades[3] === 1 && <br />
}
{
corp.unlockUpgrades[3] === 1 &&
"Competition: " + numeralWrapper.format(product.cmp, nf)
}
</span>
</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><br />
<p className={"tooltip"}>
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
in more sales.
</span>
</p>
<div>
<button className={"std-button"} onClick={sellButtonOnClick}>
{sellButtonText}
</button><br />
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
{limitProductionButtonText}
</button>
<button className={"std-button"} onClick={discontinueButtonOnClick}>
Discontinue
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
Market-TA
</button>
}
</div>
</div>
)
}
// Creates the UI for a single Material type
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;
// Competition and demand info, if they're unlocked
let cmpAndDmdText = "";
if (corp.unlockUpgrades[2] === 1) {
cmpAndDmdText += "<br>Demand: " + numeralWrapper.format(mat.dmd, nf);
}
if (corp.unlockUpgrades[3] === 1) {
cmpAndDmdText += "<br>Competition: " + numeralWrapper.format(mat.cmp, nf);
}
// Flag that determines whether this industry is "new" and the current material should be
// marked with flashing-red lights
const tutorial = division.newInd && Object.keys(division.reqMats).includes(mat.name) &&
mat.buy === 0 && mat.imp === 0;
// Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`;
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
// Export material button
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
// Sell material button
let sellButtonText;
if (mat.sllman[0]) {
if (isString(mat.sllman[1])) {
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${mat.sllman[1]})`
} else {
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${numeralWrapper.format(mat.sllman[1], nf)})`;
}
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 {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost);
}
}
} else {
sellButtonText = "Sell (0.000/0.000)";
}
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
// Market TA button
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
return (
<div className={"cmpy-mgmt-warehouse-material-div"}>
<div style={{display: "inline-block"}}>
<p className={"tooltip"}>
{mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
<span className={"tooltiptext"}>
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 />
}
{
corp.unlockUpgrades[2] === 1 &&
"Demand: " + numeralWrapper.format(mat.dmd, nf)
}
{
corp.unlockUpgrades[3] === 1 && <br />
}
{
corp.unlockUpgrades[3] === 1 &&
"Competition: " + numeralWrapper.format(mat.cmp, nf)
}
</span>
</p><br />
<p className={"tooltip"}>
MP: {numeralWrapper.formatMoney(mat.bCost)}
<span className={"tooltiptext"}>
Market Price: The price you would pay if you were to buy this material on the market
</span>
</p> <br />
<p className={"tooltip"}>
Quality: {numeralWrapper.format(mat.qlt, "0.00a")}
<span className={"tooltiptext"}>
The quality of your material. Higher quality will lead to more sales
</span>
</p>
</div>
<div style={{display: "inline-block"}}>
<button className={purchaseButtonClass} onClick={purchaseButtonOnClick}>
{purchaseButtonText}
{
tutorial &&
<span className={"tooltiptext"}>
Purchase your required materials to get production started!
</span>
}
</button>
{
corp.unlockUpgrades[0] === 1 &&
<button className={"std-button"} onClick={exportButtonOnClick}>
Export
</button>
}
<br />
<button className={"std-button"} onClick={sellButtonOnClick}>
{sellButtonText}
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
Market-TA
</button>
}
</div>
</div>
)
}
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()
const warehouse = division.warehouses[this.props.currentCity]; // Validated in render()
// General Storage information at the top
const sizeUsageStyle = {
color: warehouse.sizeUsed >= warehouse.size ? "red" : "white",
margin: "5px",
}
// Upgrade Warehouse size button
const sizeUpgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
const upgradeWarehouseOnClick = () => {
++warehouse.level;
warehouse.updateSize(corp, division);
corp.funds = corp.funds.minus(sizeUpgradeCost);
corp.rerender();
return;
}
// Industry material Requirements
let generalReqsText = "This Industry uses [" + Object.keys(division.reqMats).join(", ") +
"] in order to ";
if (division.prodMats.length > 0) {
generalReqsText += "produce [" + division.prodMats.join(", ") + "] ";
if (division.makesProducts) {
generalReqsText += " and " + division.getProductDescriptionText();
}
} else if (division.makesProducts) {
generalReqsText += (division.getProductDescriptionText() + ".");
}
const ratioLines = [];
for (const matName in division.reqMats) {
if (division.reqMats.hasOwnProperty(matName)) {
const text = [" *", division.reqMats[matName], matName].join(" ");
ratioLines.push((
<div key={matName}>
<p>{text}</p>
</div>
));
}
}
let createdItemsText = "in order to create ";
if (division.prodMats.length > 0) {
createdItemsText += "one of each produced Material (" + division.prodMats.join(", ") + ") ";
if (division.makesProducts) {
createdItemsText += "or to create one of its Products";
}
} else if (division.makesProducts) {
createdItemsText += "one of its Products";
}
// Current State:
let stateText;
switch(division.state) {
case "START":
stateText = "Current state: Preparing...";
break;
case "PURCHASE":
stateText = "Current state: Purchasing materials...";
break;
case "PRODUCTION":
stateText = "Current state: Producing materials and/or products...";
break;
case "SALE":
stateText = "Current state: Selling materials and/or products...";
break;
case "EXPORT":
stateText = "Current state: Exporting materials and/or products...";
break;
default:
console.error(`Invalid state: ${division.state}`);
break;
}
// Smart Supply Checkbox
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
const smartSupplyOnChange = (e) => {
warehouse.smartSupplyEnabled = e.target.checked;
corp.rerender();
}
// Create React components for materials
const mats = [];
for (const matName in warehouse.materials) {
if (warehouse.materials[matName] instanceof Material) {
// Only create UI for materials that are relevant for the industry
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} />);
}
}
}
// Create React components for products
const products = [];
if (division.makesProducts && Object.keys(division.products).length > 0) {
for (const productName in division.products) {
if (division.products[productName] instanceof Product) {
products.push(<ProductComponent
city={this.props.currentCity}
corp={corp}
division={division}
eventHandler={this.eventHandler()}
key={productName}
product={division.products[productName]}
warehouse={warehouse} />);
}
}
}
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"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
</p>
<button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}>
Upgrade Warehouse Size - {numeralWrapper.formatMoney(sizeUpgradeCost)}
</button>
<p>{generalReqsText}. The exact requirements for production are:</p><br />
{ratioLines}<br />
<p>{createdItemsText}</p>
<p>
To get started with production, purchase your required materials
or import them from another of your company's divisions.
</p><br />
<p>{stateText}</p>
{
corp.unlockUpgrades[1] &&
<div>
<label style={{color: "white"}} htmlFor={smartSupplyCheckboxId}>
Enable Smart Supply
</label>
<input type={"checkbox"}
id={smartSupplyCheckboxId}
onChange={smartSupplyOnChange}
style={{margin: "3px"}}
checked={warehouse.smartSupplyEnabled}
/>
</div>
}
{mats}
{products}
</div>
)
}
render() {
const corp = this.corp();
const division = this.routing().currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const warehouse = division.warehouses[this.props.currentCity];
const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity);
if (warehouse instanceof Warehouse) {
return this.renderWarehouseUI();
} else {
return (
<div className={"cmpy-mgmt-warehouse-panel"}>
<button className={"std-button"} onClick={newWarehouseOnClick}>
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
</button>
</div>
)
}
}
}

@ -0,0 +1,36 @@
// React components for the levelable upgrade buttons on the overview panel
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
export class LevelableUpgrade extends BaseReactComponent {
render() {
const data = this.props.upgradeData;
const level = this.props.upgradeLevel;
const baseCost = data[1];
const priceMult = data[2];
const cost = baseCost * Math.pow(priceMult, level);
const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}`
const tooltip = data[5];
const onClick = () => {
const corp = this.corp();
if (corp.funds.lt(cost)) {
dialogBoxCreate("Insufficient funds");
} else {
corp.upgrade(data);
corp.rerender();
}
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{"width" : "45%"}} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
)
}
}

@ -0,0 +1,106 @@
// React Component for the element that contains the actual info/data
// for the Corporation UI. This panel lies below the header tabs and will
// be filled with whatever is needed based on the routing/navigation
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { CityTabs } from "./CityTabs";
import { Industry } from "./Industry";
import { Overview } from "./Overview";
import { overviewPage } from "./Routing";
import { OfficeSpace } from "../Corporation";
import { Cities } from "../../Locations/Cities";
export class MainPanel extends BaseReactComponent {
constructor(props) {
super(props);
this.state = {
division: "",
city: Cities.Sector12,
}
}
// 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()) {
// Corporation overview Content
return this.renderOverviewPage();
} else {
// Division content
// First, check if we're at a new division. If so, we need to reset the city to Sector-12
// Otherwise, just switch the 'city' state
const currentDivision = this.routing().current();
if (currentDivision !== this.state.division) {
this.state.division = currentDivision;
this.state.city = Cities.Sector12;
}
return this.renderDivisionPage();
}
}
renderOverviewPage() {
return (
<div id="cmpy-mgmt-panel">
<Overview {...this.props} />
</div>
)
}
renderDivisionPage() {
// Note: Division is the same thing as Industry...I wasn't consistent with naming
const division = this.routing().currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
// City tabs
const onClicks = {};
for (const cityName in division.offices) {
if (division.offices[cityName] instanceof OfficeSpace) {
onClicks[cityName] = () => {
this.state.city = cityName;
this.corp().rerender();
}
}
}
const cityTabs = (
<CityTabs
{...this.props}
city={this.state.city}
onClicks={onClicks}
cityStateSetter={this.changeCityState.bind(this)}
/>
)
// Rest of Industry UI
const industry = (
<Industry {...this.props} currentCity={this.state.city} />
)
return (
<div id="cmpy-mgmt-panel">
{cityTabs}
{industry}
</div>
)
}
render() {
return this.renderContent();
}
}

@ -0,0 +1,323 @@
// React Component for displaying Corporation Overview info
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeThreshold } from "../Corporation";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
import { CorporationUpgrades } from "../data/CorporationUpgrades";
import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
export class Overview extends BaseReactComponent {
// Generic Function for Creating a button
createButton(props) {
let className = props.class ? props.class : "std-button";
const displayStyle = props.display ? props.display : "block";
const hasTooltip = (props.tooltip != null);
if (hasTooltip) {
className += " tooltip";
}
return (
<a className={className} onClick={props.onClick} style={{display: {displayStyle}}}>
{props.text}
{
hasTooltip &&
<span className={"tooltiptext"}>
{props.tooltip}
</span>
}
</a>
)
}
// Returns a string with general information about Corporation
getOverviewText() {
// Formatted text for profit
var profit = this.corp().revenue.minus(this.corp().expenses).toNumber(),
profitStr = profit >= 0 ? numeralWrapper.formatMoney(profit) : "-" + numeralWrapper.format(-1 * profit, "$0.000a");
// Formatted text for dividend information, if applicable
let dividendStr = "";
if (this.corp().dividendPercentage > 0 && profit > 0) {
const totalDividends = (this.corp().dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends;
const dividendsPerShare = totalDividends / this.corp().totalShares;
const playerEarnings = this.corp().numShares * dividendsPerShare;
dividendStr = `Retained Profits (after dividends): ${numeralWrapper.format(retainedEarnings, "$0.000a")} / s<br><br>` +
`Dividend Percentage: ${numeralWrapper.format(this.corp().dividendPercentage / 100, "0%")}<br>` +
`Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s<br>` +
`Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s<br>` +
`Dividend Tax Rate: ${this.corp().dividendTaxPercentage}%<br>` +
`Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s<br>`;
}
let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + "<br>" +
"Total Revenue: " + numeralWrapper.format(this.corp().revenue.toNumber(), "$0.000a") + " / s<br>" +
"Total Expenses: " + numeralWrapper.format(this.corp().expenses.toNumber(), "$0.000a") + "/ s<br>" +
"Total Profits: " + profitStr + " / s<br>" +
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>" +
"<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>` +
`Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` +
"</span></p><br><br>";
const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle / 1000;
if (storedTime > 15) {
txt += `Bonus Time: ${storedTime} seconds<br><br>`;
}
let prodMult = this.corp().getProductionMultiplier(),
storageMult = this.corp().getStorageMultiplier(),
advMult = this.corp().getAdvertisingMultiplier(),
empCreMult = this.corp().getEmployeeCreMultiplier(),
empChaMult = this.corp().getEmployeeChaMultiplier(),
empIntMult = this.corp().getEmployeeIntMultiplier(),
empEffMult = this.corp().getEmployeeEffMultiplier(),
salesMult = this.corp().getSalesMultiplier(),
sciResMult = this.corp().getScientificResearchMultiplier();
if (prodMult > 1) {txt += "Production Multiplier: " + numeralWrapper.format(prodMult, "0.000") + "<br>";}
if (storageMult > 1) {txt += "Storage Multiplier: " + numeralWrapper.format(storageMult, "0.000") + "<br>";}
if (advMult > 1) {txt += "Advertising Multiplier: " + numeralWrapper.format(advMult, "0.000") + "<br>";}
if (empCreMult > 1) {txt += "Empl. Creativity Multiplier: " + numeralWrapper.format(empCreMult, "0.000") + "<br>";}
if (empChaMult > 1) {txt += "Empl. Charisma Multiplier: " + numeralWrapper.format(empChaMult, "0.000") + "<br>";}
if (empIntMult > 1) {txt += "Empl. Intelligence Multiplier: " + numeralWrapper.format(empIntMult, "0.000") + "<br>";}
if (empEffMult > 1) {txt += "Empl. Efficiency Multiplier: " + numeralWrapper.format(empEffMult, "0.000") + "<br>";}
if (salesMult > 1) {txt += "Sales Multiplier: " + numeralWrapper.format(salesMult, "0.000") + "<br>";}
if (sciResMult > 1) {txt += "Scientific Research Multiplier: " + numeralWrapper.format(sciResMult, "0.000") + "<br>";}
return txt;
}
// Render the buttons that lie below the overview text.
// These are mainly for things such as managing finances/stock
renderButtons() {
// Create a "Getting Started Guide" button that lets player view the
// handbook and adds it to the players home computer
const getStarterGuideOnClick = this.corp().getStarterGuide.bind(this.corp());
const getStarterGuideBtn = this.createButton({
class: "a-link-button",
display: "inline-block",
onClick: getStarterGuideOnClick,
text: "Getting Started Guide",
tooltip: "Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " +
"This is a .lit file that guides you through the beginning of setting up a Corporation and " +
"provides some tips/pointers for helping you get started with managing it.",
});
// Create a "Bribe Factions" button if your Corporation is powerful enough.
// This occurs regardless of whether you're public or private
const canBribe = (this.corp().determineValuation() >= BribeThreshold);
const bribeFactionsOnClick = this.eventHandler().createBribeFactionsPopup.bind(this.eventHandler());
const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive");
const bribeFactionsBtn = this.createButton({
class: bribeFactionsClass,
display: "inline-block",
onClick: bribeFactionsOnClick,
text: "Bribe Factions",
tooltip: (canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
: "Your Corporation is not powerful enough to bribe Faction leaders"),
});
const generalBtns = {
bribeFactions: bribeFactionsBtn,
getStarterGuide: getStarterGuideBtn,
};
if (this.corp().public) {
return this.renderPublicButtons(generalBtns);
} else {
return this.renderPrivateButtons(generalBtns);
}
}
// Render the buttons for when your Corporation is still private
renderPrivateButtons(generalBtns) {
const fundingAvailable = (this.corp().fundingRound < 4);
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : null;
const findInvestorsOnClick = this.corp().getInvestment.bind(this.corp());
const goPublicOnClick = this.corp().goPublic.bind(this.corp());
const findInvestorsBtn = this.createButton({
class: findInvestorsClassName,
onClick: findInvestorsOnClick,
style: "inline-block",
text: "Find Investors",
tooltip: findInvestorsTooltip
});
const goPublicBtn = this.createButton({
class: "std-button",
onClick: goPublicOnClick,
style: "inline-block",
text: "Go Public",
tooltip: "Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
"company, your shares will be traded on the stock market."
});
return (
<div>
{generalBtns.getStarterGuide}
{findInvestorsBtn}
{goPublicBtn}
<br />
{generalBtns.bribeFactions}
</div>
)
}
// Render the buttons for when your Corporation has gone public
renderPublicButtons(generalBtns) {
const corp = this.corp();
const sellSharesOnClick = this.eventHandler().createSellSharesPopup.bind(this.eventHandler());
const sellSharesOnCd = (corp.shareSaleCooldown > 0);
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture."
const sellSharesBtn = this.createButton({
class: sellSharesClass,
display: "inline-block",
onClick: sellSharesOnClick,
text: "Sell Shares",
tooltip: sellSharesTooltip,
});
const buybackSharesOnClick = this.eventHandler().createBuybackSharesPopup.bind(this.eventHandler());
const buybackSharesBtn = this.createButton({
class: "std-button",
display: "inline-block",
onClick: buybackSharesOnClick,
text: "Buyback shares",
tooltip: "Buy back shares you that previously issued or sold at market price.",
});
const issueNewSharesOnClick = this.eventHandler().createIssueNewSharesPopup.bind(this.eventHandler());
const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0);
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
const issueNewSharesBtn = this.createButton({
class: issueNewSharesClass,
display: "inline-block",
onClick: issueNewSharesOnClick,
text: "Issue New Shares",
tooltip: issueNewSharesTooltip,
});
const issueDividendsOnClick = this.eventHandler().createIssueDividendsPopup.bind(this.eventHandler());
const issueDividendsBtn = this.createButton({
class: "std-button",
display: "inline-block",
onClick: issueDividendsOnClick,
text: "Issue Dividends",
tooltip: "Manage the dividends that are paid out to shareholders (including yourself)",
});
return (
<div>
{generalBtns.getStarterGuide}
{sellSharesBtn}
{buybackSharesBtn}
<br />
{issueNewSharesBtn}
{issueDividendsBtn}
<br />
{generalBtns.bribeFactions}
</div>
)
}
// Render the UI for Corporation upgrades
renderUpgrades() {
// Don't show upgrades
if (this.corp().divisions.length <= 0) { return; }
// Create an array of all Unlocks
const unlockUpgrades = [];
Object.values(CorporationUnlockUpgrades).forEach((unlockData) => {
if (this.corp().unlockUpgrades[unlockData[0]] === 0) {
unlockUpgrades.push(this.renderUnlockUpgrade(unlockData));
}
});
// Create an array of properties of all unlocks
const levelableUpgradeProps = [];
for (let i = 0; i < this.corp().upgrades.length; ++i) {
const upgradeData = CorporationUpgrades[i];
const level = this.corp().upgrades[i];
levelableUpgradeProps.push({
upgradeData: upgradeData,
upgradeLevel: level,
});
}
return (
<div className={"cmpy-mgmt-upgrade-container"}>
<h1 className={"cmpy-mgmt-upgrade-header"}> Unlocks </h1>
{unlockUpgrades}
<h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1>
{
levelableUpgradeProps.map((data) => {
return this.renderLevelableUpgrade(data);
})
}
</div>
)
}
renderUnlockUpgrade(data) {
return (
<UnlockUpgrade
{...this.props}
upgradeData={data}
key={data[0]}
/>
)
}
renderLevelableUpgrade(data) {
return (
<LevelableUpgrade
{...this.props}
upgradeData={data.upgradeData}
upgradeLevel={data.upgradeLevel}
key={data.upgradeData[0]}
/>
)
}
render() {
return (
<div>
<p dangerouslySetInnerHTML={{__html: this.getOverviewText()}}></p>
{this.renderButtons()}
<br />
{this.renderUpgrades()}
</div>
)
}
}

@ -0,0 +1,17 @@
// Root React Component for the Corporation UI
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { HeaderTabs } from "./HeaderTabs";
import { MainPanel } from "./MainPanel";
export class CorporationRoot extends BaseReactComponent {
render() {
return (
<div>
<HeaderTabs {...this.props} />
<MainPanel {...this.props} />
</div>
)
}
}

@ -0,0 +1,96 @@
import { IMap } from "../../types";
export const overviewPage: string = "Overview";
// Interfaces for whatever's required to sanitize routing with Corporation Data
interface IOfficeSpace {
}
interface IDivision {
name: string;
offices: IMap<IOfficeSpace>
}
interface ICorporation {
divisions: IDivision[];
}
/**
* Keeps track of what content is currently being displayed for the Corporation UI
*/
export class CorporationRouting {
private currentPage: string = overviewPage;
// Stores a reference to the Corporation instance
private corp: ICorporation;
// Stores a reference to the Division instance that the routing is currently on
// This will be null if routing is on the overview page
currentDivision: IDivision | null = null;
constructor(corp: ICorporation) {
this.corp = corp;
}
current(): string {
return this.currentPage;
}
/**
* Checks that the specified page has a valid value
*/
isValidPage(page: string): boolean {
if (page === overviewPage) { return true; }
for (const division of this.corp.divisions) {
if (division.name === page) { return true; }
}
return false;
}
/**
* Returns a boolean indicating whether or not the player is on the given page
*/
isOn(page: string): boolean {
if (!this.isValidPage(page)) { return false; }
return page === this.currentPage;
}
isOnOverviewPage(): boolean {
return this.currentPage === overviewPage;
}
/**
* Routes to the specified page
*/
routeTo(page: string): void {
if (!this.isValidPage(page)) { return; }
this.currentDivision = null;
if (page !== overviewPage) {
// Iterate through Corporation data to get a reference to the current division
for (let i = 0; i < this.corp.divisions.length; ++i) {
if (this.corp.divisions[i].name === page) {
this.currentDivision = this.corp.divisions[i];
};
}
// 'currentDivision' should not be null, since the routing is either on
// the overview page or a division page
if (this.currentDivision == null) {
console.warn(`Routing could not find division ${page}`);
}
}
this.currentPage = page;
}
routeToOverviewPage(): void {
this.currentPage = overviewPage;
this.currentDivision = null;
}
}

@ -0,0 +1,30 @@
// React Components for the Unlock upgrade buttons on the overview page
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
export class UnlockUpgrade extends BaseReactComponent {
render() {
const data = this.props.upgradeData;
const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`;
const tooltip = data[3];
const onClick = () => {
const corp = this.corp();
if (corp.funds.lt(data[1])) {
dialogBoxCreate("Insufficient funds");
} else {
corp.unlock(data);
corp.rerender();
}
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{"width" : "45%"}} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
)
}
}

@ -1,7 +1,7 @@
import { DarkWebItems } from "./DarkWebItems";
import { Player } from "../Player";
import { SpecialServerIps } from "../SpecialServerIps";
import { SpecialServerIps } from "../Server/SpecialServerIps";
import { post } from "../ui/postToTerminal";
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";

@ -8,7 +8,7 @@ import { Company } from "./Company/Company";
import { Programs } from "./Programs/Programs";
import { Factions } from "./Faction/Factions";
import { Player } from "./Player";
import { AllServers } from "./Server";
import { AllServers } from "./Server/AllServers";
import { hackWorldDaemon } from "./RedPill";
import { StockMarket,
SymbolToStockMap } from "./StockMarket/StockMarket";
@ -494,6 +494,29 @@ export function createDevMenu() {
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"});
@ -686,6 +709,10 @@ export function createDevMenu() {
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);

@ -1,16 +1,7 @@
import {parse, Node} from "../utils/acorn";
import {dialogBoxCreate} from "../utils/DialogBox";
import { FconfSettings } from "./FconfSettings";
var FconfSettings = {
ENABLE_BASH_HOTKEYS: false,
ENABLE_TIMESTAMPS: false,
MAIN_MENU_STYLE: "default",
THEME_BACKGROUND_COLOR: "#000000",
THEME_FONT_COLOR: "#66ff33",
THEME_HIGHLIGHT_COLOR: "#ffffff",
THEME_PROMPT_COLOR: "#f92672",
WRAP_INPUT: false,
}
import { parse, Node } from "../../utils/acorn";
import { dialogBoxCreate } from "../../utils/DialogBox";
var FconfComments = {
ENABLE_BASH_HOTKEYS: "Improved Bash emulation mode. Setting this to 1 enables several\n" +

@ -0,0 +1,10 @@
export const FconfSettings = {
ENABLE_BASH_HOTKEYS: false,
ENABLE_TIMESTAMPS: false,
MAIN_MENU_STYLE: "default",
THEME_BACKGROUND_COLOR: "#000000",
THEME_FONT_COLOR: "#66ff33",
THEME_HIGHLIGHT_COLOR: "#ffffff",
THEME_PROMPT_COLOR: "#f92672",
WRAP_INPUT: false,
}

@ -1,6 +1,6 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { Player } from "./Player";
import { Server } from "./Server";
import { Server } from "./Server/Server";
/**
* Returns the chance the player has to successfully hack a server

@ -11,13 +11,16 @@ import {beginInfiltration} from "./Infiltration";
import {hasBladeburnerSF} from "./NetscriptFunctions";
import {Locations} from "./Locations";
import {Player} from "./Player";
import {Server, AllServers, AddToAllServers} from "./Server";
import { AllServers,
AddToAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server";
import { getPurchaseServerCost,
purchaseServer,
purchaseRamForHomeComputer} from "./ServerPurchases";
purchaseRamForHomeComputer } from "./Server/ServerPurchases";
import {Settings} from "./Settings/Settings";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import {SpecialServerNames, SpecialServerIps} from "./SpecialServerIps";
import { SpecialServerNames,
SpecialServerIps } from "./Server/SpecialServerIps";
import {numeralWrapper} from "./ui/numeralFormat";

@ -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.values(Cities);
for (let i = 0; i < cities.length; ++i) {
map[cities[i]] = initValue;
}
return map;
}

32
src/Message/Message.ts Normal file

@ -0,0 +1,32 @@
import { Reviver,
Generic_toJSON,
Generic_fromJSON } from "../../utils/JSONReviver";
export class Message {
// Initializes a Message Object from a JSON save state
static fromJSON(value: any): Message {
return Generic_fromJSON(Message, value.data);
}
// Name of Message file
filename: string = "";
// The text contains in the Message
msg: string = "";
// Flag indicating whether this Message has been received by the player
recvd: boolean = false;
constructor(filename="", msg="") {
this.filename = filename;
this.msg = msg;
this.recvd = false;
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("Message", this);
}
}
Reviver.constructors.Message = Message;

@ -1,34 +1,15 @@
import { Augmentatation } from "./Augmentation/Augmentation";
import { Augmentations } from "./Augmentation/Augmentations";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { Programs } from "./Programs/Programs";
import { inMission } from "./Missions";
import { Player } from "./Player";
import { redPillFlag } from "./RedPill";
import { GetServerByHostname } from "./Server";
import { Settings } from "./Settings/Settings";
import { Message } from "./Message";
import { Augmentatation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { Programs } from "../Programs/Programs";
import { inMission } from "../Missions";
import { Player } from "../Player";
import { redPillFlag } from "../RedPill";
import { GetServerByHostname } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { dialogBoxCreate,
dialogBoxOpened} from "../utils/DialogBox";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver";
/* Message.js */
function Message(filename="", msg="") {
this.filename = filename;
this.msg = msg;
this.recvd = false;
}
Message.prototype.toJSON = function() {
return Generic_toJSON("Message", this);
}
Message.fromJSON = function(value) {
return Generic_fromJSON(Message, value.data);
}
Reviver.constructors.Message = Message;
dialogBoxOpened} from "../../utils/DialogBox";
//Sends message to player, including a pop up
function sendMessage(msg, forced=false) {

@ -3,10 +3,12 @@ import { CONSTANTS } from "./Constants";
import { Player } from "./Player";
import { Environment } from "./NetscriptEnvironment";
import { WorkerScript, addWorkerScript} from "./NetscriptWorker";
import { Server, getServer} from "./Server";
import { Server } from "./Server/Server";
import { getServer } from "./Server/ServerHelpers";
import { Settings } from "./Settings/Settings";
import { Script, findRunningScript,
RunningScript } from "./Script";
import { RunningScript } from "./Script/RunningScript";
import { Script } from "./Script/Script";
import { findRunningScript } from "./Script/ScriptHelpers";
import { setTimeoutRef } from "./utils/SetTimeoutRef";

@ -28,23 +28,31 @@ import { Factions,
factionExists } from "./Faction/Factions";
import { joinFaction,
purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { getCostOfNextHacknetNode,
purchaseHacknet } from "./HacknetNode";
import {Locations} from "./Locations";
import {Message, Messages} from "./Message";
import { Message } from "./Message/Message";
import { Messages } from "./Message/MessageHelpers";
import {inMission} from "./Missions";
import {Player} from "./Player";
import { Programs } from "./Programs/Programs";
import {Script, findRunningScript, RunningScript,
isScriptFilename} from "./Script";
import {Server, getServer, AddToAllServers,
AllServers, processSingleServerGrowth,
GetServerByHostname, numCycleForGrowth} from "./Server";
import { Script } from "./Script/Script";
import { findRunningScript } from "./Script/ScriptHelpers";
import { isScriptFilename } from "./Script/ScriptHelpersTS";
import { AllServers,
AddToAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server";
import { GetServerByHostname,
getServer,
getServerOnNetwork,
numCycleForGrowth,
processSingleServerGrowth } from "./Server/ServerHelpers";
import { getPurchaseServerCost,
getPurchaseServerLimit,
getPurchaseServerMaxRam } from "./ServerPurchases";
getPurchaseServerMaxRam } from "./Server/ServerPurchases";
import {Settings} from "./Settings/Settings";
import {SpecialServerIps} from "./SpecialServerIps";
import {SpecialServerIps} from "./Server/SpecialServerIps";
import {Stock} from "./StockMarket/Stock";
import {StockMarket, StockSymbols, SymbolToStockMap,
initStockMarket, initSymbolToStockMap, buyStock,
@ -53,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,
@ -64,6 +73,7 @@ import {WorkerScript, workerScripts,
import {makeRuntimeRejectMsg, netscriptDelay,
runScriptFromScript} from "./NetscriptEvaluator";
import {NetscriptPort} from "./NetscriptPort";
import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum"
import {Page, routing} from "./ui/navigationTracking";
import {numeralWrapper} from "./ui/numeralFormat";
@ -306,9 +316,9 @@ function NetscriptFunctions(workerScript) {
for (var i = 0; i < server.serversOnNetwork.length; i++) {
var entry;
if (hostnames) {
entry = server.getServerOnNetwork(i).hostname;
entry = getServerOnNetwork(server, i).hostname;
} else {
entry = server.getServerOnNetwork(i).ip;
entry = getServerOnNetwork(server, i).ip;
}
if (entry == null) {
continue;
@ -484,7 +494,7 @@ function NetscriptFunctions(workerScript) {
if (workerScript.env.stopFlag) {return Promise.reject(workerScript);}
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
server.moneyAvailable += (1 * threads); //It can be grown even if it has no money
var growthPercentage = processSingleServerGrowth(server, 450 * threads);
var growthPercentage = processSingleServerGrowth(server, 450 * threads, Player);
const moneyAfter = server.moneyAvailable;
workerScript.scriptRef.recordGrow(server.ip, threads);
var expGain = calculateHackingExpGain(server) * threads;
@ -513,7 +523,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`);
}
return numCycleForGrowth(server, Number(growth));
return numCycleForGrowth(server, Number(growth), Player);
},
weaken : function(ip) {
if (workerScript.checkingRam) {
@ -4189,6 +4199,25 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, "getBlackOpNames() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " +
"at the Bladeburner division or because you do not have Source-File 7");
},
getBlackOpRank : function(name="") {
if (workerScript.checkingRam) {
return updateStaticRam("getBlackOpRank", CONSTANTS.ScriptBladeburnerApiBaseRamCost / 2);
}
updateDynamicRam("getBlackOpRank", CONSTANTS.ScriptBladeburnerApiBaseRamCost / 2);
if (Player.bladeburner instanceof Bladeburner && (Player.bitNodeN === 7 || hasBladeburner2079SF)) {
const actionId = Player.bladeburner.getActionIdFromTypeAndName('blackops', name)
if (!actionId) {
return -1;
}
const actionObj = Player.bladeburner.getActionObject(actionId);
if (!actionObj) {
return -1;
}
return actionObj.reqdRank;
}
throw makeRuntimeRejectMsg(workerScript, "getBlackOpRank() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " +
"at the Bladeburner division or because you do not have Source-File 7");
},
getGeneralActionNames : function() {
if (workerScript.checkingRam) {
return updateStaticRam("getGeneralActionNames", CONSTANTS.ScriptBladeburnerApiBaseRamCost / 10);
@ -4781,7 +4810,277 @@ 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[i];
return {
shock: 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(),
}
},
} // End sleeve
} //End return
} //End NetscriptFunction()

@ -11,7 +11,7 @@ import {evaluate, isScriptErrorMessage,
import {NetscriptFunctions} from "./NetscriptFunctions";
import {executeJSScript} from "./NetscriptJSEvaluator";
import {NetscriptPort} from "./NetscriptPort";
import {AllServers} from "./Server";
import { AllServers } from "./Server/AllServers";
import {Settings} from "./Settings/Settings";
import { setTimeoutRef } from "./utils/SetTimeoutRef";

@ -20,6 +20,7 @@ export interface IPlayer {
city: string;
companyName: string;
corporation: any;
currentServer: string;
factions: string[];
hacknetNodes: any[];
hasWseAccount: boolean;

@ -614,7 +614,6 @@ export class Sleeve extends Person {
*/
travel(p: IPlayer, newCity: string): boolean {
if (Cities[newCity] == null) {
console.error(`Invalid city ${newCity} passed into Sleeve.travel()`);
return false;
}
@ -641,8 +640,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 +683,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;
}

@ -105,13 +105,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);
if (p.canAfford(aug.startingCost)) {
p.loseMoney(aug.startingCost);
sleeve.installAugmentation(aug);
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
removeElementById(popupId);

@ -24,9 +24,11 @@ import {Gang, resetGangs} from "./Gang";
import {Locations} from "./Locations";
import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import {AllServers, Server, AddToAllServers} from "./Server";
import { AllServers,
AddToAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server";
import {Settings} from "./Settings/Settings";
import {SpecialServerIps, SpecialServerNames} from "./SpecialServerIps";
import {SpecialServerIps, SpecialServerNames} from "./Server/SpecialServerIps";
import {SourceFiles, applySourceFile} from "./SourceFile";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import Decimal from "decimal.js";
@ -281,7 +283,11 @@ PlayerObject.prototype.prestigeAugmentation = function() {
for (let i = 0; i < this.sleeves.length; ++i) {
if (this.sleeves[i] instanceof Sleeve) {
this.sleeves[i].shockRecovery(this);
if (this.sleeves[i].shock >= 100) {
this.sleeves[i].synchronize(this);
} else {
this.sleeves[i].shockRecovery(this);
}
}
}

@ -16,20 +16,25 @@ import { Factions,
import { joinFaction } from "./Faction/FactionHelpers";
import {deleteGangDisplayContent} from "./Gang";
import {Locations} from "./Location";
import {initMessages, Messages, Message} from "./Message";
import { Message } from "./Message/Message";
import { initMessages,
Messages } from "./Message/MessageHelpers";
import {initSingularitySFFlags, hasWallStreetSF}from "./NetscriptFunctions";
import {WorkerScript, workerScripts,
prestigeWorkerScripts} from "./NetscriptWorker";
import {Player} from "./Player";
import {AllServers, AddToAllServers,
initForeignServers, Server,
prestigeAllServers,
prestigeHomeComputer} from "./Server";
import { AllServers,
AddToAllServers,
initForeignServers,
prestigeAllServers } from "./Server/AllServers";
import { Server } from "./Server/Server"
import { prestigeHomeComputer } from "./Server/ServerHelpers";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import {SpecialServerIps, SpecialServerIpsMap,
prestigeSpecialServerIps,
SpecialServerNames} from "./SpecialServerIps";
import { SpecialServerIps,
SpecialServerIpsMap,
prestigeSpecialServerIps,
SpecialServerNames} from "./Server/SpecialServerIps";
import {initStockMarket, initSymbolToStockMap,
stockMarketContentCreated,
setStockMarketContentCreated} from "./StockMarket/StockMarket";
@ -89,7 +94,7 @@ function prestigeAugmentation() {
}
//Re-create foreign servers
initForeignServers();
initForeignServers(Player.getHomeComputer());
//Darkweb is purchase-able
document.getElementById("location-purchase-tor").setAttribute("class", "a-link-button");
@ -194,7 +199,7 @@ function prestigeSourceFile() {
prestigeHomeComputer(homeComp);
//Re-create foreign servers
initForeignServers();
initForeignServers(Player.getHomeComputer());
var srcFile1Owned = false;
for (var i = 0; i < Player.sourceFiles.length; ++i) {

@ -7,15 +7,18 @@ import {Engine} from "./engine";
import { Factions,
loadFactions } from "./Faction/Factions";
import { processPassiveFactionRepGain } from "./Faction/FactionHelpers";
import {FconfSettings, loadFconf} from "./Fconf";
import { loadFconf } from "./Fconf/Fconf";
import { FconfSettings } from "./Fconf/FconfSettings";
import {loadAllGangs, AllGangs} from "./Gang";
import {processAllHacknetNodeEarnings} from "./HacknetNode";
import {loadMessages, initMessages, Messages} from "./Message";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import {Player, loadPlayer} from "./Player";
import {loadAllRunningScripts} from "./Script";
import {AllServers, loadAllServers} from "./Server";
import {Settings} from "./Settings/Settings";
import {loadSpecialServerIps, SpecialServerIps} from "./SpecialServerIps";
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
import { AllServers,
loadAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { loadSpecialServerIps,
SpecialServerIps } from "./Server/SpecialServerIps";
import {loadStockMarket, StockMarket} from "./StockMarket/StockMarket";
import { setTimeoutRef } from "./utils/SetTimeoutRef";

File diff suppressed because it is too large Load Diff

1
src/Script/RamCalculations.d.ts vendored Normal file

@ -0,0 +1 @@
export declare function calculateRamUsage(codeCopy: string): number;

@ -0,0 +1,409 @@
// Calculate a script's RAM usage
const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason.
import { CONSTANTS } from "../Constants";
import {evaluateImport} from "../NetscriptEvaluator";
import { WorkerScript } from "../NetscriptWorker";
import { Player } from "../Player";
import {parse, Node} from "../../utils/acorn";
// These special strings are used to reference the presence of a given logical
// construct within a user script.
const specialReferenceIF = "__SPECIAL_referenceIf";
const specialReferenceFOR = "__SPECIAL_referenceFor";
const specialReferenceWHILE = "__SPECIAL_referenceWhile";
// The global scope of a script is registered under this key during parsing.
const memCheckGlobalKey = ".__GLOBAL__";
// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only,
// rather than NetscriptEvaluator. This is useful because NetscriptJS code does
// not work under NetscriptEvaluator.
async function parseOnlyRamCalculate(server, code, workerScript) {
try {
// Maps dependent identifiers to their dependencies.
//
// The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__.
// It depends on all the functions declared in the module, all the global scopes
// of its imports, and any identifiers referenced in this global scope. Each
// function depends on all the identifiers referenced internally.
// We walk the dependency graph to calculate RAM usage, given that some identifiers
// reference Netscript functions which have a RAM cost.
let dependencyMap = {};
// Scripts we've parsed.
const completedParses = new Set();
// Scripts we've discovered that need to be parsed.
const parseQueue = [];
// Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap.
function parseCode(code, moduleName) {
const result = parseOnlyCalculateDeps(code, moduleName);
completedParses.add(moduleName);
// Add any additional modules to the parse queue;
for (let i = 0; i < result.additionalModules.length; ++i) {
if (!completedParses.has(result.additionalModules[i])) {
parseQueue.push(result.additionalModules[i]);
}
}
// Splice all the references in.
//Spread syntax not supported in edge, use Object.assign instead
//dependencyMap = {...dependencyMap, ...result.dependencyMap};
dependencyMap = Object.assign(dependencyMap, result.dependencyMap);
}
const initialModule = "__SPECIAL_INITIAL_MODULE__";
parseCode(code, initialModule);
while (parseQueue.length > 0) {
// Get the code from the server.
const nextModule = parseQueue.shift();
let code;
if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) {
try {
const module = await eval('import(nextModule)');
code = "";
for (const prop in module) {
if (typeof module[prop] === 'function') {
code += module[prop].toString() + ";\n";
}
}
} catch(e) {
console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`);
return -1;
}
} else {
const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule);
if (!script) {
console.warn("Invalid script");
return -1; // No such script on the server.
}
code = script.code;
}
parseCode(code, nextModule);
}
// Finally, walk the reference map and generate a ram cost. The initial set of keys to scan
// are those that start with __SPECIAL_INITIAL_MODULE__.
let ram = CONSTANTS.ScriptBaseRamCost;
const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule));
const resolvedRefs = new Set();
while (unresolvedRefs.length > 0) {
const ref = unresolvedRefs.shift();
// Check if this is one of the special keys, and add the appropriate ram cost if so.
if (ref === "hacknet" && !resolvedRefs.has("hacknet")) {
ram += CONSTANTS.ScriptHacknetNodesRamCost;
}
if (ref === "document" && !resolvedRefs.has("document")) {
ram += CONSTANTS.ScriptDomRamCost;
}
if (ref === "window" && !resolvedRefs.has("window")) {
ram += CONSTANTS.ScriptDomRamCost;
}
resolvedRefs.add(ref);
if (ref.endsWith(".*")) {
// A prefix reference. We need to find all matching identifiers.
const prefix = ref.slice(0, ref.length - 2);
for (let ident of Object.keys(dependencyMap).filter(k => k.startsWith(prefix))) {
for (let dep of dependencyMap[ident] || []) {
if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep);
}
}
} else {
// An exact reference. Add all dependencies of this ref.
for (let dep of dependencyMap[ref] || []) {
if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep);
}
}
// Check if this ident is a function in the workerscript env. If it is, then we need to
// get its RAM cost. We do this by calling it, which works because the running script
// is in checkingRam mode.
//
// TODO it would be simpler to just reference a dictionary.
try {
function applyFuncRam(func) {
if (typeof func === "function") {
try {
let res;
if (func.constructor.name === "AsyncFunction") {
res = 0; // Async functions will always be 0 RAM
} else {
res = func.apply(null, []);
}
if (typeof res === "number") {
return res;
}
return 0;
} catch(e) {
console.log("ERROR applying function: " + e);
return 0;
}
} else {
return 0;
}
}
//Special logic for namespaces (Bladeburner, CodingCOntract)
var func;
if (ref in workerScript.env.vars.bladeburner) {
func = workerScript.env.vars.bladeburner[ref];
} else if (ref in workerScript.env.vars.codingcontract) {
func = workerScript.env.vars.codingcontract[ref];
} else if (ref in workerScript.env.vars.gang) {
func = workerScript.env.vars.gang[ref];
} else {
func = workerScript.env.get(ref);
}
ram += applyFuncRam(func);
} catch (error) {continue;}
}
return ram;
} catch (error) {
// console.info("parse or eval error: ", error);
// This is not unexpected. The user may be editing a script, and it may be in
// a transitory invalid state.
return -1;
}
}
// Parses one script and calculates its ram usage, for the global scope and each function.
// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined
// onto the main reference map, and a list of modules that need to be parsed.
function parseOnlyCalculateDeps(code, currentModule) {
const ast = parse(code, {sourceType:"module", ecmaVersion: 8});
// Everything from the global scope goes in ".". Everything else goes in ".function", where only
// the outermost layer of functions counts.
const globalKey = currentModule + memCheckGlobalKey;
const dependencyMap = {};
dependencyMap[globalKey] = new Set();
// If we reference this internal name, we're really referencing that external name.
// Filled when we import names from other modules.
let internalToExternal = {};
var additionalModules = [];
// References get added pessimistically. They are added for thisModule.name, name, and for
// any aliases.
function addRef(key, name) {
const s = dependencyMap[key] || (dependencyMap[key] = new Set());
if (name in internalToExternal) {
s.add(internalToExternal[name]);
}
s.add(currentModule + "." + name);
s.add(name); // For builtins like hack.
}
//A list of identifiers that resolve to "native Javascript code"
const objectPrototypeProperties = Object.getOwnPropertyNames(Object.prototype);
// If we discover a dependency identifier, state.key is the dependent identifier.
// walkDeeper is for doing recursive walks of expressions in composites that we handle.
function commonVisitors() {
return {
Identifier: (node, st, walkDeeper) => {
if (objectPrototypeProperties.includes(node.name)) {return;}
addRef(st.key, node.name);
},
WhileStatement: (node, st, walkDeeper) => {
addRef(st.key, specialReferenceWHILE);
node.test && walkDeeper(node.test, st);
node.body && walkDeeper(node.body, st);
},
DoWhileStatement: (node, st, walkDeeper) => {
addRef(st.key, specialReferenceWHILE);
node.test && walkDeeper(node.test, st);
node.body && walkDeeper(node.body, st);
},
ForStatement: (node, st, walkDeeper) => {
addRef(st.key, specialReferenceFOR);
node.init && walkDeeper(node.init, st);
node.test && walkDeeper(node.test, st);
node.update && walkDeeper(node.update, st);
node.body && walkDeeper(node.body, st);
},
IfStatement: (node, st, walkDeeper) => {
addRef(st.key, specialReferenceIF);
node.test && walkDeeper(node.test, st);
node.consequent && walkDeeper(node.consequent, st);
node.alternate && walkDeeper(node.alternate, st);
},
MemberExpression: (node, st, walkDeeper) => {
node.object && walkDeeper(node.object, st);
node.property && walkDeeper(node.property, st);
},
}
}
//Spread syntax not supported in Edge yet, use Object.assign
/*
walk.recursive(ast, {key: globalKey}, {
ImportDeclaration: (node, st, walkDeeper) => {
const importModuleName = node.source.value;
additionalModules.push(importModuleName);
// This module's global scope refers to that module's global scope, no matter how we
// import it.
dependencyMap[st.key].add(importModuleName + memCheckGlobalKey);
for (let i = 0; i < node.specifiers.length; ++i) {
const spec = node.specifiers[i];
if (spec.imported !== undefined && spec.local !== undefined) {
// We depend on specific things.
internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name;
} else {
// We depend on everything.
dependencyMap[st.key].add(importModuleName + ".*");
}
}
},
FunctionDeclaration: (node, st, walkDeeper) => {
// Don't use walkDeeper, because we are changing the visitor set.
const key = currentModule + "." + node.id.name;
walk.recursive(node, {key: key}, commonVisitors());
},
...commonVisitors()
});
*/
walk.recursive(ast, {key: globalKey}, Object.assign({
ImportDeclaration: (node, st, walkDeeper) => {
const importModuleName = node.source.value;
additionalModules.push(importModuleName);
// This module's global scope refers to that module's global scope, no matter how we
// import it.
dependencyMap[st.key].add(importModuleName + memCheckGlobalKey);
for (let i = 0; i < node.specifiers.length; ++i) {
const spec = node.specifiers[i];
if (spec.imported !== undefined && spec.local !== undefined) {
// We depend on specific things.
internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name;
} else {
// We depend on everything.
dependencyMap[st.key].add(importModuleName + ".*");
}
}
},
FunctionDeclaration: (node, st, walkDeeper) => {
// Don't use walkDeeper, because we are changing the visitor set.
const key = currentModule + "." + node.id.name;
walk.recursive(node, {key: key}, commonVisitors());
},
}, commonVisitors()));
return {dependencyMap: dependencyMap, additionalModules: additionalModules};
}
export async function calculateRamUsage(codeCopy) {
//Create a temporary/mock WorkerScript and an AST from the code
var currServ = Player.getCurrentServer();
var workerScript = new WorkerScript({
filename:"foo",
scriptRef: {code:""},
args:[],
getCode: function() { return ""; }
});
workerScript.checkingRam = true; //Netscript functions will return RAM usage
workerScript.serverIp = currServ.ip;
try {
return await parseOnlyRamCalculate(currServ, codeCopy, workerScript);
} catch (e) {
console.log("Failed to parse ram using new method. Falling back.", e);
}
// Try the old way.
try {
var ast = parse(codeCopy, {sourceType:"module"});
} catch(e) {
return -1;
}
//Search through AST, scanning for any 'Identifier' nodes for functions, or While/For/If nodes
var queue = [], ramUsage = CONSTANTS.ScriptBaseRamCost;
var whileUsed = false, forUsed = false, ifUsed = false;
queue.push(ast);
while (queue.length != 0) {
var exp = queue.shift();
switch (exp.type) {
case "ImportDeclaration":
//Gets an array of all imported functions as AST expressions
//and pushes them on the queue.
var res = evaluateImport(exp, workerScript, true);
for (var i = 0; i < res.length; ++i) {
queue.push(res[i]);
}
break;
case "BlockStatement":
case "Program":
for (var i = 0; i < exp.body.length; ++i) {
if (exp.body[i] instanceof Node) {
queue.push(exp.body[i]);
}
}
break;
case "WhileStatement":
if (!whileUsed) {
ramUsage += CONSTANTS.ScriptWhileRamCost;
whileUsed = true;
}
break;
case "ForStatement":
if (!forUsed) {
ramUsage += CONSTANTS.ScriptForRamCost;
forUsed = true;
}
break;
case "IfStatement":
if (!ifUsed) {
ramUsage += CONSTANTS.ScriptIfRamCost;
ifUsed = true;
}
break;
case "Identifier":
if (exp.name in workerScript.env.vars) {
var func = workerScript.env.get(exp.name);
if (typeof func === "function") {
try {
var res = func.apply(null, []);
if (typeof res === "number") {
ramUsage += res;
}
} catch(e) {
console.log("ERROR applying function: " + e);
}
}
}
break;
default:
break;
}
for (var prop in exp) {
if (exp.hasOwnProperty(prop)) {
if (exp[prop] instanceof Node) {
queue.push(exp[prop]);
}
}
}
}
//Special case: hacknetnodes array
if (codeCopy.includes("hacknet")) {
ramUsage += CONSTANTS.ScriptHacknetNodesRamCost;
}
return ramUsage;
}

161
src/Script/RunningScript.ts Normal file

@ -0,0 +1,161 @@
// Class representing a Script instance that is actively running.
// A Script can have multiple active instances
import { Script } from "./Script";
import { FconfSettings } from "../Fconf/FconfSettings";
import { AllServers } from "../Server/AllServers";
import { Settings } from "../Settings/Settings";
import { IMap } from "../types";
import { post } from "../ui/postToTerminal";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
import { getTimestamp } from "../../utils/helpers/getTimestamp";
export class RunningScript {
// Initializes a RunningScript Object from a JSON save state
static fromJSON(value: any): RunningScript {
return Generic_fromJSON(RunningScript, value.data);
}
// Script arguments
args: any[] = [];
// Holds a map of servers hacked, where server = key and the value for each
// server is an array of four numbers. The four numbers represent:
// [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// This data is used for offline progress
dataMap: IMap<number[]> = {};
// Script filename
filename: string = "";
// This script's logs. An array of log entries
logs: string[] = [];
// Flag indicating whether the logs have been updated since
// the last time the UI was updated
logUpd: boolean = false;
// Total amount of hacking experience earned from this script when offline
offlineExpGained: number = 0;
// Total amount of money made by this script when offline
offlineMoneyMade: number = 0;
// Number of seconds that the script has been running offline
offlineRunningTime: number = 0.01;
// Total amount of hacking experience earned from this script when online
onlineExpGained: number = 0;
// Total amount of money made by this script when online
onlineMoneyMade: number = 0;
// Number of seconds that this script has been running online
onlineRunningTime: number = 0.01;
// How much RAM this script uses for ONE thread
ramUsage: number = 0;
// IP of the server on which this script is running
server: string = "";
// Number of threads that this script is running with
threads: number = 1;
constructor(script: Script | null = null, args: any[] = []) {
if (script == null) { return; }
this.filename = script.filename;
this.args = args;
this.server = script.server; //IP Address only
this.ramUsage = script.ramUsage;
}
getCode(): string {
const server = AllServers[this.server];
if (server == null) { return ""; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
return server.scripts[i].code;
}
}
return "";
}
getRamUsage(): number {
if (this.ramUsage != null && this.ramUsage > 0) { return this.ramUsage; } // Use cached value
const server = AllServers[this.server];
if (server == null) { return 0; }
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.filename) {
// Cache the ram usage for the next call
this.ramUsage = server.scripts[i].ramUsage;
return this.ramUsage;
}
}
return 0;
}
log(txt: string): void {
if (this.logs.length > Settings.MaxLogCapacity) {
//Delete first element and add new log entry to the end.
//TODO Eventually it might be better to replace this with circular array
//to improve performance
this.logs.shift();
}
let logEntry = txt;
if (FconfSettings.ENABLE_TIMESTAMPS) {
logEntry = "[" + getTimestamp() + "] " + logEntry;
}
this.logs.push(logEntry);
this.logUpd = true;
}
displayLog(): void {
for (var i = 0; i < this.logs.length; ++i) {
post(this.logs[i]);
}
}
clearLog(): void {
this.logs.length = 0;
}
// Update the moneyStolen and numTimesHack maps when hacking
recordHack(serverIp: string, moneyGained: number, n: number=1) {
if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
this.dataMap[serverIp] = [0, 0, 0, 0];
}
this.dataMap[serverIp][0] += moneyGained;
this.dataMap[serverIp][1] += n;
}
// Update the grow map when calling grow()
recordGrow(serverIp: string, n: number=1) {
if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
this.dataMap[serverIp] = [0, 0, 0, 0];
}
this.dataMap[serverIp][2] += n;
}
// Update the weaken map when calling weaken() {
recordWeaken(serverIp: string, n: number=1) {
if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) {
this.dataMap[serverIp] = [0, 0, 0, 0];
}
this.dataMap[serverIp][3] += n;
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("RunningScript", this);
}
}
Reviver.constructors.RunningScript = RunningScript;

106
src/Script/Script.ts Normal file

@ -0,0 +1,106 @@
// Class representing a script file
// This does NOT represent a script that is actively running and
// being evaluated. See RunningScript for that
import { calculateRamUsage } from "./RamCalculations";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Page,
routing } from "../ui/navigationTracking";
import { setTimeoutRef } from "../utils/SetTimeoutRef";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
export class Script {
// Initializes a Script Object from a JSON save state
static fromJSON(value: any): Script {
return Generic_fromJSON(Script, value.data);
}
// Code for this script
code: string = "";
// Filename for the script file
filename: string = "";
// The dynamic module generated for this script when it is run.
// This is only applicable for NetscriptJS
module: any = "";
// Amount of RAM this Script requres to run
ramUsage: number = 0;
// IP of server that this script is on.
server: string = "";
constructor(fn: string = "", code: string = "", server: string = "") {
this.filename = fn;
this.code = code;
this.ramUsage = 0;
this.server = server; // IP of server this script is on
this.module = "";
if (this.code !== "") {this.updateRamUsage();}
};
download(): void {
const filename = this.filename + ".js";
const file = new Blob([this.code], {type: 'text/plain'});
if (window.navigator.msSaveOrOpenBlob) {// IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
} else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeoutRef(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
// Save a script FROM THE SCRIPT EDITOR
saveScript(code: string, p: IPlayer): void {
if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename
this.code = code.replace(/^\s+|\s+$/g, '');
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement;
if (filenameElem == null) {
console.error(`Failed to get Script filename DOM element`);
return;
}
this.filename = filenameElem!.value;
// Server
this.server = p.currentServer;
//Calculate/update ram usage, execution time, etc.
this.updateRamUsage();
this.module = "";
}
}
// Updates the script's RAM usage based on its code
async updateRamUsage() {
// TODO Commented this out because I think its unnecessary
// DOuble check/Test
// var codeCopy = this.code.repeat(1);
var res = await calculateRamUsage(this.code);
if (res !== -1) {
this.ramUsage = roundToTwo(res);
}
}
// Serialize the current object to a JSON save state
toJSON(): any {
return Generic_toJSON("Script", this);
}
}
Reviver.constructors.Script = Script;

443
src/Script/ScriptHelpers.js Normal file

@ -0,0 +1,443 @@
import { Script } from "./Script";
import { calculateRamUsage } from "./RamCalculations";
import { isScriptFilename } from "./ScriptHelpersTS";
import {CONSTANTS} from "../Constants";
import {Engine} from "../engine";
import { parseFconfSettings } from "../Fconf/Fconf";
import { FconfSettings } from "../Fconf/FconfSettings";
import {iTutorialSteps, iTutorialNextStep,
ITutorial} from "../InteractiveTutorial";
import { addWorkerScript } from "../NetscriptWorker";
import { Player } from "../Player";
import { AceEditor } from "../ScriptEditor/Ace";
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
import { AllServers } from "../Server/AllServers";
import { processSingleServerGrowth } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings";
import { EditorSetting } from "../Settings/SettingEnums";
import {TextFile} from "../TextFile";
import {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat";
import {dialogBoxCreate} from "../../utils/DialogBox";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../../utils/JSONReviver";
import {compareArrays} from "../../utils/helpers/compareArrays";
import {createElement} from "../../utils/uiHelpers/createElement";
var scriptEditorRamCheck = null, scriptEditorRamText = null;
export function scriptEditorInit() {
// Wrapper container that holds all the buttons below the script editor
const wrapper = document.getElementById("script-editor-buttons-wrapper");
if (wrapper == null) {
console.error("Could not find 'script-editor-buttons-wrapper'");
return false;
}
// Beautify button
const beautifyButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Beautify",
clickListener:()=>{
let editor = getCurrentEditor();
if (editor != null) {
editor.beautifyScript();
}
return false;
}
});
// Text that displays RAM calculation
scriptEditorRamText = createElement("p", {
display:"inline-block", margin:"10px", id:"script-editor-status-text"
});
// Label for checkbox (defined below)
const checkboxLabel = createElement("label", {
for:"script-editor-ram-check", margin:"4px", marginTop: "8px",
innerText:"Dynamic RAM Usage Checker", color:"white",
tooltip:"Enable/Disable the dynamic RAM Usage display. You may " +
"want to disable it for very long scripts because there may be " +
"performance issues"
});
// Checkbox for enabling/disabling dynamic RAM calculation
scriptEditorRamCheck = createElement("input", {
type:"checkbox", name:"script-editor-ram-check", id:"script-editor-ram-check",
margin:"4px", marginTop: "8px",
});
scriptEditorRamCheck.checked = true;
// Link to Netscript documentation
const documentationButton = createElement("a", {
class: "std-button",
display: "inline-block",
href:"https://bitburner.readthedocs.io/en/latest/index.html",
innerText:"Netscript Documentation",
target:"_blank",
});
// Save and Close button
const closeButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Save & Close (Ctrl/Cmd + b)",
clickListener:()=>{
saveAndCloseScriptEditor();
return false;
}
});
// Add all buttons to the UI
wrapper.appendChild(beautifyButton);
wrapper.appendChild(closeButton);
wrapper.appendChild(scriptEditorRamText);
wrapper.appendChild(scriptEditorRamCheck);
wrapper.appendChild(checkboxLabel);
wrapper.appendChild(documentationButton);
// Initialize editors
const initParams = {
saveAndCloseFn: saveAndCloseScriptEditor,
quitFn: Engine.loadTerminalContent,
}
AceEditor.init(initParams);
CodeMirrorEditor.init(initParams);
// Setup the selector for which Editor to use
const editorSelector = document.getElementById("script-editor-option-editor");
if (editorSelector == null) {
console.error(`Could not find DOM Element for editor selector (id=script-editor-option-editor)`);
return false;
}
for (let i = 0; i < editorSelector.options.length; ++i) {
if (editorSelector.options[i].value === Settings.Editor) {
editorSelector.selectedIndex = i;
break;
}
}
editorSelector.onchange = () => {
const opt = editorSelector.value;
switch (opt) {
case EditorSetting.Ace:
const codeMirrorCode = CodeMirrorEditor.getCode();
const codeMirrorFn = CodeMirrorEditor.getFilename();
AceEditor.create();
CodeMirrorEditor.setInvisible();
AceEditor.openScript(codeMirrorFn, codeMirrorCode);
break;
case EditorSetting.CodeMirror:
const aceCode = AceEditor.getCode();
const aceFn = AceEditor.getFilename();
CodeMirrorEditor.create();
AceEditor.setInvisible();
CodeMirrorEditor.openScript(aceFn, aceCode);
break;
default:
console.error(`Unrecognized Editor Setting: ${opt}`);
return;
}
Settings.Editor = opt;
}
editorSelector.onchange(); // Trigger the onchange event handler
}
export function getCurrentEditor() {
switch (Settings.Editor) {
case EditorSetting.Ace:
return AceEditor;
case EditorSetting.CodeMirror:
return CodeMirrorEditor;
default:
console.log(`Invalid Editor Setting: ${Settings.Editor}`);
throw new Error(`Invalid Editor Setting: ${Settings.Editor}`);
return null;
}
}
//Updates RAM usage in script
export async function updateScriptEditorContent() {
var filename = document.getElementById("script-editor-filename").value;
if (scriptEditorRamCheck == null || !scriptEditorRamCheck.checked || !isScriptFilename(filename)) {
scriptEditorRamText.innerText = "N/A";
return;
}
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
scriptEditorRamText.innerText = "RAM: ERROR";
return;
}
var codeCopy = code.repeat(1);
var ramUsage = await calculateRamUsage(codeCopy);
if (ramUsage !== -1) {
scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB";
} else {
scriptEditorRamText.innerText = "RAM: Syntax Error";
}
}
//Define key commands in script editor (ctrl o to save + close, etc.)
$(document).keydown(function(e) {
if (Settings.DisableHotkeys === true) {return;}
if (routing.isOn(Page.ScriptEditor)) {
//Ctrl + b
if (e.keyCode == 66 && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
saveAndCloseScriptEditor();
}
}
});
function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value;
let code;
try {
code = getCurrentEditor().getCode();
} catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details");
return;
}
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (filename !== "foodnstuff.script") {
dialogBoxCreate("Leave the script name as 'foodnstuff'!");
return;
}
code = code.replace(/\s/g, "");
if (code.indexOf("while(true){hack('foodnstuff');}") == -1) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}
//Save the script
let s = Player.getCurrentServer();
for (var i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
Engine.loadTerminalContent();
return iTutorialNextStep();
}
}
//If the current script does NOT exist, create a new one
let script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player);
s.scripts.push(script);
return iTutorialNextStep();
}
if (filename == "") {
dialogBoxCreate("You must specify a filename!");
return;
}
if (checkValidFilename(filename) == false) {
dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores");
return;
}
var s = Player.getCurrentServer();
if (filename === ".fconf") {
try {
parseFconfSettings(code);
} catch(e) {
dialogBoxCreate(`Invalid .fconf file: ${e}`);
return;
}
} else if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it
for (var i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player);
Engine.loadTerminalContent();
return;
}
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player);
s.scripts.push(script);
} else if (filename.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) {
if (s.textFiles[i].fn === filename) {
s.textFiles[i].write(code);
Engine.loadTerminalContent();
return;
}
}
var textFile = new TextFile(filename, code);
s.textFiles.push(textFile);
} else {
dialogBoxCreate("Invalid filename. Must be either a script (.script) or " +
" or text file (.txt)")
return;
}
Engine.loadTerminalContent();
}
//Checks that the string contains only valid characters for a filename, which are alphanumeric,
// underscores, hyphens, and dots
function checkValidFilename(filename) {
var regex = /^[.a-zA-Z0-9_-]+$/;
if (filename.match(regex)) {
return true;
}
return false;
}
//Called when the game is loaded. Loads all running scripts (from all servers)
//into worker scripts so that they will start running
export function loadAllRunningScripts() {
var total = 0;
let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1);
if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); }
for (var property in AllServers) {
if (AllServers.hasOwnProperty(property)) {
var server = AllServers[property];
//Reset each server's RAM usage to 0
server.ramUsed = 0;
//Reset modules on all scripts
for (var i = 0; i < server.scripts.length; ++i) {
server.scripts[i].module = "";
}
if (skipScriptLoad) {
//Start game with no scripts
server.runningScripts.length = 0;
} else {
for (var j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server);
//Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
}
}
}
}
return total;
}
function scriptCalculateOfflineProduction(runningScriptObj) {
//The Player object stores the last update time from when we were online
var thisUpdate = new Date().getTime();
var lastUpdate = Player.lastUpdate;
var timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds
//Calculate the "confidence" rating of the script's true production. This is based
//entirely off of time. We will arbitrarily say that if a script has been running for
//4 hours (14400 sec) then we are completely confident in its ability
var confidence = (runningScriptObj.onlineRunningTime) / 14400;
if (confidence >= 1) {confidence = 1;}
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// Grow
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed);
console.log(runningScriptObj.filename + " called grow() on " + serv.hostname + " " + timesGrown + " times while offline");
runningScriptObj.log("Called grow() on " + serv.hostname + " " + timesGrown + " times while offline");
var growth = processSingleServerGrowth(serv, timesGrown * 450, Player);
runningScriptObj.log(serv.hostname + " grown by " + numeralWrapper.format(growth * 100 - 100, '0.000000%') + " from grow() calls made while offline");
}
}
// Money from hacking
var totalOfflineProduction = 0;
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][0] == 0 || runningScriptObj.dataMap[ip][0] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var production = 0.5 * runningScriptObj.dataMap[ip][0] / runningScriptObj.onlineRunningTime * timePassed;
production *= confidence;
if (production > serv.moneyAvailable) {
production = serv.moneyAvailable;
}
totalOfflineProduction += production;
Player.gainMoney(production);
Player.recordMoneySource(production, "hacking");
console.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname);
runningScriptObj.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname);
serv.moneyAvailable -= production;
if (serv.moneyAvailable < 0) {serv.moneyAvailable = 0;}
if (isNaN(serv.moneyAvailable)) {serv.moneyAvailable = 0;}
}
}
// Offline EXP gain
// A script's offline production will always be at most half of its online production.
var expGain = 0.5 * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed;
expGain *= confidence;
Player.gainHackingExp(expGain);
// Update script stats
runningScriptObj.offlineMoneyMade += totalOfflineProduction;
runningScriptObj.offlineRunningTime += timePassed;
runningScriptObj.offlineExpGained += expGain;
// Fortify a server's security based on how many times it was hacked
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][1] == 0 || runningScriptObj.dataMap[ip][1] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var timesHacked = Math.round(0.5 * runningScriptObj.dataMap[ip][1] / runningScriptObj.onlineRunningTime * timePassed);
console.log(runningScriptObj.filename + " hacked " + serv.hostname + " " + timesHacked + " times while offline");
runningScriptObj.log("Hacked " + serv.hostname + " " + timesHacked + " times while offline");
serv.fortify(CONSTANTS.ServerFortifyAmount * timesHacked);
}
}
// Weaken
for (var ip in runningScriptObj.dataMap) {
if (runningScriptObj.dataMap.hasOwnProperty(ip)) {
if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;}
var serv = AllServers[ip];
if (serv == null) {continue;}
var timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed);
console.log(runningScriptObj.filename + " called weaken() on " + serv.hostname + " " + timesWeakened + " times while offline");
runningScriptObj.log("Called weaken() on " + serv.hostname + " " + timesWeakened + " times while offline");
serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened);
}
}
return totalOfflineProduction;
}
//Returns a RunningScript object matching the filename and arguments on the
//designated server, and false otherwise
export function findRunningScript(filename, args, server) {
for (var i = 0; i < server.runningScripts.length; ++i) {
if (server.runningScripts[i].filename == filename &&
compareArrays(server.runningScripts[i].args, args)) {
return server.runningScripts[i];
}
}
return null;
}

@ -0,0 +1,4 @@
// Script helper functions
export function isScriptFilename(f: string) {
return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns");
}

@ -117,15 +117,20 @@ let NetscriptFunctions =
"getGeneralActionNames|getSkillNames|startAction|stopBladeburnerAction|" +
"getActionTime|getActionEstimatedSuccessChance|getActionCountRemaining|" +
"getActionMaxLevel|getActionCurrentLevel|getActionAutolevel|" +
"getActionRepGain|setActionAutolevel|setActionLevel|" +
"getActionRepGain|setActionAutolevel|setActionLevel|getBlackOpRank|" +
"getRank|getSkillPoints|getSkillLevel|getSkillUpgradeCost|" +
"upgradeSkill|getTeamSize|getCity|" +
"upgradeSkill|getTeamSize|getCity|getCurrentAction|" +
"setTeamSize|getCityEstimatedPopulation|getCityEstimatedCommunities|" +
"getCityChaos|switchCity|getStamina|joinBladeburnerFaction|getBonusTime|" +
// Coding Contract API
"codingcontract|attempt|getContractType|getData|getDescription|" +
"getNumTriesRemaining";
"getNumTriesRemaining|" +
// Sleeve API
"sleeve|getNumSleeves|setToShockRecovery|setToSynchronize|setToCommitCrime|" +
"setToUniversityCourse|travel|setToCompanyWork|setToFactionWork|setToGymWorkout|" +
"getSleeveStats|getTask|getInformation";
var NetscriptHighlightRules = function(options) {
var keywordMapper = this.createKeywordMapper({

@ -208,6 +208,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"getSkillNames": atom,
"startAction": atom,
"stopBladeburnerAction": atom,
"getCurrentAction": atom,
"getActionTime": atom,
"getActionEstimatedSuccessChance": atom,
"getActionCountRemaining": atom,
@ -218,6 +219,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"setActionAutolevel": atom,
"setActionLevel": atom,
"getRank": atom,
"getBlackOpRank": atom,
"getSkillPoints": atom,
"getSkillLevel": atom,
"getSkillUpgradeCost": atom,
@ -231,7 +233,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"switchCity": atom,
"getStamina": atom,
"joinBladeburnerFaction": atom,
"getBonusTime": atom,
// Repeat of above "getBonusTime": atom,
// Netscript Coding Contract API
"codingcontract": atom,
@ -240,6 +242,21 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"getData": atom,
"getDescription": atom,
"getNumTriesRemaining": atom,
// Sleeve API
"sleeve": atom,
"getNumSleeves": atom,
"setToShockRecovery": atom,
"setToSynchronize": atom,
"setToCommitCrime": atom,
"setToUniversityCourse": atom,
"travel": atom,
"setToCompanyWork": atom,
"setToFactionWork": atom,
"setToGymWorkout": atom,
"getSleeveStats": atom,
"getTask": atom,
"getInformation": atom,
};
}();

@ -1,468 +0,0 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CodingContract,
ContractTypes } from "./CodingContracts";
import { CONSTANTS } from "./Constants";
import { Script,
isScriptFilename } from "./Script";
import { Player } from "./Player";
import { Programs } from "./Programs/Programs";
import { SpecialServerIps } from "./SpecialServerIps";
import { TextFile } from "./TextFile";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { createRandomIp,
ipExists } from "../utils/IPAddress";
import { serverMetadata } from "./data/servers";
import { Reviver,
Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver";
import {isValidIPAddress} from "../utils/helpers/isValidIPAddress";
function Server(params={ip:createRandomIp(), hostname:""}) {
/* Properties */
//Connection information
this.ip = params.ip ? params.ip : createRandomIp();
var hostname = params.hostname;
var i = 0;
var suffix = "";
while (GetServerByHostname(hostname+suffix) != null) {
//Server already exists
suffix = "-" + i;
++i;
}
this.hostname = hostname + suffix;
this.organizationName = params.organizationName != null ? params.organizationName : "";
this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false;
//Access information
this.hasAdminRights = params.adminRights != null ? params.adminRights : false;
this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false;
this.manuallyHacked = false; //Flag that tracks whether or not the server has been hacked at least once
//RAM, CPU speed and Scripts
this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB
this.ramUsed = 0;
this.cpuCores = 1; //Max of 8, affects hacking times and Hacking Mission starting Cores
this.scripts = [];
this.runningScripts = []; //Stores RunningScript objects
this.programs = [];
this.messages = [];
this.textFiles = [];
this.contracts = [];
this.dir = 0; //new Directory(this, null, ""); TODO
/* Hacking information (only valid for "foreign" aka non-purchased servers) */
this.requiredHackingSkill = params.requiredHackingSkill != null ? params.requiredHackingSkill : 1;
this.moneyAvailable = params.moneyAvailable != null ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney : 0;
this.moneyMax = 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney;
//Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty
this.hackDifficulty = params.hackDifficulty != null ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity : 1;
this.baseDifficulty = this.hackDifficulty;
this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3));
this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow()
//The IP's of all servers reachable from this one (what shows up if you run scan/netstat)
// NOTE: Only contains IP and not the Server objects themselves
this.serversOnNetwork = [];
//Port information, required for porthacking servers to get admin rights
this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5;
this.sshPortOpen = false; //Port 22
this.ftpPortOpen = false; //Port 21
this.smtpPortOpen = false; //Port 25
this.httpPortOpen = false; //Port 80
this.sqlPortOpen = false; //Port 1433
this.openPortCount = 0;
};
Server.prototype.setMaxRam = function(ram) {
this.maxRam = ram;
}
//The serverOnNetwork array holds the IP of all the servers. This function
//returns the actual Server objects
Server.prototype.getServerOnNetwork = function(i) {
if (i > this.serversOnNetwork.length) {
console.log("Tried to get server on network that was out of range");
return;
}
return AllServers[this.serversOnNetwork[i]];
}
//Given the name of the script, returns the corresponding
//script object on the server (if it exists)
Server.prototype.getScript = function(scriptName) {
for (var i = 0; i < this.scripts.length; i++) {
if (this.scripts[i].filename == scriptName) {
return this.scripts[i];
}
}
return null;
}
Server.prototype.capDifficulty = function() {
if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;}
if (this.hackDifficulty < 1) {this.hackDifficulty = 1;}
//Place some arbitrarily limit that realistically should never happen unless someone is
//screwing around with the game
if (this.hackDifficulty > 1000000) {this.hackDifficulty = 1000000;}
}
//Strengthens a server's security level (difficulty) by the specified amount
Server.prototype.fortify = function(amt) {
this.hackDifficulty += amt;
this.capDifficulty();
}
Server.prototype.weaken = function(amt) {
this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate);
this.capDifficulty();
}
// Write to a script file
// Overwrites existing files. Creates new files if the script does not eixst
Server.prototype.writeToScriptFile = function(fn, code) {
var ret = {success: false, overwritten: false};
if (!isScriptFilename(fn)) { return ret; }
//Check if the script already exists, and overwrite it if it does
for (let i = 0; i < this.scripts.length; ++i) {
if (fn === this.scripts[i].filename) {
let script = this.scripts[i];
script.code = code;
script.updateRamUsage();
script.module = "";
ret.overwritten = true;
ret.success = true;
return ret;
}
}
//Otherwise, create a new script
var newScript = new Script();
newScript.filename = fn;
newScript.code = code;
newScript.updateRamUsage();
newScript.server = this.ip;
this.scripts.push(newScript);
ret.success = true;
return ret;
}
// Write to a text file
// Overwrites existing files. Creates new files if the text file does not exist
Server.prototype.writeToTextFile = function(fn, txt) {
var ret = {success: false, overwritten: false};
if (!fn.endsWith("txt")) { return ret; }
//Check if the text file already exists, and overwrite if it does
for (let i = 0; i < this.textFiles.length; ++i) {
if (this.textFiles[i].fn === fn) {
ret.overwritten = true;
this.textFiles[i].text = txt;
ret.success = true;
return ret;
}
}
//Otherwise create a new text file
var newFile = new TextFile(fn, txt);
this.textFiles.push(newFile);
ret.success = true;
return ret;
}
Server.prototype.addContract = function(contract) {
this.contracts.push(contract);
}
Server.prototype.removeContract = function(contract) {
if (contract instanceof CodingContract) {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract.fn;
});
} else {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract;
});
}
}
Server.prototype.getContract = function(contractName) {
for (const contract of this.contracts) {
if (contract.fn === contractName) {
return contract;
}
}
return null;
}
//Functions for loading and saving a Server
Server.prototype.toJSON = function() {
return Generic_toJSON("Server", this);
}
Server.fromJSON = function(value) {
return Generic_fromJSON(Server, value.data);
}
Reviver.constructors.Server = Server;
export function initForeignServers() {
/* Create a randomized network for all the foreign servers */
//Groupings for creating a randomized network
const networkLayers = [];
for (let i = 0; i < 15; i++) {
networkLayers.push([]);
}
// Essentially any property that is of type 'number | IMinMaxRange'
const propertiesToPatternMatch = [
"hackDifficulty",
"moneyAvailable",
"requiredHackingSkill",
"serverGrowth"
];
const toNumber = (value) => {
switch (typeof value) {
case 'number':
return value;
case 'object':
return getRandomInt(value.min, value.max);
default:
throw Error(`Do not know how to convert the type '${typeof value}' to a number`);
}
}
for (const metadata of serverMetadata) {
const serverParams = {
hostname: metadata.hostname,
ip: createRandomIp(),
numOpenPortsRequired: metadata.numOpenPortsRequired,
organizationName: metadata.organizationName
};
if (metadata.maxRamExponent !== undefined) {
serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent));
}
for (const prop of propertiesToPatternMatch) {
if (metadata[prop] !== undefined) {
serverParams[prop] = toNumber(metadata[prop]);
}
}
const server = new Server(serverParams);
for (const filename of (metadata.literature || [])) {
server.messages.push(filename);
}
if (metadata.specialName !== undefined) {
SpecialServerIps.addIp(metadata.specialName, server.ip);
}
AddToAllServers(server);
if (metadata.networkLayer !== undefined) {
networkLayers[toNumber(metadata.networkLayer) - 1].push(server);
}
}
/* Create a randomized network for all the foreign servers */
const linkComputers = (server1, server2) => {
server1.serversOnNetwork.push(server2.ip);
server2.serversOnNetwork.push(server1.ip);
};
const getRandomArrayItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
const linkNetworkLayers = (network1, selectServer) => {
for (const server of network1) {
linkComputers(server, selectServer());
}
};
// Connect the first tier of servers to the player's home computer
linkNetworkLayers(networkLayers[0], () => Player.getHomeComputer());
for (let i = 1; i < networkLayers.length; i++) {
linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1]));
}
}
// Returns the number of cycles needed to grow the specified server by the
// specified amount. 'growth' parameter is in decimal form, not percentage
export function numCycleForGrowth(server, growth) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
}
const serverGrowthPercentage = server.serverGrowth / 100;
const cycles = Math.log(growth)/(Math.log(ajdGrowthRate)*Player.hacking_grow_mult*serverGrowthPercentage);
return cycles;
}
//Applied server growth for a single server. Returns the percentage growth
export function processSingleServerGrowth(server, numCycles) {
//Server growth processed once every 450 game cycles
const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0);
//Get adjusted growth rate, which accounts for server security
const growthRate = CONSTANTS.ServerBaseGrowthRate;
var adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty;
if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;}
//Calculate adjusted server growth rate based on parameters
const serverGrowthPercentage = server.serverGrowth / 100;
const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate;
//Apply serverGrowth for the calculated number of growth cycles
var serverGrowth = Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * Player.hacking_grow_mult);
if (serverGrowth < 1) {
console.log("WARN: serverGrowth calculated to be less than 1");
serverGrowth = 1;
}
const oldMoneyAvailable = server.moneyAvailable;
server.moneyAvailable *= serverGrowth;
// in case of data corruption
if (server.moneyMax && isNaN(server.moneyAvailable)) {
server.moneyAvailable = server.moneyMax;
}
// cap at max
if (server.moneyMax && server.moneyAvailable > server.moneyMax) {
server.moneyAvailable = server.moneyMax;
}
// if there was any growth at all, increase security
if (oldMoneyAvailable !== server.moneyAvailable) {
//Growing increases server security twice as much as hacking
let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable);
usedCycles = Math.max(0, usedCycles);
server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles));
}
return server.moneyAvailable / oldMoneyAvailable;
}
export function prestigeHomeComputer(homeComp) {
const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name);
homeComp.programs.length = 0; //Remove programs
homeComp.runningScripts = [];
homeComp.serversOnNetwork = [];
homeComp.isConnectedTo = true;
homeComp.ramUsed = 0;
homeComp.programs.push(Programs.NukeProgram.name);
if (hasBitflume) { homeComp.programs.push(Programs.BitFlume.name); }
//Update RAM usage on all scripts
homeComp.scripts.forEach(function(script) {
script.updateRamUsage();
});
homeComp.messages.length = 0; //Remove .lit and .msg files
homeComp.messages.push("hackers-starting-handbook.lit");
}
//List of all servers that exist in the game, indexed by their ip
let AllServers = {};
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
export function loadAllServers(saveString) {
AllServers = JSON.parse(saveString, Reviver);
}
function SizeOfAllServers() {
var size = 0, key;
for (key in AllServers) {
if (AllServers.hasOwnProperty(key)) size++;
}
return size;
}
//Add a server onto the map of all servers in the game
export function AddToAllServers(server) {
var serverIp = server.ip;
if (ipExists(serverIp)) {
console.log("IP of server that's being added: " + serverIp);
console.log("Hostname of the server thats being added: " + server.hostname);
console.log("The server that already has this IP is: " + AllServers[serverIp].hostname);
throw new Error("Error: Trying to add a server with an existing IP");
return;
}
AllServers[serverIp] = server;
}
//Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot
export function GetServerByHostname(hostname) {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) {
return AllServers[ip];
}
}
}
return null;
}
//Get server by IP or hostname. Returns null if invalid
export function getServer(s) {
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);
}
if(AllServers[s] !== undefined) {
return AllServers[s];
}
return null;
}
//Debugging tool
function PrintAllServers() {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
console.log("Ip: " + ip + ", hostname: " + AllServers[ip].hostname);
}
}
}
// Directory object (folders)
function Directory(server, parent, name) {
this.s = server; //Ref to server
this.p = parent; //Ref to parent directory
this.c = []; //Subdirs
this.n = name;
this.d = parent.d + 1; //We'll only have a maximum depth of 3 or something
this.scrs = []; //Holds references to the scripts in server.scripts
this.pgms = [];
this.msgs = [];
}
Directory.prototype.createSubdir = function(name) {
var subdir = new Directory(this.s, this, name);
}
Directory.prototype.getPath = function(name) {
var res = [];
var i = this;
while (i !== null) {
res.unshift(i.n, "/");
i = i.parent;
}
res.unshift("/");
return res.join("");
}
export {Server, AllServers};

132
src/Server/AllServers.ts Normal file

@ -0,0 +1,132 @@
import { Server } from "./Server";
import { SpecialServerIps } from "./SpecialServerIps";
import { serverMetadata } from "./data/servers";
import { IMap } from "../types";
import { createRandomIp,
ipExists } from "../../utils/IPAddress";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { Reviver } from "../../utils/JSONReviver";
// Map of all Servers that exist in the game
// Key (string) = IP
// Value = Server object
export let AllServers: IMap<Server> = {};
// Saftely add a Server to the AllServers map
export function AddToAllServers(server: Server): void {
var serverIp = server.ip;
if (ipExists(serverIp)) {
console.log("IP of server that's being added: " + serverIp);
console.log("Hostname of the server thats being added: " + server.hostname);
console.log("The server that already has this IP is: " + AllServers[serverIp].hostname);
throw new Error("Error: Trying to add a server with an existing IP");
}
AllServers[serverIp] = server;
}
interface IServerParams {
hackDifficulty?: number;
hostname: string;
ip: string;
maxRam?: number;
moneyAvailable?: number;
numOpenPortsRequired: number;
organizationName: string;
requiredHackingSkill?: number;
serverGrowth?: number;
[key: string]: any;
}
export function initForeignServers(homeComputer: Server) {
/* Create a randomized network for all the foreign servers */
//Groupings for creating a randomized network
const networkLayers: Server[][] = [];
for (let i = 0; i < 15; i++) {
networkLayers.push([]);
}
// Essentially any property that is of type 'number | IMinMaxRange'
const propertiesToPatternMatch: string[] = [
"hackDifficulty",
"moneyAvailable",
"requiredHackingSkill",
"serverGrowth"
];
const toNumber = (value: any) => {
switch (typeof value) {
case 'number':
return value;
case 'object':
return getRandomInt(value.min, value.max);
default:
throw Error(`Do not know how to convert the type '${typeof value}' to a number`);
}
}
for (const metadata of serverMetadata) {
const serverParams: IServerParams = {
hostname: metadata.hostname,
ip: createRandomIp(),
numOpenPortsRequired: metadata.numOpenPortsRequired,
organizationName: metadata.organizationName
};
if (metadata.maxRamExponent !== undefined) {
serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent));
}
for (const prop of propertiesToPatternMatch) {
if (metadata[prop] !== undefined) {
serverParams[prop] = toNumber(metadata[prop]);
}
}
const server = new Server(serverParams);
for (const filename of (metadata.literature || [])) {
server.messages.push(filename);
}
if (metadata.specialName !== undefined) {
SpecialServerIps.addIp(metadata.specialName, server.ip);
}
AddToAllServers(server);
if (metadata.networkLayer !== undefined) {
networkLayers[toNumber(metadata.networkLayer) - 1].push(server);
}
}
/* Create a randomized network for all the foreign servers */
const linkComputers = (server1: Server, server2: Server) => {
server1.serversOnNetwork.push(server2.ip);
server2.serversOnNetwork.push(server1.ip);
};
const getRandomArrayItem = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)];
const linkNetworkLayers = (network1: Server[], selectServer: () => Server) => {
for (const server of network1) {
linkComputers(server, selectServer());
}
};
// Connect the first tier of servers to the player's home computer
linkNetworkLayers(networkLayers[0], () => homeComputer);
for (let i = 1; i < networkLayers.length; i++) {
linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1]));
}
}
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
export function loadAllServers(saveString: string) {
AllServers = JSON.parse(saveString, Reviver);
}

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