mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 14:28:36 +02:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29c5c9b99d | ||
|
|
49dc9b7cb0 | ||
|
|
856d02a129 | ||
|
|
9542879f86 | ||
|
|
1034f7cac5 | ||
|
|
9a7061503a | ||
|
|
449605d330 | ||
|
|
3556d6a1ff | ||
|
|
4858dabe99 | ||
|
|
d54439be8b | ||
|
|
31e703d126 | ||
|
|
2754a644ae | ||
|
|
b7170e4ded | ||
|
|
4df6638444 | ||
|
|
d4c72474cb | ||
|
|
1c0201534b | ||
|
|
fa14bb5362 | ||
|
|
f88c585714 | ||
|
|
8514104560 | ||
|
|
5b7743967e | ||
|
|
2e9fe02023 | ||
|
|
b8bdf0da82 | ||
|
|
1f639c6a3a | ||
|
|
035cdb8b0d | ||
|
|
2ba7ac82eb | ||
|
|
77571543f2 | ||
|
|
3f312d0ce6 | ||
|
|
4bdb34bc7b | ||
|
|
f7fd3c859f | ||
|
|
dfd7aa2d2e | ||
|
|
0df437ef35 | ||
|
|
bedd615c0e | ||
|
|
1f4f6bd179 | ||
|
|
d75ff5d95b | ||
|
|
c3bc6a0c28 | ||
|
|
066ccf343a | ||
|
|
c973663dc2 | ||
|
|
08136524be | ||
|
|
261abdea78 | ||
|
|
b132efbded | ||
|
|
f8f4299ed5 | ||
|
|
9f715020df | ||
|
|
060e0f7bfc | ||
|
|
8df7f8de4b | ||
|
|
e6c5ff7ab7 | ||
|
|
a28fe7ab9f | ||
|
|
c3ecc189fd | ||
|
|
b4057fcb26 | ||
|
|
8470f307ac | ||
|
|
8eecb1539c | ||
|
|
5230837dad | ||
|
|
da97c1b4b9 | ||
|
|
963cb0039d | ||
|
|
3e10f5de6d | ||
|
|
6969a8f92c | ||
|
|
2198bfd962 | ||
|
|
d4d5202cbd | ||
|
|
cde2c9e615 | ||
|
|
59018d9902 | ||
|
|
c378400a10 | ||
|
|
58062bf421 | ||
|
|
f3dbdad011 | ||
|
|
d9ee715bc7 | ||
|
|
bd13234f06 | ||
|
|
fa4e98d06c | ||
|
|
cca32923e3 | ||
|
|
a22bfb5015 | ||
|
|
18d8b2ecd4 | ||
|
|
9879d07d7c | ||
|
|
d54e39c9c6 | ||
|
|
67d083772a | ||
|
|
e6a3794849 | ||
|
|
473f0f1447 | ||
|
|
e1b8a23f1e | ||
|
|
af3323a111 |
@@ -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,
|
||||
@@ -32,4 +27,4 @@ publish, and distribute your contributions to the project. A formal
|
||||
Contributor's License Agreement will be drawn up in the future.
|
||||
|
||||
If you would like to make significant contributions to the project as a
|
||||
collaborator, please reach out to @danielyxie to help coordinate the effort.
|
||||
collaborator, please reach out to @danielyxie to help coordinate the effort.
|
||||
|
||||
@@ -51,6 +51,44 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Checkbox for (de)selecting autoleveling */
|
||||
.bbcheckbox {
|
||||
position: relative;
|
||||
display: inline;
|
||||
label {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: black;
|
||||
border-width: 1px;
|
||||
border-color: white;
|
||||
border-style: solid;
|
||||
&:after {
|
||||
content: '';
|
||||
width: 9px;
|
||||
height: 5px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
border: 3px solid white;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
opacity: 0;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
input[type=checkbox] {
|
||||
margin: 3px;
|
||||
visibility: hidden;
|
||||
&:checked + label:after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Bladeburner Console */
|
||||
.bladeburner-console-div {
|
||||
display: inline-block;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
36
css/dev-menu.css
Normal file
36
css/dev-menu.css
Normal file
@@ -0,0 +1,36 @@
|
||||
.add-exp-button {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.remove-exp-button {
|
||||
margin-left:0px;
|
||||
}
|
||||
|
||||
.exp-input {
|
||||
margin-right: 0px;
|
||||
margin-left:0px;
|
||||
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.touch-right {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.touch-left {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.touch-sides {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
6
css/grid.min.css
vendored
Normal file
6
css/grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -62,6 +62,9 @@ a:visited {
|
||||
.text-input {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
/* Notification icon (for create program right now only) */
|
||||
@@ -203,7 +206,6 @@ a:visited {
|
||||
|
||||
.status-text {
|
||||
display: inline-block;
|
||||
height: 15%;
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
-webkit-animation: status-text 3s 1;
|
||||
@@ -215,10 +217,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;
|
||||
|
||||
2
dist/engine.bundle.js
vendored
2
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2578
dist/engine.css
vendored
2578
dist/engine.css
vendored
File diff suppressed because it is too large
Load Diff
152
dist/vendor.bundle.js
vendored
152
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -14,6 +14,8 @@ Sleeve technology unlocks two different gameplay features:
|
||||
|
||||
Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
|
||||
|
||||
.. _gameplay_duplicatesleeves:
|
||||
|
||||
Duplicate Sleeves
|
||||
^^^^^^^^^^^^^^^^^
|
||||
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
|
||||
@@ -28,6 +30,19 @@ Sleeves are their own individuals, which means they each have their own experien
|
||||
When a sleeve earns experience, it earns experience for itself, the player's
|
||||
original consciousness, as well as all of the player's other sleeves.
|
||||
|
||||
Duplicate Sleeves are **not** reset when installing Augmentations, but they are reset
|
||||
when switching BitNodes.
|
||||
|
||||
Obtaining Duplicate Sleeves
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
There are two methods of obtaining Duplicate Sleeves:
|
||||
|
||||
1. Destroy BitNode-10. Each completion give you one additional Duplicate Sleeve
|
||||
2. Purchase Duplicate Sleeves from :ref:`the faction The Covenant <gameplay_factions>`.
|
||||
This is only available in BitNodes-10 and above, and is only available after defeating
|
||||
BitNode-10 at least once. Sleeves purchased this way are **permanent** (they persist
|
||||
through BitNodes). You can purchase up to 5 Duplicate Sleeves from The Covenant.
|
||||
|
||||
Synchronization
|
||||
~~~~~~~~~~~~~~~
|
||||
Synchronization is a measure of how aligned your consciousness is with that of your
|
||||
@@ -50,15 +65,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,48 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v0.45.1 - 3/23/2019
|
||||
-------------------
|
||||
* Added two new Corporation Researches
|
||||
* General UI improvements (by hydroflame and koriar)
|
||||
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors
|
||||
* Bug Fix: sleeve.getSleeveStats() should now work properly
|
||||
|
||||
v0.45.0 - 3/22/2019
|
||||
-------------------
|
||||
* Corporation changes:
|
||||
* Decreased the time of a full market cycle from 15 seconds to 10 seconds.
|
||||
* This means that each Corporation 'state' will now only take 2 seconds, rather than 3
|
||||
* Increased initial salaries for newly-hired employees
|
||||
* Increased the cost multiplier for upgrading office size (the cost will increase faster)
|
||||
* The stats of your employees now has a slightly larger effect on production & sales
|
||||
* Added several new Research upgrades
|
||||
* Market-TA research now allows you to automatically set sale price at optimal values
|
||||
* Market-TA research now works for Products (not just Materials)
|
||||
* Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k
|
||||
* Energy Material requirement of the Software industry reduced from 1 to 0.5
|
||||
* It is now slightly easier to increase the Software industry's production multiplier
|
||||
* Industries now have a maximum number of allowed products, starting at 3. This can be increased through research.
|
||||
* You can now see an approximation of how each material affects an industry's production multiplier by clicking the "?" help tip next to it
|
||||
* Significantly changed the effects of the different employee positions. See updated descriptions
|
||||
* Reduced the amount of money you gain from private investors
|
||||
* Training employees is now 3x more effective
|
||||
* Bug Fix: An industry's products are now properly separated between different cities
|
||||
|
||||
* The QLink Augemntation is now significantly stronger, but also significantly more expensive (by hydroflame)
|
||||
* Added a Netscript API for Duplicate Sleeves (by hydroflame)
|
||||
* Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder
|
||||
* After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0
|
||||
* Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina
|
||||
* Bug Fix: growthAnalyze() function now properly accounts for BitNode multipliers
|
||||
* Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself
|
||||
|
||||
v0.44.1 - 3/4/2019
|
||||
------------------
|
||||
* Duplicate Sleeve changes:
|
||||
** 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.
|
||||
|
||||
10
doc/source/netscript/bladeburnerapi/getBlackOpRank.rst
Normal file
10
doc/source/netscript/bladeburnerapi/getBlackOpRank.rst
Normal file
@@ -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
|
||||
@@ -20,7 +20,7 @@ Note that Singularity Functions require twice as much RAM outside of BitNode-4
|
||||
|
||||
.. toctree::
|
||||
:caption: Functions:
|
||||
|
||||
|
||||
universityCourse() <singularityfunctions/universityCourse>
|
||||
gymWorkout() <singularityfunctions/gymWorkout>
|
||||
travelToCity() <singularityfunctions/travelToCity>
|
||||
|
||||
76
doc/source/netscript/netscriptsleeveapi.rst
Normal file
76
doc/source/netscript/netscriptsleeveapi.rst
Normal file
@@ -0,0 +1,76 @@
|
||||
.. _netscript_sleeveapi:
|
||||
|
||||
Netscript Sleeve API
|
||||
=========================
|
||||
Netscript provides the following API for interacting with the game's
|
||||
:ref:`Duplicate Sleeve <gameplay_duplicatesleeves>` mechanic.
|
||||
|
||||
The Sleeve API is **not** immediately available to the player and must be unlocked
|
||||
later in the game.
|
||||
|
||||
.. warning:: This page contains spoilers for the game
|
||||
|
||||
The Sleeve API is unlocked in BitNode-10. If you are in BitNode-10, you will
|
||||
automatically gain access to this API. Otherwise, you must have Source-File 10 in
|
||||
order to use this API in other BitNodes
|
||||
|
||||
**Sleeve API functions must be accessed through the 'sleeve' namespace**
|
||||
|
||||
In :ref:`netscript1`::
|
||||
|
||||
sleeve.synchronize(0);
|
||||
sleeve.commitCrime(0, "shoplift");
|
||||
|
||||
In :ref:`netscriptjs`::
|
||||
|
||||
ns.sleeve.synchronize(0);
|
||||
ns.sleeve.commitCrime(0, "shoplift");
|
||||
|
||||
.. toctree::
|
||||
:caption: API Functions:
|
||||
|
||||
getNumSleeves() <sleeveapi/getNumSleeves>
|
||||
getSleeveStats() <sleeveapi/getSleeveStats>
|
||||
getInformation() <sleeveapi/getInformation>
|
||||
getTask() <sleeveapi/getTask>
|
||||
setToShockRecovery() <sleeveapi/setToShockRecovery>
|
||||
setToSynchronize() <sleeveapi/setToSynchronize>
|
||||
setToCommitCrime() <sleeveapi/setToCommitCrime>
|
||||
setToFactionWork() <sleeveapi/setToFactionWork>
|
||||
setToCompanyWork() <sleeveapi/setToCompanyWork>
|
||||
setToUniversityCourse() <sleeveapi/setToUniversityCourse>
|
||||
setToGymWorkout() <sleeveapi/setToGymWorkout>
|
||||
travel() <sleeveapi/travel>
|
||||
|
||||
.. _netscript_sleeveapi_referencingaduplicatesleeve:
|
||||
|
||||
Referencing a Duplicate Sleeve
|
||||
------------------------------
|
||||
Most of the functions in the Sleeve API perform an operation on a single Duplicate
|
||||
Sleeve. In order to specify which Sleeve the operation should be performed on,
|
||||
a numeric index is used as an identifier. The index should follow array-notation, such
|
||||
that the first Duplicate Sleeve has an index of 0, the second Duplicate Sleeve has
|
||||
an index of 1, and so on.
|
||||
|
||||
The order of the Duplicate Sleeves matches the order on the UI page.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
**Basic example usage**::
|
||||
|
||||
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
|
||||
sleeve.shockRecovery(i);
|
||||
}
|
||||
|
||||
sleep(10*60*60); // wait 10h
|
||||
|
||||
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
|
||||
sleeve.synchronize(i);
|
||||
}
|
||||
|
||||
sleep(10*60*60); // wait 10h
|
||||
|
||||
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
|
||||
sleeve.commitCrime(i, 'shoplift');
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
workForCompany() Netscript Function
|
||||
===================================
|
||||
|
||||
.. js:function:: workForCompany()
|
||||
.. js:function:: workForCompany(companyName=lastCompany)
|
||||
|
||||
:param string companyName: Name of company to work for. Must be an exact match.
|
||||
Optional. If not specified, this argument defaults to
|
||||
the last job that you worked
|
||||
|
||||
If you are not in BitNode-4, then you must have Level 2 of Source-File 4 in order to use this function.
|
||||
|
||||
|
||||
65
doc/source/netscript/sleeveapi/getInformation.rst
Normal file
65
doc/source/netscript/sleeveapi/getInformation.rst
Normal file
@@ -0,0 +1,65 @@
|
||||
getInformation() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: getInformation(sleeveNumber)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to retrieve information. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
|
||||
Return a struct containing tons of information about this sleeve
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
city: location of the sleeve,
|
||||
hp: current hp of the sleeve,
|
||||
maxHp: max hp of the sleeve,
|
||||
jobs: jobs available to the sleeve,
|
||||
jobTitle: job titles available to the sleeve,
|
||||
tor: does this sleeve have access to the tor router,
|
||||
mult: {
|
||||
agility: agility multiplier,
|
||||
agilityExp: agility exp multiplier,
|
||||
companyRep: company reputation multiplier,
|
||||
crimeMoney: crime money multiplier,
|
||||
crimeSuccess: crime success chance multiplier,
|
||||
defense: defense multiplier,
|
||||
defenseExp: defense exp multiplier,
|
||||
dexterity: dexterity multiplier,
|
||||
dexterityExp: dexterity exp multiplier,
|
||||
factionRep: faction reputation multiplier,
|
||||
hacking: hacking skill multiplier,
|
||||
hackingExp: hacking exp multiplier,
|
||||
strength: strength multiplier,
|
||||
strengthExp: strength exp multiplier,
|
||||
workMoney: work money multiplier,
|
||||
},
|
||||
timeWorked: time spent on the current task in milliseconds,
|
||||
earningsForSleeves : { earnings synchronized to other sleeves
|
||||
workHackExpGain: hacking exp gained from work,
|
||||
workStrExpGain: strength exp gained from work,
|
||||
workDefExpGain: defense exp gained from work,
|
||||
workDexExpGain: dexterity exp gained from work,
|
||||
workAgiExpGain: agility exp gained from work,
|
||||
workChaExpGain: charisma exp gained from work,
|
||||
workMoneyGain: money gained from work,
|
||||
},
|
||||
earningsForPlayer : { earnings synchronized to the player
|
||||
workHackExpGain: hacking exp gained from work,
|
||||
workStrExpGain: strength exp gained from work,
|
||||
workDefExpGain: defense exp gained from work,
|
||||
workDexExpGain: dexterity exp gained from work,
|
||||
workAgiExpGain: agility exp gained from work,
|
||||
workChaExpGain: charisma exp gained from work,
|
||||
workMoneyGain: money gained from work,
|
||||
},
|
||||
earningsForTask : { earnings for this sleeve
|
||||
workHackExpGain: hacking exp gained from work,
|
||||
workStrExpGain: strength exp gained from work,
|
||||
workDefExpGain: defense exp gained from work,
|
||||
workDexExpGain: dexterity exp gained from work,
|
||||
workAgiExpGain: agility exp gained from work,
|
||||
workChaExpGain: charisma exp gained from work,
|
||||
workMoneyGain: money gained from work,
|
||||
},
|
||||
workRepGain: sl.getRepGain(),
|
||||
}
|
||||
6
doc/source/netscript/sleeveapi/getNumSleeves.rst
Normal file
6
doc/source/netscript/sleeveapi/getNumSleeves.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
getNumSleeves() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: getNumSleeves()
|
||||
|
||||
Return the number of duplicate sleeves the player has.
|
||||
21
doc/source/netscript/sleeveapi/getSleeveStats.rst
Normal file
21
doc/source/netscript/sleeveapi/getSleeveStats.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
getSleeveStats() Netscript Function
|
||||
===================================
|
||||
|
||||
.. js:function:: getSleeveStats(sleeveNumber)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to get stats of. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
|
||||
Return a structure containing the stats of the sleeve
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
shock: current shock of the sleeve [0-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,
|
||||
}
|
||||
18
doc/source/netscript/sleeveapi/getTask.rst
Normal file
18
doc/source/netscript/sleeveapi/getTask.rst
Normal file
@@ -0,0 +1,18 @@
|
||||
getTask() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: getTask(sleeveNumber)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to retrieve task from. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
|
||||
Return the current task that the sleeve is performing. type is set to "Idle" if the sleeve isn't doing anything
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
task: string, // task type
|
||||
crime: string, // crime currently attempting, if any
|
||||
location: string, // location of the task, if any
|
||||
gymStatType: string, // stat being trained at the gym, if any
|
||||
factionWorkType: string, // faction work type being performed, if any
|
||||
}
|
||||
11
doc/source/netscript/sleeveapi/setToCommitCrime.rst
Normal file
11
doc/source/netscript/sleeveapi/setToCommitCrime.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
setToCommitCrime() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: setToCommitCrime(sleeveNumber, name)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to start commiting crime. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
:param string name: Name of the crime. Must be an exact match.
|
||||
|
||||
Return a boolean indicating whether or not this action was set successfully.
|
||||
|
||||
Returns false if an invalid action is specified.
|
||||
9
doc/source/netscript/sleeveapi/setToCompanyWork.rst
Normal file
9
doc/source/netscript/sleeveapi/setToCompanyWork.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
setToCompanyWork() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: setToCompanyWork(sleeveNumber, companyName)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to work for the company. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
:param string companyName: Name of the company to work for.
|
||||
|
||||
Return a boolean indicating whether or not the sleeve started working or this company.
|
||||
10
doc/source/netscript/sleeveapi/setToFactionWork.rst
Normal file
10
doc/source/netscript/sleeveapi/setToFactionWork.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
setToFactionWork() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: setToFactionWork(sleeveNumber, factionName, factionWorkType)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to work for the faction. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
:param string factionName: Name of the faction to work for.
|
||||
:param string factionWorkType: Name of the action to perform for this faction.
|
||||
|
||||
Return a boolean indicating whether or not the sleeve started working or this faction.
|
||||
10
doc/source/netscript/sleeveapi/setToGymWorkout.rst
Normal file
10
doc/source/netscript/sleeveapi/setToGymWorkout.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
setToGymWorkout() Netscript Function
|
||||
====================================
|
||||
|
||||
.. js:function:: setToGymWorkout(sleeveNumber, gymName, stat)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to workout at the gym. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
:param string gymName: Name of the gym.
|
||||
:param string stat: Name of the stat to train.
|
||||
|
||||
Return a boolean indicating whether or not the sleeve started working out.
|
||||
8
doc/source/netscript/sleeveapi/setToShockRecovery.rst
Normal file
8
doc/source/netscript/sleeveapi/setToShockRecovery.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
setToShockRecovery() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: setToShockRecovery(sleeveNumber)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to start recovery. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
|
||||
Return a boolean indicating whether or not this action was set successfully.
|
||||
8
doc/source/netscript/sleeveapi/setToSynchronize.rst
Normal file
8
doc/source/netscript/sleeveapi/setToSynchronize.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
setToSynchronize() Netscript Function
|
||||
=====================================
|
||||
|
||||
.. js:function:: setToSynchronize(sleeveNumber)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to start synchronizing. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
|
||||
Return a boolean indicating whether or not this action was set successfully.
|
||||
10
doc/source/netscript/sleeveapi/setToUniversityCourse.rst
Normal file
10
doc/source/netscript/sleeveapi/setToUniversityCourse.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
setToUniversityCourse() Netscript Function
|
||||
==========================================
|
||||
|
||||
.. js:function:: setToUniversityCourse(sleeveNumber, university, className)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to start taking class. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
:param string university: Name of the university to attend.
|
||||
:param string className: Name of the class to follow.
|
||||
|
||||
Return a boolean indicating whether or not this action was set successfully.
|
||||
9
doc/source/netscript/sleeveapi/travel.rst
Normal file
9
doc/source/netscript/sleeveapi/travel.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
travel() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: travel(sleeveNumber, cityName)
|
||||
|
||||
:param int sleeveNumber: Index of the sleeve to travel. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
|
||||
:param string cityName: Name of the destination city.
|
||||
|
||||
Return a boolean indicating whether or not the sleeve reached destination.
|
||||
42
index.html
42
index.html
@@ -131,7 +131,7 @@
|
||||
<h1 style="color:white;"> Script Editor Options </h1>
|
||||
<fieldset>
|
||||
<label for="script-editor-option-editor">Editor</label>
|
||||
<select id="script-editor-option-editor">
|
||||
<select id="script-editor-option-editor" class="dropdown">
|
||||
<option value="Ace">Ace</option>
|
||||
<option value="CodeMirror">CodeMirror</option>
|
||||
</select>
|
||||
@@ -139,12 +139,12 @@
|
||||
|
||||
<fieldset>
|
||||
<label for="script-editor-option-theme">Theme</label>
|
||||
<select id="script-editor-option-theme"></select>
|
||||
<select id="script-editor-option-theme" class="dropdown"></select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="script-editor-option-keybinding">Key Binding</label>
|
||||
<select id="script-editor-option-keybinding"></select>
|
||||
<select id="script-editor-option-keybinding" class="dropdown"></select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
@@ -689,7 +689,7 @@
|
||||
<a id="stock-market-expand-tickers" class="a-link-button tooltip">Expand tickers</a>
|
||||
<a id="stock-market-collapse-tickers" class="a-link-button tooltip">Collapse tickers</a>
|
||||
<br/><br/>
|
||||
<input id="stock-market-watchlist-filter" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
|
||||
<input id="stock-market-watchlist-filter" class="text-input" type="text" placeholder="Filter Stocks by symbol (comma-separated list)"/>
|
||||
<a id="stock-market-watchlist-filter-update" class="a-link-button"> Update Watchlist </a>
|
||||
<ul id="stock-market-list" style="list-style:none;">
|
||||
</ul>
|
||||
@@ -744,7 +744,7 @@
|
||||
<p id="infiltration-box-text"> </p>
|
||||
|
||||
<button id="infiltration-box-sell" class="a-link-button"> Sell on Black Market </button> <br/><br/>
|
||||
<select id="infiltration-faction-select"> </select> <br/>
|
||||
<select id="infiltration-faction-select" class="dropdown"> </select> <br/>
|
||||
<button id="infiltration-box-faction" class="a-link-button"> Give to Faction for Reputation </button>
|
||||
|
||||
</div>
|
||||
@@ -783,35 +783,7 @@
|
||||
<div id="character-overview-wrapper">
|
||||
<div id="character-overview-container">
|
||||
<div id="character-overview-text">
|
||||
<table>
|
||||
<tr id="character-hp-wrapper">
|
||||
<td>Hp:</td><td id="character-hp-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-money-wrapper">
|
||||
<td>Money: </td><td id="character-money-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-hack-wrapper">
|
||||
<td>Hack: </td><td id="character-hack-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-str-wrapper">
|
||||
<td>Str: </td><td id="character-str-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-def-wrapper">
|
||||
<td>Def: </td><td id="character-def-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-dex-wrapper">
|
||||
<td>Dex: </td><td id="character-dex-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-agi-wrapper">
|
||||
<td>Agi: </td><td id="character-agi-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-cha-wrapper">
|
||||
<td>Cha: </td><td id="character-cha-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
<tr id="character-int-wrapper">
|
||||
<td>Int: </td><td id="character-int-text" class="character-stat-cell"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- ReactJS Component -->
|
||||
</div>
|
||||
<div class="character-quick-options">
|
||||
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
|
||||
@@ -959,7 +931,7 @@
|
||||
Sets the locale for displaying numbers. Defaults to 'en'
|
||||
</span>
|
||||
</label>
|
||||
<select name="settingsLocale" id="settingsLocale">
|
||||
<select name="settingsLocale" id="settingsLocale" class="dropdown">
|
||||
<option value="en">en</option>
|
||||
<option value="bg">bg</option>
|
||||
<option value="cs">cs</option>
|
||||
|
||||
843
package-lock.json
generated
843
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -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",
|
||||
@@ -22,7 +24,7 @@
|
||||
"file-saver": "^1.3.8",
|
||||
"interpret": "^1.0.0",
|
||||
"jquery": "^3.3.1",
|
||||
"jshint": "^2.9.7",
|
||||
"jshint": "^2.10.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"jsplumb": "^2.6.8",
|
||||
"jszip": "^3.1.5",
|
||||
@@ -31,14 +33,18 @@
|
||||
"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",
|
||||
"uuid": "^3.2.1",
|
||||
"w3c-blob": "0.0.1"
|
||||
},
|
||||
"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",
|
||||
@@ -76,6 +82,8 @@
|
||||
"ts-loader": "^4.4.1",
|
||||
"tslint": "^5.10.0",
|
||||
"typescript": "^2.9.2",
|
||||
"uglify-es": "^3.3.9",
|
||||
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||
"url-loader": "^1.0.1",
|
||||
"watchpack": "^1.6.0",
|
||||
"webpack": "^4.12.0",
|
||||
@@ -106,5 +114,5 @@
|
||||
"watch": "webpack --watch --mode production",
|
||||
"watch:dev": "webpack --watch --mode development"
|
||||
},
|
||||
"version": "0.40.2"
|
||||
"version": "0.45.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;
|
||||
|
||||
@@ -31,6 +31,8 @@ import { getTimestamp } from "../utils/helpers/getTi
|
||||
import { removeElement } from "../utils/uiHelpers/removeElement";
|
||||
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
||||
|
||||
const stealthIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style="fill:#adff2f;"><g><path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z"/><path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z"/></g></svg> `
|
||||
const killIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style="fill:#adff2f;"><path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0"/><path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0"/><path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0"/><path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0"/></svg>`
|
||||
|
||||
const CityNames = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"];
|
||||
|
||||
@@ -1417,7 +1419,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`);
|
||||
@@ -1799,6 +1801,12 @@ Bladeburner.prototype.createContent = function() {
|
||||
DomElems.bladeburnerDiv.appendChild(DomElems.overviewConsoleParentDiv);
|
||||
DomElems.bladeburnerDiv.appendChild(DomElems.actionAndSkillsDiv);
|
||||
|
||||
|
||||
// legend
|
||||
const legend = createElement("div")
|
||||
legend.innerHTML = `<span class="text">${stealthIcon}= This action requires stealth, ${killIcon} = This action involves retirement</span>`
|
||||
DomElems.bladeburnerDiv.appendChild(legend);
|
||||
|
||||
document.getElementById("entire-game-container").appendChild(DomElems.bladeburnerDiv);
|
||||
|
||||
if (this.consoleLogs.length === 0) {
|
||||
@@ -2166,12 +2174,12 @@ Bladeburner.prototype.createBlackOpsContent = function() {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
for (var i = 0; i < blackops.length; ++i) {
|
||||
for (var i = blackops.length-1; i >= 0 ; --i) {
|
||||
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
|
||||
DomElems.blackops[blackops[i].name] = createElement("div", {
|
||||
class:"bladeburner-action", name:blackops[i].name
|
||||
});
|
||||
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
|
||||
if (this.blackops[[blackops[i].name]] == null) {break;} //Can't be found in completed blackops
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2504,7 +2512,8 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
|
||||
el.appendChild(createElement("pre", { //Info
|
||||
display:"inline-block",
|
||||
innerHTML:action.desc + "\n\n" +
|
||||
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
||||
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
|
||||
|
||||
"Time Required (s): " + formatNumber(actionTime, 0) + "\n" +
|
||||
"Contracts remaining: " + Math.floor(action.count) + "\n" +
|
||||
"Successes: " + action.successes + "\n" +
|
||||
@@ -2518,14 +2527,21 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) {
|
||||
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
||||
tooltip:"Automatically increase contract level when possible"
|
||||
}));
|
||||
var autolevelCheckbox = createElement("input", {
|
||||
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
||||
checked:action.autoLevel,
|
||||
changeListener:()=>{
|
||||
action.autoLevel = autolevelCheckbox.checked;
|
||||
}
|
||||
|
||||
const checkboxDiv = createElement("div", { class: "bbcheckbox" });
|
||||
const checkboxInput = createElement("input", {
|
||||
type:"checkbox",
|
||||
id: autolevelCheckboxId,
|
||||
checked: action.autoLevel,
|
||||
changeListener: () => {
|
||||
action.autoLevel = checkboxInput.checked;
|
||||
},
|
||||
});
|
||||
el.appendChild(autolevelCheckbox);
|
||||
const checkmarkLabel = createElement("label", { for: autolevelCheckboxId });
|
||||
checkboxDiv.appendChild(checkboxInput);
|
||||
checkboxDiv.appendChild(checkmarkLabel);
|
||||
|
||||
el.appendChild(checkboxDiv);
|
||||
}
|
||||
|
||||
Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
||||
@@ -2640,8 +2656,8 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
||||
el.appendChild(createElement("pre", {
|
||||
display:"inline-block",
|
||||
innerHTML:action.desc + "\n\n" +
|
||||
"Estimated success chance: " + formatNumber(estimatedSuccessChance*100, 1) + "%\n" +
|
||||
"Time Required(s): " + formatNumber(actionTime, 1) + "\n" +
|
||||
`Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
|
||||
"Time Required(s): " + formatNumber(actionTime, 0) + "\n" +
|
||||
"Operations remaining: " + Math.floor(action.count) + "\n" +
|
||||
"Successes: " + action.successes + "\n" +
|
||||
"Failures: " + action.failures,
|
||||
@@ -2654,14 +2670,21 @@ Bladeburner.prototype.updateOperationsUIElement = function(el, action) {
|
||||
for:autolevelCheckboxId, innerText:"Autolevel",color:"white",
|
||||
tooltip:"Automatically increase operation level when possible"
|
||||
}));
|
||||
var autolevelCheckbox = createElement("input", {
|
||||
type:"checkbox", id:autolevelCheckboxId, margin:"4px",
|
||||
checked:action.autoLevel,
|
||||
changeListener:()=>{
|
||||
action.autoLevel = autolevelCheckbox.checked;
|
||||
}
|
||||
|
||||
const checkboxDiv = createElement("div", { class: "bbcheckbox" });
|
||||
const checkboxInput = createElement("input", {
|
||||
type:"checkbox",
|
||||
id: autolevelCheckboxId,
|
||||
checked: action.autoLevel,
|
||||
changeListener: () => {
|
||||
action.autoLevel = checkboxInput.checked;
|
||||
},
|
||||
});
|
||||
el.appendChild(autolevelCheckbox);
|
||||
const checkmarkLabel = createElement("label", { for: autolevelCheckboxId });
|
||||
checkboxDiv.appendChild(checkboxInput);
|
||||
checkboxDiv.appendChild(checkmarkLabel);
|
||||
|
||||
el.appendChild(checkboxDiv);
|
||||
}
|
||||
|
||||
Bladeburner.prototype.updateBlackOpsUIElement = function(el, action) {
|
||||
@@ -2760,8 +2783,8 @@ 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),
|
||||
innerHTML:`Estimated Success Chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` +
|
||||
"Time Required(s): " + formatNumber(actionTime, 0),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -3953,7 +3976,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.1",
|
||||
|
||||
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||
@@ -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,37 @@ 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.1
|
||||
* Added two new Corporation Researches
|
||||
* General UI improvements (by hydroflame and koriar)
|
||||
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors
|
||||
* Bug Fix: sleeve.getSleeveStats() should now work properly
|
||||
|
||||
* 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
|
||||
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
|
||||
* 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
2386
src/Corporation/Corporation.jsx
Normal file
2386
src/Corporation/Corporation.jsx
Normal file
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");
|
||||
}
|
||||
|
||||
115
src/Corporation/Warehouse.ts
Normal file
115
src/Corporation/Warehouse.ts
Normal file
@@ -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,19 +14,21 @@ 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");
|
||||
const dronesTransport: Node = makeNode("Drones - Transport");
|
||||
const goJuice: Node = makeNode("Go-Juice");
|
||||
const hrRecruitment: Node = makeNode("HRBuddy-Recruitment");
|
||||
const hrTraining: Node = makeNode("HRBuddy-Training");
|
||||
const joywire: Node = makeNode("JoyWire");
|
||||
const marketta1: Node = makeNode("Market-TA.I");
|
||||
const marketta2: Node = makeNode("Market-TA.II");
|
||||
@@ -40,6 +42,8 @@ export function getBaseResearchTreeCopy(): ResearchTree {
|
||||
drones.addChild(dronesAssembly);
|
||||
drones.addChild(dronesTransport);
|
||||
|
||||
hrRecruitment.addChild(hrTraining);
|
||||
|
||||
marketta1.addChild(marketta2);
|
||||
|
||||
overclock.addChild(stimu);
|
||||
@@ -47,13 +51,40 @@ export function getBaseResearchTreeCopy(): ResearchTree {
|
||||
rootNode.addChild(autoBrew);
|
||||
rootNode.addChild(autoParty);
|
||||
rootNode.addChild(autoDrugs);
|
||||
rootNode.addChild(bulkPurchasing);
|
||||
rootNode.addChild(drones);
|
||||
rootNode.addChild(hrRecruitment);
|
||||
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,13 +70,28 @@ 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 " +
|
||||
"production by 10%.",
|
||||
sciResearchMult: 1.1,
|
||||
},
|
||||
{
|
||||
name: "HRBuddy-Recruitment",
|
||||
cost: 15e3,
|
||||
desc: "Use automated software to handle the hiring of employees. With this " +
|
||||
"research, each office will automatically hire one employee per " +
|
||||
"market cycle if there is available space."
|
||||
|
||||
},
|
||||
{
|
||||
name: "HRBuddy-Training",
|
||||
cost: 20e3,
|
||||
desc: "Use automated software to handle the training of employees. With this " +
|
||||
"research, each employee hired with HRBuddy-Recruitment will automatically " +
|
||||
"be assigned to 'Training', rather than being unassigned."
|
||||
},
|
||||
{
|
||||
name: "JoyWire",
|
||||
cost: 20e3,
|
||||
@@ -83,15 +104,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 +142,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,
|
||||
},
|
||||
];
|
||||
|
||||
22
src/Corporation/ui/BaseReactComponent.js
Normal file
22
src/Corporation/ui/BaseReactComponent.js
Normal file
@@ -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() {}
|
||||
}
|
||||
62
src/Corporation/ui/CityTabs.jsx
Normal file
62
src/Corporation/ui/CityTabs.jsx
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
1736
src/Corporation/ui/CorporationUIEventHandler.js
Normal file
1736
src/Corporation/ui/CorporationUIEventHandler.js
Normal file
File diff suppressed because it is too large
Load Diff
80
src/Corporation/ui/HeaderTabs.jsx
Normal file
80
src/Corporation/ui/HeaderTabs.jsx
Normal file
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
34
src/Corporation/ui/Industry.jsx
Normal file
34
src/Corporation/ui/Industry.jsx
Normal file
@@ -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>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
609
src/Corporation/ui/IndustryOffice.jsx
Normal file
609
src/Corporation/ui/IndustryOffice.jsx
Normal file
@@ -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();
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
277
src/Corporation/ui/IndustryOverview.jsx
Normal file
277
src/Corporation/ui/IndustryOverview.jsx
Normal file
@@ -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: ${convertEffectFacToGraphic(division.hwFac)}<br>` +
|
||||
`Robots: ${convertEffectFacToGraphic(division.robFac)}<br>` +
|
||||
`AI Cores: ${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>
|
||||
)
|
||||
}
|
||||
}
|
||||
535
src/Corporation/ui/IndustryWarehouse.jsx
Normal file
535
src/Corporation/ui/IndustryWarehouse.jsx
Normal file
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/Corporation/ui/LevelableUpgrade.jsx
Normal file
36
src/Corporation/ui/LevelableUpgrade.jsx
Normal file
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
106
src/Corporation/ui/MainPanel.jsx
Normal file
106
src/Corporation/ui/MainPanel.jsx
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
323
src/Corporation/ui/Overview.jsx
Normal file
323
src/Corporation/ui/Overview.jsx
Normal file
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
17
src/Corporation/ui/Root.jsx
Normal file
17
src/Corporation/ui/Root.jsx
Normal file
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
96
src/Corporation/ui/Routing.ts
Normal file
96
src/Corporation/ui/Routing.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
30
src/Corporation/ui/UnlockUpgrade.jsx
Normal file
30
src/Corporation/ui/UnlockUpgrade.jsx
Normal file
@@ -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";
|
||||
|
||||
713
src/DevMenu.js
713
src/DevMenu.js
@@ -1,713 +0,0 @@
|
||||
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
||||
import { CodingContractTypes } from "./CodingContracts";
|
||||
import { generateContract,
|
||||
generateRandomContract,
|
||||
generateRandomContractOnHome } from "./CodingContractGenerator";
|
||||
import { Companies } from "./Company/Companies";
|
||||
import { Company } from "./Company/Company";
|
||||
import { Programs } from "./Programs/Programs";
|
||||
import { Factions } from "./Faction/Factions";
|
||||
import { Player } from "./Player";
|
||||
import { AllServers } from "./Server";
|
||||
import { hackWorldDaemon } from "./RedPill";
|
||||
import { StockMarket,
|
||||
SymbolToStockMap } from "./StockMarket/StockMarket";
|
||||
import { Stock } from "./StockMarket/Stock";
|
||||
import { Terminal } from "./Terminal";
|
||||
|
||||
import { numeralWrapper } from "./ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../utils/DialogBox";
|
||||
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
||||
import { createElement } from "../utils/uiHelpers/createElement";
|
||||
import { createOptionElement } from "../utils/uiHelpers/createOptionElement";
|
||||
import { getSelectText } from "../utils/uiHelpers/getSelectData";
|
||||
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
||||
|
||||
const devMenuContainerId = "dev-menu-container";
|
||||
|
||||
export function createDevMenu() {
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
throw new Error("Cannot create Dev Menu because you are not in a dev build");
|
||||
}
|
||||
|
||||
const devMenuText = createElement("h1", {
|
||||
display: "block",
|
||||
innerText: "Development Menu - Only meant to be used for testing/debugging",
|
||||
});
|
||||
|
||||
// Generic
|
||||
const genericHeader = createElement("h2", {
|
||||
display: "block",
|
||||
innerText: "Generic"
|
||||
});
|
||||
|
||||
const addMoney = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
Player.gainMoney(1e15);
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Add $1000t",
|
||||
});
|
||||
|
||||
const addMoney2 = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
Player.gainMoney(1e12);
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Add $1t",
|
||||
})
|
||||
|
||||
const addRam = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
Player.getHomeComputer().maxRam *= 2;
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Double Home Computer RAM",
|
||||
});
|
||||
|
||||
const triggerBitflume = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
hackWorldDaemon(Player.bitNodeN, true);
|
||||
},
|
||||
innerText: "Trigger BitFlume",
|
||||
});
|
||||
|
||||
const destroyCurrentBitnode = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
hackWorldDaemon(Player.bitNodeN);
|
||||
},
|
||||
innerText: "Destroy Current BitNode",
|
||||
tooltip: "Will grant Source-File for the BitNode",
|
||||
});
|
||||
|
||||
// Experience / stats
|
||||
const statsHeader = createElement("h2", {
|
||||
display: "block",
|
||||
innerText: "Experience/Stats"
|
||||
});
|
||||
|
||||
const statsHackingExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- hacking exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsHackingExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsHackingExpInput.value);
|
||||
Player.gainHackingExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Hacking Exp",
|
||||
});
|
||||
|
||||
const statsStrengthExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- strength exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsStrengthExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsStrengthExpInput.value);
|
||||
Player.gainStrengthExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Strength Exp",
|
||||
});
|
||||
|
||||
const statsDefenseExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- defense exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsDefenseExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsDefenseExpInput.value);
|
||||
Player.gainDefenseExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Defense Exp",
|
||||
});
|
||||
|
||||
const statsDexterityExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- dexterity exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsDexterityExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsDexterityExpInput.value);
|
||||
Player.gainDexterityExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Dexterity Exp",
|
||||
});
|
||||
|
||||
const statsAgilityExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- agility exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsAgilityExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsAgilityExpInput.value);
|
||||
Player.gainAgilityExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Agility Exp",
|
||||
});
|
||||
|
||||
const statsCharismaExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- charisma exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsCharismaExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsCharismaExpInput.value);
|
||||
Player.gainCharismaExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Charisma Exp",
|
||||
});
|
||||
|
||||
const statsIntelligenceExpInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "+/- intelligence exp",
|
||||
type: "number",
|
||||
});
|
||||
const statsIntelligenceExpButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const exp = parseInt(statsIntelligenceExpInput.value);
|
||||
Player.gainIntelligenceExp(exp);
|
||||
Player.updateSkillLevels();
|
||||
},
|
||||
innerText: "Add Intelligence Exp",
|
||||
});
|
||||
|
||||
const statsEnableIntelligenceButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
Player.intelligence = 1;
|
||||
},
|
||||
innerText: "Enable Intelligence"
|
||||
});
|
||||
|
||||
const statsDisableIntelligenceButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
Player.intelligence = 0;
|
||||
},
|
||||
innerText: "Disable Intelligence"
|
||||
});
|
||||
|
||||
// Factions
|
||||
const factionsHeader = createElement("h2", {innerText: "Factions"});
|
||||
|
||||
const factionsDropdown = createElement("select", {
|
||||
class: "dropdown",
|
||||
margin: "5px",
|
||||
});
|
||||
for (const i in Factions) {
|
||||
factionsDropdown.options[factionsDropdown.options.length] = new Option(Factions[i].name, Factions[i].name);
|
||||
}
|
||||
|
||||
const factionsAddButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const facName = factionsDropdown.options[factionsDropdown.selectedIndex].value;
|
||||
Player.receiveInvite(facName);
|
||||
},
|
||||
innerText: "Receive Invite to Faction",
|
||||
});
|
||||
|
||||
const factionsReputationInput = createElement("input", {
|
||||
placeholder: "Rep to add to faction",
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const factionsReputationButton = createElement("button", {
|
||||
class: "std-button",
|
||||
innerText: "Add rep to faction",
|
||||
clickListener: () => {
|
||||
const facName = getSelectText(factionsDropdown);
|
||||
const fac = Factions[facName];
|
||||
const rep = parseFloat(factionsReputationInput.value);
|
||||
if (fac != null && !isNaN(rep)) {
|
||||
fac.playerReputation += rep;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Augmentations
|
||||
const augmentationsHeader = createElement("h2", {innerText: "Augmentations"});
|
||||
|
||||
const augmentationsDropdown = createElement("select", {
|
||||
class: "dropdown",
|
||||
margin: "5px",
|
||||
});
|
||||
for (const i in AugmentationNames) {
|
||||
const augName = AugmentationNames[i];
|
||||
augmentationsDropdown.options[augmentationsDropdown.options.length] = new Option(augName, augName);
|
||||
}
|
||||
|
||||
const augmentationsQueueButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
Player.queueAugmentation(augmentationsDropdown.options[augmentationsDropdown.selectedIndex].value);
|
||||
},
|
||||
innerText: "Queue Augmentation",
|
||||
});
|
||||
|
||||
const giveAllAugmentationsButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
for (const i in AugmentationNames) {
|
||||
const augName = AugmentationNames[i];
|
||||
Player.queueAugmentation(augName);
|
||||
}
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Queue All Augmentations",
|
||||
});
|
||||
|
||||
// Source Files
|
||||
const sourceFilesHeader = createElement("h2", { innerText: "Source-Files" });
|
||||
|
||||
const removeSourceFileDropdown = createElement("select", {
|
||||
class: "dropdown",
|
||||
margin: "5px",
|
||||
});
|
||||
for (let i = 0; i < 24; ++i) {
|
||||
removeSourceFileDropdown.add(createOptionElement(String(i)));
|
||||
}
|
||||
|
||||
const removeSourceFileButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const numToRemove = parseInt(getSelectText(removeSourceFileDropdown));
|
||||
for (let i = 0; i < Player.sourceFiles.length; ++i) {
|
||||
if (Player.sourceFiles[i].n === numToRemove) {
|
||||
Player.sourceFiles.splice(i, 1);
|
||||
hackWorldDaemon(Player.bitNodeN, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
innerText: "Remove Source File and Trigger Bitflume",
|
||||
});
|
||||
|
||||
// Programs
|
||||
const programsHeader = createElement("h2", {innerText: "Programs"});
|
||||
|
||||
const programsAddDropdown = createElement("select", {
|
||||
class: "dropdown",
|
||||
margin: "5px",
|
||||
});
|
||||
for (const i in Programs) {
|
||||
const progName = Programs[i].name;
|
||||
programsAddDropdown.options[programsAddDropdown.options.length] = new Option(progName, progName);
|
||||
}
|
||||
|
||||
const programsAddButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const program = programsAddDropdown.options[programsAddDropdown.selectedIndex].value;
|
||||
if(!Player.hasProgram(program)) {
|
||||
Player.getHomeComputer().programs.push(program);
|
||||
}
|
||||
},
|
||||
innerText: "Add Program",
|
||||
})
|
||||
|
||||
// Servers
|
||||
const serversHeader = createElement("h2", {innerText: "Servers"});
|
||||
|
||||
const serversOpenAll = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
for (const i in AllServers) {
|
||||
AllServers[i].hasAdminRights = true;
|
||||
AllServers[i].sshPortOpen = true;
|
||||
AllServers[i].ftpPortOpen = true;
|
||||
AllServers[i].smtpPortOpen = true;
|
||||
AllServers[i].httpPortOpen = true;
|
||||
AllServers[i].sqlPortOpen = true;
|
||||
AllServers[i].openPortCount = 5;
|
||||
}
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Get Admin Rights to all servers",
|
||||
});
|
||||
|
||||
const serversMinSecurityAll = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
for (const i in AllServers) {
|
||||
AllServers[i].hackDifficulty = AllServers[i].minDifficulty;
|
||||
}
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Set all servers to min security",
|
||||
});
|
||||
|
||||
const serversMaxMoneyAll = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
for (const i in AllServers) {
|
||||
AllServers[i].moneyAvailable = AllServers[i].moneyMax;
|
||||
}
|
||||
},
|
||||
display: "block",
|
||||
innerText: "Set all servers to max money",
|
||||
});
|
||||
|
||||
const serversConnectToDropdown = createElement("select", {class: "dropdown"});
|
||||
for (const i in AllServers) {
|
||||
const hn = AllServers[i].hostname;
|
||||
serversConnectToDropdown.options[serversConnectToDropdown.options.length] = new Option(hn, hn);
|
||||
}
|
||||
|
||||
const serversConnectToButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const host = serversConnectToDropdown.options[serversConnectToDropdown.selectedIndex].value;
|
||||
Terminal.connectToServer(host);
|
||||
},
|
||||
innerText: "Connect to server",
|
||||
});
|
||||
|
||||
// Companies
|
||||
const companiesHeader = createElement("h2", { innerText: "Companies" });
|
||||
|
||||
const companiesDropdown = createElement("select", {
|
||||
class: "dropdown",
|
||||
margin: "5px",
|
||||
});
|
||||
for (const c in Companies) {
|
||||
companiesDropdown.add(createOptionElement(Companies[c].name));
|
||||
}
|
||||
|
||||
const companyReputationInput = createElement("input", {
|
||||
margin: "5px",
|
||||
placeholder: "Rep to add to company",
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const companyReputationButton = createElement("button", {
|
||||
class: "std-button",
|
||||
innerText: "Add rep to company",
|
||||
clickListener: () => {
|
||||
const compName = getSelectText(companiesDropdown);
|
||||
const company = Companies[compName];
|
||||
const rep = parseFloat(companyReputationInput.value);
|
||||
if (company != null && !isNaN(rep)) {
|
||||
company.playerReputation += rep;
|
||||
} else {
|
||||
console.warn(`Invalid input for Dev Menu Company Rep. Company Name: ${compName}. Rep: ${rep}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Bladeburner
|
||||
const bladeburnerHeader = createElement("h2", {innerText: "Bladeburner"});
|
||||
|
||||
const bladeburnerGainRankInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "Rank to gain (or negative to lose rank)",
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const bladeburnerGainRankButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
try {
|
||||
const rank = parseInt(bladeburnerGainRankInput.value);
|
||||
Player.bladeburner.changeRank(rank);
|
||||
} catch(e) {
|
||||
exceptionAlert(`Failed to change Bladeburner Rank in dev menu: ${e}`);
|
||||
}
|
||||
},
|
||||
innerText: "Gain Bladeburner Rank",
|
||||
});
|
||||
|
||||
const bladeburnerStoredCyclesInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "# Cycles to Add",
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const bladeburnerStoredCyclesButton = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
try {
|
||||
const cycles = parseInt(bladeburnerStoredCyclesInput.value);
|
||||
Player.bladeburner.storedCycles += cycles;
|
||||
} catch(e) {
|
||||
exceptionAlert(`Failed to add cycles to Bladeburner in dev menu: ${e}`);
|
||||
}
|
||||
},
|
||||
innerText: "Add Cycles to Bladeburner mechanic",
|
||||
});
|
||||
|
||||
// Gang
|
||||
const gangHeader = createElement("h2", {innerText: "Gang"});
|
||||
|
||||
const gangStoredCyclesInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "# Cycles to add",
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const gangAddStoredCycles = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
try {
|
||||
const cycles = parseInt(gangStoredCyclesInput.value);
|
||||
Player.gang.storedCycles += cycles;
|
||||
} catch(e) {
|
||||
exceptionAlert(`Failed to add stored cycles to gang mechanic: ${e}`);
|
||||
}
|
||||
},
|
||||
innerText: "Add cycles to Gang mechanic",
|
||||
});
|
||||
|
||||
// Coding Contracts
|
||||
const contractsHeader = createElement("h2", {innerText: "Coding Contracts"});
|
||||
|
||||
const generateRandomContractBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
generateRandomContract();
|
||||
},
|
||||
innerText: "Generate Random Contract",
|
||||
});
|
||||
|
||||
const generateRandomContractOnHomeBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
generateRandomContractOnHome();
|
||||
},
|
||||
innerText: "Generate Random Contract on Home Comp",
|
||||
});
|
||||
|
||||
const generateContractWithTypeSelector = createElement("select", { margin: "5px" });
|
||||
const contractTypes = Object.keys(CodingContractTypes);
|
||||
for (let i = 0; i < contractTypes.length; ++i) {
|
||||
generateContractWithTypeSelector.add(createOptionElement(contractTypes[i]));
|
||||
}
|
||||
|
||||
const generateContractWithTypeBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
generateContract({
|
||||
problemType: getSelectText(generateContractWithTypeSelector),
|
||||
server: "home",
|
||||
});
|
||||
},
|
||||
innerText: "Generate Specified Contract Type on Home Comp",
|
||||
});
|
||||
|
||||
// Stock Market
|
||||
const stockmarketHeader = createElement("h2", {innerText: "Stock Market"});
|
||||
|
||||
const stockInput = createElement("input", {
|
||||
class: "text-input",
|
||||
display: "block",
|
||||
placeholder: "Stock symbol(s), or 'all'",
|
||||
});
|
||||
|
||||
function processStocks(cb) {
|
||||
const input = stockInput.value.toString().replace(/\s/g, '');
|
||||
|
||||
// Empty input, or "all", will process all stocks
|
||||
if (input === "" || input.toLowerCase() === "all") {
|
||||
for (const name in StockMarket) {
|
||||
if (StockMarket.hasOwnProperty(name)) {
|
||||
const stock = StockMarket[name];
|
||||
if (stock instanceof Stock) {
|
||||
cb(stock);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const stockSymbols = input.split(",");
|
||||
for (let i = 0; i < stockSymbols.length; ++i) {
|
||||
const stock = SymbolToStockMap[stockSymbols];
|
||||
if (stock instanceof Stock) {
|
||||
cb(stock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stockPriceChangeInput = createElement("input", {
|
||||
class: "text-input",
|
||||
margin: "5px",
|
||||
placeholder: "Price to change stock(s) to",
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const stockPriceChangeBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
const price = parseInt(stockPriceChangeInput.value);
|
||||
if (isNaN(price)) { return; }
|
||||
|
||||
processStocks((stock) => {
|
||||
stock.price = price;
|
||||
});
|
||||
dialogBoxCreate(`Stock Prices changed to ${price}`);
|
||||
},
|
||||
innerText: "Change Stock Price(s)",
|
||||
});
|
||||
|
||||
const stockViewPriceCapBtn = createElement("button", {
|
||||
class: "std-button",
|
||||
clickListener: () => {
|
||||
let text = "";
|
||||
processStocks((stock) => {
|
||||
text += `${stock.symbol}: ${numeralWrapper.format(stock.cap, '$0.000a')}<br>`;
|
||||
});
|
||||
dialogBoxCreate(text);
|
||||
},
|
||||
innerText: "View Stock Price Caps",
|
||||
});
|
||||
|
||||
// Sleeves
|
||||
const sleevesHeader = createElement("h2", { innerText: "Sleeves" });
|
||||
|
||||
const sleevesRemoveAllShockRecovery = createElement("button", {
|
||||
class: "std-button",
|
||||
display: "block",
|
||||
innerText: "Set Shock Recovery of All Sleeves to 0",
|
||||
clickListener: () => {
|
||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||
Player.sleeves[i].shock = 100;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add everything to container, then append to main menu
|
||||
const devMenuContainer = createElement("div", {
|
||||
class: "generic-menupage-container",
|
||||
id: devMenuContainerId,
|
||||
});
|
||||
|
||||
devMenuContainer.appendChild(devMenuText);
|
||||
devMenuContainer.appendChild(genericHeader);
|
||||
devMenuContainer.appendChild(addMoney);
|
||||
devMenuContainer.appendChild(addMoney2);
|
||||
devMenuContainer.appendChild(addRam);
|
||||
devMenuContainer.appendChild(triggerBitflume);
|
||||
devMenuContainer.appendChild(destroyCurrentBitnode);
|
||||
devMenuContainer.appendChild(statsHeader);
|
||||
devMenuContainer.appendChild(statsHackingExpInput);
|
||||
devMenuContainer.appendChild(statsHackingExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsStrengthExpInput);
|
||||
devMenuContainer.appendChild(statsStrengthExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsDefenseExpInput);
|
||||
devMenuContainer.appendChild(statsDefenseExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsDexterityExpInput);
|
||||
devMenuContainer.appendChild(statsDexterityExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsAgilityExpInput);
|
||||
devMenuContainer.appendChild(statsAgilityExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsCharismaExpInput);
|
||||
devMenuContainer.appendChild(statsCharismaExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsIntelligenceExpInput);
|
||||
devMenuContainer.appendChild(statsIntelligenceExpButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(statsEnableIntelligenceButton);
|
||||
devMenuContainer.appendChild(statsDisableIntelligenceButton);
|
||||
devMenuContainer.appendChild(factionsHeader);
|
||||
devMenuContainer.appendChild(factionsDropdown);
|
||||
devMenuContainer.appendChild(factionsAddButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(factionsReputationInput);
|
||||
devMenuContainer.appendChild(factionsReputationButton);
|
||||
devMenuContainer.appendChild(augmentationsHeader);
|
||||
devMenuContainer.appendChild(augmentationsDropdown);
|
||||
devMenuContainer.appendChild(augmentationsQueueButton);
|
||||
devMenuContainer.appendChild(giveAllAugmentationsButton);
|
||||
devMenuContainer.appendChild(sourceFilesHeader);
|
||||
devMenuContainer.appendChild(removeSourceFileDropdown);
|
||||
devMenuContainer.appendChild(removeSourceFileButton);
|
||||
devMenuContainer.appendChild(programsHeader);
|
||||
devMenuContainer.appendChild(programsAddDropdown);
|
||||
devMenuContainer.appendChild(programsAddButton);
|
||||
devMenuContainer.appendChild(serversHeader);
|
||||
devMenuContainer.appendChild(serversOpenAll);
|
||||
devMenuContainer.appendChild(serversMinSecurityAll);
|
||||
devMenuContainer.appendChild(serversMaxMoneyAll);
|
||||
devMenuContainer.appendChild(serversConnectToDropdown);
|
||||
devMenuContainer.appendChild(serversConnectToButton);
|
||||
devMenuContainer.appendChild(companiesHeader);
|
||||
devMenuContainer.appendChild(companiesDropdown);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(companyReputationInput);
|
||||
devMenuContainer.appendChild(companyReputationButton);
|
||||
devMenuContainer.appendChild(bladeburnerHeader);
|
||||
devMenuContainer.appendChild(bladeburnerGainRankInput);
|
||||
devMenuContainer.appendChild(bladeburnerGainRankButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(bladeburnerStoredCyclesInput);
|
||||
devMenuContainer.appendChild(bladeburnerStoredCyclesButton);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(gangHeader);
|
||||
devMenuContainer.appendChild(gangStoredCyclesInput);
|
||||
devMenuContainer.appendChild(gangAddStoredCycles);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(contractsHeader);
|
||||
devMenuContainer.appendChild(generateRandomContractBtn);
|
||||
devMenuContainer.appendChild(generateRandomContractOnHomeBtn);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(generateContractWithTypeSelector);
|
||||
devMenuContainer.appendChild(generateContractWithTypeBtn);
|
||||
devMenuContainer.appendChild(stockmarketHeader);
|
||||
devMenuContainer.appendChild(stockInput);
|
||||
devMenuContainer.appendChild(stockPriceChangeInput);
|
||||
devMenuContainer.appendChild(stockPriceChangeBtn);
|
||||
devMenuContainer.appendChild(createElement("br"));
|
||||
devMenuContainer.appendChild(stockViewPriceCapBtn);
|
||||
devMenuContainer.appendChild(sleevesHeader);
|
||||
devMenuContainer.appendChild(sleevesRemoveAllShockRecovery);
|
||||
|
||||
const entireGameContainer = document.getElementById("entire-game-container");
|
||||
if (entireGameContainer == null) {
|
||||
throw new Error("Could not find entire-game-container DOM element");
|
||||
}
|
||||
entireGameContainer.appendChild(devMenuContainer);
|
||||
}
|
||||
|
||||
export function closeDevMenu() {
|
||||
removeElementById(devMenuContainerId);
|
||||
}
|
||||
1213
src/DevMenu.jsx
Normal file
1213
src/DevMenu.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -199,7 +199,7 @@ function displayFactionContent(factionName) {
|
||||
innerText:"This donation will result in 0.000 reputation gain"
|
||||
});
|
||||
var donateAmountInput = createElement("input", {
|
||||
placeholder:"Donation amount",
|
||||
class: "text-input", placeholder:"Donation amount",
|
||||
inputListener:()=>{
|
||||
let amt = 0;
|
||||
if(donateAmountInput.value !== "") {
|
||||
|
||||
@@ -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" +
|
||||
10
src/Fconf/FconfSettings.ts
Normal file
10
src/Fconf/FconfSettings.ts
Normal file
@@ -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,
|
||||
}
|
||||
@@ -1784,6 +1784,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) {
|
||||
id: name + "gang-member-task",
|
||||
});
|
||||
const taskSelector = createElement("select", {
|
||||
class: "dropdown",
|
||||
id: name + "gang-member-task-selector",
|
||||
});
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
18
src/Locations/createCityMap.ts
Normal file
18
src/Locations/createCityMap.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Utility function that creates a "city map", which is an object where
|
||||
* each city is a key (property).
|
||||
*
|
||||
* This map uses the official name of the city, NOT its key in the 'Cities' object
|
||||
*/
|
||||
import { Cities } from "./Cities";
|
||||
import { IMap } from "../types";
|
||||
|
||||
export function createCityMap<T>(initValue: T): IMap<T> {
|
||||
const map: IMap<any> = {};
|
||||
const cities = Object.values(Cities);
|
||||
for (let i = 0; i < cities.length; ++i) {
|
||||
map[cities[i]] = initValue;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
32
src/Message/Message.ts
Normal file
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[sleeveNumber];
|
||||
return {
|
||||
shock: 100 - sl.shock,
|
||||
sync: sl.sync,
|
||||
hacking_skill: sl.hacking_skill,
|
||||
strength: sl.strength,
|
||||
defense: sl.defense,
|
||||
dexterity: sl.dexterity,
|
||||
agility: sl.agility,
|
||||
charisma: sl.charisma,
|
||||
};
|
||||
},
|
||||
getTask : function(sleeveNumber=0) {
|
||||
if (workerScript.checkingRam) {
|
||||
return updateStaticRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||
}
|
||||
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "getTask() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||
}
|
||||
updateDynamicRam("getTask", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||
workerScript.log(`ERROR: sleeve.getTask(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const sl = Player.sleeves[sleeveNumber];
|
||||
return {
|
||||
task: SleeveTaskType[sl.currentTask],
|
||||
crime: sl.crimeType,
|
||||
location: sl.currentTaskLocation,
|
||||
gymStatType: sl.gymStatType,
|
||||
factionWorkType: FactionWorkType[sl.factionWorkType],
|
||||
};
|
||||
},
|
||||
getInformation : function(sleeveNumber=0) {
|
||||
if (workerScript.checkingRam) {
|
||||
return updateStaticRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||
}
|
||||
if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) {
|
||||
throw makeRuntimeRejectMsg(workerScript, "getInformation() failed because you do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10");
|
||||
}
|
||||
updateDynamicRam("getInformation", CONSTANTS.ScriptSleeveBaseRamCost);
|
||||
if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) {
|
||||
workerScript.log(`ERROR: sleeve.getInformation(${sleeveNumber}) failed because it is an invalid sleeve number.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const sl = Player.sleeves[sleeveNumber];
|
||||
return {
|
||||
city: sl.city,
|
||||
hp: sl.hp,
|
||||
jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player.
|
||||
jobTitle: Object.values(Player.jobs),
|
||||
maxHp: sl.max_hp,
|
||||
tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used.
|
||||
|
||||
mult: {
|
||||
agility: sl.agility_mult,
|
||||
agilityExp: sl.agility_exp_mult,
|
||||
companyRep: sl.company_rep_mult,
|
||||
crimeMoney: sl.crime_money_mult,
|
||||
crimeSuccess: sl.crime_success_mult,
|
||||
defense: sl.defense_mult,
|
||||
defenseExp: sl.defense_exp_mult,
|
||||
dexterity: sl.dexterity_mult,
|
||||
dexterityExp: sl.dexterity_exp_mult,
|
||||
factionRep: sl.faction_rep_mult,
|
||||
hacking: sl.hacking_mult,
|
||||
hackingExp: sl.hacking_exp_mult,
|
||||
strength: sl.strength_mult,
|
||||
strengthExp: sl.strength_exp_mult,
|
||||
workMoney: sl.work_money_mult,
|
||||
},
|
||||
|
||||
timeWorked: sl.currentTaskTime,
|
||||
earningsForSleeves : {
|
||||
workHackExpGain: sl.earningsForSleeves.hack,
|
||||
workStrExpGain: sl.earningsForSleeves.str,
|
||||
workDefExpGain: sl.earningsForSleeves.def,
|
||||
workDexExpGain: sl.earningsForSleeves.dex,
|
||||
workAgiExpGain: sl.earningsForSleeves.agi,
|
||||
workChaExpGain: sl.earningsForSleeves.cha,
|
||||
workMoneyGain: sl.earningsForSleeves.money,
|
||||
},
|
||||
earningsForPlayer : {
|
||||
workHackExpGain: sl.earningsForPlayer.hack,
|
||||
workStrExpGain: sl.earningsForPlayer.str,
|
||||
workDefExpGain: sl.earningsForPlayer.def,
|
||||
workDexExpGain: sl.earningsForPlayer.dex,
|
||||
workAgiExpGain: sl.earningsForPlayer.agi,
|
||||
workChaExpGain: sl.earningsForPlayer.cha,
|
||||
workMoneyGain: sl.earningsForPlayer.money,
|
||||
},
|
||||
earningsForTask : {
|
||||
workHackExpGain: sl.earningsForTask.hack,
|
||||
workStrExpGain: sl.earningsForTask.str,
|
||||
workDefExpGain: sl.earningsForTask.def,
|
||||
workDexExpGain: sl.earningsForTask.dex,
|
||||
workAgiExpGain: sl.earningsForTask.agi,
|
||||
workChaExpGain: sl.earningsForTask.cha,
|
||||
workMoneyGain: sl.earningsForTask.money,
|
||||
},
|
||||
workRepGain: sl.getRepGain(),
|
||||
}
|
||||
},
|
||||
} // 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;
|
||||
|
||||
@@ -96,7 +96,7 @@ export function createResleevesPage(p: IPlayer) {
|
||||
display: "inline-block",
|
||||
innerText: "Sort By: "
|
||||
});
|
||||
UIElems.sortSelector = createElement("select") as HTMLSelectElement;
|
||||
UIElems.sortSelector = createElement("select",{class:"dropdown"}) as HTMLSelectElement;
|
||||
|
||||
enum SortOption {
|
||||
Cost = "Cost",
|
||||
@@ -309,7 +309,7 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
|
||||
elems.statsPanel.appendChild(elems.multipliersButton);
|
||||
|
||||
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
|
||||
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement;
|
||||
elems.augSelector = createElement("select", { class: "resleeve-aug-selector dropdown" }) as HTMLSelectElement;
|
||||
elems.augDescription = createElement("p");
|
||||
for (let i = 0; i < resleeve.augmentations.length; ++i) {
|
||||
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
|
||||
|
||||
@@ -118,7 +118,7 @@ export class Sleeve extends Person {
|
||||
memory: number = 0;
|
||||
|
||||
/**
|
||||
* Sleeve shock. Number between 1 and 100
|
||||
* Sleeve shock. Number between 0 and 100
|
||||
* Trauma/shock that comes with being in a sleeve. Experience earned
|
||||
* is multipled by shock%. This gets applied before synchronization
|
||||
*
|
||||
@@ -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);
|
||||
|
||||
@@ -288,7 +288,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
|
||||
}
|
||||
|
||||
elems.taskPanel = createElement("div", { class: "sleeve-panel", width: "40%" });
|
||||
elems.taskSelector = createElement("select") as HTMLSelectElement;
|
||||
elems.taskSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
|
||||
elems.taskSelector.add(createOptionElement("------"));
|
||||
elems.taskSelector.add(createOptionElement("Work for Company"));
|
||||
elems.taskSelector.add(createOptionElement("Work for Faction"));
|
||||
@@ -297,8 +297,8 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
|
||||
elems.taskSelector.add(createOptionElement("Workout at Gym"));
|
||||
elems.taskSelector.add(createOptionElement("Shock Recovery"));
|
||||
elems.taskSelector.add(createOptionElement("Synchronize"));
|
||||
elems.taskDetailsSelector = createElement("select") as HTMLSelectElement;
|
||||
elems.taskDetailsSelector2 = createElement("select") as HTMLSelectElement;
|
||||
elems.taskDetailsSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
|
||||
elems.taskDetailsSelector2 = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
|
||||
elems.taskDescription = createElement("p");
|
||||
elems.taskProgressBar = createElement("p");
|
||||
elems.taskSelector.addEventListener("change", () => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -52,7 +55,6 @@ function BitburnerSaveObject() {
|
||||
this.FconfSettingsSave = "";
|
||||
this.VersionSave = "";
|
||||
this.AllGangsSave = "";
|
||||
this.CorporationResearchTreesSave = "";
|
||||
}
|
||||
|
||||
BitburnerSaveObject.prototype.getSaveString = function() {
|
||||
|
||||
1037
src/Script.js
1037
src/Script.js
File diff suppressed because it is too large
Load Diff
1
src/Script/RamCalculations.d.ts
vendored
Normal file
1
src/Script/RamCalculations.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function calculateRamUsage(codeCopy: string): number;
|
||||
411
src/Script/RamCalculations.js
Normal file
411
src/Script/RamCalculations.js
Normal file
@@ -0,0 +1,411 @@
|
||||
// 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 if (ref in workerScript.env.vars.sleeve) {
|
||||
func = workerScript.env.vars.sleeve[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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user